lazypock 0.16.1 → 0.17.1

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
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  getFileUrl: () => getFileUrl,
40
40
  getScaleUrl: () => getScaleUrl,
41
41
  getThumbUrl: () => getThumbUrl,
42
+ getVariantUrl: () => getVariantUrl,
42
43
  schemaFieldType: () => schemaFieldType,
43
44
  wsUrlFromBaseUrl: () => wsUrlFromBaseUrl
44
45
  });
@@ -385,20 +386,24 @@ var AuthStore = class {
385
386
  }
386
387
  /**
387
388
  * Set the auth collection name (used internally by auto-refresh).
389
+ *
390
+ * Persisted, so it survives a reload together with the token and model.
388
391
  * @param name The collection name, or null for superuser tokens.
389
392
  */
390
393
  setCollectionName(name) {
391
394
  this._collectionName = name;
395
+ void this.persistCollectionName();
392
396
  }
393
397
  /**
394
398
  * Load persisted auth state from storage.
395
399
  * Should be called once at application startup.
396
400
  */
397
401
  async init() {
398
- const [token, model, expiresAt] = await Promise.all([
402
+ const [token, model, expiresAt, collectionName] = await Promise.all([
399
403
  this.storage.get("auth_token"),
400
404
  this.storage.get("auth_model"),
401
- this.storage.get("auth_expires_at")
405
+ this.storage.get("auth_expires_at"),
406
+ this.storage.get("auth_collection")
402
407
  ]);
403
408
  if (token) this._token = token;
404
409
  if (expiresAt) this._tokenExpiresAt = parseInt(expiresAt, 10) || null;
@@ -408,6 +413,11 @@ var AuthStore = class {
408
413
  } catch {
409
414
  }
410
415
  }
416
+ if (collectionName) {
417
+ this._collectionName = collectionName;
418
+ } else if (this._model && typeof this._model.collectionName === "string") {
419
+ this._collectionName = this._model.collectionName;
420
+ }
411
421
  }
412
422
  /**
413
423
  * Update the current auth token and model, persist to storage, and notify listeners.
@@ -418,15 +428,19 @@ var AuthStore = class {
418
428
  this._token = token;
419
429
  this._model = model;
420
430
  this._tokenExpiresAt = Date.now() + TOKEN_TTL_MS;
431
+ if (!this._collectionName && model && typeof model.collectionName === "string") {
432
+ this._collectionName = model.collectionName;
433
+ }
421
434
  void Promise.all([
422
435
  this.storage.set("auth_token", token),
423
436
  this.storage.set("auth_expires_at", String(this._tokenExpiresAt)),
424
- model ? this.storage.set("auth_model", JSON.stringify(model)) : this.storage.remove("auth_model")
437
+ model ? this.storage.set("auth_model", JSON.stringify(model)) : this.storage.remove("auth_model"),
438
+ this.persistCollectionName()
425
439
  ]);
426
440
  this.notify();
427
441
  }
428
442
  /**
429
- * Clear all auth state (token, model, expiry) and notify listeners.
443
+ * Clear all auth state (token, model, expiry, collection) and notify listeners.
430
444
  */
431
445
  clear() {
432
446
  this._token = "";
@@ -436,10 +450,14 @@ var AuthStore = class {
436
450
  void Promise.all([
437
451
  this.storage.remove("auth_token"),
438
452
  this.storage.remove("auth_expires_at"),
439
- this.storage.remove("auth_model")
453
+ this.storage.remove("auth_model"),
454
+ this.storage.remove("auth_collection")
440
455
  ]);
441
456
  this.notify();
442
457
  }
458
+ persistCollectionName() {
459
+ return this._collectionName ? this.storage.set("auth_collection", this._collectionName) : this.storage.remove("auth_collection");
460
+ }
443
461
  /**
444
462
  * Register a listener for auth state changes.
445
463
  * @param fn Callback invoked with (model, token) on every change.
@@ -1229,7 +1247,7 @@ var CollectionService = class _CollectionService {
1229
1247
  requestKey,
1230
1248
  autoCancel,
1231
1249
  cancelKey,
1232
- fetch,
1250
+ fetch: fetch2,
1233
1251
  headers,
1234
1252
  signal,
1235
1253
  singleFlight,
@@ -1273,7 +1291,7 @@ var CollectionService = class _CollectionService {
1273
1291
  requestKey,
1274
1292
  autoCancel,
1275
1293
  cancelKey,
1276
- fetch,
1294
+ fetch: fetch2,
1277
1295
  headers,
1278
1296
  signal,
1279
1297
  singleFlight,
@@ -1999,9 +2017,28 @@ function getThumbUrl(baseUrl, fileId, size) {
1999
2017
  function getScaleUrl(baseUrl, fileId, size) {
2000
2018
  return baseUrl.replace(/\/+$/, "") + "/files/" + encodeURIComponent(fileId) + "/scale/" + encodeURIComponent(size);
2001
2019
  }
2020
+ function getVariantUrl(baseUrl, fileId, preset) {
2021
+ return getScaleUrl(baseUrl, fileId, preset);
2022
+ }
2023
+ function parseUploadTarget(rawUrl, allowedHosts) {
2024
+ let parsed;
2025
+ try {
2026
+ parsed = new URL(rawUrl);
2027
+ } catch {
2028
+ throw new Error("Direct upload: the server returned an invalid upload URL");
2029
+ }
2030
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
2031
+ throw new Error(`Direct upload: unsupported protocol ${parsed.protocol}`);
2032
+ }
2033
+ if (allowedHosts && allowedHosts.length > 0 && !allowedHosts.includes(parsed.host)) {
2034
+ throw new Error(`Direct upload: host ${parsed.host} is not in the allowlist`);
2035
+ }
2036
+ return parsed;
2037
+ }
2002
2038
  var FilesService = class {
2003
- constructor(http) {
2039
+ constructor(http, allowedUploadHosts) {
2004
2040
  this.http = http;
2041
+ this.allowedUploadHosts = allowedUploadHosts;
2005
2042
  }
2006
2043
  /**
2007
2044
  * Upload a file or blob.
@@ -2022,6 +2059,8 @@ var FilesService = class {
2022
2059
  formData.append("collection_name", meta.collectionName);
2023
2060
  if (meta?.recordId) formData.append("record_id", meta.recordId);
2024
2061
  if (meta?.fieldName) formData.append("field_name", meta.fieldName);
2062
+ if (meta?.origin) formData.append("origin", meta.origin);
2063
+ if (meta?.variants?.length) formData.append("variants", meta.variants.join(","));
2025
2064
  const data = await this.http.request(
2026
2065
  "POST",
2027
2066
  "/files",
@@ -2030,6 +2069,62 @@ var FilesService = class {
2030
2069
  );
2031
2070
  return data;
2032
2071
  }
2072
+ /**
2073
+ * Direct upload (Mode B): the browser PUTs straight to S3/R2.
2074
+ *
2075
+ * Presigns, uploads, then asks the server to verify the object. Falls back to
2076
+ * `upload()` when the server has no direct-upload support configured.
2077
+ */
2078
+ async uploadDirect(file, opts) {
2079
+ const filename = opts?.filename || (file instanceof File ? file.name : "file");
2080
+ const mime = file.type || "application/octet-stream";
2081
+ const presigned = await this.presign({
2082
+ filename,
2083
+ size: file.size,
2084
+ mime,
2085
+ collectionName: opts?.collectionName,
2086
+ fieldName: opts?.fieldName,
2087
+ recordId: opts?.recordId
2088
+ });
2089
+ if (!presigned) return this.upload(file, filename, { signal: opts?.signal }, { collectionName: opts?.collectionName, fieldName: opts?.fieldName, recordId: opts?.recordId });
2090
+ const target = parseUploadTarget(presigned.url, this.allowedUploadHosts);
2091
+ const put = await fetch(target.href, {
2092
+ method: presigned.method || "PUT",
2093
+ headers: presigned.headers,
2094
+ body: file,
2095
+ signal: opts?.signal
2096
+ });
2097
+ if (!put.ok) throw new Error(`Direct upload failed: HTTP ${put.status}`);
2098
+ return this.complete(presigned.id, { variant: opts?.variant, signal: opts?.signal });
2099
+ }
2100
+ /** Step 1 of a direct upload: reserve an object and get a presigned PUT. */
2101
+ async presign(params) {
2102
+ const data = await this.http.request(
2103
+ "POST",
2104
+ "/files/presign",
2105
+ {
2106
+ filename: params.filename,
2107
+ size: params.size,
2108
+ mime: params.mime,
2109
+ collection_name: params.collectionName,
2110
+ field_name: params.fieldName,
2111
+ record_id: params.recordId
2112
+ }
2113
+ );
2114
+ if (!data || typeof data.url !== "string") return null;
2115
+ return data;
2116
+ }
2117
+ /** Step 2 of a direct upload: ask the server to verify the object. */
2118
+ async complete(fileId, opts) {
2119
+ const query = opts?.variant ? `?variants=${encodeURIComponent(opts.variant)}` : "";
2120
+ const data = await this.http.request(
2121
+ "POST",
2122
+ `/files/${encodeURIComponent(fileId)}/complete${query}`,
2123
+ {},
2124
+ { signal: opts?.signal }
2125
+ );
2126
+ return data;
2127
+ }
2033
2128
  /**
2034
2129
  * List uploaded files (newest first), with optional filters.
2035
2130
  *
@@ -2842,6 +2937,7 @@ function createClient(options) {
2842
2937
  getFileUrl,
2843
2938
  getScaleUrl,
2844
2939
  getThumbUrl,
2940
+ getVariantUrl,
2845
2941
  schemaFieldType,
2846
2942
  wsUrlFromBaseUrl
2847
2943
  });