svelte-ag 1.2.6 → 1.2.8
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;
|
|
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]
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Useful svelte components",
|
|
5
5
|
"bugs": "https://github.com/ageorgeh/svelte-ag/issues",
|
|
6
6
|
"author": "Alexander Hornung",
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
"sveltekit-superforms": "2.30.1",
|
|
64
64
|
"tailwind-merge": "^3.6.0",
|
|
65
65
|
"tailwind-variants": "^3.2.2",
|
|
66
|
-
"ts-ag": "^1.2.7",
|
|
67
66
|
"tsc-alias": "^1.8.17",
|
|
68
67
|
"valibot": "^1.4.1"
|
|
69
68
|
},
|
|
@@ -102,6 +101,7 @@
|
|
|
102
101
|
"svelte-preprocess": "^6.0.5",
|
|
103
102
|
"svelte2tsx": "^0.7.56",
|
|
104
103
|
"tailwindcss": "4.3.0",
|
|
104
|
+
"ts-ag": "^1.2.7",
|
|
105
105
|
"tsdown": "^0.22.2",
|
|
106
106
|
"tsx": "^4.22.4",
|
|
107
107
|
"tw-animate-css": "^1.4.0",
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
"svelte": "^5.0.0",
|
|
118
118
|
"tailwind-variants": "^3.2.2",
|
|
119
119
|
"tailwindcss": "^4.2.1",
|
|
120
|
+
"ts-ag": "^1.2.7",
|
|
120
121
|
"tw-animate-css": "^1.4.0"
|
|
121
122
|
},
|
|
122
123
|
"tailwindSources": "./dist/tailwind-sources.manifest.jsonc"
|
|
@@ -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]
|
|
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
|
-
|
|
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', () => {
|