@wtfalch/files 0.5.0 → 0.7.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 CHANGED
@@ -41,6 +41,16 @@ part in order. To resume, ask for fresh URLs for the parts that failed. A
41
41
  multipart file has `checksum: null`. The R2 backend supports this; the
42
42
  local-disk backend does not yet.
43
43
 
44
+ `getFile(id, version)` and `downloadFile(id, { version })` pin the read to an
45
+ exact file version. A mismatch against the file's current version throws
46
+ `ApiError` with `status: 409` rather than serving the current version's
47
+ bytes -- the backend keeps only the current version's object, so there is no
48
+ older version to fall back to. Every `StoredFile` also carries `sha256`
49
+ (`null` unless the storage backend exposed byte access at confirm time) and
50
+ `principalRef`, the principal whose upload last confirmed it at its current
51
+ version (`null` if that upload is no longer on record, or the file was never
52
+ confirmed).
53
+
44
54
  Files can sit in folders inside a collection. Pass `path` (for example
45
55
  `reports/2026`) when you reserve an upload. `listFiles(collectionId, { path })`
46
56
  lists the files directly in that folder, and `listFolders(collectionId, path)`
@@ -48,6 +58,15 @@ lists its subfolders. A folder exists while it holds a file, so there are no
48
58
  empty folders. A replacement keeps the file's path; moving a file is not
49
59
  supported yet.
50
60
 
61
+ Image files can be resized on demand instead of a tenant running its own
62
+ thumbnail pipeline: `getFileVariant(fileId, width, format)` returns a signed
63
+ URL to a resized, re-encoded copy, built with `sharp` on first request and
64
+ served from a stored copy on every one after. `width` must be one of
65
+ `VARIANT_WIDTHS` (64, 256 or 1024); `format` is `'webp'` or `'jpeg'`. Only a
66
+ file whose content type `sharp` can decode (JPEG, PNG, WebP, GIF, AVIF)
67
+ qualifies -- anything else answers `invalid_request`. A variant's bytes count
68
+ against the same byte quota an upload does.
69
+
51
70
  The root export provides the client and public request/response types.
52
71
  `@wtfalch/files/client` is also supported. There are no server, database,
53
72
  migration, provider-adapter or authorization-engine exports. The service
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ClientOptions, type ManagementClientOptions } from './transport.js';
2
- import type { AuditPage, AuditQuery, BudgetSummary, CreatedShare, FilesRate, OrganisationShare, OrganisationSummary, ProvisionOrganisationInput, ProvisionOrganisationResult, QuotaSummary, SetRateInput, SetWebhookResult, Share, StoredFile, UpdateOrganisationCeilingInput, UploadInput, UploadReservation, UploadedPart, UsageDay, WebhookConfig } from './types.js';
2
+ import type { AuditPage, AuditQuery, BudgetSummary, CreatedShare, FilesRate, OrganisationShare, OrganisationSummary, ProvisionOrganisationInput, ProvisionOrganisationResult, QuotaSummary, SetRateInput, SetWebhookResult, Share, StoredFile, UpdateOrganisationCeilingInput, UploadInput, UploadReservation, UploadedPart, UsageDay, VariantFormat, VariantWidth, WebhookConfig } from './types.js';
3
3
  export { ApiError, type ClientOptions, type ManagementClientOptions } from './transport.js';
