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.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() {
|
|
@@ -1276,10 +1423,17 @@ var LazypockClient = class {
|
|
|
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 @@ var LazypockClient = class {
|
|
|
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
|
}
|
|
@@ -1362,6 +1520,7 @@ function createClient(options) {
|
|
|
1362
1520
|
CollectionService,
|
|
1363
1521
|
CollectionsService,
|
|
1364
1522
|
FilesService,
|
|
1523
|
+
HttpClient,
|
|
1365
1524
|
LazypockClient,
|
|
1366
1525
|
RealtimeService,
|
|
1367
1526
|
TypedClient,
|
|
@@ -1371,6 +1530,8 @@ function createClient(options) {
|
|
|
1371
1530
|
fieldTypeScriptType,
|
|
1372
1531
|
generateTypes,
|
|
1373
1532
|
getFileUrl,
|
|
1533
|
+
getScaleUrl,
|
|
1534
|
+
getThumbUrl,
|
|
1374
1535
|
schemaFieldType,
|
|
1375
1536
|
wsUrlFromBaseUrl
|
|
1376
1537
|
});
|