@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/LICENSE +21 -0
- package/dist/index.d.mts +421 -40
- package/dist/index.d.ts +421 -40
- package/dist/index.js +441 -50
- package/dist/index.mjs +441 -50
- package/package.json +11 -11
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 reprocessResultSchema = z.object({
|
|
101
|
+
key: z.string(),
|
|
102
|
+
triggered: z.array(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,40 @@ var StowServer = class {
|
|
|
377
636
|
fileResultSchema
|
|
378
637
|
);
|
|
379
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Reprocess a file: reset all derived data (embeddings, colors, dimensions,
|
|
641
|
+
* AI metadata, taxonomies) and re-trigger processing tasks.
|
|
642
|
+
*/
|
|
643
|
+
reprocessFile(key, options) {
|
|
644
|
+
const path = `/api/files/${encodeURIComponent(key)}/reprocess`;
|
|
645
|
+
return this.request(
|
|
646
|
+
this.withBucket(path, options?.bucket),
|
|
647
|
+
{ method: "POST" },
|
|
648
|
+
reprocessResultSchema
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Replace a file's content by fetching from a new URL.
|
|
653
|
+
*
|
|
654
|
+
* Keeps the same file key but replaces the stored object and resets all
|
|
655
|
+
* derived data (dimensions, embeddings, colors, AI metadata). Processing
|
|
656
|
+
* tasks are re-dispatched as if the file were newly uploaded.
|
|
657
|
+
*/
|
|
658
|
+
replaceFile(key, url, options) {
|
|
659
|
+
const path = `/api/files/${encodeURIComponent(key)}/replace`;
|
|
660
|
+
return this.request(
|
|
661
|
+
this.withBucket(path, options?.bucket),
|
|
662
|
+
{
|
|
663
|
+
method: "PUT",
|
|
664
|
+
headers: { "Content-Type": "application/json" },
|
|
665
|
+
body: JSON.stringify({
|
|
666
|
+
url,
|
|
667
|
+
...options?.headers ? { headers: options.headers } : {}
|
|
668
|
+
})
|
|
669
|
+
},
|
|
670
|
+
replaceResultSchema
|
|
671
|
+
);
|
|
672
|
+
}
|
|
380
673
|
/**
|
|
381
674
|
* Get a transform URL for an image.
|
|
382
675
|
*
|
|
@@ -388,25 +681,23 @@ var StowServer = class {
|
|
|
388
681
|
* @param options - Transform options (width, height, quality, format)
|
|
389
682
|
*/
|
|
390
683
|
getTransformUrl(url, options) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
params.set("w", options.width.toString());
|
|
684
|
+
if (!(options && (options.width || options.height || options.quality || options.format))) {
|
|
685
|
+
return url;
|
|
394
686
|
}
|
|
395
|
-
|
|
396
|
-
|
|
687
|
+
const parsed = new URL(url);
|
|
688
|
+
if (options.width) {
|
|
689
|
+
parsed.searchParams.set("w", String(options.width));
|
|
397
690
|
}
|
|
398
|
-
if (options
|
|
399
|
-
|
|
691
|
+
if (options.height) {
|
|
692
|
+
parsed.searchParams.set("h", String(options.height));
|
|
400
693
|
}
|
|
401
|
-
if (options
|
|
402
|
-
|
|
694
|
+
if (options.quality) {
|
|
695
|
+
parsed.searchParams.set("q", String(options.quality));
|
|
403
696
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return url;
|
|
697
|
+
if (options.format) {
|
|
698
|
+
parsed.searchParams.set("f", options.format);
|
|
407
699
|
}
|
|
408
|
-
|
|
409
|
-
return `${url}${separator}${query}`;
|
|
700
|
+
return parsed.toString();
|
|
410
701
|
}
|
|
411
702
|
// ============================================================
|
|
412
703
|
// TAGS - Org-scoped labels for file organization
|
|
@@ -468,10 +759,13 @@ var StowServer = class {
|
|
|
468
759
|
get search() {
|
|
469
760
|
return {
|
|
470
761
|
similar: (params) => this.searchSimilar(params),
|
|
471
|
-
|
|
762
|
+
diverse: (params) => this.searchDiverse(params ?? {}),
|
|
763
|
+
text: (params) => this.searchText(params),
|
|
764
|
+
color: (params) => this.searchColor(params)
|
|
472
765
|
};
|
|
473
766
|
}
|
|
474
767
|
searchSimilar(params) {
|
|
768
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
475
769
|
return this.request("/api/search/similar", {
|
|
476
770
|
method: "POST",
|
|
477
771
|
headers: { "Content-Type": "application/json" },
|
|
@@ -479,19 +773,62 @@ var StowServer = class {
|
|
|
479
773
|
...params.fileKey ? { fileKey: params.fileKey } : {},
|
|
480
774
|
...params.vector ? { vector: params.vector } : {},
|
|
481
775
|
...params.profileId ? { profileId: params.profileId } : {},
|
|
482
|
-
...params.
|
|
483
|
-
...params.
|
|
776
|
+
...params.clusterId ? { clusterId: params.clusterId } : {},
|
|
777
|
+
...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
|
|
778
|
+
...bucket ? { bucket } : {},
|
|
779
|
+
...params.limit ? { limit: params.limit } : {},
|
|
780
|
+
...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
|
|
781
|
+
...params.filters ? { filters: params.filters } : {},
|
|
782
|
+
...params.include?.length ? { include: params.include } : {}
|
|
783
|
+
})
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
searchDiverse(params) {
|
|
787
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
788
|
+
return this.request("/api/search/diverse", {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: { "Content-Type": "application/json" },
|
|
791
|
+
body: JSON.stringify({
|
|
792
|
+
...params.fileKey ? { fileKey: params.fileKey } : {},
|
|
793
|
+
...params.vector ? { vector: params.vector } : {},
|
|
794
|
+
...params.profileId ? { profileId: params.profileId } : {},
|
|
795
|
+
...params.clusterId ? { clusterId: params.clusterId } : {},
|
|
796
|
+
...params.clusterIds?.length ? { clusterIds: params.clusterIds } : {},
|
|
797
|
+
...bucket ? { bucket } : {},
|
|
798
|
+
...params.limit ? { limit: params.limit } : {},
|
|
799
|
+
...params.lambda !== void 0 ? { lambda: params.lambda } : {},
|
|
800
|
+
...params.excludeKeys?.length ? { excludeKeys: params.excludeKeys } : {},
|
|
801
|
+
...params.filters ? { filters: params.filters } : {},
|
|
802
|
+
...params.include?.length ? { include: params.include } : {}
|
|
484
803
|
})
|
|
485
804
|
});
|
|
486
805
|
}
|
|
487
806
|
searchText(params) {
|
|
807
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
488
808
|
return this.request("/api/search/text", {
|
|
489
809
|
method: "POST",
|
|
490
810
|
headers: { "Content-Type": "application/json" },
|
|
491
811
|
body: JSON.stringify({
|
|
492
812
|
query: params.query,
|
|
493
|
-
...
|
|
494
|
-
...params.limit ? { limit: params.limit } : {}
|
|
813
|
+
...bucket ? { bucket } : {},
|
|
814
|
+
...params.limit ? { limit: params.limit } : {},
|
|
815
|
+
...params.filters ? { filters: params.filters } : {},
|
|
816
|
+
...params.include?.length ? { include: params.include } : {}
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
searchColor(params) {
|
|
821
|
+
const bucket = this.resolveBucket(params.bucket);
|
|
822
|
+
return this.request("/api/search/color", {
|
|
823
|
+
method: "POST",
|
|
824
|
+
headers: { "Content-Type": "application/json" },
|
|
825
|
+
body: JSON.stringify({
|
|
826
|
+
...params.hex ? { hex: params.hex } : {},
|
|
827
|
+
...params.oklab ? { oklab: params.oklab } : {},
|
|
828
|
+
...bucket ? { bucket } : {},
|
|
829
|
+
...params.limit ? { limit: params.limit } : {},
|
|
830
|
+
...params.minProportion !== void 0 ? { minProportion: params.minProportion } : {},
|
|
831
|
+
...params.dominantOnly ? { dominantOnly: params.dominantOnly } : {}
|
|
495
832
|
})
|
|
496
833
|
});
|
|
497
834
|
}
|
|
@@ -532,7 +869,7 @@ var StowServer = class {
|
|
|
532
869
|
throw new StowError("Failed to upload to storage", putRes.status);
|
|
533
870
|
}
|
|
534
871
|
return this.request(
|
|
535
|
-
"/api/drops/presign/confirm",
|
|
872
|
+
presign.confirmUrl || "/api/drops/presign/confirm",
|
|
536
873
|
{
|
|
537
874
|
method: "POST",
|
|
538
875
|
headers: { "Content-Type": "application/json" },
|
|
@@ -574,7 +911,12 @@ var StowServer = class {
|
|
|
574
911
|
get: (id) => this.getProfile(id),
|
|
575
912
|
delete: (id) => this.deleteProfile(id),
|
|
576
913
|
addFiles: (id, fileKeys, bucket) => this.addProfileFiles(id, fileKeys, bucket),
|
|
577
|
-
removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket)
|
|
914
|
+
removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket),
|
|
915
|
+
signal: (id, signals, bucket) => this.signalProfile(id, signals, bucket),
|
|
916
|
+
deleteSignals: (id, signalIds) => this.deleteProfileSignals(id, signalIds),
|
|
917
|
+
clusters: (id) => this.getProfileClusters(id),
|
|
918
|
+
recluster: (id, params) => this.reclusterProfile(id, params),
|
|
919
|
+
renameCluster: (profileId, clusterId, params) => this.renameProfileCluster(profileId, clusterId, params)
|
|
578
920
|
};
|
|
579
921
|
}
|
|
580
922
|
createProfile(params) {
|
|
@@ -632,6 +974,55 @@ var StowServer = class {
|
|
|
632
974
|
profileFilesResultSchema
|
|
633
975
|
);
|
|
634
976
|
}
|
|
977
|
+
signalProfile(id, signals, bucket) {
|
|
978
|
+
return this.request(
|
|
979
|
+
`/api/profiles/${encodeURIComponent(id)}/signals`,
|
|
980
|
+
{
|
|
981
|
+
method: "POST",
|
|
982
|
+
headers: { "Content-Type": "application/json" },
|
|
983
|
+
body: JSON.stringify({
|
|
984
|
+
signals,
|
|
985
|
+
...bucket ? { bucket } : {}
|
|
986
|
+
})
|
|
987
|
+
},
|
|
988
|
+
profileSignalsResponseSchema
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
deleteProfileSignals(id, signalIds) {
|
|
992
|
+
return this.request(
|
|
993
|
+
`/api/profiles/${encodeURIComponent(id)}/signals`,
|
|
994
|
+
{
|
|
995
|
+
method: "DELETE",
|
|
996
|
+
headers: { "Content-Type": "application/json" },
|
|
997
|
+
body: JSON.stringify({ signalIds })
|
|
998
|
+
},
|
|
999
|
+
deleteProfileSignalsResponseSchema
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
getProfileClusters(id) {
|
|
1003
|
+
return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
|
|
1004
|
+
method: "GET"
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
reclusterProfile(id, params) {
|
|
1008
|
+
return this.request(`/api/profiles/${encodeURIComponent(id)}/clusters`, {
|
|
1009
|
+
method: "POST",
|
|
1010
|
+
headers: { "Content-Type": "application/json" },
|
|
1011
|
+
body: JSON.stringify({
|
|
1012
|
+
...params?.clusterCount !== void 0 ? { clusterCount: params.clusterCount } : {}
|
|
1013
|
+
})
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
renameProfileCluster(profileId, clusterId, params) {
|
|
1017
|
+
return this.request(
|
|
1018
|
+
`/api/profiles/${encodeURIComponent(profileId)}/clusters/${encodeURIComponent(clusterId)}`,
|
|
1019
|
+
{
|
|
1020
|
+
method: "PUT",
|
|
1021
|
+
headers: { "Content-Type": "application/json" },
|
|
1022
|
+
body: JSON.stringify(params)
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
635
1026
|
};
|
|
636
1027
|
export {
|
|
637
1028
|
StowError,
|