@stackshift-cloud/sdk 1.0.0 → 1.2.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.
Files changed (53) hide show
  1. package/README.md +117 -0
  2. package/dist/asset-media-v2.d.ts +65 -0
  3. package/dist/asset-media-v2.js +1 -0
  4. package/dist/asset-workflows.d.ts +269 -0
  5. package/dist/asset-workflows.js +69 -0
  6. package/dist/assets-dam-types.d.ts +225 -0
  7. package/dist/assets-dam-types.js +1 -0
  8. package/dist/assets-dam.d.ts +96 -0
  9. package/dist/assets-dam.js +47 -0
  10. package/dist/assets-platform-types.d.ts +218 -0
  11. package/dist/assets-platform-types.js +1 -0
  12. package/dist/assets-platform.d.ts +55 -0
  13. package/dist/assets-platform.js +90 -0
  14. package/dist/assets-video-types.d.ts +116 -0
  15. package/dist/assets-video-types.js +1 -0
  16. package/dist/assets-video.d.ts +25 -0
  17. package/dist/assets-video.js +19 -0
  18. package/dist/assets.d.ts +21 -1
  19. package/dist/assets.js +85 -132
  20. package/dist/client.d.ts +10 -0
  21. package/dist/client.js +26 -6
  22. package/dist/generated/assets-contract.d.ts +512 -2
  23. package/dist/generated/assets-contract.js +102 -0
  24. package/dist/index.d.ts +21 -0
  25. package/dist/index.js +11 -0
  26. package/dist/mail-advanced-types.d.ts +9 -0
  27. package/dist/mail-billing.d.ts +34 -0
  28. package/dist/mail-billing.js +7 -0
  29. package/dist/mail-brands.d.ts +35 -0
  30. package/dist/mail-brands.js +17 -0
  31. package/dist/mail-campaigns.d.ts +72 -10
  32. package/dist/mail-campaigns.js +25 -2
  33. package/dist/mail-marketing.d.ts +84 -0
  34. package/dist/mail-marketing.js +24 -0
  35. package/dist/mail-migration.d.ts +16 -0
  36. package/dist/mail-migration.js +10 -0
  37. package/dist/mail-otp-types.d.ts +4 -0
  38. package/dist/mail-simulation.d.ts +4 -0
  39. package/dist/mail-simulation.js +1 -0
  40. package/dist/mail-subscribers.d.ts +39 -0
  41. package/dist/mail-subscribers.js +1 -0
  42. package/dist/mail-template-types.d.ts +8 -0
  43. package/dist/mail-tracking.d.ts +59 -0
  44. package/dist/mail-tracking.js +14 -0
  45. package/dist/mail-webhook-types.d.ts +4 -1
  46. package/dist/mail.d.ts +12 -0
  47. package/dist/mail.js +19 -0
  48. package/dist/types.d.ts +19 -0
  49. package/dist/upload-controller.d.ts +70 -0
  50. package/dist/upload-controller.js +223 -0
  51. package/dist/workload-security.d.ts +172 -0
  52. package/dist/workload-security.js +50 -0
  53. package/package.json +2 -2
