@stackshift-cloud/sdk 1.0.0 → 1.1.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/README.md +60 -0
- package/dist/asset-media-v2.d.ts +65 -0
- package/dist/asset-media-v2.js +1 -0
- package/dist/asset-workflows.d.ts +269 -0
- package/dist/asset-workflows.js +69 -0
- package/dist/assets-platform-types.d.ts +218 -0
- package/dist/assets-platform-types.js +1 -0
- package/dist/assets-platform.d.ts +55 -0
- package/dist/assets-platform.js +90 -0
- package/dist/assets.d.ts +16 -0
- package/dist/assets.js +74 -130
- package/dist/client.d.ts +6 -0
- package/dist/client.js +15 -6
- package/dist/generated/assets-contract.d.ts +267 -2
- package/dist/generated/assets-contract.js +53 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +6 -0
- package/dist/upload-controller.d.ts +70 -0
- package/dist/upload-controller.js +223 -0
- package/package.json +2 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { StackShift } from './client.js';
|
|
2
|
+
import type { AssetBucket } from './types.js';
|
|
3
|
+
import type { AssetConnection, AssetConnectionInput, AssetConnectionListResult, AssetMigration, AssetMigrationInput, AssetMigrationListResult, AssetMigrationReport, AssetRelocationInput, AssetS2Bucket, AssetS2BucketListResult, AssetStorageConfiguration, AssetTextExtraction, AssetTextExtractionInput, AssetUploadCapabilityInput, CreatedAssetUploadCapability } from './assets-platform-types.js';
|
|
4
|
+
export declare class AssetConnectionsClient {
|
|
5
|
+
private readonly client;
|
|
6
|
+
constructor(client: StackShift);
|
|
7
|
+
list(): Promise<AssetConnectionListResult>;
|
|
8
|
+
get(connectionId: string): Promise<AssetConnection>;
|
|
9
|
+
create(input: AssetConnectionInput): Promise<AssetConnection>;
|
|
10
|
+
update(connectionId: string, input: Partial<AssetConnectionInput>): Promise<AssetConnection>;
|
|
11
|
+
verify(connectionId: string): Promise<AssetConnection>;
|
|
12
|
+
delete(connectionId: string): Promise<{
|
|
13
|
+
deleted: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export declare class AssetStorageClient {
|
|
17
|
+
private readonly client;
|
|
18
|
+
constructor(client: StackShift);
|
|
19
|
+
configure(bucketId: string, revision: number, input: AssetStorageConfiguration): Promise<AssetBucket>;
|
|
20
|
+
listS2Buckets(): Promise<AssetS2BucketListResult>;
|
|
21
|
+
createS2Bucket(input: {
|
|
22
|
+
name: string;
|
|
23
|
+
region: string;
|
|
24
|
+
}): Promise<AssetS2Bucket>;
|
|
25
|
+
relocate(bucketId: string, input?: AssetRelocationInput): Promise<AssetMigration>;
|
|
26
|
+
}
|
|
27
|
+
export declare class AssetMigrationsClient {
|
|
28
|
+
private readonly client;
|
|
29
|
+
constructor(client: StackShift);
|
|
30
|
+
start(input: AssetMigrationInput): Promise<AssetMigration>;
|
|
31
|
+
list(options?: {
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<AssetMigrationListResult>;
|
|
34
|
+
get(migrationId: string): Promise<AssetMigration>;
|
|
35
|
+
cancel(migrationId: string): Promise<{
|
|
36
|
+
canceled: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
retryItem(migrationId: string, itemId: string): Promise<{
|
|
39
|
+
queued: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
report(migrationId: string, format?: 'json'): Promise<AssetMigrationReport>;
|
|
42
|
+
downloadReport(migrationId: string, format?: 'json' | 'csv'): Promise<Blob>;
|
|
43
|
+
}
|
|
44
|
+
export declare class AssetTextClient {
|
|
45
|
+
private readonly client;
|
|
46
|
+
constructor(client: StackShift);
|
|
47
|
+
start(assetId: string, input?: AssetTextExtractionInput): Promise<AssetTextExtraction>;
|
|
48
|
+
get(assetId: string): Promise<AssetTextExtraction>;
|
|
49
|
+
retry(extractionId: string): Promise<AssetTextExtraction>;
|
|
50
|
+
}
|
|
51
|
+
export declare class AssetUploadCapabilitiesClient {
|
|
52
|
+
private readonly client;
|
|
53
|
+
constructor(client: StackShift);
|
|
54
|
+
create(input: AssetUploadCapabilityInput): Promise<CreatedAssetUploadCapability>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const encoded = encodeURIComponent;
|
|
2
|
+
export class AssetConnectionsClient {
|
|
3
|
+
client;
|
|
4
|
+
constructor(client) {
|
|
5
|
+
this.client = client;
|
|
6
|
+
}
|
|
7
|
+
list() { return this.client.request('/assets/connections'); }
|
|
8
|
+
get(connectionId) { return this.client.request(`/assets/connections/${encoded(connectionId)}`); }
|
|
9
|
+
create(input) {
|
|
10
|
+
return this.client.request('/assets/connections', { method: 'POST', body: input });
|
|
11
|
+
}
|
|
12
|
+
update(connectionId, input) {
|
|
13
|
+
return this.client.request(`/assets/connections/${encoded(connectionId)}`, { method: 'PATCH', body: input });
|
|
14
|
+
}
|
|
15
|
+
verify(connectionId) {
|
|
16
|
+
return this.client.request(`/assets/connections/${encoded(connectionId)}/verify`, { method: 'POST' });
|
|
17
|
+
}
|
|
18
|
+
delete(connectionId) {
|
|
19
|
+
return this.client.request(`/assets/connections/${encoded(connectionId)}`, { method: 'DELETE' });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class AssetStorageClient {
|
|
23
|
+
client;
|
|
24
|
+
constructor(client) {
|
|
25
|
+
this.client = client;
|
|
26
|
+
}
|
|
27
|
+
configure(bucketId, revision, input) {
|
|
28
|
+
return this.client.request(`/assets/buckets/${encoded(bucketId)}/storage`, {
|
|
29
|
+
method: 'PUT', body: input, headers: { 'If-Match': `"${revision}"` },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
listS2Buckets() { return this.client.request('/assets/s2-buckets'); }
|
|
33
|
+
createS2Bucket(input) {
|
|
34
|
+
return this.client.request('/assets/s2-buckets', { method: 'POST', body: input });
|
|
35
|
+
}
|
|
36
|
+
relocate(bucketId, input = {}) {
|
|
37
|
+
return this.client.request(`/assets/buckets/${encoded(bucketId)}/relocations`, { method: 'POST', body: input });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class AssetMigrationsClient {
|
|
41
|
+
client;
|
|
42
|
+
constructor(client) {
|
|
43
|
+
this.client = client;
|
|
44
|
+
}
|
|
45
|
+
start(input) {
|
|
46
|
+
return this.client.request('/assets/migrations', { method: 'POST', body: input });
|
|
47
|
+
}
|
|
48
|
+
list(options = {}) {
|
|
49
|
+
const query = new URLSearchParams();
|
|
50
|
+
if (options.limit !== undefined)
|
|
51
|
+
query.set('limit', String(options.limit));
|
|
52
|
+
const suffix = query.toString();
|
|
53
|
+
return this.client.request(`/assets/migrations${suffix ? `?${suffix}` : ''}`);
|
|
54
|
+
}
|
|
55
|
+
get(migrationId) { return this.client.request(`/assets/migrations/${encoded(migrationId)}`); }
|
|
56
|
+
cancel(migrationId) {
|
|
57
|
+
return this.client.request(`/assets/migrations/${encoded(migrationId)}/cancel`, { method: 'POST' });
|
|
58
|
+
}
|
|
59
|
+
retryItem(migrationId, itemId) {
|
|
60
|
+
return this.client.request(`/assets/migrations/${encoded(migrationId)}/items/${encoded(itemId)}/retry`, { method: 'POST' });
|
|
61
|
+
}
|
|
62
|
+
report(migrationId, format = 'json') {
|
|
63
|
+
return this.client.request(`/assets/migrations/${encoded(migrationId)}/report?format=${format}`);
|
|
64
|
+
}
|
|
65
|
+
async downloadReport(migrationId, format = 'json') {
|
|
66
|
+
return this.client.requestRaw(`/assets/migrations/${encoded(migrationId)}/report?format=${format}`).then((response) => response.blob());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class AssetTextClient {
|
|
70
|
+
client;
|
|
71
|
+
constructor(client) {
|
|
72
|
+
this.client = client;
|
|
73
|
+
}
|
|
74
|
+
start(assetId, input = {}) {
|
|
75
|
+
return this.client.request(`/assets/${encoded(assetId)}/text-extraction`, { method: 'POST', body: input });
|
|
76
|
+
}
|
|
77
|
+
get(assetId) { return this.client.request(`/assets/${encoded(assetId)}/text-extraction`); }
|
|
78
|
+
retry(extractionId) {
|
|
79
|
+
return this.client.request(`/assets/text-extractions/${encoded(extractionId)}/retry`, { method: 'POST' });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export class AssetUploadCapabilitiesClient {
|
|
83
|
+
client;
|
|
84
|
+
constructor(client) {
|
|
85
|
+
this.client = client;
|
|
86
|
+
}
|
|
87
|
+
create(input) {
|
|
88
|
+
return this.client.request('/assets/upload-capabilities', { method: 'POST', body: input });
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/assets.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { StackShift } from './client.js';
|
|
2
|
+
import { AssetConnectionsClient, AssetMigrationsClient, AssetStorageClient, AssetTextClient, AssetUploadCapabilitiesClient } from './assets-platform.js';
|
|
3
|
+
import { AssetWorkflowsClient } from './asset-workflows.js';
|
|
4
|
+
import type { AssetDerivedAsset, AssetImageGenerationInput, AssetTransformDefinition, AssetTransformMaterializeInput } from './asset-media-v2.js';
|
|
2
5
|
import type { Asset, AssetBucket, AssetBucketInput, AssetAIAccountConfig, AssetPatchInput, AssetCustomDomain, AssetEvent, AssetJob, AssetJobSubmission, AssetLifecycleRule, AssetUsageSummary, AssetWebhook, AssetWebhookDelivery, AssetAIInfo, AssetCollection, AssetContentPolicy, AssetNamedTransformInput, AssetListOptions, AssetListResult, AssetSavedSearch, AssetTranscript, AssetUploadProgressCallbacks, AssetVersion, AssetSignedUploadUrl, AssetSignedUploadUrlOptions, AssetSignedTransformOptions, AssetSignedUrl, AssetSignedUrlOptions, AssetTransformOptions, AssetTransformPreset, AssetUploadOptions } from './types.js';
|
|
3
6
|
export declare const assetTransformPresets: AssetTransformPreset[];
|
|
4
7
|
export declare function getAssetTransformPreset(name: string): AssetTransformPreset | undefined;
|
|
@@ -6,6 +9,12 @@ export declare function assetTransformOptions(preset: AssetTransformPreset): Ass
|
|
|
6
9
|
export declare function assetTransformSpec(options: AssetTransformOptions): string;
|
|
7
10
|
export declare class AssetsClient {
|
|
8
11
|
private readonly client;
|
|
12
|
+
readonly connections: AssetConnectionsClient;
|
|
13
|
+
readonly storage: AssetStorageClient;
|
|
14
|
+
readonly migrations: AssetMigrationsClient;
|
|
15
|
+
readonly text: AssetTextClient;
|
|
16
|
+
readonly uploadCapabilities: AssetUploadCapabilitiesClient;
|
|
17
|
+
readonly workflows: AssetWorkflowsClient;
|
|
9
18
|
constructor(client: StackShift);
|
|
10
19
|
upload(file: Blob, options?: AssetUploadOptions): Promise<Asset>;
|
|
11
20
|
list(options?: AssetListOptions): Promise<AssetListResult>;
|
|
@@ -95,6 +104,13 @@ export declare class AssetsClient {
|
|
|
95
104
|
getPolicy(): Promise<AssetContentPolicy>;
|
|
96
105
|
updatePolicy(policy: AssetContentPolicy): Promise<AssetContentPolicy>;
|
|
97
106
|
createTransformation(input: AssetNamedTransformInput): Promise<unknown>;
|
|
107
|
+
createTransformationV2(name: string, definition: AssetTransformDefinition, eager?: boolean): Promise<unknown>;
|
|
108
|
+
materializeTransform(assetId: string, input: AssetTransformMaterializeInput, idempotencyKey?: string): Promise<AssetJobSubmission>;
|
|
109
|
+
derivatives(assetId: string): Promise<{
|
|
110
|
+
derivatives: AssetDerivedAsset[];
|
|
111
|
+
}>;
|
|
112
|
+
generate(input: AssetImageGenerationInput, idempotencyKey: string): Promise<AssetJobSubmission>;
|
|
113
|
+
automation(): Promise<import("./asset-workflows.js").AssetAutomationOverview>;
|
|
98
114
|
listTransformations(): Promise<unknown>;
|
|
99
115
|
deleteTransformation(name: string): Promise<{
|
|
100
116
|
deleted: boolean;
|
package/dist/assets.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { AssetConnectionsClient, AssetMigrationsClient, AssetStorageClient, AssetTextClient, AssetUploadCapabilitiesClient } from './assets-platform.js';
|
|
2
|
+
import { AssetWorkflowsClient } from './asset-workflows.js';
|
|
3
|
+
import { ChunkedUploadController, putBlob, sha256Blob } from './upload-controller.js';
|
|
1
4
|
export const assetTransformPresets = [
|
|
2
5
|
preset('web-thumbnail', 'responsive-web', 'Compact grid thumbnail for libraries and dashboards.', 320, 180, 'fill', 'webp', 78),
|
|
3
6
|
preset('web-card', 'responsive-web', 'Balanced card image for product and content listings.', 640, 360, 'fill', 'webp', 82),
|
|
@@ -41,8 +44,20 @@ export function assetTransformSpec(options) {
|
|
|
41
44
|
}
|
|
42
45
|
export class AssetsClient {
|
|
43
46
|
client;
|
|
47
|
+
connections;
|
|
48
|
+
storage;
|
|
49
|
+
migrations;
|
|
50
|
+
text;
|
|
51
|
+
uploadCapabilities;
|
|
52
|
+
workflows;
|
|
44
53
|
constructor(client) {
|
|
45
54
|
this.client = client;
|
|
55
|
+
this.connections = new AssetConnectionsClient(client);
|
|
56
|
+
this.storage = new AssetStorageClient(client);
|
|
57
|
+
this.migrations = new AssetMigrationsClient(client);
|
|
58
|
+
this.text = new AssetTextClient(client);
|
|
59
|
+
this.uploadCapabilities = new AssetUploadCapabilitiesClient(client);
|
|
60
|
+
this.workflows = new AssetWorkflowsClient(client);
|
|
46
61
|
}
|
|
47
62
|
upload(file, options = {}) {
|
|
48
63
|
return this.client.request('/assets/upload', {
|
|
@@ -130,7 +145,13 @@ export class AssetsClient {
|
|
|
130
145
|
const target = uploadUrlOrToken.startsWith('http')
|
|
131
146
|
? `${uploadUrlOrToken.replace(/\/$/, '')}/parts/${partNumber}`
|
|
132
147
|
: `${this.originBaseUrl()}/uploads/${encodeURIComponent(uploadUrlOrToken)}/parts/${partNumber}`;
|
|
133
|
-
return
|
|
148
|
+
return putBlob(target, chunk, {
|
|
149
|
+
signal: callbacks.signal,
|
|
150
|
+
onProgress: (progress) => {
|
|
151
|
+
callbacks.onProgress?.(progress);
|
|
152
|
+
callbacks.onPartProgress?.(partNumber, progress);
|
|
153
|
+
},
|
|
154
|
+
}, { 'X-Content-SHA256': await sha256Blob(chunk) });
|
|
134
155
|
}
|
|
135
156
|
resumeUploadSession(sessionId) {
|
|
136
157
|
return this.client.request(`/assets/upload-sessions/${encodeURIComponent(sessionId)}`);
|
|
@@ -190,56 +211,22 @@ export class AssetsClient {
|
|
|
190
211
|
fileName: options.fileName ?? blobName(file),
|
|
191
212
|
mimeType: options.mimeType ?? file.type,
|
|
192
213
|
});
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
};
|
|
209
|
-
reportProgress();
|
|
210
|
-
const concurrency = boundedInteger(options.concurrency, 4, 1, 8);
|
|
211
|
-
const attempts = boundedInteger(options.retryAttempts, 4, 1, 8);
|
|
212
|
-
let next = 0;
|
|
213
|
-
const worker = async () => {
|
|
214
|
-
while (next < tasks.length) {
|
|
215
|
-
throwIfAborted(callbacks.signal);
|
|
216
|
-
const task = tasks[next++];
|
|
217
|
-
await retryWithBackoff(async () => {
|
|
218
|
-
throwIfAborted(callbacks.signal);
|
|
219
|
-
await this.uploadChunk(session.upload_session_id, task.part, task.chunk, {
|
|
220
|
-
signal: callbacks.signal,
|
|
221
|
-
onProgress: (progress) => {
|
|
222
|
-
loadedByPart.set(task.part, progress.loaded);
|
|
223
|
-
callbacks.onPartProgress?.(task.part, progress);
|
|
224
|
-
reportProgress();
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
loadedByPart.set(task.part, task.chunk.size);
|
|
228
|
-
reportProgress();
|
|
229
|
-
}, attempts, callbacks.signal);
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
try {
|
|
233
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length || 1) }, worker));
|
|
234
|
-
throwIfAborted(callbacks.signal);
|
|
235
|
-
return await retryWithBackoff(() => this.completeUploadSession(sessionId), attempts, callbacks.signal);
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
if (isAbortError(error)) {
|
|
239
|
-
await this.cancelUploadSession(sessionId).catch(() => undefined);
|
|
240
|
-
}
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
214
|
+
const controller = new ChunkedUploadController({
|
|
215
|
+
create: async () => toControllerSession(session),
|
|
216
|
+
resume: async (sessionId) => toControllerSession(await this.resumeUploadSession(sessionId)),
|
|
217
|
+
uploadPart: async (current, partNumber, chunk, checksum, progress) => {
|
|
218
|
+
const target = `${this.originBaseUrl()}/uploads/${encodeURIComponent(current.uploadToken)}/parts/${partNumber}`;
|
|
219
|
+
await putBlob(target, chunk, progress, { 'X-Content-SHA256': checksum });
|
|
220
|
+
},
|
|
221
|
+
complete: (current) => this.completeUploadSession(current.id),
|
|
222
|
+
cancel: (current) => this.cancelUploadSession(current.id).then(() => undefined),
|
|
223
|
+
});
|
|
224
|
+
return controller.upload(file, {
|
|
225
|
+
key: options.key ?? options.fileName ?? blobName(file) ?? 'upload',
|
|
226
|
+
fileName: options.fileName ?? blobName(file) ?? 'upload',
|
|
227
|
+
mimeType: options.mimeType ?? file.type,
|
|
228
|
+
metadata: options.metadata,
|
|
229
|
+
}, { ...callbacks, concurrency: options.concurrency, retryAttempts: options.retryAttempts, cancelOnAbort: true });
|
|
243
230
|
}
|
|
244
231
|
url(assetId, options) {
|
|
245
232
|
return `${this.client.cdnBaseUrl}/assets/${encodeURIComponent(assetId)}/tr/${encodeURIComponent(transformSpec(options))}`;
|
|
@@ -346,6 +333,28 @@ export class AssetsClient {
|
|
|
346
333
|
body: input,
|
|
347
334
|
});
|
|
348
335
|
}
|
|
336
|
+
createTransformationV2(name, definition, eager = false) {
|
|
337
|
+
return this.client.request('/assets/transformations', {
|
|
338
|
+
method: 'POST', body: { name, definition, eager },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
materializeTransform(assetId, input, idempotencyKey) {
|
|
342
|
+
return this.client.request(`/assets/${encodeURIComponent(assetId)}/transforms`, {
|
|
343
|
+
method: 'POST', body: input,
|
|
344
|
+
headers: idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : undefined,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
derivatives(assetId) {
|
|
348
|
+
return this.client.request(`/assets/${encodeURIComponent(assetId)}/derivatives`);
|
|
349
|
+
}
|
|
350
|
+
generate(input, idempotencyKey) {
|
|
351
|
+
return this.client.request('/assets/generations', {
|
|
352
|
+
method: 'POST', body: input, headers: { 'Idempotency-Key': idempotencyKey },
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
automation() {
|
|
356
|
+
return this.client.request('/assets/automation');
|
|
357
|
+
}
|
|
349
358
|
listTransformations() {
|
|
350
359
|
return this.client.request('/assets/transformations');
|
|
351
360
|
}
|
|
@@ -441,7 +450,7 @@ export class AssetsClient {
|
|
|
441
450
|
mode: 'single',
|
|
442
451
|
maxBytes: options.maxBytes ?? file.size,
|
|
443
452
|
});
|
|
444
|
-
await
|
|
453
|
+
await putBlob(session.url, file, callbacks);
|
|
445
454
|
return session;
|
|
446
455
|
}
|
|
447
456
|
}
|
|
@@ -495,82 +504,6 @@ const defaultPartSize = 5 * 1024 * 1024;
|
|
|
495
504
|
function blobName(file) {
|
|
496
505
|
return file.name;
|
|
497
506
|
}
|
|
498
|
-
function uploadBlob(url, blob, callbacks, partNumber, headers = {}) {
|
|
499
|
-
if (typeof XMLHttpRequest === 'undefined') {
|
|
500
|
-
return fetch(url, { method: 'PUT', body: blob, signal: callbacks.signal, headers }).then((response) => {
|
|
501
|
-
if (!response.ok)
|
|
502
|
-
throw new Error(`Upload failed with ${response.status}`);
|
|
503
|
-
const progress = { loaded: blob.size, total: blob.size, percent: 100 };
|
|
504
|
-
callbacks.onProgress?.(progress);
|
|
505
|
-
if (partNumber)
|
|
506
|
-
callbacks.onPartProgress?.(partNumber, progress);
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
return new Promise((resolve, reject) => {
|
|
510
|
-
const xhr = new XMLHttpRequest();
|
|
511
|
-
xhr.open('PUT', url);
|
|
512
|
-
for (const [name, value] of Object.entries(headers))
|
|
513
|
-
xhr.setRequestHeader(name, value);
|
|
514
|
-
const abort = () => xhr.abort();
|
|
515
|
-
callbacks.signal?.addEventListener('abort', abort, { once: true });
|
|
516
|
-
xhr.upload.onprogress = (event) => {
|
|
517
|
-
const total = event.total || blob.size;
|
|
518
|
-
const progress = { loaded: event.loaded, total, percent: total ? Math.round((event.loaded / total) * 100) : 0 };
|
|
519
|
-
callbacks.onProgress?.(progress);
|
|
520
|
-
if (partNumber)
|
|
521
|
-
callbacks.onPartProgress?.(partNumber, progress);
|
|
522
|
-
};
|
|
523
|
-
const cleanup = () => callbacks.signal?.removeEventListener('abort', abort);
|
|
524
|
-
xhr.onload = () => {
|
|
525
|
-
cleanup();
|
|
526
|
-
if (xhr.status >= 200 && xhr.status < 300)
|
|
527
|
-
resolve();
|
|
528
|
-
else
|
|
529
|
-
reject(new Error(`Upload failed with ${xhr.status}`));
|
|
530
|
-
};
|
|
531
|
-
xhr.onerror = () => { cleanup(); reject(new Error('Upload failed')); };
|
|
532
|
-
xhr.onabort = () => { cleanup(); reject(abortError()); };
|
|
533
|
-
xhr.send(blob);
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
async function sha256Blob(blob) {
|
|
537
|
-
if (!globalThis.crypto?.subtle)
|
|
538
|
-
throw new Error('Web Crypto SHA-256 is required for multipart uploads');
|
|
539
|
-
const digest = await globalThis.crypto.subtle.digest('SHA-256', await blob.arrayBuffer());
|
|
540
|
-
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
541
|
-
}
|
|
542
|
-
function boundedInteger(value, fallback, minimum, maximum) {
|
|
543
|
-
if (value === undefined)
|
|
544
|
-
return fallback;
|
|
545
|
-
if (!Number.isSafeInteger(value))
|
|
546
|
-
throw new Error('Upload concurrency and retry attempts must be integers');
|
|
547
|
-
return Math.min(maximum, Math.max(minimum, value));
|
|
548
|
-
}
|
|
549
|
-
async function retryWithBackoff(operation, attempts, signal) {
|
|
550
|
-
let lastError;
|
|
551
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
552
|
-
try {
|
|
553
|
-
return await operation();
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
if (isAbortError(error) || signal?.aborted)
|
|
557
|
-
throw abortError();
|
|
558
|
-
lastError = error;
|
|
559
|
-
if (attempt + 1 < attempts)
|
|
560
|
-
await abortableDelay(250 * 2 ** attempt, signal);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
throw lastError;
|
|
564
|
-
}
|
|
565
|
-
function abortableDelay(milliseconds, signal) {
|
|
566
|
-
throwIfAborted(signal);
|
|
567
|
-
return new Promise((resolve, reject) => {
|
|
568
|
-
const timer = setTimeout(() => { cleanup(); resolve(); }, milliseconds);
|
|
569
|
-
const abort = () => { clearTimeout(timer); cleanup(); reject(abortError()); };
|
|
570
|
-
const cleanup = () => signal?.removeEventListener('abort', abort);
|
|
571
|
-
signal?.addEventListener('abort', abort, { once: true });
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
507
|
function throwIfAborted(signal) {
|
|
575
508
|
if (signal?.aborted)
|
|
576
509
|
throw abortError();
|
|
@@ -578,6 +511,17 @@ function throwIfAborted(signal) {
|
|
|
578
511
|
function abortError() {
|
|
579
512
|
return typeof DOMException === 'undefined' ? Object.assign(new Error('Upload cancelled'), { name: 'AbortError' }) : new DOMException('Upload cancelled', 'AbortError');
|
|
580
513
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
514
|
+
function toControllerSession(session) {
|
|
515
|
+
const id = session.session_id;
|
|
516
|
+
if (!id)
|
|
517
|
+
throw new Error('Chunked upload session did not include a resumable session id');
|
|
518
|
+
return {
|
|
519
|
+
id,
|
|
520
|
+
uploadToken: session.upload_session_id,
|
|
521
|
+
expiresAt: session.expires_at,
|
|
522
|
+
maxBytes: session.max_bytes,
|
|
523
|
+
partSize: session.part_size ?? defaultPartSize,
|
|
524
|
+
parts: (session.parts ?? []).map((part) => ({ partNumber: part.part_number, size: part.size, checksumSHA256: part.checksum_sha256 })),
|
|
525
|
+
resumeContext: id,
|
|
526
|
+
};
|
|
583
527
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -17,12 +17,18 @@ export declare class StackShift {
|
|
|
17
17
|
body?: unknown;
|
|
18
18
|
headers?: Record<string, string>;
|
|
19
19
|
}): Promise<T>;
|
|
20
|
+
requestRaw(path: string, init?: {
|
|
21
|
+
method?: string;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
}): Promise<Response>;
|
|
20
25
|
mailRequest<T>(path: string, init?: {
|
|
21
26
|
method?: string;
|
|
22
27
|
body?: unknown;
|
|
23
28
|
headers?: Record<string, string>;
|
|
24
29
|
}): Promise<T>;
|
|
25
30
|
private requestFrom;
|
|
31
|
+
private fetchFrom;
|
|
26
32
|
}
|
|
27
33
|
export declare class StackShiftAPIError extends Error {
|
|
28
34
|
readonly status: number;
|
package/dist/client.js
CHANGED
|
@@ -30,10 +30,22 @@ export class StackShift {
|
|
|
30
30
|
async request(path, init = {}) {
|
|
31
31
|
return this.requestFrom(this.baseUrl, path, init);
|
|
32
32
|
}
|
|
33
|
+
requestRaw(path, init = {}) {
|
|
34
|
+
return this.fetchFrom(this.baseUrl, path, init);
|
|
35
|
+
}
|
|
33
36
|
async mailRequest(path, init = {}) {
|
|
34
37
|
return this.requestFrom(this.mailBaseUrl, path, init);
|
|
35
38
|
}
|
|
36
39
|
async requestFrom(baseUrl, path, init) {
|
|
40
|
+
const response = await this.fetchFrom(baseUrl, path, init);
|
|
41
|
+
const raw = await response.text();
|
|
42
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
43
|
+
if (parsed && typeof parsed === 'object' && 'success' in parsed && 'data' in parsed) {
|
|
44
|
+
return parsed.data;
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
async fetchFrom(baseUrl, path, init) {
|
|
37
49
|
const isFormData = typeof FormData !== 'undefined' && init.body instanceof FormData;
|
|
38
50
|
const response = await this.fetcher(`${baseUrl}${path}`, {
|
|
39
51
|
method: init.method ?? 'GET',
|
|
@@ -44,16 +56,13 @@ export class StackShift {
|
|
|
44
56
|
}),
|
|
45
57
|
body: (init.body === undefined ? undefined : isFormData ? init.body : JSON.stringify(stripUndefined(init.body))),
|
|
46
58
|
});
|
|
47
|
-
const raw = await response.text();
|
|
48
|
-
const parsed = raw ? JSON.parse(raw) : null;
|
|
49
59
|
if (!response.ok) {
|
|
60
|
+
const raw = await response.text();
|
|
61
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
50
62
|
const apiError = parsed?.error && typeof parsed.error === 'object' ? parsed.error : undefined;
|
|
51
63
|
throw new StackShiftAPIError(apiError?.message ?? parsed?.message ?? parsed?.error ?? `StackShift API request failed with ${response.status}`, response.status, apiError?.code, apiError?.details);
|
|
52
64
|
}
|
|
53
|
-
|
|
54
|
-
return parsed.data;
|
|
55
|
-
}
|
|
56
|
-
return parsed;
|
|
65
|
+
return response;
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
export class StackShiftAPIError extends Error {
|