@portabyte/node 0.0.1
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/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/index.cjs +462 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +194 -0
- package/dist/index.d.ts +194 -0
- package/dist/index.js +433 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var PortabyteError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
requestId;
|
|
6
|
+
constructor(message, status, code, requestId) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "PortabyteError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.requestId = requestId;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/client.ts
|
|
16
|
+
var SDK_HEADER_NAME = "X-Portabyte-SDK";
|
|
17
|
+
var RetryableError = class extends Error {
|
|
18
|
+
constructor(retryAfterMs, fallback) {
|
|
19
|
+
super("retryable request failure");
|
|
20
|
+
this.retryAfterMs = retryAfterMs;
|
|
21
|
+
this.fallback = fallback;
|
|
22
|
+
}
|
|
23
|
+
retryAfterMs;
|
|
24
|
+
fallback;
|
|
25
|
+
};
|
|
26
|
+
var HttpClient = class {
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
options;
|
|
31
|
+
async request(options) {
|
|
32
|
+
const idempotent = options.method === "GET";
|
|
33
|
+
return this.run(idempotent, async () => {
|
|
34
|
+
const response = await this.send(this.options.baseUrl + options.path, {
|
|
35
|
+
method: options.method,
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${this.options.apiKey}`,
|
|
38
|
+
[SDK_HEADER_NAME]: this.options.sdkHeaderValue,
|
|
39
|
+
...options.body !== void 0 || options.method !== "GET" ? { "Content-Type": "application/json" } : {}
|
|
40
|
+
},
|
|
41
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw await responseError(response, idempotent);
|
|
45
|
+
}
|
|
46
|
+
if (response.status === 204) {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
return await response.json();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// The gateway's CORS policy allows only Content-Type; the signed URL is
|
|
53
|
+
// the authorization, and the runtime sets the exact-match Content-Length.
|
|
54
|
+
async putBytes(uploadUrl, contentType, data) {
|
|
55
|
+
await this.run(true, async () => {
|
|
56
|
+
const response = await this.send(uploadUrl, {
|
|
57
|
+
method: "PUT",
|
|
58
|
+
headers: { "Content-Type": contentType },
|
|
59
|
+
body: data
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw await responseError(response, true);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async putBytesJSON(uploadUrl, contentType, data) {
|
|
67
|
+
return this.run(true, async () => {
|
|
68
|
+
const response = await this.send(uploadUrl, {
|
|
69
|
+
method: "PUT",
|
|
70
|
+
headers: { "Content-Type": contentType },
|
|
71
|
+
body: data
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw await responseError(response, true);
|
|
75
|
+
}
|
|
76
|
+
return await response.json();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async uploadJSON(uploadUrl, method, body, idempotent) {
|
|
80
|
+
return this.run(idempotent, async () => {
|
|
81
|
+
const response = await this.send(uploadUrl, {
|
|
82
|
+
method,
|
|
83
|
+
headers: { "Content-Type": "application/json" },
|
|
84
|
+
body: JSON.stringify(body)
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw await responseError(response, idempotent);
|
|
88
|
+
}
|
|
89
|
+
if (response.status === 204) {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
return await response.json();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async send(url, init) {
|
|
96
|
+
try {
|
|
97
|
+
return await this.options.fetchImpl(url, {
|
|
98
|
+
...init,
|
|
99
|
+
signal: this.options.timeoutMs > 0 ? AbortSignal.timeout(this.options.timeoutMs) : void 0
|
|
100
|
+
});
|
|
101
|
+
} catch (cause) {
|
|
102
|
+
throw new RetryableError(
|
|
103
|
+
null,
|
|
104
|
+
new PortabyteError(
|
|
105
|
+
cause instanceof Error ? cause.message : "Network request failed.",
|
|
106
|
+
0,
|
|
107
|
+
"network_error"
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async run(idempotent, send) {
|
|
113
|
+
for (let attempt = 0; ; attempt++) {
|
|
114
|
+
try {
|
|
115
|
+
return await send();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const retry = error instanceof RetryableError;
|
|
118
|
+
if (!retry || !idempotent || attempt >= this.options.maxRetries) {
|
|
119
|
+
throw retry ? error.fallback : error;
|
|
120
|
+
}
|
|
121
|
+
await sleep(error.retryAfterMs ?? backoffMs(attempt));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
async function responseError(response, idempotent) {
|
|
127
|
+
const error = await errorFromResponse(response);
|
|
128
|
+
if (idempotent && (response.status === 429 || response.status >= 500)) {
|
|
129
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
130
|
+
const retryAfterMs = Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1e3 : null;
|
|
131
|
+
return new RetryableError(retryAfterMs, error);
|
|
132
|
+
}
|
|
133
|
+
return error;
|
|
134
|
+
}
|
|
135
|
+
function backoffMs(attempt) {
|
|
136
|
+
const baseMs = 500;
|
|
137
|
+
const capMs = 8e3;
|
|
138
|
+
return Math.floor(Math.random() * Math.min(capMs, baseMs * 2 ** attempt));
|
|
139
|
+
}
|
|
140
|
+
function sleep(ms) {
|
|
141
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
142
|
+
}
|
|
143
|
+
async function errorFromResponse(response) {
|
|
144
|
+
let code = "request_failed";
|
|
145
|
+
let message = `Request failed with status ${response.status}.`;
|
|
146
|
+
let requestId;
|
|
147
|
+
try {
|
|
148
|
+
const body = await response.json();
|
|
149
|
+
code = body.code ?? code;
|
|
150
|
+
message = body.message ?? message;
|
|
151
|
+
requestId = body.requestId;
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
return new PortabyteError(message, response.status, code, requestId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/assets.ts
|
|
158
|
+
var FilesAPI = class {
|
|
159
|
+
constructor(http) {
|
|
160
|
+
this.http = http;
|
|
161
|
+
}
|
|
162
|
+
http;
|
|
163
|
+
/** Creates a signed upload session. Prefer {@link upload}, which runs every step. */
|
|
164
|
+
async create(input) {
|
|
165
|
+
const body = {
|
|
166
|
+
name: input.name,
|
|
167
|
+
contentType: input.contentType,
|
|
168
|
+
sizeBytes: input.sizeBytes,
|
|
169
|
+
...input.path !== void 0 && { path: input.path },
|
|
170
|
+
...input.visibility !== void 0 && { visibility: input.visibility },
|
|
171
|
+
...input.corsOrigin !== void 0 && { corsOrigin: input.corsOrigin }
|
|
172
|
+
};
|
|
173
|
+
return this.http.request({
|
|
174
|
+
method: "POST",
|
|
175
|
+
path: this.path("assets"),
|
|
176
|
+
body
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Prepares a direct browser upload. Call this only from your trusted server,
|
|
181
|
+
* then return the result to the browser. The browser uploads bytes directly
|
|
182
|
+
* to uploadUrl; call {@link confirm} from your server once it reports success.
|
|
183
|
+
*/
|
|
184
|
+
async prepareBrowserUpload(input) {
|
|
185
|
+
const session = await this.create(input);
|
|
186
|
+
return {
|
|
187
|
+
assetId: session.id,
|
|
188
|
+
uploadUrl: session.uploadUrl,
|
|
189
|
+
uploadExpiresAt: session.uploadExpiresAt,
|
|
190
|
+
uploadMode: session.uploadMode,
|
|
191
|
+
...session.partSize !== void 0 && { partSize: session.partSize },
|
|
192
|
+
...session.maxConcurrency !== void 0 && {
|
|
193
|
+
maxConcurrency: session.maxConcurrency
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Confirms a completed direct upload and returns its live asset record.
|
|
199
|
+
* Call this from your trusted server, never from a browser.
|
|
200
|
+
*/
|
|
201
|
+
async confirm(assetID) {
|
|
202
|
+
return this.http.request({
|
|
203
|
+
method: "POST",
|
|
204
|
+
path: this.path(`assets/${assetID}/uploaded`)
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Uploads a File, Blob, or bytes end to end. */
|
|
208
|
+
async upload(request) {
|
|
209
|
+
const described = describeUpload(request);
|
|
210
|
+
const { file, multipart } = request;
|
|
211
|
+
const createOptions = {
|
|
212
|
+
...request.path !== void 0 && { path: request.path },
|
|
213
|
+
...request.visibility !== void 0 && { visibility: request.visibility },
|
|
214
|
+
...request.corsOrigin !== void 0 && { corsOrigin: request.corsOrigin }
|
|
215
|
+
};
|
|
216
|
+
const session = await this.create({ ...described, ...createOptions });
|
|
217
|
+
await this.transfer(
|
|
218
|
+
session,
|
|
219
|
+
file,
|
|
220
|
+
described.contentType,
|
|
221
|
+
multipart
|
|
222
|
+
);
|
|
223
|
+
try {
|
|
224
|
+
return await this.confirm(session.id);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (error instanceof PortabyteError && error.status !== 0) {
|
|
227
|
+
await this.remove(session.id).catch(() => void 0);
|
|
228
|
+
}
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Continues a previously-created session. Persist the create session and
|
|
234
|
+
* MultipartUploadState after each part to resume after an interruption.
|
|
235
|
+
*/
|
|
236
|
+
async resume(session, request) {
|
|
237
|
+
const contentType = request.contentType ?? (request.file instanceof Blob ? request.file.type : "");
|
|
238
|
+
if (fileSize(request.file) !== session.sizeBytes || contentType !== session.contentType) {
|
|
239
|
+
throw new PortabyteError(
|
|
240
|
+
"The selected file does not match this upload session.",
|
|
241
|
+
0,
|
|
242
|
+
"invalid_argument"
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
await this.transfer(session, request.file, contentType, request);
|
|
246
|
+
return this.confirm(session.id);
|
|
247
|
+
}
|
|
248
|
+
async list(options = {}) {
|
|
249
|
+
const params = new URLSearchParams();
|
|
250
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
251
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
252
|
+
const query = params.size > 0 ? `?${params}` : "";
|
|
253
|
+
return this.http.request({
|
|
254
|
+
method: "GET",
|
|
255
|
+
path: this.path(`assets${query}`)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async get(assetID) {
|
|
259
|
+
return this.http.request({
|
|
260
|
+
method: "GET",
|
|
261
|
+
path: this.path(`assets/${assetID}`)
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async remove(assetID) {
|
|
265
|
+
await this.http.request({
|
|
266
|
+
method: "DELETE",
|
|
267
|
+
path: this.path(`assets/${assetID}`)
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Cancels a multipart transfer and removes its pending asset. The gateway
|
|
272
|
+
* abort is best-effort: removing the pending asset guarantees the object
|
|
273
|
+
* can never be confirmed or delivered, even if its signed URL has expired.
|
|
274
|
+
*/
|
|
275
|
+
async cancel(session, state) {
|
|
276
|
+
if (session.uploadMode === "multipart" && state?.uploadId) {
|
|
277
|
+
await this.http.uploadJSON(
|
|
278
|
+
`${session.uploadUrl}/multipart/${state.uploadId}`,
|
|
279
|
+
"DELETE",
|
|
280
|
+
void 0,
|
|
281
|
+
false
|
|
282
|
+
).catch(() => void 0);
|
|
283
|
+
}
|
|
284
|
+
await this.remove(session.id);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Returns the URL the asset is served from: stable and cacheable for
|
|
288
|
+
* public assets, short-lived signed otherwise.
|
|
289
|
+
*/
|
|
290
|
+
async url(assetID) {
|
|
291
|
+
return this.http.request({
|
|
292
|
+
method: "GET",
|
|
293
|
+
path: this.path(`assets/${assetID}/url`)
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
path(suffix) {
|
|
297
|
+
return `/v1/${suffix}`;
|
|
298
|
+
}
|
|
299
|
+
async transfer(session, body, contentType, options) {
|
|
300
|
+
if (session.uploadMode === "single") {
|
|
301
|
+
await this.http.putBytes(session.uploadUrl, contentType, body);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await this.uploadMultipart(session, body, contentType, options);
|
|
305
|
+
}
|
|
306
|
+
async uploadMultipart(session, body, contentType, options = {}) {
|
|
307
|
+
if (!session.partSize || session.partSize < 5 * 1024 * 1024) {
|
|
308
|
+
throw new PortabyteError(
|
|
309
|
+
"Multipart upload session is missing a valid part size.",
|
|
310
|
+
0,
|
|
311
|
+
"invalid_upload"
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const state = {
|
|
315
|
+
uploadId: options.state?.uploadId,
|
|
316
|
+
parts: [...options.state?.parts ?? []]
|
|
317
|
+
};
|
|
318
|
+
if (!state.uploadId) {
|
|
319
|
+
const started = await this.http.uploadJSON(
|
|
320
|
+
`${session.uploadUrl}/multipart`,
|
|
321
|
+
"POST",
|
|
322
|
+
{},
|
|
323
|
+
false
|
|
324
|
+
);
|
|
325
|
+
state.uploadId = started.uploadId;
|
|
326
|
+
await options.onStateChange?.({ ...state, parts: [...state.parts] });
|
|
327
|
+
}
|
|
328
|
+
const partCount = Math.ceil(session.sizeBytes / session.partSize);
|
|
329
|
+
const completed = new Map(
|
|
330
|
+
state.parts.map((part) => [part.partNumber, part])
|
|
331
|
+
);
|
|
332
|
+
const concurrency = Math.max(
|
|
333
|
+
1,
|
|
334
|
+
Math.min(options.concurrency ?? session.maxConcurrency ?? 3, 3)
|
|
335
|
+
);
|
|
336
|
+
let nextPart = 1;
|
|
337
|
+
const uploadNext = async () => {
|
|
338
|
+
for (; ; ) {
|
|
339
|
+
const partNumber = nextPart;
|
|
340
|
+
nextPart += 1;
|
|
341
|
+
if (partNumber > partCount) return;
|
|
342
|
+
if (completed.has(partNumber)) continue;
|
|
343
|
+
const start = (partNumber - 1) * session.partSize;
|
|
344
|
+
const end = Math.min(start + session.partSize, session.sizeBytes);
|
|
345
|
+
const part = await this.http.putBytesJSON(
|
|
346
|
+
`${session.uploadUrl}/multipart/${state.uploadId}/parts/${partNumber}`,
|
|
347
|
+
contentType,
|
|
348
|
+
sliceBody(body, start, end)
|
|
349
|
+
);
|
|
350
|
+
completed.set(part.partNumber, part);
|
|
351
|
+
state.parts = [...completed.values()].sort(
|
|
352
|
+
(left, right) => left.partNumber - right.partNumber
|
|
353
|
+
);
|
|
354
|
+
await options.onStateChange?.({ ...state, parts: [...state.parts] });
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
await Promise.all(
|
|
358
|
+
Array.from({ length: Math.min(concurrency, partCount) }, uploadNext)
|
|
359
|
+
);
|
|
360
|
+
await this.http.uploadJSON(
|
|
361
|
+
`${session.uploadUrl}/multipart/${state.uploadId}/complete`,
|
|
362
|
+
"POST",
|
|
363
|
+
{ parts: state.parts },
|
|
364
|
+
true
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
function describeUpload(request) {
|
|
369
|
+
const filename = request.name ?? fileName(request.file);
|
|
370
|
+
const mimeType = request.contentType ?? (request.file instanceof Blob ? request.file.type : "");
|
|
371
|
+
if (!filename) {
|
|
372
|
+
throw new PortabyteError(
|
|
373
|
+
"A file name is required when uploading a Blob or bytes.",
|
|
374
|
+
0,
|
|
375
|
+
"invalid_argument"
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
if (!mimeType) {
|
|
379
|
+
throw new PortabyteError(
|
|
380
|
+
"A content type is required when uploading bytes or a Blob without a type.",
|
|
381
|
+
0,
|
|
382
|
+
"invalid_argument"
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
name: filename,
|
|
387
|
+
contentType: mimeType,
|
|
388
|
+
sizeBytes: request.file instanceof Blob ? request.file.size : request.file.byteLength
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function fileName(file) {
|
|
392
|
+
if (!(file instanceof Blob)) return void 0;
|
|
393
|
+
const name = file.name;
|
|
394
|
+
return typeof name === "string" && name.length > 0 ? name : void 0;
|
|
395
|
+
}
|
|
396
|
+
function fileSize(file) {
|
|
397
|
+
return file instanceof Blob ? file.size : file.byteLength;
|
|
398
|
+
}
|
|
399
|
+
function sliceBody(body, start, end) {
|
|
400
|
+
return body instanceof Blob ? body.slice(start, end) : body.slice(start, end);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/index.ts
|
|
404
|
+
var VERSION = "0.0.1";
|
|
405
|
+
var DEFAULT_BASE_URL = "https://api.portabyte.dev";
|
|
406
|
+
var Portabyte = class {
|
|
407
|
+
/** Preferred API for application file uploads. */
|
|
408
|
+
files;
|
|
409
|
+
constructor(options) {
|
|
410
|
+
if (!options.apiKey.startsWith("pbt_sk_live_")) {
|
|
411
|
+
throw new PortabyteError(
|
|
412
|
+
'apiKey must be a server API key starting with "pbt_sk_live_".',
|
|
413
|
+
0,
|
|
414
|
+
"invalid_argument"
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const http = new HttpClient({
|
|
418
|
+
baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""),
|
|
419
|
+
apiKey: options.apiKey,
|
|
420
|
+
fetchImpl: options.fetch ?? fetch,
|
|
421
|
+
maxRetries: options.maxRetries ?? 2,
|
|
422
|
+
timeoutMs: options.timeoutMs ?? 3e4,
|
|
423
|
+
sdkHeaderValue: `typescript/${VERSION}`
|
|
424
|
+
});
|
|
425
|
+
this.files = new FilesAPI(http);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
export {
|
|
429
|
+
Portabyte,
|
|
430
|
+
PortabyteError,
|
|
431
|
+
VERSION
|
|
432
|
+
};
|
|
433
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/assets.ts","../src/index.ts"],"sourcesContent":["/**\n * Thrown for any failed Portabyte API request. `status` is `0` when the\n * request never reached the API.\n */\nexport class PortabyteError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n code: string,\n requestId?: string,\n ) {\n super(message);\n this.name = 'PortabyteError';\n this.status = status;\n this.code = code;\n this.requestId = requestId;\n }\n}\n","import { PortabyteError } from './errors';\n\nexport interface RequestOptions {\n method: string;\n path: string;\n body?: unknown;\n}\n\nexport const SDK_HEADER_NAME = 'X-Portabyte-SDK';\n\nexport interface HttpClientOptions {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n maxRetries: number;\n timeoutMs: number;\n sdkHeaderValue: string;\n}\n\nclass RetryableError extends Error {\n constructor(\n readonly retryAfterMs: number | null,\n readonly fallback: PortabyteError,\n ) {\n super('retryable request failure');\n }\n}\n\nexport class HttpClient {\n constructor(private readonly options: HttpClientOptions) {}\n\n async request<T>(options: RequestOptions): Promise<T> {\n const idempotent = options.method === 'GET';\n return this.run(idempotent, async () => {\n const response = await this.send(this.options.baseUrl + options.path, {\n method: options.method,\n headers: {\n Authorization: `Bearer ${this.options.apiKey}`,\n [SDK_HEADER_NAME]: this.options.sdkHeaderValue,\n ...(options.body !== undefined || options.method !== 'GET'\n ? { 'Content-Type': 'application/json' }\n : {}),\n },\n body:\n options.body === undefined ? undefined : JSON.stringify(options.body),\n });\n if (!response.ok) {\n throw await responseError(response, idempotent);\n }\n if (response.status === 204) {\n return undefined as T;\n }\n return (await response.json()) as T;\n });\n }\n\n // The gateway's CORS policy allows only Content-Type; the signed URL is\n // the authorization, and the runtime sets the exact-match Content-Length.\n async putBytes(\n uploadUrl: string,\n contentType: string,\n data: Blob | Uint8Array,\n ): Promise<void> {\n await this.run(true, async () => {\n const response = await this.send(uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': contentType },\n body: data as BodyInit,\n });\n if (!response.ok) {\n throw await responseError(response, true);\n }\n });\n }\n\n async putBytesJSON<T>(\n uploadUrl: string,\n contentType: string,\n data: Blob | Uint8Array,\n ): Promise<T> {\n return this.run(true, async () => {\n const response = await this.send(uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': contentType },\n body: data as BodyInit,\n });\n if (!response.ok) {\n throw await responseError(response, true);\n }\n return (await response.json()) as T;\n });\n }\n\n async uploadJSON<T>(\n uploadUrl: string,\n method: 'POST' | 'PUT' | 'DELETE',\n body: unknown,\n idempotent: boolean,\n ): Promise<T> {\n return this.run(idempotent, async () => {\n const response = await this.send(uploadUrl, {\n method,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n throw await responseError(response, idempotent);\n }\n if (response.status === 204) {\n return undefined as T;\n }\n return (await response.json()) as T;\n });\n }\n\n private async send(url: string, init: RequestInit): Promise<Response> {\n try {\n return await this.options.fetchImpl(url, {\n ...init,\n signal:\n this.options.timeoutMs > 0\n ? AbortSignal.timeout(this.options.timeoutMs)\n : undefined,\n });\n } catch (cause) {\n throw new RetryableError(\n null,\n new PortabyteError(\n cause instanceof Error ? cause.message : 'Network request failed.',\n 0,\n 'network_error',\n ),\n );\n }\n }\n\n private async run<T>(\n idempotent: boolean,\n send: () => Promise<T>,\n ): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await send();\n } catch (error) {\n const retry = error instanceof RetryableError;\n if (!retry || !idempotent || attempt >= this.options.maxRetries) {\n throw retry ? error.fallback : error;\n }\n await sleep(error.retryAfterMs ?? backoffMs(attempt));\n }\n }\n }\n}\n\nasync function responseError(\n response: Response,\n idempotent: boolean,\n): Promise<Error> {\n const error = await errorFromResponse(response);\n if (idempotent && (response.status === 429 || response.status >= 500)) {\n const retryAfter = Number(response.headers.get('retry-after'));\n const retryAfterMs =\n Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1000 : null;\n return new RetryableError(retryAfterMs, error);\n }\n return error;\n}\n\nexport function backoffMs(attempt: number): number {\n const baseMs = 500;\n const capMs = 8_000;\n return Math.floor(Math.random() * Math.min(capMs, baseMs * 2 ** attempt));\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function errorFromResponse(response: Response): Promise<PortabyteError> {\n let code = 'request_failed';\n let message = `Request failed with status ${response.status}.`;\n let requestId: string | undefined;\n try {\n const body = (await response.json()) as {\n code?: string;\n message?: string;\n requestId?: string;\n };\n code = body.code ?? code;\n message = body.message ?? message;\n requestId = body.requestId;\n } catch {\n // non-JSON error body\n }\n return new PortabyteError(message, response.status, code, requestId);\n}\n","import { HttpClient } from './client';\nimport { PortabyteError } from './errors';\nimport type {\n Asset,\n AssetDeliveryURL,\n BrowserUploadSession,\n CreateSession,\n CreateSessionRequest,\n ListAssetsResult,\n MultipartPart,\n MultipartUploadOptions,\n MultipartUploadState,\n ResumeUploadRequest,\n UploadRequest,\n} from './types';\n\nexport interface ListOptions {\n cursor?: string;\n limit?: number;\n}\n\nexport class FilesAPI {\n constructor(private readonly http: HttpClient) {}\n\n /** Creates a signed upload session. Prefer {@link upload}, which runs every step. */\n async create(input: CreateSessionRequest): Promise<CreateSession> {\n const body = {\n name: input.name,\n contentType: input.contentType,\n sizeBytes: input.sizeBytes,\n ...(input.path !== undefined && { path: input.path }),\n ...(input.visibility !== undefined && { visibility: input.visibility }),\n ...(input.corsOrigin !== undefined && { corsOrigin: input.corsOrigin }),\n };\n return this.http.request<CreateSession>({\n method: 'POST',\n path: this.path('assets'),\n body,\n });\n }\n\n /**\n * Prepares a direct browser upload. Call this only from your trusted server,\n * then return the result to the browser. The browser uploads bytes directly\n * to uploadUrl; call {@link confirm} from your server once it reports success.\n */\n async prepareBrowserUpload(\n input: CreateSessionRequest,\n ): Promise<BrowserUploadSession> {\n const session = await this.create(input);\n return {\n assetId: session.id,\n uploadUrl: session.uploadUrl,\n uploadExpiresAt: session.uploadExpiresAt,\n uploadMode: session.uploadMode,\n ...(session.partSize !== undefined && { partSize: session.partSize }),\n ...(session.maxConcurrency !== undefined && {\n maxConcurrency: session.maxConcurrency,\n }),\n };\n }\n\n /**\n * Confirms a completed direct upload and returns its live asset record.\n * Call this from your trusted server, never from a browser.\n */\n async confirm(assetID: string): Promise<Asset> {\n return this.http.request<Asset>({\n method: 'POST',\n path: this.path(`assets/${assetID}/uploaded`),\n });\n }\n\n /** Uploads a File, Blob, or bytes end to end. */\n async upload(request: UploadRequest): Promise<Asset> {\n const described = describeUpload(request);\n const { file, multipart } = request;\n const createOptions = {\n ...(request.path !== undefined && { path: request.path }),\n ...(request.visibility !== undefined && { visibility: request.visibility }),\n ...(request.corsOrigin !== undefined && { corsOrigin: request.corsOrigin }),\n };\n const session = await this.create({ ...described, ...createOptions });\n await this.transfer(\n session,\n file,\n described.contentType,\n multipart,\n );\n try {\n return await this.confirm(session.id);\n } catch (error) {\n if (error instanceof PortabyteError && error.status !== 0) {\n await this.remove(session.id).catch(() => undefined);\n }\n throw error;\n }\n }\n\n /**\n * Continues a previously-created session. Persist the create session and\n * MultipartUploadState after each part to resume after an interruption.\n */\n async resume(\n session: CreateSession,\n request: ResumeUploadRequest,\n ): Promise<Asset> {\n const contentType =\n request.contentType ??\n (request.file instanceof Blob ? request.file.type : '');\n if (\n fileSize(request.file) !== session.sizeBytes ||\n contentType !== session.contentType\n ) {\n throw new PortabyteError(\n 'The selected file does not match this upload session.',\n 0,\n 'invalid_argument',\n );\n }\n await this.transfer(session, request.file, contentType, request);\n return this.confirm(session.id);\n }\n\n async list(options: ListOptions = {}): Promise<ListAssetsResult> {\n const params = new URLSearchParams();\n if (options.cursor) params.set('cursor', options.cursor);\n if (options.limit !== undefined) params.set('limit', String(options.limit));\n const query = params.size > 0 ? `?${params}` : '';\n return this.http.request<ListAssetsResult>({\n method: 'GET',\n path: this.path(`assets${query}`),\n });\n }\n\n async get(assetID: string): Promise<Asset> {\n return this.http.request<Asset>({\n method: 'GET',\n path: this.path(`assets/${assetID}`),\n });\n }\n\n async remove(assetID: string): Promise<void> {\n await this.http.request<void>({\n method: 'DELETE',\n path: this.path(`assets/${assetID}`),\n });\n }\n\n /**\n * Cancels a multipart transfer and removes its pending asset. The gateway\n * abort is best-effort: removing the pending asset guarantees the object\n * can never be confirmed or delivered, even if its signed URL has expired.\n */\n async cancel(\n session: CreateSession,\n state?: MultipartUploadState,\n ): Promise<void> {\n if (session.uploadMode === 'multipart' && state?.uploadId) {\n await this.http\n .uploadJSON(\n `${session.uploadUrl}/multipart/${state.uploadId}`,\n 'DELETE',\n undefined,\n false,\n )\n .catch(() => undefined);\n }\n await this.remove(session.id);\n }\n\n /**\n * Returns the URL the asset is served from: stable and cacheable for\n * public assets, short-lived signed otherwise.\n */\n async url(assetID: string): Promise<AssetDeliveryURL> {\n return this.http.request<AssetDeliveryURL>({\n method: 'GET',\n path: this.path(`assets/${assetID}/url`),\n });\n }\n\n private path(suffix: string): string {\n return `/v1/${suffix}`;\n }\n\n private async transfer(\n session: CreateSession,\n body: Blob | Uint8Array,\n contentType: string,\n options?: MultipartUploadOptions,\n ): Promise<void> {\n if (session.uploadMode === 'single') {\n await this.http.putBytes(session.uploadUrl, contentType, body);\n return;\n }\n await this.uploadMultipart(session, body, contentType, options);\n }\n\n private async uploadMultipart(\n session: CreateSession,\n body: Blob | Uint8Array,\n contentType: string,\n options: MultipartUploadOptions = {},\n ): Promise<void> {\n if (!session.partSize || session.partSize < 5 * 1024 * 1024) {\n throw new PortabyteError(\n 'Multipart upload session is missing a valid part size.',\n 0,\n 'invalid_upload',\n );\n }\n const state: MultipartUploadState = {\n uploadId: options.state?.uploadId,\n parts: [...(options.state?.parts ?? [])],\n };\n if (!state.uploadId) {\n const started = await this.http.uploadJSON<{ uploadId: string }>(\n `${session.uploadUrl}/multipart`,\n 'POST',\n {},\n false,\n );\n state.uploadId = started.uploadId;\n await options.onStateChange?.({ ...state, parts: [...state.parts] });\n }\n const partCount = Math.ceil(session.sizeBytes / session.partSize);\n const completed = new Map(\n state.parts.map((part) => [part.partNumber, part]),\n );\n const concurrency = Math.max(\n 1,\n Math.min(options.concurrency ?? session.maxConcurrency ?? 3, 3),\n );\n let nextPart = 1;\n const uploadNext = async () => {\n for (;;) {\n const partNumber = nextPart;\n nextPart += 1;\n if (partNumber > partCount) return;\n if (completed.has(partNumber)) continue;\n const start = (partNumber - 1) * session.partSize!;\n const end = Math.min(start + session.partSize!, session.sizeBytes);\n const part = await this.http.putBytesJSON<MultipartPart>(\n `${session.uploadUrl}/multipart/${state.uploadId}/parts/${partNumber}`,\n contentType,\n sliceBody(body, start, end),\n );\n completed.set(part.partNumber, part);\n state.parts = [...completed.values()].sort(\n (left, right) => left.partNumber - right.partNumber,\n );\n await options.onStateChange?.({ ...state, parts: [...state.parts] });\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(concurrency, partCount) }, uploadNext),\n );\n await this.http.uploadJSON(\n `${session.uploadUrl}/multipart/${state.uploadId}/complete`,\n 'POST',\n { parts: state.parts },\n true,\n );\n }\n}\n\nfunction describeUpload(request: UploadRequest): {\n name: string;\n contentType: string;\n sizeBytes: number;\n} {\n const filename = request.name ?? fileName(request.file);\n const mimeType =\n request.contentType ?? (request.file instanceof Blob ? request.file.type : '');\n if (!filename) {\n throw new PortabyteError(\n 'A file name is required when uploading a Blob or bytes.',\n 0,\n 'invalid_argument',\n );\n }\n if (!mimeType) {\n throw new PortabyteError(\n 'A content type is required when uploading bytes or a Blob without a type.',\n 0,\n 'invalid_argument',\n );\n }\n return {\n name: filename,\n contentType: mimeType,\n sizeBytes:\n request.file instanceof Blob ? request.file.size : request.file.byteLength,\n };\n}\n\nfunction fileName(file: Blob | Uint8Array): string | undefined {\n if (!(file instanceof Blob)) return undefined;\n const name = (file as Blob & { name?: unknown }).name;\n return typeof name === 'string' && name.length > 0 ? name : undefined;\n}\n\nfunction fileSize(file: Blob | Uint8Array): number {\n return file instanceof Blob ? file.size : file.byteLength;\n}\n\nfunction sliceBody(\n body: Blob | Uint8Array,\n start: number,\n end: number,\n): Blob | Uint8Array {\n return body instanceof Blob ? body.slice(start, end) : body.slice(start, end);\n}\n","import { FilesAPI } from './assets';\nimport { HttpClient } from './client';\nimport { PortabyteError } from './errors';\nimport type {\n Asset,\n AssetDeliveryURL,\n BrowserUploadSession,\n CreateSession,\n CreateSessionOptions,\n CreateSessionRequest,\n ListAssetsResult,\n MultipartPart,\n MultipartUploadOptions,\n MultipartUploadState,\n ResumeUploadRequest,\n UploadRequest,\n} from './types';\n\nexport const VERSION = '0.0.1';\n\nexport interface PortabyteOptions {\n apiKey: string;\n baseUrl?: string;\n // idempotent requests only; mutating requests never retry\n maxRetries?: number;\n timeoutMs?: number;\n fetch?: typeof fetch;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.portabyte.dev';\n\n/**\n * Server-side Portabyte client. Its API key is scoped to one project, so a\n * project ID is never required in application configuration.\n */\nexport class Portabyte {\n /** Preferred API for application file uploads. */\n readonly files: FilesAPI;\n\n constructor(options: PortabyteOptions) {\n if (!options.apiKey.startsWith('pbt_sk_live_')) {\n throw new PortabyteError(\n 'apiKey must be a server API key starting with \"pbt_sk_live_\".',\n 0,\n 'invalid_argument',\n );\n }\n const http = new HttpClient({\n baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, ''),\n apiKey: options.apiKey,\n fetchImpl: options.fetch ?? fetch,\n maxRetries: options.maxRetries ?? 2,\n timeoutMs: options.timeoutMs ?? 30_000,\n sdkHeaderValue: `typescript/${VERSION}`,\n });\n this.files = new FilesAPI(http);\n }\n}\n\nexport { PortabyteError };\nexport type {\n Asset,\n AssetDeliveryURL,\n BrowserUploadSession,\n CreateSession,\n CreateSessionOptions,\n CreateSessionRequest,\n ListAssetsResult,\n MultipartPart,\n MultipartUploadOptions,\n MultipartUploadState,\n ResumeUploadRequest,\n UploadRequest,\n};\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@portabyte/node",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Official Node.js SDK for Portabyte file infrastructure.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"lint": "eslint src",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"format": "prettier --write 'src/**/*.ts'",
|
|
29
|
+
"verify": "pnpm run lint && pnpm run test && pnpm run typecheck"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"packageManager": "pnpm@11.9.0",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@types/node": "^25.9.5",
|
|
38
|
+
"eslint": "^10.2.0",
|
|
39
|
+
"eslint-config-prettier": "~10.1.8",
|
|
40
|
+
"prettier": "^3.8.1",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
42
|
+
"typescript": "^5.9.2",
|
|
43
|
+
"typescript-eslint": "^8.58.0",
|
|
44
|
+
"vitest": "^3.2.4"
|
|
45
|
+
}
|
|
46
|
+
}
|