@nhost/hasura-storage-js
Version:
Hasura-storage client
731 lines (730 loc) • 21.8 kB
JavaScript
var C = Object.defineProperty;
var $ = (t, e, r) => e in t ? C(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
var f = (t, e, r) => $(t, typeof e != "symbol" ? e + "" : e, r);
import M from "fetch-ponyfill";
import w from "form-data";
import { createMachine as H, assign as c, spawn as N, send as P, actions as j } from "xstate";
let k = globalThis.fetch;
const E = async (t, e, {
accessToken: r,
name: s,
fileId: a,
bucketId: i,
adminSecret: o,
onUploadProgress: d,
headers: l = {}
} = {}) => {
var L;
const h = {
...l
};
i && e.append("bucket-id", i), o && (h["x-hasura-admin-secret"] = o), r && (h.Authorization = `Bearer ${r}`);
const T = `${t}/files`;
if (typeof XMLHttpRequest == "undefined")
try {
e instanceof w && (k = M().fetch);
const u = await k(T, {
method: "POST",
headers: h,
body: e
// * https://github.com/form-data/form-data/issues/513
}), n = await u.json();
return u.ok ? { fileMetadata: n, error: null } : { error: {
status: u.status,
message: ((L = n == null ? void 0 : n.error) == null ? void 0 : L.message) || u.statusText,
// * errors from hasura-storage are not codified
error: u.statusText
}, fileMetadata: null };
} catch (u) {
return { error: {
status: 0,
message: u.message,
error: u.message
}, fileMetadata: null };
}
return new Promise((u) => {
let n = new XMLHttpRequest();
n.responseType = "json", n.onload = () => {
var p, g, S, F, R, _, x, I;
if (n.status < 200 || n.status >= 300) {
const b = {
error: (R = (F = (g = (p = n.response) == null ? void 0 : p.error) == null ? void 0 : g.message) != null ? F : (S = n.response) == null ? void 0 : S.error) != null ? R : n.response,
message: (I = (x = (_ = n.response) == null ? void 0 : _.error) == null ? void 0 : x.message) != null ? I : n.response,
status: n.status
};
return u({
fileMetadata: null,
error: b
});
}
return u({ fileMetadata: n.response, error: null });
}, n.onerror = () => {
const p = {
error: n.statusText,
message: n.statusText,
status: n.status
};
return u({
fileMetadata: null,
error: p
});
}, d && n.upload.addEventListener("progress", d, !1), n.open("POST", T, !0), Object.entries(h).forEach(([p, g]) => {
n.setRequestHeader(p, g);
}), n.send(e);
});
};
function D(t, e) {
if (!e || Object.keys(e).length === 0)
return t;
const r = new URL(t), s = Object.entries(e).reduce(
(a, [i, o]) => ({ ...a, [i.charAt(0)]: o }),
{}
);
return Object.entries(s).forEach(([a, i]) => {
i && r.searchParams.set(a, i);
}), r.toString();
}
let m;
typeof m == "undefined" && (m = M().fetch);
class z {
constructor({ url: e }) {
f(this, "url");
f(this, "accessToken");
f(this, "adminSecret");
f(this, "headers", {});
this.url = e;
}
async uploadFormData({
formData: e,
bucketId: r,
headers: s
}) {
const { error: a, fileMetadata: i } = await E(this.url, e, {
bucketId: r,
headers: {
...this.headers,
// global nhost storage client headers to be sent with all `uploadFormData` calls
...s
// extra headers to be sent with a specific call
},
accessToken: this.accessToken,
adminSecret: this.adminSecret
});
return a ? { fileMetadata: null, error: a } : i && !("processedFiles" in i) ? {
fileMetadata: {
processedFiles: [i]
},
error: null
} : { fileMetadata: i, error: null };
}
async uploadFile({
file: e,
bucketId: r,
id: s,
name: a,
headers: i
}) {
const o = typeof window == "undefined" ? new w() : new FormData();
o.append("file[]", e), o.append("metadata[]", JSON.stringify({ id: s, name: a }));
const { error: d, fileMetadata: l } = await E(this.url, o, {
accessToken: this.accessToken,
adminSecret: this.adminSecret,
bucketId: r,
fileId: s,
name: a,
headers: {
...this.headers,
// global nhost storage client headers to be sent with all `uploadFile` calls
...i
// extra headers to be sent with a specific call
}
});
return d ? { fileMetadata: null, error: d } : l && "processedFiles" in l ? {
fileMetadata: l.processedFiles[0],
error: null
} : { fileMetadata: l, error: null };
}
async downloadFile(e) {
try {
const { fileId: r, headers: s, ...a } = e, i = D(
`${this.url}/files/${r}`,
a
), o = await m(i, {
method: "GET",
headers: {
...this.generateAuthHeaders(),
...this.headers,
// global nhost storage client headers to be sent with all `downloadFile` calls
...s
// extra headers to be sent with a specific call
}
});
if (!o.ok)
throw new Error(await o.text());
return { file: await o.blob(), error: null };
} catch (r) {
return { file: null, error: r };
}
}
async getPresignedUrl(e) {
try {
const { fileId: r, headers: s } = e, a = await m(`${this.url}/files/${r}/presignedurl`, {
method: "GET",
headers: {
...this.generateAuthHeaders(),
...this.headers,
// global nhost storage client headers to be sent with all `getPresignedUrl` calls
...s
// extra headers to be sent with a specific call
}
});
if (!a.ok)
throw new Error(await a.text());
return { presignedUrl: await a.json(), error: null };
} catch (r) {
return { presignedUrl: null, error: r };
}
}
async delete(e) {
try {
const { fileId: r, headers: s } = e, a = await m(`${this.url}/files/${r}`, {
method: "DELETE",
headers: {
...this.generateAuthHeaders(),
...this.headers,
// global nhost storage client headers to be sent with all `delete` calls
...s
// extra headers to be sent with a specific call
}
});
if (!a.ok)
throw new Error(await a.text());
return { error: null };
} catch (r) {
return { error: r };
}
}
/**
* Set the access token to use for authentication.
*
* @param accessToken Access token
* @returns Hasura Storage API instance
*/
setAccessToken(e) {
return this.accessToken = e, this;
}
/**
* Set the admin secret to use for authentication.
*
* @param adminSecret Hasura admin secret
* @returns Hasura Storage API instance
*/
setAdminSecret(e) {
return this.adminSecret = e, this;
}
/**
* Get global headers sent with all requests.
*
* @returns Record<string, string>
*/
getHeaders() {
return this.headers;
}
/**
* Set global headers to be sent with all requests.
*
* @param headers a key value pair headers object
* @returns Hasura Storage API instance
*/
setHeaders(e) {
return e ? (this.headers = {
...this.headers,
...e
}, this) : this;
}
/**
* Remove global headers sent with all requests, except for the role header to preserve
* the role set by 'setRole' method.
*
* @returns {HasuraStorageApi} - Hasura Storage API instance.
*/
unsetHeaders() {
const e = this.headers["x-hasura-role"];
return this.headers = e ? { "x-hasura-role": e } : {}, this;
}
generateAuthHeaders() {
if (!(!this.adminSecret && !this.accessToken))
return this.adminSecret ? {
"x-hasura-admin-secret": this.adminSecret
} : {
Authorization: `Bearer ${this.accessToken}`
};
}
}
class W {
constructor({ url: e, adminSecret: r }) {
f(this, "url");
f(this, "api");
this.url = e, this.api = new z({ url: e }), this.setAdminSecret(r);
}
async upload(e) {
return "file" in e ? this.api.uploadFile(e) : this.api.uploadFormData(e);
}
/**
* Use `nhost.storage.getPublicUrl` to get the public URL of a file. The public URL can be used for un-authenticated users to access files. To access public files the `public` role must have permissions to select the file in the `storage.files` table.
*
* @example
* ```ts
* const publicUrl = nhost.storage.getPublicUrl({ fileId: '<File-ID>' })
* ```
*
* @docs https://docs.nhost.io/reference/javascript/storage/get-public-url
*/
getPublicUrl(e) {
const { fileId: r, ...s } = e;
return D(
`${this.url}/files/${r}`,
s
);
}
/**
* Use `nhost.storage.getPresignedUrl` to get a presigned URL of a file. To get a presigned URL the user must have permission to select the file in the `storage.files` table.
*
* @example
* ```ts
* const { presignedUrl, error} = await nhost.storage.getPresignedUrl({ fileId: '<File-ID>' })
*
* if (error) {
* throw error;
* }
*
* console.log('url: ', presignedUrl.url)
* console.log('expiration: ', presignedUrl.expiration)
* ```
*
* @docs https://docs.nhost.io/reference/javascript/storage/get-presigned-url
*/
async getPresignedUrl(e) {
const { fileId: r, headers: s, ...a } = e, { presignedUrl: i, error: o } = await this.api.getPresignedUrl(e);
if (o)
return { presignedUrl: null, error: o };
if (!i)
return { presignedUrl: null, error: new Error("Invalid file id") };
const d = D(
i.url,
a
);
return {
presignedUrl: {
...i,
url: d
},
error: null
};
}
/**
* Use `nhost.storage.download` to download a file. To download a file the user must have permission to select the file in the `storage.files` table.
*
* @example
* ```ts
* const { file, error} = await nhost.storage.download({ fileId: '<File-ID>' })
* ```
*
* @docs https://docs.nhost.io/reference/javascript/storage/download
*/
async download(e) {
const { file: r, error: s } = await this.api.downloadFile(e);
return s ? { file: null, error: s } : r ? {
file: r,
error: null
} : { file: null, error: new Error("File does not exist") };
}
/**
* Use `nhost.storage.delete` to delete a file. To delete a file the user must have permissions to delete the file in the `storage.files` table. Deleting the file using `nhost.storage.delete()` will delete both the file and its metadata.
*
* @example
* ```ts
* const { error } = await nhost.storage.delete({ fileId: 'uuid' })
* ```
*
* @docs https://docs.nhost.io/reference/javascript/storage/delete
*/
async delete(e) {
const { error: r } = await this.api.delete(e);
return r ? { error: r } : { error: null };
}
/**
* Use `nhost.storage.setAccessToken` to a set an access token to be used in subsequent storage requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
*
* @example
* ```ts
* nhost.storage.setAccessToken('some-access-token')
* ```
*
* @param accessToken Access token
*
* @docs https://docs.nhost.io/reference/javascript/storage/set-access-token
*/
setAccessToken(e) {
return this.api.setAccessToken(e), this;
}
/**
* Use `nhost.storage.adminSecret` to set the admin secret to be used for subsequent storage requests. This is useful if you want to run storage in "admin mode".
*
* @example
* ```ts
* nhost.storage.setAdminSecret('some-admin-secret')
* ```
*
* @param adminSecret Hasura admin secret
*
* @docs https://docs.nhost.io/reference/javascript/storage/set-admin-secret
*/
setAdminSecret(e) {
return this.api.setAdminSecret(e), this;
}
/**
* Use `nhost.storage.getHeaders` to get global headers sent with all storage requests.
*
* @example
* ```ts
* nhost.storage.getHeaders()
* ```
*
* @docs https://docs.nhost.io/reference/javascript/storage/get-headers
*/
getHeaders() {
return this.api.getHeaders();
}
/**
* Use `nhost.storage.setHeaders` to set global headers to be sent for all subsequent storage requests.
*
* @example
* ```ts
* nhost.storage.setHeaders({
* 'x-hasura-role': 'admin'
* })
* ```
*
* @param headers key value headers object
*
* @docs https://docs.nhost.io/reference/javascript/storage/set-headers
*/
setHeaders(e) {
return this.api.setHeaders(e), this;
}
/**
* Use `nhost.storage.unsetHeaders` to remove the global headers sent for all subsequent storage requests.
*
* @example
* ```ts
* nhost.storage.unsetHeaders()
* ```
*
* @param headers key value headers object
*
* @docs https://docs.nhost.io/reference/javascript/storage/unset-headers
*/
unsetHeaders() {
return this.api.unsetHeaders(), this;
}
}
let O;
typeof O == "undefined" && (O = w);
const U = {
progress: null,
loaded: 0,
error: null,
bucketId: void 0,
file: void 0,
id: void 0
}, G = () => H(
{
predictableActionArguments: !0,
schema: {
context: {},
events: {}
},
tsTypes: {},
context: { ...U },
initial: "idle",
on: {
DESTROY: { actions: "sendDestroy", target: "stopped" }
},
states: {
idle: {
on: {
ADD: { actions: "addFile" },
UPLOAD: { cond: "hasFile", target: "uploading" }
}
},
uploading: {
entry: "resetProgress",
on: {
UPLOAD_PROGRESS: { actions: ["incrementProgress", "sendProgress"] },
UPLOAD_DONE: "uploaded",
UPLOAD_ERROR: "error",
CANCEL: "idle"
},
invoke: { src: "uploadFile" }
},
uploaded: {
entry: ["setFileMetadata", "sendDone"],
on: {
ADD: { actions: "addFile", target: "idle" },
UPLOAD: { actions: "resetContext", target: "uploading" }
}
},
error: {
entry: ["setError", "sendError"],
on: {
ADD: { actions: "addFile", target: "idle" },
UPLOAD: { actions: "resetContext", target: "uploading" }
}
},
stopped: { type: "final" }
}
},
{
guards: {
hasFile: (t, e) => !!t.file || !!e.file
},
actions: {
incrementProgress: c({
loaded: (t, { loaded: e }) => e,
progress: (t, { progress: e }) => e
}),
setFileMetadata: c({
id: (t, { id: e }) => e,
bucketId: (t, { bucketId: e }) => e,
progress: (t) => 100
}),
setError: c({ error: (t, { error: e }) => e }),
sendProgress: () => {
},
sendError: () => {
},
sendDestroy: () => {
},
sendDone: () => {
},
resetProgress: c({ progress: (t) => null, loaded: (t) => 0 }),
resetContext: c((t) => U),
addFile: c({
file: (t, { file: e }) => e,
bucketId: (t, { bucketId: e }) => e,
id: (t, { id: e }) => e
})
},
services: {
uploadFile: (t, e) => (r) => {
const s = e.file || t.file, a = new O();
a.append("file[]", s);
let i = 0;
return E(e.url, a, {
fileId: e.id || t.id,
bucketId: e.bucketId || t.bucketId,
accessToken: e.accessToken,
adminSecret: e.adminSecret,
name: e.name || s.name,
onUploadProgress: (o) => {
const d = o.total ? Math.round(o.loaded * s.size / o.total) : 0, l = d - i;
i = d, r({
type: "UPLOAD_PROGRESS",
progress: o.total ? Math.round(d * 100 / o.total) : 0,
loaded: d,
additions: l
});
}
}).then(({ fileMetadata: o, error: d }) => {
if (d && r({ type: "UPLOAD_ERROR", error: d }), o && !("processedFiles" in o)) {
const { id: l, bucketId: h } = o;
r({ type: "UPLOAD_DONE", id: l, bucketId: h });
}
if (o && "processedFiles" in o) {
const { id: l, bucketId: h } = o.processedFiles[0];
r({ type: "UPLOAD_DONE", id: l, bucketId: h });
}
}), () => {
};
}
}
}
), { pure: y, sendParent: A } = j, Y = () => H(
{
id: "files-list",
schema: {
context: {},
events: {}
},
tsTypes: {},
predictableActionArguments: !0,
context: {
progress: null,
files: [],
loaded: 0,
total: 0
},
initial: "idle",
on: {
UPLOAD: { cond: "hasFileToDownload", actions: "addItem", target: "uploading" },
ADD: { actions: "addItem" },
REMOVE: { actions: "removeItem" }
},
states: {
idle: {
entry: ["resetProgress", "resetLoaded", "resetTotal"],
on: {
CLEAR: { actions: "clearList", target: "idle" }
}
},
uploading: {
entry: ["upload", "startProgress", "resetLoaded", "resetTotal"],
on: {
UPLOAD_PROGRESS: { actions: ["incrementProgress"] },
UPLOAD_DONE: [
{ cond: "isAllUploaded", target: "uploaded" },
{ cond: "isAllUploadedOrError", target: "error" }
],
UPLOAD_ERROR: [
{ cond: "isAllUploaded", target: "uploaded" },
{ cond: "isAllUploadedOrError", target: "error" }
],
CANCEL: { actions: "cancel", target: "idle" }
}
},
uploaded: {
entry: "setUploaded",
on: {
CLEAR: { actions: "clearList", target: "idle" }
}
},
error: {
on: {
CLEAR: { actions: "clearList", target: "idle" }
}
}
}
},
{
guards: {
hasFileToDownload: (t, e) => t.files.some((r) => r.getSnapshot().matches("idle")) || !!e.files,
isAllUploaded: (t) => t.files.every((e) => {
var r;
return (r = e.getSnapshot()) == null ? void 0 : r.matches("uploaded");
}),
isAllUploadedOrError: (t) => t.files.every((e) => {
const r = e.getSnapshot();
return (r == null ? void 0 : r.matches("error")) || (r == null ? void 0 : r.matches("uploaded"));
})
},
actions: {
incrementProgress: c((t, e) => {
const r = t.loaded + e.additions, s = Math.round(r * 100 / t.total);
return { ...t, loaded: r, progress: s };
}),
setUploaded: c({
progress: (t) => 100,
loaded: ({ files: t }) => t.map((e) => e.getSnapshot()).filter((e) => e.matches("uploaded")).reduce((e, r) => {
var s;
return e + ((s = r.context.file) == null ? void 0 : s.size);
}, 0)
}),
resetTotal: c({
total: ({ files: t }) => t.map((e) => e.getSnapshot()).filter((e) => !e.matches("uploaded")).reduce((e, r) => {
var s;
return e + ((s = r.context.file) == null ? void 0 : s.size);
}, 0)
}),
resetLoaded: c({ loaded: (t) => 0 }),
startProgress: c({ progress: (t) => 0 }),
resetProgress: c({ progress: (t) => null }),
addItem: c((t, { files: e, bucketId: r }) => {
const s = e ? Array.isArray(e) ? e : "item" in e ? Array.from(e) : [e] : [], a = t.total + s.reduce((o, d) => o + d.size, 0), i = Math.round(t.loaded * 100 / a);
return {
files: [
...t.files,
...s.map(
(o) => N(
G().withConfig({
actions: {
sendProgress: A((d, { additions: l }) => ({
type: "UPLOAD_PROGRESS",
additions: l
})),
sendDone: A("UPLOAD_DONE"),
sendError: A("UPLOAD_ERROR"),
sendDestroy: A("REMOVE")
}
}).withContext({ ...U, file: o, bucketId: r }),
{ sync: !0 }
)
)
],
total: a,
loaded: t.loaded,
progress: i
};
}),
removeItem: c({
files: (t) => t.files.filter((e) => {
var s, a;
const r = (s = e.getSnapshot()) == null ? void 0 : s.matches("stopped");
return r && ((a = e.stop) == null || a.call(e)), !r;
})
}),
clearList: y(
(t) => t.files.map((e) => P({ type: "DESTROY" }, { to: e.id }))
),
upload: y((t, e) => t.files.map((r) => P(e, { to: r.id }))),
cancel: y(
(t) => t.files.map((e) => P({ type: "CANCEL" }, { to: e.id }))
)
}
}
), J = async (t, e) => new Promise((r) => {
e.send({
type: "UPLOAD",
...t
}), e.subscribe((s) => {
var a;
s.matches("error") ? r({
error: s.context.error,
isError: !0,
isUploaded: !1
}) : s.matches("uploaded") && r({
error: null,
isError: !1,
isUploaded: !0,
id: s.context.id,
bucketId: s.context.id,
name: (a = s.context.file) == null ? void 0 : a.name
});
});
}), K = async (t, e) => new Promise((r) => {
e.send({
type: "UPLOAD",
...t,
files: t.files
}), e.onTransition((s) => {
s.matches("error") ? r({
errors: s.context.files.filter((a) => {
var i;
return (i = a.getSnapshot()) == null ? void 0 : i.context.error;
}),
isError: !0,
files: []
}) : s.matches("uploaded") && r({ errors: [], isError: !1, files: s.context.files });
});
});
export {
z as HasuraStorageApi,
W as HasuraStorageClient,
U as INITIAL_FILE_CONTEXT,
D as appendImageTransformationParameters,
G as createFileUploadMachine,
Y as createMultipleFilesUploadMachine,
J as uploadFilePromise,
K as uploadMultipleFilesPromise
};
//# sourceMappingURL=index.esm.js.map