@ibanzajoe/uploader 0.3.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * SDK-level option and result types for UploaderClient.
3
+ *
4
+ * The upload-contract types below are VENDORED from @uploader/shared so that
5
+ * @ibanzajoe/uploader is self-contained for consumers outside this monorepo — one
6
+ * install, no @uploader/* or zod transitive deps. They mirror the API contract
7
+ * exactly; `contract.conformance.ts` asserts at typecheck time that they stay
8
+ * structurally identical to the shared definitions, failing the build on drift.
9
+ */
10
+ /** A stored file result returned by the upload API and SDK. Mirrors Filestack's FileResult. */
11
+ type FileResult = {
12
+ handle: string;
13
+ url: string;
14
+ filename: string;
15
+ mimetype: string;
16
+ size: number;
17
+ status: 'Stored';
18
+ uploadId?: string;
19
+ };
20
+ /** Response from the picker overlay / upload batch. */
21
+ type PickerResponse = {
22
+ filesUploaded: FileResult[];
23
+ filesFailed: FileResult[];
24
+ };
25
+ /** JSON payload that is base64-encoded and HMAC-signed as a security policy. */
26
+ type PolicySpec = {
27
+ /** Unix epoch seconds when the policy expires. */
28
+ expiry: number;
29
+ /** Allowed API calls, e.g. ["store", "read", "remove"]. */
30
+ call: string[];
31
+ /** Restrict to a specific file handle (read/remove). */
32
+ handle?: string;
33
+ /** Maximum file size in bytes (store). */
34
+ maxSize?: number;
35
+ /** Minimum file size in bytes (store). */
36
+ minSize?: number;
37
+ /** Restrict store destination path prefix. */
38
+ path?: string;
39
+ };
40
+ /** Options passed to UploaderClient constructor. */
41
+ /**
42
+ * Per-file delivery-protection mode (docs/13). Chosen by the developer at upload:
43
+ * public — anyone with the link.
44
+ * hotlink — only loads from an allowed origin (Origin/Referer allowlist).
45
+ * signed — requires a valid signed read URL (optionally origin-locked too).
46
+ * The chosen mode must be permitted by the account's plan or the server rejects
47
+ * the upload (403). Omitted → the file inherits the account's default mode.
48
+ */
49
+ type DeliveryProtection = 'public' | 'hotlink' | 'signed';
50
+ type UploaderClientOptions = {
51
+ /** Public API key (pk_...). */
52
+ apikey: string;
53
+ /** Base URL of the upload API. Defaults to the hosted endpoint. */
54
+ apiUrl?: string;
55
+ /** Optional signed security policy. */
56
+ security?: {
57
+ policy: string;
58
+ signature: string;
59
+ };
60
+ /**
61
+ * Direct-to-bucket upload control:
62
+ * undefined (default) — auto-detect via GET /api/capabilities.
63
+ * false — always use the proxied flow (skip direct upload + the probe).
64
+ * The server ultimately gates direct upload per account regardless.
65
+ */
66
+ directUpload?: boolean;
67
+ /**
68
+ * Client-level default delivery protection applied to every upload from this
69
+ * client (docs/13). Useful for a dedicated uploader configured for sensitive
70
+ * files. A per-upload `deliveryProtection` overrides it. Omitted → inherit the
71
+ * account default server-side.
72
+ */
73
+ deliveryProtection?: DeliveryProtection;
74
+ /**
75
+ * Client-level default per-file allowed origins (a domain lock layered on top
76
+ * of `signed`, or the allowlist for `hotlink`). Overridden per upload by
77
+ * `allowedOrigins`. Omitted → no per-file lock.
78
+ */
79
+ allowedOrigins?: string[];
80
+ };
81
+ /** Per-file upload options. */
82
+ type UploadOptions = {
83
+ /** Progress callback, value 0–100. */
84
+ onProgress?: (percent: number) => void;
85
+ /** Storage path prefix. */
86
+ path?: string;
87
+ /** Override the file's stored name. */
88
+ filename?: string;
89
+ /** AbortSignal to cancel the upload. */
90
+ signal?: AbortSignal;
91
+ /**
92
+ * Chunk size in bytes for multipart uploads.
93
+ * Defaults to ~5 MB; files larger than this threshold use multipart.
94
+ */
95
+ chunkSize?: number;
96
+ /**
97
+ * Per-upload delivery protection (docs/13). Overrides the client-level default
98
+ * for this file. Must be permitted by the account's plan. Omitted → the client
99
+ * default, else the account default.
100
+ */
101
+ deliveryProtection?: DeliveryProtection;
102
+ /**
103
+ * Per-upload allowed origins for this file's domain lock. Overrides the
104
+ * client-level default. Omitted → the client default, else no per-file lock.
105
+ */
106
+ allowedOrigins?: string[];
107
+ };
108
+ /** Options for uploadAll — extends per-file options with concurrency control. */
109
+ type UploadAllOptions = UploadOptions & {
110
+ /** Maximum concurrent uploads. Defaults to 3. */
111
+ concurrency?: number;
112
+ };
113
+
114
+ /**
115
+ * UploaderClient — headless upload client.
116
+ *
117
+ * Chooses single-shot (POST /api/store, multipart/form-data) vs multipart
118
+ * (start/part/complete) based on file size relative to MULTIPART_THRESHOLD
119
+ * (5 MB). Parts are retried individually with exponential backoff. Progress
120
+ * is emitted as a 0–100 integer. AbortSignal cancels in-flight work.
121
+ */
122
+
123
+ declare class UploaderClient {
124
+ #private;
125
+ readonly apikey: string;
126
+ readonly apiUrl: string;
127
+ readonly security: UploaderClientOptions['security'];
128
+ constructor(options: UploaderClientOptions);
129
+ /**
130
+ * Upload a single file.
131
+ *
132
+ * Automatically selects single-shot vs multipart upload based on file size.
133
+ * Emits progress via `opts.onProgress` (0–100). Respects `opts.signal` for
134
+ * cancellation. Retries network errors and 5xx responses up to 3 times with
135
+ * exponential backoff; 4xx errors are surfaced immediately.
136
+ *
137
+ * @throws {UploaderError} with code ABORTED | NETWORK_ERROR | SERVER_ERROR |
138
+ * CLIENT_ERROR | INVALID_RESPONSE
139
+ */
140
+ upload(file: File | Blob, opts?: UploadOptions): Promise<FileResult>;
141
+ /**
142
+ * Upload multiple files with concurrency limiting.
143
+ *
144
+ * Resolves once all uploads settle (fulfilled or rejected). The returned
145
+ * array preserves input order. `opts.concurrency` caps simultaneous
146
+ * in-flight uploads (default 3). Each file shares the same opts
147
+ * (including onProgress — the callback fires per-file, not aggregate).
148
+ *
149
+ * @returns Array of PromiseSettledResult in input order.
150
+ */
151
+ uploadAll(files: Array<File | Blob>, opts?: UploadAllOptions): Promise<PromiseSettledResult<FileResult>[]>;
152
+ }
153
+
154
+ export { type DeliveryProtection as D, type FileResult as F, type PickerResponse as P, type UploadAllOptions as U, type PolicySpec as a, type UploadOptions as b, UploaderClient as c, type UploaderClientOptions as d };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * SDK-level option and result types for UploaderClient.
3
+ *
4
+ * The upload-contract types below are VENDORED from @uploader/shared so that
5
+ * @ibanzajoe/uploader is self-contained for consumers outside this monorepo — one
6
+ * install, no @uploader/* or zod transitive deps. They mirror the API contract
7
+ * exactly; `contract.conformance.ts` asserts at typecheck time that they stay
8
+ * structurally identical to the shared definitions, failing the build on drift.
9
+ */
10
+ /** A stored file result returned by the upload API and SDK. Mirrors Filestack's FileResult. */
11
+ type FileResult = {
12
+ handle: string;
13
+ url: string;
14
+ filename: string;
15
+ mimetype: string;
16
+ size: number;
17
+ status: 'Stored';
18
+ uploadId?: string;
19
+ };
20
+ /** Response from the picker overlay / upload batch. */
21
+ type PickerResponse = {
22
+ filesUploaded: FileResult[];
23
+ filesFailed: FileResult[];
24
+ };
25
+ /** JSON payload that is base64-encoded and HMAC-signed as a security policy. */
26
+ type PolicySpec = {
27
+ /** Unix epoch seconds when the policy expires. */
28
+ expiry: number;
29
+ /** Allowed API calls, e.g. ["store", "read", "remove"]. */
30
+ call: string[];
31
+ /** Restrict to a specific file handle (read/remove). */
32
+ handle?: string;
33
+ /** Maximum file size in bytes (store). */
34
+ maxSize?: number;
35
+ /** Minimum file size in bytes (store). */
36
+ minSize?: number;
37
+ /** Restrict store destination path prefix. */
38
+ path?: string;
39
+ };
40
+ /** Options passed to UploaderClient constructor. */
41
+ /**
42
+ * Per-file delivery-protection mode (docs/13). Chosen by the developer at upload:
43
+ * public — anyone with the link.
44
+ * hotlink — only loads from an allowed origin (Origin/Referer allowlist).
45
+ * signed — requires a valid signed read URL (optionally origin-locked too).
46
+ * The chosen mode must be permitted by the account's plan or the server rejects
47
+ * the upload (403). Omitted → the file inherits the account's default mode.
48
+ */
49
+ type DeliveryProtection = 'public' | 'hotlink' | 'signed';
50
+ type UploaderClientOptions = {
51
+ /** Public API key (pk_...). */
52
+ apikey: string;
53
+ /** Base URL of the upload API. Defaults to the hosted endpoint. */
54
+ apiUrl?: string;
55
+ /** Optional signed security policy. */
56
+ security?: {
57
+ policy: string;
58
+ signature: string;
59
+ };
60
+ /**
61
+ * Direct-to-bucket upload control:
62
+ * undefined (default) — auto-detect via GET /api/capabilities.
63
+ * false — always use the proxied flow (skip direct upload + the probe).
64
+ * The server ultimately gates direct upload per account regardless.
65
+ */
66
+ directUpload?: boolean;
67
+ /**
68
+ * Client-level default delivery protection applied to every upload from this
69
+ * client (docs/13). Useful for a dedicated uploader configured for sensitive
70
+ * files. A per-upload `deliveryProtection` overrides it. Omitted → inherit the
71
+ * account default server-side.
72
+ */
73
+ deliveryProtection?: DeliveryProtection;
74
+ /**
75
+ * Client-level default per-file allowed origins (a domain lock layered on top
76
+ * of `signed`, or the allowlist for `hotlink`). Overridden per upload by
77
+ * `allowedOrigins`. Omitted → no per-file lock.
78
+ */
79
+ allowedOrigins?: string[];
80
+ };
81
+ /** Per-file upload options. */
82
+ type UploadOptions = {
83
+ /** Progress callback, value 0–100. */
84
+ onProgress?: (percent: number) => void;
85
+ /** Storage path prefix. */
86
+ path?: string;
87
+ /** Override the file's stored name. */
88
+ filename?: string;
89
+ /** AbortSignal to cancel the upload. */
90
+ signal?: AbortSignal;
91
+ /**
92
+ * Chunk size in bytes for multipart uploads.
93
+ * Defaults to ~5 MB; files larger than this threshold use multipart.
94
+ */
95
+ chunkSize?: number;
96
+ /**
97
+ * Per-upload delivery protection (docs/13). Overrides the client-level default
98
+ * for this file. Must be permitted by the account's plan. Omitted → the client
99
+ * default, else the account default.
100
+ */
101
+ deliveryProtection?: DeliveryProtection;
102
+ /**
103
+ * Per-upload allowed origins for this file's domain lock. Overrides the
104
+ * client-level default. Omitted → the client default, else no per-file lock.
105
+ */
106
+ allowedOrigins?: string[];
107
+ };
108
+ /** Options for uploadAll — extends per-file options with concurrency control. */
109
+ type UploadAllOptions = UploadOptions & {
110
+ /** Maximum concurrent uploads. Defaults to 3. */
111
+ concurrency?: number;
112
+ };
113
+
114
+ /**
115
+ * UploaderClient — headless upload client.
116
+ *
117
+ * Chooses single-shot (POST /api/store, multipart/form-data) vs multipart
118
+ * (start/part/complete) based on file size relative to MULTIPART_THRESHOLD
119
+ * (5 MB). Parts are retried individually with exponential backoff. Progress
120
+ * is emitted as a 0–100 integer. AbortSignal cancels in-flight work.
121
+ */
122
+
123
+ declare class UploaderClient {
124
+ #private;
125
+ readonly apikey: string;
126
+ readonly apiUrl: string;
127
+ readonly security: UploaderClientOptions['security'];
128
+ constructor(options: UploaderClientOptions);
129
+ /**
130
+ * Upload a single file.
131
+ *
132
+ * Automatically selects single-shot vs multipart upload based on file size.
133
+ * Emits progress via `opts.onProgress` (0–100). Respects `opts.signal` for
134
+ * cancellation. Retries network errors and 5xx responses up to 3 times with
135
+ * exponential backoff; 4xx errors are surfaced immediately.
136
+ *
137
+ * @throws {UploaderError} with code ABORTED | NETWORK_ERROR | SERVER_ERROR |
138
+ * CLIENT_ERROR | INVALID_RESPONSE
139
+ */
140
+ upload(file: File | Blob, opts?: UploadOptions): Promise<FileResult>;
141
+ /**
142
+ * Upload multiple files with concurrency limiting.
143
+ *
144
+ * Resolves once all uploads settle (fulfilled or rejected). The returned
145
+ * array preserves input order. `opts.concurrency` caps simultaneous
146
+ * in-flight uploads (default 3). Each file shares the same opts
147
+ * (including onProgress — the callback fires per-file, not aggregate).
148
+ *
149
+ * @returns Array of PromiseSettledResult in input order.
150
+ */
151
+ uploadAll(files: Array<File | Blob>, opts?: UploadAllOptions): Promise<PromiseSettledResult<FileResult>[]>;
152
+ }
153
+
154
+ export { type DeliveryProtection as D, type FileResult as F, type PickerResponse as P, type UploadAllOptions as U, type PolicySpec as a, type UploadOptions as b, UploaderClient as c, type UploaderClientOptions as d };
package/dist/core.cjs CHANGED
@@ -32,7 +32,8 @@ __export(core_exports, {
32
32
  quality: () => quality,
33
33
  resize: () => resize,
34
34
  rotate: () => rotate,
35
- transformUrl: () => transformUrl
35
+ transformUrl: () => transformUrl,
36
+ withSignedPolicy: () => withSignedPolicy
36
37
  });
