@stackshift-cloud/sdk 0.1.3 → 0.1.4

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.
@@ -0,0 +1,21 @@
1
+ import { AssetsClient } from './assets.js';
2
+ import type { AssetSignedTransformOptions, AssetSignedUploadUrl, AssetTransformOptions } from './types.js';
3
+ export interface ResponsiveImageOptions extends Omit<AssetSignedTransformOptions, 'width'> {
4
+ widths: number[];
5
+ sizes?: string;
6
+ }
7
+ export interface ResponsiveImageDescriptor {
8
+ src: string;
9
+ srcSet: string;
10
+ sizes: string;
11
+ widthUrls: Record<number, string>;
12
+ }
13
+ /** Shared by Next.js server actions, React/TanStack loaders, and NestJS services. */
14
+ export declare function createResponsiveImage(assets: AssetsClient, assetId: string, options: ResponsiveImageOptions): Promise<ResponsiveImageDescriptor>;
15
+ /** Uploads through a short-lived session without exposing the account API key. */
16
+ export declare function uploadBrowserFile(session: AssetSignedUploadUrl, file: Blob, fetcher?: typeof fetch): Promise<Response>;
17
+ export declare function namedResponsiveSource(assets: AssetsClient, assetId: string, preset: string, widths: number[]): string;
18
+ export type ArtDirectionSource = {
19
+ media: string;
20
+ transform: AssetTransformOptions;
21
+ };
@@ -0,0 +1,34 @@
1
+ /** Shared by Next.js server actions, React/TanStack loaders, and NestJS services. */
2
+ export async function createResponsiveImage(assets, assetId, options) {
3
+ const widths = [...new Set(options.widths)].filter((width) => Number.isSafeInteger(width) && width > 0).sort((a, b) => a - b);
4
+ if (widths.length === 0 || widths.length > 12)
5
+ throw new Error('Responsive images require 1-12 positive integer widths');
6
+ const { widths: _widths, sizes = '100vw', ...transform } = options;
7
+ const signed = await Promise.all(widths.map(async (width) => [width, await assets.signedTransformUrl(assetId, { ...transform, width })]));
8
+ const widthUrls = Object.fromEntries(signed.map(([width, result]) => [width, result.url]));
9
+ return {
10
+ src: widthUrls[widths[widths.length - 1]],
11
+ srcSet: widths.map((width) => `${widthUrls[width]} ${width}w`).join(', '),
12
+ sizes,
13
+ widthUrls,
14
+ };
15
+ }
16
+ /** Uploads through a short-lived session without exposing the account API key. */
17
+ export async function uploadBrowserFile(session, file, fetcher = fetch) {
18
+ if (session.method !== 'PUT')
19
+ throw new Error(`Unsupported upload method: ${session.method}`);
20
+ if (file.size > session.max_bytes)
21
+ throw new Error(`File exceeds signed upload limit of ${session.max_bytes} bytes`);
22
+ const response = await fetcher(session.url, {
23
+ method: session.method,
24
+ body: file,
25
+ headers: file.type ? { 'Content-Type': file.type } : undefined,
26
+ });
27
+ if (!response.ok)
28
+ throw new Error(`StackShift browser upload failed with HTTP ${response.status}`);
29
+ return response;
30
+ }
31
+ export function namedResponsiveSource(assets, assetId, preset, widths) {
32
+ const clean = [...new Set(widths)].filter((width) => Number.isSafeInteger(width) && width > 0).sort((a, b) => a - b);
33
+ return clean.map((width) => `${assets.namedUrl(assetId, `${preset}-${width}`)} ${width}w`).join(', ');
34
+ }
package/dist/assets.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { StackShift } from './client.js';
2
- import type { Asset, AssetAIInfo, AssetCollection, AssetContentPolicy, AssetNamedTransformInput, AssetListOptions, AssetListResult, AssetSavedSearch, AssetTranscript, AssetUploadProgressCallbacks, AssetVersion, AssetSignedUploadUrl, AssetSignedUploadUrlOptions, AssetSignedTransformOptions, AssetSignedUrl, AssetSignedUrlOptions, AssetTransformOptions, AssetTransformPreset, AssetUploadOptions } from './types.js';
2
+ 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
3
  export declare const assetTransformPresets: AssetTransformPreset[];
4
4
  export declare function getAssetTransformPreset(name: string): AssetTransformPreset | undefined;
5
5
  export declare function assetTransformOptions(preset: AssetTransformPreset): AssetTransformOptions;
