@weapnl/js-junction 0.1.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -9,6 +9,7 @@ declare class Api {
9
9
  constructor();
10
10
 
11
11
  host(host: string): this;
12
+ chunkUploadsBySize(bytes: number): this;
12
13
  suffix(suffix: string): this;
13
14
  readonly baseUrl: string;
14
15
 
@@ -80,7 +81,7 @@ declare class Request extends Mixins {
80
81
  post(data?: object): Promise<this>;
81
82
  put(data?: object): Promise<this>;
82
83
  setKey(key: string): this;
83
- delete(): Promise<this>;
84
+ delete(data?: object): Promise<this>;
84
85
  storeFiles(files?: object, data?: object, url?: string|null): Promise<this>;
85
86
  readonly bodyParameters: object;
86
87
  onSuccess<T = any>(callback?: (result: T, data: any, response: Response) => void): this;
@@ -126,7 +127,7 @@ export class Model extends Request {
126
127
 
127
128
  update(extraData?: JsonMap): Promise<Model>;
128
129
 
129
- destroy(): Promise<boolean>;
130
+ destroy(extraData?: JsonMap): Promise<boolean>;
130
131
 
131
132
  save(extraData?: JsonMap): Promise<Model>;
132
133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weapnl/js-junction",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "This project allows you to easily consume API's built with Junction.",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
package/src/api.js CHANGED
@@ -12,6 +12,7 @@ export default class Api {
12
12
  this.setHeader('X-Requested-With', 'XMLHttpRequest');
13
13
 
14
14
  this._requests = [];
15
+ this._chunkUploadSize = null;
15
16
 
16
17
  this.host('/').suffix('');
17
18
 
@@ -29,6 +30,20 @@ export default class Api {
29
30
  return this;
30
31
  }
31
32
 
33
+ /**
34
+ * Chunk file uploads into multiple requests when total size exceeds the given size.
35
+ * Useful when the API has a max upload limit, or to split large batches into smaller requests.
36
+ *
37
+ * @param {number} bytes Maximum total file size per request in bytes.
38
+ *
39
+ * @returns {this} The current instance.
40
+ */
41
+ chunkUploadsBySize (bytes) {
42
+ this._chunkUploadSize = bytes;
43
+
44
+ return this;
45
+ }
46
+
32
47
  /**
33
48
  * @param {string} suffix
34
49
  *
@@ -202,7 +202,12 @@ export default class Model extends Request {
202
202
 
203
203
  this._response = await this._connection.post(
204
204
  this._queryString(),
205
- { ...this._attributes.toJson(this), ...this._mediaCollections.toJson(this), ...extraData },
205
+ {
206
+ ...this._attributes.toJson(this),
207
+ ...this._mediaCollections.toJson(this),
208
+ ..._.merge(...this._customParameters),
209
+ ...extraData,
210
+ },
206
211
  );
207
212
 
208
213
  this._connection.removeRequest(this);
@@ -234,7 +239,12 @@ export default class Model extends Request {
234
239
 
235
240
  this._response = await this._connection.put(
236
241
  this._queryString(this._identifier),
237
- { ...this._attributes.toJson(this), ...this._mediaCollections.toJson(this), ...extraData },
242
+ {
243
+ ...this._attributes.toJson(this),
244
+ ...this._mediaCollections.toJson(this),
245
+ ..._.merge(...this._customParameters),
246
+ ...extraData,
247
+ },
238
248
  );
239
249
 
240
250
  this._connection.removeRequest(this);
@@ -257,13 +267,19 @@ export default class Model extends Request {
257
267
  /**
258
268
  * Delete the current model.
259
269
  *
270
+ * @param {Object} [extraData] Extra data to send to the API
271
+ *
260
272
  * @returns {boolean} Whether the deletion was successful.
261
273
  */
262
- async destroy () {
274
+ async destroy (extraData = {}) {
263
275
  this._connection.cancelRunning(this);
264
276
 
265
277
  this._response = await this._connection.delete(
266
278
  this._queryString(this._identifier),
279
+ {
280
+ ..._.merge(...this._customParameters),
281
+ ...extraData,
282
+ },
267
283
  );
268
284
 
269
285
  this._connection.removeRequest(this);
@@ -286,20 +302,69 @@ export default class Model extends Request {
286
302
  */
287
303
  async upload (files, collection) {
288
304
  this._media ??= {};
289
- const filesArray = (Array.isArray(files) ? files : [files]).filter((value) => value !== null);
305
+ files = _.flatMapDeep(Array.isArray(files) ? files : [files]).filter((value) => value instanceof File);
290
306
 
291
- if (filesArray.length === 0) {
307
+ if (files.length === 0) {
292
308
  this._media[collection] = {};
293
309
  return;
294
310
  }
295
311
 
296
- const request = await this.storeFiles({
297
- files: _.flatMapDeep(filesArray),
298
- }, {}, '/media/upload');
312
+ const chunkSize = this._connection.getChunkUploadSize();
313
+
314
+ const chunks = chunkSize ? this._chunkFilesBySize(files, chunkSize) : [files];
315
+
316
+ let allResponseData = [];
317
+
318
+ for (const chunk of chunks) {
319
+ const request = await this.storeFiles({
320
+ files: chunk,
321
+ }, {}, '/media/upload');
322
+
323
+ if (request._response.data) {
324
+ allResponseData = allResponseData.concat(
325
+ _.castArray(request._response.data),
326
+ );
327
+ }
328
+ }
329
+
330
+ this._media[collection] = allResponseData;
331
+
332
+ return allResponseData;
333
+ }
299
334
 
300
- this._media[collection] = request._response.data;
335
+ /**
336
+ * Split files into chunks that don't exceed the given max size.
337
+ * A single file that exceeds the limit will still be sent as its own chunk.
338
+ *
339
+ * @param {File[]} files
340
+ * @param {number} maxSize Maximum total file size per chunk in bytes.
341
+ *
342
+ * @returns {File[][]} Array of file arrays (chunks).
343
+ * @private
344
+ */
345
+ _chunkFilesBySize (files, maxSize) {
346
+ const chunks = [];
347
+ let currentChunk = [];
348
+ let currentSize = 0;
349
+
350
+ for (const file of files) {
351
+ const fileSize = file.size || 0;
352
+
353
+ if (currentChunk.length > 0 && currentSize + fileSize > maxSize) {
354
+ chunks.push(currentChunk);
355
+ currentChunk = [];
356
+ currentSize = 0;
357
+ }
358
+
359
+ currentChunk.push(file);
360
+ currentSize += fileSize;
361
+ }
362
+
363
+ if (currentChunk.length > 0) {
364
+ chunks.push(currentChunk);
365
+ }
301
366
 
302
- return request._response.data;
367
+ return chunks;
303
368
  }
304
369
 
305
370
  /**
package/src/connection.js CHANGED
@@ -33,6 +33,10 @@ export default class Connection {
33
33
  return this._config;
34
34
  }
35
35
 
36
+ getChunkUploadSize () {
37
+ return this._api?._chunkUploadSize ?? null;
38
+ }
39
+
36
40
  setConfig (config) {
37
41
  this._config = config;
38
42
  }
@@ -49,12 +53,12 @@ export default class Connection {
49
53
  return this._execute(query, 'post', data);
50
54
  }
51
55
 
52
- async put (query, params) {
53
- return this._execute(query, 'put', params);
56
+ async put (query, data) {
57
+ return this._execute(query, 'put', data);
54
58
  }
55
59
 
56
- async delete (query) {
57
- return this._execute(query, 'delete');
60
+ async delete (query, data) {
61
+ return this._execute(query, 'delete', data);
58
62
  }
59
63
 
60
64
  async _execute (url, method, data) {
package/src/request.js CHANGED
@@ -150,15 +150,18 @@ export default class Request {
150
150
  }
151
151
 
152
152
  /**
153
+ * @param {Object} data
154
+ *
153
155
  * @returns {this} The current instance.
154
156
  */
155
- async delete () {
157
+ async delete (data = {}) {
156
158
  const url = this.url ?? this.constructor.endpoint;
157
159
 
158
160
  this._connection.cancelRunning(this);
159
161
 
160
162
  this._response = await this._connection.delete(
161
163
  url,
164
+ { ...data, ...this.bodyParameters },
162
165
  );
163
166
 
164
167
  this._connection.removeRequest(this);