@howells/stow-server 0.1.2 → 0.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/dist/index.d.ts CHANGED
@@ -38,11 +38,67 @@ interface StowServerConfig {
38
38
  }
39
39
  interface UploadResult {
40
40
  contentType: string;
41
+ /** True when upload short-circuited to an existing file by content hash dedupe. */
42
+ deduped?: boolean;
41
43
  key: string;
42
44
  metadata?: Record<string, string>;
43
45
  size: number;
44
46
  url: string | null;
45
47
  }
48
+ interface BucketResult {
49
+ allowedTypes?: string[] | null;
50
+ createdAt?: string;
51
+ description?: string | null;
52
+ fileCount?: number;
53
+ fileCountLimit?: number | null;
54
+ id: string;
55
+ isPublic?: boolean;
56
+ maxFileSize?: number | null;
57
+ name: string;
58
+ searchable?: boolean;
59
+ storageQuota?: number | null;
60
+ usageBytes?: number;
61
+ }
62
+ interface ListBucketsResult {
63
+ buckets: BucketResult[];
64
+ }
65
+ interface CreateBucketRequest {
66
+ allowedTypes?: string[];
67
+ description?: string;
68
+ fileCountLimit?: number;
69
+ isPublic?: boolean;
70
+ maxFileSize?: number;
71
+ name: string;
72
+ searchable?: boolean;
73
+ storageQuota?: number;
74
+ webhookUrl?: string;
75
+ }
76
+ interface UpdateBucketRequest {
77
+ allowedTypes?: string[] | null;
78
+ description?: string | null;
79
+ fileCountLimit?: number | null;
80
+ isPublic?: boolean;
81
+ maxFileSize?: number | null;
82
+ name?: string;
83
+ searchable?: boolean;
84
+ storageQuota?: number | null;
85
+ webhookUrl?: string | null;
86
+ }
87
+ interface WhoamiResult {
88
+ key?: {
89
+ name: string;
90
+ permissions: Record<string, boolean>;
91
+ scope: string;
92
+ };
93
+ stats: {
94
+ bucketCount: number;
95
+ totalBytes: number;
96
+ totalFiles: number;
97
+ };
98
+ user: {
99
+ email: string;
100
+ };
101
+ }
46
102
  interface TransformOptions {
47
103
  format?: "webp" | "avif" | "jpeg" | "png";
48
104
  height?: number;
@@ -50,15 +106,69 @@ interface TransformOptions {
50
106
  width?: number;
51
107
  }
52
108
  interface ListFilesResult {
53
- files: Array<{
54
- key: string;
55
- size: number;
56
- lastModified: string;
57
- url: string | null;
58
- metadata?: Record<string, string>;
59
- }>;
109
+ files: ListFilesItem[];
60
110
  nextCursor: string | null;
61
111
  }
112
+ interface FileColor {
113
+ hex: string;
114
+ hsl: {
115
+ h: number;
116
+ s: number;
117
+ l: number;
118
+ };
119
+ name: string | null;
120
+ oklab: {
121
+ L: number;
122
+ a: number;
123
+ b: number;
124
+ } | null;
125
+ oklch: {
126
+ l: number;
127
+ c: number;
128
+ h: number;
129
+ } | null;
130
+ position: number;
131
+ proportion: number;
132
+ }
133
+ interface FileColorProfile {
134
+ accent: {
135
+ hex: string;
136
+ name: string | null;
137
+ oklab: {
138
+ L: number;
139
+ a: number;
140
+ b: number;
141
+ } | null;
142
+ oklch: {
143
+ l: number;
144
+ c: number;
145
+ h: number;
146
+ } | null;
147
+ } | null;
148
+ backgroundHex: string | null;
149
+ colorCount: number;
150
+ extractedAt: string;
151
+ palette: {
152
+ mood: string;
153
+ brightness: number;
154
+ temperature: number;
155
+ vibrancy: number;
156
+ complexity: number;
157
+ dominantFamily: string | null;
158
+ };
159
+ }
160
+ interface ListFilesItem {
161
+ colorProfile?: FileColorProfile | null;
162
+ colors?: FileColor[];
163
+ duration?: number | null;
164
+ height?: number | null;
165
+ key: string;
166
+ lastModified: string;
167
+ metadata?: Record<string, string>;
168
+ size: number;
169
+ url: string | null;
170
+ width?: number | null;
171
+ }
62
172
  interface DropResult {
63
173
  contentType: string;
64
174
  filename: string;
@@ -82,6 +192,127 @@ interface ListDropsResult {
82
192
  limit: number;
83
193
  };
84
194
  }
195
+ interface FileResult {
196
+ colorProfile: FileColorProfile | null;
197
+ colors: FileColor[];
198
+ contentType: string;
199
+ createdAt: string;
200
+ duration: number | null;
201
+ embeddingStatus: string | null;
202
+ height: number | null;
203
+ key: string;
204
+ metadata: Record<string, string> | null;
205
+ size: number;
206
+ url: string | null;
207
+ width: number | null;
208
+ }
209
+ interface ProfileCreateRequest {
210
+ bucket?: string;
211
+ fileKeys?: string[];
212
+ name?: string;
213
+ }
214
+ interface ProfileClusterResult {
215
+ description: string | null;
216
+ id: string;
217
+ index: number;
218
+ name: string | null;
219
+ nameGeneratedAt: string | null;
220
+ signalCount: number;
221
+ totalWeight: number;
222
+ }
223
+ interface ProfileResult {
224
+ clusters?: ProfileClusterResult[];
225
+ createdAt: string;
226
+ fileCount: number;
227
+ id: string;
228
+ name: string | null;
229
+ signalCount: number;
230
+ updatedAt: string;
231
+ vector: number[] | null;
232
+ }
233
+ interface ReclusterRequest {
234
+ clusterCount?: number;
235
+ }
236
+ interface ReclusterResult {
237
+ clustered: boolean;
238
+ clusters: ProfileClusterResult[];
239
+ profileId: string;
240
+ totalSignals: number;
241
+ }
242
+ interface RenameClusterRequest {
243
+ description?: string;
244
+ name?: string;
245
+ }
246
+ interface ProfileFilesResult {
247
+ fileCount: number;
248
+ id: string;
249
+ }
250
+ type ProfileSignalType = "view" | "view_long" | "click" | "like" | "save" | "choose" | "purchase" | "share" | "dismiss" | "skip" | "reject" | "report" | "custom";
251
+ interface ProfileSignalInput {
252
+ context?: Record<string, unknown>;
253
+ fileKey: string;
254
+ type: ProfileSignalType;
255
+ weight?: number;
256
+ }
257
+ interface ProfileSignalResult {
258
+ fileKey: string;
259
+ id: string;
260
+ type: ProfileSignalType;
261
+ weight: number;
262
+ }
263
+ interface ProfileSignalsResponse {
264
+ profileId: string;
265
+ signals: ProfileSignalResult[];
266
+ totalSignals: number;
267
+ vectorUpdated: boolean;
268
+ }
269
+ interface DeleteProfileSignalsResult {
270
+ profileId: string;
271
+ removed: number;
272
+ totalSignals: number;
273
+ vectorUpdated: boolean;
274
+ }
275
+ interface TaskTriggerResult {
276
+ key: string;
277
+ triggered: string;
278
+ }
279
+ interface ReplaceResult {
280
+ contentType: string;
281
+ key: string;
282
+ size: number;
283
+ triggered: string[];
284
+ }
285
+ interface SearchFilters {
286
+ color?: {
287
+ dominantFamily?: string[];
288
+ mood?: string[];
289
+ temperature?: {
290
+ min?: number;
291
+ max?: number;
292
+ };
293
+ brightness?: {
294
+ min?: number;
295
+ max?: number;
296
+ };
297
+ vibrancy?: {
298
+ min?: number;
299
+ max?: number;
300
+ };
301
+ };
302
+ contentType?: string[];
303
+ metadata?: Record<string, string>;
304
+ tags?: string[];
305
+ taxonomies?: string[];
306
+ taxonomyGroups?: string[];
307
+ }
308
+ type SearchIncludeField = "tags" | "taxonomies" | "colors" | "colorProfile";
309
+ interface TextSearchRequest {
310
+ bucket?: string;
311
+ filters?: SearchFilters;
312
+ include?: SearchIncludeField[];
313
+ limit?: number;
314
+ query: string;
315
+ }
85
316
  /** Normal presign response — client must PUT to uploadUrl then call confirm. */
86
317
  interface PresignNewResult {
87
318
  /** URL to call to confirm the upload */
@@ -89,8 +320,6 @@ interface PresignNewResult {
89
320
  dedupe?: false;
90
321
  /** The file key that will be created */
91
322
  fileKey: string;
92
- /** The R2 key for the file */
93
- r2Key: string;
94
323
  /** URL to PUT the file to */
95
324
  uploadUrl: string;
96
325
  }
@@ -119,6 +348,8 @@ interface PresignRequest {
119
348
  size: number;
120
349
  }
121
350
  interface ConfirmUploadRequest {
351
+ /** Request AI-generated alt text for images */
352
+ altText?: boolean;
122
353
  /** Override the default bucket */
123
354
  bucket?: string;
124
355
  /** SHA-256 hex digest of file content. Stored with the file record for future dedup lookups. */
@@ -126,18 +357,32 @@ interface ConfirmUploadRequest {
126
357
  contentType: string;
127
358
  /** Fire KV sync in background instead of blocking. Saves ~100ms per upload. */
128
359
  deferKvSync?: boolean;
360
+ /** Request AI-generated description for images */
361
+ describe?: boolean;
129
362
  fileKey: string;
130
363
  /** Custom metadata to attach to the file */
131
364
  metadata?: Record<string, string>;
132
365
  size: number;
133
366
  /** Skip R2 HEAD verification — trust the presign PUT. Saves ~100ms per upload. */
134
367
  skipVerify?: boolean;
368
+ /** Request AI-generated title for images */
369
+ title?: boolean;
135
370
  }
136
371
  interface SimilarSearchRequest {
137
372
  /** Bucket name or ID to scope search */
138
373
  bucket?: string;
374
+ /** Use a specific cluster centroid instead of the profile master vector. Requires profileId. */
375
+ clusterId?: string;
376
+ /** Blend multiple cluster centroids as query vector. Requires profileId. */
377
+ clusterIds?: string[];
378
+ /** File keys to exclude from results (e.g. already-seen items). Max 200. */
379
+ excludeKeys?: string[];
139
380
  /** Find files similar to this file key */
140
381
  fileKey?: string;
382
+ /** Structured post-filters (taxonomy, tag, color, content type, metadata) */
383
+ filters?: SearchFilters;
384
+ /** Opt-in enrichment fields to include in results */
385
+ include?: SearchIncludeField[];
141
386
  /** Max results (default 10, max 50) */
142
387
  limit?: number;
143
388
  /** Search using a taste profile's vector */
@@ -145,49 +390,104 @@ interface SimilarSearchRequest {
145
390
  /** Search directly with a vector (1024 dimensions) */
146
391
  vector?: number[];
147
392
  }
148
- interface SimilarSearchResult {
149
- results: Array<{
150
- id: string;
151
- key: string;
152
- bucketId: string;
153
- originalFilename: string | null;
154
- size: number;
155
- contentType: string;
156
- metadata: Record<string, string> | null;
157
- embeddingStatus: string | null;
158
- similarity: number;
159
- createdAt: string;
160
- }>;
393
+ interface DiverseSearchRequest {
394
+ /** Bucket name or ID to scope search */
395
+ bucket?: string;
396
+ /** Use a specific cluster centroid instead of the profile master vector. Requires profileId. */
397
+ clusterId?: string;
398
+ /** Blend multiple cluster centroids as query vector. Requires profileId. */
399
+ clusterIds?: string[];
400
+ /** File keys to exclude from results (e.g. already-seen items). Max 200. */
401
+ excludeKeys?: string[];
402
+ /** Find diverse files seeded by this file key */
403
+ fileKey?: string;
404
+ /** Structured post-filters (taxonomy, tag, color, content type, metadata) */
405
+ filters?: SearchFilters;
406
+ /** Opt-in enrichment fields to include in results */
407
+ include?: SearchIncludeField[];
408
+ /** Tradeoff between relevance and diversity (0–1, default 0.5). 0 = pure diversity, 1 = pure similarity. */
409
+ lambda?: number;
410
+ /** Max results (default 10, max 50) */
411
+ limit?: number;
412
+ /** Search using a taste profile's vector as the relevance seed */
413
+ profileId?: string;
414
+ /** Search directly with a vector (1024 dimensions) as the relevance seed */
415
+ vector?: number[];
161
416
  }
162
- interface FileResult {
417
+ interface SearchResultItem {
418
+ bucketId: string;
419
+ colorProfile?: FileColorProfile | null;
420
+ colors?: FileColor[];
163
421
  contentType: string;
164
422
  createdAt: string;
423
+ duration?: number | null;
165
424
  embeddingStatus: string | null;
425
+ height?: number | null;
426
+ id: string;
166
427
  key: string;
167
428
  metadata: Record<string, string> | null;
429
+ originalFilename: string | null;
430
+ similarity: number;
168
431
  size: number;
169
- url: string | null;
432
+ tags?: Array<{
433
+ slug: string;
434
+ name: string;
435
+ }>;
436
+ taxonomies?: Array<{
437
+ slug: string;
438
+ name: string;
439
+ group: string;
440
+ similarity: number;
441
+ }>;
442
+ width?: number | null;
170
443
  }
171
- interface ProfileCreateRequest {
444
+ interface FilteredMetadata {
445
+ candidatesScanned: number;
446
+ requested: number;
447
+ returned: number;
448
+ }
449
+ interface SimilarSearchResult {
450
+ filtered?: FilteredMetadata;
451
+ results: SearchResultItem[];
452
+ }
453
+ interface ColorSearchRequest {
454
+ /** Bucket name or ID to scope search */
172
455
  bucket?: string;
173
- fileKeys?: string[];
174
- name?: string;
456
+ /** Only search the dominant (first) color per file */
457
+ dominantOnly?: boolean;
458
+ /** Target color as hex (e.g. "#FF0000") */
459
+ hex?: string;
460
+ /** Max results (default 10, max 50) */
461
+ limit?: number;
462
+ /** Minimum color proportion in the palette (0–1, default 0.05) */
463
+ minProportion?: number;
464
+ /** Target color in OKLab space */
465
+ oklab?: {
466
+ L: number;
467
+ a: number;
468
+ b: number;
469
+ };
175
470
  }
176
- interface ProfileResult {
471
+ interface ColorSearchResultItem {
472
+ bucketId: string;
473
+ colorDistance: number;
474
+ contentType: string;
177
475
  createdAt: string;
178
- fileCount: number;
179
- id: string;
180
- name: string | null;
181
- updatedAt: string;
182
- }
183
- interface ProfileFilesResult {
184
- fileCount: number;
185
476
  id: string;
477
+ key: string;
478
+ matchedColor: {
479
+ hex: string;
480
+ oklab: {
481
+ L: number;
482
+ a: number;
483
+ b: number;
484
+ } | null;
485
+ position: number;
486
+ proportion: number;
487
+ };
186
488
  }
187
- interface TextSearchRequest {
188
- bucket?: string;
189
- limit?: number;
190
- query: string;
489
+ interface ColorSearchResult {
490
+ results: ColorSearchResultItem[];
191
491
  }
192
492
  declare class StowServer {
193
493
  private readonly apiKey;
@@ -200,6 +500,38 @@ declare class StowServer {
200
500
  * Get the base URL for this instance (used by client SDK)
201
501
  */
202
502
  getBaseUrl(): string;
503
+ /**
504
+ * Return account usage and API key info for the current credential.
505
+ */
506
+ whoami(): Promise<WhoamiResult>;
507
+ /**
508
+ * List all buckets available to the current organization.
509
+ */
510
+ listBuckets(): Promise<ListBucketsResult>;
511
+ /**
512
+ * Create a new bucket.
513
+ */
514
+ createBucket(request: CreateBucketRequest): Promise<BucketResult>;
515
+ /**
516
+ * Get bucket details by id.
517
+ */
518
+ getBucket(id: string): Promise<BucketResult>;
519
+ /**
520
+ * Update bucket settings by id.
521
+ */
522
+ updateBucket(id: string, updates: UpdateBucketRequest): Promise<BucketResult>;
523
+ /**
524
+ * Rename/update a bucket by current bucket name.
525
+ */
526
+ updateBucketByName(name: string, updates: UpdateBucketRequest): Promise<BucketResult>;
527
+ /**
528
+ * Rename a bucket by current bucket name.
529
+ */
530
+ renameBucket(name: string, newName: string): Promise<BucketResult>;
531
+ /**
532
+ * Delete a bucket by id.
533
+ */
534
+ deleteBucket(id: string): Promise<void>;
203
535
  /**
204
536
  * Resolve the effective bucket for this request.
205
537
  * Per-call override > constructor default.
@@ -229,6 +561,12 @@ declare class StowServer {
229
561
  bucket?: string;
230
562
  /** Custom metadata to attach to the file */
231
563
  metadata?: Record<string, string>;
564
+ /** Request AI-generated title for images */
565
+ title?: boolean;
566
+ /** Request AI-generated description for images */
567
+ describe?: boolean;
568
+ /** Request AI-generated alt text for images */
569
+ altText?: boolean;
232
570
  }): Promise<UploadResult>;
233
571
  /**
234
572
  * Upload a file from a URL (server-side fetch + upload)
@@ -238,6 +576,12 @@ declare class StowServer {
238
576
  metadata?: Record<string, string>;
239
577
  /** Headers to forward when fetching the URL (e.g. User-Agent, Referer) */
240
578
  headers?: Record<string, string>;
579
+ /** Request AI-generated title for images */
580
+ title?: boolean;
581
+ /** Request AI-generated description for images */
582
+ describe?: boolean;
583
+ /** Request AI-generated alt text for images */
584
+ altText?: boolean;
241
585
  }): Promise<UploadResult>;
242
586
  /**
243
587
  * Get a presigned URL for direct client-side upload.
@@ -286,6 +630,32 @@ declare class StowServer {
286
630
  getFile(key: string, options?: {
287
631
  bucket?: string;
288
632
  }): Promise<FileResult>;
633
+ /**
634
+ * Extract color palette from an image file.
635
+ * Requires a searchable bucket.
636
+ */
637
+ extractColors(key: string): Promise<TaskTriggerResult>;
638
+ /**
639
+ * Extract dimensions (width/height) from an image or video file.
640
+ */
641
+ extractDimensions(key: string): Promise<TaskTriggerResult>;
642
+ /**
643
+ * Generate a vector embedding for an image file.
644
+ * Requires a searchable bucket.
645
+ */
646
+ embed(key: string): Promise<TaskTriggerResult>;
647
+ /**
648
+ * Replace a file's content by fetching from a new URL.
649
+ *
650
+ * Keeps the same file key but replaces the stored object and resets all
651
+ * derived data (dimensions, embeddings, colors, AI metadata). Processing
652
+ * tasks are re-dispatched as if the file were newly uploaded.
653
+ */
654
+ replaceFile(key: string, url: string, options?: {
655
+ bucket?: string;
656
+ /** Headers to forward when fetching the URL (e.g. User-Agent, Referer) */
657
+ headers?: Record<string, string>;
658
+ }): Promise<ReplaceResult>;
289
659
  /**
290
660
  * Get a transform URL for an image.
291
661
  *
@@ -342,10 +712,14 @@ declare class StowServer {
342
712
  */
343
713
  get search(): {
344
714
  similar: (params: SimilarSearchRequest) => Promise<SimilarSearchResult>;
715
+ diverse: (params?: DiverseSearchRequest) => Promise<SimilarSearchResult>;
345
716
  text: (params: TextSearchRequest) => Promise<SimilarSearchResult>;
717
+ color: (params: ColorSearchRequest) => Promise<ColorSearchResult>;
346
718
  };
347
719
  private searchSimilar;
720
+ private searchDiverse;
348
721
  private searchText;
722
+ private searchColor;
349
723
  /**
350
724
  * Upload a file as a drop (quick share)
351
725
  *
@@ -379,12 +753,26 @@ declare class StowServer {
379
753
  delete: (id: string) => Promise<void>;
380
754
  addFiles: (id: string, fileKeys: string[], bucket?: string) => Promise<ProfileFilesResult>;
381
755
  removeFiles: (id: string, fileKeys: string[], bucket?: string) => Promise<ProfileFilesResult>;
756
+ signal: (id: string, signals: ProfileSignalInput[], bucket?: string) => Promise<ProfileSignalsResponse>;
757
+ deleteSignals: (id: string, signalIds: string[]) => Promise<DeleteProfileSignalsResult>;
758
+ clusters: (id: string) => Promise<{
759
+ profileId: string;
760
+ signalCount: number;
761
+ clusters: ProfileClusterResult[];
762
+ }>;
763
+ recluster: (id: string, params?: ReclusterRequest) => Promise<ReclusterResult>;
764
+ renameCluster: (profileId: string, clusterId: string, params: RenameClusterRequest) => Promise<ProfileClusterResult>;
382
765
  };
383
766
  private createProfile;
384
767
  private getProfile;
385
768
  private deleteProfile;
386
769
  private addProfileFiles;
387
770
  private removeProfileFiles;
771
+ private signalProfile;
772
+ private deleteProfileSignals;
773
+ private getProfileClusters;
774
+ private reclusterProfile;
775
+ private renameProfileCluster;
388
776
  }
389
777
 
390
- export { type ConfirmUploadRequest, type Drop, type DropResult, type FileResult, type ListDropsResult, type ListFilesResult, type PresignDedupeResult, type PresignNewResult, type PresignRequest, type PresignResult, type ProfileCreateRequest, type ProfileFilesResult, type ProfileResult, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TextSearchRequest, type TransformOptions, type UploadResult };
778
+ export { type BucketResult, type ColorSearchRequest, type ColorSearchResult, type ColorSearchResultItem, type ConfirmUploadRequest, type CreateBucketRequest, type DeleteProfileSignalsResult, type DiverseSearchRequest, type Drop, type DropResult, type FileColor, type FileColorProfile, type FileResult, 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 ReclusterRequest, type ReclusterResult, type RenameClusterRequest, type ReplaceResult, type SearchFilters, type SearchIncludeField, type SearchResultItem, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TaskTriggerResult, type TextSearchRequest, type TransformOptions, type UpdateBucketRequest, type UploadResult, type WhoamiResult };