37
38
  module.exports = __toCommonJS(core_exports);
38
39
 
@@ -68,6 +69,8 @@ function planChunks(file, chunkSize = DEFAULT_CHUNK_SIZE) {
68
69
  // src/core/client.ts
69
70
  var MAX_RETRIES = 3;
70
71
  var RETRY_BASE_MS = 200;
72
+ var MAX_DIRECT_PUT_BYTES = 5 * 1024 * 1024 * 1024;
73
+ var SHA256_MAX_BYTES = 64 * 1024 * 1024;
71
74
  function sleep(ms, signal) {
72
75
  return new Promise((resolve, reject) => {
73
76
  if (signal?.aborted) {
@@ -129,14 +132,153 @@ async function fetchWithRetry(url, init, signal, maxRetries = MAX_RETRIES) {
129
132
  }
130
133
  throw lastErr;
131
134
  }
135
+ async function sha256Hex(blob) {
136
+ try {
137
+ const c = globalThis.crypto;
138
+ if (!c?.subtle || blob.size > SHA256_MAX_BYTES) return void 0;
139
+ const digest = await c.subtle.digest("SHA-256", await blob.arrayBuffer());
140
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
141
+ } catch {
142
+ return void 0;
143
+ }
144
+ }
145
+ function xhrPut(url, body, opts) {
146
+ return new Promise((resolve, reject) => {
147
+ if (opts.signal?.aborted) {
148
+ reject(new UploaderError("ABORTED", "Upload aborted"));
149
+ return;
150
+ }
151
+ const xhr = new XMLHttpRequest();
152
+ xhr.open("PUT", url);
153
+ xhr.setRequestHeader("Content-Type", opts.contentType);
154
+ if (opts.onProgress) {
155
+ xhr.upload.onprogress = (e) => {
156
+ if (e.lengthComputable) {
157
+ opts.onProgress(Math.round(e.loaded / e.total * 95));
158
+ }
159
+ };
160
+ }
161
+ xhr.onload = () => {
162
+ if (xhr.status >= 200 && xhr.status < 300) {
163
+ resolve();
164
+ } else {
165
+ const code = xhr.status >= 400 && xhr.status < 500 ? "CLIENT_ERROR" : "SERVER_ERROR";
166
+ reject(new UploaderError(code, `Bucket PUT failed: HTTP ${xhr.status}`, xhr.status));
167
+ }
168
+ };
169
+ xhr.onerror = () => reject(new UploaderError("NETWORK_ERROR", "Bucket PUT network error"));
170
+ xhr.onabort = () => reject(new UploaderError("ABORTED", "Upload aborted"));
171
+ if (opts.signal) {
172
+ opts.signal.addEventListener("abort", () => xhr.abort(), { once: true });
173
+ }
174
+ xhr.send(body);
175
+ });
176
+ }
132
177
  var UploaderClient = class {
133
178
  apikey;
134
179
  apiUrl;
135
180
  security;
181
+ #directUploadOption;
182
+ /** Client-level default per-file delivery protection (docs/13). */
183
+ #deliveryProtection;
184
+ /** Client-level default per-file allowed origins (docs/13). */
185
+ #allowedOrigins;
186
+ /** Memoized capability probe — one request per client, shared across uploads. */
187
+ #capsPromise = null;
136
188
  constructor(options) {
137
189
  this.apikey = options.apikey;
138
190
  this.apiUrl = (options.apiUrl ?? "https://api.uploaderhq.io").replace(/\/$/, "");
139
191
  this.security = options.security;
192
+ this.#directUploadOption = options.directUpload;
193
+ this.#deliveryProtection = options.deliveryProtection;
194
+ this.#allowedOrigins = options.allowedOrigins;
195
+ }
196
+ /**
197
+ * Resolve the effective per-file protection for one upload: a per-upload value
198
+ * overrides the client-level default (docs/13). Returns an object carrying ONLY
199
+ * the keys that are set, so callers spread it into the request body and unset
200
+ * fields are omitted entirely — an old server ignores them and the file inherits
201
+ * the account mode (`null`).
202
+ */
203
+ #resolveProtection(opts) {
204
+ const out = {};
205
+ const mode = opts.deliveryProtection ?? this.#deliveryProtection;
206
+ if (mode !== void 0) out.deliveryProtection = mode;
207
+ const origins = opts.allowedOrigins ?? this.#allowedOrigins;
208
+ if (origins !== void 0) out.allowedOrigins = origins;
209
+ return out;
210
+ }
211
+ // ─── Capability negotiation ─────────────────────────────────────────────────
212
+ /**
213
+ * Probe GET /api/capabilities once per client and cache the result. Fails OPEN
214
+ * to the proxied flow ({ directUpload: false }) on any error/timeout, so a
215
+ * flaky probe never blocks uploads and old servers (404) are handled.
216
+ */
217
+ #getCapabilities() {
218
+ if (this.#directUploadOption === false) {
219
+ return Promise.resolve({ directUpload: false });
220
+ }
221
+ if (!this.#capsPromise) {
222
+ this.#capsPromise = fetch(`${this.apiUrl}/api/capabilities`, {
223
+ headers: this.#authHeaders()
224
+ }).then(async (res) => {
225
+ if (!res.ok) return { directUpload: false };
226
+ const body = await res.json().catch(() => ({}));
227
+ return { directUpload: body["directUpload"] === true };
228
+ }).catch(() => ({ directUpload: false }));
229
+ }
230
+ return this.#capsPromise;
231
+ }
232
+ // ─── Direct-to-bucket upload ────────────────────────────────────────────────
233
+ /**
234
+ * Presign → PUT-to-bucket → confirm. Bytes go browser → bucket directly; our
235
+ * server only signs and records. Used when the account has the directUpload
236
+ * capability and the file fits a single PUT.
237
+ */
238
+ async #uploadDirect(file, opts) {
239
+ const { onProgress, filename, signal } = opts;
240
+ const name = filename ?? (file instanceof File ? file.name : "upload");
241
+ const contentType = file instanceof File && file.type ? file.type : "application/octet-stream";
242
+ onProgress?.(0);
243
+ checkAbort(signal);
244
+ const protection = this.#resolveProtection(opts);
245
+ const presign = async () => {
246
+ const res = await fetchWithRetry(
247
+ `${this.apiUrl}/api/uploads/presign`,
248
+ {
249
+ method: "POST",
250
+ headers: { ...this.#authHeaders(), "Content-Type": "application/json" },
251
+ body: JSON.stringify({ filename: name, contentType, size: file.size, ...protection })
252
+ },
253
+ signal
254
+ );
255
+ return await res.json();
256
+ };
257
+ let signed = await presign();
258
+ const checksum = await sha256Hex(file);
259
+ try {
260
+ await xhrPut(signed.putUrl, file, { contentType: signed.contentType, onProgress, signal });
261
+ } catch (err) {
262
+ if (err instanceof UploaderError && err.statusCode === 403) {
263
+ signed = await presign();
264
+ await xhrPut(signed.putUrl, file, { contentType: signed.contentType, onProgress, signal });
265
+ } else {
266
+ throw err;
267
+ }
268
+ }
269
+ checkAbort(signal);
270
+ const confirmRes = await fetchWithRetry(
271
+ `${this.apiUrl}/api/uploads/confirm`,
272
+ {
273
+ method: "POST",
274
+ headers: { ...this.#authHeaders(), "Content-Type": "application/json" },
275
+ body: JSON.stringify({ handle: signed.handle, checksum })
276
+ },
277
+ signal
278
+ );
279
+ const body = await confirmRes.json();
280
+ onProgress?.(100);
281
+ return this.#parseFileResult(body);
140
282
  }
141
283
  // ─── Auth headers ──────────────────────────────────────────────────────────
142
284
  /**
@@ -164,6 +306,9 @@ var UploaderClient = class {
164
306
  const form = new FormData();
165
307
  form.append("file", file, filename ?? (file instanceof File ? file.name : "upload"));
166
308
  if (filename) form.append("filename", filename);
309
+ const protection = this.#resolveProtection(opts);
310
+ if (protection.deliveryProtection) form.append("deliveryProtection", protection.deliveryProtection);
311
+ if (protection.allowedOrigins) form.append("allowedOrigins", JSON.stringify(protection.allowedOrigins));
167
312
  const res = await fetchWithRetry(
168
313
  `${this.apiUrl}/api/store`,
169
314
  {
@@ -187,12 +332,13 @@ var UploaderClient = class {
187
332
  const mime = file instanceof File ? file.type : "application/octet-stream";
188
333
  onProgress?.(0);
189
334
  checkAbort(signal);
335
+ const protection = this.#resolveProtection(opts);
190
336
  const startRes = await fetchWithRetry(
191
337
  `${this.apiUrl}/api/upload/start`,
192
338
  {
193
339
  method: "POST",
194
340
  headers: { ...this.#authHeaders(), "Content-Type": "application/json" },
195
- body: JSON.stringify({ filename: name, mimetype: mime, size: file.size })
341
+ body: JSON.stringify({ filename: name, mimetype: mime, size: file.size, ...protection })
196
342
  },
197
343
  signal
198
344
  );
@@ -255,6 +401,10 @@ var UploaderClient = class {
255
401
  * CLIENT_ERROR | INVALID_RESPONSE
256
402
  */
257
403
  async upload(file, opts = {}) {
404
+ const caps = await this.#getCapabilities();
405
+ if (caps.directUpload && file.size <= MAX_DIRECT_PUT_BYTES) {
406
+ return this.#uploadDirect(file, opts);
407
+ }
258
408
  const plan = planChunks(file, opts.chunkSize);
259
409
  if (plan.mode === "single") {
260
410
  return this.#uploadSingleShot(file, opts);
@@ -334,6 +484,10 @@ function transformUrl({ handle, ops, apiUrl = "" }) {
334
484
  const chain = ops.map(serializeOp).join("/");
335
485
  return chain ? `${base}/${chain}/${handle}` : `${base}/${handle}`;
336
486
  }
487
+ function withSignedPolicy(url, security) {
488
+ const sep = url.includes("?") ? "&" : "?";
489
+ return `${url}${sep}policy=${encodeURIComponent(security.policy)}&signature=${encodeURIComponent(security.signature)}`;
490
+ }
337
491
  // Annotate the CommonJS export names for ESM import in node:
338
492
  0 && (module.exports = {
339
493
  DEFAULT_CHUNK_SIZE,
@@ -348,6 +502,7 @@ function transformUrl({ handle, ops, apiUrl = "" }) {
348
502
  quality,
349
503
  resize,
350
504
  rotate,
351
- transformUrl
505
+ transformUrl,
506
+ withSignedPolicy
352
507
  });
353
508
  //# sourceMappingURL=core.cjs.map