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.js
CHANGED
|
@@ -8,21 +8,33 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/types.ts
|
|
10
10
|
var ApiError = class extends Error {
|
|
11
|
-
constructor(message, data, status) {
|
|
11
|
+
constructor(message, data, status, isAbort = false) {
|
|
12
12
|
super(message);
|
|
13
13
|
this.name = "ApiError";
|
|
14
14
|
this.data = data;
|
|
15
15
|
this.status = status;
|
|
16
|
+
this.isAbort = isAbort;
|
|
16
17
|
}
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
// src/http.ts
|
|
21
|
+
function isAbortError(err) {
|
|
22
|
+
return err instanceof Error && (err.name === "AbortError" || err.message === "Aborted");
|
|
23
|
+
}
|
|
20
24
|
var HttpClient = class {
|
|
21
25
|
/**
|
|
22
26
|
* @param baseUrl The API base URL (e.g. `http://localhost:4000/api`). Trailing slash stripped.
|
|
23
27
|
* @param authStore The auth store providing the token for Authorization headers.
|
|
24
28
|
*/
|
|
25
29
|
constructor(baseUrl, authStore) {
|
|
30
|
+
/**
|
|
31
|
+
* Abort controllers for in-flight requests, keyed by their cancellation key
|
|
32
|
+
* (default `METHOD path`). A new request with the same key aborts the
|
|
33
|
+
* previous one — PocketBase-style auto-cancellation of duplicated requests.
|
|
34
|
+
*/
|
|
35
|
+
this.cancelControllers = {};
|
|
36
|
+
/** Global toggle for the auto-cancellation behaviour (default: on). */
|
|
37
|
+
this.enableAutoCancellation = true;
|
|
26
38
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
27
39
|
this.authStore = authStore;
|
|
28
40
|
this.defaultFetch = globalThis.fetch.bind(globalThis);
|
|
@@ -59,20 +71,73 @@ var HttpClient = class {
|
|
|
59
71
|
return null;
|
|
60
72
|
}
|
|
61
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Globally enable or disable auto-cancellation of duplicated pending requests.
|
|
76
|
+
* Fluent — returns `this` for chaining.
|
|
77
|
+
*/
|
|
78
|
+
autoCancellation(enable) {
|
|
79
|
+
this.enableAutoCancellation = !!enable;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Abort a pending request identified by its cancellation key
|
|
84
|
+
* (default `METHOD path`, e.g. `"GET /api/posts"`). No-op if not pending.
|
|
85
|
+
*/
|
|
86
|
+
cancelRequest(requestKey) {
|
|
87
|
+
const controller = this.cancelControllers[requestKey];
|
|
88
|
+
if (controller) {
|
|
89
|
+
controller.abort();
|
|
90
|
+
delete this.cancelControllers[requestKey];
|
|
91
|
+
}
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
/** Abort all pending requests. */
|
|
95
|
+
cancelAllRequests() {
|
|
96
|
+
for (const key in this.cancelControllers) {
|
|
97
|
+
this.cancelControllers[key].abort();
|
|
98
|
+
}
|
|
99
|
+
this.cancelControllers = {};
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
62
102
|
/**
|
|
63
103
|
* Make an HTTP request with automatic auth token injection and optional auto-refresh.
|
|
64
104
|
*
|
|
105
|
+
* Auto-cancellation: a request keyed by `options.requestKey` (default
|
|
106
|
+
* `METHOD path`) aborts any previous pending request with the same key,
|
|
107
|
+
* so only the last duplicate executes. Set `requestKey: null` or
|
|
108
|
+
* `autoCancel: false` to opt out per request.
|
|
109
|
+
*
|
|
65
110
|
* @param method HTTP method.
|
|
66
111
|
* @param path URL path (appended to baseUrl).
|
|
67
112
|
* @param body JSON-serializable body, or FormData for file uploads.
|
|
68
113
|
* @param options Optional request options.
|
|
69
114
|
* @returns Parsed JSON response, or null for 204 No Content.
|
|
70
|
-
* @throws {ApiError} On non-2xx responses
|
|
115
|
+
* @throws {ApiError} On non-2xx responses or when the request is aborted
|
|
116
|
+
* (aborted requests throw an `ApiError` with `isAbort === true`).
|
|
71
117
|
*/
|
|
72
118
|
async request(method, path, body, options) {
|
|
73
119
|
if (this.authStore.isExpired && this.authStore.collectionName) {
|
|
74
120
|
await this.refreshAuth();
|
|
75
121
|
}
|
|
122
|
+
let requestKey = options?.requestKey === void 0 ? options?.cancelKey ?? `${method} ${path}` : options.requestKey;
|
|
123
|
+
if (options?.autoCancel === false) requestKey = null;
|
|
124
|
+
let controller = null;
|
|
125
|
+
const externalSignal = options?.signal;
|
|
126
|
+
if (requestKey !== null) {
|
|
127
|
+
if (this.enableAutoCancellation) {
|
|
128
|
+
this.cancelRequest(requestKey);
|
|
129
|
+
}
|
|
130
|
+
controller = new AbortController();
|
|
131
|
+
this.cancelControllers[requestKey] = controller;
|
|
132
|
+
if (externalSignal?.aborted) {
|
|
133
|
+
controller.abort();
|
|
134
|
+
} else if (externalSignal) {
|
|
135
|
+
externalSignal.addEventListener("abort", () => controller?.abort(), {
|
|
136
|
+
once: true
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const signal = controller?.signal ?? externalSignal;
|
|
76
141
|
let url = this.baseUrl + path;
|
|
77
142
|
if (options?.params) {
|
|
78
143
|
const qs = new URLSearchParams(options.params).toString();
|
|
@@ -92,7 +157,7 @@ var HttpClient = class {
|
|
|
92
157
|
const init = {
|
|
93
158
|
method,
|
|
94
159
|
headers,
|
|
95
|
-
signal
|
|
160
|
+
signal
|
|
96
161
|
};
|
|
97
162
|
if (body != null && method !== "GET" && method !== "DELETE") {
|
|
98
163
|
if (body instanceof FormData) {
|
|
@@ -101,8 +166,25 @@ var HttpClient = class {
|
|
|
101
166
|
init.body = JSON.stringify(body);
|
|
102
167
|
}
|
|
103
168
|
}
|
|
169
|
+
let res = null;
|
|
104
170
|
const fetcher = options?.fetch ?? this.defaultFetch;
|
|
105
|
-
|
|
171
|
+
try {
|
|
172
|
+
res = await fetcher(url, init);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (isAbortError(err)) {
|
|
175
|
+
throw new ApiError(
|
|
176
|
+
"The request was aborted (most likely auto-cancelled by a newer request with the same requestKey)",
|
|
177
|
+
{},
|
|
178
|
+
0,
|
|
179
|
+
true
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
} finally {
|
|
184
|
+
if (requestKey !== null && this.cancelControllers[requestKey] === controller) {
|
|
185
|
+
delete this.cancelControllers[requestKey];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
106
188
|
if (res.status === 204) return null;
|
|
107
189
|
let bodyText = "";
|
|
108
190
|
let data = {};
|
|
@@ -328,17 +410,19 @@ var CollectionService = class {
|
|
|
328
410
|
* @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
|
|
329
411
|
*/
|
|
330
412
|
getList(page = 1, perPage = 30, options) {
|
|
331
|
-
const { ...rest } = options ?? {};
|
|
413
|
+
const { requestKey, autoCancel, cancelKey, ...rest } = options ?? {};
|
|
332
414
|
const qs = new URLSearchParams(
|
|
333
415
|
Object.fromEntries(
|
|
334
|
-
Object.entries({
|
|
335
|
-
|
|
336
|
-
|
|
416
|
+
Object.entries({
|
|
417
|
+
page: String(page),
|
|
418
|
+
perPage: String(perPage),
|
|
419
|
+
...rest
|
|
420
|
+
}).map(([k, v]) => [k, String(v)])
|
|
337
421
|
)
|
|
338
422
|
).toString();
|
|
339
423
|
return this.http.get(
|
|
340
424
|
"/" + this.encodeId(this.collectionName) + "?" + qs,
|
|
341
|
-
|
|
425
|
+
{ requestKey, autoCancel, cancelKey }
|
|
342
426
|
);
|
|
343
427
|
}
|
|
344
428
|
/**
|
|
@@ -355,7 +439,11 @@ var CollectionService = class {
|
|
|
355
439
|
const res = await this.getList(
|
|
356
440
|
page,
|
|
357
441
|
batch,
|
|
358
|
-
|
|
442
|
+
{
|
|
443
|
+
// disable auto-cancellation across pages — each page request is unique
|
|
444
|
+
...rest,
|
|
445
|
+
requestKey: null
|
|
446
|
+
}
|
|
359
447
|
);
|
|
360
448
|
if (!res || !res.items || res.items.length === 0) break;
|
|
361
449
|
items.push(...res.items);
|
|
@@ -761,6 +849,12 @@ var RealtimeService = class {
|
|
|
761
849
|
function getFileUrl(baseUrl, fileId) {
|
|
762
850
|
return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId);
|
|
763
851
|
}
|
|
852
|
+
function getThumbUrl(baseUrl, fileId, size) {
|
|
853
|
+
return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId) + "/thumbs/" + encodeURIComponent(size);
|
|
854
|
+
}
|
|
855
|
+
function getScaleUrl(baseUrl, fileId, size) {
|
|
856
|
+
return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId) + "/scale/" + encodeURIComponent(size);
|
|
857
|
+
}
|
|
764
858
|
var FilesService = class {
|
|
765
859
|
constructor(http) {
|
|
766
860
|
this.http = http;
|
|
@@ -792,6 +886,25 @@ var FilesService = class {
|
|
|
792
886
|
);
|
|
793
887
|
return data;
|
|
794
888
|
}
|
|
889
|
+
/**
|
|
890
|
+
* List uploaded files (newest first), with optional filters.
|
|
891
|
+
*
|
|
892
|
+
* @param options Filters and pagination.
|
|
893
|
+
*/
|
|
894
|
+
async list(options) {
|
|
895
|
+
const params = {};
|
|
896
|
+
if (options?.page !== void 0) params["page"] = String(options.page);
|
|
897
|
+
if (options?.perPage !== void 0) params["perPage"] = String(options.perPage);
|
|
898
|
+
if (options?.collectionName) params["collectionName"] = options.collectionName;
|
|
899
|
+
if (options?.fieldName) params["fieldName"] = options.fieldName;
|
|
900
|
+
if (options?.mime) params["mime"] = options.mime;
|
|
901
|
+
const data = await this.http.request("GET", "/files", void 0, { params });
|
|
902
|
+
return data ?? { items: [], page: 1, perPage: 50, total: 0 };
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Fetch file metadata including URL.
|
|
906
|
+
* @param fileId The file ID.
|
|
907
|
+
*/
|
|
795
908
|
/**
|
|
796
909
|
* Fetch file metadata including URL.
|
|
797
910
|
* @param fileId The file ID.
|
|
@@ -1016,6 +1129,37 @@ var LazypockClient = class {
|
|
|
1016
1129
|
const schemas = this.schemaByName ? [...this.schemaByName.values()] : [];
|
|
1017
1130
|
return generateTypes(schemas, options);
|
|
1018
1131
|
}
|
|
1132
|
+
// ── Auto-cancellation (PocketBase `autoCancellation` parity) ──
|
|
1133
|
+
/**
|
|
1134
|
+
* Globally enable or disable auto-cancellation of duplicated pending requests.
|
|
1135
|
+
*
|
|
1136
|
+
* When enabled (default), a new request whose `requestKey` (default
|
|
1137
|
+
* `HTTP_METHOD + path`) matches a still-pending request aborts the previous
|
|
1138
|
+
* one — only the last duplicate executes.
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```ts
|
|
1142
|
+
* client.autoCancellation(false); // keep every request
|
|
1143
|
+
* ```
|
|
1144
|
+
*/
|
|
1145
|
+
autoCancellation(enable) {
|
|
1146
|
+
this.http.autoCancellation(enable);
|
|
1147
|
+
return this;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Abort a single pending request by its cancellation key
|
|
1151
|
+
* (default `HTTP_METHOD + path`, e.g. `"GET /api/posts?page=1"`).
|
|
1152
|
+
* The request rejects with an `ApiError` whose `isAbort` is `true`.
|
|
1153
|
+
*/
|
|
1154
|
+
cancelRequest(requestKey) {
|
|
1155
|
+
this.http.cancelRequest(requestKey);
|
|
1156
|
+
return this;
|
|
1157
|
+
}
|
|
1158
|
+
/** Abort all pending requests. */
|
|
1159
|
+
cancelAllRequests() {
|
|
1160
|
+
this.http.cancelAllRequests();
|
|
1161
|
+
return this;
|
|
1162
|
+
}
|
|
1019
1163
|
// ── Auth ──
|
|
1020
1164
|
/** Check whether any superuser exists (for login vs setup screen routing). */
|
|
1021
1165
|
async checkSuperuser() {
|
|
@@ -1059,10 +1203,17 @@ var LazypockClient = class {
|
|
|
1059
1203
|
this.authStore.set(data.token, data.record);
|
|
1060
1204
|
}
|
|
1061
1205
|
} else {
|
|
1062
|
-
|
|
1063
|
-
"/
|
|
1064
|
-
|
|
1065
|
-
|
|
1206
|
+
try {
|
|
1207
|
+
data = await this.http.post("/_superusers/auth-with-password", {
|
|
1208
|
+
identity: email,
|
|
1209
|
+
password
|
|
1210
|
+
});
|
|
1211
|
+
} catch {
|
|
1212
|
+
data = null;
|
|
1213
|
+
}
|
|
1214
|
+
if (!data) {
|
|
1215
|
+
data = await this.http.post("/superusers/login", { email, password });
|
|
1216
|
+
}
|
|
1066
1217
|
if (data) {
|
|
1067
1218
|
this.authStore.setCollectionName(null);
|
|
1068
1219
|
this.authStore.set(data.token, null);
|
|
@@ -1070,9 +1221,13 @@ var LazypockClient = class {
|
|
|
1070
1221
|
}
|
|
1071
1222
|
return data;
|
|
1072
1223
|
}
|
|
1073
|
-
/**
|
|
1224
|
+
/**
|
|
1225
|
+
* Fetch the current authenticated identity (superuser OR auth collection user).
|
|
1226
|
+
* Uses `GET /api/me` (PocketBase parity) — works with both superuser tokens
|
|
1227
|
+
* and auth collection user tokens.
|
|
1228
|
+
*/
|
|
1074
1229
|
async me(options) {
|
|
1075
|
-
const data = await this.http.get("/
|
|
1230
|
+
const data = await this.http.get("/me", options);
|
|
1076
1231
|
if (data) {
|
|
1077
1232
|
this.authStore.set(this.authStore.token, data);
|
|
1078
1233
|
}
|
|
@@ -1144,6 +1299,7 @@ export {
|
|
|
1144
1299
|
CollectionService,
|
|
1145
1300
|
CollectionsService,
|
|
1146
1301
|
FilesService,
|
|
1302
|
+
HttpClient,
|
|
1147
1303
|
LazypockClient,
|
|
1148
1304
|
RealtimeService,
|
|
1149
1305
|
TypedClient,
|
|
@@ -1153,6 +1309,8 @@ export {
|
|
|
1153
1309
|
fieldTypeScriptType,
|
|
1154
1310
|
generateTypes,
|
|
1155
1311
|
getFileUrl,
|
|
1312
|
+
getScaleUrl,
|
|
1313
|
+
getThumbUrl,
|
|
1156
1314
|
schemaFieldType,
|
|
1157
1315
|
wsUrlFromBaseUrl
|
|
1158
1316
|
};
|