@@ -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
+ }
@@ -0,0 +1,116 @@
1
+ export interface AssetVideoPackage {
2
+ id: string;
3
+ asset_space_id: string;
4
+ asset_id: string;
5
+ version_id: string;
6
+ source_checksum_sha256: string;
7
+ recipe_hash: string;
8
+ recipe: string;
9
+ processor_revision: string;
10
+ status: 'queued' | 'processing' | 'ready' | 'degraded' | 'failed' | 'cancelled';
11
+ phase: string;
12
+ progress_percent: number;
13
+ job_id?: string;
14
+ published_attempt?: string;
15
+ admitted_at?: string;
16
+ created_at: string;
17
+ updated_at: string;
18
+ failure_code: string;
19
+ metadata: {
20
+ asset_id: string;
21
+ duration_seconds: number;
22
+ width: number;
23
+ height: number;
24
+ codec: string;
25
+ bitrate: number;
26
+ frame_rate: number;
27
+ has_audio: boolean;
28
+ rotation: number;
29
+ created_at: string;
30
+ updated_at: string;
31
+ };
32
+ }
33
+ export interface AssetVideoWorkspace {
34
+ active?: AssetVideoPackage;
35
+ pending?: AssetVideoPackage;
36
+ source?: {
37
+ version_id: string;
38
+ scan_status: 'pending' | 'clean' | 'infected' | 'failed';
39
+ review_status: 'not_required' | 'pending' | 'approved' | 'rejected';
40
+ review_reason: string;
41
+ };
42
+ renditions: Array<{
43
+ id: string;
44
+ package_id?: string;
45
+ asset_id: string;
46
+ source_checksum_sha256: string;
47
+ kind: 'hls' | 'mp4' | 'poster';
48
+ profile: string;
49
+ width: number;
50
+ height: number;
51
+ size: number;
52
+ mime_type: string;
53
+ status: string;
54
+ created_at: string;
55
+ updated_at: string;
56
+ }>;
57
+ admission_enabled: boolean;
58
+ }
59
+ export interface AssetVideoCaptionInput {
60
+ /** Atomically replace this active track; the new reference receives a new ID. */
61
+ replace_caption_id?: string;
62
+ caption_asset_id: string;
63
+ caption_version_id: string;
64
+ language: string;
65
+ label: string;
66
+ kind: 'captions' | 'subtitles';
67
+ is_default?: boolean;
68
+ }
69
+ export interface AssetVideoCaption extends AssetVideoCaptionInput {
70
+ id: string;
71
+ asset_space_id: string;
72
+ asset_id: string;
73
+ version_id: string;
74
+ is_default: boolean;
75
+ }
76
+ export interface AssetPlaybackGrant {
77
+ has_audio: boolean;
78
+ session: {
79
+ id: string;
80
+ asset_space_id: string;
81
+ asset_id: string;
82
+ package_id: string;
83
+ version_id: string;
84
+ owner_id?: string;
85
+ is_private: boolean;
86
+ expires_at: string;
87
+ maximum_expires_at: string;
88
+ revoked_at?: string;
89
+ created_at: string;
90
+ };
91
+ credential: string;
92
+ hls: string;
93
+ mp4: string;
94
+ poster?: string;
95
+ captions: Array<{
96
+ id: string;
97
+ url: string;
98
+ language: string;
99
+ label: string;
100
+ kind: 'captions' | 'subtitles';
101
+ is_default: boolean;
102
+ }>;
103
+ }
104
+ export interface AssetPlaybackEvent {
105
+ sequence: number;
106
+ kind: 'startup' | 'rebuffer' | 'error' | 'quality' | 'watched' | 'completed';
107
+ value: number;
108
+ }
109
+ export interface AssetPlaybackAnalytics {
110
+ starts: number;
111
+ completions: number;
112
+ errors: number;
113
+ startup_ms: number;
114
+ rebuffer_ms: number;
115
+ watched_ms: number;
116
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { StackShift } from './client.js';
2
+ import type { AssetVideoWorkspace, AssetVideoPackage, AssetVideoCaption, AssetVideoCaptionInput, AssetPlaybackGrant, AssetPlaybackEvent, AssetPlaybackAnalytics } from './assets-video-types.js';
3
+ export declare class AssetVideoClient {
4
+ private readonly client;
5
+ constructor(client: StackShift);
6
+ workspace(assetId: string): Promise<AssetVideoWorkspace>;
7
+ process(assetId: string, versionId: string): Promise<AssetVideoPackage>;
8
+ createSession(assetId: string): Promise<AssetPlaybackGrant>;
9
+ renewSession(credential: string): Promise<AssetPlaybackGrant>;
10
+ revokeSession(assetId: string, sessionId: string): Promise<{
11
+ revoked: boolean;
12
+ }>;
13
+ captions(assetId: string, versionId: string): Promise<AssetVideoCaption[]>;
14
+ addCaption(assetId: string, versionId: string, input: AssetVideoCaptionInput): Promise<AssetVideoCaption>;
15
+ removeCaption(assetId: string, versionId: string, captionId: string): Promise<{
16
+ removed: boolean;
17
+ }>;
18
+ review(assetId: string, versionId: string, decision: 'approved' | 'rejected', reason: string): Promise<{
19
+ reviewed: boolean;
20
+ }>;
21
+ events(credential: string, events: AssetPlaybackEvent[]): Promise<{
22
+ accepted: boolean;
23
+ }>;
24
+ analytics(assetId: string): Promise<AssetPlaybackAnalytics>;
25
+ }
@@ -0,0 +1,19 @@
1
+ const assetPath = (id) => `/assets/${encodeURIComponent(id)}`;
2
+ const versionPath = (id, version) => `${assetPath(id)}/versions/${encodeURIComponent(version)}/video`;
3
+ export class AssetVideoClient {
4
+ client;
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ workspace(assetId) { return this.client.request(`${assetPath(assetId)}/video`); }
9
+ process(assetId, versionId) { return this.client.request(versionPath(assetId, versionId), { method: 'POST' }); }
10
+ createSession(assetId) { return this.client.request(`${assetPath(assetId)}/playback-sessions`, { method: 'POST' }); }
11
+ renewSession(credential) { return this.client.playbackRequest('/sessions/renew', credential, {}); }
12
+ revokeSession(assetId, sessionId) { return this.client.request(`${assetPath(assetId)}/playback-sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' }); }
13
+ captions(assetId, versionId) { return this.client.request(`${versionPath(assetId, versionId)}/captions`); }
14
+ addCaption(assetId, versionId, input) { return this.client.request(`${versionPath(assetId, versionId)}/captions`, { method: 'POST', body: input }); }
15
+ removeCaption(assetId, versionId, captionId) { return this.client.request(`${versionPath(assetId, versionId)}/captions/${encodeURIComponent(captionId)}`, { method: 'DELETE' }); }
16
+ review(assetId, versionId, decision, reason) { return this.client.request(`${versionPath(assetId, versionId)}/review`, { method: 'POST', body: { decision, reason } }); }
17
+ events(credential, events) { return this.client.playbackRequest('/events', credential, { events }); }
18
+ analytics(assetId) { return this.client.request(`${assetPath(assetId)}/video/analytics`); }
19
+ }
package/dist/assets.d.ts CHANGED
@@ -1,4 +1,9 @@
1
+ import { AssetDAMClient } from './assets-dam.js';
2
+ import { AssetVideoClient } from './assets-video.js';
1
3
  import type { StackShift } from './client.js';
4
+ import { AssetConnectionsClient, AssetMigrationsClient, AssetStorageClient, AssetTextClient, AssetUploadCapabilitiesClient } from './assets-platform.js';
5
+ import { AssetWorkflowsClient } from './asset-workflows.js';
6
+ import type { AssetDerivedAsset, AssetImageGenerationInput, AssetTransformDefinition, AssetTransformMaterializeInput } from './asset-media-v2.js';
2
7
  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
8
  export declare const assetTransformPresets: AssetTransformPreset[];
4
9
  export declare function getAssetTransformPreset(name: string): AssetTransformPreset | undefined;
@@ -6,6 +11,14 @@ export declare function assetTransformOptions(preset: AssetTransformPreset): Ass
6
11
  export declare function assetTransformSpec(options: AssetTransformOptions): string;
7
12
  export declare class AssetsClient {
8
13
  private readonly client;
14
+ readonly dam: AssetDAMClient;
15
+ readonly video: AssetVideoClient;
16
+ readonly connections: AssetConnectionsClient;
17
+ readonly storage: AssetStorageClient;
18
+ readonly migrations: AssetMigrationsClient;
19
+ readonly text: AssetTextClient;
20
+ readonly uploadCapabilities: AssetUploadCapabilitiesClient;
21
+ readonly workflows: AssetWorkflowsClient;
9
22
  constructor(client: StackShift);
10
23
  upload(file: Blob, options?: AssetUploadOptions): Promise<Asset>;
11
24
  list(options?: AssetListOptions): Promise<AssetListResult>;
@@ -70,7 +83,7 @@ export declare class AssetsClient {
70
83
  description?: string;
71
84
  asset_ids?: string[];
72
85
  }): Promise<AssetCollection>;
73
- updateCollection(collectionId: string, input: {
86
+ updateCollection(collectionId: string, revision: number, input: {
74
87
  name: string;
75
88
  description?: string;
76
89
  asset_ids?: string[];
@@ -95,6 +108,13 @@ export declare class AssetsClient {
95
108
  getPolicy(): Promise<AssetContentPolicy>;
96
109
  updatePolicy(policy: AssetContentPolicy): Promise<AssetContentPolicy>;
97
110
  createTransformation(input: AssetNamedTransformInput): Promise<unknown>;
111
+ createTransformationV2(name: string, definition: AssetTransformDefinition, eager?: boolean): Promise<unknown>;
112
+ materializeTransform(assetId: string, input: AssetTransformMaterializeInput, idempotencyKey?: string): Promise<AssetJobSubmission>;
113
+ derivatives(assetId: string): Promise<{
114
+ derivatives: AssetDerivedAsset[];
115
+ }>;
116
+ generate(input: AssetImageGenerationInput, idempotencyKey: string): Promise<AssetJobSubmission>;
117
+ automation(): Promise<import("./asset-workflows.js").AssetAutomationOverview>;
98
118
  listTransformations(): Promise<unknown>;
99
119
  deleteTransformation(name: string): Promise<{
100
120
  deleted: boolean;
package/dist/assets.js CHANGED
@@ -1,3 +1,8 @@
1
+ import { AssetDAMClient } from './assets-dam.js';
2
+ import { AssetVideoClient } from './assets-video.js';
3
+ import { AssetConnectionsClient, AssetMigrationsClient, AssetStorageClient, AssetTextClient, AssetUploadCapabilitiesClient } from './assets-platform.js';
4
+ import { AssetWorkflowsClient } from './asset-workflows.js';
5
+ import { ChunkedUploadController, putBlob, sha256Blob } from './upload-controller.js';
1
6
  export const assetTransformPresets = [
2
7
  preset('web-thumbnail', 'responsive-web', 'Compact grid thumbnail for libraries and dashboards.', 320, 180, 'fill', 'webp', 78),
3
8
  preset('web-card', 'responsive-web', 'Balanced card image for product and content listings.', 640, 360, 'fill', 'webp', 82),
@@ -41,8 +46,24 @@ export function assetTransformSpec(options) {
41
46
  }
42
47
  export class AssetsClient {
43
48
  client;
49
+ dam;
50
+ video;
51
+ connections;
52
+ storage;
53
+ migrations;
54
+ text;
55
+ uploadCapabilities;
56
+ workflows;
44
57
  constructor(client) {
45
58
  this.client = client;
59
+ this.dam = new AssetDAMClient(client);
60
+ this.video = new AssetVideoClient(client);
61
+ this.connections = new AssetConnectionsClient(client);
62
+ this.storage = new AssetStorageClient(client);
63
+ this.migrations = new AssetMigrationsClient(client);
64
+ this.text = new AssetTextClient(client);
65
+ this.uploadCapabilities = new AssetUploadCapabilitiesClient(client);
66
+ this.workflows = new AssetWorkflowsClient(client);
46
67
  }
47
68
  upload(file, options = {}) {
48
69
  return this.client.request('/assets/upload', {
@@ -61,6 +82,9 @@ export class AssetsClient {
61
82
  append(query, 'type', options.type);
62
83
  append(query, 'folder', options.folder);
63
84
  append(query, 'checksum', options.checksum);
85
+ append(query, 'collection_id', options.collectionId);
86
+ append(query, 'duplicates', options.duplicates === undefined ? undefined : String(options.duplicates));
87
+ append(query, 'security', options.security);
64
88
  append(query, 'status', options.status);
65
89
  append(query, 'scan_status', options.scanStatus);
66
90
  append(query, 'replication_status', options.replicationStatus);
@@ -130,7 +154,13 @@ export class AssetsClient {
130
154
  const target = uploadUrlOrToken.startsWith('http')
131
155
  ? `${uploadUrlOrToken.replace(/\/$/, '')}/parts/${partNumber}`
132
156
  : `${this.originBaseUrl()}/uploads/${encodeURIComponent(uploadUrlOrToken)}/parts/${partNumber}`;
133
- return uploadBlob(target, chunk, callbacks, partNumber, { 'X-Content-SHA256': await sha256Blob(chunk) });
157
+ return putBlob(target, chunk, {
158
+ signal: callbacks.signal,
159
+ onProgress: (progress) => {
160
+ callbacks.onProgress?.(progress);
161
+ callbacks.onPartProgress?.(partNumber, progress);
162
+ },
163
+ }, { 'X-Content-SHA256': await sha256Blob(chunk) });
134
164
  }
135
165
  resumeUploadSession(sessionId) {
136
166
  return this.client.request(`/assets/upload-sessions/${encodeURIComponent(sessionId)}`);
@@ -190,56 +220,22 @@ export class AssetsClient {
190
220
  fileName: options.fileName ?? blobName(file),
191
221
  mimeType: options.mimeType ?? file.type,
192
222
  });
193
- const partSize = session.part_size ?? defaultPartSize;
194
- const sessionId = session.session_id ?? options.uploadSessionId;
195
- if (!sessionId) {
196
- throw new Error('Chunked upload session did not include a resumable session id');
197
- }
198
- const completed = new Map((session.parts ?? []).map((part) => [part.part_number, part.size]));
199
- const loadedByPart = new Map(completed);
200
- const tasks = [];
201
- for (let offset = 0, part = 1; offset < size; offset += partSize, part += 1) {
202
- if (!completed.has(part))
203
- tasks.push({ part, chunk: file.slice(offset, offset + partSize) });
204
- }
205
- const reportProgress = () => {
206
- const loaded = Math.min(size, Array.from(loadedByPart.values()).reduce((sum, value) => sum + value, 0));
207
- callbacks.onProgress?.({ loaded, total: size, percent: size ? Math.round((loaded / size) * 100) : 100 });
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
- }
223
+ const controller = new ChunkedUploadController({
224
+ create: async () => toControllerSession(session),
225
+ resume: async (sessionId) => toControllerSession(await this.resumeUploadSession(sessionId)),
226
+ uploadPart: async (current, partNumber, chunk, checksum, progress) => {
227
+ const target = `${this.originBaseUrl()}/uploads/${encodeURIComponent(current.uploadToken)}/parts/${partNumber}`;
228
+ await putBlob(target, chunk, progress, { 'X-Content-SHA256': checksum });
229
+ },
230
+ complete: (current) => this.completeUploadSession(current.id),
231
+ cancel: (current) => this.cancelUploadSession(current.id).then(() => undefined),
232
+ });
233
+ return controller.upload(file, {
234
+ key: options.key ?? options.fileName ?? blobName(file) ?? 'upload',
235
+ fileName: options.fileName ?? blobName(file) ?? 'upload',
236
+ mimeType: options.mimeType ?? file.type,
237
+ metadata: options.metadata,
238
+ }, { ...callbacks, concurrency: options.concurrency, retryAttempts: options.retryAttempts, cancelOnAbort: true });
243
239
  }
244
240
  url(assetId, options) {
245
241
  return `${this.client.cdnBaseUrl}/assets/${encodeURIComponent(assetId)}/tr/${encodeURIComponent(transformSpec(options))}`;
@@ -313,8 +309,8 @@ export class AssetsClient {
313
309
  createCollection(input) {
314
310
  return this.client.request('/assets/collections', { method: 'POST', body: input });
315
311
  }
316
- updateCollection(collectionId, input) {
317
- return this.client.request(`/assets/collections/${encodeURIComponent(collectionId)}`, { method: 'PUT', body: input });
312
+ updateCollection(collectionId, revision, input) {
313
+ return this.client.request(`/assets/collections/${encodeURIComponent(collectionId)}`, { method: 'PUT', body: input, headers: { 'If-Match': `"${revision}"` } });
318
314
  }
319
315
  deleteCollection(collectionId) {
320
316
  return this.client.request(`/assets/collections/${encodeURIComponent(collectionId)}`, { method: 'DELETE' });
@@ -346,6 +342,28 @@ export class AssetsClient {
346
342
  body: input,
347
343
  });
348
344
  }
345
+ createTransformationV2(name, definition, eager = false) {
346
+ return this.client.request('/assets/transformations', {
347
+ method: 'POST', body: { name, definition, eager },
348
+ });
349
+ }
350
+ materializeTransform(assetId, input, idempotencyKey) {
351
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/transforms`, {
352
+ method: 'POST', body: input,
353
+ headers: idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : undefined,
354
+ });
355
+ }
356
+ derivatives(assetId) {
357
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/derivatives`);
358
+ }
359
+ generate(input, idempotencyKey) {
360
+ return this.client.request('/assets/generations', {
361
+ method: 'POST', body: input, headers: { 'Idempotency-Key': idempotencyKey },
362
+ });
363
+ }
364
+ automation() {
365
+ return this.client.request('/assets/automation');
366
+ }
349
367
  listTransformations() {
350
368
  return this.client.request('/assets/transformations');
351
369
  }
@@ -441,7 +459,7 @@ export class AssetsClient {
441
459
  mode: 'single',
442
460
  maxBytes: options.maxBytes ?? file.size,
443
461
  });
444
- await uploadBlob(session.url, file, callbacks);
462
+ await putBlob(session.url, file, callbacks);
445
463
  return session;
446
464
  }
447
465
  }
@@ -495,82 +513,6 @@ const defaultPartSize = 5 * 1024 * 1024;
495
513
  function blobName(file) {
496
514
  return file.name;
497
515
  }
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
516
  function throwIfAborted(signal) {
575
517
  if (signal?.aborted)
576
518
  throw abortError();
@@ -578,6 +520,17 @@ function throwIfAborted(signal) {
578
520
  function abortError() {
579
521
  return typeof DOMException === 'undefined' ? Object.assign(new Error('Upload cancelled'), { name: 'AbortError' }) : new DOMException('Upload cancelled', 'AbortError');
580
522
  }
581
- function isAbortError(error) {
582
- return error instanceof Error && error.name === 'AbortError';
523
+ function toControllerSession(session) {
524
+ const id = session.session_id;
525
+ if (!id)
526
+ throw new Error('Chunked upload session did not include a resumable session id');
527
+ return {
528
+ id,
529
+ uploadToken: session.upload_session_id,
530
+ expiresAt: session.expires_at,
531
+ maxBytes: session.max_bytes,
532
+ partSize: session.part_size ?? defaultPartSize,
533
+ parts: (session.parts ?? []).map((part) => ({ partNumber: part.part_number, size: part.size, checksumSHA256: part.checksum_sha256 })),
534
+ resumeContext: id,
535
+ };
583
536
  }
package/dist/client.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { AssetsClient } from './assets.js';
2
2
  import { MailClient } from './mail.js';
3
3
  import { ProjectsClient } from './projects.js';
4
+ import { WorkloadSecurityClient } from './workload-security.js';
4
5
  import type { StackShiftOptions } from './types.js';
5
6
  export declare class StackShift {
7
+ readonly assetSpaceId?: string;
6
8
  readonly apiKey: string;
7
9
  readonly baseUrl: string;
8
10
  readonly mailBaseUrl: string;
@@ -10,6 +12,7 @@ export declare class StackShift {
10
12
  readonly projects: ProjectsClient;
11
13
  readonly assets: AssetsClient;
12
14
  readonly mail: MailClient;
15
+ readonly workloadSecurity: WorkloadSecurityClient;
13
16
  private readonly fetcher;
14
17
  constructor(options?: StackShiftOptions);
15
18
  request<T>(path: string, init?: {
@@ -17,12 +20,19 @@ export declare class StackShift {
17
20
  body?: unknown;
18
21
  headers?: Record<string, string>;
19
22
  }): Promise<T>;
23
+ requestRaw(path: string, init?: {
24
+ method?: string;
25
+ body?: unknown;
26
+ headers?: Record<string, string>;
27
+ }): Promise<Response>;
20
28
  mailRequest<T>(path: string, init?: {
21
29
  method?: string;
22
30
  body?: unknown;
23
31
  headers?: Record<string, string>;
24
32
  }): Promise<T>;
33
+ playbackRequest<T>(path: '/sessions/renew' | '/events', credential: string, body: unknown): Promise<T>;
25
34
  private requestFrom;
35
+ private fetchFrom;
26
36
  }
27
37
  export declare class StackShiftAPIError extends Error {
28
38
  readonly status: number;