@dyrected/sdk 2.5.49 → 2.5.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -253,8 +253,9 @@ var DyrectedClient = class {
253
253
  * Upload a file to this collection. Sends as multipart/form-data.
254
254
  * @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
255
255
  * @param data - Additional metadata fields to save alongside the file (e.g. alt, caption)
256
+ * @param options - Upload options, including an `onProgress` callback for byte-level progress.
256
257
  */
257
- upload: (file, data) => this._upload(slug, file, data),
258
+ upload: (file, data, options) => this._upload(slug, file, data, options),
258
259
  // ---- Auth methods (only meaningful when the collection has auth: true) ----
259
260
  /**
260
261
  * Log in with email + password. Returns a JWT token and the user document.
@@ -469,7 +470,7 @@ var DyrectedClient = class {
469
470
  /**
470
471
  * Internal upload implementation shared by collection().upload() and uploadMedia().
471
472
  */
472
- async _upload(collection, file, data) {
473
+ async _upload(collection, file, data, options) {
473
474
  const formData = new FormData();
474
475
  formData.append("file", file);
475
476
  if (data) {
@@ -477,12 +478,69 @@ var DyrectedClient = class {
477
478
  formData.append(key, value);
478
479
  }
479
480
  }
481
+ if (options?.onProgress && typeof XMLHttpRequest !== "undefined") {
482
+ return this._uploadWithProgress(collection, formData, options);
483
+ }
480
484
  return this.request(`/api/collections/${collection}`, {
481
485
  method: "POST",
482
486
  headers: { "Content-Type": void 0 },
483
487
  body: formData
484
488
  });
485
489
  }
490
+ /**
491
+ * XHR-based upload used when a caller requests progress. Mirrors `request()`'s auth
492
+ * headers and error handling (rate-limit event + DyrectedError) while exposing the
493
+ * upload's byte-level progress via `options.onProgress`.
494
+ */
495
+ _uploadWithProgress(collection, formData, options) {
496
+ const url = `${this.baseUrl}/api/collections/${collection}`;
497
+ return new Promise((resolve, reject) => {
498
+ const xhr = new XMLHttpRequest();
499
+ xhr.open("POST", url, true);
500
+ for (const [key, value] of Object.entries(this.headers)) {
501
+ if (key.toLowerCase() === "content-type") continue;
502
+ xhr.setRequestHeader(key, value);
503
+ }
504
+ if (options.signal) {
505
+ if (options.signal.aborted) {
506
+ reject(new DyrectedError("Upload aborted", 0));
507
+ return;
508
+ }
509
+ options.signal.addEventListener("abort", () => xhr.abort(), { once: true });
510
+ }
511
+ xhr.upload.onprogress = (event) => {
512
+ if (event.lengthComputable) {
513
+ options.onProgress?.(Math.round(event.loaded / event.total * 100));
514
+ }
515
+ };
516
+ const parse = () => {
517
+ try {
518
+ return JSON.parse(xhr.responseText);
519
+ } catch {
520
+ return { message: "Unknown error" };
521
+ }
522
+ };
523
+ xhr.onload = () => {
524
+ if (xhr.status >= 200 && xhr.status < 300) {
525
+ options.onProgress?.(100);
526
+ resolve(parse());
527
+ return;
528
+ }
529
+ const body = parse();
530
+ if (xhr.status === 429 && typeof window !== "undefined") {
531
+ window.dispatchEvent(
532
+ new CustomEvent("dyrected:rate-limit", {
533
+ detail: { message: body.message, code: body.code }
534
+ })
535
+ );
536
+ }
537
+ reject(new DyrectedError(body.message || `Request failed with status ${xhr.status}`, xhr.status, body.code));
538
+ };
539
+ xhr.onerror = () => reject(new DyrectedError("Network error during upload", 0));
540
+ xhr.onabort = () => reject(new DyrectedError("Upload aborted", 0));
541
+ xhr.send(formData);
542
+ });
543
+ }
486
544
  async deleteMedia(id, collection = "media") {
487
545
  return this.delete(collection, id);
488
546
  }
package/dist/index.d.cts CHANGED
@@ -126,6 +126,18 @@ interface BaseSchema {
126
126
  collections: Record<string, UnknownRecord>;
127
127
  globals: Record<string, UnknownRecord>;
128
128
  }
129
+ /**
130
+ * Options for file uploads.
131
+ * When `onProgress` is provided and the runtime supports XMLHttpRequest (browsers),
132
+ * the upload reports real byte-level progress. In other environments (SSR, custom
133
+ * fetch) the callback is ignored and the standard fetch path is used.
134
+ */
135
+ interface UploadOptions {
136
+ /** Called with an integer 0–100 as the file bytes are sent. */
137
+ onProgress?: (percent: number) => void;
138
+ /** Abort the in-flight upload. */
139
+ signal?: AbortSignal;
140
+ }
129
141
  declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
130
142
  private baseUrl;
131
143
  private headers;
@@ -200,8 +212,9 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
200
212
  * Upload a file to this collection. Sends as multipart/form-data.
201
213
  * @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
202
214
  * @param data - Additional metadata fields to save alongside the file (e.g. alt, caption)
215
+ * @param options - Upload options, including an `onProgress` callback for byte-level progress.
203
216
  */
204
- upload: (file: File | Blob, data?: Record<string, string>) => Promise<FileData>;
217
+ upload: (file: File | Blob, data?: Record<string, string>, options?: UploadOptions) => Promise<FileData>;
205
218
  /**
206
219
  * Log in with email + password. Returns a JWT token and the user document.
207
220
  * Call `client.setToken(token)` afterwards to authenticate subsequent requests.
@@ -360,6 +373,12 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
360
373
  * Internal upload implementation shared by collection().upload() and uploadMedia().
361
374
  */
362
375
  private _upload;
376
+ /**
377
+ * XHR-based upload used when a caller requests progress. Mirrors `request()`'s auth
378
+ * headers and error handling (rate-limit event + DyrectedError) while exposing the
379
+ * upload's byte-level progress via `options.onProgress`.
380
+ */
381
+ private _uploadWithProgress;
363
382
  deleteMedia(id: string, collection?: string): Promise<{
364
383
  message: string;
365
384
  }>;
@@ -367,4 +386,4 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
367
386
  }
368
387
  declare function createClient<TSchema extends BaseSchema = BaseSchema>(config: DyrectedClientConfig): DyrectedClient<TSchema>;
369
388
 
370
- export { type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, type TransitionOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient };
389
+ export { type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, type TransitionOptions, type UploadOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient };
package/dist/index.d.ts CHANGED
@@ -126,6 +126,18 @@ interface BaseSchema {
126
126
  collections: Record<string, UnknownRecord>;
127
127
  globals: Record<string, UnknownRecord>;
128
128
  }
129
+ /**
130
+ * Options for file uploads.
131
+ * When `onProgress` is provided and the runtime supports XMLHttpRequest (browsers),
132
+ * the upload reports real byte-level progress. In other environments (SSR, custom
133
+ * fetch) the callback is ignored and the standard fetch path is used.
134
+ */
135
+ interface UploadOptions {
136
+ /** Called with an integer 0–100 as the file bytes are sent. */
137
+ onProgress?: (percent: number) => void;
138
+ /** Abort the in-flight upload. */
139
+ signal?: AbortSignal;
140
+ }
129
141
  declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
130
142
  private baseUrl;
131
143
  private headers;
@@ -200,8 +212,9 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
200
212
  * Upload a file to this collection. Sends as multipart/form-data.
201
213
  * @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
202
214
  * @param data - Additional metadata fields to save alongside the file (e.g. alt, caption)
215
+ * @param options - Upload options, including an `onProgress` callback for byte-level progress.
203
216
  */
204
- upload: (file: File | Blob, data?: Record<string, string>) => Promise<FileData>;
217
+ upload: (file: File | Blob, data?: Record<string, string>, options?: UploadOptions) => Promise<FileData>;
205
218
  /**
206
219
  * Log in with email + password. Returns a JWT token and the user document.
207
220
  * Call `client.setToken(token)` afterwards to authenticate subsequent requests.
@@ -360,6 +373,12 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
360
373
  * Internal upload implementation shared by collection().upload() and uploadMedia().
361
374
  */
362
375
  private _upload;
376
+ /**
377
+ * XHR-based upload used when a caller requests progress. Mirrors `request()`'s auth
378
+ * headers and error handling (rate-limit event + DyrectedError) while exposing the
379
+ * upload's byte-level progress via `options.onProgress`.
380
+ */
381
+ private _uploadWithProgress;
363
382
  deleteMedia(id: string, collection?: string): Promise<{
364
383
  message: string;
365
384
  }>;
@@ -367,4 +386,4 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
367
386
  }
