@uploadista/data-store-gcs 0.0.3
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/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-check.log +5 -0
- package/LICENSE +21 -0
- package/README.md +479 -0
- package/dist/examples.d.ts +44 -0
- package/dist/examples.d.ts.map +1 -0
- package/dist/examples.js +82 -0
- package/dist/gcs-store-rest.d.ts +16 -0
- package/dist/gcs-store-rest.d.ts.map +1 -0
- package/dist/gcs-store-rest.js +188 -0
- package/dist/gcs-store-v2.d.ts +13 -0
- package/dist/gcs-store-v2.d.ts.map +1 -0
- package/dist/gcs-store-v2.js +190 -0
- package/dist/gcs-store.d.ts +12 -0
- package/dist/gcs-store.d.ts.map +1 -0
- package/dist/gcs-store.js +282 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/services/gcs-client-nodejs.service.d.ts +4 -0
- package/dist/services/gcs-client-nodejs.service.d.ts.map +1 -0
- package/dist/services/gcs-client-nodejs.service.js +312 -0
- package/dist/services/gcs-client-rest.service.d.ts +4 -0
- package/dist/services/gcs-client-rest.service.d.ts.map +1 -0
- package/dist/services/gcs-client-rest.service.js +299 -0
- package/dist/services/gcs-client.service.d.ts +56 -0
- package/dist/services/gcs-client.service.d.ts.map +1 -0
- package/dist/services/gcs-client.service.js +3 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +3 -0
- package/package.json +31 -0
- package/src/gcs-store-v2.ts +286 -0
- package/src/gcs-store.ts +398 -0
- package/src/index.ts +6 -0
- package/src/services/gcs-client-nodejs.service.ts +435 -0
- package/src/services/gcs-client-rest.service.ts +406 -0
- package/src/services/gcs-client.service.ts +117 -0
- package/src/services/index.ts +3 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import {
|
|
4
|
+
type GCSClientConfig,
|
|
5
|
+
GCSClientService,
|
|
6
|
+
type GCSObjectMetadata,
|
|
7
|
+
type GCSOperationContext,
|
|
8
|
+
} from "./gcs-client.service";
|
|
9
|
+
|
|
10
|
+
function createRESTGCSClient(config: GCSClientConfig) {
|
|
11
|
+
if (!config.accessToken) {
|
|
12
|
+
throw new Error("accessToken is required for REST API implementation");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const baseUrl = `https://storage.googleapis.com/storage/v1/b/${config.bucket}`;
|
|
16
|
+
const uploadUrl = `https://storage.googleapis.com/upload/storage/v1/b/${config.bucket}/o`;
|
|
17
|
+
const accessToken = config.accessToken;
|
|
18
|
+
|
|
19
|
+
const getAuthHeaders = () => ({
|
|
20
|
+
Authorization: `Bearer ${accessToken}`,
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const getObject = (key: string) =>
|
|
25
|
+
Effect.tryPromise({
|
|
26
|
+
try: async () => {
|
|
27
|
+
const response = await fetch(
|
|
28
|
+
`${baseUrl}/o/${encodeURIComponent(key)}?alt=media`,
|
|
29
|
+
{
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `Bearer ${accessToken}`,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
if (response.status === 404) {
|
|
38
|
+
throw new Error("File not found");
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
if (!response.body) {
|
|
43
|
+
throw new Error("body not found");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return response.body;
|
|
47
|
+
},
|
|
48
|
+
catch: (error) => {
|
|
49
|
+
if (error instanceof Error && error.message.includes("not found")) {
|
|
50
|
+
return UploadistaError.fromCode("FILE_NOT_FOUND");
|
|
51
|
+
}
|
|
52
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR", { cause: error });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const getObjectMetadata = (key: string) =>
|
|
57
|
+
Effect.tryPromise({
|
|
58
|
+
try: async () => {
|
|
59
|
+
const response = await fetch(
|
|
60
|
+
`${baseUrl}/o/${encodeURIComponent(key)}`,
|
|
61
|
+
{
|
|
62
|
+
headers: getAuthHeaders(),
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
if (response.status === 404) {
|
|
68
|
+
throw new Error("File not found");
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = (await response.json()) as {
|
|
74
|
+
name: string;
|
|
75
|
+
bucket: string;
|
|
76
|
+
size?: string;
|
|
77
|
+
contentType?: string;
|
|
78
|
+
metadata?: Record<string, string>;
|
|
79
|
+
generation?: string;
|
|
80
|
+
timeCreated?: string;
|
|
81
|
+
updated?: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
name: data.name,
|
|
86
|
+
bucket: data.bucket,
|
|
87
|
+
size: data.size ? Number.parseInt(data.size, 10) : undefined,
|
|
88
|
+
contentType: data.contentType,
|
|
89
|
+
metadata: data.metadata || {},
|
|
90
|
+
generation: data.generation,
|
|
91
|
+
timeCreated: data.timeCreated,
|
|
92
|
+
updated: data.updated,
|
|
93
|
+
} as GCSObjectMetadata;
|
|
94
|
+
},
|
|
95
|
+
catch: (error) => {
|
|
96
|
+
if (error instanceof Error && error.message.includes("not found")) {
|
|
97
|
+
return UploadistaError.fromCode("FILE_NOT_FOUND");
|
|
98
|
+
}
|
|
99
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR", { cause: error });
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const objectExists = (key: string) =>
|
|
104
|
+
Effect.tryPromise({
|
|
105
|
+
try: async () => {
|
|
106
|
+
const response = await fetch(
|
|
107
|
+
`${baseUrl}/o/${encodeURIComponent(key)}`,
|
|
108
|
+
{
|
|
109
|
+
method: "HEAD",
|
|
110
|
+
headers: {
|
|
111
|
+
Authorization: `Bearer ${accessToken}`,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return response.ok;
|
|
117
|
+
},
|
|
118
|
+
catch: (error) => {
|
|
119
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR", { cause: error });
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const putObject = (
|
|
124
|
+
key: string,
|
|
125
|
+
body: Uint8Array,
|
|
126
|
+
context?: Partial<GCSOperationContext>,
|
|
127
|
+
) =>
|
|
128
|
+
Effect.tryPromise({
|
|
129
|
+
try: async () => {
|
|
130
|
+
const metadata = {
|
|
131
|
+
name: key,
|
|
132
|
+
contentType: context?.contentType || "application/octet-stream",
|
|
133
|
+
metadata: context?.metadata || {},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const response = await fetch(
|
|
137
|
+
`${uploadUrl}?uploadType=media&name=${encodeURIComponent(key)}`,
|
|
138
|
+
{
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: {
|
|
141
|
+
Authorization: `Bearer ${accessToken}`,
|
|
142
|
+
"Content-Type": metadata.contentType,
|
|
143
|
+
"Content-Length": body.length.toString(),
|
|
144
|
+
},
|
|
145
|
+
body: body,
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return key;
|
|
154
|
+
},
|
|
155
|
+
catch: (error) => {
|
|
156
|
+
return UploadistaError.fromCode("FILE_WRITE_ERROR", { cause: error });
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const deleteObject = (key: string) =>
|
|
161
|
+
Effect.tryPromise({
|
|
162
|
+
try: async () => {
|
|
163
|
+
const response = await fetch(
|
|
164
|
+
`${baseUrl}/o/${encodeURIComponent(key)}`,
|
|
165
|
+
{
|
|
166
|
+
method: "DELETE",
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: `Bearer ${accessToken}`,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// 404 is OK - object didn't exist
|
|
174
|
+
if (!response.ok && response.status !== 404) {
|
|
175
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
catch: (error) => {
|
|
179
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR", { cause: error });
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const createResumableUpload = (context: GCSOperationContext) =>
|
|
184
|
+
Effect.tryPromise({
|
|
185
|
+
try: async () => {
|
|
186
|
+
const metadata = {
|
|
187
|
+
name: context.key,
|
|
188
|
+
contentType: context.contentType || "application/octet-stream",
|
|
189
|
+
metadata: context.metadata || {},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const response = await fetch(
|
|
193
|
+
`${uploadUrl}?uploadType=resumable&name=${encodeURIComponent(context.key)}`,
|
|
194
|
+
{
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
Authorization: `Bearer ${accessToken}`,
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify(metadata),
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const resumableUploadUrl = response.headers.get("Location");
|
|
209
|
+
if (!resumableUploadUrl) {
|
|
210
|
+
throw new Error("No upload URL returned");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return resumableUploadUrl;
|
|
214
|
+
},
|
|
215
|
+
catch: (error) => {
|
|
216
|
+
return UploadistaError.fromCode("FILE_WRITE_ERROR", { cause: error });
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const uploadChunk = (
|
|
221
|
+
uploadUrl: string,
|
|
222
|
+
chunk: Uint8Array,
|
|
223
|
+
start: number,
|
|
224
|
+
total?: number,
|
|
225
|
+
) =>
|
|
226
|
+
Effect.tryPromise({
|
|
227
|
+
try: async () => {
|
|
228
|
+
const end = start + chunk.length - 1;
|
|
229
|
+
const contentRange = total
|
|
230
|
+
? `bytes ${start}-${end}/${total}`
|
|
231
|
+
: `bytes ${start}-${end}/*`;
|
|
232
|
+
|
|
233
|
+
const response = await fetch(uploadUrl, {
|
|
234
|
+
method: "PUT",
|
|
235
|
+
headers: {
|
|
236
|
+
"Content-Length": chunk.length.toString(),
|
|
237
|
+
"Content-Range": contentRange,
|
|
238
|
+
},
|
|
239
|
+
body: chunk,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// 308 means more data needed, 200/201 means complete
|
|
243
|
+
const completed = response.status === 200 || response.status === 201;
|
|
244
|
+
|
|
245
|
+
if (!completed && response.status !== 308) {
|
|
246
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
completed,
|
|
251
|
+
bytesUploaded: end + 1,
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
catch: (error) => {
|
|
255
|
+
return UploadistaError.fromCode("FILE_WRITE_ERROR", { cause: error });
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const getUploadStatus = (uploadUrl: string) =>
|
|
260
|
+
Effect.tryPromise({
|
|
261
|
+
try: async () => {
|
|
262
|
+
const response = await fetch(uploadUrl, {
|
|
263
|
+
method: "PUT",
|
|
264
|
+
headers: {
|
|
265
|
+
"Content-Range": "bytes */*",
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (response.status === 308) {
|
|
270
|
+
// Upload incomplete
|
|
271
|
+
const range = response.headers.get("Range");
|
|
272
|
+
const bytesUploaded = range
|
|
273
|
+
? Number.parseInt(range.split("-")[1], 10) + 1
|
|
274
|
+
: 0;
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
bytesUploaded,
|
|
278
|
+
completed: false,
|
|
279
|
+
};
|
|
280
|
+
} else if (response.status === 200 || response.status === 201) {
|
|
281
|
+
// Upload complete
|
|
282
|
+
return {
|
|
283
|
+
bytesUploaded: 0, // We don't know the exact size
|
|
284
|
+
completed: true,
|
|
285
|
+
};
|
|
286
|
+
} else {
|
|
287
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
catch: (error) => {
|
|
291
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR", { cause: error });
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const cancelUpload = (uploadUrl: string) =>
|
|
296
|
+
Effect.tryPromise({
|
|
297
|
+
try: async () => {
|
|
298
|
+
// Cancel by sending DELETE to upload URL
|
|
299
|
+
await fetch(uploadUrl, {
|
|
300
|
+
method: "DELETE",
|
|
301
|
+
});
|
|
302
|
+
},
|
|
303
|
+
catch: (error) => {
|
|
304
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR", { cause: error });
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const composeObjects = (
|
|
309
|
+
sourceKeys: string[],
|
|
310
|
+
destinationKey: string,
|
|
311
|
+
context?: Partial<GCSOperationContext>,
|
|
312
|
+
) =>
|
|
313
|
+
Effect.tryPromise({
|
|
314
|
+
try: async () => {
|
|
315
|
+
const composeRequest = {
|
|
316
|
+
kind: "storage#composeRequest",
|
|
317
|
+
sourceObjects: sourceKeys.map((key) => ({ name: key })),
|
|
318
|
+
destination: {
|
|
319
|
+
name: destinationKey,
|
|
320
|
+
contentType: context?.contentType || "application/octet-stream",
|
|
321
|
+
metadata: context?.metadata || {},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const response = await fetch(
|
|
326
|
+
`${baseUrl}/o/${encodeURIComponent(destinationKey)}/compose`,
|
|
327
|
+
{
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: getAuthHeaders(),
|
|
330
|
+
body: JSON.stringify(composeRequest),
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return destinationKey;
|
|
339
|
+
},
|
|
340
|
+
catch: (error) => {
|
|
341
|
+
return UploadistaError.fromCode("FILE_WRITE_ERROR", { cause: error });
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const putTemporaryObject = (
|
|
346
|
+
key: string,
|
|
347
|
+
body: Uint8Array,
|
|
348
|
+
context?: Partial<GCSOperationContext>,
|
|
349
|
+
) => putObject(`${key}_tmp`, body, context);
|
|
350
|
+
|
|
351
|
+
const getTemporaryObject = (key: string) =>
|
|
352
|
+
Effect.tryPromise({
|
|
353
|
+
try: async () => {
|
|
354
|
+
try {
|
|
355
|
+
return await getObject(`${key}_tmp`).pipe(Effect.runPromise);
|
|
356
|
+
} catch {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
catch: () => {
|
|
361
|
+
return UploadistaError.fromCode("UNKNOWN_ERROR");
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const deleteTemporaryObject = (key: string) => deleteObject(`${key}_tmp`);
|
|
366
|
+
|
|
367
|
+
const getObjectBuffer = (key: string) =>
|
|
368
|
+
Effect.tryPromise({
|
|
369
|
+
try: async () => {
|
|
370
|
+
const response = await fetch(
|
|
371
|
+
`${baseUrl}/o/${encodeURIComponent(key)}?alt=media`,
|
|
372
|
+
{
|
|
373
|
+
headers: getAuthHeaders(),
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
if (!response.ok) {
|
|
377
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
378
|
+
}
|
|
379
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
380
|
+
},
|
|
381
|
+
catch: (error) => {
|
|
382
|
+
return UploadistaError.fromCode("FILE_READ_ERROR", { cause: error });
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
bucket: config.bucket,
|
|
388
|
+
getObject,
|
|
389
|
+
getObjectBuffer,
|
|
390
|
+
getObjectMetadata,
|
|
391
|
+
objectExists,
|
|
392
|
+
putObject,
|
|
393
|
+
deleteObject,
|
|
394
|
+
createResumableUpload,
|
|
395
|
+
uploadChunk,
|
|
396
|
+
getUploadStatus,
|
|
397
|
+
cancelUpload,
|
|
398
|
+
composeObjects,
|
|
399
|
+
putTemporaryObject,
|
|
400
|
+
getTemporaryObject,
|
|
401
|
+
deleteTemporaryObject,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export const GCSClientRESTLayer = (config: GCSClientConfig) =>
|
|
406
|
+
Layer.succeed(GCSClientService, createRESTGCSClient(config));
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import { Context, type Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
export interface GCSOperationContext {
|
|
5
|
+
bucket: string;
|
|
6
|
+
key: string;
|
|
7
|
+
contentType?: string;
|
|
8
|
+
metadata?: Record<string, string | null>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GCSObjectMetadata {
|
|
12
|
+
name: string;
|
|
13
|
+
bucket: string;
|
|
14
|
+
size?: number;
|
|
15
|
+
contentType?: string;
|
|
16
|
+
metadata?: Record<string, string | null>;
|
|
17
|
+
generation?: string;
|
|
18
|
+
timeCreated?: string;
|
|
19
|
+
updated?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type GCSClient = {
|
|
23
|
+
readonly bucket: string;
|
|
24
|
+
|
|
25
|
+
// Basic GCS operations
|
|
26
|
+
readonly getObject: (
|
|
27
|
+
key: string,
|
|
28
|
+
) => Effect.Effect<ReadableStream, UploadistaError>;
|
|
29
|
+
readonly getObjectMetadata: (
|
|
30
|
+
key: string,
|
|
31
|
+
) => Effect.Effect<GCSObjectMetadata, UploadistaError>;
|
|
32
|
+
readonly getObjectBuffer: (
|
|
33
|
+
key: string,
|
|
34
|
+
) => Effect.Effect<Uint8Array, UploadistaError>;
|
|
35
|
+
readonly objectExists: (
|
|
36
|
+
key: string,
|
|
37
|
+
) => Effect.Effect<boolean, UploadistaError>;
|
|
38
|
+
readonly putObject: (
|
|
39
|
+
key: string,
|
|
40
|
+
body: Uint8Array,
|
|
41
|
+
context?: Partial<GCSOperationContext>,
|
|
42
|
+
) => Effect.Effect<string, UploadistaError>;
|
|
43
|
+
readonly putObjectFromStream?: (
|
|
44
|
+
key: string,
|
|
45
|
+
offset: number,
|
|
46
|
+
readableStream: ReadableStream,
|
|
47
|
+
context?: Partial<GCSOperationContext>,
|
|
48
|
+
onProgress?: (chunkSize: number) => void, // Called with incremental bytes per chunk
|
|
49
|
+
) => Effect.Effect<number, UploadistaError>;
|
|
50
|
+
readonly putObjectFromStreamWithPatching?: (
|
|
51
|
+
key: string,
|
|
52
|
+
offset: number,
|
|
53
|
+
readableStream: ReadableStream,
|
|
54
|
+
context?: Partial<GCSOperationContext>,
|
|
55
|
+
onProgress?: (chunkSize: number) => void, // Called with incremental bytes per chunk
|
|
56
|
+
isAppend?: boolean,
|
|
57
|
+
) => Effect.Effect<number, UploadistaError>;
|
|
58
|
+
readonly deleteObject: (key: string) => Effect.Effect<void, UploadistaError>;
|
|
59
|
+
|
|
60
|
+
// Resumable upload operations
|
|
61
|
+
readonly createResumableUpload: (
|
|
62
|
+
context: GCSOperationContext,
|
|
63
|
+
) => Effect.Effect<string, UploadistaError>; // Returns upload URL
|
|
64
|
+
readonly uploadChunk: (
|
|
65
|
+
uploadUrl: string,
|
|
66
|
+
chunk: Uint8Array,
|
|
67
|
+
start: number,
|
|
68
|
+
total?: number,
|
|
69
|
+
) => Effect.Effect<
|
|
70
|
+
{ completed: boolean; bytesUploaded: number },
|
|
71
|
+
UploadistaError
|
|
72
|
+
>;
|
|
73
|
+
readonly getUploadStatus: (
|
|
74
|
+
uploadUrl: string,
|
|
75
|
+
) => Effect.Effect<
|
|
76
|
+
{ bytesUploaded: number; completed: boolean },
|
|
77
|
+
UploadistaError
|
|
78
|
+
>;
|
|
79
|
+
readonly cancelUpload: (
|
|
80
|
+
uploadUrl: string,
|
|
81
|
+
) => Effect.Effect<void, UploadistaError>;
|
|
82
|
+
|
|
83
|
+
// Compose operations (GCS specific - for combining files)
|
|
84
|
+
readonly composeObjects: (
|
|
85
|
+
sourceKeys: string[],
|
|
86
|
+
destinationKey: string,
|
|
87
|
+
context?: Partial<GCSOperationContext>,
|
|
88
|
+
) => Effect.Effect<string, UploadistaError>;
|
|
89
|
+
|
|
90
|
+
// Temporary file operations (for patches)
|
|
91
|
+
readonly putTemporaryObject: (
|
|
92
|
+
key: string,
|
|
93
|
+
body: Uint8Array,
|
|
94
|
+
context?: Partial<GCSOperationContext>,
|
|
95
|
+
) => Effect.Effect<string, UploadistaError>;
|
|
96
|
+
readonly getTemporaryObject: (
|
|
97
|
+
key: string,
|
|
98
|
+
) => Effect.Effect<ReadableStream | undefined, UploadistaError>;
|
|
99
|
+
readonly deleteTemporaryObject: (
|
|
100
|
+
key: string,
|
|
101
|
+
) => Effect.Effect<void, UploadistaError>;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export class GCSClientService extends Context.Tag("GCSClientService")<
|
|
105
|
+
GCSClientService,
|
|
106
|
+
GCSClient
|
|
107
|
+
>() {}
|
|
108
|
+
|
|
109
|
+
export interface GCSClientConfig {
|
|
110
|
+
bucket: string;
|
|
111
|
+
// For Node.js implementation
|
|
112
|
+
keyFilename?: string;
|
|
113
|
+
credentials?: object;
|
|
114
|
+
projectId?: string;
|
|
115
|
+
// For REST API implementation
|
|
116
|
+
accessToken?: string;
|
|
117
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/gcs-store-v2.ts","./src/gcs-store.ts","./src/index.ts","./src/services/gcs-client-nodejs.service.ts","./src/services/gcs-client-rest.service.ts","./src/services/gcs-client.service.ts","./src/services/index.ts"],"version":"5.9.3"}
|