@howells/stow-server 0.1.2 → 0.2.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 reprocessResultSchema = import_zod.z.object({
126
+ key: import_zod.z.string(),
127
+ triggered: import_zod.z.array(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,40 @@ var StowServer = class {
402
661
  fileResultSchema
403
662
  );
404
663
  }
664
+ /**
665
+ * Reprocess a file: reset all derived data (embeddings, colors, dimensions,
666
+ * AI metadata, taxonomies) and re-trigger processing tasks.
667
+ */
668
+ reprocessFile(key, options) {
669
+ const path = `/api/files/${encodeURIComponent(key)}/reprocess`;
670
+ return this.request(
671
+ this.withBucket(path, options?.bucket),
672
+ { method: "POST" },
673
+ reprocessResultSchema
674
+ );
675
+ }
676
+ /**
677
+ * Replace a file's content by fetching from a new URL.
678
+ *
679
+ * Keeps the same file key but replaces the stored object and resets all
680
+ * derived data (dimensions, embeddings, colors, AI metadata). Processing
681
+ * tasks are re-dispatched as if the file were newly uploaded.
682
+ */
683
+ replaceFile(key, url, options) {
684
+ const path = `/api/files/${encodeURIComponent(key)}/replace`;
685
+ return this.request(
686
+ this.withBucket(path, options?.bucket),
687
+ {
688
+ method: "PUT",
689
+ headers: { "Content-Type": "application/json" },
690
+ body: JSON.stringify({
691
+ url,
692
+ ...options?.headers ? { headers: options.headers } : {}
693
+ })
694
+ },
695
+ replaceResultSchema
696
+ );
697
+ }
405
698
  /**
406
699
  * Get a transform URL for an image.
407
700
  *
@@ -413,25 +706,23 @@ var StowServer = class {
413
706
  * @param options - Transform options (width, height, quality, format)
414
707
  */
415
708
  getTransformUrl(url, options) {
416
- const params = new URLSearchParams();
417
- if (options?.width) {
418
- params.set("w", options.width.toString());
709
+ if (!(options && (options.width || options.height || options.quality || options.format))) {
710
+ return url;
419
711
  }
420
- if (options?.height) {
421
- params.set("h", options.height.toString());
712
+ const parsed = new URL(url);
713
+ if (options.width) {
714
+ parsed.searchParams.set("w", String(options.width));
422
715
  }
423
- if (options?.quality) {
424
- params.set("q", options.quality.toString());
716
+ if (options.height) {
717
+ parsed.searchParams.set("h", String(options.height));
425
718
  }
426
- if (options?.format) {
427
- params.set("f", options.format);
719
+ if (options.quality) {
720
+ parsed.searchParams.set("q", String(options.quality));
428
721
  }
429
- const query = params.toString();
430
- if (!query) {
431
- return url;
722
+ if (options.format) {
723
+ parsed.searchParams.set("f", options.format);
432
724
  }
433
- const separator = url.includes("?") ? "&" : "?";
434
- return `${url}${separator}${query}`;
725
+ return parsed.toString();
435
726
  }
436
727
  // ============================================================
437
728
  // TAGS - Org-scoped labels for file organization
@@ -493,10 +784,13 @@ var StowServer = class {
493
784
  get search() {
494
785
  return {
495
786
  similar: (params) => this.searchSimilar(params),
496
- text: (params) => this.searchText(params)
787
+ diverse: (params) => this.searchDiverse(params ?? {}),
788
+ text: (params) => this.searchText(params),
789
+ color: (params) => this.searchColor(params)
497
790
  };
498
791
  }
499
792
  searchSimilar(params) {
793
+ const bucket = this.resolveBucket(params.bucket);
500
794
  return this.request("/api/search/similar", {
501
795
  method: "POST",
502
796
  headers: { "Content-Type": "application/json" },
@@ -504,19 +798,62 @@ var StowServer = class {
504
798
  ...params.fileKey ? { fileKey: params.fileKey } : {},
505
799
  ...params.vector ? { vector: params.vector } : {},
506
800
  ...params.profileId ? { profileId: params.profileId } : {},
507
- ...params.bucket ? { bucket: params.bucket } : {},
508
- ...params.limit ? { limit: params.limit } : {}
801
+ ...params.clusterId ? { clusterId: params.clusterId } : {},
802
+ ...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
803
+ ...bucket ? { bucket } : {},
804
+ ...params.limit ? { limit: params.limit } : {},
805
+ ...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
806
+ ...params.filters ? { filters: params.filters } : {},
807
+ ...params.include?.length ? { include: params.include } : {}
808
+ })
809
+ });
810
+ }
811
+ searchDiverse(params) {
812
+ const bucket = this.resolveBucket(params.bucket);
813
+ return this.request("/api/search/diverse", {
814
+ method: "POST",
815
+ headers: { "Content-Type": "application/json" },
816
+ body: JSON.stringify({
817
+ ...params.fileKey ? { fileKey: params.fileKey } : {},
818
+ ...params.vector ? { vector: params.vector } : {},
819
+ ...params.profileId ? { profileId: params.profileId } : {},
820
+ ...params.clusterId ? { clusterId: params.clusterId } : {},
821
+ ...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
822
+ ...bucket ? { bucket } : {},
823
+ ...params.limit ? { limit: params.limit } : {},
824
+ ...params.lambda !== void 0 ? { lambda: params.lambda } : {},
825
+ ...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
826
+ ...params.filters ? { filters: params.filters } : {},
827
+ ...params.include?.length ? { include: params.include } : {}
509
828
  })
510
829
  });
511
830
  }
512
831
  searchText(params) {
832
+ const bucket = this.resolveBucket(params.bucket);
513
833
  return this.request("/api/search/text", {
514
834
  method: "POST",
515
835
  headers: { "Content-Type": "application/json" },
516
836
  body: JSON.stringify({
517
837
  query: params.query,
518
- ...params.bucket ? { bucket: params.bucket } : {},
519
- ...params.limit ? { limit: params.limit } : {}
838
+ ...bucket ? { bucket } : {},
839
+ ...params.limit ? { limit: params.limit } : {},
840
+ ...params.filters ? { filters: params.filters } : {},
841
+ ...params.include?.length ? { include: params.include } : {}
842
+ })
843
+ });
844
+ }
845
+ searchColor(params) {
846
+ const bucket = this.resolveBucket(params.bucket);
847
+ return this.request("/api/search/color", {
848
+ method: "POST",
849
+ headers: { "Content-Type": "application/json" },
850
+ body: JSON.stringify({
851
+ ...params.hex ? { hex: params.hex } : {},
852
+ ...params.oklab ? { oklab: params.oklab } : {},
853
+ ...bucket ? { bucket } : {},
854
+ ...params.limit ? { limit: params.limit } : {},
855
+ ...params.minProportion !== void 0 ? { minProportion: params.minProportion } : {},
856
+ ...params.dominantOnly ? { dominantOnly: params.dominantOnly } : {}
520
857
  })
521
858
  });
522
859
  }
@@ -557,7 +894,7 @@ var StowServer = class {
557
894
  throw new StowError("Failed to upload to storage", putRes.status);
558
895
  }
559
896
  return this.request(
560
- "/api/drops/presign/confirm",
897
+ presign.confirmUrl || "/api/drops/presign/confirm",
561
898
  {
562
899
  method: "POST",
563
900
  headers: { "Content-Type": "application/json" },
@@ -599,7 +936,12 @@ var StowServer = class {
599
936
  get: (id) => this.getProfile(id),
600
937
  delete: (id) => this.deleteProfile(id),
601
938
  addFiles: (id, fileKeys, bucket) => this.addProfileFiles(id, fileKeys, bucket),
602
- removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket)
939
+ removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket),
940
+ signal: (id, signals, bucket) => this.signalProfile(id, signals, bucket),
941
+ deleteSignals: (id, signalIds) => this.deleteProfileSignals(id, signalIds),
942
+ clusters: (id) => this.getProfileClusters(id),
943
+ recluster: (id, params) => this.reclusterProfile(id, params),
944
+ renameCluster: (profileId, clusterId, params) => this.renameProfileCluster(profileId, clusterId, params)
603
945
  };
604
946
  }
605
947
  createProfile(params) {
@@ -657,6 +999,55 @@ var StowServer = class {
657
999
  profileFilesResultSchema
658
1000
  );
659
1001
  }
1002
+ signalProfile(id, signals, bucket) {
1003
+ return this.request(
1004
+ `/api/profiles/${encodeURIComponent(id)}/signals`,
1005
+ {
1006
+ method: "POST",
1007
+ headers: { "Content-Type": "application/json" },
1008
+ body: JSON.stringify({
1009
+ signals,
1010
+ ...bucket ? { bucket } : {}
1011
+ })
1012
+ },
1013
+ profileSignalsResponseSchema
1014
+ );
1015
+ }
1016
+ deleteProfileSignals(id, signalIds) {
1017
+ return this.request(
1018
+ `/api/profiles/${encodeURIComponent(id)}/signals`,
1019
+ {
1020
+ method: "DELETE",
1021
+ headers: { "Content-Type": "application/json" },
1022
+ body: JSON.stringify({ signalIds })
1023
+ },
1024
+ deleteProfileSignalsResponseSchema
1025
+ );
1026
+ }
1027
+ getProfileClusters(id) {
1028
+ return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
1029
+ method: "GET"
1030
+ });
1031
+ }
1032
+ reclusterProfile(id, params) {
1033
+ return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
1034
+ method: "POST",
1035
+ headers: { "Content-Type": "application/json" },
1036
+ body: JSON.stringify({
1037
+ ...params?.clusterCount !== void 0 ? { clusterCount: params.clusterCount } : {}
1038
+ })
1039
+ });
1040
+ }
1041
+ renameProfileCluster(profileId, clusterId, params) {
1042
+ return this.request(
1043
+ `/api/profiles/${encodeURIComponent(profileId)}/clusters/${encodeURIComponent(clusterId)}`,
1044
+ {
1045
+ method: "PUT",
1046
+ headers: { "Content-Type": "application/json" },
1047
+ body: JSON.stringify(params)
1048
+ }
1049
+ );
1050
+ }
660
1051
  };
661
1052
  // Annotate the CommonJS export names for ESM import in node:
662
1053
  0 && (module.exports = {