@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,188 @@
|
|
|
1
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import { UploadFileDataStore, UploadFileKVStore, } from "@uploadista/core/types";
|
|
3
|
+
import { Effect, Layer, Stream } from "effect";
|
|
4
|
+
/**
|
|
5
|
+
* Google Cloud Storage implementation using REST API
|
|
6
|
+
* Compatible with Cloudflare Workers
|
|
7
|
+
*/
|
|
8
|
+
export function gcsStoreRest({ bucketName, accessToken, projectId, kvStore, }) {
|
|
9
|
+
const baseUrl = `https://storage.googleapis.com/storage/v1/b/${bucketName}`;
|
|
10
|
+
const uploadUrl = `https://storage.googleapis.com/upload/storage/v1/b/${bucketName}/o`;
|
|
11
|
+
const getCapabilities = () => {
|
|
12
|
+
return {
|
|
13
|
+
supportsParallelUploads: false,
|
|
14
|
+
supportsConcatenation: false, // REST API doesn't support combine easily
|
|
15
|
+
supportsDeferredLength: true,
|
|
16
|
+
supportsResumableUploads: true, // Via resumable uploads
|
|
17
|
+
supportsTransactionalUploads: false,
|
|
18
|
+
maxConcurrentUploads: 1,
|
|
19
|
+
minChunkSize: undefined,
|
|
20
|
+
maxChunkSize: undefined,
|
|
21
|
+
maxParts: undefined,
|
|
22
|
+
optimalChunkSize: 8 * 1024 * 1024, // 8MB
|
|
23
|
+
requiresOrderedChunks: true,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const validateUploadStrategy = (strategy) => {
|
|
27
|
+
const capabilities = getCapabilities();
|
|
28
|
+
const result = strategy === "single" || capabilities.supportsParallelUploads;
|
|
29
|
+
return Effect.succeed(result);
|
|
30
|
+
};
|
|
31
|
+
const getUpload = (id) => {
|
|
32
|
+
return Effect.gen(function* () {
|
|
33
|
+
try {
|
|
34
|
+
const response = yield* Effect.promise(() => fetch(`${baseUrl}/o/${encodeURIComponent(id)}?alt=json`, {
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${accessToken}`,
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
if (response.status === 404) {
|
|
41
|
+
return yield* Effect.fail(UploadistaError.fromCode("FILE_NOT_FOUND"));
|
|
42
|
+
}
|
|
43
|
+
return yield* Effect.fail(UploadistaError.fromCode("FILE_READ_ERROR", {
|
|
44
|
+
cause: new Error(`HTTP ${response.status}: ${response.statusText}`),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
const metadata = yield* Effect.promise(() => response.json());
|
|
48
|
+
const file = yield* kvStore.get(id);
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
size: metadata.size ? Number.parseInt(metadata.size, 10) : undefined,
|
|
52
|
+
offset: metadata.size ? Number.parseInt(metadata.size, 10) : 0,
|
|
53
|
+
metadata: metadata.metadata || {},
|
|
54
|
+
storage: {
|
|
55
|
+
id: file.storage.id,
|
|
56
|
+
type: file.storage.type,
|
|
57
|
+
path: id,
|
|
58
|
+
bucket: bucketName,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
bucket: bucketName,
|
|
69
|
+
create: (file) => {
|
|
70
|
+
return Effect.gen(function* () {
|
|
71
|
+
if (!file.id) {
|
|
72
|
+
return yield* Effect.fail(UploadistaError.fromCode("FILE_NOT_FOUND"));
|
|
73
|
+
}
|
|
74
|
+
file.storage = {
|
|
75
|
+
id: file.storage.id,
|
|
76
|
+
type: file.storage.type,
|
|
77
|
+
path: file.id,
|
|
78
|
+
bucket: bucketName,
|
|
79
|
+
};
|
|
80
|
+
// Create empty file using REST API
|
|
81
|
+
const metadata = {
|
|
82
|
+
name: file.id,
|
|
83
|
+
metadata: {
|
|
84
|
+
contentType: file.metadata?.contentType || "application/octet-stream",
|
|
85
|
+
uploadId: file.id,
|
|
86
|
+
originalName: file.metadata?.originalName,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return yield* Effect.tryPromise({
|
|
90
|
+
try: async () => {
|
|
91
|
+
const response = await fetch(`${uploadUrl}?uploadType=media&name=${encodeURIComponent(file.id)}`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Bearer ${accessToken}`,
|
|
95
|
+
"Content-Type": "application/octet-stream",
|
|
96
|
+
"Content-Length": "0",
|
|
97
|
+
},
|
|
98
|
+
body: new Uint8Array(0),
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
102
|
+
}
|
|
103
|
+
return file;
|
|
104
|
+
},
|
|
105
|
+
catch: (error) => {
|
|
106
|
+
console.error("error creating file", error);
|
|
107
|
+
return UploadistaError.fromCode("FILE_WRITE_ERROR", {
|
|
108
|
+
cause: error,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
remove: (file_id) => {
|
|
115
|
+
return Effect.promise(() => fetch(`${baseUrl}/o/${encodeURIComponent(file_id)}`, {
|
|
116
|
+
method: "DELETE",
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: `Bearer ${accessToken}`,
|
|
119
|
+
},
|
|
120
|
+
}).then((response) => {
|
|
121
|
+
if (!response.ok && response.status !== 404) {
|
|
122
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
125
|
+
},
|
|
126
|
+
write: (options, dependencies) => {
|
|
127
|
+
return Effect.gen(function* () {
|
|
128
|
+
const { file_id, offset, stream: effectStream } = options;
|
|
129
|
+
const { onProgress } = dependencies;
|
|
130
|
+
// For simplicity, we'll use simple upload for now
|
|
131
|
+
// For production, you'd want to implement resumable uploads
|
|
132
|
+
return yield* Effect.promise(() => new Promise(async (resolve, reject) => {
|
|
133
|
+
try {
|
|
134
|
+
const readableStream = Stream.toReadableStream(effectStream);
|
|
135
|
+
const reader = readableStream.getReader();
|
|
136
|
+
const chunks = [];
|
|
137
|
+
let totalBytes = 0;
|
|
138
|
+
// Read all chunks
|
|
139
|
+
while (true) {
|
|
140
|
+
const { done, value } = await reader.read();
|
|
141
|
+
if (done)
|
|
142
|
+
break;
|
|
143
|
+
chunks.push(value);
|
|
144
|
+
totalBytes += value.byteLength;
|
|
145
|
+
onProgress?.(totalBytes);
|
|
146
|
+
}
|
|
147
|
+
// Combine all chunks
|
|
148
|
+
const combinedArray = new Uint8Array(totalBytes);
|
|
149
|
+
let position = 0;
|
|
150
|
+
for (const chunk of chunks) {
|
|
151
|
+
combinedArray.set(chunk, position);
|
|
152
|
+
position += chunk.byteLength;
|
|
153
|
+
}
|
|
154
|
+
// Upload to GCS
|
|
155
|
+
const response = await fetch(`${uploadUrl}?uploadType=media&name=${encodeURIComponent(file_id)}`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
Authorization: `Bearer ${accessToken}`,
|
|
159
|
+
"Content-Type": "application/octet-stream",
|
|
160
|
+
"Content-Length": totalBytes.toString(),
|
|
161
|
+
},
|
|
162
|
+
body: combinedArray,
|
|
163
|
+
});
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
166
|
+
}
|
|
167
|
+
resolve(totalBytes);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error("error writing file", error);
|
|
171
|
+
reject(UploadistaError.fromCode("FILE_WRITE_ERROR", {
|
|
172
|
+
cause: error,
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
getCapabilities,
|
|
179
|
+
validateUploadStrategy,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export function createGCSStoreRest({ bucketName, accessToken, projectId, }) {
|
|
183
|
+
return Effect.gen(function* () {
|
|
184
|
+
const kvStore = yield* UploadFileKVStore;
|
|
185
|
+
return gcsStoreRest({ bucketName, accessToken, projectId, kvStore });
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
export const GCSStoreRestLayer = (options) => Layer.effect(UploadFileDataStore, createGCSStoreRest(options));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DataStore, type KvStore, type UploadFile, UploadFileDataStore, UploadFileKVStore } from "@uploadista/core/types";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import type { GCSClientConfig } from "./services";
|
|
4
|
+
import { GCSClientService } from "./services";
|
|
5
|
+
export type GCSStoreOptions = {
|
|
6
|
+
kvStore: KvStore<UploadFile>;
|
|
7
|
+
} & GCSClientConfig;
|
|
8
|
+
export declare function createGCSStore(options: Omit<GCSStoreOptions, "kvStore">): Effect.Effect<DataStore<UploadFile>, never, GCSClientService | UploadFileKVStore>;
|
|
9
|
+
export declare function createGCSStoreImplementation(config: GCSStoreOptions): Effect.Effect<DataStore<UploadFile>, never, GCSClientService>;
|
|
10
|
+
export declare const GCSStoreLayer: (options: Omit<GCSStoreOptions, "kvStore">) => Layer.Layer<UploadFileDataStore, never, GCSClientService | UploadFileKVStore>;
|
|
11
|
+
export declare const gcsStoreRest: (config: GCSStoreOptions) => Promise<DataStore<UploadFile>>;
|
|
12
|
+
export declare const gcsStoreNodejs: (config: GCSStoreOptions) => Promise<DataStore<UploadFile>>;
|
|
13
|
+
//# sourceMappingURL=gcs-store-v2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcs-store-v2.d.ts","sourceRoot":"","sources":["../src/gcs-store-v2.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,SAAS,EAGd,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,mBAAmB,EACnB,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAC/C,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B,GAAG,eAAe,CAAC;AAgDpB,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,qFAKvE;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,eAAe,GACtB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAwL/D;AAED,eAAO,MAAM,aAAa,GAAI,SAAS,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,kFACX,CAAC;AAE7D,eAAO,MAAM,YAAY,GAAI,QAAQ,eAAe,mCAMnD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,QAAQ,eAAe,mCAMrD,CAAC"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import { UploadFileDataStore, UploadFileKVStore, } from "@uploadista/core/types";
|
|
3
|
+
import { Effect, Layer, Stream } from "effect";
|
|
4
|
+
import { GCSClientNodeJSLayer, GCSClientRESTLayer, GCSClientService, } from "./services";
|
|
5
|
+
/**
|
|
6
|
+
* Convert the Upload object to a format that can be stored in GCS metadata.
|
|
7
|
+
*/
|
|
8
|
+
function stringifyUploadKeys(upload) {
|
|
9
|
+
return {
|
|
10
|
+
size: upload.size?.toString() ?? null,
|
|
11
|
+
sizeIsDeferred: `${upload.sizeIsDeferred}`,
|
|
12
|
+
offset: upload.offset?.toString() ?? "0",
|
|
13
|
+
metadata: JSON.stringify(upload.metadata),
|
|
14
|
+
storage: JSON.stringify(upload.storage),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const getUpload = (id, kvStore, gcsClient) => {
|
|
18
|
+
return Effect.gen(function* () {
|
|
19
|
+
try {
|
|
20
|
+
const metadata = yield* gcsClient.getObjectMetadata(id);
|
|
21
|
+
const file = yield* kvStore.get(id);
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
size: metadata.size,
|
|
25
|
+
offset: metadata.size || 0,
|
|
26
|
+
metadata: metadata.metadata,
|
|
27
|
+
storage: {
|
|
28
|
+
id: file.storage.id,
|
|
29
|
+
type: file.storage.type,
|
|
30
|
+
path: id,
|
|
31
|
+
bucket: gcsClient.bucket,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof UploadistaError && error.code === "FILE_NOT_FOUND") {
|
|
37
|
+
return yield* Effect.fail(error);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
export function createGCSStore(options) {
|
|
44
|
+
return Effect.gen(function* () {
|
|
45
|
+
const kvStore = yield* UploadFileKVStore;
|
|
46
|
+
return yield* createGCSStoreImplementation({ ...options, kvStore });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function createGCSStoreImplementation(config) {
|
|
50
|
+
return Effect.gen(function* () {
|
|
51
|
+
const gcsClient = yield* GCSClientService;
|
|
52
|
+
const { kvStore } = config;
|
|
53
|
+
const getCapabilities = () => {
|
|
54
|
+
return {
|
|
55
|
+
supportsParallelUploads: false, // GCS doesn't have native multipart upload like S3
|
|
56
|
+
supportsConcatenation: true, // Can combine files using compose
|
|
57
|
+
supportsDeferredLength: true,
|
|
58
|
+
supportsResumableUploads: true, // Through resumable uploads
|
|
59
|
+
supportsTransactionalUploads: false,
|
|
60
|
+
maxConcurrentUploads: 1, // Sequential operations
|
|
61
|
+
minChunkSize: undefined,
|
|
62
|
+
maxChunkSize: undefined,
|
|
63
|
+
maxParts: undefined,
|
|
64
|
+
optimalChunkSize: 8 * 1024 * 1024, // 8MB default
|
|
65
|
+
requiresOrderedChunks: true, // Due to compose operation
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const validateUploadStrategy = (strategy) => {
|
|
69
|
+
const capabilities = getCapabilities();
|
|
70
|
+
const result = (() => {
|
|
71
|
+
switch (strategy) {
|
|
72
|
+
case "parallel":
|
|
73
|
+
return capabilities.supportsParallelUploads;
|
|
74
|
+
case "single":
|
|
75
|
+
return true;
|
|
76
|
+
default:
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
80
|
+
return Effect.succeed(result);
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
bucket: gcsClient.bucket,
|
|
84
|
+
create: (file) => {
|
|
85
|
+
return Effect.gen(function* () {
|
|
86
|
+
if (!file.id) {
|
|
87
|
+
return yield* Effect.fail(UploadistaError.fromCode("FILE_NOT_FOUND"));
|
|
88
|
+
}
|
|
89
|
+
file.storage = {
|
|
90
|
+
id: file.storage.id,
|
|
91
|
+
type: file.storage.type,
|
|
92
|
+
path: file.id,
|
|
93
|
+
bucket: gcsClient.bucket,
|
|
94
|
+
};
|
|
95
|
+
// Create empty file
|
|
96
|
+
const context = {
|
|
97
|
+
bucket: gcsClient.bucket,
|
|
98
|
+
key: file.id,
|
|
99
|
+
contentType: file.metadata?.contentType?.toString() ||
|
|
100
|
+
"application/octet-stream",
|
|
101
|
+
metadata: stringifyUploadKeys(file),
|
|
102
|
+
};
|
|
103
|
+
yield* gcsClient.putObject(file.id, new Uint8Array(0), context);
|
|
104
|
+
return file;
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
remove: (file_id) => {
|
|
108
|
+
return gcsClient.deleteObject(file_id);
|
|
109
|
+
},
|
|
110
|
+
write: (options, dependencies) => {
|
|
111
|
+
return Effect.gen(function* () {
|
|
112
|
+
const { file_id, offset, stream: effectStream } = options;
|
|
113
|
+
const { onProgress } = dependencies;
|
|
114
|
+
// Get current upload metadata
|
|
115
|
+
const upload = yield* getUpload(file_id, kvStore, gcsClient);
|
|
116
|
+
upload.offset = offset;
|
|
117
|
+
// Persist the updated offset
|
|
118
|
+
yield* kvStore.set(file_id, upload);
|
|
119
|
+
const context = {
|
|
120
|
+
bucket: gcsClient.bucket,
|
|
121
|
+
key: file_id,
|
|
122
|
+
contentType: upload.metadata?.contentType || "application/octet-stream",
|
|
123
|
+
metadata: stringifyUploadKeys(upload),
|
|
124
|
+
};
|
|
125
|
+
// Convert Effect Stream to ReadableStream
|
|
126
|
+
const readableStream = Stream.toReadableStream(effectStream);
|
|
127
|
+
// Use native streams if available (Node.js implementation)
|
|
128
|
+
if (gcsClient.putObjectFromStreamWithPatching) {
|
|
129
|
+
const isAppend = upload.offset > 0; // Check original file size, not write offset
|
|
130
|
+
return yield* gcsClient.putObjectFromStreamWithPatching(file_id, upload.offset, readableStream, context, onProgress, isAppend);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Fallback to chunk-based approach for REST implementation
|
|
134
|
+
const reader = readableStream.getReader();
|
|
135
|
+
const chunks = [];
|
|
136
|
+
let totalBytes = 0;
|
|
137
|
+
// Read all chunks
|
|
138
|
+
while (true) {
|
|
139
|
+
const { done, value } = yield* Effect.promise(() => reader.read());
|
|
140
|
+
if (done)
|
|
141
|
+
break;
|
|
142
|
+
chunks.push(value);
|
|
143
|
+
const chunkSize = value.byteLength;
|
|
144
|
+
totalBytes += chunkSize;
|
|
145
|
+
onProgress?.(totalBytes);
|
|
146
|
+
}
|
|
147
|
+
// Combine all chunks
|
|
148
|
+
const combinedArray = new Uint8Array(totalBytes);
|
|
149
|
+
let position = 0;
|
|
150
|
+
for (const chunk of chunks) {
|
|
151
|
+
combinedArray.set(chunk, position);
|
|
152
|
+
position += chunk.byteLength;
|
|
153
|
+
}
|
|
154
|
+
// Check if we need to handle patches (append data)
|
|
155
|
+
if (upload.offset === 0) {
|
|
156
|
+
// Direct upload
|
|
157
|
+
yield* gcsClient.putObject(file_id, combinedArray, context);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// We need to combine with existing data
|
|
161
|
+
const patchKey = `${file_id}_patch`;
|
|
162
|
+
// Upload patch data
|
|
163
|
+
yield* gcsClient.putTemporaryObject(patchKey, combinedArray, context);
|
|
164
|
+
// Combine original file with patch
|
|
165
|
+
yield* gcsClient.composeObjects([file_id, patchKey], file_id, context);
|
|
166
|
+
// Clean up patch file
|
|
167
|
+
yield* gcsClient.deleteTemporaryObject(patchKey);
|
|
168
|
+
}
|
|
169
|
+
return totalBytes;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
getCapabilities,
|
|
174
|
+
validateUploadStrategy,
|
|
175
|
+
read: (file_id) => {
|
|
176
|
+
return Effect.gen(function* () {
|
|
177
|
+
const buffer = yield* gcsClient.getObjectBuffer(file_id);
|
|
178
|
+
return buffer;
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export const GCSStoreLayer = (options) => Layer.effect(UploadFileDataStore, createGCSStore(options));
|
|
185
|
+
export const gcsStoreRest = (config) => {
|
|
186
|
+
return Effect.runPromise(createGCSStoreImplementation(config).pipe(Effect.provide(GCSClientRESTLayer(config))));
|
|
187
|
+
};
|
|
188
|
+
export const gcsStoreNodejs = (config) => {
|
|
189
|
+
return Effect.runPromise(createGCSStoreImplementation(config).pipe(Effect.provide(GCSClientNodeJSLayer(config))));
|
|
190
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DataStore, type KvStore, type UploadFile, UploadFileDataStore, UploadFileKVStore } from "@uploadista/core/types";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
export type GCSStoreOptions = {
|
|
4
|
+
keyFilename?: string;
|
|
5
|
+
credentials?: object;
|
|
6
|
+
bucketName: string;
|
|
7
|
+
kvStore: KvStore<UploadFile>;
|
|
8
|
+
};
|
|
9
|
+
export declare function createGCSStore({ keyFilename, credentials, bucketName, }: Omit<GCSStoreOptions, "kvStore">): Effect.Effect<DataStore<UploadFile>, never, UploadFileKVStore>;
|
|
10
|
+
export declare function gcsStore({ keyFilename, credentials, bucketName, kvStore, }: GCSStoreOptions): DataStore<UploadFile>;
|
|
11
|
+
export declare const GCSStoreLayer: (options: Omit<GCSStoreOptions, "kvStore">) => Layer.Layer<UploadFileDataStore, never, UploadFileKVStore>;
|
|
12
|
+
//# sourceMappingURL=gcs-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcs-store.d.ts","sourceRoot":"","sources":["../src/gcs-store.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,SAAS,EAGd,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,mBAAmB,EACnB,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAahC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAE/C,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B,CAAC;AAsDF,wBAAgB,cAAc,CAAC,EAC7B,WAAW,EACX,WAAW,EACX,UAAU,GACX,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,kEAKlC;AAED,wBAAgB,QAAQ,CAAC,EACvB,WAAW,EACX,WAAW,EACX,UAAU,EACV,OAAO,GACR,EAAE,eAAe,GAAG,SAAS,CAAC,UAAU,CAAC,CAmSzC;AAED,eAAO,MAAM,aAAa,GAAI,SAAS,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,+DACX,CAAC"}
|