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/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: options?.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
- const res = await fetcher(url, init);
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({ page: String(page), perPage: String(perPage), ...rest }).map(
368
- ([k, v]) => [k, String(v)]
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
- options
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
- rest
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
- data = await this.http.post(
1280
- "/superusers/login",
1281
- { email, password }
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
- /** Fetch the current superuser profile and refresh the auth model. */
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("/superusers/me", options);
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
  });