@sylphx/sdk 0.9.0 → 0.10.0
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/index.d.ts +104 -291
- package/dist/index.mjs +461 -194
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.ts +91 -310
- package/dist/react/index.mjs +1273 -977
- package/dist/react/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -8002,169 +8002,248 @@ async function getBillingUsage(config, options) {
|
|
|
8002
8002
|
});
|
|
8003
8003
|
}
|
|
8004
8004
|
|
|
8005
|
-
// src/
|
|
8006
|
-
import { storageEndpoints } from "@sylphx/contract";
|
|
8005
|
+
// src/lib/retry.ts
|
|
8007
8006
|
init_constants();
|
|
8008
|
-
|
|
8009
|
-
var UPLOAD_RETRY_CONFIG = {
|
|
8010
|
-
/** Maximum number of retry attempts (AWS S3 pattern) */
|
|
8007
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
8011
8008
|
maxRetries: 5,
|
|
8012
|
-
/** Base delay in milliseconds */
|
|
8013
8009
|
baseDelayMs: BASE_RETRY_DELAY_MS,
|
|
8014
|
-
|
|
8015
|
-
maxDelayMs: MAX_RETRY_DELAY_MS,
|
|
8016
|
-
/** Jitter type: 'full' for full jitter (AWS recommended) */
|
|
8017
|
-
jitter: "full"
|
|
8010
|
+
maxDelayMs: MAX_RETRY_DELAY_MS
|
|
8018
8011
|
};
|
|
8019
|
-
function calculateBackoffDelay(attempt) {
|
|
8020
|
-
const
|
|
8021
|
-
const
|
|
8022
|
-
|
|
8023
|
-
return Math.random() * cappedDelay;
|
|
8012
|
+
function calculateBackoffDelay(attempt, config = DEFAULT_RETRY_CONFIG) {
|
|
8013
|
+
const exp = config.baseDelayMs * 2 ** attempt;
|
|
8014
|
+
const capped = Math.min(exp, config.maxDelayMs);
|
|
8015
|
+
return Math.random() * capped;
|
|
8024
8016
|
}
|
|
8025
|
-
|
|
8017
|
+
function sleep(ms, signal) {
|
|
8026
8018
|
return new Promise((resolve, reject) => {
|
|
8027
8019
|
if (signal?.aborted) {
|
|
8028
|
-
reject(
|
|
8020
|
+
reject(toAbortError());
|
|
8029
8021
|
return;
|
|
8030
8022
|
}
|
|
8031
|
-
const
|
|
8023
|
+
const timer = setTimeout(resolve, ms);
|
|
8032
8024
|
signal?.addEventListener(
|
|
8033
8025
|
"abort",
|
|
8034
8026
|
() => {
|
|
8035
|
-
clearTimeout(
|
|
8036
|
-
reject(
|
|
8027
|
+
clearTimeout(timer);
|
|
8028
|
+
reject(toAbortError());
|
|
8037
8029
|
},
|
|
8038
8030
|
{ once: true }
|
|
8039
8031
|
);
|
|
8040
8032
|
});
|
|
8041
8033
|
}
|
|
8034
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
8042
8035
|
function isRetryableError2(error) {
|
|
8043
|
-
if (error
|
|
8044
|
-
|
|
8045
|
-
}
|
|
8046
|
-
if (error instanceof TypeError) {
|
|
8047
|
-
return true;
|
|
8048
|
-
}
|
|
8036
|
+
if (isAbortError(error)) return false;
|
|
8037
|
+
if (error instanceof TypeError) return true;
|
|
8049
8038
|
if (error instanceof Error && "status" in error) {
|
|
8050
8039
|
const status = error.status;
|
|
8051
|
-
return status >= 500 || status
|
|
8040
|
+
return status >= 500 || RETRYABLE_STATUSES.has(status);
|
|
8052
8041
|
}
|
|
8053
8042
|
return false;
|
|
8054
8043
|
}
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
if (
|
|
8058
|
-
|
|
8044
|
+
function isAbortError(error) {
|
|
8045
|
+
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
8046
|
+
if (error instanceof Error && error.name === "AbortError") return true;
|
|
8047
|
+
return false;
|
|
8048
|
+
}
|
|
8049
|
+
function toAbortError(message2 = "Aborted") {
|
|
8050
|
+
if (typeof DOMException !== "undefined") {
|
|
8051
|
+
return new DOMException(message2, "AbortError");
|
|
8059
8052
|
}
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8053
|
+
const err = new Error(message2);
|
|
8054
|
+
err.name = "AbortError";
|
|
8055
|
+
return err;
|
|
8056
|
+
}
|
|
8057
|
+
async function withRetry(fn, options = {}) {
|
|
8058
|
+
const cfg = {
|
|
8059
|
+
maxRetries: options.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
|
|
8060
|
+
baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,
|
|
8061
|
+
maxDelayMs: options.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs
|
|
8062
|
+
};
|
|
8063
|
+
let lastError;
|
|
8064
|
+
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
|
|
8065
|
+
if (options.signal?.aborted) throw toAbortError("Aborted");
|
|
8063
8066
|
try {
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8067
|
+
return await fn();
|
|
8068
|
+
} catch (err) {
|
|
8069
|
+
lastError = err;
|
|
8070
|
+
if (isAbortError(err)) throw err;
|
|
8071
|
+
if (attempt === cfg.maxRetries) break;
|
|
8072
|
+
if (!isRetryableError2(err)) throw err;
|
|
8073
|
+
const retryAfter = extractRetryAfter(err);
|
|
8074
|
+
const delay = retryAfter ?? calculateBackoffDelay(attempt, cfg);
|
|
8075
|
+
options.onRetry?.(attempt, delay, err);
|
|
8076
|
+
await sleep(delay, options.signal);
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
throw lastError;
|
|
8080
|
+
}
|
|
8081
|
+
function extractRetryAfter(err) {
|
|
8082
|
+
if (err && typeof err === "object" && "retryAfter" in err) {
|
|
8083
|
+
const v = err.retryAfter;
|
|
8084
|
+
if (typeof v === "number" && v > 0) return v * 1e3;
|
|
8085
|
+
}
|
|
8086
|
+
return void 0;
|
|
8087
|
+
}
|
|
8088
|
+
function uuidv7() {
|
|
8089
|
+
const ms = BigInt(Date.now());
|
|
8090
|
+
const bytes = randomBytes(16);
|
|
8091
|
+
bytes[0] = Number(ms >> 40n & 0xffn);
|
|
8092
|
+
bytes[1] = Number(ms >> 32n & 0xffn);
|
|
8093
|
+
bytes[2] = Number(ms >> 24n & 0xffn);
|
|
8094
|
+
bytes[3] = Number(ms >> 16n & 0xffn);
|
|
8095
|
+
bytes[4] = Number(ms >> 8n & 0xffn);
|
|
8096
|
+
bytes[5] = Number(ms & 0xffn);
|
|
8097
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
8098
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
8099
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
8100
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
8101
|
+
}
|
|
8102
|
+
function randomBytes(n) {
|
|
8103
|
+
const out = new Uint8Array(n);
|
|
8104
|
+
const c = globalThis.crypto;
|
|
8105
|
+
if (c?.getRandomValues) {
|
|
8106
|
+
c.getRandomValues(out);
|
|
8107
|
+
return out;
|
|
8108
|
+
}
|
|
8109
|
+
for (let i = 0; i < n; i++) out[i] = Math.floor(Math.random() * 256);
|
|
8110
|
+
return out;
|
|
8111
|
+
}
|
|
8112
|
+
|
|
8113
|
+
// src/storage.ts
|
|
8114
|
+
var PATHS = {
|
|
8115
|
+
uploads: "/storage/uploads",
|
|
8116
|
+
upload: (id) => `/storage/uploads/${encodeURIComponent(String(id))}`,
|
|
8117
|
+
uploadComplete: (id) => `/storage/uploads/${encodeURIComponent(String(id))}:complete`,
|
|
8118
|
+
uploadPart: (id, n) => `/storage/uploads/${encodeURIComponent(String(id))}/parts/${n}`,
|
|
8119
|
+
files: "/storage/files",
|
|
8120
|
+
file: (id) => `/storage/files/${encodeURIComponent(String(id))}`,
|
|
8121
|
+
fileRestore: (id) => `/storage/files/${encodeURIComponent(String(id))}:restore`,
|
|
8122
|
+
fileSignedUrl: (id) => `/storage/files/${encodeURIComponent(String(id))}:signedUrl`,
|
|
8123
|
+
fileCopy: (id) => `/storage/files/${encodeURIComponent(String(id))}:copy`,
|
|
8124
|
+
versions: (id) => `/storage/files/${encodeURIComponent(String(id))}/versions`,
|
|
8125
|
+
versionRestore: (id, vid) => `/storage/files/${encodeURIComponent(String(id))}/versions/${encodeURIComponent(String(vid))}:restore`
|
|
8126
|
+
};
|
|
8127
|
+
var isBrowser = () => typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
8128
|
+
var hasXhr = () => typeof globalThis.XMLHttpRequest !== "undefined";
|
|
8129
|
+
var hasLocalStorage = () => {
|
|
8130
|
+
try {
|
|
8131
|
+
const ls = globalThis.localStorage;
|
|
8132
|
+
return Boolean(ls && typeof ls.getItem === "function");
|
|
8133
|
+
} catch {
|
|
8134
|
+
return false;
|
|
8135
|
+
}
|
|
8136
|
+
};
|
|
8137
|
+
async function computeSha256Hex(blob) {
|
|
8138
|
+
const subtle = globalThis.crypto?.subtle;
|
|
8139
|
+
if (subtle?.digest) {
|
|
8140
|
+
const buf2 = await blob.arrayBuffer();
|
|
8141
|
+
const digest2 = await subtle.digest("SHA-256", buf2);
|
|
8142
|
+
return bytesToHex(new Uint8Array(digest2));
|
|
8143
|
+
}
|
|
8144
|
+
const nodeCrypto = await import("crypto");
|
|
8145
|
+
const hash = nodeCrypto.createHash("sha256");
|
|
8146
|
+
const buf = Buffer.from(await blob.arrayBuffer());
|
|
8147
|
+
hash.update(buf);
|
|
8148
|
+
return hash.digest("hex");
|
|
8149
|
+
}
|
|
8150
|
+
function bytesToHex(b) {
|
|
8151
|
+
let s = "";
|
|
8152
|
+
for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
|
|
8153
|
+
return s;
|
|
8154
|
+
}
|
|
8155
|
+
var RESUME_KEY_PREFIX = "sylphx_upload_";
|
|
8156
|
+
function resumeKey(uploadId) {
|
|
8157
|
+
return `${RESUME_KEY_PREFIX}${uploadId}`;
|
|
8158
|
+
}
|
|
8159
|
+
function persistResume(rec) {
|
|
8160
|
+
if (hasLocalStorage()) {
|
|
8161
|
+
try {
|
|
8162
|
+
localStorage.setItem(resumeKey(rec.uploadId), JSON.stringify(rec));
|
|
8163
|
+
} catch {
|
|
8102
8164
|
}
|
|
8165
|
+
return;
|
|
8103
8166
|
}
|
|
8104
|
-
if (!
|
|
8105
|
-
|
|
8106
|
-
code: "BAD_REQUEST"
|
|
8107
|
-
});
|
|
8167
|
+
if (!isBrowser()) {
|
|
8168
|
+
void persistResumeNode(rec);
|
|
8108
8169
|
}
|
|
8109
|
-
const { uploadUrl, publicUrl } = await tokenResponse.json();
|
|
8110
|
-
return executeUploadWithRetry(file, uploadUrl, publicUrl, options);
|
|
8111
8170
|
}
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
let lastError = null;
|
|
8115
|
-
for (let attempt = 0; attempt <= UPLOAD_RETRY_CONFIG.maxRetries; attempt++) {
|
|
8171
|
+
function clearResume(uploadId) {
|
|
8172
|
+
if (hasLocalStorage()) {
|
|
8116
8173
|
try {
|
|
8117
|
-
|
|
8118
|
-
} catch
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8174
|
+
localStorage.removeItem(resumeKey(uploadId));
|
|
8175
|
+
} catch {
|
|
8176
|
+
}
|
|
8177
|
+
return;
|
|
8178
|
+
}
|
|
8179
|
+
if (!isBrowser()) void clearResumeNode(uploadId);
|
|
8180
|
+
}
|
|
8181
|
+
async function persistResumeNode(rec) {
|
|
8182
|
+
try {
|
|
8183
|
+
const fs = await import("fs/promises");
|
|
8184
|
+
const path = await import("path");
|
|
8185
|
+
const os = await import("os");
|
|
8186
|
+
const dir = path.join(os.homedir(), ".sylphx");
|
|
8187
|
+
await fs.mkdir(dir, { recursive: true });
|
|
8188
|
+
const file = path.join(dir, "uploads.json");
|
|
8189
|
+
let store = {};
|
|
8190
|
+
try {
|
|
8191
|
+
const text = await fs.readFile(file, "utf8");
|
|
8192
|
+
store = JSON.parse(text);
|
|
8193
|
+
} catch {
|
|
8194
|
+
store = {};
|
|
8129
8195
|
}
|
|
8196
|
+
store[rec.uploadId] = rec;
|
|
8197
|
+
await fs.writeFile(file, JSON.stringify(store), "utf8");
|
|
8198
|
+
} catch {
|
|
8199
|
+
}
|
|
8200
|
+
}
|
|
8201
|
+
async function clearResumeNode(uploadId) {
|
|
8202
|
+
try {
|
|
8203
|
+
const fs = await import("fs/promises");
|
|
8204
|
+
const path = await import("path");
|
|
8205
|
+
const os = await import("os");
|
|
8206
|
+
const file = path.join(os.homedir(), ".sylphx", "uploads.json");
|
|
8207
|
+
const text = await fs.readFile(file, "utf8");
|
|
8208
|
+
const store = JSON.parse(text);
|
|
8209
|
+
delete store[uploadId];
|
|
8210
|
+
await fs.writeFile(file, JSON.stringify(store), "utf8");
|
|
8211
|
+
} catch {
|
|
8130
8212
|
}
|
|
8131
|
-
throw lastError ?? new Error("Upload failed after retries");
|
|
8132
8213
|
}
|
|
8133
|
-
function
|
|
8134
|
-
|
|
8214
|
+
function putBlob(url, body, headers, signal, onProgress) {
|
|
8215
|
+
if (hasXhr()) return putBlobXhr(url, body, headers, signal, onProgress);
|
|
8216
|
+
return putBlobFetch(url, body, headers, signal, onProgress);
|
|
8217
|
+
}
|
|
8218
|
+
function putBlobXhr(url, body, headers, signal, onProgress) {
|
|
8135
8219
|
return new Promise((resolve, reject) => {
|
|
8136
8220
|
const xhr = new XMLHttpRequest();
|
|
8137
8221
|
const handleAbort = () => {
|
|
8138
|
-
|
|
8139
|
-
|
|
8222
|
+
try {
|
|
8223
|
+
xhr.abort();
|
|
8224
|
+
} catch {
|
|
8225
|
+
}
|
|
8226
|
+
reject(toAbortError("Upload aborted"));
|
|
8140
8227
|
};
|
|
8141
8228
|
if (signal?.aborted) {
|
|
8142
|
-
reject(
|
|
8229
|
+
reject(toAbortError("Upload aborted"));
|
|
8143
8230
|
return;
|
|
8144
8231
|
}
|
|
8145
8232
|
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
onProgress(
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
progress: Math.round(event.loaded / event.total * 100)
|
|
8152
|
-
});
|
|
8153
|
-
}
|
|
8154
|
-
});
|
|
8233
|
+
if (onProgress) {
|
|
8234
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
8235
|
+
if (e.lengthComputable) onProgress(e.loaded);
|
|
8236
|
+
});
|
|
8237
|
+
}
|
|
8155
8238
|
xhr.addEventListener("load", () => {
|
|
8156
8239
|
signal?.removeEventListener("abort", handleAbort);
|
|
8157
8240
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
pathname: options?.path ? `${options.path}/${file.name}` : file.name,
|
|
8161
|
-
contentType: file.type,
|
|
8162
|
-
size: file.size
|
|
8163
|
-
});
|
|
8241
|
+
const etag = resolveXhrEtag(xhr);
|
|
8242
|
+
resolve({ etag: stripQuotes(etag), status: xhr.status });
|
|
8164
8243
|
} else {
|
|
8165
|
-
const
|
|
8166
|
-
|
|
8167
|
-
reject(
|
|
8244
|
+
const err = new Error(`PUT failed with status ${xhr.status}`);
|
|
8245
|
+
err.status = xhr.status;
|
|
8246
|
+
reject(err);
|
|
8168
8247
|
}
|
|
8169
8248
|
});
|
|
8170
8249
|
xhr.addEventListener("error", () => {
|
|
@@ -8173,93 +8252,291 @@ function executeUpload(file, uploadUrl, publicUrl, options) {
|
|
|
8173
8252
|
});
|
|
8174
8253
|
xhr.addEventListener("abort", () => {
|
|
8175
8254
|
signal?.removeEventListener("abort", handleAbort);
|
|
8176
|
-
reject(
|
|
8255
|
+
reject(toAbortError("Upload aborted"));
|
|
8177
8256
|
});
|
|
8178
|
-
xhr.open("PUT",
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
}
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
type: "avatar",
|
|
8187
|
-
userId
|
|
8257
|
+
xhr.open("PUT", url);
|
|
8258
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
8259
|
+
try {
|
|
8260
|
+
xhr.setRequestHeader(k, v);
|
|
8261
|
+
} catch {
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
8264
|
+
xhr.send(body);
|
|
8188
8265
|
});
|
|
8189
8266
|
}
|
|
8190
|
-
|
|
8191
|
-
const
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
endpoint.path.replace(":id", encodeURIComponent(fileId)),
|
|
8195
|
-
{ method: endpoint.method }
|
|
8196
|
-
);
|
|
8197
|
-
}
|
|
8198
|
-
async function getFileUrl(config, fileId) {
|
|
8199
|
-
const data = await callApi(config, `/storage/files/${fileId}`, { method: "GET" });
|
|
8200
|
-
return data.url;
|
|
8267
|
+
function resolveXhrEtag(xhr) {
|
|
8268
|
+
const canonical = xhr.getResponseHeader("ETag");
|
|
8269
|
+
if (canonical !== null) return canonical;
|
|
8270
|
+
return xhr.getResponseHeader("etag") ?? "";
|
|
8201
8271
|
}
|
|
8202
|
-
async function
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8272
|
+
async function putBlobFetch(url, body, headers, signal, onProgress) {
|
|
8273
|
+
let stream = body;
|
|
8274
|
+
if (onProgress && typeof TransformStream !== "undefined" && typeof body.stream === "function") {
|
|
8275
|
+
let loaded = 0;
|
|
8276
|
+
const counter = new TransformStream({
|
|
8277
|
+
transform(chunk, controller) {
|
|
8278
|
+
loaded += chunk.byteLength;
|
|
8279
|
+
onProgress(loaded);
|
|
8280
|
+
controller.enqueue(chunk);
|
|
8281
|
+
}
|
|
8282
|
+
});
|
|
8283
|
+
stream = body.stream().pipeThrough(counter);
|
|
8284
|
+
}
|
|
8285
|
+
const res = await fetch(url, {
|
|
8286
|
+
method: "PUT",
|
|
8287
|
+
body: stream,
|
|
8288
|
+
headers,
|
|
8289
|
+
signal,
|
|
8290
|
+
// Required when streaming a request body in Chromium / undici.
|
|
8291
|
+
// Cast: TS lib lacks `duplex`.
|
|
8292
|
+
...{ duplex: "half" }
|
|
8206
8293
|
});
|
|
8294
|
+
if (!res.ok) {
|
|
8295
|
+
const err = new Error(`PUT failed with status ${res.status}`);
|
|
8296
|
+
err.status = res.status;
|
|
8297
|
+
throw err;
|
|
8298
|
+
}
|
|
8299
|
+
const etag = resolveFetchEtag(res.headers);
|
|
8300
|
+
if (onProgress) onProgress(body.size);
|
|
8301
|
+
return { etag: stripQuotes(etag), status: res.status };
|
|
8207
8302
|
}
|
|
8208
|
-
|
|
8209
|
-
const
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
{ method: "GET" }
|
|
8213
|
-
);
|
|
8214
|
-
return data.versions;
|
|
8303
|
+
function resolveFetchEtag(headers) {
|
|
8304
|
+
const lowerCase = headers.get("etag");
|
|
8305
|
+
if (lowerCase !== null) return lowerCase;
|
|
8306
|
+
return headers.get("ETag") ?? "";
|
|
8215
8307
|
}
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
`/storage/files/${encodeURIComponent(fileId)}/versions/${encodeURIComponent(versionId)}/restore`,
|
|
8220
|
-
{ method: "POST" }
|
|
8221
|
-
);
|
|
8222
|
-
return data.version;
|
|
8308
|
+
function stripQuotes(s) {
|
|
8309
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) return s.slice(1, -1);
|
|
8310
|
+
return s;
|
|
8223
8311
|
}
|
|
8224
|
-
async function
|
|
8225
|
-
|
|
8226
|
-
|
|
8312
|
+
async function putWithRetry(url, body, headers, signal, onProgress) {
|
|
8313
|
+
let lastErr;
|
|
8314
|
+
const maxRetries = 5;
|
|
8315
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
8316
|
+
if (signal?.aborted) throw toAbortError();
|
|
8317
|
+
try {
|
|
8318
|
+
return await putBlob(url, body, headers, signal, onProgress);
|
|
8319
|
+
} catch (err) {
|
|
8320
|
+
if (isAbortError(err)) throw err;
|
|
8321
|
+
lastErr = err;
|
|
8322
|
+
if (attempt === maxRetries || !isRetryableError2(err)) throw err;
|
|
8323
|
+
await sleep(calculateBackoffDelay(attempt), signal);
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
throw lastErr;
|
|
8327
|
+
}
|
|
8328
|
+
async function uploadsCreate(config, blob, options) {
|
|
8329
|
+
const signal = options.signal;
|
|
8330
|
+
if (signal?.aborted) throw toAbortError();
|
|
8331
|
+
const filename = options.filename ?? (isFile(blob) ? blob.name : void 0) ?? "upload.bin";
|
|
8332
|
+
const contentType = options.contentType ?? (blob.type && blob.type.length > 0 ? blob.type : "application/octet-stream");
|
|
8333
|
+
const size = blob.size;
|
|
8334
|
+
const checksumSha256 = options.checksumSha256 ?? await computeSha256Hex(blob);
|
|
8335
|
+
const idempotencyKey = options.idempotencyKey ?? uuidv7();
|
|
8336
|
+
const createBody = {
|
|
8337
|
+
filename,
|
|
8338
|
+
contentType,
|
|
8339
|
+
size,
|
|
8340
|
+
folder: options.folder,
|
|
8341
|
+
visibility: options.visibility,
|
|
8342
|
+
metadata: options.metadata,
|
|
8343
|
+
checksumSha256,
|
|
8344
|
+
ifNoneMatch: options.ifNoneMatch
|
|
8345
|
+
};
|
|
8346
|
+
const session = await callApi(config, PATHS.uploads, {
|
|
8347
|
+
method: "POST",
|
|
8348
|
+
body: createBody,
|
|
8349
|
+
idempotencyKey,
|
|
8350
|
+
signal
|
|
8227
8351
|
});
|
|
8352
|
+
const uploadId = session.uploadId;
|
|
8353
|
+
const onAborted = async () => {
|
|
8354
|
+
try {
|
|
8355
|
+
await callApi(config, PATHS.upload(uploadId), { method: "DELETE" });
|
|
8356
|
+
} catch {
|
|
8357
|
+
}
|
|
8358
|
+
clearResume(String(uploadId));
|
|
8359
|
+
};
|
|
8360
|
+
try {
|
|
8361
|
+
if (session.method === "PUT") {
|
|
8362
|
+
return await runSinglePart(config, blob, session, options, idempotencyKey);
|
|
8363
|
+
}
|
|
8364
|
+
return await runMultipart(config, blob, session, options, idempotencyKey);
|
|
8365
|
+
} catch (err) {
|
|
8366
|
+
if (isAbortError(err)) {
|
|
8367
|
+
await onAborted();
|
|
8368
|
+
}
|
|
8369
|
+
throw err;
|
|
8370
|
+
}
|
|
8228
8371
|
}
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
config,
|
|
8232
|
-
`/storage/files/${encodeURIComponent(fileId)}/restore`,
|
|
8233
|
-
{ method: "POST" }
|
|
8234
|
-
);
|
|
8235
|
-
return data.file;
|
|
8372
|
+
function isFile(b) {
|
|
8373
|
+
return typeof b.name === "string";
|
|
8236
8374
|
}
|
|
8237
|
-
async function
|
|
8238
|
-
const
|
|
8375
|
+
async function runSinglePart(config, blob, session, options, idempotencyKey) {
|
|
8376
|
+
const { onProgress, signal } = options;
|
|
8377
|
+
const total = blob.size;
|
|
8378
|
+
const trackProgress = onProgress ? (loaded) => {
|
|
8379
|
+
onProgress({ loaded, total, partsCompleted: 0, partsTotal: 1 });
|
|
8380
|
+
} : void 0;
|
|
8381
|
+
const put = await putWithRetry(session.url, blob, session.headers, signal, trackProgress);
|
|
8382
|
+
if (onProgress) onProgress({ loaded: total, total, partsCompleted: 1, partsTotal: 1 });
|
|
8383
|
+
const completion = await callApi(
|
|
8239
8384
|
config,
|
|
8240
|
-
|
|
8385
|
+
PATHS.uploadComplete(session.uploadId),
|
|
8241
8386
|
{
|
|
8242
8387
|
method: "POST",
|
|
8243
|
-
body: {
|
|
8388
|
+
body: { parts: [{ partNumber: 1, etag: put.etag }] },
|
|
8389
|
+
idempotencyKey: `${idempotencyKey}:complete`,
|
|
8390
|
+
signal
|
|
8244
8391
|
}
|
|
8245
8392
|
);
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8393
|
+
clearResume(String(session.uploadId));
|
|
8394
|
+
return await fetchFile(config, completion.fileId);
|
|
8395
|
+
}
|
|
8396
|
+
async function runMultipart(config, blob, session, options, idempotencyKey) {
|
|
8397
|
+
const { onProgress, signal } = options;
|
|
8398
|
+
const partSize = session.partSize;
|
|
8399
|
+
const total = blob.size;
|
|
8400
|
+
const partsTotal = session.partCount;
|
|
8401
|
+
const completedParts = [];
|
|
8402
|
+
const partLoaded = /* @__PURE__ */ new Map();
|
|
8403
|
+
const reportProgress = () => {
|
|
8404
|
+
if (!onProgress) return;
|
|
8405
|
+
let loaded = 0;
|
|
8406
|
+
for (const v of partLoaded.values()) loaded += v;
|
|
8407
|
+
onProgress({ loaded, total, partsCompleted: completedParts.length, partsTotal });
|
|
8408
|
+
};
|
|
8409
|
+
for (const part of session.parts) {
|
|
8410
|
+
if (signal?.aborted) throw toAbortError();
|
|
8411
|
+
const offset = (part.partNumber - 1) * partSize;
|
|
8412
|
+
const end = Math.min(offset + partSize, total);
|
|
8413
|
+
const slice = blob.slice(offset, end);
|
|
8414
|
+
const trackProgress = onProgress ? (loaded) => {
|
|
8415
|
+
partLoaded.set(part.partNumber, loaded);
|
|
8416
|
+
reportProgress();
|
|
8417
|
+
} : void 0;
|
|
8418
|
+
const result = await putWithRetry(part.url, slice, {}, signal, trackProgress);
|
|
8419
|
+
completedParts.push({ partNumber: part.partNumber, etag: result.etag });
|
|
8420
|
+
partLoaded.set(part.partNumber, slice.size);
|
|
8421
|
+
reportProgress();
|
|
8422
|
+
persistResume({
|
|
8423
|
+
uploadId: String(session.uploadId),
|
|
8424
|
+
completedParts: [...completedParts],
|
|
8425
|
+
updatedAt: Date.now()
|
|
8250
8426
|
});
|
|
8251
8427
|
}
|
|
8252
|
-
|
|
8428
|
+
const completion = await callApi(
|
|
8429
|
+
config,
|
|
8430
|
+
PATHS.uploadComplete(session.uploadId),
|
|
8431
|
+
{
|
|
8432
|
+
method: "POST",
|
|
8433
|
+
body: { parts: completedParts },
|
|
8434
|
+
idempotencyKey: `${idempotencyKey}:complete`,
|
|
8435
|
+
signal
|
|
8436
|
+
}
|
|
8437
|
+
);
|
|
8438
|
+
clearResume(String(session.uploadId));
|
|
8439
|
+
return await fetchFile(config, completion.fileId);
|
|
8253
8440
|
}
|
|
8254
|
-
async function
|
|
8255
|
-
return callApi(config, "
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8441
|
+
async function fetchFile(config, fileId) {
|
|
8442
|
+
return callApi(config, PATHS.file(fileId), { method: "GET" });
|
|
8443
|
+
}
|
|
8444
|
+
async function uploadsAbort(config, uploadId) {
|
|
8445
|
+
await withRetry(() => callApi(config, PATHS.upload(uploadId), { method: "DELETE" }), {
|
|
8446
|
+
maxRetries: 3
|
|
8447
|
+
});
|
|
8448
|
+
clearResume(String(uploadId));
|
|
8449
|
+
}
|
|
8450
|
+
function filesListPage(config, options, cursor) {
|
|
8451
|
+
const query = {
|
|
8452
|
+
folder: options.folder,
|
|
8453
|
+
cursor: cursor ?? options.cursor,
|
|
8454
|
+
limit: options.limit,
|
|
8455
|
+
includeDeleted: options.includeDeleted
|
|
8456
|
+
};
|
|
8457
|
+
return callApi(config, PATHS.files, {
|
|
8458
|
+
method: "GET",
|
|
8459
|
+
query: {
|
|
8460
|
+
folder: query.folder,
|
|
8461
|
+
cursor: query.cursor,
|
|
8462
|
+
limit: query.limit,
|
|
8463
|
+
includeDeleted: query.includeDeleted
|
|
8260
8464
|
}
|
|
8261
8465
|
});
|
|
8262
8466
|
}
|
|
8467
|
+
function filesList(config, options = {}) {
|
|
8468
|
+
const fetchPage = (cursor) => filesListPage(config, options, cursor);
|
|
8469
|
+
const iter = {
|
|
8470
|
+
[Symbol.asyncIterator]: async function* () {
|
|
8471
|
+
let cursor = options.cursor;
|
|
8472
|
+
do {
|
|
8473
|
+
const page2 = await fetchPage(cursor ?? void 0);
|
|
8474
|
+
for (const f of page2.files) yield f;
|
|
8475
|
+
cursor = page2.nextCursor;
|
|
8476
|
+
} while (cursor);
|
|
8477
|
+
}
|
|
8478
|
+
};
|
|
8479
|
+
return Object.assign(iter, { fetchPage });
|
|
8480
|
+
}
|
|
8481
|
+
async function filesGet(config, fileId) {
|
|
8482
|
+
return callApi(config, PATHS.file(fileId), { method: "GET" });
|
|
8483
|
+
}
|
|
8484
|
+
async function filesDelete(config, fileId) {
|
|
8485
|
+
return callApi(config, PATHS.file(fileId), { method: "DELETE" });
|
|
8486
|
+
}
|
|
8487
|
+
async function filesRestore(config, fileId) {
|
|
8488
|
+
return callApi(config, PATHS.fileRestore(fileId), { method: "POST" });
|
|
8489
|
+
}
|
|
8490
|
+
async function filesSignedUrl(config, fileId, options = {}) {
|
|
8491
|
+
const body = {
|
|
8492
|
+
expiresIn: options.expiresIn,
|
|
8493
|
+
disposition: options.disposition,
|
|
8494
|
+
userId: options.userId
|
|
8495
|
+
};
|
|
8496
|
+
return callApi(config, PATHS.fileSignedUrl(fileId), {
|
|
8497
|
+
method: "POST",
|
|
8498
|
+
body
|
|
8499
|
+
});
|
|
8500
|
+
}
|
|
8501
|
+
async function filesCopy(config, fileId, options) {
|
|
8502
|
+
const body = {
|
|
8503
|
+
folder: options.folder,
|
|
8504
|
+
filename: options.filename,
|
|
8505
|
+
visibility: options.visibility,
|
|
8506
|
+
metadata: options.metadata
|
|
8507
|
+
};
|
|
8508
|
+
return callApi(config, PATHS.fileCopy(fileId), {
|
|
8509
|
+
method: "POST",
|
|
8510
|
+
body
|
|
8511
|
+
});
|
|
8512
|
+
}
|
|
8513
|
+
async function filesVersionsList(config, fileId) {
|
|
8514
|
+
const data = await callApi(config, PATHS.versions(fileId), {
|
|
8515
|
+
method: "GET"
|
|
8516
|
+
});
|
|
8517
|
+
return data.versions;
|
|
8518
|
+
}
|
|
8519
|
+
async function filesVersionsRestore(config, fileId, versionId) {
|
|
8520
|
+
return callApi(config, PATHS.versionRestore(fileId, versionId), { method: "POST" });
|
|
8521
|
+
}
|
|
8522
|
+
var storage = {
|
|
8523
|
+
uploads: {
|
|
8524
|
+
create: uploadsCreate,
|
|
8525
|
+
abort: uploadsAbort
|
|
8526
|
+
},
|
|
8527
|
+
files: {
|
|
8528
|
+
list: filesList,
|
|
8529
|
+
get: filesGet,
|
|
8530
|
+
delete: filesDelete,
|
|
8531
|
+
restore: filesRestore,
|
|
8532
|
+
signedUrl: filesSignedUrl,
|
|
8533
|
+
copy: filesCopy,
|
|
8534
|
+
versions: {
|
|
8535
|
+
list: filesVersionsList,
|
|
8536
|
+
restore: filesVersionsRestore
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
};
|
|
8263
8540
|
|
|
8264
8541
|
// src/lib/notifications/service-worker.ts
|
|
8265
8542
|
function initPushServiceWorker(config = {}) {
|
|
@@ -10706,7 +10983,6 @@ export {
|
|
|
10706
10983
|
deleteCron,
|
|
10707
10984
|
deleteDocument,
|
|
10708
10985
|
deleteEnvVar,
|
|
10709
|
-
deleteFile,
|
|
10710
10986
|
deleteOrganization,
|
|
10711
10987
|
deletePasskey,
|
|
10712
10988
|
deletePermission,
|
|
@@ -10718,7 +10994,6 @@ export {
|
|
|
10718
10994
|
disableDebug,
|
|
10719
10995
|
disableTwoFactor,
|
|
10720
10996
|
disconnectOAuthProvider,
|
|
10721
|
-
downloadFileVersion,
|
|
10722
10997
|
dpop,
|
|
10723
10998
|
embed,
|
|
10724
10999
|
enableDebug,
|
|
@@ -10751,8 +11026,6 @@ export {
|
|
|
10751
11026
|
getErrorCode,
|
|
10752
11027
|
getErrorMessage,
|
|
10753
11028
|
getFacets,
|
|
10754
|
-
getFileInfo,
|
|
10755
|
-
getFileUrl,
|
|
10756
11029
|
getFlagPayload,
|
|
10757
11030
|
getFlags,
|
|
10758
11031
|
getLeaderboard,
|
|
@@ -10779,7 +11052,6 @@ export {
|
|
|
10779
11052
|
getSecrets,
|
|
10780
11053
|
getSecurityScore,
|
|
10781
11054
|
getSession,
|
|
10782
|
-
getSignedUrl,
|
|
10783
11055
|
getStreak,
|
|
10784
11056
|
getSubscription,
|
|
10785
11057
|
getTask,
|
|
@@ -10837,7 +11109,6 @@ export {
|
|
|
10837
11109
|
leaveOrganization,
|
|
10838
11110
|
linkAnonymousConsents,
|
|
10839
11111
|
listEnvVars,
|
|
10840
|
-
listFileVersions,
|
|
10841
11112
|
listOAuthProviders,
|
|
10842
11113
|
listPasskeys,
|
|
10843
11114
|
listPermissions,
|
|
@@ -10885,8 +11156,6 @@ export {
|
|
|
10885
11156
|
resetPassword,
|
|
10886
11157
|
resetPlatformCookieCache,
|
|
10887
11158
|
resetPlatformJwksCache,
|
|
10888
|
-
restoreFile,
|
|
10889
|
-
restoreFileVersion,
|
|
10890
11159
|
resumeCron,
|
|
10891
11160
|
revokeAllTokens,
|
|
10892
11161
|
revokeOrganizationInvitation,
|
|
@@ -10908,8 +11177,8 @@ export {
|
|
|
10908
11177
|
signIn,
|
|
10909
11178
|
signOut,
|
|
10910
11179
|
signUp,
|
|
10911
|
-
softDeleteFile,
|
|
10912
11180
|
startPasskeyRegistration,
|
|
11181
|
+
storage,
|
|
10913
11182
|
streamToString,
|
|
10914
11183
|
submitScore,
|
|
10915
11184
|
suspendUser,
|
|
@@ -10930,8 +11199,6 @@ export {
|
|
|
10930
11199
|
updateUserMetadata,
|
|
10931
11200
|
updateUserProfile,
|
|
10932
11201
|
updateWebhookConfig,
|
|
10933
|
-
uploadAvatar,
|
|
10934
|
-
uploadFile,
|
|
10935
11202
|
upsertDocument,
|
|
10936
11203
|
user,
|
|
10937
11204
|
userInfo,
|