@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.js CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  StowServer: () => StowServer
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
+ var import_node_crypto = require("crypto");
27
28
  var import_zod = require("zod");
28
29
  var StowError = class extends Error {
29
30
  status;
@@ -35,12 +36,74 @@ var StowError = class extends Error {
35
36
  this.code = code;
36
37
  }
37
38
  };
39
+ var fileColorSchema = import_zod.z.object({
40
+ position: import_zod.z.number().int(),
41
+ proportion: import_zod.z.number(),
42
+ hex: import_zod.z.string(),
43
+ name: import_zod.z.string().nullable(),
44
+ hsl: import_zod.z.object({ h: import_zod.z.number(), s: import_zod.z.number(), l: import_zod.z.number() }),
45
+ oklab: import_zod.z.object({ L: import_zod.z.number(), a: import_zod.z.number(), b: import_zod.z.number() }).nullable(),
46
+ oklch: import_zod.z.object({ l: import_zod.z.number(), c: import_zod.z.number(), h: import_zod.z.number() }).nullable()
47
+ });
48
+ var fileColorProfileSchema = import_zod.z.object({
49
+ palette: import_zod.z.object({
50
+ mood: import_zod.z.string(),
51
+ brightness: import_zod.z.number(),
52
+ temperature: import_zod.z.number(),
53
+ vibrancy: import_zod.z.number(),
54
+ complexity: import_zod.z.number(),
55
+ dominantFamily: import_zod.z.string().nullable()
56
+ }),
57
+ backgroundHex: import_zod.z.string().nullable(),
58
+ accent: import_zod.z.object({
59
+ hex: import_zod.z.string(),
60
+ name: import_zod.z.string().nullable(),
61
+ oklab: import_zod.z.object({ L: import_zod.z.number(), a: import_zod.z.number(), b: import_zod.z.number() }).nullable(),
62
+ oklch: import_zod.z.object({ l: import_zod.z.number(), c: import_zod.z.number(), h: import_zod.z.number() }).nullable()
63
+ }).nullable(),
64
+ extractedAt: import_zod.z.string(),
65
+ colorCount: import_zod.z.number().int()
66
+ });
38
67
  var uploadResultSchema = import_zod.z.object({
39
68
  key: import_zod.z.string(),
40
69
  url: import_zod.z.string().nullable(),
41
70
  size: import_zod.z.number(),
42
71
  contentType: import_zod.z.string().optional(),
43
- metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
72
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
73
+ deduped: import_zod.z.boolean().optional()
74
+ });
75
+ var bucketSchema = import_zod.z.object({
76
+ id: import_zod.z.string(),
77
+ name: import_zod.z.string(),
78
+ description: import_zod.z.string().nullable().optional(),
79
+ isPublic: import_zod.z.boolean().optional(),
80
+ searchable: import_zod.z.boolean().optional(),
81
+ allowedTypes: import_zod.z.array(import_zod.z.string()).nullable().optional(),
82
+ maxFileSize: import_zod.z.coerce.number().nullable().optional(),
83
+ storageQuota: import_zod.z.coerce.number().nullable().optional(),
84
+ fileCountLimit: import_zod.z.coerce.number().nullable().optional(),
85
+ fileCount: import_zod.z.coerce.number().optional(),
86
+ usageBytes: import_zod.z.coerce.number().optional(),
87
+ createdAt: import_zod.z.string().optional()
88
+ });
89
+ var listBucketsSchema = import_zod.z.object({
90
+ buckets: import_zod.z.array(bucketSchema)
91
+ });
92
+ var bucketResponseSchema = import_zod.z.object({
93
+ bucket: bucketSchema
94
+ });
95
+ var whoamiSchema = import_zod.z.object({
96
+ user: import_zod.z.object({ email: import_zod.z.string() }),
97
+ stats: import_zod.z.object({
98
+ totalBytes: import_zod.z.coerce.number(),
99
+ totalFiles: import_zod.z.coerce.number(),
100
+ bucketCount: import_zod.z.coerce.number()
101
+ }),
102
+ key: import_zod.z.object({
103
+ name: import_zod.z.string(),
104
+ scope: import_zod.z.string(),
105
+ permissions: import_zod.z.record(import_zod.z.string(), import_zod.z.boolean())
106
+ }).optional()
44
107
  });
