lazypock 0.1.5 → 0.1.6
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/README.md +58 -0
- package/dist/index.cjs +160 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -7
- package/dist/index.d.ts +115 -7
- package/dist/index.global.js +157 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +157 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/collection.ts +12 -7
- package/src/files.ts +75 -0
- package/src/http.ts +120 -8
- package/src/index.ts +3 -0
- package/src/lazypock.ts +45 -1
- package/src/types.ts +30 -1
package/README.md
CHANGED
|
@@ -180,6 +180,12 @@ The main client class.
|
|
|
180
180
|
| `authStore` | `AuthStore` | auto-created | Explicit auth store instance |
|
|
181
181
|
| `realtime` | `RealtimeService` | auto-created | Real-time service for WebSocket subscriptions |
|
|
182
182
|
|
|
183
|
+
#### Auto-Cancellation Methods
|
|
184
|
+
|
|
185
|
+
- `autoCancellation(enable)` — Globally enable/disable auto-cancellation of duplicated pending requests
|
|
186
|
+
- `cancelRequest(requestKey)` — Abort a single pending request by key (default `HTTP_METHOD + path`)
|
|
187
|
+
- `cancelAllRequests()` — Abort all pending requests
|
|
188
|
+
|
|
183
189
|
#### Authentication Methods
|
|
184
190
|
|
|
185
191
|
- `login(email, password, collection?)` — Login as superuser or auth collection user
|
|
@@ -291,6 +297,58 @@ interface RequestOptions {
|
|
|
291
297
|
}
|
|
292
298
|
```
|
|
293
299
|
|
|
300
|
+
## Auto Cancellation
|
|
301
|
+
|
|
302
|
+
The SDK auto-cancels duplicated pending requests for you (PocketBase-compatible
|
|
303
|
+
behaviour). When a new request is issued with the same request key as a
|
|
304
|
+
still-pending request, the previous one is aborted — only the last request
|
|
305
|
+
executes:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Only the last call will execute; the first two are auto-cancelled
|
|
309
|
+
await client.collection('posts').getList(1, 20); // cancelled
|
|
310
|
+
await client.collection('posts').getList(2, 20); // cancelled
|
|
311
|
+
await client.collection('posts').getList(3, 20); // executed
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
By default the request key is `HTTP_METHOD + path` (e.g. `"GET /api/posts?page=1"`), so
|
|
315
|
+
duplicate calls with identical URLs cancel each other. Cancelled requests reject
|
|
316
|
+
with an `ApiError` whose `isAbort` is `true`:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
try {
|
|
320
|
+
await client.collection('posts').getList(1, 20);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (err instanceof ApiError && err.isAbort) {
|
|
323
|
+
// superseded by a newer request — safe to ignore
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### Per-request control
|
|
329
|
+
|
|
330
|
+
Pass `requestKey` in the request options to customize the key, or disable
|
|
331
|
+
auto-cancellation for a specific request:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
await client.collection('posts').getList(1, 20, { requestKey: 'my-list' }); // cancelled
|
|
335
|
+
await client.collection('posts').getList(1, 20, { requestKey: 'my-list' }); // executed
|
|
336
|
+
|
|
337
|
+
await client.collection('posts').getList(1, 20, { requestKey: null }); // executed
|
|
338
|
+
await client.collection('posts').getList(1, 20, { requestKey: null }); // executed
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### Global control
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// Disable auto-cancellation globally
|
|
345
|
+
client.autoCancellation(false);
|
|
346
|
+
|
|
347
|
+
// Manually cancel pending requests
|
|
348
|
+
client.cancelRequest('GET /api/posts?page=1');
|
|
349
|
+
client.cancelAllRequests();
|
|
350
|
+
```
|
|
351
|
+
|
|
294
352
|
## Error Handling
|
|
295
353
|
|
|
296
354
|
The SDK throws `ApiError` on non-2xx responses:
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
CollectionService: () => CollectionService,
|
|
26
26
|
CollectionsService: () => CollectionsService,
|
|
27
27
|
FilesService: () => FilesService,
|
|
28
|
+
HttpClient: () => HttpClient,
|
|
28
29
|
LazypockClient: () => LazypockClient,
|
|
29
30
|
RealtimeService: () => RealtimeService,
|
|
30
31
|
TypedClient: () => TypedClient,
|
|
@@ -34,6 +35,8 @@ __export(index_exports, {
|
|
|
34
35
|
fieldTypeScriptType: () => fieldTypeScriptType,
|
|
35
36
|
generateTypes: () => generateTypes,
|
|
36
37
|
getFileUrl: () => getFileUrl,
|
|
38
|
+
getScaleUrl: () => getScaleUrl,
|
|
39
|
+
getThumbUrl: () => getThumbUrl,
|
|
37
40
|
schemaFieldType: () => schemaFieldType,
|
|
38
41
|
wsUrlFromBaseUrl: () => wsUrlFromBaseUrl
|
|
39
42
|
});
|
|
@@ -41,21 +44,33 @@ module.exports = __toCommonJS(index_exports);
|
|
|
41
44
|
|
|
42
45
|
// src/types.ts
|
|
43
46
|
var ApiError = class extends Error {
|
|
44
|
-
constructor(message, data, status) {
|
|
47
|
+
constructor(message, data, status, isAbort = false) {
|
|
45
48
|
super(message);
|
|
46
49
|
this.name = "ApiError";
|
|
47
50
|
this.data = data;
|
|
48
51
|
this.status = status;
|
|
52
|
+
this.isAbort = isAbort;
|
|
49
53
|
}
|
|
50
54
|
};
|
|
51
55
|
|
|
52
56
|
// src/http.ts
|
|
57
|
+
function isAbortError(err) {
|
|
58
|
+
return err instanceof Error && (err.name === "AbortError" || err.message === "Aborted");
|
|
59
|
+
}
|
|
53
60
|
var HttpClient = class {
|
|
54
61
|
/**
|
|
55
62
|
* @param baseUrl The API base URL (e.g. `http://localhost:4000/api`). Trailing slash stripped.
|
|
56
63
|
* @param authStore The auth store providing the token for Authorization headers.
|
|
57
64
|
*/
|
|
58
65
|
constructor(baseUrl, authStore) {
|
|
66
|
+
/**
|
|
67
|
+
* Abort controllers for in-flight requests, keyed by their cancellation key
|
|
68
|
+
* (default `METHOD path`). A new request with the same key aborts the
|
|
69
|
+
* previous one — PocketBase-style auto-cancellation of duplicated requests.
|
|
70
|
+
*/
|
|
71
|
+
this.cancelControllers = {};
|
|
72
|
+
/** Global toggle for the auto-cancellation behaviour (default: on). */
|
|
73
|
+
this.enableAutoCancellation = true;
|
|
59
74
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
60
75
|
this.authStore = authStore;
|
|
61
76
|
this.defaultFetch = globalThis.fetch.bind(globalThis);
|
|
@@ -92,20 +107,73 @@ var HttpClient = class {
|
|
|
92
107
|
return null;
|
|
93
108
|
}
|
|
94
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Globally enable or disable auto-cancellation of duplicated pending requests.
|
|
112
|
+
* Fluent — returns `this` for chaining.
|
|
113
|
+
*/
|
|
114
|
+
autoCancellation(enable) {
|
|
115
|
+
this.enableAutoCancellation = !!enable;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Abort a pending request identified by its cancellation key
|
|
120
|
+
* (default `METHOD path`, e.g. `"GET /api/posts"`). No-op if not pending.
|
|
121
|
+
*/
|
|
122
|
+
cancelRequest(requestKey) {
|
|
123
|
+
const controller = this.cancelControllers[requestKey];
|
|
124
|
+
if (controller) {
|
|
125
|
+
controller.abort();
|
|
126
|
+
delete this.cancelControllers[requestKey];
|
|
127
|
+
}
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/** Abort all pending requests. */
|
|
131
|
+
cancelAllRequests() {
|
|
132
|
+
for (const key in this.cancelControllers) {
|
|
133
|
+
this.cancelControllers[key].abort();
|
|
134
|
+
}
|
|
135
|
+
this.cancelControllers = {};
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
95
138
|
/**
|
|
96
139
|
* Make an HTTP request with automatic auth token injection and optional auto-refresh.
|
|
97
140
|
*
|
|
141
|
+
* Auto-cancellation: a request keyed by `options.requestKey` (default
|
|
142
|
+
* `METHOD path`) aborts any previous pending request with the same key,
|
|
143
|
+
* so only the last duplicate executes. Set `requestKey: null` or
|
|
144
|
+
* `autoCancel: false` to opt out per request.
|
|
145
|
+
*
|
|
98
146
|
* @param method HTTP method.
|
|
99
147
|
* @param path URL path (appended to baseUrl).
|
|
100
148
|
* @param body JSON-serializable body, or FormData for file uploads.
|
|
101
149
|
* @param options Optional request options.
|
|
102
150
|
* @returns Parsed JSON response, or null for 204 No Content.
|
|
103
|
-
* @throws {ApiError} On non-2xx responses
|
|
151
|
+
* @throws {ApiError} On non-2xx responses or when the request is aborted
|
|
152
|
+
* (aborted requests throw an `ApiError` with `isAbort === true`).
|
|
104
153
|
*/
|
|
105
154
|
async request(method, path, body, options) {
|
|
106
155
|
if (this.authStore.isExpired && this.authStore.collectionName) {
|
|
107
156
|
await this.refreshAuth();
|
|
108
157
|
}
|
|
158
|
+
let requestKey = options?.requestKey === void 0 ? options?.cancelKey ?? `${method} ${path}` : options.requestKey;
|
|
159
|
+
if (options?.autoCancel === false) requestKey = null;
|
|
160
|
+
let controller = null;
|
|
161
|
+
const externalSignal = options?.signal;
|
|
162
|
+
if (requestKey !== null) {
|
|
163
|
+
if (this.enableAutoCancellation) {
|
|
164
|
+
this.cancelRequest(requestKey);
|
|
165
|
+
}
|
|
166
|
+
controller = new AbortController();
|
|
167
|
+
this.cancelControllers[requestKey] = controller;
|
|
168
|
+
if (externalSignal?.aborted) {
|
|
169
|
+
controller.abort();
|
|
170
|
+
} else if (externalSignal) {
|
|
171
|
+
externalSignal.addEventListener("abort", () => controller?.abort(), {
|
|
172
|
+
once: true
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const signal = controller?.signal ?? externalSignal;
|
|
109
177
|
let url = this.baseUrl + path;
|
|
110
178
|
if (options?.params) {
|
|
111
179
|
const qs = new URLSearchParams(options.params).toString();
|
|
@@ -125,7 +193,7 @@ var HttpClient = class {
|
|
|
125
193
|
const init = {
|
|
126
194
|
method,
|
|
127
195
|
headers,
|
|
128
|
-
signal
|
|
196
|
+
signal
|
|
129
197
|
};
|
|
130
198
|
if (body != null && method !== "GET" && method !== "DELETE") {
|
|
131
199
|
if (body instanceof FormData) {
|
|
@@ -134,8 +202,25 @@ var HttpClient = class {
|
|
|
134
202
|
init.body = JSON.stringify(body);
|
|
135
203
|
}
|
|
136
204
|
}
|
|
205
|
+
let res = null;
|
|
137
206
|
const fetcher = options?.fetch ?? this.defaultFetch;
|
|
138
|
-
|
|
207
|
+
try {
|
|
208
|
+
res = await fetcher(url, init);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
if (isAbortError(err)) {
|
|
211
|
+
throw new ApiError(
|
|
212
|
+
"The request was aborted (most likely auto-cancelled by a newer request with the same requestKey)",
|
|
213
|
+
{},
|
|
214
|
+
0,
|
|
215
|
+
true
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
throw err;
|
|
219
|
+
} finally {
|
|
220
|
+
if (requestKey !== null && this.cancelControllers[requestKey] === controller) {
|
|
221
|
+
delete this.cancelControllers[requestKey];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
139
224
|
if (res.status === 204) return null;
|
|
140
225
|
let bodyText = "";
|
|
141
226
|
let data = {};
|
|
@@ -361,17 +446,19 @@ var CollectionService = class {
|
|
|
361
446
|
* @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
|
|
362
447
|
*/
|
|
363
448
|
getList(page = 1, perPage = 30, options) {
|
|
364
|
-
const { ...rest } = options ?? {};
|
|
449
|
+
const { requestKey, autoCancel, cancelKey, ...rest } = options ?? {};
|
|
365
450
|
const qs = new URLSearchParams(
|
|
366
451
|
Object.fromEntries(
|
|
367
|
-
Object.entries({
|
|
368
|
-
|
|
369
|
-
|
|
452
|
+
Object.entries({
|
|
453
|
+
page: String(page),
|
|
454
|
+
perPage: String(perPage),
|
|
455
|
+
...rest
|
|
456
|
+
}).map(([k, v]) => [k, String(v)])
|
|
370
457
|
)
|
|
371
458
|
).toString();
|
|
372
459
|
return this.http.get(
|
|
373
460
|
"/" + this.encodeId(this.collectionName) + "?" + qs,
|
|
374
|
-
|
|
461
|
+
{ requestKey, autoCancel, cancelKey }
|
|
375
462
|
);
|
|
376
463
|
}
|
|
377
464
|
/**
|
|
@@ -388,7 +475,11 @@ var CollectionService = class {
|
|
|
388
475
|
const res = await this.getList(
|
|
389
476
|
page,
|
|
390
477
|
batch,
|
|
391
|
-
|
|
478
|
+
{
|
|
479
|
+
// disable auto-cancellation across pages — each page request is unique
|
|
480
|
+
...rest,
|
|
481
|
+
requestKey: null
|
|
482
|
+
}
|
|
392
483
|
);
|
|
393
484
|
if (!res || !res.items || res.items.length === 0) break;
|
|
394
485
|
items.push(...res.items);
|
|
@@ -794,6 +885,12 @@ var RealtimeService = class {
|
|
|
794
885
|
function getFileUrl(baseUrl, fileId) {
|
|
795
886
|
return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId);
|
|
796
887
|
}
|
|
888
|
+
function getThumbUrl(baseUrl, fileId, size) {
|
|
889
|
+
return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId) + "/thumbs/" + encodeURIComponent(size);
|
|
890
|
+
}
|
|
891
|
+
function getScaleUrl(baseUrl, fileId, size) {
|
|
892
|
+
return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId) + "/scale/" + encodeURIComponent(size);
|
|
893
|
+
}
|
|
797
894
|
var FilesService = class {
|
|
798
895
|
constructor(http) {
|
|
799
896
|
this.http = http;
|
|
@@ -825,6 +922,25 @@ var FilesService = class {
|
|
|
825
922
|
);
|
|
826
923
|
return data;
|
|
827
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* List uploaded files (newest first), with optional filters.
|
|
927
|
+
*
|
|
928
|
+
* @param options Filters and pagination.
|
|
929
|
+
*/
|
|
930
|
+
async list(options) {
|
|
931
|
+
const params = {};
|
|
932
|
+
if (options?.page !== void 0) params["page"] = String(options.page);
|
|
933
|
+
if (options?.perPage !== void 0) params["perPage"] = String(options.perPage);
|
|
934
|
+
if (options?.collectionName) params["collectionName"] = options.collectionName;
|
|
935
|
+
if (options?.fieldName) params["fieldName"] = options.fieldName;
|
|
936
|
+
if (options?.mime) params["mime"] = options.mime;
|
|
937
|
+
const data = await this.http.request("GET", "/files", void 0, { params });
|
|
938
|
+
return data ?? { items: [], page: 1, perPage: 50, total: 0 };
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Fetch file metadata including URL.
|
|
942
|
+
* @param fileId The file ID.
|
|
943
|
+
*/
|
|
828
944
|
/**
|
|
829
945
|
* Fetch file metadata including URL.
|
|
830
946
|
* @param fileId The file ID.
|
|
@@ -1233,6 +1349,37 @@ var LazypockClient = class {
|
|
|
1233
1349
|
const schemas = this.schemaByName ? [...this.schemaByName.values()] : [];
|
|
1234
1350
|
return generateTypes(schemas, options);
|
|
1235
1351
|
}
|
|
1352
|
+
// ── Auto-cancellation (PocketBase `autoCancellation` parity) ──
|
|
1353
|
+
/**
|
|
1354
|
+
* Globally enable or disable auto-cancellation of duplicated pending requests.
|
|
1355
|
+
*
|
|
1356
|
+
* When enabled (default), a new request whose `requestKey` (default
|
|
1357
|
+
* `HTTP_METHOD + path`) matches a still-pending request aborts the previous
|
|
1358
|
+
* one — only the last duplicate executes.
|
|
1359
|
+
*
|
|
1360
|
+
* @example
|
|
1361
|
+
* ```ts
|
|
1362
|
+
* client.autoCancellation(false); // keep every request
|
|
1363
|
+
* ```
|
|
1364
|
+
*/
|
|
1365
|
+
autoCancellation(enable) {
|
|
1366
|
+
this.http.autoCancellation(enable);
|
|
1367
|
+
return this;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Abort a single pending request by its cancellation key
|
|
1371
|
+
* (default `HTTP_METHOD + path`, e.g. `"GET /api/posts?page=1"`).
|
|
1372
|
+
* The request rejects with an `ApiError` whose `isAbort` is `true`.
|
|
1373
|
+
*/
|
|
1374
|
+
cancelRequest(requestKey) {
|
|
1375
|
+
this.http.cancelRequest(requestKey);
|
|
1376
|
+
return this;
|
|
1377
|
+
}
|
|
1378
|
+
/** Abort all pending requests. */
|
|
1379
|
+
cancelAllRequests() {
|
|
1380
|
+
this.http.cancelAllRequests();
|
|
1381
|
+
return this;
|
|
1382
|
+
}
|
|
1236
1383
|
// ── Auth ──
|
|
1237
1384
|
/** Check whether any superuser exists (for login vs setup screen routing). */
|
|
1238
1385
|
async checkSuperuser() {
|
|
@@ -1362,6 +1509,7 @@ function createClient(options) {
|
|
|
1362
1509
|
CollectionService,
|
|
1363
1510
|
CollectionsService,
|
|
1364
1511
|
FilesService,
|
|
1512
|
+
HttpClient,
|
|
1365
1513
|
LazypockClient,
|
|
1366
1514
|
RealtimeService,
|
|
1367
1515
|
TypedClient,
|
|
@@ -1371,6 +1519,8 @@ function createClient(options) {
|
|
|
1371
1519
|
fieldTypeScriptType,
|
|
1372
1520
|
generateTypes,
|
|
1373
1521
|
getFileUrl,
|
|
1522
|
+
getScaleUrl,
|
|
1523
|
+
getThumbUrl,
|
|
1374
1524
|
schemaFieldType,
|
|
1375
1525
|
wsUrlFromBaseUrl
|
|
1376
1526
|
});
|