@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.mts +428 -40
- package/dist/index.d.ts +428 -40
- package/dist/index.js +461 -50
- package/dist/index.mjs +461 -50
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
var StowError = class extends Error {
|
|
4
5
|
status;
|
|
@@ -10,12 +11,74 @@ var StowError = class extends Error {
|
|
|
10
11
|
this.code = code;
|
|
11
12
|
}
|
|
12
13
|
};
|
|
14
|
+
var fileColorSchema = z.object({
|
|
15
|
+
position: z.number().int(),
|
|
16
|
+
proportion: z.number(),
|
|
17
|
+
hex: z.string(),
|
|
18
|
+
name: z.string().nullable(),
|
|
19
|
+
hsl: z.object({ h: z.number(), s: z.number(), l: z.number() }),
|
|
20
|
+
oklab: z.object({ L: z.number(), a: z.number(), b: z.number() }).nullable(),
|
|
21
|
+
oklch: z.object({ l: z.number(), c: z.number(), h: z.number() }).nullable()
|
|
22
|
+
});
|
|
23
|
+
var fileColorProfileSchema = z.object({
|
|
24
|
+
palette: z.object({
|
|
25
|
+
mood: z.string(),
|
|
26
|
+
brightness: z.number(),
|
|
27
|
+
temperature: z.number(),
|
|
28
|
+
vibrancy: z.number(),
|
|
29
|
+
complexity: z.number(),
|
|
30
|
+
dominantFamily: z.string().nullable()
|
|
31
|
+
}),
|
|
32
|
+
backgroundHex: z.string().nullable(),
|
|
33
|
+
accent: z.object({
|
|
34
|
+
hex: z.string(),
|
|
35
|
+
name: z.string().nullable(),
|
|
36
|
+
oklab: z.object({ L: z.number(), a: z.number(), b: z.number() }).nullable(),
|
|
37
|
+
oklch: z.object({ l: z.number(), c: z.number(), h: z.number() }).nullable()
|
|
38
|
+
}).nullable(),
|
|
39
|
+
extractedAt: z.string(),
|
|
40
|
+
colorCount: z.number().int()
|
|
41
|
+
});
|
|
13
42
|
var uploadResultSchema = z.object({
|
|
14
43
|
key: z.string(),
|
|
15
44
|
url: z.string().nullable(),
|
|
16
45
|
size: z.number(),
|
|
17
46
|
contentType: z.string().optional(),
|
|
18
|
-
metadata: z.record(z.string(), z.string()).optional()
|
|
47
|
+
metadata: z.record(z.string(), z.string()).optional(),
|
|
48
|
+
deduped: z.boolean().optional()
|
|
49
|
+
});
|
|
50
|
+
var bucketSchema = z.object({
|
|
51
|
+
id: z.string(),
|
|
52
|
+
name: z.string(),
|
|
53
|
+
description: z.string().nullable().optional(),
|
|
54
|
+
isPublic: z.boolean().optional(),
|
|
55
|
+
searchable: z.boolean().optional(),
|
|
56
|
+
allowedTypes: z.array(z.string()).nullable().optional(),
|
|
57
|
+
maxFileSize: z.coerce.number().nullable().optional(),
|
|
58
|
+
storageQuota: z.coerce.number().nullable().optional(),
|
|
59
|
+
fileCountLimit: z.coerce.number().nullable().optional(),
|
|
60
|
+
fileCount: z.coerce.number().optional(),
|
|
61
|
+
usageBytes: z.coerce.number().optional(),
|
|
62
|
+
createdAt: z.string().optional()
|
|
63
|
+
});
|
|
64
|
+
var listBucketsSchema = z.object({
|
|
65
|
+
buckets: z.array(bucketSchema)
|
|
66
|
+
});
|
|
67
|
+
var bucketResponseSchema = z.object({
|
|
68
|
+
bucket: bucketSchema
|
|
69
|
+
});
|
|
70
|
+
var whoamiSchema = z.object({
|
|
71
|
+
user: z.object({ email: z.string() }),
|
|
72
|
+
stats: z.object({
|
|
73
|
+
totalBytes: z.coerce.number(),
|
|
74
|
+
totalFiles: z.coerce.number(),
|
|
75
|
+
bucketCount: z.coerce.number()
|
|
76
|
+
}),
|
|
77
|
+
key: z.object({
|
|
78
|
+
name: z.string(),
|
|
79
|
+
scope: z.string(),
|
|
80
|
+
permissions: z.record(z.string(), z.boolean())
|
|
81
|
+
}).optional()
|
|
19
82
|
});
|
|
20
83
|
var listFilesSchema = z.object({
|
|
21
84
|
files: z.array(
|
|
@@ -24,11 +87,26 @@ var listFilesSchema = z.object({
|
|
|
24
87
|
size: z.number(),
|
|
25
88
|
lastModified: z.string(),
|
|
26
89
|
url: z.string().nullable(),
|
|
27
|
-
|
|
90
|
+
width: z.number().nullable().optional(),
|
|
91
|
+
height: z.number().nullable().optional(),
|
|
92
|
+
duration: z.number().nullable().optional(),
|
|
93
|
+
metadata: z.record(z.string(), z.string()).optional(),
|
|
94
|
+
colorProfile: fileColorProfileSchema.nullable().optional(),
|
|
95
|
+
colors: z.array(fileColorSchema).optional()
|
|
28
96
|
})
|
|
29
97
|
),
|
|
30
98
|
nextCursor: z.string().nullable()
|
|
31
99
|
});
|
|
100
|
+
var taskTriggerResultSchema = z.object({
|
|
101
|
+
key: z.string(),
|
|
102
|
+
triggered: z.string()
|
|
103
|
+
});
|
|
104
|
+
var replaceResultSchema = z.object({
|
|
105
|
+
key: z.string(),
|
|
106
|
+
size: z.number(),
|
|
107
|
+
contentType: z.string(),
|
|
108
|
+
triggered: z.array(z.string())
|
|
109
|
+
});
|
|
32
110
|
var errorSchema = z.object({
|
|
33
111
|
error: z.string(),
|
|
34
112
|
code: z.string().optional()
|
|
@@ -56,7 +134,6 @@ var listDropsSchema = z.object({
|
|
|
56
134
|
var presignNewResultSchema = z.object({
|
|
57
135
|
fileKey: z.string(),
|
|
58
136
|
uploadUrl: z.string(),
|
|
59
|
-
r2Key: z.string(),
|
|
60
137
|
confirmUrl: z.string(),
|
|
61
138
|
dedupe: z.literal(false).optional()
|
|
62
139
|
});
|
|
@@ -83,14 +160,31 @@ var fileResultSchema = z.object({
|
|
|
83
160
|
size: z.number(),
|
|
84
161
|
contentType: z.string(),
|
|
85
162
|
url: z.string().nullable(),
|
|
163
|
+
width: z.number().nullable(),
|
|
164
|
+
height: z.number().nullable(),
|
|
165
|
+
duration: z.number().nullable(),
|
|
86
166
|
metadata: z.record(z.string(), z.string()).nullable(),
|
|
167
|
+
colorProfile: fileColorProfileSchema.nullable(),
|
|
168
|
+
colors: z.array(fileColorSchema),
|
|
87
169
|
embeddingStatus: z.string().nullable(),
|
|
88
170
|
createdAt: z.string()
|
|
89
171
|
});
|
|
172
|
+
var profileClusterResultSchema = z.object({
|
|
173
|
+
id: z.string(),
|
|
174
|
+
index: z.number().int(),
|
|
175
|
+
name: z.string().nullable(),
|
|
176
|
+
description: z.string().nullable(),
|
|
177
|
+
signalCount: z.number().int(),
|
|
178
|
+
totalWeight: z.number(),
|
|
179
|
+
nameGeneratedAt: z.string().nullable()
|
|
180
|
+
});
|
|
90
181
|
var profileResultSchema = z.object({
|
|
91
182
|
id: z.string(),
|
|
92
183
|
name: z.string().nullable(),
|
|
93
184
|
fileCount: z.number(),
|
|
185
|
+
signalCount: z.number(),
|
|
186
|
+
vector: z.array(z.number()).nullable(),
|
|
187
|
+
clusters: z.array(profileClusterResultSchema).optional(),
|
|
94
188
|
createdAt: z.string(),
|
|
95
189
|
updatedAt: z.string()
|
|
96
190
|
});
|
|
@@ -98,6 +192,38 @@ var profileFilesResultSchema = z.object({
|
|
|
98
192
|
id: z.string(),
|
|
99
193
|
fileCount: z.number()
|
|
100
194
|
});
|
|
195
|
+
var profileSignalResultSchema = z.object({
|
|
196
|
+
id: z.string(),
|
|
197
|
+
fileKey: z.string(),
|
|
198
|
+
type: z.enum([
|
|
199
|
+
"view",
|
|
200
|
+
"view_long",
|
|
201
|
+
"click",
|
|
202
|
+
"like",
|
|
203
|
+
"save",
|
|
204
|
+
"choose",
|
|
205
|
+
"purchase",
|
|
206
|
+
"share",
|
|
207
|
+
"dismiss",
|
|
208
|
+
"skip",
|
|
209
|
+
"reject",
|
|
210
|
+
"report",
|
|
211
|
+
"custom"
|
|
212
|
+
]),
|
|
213
|
+
weight: z.number()
|
|
214
|
+
});
|
|
215
|
+
var profileSignalsResponseSchema = z.object({
|
|
216
|
+
profileId: z.string(),
|
|
217
|
+
signals: z.array(profileSignalResultSchema),
|
|
218
|
+
totalSignals: z.number(),
|
|
219
|
+
vectorUpdated: z.boolean()
|
|
220
|
+
});
|
|
221
|
+
var deleteProfileSignalsResponseSchema = z.object({
|
|
222
|
+
profileId: z.string(),
|
|
223
|
+
removed: z.number(),
|
|
224
|
+
totalSignals: z.number(),
|
|
225
|
+
vectorUpdated: z.boolean()
|
|
226
|
+
});
|
|
101
227
|
var StowServer = class {
|
|
102
228
|
apiKey;
|
|
103
229
|
baseUrl;
|
|
@@ -124,6 +250,88 @@ var StowServer = class {
|
|
|
124
250
|
getBaseUrl() {
|
|
125
251
|
return this.baseUrl;
|
|
126
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Return account usage and API key info for the current credential.
|
|
255
|
+
*/
|
|
256
|
+
whoami() {
|
|
257
|
+
return this.request("/api/whoami", { method: "GET" }, whoamiSchema);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* List all buckets available to the current organization.
|
|
261
|
+
*/
|
|
262
|
+
listBuckets() {
|
|
263
|
+
return this.request("/api/buckets", { method: "GET" }, listBucketsSchema);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Create a new bucket.
|
|
267
|
+
*/
|
|
268
|
+
async createBucket(request) {
|
|
269
|
+
const result = await this.request(
|
|
270
|
+
"/api/buckets",
|
|
271
|
+
{
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: { "Content-Type": "application/json" },
|
|
274
|
+
body: JSON.stringify(request)
|
|
275
|
+
},
|
|
276
|
+
bucketResponseSchema
|
|
277
|
+
);
|
|
278
|
+
return result.bucket;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get bucket details by id.
|
|
282
|
+
*/
|
|
283
|
+
async getBucket(id) {
|
|
284
|
+
const result = await this.request(
|
|
285
|
+
`/api/buckets/${encodeURIComponent(id)}`,
|
|
286
|
+
{ method: "GET" },
|
|
287
|
+
bucketResponseSchema
|
|
288
|
+
);
|
|
289
|
+
return result.bucket;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Update bucket settings by id.
|
|
293
|
+
*/
|
|
294
|
+
async updateBucket(id, updates) {
|
|
295
|
+
const result = await this.request(
|
|
296
|
+
`/api/buckets/${encodeURIComponent(id)}`,
|
|
297
|
+
{
|
|
298
|
+
method: "PATCH",
|
|
299
|
+
headers: { "Content-Type": "application/json" },
|
|
300
|
+
body: JSON.stringify(updates)
|
|
301
|
+
},
|
|
302
|
+
bucketResponseSchema
|
|
303
|
+
);
|
|
304
|
+
return result.bucket;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Rename/update a bucket by current bucket name.
|
|
308
|
+
*/
|
|
309
|
+
async updateBucketByName(name, updates) {
|
|
310
|
+
const result = await this.request(
|
|
311
|
+
`/api/buckets/_?bucket=${encodeURIComponent(name)}`,
|
|
312
|
+
{
|
|
313
|
+
method: "PATCH",
|
|
314
|
+
headers: { "Content-Type": "application/json" },
|
|
315
|
+
body: JSON.stringify(updates)
|
|
316
|
+
},
|
|
317
|
+
bucketResponseSchema
|
|
318
|
+
);
|
|
319
|
+
return result.bucket;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Rename a bucket by current bucket name.
|
|
323
|
+
*/
|
|
324
|
+
renameBucket(name, newName) {
|
|
325
|
+
return this.updateBucketByName(name, { name: newName });
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Delete a bucket by id.
|
|
329
|
+
*/
|
|
330
|
+
async deleteBucket(id) {
|
|
331
|
+
await this.request(`/api/buckets/${encodeURIComponent(id)}`, {
|
|
332
|
+
method: "DELETE"
|
|
333
|
+
});
|
|
334
|
+
}
|
|
127
335
|
/**
|
|
128
336
|
* Resolve the effective bucket for this request.
|
|
129
337
|
* Per-call override > constructor default.
|
|
@@ -149,6 +357,7 @@ var StowServer = class {
|
|
|
149
357
|
* - AbortController timeout (default 30s).
|
|
150
358
|
* - Consumer can pass `signal` in options to cancel.
|
|
151
359
|
*/
|
|
360
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: retry + timeout + error normalization intentionally handled in one request pipeline
|
|
152
361
|
async request(path, options, schema) {
|
|
153
362
|
const maxAttempts = this.retries + 1;
|
|
154
363
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
@@ -183,6 +392,13 @@ var StowServer = class {
|
|
|
183
392
|
if (err instanceof StowError) {
|
|
184
393
|
throw err;
|
|
185
394
|
}
|
|
395
|
+
if (err instanceof z.ZodError) {
|
|
396
|
+
throw new StowError(
|
|
397
|
+
"Invalid response format",
|
|
398
|
+
500,
|
|
399
|
+
"INVALID_RESPONSE"
|
|
400
|
+
);
|
|
401
|
+
}
|
|
186
402
|
if (err instanceof DOMException || err instanceof Error && err.name === "AbortError") {
|
|
187
403
|
throw new StowError("Request timed out", 408, "TIMEOUT");
|
|
188
404
|
}
|
|
@@ -208,32 +424,58 @@ var StowServer = class {
|
|
|
208
424
|
* Upload a file directly from the server
|
|
209
425
|
*/
|
|
210
426
|
async uploadFile(file, options) {
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
427
|
+
const filename = options?.filename || "file";
|
|
428
|
+
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
|
|
429
|
+
const contentType = options?.contentType || (!Buffer.isBuffer(file) && file.type ? file.type : "application/octet-stream");
|
|
430
|
+
const contentHash = createHash("sha256").update(buffer).digest("hex");
|
|
431
|
+
const presign = await this.getPresignedUrl({
|
|
432
|
+
filename,
|
|
433
|
+
contentType,
|
|
434
|
+
size: buffer.length,
|
|
435
|
+
...options?.route ? { route: options.route } : {},
|
|
436
|
+
...options?.bucket ? { bucket: options.bucket } : {},
|
|
437
|
+
...options?.metadata ? { metadata: options.metadata } : {},
|
|
438
|
+
contentHash
|
|
439
|
+
});
|
|
440
|
+
if (presign.dedupe) {
|
|
441
|
+
return {
|
|
442
|
+
key: presign.key,
|
|
443
|
+
url: presign.url,
|
|
444
|
+
size: presign.size,
|
|
445
|
+
contentType: presign.contentType,
|
|
446
|
+
deduped: true
|
|
447
|
+
};
|
|
218
448
|
}
|
|
219
|
-
|
|
220
|
-
|
|
449
|
+
const uploadRes = await fetch(presign.uploadUrl, {
|
|
450
|
+
method: "PUT",
|
|
451
|
+
headers: { "Content-Type": contentType },
|
|
452
|
+
body: new Uint8Array(buffer)
|
|
453
|
+
});
|
|
454
|
+
if (!uploadRes.ok) {
|
|
455
|
+
throw new StowError("Failed to upload to storage", uploadRes.status);
|
|
221
456
|
}
|
|
222
|
-
|
|
223
|
-
this.withBucket(
|
|
457
|
+
return this.request(
|
|
458
|
+
this.withBucket(
|
|
459
|
+
presign.confirmUrl || "/api/presign/confirm",
|
|
460
|
+
options?.bucket
|
|
461
|
+
),
|
|
224
462
|
{
|
|
225
463
|
method: "POST",
|
|
226
|
-
|
|
464
|
+
headers: { "Content-Type": "application/json" },
|
|
465
|
+
body: JSON.stringify({
|
|
466
|
+
fileKey: presign.fileKey,
|
|
467
|
+
size: buffer.length,
|
|
468
|
+
contentType,
|
|
469
|
+
...options?.metadata ? { metadata: options.metadata } : {},
|
|
470
|
+
contentHash,
|
|
471
|
+
skipVerify: true,
|
|
472
|
+
...options?.title ? { title: true } : {},
|
|
473
|
+
...options?.describe ? { describe: true } : {},
|
|
474
|
+
...options?.altText ? { altText: true } : {}
|
|
475
|
+
})
|
|
227
476
|
},
|
|
228
|
-
|
|
477
|
+
confirmResultSchema
|
|
229
478
|
);
|
|
230
|
-
return {
|
|
231
|
-
key: result.key,
|
|
232
|
-
url: result.url,
|
|
233
|
-
size: result.size,
|
|
234
|
-
contentType: result.contentType || options?.contentType || "application/octet-stream",
|
|
235
|
-
...result.metadata ? { metadata: result.metadata } : {}
|
|
236
|
-
};
|
|
237
479
|
}
|
|
238
480
|
/**
|
|
239
481
|
* Upload a file from a URL (server-side fetch + upload)
|
|
@@ -248,7 +490,10 @@ var StowServer = class {
|
|
|
248
490
|
url,
|
|
249
491
|
filename,
|
|
250
492
|
...options?.metadata ? { metadata: options.metadata } : {},
|
|
251
|
-
...options?.headers ? { headers: options.headers } : {}
|
|
493
|
+
...options?.headers ? { headers: options.headers } : {},
|
|
494
|
+
...options?.title ? { title: true } : {},
|
|
495
|
+
...options?.describe ? { describe: true } : {},
|
|
496
|
+
...options?.altText ? { altText: true } : {}
|
|
252
497
|
})
|
|
253
498
|
},
|
|
254
499
|
uploadResultSchema
|
|
@@ -271,7 +516,15 @@ var StowServer = class {
|
|
|
271
516
|
* 4. Client calls confirmUpload to finalize
|
|
272
517
|
*/
|
|
273
518
|
getPresignedUrl(request) {
|
|
274
|
-
const {
|
|
519
|
+
const {
|
|
520
|
+
filename,
|
|
521
|
+
contentType,
|
|
522
|
+
size,
|
|
523
|
+
route,
|
|
524
|
+
bucket,
|
|
525
|
+
metadata,
|
|
526
|
+
contentHash
|
|
527
|
+
} = request;
|
|
275
528
|
return this.request(
|
|
276
529
|
this.withBucket("/api/presign", bucket),
|
|
277
530
|
{
|
|
@@ -302,7 +555,10 @@ var StowServer = class {
|
|
|
302
555
|
metadata,
|
|
303
556
|
skipVerify,
|
|
304
557
|
deferKvSync,
|
|
305
|
-
contentHash
|
|
558
|
+
contentHash,
|
|
559
|
+
title,
|
|
560
|
+
describe,
|
|
561
|
+
altText
|
|
306
562
|
} = request;
|
|
307
563
|
return this.request(
|
|
308
564
|
this.withBucket("/api/presign/confirm", bucket),
|
|
@@ -316,7 +572,10 @@ var StowServer = class {
|
|
|
316
572
|
...metadata ? { metadata } : {},
|
|
317
573
|
...skipVerify ? { skipVerify } : {},
|
|
318
574
|
...deferKvSync ? { deferKvSync } : {},
|
|
319
|
-
...contentHash ? { contentHash } : {}
|
|
575
|
+
...contentHash ? { contentHash } : {},
|
|
576
|
+
...title ? { title } : {},
|
|
577
|
+
...describe ? { describe } : {},
|
|
578
|
+
...altText ? { altText } : {}
|
|
320
579
|
})
|
|
321
580
|
},
|
|
322
581
|
confirmResultSchema
|
|
@@ -358,7 +617,7 @@ var StowServer = class {
|
|
|
358
617
|
/**
|
|
359
618
|
* Update metadata on an existing file
|
|
360
619
|
*/
|
|
361
|
-
|
|
620
|
+
updateFileMetadata(key, metadata, options) {
|
|
362
621
|
const path = `/api/files/${encodeURIComponent(key)}`;
|
|
363
622
|
return this.request(this.withBucket(path, options?.bucket), {
|
|
364
623
|
method: "PATCH",
|
|
@@ -369,7 +628,7 @@ var StowServer = class {
|
|
|
369
628
|
/**
|
|
370
629
|
* Get a single file by key
|
|
371
630
|
*/
|
|
372
|
-
|
|
631
|
+
getFile(key, options) {
|
|
373
632
|
const path = `/api/files/${encodeURIComponent(key)}`;
|
|
374
633
|
return this.request(
|
|
375
634
|
this.withBucket(path, options?.bucket),
|
|
@@ -377,6 +636,60 @@ var StowServer = class {
|
|
|
377
636
|
fileResultSchema
|
|
378
637
|
);
|
|
379
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Extract color palette from an image file.
|
|
641
|
+
* Requires a searchable bucket.
|
|
642
|
+
*/
|
|
643
|
+
extractColors(key) {
|
|
644
|
+
return this.request(
|
|
645
|
+
`/api/files/${encodeURIComponent(key)}/extract-colors`,
|
|
646
|
+
{ method: "POST" },
|
|
647
|
+
taskTriggerResultSchema
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Extract dimensions (width/height) from an image or video file.
|
|
652
|
+
*/
|
|
653
|
+
extractDimensions(key) {
|
|
654
|
+
return this.request(
|
|
655
|
+
`/api/files/${encodeURIComponent(key)}/extract-dimensions`,
|
|
656
|
+
{ method: "POST" },
|
|
657
|
+
taskTriggerResultSchema
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Generate a vector embedding for an image file.
|
|
662
|
+
* Requires a searchable bucket.
|
|
663
|
+
*/
|
|
664
|
+
embed(key) {
|
|
665
|
+
return this.request(
|
|
666
|
+
`/api/files/${encodeURIComponent(key)}/embed`,
|
|
667
|
+
{ method: "POST" },
|
|
668
|
+
taskTriggerResultSchema
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Replace a file's content by fetching from a new URL.
|
|
673
|
+
*
|
|
674
|
+
* Keeps the same file key but replaces the stored object and resets all
|
|
675
|
+
* derived data (dimensions, embeddings, colors, AI metadata). Processing
|
|
676
|
+
* tasks are re-dispatched as if the file were newly uploaded.
|
|
677
|
+
*/
|
|
678
|
+
replaceFile(key, url, options) {
|
|
679
|
+
const path = `/api/files/${encodeURIComponent(key)}/replace`;
|
|
680
|
+
return this.request(
|
|
681
|
+
this.withBucket(path, options?.bucket),
|
|
682
|
+
{
|
|
683
|
+
method: "PUT",
|
|
684
|
+
headers: { "Content-Type": "application/json" },
|
|
685
|
+
body: JSON.stringify({
|
|
686
|
+
url,
|
|
687
|
+
...options?.headers ? { headers: options.headers } : {}
|
|
688
|
+
})
|
|
689
|
+
},
|
|
690
|
+
replaceResultSchema
|
|
691
|
+
);
|
|
692
|
+
}
|
|
380
693
|
/**
|
|
381
694
|
* Get a transform URL for an image.
|
|
382
695
|
*
|
|
@@ -388,25 +701,23 @@ var StowServer = class {
|
|
|
388
701
|
* @param options - Transform options (width, height, quality, format)
|
|
389
702
|
*/
|
|
390
703
|
getTransformUrl(url, options) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
params.set("w", options.width.toString());
|
|
704
|
+
if (!(options && (options.width || options.height || options.quality || options.format))) {
|
|
705
|
+
return url;
|
|
394
706
|
}
|
|
395
|
-
|
|
396
|
-
|
|
707
|
+
const parsed = new URL(url);
|
|
708
|
+
if (options.width) {
|
|
709
|
+
parsed.searchParams.set("w", String(options.width));
|
|
397
710
|
}
|
|
398
|
-
if (options
|
|
399
|
-
|
|
711
|
+
if (options.height) {
|
|
712
|
+
parsed.searchParams.set("h", String(options.height));
|
|
400
713
|
}
|
|
401
|
-
if (options
|
|
402
|
-
|
|
714
|
+
if (options.quality) {
|
|
715
|
+
parsed.searchParams.set("q", String(options.quality));
|
|
403
716
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return url;
|
|
717
|
+
if (options.format) {
|
|
718
|
+
parsed.searchParams.set("f", options.format);
|
|
407
719
|
}
|
|
408
|
-
|
|
409
|
-
return `${url}${separator}${query}`;
|
|
720
|
+
return parsed.toString();
|
|
410
721
|
}
|
|
411
722
|
// ============================================================
|
|
412
723
|
// TAGS - Org-scoped labels for file organization
|
|
@@ -468,10 +779,13 @@ var StowServer = class {
|
|
|
468
779
|
get search() {
|
|
469
780
|
return {
|
|
470
781
|
similar: (params) => this.searchSimilar(params),
|
|
471
|
-
|
|
782
|
+
diverse: (params) => this.searchDiverse(params ?? {}),
|
|
783
|
+
text: (params) => this.searchText(params),
|
|
784
|
+
color: (params) => this.searchColor(params)
|
|
472
785
|
};
|
|
473
786
|
}
|
|
474
787
|
searchSimilar(params) {
|
|
788
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
475
789
|
return this.request("/api/search/similar", {
|
|
476
790
|
method: "POST",
|
|
477
791
|
headers: { "Content-Type": "application/json" },
|
|
@@ -479,19 +793,62 @@ var StowServer = class {
|
|
|
479
793
|
...params.fileKey ? { fileKey: params.fileKey } : {},
|
|
480
794
|
...params.vector ? { vector: params.vector } : {},
|
|
481
795
|
...params.profileId ? { profileId: params.profileId } : {},
|
|
482
|
-
...params.
|
|
483
|
-
...params.
|
|
796
|
+
...params.clusterId ? { clusterId: params.clusterId } : {},
|
|
797
|
+
...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
|
|
798
|
+
...bucket ? { bucket } : {},
|
|
799
|
+
...params.limit ? { limit: params.limit } : {},
|
|
800
|
+
...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
|
|
801
|
+
...params.filters ? { filters: params.filters } : {},
|
|
802
|
+
...params.include?.length ? { include: params.include } : {}
|
|
803
|
+
})
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
searchDiverse(params) {
|
|
807
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
808
|
+
return this.request("/api/search/diverse", {
|
|
809
|
+
method: "POST",
|
|
810
|
+
headers: { "Content-Type": "application/json" },
|
|
811
|
+
body: JSON.stringify({
|
|
812
|
+
...params.fileKey ? { fileKey: params.fileKey } : {},
|
|
813
|
+
...params.vector ? { vector: params.vector } : {},
|
|
814
|
+
...params.profileId ? { profileId: params.profileId } : {},
|
|
815
|
+
...params.clusterId ? { clusterId: params.clusterId } : {},
|
|
816
|
+
...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
|
|
817
|
+
...bucket ? { bucket } : {},
|
|
818
|
+
...params.limit ? { limit: params.limit } : {},
|
|
819
|
+
...params.lambda !== void 0 ? { lambda: params.lambda } : {},
|
|
820
|
+
...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
|
|
821
|
+
...params.filters ? { filters: params.filters } : {},
|
|
822
|
+
...params.include?.length ? { include: params.include } : {}
|
|
484
823
|
})
|
|
485
824
|
});
|
|
486
825
|
}
|
|
487
826
|
searchText(params) {
|
|
827
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
488
828
|
return this.request("/api/search/text", {
|
|
489
829
|
method: "POST",
|
|
490
830
|
headers: { "Content-Type": "application/json" },
|
|
491
831
|
body: JSON.stringify({
|
|
492
832
|
query: params.query,
|
|
493
|
-
...
|
|
494
|
-
...params.limit ? { limit: params.limit } : {}
|
|
833
|
+
...bucket ? { bucket } : {},
|
|
834
|
+
...params.limit ? { limit: params.limit } : {},
|
|
835
|
+
...params.filters ? { filters: params.filters } : {},
|
|
836
|
+
...params.include?.length ? { include: params.include } : {}
|
|
837
|
+
})
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
searchColor(params) {
|
|
841
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
842
|
+
return this.request("/api/search/color", {
|
|
843
|
+
method: "POST",
|
|
844
|
+
headers: { "Content-Type": "application/json" },
|
|
845
|
+
body: JSON.stringify({
|
|
846
|
+
...params.hex ? { hex: params.hex } : {},
|
|
847
|
+
...params.oklab ? { oklab: params.oklab } : {},
|
|
848
|
+
...bucket ? { bucket } : {},
|
|
849
|
+
...params.limit ? { limit: params.limit } : {},
|
|
850
|
+
...params.minProportion !== void 0 ? { minProportion: params.minProportion } : {},
|
|
851
|
+
...params.dominantOnly ? { dominantOnly: params.dominantOnly } : {}
|
|
495
852
|
})
|
|
496
853
|
});
|
|
497
854
|
}
|
|
@@ -532,7 +889,7 @@ var StowServer = class {
|
|
|
532
889
|
throw new StowError("Failed to upload to storage", putRes.status);
|
|
533
890
|
}
|
|
534
891
|
return this.request(
|
|
535
|
-
"/api/drops/presign/confirm",
|
|
892
|
+
presign.confirmUrl || "/api/drops/presign/confirm",
|
|
536
893
|
{
|
|
537
894
|
method: "POST",
|
|
538
895
|
headers: { "Content-Type": "application/json" },
|
|
@@ -574,7 +931,12 @@ var StowServer = class {
|
|
|
574
931
|
get: (id) => this.getProfile(id),
|
|
575
932
|
delete: (id) => this.deleteProfile(id),
|
|
576
933
|
addFiles: (id, fileKeys, bucket) => this.addProfileFiles(id, fileKeys, bucket),
|
|
577
|
-
removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket)
|
|
934
|
+
removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket),
|
|
935
|
+
signal: (id, signals, bucket) => this.signalProfile(id, signals, bucket),
|
|
936
|
+
deleteSignals: (id, signalIds) => this.deleteProfileSignals(id, signalIds),
|
|
937
|
+
clusters: (id) => this.getProfileClusters(id),
|
|
938
|
+
recluster: (id, params) => this.reclusterProfile(id, params),
|
|
939
|
+
renameCluster: (profileId, clusterId, params) => this.renameProfileCluster(profileId, clusterId, params)
|
|
578
940
|
};
|
|
579
941
|
}
|
|
580
942
|
createProfile(params) {
|
|
@@ -632,6 +994,55 @@ var StowServer = class {
|
|
|
632
994
|
profileFilesResultSchema
|
|
633
995
|
);
|
|
634
996
|
}
|
|
997
|
+
signalProfile(id, signals, bucket) {
|
|
998
|
+
return this.request(
|
|
999
|
+
`/api/profiles/${encodeURIComponent(id)}/signals`,
|
|
1000
|
+
{
|
|
1001
|
+
method: "POST",
|
|
1002
|
+
headers: { "Content-Type": "application/json" },
|
|
1003
|
+
body: JSON.stringify({
|
|
1004
|
+
signals,
|
|
1005
|
+
...bucket ? { bucket } : {}
|
|
1006
|
+
})
|
|
1007
|
+
},
|
|
1008
|
+
profileSignalsResponseSchema
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
deleteProfileSignals(id, signalIds) {
|
|
1012
|
+
return this.request(
|
|
1013
|
+
`/api/profiles/${encodeURIComponent(id)}/signals`,
|
|
1014
|
+
{
|
|
1015
|
+
method: "DELETE",
|
|
1016
|
+
headers: { "Content-Type": "application/json" },
|
|
1017
|
+
body: JSON.stringify({ signalIds })
|
|
1018
|
+
},
|
|
1019
|
+
deleteProfileSignalsResponseSchema
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
getProfileClusters(id) {
|
|
1023
|
+
return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
|
|
1024
|
+
method: "GET"
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
reclusterProfile(id, params) {
|
|
1028
|
+
return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
|
|
1029
|
+
method: "POST",
|
|
1030
|
+
headers: { "Content-Type": "application/json" },
|
|
1031
|
+
body: JSON.stringify({
|
|
1032
|
+
...params?.clusterCount !== void 0 ? { clusterCount: params.clusterCount } : {}
|
|
1033
|
+
})
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
renameProfileCluster(profileId, clusterId, params) {
|
|
1037
|
+
return this.request(
|
|
1038
|
+
`/api/profiles/${encodeURIComponent(profileId)}/clusters/${encodeURIComponent(clusterId)}`,
|
|
1039
|
+
{
|
|
1040
|
+
method: "PUT",
|
|
1041
|
+
headers: { "Content-Type": "application/json" },
|
|
1042
|
+
body: JSON.stringify(params)
|
|
1043
|
+
}
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
635
1046
|
};
|
|
636
1047
|
export {
|
|
637
1048
|
StowError,
|