368
387
  declare function createClient<TSchema extends BaseSchema = BaseSchema>(config: DyrectedClientConfig): DyrectedClient<TSchema>;
369
388
 
370
- export { type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, type TransitionOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient };
389
+ export { type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, type TransitionOptions, type UploadOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient };
package/dist/index.js CHANGED
@@ -225,8 +225,9 @@ var DyrectedClient = class {
225
225
  * Upload a file to this collection. Sends as multipart/form-data.
226
226
  * @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
227
227
  * @param data - Additional metadata fields to save alongside the file (e.g. alt, caption)
228
+ * @param options - Upload options, including an `onProgress` callback for byte-level progress.
228
229
  */
229
- upload: (file, data) => this._upload(slug, file, data),
230
+ upload: (file, data, options) => this._upload(slug, file, data, options),
230
231
  // ---- Auth methods (only meaningful when the collection has auth: true) ----
231
232
  /**
232
233
  * Log in with email + password. Returns a JWT token and the user document.
@@ -441,7 +442,7 @@ var DyrectedClient = class {
441
442
  /**
442
443
  * Internal upload implementation shared by collection().upload() and uploadMedia().
443
444
  */
444
- async _upload(collection, file, data) {
445
+ async _upload(collection, file, data, options) {
445
446
  const formData = new FormData();
446
447
  formData.append("file", file);
447
448
  if (data) {
@@ -449,12 +450,69 @@ var DyrectedClient = class {
449
450
  formData.append(key, value);
450
451
  }
451
452
  }
453
+ if (options?.onProgress && typeof XMLHttpRequest !== "undefined") {
454
+ return this._uploadWithProgress(collection, formData, options);
455
+ }
452
456
  return this.request(`/api/collections/${collection}`, {
453
457
  method: "POST",
454
458
  headers: { "Content-Type": void 0 },
455
459
  body: formData
456
460
  });
457
461
  }
462
+ /**
463
+ * XHR-based upload used when a caller requests progress. Mirrors `request()`'s auth
464
+ * headers and error handling (rate-limit event + DyrectedError) while exposing the
465
+ * upload's byte-level progress via `options.onProgress`.
466
+ */
467
+ _uploadWithProgress(collection, formData, options) {
468
+ const url = `${this.baseUrl}/api/collections/${collection}`;
469
+ return new Promise((resolve, reject) => {
470
+ const xhr = new XMLHttpRequest();
471
+ xhr.open("POST", url, true);
472
+ for (const [key, value] of Object.entries(this.headers)) {
473
+ if (key.toLowerCase() === "content-type") continue;
474
+ xhr.setRequestHeader(key, value);
475
+ }
476
+ if (options.signal) {
477
+ if (options.signal.aborted) {
478
+ reject(new DyrectedError("Upload aborted", 0));
479
+ return;
480
+ }
481
+ options.signal.addEventListener("abort", () => xhr.abort(), { once: true });
482
+ }
483
+ xhr.upload.onprogress = (event) => {
484
+ if (event.lengthComputable) {
485
+ options.onProgress?.(Math.round(event.loaded / event.total * 100));
486
+ }
487
+ };
488
+ const parse = () => {
489
+ try {
490
+ return JSON.parse(xhr.responseText);
491
+ } catch {
492
+ return { message: "Unknown error" };
493
+ }
494
+ };
495
+ xhr.onload = () => {
496
+ if (xhr.status >= 200 && xhr.status < 300) {
497
+ options.onProgress?.(100);
498
+ resolve(parse());
499
+ return;
500
+ }
501
+ const body = parse();
502
+ if (xhr.status === 429 && typeof window !== "undefined") {
503
+ window.dispatchEvent(
504
+ new CustomEvent("dyrected:rate-limit", {
505
+ detail: { message: body.message, code: body.code }
506
+ })
507
+ );
508
+ }
509
+ reject(new DyrectedError(body.message || `Request failed with status ${xhr.status}`, xhr.status, body.code));
510
+ };
511
+ xhr.onerror = () => reject(new DyrectedError("Network error during upload", 0));
512
+ xhr.onabort = () => reject(new DyrectedError("Upload aborted", 0));
513
+ xhr.send(formData);
514
+ });
515
+ }
458
516
  async deleteMedia(id, collection = "media") {
459
517
  return this.delete(collection, id);
460
518
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/sdk",
3
- "version": "2.5.49",
3
+ "version": "2.5.52",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -30,7 +30,7 @@
30
30
  "tsup": "^8.5.1",
31
31
  "typescript": "^5.4.5",
32
32
  "vitest": "^1.0.0",
33
- "@dyrected/core": "2.5.49"
33
+ "@dyrected/core": "2.5.52"
34
34
  },
35
35
  "license": "BSL-1.1",
36
36
  "author": "Busola Okeowo <busolaokemoney@gmail.com>",