nexabase-console 2.0.4 → 2.0.6
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/dist/storage/NexaStorageError.d.ts +8 -0
- package/dist/storage/NexaStorageError.js +14 -0
- package/dist/storage/Storage.d.ts +12 -1
- package/dist/storage/Storage.js +104 -22
- package/dist/storage/StorageReference.d.ts +54 -19
- package/dist/storage/StorageReference.js +152 -27
- package/dist/storage/UploadTask.d.ts +33 -6
- package/dist/storage/UploadTask.js +127 -8
- package/dist/storage/types.d.ts +55 -0
- package/dist/storage/types.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StorageErrorCode } from './types';
|
|
2
|
+
export declare class NexaStorageError extends Error {
|
|
3
|
+
code: StorageErrorCode;
|
|
4
|
+
status?: number;
|
|
5
|
+
retryable: boolean;
|
|
6
|
+
cause?: any;
|
|
7
|
+
constructor(code: StorageErrorCode, message: string, status?: number, retryable?: boolean, cause?: any);
|
|
8
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NexaStorageError = void 0;
|
|
4
|
+
class NexaStorageError extends Error {
|
|
5
|
+
constructor(code, message, status, retryable = false, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'NexaStorageError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.retryable = retryable;
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.NexaStorageError = NexaStorageError;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { UploadMetadata, StorageMetadata, SettableMetadata, ListOptions, ListResult } from './types';
|
|
2
3
|
import { UploadOptions } from '../types/index';
|
|
4
|
+
import { UploadTask } from './UploadTask';
|
|
5
|
+
import { IStorageReference } from './StorageReference';
|
|
3
6
|
export declare class Storage {
|
|
4
7
|
private projectId;
|
|
5
8
|
private client;
|
|
9
|
+
emulatorUrl?: string;
|
|
6
10
|
constructor(projectId: string, client: AxiosInstance);
|
|
11
|
+
ref(path?: string): IStorageReference;
|
|
12
|
+
refFromURL(url: string): IStorageReference;
|
|
13
|
+
createUploadTask(path: string, file: Blob, metadata?: UploadMetadata): UploadTask;
|
|
7
14
|
uploadFile(path: string, file: File | Blob, options?: UploadOptions): Promise<{
|
|
8
15
|
url: string;
|
|
9
16
|
path: string;
|
|
@@ -13,5 +20,9 @@ export declare class Storage {
|
|
|
13
20
|
success: boolean;
|
|
14
21
|
message?: string;
|
|
15
22
|
}>;
|
|
23
|
+
getMetadata(path: string): Promise<StorageMetadata>;
|
|
24
|
+
updateMetadata(path: string, metadata: SettableMetadata): Promise<StorageMetadata>;
|
|
25
|
+
list(path: string, options?: ListOptions): Promise<ListResult>;
|
|
16
26
|
}
|
|
17
|
-
export declare const getStorage: (app
|
|
27
|
+
export declare const getStorage: (app?: any) => Storage;
|
|
28
|
+
export declare const connectStorageEmulator: (storage: Storage, host: string, port: number) => void;
|
package/dist/storage/Storage.js
CHANGED
|
@@ -1,49 +1,114 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getStorage = exports.Storage = void 0;
|
|
3
|
+
exports.connectStorageEmulator = exports.getStorage = exports.Storage = void 0;
|
|
4
4
|
const UploadTask_1 = require("./UploadTask");
|
|
5
|
-
const
|
|
5
|
+
const NexaStorageError_1 = require("./NexaStorageError");
|
|
6
|
+
const StorageReference_1 = require("./StorageReference");
|
|
6
7
|
class Storage {
|
|
7
8
|
constructor(projectId, client) {
|
|
8
9
|
this.projectId = projectId;
|
|
9
10
|
this.client = client;
|
|
10
11
|
}
|
|
12
|
+
ref(path = '') {
|
|
13
|
+
return new StorageReference_1.StorageReferenceImpl(path, this);
|
|
14
|
+
}
|
|
15
|
+
refFromURL(url) {
|
|
16
|
+
// Handle both NexaBase and standard URL paths
|
|
17
|
+
const match = url.match(/[?&]path=([^&]+)/) || url.match(/storage\/file\/([^?]+)/) || url.match(/storage\/([^?]+)/);
|
|
18
|
+
if (match && match[1]) {
|
|
19
|
+
return this.ref(decodeURIComponent(match[1]));
|
|
20
|
+
}
|
|
21
|
+
throw new NexaStorageError_1.NexaStorageError('storage/invalid-path', 'Invalid storage URL format');
|
|
22
|
+
}
|
|
23
|
+
createUploadTask(path, file, metadata) {
|
|
24
|
+
const task = new UploadTask_1.UploadTask(this.projectId, this.client, path, file, metadata);
|
|
25
|
+
task.ref = this.ref(path);
|
|
26
|
+
return task;
|
|
27
|
+
}
|
|
28
|
+
// Legacy upload logic
|
|
11
29
|
async uploadFile(path, file, options) {
|
|
30
|
+
const task = this.createUploadTask(path, file);
|
|
31
|
+
task.options = options;
|
|
32
|
+
await task;
|
|
33
|
+
const url = await this.getDownloadURL(path);
|
|
34
|
+
return { url, path };
|
|
35
|
+
}
|
|
36
|
+
async getDownloadURL(path) {
|
|
12
37
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
const res = await this.client.get(`/api/project/${this.projectId}/storage/url?path=${encodeURIComponent(path)}`);
|
|
39
|
+
let url = res.data.downloadUrl || res.data.url;
|
|
40
|
+
// If the backend returns a relative URL, build full absolute URL using client baseURL or window.location.origin
|
|
41
|
+
if (url && !url.startsWith('http://') && !url.startsWith('https://')) {
|
|
42
|
+
let baseUrl = this.client.defaults.baseURL || '';
|
|
43
|
+
if (!baseUrl && typeof window !== 'undefined' && window.location) {
|
|
44
|
+
baseUrl = window.location.origin;
|
|
45
|
+
}
|
|
46
|
+
if (baseUrl) {
|
|
47
|
+
const cleanBase = baseUrl.replace(/\/+$/, '');
|
|
48
|
+
const cleanPath = url.startsWith('/') ? url : `/${url}`;
|
|
49
|
+
url = `${cleanBase}${cleanPath}`;
|
|
23
50
|
}
|
|
24
|
-
}
|
|
51
|
+
}
|
|
52
|
+
return url;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err.response) {
|
|
56
|
+
throw new NexaStorageError_1.NexaStorageError(err.response.data?.error?.code || 'storage/server-error', err.response.data?.error?.message || err.message, err.response.status);
|
|
57
|
+
}
|
|
58
|
+
throw new NexaStorageError_1.NexaStorageError('storage/network-error', err.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async deleteFile(path) {
|
|
62
|
+
try {
|
|
63
|
+
const res = await this.client.delete(`/api/project/${this.projectId}/storage/file?path=${encodeURIComponent(path)}`);
|
|
25
64
|
return res.data;
|
|
26
65
|
}
|
|
27
66
|
catch (err) {
|
|
28
|
-
|
|
67
|
+
if (err.response) {
|
|
68
|
+
throw new NexaStorageError_1.NexaStorageError(err.response.data?.error?.code || 'storage/server-error', err.response.data?.error?.message || err.message, err.response.status);
|
|
69
|
+
}
|
|
70
|
+
throw new NexaStorageError_1.NexaStorageError('storage/network-error', err.message);
|
|
29
71
|
}
|
|
30
72
|
}
|
|
31
|
-
async
|
|
73
|
+
async getMetadata(path) {
|
|
32
74
|
try {
|
|
33
|
-
const res = await this.client.get(`/api/project/${this.projectId}/storage/
|
|
34
|
-
return res.data
|
|
75
|
+
const res = await this.client.get(`/api/project/${this.projectId}/storage/metadata?path=${encodeURIComponent(path)}`);
|
|
76
|
+
return res.data;
|
|
35
77
|
}
|
|
36
78
|
catch (err) {
|
|
37
|
-
|
|
79
|
+
if (err.response) {
|
|
80
|
+
throw new NexaStorageError_1.NexaStorageError(err.response.data?.error?.code || 'storage/server-error', err.response.data?.error?.message || err.message, err.response.status);
|
|
81
|
+
}
|
|
82
|
+
throw new NexaStorageError_1.NexaStorageError('storage/network-error', err.message);
|
|
38
83
|
}
|
|
39
84
|
}
|
|
40
|
-
async
|
|
85
|
+
async updateMetadata(path, metadata) {
|
|
41
86
|
try {
|
|
42
|
-
const res = await this.client.
|
|
87
|
+
const res = await this.client.put(`/api/project/${this.projectId}/storage/metadata?path=${encodeURIComponent(path)}`, metadata);
|
|
43
88
|
return res.data;
|
|
44
89
|
}
|
|
45
90
|
catch (err) {
|
|
46
|
-
|
|
91
|
+
if (err.response) {
|
|
92
|
+
throw new NexaStorageError_1.NexaStorageError(err.response.data?.error?.code || 'storage/server-error', err.response.data?.error?.message || err.message, err.response.status);
|
|
93
|
+
}
|
|
94
|
+
throw new NexaStorageError_1.NexaStorageError('storage/network-error', err.message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async list(path, options) {
|
|
98
|
+
try {
|
|
99
|
+
const params = new URLSearchParams();
|
|
100
|
+
if (path)
|
|
101
|
+
params.append('path', path);
|
|
102
|
+
if (options?.maxResults)
|
|
103
|
+
params.append('maxResults', options.maxResults.toString());
|
|
104
|
+
const res = await this.client.get(`/api/project/${this.projectId}/storage/list?${params.toString()}`);
|
|
105
|
+
return res.data;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (err.response) {
|
|
109
|
+
throw new NexaStorageError_1.NexaStorageError(err.response.data?.error?.code || 'storage/server-error', err.response.data?.error?.message || err.message, err.response.status);
|
|
110
|
+
}
|
|
111
|
+
throw new NexaStorageError_1.NexaStorageError('storage/network-error', err.message);
|
|
47
112
|
}
|
|
48
113
|
}
|
|
49
114
|
}
|
|
@@ -52,6 +117,23 @@ const getStorage = (app) => {
|
|
|
52
117
|
if (app && typeof app.storage === 'function') {
|
|
53
118
|
return app.storage();
|
|
54
119
|
}
|
|
55
|
-
|
|
120
|
+
if (app && app.storageService) {
|
|
121
|
+
return app.storageService;
|
|
122
|
+
}
|
|
123
|
+
if (app instanceof Storage) {
|
|
124
|
+
return app;
|
|
125
|
+
}
|
|
126
|
+
throw new Error('NexaApp instance is required for getStorage(app)');
|
|
56
127
|
};
|
|
57
128
|
exports.getStorage = getStorage;
|
|
129
|
+
const connectStorageEmulator = (storage, host, port) => {
|
|
130
|
+
if (storage.emulatorUrl) {
|
|
131
|
+
throw new Error('Emulator already connected.');
|
|
132
|
+
}
|
|
133
|
+
storage.emulatorUrl = `http://${host}:${port}`;
|
|
134
|
+
const client = storage.client;
|
|
135
|
+
if (client) {
|
|
136
|
+
client.defaults.baseURL = storage.emulatorUrl;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
exports.connectStorageEmulator = connectStorageEmulator;
|
|
@@ -1,31 +1,66 @@
|
|
|
1
|
-
import { StorageReference as IStorageReference, UploadOptions } from '../types/index';
|
|
2
1
|
import { Storage } from './Storage';
|
|
3
|
-
|
|
2
|
+
import { UploadTask } from './UploadTask';
|
|
3
|
+
import { UploadMetadata, StorageMetadata, SettableMetadata, StringFormat, UploadResult, ListOptions, ListResult } from './types';
|
|
4
|
+
import { UploadOptions } from '../types/index';
|
|
5
|
+
export interface IStorageReference {
|
|
6
|
+
name: string;
|
|
7
|
+
bucket: string;
|
|
8
|
+
fullPath: string;
|
|
4
9
|
path: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
root: IStorageReference;
|
|
11
|
+
parent: IStorageReference | null;
|
|
12
|
+
storage: Storage;
|
|
13
|
+
put(file: Blob | Uint8Array | ArrayBuffer, metadata?: UploadMetadata): UploadTask;
|
|
14
|
+
putString(data: string, format?: StringFormat, metadata?: UploadMetadata): UploadTask;
|
|
15
|
+
getDownloadURL(): Promise<string>;
|
|
16
|
+
getMetadata(): Promise<StorageMetadata>;
|
|
17
|
+
updateMetadata(metadata: SettableMetadata): Promise<StorageMetadata>;
|
|
18
|
+
delete(): Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
message?: string;
|
|
21
|
+
}>;
|
|
22
|
+
listAll(): Promise<ListResult>;
|
|
23
|
+
list(options?: ListOptions): Promise<ListResult>;
|
|
24
|
+
upload?: (file: File | Blob, options?: UploadOptions) => Promise<{
|
|
8
25
|
url: string;
|
|
9
26
|
path: string;
|
|
10
27
|
}>;
|
|
28
|
+
}
|
|
29
|
+
export declare class StorageReferenceImpl implements IStorageReference {
|
|
30
|
+
path: string;
|
|
31
|
+
fullPath: string;
|
|
32
|
+
name: string;
|
|
33
|
+
bucket: string;
|
|
34
|
+
storage: Storage;
|
|
35
|
+
constructor(path: string, storage: Storage);
|
|
36
|
+
get root(): IStorageReference;
|
|
37
|
+
get parent(): IStorageReference | null;
|
|
38
|
+
put(data: Blob | Uint8Array | ArrayBuffer, metadata?: UploadMetadata): UploadTask;
|
|
39
|
+
putString(data: string, format?: StringFormat, metadata?: UploadMetadata): UploadTask;
|
|
11
40
|
getDownloadURL(): Promise<string>;
|
|
41
|
+
getMetadata(): Promise<StorageMetadata>;
|
|
42
|
+
updateMetadata(metadata: SettableMetadata): Promise<StorageMetadata>;
|
|
12
43
|
delete(): Promise<{
|
|
13
44
|
success: boolean;
|
|
14
45
|
message?: string;
|
|
15
46
|
}>;
|
|
47
|
+
listAll(): Promise<ListResult>;
|
|
48
|
+
list(options?: ListOptions): Promise<ListResult>;
|
|
49
|
+
upload(file: File | Blob, options?: UploadOptions): Promise<{
|
|
50
|
+
url: string;
|
|
51
|
+
path: string;
|
|
52
|
+
}>;
|
|
16
53
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export declare const
|
|
24
|
-
url: string;
|
|
25
|
-
path: string;
|
|
26
|
-
}>;
|
|
54
|
+
/**
|
|
55
|
+
* 1:1 Modular Firebase Storage API functions
|
|
56
|
+
*/
|
|
57
|
+
export declare const ref: (storageOrRef: Storage | IStorageReference, path?: string) => IStorageReference;
|
|
58
|
+
export declare const uploadBytes: (storageRef: IStorageReference, data: Blob | Uint8Array | ArrayBuffer, metadata?: UploadMetadata) => Promise<UploadResult>;
|
|
59
|
+
export declare const uploadBytesResumable: (storageRef: IStorageReference, data: Blob | Uint8Array | ArrayBuffer, metadata?: UploadMetadata) => UploadTask;
|
|
60
|
+
export declare const uploadString: (storageRef: IStorageReference, value: string, format?: StringFormat, metadata?: UploadMetadata) => Promise<UploadResult>;
|
|
27
61
|
export declare const getDownloadURL: (storageRef: IStorageReference) => Promise<string>;
|
|
28
|
-
export declare const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
62
|
+
export declare const getMetadata: (storageRef: IStorageReference) => Promise<StorageMetadata>;
|
|
63
|
+
export declare const updateMetadata: (storageRef: IStorageReference, metadata: SettableMetadata) => Promise<StorageMetadata>;
|
|
64
|
+
export declare const deleteObject: (storageRef: IStorageReference) => Promise<void>;
|
|
65
|
+
export declare const listAll: (storageRef: IStorageReference) => Promise<ListResult>;
|
|
66
|
+
export declare const list: (storageRef: IStorageReference, options?: ListOptions) => Promise<ListResult>;
|
|
@@ -1,52 +1,177 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteObject = exports.getDownloadURL = exports.
|
|
3
|
+
exports.list = exports.listAll = exports.deleteObject = exports.updateMetadata = exports.getMetadata = exports.getDownloadURL = exports.uploadString = exports.uploadBytesResumable = exports.uploadBytes = exports.ref = exports.StorageReferenceImpl = void 0;
|
|
4
|
+
const NexaStorageError_1 = require("./NexaStorageError");
|
|
4
5
|
class StorageReferenceImpl {
|
|
5
6
|
constructor(path, storage) {
|
|
6
|
-
|
|
7
|
+
// Normalize path by stripping leading slashes
|
|
8
|
+
this.path = (path || '').replace(/^\/+/, '');
|
|
9
|
+
this.fullPath = this.path;
|
|
10
|
+
this.name = this.path.split('/').filter(Boolean).pop() || '';
|
|
11
|
+
this.bucket = storage.projectId || '';
|
|
7
12
|
this.storage = storage;
|
|
8
13
|
}
|
|
9
|
-
|
|
10
|
-
return this.storage
|
|
14
|
+
get root() {
|
|
15
|
+
return new StorageReferenceImpl('', this.storage);
|
|
16
|
+
}
|
|
17
|
+
get parent() {
|
|
18
|
+
const parts = this.path.split('/').filter(Boolean);
|
|
19
|
+
if (parts.length <= 1)
|
|
20
|
+
return null;
|
|
21
|
+
parts.pop();
|
|
22
|
+
return new StorageReferenceImpl(parts.join('/'), this.storage);
|
|
23
|
+
}
|
|
24
|
+
put(data, metadata) {
|
|
25
|
+
let blob;
|
|
26
|
+
if (data instanceof Blob) {
|
|
27
|
+
blob = data;
|
|
28
|
+
}
|
|
29
|
+
else if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
|
30
|
+
blob = new Blob([data], { type: metadata?.contentType || 'application/octet-stream' });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
blob = new Blob([data]);
|
|
34
|
+
}
|
|
35
|
+
return this.storage.createUploadTask(this.path, blob, metadata);
|
|
11
36
|
}
|
|
12
|
-
|
|
37
|
+
putString(data, format = 'raw', metadata) {
|
|
38
|
+
let blob;
|
|
39
|
+
try {
|
|
40
|
+
if (format === 'base64') {
|
|
41
|
+
const byteCharacters = atob(data);
|
|
42
|
+
const byteNumbers = new Uint8Array(byteCharacters.length);
|
|
43
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
44
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
45
|
+
}
|
|
46
|
+
blob = new Blob([byteNumbers], { type: metadata?.contentType || 'application/octet-stream' });
|
|
47
|
+
}
|
|
48
|
+
else if (format === 'base64url') {
|
|
49
|
+
let base64 = data.replace(/-/g, '+').replace(/_/g, '/');
|
|
50
|
+
while (base64.length % 4) {
|
|
51
|
+
base64 += '=';
|
|
52
|
+
}
|
|
53
|
+
const byteCharacters = atob(base64);
|
|
54
|
+
const byteNumbers = new Uint8Array(byteCharacters.length);
|
|
55
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
56
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
57
|
+
}
|
|
58
|
+
blob = new Blob([byteNumbers], { type: metadata?.contentType || 'application/octet-stream' });
|
|
59
|
+
}
|
|
60
|
+
else if (format === 'data_url') {
|
|
61
|
+
const parts = data.split(',');
|
|
62
|
+
const mimeMatch = parts[0]?.match(/:(.*?);/);
|
|
63
|
+
const detectedMime = mimeMatch ? mimeMatch[1] : 'application/octet-stream';
|
|
64
|
+
const b64Data = parts[1] || '';
|
|
65
|
+
const byteCharacters = atob(b64Data);
|
|
66
|
+
const byteNumbers = new Uint8Array(byteCharacters.length);
|
|
67
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
68
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
69
|
+
}
|
|
70
|
+
blob = new Blob([byteNumbers], { type: metadata?.contentType || detectedMime });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
blob = new Blob([data], { type: metadata?.contentType || 'text/plain;charset=utf-8' });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new NexaStorageError_1.NexaStorageError('storage/invalid-format', `Failed to parse data as ${format}: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
return this.put(blob, metadata);
|
|
80
|
+
}
|
|
81
|
+
getDownloadURL() {
|
|
13
82
|
return this.storage.getDownloadURL(this.path);
|
|
14
83
|
}
|
|
15
|
-
|
|
84
|
+
getMetadata() {
|
|
85
|
+
return this.storage.getMetadata(this.path);
|
|
86
|
+
}
|
|
87
|
+
updateMetadata(metadata) {
|
|
88
|
+
return this.storage.updateMetadata(this.path, metadata);
|
|
89
|
+
}
|
|
90
|
+
delete() {
|
|
16
91
|
return this.storage.deleteFile(this.path);
|
|
17
92
|
}
|
|
93
|
+
listAll() {
|
|
94
|
+
return this.storage.list(this.path);
|
|
95
|
+
}
|
|
96
|
+
list(options) {
|
|
97
|
+
return this.storage.list(this.path, options);
|
|
98
|
+
}
|
|
99
|
+
// Legacy for backward compatibility
|
|
100
|
+
async upload(file, options) {
|
|
101
|
+
const task = this.put(file, options);
|
|
102
|
+
task.options = options;
|
|
103
|
+
await task;
|
|
104
|
+
const url = await this.getDownloadURL();
|
|
105
|
+
return { url, path: this.path };
|
|
106
|
+
}
|
|
18
107
|
}
|
|
19
108
|
exports.StorageReferenceImpl = StorageReferenceImpl;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
109
|
+
/**
|
|
110
|
+
* 1:1 Modular Firebase Storage API functions
|
|
111
|
+
*/
|
|
112
|
+
const ref = (storageOrRef, path) => {
|
|
113
|
+
if (!storageOrRef) {
|
|
114
|
+
throw new NexaStorageError_1.NexaStorageError('storage/invalid-path', 'No storage instance or reference provided');
|
|
115
|
+
}
|
|
116
|
+
if ('storage' in storageOrRef && typeof storageOrRef.storage?.ref === 'function') {
|
|
117
|
+
// First argument is a StorageReference, child path is appended
|
|
118
|
+
const parent = storageOrRef;
|
|
119
|
+
const combinedPath = path ? `${parent.path.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}` : parent.path;
|
|
120
|
+
return new StorageReferenceImpl(combinedPath, parent.storage);
|
|
121
|
+
}
|
|
122
|
+
const storage = storageOrRef;
|
|
123
|
+
return new StorageReferenceImpl(path || '', storage);
|
|
26
124
|
};
|
|
27
125
|
exports.ref = ref;
|
|
28
|
-
const uploadBytes = async (storageRef,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
126
|
+
const uploadBytes = async (storageRef, data, metadata) => {
|
|
127
|
+
const task = storageRef.put(data, metadata);
|
|
128
|
+
const snapshot = await task;
|
|
129
|
+
return {
|
|
130
|
+
bytesTransferred: snapshot.bytesTransferred,
|
|
131
|
+
totalBytes: snapshot.totalBytes,
|
|
132
|
+
state: snapshot.state,
|
|
133
|
+
metadata: snapshot.metadata,
|
|
134
|
+
ref: storageRef
|
|
135
|
+
};
|
|
33
136
|
};
|
|
34
137
|
exports.uploadBytes = uploadBytes;
|
|
35
|
-
const uploadBytesResumable = (storageRef,
|
|
36
|
-
return
|
|
138
|
+
const uploadBytesResumable = (storageRef, data, metadata) => {
|
|
139
|
+
return storageRef.put(data, metadata);
|
|
37
140
|
};
|
|
38
141
|
exports.uploadBytesResumable = uploadBytesResumable;
|
|
142
|
+
const uploadString = async (storageRef, value, format = 'raw', metadata) => {
|
|
143
|
+
const task = storageRef.putString(value, format, metadata);
|
|
144
|
+
const snapshot = await task;
|
|
145
|
+
return {
|
|
146
|
+
bytesTransferred: snapshot.bytesTransferred,
|
|
147
|
+
totalBytes: snapshot.totalBytes,
|
|
148
|
+
state: snapshot.state,
|
|
149
|
+
metadata: snapshot.metadata,
|
|
150
|
+
ref: storageRef
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
exports.uploadString = uploadString;
|
|
39
154
|
const getDownloadURL = async (storageRef) => {
|
|
40
|
-
|
|
41
|
-
return storageRef.getDownloadURL();
|
|
42
|
-
}
|
|
43
|
-
throw new Error('Invalid storage reference');
|
|
155
|
+
return storageRef.getDownloadURL();
|
|
44
156
|
};
|
|
45
157
|
exports.getDownloadURL = getDownloadURL;
|
|
158
|
+
const getMetadata = async (storageRef) => {
|
|
159
|
+
return storageRef.getMetadata();
|
|
160
|
+
};
|
|
161
|
+
exports.getMetadata = getMetadata;
|
|
162
|
+
const updateMetadata = async (storageRef, metadata) => {
|
|
163
|
+
return storageRef.updateMetadata(metadata);
|
|
164
|
+
};
|
|
165
|
+
exports.updateMetadata = updateMetadata;
|
|
46
166
|
const deleteObject = async (storageRef) => {
|
|
47
|
-
|
|
48
|
-
return storageRef.delete();
|
|
49
|
-
}
|
|
50
|
-
throw new Error('Invalid storage reference');
|
|
167
|
+
await storageRef.delete();
|
|
51
168
|
};
|
|
52
169
|
exports.deleteObject = deleteObject;
|
|
170
|
+
const listAll = async (storageRef) => {
|
|
171
|
+
return storageRef.listAll();
|
|
172
|
+
};
|
|
173
|
+
exports.listAll = listAll;
|
|
174
|
+
const list = async (storageRef, options) => {
|
|
175
|
+
return storageRef.list(options);
|
|
176
|
+
};
|
|
177
|
+
exports.list = list;
|
|
@@ -1,10 +1,37 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { UploadMetadata, UploadTaskSnapshot } from './types';
|
|
1
3
|
import { UploadOptions } from '../types/index';
|
|
2
|
-
|
|
4
|
+
import { NexaStorageError } from './NexaStorageError';
|
|
5
|
+
export declare class UploadTask implements PromiseLike<UploadTaskSnapshot> {
|
|
3
6
|
private file;
|
|
4
7
|
private path;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
options?: UploadOptions;
|
|
9
|
+
private metadata?;
|
|
10
|
+
private client;
|
|
11
|
+
private projectId;
|
|
12
|
+
private state;
|
|
13
|
+
private bytesTransferred;
|
|
14
|
+
private totalBytes;
|
|
15
|
+
private sessionUrl;
|
|
16
|
+
private abortController;
|
|
17
|
+
private listeners;
|
|
18
|
+
private errorListeners;
|
|
19
|
+
private completeListeners;
|
|
20
|
+
private _promise;
|
|
21
|
+
ref: any;
|
|
22
|
+
constructor(projectId: string, client: AxiosInstance, path: string, file: File | Blob, metadata?: UploadMetadata, options?: UploadOptions);
|
|
23
|
+
on(event: 'state_changed', nextOrObserver?: ((snapshot: UploadTaskSnapshot) => void) | {
|
|
24
|
+
next?: (s: UploadTaskSnapshot) => void;
|
|
25
|
+
error?: (e: NexaStorageError) => void;
|
|
26
|
+
complete?: () => void;
|
|
27
|
+
}, error?: (error: NexaStorageError) => void, complete?: () => void): () => void;
|
|
28
|
+
private startPromise;
|
|
29
|
+
then<TResult1 = UploadTaskSnapshot, TResult2 = never>(onfulfilled?: ((value: UploadTaskSnapshot) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
30
|
+
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null): Promise<UploadTaskSnapshot | TResult>;
|
|
31
|
+
private _executeUpload;
|
|
32
|
+
pause(): void;
|
|
33
|
+
resume(): void;
|
|
34
|
+
cancel(): void;
|
|
35
|
+
getSnapshot(): UploadTaskSnapshot;
|
|
36
|
+
private notify;
|
|
10
37
|
}
|
|
@@ -1,21 +1,140 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UploadTask = void 0;
|
|
4
|
+
const NexaStorageError_1 = require("./NexaStorageError");
|
|
4
5
|
class UploadTask {
|
|
5
|
-
constructor(path, file, options) {
|
|
6
|
+
constructor(projectId, client, path, file, metadata, options) {
|
|
7
|
+
this.state = 'running';
|
|
8
|
+
this.bytesTransferred = 0;
|
|
9
|
+
this.totalBytes = 0;
|
|
10
|
+
this.sessionUrl = null;
|
|
11
|
+
this.listeners = [];
|
|
12
|
+
this.errorListeners = [];
|
|
13
|
+
this.completeListeners = [];
|
|
14
|
+
this._promise = null;
|
|
15
|
+
this.projectId = projectId;
|
|
16
|
+
this.client = client;
|
|
6
17
|
this.path = path;
|
|
7
18
|
this.file = file;
|
|
19
|
+
this.metadata = metadata;
|
|
8
20
|
this.options = options;
|
|
21
|
+
this.totalBytes = file.size;
|
|
22
|
+
this.abortController = new AbortController();
|
|
9
23
|
}
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
on(event, nextOrObserver, error, complete) {
|
|
25
|
+
if (typeof nextOrObserver === 'function') {
|
|
26
|
+
this.listeners.push(nextOrObserver);
|
|
27
|
+
if (error)
|
|
28
|
+
this.errorListeners.push(error);
|
|
29
|
+
if (complete)
|
|
30
|
+
this.completeListeners.push(complete);
|
|
31
|
+
}
|
|
32
|
+
else if (nextOrObserver) {
|
|
33
|
+
if (nextOrObserver.next)
|
|
34
|
+
this.listeners.push(nextOrObserver.next);
|
|
35
|
+
if (nextOrObserver.error)
|
|
36
|
+
this.errorListeners.push(nextOrObserver.error);
|
|
37
|
+
if (nextOrObserver.complete)
|
|
38
|
+
this.completeListeners.push(nextOrObserver.complete);
|
|
39
|
+
}
|
|
40
|
+
// Auto start if not started
|
|
41
|
+
this.startPromise();
|
|
42
|
+
return () => {
|
|
43
|
+
this.listeners = [];
|
|
44
|
+
this.errorListeners = [];
|
|
45
|
+
this.completeListeners = [];
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
startPromise() {
|
|
49
|
+
if (!this._promise) {
|
|
50
|
+
this._promise = this._executeUpload();
|
|
51
|
+
}
|
|
52
|
+
return this._promise;
|
|
53
|
+
}
|
|
54
|
+
then(onfulfilled, onrejected) {
|
|
55
|
+
return this.startPromise().then(onfulfilled, onrejected);
|
|
56
|
+
}
|
|
57
|
+
catch(onrejected) {
|
|
58
|
+
return this.startPromise().catch(onrejected);
|
|
59
|
+
}
|
|
60
|
+
async _executeUpload() {
|
|
61
|
+
try {
|
|
62
|
+
this.state = 'running';
|
|
63
|
+
this.notify();
|
|
64
|
+
const sessionRes = await this.client.post(`/api/project/${this.projectId}/storage/upload/session`, {
|
|
65
|
+
path: this.path,
|
|
66
|
+
metadata: {
|
|
67
|
+
contentType: this.file.type,
|
|
68
|
+
...this.metadata
|
|
69
|
+
}
|
|
70
|
+
}, { signal: this.abortController.signal });
|
|
71
|
+
this.sessionUrl = sessionRes.data.sessionUrl;
|
|
72
|
+
const uploadRes = await this.client.put(this.sessionUrl, this.file, {
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/octet-stream',
|
|
75
|
+
'x-upload-final': 'true'
|
|
76
|
+
},
|
|
77
|
+
signal: this.abortController.signal,
|
|
78
|
+
onUploadProgress: (progressEvent) => {
|
|
79
|
+
if (progressEvent.total) {
|
|
80
|
+
this.bytesTransferred = progressEvent.loaded;
|
|
81
|
+
this.notify();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this.state = 'success';
|
|
86
|
+
this.bytesTransferred = this.totalBytes;
|
|
87
|
+
const finalSnapshot = this.getSnapshot();
|
|
88
|
+
finalSnapshot.metadata = uploadRes.data.metadata;
|
|
89
|
+
this.notify();
|
|
90
|
+
this.completeListeners.forEach(cb => cb());
|
|
91
|
+
return finalSnapshot;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
this.state = 'error';
|
|
95
|
+
let nexaErr = new NexaStorageError_1.NexaStorageError('storage/network-error', err.message);
|
|
96
|
+
if (err.response) {
|
|
97
|
+
nexaErr = new NexaStorageError_1.NexaStorageError(err.response.data?.error?.code || 'storage/server-error', err.response.data?.error?.message || err.message, err.response.status);
|
|
98
|
+
}
|
|
99
|
+
this.errorListeners.forEach(cb => cb(nexaErr));
|
|
100
|
+
throw nexaErr;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
pause() {
|
|
104
|
+
if (this.state === 'running') {
|
|
105
|
+
this.state = 'paused';
|
|
106
|
+
this.notify();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
resume() {
|
|
110
|
+
if (this.state === 'paused') {
|
|
111
|
+
this.state = 'running';
|
|
112
|
+
this.notify();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
cancel() {
|
|
116
|
+
if (this.state === 'running' || this.state === 'paused') {
|
|
117
|
+
this.state = 'canceled';
|
|
118
|
+
this.abortController.abort();
|
|
119
|
+
this.notify();
|
|
120
|
+
const err = new NexaStorageError_1.NexaStorageError('storage/canceled', 'Upload canceled');
|
|
121
|
+
this.errorListeners.forEach(cb => cb(err));
|
|
122
|
+
}
|
|
12
123
|
}
|
|
13
|
-
|
|
14
|
-
return
|
|
124
|
+
getSnapshot() {
|
|
125
|
+
return {
|
|
126
|
+
bytesTransferred: this.bytesTransferred,
|
|
127
|
+
totalBytes: this.totalBytes,
|
|
128
|
+
state: this.state,
|
|
129
|
+
task: this,
|
|
130
|
+
ref: this.ref
|
|
131
|
+
};
|
|
15
132
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
133
|
+
notify() {
|
|
134
|
+
const snap = this.getSnapshot();
|
|
135
|
+
this.listeners.forEach(cb => cb(snap));
|
|
136
|
+
if (this.options?.onProgress) {
|
|
137
|
+
this.options.onProgress(Math.round((snap.bytesTransferred / snap.totalBytes) * 100));
|
|
19
138
|
}
|
|
20
139
|
}
|
|
21
140
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type StorageErrorCode = 'storage/unauthenticated' | 'storage/unauthorized' | 'storage/object-not-found' | 'storage/object-already-exists' | 'storage/invalid-path' | 'storage/invalid-file' | 'storage/invalid-format' | 'storage/invalid-metadata' | 'storage/invalid-checksum' | 'storage/quota-exceeded' | 'storage/upload-session-expired' | 'storage/retry-limit-exceeded' | 'storage/canceled' | 'storage/conflict' | 'storage/network-error' | 'storage/server-error';
|
|
2
|
+
export interface StorageMetadata {
|
|
3
|
+
name: string;
|
|
4
|
+
bucket: string;
|
|
5
|
+
generation: string;
|
|
6
|
+
metageneration: string;
|
|
7
|
+
fullPath: string;
|
|
8
|
+
size: number;
|
|
9
|
+
timeCreated: string;
|
|
10
|
+
updated: string;
|
|
11
|
+
md5Hash?: string;
|
|
12
|
+
cacheControl?: string;
|
|
13
|
+
contentDisposition?: string;
|
|
14
|
+
contentEncoding?: string;
|
|
15
|
+
contentLanguage?: string;
|
|
16
|
+
contentType?: string;
|
|
17
|
+
customMetadata?: Record<string, string>;
|
|
18
|
+
downloadTokens?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface UploadMetadata {
|
|
21
|
+
md5Hash?: string;
|
|
22
|
+
cacheControl?: string;
|
|
23
|
+
contentDisposition?: string;
|
|
24
|
+
contentEncoding?: string;
|
|
25
|
+
contentLanguage?: string;
|
|
26
|
+
contentType?: string;
|
|
27
|
+
customMetadata?: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
export type SettableMetadata = UploadMetadata;
|
|
30
|
+
export type StringFormat = 'raw' | 'base64' | 'base64url' | 'data_url';
|
|
31
|
+
export type TaskState = 'running' | 'paused' | 'success' | 'canceled' | 'error';
|
|
32
|
+
export interface UploadTaskSnapshot {
|
|
33
|
+
bytesTransferred: number;
|
|
34
|
+
totalBytes: number;
|
|
35
|
+
state: TaskState;
|
|
36
|
+
metadata?: StorageMetadata;
|
|
37
|
+
task: any;
|
|
38
|
+
ref: any;
|
|
39
|
+
}
|
|
40
|
+
export interface UploadResult {
|
|
41
|
+
bytesTransferred: number;
|
|
42
|
+
totalBytes: number;
|
|
43
|
+
state: TaskState;
|
|
44
|
+
metadata?: StorageMetadata;
|
|
45
|
+
ref: any;
|
|
46
|
+
}
|
|
47
|
+
export interface ListOptions {
|
|
48
|
+
maxResults?: number;
|
|
49
|
+
pageToken?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ListResult {
|
|
52
|
+
items: any[];
|
|
53
|
+
prefixes: any[];
|
|
54
|
+
nextPageToken?: string;
|
|
55
|
+
}
|
package/package.json
CHANGED