@howells/stow-server 2.2.0 → 2.3.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
@@ -67,10 +67,7 @@ const result = await stow.uploadFile(buffer, {
67
67
  Upload a file from a URL (server-side fetch + upload).
68
68
 
69
69
  ```typescript
70
- const result = await stow.uploadFromUrl(
71
- "https://example.com/image.jpg",
72
- "downloaded-image.jpg"
73
- );
70
+ const result = await stow.uploadFromUrl("https://example.com/image.jpg", "downloaded-image.jpg");
74
71
  ```
75
72
 
76
73
  ### `getPresignedUrl(filename, contentType, route?)`
@@ -81,7 +78,7 @@ Get a presigned URL for client-side upload.
81
78
  const { uploadUrl, fileKey, fileUrl } = await stow.getPresignedUrl(
82
79
  "photo.jpg",
83
80
  "image/jpeg",
84
- "avatars"
81
+ "avatars",
85
82
  );
86
83
 
87
84
  // Client can now PUT directly to uploadUrl
@@ -122,6 +119,7 @@ const transformedUrl = stow.getTransformUrl("https://photos.stow.sh/image.jpg",
122
119
  ```
123
120
 
124
121
  Transform options:
122
+
125
123
  - `width` — Max width in pixels (clamped to 4096)
126
124
  - `height` — Max height in pixels (clamped to 4096)
127
125
  - `quality` — 1–100, quantized to nearest 5 (default: 80)
package/dist/index.d.mts CHANGED
@@ -1,3 +1,10 @@
1
+ /** Error thrown when the Stow API returns a non-success response. */
2
+ declare class StowError extends Error {
3
+ readonly status: number;
4
+ readonly code?: string;
5
+ constructor(message: string, status: number, code?: string);
6
+ }
7
+
1
8
  /**
2
9
  * Stow Server SDK
3
10
  *
@@ -21,12 +28,6 @@
21
28
  * console.log(result.url);
22
29
  * ```
23
30
  */
24
- /** Error thrown when the Stow API returns a non-success response. */
25
- declare class StowError extends Error {
26
- readonly status: number;
27
- readonly code?: string;
28
- constructor(message: string, status: number, code?: string);
29
- }
30
31
  /** Configuration for creating a {@link StowServer} instance. */
31
32
  interface StowServerConfig {
32
33
  apiKey: string;
@@ -257,10 +258,21 @@ interface FileResult {
257
258
  url: string | null;
258
259
  width: number | null;
259
260
  }
260
- /** Input for creating a taste profile. */
261
+ /**
262
+ * Input for creating a taste profile.
263
+ *
264
+ * Profiles are the right abstraction when you want a reusable preference vector
265
+ * that can evolve over time through file membership and weighted interaction
266
+ * signals such as `like`, `save`, or `purchase`.
267
+ *
268
+ * Use {@link ClusterCreateRequest} instead when you want to group a fixed,
269
+ * curated subset of files without behavioral weighting.
270
+ */
261
271
  interface ProfileCreateRequest {
262
272
  bucket?: string;
273
+ /** Initial files to seed into the profile before any interaction signals exist. */
263
274
  fileKeys?: string[];
275
+ /** Optional display name for dashboards, logs, or search integrations. */
264
276
  name?: string;
265
277
  }
266
278
  /** Cluster summary within a taste profile. */
@@ -275,13 +287,13 @@ interface ProfileClusterResult {
275
287
  * Optional taxonomy terms inferred for the cluster centroid.
276
288
  * Absent when taxonomy data is unavailable for the bucket/profile.
277
289
  */
278
- tags?: Array<{
290
+ tags?: {
279
291
  group: string;
280
292
  groupSlug: string;
281
293
  similarity: number;
282
294
  tag: string;
283
295
  tagSlug: string;
284
- }>;
296
+ }[];
285
297
  totalWeight: number;
286
298
  }
287
299
  /** Taste profile detail payload. */
@@ -295,8 +307,118 @@ interface ProfileResult {
295
307
  updatedAt: string;
296
308
  vector: number[] | null;
297
309
  }
298
- /** Input for recomputing profile clusters. */
310
+ /**
311
+ * Input for creating a curated clustering resource from an explicit file set.
312
+ *
313
+ * This resource is designed for cases like "cluster these 200 featured images
314
+ * into 12 visual groups" where every listed file should be treated equally.
315
+ * No behavioral signals or master preference vector are involved.
316
+ *
317
+ * The API returns immediately with the persisted resource. Clustering and
318
+ * cluster naming complete asynchronously, so call `stow.clusters.get(id)` to
319
+ * observe progress.
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const resource = await stow.clusters.create({
324
+ * bucket: "inspiration",
325
+ * name: "Spring navigator",
326
+ * fileKeys: featuredKeys,
327
+ * clusterCount: 12,
328
+ * });
329
+ * ```
330
+ */
331
+ interface ClusterCreateRequest {
332
+ /** Bucket name or ID. Optional when the {@link StowServer} instance already has a default bucket. */
333
+ bucket?: string;
334
+ /**
335
+ * Number of K-means clusters to produce.
336
+ *
337
+ * When omitted, the API auto-selects a value using
338
+ * `max(3, min(15, round(sqrt(fileCount / 10))))`.
339
+ */
340
+ clusterCount?: number;
341
+ /** Exact file keys to cluster. Files without embeddings cannot participate. */
342
+ fileKeys: string[];
343
+ /** Optional human-friendly resource name. */
344
+ name?: string;
345
+ }
346
+ /**
347
+ * One group within a curated clustering resource.
348
+ *
349
+ * Group names and descriptions are generated asynchronously. Immediately after
350
+ * creation or reclustering, `name` and `description` may be `null` until the
351
+ * naming worker completes or a human renames the cluster manually.
352
+ */
353
+ interface ClusterGroupResult {
354
+ description: string | null;
355
+ /** Number of files currently assigned to this group. */
356
+ fileCount: number;
357
+ id: string;
358
+ /** Zero-based index produced by the clustering run. */
359
+ index: number;
360
+ name: string | null;
361
+ /** Timestamp for the most recent generated or manual naming pass. */
362
+ nameGeneratedAt: string | null;
363
+ }
364
+ /**
365
+ * Persisted curated clustering resource.
366
+ *
367
+ * `clusterCount` is the requested or auto-selected target. `clusters.length`
368
+ * represents the current persisted groups for the latest clustering run.
369
+ * `clusteredAt` stays `null` until the worker has written assignments.
370
+ */
371
+ interface ClusterResourceResult {
372
+ clusterCount: number;
373
+ /** Timestamp of the last completed clustering pass. Null while work is still pending. */
374
+ clusteredAt: string | null;
375
+ clusters: ClusterGroupResult[];
376
+ createdAt: string;
377
+ /** Number of source files included in the resource. */
378
+ fileCount: number;
379
+ id: string;
380
+ name: string | null;
381
+ updatedAt: string;
382
+ }
383
+ /**
384
+ * One file assigned to a curated cluster group.
385
+ *
386
+ * When returned from `stow.clusters.files(...)`, items are ordered by ascending
387
+ * distance to the group centroid so the first files are usually the best
388
+ * representatives for labeling, previews, or inspiration navigators.
389
+ */
390
+ interface ClusterFileResult {
391
+ bucketId: string;
392
+ contentType: string;
393
+ createdAt: string;
394
+ /** Euclidean distance from the cluster centroid. Lower means more representative. */
395
+ distance: number | null;
396
+ id: string;
397
+ key: string;
398
+ metadata: Record<string, string> | null;
399
+ originalFilename: string | null;
400
+ size: number;
401
+ }
402
+ /**
403
+ * Paginated files assigned to one curated cluster group.
404
+ *
405
+ * Results are stable within a clustering run and ordered by distance to the
406
+ * centroid, making page 1 suitable for "representative examples".
407
+ */
408
+ interface ClusterFilesResult {
409
+ clusterId: string;
410
+ files: ClusterFileResult[];
411
+ limit: number;
412
+ offset: number;
413
+ total: number;
414
+ }
415
+ /**
416
+ * Input for recomputing clusters.
417
+ *
418
+ * Used by both `profiles.recluster(...)` and `clusters.recluster(...)`.
419
+ */
299
420
  interface ReclusterRequest {
421
+ /** Override the existing cluster count for the next clustering pass. */
300
422
  clusterCount?: number;
301
423
  }
302
424
  /** Result payload from cluster recomputation. */
@@ -306,7 +428,12 @@ interface ReclusterResult {
306
428
  profileId: string;
307
429
  totalSignals: number;
308
430
  }
309
- /** Input for updating cluster display metadata. */
431
+ /**
432
+ * Input for manually naming a cluster.
433
+ *
434
+ * This is intended for the final editorial pass after generated names are
435
+ * available. You can send just `name`, just `description`, or both.
436
+ */
310
437
  interface RenameClusterRequest {
311
438
  description?: string;
312
439
  name?: string;
@@ -451,15 +578,39 @@ interface ConfirmUploadRequest {
451
578
  /** Request AI-generated title for images */
452
579
  title?: boolean;
453
580
  }
454
- /** Input payload for similarity search. */
581
+ /**
582
+ * Input payload for similarity search.
583
+ *
584
+ * Provide exactly one semantic seed:
585
+ * - `fileKey`
586
+ * - `anchorId`
587
+ * - `profileId`
588
+ * - `clusterId`
589
+ * - `clusterIds`
590
+ * - `vector`
591
+ *
592
+ * Cluster seeds follow the same shape as profile clustering:
593
+ * - with `profileId`, `clusterId` or `clusterIds` reference profile clusters
594
+ * - without `profileId`, they reference curated clusters created via `/clusters`
595
+ */
455
596
  interface SimilarSearchRequest {
456
597
  /** Use an anchor's embedding as the query vector */
457
598
  anchorId?: string;
458
599
  /** Bucket name or ID to scope search */
459
600
  bucket?: string;
460
- /** Use a specific cluster centroid instead of the profile master vector. Requires profileId. */
601
+ /**
602
+ * Use a specific cluster centroid as the query vector.
603
+ *
604
+ * With `profileId`, this resolves a profile cluster. Without `profileId`, it
605
+ * resolves a curated cluster group from `stow.clusters`.
606
+ */
461
607
  clusterId?: string;
462
- /** Blend multiple cluster centroids as query vector. Requires profileId. */
608
+ /**
609
+ * Blend multiple cluster centroids into one query vector.
610
+ *
611
+ * With `profileId`, these resolve profile clusters. Without `profileId`, they
612
+ * resolve curated cluster groups from `stow.clusters`.
613
+ */
463
614
  clusterIds?: string[];
464
615
  /** File keys to exclude from results (e.g. already-seen items). Max 500. */
465
616
  excludeKeys?: string[];
@@ -471,20 +622,35 @@ interface SimilarSearchRequest {
471
622
  include?: SearchIncludeField[];
472
623
  /** Max results (default 10, max 50) */
473
624
  limit?: number;
474
- /** Search using a taste profile's vector */
625
+ /** Search using a taste profile's master vector. */
475
626
  profileId?: string;
476
627
  /** Search directly with a vector (1024 dimensions) */
477
628
  vector?: number[];
478
629
  }
479
- /** Input payload for diversity-aware search. */
630
+ /**
631
+ * Input payload for diversity-aware search.
632
+ *
633
+ * This uses the same seed rules as {@link SimilarSearchRequest} but balances
634
+ * relevance against diversity using `lambda`.
635
+ */
480
636
  interface DiverseSearchRequest {
481
637
  /** Use an anchor's embedding as the query vector */
482
638
  anchorId?: string;
483
639
  /** Bucket name or ID to scope search */
484
640
  bucket?: string;
485
- /** Use a specific cluster centroid instead of the profile master vector. Requires profileId. */
641
+ /**
642
+ * Use a specific cluster centroid as the seed vector.
643
+ *
644
+ * With `profileId`, this resolves a profile cluster. Without `profileId`, it
645
+ * resolves a curated cluster group from `stow.clusters`.
646
+ */
486
647
  clusterId?: string;
487
- /** Blend multiple cluster centroids as query vector. Requires profileId. */
648
+ /**
649
+ * Blend multiple cluster centroids into one seed vector.
650
+ *
651
+ * With `profileId`, these resolve profile clusters. Without `profileId`, they
652
+ * resolve curated cluster groups from `stow.clusters`.
653
+ */
488
654
  clusterIds?: string[];
489
655
  /** File keys to exclude from results (e.g. already-seen items). Max 500. */
490
656
  excludeKeys?: string[];
@@ -519,18 +685,18 @@ interface SearchResultItem {
519
685
  originalFilename: string | null;
520
686
  similarity: number;
521
687
  size: number;
522
- tags?: Array<{
688
+ tags?: {
523
689
  slug: string;
524
690
  name: string;
525
- }>;
526
- taxonomies?: Array<{
691
+ }[];
692
+ taxonomies?: {
527
693
  externalUri: string | null;
528
694
  group: string;
529
695
  name: string;
530
696
  similarity: number;
531
697
  slug: string;
532
698
  source: string;
533
- }>;
699
+ }[];
534
700
  width?: number | null;
535
701
  }
536
702
  /** A text anchor — a named semantic reference point in a bucket's vector space. */
@@ -683,17 +849,57 @@ interface TaxonomyGroup {
683
849
  interface TaxonomyListResult {
684
850
  groups: TaxonomyGroup[];
685
851
  }
686
- /** Server-side SDK client for Stow's API. */
852
+ /**
853
+ * Server-side SDK client for Stow's HTTP API.
854
+ *
855
+ * Use this package from trusted environments only: Next.js route handlers,
856
+ * server actions, workers, backend services, CLIs, or scripts. It owns API-key
857
+ * auth and exposes the full bucket/file/search/profile/cluster surface.
858
+ *
859
+ * Bucket scoping rules:
860
+ * - pass `bucket` to the constructor to set a default for the instance
861
+ * - pass `bucket` to a method to override that default for one call
862
+ * - omit both only when the API key itself is bucket-scoped
863
+ *
864
+ * @example
865
+ * ```typescript
866
+ * const stow = new StowServer({
867
+ * apiKey: process.env.STOW_API_KEY!,
868
+ * bucket: "brand-assets",
869
+ * });
870
+ *
871
+ * const upload = await stow.uploadFile(buffer, {
872
+ * filename: "hero.jpg",
873
+ * contentType: "image/jpeg",
874
+ * title: true,
875
+ * altText: true,
876
+ * });
877
+ *
878
+ * const { results } = await stow.search.similar({ fileKey: upload.key, limit: 8 });
879
+ * ```
880
+ */
687
881
  declare class StowServer {
688
882
  private readonly apiKey;
689
883
  private readonly baseUrl;
690
884
  private readonly bucket?;
691
885
  private readonly timeout;
692
886
  private readonly retries;
693
- constructor(config: StowServerConfig | string);
694
887
  /**
695
- * Get the base URL for this instance (used by client SDK)
888
+ * Pure helper for building signed transform URLs from an existing public file URL.
889
+ *
890
+ * This does not perform any network I/O and is safe to pass around as a plain
891
+ * function, for example to view-layer code that needs responsive image URLs.
892
+ */
893
+ readonly getTransformUrl: (url: string, options?: TransformOptions) => string;
894
+ /**
895
+ * Create a server SDK instance.
896
+ *
897
+ * Pass a bare string when you only need the API key and want default values
898
+ * for `baseUrl`, `timeout`, and retries. Pass an object when you want a
899
+ * default bucket, a non-production API origin, or custom transport settings.
696
900
  */
901
+ constructor(config: StowServerConfig | string);
902
+ /** Return the configured API origin, mainly for adapter packages such as `stow-next`. */
697
903
  getBaseUrl(): string;
698
904
  /**
699
905
  * Return account usage and API key info for the current credential.
@@ -744,9 +950,30 @@ declare class StowServer {
744
950
  * - Consumer can pass `signal` in options to cancel.
745
951
  */
746
952
  private request;
747
- private sleep;
748
953
  /**
749
- * Upload a file directly from the server
954
+ * Upload bytes from a trusted server environment.
955
+ *
956
+ * This is the highest-level server upload helper:
957
+ * 1. compute a SHA-256 hash for dedupe
958
+ * 2. request a presigned upload URL
959
+ * 3. PUT bytes to storage
960
+ * 4. confirm the upload with optional AI metadata generation
961
+ *
962
+ * Prefer this method when your code already has the file bytes in memory.
963
+ * Use `getPresignedUrl()` + `confirmUpload()` for direct browser uploads
964
+ * instead, and `uploadFromUrl()` when the source is an external URL.
965
+ *
966
+ * @example
967
+ * ```typescript
968
+ * await stow.uploadFile(buffer, {
969
+ * filename: "product.jpg",
970
+ * contentType: "image/jpeg",
971
+ * bucket: "catalog",
972
+ * metadata: { sku: "SKU-123" },
973
+ * title: true,
974
+ * altText: true,
975
+ * });
976
+ * ```
750
977
  */
751
978
  uploadFile(file: Buffer | Blob, options?: {
752
979
  filename?: string;
@@ -764,7 +991,12 @@ declare class StowServer {
764
991
  altText?: boolean;
765
992
  }): Promise<UploadResult>;
766
993
  /**
767
- * Upload a file from a URL (server-side fetch + upload)
994
+ * Import a remote asset by URL.
995
+ *
996
+ * Stow fetches the remote URL server-side, stores the resulting bytes, and
997
+ * persists the file as if it had been uploaded normally. This is useful for
998
+ * migrations, ingestion pipelines, or bringing third-party assets into Stow
999
+ * without downloading them into your own process first.
768
1000
  */
769
1001
  uploadFromUrl(url: string, filename: string, options?: {
770
1002
  bucket?: string;
@@ -798,24 +1030,36 @@ declare class StowServer {
798
1030
  altText?: boolean;
799
1031
  }): Promise<QueuedResult>;
800
1032
  /**
801
- * Get a presigned URL for direct client-side upload.
1033
+ * Get a presigned URL for a direct client upload.
802
1034
  *
803
- * This enables uploads that bypass your server entirely:
804
- * 1. Client calls your endpoint
805
- * 2. Your endpoint calls this method
806
- * 3. Client PUTs directly to the returned uploadUrl
807
- * 4. Client calls confirmUpload to finalize
1035
+ * This is the server-side half of the browser upload flow used by
1036
+ * `@howells/stow-client` and `@howells/stow-next`:
1037
+ * 1. browser calls your app
1038
+ * 2. your app calls `getPresignedUrl()`
1039
+ * 3. browser PUTs bytes to `uploadUrl`
1040
+ * 4. browser or your app calls `confirmUpload()`
1041
+ *
1042
+ * If `contentHash` matches an existing file in the target bucket, the API
1043
+ * short-circuits with `{ dedupe: true, ... }` and no upload is required.
808
1044
  */
809
1045
  getPresignedUrl(request: PresignRequest): Promise<PresignResult>;
810
1046
  /**
811
- * Confirm a presigned upload after the client has uploaded to R2.
812
- * This creates the file record in the database.
1047
+ * Confirm a direct upload after the client has finished the storage PUT.
1048
+ *
1049
+ * This finalizes the file record in the database and optionally triggers
1050
+ * post-processing such as AI-generated title/description/alt text.
1051
+ *
1052
+ * Call this exactly once per successful presigned upload.
813
1053
  */
814
1054
  confirmUpload(request: ConfirmUploadRequest): Promise<UploadResult>;
815
1055
  /**
816
- * List files in the bucket
1056
+ * List files in a bucket with optional prefix filtering and enrichment blocks.
1057
+ *
1058
+ * Use `cursor` to continue pagination from a previous page. When requesting
1059
+ * `include`, Stow expands those relationships inline so you can avoid follow-up
1060
+ * per-file lookups.
817
1061
  *
818
- * @param options.include - Optional enrichment fields: `"tags"`, `"taxonomies"`
1062
+ * @param options.include Optional enrichment fields: `"tags"`, `"taxonomies"`
819
1063
  */
820
1064
  listFiles(options?: {
821
1065
  prefix?: string;
@@ -843,9 +1087,12 @@ declare class StowServer {
843
1087
  metadata: Record<string, string>;
844
1088
  }>;
845
1089
  /**
846
- * Get a single file by key
1090
+ * Get one file by key.
1091
+ *
1092
+ * This is the detailed file view and includes dimensions, embeddings status,
1093
+ * extracted colors, and AI metadata fields when available.
847
1094
  *
848
- * @param options.include - Optional enrichment fields: `"tags"`, `"taxonomies"`
1095
+ * @param options.include Optional enrichment fields: `"tags"`, `"taxonomies"`
849
1096
  */
850
1097
  getFile(key: string, options?: {
851
1098
  bucket?: string;
@@ -904,29 +1151,18 @@ declare class StowServer {
904
1151
  /** Headers to forward when fetching the URL (e.g. User-Agent, Referer) */
905
1152
  headers?: Record<string, string>;
906
1153
  }): Promise<ReplaceResult>;
907
- /**
908
- * Get a transform URL for an image.
909
- *
910
- * Appends transform query params (?w=, ?h=, ?q=, ?f=) to a file URL.
911
- * Transforms are applied at the edge by the Cloudflare Worker — no
912
- * server round-trip needed.
913
- *
914
- * @param url - Full file URL (e.g. from upload result's fileUrl)
915
- * @param options - Transform options (width, height, quality, format)
916
- */
917
- getTransformUrl(url: string, options?: TransformOptions): string;
918
1154
  /**
919
1155
  * Tags namespace for creating, listing, and deleting tags
920
1156
  */
921
1157
  get tags(): {
922
1158
  list: () => Promise<{
923
- tags: Array<{
1159
+ tags: {
924
1160
  id: string;
925
1161
  name: string;
926
1162
  slug: string;
927
1163
  color: string | null;
928
1164
  createdAt: string;
929
- }>;
1165
+ }[];
930
1166
  }>;
931
1167
  create: (params: {
932
1168
  name: string;
@@ -971,7 +1207,34 @@ declare class StowServer {
971
1207
  };
972
1208
  private listTaxonomies;
973
1209
  /**
974
- * Search namespace for vector similarity search
1210
+ * Semantic search namespace.
1211
+ *
1212
+ * Methods:
1213
+ * - `text(...)` embeds text and finds matching files
1214
+ * - `similar(...)` finds nearest neighbors for a file, anchor, profile, or cluster
1215
+ * - `diverse(...)` balances similarity against result spread
1216
+ * - `color(...)` performs palette similarity search
1217
+ * - `image(...)` searches using an existing file or an external image URL
1218
+ *
1219
+ * Cluster-aware search examples:
1220
+ *
1221
+ * @example
1222
+ * ```typescript
1223
+ * const cluster = await stow.clusters.create({
1224
+ * bucket: "inspiration",
1225
+ * fileKeys,
1226
+ * clusterCount: 12,
1227
+ * });
1228
+ *
1229
+ * const firstGroup = cluster.clusters[0];
1230
+ * if (firstGroup) {
1231
+ * const related = await stow.search.similar({
1232
+ * bucket: "inspiration",
1233
+ * clusterId: firstGroup.id,
1234
+ * limit: 20,
1235
+ * });
1236
+ * }
1237
+ * ```
975
1238
  */
976
1239
  get search(): {
977
1240
  similar: (params: SimilarSearchRequest) => Promise<SimilarSearchResult>;
@@ -1010,7 +1273,11 @@ declare class StowServer {
1010
1273
  */
1011
1274
  deleteDrop(id: string): Promise<void>;
1012
1275
  /**
1013
- * Profiles namespace for managing taste profiles
1276
+ * Taste-profile namespace.
1277
+ *
1278
+ * Profiles are long-lived preference objects. They can be seeded from files,
1279
+ * updated through weighted signals, clustered into interpretable segments, and
1280
+ * then reused as semantic search seeds.
1014
1281
  */
1015
1282
  get profiles(): {
1016
1283
  create: (params?: ProfileCreateRequest) => Promise<ProfileResult>;
@@ -1028,6 +1295,50 @@ declare class StowServer {
1028
1295
  recluster: (id: string, params?: ReclusterRequest, bucket?: string) => Promise<ReclusterResult>;
1029
1296
  renameCluster: (profileId: string, clusterId: string, params: RenameClusterRequest, bucket?: string) => Promise<ProfileClusterResult>;
1030
1297
  };
1298
+ /**
1299
+ * Curated cluster namespace.
1300
+ *
1301
+ * Use this when you have an explicit file set that should be grouped by visual
1302
+ * similarity, but should not be modeled as a behavioral profile.
1303
+ *
1304
+ * Typical workflow:
1305
+ * 1. `create({ fileKeys, clusterCount })`
1306
+ * 2. poll `get(id)` until `clusteredAt` is non-null
1307
+ * 3. inspect `clusters`
1308
+ * 4. fetch representative files with `files(id, clusterId)`
1309
+ * 5. optionally `renameCluster(...)`
1310
+ * 6. use `clusterId` with `search.similar(...)` or `search.diverse(...)`
1311
+ *
1312
+ * @example
1313
+ * ```typescript
1314
+ * const resource = await stow.clusters.create({
1315
+ * bucket: "featured-products",
1316
+ * fileKeys: featuredKeys,
1317
+ * clusterCount: 12,
1318
+ * name: "Navigator groups",
1319
+ * });
1320
+ *
1321
+ * const latest = await stow.clusters.get(resource.id, "featured-products");
1322
+ * const group = latest.clusters[0];
1323
+ * if (group) {
1324
+ * const representatives = await stow.clusters.files(resource.id, group.id, {
1325
+ * limit: 12,
1326
+ * offset: 0,
1327
+ * });
1328
+ * }
1329
+ * ```
1330
+ */
1331
+ get clusters(): {
1332
+ create: (params: ClusterCreateRequest) => Promise<ClusterResourceResult>;
1333
+ get: (id: string, bucket?: string) => Promise<ClusterResourceResult>;
1334
+ recluster: (id: string, params?: ReclusterRequest, bucket?: string) => Promise<ClusterResourceResult>;
1335
+ files: (id: string, clusterId: string, params?: {
1336
+ limit?: number;
1337
+ offset?: number;
1338
+ }, bucket?: string) => Promise<ClusterFilesResult>;
1339
+ renameCluster: (id: string, clusterId: string, params: RenameClusterRequest, bucket?: string) => Promise<ClusterGroupResult>;
1340
+ delete: (id: string, bucket?: string) => Promise<void>;
1341
+ };
1031
1342
  private createProfile;
1032
1343
  private getProfile;
1033
1344
  private deleteProfile;
@@ -1038,6 +1349,12 @@ declare class StowServer {
1038
1349
  private getProfileClusters;
1039
1350
  private reclusterProfile;
1040
1351
  private renameProfileCluster;
1352
+ private createClustersResource;
1353
+ private getClustersResource;
1354
+ private reclusterClustersResource;
1355
+ private getClusterFiles;
1356
+ private renameClusterGroup;
1357
+ private deleteClustersResource;
1041
1358
  /**
1042
1359
  * Anchors namespace for creating, listing, updating, and deleting text anchors.
1043
1360
  *
@@ -1069,4 +1386,4 @@ declare class StowServer {
1069
1386
  private deleteAnchor;
1070
1387
  }
1071
1388
 
1072
- export { type Anchor, type AnchorSearchResult, type AppliedFilters, type BucketResult, type ColorSearchRequest, type ColorSearchResult, type ColorSearchResultItem, type ConfirmUploadRequest, type CreateAnchorRequest, type CreateBucketRequest, type DeleteProfileSignalsResult, type DiverseSearchRequest, type Drop, type DropResult, type FileColor, type FileColorProfile, type FileIncludeField, type FileResult, type FileTag, type FileTaxonomy, type FilteredMetadata, type ListBucketsResult, type ListDropsResult, type ListFilesItem, type ListFilesResult, type PresignDedupeResult, type PresignNewResult, type PresignRequest, type PresignResult, type ProfileClusterResult, type ProfileCreateRequest, type ProfileFilesResult, type ProfileResult, type ProfileSignalInput, type ProfileSignalResult, type ProfileSignalType, type ProfileSignalsResponse, type QueuedResult, type ReclusterRequest, type ReclusterResult, type RenameClusterRequest, type ReplaceResult, type SearchByImageInput, type SearchByImageOptions, type SearchByImageResult, type SearchByImageSource, type SearchFilters, type SearchIncludeField, type SearchResultItem, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TaskTriggerResult, type TaxonomyGroup, type TaxonomyListResult, type TaxonomyTerm, type TextSearchRequest, type TransformOptions, type UpdateAnchorRequest, type UpdateBucketRequest, type UploadResult, type WhoamiResult };
1389
+ export { type Anchor, type AnchorSearchResult, type AppliedFilters, type BucketResult, type ClusterCreateRequest, type ClusterFileResult, type ClusterFilesResult, type ClusterGroupResult, type ClusterResourceResult, type ColorSearchRequest, type ColorSearchResult, type ColorSearchResultItem, type ConfirmUploadRequest, type CreateAnchorRequest, type CreateBucketRequest, type DeleteProfileSignalsResult, type DiverseSearchRequest, type Drop, type DropResult, type FileColor, type FileColorProfile, type FileIncludeField, type FileResult, type FileTag, type FileTaxonomy, type FilteredMetadata, type ListBucketsResult, type ListDropsResult, type ListFilesItem, type ListFilesResult, type PresignDedupeResult, type PresignNewResult, type PresignRequest, type PresignResult, type ProfileClusterResult, type ProfileCreateRequest, type ProfileFilesResult, type ProfileResult, type ProfileSignalInput, type ProfileSignalResult, type ProfileSignalType, type ProfileSignalsResponse, type QueuedResult, type ReclusterRequest, type ReclusterResult, type RenameClusterRequest, type ReplaceResult, type SearchByImageInput, type SearchByImageOptions, type SearchByImageResult, type SearchByImageSource, type SearchFilters, type SearchIncludeField, type SearchResultItem, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TaskTriggerResult, type TaxonomyGroup, type TaxonomyListResult, type TaxonomyTerm, type TextSearchRequest, type TransformOptions, type UpdateAnchorRequest, type UpdateBucketRequest, type UploadResult, type WhoamiResult };