svelte-ag 1.2.6 → 1.2.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"query.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/api/query/query.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEnH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,qBAAa,KAAK,CAChB,GAAG,SAAS,YAAY,EACxB,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC;;gBAyBzC,EACV,IAAI,EACJ,MAAM,EACN,KAAK,EACL,SAAS,EACT,KAAK,EACL,IAAI,EACL,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,KAAK,EAAE,KAAK,CAAC;QACb,IAAI,CAAC,EAAE;YACL,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC1C,CAAC;KACH;IAgBK,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAyCxD,IAAI,QAAQ,WAEX;IACD,IAAI,QAAQ,YAEX;IACD,UAAU;IAMV,IAAI,MAAM,gBAET;IACD,IAAI,IAAI,IAAI,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAEnD;IACD,IAAI,SAAS,IAAI,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAEtD;CACF;AAED,qBAAa,SAAS,CACpB,GAAG,SAAS,YAAY,EACxB,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC;;gBA6BnD,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAChC,MAAM,EAAE,KAAK,EACb,YAAY,CAAC,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC;YAelC,KAAK;IAOnB;;;OAGG;YACW,eAAe;IA6BvB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;CAmB3F"}
1
+ {"version":3,"file":"query.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/api/query/query.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEnH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,qBAAa,KAAK,CAChB,GAAG,SAAS,YAAY,EACxB,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC;;gBAyBzC,EACV,IAAI,EACJ,MAAM,EACN,KAAK,EACL,SAAS,EACT,KAAK,EACL,IAAI,EACL,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,KAAK,EAAE,KAAK,CAAC;QACb,IAAI,CAAC,EAAE;YACL,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC1C,CAAC;KACH;IAgBK,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAyCxD,IAAI,QAAQ,WAEX;IACD,IAAI,QAAQ,YAEX;IACD,UAAU;IAMV,IAAI,MAAM,gBAET;IACD,IAAI,IAAI,IAAI,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAEnD;IACD,IAAI,SAAS,IAAI,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAEtD;CACF;AAED,qBAAa,SAAS,CACpB,GAAG,SAAS,YAAY,EACxB,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC;;gBA6BnD,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAChC,MAAM,EAAE,KAAK,EACb,YAAY,CAAC,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC;YAelC,KAAK;IAOnB;;;OAGG;YACW,eAAe;IA6BvB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;CAuB3F"}
@@ -127,7 +127,9 @@ export class Requestor {
127
127
  * Then it separates the outputs and resolves each of the promises
128
128
  */
129
129
  async flushBatchQueue(batchId) {
130
- const queue = this.#batchQueue[batchId].splice(0);
130
+ const queue = this.#batchQueue[batchId]?.splice(0) ?? [];
131
+ if (queue.length === 0)
132
+ return;
131
133
  // TODO maybe remove the unBatchOutput function and just always return the
132
134
  // same response and then its on each consumer of each query to find the relevant records
133
135
  try {
@@ -157,8 +159,11 @@ export class Requestor {
157
159
  this.#batchQueue[batchId].push({ input, resolve, reject });
158
160
  if (!this.#batchTimers[batchId]) {
159
161
  this.#batchTimers[batchId] = setTimeout(() => {
162
+ delete this.#batchTimers[batchId];
160
163
  void this.flushBatchQueue(batchId).finally(() => {
161
- delete this.#batchTimers[batchId];
164
+ if (this.#batchQueue[batchId]?.length === 0) {
165
+ delete this.#batchQueue[batchId];
166
+ }
162
167
  });
163
168
  }, this.#batchDelay);
164
169
  }
@@ -180,6 +180,48 @@ describe('Requestor', () => {
180
180
  await vi.advanceTimersByTimeAsync(100);
181
181
  await Promise.all([p1Expectation, p2Expectation]);
182
182
  });
183
+ it('schedules a new batch for requests added while a previous batch is in flight', async () => {
184
+ const firstFetch = deferred();
185
+ const secondFetch = deferred();
186
+ const fetchMock = vi.fn((_url, init) => {
187
+ if (init?.body === JSON.stringify({ ids: [1] })) {
188
+ return firstFetch.promise;
189
+ }
190
+ if (init?.body === JSON.stringify({ ids: [2] })) {
191
+ return secondFetch.promise;
192
+ }
193
+ throw new Error(`Unexpected request body: ${String(init?.body)}`);
194
+ });
195
+ vi.stubGlobal('fetch', fetchMock);
196
+ const requestor = createBatchedRequestor({
197
+ canBatch: (input) => ('group' in input && input.group) || false,
198
+ batchInput: (inputs) => ({ ids: inputs.map(getSingleId) }),
199
+ unBatchOutput: (inputs) => inputs.map((input) => jsonResponse({ id: getSingleId(input) }))
200
+ });
201
+ const p1 = requestor.request({ id: 1, group: 'team' });
202
+ await vi.advanceTimersByTimeAsync(100);
203
+ expect(fetchMock).toHaveBeenCalledTimes(1);
204
+ expect(fetchMock).toHaveBeenLastCalledWith(`${API_URL}//users`, expect.objectContaining({
205
+ method: 'POST',
206
+ body: JSON.stringify({ ids: [1] }),
207
+ credentials: 'include'
208
+ }));
209
+ const p2 = requestor.request({ id: 2, group: 'team' });
210
+ await vi.advanceTimersByTimeAsync(99);
211
+ expect(fetchMock).toHaveBeenCalledTimes(1);
212
+ await vi.advanceTimersByTimeAsync(1);
213
+ expect(fetchMock).toHaveBeenCalledTimes(2);
214
+ expect(fetchMock).toHaveBeenLastCalledWith(`${API_URL}//users`, expect.objectContaining({
215
+ method: 'POST',
216
+ body: JSON.stringify({ ids: [2] }),
217
+ credentials: 'include'
218
+ }));
219
+ firstFetch.resolve(jsonFetchResponse({ ok: true }));
220
+ secondFetch.resolve(jsonFetchResponse({ ok: true }));
221
+ const [response1, response2] = await Promise.all([p1, p2]);
222
+ await expect(response1.json()).resolves.toEqual({ id: 1 });
223
+ await expect(response2.json()).resolves.toEqual({ id: 2 });
224
+ });
183
225
  });
184
226
  describe('Query', () => {
185
227
  beforeEach(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-ag",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "Useful svelte components",
5
5
  "bugs": "https://github.com/ageorgeh/svelte-ag/issues",
6
6
  "author": "Alexander Hornung",
@@ -195,19 +195,19 @@ export class Requestor<
195
195
  * Then it separates the outputs and resolves each of the promises
196
196
  */
197
197
  private async flushBatchQueue(batchId: string): Promise<void> {
198
- const queue = this.#batchQueue[batchId].splice(0);
198
+ const queue = this.#batchQueue[batchId]?.splice(0) ?? [];
199
+ if (queue.length === 0) return;
199
200
 
200
201
  // TODO maybe remove the unBatchOutput function and just always return the
201
202
  // same response and then its on each consumer of each query to find the relevant records
202
203
  try {
203
204
  const batchedInput = this.#batchInput(queue.map((q) => q.input));
204
-
205
205
  const res = await this.fetch(batchedInput);
206
+
206
207
  const output = await this.#unBatchOutput(
207
208
  queue.map((q) => q.input),
208
209
  res
209
210
  );
210
-
211
211
  if (output.length !== queue.length) {
212
212
  throw new Error(`Batch output length mismatch for ${batchId}`);
213
213
  }
@@ -232,8 +232,12 @@ export class Requestor<
232
232
 
233
233
  if (!this.#batchTimers[batchId]) {
234
234
  this.#batchTimers[batchId] = setTimeout(() => {
235
+ delete this.#batchTimers[batchId];
236
+
235
237
  void this.flushBatchQueue(batchId).finally(() => {
236
- delete this.#batchTimers[batchId];
238
+ if (this.#batchQueue[batchId]?.length === 0) {
239
+ delete this.#batchQueue[batchId];
240
+ }
237
241
  });
238
242
  }, this.#batchDelay);
239
243
  }
@@ -272,6 +272,70 @@ describe('Requestor', () => {
272
272
 
273
273
  await Promise.all([p1Expectation, p2Expectation]);
274
274
  });
275
+
276
+ it('schedules a new batch for requests added while a previous batch is in flight', async () => {
277
+ const firstFetch = deferred<Response>();
278
+ const secondFetch = deferred<Response>();
279
+
280
+ const fetchMock = vi.fn((_url: string, init?: RequestInit) => {
281
+ if (init?.body === JSON.stringify({ ids: [1] })) {
282
+ return firstFetch.promise;
283
+ }
284
+
285
+ if (init?.body === JSON.stringify({ ids: [2] })) {
286
+ return secondFetch.promise;
287
+ }
288
+
289
+ throw new Error(`Unexpected request body: ${String(init?.body)}`);
290
+ });
291
+
292
+ vi.stubGlobal('fetch', fetchMock);
293
+
294
+ const requestor = createBatchedRequestor({
295
+ canBatch: (input) => ('group' in input && input.group) || false,
296
+ batchInput: (inputs) => ({ ids: inputs.map(getSingleId) }),
297
+ unBatchOutput: (inputs) => inputs.map((input) => jsonResponse({ id: getSingleId(input) }))
298
+ });
299
+
300
+ const p1 = requestor.request({ id: 1, group: 'team' });
301
+
302
+ await vi.advanceTimersByTimeAsync(100);
303
+
304
+ expect(fetchMock).toHaveBeenCalledTimes(1);
305
+ expect(fetchMock).toHaveBeenLastCalledWith(
306
+ `${API_URL}//users`,
307
+ expect.objectContaining({
308
+ method: 'POST',
309
+ body: JSON.stringify({ ids: [1] }),
310
+ credentials: 'include'
311
+ })
312
+ );
313
+
314
+ const p2 = requestor.request({ id: 2, group: 'team' });
315
+
316
+ await vi.advanceTimersByTimeAsync(99);
317
+ expect(fetchMock).toHaveBeenCalledTimes(1);
318
+
319
+ await vi.advanceTimersByTimeAsync(1);
320
+
321
+ expect(fetchMock).toHaveBeenCalledTimes(2);
322
+ expect(fetchMock).toHaveBeenLastCalledWith(
323
+ `${API_URL}//users`,
324
+ expect.objectContaining({
325
+ method: 'POST',
326
+ body: JSON.stringify({ ids: [2] }),
327
+ credentials: 'include'
328
+ })
329
+ );
330
+
331
+ firstFetch.resolve(jsonFetchResponse({ ok: true }));
332
+ secondFetch.resolve(jsonFetchResponse({ ok: true }));
333
+
334
+ const [response1, response2] = await Promise.all([p1, p2]);
335
+
336
+ await expect(response1.json()).resolves.toEqual({ id: 1 });
337
+ await expect(response2.json()).resolves.toEqual({ id: 2 });
338
+ });
275
339
  });
276
340
 
277
341
  describe('Query', () => {