@masters-union/union-stack 0.1.7 → 0.1.9

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.
@@ -60,10 +60,6 @@ var Uploader = class {
60
60
  source: "local"
61
61
  };
62
62
  }
63
- /**
64
- * Upload a single Blob/File. Returns the persisted UploadedFile.
65
- * Fires Filestack-style per-file callbacks from `opts`.
66
- */
67
63
  async upload(file, opts = {}) {
68
64
  const picked = this.describe(file, { filename: opts.filename, mimeType: opts.mimeType });
69
65
  opts.onFileUploadStarted?.(picked);
@@ -79,8 +75,11 @@ var Uploader = class {
79
75
  size: completed.size,
80
76
  mimetype: completed.mimetype,
81
77
  filename: completed.filename,
78
+ key: completed.key,
79
+ container: completed.container,
82
80
  status: "Stored",
83
- etag: completed.etag
81
+ etag: completed.etag,
82
+ metadata: completed.metadata
84
83
  };
85
84
  opts.onFileUploadFinished?.(uploaded);
86
85
  return uploaded;
@@ -254,6 +253,31 @@ function withSessionId(err, sessionId) {
254
253
 
255
254
  // src/client.ts
256
255
  var API_BASE = "https://api.unionstack.link/v1";
256
+ var PRELOADER_STYLE_ID = "unionstack-preloader-styles";
257
+ function showPickerPreloader() {
258
+ if (typeof document === "undefined") return () => {
259
+ };
260
+ if (!document.getElementById(PRELOADER_STYLE_ID)) {
261
+ const style = document.createElement("style");
262
+ style.id = PRELOADER_STYLE_ID;
263
+ style.textContent = "@keyframes us-preload-spin{to{transform:rotate(360deg)}}@keyframes us-preload-fade{from{opacity:0}to{opacity:1}}";
264
+ document.head.appendChild(style);
265
+ }
266
+ const backdrop = document.createElement("div");
267
+ backdrop.setAttribute("role", "status");
268
+ backdrop.setAttribute("aria-label", "Loading file picker");
269
+ backdrop.style.cssText = "position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;background:rgba(2,6,23,0.4);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:us-preload-fade 140ms ease-out;";
270
+ const spinner = document.createElement("div");
271
+ spinner.style.cssText = "width:36px;height:36px;border-radius:50%;border:3px solid rgba(220,225,251,0.25);border-top-color:#c0c1ff;animation:us-preload-spin 0.8s linear infinite;";
272
+ backdrop.appendChild(spinner);
273
+ document.body.appendChild(backdrop);
274
+ let removed = false;
275
+ return () => {
276
+ if (removed) return;
277
+ removed = true;
278
+ backdrop.remove();
279
+ };
280
+ }
257
281
  var UnionStackClient = class {
258
282
  constructor(cfg) {
259
283
  if (!cfg.apiKey) throw makeError("CONFIG", "apiKey is required.", { retryable: false });
@@ -261,16 +285,9 @@ var UnionStackClient = class {
261
285
  this.uploader = new Uploader(this.resolvedCfg);
262
286
  this.pickerConfigPromise = this.resolvedCfg.skipConfigPrefetch ? Promise.resolve(null) : this.uploader.fetchPickerConfig().catch(() => null);
263
287
  }
264
- /** Upload a single file. Mirrors Filestack's `client.upload()`. */
265
288
  upload(file, opts = {}) {
266
289
  return this.uploader.upload(file, opts);
267
290
  }
268
- /**
269
- * Upload multiple files concurrently. Mirrors Filestack's batch behavior:
270
- * onUploadDone fires once even if some files failed — failures land in
271
- * `filesFailed`, successes in `filesUploaded`. `onError` is reserved for
272
- * batch-level failures (no files even started).
273
- */
274
291
  async uploadMany(files, opts = {}) {
275
292
  if (!Array.isArray(files) || files.length === 0) {
276
293
  const err = makeError("VALIDATION", "uploadMany requires a non-empty array of files.", { retryable: false });
@@ -296,10 +313,6 @@ var UnionStackClient = class {
296
313
  opts.onUploadDone?.(result);
297
314
  return result;
298
315
  }
299
- /**
300
- * Open the file picker modal. Lazy-loads the picker bundle so the core
301
- * SDK stays small for callers that only use `upload()` directly.
302
- */
303
316
  picker(opts = {}) {
304
317
  let real = null;
305
318
  let opened = false;
@@ -313,13 +326,24 @@ var UnionStackClient = class {
313
326
  open: async () => {
314
327
  if (opened) return donePromise;
315
328
  opened = true;
329
+ const hidePreloader = showPickerPreloader();
316
330
  try {
317
331
  const mod = await import('./picker.js');
318
- real = mod.openPicker(this, opts);
332
+ real = mod.openPicker(this, {
333
+ ...opts,
334
+ // Picker fires onOpen right after the modal mounts — that's the
335
+ // moment the preloader hands off to the real UI.
336
+ onOpen: () => {
337
+ hidePreloader();
338
+ opts.onOpen?.();
339
+ }
340
+ });
319
341
  const result = await real.open();
342
+ hidePreloader();
320
343
  pendingResolve(result);
321
344
  return result;
322
345
  } catch (err) {
346
+ hidePreloader();
323
347
  const e = err?.code ? err : makeError("CONFIG", "Failed to load picker.", { retryable: false, cause: err });
324
348
  pendingReject(e);
325
349
  throw e;
@@ -340,5 +364,5 @@ var UnionStackClient = class {
340
364
  };
341
365
 
342
366
  export { UnionStackClient };
343
- //# sourceMappingURL=chunk-5OCCDRQG.js.map
344
- //# sourceMappingURL=chunk-5OCCDRQG.js.map
367
+ //# sourceMappingURL=chunk-6Z46YEXF.js.map
368
+ //# sourceMappingURL=chunk-6Z46YEXF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/uploader.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-6Z46YEXF.js","sourcesContent":["import type { UploadError, UploadErrorCode } from './types.js';\n\nexport function makeError(\n code: UploadErrorCode,\n message: string,\n opts: { status?: number; retryable?: boolean; cause?: unknown } = {}\n): UploadError {\n return {\n code,\n message,\n status: opts.status,\n retryable: opts.retryable ?? defaultRetryable(code, opts.status),\n cause: opts.cause,\n };\n}\n\nfunction defaultRetryable(code: UploadErrorCode, status?: number): boolean {\n if (code === 'NETWORK' || code === 'PART_FAILED') return true;\n if (code === 'SERVER' && status && status >= 500) return true;\n return false;\n}\n\n/** Map an HTTP error response from the cdn-be API to an UploadError. */\nexport function fromApiResponse(status: number, body: unknown): UploadError {\n const err = (body as { error?: { code?: string; message?: string } } | null)?.error;\n const msg = err?.message || `Request failed with status ${status}`;\n const code = (err?.code || '').toUpperCase();\n\n if (status === 401) {\n return makeError('AUTH', msg, { status, retryable: false });\n }\n if (status === 403) {\n return makeError('AUTH', msg, { status, retryable: false });\n }\n if (status === 413) {\n return makeError('VALIDATION', msg, { status, retryable: false });\n }\n if (status === 415) {\n return makeError('VALIDATION', msg, { status, retryable: false });\n }\n if (status === 429) {\n const isQuota = code === 'QUOTA_EXCEEDED';\n return makeError(isQuota ? 'QUOTA' : 'NETWORK', msg, { status, retryable: !isQuota });\n }\n if (status >= 500) {\n return makeError('SERVER', msg, { status, retryable: true });\n }\n return makeError('SERVER', msg, { status, retryable: false });\n}\n","import type {\n ResolvedClientConfig,\n PickedFile,\n PickerConfig,\n UploadedFile,\n UploadOptions,\n ProgressEvent as USProgressEvent,\n} from './types.js';\nimport { fromApiResponse, makeError } from './errors.js';\n\ninterface InitResponse {\n sessionId: string;\n fileId: string;\n chunkSize: number;\n totalParts: number;\n partUrls: Array<{ partNumber: number; url: string }>;\n expiresAt: string;\n}\n\ninterface CompleteResponse {\n handle: string;\n fileId: string;\n url: string;\n filename: string;\n mimetype: string;\n size: number;\n key: string;\n container: string;\n status: 'Stored';\n etag?: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface PartResult {\n partNumber: number;\n etag: string;\n size: number;\n}\n\nconst DEFAULT_CONCURRENCY = 3;\nconst DEFAULT_MAX_RETRIES_PER_PART = 3;\n// Backoff schedule between part retries. Capped — past this we surrender.\nconst BACKOFF_MS = [400, 1200, 3600];\n\nexport class Uploader {\n constructor(private cfg: ResolvedClientConfig) {}\n\n /** Returns a stable PickedFile descriptor for the input blob/file. */\n describe(file: File | Blob, opts: { filename?: string; mimeType?: string } = {}): PickedFile {\n const isFile = typeof File !== 'undefined' && file instanceof File;\n const filename =\n opts.filename ||\n (isFile ? (file as File).name : 'untitled');\n const mimetype =\n opts.mimeType || (file as Blob).type || 'application/octet-stream';\n return {\n uploadId: cryptoRandomId(),\n filename,\n mimetype,\n size: file.size,\n source: 'local',\n };\n }\n\n async upload(file: File | Blob, opts: UploadOptions = {}): Promise<UploadedFile> {\n const picked = this.describe(file, { filename: opts.filename, mimeType: opts.mimeType });\n opts.onFileUploadStarted?.(picked);\n\n try {\n const init = await this.initUpload(picked, opts);\n const parts = await this.uploadAllParts(file, init, picked, opts);\n const completed = await this.completeUpload(init.sessionId, parts, opts);\n\n const uploaded: UploadedFile = {\n ...picked,\n handle: completed.handle,\n fileId: completed.fileId,\n url: completed.url,\n size: completed.size,\n mimetype: completed.mimetype,\n filename: completed.filename,\n key: completed.key,\n container: completed.container,\n status: 'Stored',\n etag: completed.etag,\n metadata: completed.metadata,\n };\n opts.onFileUploadFinished?.(uploaded);\n return uploaded;\n } catch (rawErr) {\n const err = normalizeError(rawErr);\n opts.onFileUploadFailed?.(picked, err);\n // Best effort: tell the server to clean up the multipart, but ignore failures.\n if ((rawErr as { sessionId?: string })?.sessionId) {\n this.abortSilently((rawErr as { sessionId: string }).sessionId).catch(() => {});\n }\n throw err;\n }\n }\n\n /** Cancel an in-flight session server-side. */\n async abort(sessionId: string): Promise<void> {\n await this.api('POST', '/sdk/v1/uploads/abort', { sessionId });\n }\n\n /** Fetch the server-managed picker config (branding, theme, constraints). */\n async fetchPickerConfig(): Promise<PickerConfig> {\n return this.api<PickerConfig>('GET', '/sdk/v1/picker-config');\n }\n\n private async initUpload(picked: PickedFile, opts: UploadOptions): Promise<InitResponse> {\n return this.api<InitResponse>('POST', '/sdk/v1/uploads/init', {\n filename: picked.filename,\n mimeType: picked.mimetype,\n size: picked.size,\n metadata: opts.metadata,\n }, opts.signal);\n }\n\n private async uploadAllParts(\n file: File | Blob,\n init: InitResponse,\n picked: PickedFile,\n opts: UploadOptions,\n ): Promise<PartResult[]> {\n const totalParts = init.totalParts;\n const chunkSize = init.chunkSize;\n const concurrency = Math.max(1, opts.concurrency ?? DEFAULT_CONCURRENCY);\n const maxRetries = opts.maxRetriesPerPart ?? DEFAULT_MAX_RETRIES_PER_PART;\n\n // Quick lookup: partNumber → presigned URL.\n const urlByPart = new Map(init.partUrls.map(p => [p.partNumber, p.url] as const));\n\n const results: PartResult[] = new Array(totalParts);\n const loadedPerPart: number[] = new Array(totalParts).fill(0);\n let cursor = 1;\n\n const totalBytes = file.size;\n\n const reportProgress = () => {\n const loaded = loadedPerPart.reduce((a, b) => a + b, 0);\n const totalPercent = totalBytes > 0 ? Math.min(100, Math.round((loaded / totalBytes) * 100)) : 100;\n const ev: USProgressEvent = { totalBytes, loaded, totalPercent };\n opts.onFileUploadProgress?.(picked, ev);\n };\n\n const worker = async (): Promise<void> => {\n while (true) {\n if (opts.signal?.aborted) {\n throw makeError('ABORTED', 'Upload aborted by caller.', { retryable: false });\n }\n const partNumber = cursor++;\n if (partNumber > totalParts) return;\n\n const start = (partNumber - 1) * chunkSize;\n const end = Math.min(start + chunkSize, file.size);\n const chunk = file.slice(start, end);\n\n let attempt = 0;\n let lastErr: unknown;\n while (attempt <= maxRetries) {\n if (opts.signal?.aborted) {\n throw makeError('ABORTED', 'Upload aborted by caller.', { retryable: false });\n }\n try {\n let url = urlByPart.get(partNumber);\n if (!url) {\n const refreshed = await this.api<{ url: string }>(\n 'POST', '/sdk/v1/uploads/sign-part',\n { sessionId: init.sessionId, partNumber }, opts.signal,\n );\n url = refreshed.url;\n urlByPart.set(partNumber, url);\n }\n\n const res = await this.cfg.fetch?.(url, {\n method: 'PUT',\n body: chunk,\n signal: opts.signal,\n }) ?? await fetch(url, { method: 'PUT', body: chunk, signal: opts.signal });\n\n if (!res.ok) {\n // If R2 rejects with 403 the URL likely expired — drop it so the\n // next attempt re-signs.\n if (res.status === 403 || res.status === 401) urlByPart.delete(partNumber);\n throw makeError('PART_FAILED', `Part ${partNumber} PUT failed (HTTP ${res.status})`, { status: res.status });\n }\n const etag = res.headers.get('etag');\n if (!etag) {\n throw makeError('PART_FAILED', `Part ${partNumber}: R2 did not return an ETag.`);\n }\n\n results[partNumber - 1] = { partNumber, etag, size: chunk.size };\n loadedPerPart[partNumber - 1] = chunk.size;\n reportProgress();\n break;\n } catch (err) {\n lastErr = err;\n attempt++;\n if (attempt > maxRetries) break;\n await sleep(BACKOFF_MS[Math.min(attempt - 1, BACKOFF_MS.length - 1)]);\n }\n }\n if (!results[partNumber - 1]) {\n throw withSessionId(\n normalizeError(lastErr ?? makeError('PART_FAILED', `Part ${partNumber} failed after ${maxRetries} retries.`)),\n init.sessionId,\n );\n }\n }\n };\n\n const workers = Array.from({ length: Math.min(concurrency, totalParts) }, worker);\n await Promise.all(workers);\n return results;\n }\n\n private async completeUpload(\n sessionId: string,\n parts: PartResult[],\n opts: UploadOptions,\n ): Promise<CompleteResponse> {\n return this.api<CompleteResponse>('POST', '/sdk/v1/uploads/complete', {\n sessionId,\n parts: parts.map(p => ({ partNumber: p.partNumber, etag: p.etag })),\n }, opts.signal);\n }\n\n private async abortSilently(sessionId: string): Promise<void> {\n try { await this.abort(sessionId); } catch { /* ignore */ }\n }\n\n private async api<T>(\n method: 'GET' | 'POST' | 'DELETE',\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n ): Promise<T> {\n const fetchImpl = this.cfg.fetch ?? fetch;\n const url = `${this.cfg.apiBase}${path}`;\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.cfg.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal,\n });\n } catch (cause) {\n if ((cause as Error)?.name === 'AbortError') {\n throw makeError('ABORTED', 'Request aborted.', { retryable: false, cause });\n }\n throw makeError('NETWORK', 'Network request failed.', { retryable: true, cause });\n }\n let parsed: unknown = null;\n try { parsed = await res.json(); } catch { /* tolerate empty body */ }\n if (!res.ok) throw fromApiResponse(res.status, parsed);\n return parsed as T;\n }\n}\n\nfunction cryptoRandomId(): string {\n // Cheap unique id for client-side uploadId. Not security-sensitive.\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return (crypto as { randomUUID: () => string }).randomUUID().replace(/-/g, '');\n }\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nfunction normalizeError(err: unknown): ReturnType<typeof makeError> {\n if (err && typeof err === 'object' && 'code' in err && 'message' in err && 'retryable' in err) {\n return err as ReturnType<typeof makeError>;\n }\n const msg = (err as Error)?.message || 'Upload failed.';\n return makeError('NETWORK', msg, { cause: err });\n}\n\nfunction withSessionId<T>(err: T, sessionId: string): T {\n if (err && typeof err === 'object') (err as { sessionId?: string }).sessionId = sessionId;\n return err;\n}\n","import type {\n ClientConfig,\n ResolvedClientConfig,\n UploadOptions,\n BatchUploadOptions,\n UploadedFile,\n PickResponse,\n PickedFile,\n PickerConfig,\n UploadError,\n} from './types.js';\nimport { Uploader } from './uploader.js';\nimport { makeError } from './errors.js';\n\n/**\n * UnionStack API URL baked into the SDK. Consumers never see or set this —\n * we ship a new SDK version if the URL ever changes.\n *\n * For local development against a staging backend, change this constant in\n * src/ and rebuild — no consumer-facing override exists by design.\n */\nconst API_BASE = 'https://api.unionstack.link/v1';\n\n// Forward-declared minimal handle so client.ts doesn't pull in the picker bundle.\nexport interface PickerHandleLike {\n open(): Promise<PickResponse>;\n close(): void;\n cancel(): void;\n}\n\nconst PRELOADER_STYLE_ID = 'unionstack-preloader-styles';\n\n// Instant spinner shown between picker.open() and the modal actually mounting\n// (covers the lazy chunk download + the server config fetch). Self-contained —\n// must not depend on the picker bundle, which is what we're waiting for. The\n// backdrop matches the picker's exactly so the handoff is seamless.\nfunction showPickerPreloader(): () => void {\n if (typeof document === 'undefined') return () => {};\n if (!document.getElementById(PRELOADER_STYLE_ID)) {\n const style = document.createElement('style');\n style.id = PRELOADER_STYLE_ID;\n style.textContent =\n '@keyframes us-preload-spin{to{transform:rotate(360deg)}}' +\n '@keyframes us-preload-fade{from{opacity:0}to{opacity:1}}';\n document.head.appendChild(style);\n }\n const backdrop = document.createElement('div');\n backdrop.setAttribute('role', 'status');\n backdrop.setAttribute('aria-label', 'Loading file picker');\n backdrop.style.cssText =\n 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;' +\n 'background:rgba(2,6,23,0.4);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);' +\n 'animation:us-preload-fade 140ms ease-out;';\n const spinner = document.createElement('div');\n spinner.style.cssText =\n 'width:36px;height:36px;border-radius:50%;' +\n 'border:3px solid rgba(220,225,251,0.25);border-top-color:#c0c1ff;' +\n 'animation:us-preload-spin 0.8s linear infinite;';\n backdrop.appendChild(spinner);\n document.body.appendChild(backdrop);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n backdrop.remove();\n };\n}\n\nexport class UnionStackClient {\n private uploader: Uploader;\n private resolvedCfg: ResolvedClientConfig;\n /**\n * Promise that resolves to the server-managed picker config. Pre-fetched on\n * construction (unless `skipConfigPrefetch: true`) so the picker opens\n * without a network hit. Fails silently — picker falls back to defaults.\n */\n pickerConfigPromise: Promise<PickerConfig | null>;\n\n constructor(cfg: ClientConfig) {\n if (!cfg.apiKey) throw makeError('CONFIG', 'apiKey is required.', { retryable: false });\n this.resolvedCfg = { ...cfg, apiBase: API_BASE };\n this.uploader = new Uploader(this.resolvedCfg);\n this.pickerConfigPromise = this.resolvedCfg.skipConfigPrefetch\n ? Promise.resolve(null)\n : this.uploader.fetchPickerConfig().catch(() => null);\n }\n\n upload(file: File | Blob, opts: UploadOptions = {}): Promise<UploadedFile> {\n return this.uploader.upload(file, opts);\n }\n\n async uploadMany(\n files: (File | Blob)[],\n opts: BatchUploadOptions = {},\n ): Promise<PickResponse> {\n if (!Array.isArray(files) || files.length === 0) {\n const err = makeError('VALIDATION', 'uploadMany requires a non-empty array of files.', { retryable: false });\n opts.onError?.(err);\n throw err;\n }\n\n const picked: PickedFile[] = files.map(f =>\n this.uploader.describe(f, { filename: opts.filename, mimeType: opts.mimeType })\n );\n opts.onUploadStarted?.(picked);\n\n const filesUploaded: UploadedFile[] = [];\n const filesFailed: Array<{ file: PickedFile; error: UploadError }> = [];\n\n await Promise.all(files.map(async (f, i) => {\n try {\n const uploaded = await this.uploader.upload(f, opts);\n // Sync the uploadId so callers can correlate picked → uploaded.\n uploaded.uploadId = picked[i].uploadId;\n filesUploaded.push(uploaded);\n } catch (err) {\n filesFailed.push({ file: picked[i], error: err as UploadError });\n }\n }));\n\n const result: PickResponse = { filesUploaded, filesFailed };\n opts.onUploadDone?.(result);\n return result;\n }\n \n picker(opts: import('./picker/types.js').PickerOptions = {}): PickerHandleLike {\n let real: PickerHandleLike | null = null;\n let opened = false;\n let pendingResolve!: (r: PickResponse) => void;\n let pendingReject!: (e: UploadError) => void;\n const donePromise = new Promise<PickResponse>((res, rej) => {\n pendingResolve = res; pendingReject = rej;\n });\n\n return {\n open: async () => {\n if (opened) return donePromise;\n opened = true;\n const hidePreloader = showPickerPreloader();\n try {\n const mod = await import('./picker/index.js');\n real = mod.openPicker(this, {\n ...opts,\n // Picker fires onOpen right after the modal mounts — that's the\n // moment the preloader hands off to the real UI.\n onOpen: () => { hidePreloader(); opts.onOpen?.(); },\n });\n const result = await real.open();\n hidePreloader();\n pendingResolve(result);\n return result;\n } catch (err) {\n hidePreloader();\n const e = (err as UploadError)?.code\n ? (err as UploadError)\n : makeError('CONFIG', 'Failed to load picker.', { retryable: false, cause: err });\n pendingReject(e);\n throw e;\n }\n },\n close: () => { real?.close(); },\n cancel: () => { real?.cancel(); },\n };\n }\n\n /** Read-only accessor used by the React/picker packages. */\n get config(): Readonly<ResolvedClientConfig> { return this.resolvedCfg; }\n}\n"]}
@@ -62,10 +62,6 @@ var Uploader = class {
62
62
  source: "local"
63
63
  };
64
64
  }
65
- /**
66
- * Upload a single Blob/File. Returns the persisted UploadedFile.
67
- * Fires Filestack-style per-file callbacks from `opts`.
68
- */
69
65
  async upload(file, opts = {}) {
70
66
  const picked = this.describe(file, { filename: opts.filename, mimeType: opts.mimeType });
71
67
  opts.onFileUploadStarted?.(picked);
@@ -81,8 +77,11 @@ var Uploader = class {
81
77
  size: completed.size,
82
78
  mimetype: completed.mimetype,
83
79
  filename: completed.filename,
80
+ key: completed.key,
81
+ container: completed.container,
84
82
  status: "Stored",
85
- etag: completed.etag
83
+ etag: completed.etag,
84
+ metadata: completed.metadata
86
85
  };
87
86
  opts.onFileUploadFinished?.(uploaded);
88
87
  return uploaded;
@@ -256,6 +255,31 @@ function withSessionId(err, sessionId) {
256
255
 
257
256
  // src/client.ts
258
257
  var API_BASE = "https://api.unionstack.link/v1";
258
+ var PRELOADER_STYLE_ID = "unionstack-preloader-styles";
259
+ function showPickerPreloader() {
260
+ if (typeof document === "undefined") return () => {
261
+ };
262
+ if (!document.getElementById(PRELOADER_STYLE_ID)) {
263
+ const style = document.createElement("style");
264
+ style.id = PRELOADER_STYLE_ID;
265
+ style.textContent = "@keyframes us-preload-spin{to{transform:rotate(360deg)}}@keyframes us-preload-fade{from{opacity:0}to{opacity:1}}";
266
+ document.head.appendChild(style);
267
+ }
268
+ const backdrop = document.createElement("div");
269
+ backdrop.setAttribute("role", "status");
270
+ backdrop.setAttribute("aria-label", "Loading file picker");
271
+ backdrop.style.cssText = "position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;background:rgba(2,6,23,0.4);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:us-preload-fade 140ms ease-out;";
272
+ const spinner = document.createElement("div");
273
+ spinner.style.cssText = "width:36px;height:36px;border-radius:50%;border:3px solid rgba(220,225,251,0.25);border-top-color:#c0c1ff;animation:us-preload-spin 0.8s linear infinite;";
274
+ backdrop.appendChild(spinner);
275
+ document.body.appendChild(backdrop);
276
+ let removed = false;
277
+ return () => {
278
+ if (removed) return;
279
+ removed = true;
280
+ backdrop.remove();
281
+ };
282
+ }
259
283
  var UnionStackClient = class {
260
284
  constructor(cfg) {
261
285
  if (!cfg.apiKey) throw makeError("CONFIG", "apiKey is required.", { retryable: false });
@@ -263,16 +287,9 @@ var UnionStackClient = class {
263
287
  this.uploader = new Uploader(this.resolvedCfg);
264
288
  this.pickerConfigPromise = this.resolvedCfg.skipConfigPrefetch ? Promise.resolve(null) : this.uploader.fetchPickerConfig().catch(() => null);
265
289
  }
266
- /** Upload a single file. Mirrors Filestack's `client.upload()`. */
267
290
  upload(file, opts = {}) {
268
291
  return this.uploader.upload(file, opts);
269
292
  }
270
- /**
271
- * Upload multiple files concurrently. Mirrors Filestack's batch behavior:
272
- * onUploadDone fires once even if some files failed — failures land in
273
- * `filesFailed`, successes in `filesUploaded`. `onError` is reserved for
274
- * batch-level failures (no files even started).
275
- */
276
293
  async uploadMany(files, opts = {}) {
277
294
  if (!Array.isArray(files) || files.length === 0) {
278
295
  const err = makeError("VALIDATION", "uploadMany requires a non-empty array of files.", { retryable: false });
@@ -298,10 +315,6 @@ var UnionStackClient = class {
298
315
  opts.onUploadDone?.(result);
299
316
  return result;
300
317
  }
301
- /**
302
- * Open the file picker modal. Lazy-loads the picker bundle so the core
303
- * SDK stays small for callers that only use `upload()` directly.
304
- */
305
318
  picker(opts = {}) {
306
319
  let real = null;
307
320
  let opened = false;
@@ -315,13 +328,24 @@ var UnionStackClient = class {
315
328
  open: async () => {
316
329
  if (opened) return donePromise;
317
330
  opened = true;
331
+ const hidePreloader = showPickerPreloader();
318
332
  try {
319
333
  const mod = await import('./picker.cjs');
320
- real = mod.openPicker(this, opts);
334
+ real = mod.openPicker(this, {
335
+ ...opts,
336
+ // Picker fires onOpen right after the modal mounts — that's the
337
+ // moment the preloader hands off to the real UI.
338
+ onOpen: () => {
339
+ hidePreloader();
340
+ opts.onOpen?.();
341
+ }
342
+ });
321
343
  const result = await real.open();
344
+ hidePreloader();
322
345
  pendingResolve(result);
323
346
  return result;
324
347
  } catch (err) {
348
+ hidePreloader();
325
349
  const e = err?.code ? err : makeError("CONFIG", "Failed to load picker.", { retryable: false, cause: err });
326
350
  pendingReject(e);
327
351
  throw e;
@@ -342,5 +366,5 @@ var UnionStackClient = class {
342
366
  };
343
367
 
344
368
  exports.UnionStackClient = UnionStackClient;
345
- //# sourceMappingURL=chunk-6AHBENOC.cjs.map
346
- //# sourceMappingURL=chunk-6AHBENOC.cjs.map
369
+ //# sourceMappingURL=chunk-DETAGOYQ.cjs.map
370
+ //# sourceMappingURL=chunk-DETAGOYQ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/uploader.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-DETAGOYQ.cjs","sourcesContent":["import type { UploadError, UploadErrorCode } from './types.js';\n\nexport function makeError(\n code: UploadErrorCode,\n message: string,\n opts: { status?: number; retryable?: boolean; cause?: unknown } = {}\n): UploadError {\n return {\n code,\n message,\n status: opts.status,\n retryable: opts.retryable ?? defaultRetryable(code, opts.status),\n cause: opts.cause,\n };\n}\n\nfunction defaultRetryable(code: UploadErrorCode, status?: number): boolean {\n if (code === 'NETWORK' || code === 'PART_FAILED') return true;\n if (code === 'SERVER' && status && status >= 500) return true;\n return false;\n}\n\n/** Map an HTTP error response from the cdn-be API to an UploadError. */\nexport function fromApiResponse(status: number, body: unknown): UploadError {\n const err = (body as { error?: { code?: string; message?: string } } | null)?.error;\n const msg = err?.message || `Request failed with status ${status}`;\n const code = (err?.code || '').toUpperCase();\n\n if (status === 401) {\n return makeError('AUTH', msg, { status, retryable: false });\n }\n if (status === 403) {\n return makeError('AUTH', msg, { status, retryable: false });\n }\n if (status === 413) {\n return makeError('VALIDATION', msg, { status, retryable: false });\n }\n if (status === 415) {\n return makeError('VALIDATION', msg, { status, retryable: false });\n }\n if (status === 429) {\n const isQuota = code === 'QUOTA_EXCEEDED';\n return makeError(isQuota ? 'QUOTA' : 'NETWORK', msg, { status, retryable: !isQuota });\n }\n if (status >= 500) {\n return makeError('SERVER', msg, { status, retryable: true });\n }\n return makeError('SERVER', msg, { status, retryable: false });\n}\n","import type {\n ResolvedClientConfig,\n PickedFile,\n PickerConfig,\n UploadedFile,\n UploadOptions,\n ProgressEvent as USProgressEvent,\n} from './types.js';\nimport { fromApiResponse, makeError } from './errors.js';\n\ninterface InitResponse {\n sessionId: string;\n fileId: string;\n chunkSize: number;\n totalParts: number;\n partUrls: Array<{ partNumber: number; url: string }>;\n expiresAt: string;\n}\n\ninterface CompleteResponse {\n handle: string;\n fileId: string;\n url: string;\n filename: string;\n mimetype: string;\n size: number;\n key: string;\n container: string;\n status: 'Stored';\n etag?: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface PartResult {\n partNumber: number;\n etag: string;\n size: number;\n}\n\nconst DEFAULT_CONCURRENCY = 3;\nconst DEFAULT_MAX_RETRIES_PER_PART = 3;\n// Backoff schedule between part retries. Capped — past this we surrender.\nconst BACKOFF_MS = [400, 1200, 3600];\n\nexport class Uploader {\n constructor(private cfg: ResolvedClientConfig) {}\n\n /** Returns a stable PickedFile descriptor for the input blob/file. */\n describe(file: File | Blob, opts: { filename?: string; mimeType?: string } = {}): PickedFile {\n const isFile = typeof File !== 'undefined' && file instanceof File;\n const filename =\n opts.filename ||\n (isFile ? (file as File).name : 'untitled');\n const mimetype =\n opts.mimeType || (file as Blob).type || 'application/octet-stream';\n return {\n uploadId: cryptoRandomId(),\n filename,\n mimetype,\n size: file.size,\n source: 'local',\n };\n }\n\n async upload(file: File | Blob, opts: UploadOptions = {}): Promise<UploadedFile> {\n const picked = this.describe(file, { filename: opts.filename, mimeType: opts.mimeType });\n opts.onFileUploadStarted?.(picked);\n\n try {\n const init = await this.initUpload(picked, opts);\n const parts = await this.uploadAllParts(file, init, picked, opts);\n const completed = await this.completeUpload(init.sessionId, parts, opts);\n\n const uploaded: UploadedFile = {\n ...picked,\n handle: completed.handle,\n fileId: completed.fileId,\n url: completed.url,\n size: completed.size,\n mimetype: completed.mimetype,\n filename: completed.filename,\n key: completed.key,\n container: completed.container,\n status: 'Stored',\n etag: completed.etag,\n metadata: completed.metadata,\n };\n opts.onFileUploadFinished?.(uploaded);\n return uploaded;\n } catch (rawErr) {\n const err = normalizeError(rawErr);\n opts.onFileUploadFailed?.(picked, err);\n // Best effort: tell the server to clean up the multipart, but ignore failures.\n if ((rawErr as { sessionId?: string })?.sessionId) {\n this.abortSilently((rawErr as { sessionId: string }).sessionId).catch(() => {});\n }\n throw err;\n }\n }\n\n /** Cancel an in-flight session server-side. */\n async abort(sessionId: string): Promise<void> {\n await this.api('POST', '/sdk/v1/uploads/abort', { sessionId });\n }\n\n /** Fetch the server-managed picker config (branding, theme, constraints). */\n async fetchPickerConfig(): Promise<PickerConfig> {\n return this.api<PickerConfig>('GET', '/sdk/v1/picker-config');\n }\n\n private async initUpload(picked: PickedFile, opts: UploadOptions): Promise<InitResponse> {\n return this.api<InitResponse>('POST', '/sdk/v1/uploads/init', {\n filename: picked.filename,\n mimeType: picked.mimetype,\n size: picked.size,\n metadata: opts.metadata,\n }, opts.signal);\n }\n\n private async uploadAllParts(\n file: File | Blob,\n init: InitResponse,\n picked: PickedFile,\n opts: UploadOptions,\n ): Promise<PartResult[]> {\n const totalParts = init.totalParts;\n const chunkSize = init.chunkSize;\n const concurrency = Math.max(1, opts.concurrency ?? DEFAULT_CONCURRENCY);\n const maxRetries = opts.maxRetriesPerPart ?? DEFAULT_MAX_RETRIES_PER_PART;\n\n // Quick lookup: partNumber → presigned URL.\n const urlByPart = new Map(init.partUrls.map(p => [p.partNumber, p.url] as const));\n\n const results: PartResult[] = new Array(totalParts);\n const loadedPerPart: number[] = new Array(totalParts).fill(0);\n let cursor = 1;\n\n const totalBytes = file.size;\n\n const reportProgress = () => {\n const loaded = loadedPerPart.reduce((a, b) => a + b, 0);\n const totalPercent = totalBytes > 0 ? Math.min(100, Math.round((loaded / totalBytes) * 100)) : 100;\n const ev: USProgressEvent = { totalBytes, loaded, totalPercent };\n opts.onFileUploadProgress?.(picked, ev);\n };\n\n const worker = async (): Promise<void> => {\n while (true) {\n if (opts.signal?.aborted) {\n throw makeError('ABORTED', 'Upload aborted by caller.', { retryable: false });\n }\n const partNumber = cursor++;\n if (partNumber > totalParts) return;\n\n const start = (partNumber - 1) * chunkSize;\n const end = Math.min(start + chunkSize, file.size);\n const chunk = file.slice(start, end);\n\n let attempt = 0;\n let lastErr: unknown;\n while (attempt <= maxRetries) {\n if (opts.signal?.aborted) {\n throw makeError('ABORTED', 'Upload aborted by caller.', { retryable: false });\n }\n try {\n let url = urlByPart.get(partNumber);\n if (!url) {\n const refreshed = await this.api<{ url: string }>(\n 'POST', '/sdk/v1/uploads/sign-part',\n { sessionId: init.sessionId, partNumber }, opts.signal,\n );\n url = refreshed.url;\n urlByPart.set(partNumber, url);\n }\n\n const res = await this.cfg.fetch?.(url, {\n method: 'PUT',\n body: chunk,\n signal: opts.signal,\n }) ?? await fetch(url, { method: 'PUT', body: chunk, signal: opts.signal });\n\n if (!res.ok) {\n // If R2 rejects with 403 the URL likely expired — drop it so the\n // next attempt re-signs.\n if (res.status === 403 || res.status === 401) urlByPart.delete(partNumber);\n throw makeError('PART_FAILED', `Part ${partNumber} PUT failed (HTTP ${res.status})`, { status: res.status });\n }\n const etag = res.headers.get('etag');\n if (!etag) {\n throw makeError('PART_FAILED', `Part ${partNumber}: R2 did not return an ETag.`);\n }\n\n results[partNumber - 1] = { partNumber, etag, size: chunk.size };\n loadedPerPart[partNumber - 1] = chunk.size;\n reportProgress();\n break;\n } catch (err) {\n lastErr = err;\n attempt++;\n if (attempt > maxRetries) break;\n await sleep(BACKOFF_MS[Math.min(attempt - 1, BACKOFF_MS.length - 1)]);\n }\n }\n if (!results[partNumber - 1]) {\n throw withSessionId(\n normalizeError(lastErr ?? makeError('PART_FAILED', `Part ${partNumber} failed after ${maxRetries} retries.`)),\n init.sessionId,\n );\n }\n }\n };\n\n const workers = Array.from({ length: Math.min(concurrency, totalParts) }, worker);\n await Promise.all(workers);\n return results;\n }\n\n private async completeUpload(\n sessionId: string,\n parts: PartResult[],\n opts: UploadOptions,\n ): Promise<CompleteResponse> {\n return this.api<CompleteResponse>('POST', '/sdk/v1/uploads/complete', {\n sessionId,\n parts: parts.map(p => ({ partNumber: p.partNumber, etag: p.etag })),\n }, opts.signal);\n }\n\n private async abortSilently(sessionId: string): Promise<void> {\n try { await this.abort(sessionId); } catch { /* ignore */ }\n }\n\n private async api<T>(\n method: 'GET' | 'POST' | 'DELETE',\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n ): Promise<T> {\n const fetchImpl = this.cfg.fetch ?? fetch;\n const url = `${this.cfg.apiBase}${path}`;\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.cfg.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal,\n });\n } catch (cause) {\n if ((cause as Error)?.name === 'AbortError') {\n throw makeError('ABORTED', 'Request aborted.', { retryable: false, cause });\n }\n throw makeError('NETWORK', 'Network request failed.', { retryable: true, cause });\n }\n let parsed: unknown = null;\n try { parsed = await res.json(); } catch { /* tolerate empty body */ }\n if (!res.ok) throw fromApiResponse(res.status, parsed);\n return parsed as T;\n }\n}\n\nfunction cryptoRandomId(): string {\n // Cheap unique id for client-side uploadId. Not security-sensitive.\n if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {\n return (crypto as { randomUUID: () => string }).randomUUID().replace(/-/g, '');\n }\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nfunction normalizeError(err: unknown): ReturnType<typeof makeError> {\n if (err && typeof err === 'object' && 'code' in err && 'message' in err && 'retryable' in err) {\n return err as ReturnType<typeof makeError>;\n }\n const msg = (err as Error)?.message || 'Upload failed.';\n return makeError('NETWORK', msg, { cause: err });\n}\n\nfunction withSessionId<T>(err: T, sessionId: string): T {\n if (err && typeof err === 'object') (err as { sessionId?: string }).sessionId = sessionId;\n return err;\n}\n","import type {\n ClientConfig,\n ResolvedClientConfig,\n UploadOptions,\n BatchUploadOptions,\n UploadedFile,\n PickResponse,\n PickedFile,\n PickerConfig,\n UploadError,\n} from './types.js';\nimport { Uploader } from './uploader.js';\nimport { makeError } from './errors.js';\n\n/**\n * UnionStack API URL baked into the SDK. Consumers never see or set this —\n * we ship a new SDK version if the URL ever changes.\n *\n * For local development against a staging backend, change this constant in\n * src/ and rebuild — no consumer-facing override exists by design.\n */\nconst API_BASE = 'https://api.unionstack.link/v1';\n\n// Forward-declared minimal handle so client.ts doesn't pull in the picker bundle.\nexport interface PickerHandleLike {\n open(): Promise<PickResponse>;\n close(): void;\n cancel(): void;\n}\n\nconst PRELOADER_STYLE_ID = 'unionstack-preloader-styles';\n\n// Instant spinner shown between picker.open() and the modal actually mounting\n// (covers the lazy chunk download + the server config fetch). Self-contained —\n// must not depend on the picker bundle, which is what we're waiting for. The\n// backdrop matches the picker's exactly so the handoff is seamless.\nfunction showPickerPreloader(): () => void {\n if (typeof document === 'undefined') return () => {};\n if (!document.getElementById(PRELOADER_STYLE_ID)) {\n const style = document.createElement('style');\n style.id = PRELOADER_STYLE_ID;\n style.textContent =\n '@keyframes us-preload-spin{to{transform:rotate(360deg)}}' +\n '@keyframes us-preload-fade{from{opacity:0}to{opacity:1}}';\n document.head.appendChild(style);\n }\n const backdrop = document.createElement('div');\n backdrop.setAttribute('role', 'status');\n backdrop.setAttribute('aria-label', 'Loading file picker');\n backdrop.style.cssText =\n 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;' +\n 'background:rgba(2,6,23,0.4);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);' +\n 'animation:us-preload-fade 140ms ease-out;';\n const spinner = document.createElement('div');\n spinner.style.cssText =\n 'width:36px;height:36px;border-radius:50%;' +\n 'border:3px solid rgba(220,225,251,0.25);border-top-color:#c0c1ff;' +\n 'animation:us-preload-spin 0.8s linear infinite;';\n backdrop.appendChild(spinner);\n document.body.appendChild(backdrop);\n let removed = false;\n return () => {\n if (removed) return;\n removed = true;\n backdrop.remove();\n };\n}\n\nexport class UnionStackClient {\n private uploader: Uploader;\n private resolvedCfg: ResolvedClientConfig;\n /**\n * Promise that resolves to the server-managed picker config. Pre-fetched on\n * construction (unless `skipConfigPrefetch: true`) so the picker opens\n * without a network hit. Fails silently — picker falls back to defaults.\n */\n pickerConfigPromise: Promise<PickerConfig | null>;\n\n constructor(cfg: ClientConfig) {\n if (!cfg.apiKey) throw makeError('CONFIG', 'apiKey is required.', { retryable: false });\n this.resolvedCfg = { ...cfg, apiBase: API_BASE };\n this.uploader = new Uploader(this.resolvedCfg);\n this.pickerConfigPromise = this.resolvedCfg.skipConfigPrefetch\n ? Promise.resolve(null)\n : this.uploader.fetchPickerConfig().catch(() => null);\n }\n\n upload(file: File | Blob, opts: UploadOptions = {}): Promise<UploadedFile> {\n return this.uploader.upload(file, opts);\n }\n\n async uploadMany(\n files: (File | Blob)[],\n opts: BatchUploadOptions = {},\n ): Promise<PickResponse> {\n if (!Array.isArray(files) || files.length === 0) {\n const err = makeError('VALIDATION', 'uploadMany requires a non-empty array of files.', { retryable: false });\n opts.onError?.(err);\n throw err;\n }\n\n const picked: PickedFile[] = files.map(f =>\n this.uploader.describe(f, { filename: opts.filename, mimeType: opts.mimeType })\n );\n opts.onUploadStarted?.(picked);\n\n const filesUploaded: UploadedFile[] = [];\n const filesFailed: Array<{ file: PickedFile; error: UploadError }> = [];\n\n await Promise.all(files.map(async (f, i) => {\n try {\n const uploaded = await this.uploader.upload(f, opts);\n // Sync the uploadId so callers can correlate picked → uploaded.\n uploaded.uploadId = picked[i].uploadId;\n filesUploaded.push(uploaded);\n } catch (err) {\n filesFailed.push({ file: picked[i], error: err as UploadError });\n }\n }));\n\n const result: PickResponse = { filesUploaded, filesFailed };\n opts.onUploadDone?.(result);\n return result;\n }\n \n picker(opts: import('./picker/types.js').PickerOptions = {}): PickerHandleLike {\n let real: PickerHandleLike | null = null;\n let opened = false;\n let pendingResolve!: (r: PickResponse) => void;\n let pendingReject!: (e: UploadError) => void;\n const donePromise = new Promise<PickResponse>((res, rej) => {\n pendingResolve = res; pendingReject = rej;\n });\n\n return {\n open: async () => {\n if (opened) return donePromise;\n opened = true;\n const hidePreloader = showPickerPreloader();\n try {\n const mod = await import('./picker/index.js');\n real = mod.openPicker(this, {\n ...opts,\n // Picker fires onOpen right after the modal mounts — that's the\n // moment the preloader hands off to the real UI.\n onOpen: () => { hidePreloader(); opts.onOpen?.(); },\n });\n const result = await real.open();\n hidePreloader();\n pendingResolve(result);\n return result;\n } catch (err) {\n hidePreloader();\n const e = (err as UploadError)?.code\n ? (err as UploadError)\n : makeError('CONFIG', 'Failed to load picker.', { retryable: false, cause: err });\n pendingReject(e);\n throw e;\n }\n },\n close: () => { real?.close(); },\n cancel: () => { real?.cancel(); },\n };\n }\n\n /** Read-only accessor used by the React/picker packages. */\n get config(): Readonly<ResolvedClientConfig> { return this.resolvedCfg; }\n}\n"]}
@@ -16,15 +16,14 @@ interface PickedFile {
16
16
  source: 'local' | 'url' | 'camera' | 'drop';
17
17
  }
18
18
  interface UploadedFile extends PickedFile {
19
- /** Server-side file id — also stored as `handle` for Filestack compatibility. */
20
19
  handle: string;
21
20
  fileId: string;
22
21
  url: string;
23
- /** Object key in R2 (debugging only). */
24
- key?: string;
22
+ key: string;
23
+ container: string;
25
24
  status: 'Stored';
26
25
  etag?: string;
27
- uploadTags?: Record<string, string>;
26
+ metadata?: Record<string, unknown>;
28
27
  }
29
28
  interface ProgressEvent {
30
29
  totalBytes: number;
@@ -135,6 +134,8 @@ interface PickerBranding {
135
134
  /** Hide "Powered by UnionStack" footer. */
136
135
  hideFooter?: boolean;
137
136
  }
137
+ /** Source tabs available in the picker. Pass an array to hide ones you don't want. */
138
+ type PickerSource = 'device' | 'url';
138
139
  interface PickerOptions extends Omit<BatchUploadOptions, 'signal'> {
139
140
  /** Constrain selectable files (HTML accept syntax). */
140
141
  accept?: string;
@@ -142,12 +143,24 @@ interface PickerOptions extends Omit<BatchUploadOptions, 'signal'> {
142
143
  maxFiles?: number;
143
144
  /** Max file size in bytes — additional client-side check before init. */
144
145
  maxFileSize?: number;
146
+ /**
147
+ * Which file sources to surface as tabs in the picker. Default is
148
+ * `['device', 'url']` (both tabs visible). Set to `['device']` to hide
149
+ * the URL-import tab entirely.
150
+ */
151
+ fromSources?: PickerSource[];
152
+ /**
153
+ * Allow per-image editing (crop / circle / rotate) on queued image rows
154
+ * before upload starts. Defaults to `true`. Set to `false` to hide the
155
+ * Edit affordance — useful if your flow forbids client-side mutation.
156
+ */
157
+ imageEditing?: boolean;
145
158
  /**
146
159
  * Start uploading immediately when the user finishes picking/dropping files,
147
- * skipping the explicit "Upload" button click. Defaults to `true` (modern
148
- * upload UX drop upload). Set to `false` if you want users to review the
149
- * selection and confirm before upload starts.
150
- * @default true
160
+ * skipping the explicit "Upload" button click. Defaults to `false` — users
161
+ * pick, optionally edit images, then click Upload. Set to `true` to skip the
162
+ * review step and kick the upload as soon as the selection is in.
163
+ * @default false
151
164
  */
152
165
  autoUpload?: boolean;
153
166
  theme?: PickerTheme;
@@ -180,19 +193,8 @@ declare class UnionStackClient {
180
193
  */
181
194
  pickerConfigPromise: Promise<PickerConfig | null>;
182
195
  constructor(cfg: ClientConfig);
183
- /** Upload a single file. Mirrors Filestack's `client.upload()`. */
184
196
  upload(file: File | Blob, opts?: UploadOptions): Promise<UploadedFile>;
185
- /**
186
- * Upload multiple files concurrently. Mirrors Filestack's batch behavior:
187
- * onUploadDone fires once even if some files failed — failures land in
188
- * `filesFailed`, successes in `filesUploaded`. `onError` is reserved for
189
- * batch-level failures (no files even started).
190
- */
191
197
  uploadMany(files: (File | Blob)[], opts?: BatchUploadOptions): Promise<PickResponse>;
192
- /**
193
- * Open the file picker modal. Lazy-loads the picker bundle so the core
194
- * SDK stays small for callers that only use `upload()` directly.
195
- */
196
198
  picker(opts?: PickerOptions): PickerHandleLike;
197
199
  /** Read-only accessor used by the React/picker packages. */
198
200
  get config(): Readonly<ResolvedClientConfig>;
@@ -16,15 +16,14 @@ interface PickedFile {
16
16
  source: 'local' | 'url' | 'camera' | 'drop';
17
17
  }
18
18
  interface UploadedFile extends PickedFile {
19
- /** Server-side file id — also stored as `handle` for Filestack compatibility. */
20
19
  handle: string;
21
20
  fileId: string;
22
21
  url: string;
23
- /** Object key in R2 (debugging only). */
24
- key?: string;
22
+ key: string;
23
+ container: string;
25
24
  status: 'Stored';
26
25
  etag?: string;
27
- uploadTags?: Record<string, string>;
26
+ metadata?: Record<string, unknown>;
28
27
  }
29
28
  interface ProgressEvent {
30
29
  totalBytes: number;
@@ -135,6 +134,8 @@ interface PickerBranding {
135
134
  /** Hide "Powered by UnionStack" footer. */
136
135
  hideFooter?: boolean;
137
136
  }
137
+ /** Source tabs available in the picker. Pass an array to hide ones you don't want. */
138
+ type PickerSource = 'device' | 'url';
138
139
  interface PickerOptions extends Omit<BatchUploadOptions, 'signal'> {
139
140
  /** Constrain selectable files (HTML accept syntax). */
140
141
  accept?: string;
@@ -142,12 +143,24 @@ interface PickerOptions extends Omit<BatchUploadOptions, 'signal'> {
142
143
  maxFiles?: number;
143
144
  /** Max file size in bytes — additional client-side check before init. */
144
145
  maxFileSize?: number;
146
+ /**
147
+ * Which file sources to surface as tabs in the picker. Default is
148
+ * `['device', 'url']` (both tabs visible). Set to `['device']` to hide
149
+ * the URL-import tab entirely.
150
+ */
151
+ fromSources?: PickerSource[];
152
+ /**
153
+ * Allow per-image editing (crop / circle / rotate) on queued image rows
154
+ * before upload starts. Defaults to `true`. Set to `false` to hide the
155
+ * Edit affordance — useful if your flow forbids client-side mutation.
156
+ */
157
+ imageEditing?: boolean;
145
158
  /**
146
159
  * Start uploading immediately when the user finishes picking/dropping files,
147
- * skipping the explicit "Upload" button click. Defaults to `true` (modern
148
- * upload UX drop upload). Set to `false` if you want users to review the
149
- * selection and confirm before upload starts.
150
- * @default true
160
+ * skipping the explicit "Upload" button click. Defaults to `false` — users
161
+ * pick, optionally edit images, then click Upload. Set to `true` to skip the
162
+ * review step and kick the upload as soon as the selection is in.
163
+ * @default false
151
164
  */
152
165
  autoUpload?: boolean;
153
166
  theme?: PickerTheme;
@@ -180,19 +193,8 @@ declare class UnionStackClient {
180
193
  */
181
194
  pickerConfigPromise: Promise<PickerConfig | null>;
182
195
  constructor(cfg: ClientConfig);
183
- /** Upload a single file. Mirrors Filestack's `client.upload()`. */
184
196
  upload(file: File | Blob, opts?: UploadOptions): Promise<UploadedFile>;
185
- /**
186
- * Upload multiple files concurrently. Mirrors Filestack's batch behavior:
187
- * onUploadDone fires once even if some files failed — failures land in
188
- * `filesFailed`, successes in `filesUploaded`. `onError` is reserved for
189
- * batch-level failures (no files even started).
190
- */
191
197
  uploadMany(files: (File | Blob)[], opts?: BatchUploadOptions): Promise<PickResponse>;
192
- /**
193
- * Open the file picker modal. Lazy-loads the picker bundle so the core
194
- * SDK stays small for callers that only use `upload()` directly.
195
- */
196
198
  picker(opts?: PickerOptions): PickerHandleLike;
197
199
  /** Read-only accessor used by the React/picker packages. */
198
200
  get config(): Readonly<ResolvedClientConfig>;
package/dist/index.cjs CHANGED
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var chunk6AHBENOC_cjs = require('./chunk-6AHBENOC.cjs');
3
+ var chunkDETAGOYQ_cjs = require('./chunk-DETAGOYQ.cjs');
4
4
 
5
5
  // src/index.ts
6
6
  var UnionStack = {
7
7
  init(cfg) {
8
- return new chunk6AHBENOC_cjs.UnionStackClient(cfg);
8
+ return new chunkDETAGOYQ_cjs.UnionStackClient(cfg);
9
9
  }
10
10
  };
11
11
 
12
12
  Object.defineProperty(exports, "UnionStackClient", {
13
13
  enumerable: true,
14
- get: function () { return chunk6AHBENOC_cjs.UnionStackClient; }
14
+ get: function () { return chunkDETAGOYQ_cjs.UnionStackClient; }
15
15
  });
16
16
  exports.UnionStack = UnionStack;
17
17
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ClientConfig, U as UnionStackClient } from './client-AMBRkgCm.cjs';
2
- export { B as BatchUploadOptions, P as PickResponse, a as PickedFile, b as ProgressEvent, S as Source, c as UploadError, d as UploadErrorCode, e as UploadOptions, f as UploadedFile } from './client-AMBRkgCm.cjs';
1
+ import { C as ClientConfig, U as UnionStackClient } from './client-BKCHbspL.cjs';
2
+ export { B as BatchUploadOptions, P as PickResponse, a as PickedFile, b as ProgressEvent, S as Source, c as UploadError, d as UploadErrorCode, e as UploadOptions, f as UploadedFile } from './client-BKCHbspL.cjs';
3
3
 
4
4
  declare const UnionStack: {
5
5
  init(cfg: ClientConfig): UnionStackClient;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ClientConfig, U as UnionStackClient } from './client-AMBRkgCm.js';
2
- export { B as BatchUploadOptions, P as PickResponse, a as PickedFile, b as ProgressEvent, S as Source, c as UploadError, d as UploadErrorCode, e as UploadOptions, f as UploadedFile } from './client-AMBRkgCm.js';
1
+ import { C as ClientConfig, U as UnionStackClient } from './client-BKCHbspL.js';
2
+ export { B as BatchUploadOptions, P as PickResponse, a as PickedFile, b as ProgressEvent, S as Source, c as UploadError, d as UploadErrorCode, e as UploadOptions, f as UploadedFile } from './client-BKCHbspL.js';
3
3
 
4
4
  declare const UnionStack: {
5
5
  init(cfg: ClientConfig): UnionStackClient;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { UnionStackClient } from './chunk-5OCCDRQG.js';
2
- export { UnionStackClient } from './chunk-5OCCDRQG.js';
1
+ import { UnionStackClient } from './chunk-6Z46YEXF.js';
2
+ export { UnionStackClient } from './chunk-6Z46YEXF.js';
3
3
 
4
4
  // src/index.ts
5
5
  var UnionStack = {