lazypock 0.1.5 → 0.2.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/README.md +58 -0
- package/dist/cli.cjs +9 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.global.js +9 -2
- package/dist/cli.global.js.map +1 -1
- package/dist/cli.js +9 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +177 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -8
- package/dist/index.d.ts +120 -8
- package/dist/index.global.js +174 -16
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +174 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +12 -3
- 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 +69 -8
- package/src/types.ts +30 -1
package/dist/index.global.js
CHANGED
|
@@ -26,6 +26,7 @@ var Lazypock = (() => {
|
|
|
26
26
|
CollectionService: () => CollectionService,
|
|
27
27
|
CollectionsService: () => CollectionsService,
|
|
28
28
|
FilesService: () => FilesService,
|
|
29
|
+
HttpClient: () => HttpClient,
|
|
29
30
|
LazypockClient: () => LazypockClient,
|
|
30
31
|
RealtimeService: () => RealtimeService,
|
|
31
32
|
TypedClient: () => TypedClient,
|
|
@@ -35,27 +36,41 @@ var Lazypock = (() => {
|
|
|
35
36
|
fieldTypeScriptType: () => fieldTypeScriptType,
|
|
36
37
|
generateTypes: () => generateTypes,
|
|
37
38
|
getFileUrl: () => getFileUrl,
|
|
39
|
+
getScaleUrl: () => getScaleUrl,
|
|
40
|
+
getThumbUrl: () => getThumbUrl,
|
|
38
41
|
schemaFieldType: () => schemaFieldType,
|
|
39
42
|
wsUrlFromBaseUrl: () => wsUrlFromBaseUrl
|
|
40
43
|
});
|
|
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 Lazypock = (() => {
|
|
|
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 Lazypock = (() => {
|
|
|
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 Lazypock = (() => {
|
|
|
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 Lazypock = (() => {
|
|
|
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 Lazypock = (() => {
|
|
|
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 Lazypock = (() => {
|
|
|
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 Lazypock = (() => {
|
|
|
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 @@ ${opts.body}
|
|
|
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() {
|
|
@@ -1276,10 +1423,17 @@ ${opts.body}
|
|
|
1276
1423
|
this.authStore.set(data.token, data.record);
|
|
1277
1424
|
}
|
|
1278
1425
|
} else {
|
|
1279
|
-
|
|
1280
|
-
"/
|
|
1281
|
-
|
|
1282
|
-
|
|
1426
|
+
try {
|
|
1427
|
+
data = await this.http.post("/_superusers/auth-with-password", {
|
|
1428
|
+
identity: email,
|
|
1429
|
+
password
|
|
1430
|
+
});
|
|
1431
|
+
} catch {
|
|
1432
|
+
data = null;
|
|
1433
|
+
}
|
|
1434
|
+
if (!data) {
|
|
1435
|
+
data = await this.http.post("/superusers/login", { email, password });
|
|
1436
|
+
}
|
|
1283
1437
|
if (data) {
|
|
1284
1438
|
this.authStore.setCollectionName(null);
|
|
1285
1439
|
this.authStore.set(data.token, null);
|
|
@@ -1287,9 +1441,13 @@ ${opts.body}
|
|
|
1287
1441
|
}
|
|
1288
1442
|
return data;
|
|
1289
1443
|
}
|
|
1290
|
-
/**
|
|
1444
|
+
/**
|
|
1445
|
+
* Fetch the current authenticated identity (superuser OR auth collection user).
|
|
1446
|
+
* Uses `GET /api/me` (PocketBase parity) — works with both superuser tokens
|
|
1447
|
+
* and auth collection user tokens.
|
|
1448
|
+
*/
|
|
1291
1449
|
async me(options) {
|
|
1292
|
-
const data = await this.http.get("/
|
|
1450
|
+
const data = await this.http.get("/me", options);
|
|
1293
1451
|
if (data) {
|
|
1294
1452
|
this.authStore.set(this.authStore.token, data);
|
|
1295
1453
|
}
|