@@ -10,10 +10,8 @@ export declare class AssetsClient {
10
10
  upload(file: Blob, options?: AssetUploadOptions): Promise<Asset>;
11
11
  list(options?: AssetListOptions): Promise<AssetListResult>;
12
12
  get(assetId: string): Promise<Asset>;
13
- delete(assetId: string): Promise<{
14
- deleted: boolean;
15
- }>;
16
- replace(assetId: string, file: Blob, options?: Pick<AssetUploadOptions, 'cacheControl' | 'metadata'>): Promise<Asset>;
13
+ delete(assetId: string, revision: number, idempotencyKey?: string): Promise<AssetJobSubmission>;
14
+ replace(assetId: string, revision: number, file: Blob, options?: Pick<AssetUploadOptions, 'cacheControl' | 'metadata'>): Promise<Asset>;
17
15
  signedUrl(assetId: string, options: AssetSignedUrlOptions): Promise<AssetSignedUrl>;
18
16
  signedUploadUrl(options?: AssetSignedUploadUrlOptions): Promise<AssetSignedUploadUrl>;
19
17
  createUploadSession(options?: AssetSignedUploadUrlOptions): Promise<AssetSignedUploadUrl>;
@@ -24,29 +22,36 @@ export declare class AssetsClient {
24
22
  cancelUploadSession(sessionId: string): Promise<{
25
23
  cancelled: boolean;
26
24
  }>;
25
+ listBuckets(): Promise<{
26
+ buckets: AssetBucket[];
27
+ }>;
28
+ createBucket(input: AssetBucketInput & {
29
+ name: string;
30
+ }): Promise<AssetBucket>;
31
+ updateBucket(bucketId: string, revision: number, input: AssetBucketInput): Promise<AssetBucket>;
32
+ deleteBucket(bucketId: string, revision: number): Promise<void>;
33
+ setTags(assetId: string, revision: number, tags: string[]): Promise<{
34
+ tags: string[];
35
+ revision: number;
36
+ }>;
37
+ patchMetadata(assetId: string, revision: number, metadata: Record<string, unknown>): Promise<{
38
+ updated: boolean;
39
+ revision: number;
40
+ }>;
41
+ patch(assetId: string, revision: number, input: AssetPatchInput): Promise<Asset>;
27
42
  uploadWithProgress(file: Blob, options?: AssetSignedUploadUrlOptions, callbacks?: AssetUploadProgressCallbacks): Promise<Asset | AssetSignedUploadUrl>;
28
43
  url(assetId: string, options: AssetTransformOptions): string;
29
44
  namedUrl(assetId: string, name: string): string;
30
- videoUrl(assetId: string, kind: 'hls' | 'poster' | 'mp4', profile?: string, token?: string): string;
45
+ videoUrl(assetId: string, kind: 'hls' | 'poster' | 'thumbnail' | 'mp4', profile?: string, token?: string): string;
31
46
  versionUrl(assetId: string, versionId: string, token?: string): string;
32
47
  branchUrl(assetId: string, branch: string, token?: string): string;
33
48
  ai(assetId: string): Promise<AssetAIInfo>;
34
- analyze(assetId: string): Promise<{
35
- analysis_queued: boolean;
36
- }>;
37
- moderate(assetId: string): Promise<{
38
- moderation_queued: boolean;
39
- }>;
49
+ analyze(assetId: string, idempotencyKey?: string): Promise<AssetJobSubmission>;
50
+ moderate(assetId: string, idempotencyKey?: string): Promise<AssetJobSubmission>;
40
51
  transcript(assetId: string): Promise<AssetTranscript>;
41
- requestTranscript(assetId: string): Promise<{
42
- transcript_queued: boolean;
43
- }>;
44
- requestSmartCrop(assetId: string): Promise<{
45
- smart_crop_queued: boolean;
46
- }>;
47
- requestBackgroundRemoval(assetId: string): Promise<{
48
- background_remove_queued: boolean;
49
- }>;
52
+ requestTranscript(assetId: string, idempotencyKey?: string): Promise<AssetJobSubmission>;
53
+ requestSmartCrop(assetId: string, idempotencyKey?: string): Promise<AssetJobSubmission>;
54
+ requestBackgroundRemoval(assetId: string, idempotencyKey?: string): Promise<AssetJobSubmission>;
50
55
  versions(assetId: string): Promise<{
51
56
  versions: AssetVersion[];
52
57
  }>;
@@ -95,6 +100,66 @@ export declare class AssetsClient {
95
100
  deleted: boolean;
96
101
  }>;
97
102
  signedTransformUrl(assetId: string, options: AssetSignedTransformOptions): Promise<AssetSignedUrl>;
103
+ getAIConfig(): Promise<AssetAIAccountConfig>;
104
+ updateAIConfig(revision: number, enabledActions: string[], monthlySpendCapMicros: number): Promise<import("./types.js").AssetAIPolicy>;
105
+ job(jobId: string): Promise<AssetJob>;
106
+ cancelJob(jobId: string): Promise<AssetJob>;
107
+ createLifecycleRule(input: {
108
+ name: string;
109
+ prefix?: string;
110
+ action: 'delete' | 'expire_versions';
111
+ age_days: number;
112
+ enabled?: boolean;
113
+ }): Promise<AssetLifecycleRule>;
114
+ listLifecycleRules(): Promise<{
115
+ lifecycle_rules: AssetLifecycleRule[];
116
+ }>;
117
+ deleteLifecycleRule(ruleId: string): Promise<{
118
+ deleted: boolean;
119
+ }>;
120
+ createDomain(domain: string): Promise<AssetCustomDomain>;
121
+ listDomains(): Promise<{
122
+ domains: AssetCustomDomain[];
123
+ }>;
124
+ verifyDomain(domainId: string): Promise<AssetCustomDomain>;
125
+ deleteDomain(domainId: string): Promise<{
126
+ deleted: boolean;
127
+ }>;
128
+ analytics(): Promise<AssetUsageSummary>;
129
+ usageSummary(): Promise<AssetUsageSummary>;
130
+ events(limit?: number): Promise<{
131
+ events: AssetEvent[];
132
+ total: number;
133
+ }>;
134
+ createWebhook(url: string, eventTypes?: string[]): Promise<{
135
+ webhook: AssetWebhook;
136
+ secret: string;
137
+ }>;
138
+ listWebhooks(): Promise<{
139
+ webhooks: AssetWebhook[];
140
+ }>;
141
+ deleteWebhook(webhookId: string): Promise<{
142
+ deleted: boolean;
143
+ }>;
144
+ listWebhookDeliveries(webhookId: string, limit?: number): Promise<{
145
+ deliveries: AssetWebhookDelivery[];
146
+ }>;
147
+ retryWebhookDelivery(deliveryId: string): Promise<AssetWebhookDelivery>;
148
+ purge(assetId: string): Promise<{
149
+ purged: boolean;
150
+ asset_id: string;
151
+ }>;
152
+ bulk(input: {
153
+ action: 'delete' | 'visibility' | 'add_tags';
154
+ items: Array<{
155
+ asset_id: string;
156
+ revision: number;
157
+ }>;
158
+ visibility?: string;
159
+ tags?: string[];
160
+ idempotencyKey?: string;
161
+ }): Promise<AssetJobSubmission>;
98
162
  private originBaseUrl;
163
+ private requestAssetJob;
99
164
  private uploadSingleWithProgress;
100
165
  }
package/dist/assets.js CHANGED
@@ -57,10 +57,17 @@ export class AssetsClient {
57
57
  append(query, 'visibility', options.visibility);
58
58
  append(query, 'query', options.query);
59
59
  append(query, 'tag', options.tag);
60
- append(query, 'mimeType', options.mimeType);
60
+ append(query, 'mime_type', options.mimeType);
61
61
  append(query, 'type', options.type);
62
62
  append(query, 'folder', options.folder);
63
63
  append(query, 'checksum', options.checksum);
64
+ append(query, 'status', options.status);
65
+ append(query, 'scan_status', options.scanStatus);
66
+ append(query, 'replication_status', options.replicationStatus);
67
+ append(query, 'moderation_status', options.moderationStatus);
68
+ append(query, 'quarantine_status', options.quarantineStatus);
69
+ append(query, 'sort', options.sort);
70
+ append(query, 'direction', options.direction);
64
71
  append(query, 'limit', options.limit);
65
72
  append(query, 'cursor', options.cursor);
66
73
  const suffix = query.toString();
@@ -69,23 +76,25 @@ export class AssetsClient {
69
76
  get(assetId) {
70
77
  return this.client.request(`/assets/${encodeURIComponent(assetId)}`);
71
78
  }
72
- delete(assetId) {
79
+ delete(assetId, revision, idempotencyKey) {
73
80
  return this.client.request(`/assets/${encodeURIComponent(assetId)}`, {
74
81
  method: 'DELETE',
82
+ headers: { 'If-Match': `"${revision}"`, ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}) },
75
83
  });
76
84
  }
77
- replace(assetId, file, options = {}) {
85
+ replace(assetId, revision, file, options = {}) {
78
86
  return this.client.request(`/assets/${encodeURIComponent(assetId)}/replace`, {
79
87
  method: 'PUT',
80
88
  body: assetForm(file, options),
89
+ headers: { 'If-Match': `"${revision}"` },
81
90
  });
82
91
  }
83
92
  signedUrl(assetId, options) {
84
93
  return this.client.request(`/assets/${encodeURIComponent(assetId)}/signed-url`, {
85
94
  method: 'POST',
86
95
  body: {
87
- expiresIn: options.expiresIn,
88
- maxDownloads: options.maxDownloads,
96
+ expires_in: options.expiresIn,
97
+ max_downloads: options.maxDownloads,
89
98
  },
90
99
  });
91
100
  }
@@ -95,32 +104,33 @@ export class AssetsClient {
95
104
  createUploadSession(options = {}) {
96
105
  return this.client.request('/assets/upload-sessions', {
97
106
  method: 'POST',
107
+ headers: options.idempotencyKey ? { 'Idempotency-Key': options.idempotencyKey } : undefined,
98
108
  body: {
99
109
  bucket: options.bucket,
100
110
  key: options.key,
101
111
  folder: options.folder,
102
112
  visibility: options.visibility,
103
- cacheControl: options.cacheControl,
113
+ cache_control: options.cacheControl,
104
114
  metadata: options.metadata,
105
115
  mode: options.mode,
106
- fileName: options.fileName,
107
- fileSize: options.fileSize,
108
- mimeType: options.mimeType,
109
- checksumSHA256: options.checksumSHA256,
110
- partSize: options.partSize,
111
- expiresIn: options.expiresIn,
112
- maxBytes: options.maxBytes,
116
+ file_name: options.fileName,
117
+ file_size: options.fileSize,
118
+ mime_type: options.mimeType,
119
+ checksum_sha256: options.checksumSHA256,
120
+ part_size: options.partSize,
121
+ expires_in: options.expiresIn,
122
+ max_bytes: options.maxBytes,
113
123
  },
114
124
  });
115
125
  }
116
126
  createChunkedUploadSession(options) {
117
127
  return this.createUploadSession({ ...options, mode: 'chunked' });
118
128
  }
119
- uploadChunk(uploadUrlOrToken, partNumber, chunk, callbacks = {}) {
129
+ async uploadChunk(uploadUrlOrToken, partNumber, chunk, callbacks = {}) {
120
130
  const target = uploadUrlOrToken.startsWith('http')
121
131
  ? `${uploadUrlOrToken.replace(/\/$/, '')}/parts/${partNumber}`
122
132
  : `${this.originBaseUrl()}/uploads/${encodeURIComponent(uploadUrlOrToken)}/parts/${partNumber}`;
123
- return uploadBlob(target, chunk, callbacks, partNumber);
133
+ return uploadBlob(target, chunk, callbacks, partNumber, { 'X-Content-SHA256': await sha256Blob(chunk) });
124
134
  }
125
135
  resumeUploadSession(sessionId) {
126
136
  return this.client.request(`/assets/upload-sessions/${encodeURIComponent(sessionId)}`);
@@ -135,25 +145,101 @@ export class AssetsClient {
135
145
  method: 'DELETE',
136
146
  });
137
147
  }
148
+ listBuckets() {
149
+ return this.client.request('/assets/buckets');
150
+ }
151
+ createBucket(input) {
152
+ return this.client.request('/assets/buckets', { method: 'POST', body: input });
153
+ }
154
+ updateBucket(bucketId, revision, input) {
155
+ return this.client.request(`/assets/buckets/${encodeURIComponent(bucketId)}`, {
156
+ method: 'PUT', body: input, headers: { 'If-Match': `"${revision}"` },
157
+ });
158
+ }
159
+ deleteBucket(bucketId, revision) {
160
+ return this.client.request(`/assets/buckets/${encodeURIComponent(bucketId)}`, {
161
+ method: 'DELETE', headers: { 'If-Match': `"${revision}"` },
162
+ });
163
+ }
164
+ setTags(assetId, revision, tags) {
165
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/tags`, {
166
+ method: 'PUT', body: { tags }, headers: { 'If-Match': `"${revision}"` },
167
+ });
168
+ }
169
+ patchMetadata(assetId, revision, metadata) {
170
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/metadata`, {
171
+ method: 'PATCH', body: { metadata }, headers: { 'If-Match': `"${revision}"` },
172
+ });
173
+ }
174
+ patch(assetId, revision, input) {
175
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}`, {
176
+ method: 'PATCH', body: input, headers: { 'If-Match': `"${revision}"` },
177
+ });
178
+ }
138
179
  async uploadWithProgress(file, options = {}, callbacks = {}) {
139
180
  const size = options.fileSize ?? file.size;
140
181
  if (size <= defaultPartSize) {
141
182
  return this.uploadSingleWithProgress(file, options, callbacks);
142
183
  }
143
- const session = await this.createChunkedUploadSession({
144
- ...options,
145
- fileSize: size,
146
- fileName: options.fileName ?? blobName(file),
147
- mimeType: options.mimeType ?? file.type,
148
- });
184
+ throwIfAborted(callbacks.signal);
185
+ const session = options.uploadSessionId
186
+ ? await this.resumeUploadSession(options.uploadSessionId)
187
+ : await this.createChunkedUploadSession({
188
+ ...options,
189
+ fileSize: size,
190
+ fileName: options.fileName ?? blobName(file),
191
+ mimeType: options.mimeType ?? file.type,
192
+ });
149
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 = [];
150
201
  for (let offset = 0, part = 1; offset < size; offset += partSize, part += 1) {
151
- await this.uploadChunk(session.upload_session_id, part, file.slice(offset, offset + partSize), callbacks);
202
+ if (!completed.has(part))
203
+ tasks.push({ part, chunk: file.slice(offset, offset + partSize) });
152
204
  }
153
- if (!session.session_id) {
154
- throw new Error('Chunked upload session did not include a resumable session id');
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;
155
242
  }
156
- return this.completeUploadSession(session.session_id);
157
243
  }
158
244
  url(assetId, options) {
159
245
  return `${this.client.cdnBaseUrl}/assets/${encodeURIComponent(assetId)}/tr/${encodeURIComponent(transformSpec(options))}`;
@@ -188,23 +274,23 @@ export class AssetsClient {
188
274
  ai(assetId) {
189
275
  return this.client.request(`/assets/${encodeURIComponent(assetId)}/ai`);
190
276
  }
191
- analyze(assetId) {
192
- return this.client.request(`/assets/${encodeURIComponent(assetId)}/ai/analyze`, { method: 'POST' });
277
+ analyze(assetId, idempotencyKey) {
278
+ return this.requestAssetJob(`/assets/${encodeURIComponent(assetId)}/ai/analyze`, idempotencyKey);
193
279
  }
194
- moderate(assetId) {
195
- return this.client.request(`/assets/${encodeURIComponent(assetId)}/moderation`, { method: 'POST' });
280
+ moderate(assetId, idempotencyKey) {
281
+ return this.requestAssetJob(`/assets/${encodeURIComponent(assetId)}/moderation`, idempotencyKey);
196
282
  }
197
283
  transcript(assetId) {
198
284
  return this.client.request(`/assets/${encodeURIComponent(assetId)}/transcript`);
199
285
  }
200
- requestTranscript(assetId) {
201
- return this.client.request(`/assets/${encodeURIComponent(assetId)}/transcript`, { method: 'POST' });
286
+ requestTranscript(assetId, idempotencyKey) {
287
+ return this.requestAssetJob(`/assets/${encodeURIComponent(assetId)}/transcript`, idempotencyKey);
202
288
  }
203
- requestSmartCrop(assetId) {
204
- return this.client.request(`/assets/${encodeURIComponent(assetId)}/smart-crop`, { method: 'POST' });
289
+ requestSmartCrop(assetId, idempotencyKey) {
290
+ return this.requestAssetJob(`/assets/${encodeURIComponent(assetId)}/smart-crop`, idempotencyKey);
205
291
  }
206
- requestBackgroundRemoval(assetId) {
207
- return this.client.request(`/assets/${encodeURIComponent(assetId)}/background-remove`, { method: 'POST' });
292
+ requestBackgroundRemoval(assetId, idempotencyKey) {
293
+ return this.requestAssetJob(`/assets/${encodeURIComponent(assetId)}/background-remove`, idempotencyKey);
208
294
  }
209
295
  versions(assetId) {
210
296
  return this.client.request(`/assets/${encodeURIComponent(assetId)}/versions`);
@@ -274,9 +360,81 @@ export class AssetsClient {
274
360
  body: transformBody(options),
275
361
  });
276
362
  }
363
+ getAIConfig() {
364
+ return this.client.request('/assets/ai/config');
365
+ }
366
+ updateAIConfig(revision, enabledActions, monthlySpendCapMicros) {
367
+ return this.client.request('/assets/ai/config', {
368
+ method: 'PUT', headers: { 'If-Match': `"${revision}"` },
369
+ body: { enabled_actions: enabledActions, monthly_spend_cap_micros: monthlySpendCapMicros },
370
+ });
371
+ }
372
+ job(jobId) {
373
+ return this.client.request(`/assets/jobs/${encodeURIComponent(jobId)}`);
374
+ }
375
+ cancelJob(jobId) {
376
+ return this.client.request(`/assets/jobs/${encodeURIComponent(jobId)}/cancel`, { method: 'POST' });
377
+ }
378
+ createLifecycleRule(input) {
379
+ return this.client.request('/assets/lifecycle-rules', { method: 'POST', body: input });
380
+ }
381
+ listLifecycleRules() {
382
+ return this.client.request('/assets/lifecycle-rules');
383
+ }
384
+ deleteLifecycleRule(ruleId) {
385
+ return this.client.request(`/assets/lifecycle-rules/${encodeURIComponent(ruleId)}`, { method: 'DELETE' });
386
+ }
387
+ createDomain(domain) {
388
+ return this.client.request('/assets/domains', { method: 'POST', body: { domain } });
389
+ }
390
+ listDomains() {
391
+ return this.client.request('/assets/domains');
392
+ }
393
+ verifyDomain(domainId) {
394
+ return this.client.request(`/assets/domains/${encodeURIComponent(domainId)}/verify`, { method: 'POST' });
395
+ }
396
+ deleteDomain(domainId) {
397
+ return this.client.request(`/assets/domains/${encodeURIComponent(domainId)}`, { method: 'DELETE' });
398
+ }
399
+ analytics() {
400
+ return this.client.request('/assets/analytics');
401
+ }
402
+ usageSummary() {
403
+ return this.client.request('/assets/summary');
404
+ }
405
+ events(limit = 50) {
406
+ return this.client.request(`/assets/events?limit=${limit}`);
407
+ }
408
+ createWebhook(url, eventTypes = ['asset.*']) {
409
+ return this.client.request('/assets/webhooks', {
410
+ method: 'POST', body: { url, event_types: eventTypes },
411
+ });
412
+ }
413
+ listWebhooks() {
414
+ return this.client.request('/assets/webhooks');
415
+ }
416
+ deleteWebhook(webhookId) {
417
+ return this.client.request(`/assets/webhooks/${encodeURIComponent(webhookId)}`, { method: 'DELETE' });
418
+ }
419
+ listWebhookDeliveries(webhookId, limit = 50) {
420
+ return this.client.request(`/assets/webhooks/${encodeURIComponent(webhookId)}/deliveries?limit=${limit}`);
421
+ }
422
+ retryWebhookDelivery(deliveryId) {
423
+ return this.client.request(`/assets/webhook-deliveries/${encodeURIComponent(deliveryId)}/retry`, { method: 'POST' });
424
+ }
425
+ purge(assetId) {
426
+ return this.client.request(`/assets/${encodeURIComponent(assetId)}/purge`, { method: 'POST' });
427
+ }
428
+ bulk(input) {
429
+ const { idempotencyKey, ...body } = input;
430
+ return this.client.request('/assets/bulk', { method: 'POST', body, headers: idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : undefined });
431
+ }
277
432
  originBaseUrl() {
278
433
  return this.client.baseUrl.replace(/\/api\/v1$/, '');
279
434
  }
435
+ requestAssetJob(path, idempotencyKey) {
436
+ return this.client.request(path, { method: 'POST', headers: idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : undefined });
437
+ }
280
438
  async uploadSingleWithProgress(file, options, callbacks) {
281
439
  const session = await this.createUploadSession({
282
440
  ...options,
@@ -294,7 +452,7 @@ function assetForm(file, options) {
294
452
  append(body, 'key', options.key);
295
453
  append(body, 'folder', options.folder);
296
454
  append(body, 'visibility', options.visibility);
297
- append(body, 'cacheControl', options.cacheControl);
455
+ append(body, 'cache_control', options.cacheControl);
298
456
  if (options.metadata) {
299
457
  body.append('metadata', JSON.stringify(options.metadata));
300
458
  }
@@ -314,6 +472,8 @@ function transformSpec(options) {
314
472
  parts.push(`c_${options.crop ?? 'fit'}`);
315
473
  parts.push(`f_${options.format ?? 'auto'}`);
316
474
  parts.push(`q_${options.quality ?? 'auto'}`);
475
+ parts.push(`g_${options.gravity ?? 'centre'}`);
476
+ parts.push(`a_${options.animation ?? 'first'}`);
317
477
  return parts.join(',');
318
478
  }
319
479
  function preset(name, category, description, width, height, crop = 'fit', format = 'auto', quality = 'auto') {
@@ -326,23 +486,33 @@ function transformBody(options) {
326
486
  crop: options.crop,
327
487
  format: options.format,
328
488
  quality: options.quality,
329
- expiresIn: options.expiresIn,
489
+ gravity: options.gravity,
490
+ animation: options.animation,
491
+ expires_in: options.expiresIn,
330
492
  };
331
493
  }
332
494
  const defaultPartSize = 5 * 1024 * 1024;
333
495
  function blobName(file) {
334
496
  return file.name;
335
497
  }
336
- function uploadBlob(url, blob, callbacks, partNumber) {
498
+ function uploadBlob(url, blob, callbacks, partNumber, headers = {}) {
337
499
  if (typeof XMLHttpRequest === 'undefined') {
338
- return fetch(url, { method: 'PUT', body: blob }).then((response) => {
500
+ return fetch(url, { method: 'PUT', body: blob, signal: callbacks.signal, headers }).then((response) => {
339
501
  if (!response.ok)
340
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);
341
507
  });
342
508
  }
343
509
  return new Promise((resolve, reject) => {
344
510
  const xhr = new XMLHttpRequest();
345
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 });
346
516
  xhr.upload.onprogress = (event) => {
347
517
  const total = event.total || blob.size;
348
518
  const progress = { loaded: event.loaded, total, percent: total ? Math.round((event.loaded / total) * 100) : 0 };
@@ -350,13 +520,64 @@ function uploadBlob(url, blob, callbacks, partNumber) {
350
520
  if (partNumber)
351
521
  callbacks.onPartProgress?.(partNumber, progress);
352
522
  };
523
+ const cleanup = () => callbacks.signal?.removeEventListener('abort', abort);
353
524
  xhr.onload = () => {
525
+ cleanup();
354
526
  if (xhr.status >= 200 && xhr.status < 300)
355
527
  resolve();
356
528
  else
357
529
  reject(new Error(`Upload failed with ${xhr.status}`));
358
530
  };
359
- xhr.onerror = () => reject(new Error('Upload failed'));
531
+ xhr.onerror = () => { cleanup(); reject(new Error('Upload failed')); };
532
+ xhr.onabort = () => { cleanup(); reject(abortError()); };
360
533
  xhr.send(blob);
361
534
  });
362
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
+ function throwIfAborted(signal) {
575
+ if (signal?.aborted)
576
+ throw abortError();
577
+ }
578
+ function abortError() {
579
+ return typeof DOMException === 'undefined' ? Object.assign(new Error('Upload cancelled'), { name: 'AbortError' }) : new DOMException('Upload cancelled', 'AbortError');
580
+ }
581
+ function isAbortError(error) {
582
+ return error instanceof Error && error.name === 'AbortError';
583
+ }
@@ -0,0 +1,329 @@
1
+ export type AssetOperationId = 'uploadAsset' | 'createUploadSession' | 'getUploadSession' | 'completeUploadSession' | 'cancelUploadSession' | 'createSignedUploadUrl' | 'createBucket' | 'listBuckets' | 'updateBucket' | 'deleteBucket' | 'getContentPolicy' | 'updateContentPolicy' | 'upsertTransformation' | 'listTransformations' | 'deleteTransformation' | 'createLifecycleRule' | 'listLifecycleRules' | 'deleteLifecycleRule' | 'createDomain' | 'listDomains' | 'verifyDomain' | 'deleteDomain' | 'getAnalytics' | 'getUsageSummary' | 'listEvents' | 'createWebhook' | 'listWebhooks' | 'deleteWebhook' | 'listWebhookDeliveries' | 'retryWebhookDelivery' | 'bulkAssets' | 'listCollections' | 'createCollection' | 'updateCollection' | 'deleteCollection' | 'listSavedSearches' | 'createSavedSearch' | 'updateSavedSearch' | 'deleteSavedSearch' | 'getAIConfig' | 'updateAIConfig' | 'getJob' | 'cancelJob' | 'listAssets' | 'getAsset' | 'patchAsset' | 'getAssetAI' | 'analyzeAsset' | 'moderateAsset' | 'getTranscript' | 'transcribeAsset' | 'smartCropAsset' | 'removeAssetBackground' | 'listAssetVersions' | 'createAssetBranch' | 'restoreAssetVersion' | 'promoteAssetBranch' | 'deleteAsset' | 'replaceAsset' | 'createSignedAssetUrl' | 'createSignedTransformUrl' | 'setAssetTags' | 'patchAssetMetadata' | 'purgeAsset';
2
+ export type AssetAPIScope = 'assets:read' | 'assets:write' | 'assets:process' | 'assets:admin';
3
+ export interface AssetContractOperation {
4
+ readonly id: AssetOperationId;
5
+ readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
6
+ readonly path: string;
7
+ readonly scope: AssetAPIScope;
8
+ }
9
+ export declare const assetContractOperations: readonly [{
10
+ readonly id: "uploadAsset";
11
+ readonly method: "POST";
12
+ readonly path: "/api/v1/assets/upload";
13
+ readonly scope: "assets:write";
14
+ }, {
15
+ readonly id: "createUploadSession";
16
+ readonly method: "POST";
17
+ readonly path: "/api/v1/assets/upload-sessions";
18
+ readonly scope: "assets:write";
19
+ }, {
20
+ readonly id: "getUploadSession";
21
+ readonly method: "GET";
22
+ readonly path: "/api/v1/assets/upload-sessions/{sessionID}";
23
+ readonly scope: "assets:read";
24
+ }, {
25
+ readonly id: "completeUploadSession";
26
+ readonly method: "POST";
27
+ readonly path: "/api/v1/assets/upload-sessions/{sessionID}/complete";
28
+ readonly scope: "assets:write";
29
+ }, {
30
+ readonly id: "cancelUploadSession";
31
+ readonly method: "DELETE";
32
+ readonly path: "/api/v1/assets/upload-sessions/{sessionID}";
33
+ readonly scope: "assets:write";
34
+ }, {
35
+ readonly id: "createSignedUploadUrl";
36
+ readonly method: "POST";
37
+ readonly path: "/api/v1/assets/upload-url";
38
+ readonly scope: "assets:write";
39
+ }, {
40
+ readonly id: "createBucket";
41
+ readonly method: "POST";
42
+ readonly path: "/api/v1/assets/buckets";
43
+ readonly scope: "assets:admin";
44
+ }, {
45
+ readonly id: "listBuckets";
46
+ readonly method: "GET";
47
+ readonly path: "/api/v1/assets/buckets";
48
+ readonly scope: "assets:read";
49
+ }, {
50
+ readonly id: "updateBucket";
51
+ readonly method: "PUT";
52
+ readonly path: "/api/v1/assets/buckets/{bucketID}";
53
+ readonly scope: "assets:admin";
54
+ }, {
55
+ readonly id: "deleteBucket";
56
+ readonly method: "DELETE";
57
+ readonly path: "/api/v1/assets/buckets/{bucketID}";
58
+ readonly scope: "assets:admin";
59
+ }, {
60
+ readonly id: "getContentPolicy";
61
+ readonly method: "GET";
62
+ readonly path: "/api/v1/assets/policy";
63
+ readonly scope: "assets:read";
64
+ }, {
65
+ readonly id: "updateContentPolicy";
66
+ readonly method: "PUT";
67
+ readonly path: "/api/v1/assets/policy";
68
+ readonly scope: "assets:admin";
69
+ }, {
70
+ readonly id: "upsertTransformation";
71
+ readonly method: "POST";
72
+ readonly path: "/api/v1/assets/transformations";
73
+ readonly scope: "assets:process";
74
+ }, {
75
+ readonly id: "listTransformations";
76
+ readonly method: "GET";
77
+ readonly path: "/api/v1/assets/transformations";
78
+ readonly scope: "assets:read";
79
+ }, {
80
+ readonly id: "deleteTransformation";
81
+ readonly method: "DELETE";
82
+ readonly path: "/api/v1/assets/transformations/{name}";
83
+ readonly scope: "assets:process";
84
+ }, {
85
+ readonly id: "createLifecycleRule";
86
+ readonly method: "POST";
87
+ readonly path: "/api/v1/assets/lifecycle-rules";
88
+ readonly scope: "assets:admin";
89
+ }, {
90
+ readonly id: "listLifecycleRules";
91
+ readonly method: "GET";
92
+ readonly path: "/api/v1/assets/lifecycle-rules";
93
+ readonly scope: "assets:read";
94
+ }, {
95
+ readonly id: "deleteLifecycleRule";
96
+ readonly method: "DELETE";
97
+ readonly path: "/api/v1/assets/lifecycle-rules/{ruleID}";
98
+ readonly scope: "assets:admin";
99
+ }, {
100
+ readonly id: "createDomain";
101
+ readonly method: "POST";
102
+ readonly path: "/api/v1/assets/domains";
103
+ readonly scope: "assets:admin";
104
+ }, {
105
+ readonly id: "listDomains";
106
+ readonly method: "GET";
107
+ readonly path: "/api/v1/assets/domains";
108
+ readonly scope: "assets:read";
109
+ }, {
110
+ readonly id: "verifyDomain";
111
+ readonly method: "POST";
112
+ readonly path: "/api/v1/assets/domains/{domainID}/verify";
113
+ readonly scope: "assets:admin";
114
+ }, {
115
+ readonly id: "deleteDomain";
116
+ readonly method: "DELETE";
117
+ readonly path: "/api/v1/assets/domains/{domainID}";
118
+ readonly scope: "assets:admin";
119
+ }, {
120
+ readonly id: "getAnalytics";
121
+ readonly method: "GET";
122
+ readonly path: "/api/v1/assets/analytics";
123
+ readonly scope: "assets:read";
124
+ }, {
125
+ readonly id: "getUsageSummary";
126
+ readonly method: "GET";
127
+ readonly path: "/api/v1/assets/summary";
128
+ readonly scope: "assets:read";
129
+ }, {
130
+ readonly id: "listEvents";
131
+ readonly method: "GET";
132
+ readonly path: "/api/v1/assets/events";
133
+ readonly scope: "assets:read";
134
+ }, {
135
+ readonly id: "createWebhook";
136
+ readonly method: "POST";
137
+ readonly path: "/api/v1/assets/webhooks";
138
+ readonly scope: "assets:admin";
139
+ }, {
140
+ readonly id: "listWebhooks";
141
+ readonly method: "GET";
142
+ readonly path: "/api/v1/assets/webhooks";
143
+ readonly scope: "assets:read";
144
+ }, {
145
+ readonly id: "deleteWebhook";
146
+ readonly method: "DELETE";
147
+ readonly path: "/api/v1/assets/webhooks/{webhookID}";
148
+ readonly scope: "assets:admin";
149
+ }, {
150
+ readonly id: "listWebhookDeliveries";
151
+ readonly method: "GET";
152
+ readonly path: "/api/v1/assets/webhooks/{webhookID}/deliveries";
153
+ readonly scope: "assets:read";
154
+ }, {
155
+ readonly id: "retryWebhookDelivery";
156
+ readonly method: "POST";
157
+ readonly path: "/api/v1/assets/webhook-deliveries/{deliveryID}/retry";
158
+ readonly scope: "assets:admin";
159
+ }, {
160
+ readonly id: "bulkAssets";
161
+ readonly method: "POST";
162
+ readonly path: "/api/v1/assets/bulk";
163
+ readonly scope: "assets:write";
164
+ }, {
165
+ readonly id: "listCollections";
166
+ readonly method: "GET";
167
+ readonly path: "/api/v1/assets/collections";
168
+ readonly scope: "assets:read";
169
+ }, {
170
+ readonly id: "createCollection";
171
+ readonly method: "POST";
172
+ readonly path: "/api/v1/assets/collections";
173
+ readonly scope: "assets:write";
174
+ }, {
175
+ readonly id: "updateCollection";
176
+ readonly method: "PUT";
177
+ readonly path: "/api/v1/assets/collections/{collectionID}";
178
+ readonly scope: "assets:write";
179
+ }, {
180
+ readonly id: "deleteCollection";
181
+ readonly method: "DELETE";
182
+ readonly path: "/api/v1/assets/collections/{collectionID}";
183
+ readonly scope: "assets:write";
184
+ }, {
185
+ readonly id: "listSavedSearches";
186
+ readonly method: "GET";
187
+ readonly path: "/api/v1/assets/saved-searches";
188
+ readonly scope: "assets:read";
189
+ }, {
190
+ readonly id: "createSavedSearch";
191
+ readonly method: "POST";
192
+ readonly path: "/api/v1/assets/saved-searches";
193
+ readonly scope: "assets:write";
194
+ }, {
195
+ readonly id: "updateSavedSearch";
196
+ readonly method: "PUT";
197
+ readonly path: "/api/v1/assets/saved-searches/{searchID}";
198
+ readonly scope: "assets:write";
199
+ }, {
200
+ readonly id: "deleteSavedSearch";
201
+ readonly method: "DELETE";
202
+ readonly path: "/api/v1/assets/saved-searches/{searchID}";
203
+ readonly scope: "assets:write";
204
+ }, {
205
+ readonly id: "getAIConfig";
206
+ readonly method: "GET";
207
+ readonly path: "/api/v1/assets/ai/config";
208
+ readonly scope: "assets:read";
209
+ }, {
210
+ readonly id: "updateAIConfig";
211
+ readonly method: "PUT";
212
+ readonly path: "/api/v1/assets/ai/config";
213
+ readonly scope: "assets:admin";
214
+ }, {
215
+ readonly id: "getJob";
216
+ readonly method: "GET";
217
+ readonly path: "/api/v1/assets/jobs/{jobID}";
218
+ readonly scope: "assets:read";
219
+ }, {
220
+ readonly id: "cancelJob";
221
+ readonly method: "POST";
222
+ readonly path: "/api/v1/assets/jobs/{jobID}/cancel";
223
+ readonly scope: "assets:process";
224
+ }, {
225
+ readonly id: "listAssets";
226
+ readonly method: "GET";
227
+ readonly path: "/api/v1/assets";
228
+ readonly scope: "assets:read";
229
+ }, {
230
+ readonly id: "getAsset";
231
+ readonly method: "GET";
232
+ readonly path: "/api/v1/assets/{assetID}";
233
+ readonly scope: "assets:read";
234
+ }, {
235
+ readonly id: "patchAsset";
236
+ readonly method: "PATCH";
237
+ readonly path: "/api/v1/assets/{assetID}";
238
+ readonly scope: "assets:write";
239
+ }, {
240
+ readonly id: "getAssetAI";
241
+ readonly method: "GET";
242
+ readonly path: "/api/v1/assets/{assetID}/ai";
243
+ readonly scope: "assets:read";
244
+ }, {
245
+ readonly id: "analyzeAsset";
246
+ readonly method: "POST";
247
+ readonly path: "/api/v1/assets/{assetID}/ai/analyze";
248
+ readonly scope: "assets:process";
249
+ }, {
250
+ readonly id: "moderateAsset";
251
+ readonly method: "POST";
252
+ readonly path: "/api/v1/assets/{assetID}/moderation";
253
+ readonly scope: "assets:process";
254
+ }, {
255
+ readonly id: "getTranscript";
256
+ readonly method: "GET";
257
+ readonly path: "/api/v1/assets/{assetID}/transcript";
258
+ readonly scope: "assets:read";
259
+ }, {
260
+ readonly id: "transcribeAsset";
261
+ readonly method: "POST";
262
+ readonly path: "/api/v1/assets/{assetID}/transcript";
263
+ readonly scope: "assets:process";
264
+ }, {
265
+ readonly id: "smartCropAsset";
266
+ readonly method: "POST";
267
+ readonly path: "/api/v1/assets/{assetID}/smart-crop";
268
+ readonly scope: "assets:process";
269
+ }, {
270
+ readonly id: "removeAssetBackground";
271
+ readonly method: "POST";
272
+ readonly path: "/api/v1/assets/{assetID}/background-remove";
273
+ readonly scope: "assets:process";
274
+ }, {
275
+ readonly id: "listAssetVersions";
276
+ readonly method: "GET";
277
+ readonly path: "/api/v1/assets/{assetID}/versions";
278
+ readonly scope: "assets:read";
279
+ }, {
280
+ readonly id: "createAssetBranch";
281
+ readonly method: "POST";
282
+ readonly path: "/api/v1/assets/{assetID}/branches";
283
+ readonly scope: "assets:write";
284
+ }, {
285
+ readonly id: "restoreAssetVersion";
286
+ readonly method: "POST";
287
+ readonly path: "/api/v1/assets/{assetID}/versions/{versionID}/restore";
288
+ readonly scope: "assets:write";
289
+ }, {
290
+ readonly id: "promoteAssetBranch";
291
+ readonly method: "POST";
292
+ readonly path: "/api/v1/assets/{assetID}/branches/{branch}/promote";
293
+ readonly scope: "assets:write";
294
+ }, {
295
+ readonly id: "deleteAsset";
296
+ readonly method: "DELETE";
297
+ readonly path: "/api/v1/assets/{assetID}";
298
+ readonly scope: "assets:admin";
299
+ }, {
300
+ readonly id: "replaceAsset";
301
+ readonly method: "PUT";
302
+ readonly path: "/api/v1/assets/{assetID}/replace";
303
+ readonly scope: "assets:write";
304
+ }, {
305
+ readonly id: "createSignedAssetUrl";
306
+ readonly method: "POST";
307
+ readonly path: "/api/v1/assets/{assetID}/signed-url";
308
+ readonly scope: "assets:read";
309
+ }, {
310
+ readonly id: "createSignedTransformUrl";
311
+ readonly method: "POST";
312
+ readonly path: "/api/v1/assets/{assetID}/transform-url";
313
+ readonly scope: "assets:process";
314
+ }, {
315
+ readonly id: "setAssetTags";
316
+ readonly method: "PUT";
317
+ readonly path: "/api/v1/assets/{assetID}/tags";
318
+ readonly scope: "assets:write";
319
+ }, {
320
+ readonly id: "patchAssetMetadata";
321
+ readonly method: "PATCH";
322
+ readonly path: "/api/v1/assets/{assetID}/metadata";
323
+ readonly scope: "assets:write";
324
+ }, {
325
+ readonly id: "purgeAsset";
326
+ readonly method: "POST";
327
+ readonly path: "/api/v1/assets/{assetID}/purge";
328
+ readonly scope: "assets:admin";
329
+ }];
@@ -0,0 +1,66 @@
1
+ export const assetContractOperations = [
2
+ { id: 'uploadAsset', method: 'POST', path: '/api/v1/assets/upload', scope: 'assets:write' },
3
+ { id: 'createUploadSession', method: 'POST', path: '/api/v1/assets/upload-sessions', scope: 'assets:write' },
4
+ { id: 'getUploadSession', method: 'GET', path: '/api/v1/assets/upload-sessions/{sessionID}', scope: 'assets:read' },
5
+ { id: 'completeUploadSession', method: 'POST', path: '/api/v1/assets/upload-sessions/{sessionID}/complete', scope: 'assets:write' },
6
+ { id: 'cancelUploadSession', method: 'DELETE', path: '/api/v1/assets/upload-sessions/{sessionID}', scope: 'assets:write' },
7
+ { id: 'createSignedUploadUrl', method: 'POST', path: '/api/v1/assets/upload-url', scope: 'assets:write' },
8
+ { id: 'createBucket', method: 'POST', path: '/api/v1/assets/buckets', scope: 'assets:admin' },
9
+ { id: 'listBuckets', method: 'GET', path: '/api/v1/assets/buckets', scope: 'assets:read' },
10
+ { id: 'updateBucket', method: 'PUT', path: '/api/v1/assets/buckets/{bucketID}', scope: 'assets:admin' },
11
+ { id: 'deleteBucket', method: 'DELETE', path: '/api/v1/assets/buckets/{bucketID}', scope: 'assets:admin' },
12
+ { id: 'getContentPolicy', method: 'GET', path: '/api/v1/assets/policy', scope: 'assets:read' },
13
+ { id: 'updateContentPolicy', method: 'PUT', path: '/api/v1/assets/policy', scope: 'assets:admin' },
14
+ { id: 'upsertTransformation', method: 'POST', path: '/api/v1/assets/transformations', scope: 'assets:process' },
15
+ { id: 'listTransformations', method: 'GET', path: '/api/v1/assets/transformations', scope: 'assets:read' },
16
+ { id: 'deleteTransformation', method: 'DELETE', path: '/api/v1/assets/transformations/{name}', scope: 'assets:process' },
17
+ { id: 'createLifecycleRule', method: 'POST', path: '/api/v1/assets/lifecycle-rules', scope: 'assets:admin' },
18
+ { id: 'listLifecycleRules', method: 'GET', path: '/api/v1/assets/lifecycle-rules', scope: 'assets:read' },
19
+ { id: 'deleteLifecycleRule', method: 'DELETE', path: '/api/v1/assets/lifecycle-rules/{ruleID}', scope: 'assets:admin' },
20
+ { id: 'createDomain', method: 'POST', path: '/api/v1/assets/domains', scope: 'assets:admin' },
21
+ { id: 'listDomains', method: 'GET', path: '/api/v1/assets/domains', scope: 'assets:read' },
22
+ { id: 'verifyDomain', method: 'POST', path: '/api/v1/assets/domains/{domainID}/verify', scope: 'assets:admin' },
23
+ { id: 'deleteDomain', method: 'DELETE', path: '/api/v1/assets/domains/{domainID}', scope: 'assets:admin' },
24
+ { id: 'getAnalytics', method: 'GET', path: '/api/v1/assets/analytics', scope: 'assets:read' },
25
+ { id: 'getUsageSummary', method: 'GET', path: '/api/v1/assets/summary', scope: 'assets:read' },
26
+ { id: 'listEvents', method: 'GET', path: '/api/v1/assets/events', scope: 'assets:read' },
27
+ { id: 'createWebhook', method: 'POST', path: '/api/v1/assets/webhooks', scope: 'assets:admin' },
28
+ { id: 'listWebhooks', method: 'GET', path: '/api/v1/assets/webhooks', scope: 'assets:read' },
29
+ { id: 'deleteWebhook', method: 'DELETE', path: '/api/v1/assets/webhooks/{webhookID}', scope: 'assets:admin' },
30
+ { id: 'listWebhookDeliveries', method: 'GET', path: '/api/v1/assets/webhooks/{webhookID}/deliveries', scope: 'assets:read' },
31
+ { id: 'retryWebhookDelivery', method: 'POST', path: '/api/v1/assets/webhook-deliveries/{deliveryID}/retry', scope: 'assets:admin' },
32
+ { id: 'bulkAssets', method: 'POST', path: '/api/v1/assets/bulk', scope: 'assets:write' },
33
+ { id: 'listCollections', method: 'GET', path: '/api/v1/assets/collections', scope: 'assets:read' },
34
+ { id: 'createCollection', method: 'POST', path: '/api/v1/assets/collections', scope: 'assets:write' },
35
+ { id: 'updateCollection', method: 'PUT', path: '/api/v1/assets/collections/{collectionID}', scope: 'assets:write' },
36
+ { id: 'deleteCollection', method: 'DELETE', path: '/api/v1/assets/collections/{collectionID}', scope: 'assets:write' },
37
+ { id: 'listSavedSearches', method: 'GET', path: '/api/v1/assets/saved-searches', scope: 'assets:read' },
38
+ { id: 'createSavedSearch', method: 'POST', path: '/api/v1/assets/saved-searches', scope: 'assets:write' },
39
+ { id: 'updateSavedSearch', method: 'PUT', path: '/api/v1/assets/saved-searches/{searchID}', scope: 'assets:write' },
40
+ { id: 'deleteSavedSearch', method: 'DELETE', path: '/api/v1/assets/saved-searches/{searchID}', scope: 'assets:write' },
41
+ { id: 'getAIConfig', method: 'GET', path: '/api/v1/assets/ai/config', scope: 'assets:read' },
42
+ { id: 'updateAIConfig', method: 'PUT', path: '/api/v1/assets/ai/config', scope: 'assets:admin' },
43
+ { id: 'getJob', method: 'GET', path: '/api/v1/assets/jobs/{jobID}', scope: 'assets:read' },
44
+ { id: 'cancelJob', method: 'POST', path: '/api/v1/assets/jobs/{jobID}/cancel', scope: 'assets:process' },
45
+ { id: 'listAssets', method: 'GET', path: '/api/v1/assets', scope: 'assets:read' },
46
+ { id: 'getAsset', method: 'GET', path: '/api/v1/assets/{assetID}', scope: 'assets:read' },
47
+ { id: 'patchAsset', method: 'PATCH', path: '/api/v1/assets/{assetID}', scope: 'assets:write' },
48
+ { id: 'getAssetAI', method: 'GET', path: '/api/v1/assets/{assetID}/ai', scope: 'assets:read' },
49
+ { id: 'analyzeAsset', method: 'POST', path: '/api/v1/assets/{assetID}/ai/analyze', scope: 'assets:process' },
50
+ { id: 'moderateAsset', method: 'POST', path: '/api/v1/assets/{assetID}/moderation', scope: 'assets:process' },
51
+ { id: 'getTranscript', method: 'GET', path: '/api/v1/assets/{assetID}/transcript', scope: 'assets:read' },
52
+ { id: 'transcribeAsset', method: 'POST', path: '/api/v1/assets/{assetID}/transcript', scope: 'assets:process' },
53
+ { id: 'smartCropAsset', method: 'POST', path: '/api/v1/assets/{assetID}/smart-crop', scope: 'assets:process' },
54
+ { id: 'removeAssetBackground', method: 'POST', path: '/api/v1/assets/{assetID}/background-remove', scope: 'assets:process' },
55
+ { id: 'listAssetVersions', method: 'GET', path: '/api/v1/assets/{assetID}/versions', scope: 'assets:read' },
56
+ { id: 'createAssetBranch', method: 'POST', path: '/api/v1/assets/{assetID}/branches', scope: 'assets:write' },
57
+ { id: 'restoreAssetVersion', method: 'POST', path: '/api/v1/assets/{assetID}/versions/{versionID}/restore', scope: 'assets:write' },
58
+ { id: 'promoteAssetBranch', method: 'POST', path: '/api/v1/assets/{assetID}/branches/{branch}/promote', scope: 'assets:write' },
59
+ { id: 'deleteAsset', method: 'DELETE', path: '/api/v1/assets/{assetID}', scope: 'assets:admin' },
60
+ { id: 'replaceAsset', method: 'PUT', path: '/api/v1/assets/{assetID}/replace', scope: 'assets:write' },
61
+ { id: 'createSignedAssetUrl', method: 'POST', path: '/api/v1/assets/{assetID}/signed-url', scope: 'assets:read' },
62
+ { id: 'createSignedTransformUrl', method: 'POST', path: '/api/v1/assets/{assetID}/transform-url', scope: 'assets:process' },
63
+ { id: 'setAssetTags', method: 'PUT', path: '/api/v1/assets/{assetID}/tags', scope: 'assets:write' },
64
+ { id: 'patchAssetMetadata', method: 'PATCH', path: '/api/v1/assets/{assetID}/metadata', scope: 'assets:write' },
65
+ { id: 'purgeAsset', method: 'POST', path: '/api/v1/assets/{assetID}/purge', scope: 'assets:admin' },
66
+ ];
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { StackShift, StackShiftAPIError } from './client.js';
2
2
  export { AssetsClient, assetTransformOptions, assetTransformPresets, assetTransformSpec, getAssetTransformPreset } from './assets.js';
3
+ export { createResponsiveImage, namedResponsiveSource, uploadBrowserFile } from './assets-framework.js';
4
+ export type { ArtDirectionSource, ResponsiveImageDescriptor, ResponsiveImageOptions } from './assets-framework.js';
5
+ export { assetContractOperations } from './generated/assets-contract.js';
6
+ export type { AssetAPIScope, AssetContractOperation, AssetOperationId } from './generated/assets-contract.js';
3
7
  export { MailBouncesClient, MailClient, MailDomainsClient, MailEventsClient, MailLimitsClient, MailMessagesClient, MailScheduledClient, MailBatchClient, MailInboundClient, MailAnalyticsClient, MailOtpChallengesClient, MailOtpClient, MailReputationClient, MailReputationEventsClient, MailSuppressionsClient, MailTemplatesClient, MailWebhooksClient, } from './mail.js';
4
8
  export { ProjectsClient } from './projects.js';
5
9
  export type { Asset, AssetAIAnnotation, AssetAIInfo, AssetCollection, AssetListOptions, AssetListResult, AssetModerationResult, AssetSavedSearch, AssetSignedUploadUrl, AssetSignedUploadUrlOptions, AssetSignedUrl, AssetSignedUrlOptions, AssetTranscript, AssetTransformOptions, AssetTransformPreset, AssetTransformPresetCategory, AssetUploadOptions, AssetVisibility, AssetVersion, JsonValue, MailAddress, MailAddressInput, MailDnsRecord, MailDomain, MailDomainListResult, MailDomainStatus, MailIdempotencyStatus, MailLog, MailMessage, MailMessageListOptions, MailMessageListResult, MailMessageSummary, MailRecordStatus, MailSendAttempt, MailSendInput, MailSendResponse, MailStatus, Project, ProjectListOptions, StackShiftOptions, } from './types.js';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { StackShift, StackShiftAPIError } from './client.js';
2
2
  export { AssetsClient, assetTransformOptions, assetTransformPresets, assetTransformSpec, getAssetTransformPreset } from './assets.js';
3
+ export { createResponsiveImage, namedResponsiveSource, uploadBrowserFile } from './assets-framework.js';
4
+ export { assetContractOperations } from './generated/assets-contract.js';
3
5
  export { MailBouncesClient, MailClient, MailDomainsClient, MailEventsClient, MailLimitsClient, MailMessagesClient, MailScheduledClient, MailBatchClient, MailInboundClient, MailAnalyticsClient, MailOtpChallengesClient, MailOtpClient, MailReputationClient, MailReputationEventsClient, MailSuppressionsClient, MailTemplatesClient, MailWebhooksClient, } from './mail.js';
4
6
  export { ProjectsClient } from './projects.js';
package/dist/types.d.ts CHANGED
@@ -149,12 +149,13 @@ export interface Asset {
149
149
  size: number;
150
150
  checksum_sha256: string;
151
151
  visibility: AssetVisibility;
152
- status: 'uploading' | 'available' | 'deleted' | 'failed';
152
+ status: 'processing' | 'ready' | 'quarantined' | 'failed' | 'deleting' | 'deleted';
153
153
  replication_status: 'pending' | 'replicated' | 'degraded' | 'failed';
154
154
  scan_status?: 'pending' | 'clean' | 'infected' | 'failed' | 'skipped';
155
155
  scan_result?: string;
156
156
  quarantine_status?: 'none' | 'infected' | 'policy';
157
157
  video_status?: 'none' | 'pending' | 'processing' | 'ready' | 'failed' | 'skipped';
158
+ video_progress_percent?: number;
158
159
  ai_status?: 'pending' | 'processing' | 'ready' | 'failed' | 'skipped';
159
160
  moderation_status?: 'pending' | 'processing' | 'clean' | 'flagged' | 'quarantined' | 'failed';
160
161
  moderation_reason?: string;
@@ -166,6 +167,8 @@ export interface Asset {
166
167
  video?: AssetVideoInfo;
167
168
  ai?: AssetAIInfo;
168
169
  url?: string;
170
+ generation: number;
171
+ revision: number;
169
172
  created_at: string;
170
173
  updated_at: string;
171
174
  }
@@ -257,6 +260,13 @@ export interface AssetListOptions {
257
260
  type?: string;
258
261
  folder?: string;
259
262
  checksum?: string;
263
+ status?: Asset['status'];
264
+ scanStatus?: string;
265
+ replicationStatus?: string;
266
+ moderationStatus?: string;
267
+ quarantineStatus?: string;
268
+ sort?: 'created_at' | 'updated_at' | 'size' | 'key' | 'original_name' | 'mime_type';
269
+ direction?: 'asc' | 'desc';
260
270
  limit?: number;
261
271
  cursor?: string;
262
272
  }
@@ -282,6 +292,55 @@ export interface AssetSignedUploadUrlOptions extends AssetUploadOptions {
282
292
  partSize?: number;
283
293
  expiresIn?: string;
284
294
  maxBytes?: number;
295
+ idempotencyKey?: string;
296
+ /** Resume an existing multipart upload instead of creating another session. */
297
+ uploadSessionId?: string;
298
+ /** Multipart worker count. Defaults to 4 and is capped at 8. */
299
+ concurrency?: number;
300
+ /** Attempts per multipart request. Defaults to 4 and is capped at 8. */
301
+ retryAttempts?: number;
302
+ }
303
+ export interface AssetBucket {
304
+ id: string;
305
+ name: string;
306
+ default_visibility: AssetVisibility;
307
+ cache_control?: string;
308
+ versioning: boolean;
309
+ retention_days: number;
310
+ max_object_bytes?: number;
311
+ allowed_mime_types: string[];
312
+ home_region: string;
313
+ replication_policy: 'global-3';
314
+ cors_origins: string[];
315
+ allowed_origins: string[];
316
+ lifecycle_policy: Record<string, unknown>;
317
+ custom_domain_id?: string;
318
+ revision: number;
319
+ created_at: string;
320
+ updated_at: string;
321
+ }
322
+ export interface AssetBucketInput {
323
+ name?: string;
324
+ default_visibility?: AssetVisibility;
325
+ cache_control?: string;
326
+ versioning?: boolean;
327
+ retention_days?: number;
328
+ max_object_bytes?: number;
329
+ allowed_mime_types?: string[];
330
+ home_region?: string;
331
+ replication_policy?: 'global-3';
332
+ cors_origins?: string[];
333
+ allowed_origins?: string[];
334
+ lifecycle_policy?: Record<string, unknown>;
335
+ custom_domain_id?: string;
336
+ }
337
+ export interface AssetPatchInput {
338
+ bucket?: string;
339
+ key?: string;
340
+ original_name?: string;
341
+ cache_control?: string;
342
+ clear_cache_control?: boolean;
343
+ visibility?: AssetVisibility;
285
344
  }
286
345
  export interface AssetSignedUploadUrl {
287
346
  upload_session_id: string;
@@ -311,10 +370,24 @@ export interface AssetUploadProgressCallbacks {
311
370
  total: number;
312
371
  percent: number;
313
372
  }) => void;
373
+ /** Cancels in-flight browser requests and the server-side upload session. */
374
+ signal?: AbortSignal;
314
375
  }
315
376
  export interface AssetUsageSummary {
316
377
  total_assets: number;
317
378
  total_bytes: number;
379
+ period_ingress_bytes: number;
380
+ period_egress_bytes: number;
381
+ period_origin_bytes: number;
382
+ period_verification_bytes: number;
383
+ period_transform_count: number;
384
+ period_ai_requests: number;
385
+ period_logical_storage_byte_hours: number;
386
+ period_physical_storage_byte_hours: number;
387
+ period_derived_storage_byte_hours: number;
388
+ period_transform_compute_ms: number;
389
+ period_video_input_seconds: number;
390
+ period_ai_cost_micros: number;
318
391
  by_bucket: Record<string, number>;
319
392
  by_mime_family: Record<string, number>;
320
393
  by_visibility: Record<string, number>;
@@ -324,6 +397,97 @@ export interface AssetUsageSummary {
324
397
  total_bytes: number;
325
398
  }>;
326
399
  }
400
+ export interface AssetJobSubmission {
401
+ job_id: string;
402
+ status: string;
403
+ status_url: string;
404
+ }
405
+ export interface AssetJob {
406
+ id: string;
407
+ job_name: string;
408
+ status: string;
409
+ progress_percent: number;
410
+ phase: 'queued' | 'processing' | 'waiting' | 'ready' | 'failed' | 'cancelled' | string;
411
+ attempt: number;
412
+ max_attempts: number;
413
+ created_at: string;
414
+ started_at?: string;
415
+ completed_at?: string;
416
+ error?: string;
417
+ }
418
+ export interface AssetAIPolicy {
419
+ enabled_actions: Array<'analyze' | 'transcribe' | 'smart_crop' | 'background_remove'>;
420
+ monthly_spend_cap_micros: number;
421
+ revision: number;
422
+ }
423
+ export interface AssetAIAccountConfig {
424
+ provider: string;
425
+ configured: boolean;
426
+ actions: string[];
427
+ policy: AssetAIPolicy;
428
+ monthly_spend_used_micros: number;
429
+ }
430
+ export interface AssetLifecycleRule {
431
+ id: string;
432
+ name: string;
433
+ prefix?: string;
434
+ action: 'delete' | 'expire_versions';
435
+ age_days: number;
436
+ enabled: boolean;
437
+ created_at: string;
438
+ updated_at: string;
439
+ last_run_at?: string;
440
+ last_error?: string;
441
+ }
442
+ export interface AssetCustomDomain {
443
+ id: string;
444
+ domain: string;
445
+ status: string;
446
+ verification_name: string;
447
+ verification_value: string;
448
+ last_error?: string;
449
+ provider_domain_id?: string;
450
+ certificate_id?: string;
451
+ created_at: string;
452
+ updated_at: string;
453
+ verified_at?: string;
454
+ }
455
+ export interface AssetEvent {
456
+ id: string;
457
+ asset_id?: string;
458
+ type: string;
459
+ data: Record<string, unknown>;
460
+ created_at: string;
461
+ }
462
+ export interface AssetWebhook {
463
+ id: string;
464
+ url: string;
465
+ event_types: string[];
466
+ status: 'active' | 'disabled';
467
+ failure_count: number;
468
+ last_success_at?: string;
469
+ last_failure_at?: string;
470
+ disabled_at?: string;
471
+ created_by: string;
472
+ created_at: string;
473
+ updated_at: string;
474
+ }
475
+ export interface AssetWebhookDelivery {
476
+ id: string;
477
+ webhook_id: string;
478
+ event_id: string;
479
+ event_type: string;
480
+ payload: Record<string, unknown>;
481
+ status: 'pending' | 'delivering' | 'succeeded' | 'failed';
482
+ attempt_count: number;
483
+ next_attempt_at: string;
484
+ status_code?: number;
485
+ response_snippet?: string;
486
+ error_message?: string;
487
+ created_at: string;
488
+ updated_at: string;
489
+ delivered_at?: string;
490
+ }
327
491
  export interface AssetContentPolicy {
328
492
  allowed_mime_types: string[];
329
493
  max_image_bytes: number;
@@ -340,6 +504,8 @@ export interface AssetTransformOptions {
340
504
  crop?: AssetTransformCrop;
341
505
  format?: AssetTransformFormat;
342
506
  quality?: AssetTransformQuality;
507
+ gravity?: 'centre' | 'attention' | 'entropy';
508
+ animation?: 'first' | 'reject';
343
509
  }
344
510
  export interface AssetNamedTransformInput extends AssetTransformOptions {
345
511
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackshift-cloud/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Official JavaScript and TypeScript SDK for StackShift.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",