4
4
  export declare function createFilesClient(options: ClientOptions): {
5
5
  createCollection: (name: string) => Promise<{
@@ -18,13 +18,24 @@ export declare function createFilesClient(options: ClientOptions): {
18
18
  }[];
19
19
  expiresAt: string;
20
20
  }>;
21
- getFile: (id: string) => Promise<StoredFile>;
21
+ /** `version`, when given, pins the read to that exact file version:
22
+ * `ApiError` with `status: 409` if the file has since moved on. */
23
+ getFile: (id: string, version?: number) => Promise<StoredFile>;
24
+ /** `version`, when given, pins the download to that exact file version,
25
+ * same as `getFile` -- only the current version's bytes exist in the
26
+ * backend at all, so a mismatch is always a 409, never a historical
27
+ * read. */
22
28
  downloadFile: (id: string, options?: {
23
29
  ttlSeconds?: number;
30
+ version?: number;
24
31
  }) => Promise<{
25
32
  url: string;
26
33
  expiresIn: number;
27
34
  }>;
35
+ getFileVariant: (id: string, width: VariantWidth, format: VariantFormat) => Promise<{
36
+ url: string;
37
+ expiresIn: number;
38
+ }>;
28
39
  deleteFile: (id: string) => Promise<void>;
29
40
  /** `path` lists only the files directly in that folder. */
30
41
  listFiles: (collection: string, page?: {
package/dist/client.js CHANGED
@@ -13,8 +13,26 @@ export function createFilesClient(options) {
13
13
  from: String(from),
14
14
  count: String(count),
15
15
  })}`),
16
- getFile: (id) => call('GET', `/v1/files/${encodeURIComponent(id)}`),
17
- downloadFile: (id, options = {}) => call('GET', `/v1/files/${encodeURIComponent(id)}/download${options.ttlSeconds === undefined ? '' : `?ttl=${options.ttlSeconds}`}`),
16
+ /** `version`, when given, pins the read to that exact file version:
17
+ * `ApiError` with `status: 409` if the file has since moved on. */
18
+ getFile: (id, version) => call('GET', `/v1/files/${encodeURIComponent(id)}${version === undefined ? '' : `?version=${version}`}`),
19
+ /** `version`, when given, pins the download to that exact file version,
20
+ * same as `getFile` -- only the current version's bytes exist in the
21
+ * backend at all, so a mismatch is always a 409, never a historical
22
+ * read. */
23
+ downloadFile: (id, options = {}) => {
24
+ const params = new URLSearchParams();
25
+ if (options.ttlSeconds !== undefined)
26
+ params.set('ttl', String(options.ttlSeconds));
27
+ if (options.version !== undefined)
28
+ params.set('version', String(options.version));
29
+ const query = params.toString();
30
+ return call('GET', `/v1/files/${encodeURIComponent(id)}/download${query ? `?${query}` : ''}`);
31
+ },
32
+ // Lazily generated image variants (task gap-32): created on first
33
+ // request, served from a stored copy on every one after. `width` must be
34
+ // one of `VARIANT_WIDTHS`; the service answers 400 otherwise.
35
+ getFileVariant: (id, width, format) => call('GET', `/v1/files/${encodeURIComponent(id)}/variants/${width}.${format}`),
18
36
  deleteFile: (id) => call('DELETE', `/v1/files/${encodeURIComponent(id)}`),
19
37
  /** `path` lists only the files directly in that folder. */
20
38
  listFiles: (collection, page = {}) => call('GET', `/v1/collections/${encodeURIComponent(collection)}/files?${new URLSearchParams({
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './client.js';
2
2
  export type * from './types.js';
3
+ export { VARIANT_WIDTHS } from './types.js';
3
4
  export { fileReference, fileIdFromReference } from './references.js';
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from './client.js';
2
+ // A runtime value, not a type -- `export type *` above does not re-export it.
3
+ export { VARIANT_WIDTHS } from './types.js';
2
4
  export { fileReference, fileIdFromReference } from './references.js';
package/dist/types.d.ts CHANGED
@@ -39,9 +39,27 @@ export interface StoredFile {
39
39
  * before this field existed. Derived from the storage backend's own etag
40
40
  * for a single-part upload; not present for a multipart one. */
41
41
  checksum: string | null;
42
+ /** `"sha256:<hex>"` of the confirmed bytes, computed directly from the
43
+ * bytes rather than derived from a backend etag. `null` when the storage
44
+ * backend exposed no byte access at confirm time, or for a file confirmed
45
+ * before this field existed. */
46
+ sha256: string | null;
42
47
  /** The folder inside the collection; `''` is the root. */
43
48
  path: string;
49
+ /** The principal (e.g. `"agent:ai-service"` or `"human:alice"`) whose
50
+ * upload confirmed this file at its current version, when that upload is
51
+ * still on record. `null` for a file at version 0 (never confirmed), or
52
+ * when the confirming upload can no longer be found. */
53
+ principalRef: string | null;
44
54
  }
55
+ /** Lazily generated image variants (task gap-32), served through
56
+ * `getFileVariant`. A fixed allowlist -- not an arbitrary size -- so a
57
+ * caller cannot mint an unbounded number of billable objects per file.
58
+ * Variant bytes count against the same organisation (and key) byte quota an
59
+ * upload does. */
60
+ export declare const VARIANT_WIDTHS: readonly [64, 256, 1024];
61
+ export type VariantWidth = (typeof VARIANT_WIDTHS)[number];
62
+ export type VariantFormat = 'webp' | 'jpeg';
45
63
  export type UploadReservation = {
46
64
  uploadId: string;
47
65
  fileId: string;
package/dist/types.js CHANGED
@@ -1 +1,6 @@
1
- export {};
1
+ /** Lazily generated image variants (task gap-32), served through
2
+ * `getFileVariant`. A fixed allowlist -- not an arbitrary size -- so a
3
+ * caller cannot mint an unbounded number of billable objects per file.
4
+ * Variant bytes count against the same organisation (and key) byte quota an
5
+ * upload does. */
6
+ export const VARIANT_WIDTHS = [64, 256, 1024];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/files",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Public client SDK and API types for the Files service.",