45
108
  var listFilesSchema = import_zod.z.object({
46
109
  files: import_zod.z.array(
@@ -49,11 +112,26 @@ var listFilesSchema = import_zod.z.object({
49
112
  size: import_zod.z.number(),
50
113
  lastModified: import_zod.z.string(),
51
114
  url: import_zod.z.string().nullable(),
52
- metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
115
+ width: import_zod.z.number().nullable().optional(),
116
+ height: import_zod.z.number().nullable().optional(),
117
+ duration: import_zod.z.number().nullable().optional(),
118
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
119
+ colorProfile: fileColorProfileSchema.nullable().optional(),
120
+ colors: import_zod.z.array(fileColorSchema).optional()
53
121
  })
54
122
  ),
55
123
  nextCursor: import_zod.z.string().nullable()
56
124
  });
125
+ var taskTriggerResultSchema = import_zod.z.object({
126
+ key: import_zod.z.string(),
127
+ triggered: import_zod.z.string()
128
+ });
129
+ var replaceResultSchema = import_zod.z.object({
130
+ key: import_zod.z.string(),
131
+ size: import_zod.z.number(),
132
+ contentType: import_zod.z.string(),
133
+ triggered: import_zod.z.array(import_zod.z.string())
134
+ });
57
135
  var errorSchema = import_zod.z.object({
58
136
  error: import_zod.z.string(),
59
137
  code: import_zod.z.string().optional()
@@ -81,7 +159,6 @@ var listDropsSchema = import_zod.z.object({
81
159
  var presignNewResultSchema = import_zod.z.object({
82
160
  fileKey: import_zod.z.string(),
83
161
  uploadUrl: import_zod.z.string(),
84
- r2Key: import_zod.z.string(),
85
162
  confirmUrl: import_zod.z.string(),
86
163
  dedupe: import_zod.z.literal(false).optional()
87
164
  });
@@ -108,14 +185,31 @@ var fileResultSchema = import_zod.z.object({
108
185
  size: import_zod.z.number(),
109
186
  contentType: import_zod.z.string(),
110
187
  url: import_zod.z.string().nullable(),
188
+ width: import_zod.z.number().nullable(),
189
+ height: import_zod.z.number().nullable(),
190
+ duration: import_zod.z.number().nullable(),
111
191
  metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).nullable(),
192
+ colorProfile: fileColorProfileSchema.nullable(),
193
+ colors: import_zod.z.array(fileColorSchema),
112
194
  embeddingStatus: import_zod.z.string().nullable(),
113
195
  createdAt: import_zod.z.string()
114
196
  });
197
+ var profileClusterResultSchema = import_zod.z.object({
198
+ id: import_zod.z.string(),
199
+ index: import_zod.z.number().int(),
200
+ name: import_zod.z.string().nullable(),
201
+ description: import_zod.z.string().nullable(),
202
+ signalCount: import_zod.z.number().int(),
203
+ totalWeight: import_zod.z.number(),
204
+ nameGeneratedAt: import_zod.z.string().nullable()
205
+ });
115
206
  var profileResultSchema = import_zod.z.object({
116
207
  id: import_zod.z.string(),
117
208
  name: import_zod.z.string().nullable(),
118
209
  fileCount: import_zod.z.number(),
210
+ signalCount: import_zod.z.number(),
211
+ vector: import_zod.z.array(import_zod.z.number()).nullable(),
212
+ clusters: import_zod.z.array(profileClusterResultSchema).optional(),
119
213
  createdAt: import_zod.z.string(),
120
214
  updatedAt: import_zod.z.string()
121
215
  });
@@ -123,6 +217,38 @@ var profileFilesResultSchema = import_zod.z.object({
123
217
  id: import_zod.z.string(),
124
218
  fileCount: import_zod.z.number()
125
219
  });
220
+ var profileSignalResultSchema = import_zod.z.object({
221
+ id: import_zod.z.string(),
222
+ fileKey: import_zod.z.string(),
223
+ type: import_zod.z.enum([
224
+ "view",
225
+ "view_long",
226
+ "click",
227
+ "like",
228
+ "save",
229
+ "choose",
230
+ "purchase",
231
+ "share",
232
+ "dismiss",
233
+ "skip",
234
+ "reject",
235
+ "report",
236
+ "custom"
237
+ ]),
238
+ weight: import_zod.z.number()
239
+ });
240
+ var profileSignalsResponseSchema = import_zod.z.object({
241
+ profileId: import_zod.z.string(),
242
+ signals: import_zod.z.array(profileSignalResultSchema),
243
+ totalSignals: import_zod.z.number(),
244
+ vectorUpdated: import_zod.z.boolean()
245
+ });
246
+ var deleteProfileSignalsResponseSchema = import_zod.z.object({
247
+ profileId: import_zod.z.string(),
248
+ removed: import_zod.z.number(),
249
+ totalSignals: import_zod.z.number(),
250
+ vectorUpdated: import_zod.z.boolean()
251
+ });
126
252
  var StowServer = class {
127
253
  apiKey;
128
254
  baseUrl;
@@ -149,6 +275,88 @@ var StowServer = class {
149
275
  getBaseUrl() {
150
276
  return this.baseUrl;
151
277
  }
278
+ /**
279
+ * Return account usage and API key info for the current credential.
280
+ */
281
+ whoami() {
282
+ return this.request("/api/whoami", { method: "GET" }, whoamiSchema);
283
+ }
284
+ /**
285
+ * List all buckets available to the current organization.
286
+ */
287
+ listBuckets() {
288
+ return this.request("/api/buckets", { method: "GET" }, listBucketsSchema);
289
+ }
290
+ /**
291
+ * Create a new bucket.
292
+ */
293
+ async createBucket(request) {
294
+ const result = await this.request(
295
+ "/api/buckets",
296
+ {
297
+ method: "POST",
298
+ headers: { "Content-Type": "application/json" },
299
+ body: JSON.stringify(request)
300
+ },
301
+ bucketResponseSchema
302
+ );
303
+ return result.bucket;
304
+ }
305
+ /**
306
+ * Get bucket details by id.
307
+ */
308
+ async getBucket(id) {
309
+ const result = await this.request(
310
+ `/api/buckets/${encodeURIComponent(id)}`,
311
+ { method: "GET" },
312
+ bucketResponseSchema
313
+ );
314
+ return result.bucket;
315
+ }
316
+ /**
317
+ * Update bucket settings by id.
318
+ */
319
+ async updateBucket(id, updates) {
320
+ const result = await this.request(
321
+ `/api/buckets/${encodeURIComponent(id)}`,
322
+ {
323
+ method: "PATCH",
324
+ headers: { "Content-Type": "application/json" },
325
+ body: JSON.stringify(updates)
326
+ },
327
+ bucketResponseSchema
328
+ );
329
+ return result.bucket;
330
+ }
331
+ /**
332
+ * Rename/update a bucket by current bucket name.
333
+ */
334
+ async updateBucketByName(name, updates) {
335
+ const result = await this.request(
336
+ `/api/buckets/_?bucket=${encodeURIComponent(name)}`,
337
+ {
338
+ method: "PATCH",
339
+ headers: { "Content-Type": "application/json" },
340
+ body: JSON.stringify(updates)
341
+ },
342
+ bucketResponseSchema
343
+ );
344
+ return result.bucket;
345
+ }
346
+ /**
347
+ * Rename a bucket by current bucket name.
348
+ */
349
+ renameBucket(name, newName) {
350
+ return this.updateBucketByName(name, { name: newName });
351
+ }
352
+ /**
353
+ * Delete a bucket by id.
354
+ */
355
+ async deleteBucket(id) {
356
+ await this.request(`/api/buckets/${encodeURIComponent(id)}`, {
357
+ method: "DELETE"
358
+ });
359
+ }
152
360
  /**
153
361
  * Resolve the effective bucket for this request.
154
362
  * Per-call override > constructor default.
@@ -174,6 +382,7 @@ var StowServer = class {
174
382
  * - AbortController timeout (default 30s).
175
383
  * - Consumer can pass `signal` in options to cancel.
176
384
  */
385
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: retry + timeout + error normalization intentionally handled in one request pipeline
177
386
  async request(path, options, schema) {
178
387
  const maxAttempts = this.retries + 1;
179
388
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -208,6 +417,13 @@ var StowServer = class {
208
417
  if (err instanceof StowError) {
209
418
  throw err;
210
419
  }
420
+ if (err instanceof import_zod.z.ZodError) {
421
+ throw new StowError(
422
+ "Invalid response format",
423
+ 500,
424
+ "INVALID_RESPONSE"
425
+ );
426
+ }
211
427
  if (err instanceof DOMException || err instanceof Error && err.name === "AbortError") {
212
428
  throw new StowError("Request timed out", 408, "TIMEOUT");
213
429
  }
@@ -233,32 +449,58 @@ var StowServer = class {
233
449
  * Upload a file directly from the server
234
450
  */
235
451
  async uploadFile(file, options) {
236
- const formData = new FormData();
237
- const blob = Buffer.isBuffer(file) ? new Blob([new Uint8Array(file)], {
238
- type: options?.contentType || "application/octet-stream"
239
- }) : file;
240
- formData.append("file", blob, options?.filename || "file");
241
- if (options?.route) {
242
- formData.append("route", options.route);
452
+ const filename = options?.filename || "file";
453
+ const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
454
+ const contentType = options?.contentType || (!Buffer.isBuffer(file) && file.type ? file.type : "application/octet-stream");
455
+ const contentHash = (0, import_node_crypto.createHash)("sha256").update(buffer).digest("hex");
456
+ const presign = await this.getPresignedUrl({
457
+ filename,
458
+ contentType,
459
+ size: buffer.length,
460
+ ...options?.route ? { route: options.route } : {},
461
+ ...options?.bucket ? { bucket: options.bucket } : {},
462
+ ...options?.metadata ? { metadata: options.metadata } : {},
463
+ contentHash
464
+ });
465
+ if (presign.dedupe) {
466
+ return {
467
+ key: presign.key,
468
+ url: presign.url,
469
+ size: presign.size,
470
+ contentType: presign.contentType,
471
+ deduped: true
472
+ };
243
473
  }
244
- if (options?.metadata) {
245
- formData.append("metadata", JSON.stringify(options.metadata));
474
+ const uploadRes = await fetch(presign.uploadUrl, {
475
+ method: "PUT",
476
+ headers: { "Content-Type": contentType },
477
+ body: new Uint8Array(buffer)
478
+ });
479
+ if (!uploadRes.ok) {
480
+ throw new StowError("Failed to upload to storage", uploadRes.status);
246
481
  }
247
- const result = await this.request(
248
- this.withBucket("/api/upload", options?.bucket),
482
+ return this.request(
483
+ this.withBucket(
484
+ presign.confirmUrl || "/api/presign/confirm",
485
+ options?.bucket
486
+ ),
249
487
  {
250
488
  method: "POST",
251
- body: formData
489
+ headers: { "Content-Type": "application/json" },
490
+ body: JSON.stringify({
491
+ fileKey: presign.fileKey,
492
+ size: buffer.length,
493
+ contentType,
494
+ ...options?.metadata ? { metadata: options.metadata } : {},
495
+ contentHash,
496
+ skipVerify: true,
497
+ ...options?.title ? { title: true } : {},
498
+ ...options?.describe ? { describe: true } : {},
499
+ ...options?.altText ? { altText: true } : {}
500
+ })
252
501
  },
253
- uploadResultSchema
502
+ confirmResultSchema
254
503
  );
255
- return {
256
- key: result.key,
257
- url: result.url,
258
- size: result.size,
259
- contentType: result.contentType || options?.contentType || "application/octet-stream",
260
- ...result.metadata ? { metadata: result.metadata } : {}
261
- };
262
504
  }
263
505
  /**
264
506
  * Upload a file from a URL (server-side fetch + upload)
@@ -273,7 +515,10 @@ var StowServer = class {
273
515
  url,
274
516
  filename,
275
517
  ...options?.metadata ? { metadata: options.metadata } : {},
276
- ...options?.headers ? { headers: options.headers } : {}
518
+ ...options?.headers ? { headers: options.headers } : {},
519
+ ...options?.title ? { title: true } : {},
520
+ ...options?.describe ? { describe: true } : {},
521
+ ...options?.altText ? { altText: true } : {}
277
522
  })
278
523
  },
279
524
  uploadResultSchema
@@ -296,7 +541,15 @@ var StowServer = class {
296
541
  * 4. Client calls confirmUpload to finalize
297
542
  */
298
543
  getPresignedUrl(request) {
299
- const { filename, contentType, size, route, bucket, metadata, contentHash } = request;
544
+ const {
545
+ filename,
546
+ contentType,
547
+ size,
548
+ route,
549
+ bucket,
550
+ metadata,
551
+ contentHash
552
+ } = request;
300
553
  return this.request(
301
554
  this.withBucket("/api/presign", bucket),
302
555
  {
@@ -327,7 +580,10 @@ var StowServer = class {
327
580
  metadata,
328
581
  skipVerify,
329
582
  deferKvSync,
330
- contentHash
583
+ contentHash,
584
+ title,
585
+ describe,
586
+ altText
331
587
  } = request;
332
588
  return this.request(
333
589
  this.withBucket("/api/presign/confirm", bucket),
@@ -341,7 +597,10 @@ var StowServer = class {
341
597
  ...metadata ? { metadata } : {},
342
598
  ...skipVerify ? { skipVerify } : {},
343
599
  ...deferKvSync ? { deferKvSync } : {},
344
- ...contentHash ? { contentHash } : {}
600
+ ...contentHash ? { contentHash } : {},
601
+ ...title ? { title } : {},
602
+ ...describe ? { describe } : {},
603
+ ...altText ? { altText } : {}
345
604
  })
346
605
  },
347
606
  confirmResultSchema
@@ -383,7 +642,7 @@ var StowServer = class {
383
642
  /**
384
643
  * Update metadata on an existing file
385
644
  */
386
- async updateFileMetadata(key, metadata, options) {
645
+ updateFileMetadata(key, metadata, options) {
387
646
  const path = `/api/files/${encodeURIComponent(key)}`;
388
647
  return this.request(this.withBucket(path, options?.bucket), {
389
648
  method: "PATCH",
@@ -394,7 +653,7 @@ var StowServer = class {
394
653
  /**
395
654
  * Get a single file by key
396
655
  */
397
- async getFile(key, options) {
656
+ getFile(key, options) {
398
657
  const path = `/api/files/${encodeURIComponent(key)}`;
399
658
  return this.request(
400
659
  this.withBucket(path, options?.bucket),
@@ -402,6 +661,60 @@ var StowServer = class {
402
661
  fileResultSchema
403
662
  );
404
663
  }
664
+ /**
665
+ * Extract color palette from an image file.
666
+ * Requires a searchable bucket.
667
+ */
668
+ extractColors(key) {
669
+ return this.request(
670
+ `/api/files/${encodeURIComponent(key)}/extract-colors`,
671
+ { method: "POST" },
672
+ taskTriggerResultSchema
673
+ );
674
+ }
675
+ /**
676
+ * Extract dimensions (width/height) from an image or video file.
677
+ */
678
+ extractDimensions(key) {
679
+ return this.request(
680
+ `/api/files/${encodeURIComponent(key)}/extract-dimensions`,
681
+ { method: "POST" },
682
+ taskTriggerResultSchema
683
+ );
684
+ }
685
+ /**
686
+ * Generate a vector embedding for an image file.
687
+ * Requires a searchable bucket.
688
+ */
689
+ embed(key) {
690
+ return this.request(
691
+ `/api/files/${encodeURIComponent(key)}/embed`,
692
+ { method: "POST" },
693
+ taskTriggerResultSchema
694
+ );
695
+ }
696
+ /**
697
+ * Replace a file's content by fetching from a new URL.
698
+ *
699
+ * Keeps the same file key but replaces the stored object and resets all
700
+ * derived data (dimensions, embeddings, colors, AI metadata). Processing
701
+ * tasks are re-dispatched as if the file were newly uploaded.
702
+ */
703
+ replaceFile(key, url, options) {
704
+ const path = `/api/files/${encodeURIComponent(key)}/replace`;
705
+ return this.request(
706
+ this.withBucket(path, options?.bucket),
707
+ {
708
+ method: "PUT",
709
+ headers: { "Content-Type": "application/json" },
710
+ body: JSON.stringify({
711
+ url,
712
+ ...options?.headers ? { headers: options.headers } : {}
713
+ })
714
+ },
715
+ replaceResultSchema
716
+ );
717
+ }
405
718
  /**
406
719
  * Get a transform URL for an image.
407
720
  *
@@ -413,25 +726,23 @@ var StowServer = class {
413
726
  * @param options - Transform options (width, height, quality, format)
414
727
  */
415
728
  getTransformUrl(url, options) {
416
- const params = new URLSearchParams();
417
- if (options?.width) {
418
- params.set("w", options.width.toString());
729
+ if (!(options && (options.width || options.height || options.quality || options.format))) {
730
+ return url;
419
731
  }
420
- if (options?.height) {
421
- params.set("h", options.height.toString());
732
+ const parsed = new URL(url);
733
+ if (options.width) {
734
+ parsed.searchParams.set("w", String(options.width));
422
735
  }
423
- if (options?.quality) {
424
- params.set("q", options.quality.toString());
736
+ if (options.height) {
737
+ parsed.searchParams.set("h", String(options.height));
425
738
  }
426
- if (options?.format) {
427
- params.set("f", options.format);
739
+ if (options.quality) {
740
+ parsed.searchParams.set("q", String(options.quality));
428
741
  }
429
- const query = params.toString();
430
- if (!query) {
431
- return url;
742
+ if (options.format) {
743
+ parsed.searchParams.set("f", options.format);
432
744
  }
433
- const separator = url.includes("?") ? "&" : "?";
434
- return `${url}${separator}${query}`;
745
+ return parsed.toString();
435
746
  }
436
747
  // ============================================================
437
748
  // TAGS - Org-scoped labels for file organization
@@ -493,10 +804,13 @@ var StowServer = class {
493
804
  get search() {
494
805
  return {
495
806
  similar: (params) => this.searchSimilar(params),
496
- text: (params) => this.searchText(params)
807
+ diverse: (params) => this.searchDiverse(params ?? {}),
808
+ text: (params) => this.searchText(params),
809
+ color: (params) => this.searchColor(params)
497
810
  };
498
811
  }
499
812
  searchSimilar(params) {
813
+ const bucket = this.resolveBucket(params.bucket);
500
814
  return this.request("/api/search/similar", {
501
815
  method: "POST",
502
816
  headers: { "Content-Type": "application/json" },
@@ -504,19 +818,62 @@ var StowServer = class {
504
818
  ...params.fileKey ? { fileKey: params.fileKey } : {},
505
819
  ...params.vector ? { vector: params.vector } : {},
506
820
  ...params.profileId ? { profileId: params.profileId } : {},
507
- ...params.bucket ? { bucket: params.bucket } : {},
508
- ...params.limit ? { limit: params.limit } : {}
821
+ ...params.clusterId ? { clusterId: params.clusterId } : {},
822
+ ...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
823
+ ...bucket ? { bucket } : {},
824
+ ...params.limit ? { limit: params.limit } : {},
825
+ ...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
826
+ ...params.filters ? { filters: params.filters } : {},
827
+ ...params.include?.length ? { include: params.include } : {}
828
+ })
829
+ });
830
+ }
831
+ searchDiverse(params) {
832
+ const bucket = this.resolveBucket(params.bucket);
833
+ return this.request("/api/search/diverse", {
834
+ method: "POST",
835
+ headers: { "Content-Type": "application/json" },
836
+ body: JSON.stringify({
837
+ ...params.fileKey ? { fileKey: params.fileKey } : {},
838
+ ...params.vector ? { vector: params.vector } : {},
839
+ ...params.profileId ? { profileId: params.profileId } : {},
840
+ ...params.clusterId ? { clusterId: params.clusterId } : {},
841
+ ...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
842
+ ...bucket ? { bucket } : {},
843
+ ...params.limit ? { limit: params.limit } : {},
844
+ ...params.lambda !== void 0 ? { lambda: params.lambda } : {},
845
+ ...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
846
+ ...params.filters ? { filters: params.filters } : {},
847
+ ...params.include?.length ? { include: params.include } : {}
509
848
  })
510
849
  });
511
850
  }
512
851
  searchText(params) {
852
+ const bucket = this.resolveBucket(params.bucket);
513
853
  return this.request("/api/search/text", {
514
854
  method: "POST",
515
855
  headers: { "Content-Type": "application/json" },
516
856
  body: JSON.stringify({
517
857
  query: params.query,
518
- ...params.bucket ? { bucket: params.bucket } : {},
519
- ...params.limit ? { limit: params.limit } : {}
858
+ ...bucket ? { bucket } : {},
859
+ ...params.limit ? { limit: params.limit } : {},
860
+ ...params.filters ? { filters: params.filters } : {},
861
+ ...params.include?.length ? { include: params.include } : {}
862
+ })
863
+ });
864
+ }
865
+ searchColor(params) {
866
+ const bucket = this.resolveBucket(params.bucket);
867
+ return this.request("/api/search/color", {
868
+ method: "POST",
869
+ headers: { "Content-Type": "application/json" },
870
+ body: JSON.stringify({
871
+ ...params.hex ? { hex: params.hex } : {},
872
+ ...params.oklab ? { oklab: params.oklab } : {},
873
+ ...bucket ? { bucket } : {},
874
+ ...params.limit ? { limit: params.limit } : {},
875
+ ...params.minProportion !== void 0 ? { minProportion: params.minProportion } : {},
876
+ ...params.dominantOnly ? { dominantOnly: params.dominantOnly } : {}
520
877
  })
521
878
  });
522
879
  }
@@ -557,7 +914,7 @@ var StowServer = class {
557
914
  throw new StowError("Failed to upload to storage", putRes.status);
558
915
  }
559
916
  return this.request(
560
- "/api/drops/presign/confirm",
917
+ presign.confirmUrl || "/api/drops/presign/confirm",
561
918
  {
562
919
  method: "POST",
563
920
  headers: { "Content-Type": "application/json" },
@@ -599,7 +956,12 @@ var StowServer = class {
599
956
  get: (id) => this.getProfile(id),
600
957
  delete: (id) => this.deleteProfile(id),
601
958
  addFiles: (id, fileKeys, bucket) => this.addProfileFiles(id, fileKeys, bucket),
602
- removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket)
959
+ removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket),
960
+ signal: (id, signals, bucket) => this.signalProfile(id, signals, bucket),
961
+ deleteSignals: (id, signalIds) => this.deleteProfileSignals(id, signalIds),
962
+ clusters: (id) => this.getProfileClusters(id),
963
+ recluster: (id, params) => this.reclusterProfile(id, params),
964
+ renameCluster: (profileId, clusterId, params) => this.renameProfileCluster(profileId, clusterId, params)
603
965
  };
604
966
  }
605
967
  createProfile(params) {
@@ -657,6 +1019,55 @@ var StowServer = class {
657
1019
  profileFilesResultSchema
658
1020
  );
659
1021
  }
1022
+ signalProfile(id, signals, bucket) {
1023
+ return this.request(
1024
+ `/api/profiles/${encodeURIComponent(id)}/signals`,
1025
+ {
1026
+ method: "POST",
1027
+ headers: { "Content-Type": "application/json" },
1028
+ body: JSON.stringify({
1029
+ signals,
1030
+ ...bucket ? { bucket } : {}
1031
+ })
1032
+ },
1033
+ profileSignalsResponseSchema
1034
+ );
1035
+ }
1036
+ deleteProfileSignals(id, signalIds) {
1037
+ return this.request(
1038
+ `/api/profiles/${encodeURIComponent(id)}/signals`,
1039
+ {
1040
+ method: "DELETE",
1041
+ headers: { "Content-Type": "application/json" },
1042
+ body: JSON.stringify({ signalIds })
1043
+ },
1044
+ deleteProfileSignalsResponseSchema
1045
+ );
1046
+ }
1047
+ getProfileClusters(id) {
1048
+ return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
1049
+ method: "GET"
1050
+ });
1051
+ }
1052
+ reclusterProfile(id, params) {
1053
+ return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
1054
+ method: "POST",
1055
+ headers: { "Content-Type": "application/json" },
1056
+ body: JSON.stringify({
1057
+ ...params?.clusterCount !== void 0 ? { clusterCount: params.clusterCount } : {}
1058
+ })
1059
+ });
1060
+ }
1061
+ renameProfileCluster(profileId, clusterId, params) {
1062
+ return this.request(
1063
+ `/api/profiles/${encodeURIComponent(profileId)}/clusters/${encodeURIComponent(clusterId)}`,
1064
+ {
1065
+ method: "PUT",
1066
+ headers: { "Content-Type": "application/json" },
1067
+ body: JSON.stringify(params)
1068
+ }
1069
+ );
1070
+ }
660
1071
  };
661
1072
  // Annotate the CommonJS export names for ESM import in node:
662
1073
  0 && (module.exports = {