@howells/stow-server 0.1.1 → 0.1.2
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 +119 -31
- package/dist/index.d.ts +119 -31
- package/dist/index.js +210 -22
- package/dist/index.mjs +210 -22
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -31,19 +31,23 @@ interface StowServerConfig {
|
|
|
31
31
|
baseUrl?: string;
|
|
32
32
|
/** Default bucket name or ID. Required for global API keys. */
|
|
33
33
|
bucket?: string;
|
|
34
|
+
/** Max retry attempts for 429/5xx errors (default 3, set 0 to disable) */
|
|
35
|
+
retries?: number;
|
|
36
|
+
/** Request timeout in ms (default 30000) */
|
|
37
|
+
timeout?: number;
|
|
34
38
|
}
|
|
35
39
|
interface UploadResult {
|
|
36
|
-
key: string;
|
|
37
|
-
url: string | null;
|
|
38
|
-
size: number;
|
|
39
40
|
contentType: string;
|
|
41
|
+
key: string;
|
|
40
42
|
metadata?: Record<string, string>;
|
|
43
|
+
size: number;
|
|
44
|
+
url: string | null;
|
|
41
45
|
}
|
|
42
46
|
interface TransformOptions {
|
|
43
|
-
|
|
47
|
+
format?: "webp" | "avif" | "jpeg" | "png";
|
|
44
48
|
height?: number;
|
|
45
49
|
quality?: number;
|
|
46
|
-
|
|
50
|
+
width?: number;
|
|
47
51
|
}
|
|
48
52
|
interface ListFilesResult {
|
|
49
53
|
files: Array<{
|
|
@@ -56,20 +60,20 @@ interface ListFilesResult {
|
|
|
56
60
|
nextCursor: string | null;
|
|
57
61
|
}
|
|
58
62
|
interface DropResult {
|
|
59
|
-
|
|
60
|
-
url: string;
|
|
63
|
+
contentType: string;
|
|
61
64
|
filename: string;
|
|
65
|
+
shortId: string;
|
|
62
66
|
size: number;
|
|
63
|
-
|
|
67
|
+
url: string;
|
|
64
68
|
}
|
|
65
69
|
interface Drop {
|
|
70
|
+
contentType: string;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
filename: string;
|
|
66
73
|
id: string;
|
|
67
74
|
shortId: string;
|
|
68
|
-
url: string;
|
|
69
|
-
filename: string;
|
|
70
75
|
size: number;
|
|
71
|
-
|
|
72
|
-
createdAt: string;
|
|
76
|
+
url: string;
|
|
73
77
|
}
|
|
74
78
|
interface ListDropsResult {
|
|
75
79
|
drops: Drop[];
|
|
@@ -78,46 +82,68 @@ interface ListDropsResult {
|
|
|
78
82
|
limit: number;
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
|
-
|
|
85
|
+
/** Normal presign response — client must PUT to uploadUrl then call confirm. */
|
|
86
|
+
interface PresignNewResult {
|
|
87
|
+
/** URL to call to confirm the upload */
|
|
88
|
+
confirmUrl: string;
|
|
89
|
+
dedupe?: false;
|
|
82
90
|
/** The file key that will be created */
|
|
83
91
|
fileKey: string;
|
|
84
|
-
/** URL to PUT the file to */
|
|
85
|
-
uploadUrl: string;
|
|
86
92
|
/** The R2 key for the file */
|
|
87
93
|
r2Key: string;
|
|
88
|
-
/** URL to
|
|
89
|
-
|
|
94
|
+
/** URL to PUT the file to */
|
|
95
|
+
uploadUrl: string;
|
|
90
96
|
}
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
/** Dedup hit — file already exists in this bucket. No upload needed. */
|
|
98
|
+
interface PresignDedupeResult {
|
|
93
99
|
contentType: string;
|
|
94
|
-
|
|
100
|
+
dedupe: true;
|
|
101
|
+
key: string;
|
|
95
102
|
size: number;
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
url: string | null;
|
|
104
|
+
}
|
|
105
|
+
/** Presign response is either a new upload or a dedup hit. Check `result.dedupe` to differentiate. */
|
|
106
|
+
type PresignResult = PresignNewResult | PresignDedupeResult;
|
|
107
|
+
interface PresignRequest {
|
|
98
108
|
/** Override the default bucket */
|
|
99
109
|
bucket?: string;
|
|
110
|
+
/** SHA-256 hex digest of file content. When provided, Stow checks for an existing file with the same hash in the bucket and returns it immediately (dedup). */
|
|
111
|
+
contentHash?: string;
|
|
112
|
+
contentType: string;
|
|
113
|
+
filename: string;
|
|
100
114
|
/** Custom metadata to attach to the file */
|
|
101
115
|
metadata?: Record<string, string>;
|
|
116
|
+
/** Optional route/folder */
|
|
117
|
+
route?: string;
|
|
118
|
+
/** File size in bytes */
|
|
119
|
+
size: number;
|
|
102
120
|
}
|
|
103
121
|
interface ConfirmUploadRequest {
|
|
104
|
-
fileKey: string;
|
|
105
|
-
size: number;
|
|
106
|
-
contentType: string;
|
|
107
122
|
/** Override the default bucket */
|
|
108
123
|
bucket?: string;
|
|
124
|
+
/** SHA-256 hex digest of file content. Stored with the file record for future dedup lookups. */
|
|
125
|
+
contentHash?: string;
|
|
126
|
+
contentType: string;
|
|
127
|
+
/** Fire KV sync in background instead of blocking. Saves ~100ms per upload. */
|
|
128
|
+
deferKvSync?: boolean;
|
|
129
|
+
fileKey: string;
|
|
109
130
|
/** Custom metadata to attach to the file */
|
|
110
131
|
metadata?: Record<string, string>;
|
|
132
|
+
size: number;
|
|
133
|
+
/** Skip R2 HEAD verification — trust the presign PUT. Saves ~100ms per upload. */
|
|
134
|
+
skipVerify?: boolean;
|
|
111
135
|
}
|
|
112
136
|
interface SimilarSearchRequest {
|
|
113
|
-
/** Find files similar to this file key */
|
|
114
|
-
fileKey?: string;
|
|
115
|
-
/** Search directly with a vector (1024 dimensions) */
|
|
116
|
-
vector?: number[];
|
|
117
137
|
/** Bucket name or ID to scope search */
|
|
118
138
|
bucket?: string;
|
|
139
|
+
/** Find files similar to this file key */
|
|
140
|
+
fileKey?: string;
|
|
119
141
|
/** Max results (default 10, max 50) */
|
|
120
142
|
limit?: number;
|
|
143
|
+
/** Search using a taste profile's vector */
|
|
144
|
+
profileId?: string;
|
|
145
|
+
/** Search directly with a vector (1024 dimensions) */
|
|
146
|
+
vector?: number[];
|
|
121
147
|
}
|
|
122
148
|
interface SimilarSearchResult {
|
|
123
149
|
results: Array<{
|
|
@@ -133,10 +159,42 @@ interface SimilarSearchResult {
|
|
|
133
159
|
createdAt: string;
|
|
134
160
|
}>;
|
|
135
161
|
}
|
|
162
|
+
interface FileResult {
|
|
163
|
+
contentType: string;
|
|
164
|
+
createdAt: string;
|
|
165
|
+
embeddingStatus: string | null;
|
|
166
|
+
key: string;
|
|
167
|
+
metadata: Record<string, string> | null;
|
|
168
|
+
size: number;
|
|
169
|
+
url: string | null;
|
|
170
|
+
}
|
|
171
|
+
interface ProfileCreateRequest {
|
|
172
|
+
bucket?: string;
|
|
173
|
+
fileKeys?: string[];
|
|
174
|
+
name?: string;
|
|
175
|
+
}
|
|
176
|
+
interface ProfileResult {
|
|
177
|
+
createdAt: string;
|
|
178
|
+
fileCount: number;
|
|
179
|
+
id: string;
|
|
180
|
+
name: string | null;
|
|
181
|
+
updatedAt: string;
|
|
182
|
+
}
|
|
183
|
+
interface ProfileFilesResult {
|
|
184
|
+
fileCount: number;
|
|
185
|
+
id: string;
|
|
186
|
+
}
|
|
187
|
+
interface TextSearchRequest {
|
|
188
|
+
bucket?: string;
|
|
189
|
+
limit?: number;
|
|
190
|
+
query: string;
|
|
191
|
+
}
|
|
136
192
|
declare class StowServer {
|
|
137
193
|
private readonly apiKey;
|
|
138
194
|
private readonly baseUrl;
|
|
139
195
|
private readonly bucket?;
|
|
196
|
+
private readonly timeout;
|
|
197
|
+
private readonly retries;
|
|
140
198
|
constructor(config: StowServerConfig | string);
|
|
141
199
|
/**
|
|
142
200
|
* Get the base URL for this instance (used by client SDK)
|
|
@@ -152,9 +210,14 @@ declare class StowServer {
|
|
|
152
210
|
*/
|
|
153
211
|
private withBucket;
|
|
154
212
|
/**
|
|
155
|
-
* Make an API request with
|
|
213
|
+
* Make an API request with retry, timeout, and error handling.
|
|
214
|
+
*
|
|
215
|
+
* - Retries on 429 (rate limit) and 5xx with exponential backoff (1s, 2s, 4s).
|
|
216
|
+
* - AbortController timeout (default 30s).
|
|
217
|
+
* - Consumer can pass `signal` in options to cancel.
|
|
156
218
|
*/
|
|
157
219
|
private request;
|
|
220
|
+
private sleep;
|
|
158
221
|
/**
|
|
159
222
|
* Upload a file directly from the server
|
|
160
223
|
*/
|
|
@@ -173,6 +236,8 @@ declare class StowServer {
|
|
|
173
236
|
uploadFromUrl(url: string, filename: string, options?: {
|
|
174
237
|
bucket?: string;
|
|
175
238
|
metadata?: Record<string, string>;
|
|
239
|
+
/** Headers to forward when fetching the URL (e.g. User-Agent, Referer) */
|
|
240
|
+
headers?: Record<string, string>;
|
|
176
241
|
}): Promise<UploadResult>;
|
|
177
242
|
/**
|
|
178
243
|
* Get a presigned URL for direct client-side upload.
|
|
@@ -215,6 +280,12 @@ declare class StowServer {
|
|
|
215
280
|
key: string;
|
|
216
281
|
metadata: Record<string, string>;
|
|
217
282
|
}>;
|
|
283
|
+
/**
|
|
284
|
+
* Get a single file by key
|
|
285
|
+
*/
|
|
286
|
+
getFile(key: string, options?: {
|
|
287
|
+
bucket?: string;
|
|
288
|
+
}): Promise<FileResult>;
|
|
218
289
|
/**
|
|
219
290
|
* Get a transform URL for an image.
|
|
220
291
|
*
|
|
@@ -271,8 +342,10 @@ declare class StowServer {
|
|
|
271
342
|
*/
|
|
272
343
|
get search(): {
|
|
273
344
|
similar: (params: SimilarSearchRequest) => Promise<SimilarSearchResult>;
|
|
345
|
+
text: (params: TextSearchRequest) => Promise<SimilarSearchResult>;
|
|
274
346
|
};
|
|
275
347
|
private searchSimilar;
|
|
348
|
+
private searchText;
|
|
276
349
|
/**
|
|
277
350
|
* Upload a file as a drop (quick share)
|
|
278
351
|
*
|
|
@@ -297,6 +370,21 @@ declare class StowServer {
|
|
|
297
370
|
* Delete a drop by ID
|
|
298
371
|
*/
|
|
299
372
|
deleteDrop(id: string): Promise<void>;
|
|
373
|
+
/**
|
|
374
|
+
* Profiles namespace for managing taste profiles
|
|
375
|
+
*/
|
|
376
|
+
get profiles(): {
|
|
377
|
+
create: (params?: ProfileCreateRequest) => Promise<ProfileResult>;
|
|
378
|
+
get: (id: string) => Promise<ProfileResult>;
|
|
379
|
+
delete: (id: string) => Promise<void>;
|
|
380
|
+
addFiles: (id: string, fileKeys: string[], bucket?: string) => Promise<ProfileFilesResult>;
|
|
381
|
+
removeFiles: (id: string, fileKeys: string[], bucket?: string) => Promise<ProfileFilesResult>;
|
|
382
|
+
};
|
|
383
|
+
private createProfile;
|
|
384
|
+
private getProfile;
|
|
385
|
+
private deleteProfile;
|
|
386
|
+
private addProfileFiles;
|
|
387
|
+
private removeProfileFiles;
|
|
300
388
|
}
|
|
301
389
|
|
|
302
|
-
export { type ConfirmUploadRequest, type Drop, type DropResult, type ListDropsResult, type ListFilesResult, type PresignRequest, type PresignResult, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TransformOptions, type UploadResult };
|
|
390
|
+
export { type ConfirmUploadRequest, type Drop, type DropResult, type FileResult, type ListDropsResult, type ListFilesResult, type PresignDedupeResult, type PresignNewResult, type PresignRequest, type PresignResult, type ProfileCreateRequest, type ProfileFilesResult, type ProfileResult, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TextSearchRequest, type TransformOptions, type UploadResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -31,19 +31,23 @@ interface StowServerConfig {
|
|
|
31
31
|
baseUrl?: string;
|
|
32
32
|
/** Default bucket name or ID. Required for global API keys. */
|
|
33
33
|
bucket?: string;
|
|
34
|
+
/** Max retry attempts for 429/5xx errors (default 3, set 0 to disable) */
|
|
35
|
+
retries?: number;
|
|
36
|
+
/** Request timeout in ms (default 30000) */
|
|
37
|
+
timeout?: number;
|
|
34
38
|
}
|
|
35
39
|
interface UploadResult {
|
|
36
|
-
key: string;
|
|
37
|
-
url: string | null;
|
|
38
|
-
size: number;
|
|
39
40
|
contentType: string;
|
|
41
|
+
key: string;
|
|
40
42
|
metadata?: Record<string, string>;
|
|
43
|
+
size: number;
|
|
44
|
+
url: string | null;
|
|
41
45
|
}
|
|
42
46
|
interface TransformOptions {
|
|
43
|
-
|
|
47
|
+
format?: "webp" | "avif" | "jpeg" | "png";
|
|
44
48
|
height?: number;
|
|
45
49
|
quality?: number;
|
|
46
|
-
|
|
50
|
+
width?: number;
|
|
47
51
|
}
|
|
48
52
|
interface ListFilesResult {
|
|
49
53
|
files: Array<{
|
|
@@ -56,20 +60,20 @@ interface ListFilesResult {
|
|
|
56
60
|
nextCursor: string | null;
|
|
57
61
|
}
|
|
58
62
|
interface DropResult {
|
|
59
|
-
|
|
60
|
-
url: string;
|
|
63
|
+
contentType: string;
|
|
61
64
|
filename: string;
|
|
65
|
+
shortId: string;
|
|
62
66
|
size: number;
|
|
63
|
-
|
|
67
|
+
url: string;
|
|
64
68
|
}
|
|
65
69
|
interface Drop {
|
|
70
|
+
contentType: string;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
filename: string;
|
|
66
73
|
id: string;
|
|
67
74
|
shortId: string;
|
|
68
|
-
url: string;
|
|
69
|
-
filename: string;
|
|
70
75
|
size: number;
|
|
71
|
-
|
|
72
|
-
createdAt: string;
|
|
76
|
+
url: string;
|
|
73
77
|
}
|
|
74
78
|
interface ListDropsResult {
|
|
75
79
|
drops: Drop[];
|
|
@@ -78,46 +82,68 @@ interface ListDropsResult {
|
|
|
78
82
|
limit: number;
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
|
-
|
|
85
|
+
/** Normal presign response — client must PUT to uploadUrl then call confirm. */
|
|
86
|
+
interface PresignNewResult {
|
|
87
|
+
/** URL to call to confirm the upload */
|
|
88
|
+
confirmUrl: string;
|
|
89
|
+
dedupe?: false;
|
|
82
90
|
/** The file key that will be created */
|
|
83
91
|
fileKey: string;
|
|
84
|
-
/** URL to PUT the file to */
|
|
85
|
-
uploadUrl: string;
|
|
86
92
|
/** The R2 key for the file */
|
|
87
93
|
r2Key: string;
|
|
88
|
-
/** URL to
|
|
89
|
-
|
|
94
|
+
/** URL to PUT the file to */
|
|
95
|
+
uploadUrl: string;
|
|
90
96
|
}
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
/** Dedup hit — file already exists in this bucket. No upload needed. */
|
|
98
|
+
interface PresignDedupeResult {
|
|
93
99
|
contentType: string;
|
|
94
|
-
|
|
100
|
+
dedupe: true;
|
|
101
|
+
key: string;
|
|
95
102
|
size: number;
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
url: string | null;
|
|
104
|
+
}
|
|
105
|
+
/** Presign response is either a new upload or a dedup hit. Check `result.dedupe` to differentiate. */
|
|
106
|
+
type PresignResult = PresignNewResult | PresignDedupeResult;
|
|
107
|
+
interface PresignRequest {
|
|
98
108
|
/** Override the default bucket */
|
|
99
109
|
bucket?: string;
|
|
110
|
+
/** SHA-256 hex digest of file content. When provided, Stow checks for an existing file with the same hash in the bucket and returns it immediately (dedup). */
|
|
111
|
+
contentHash?: string;
|
|
112
|
+
contentType: string;
|
|
113
|
+
filename: string;
|
|
100
114
|
/** Custom metadata to attach to the file */
|
|
101
115
|
metadata?: Record<string, string>;
|
|
116
|
+
/** Optional route/folder */
|
|
117
|
+
route?: string;
|
|
118
|
+
/** File size in bytes */
|
|
119
|
+
size: number;
|
|
102
120
|
}
|
|
103
121
|
interface ConfirmUploadRequest {
|
|
104
|
-
fileKey: string;
|
|
105
|
-
size: number;
|
|
106
|
-
contentType: string;
|
|
107
122
|
/** Override the default bucket */
|
|
108
123
|
bucket?: string;
|
|
124
|
+
/** SHA-256 hex digest of file content. Stored with the file record for future dedup lookups. */
|
|
125
|
+
contentHash?: string;
|
|
126
|
+
contentType: string;
|
|
127
|
+
/** Fire KV sync in background instead of blocking. Saves ~100ms per upload. */
|
|
128
|
+
deferKvSync?: boolean;
|
|
129
|
+
fileKey: string;
|
|
109
130
|
/** Custom metadata to attach to the file */
|
|
110
131
|
metadata?: Record<string, string>;
|
|
132
|
+
size: number;
|
|
133
|
+
/** Skip R2 HEAD verification — trust the presign PUT. Saves ~100ms per upload. */
|
|
134
|
+
skipVerify?: boolean;
|
|
111
135
|
}
|
|
112
136
|
interface SimilarSearchRequest {
|
|
113
|
-
/** Find files similar to this file key */
|
|
114
|
-
fileKey?: string;
|
|
115
|
-
/** Search directly with a vector (1024 dimensions) */
|
|
116
|
-
vector?: number[];
|
|
117
137
|
/** Bucket name or ID to scope search */
|
|
118
138
|
bucket?: string;
|
|
139
|
+
/** Find files similar to this file key */
|
|
140
|
+
fileKey?: string;
|
|
119
141
|
/** Max results (default 10, max 50) */
|
|
120
142
|
limit?: number;
|
|
143
|
+
/** Search using a taste profile's vector */
|
|
144
|
+
profileId?: string;
|
|
145
|
+
/** Search directly with a vector (1024 dimensions) */
|
|
146
|
+
vector?: number[];
|
|
121
147
|
}
|
|
122
148
|
interface SimilarSearchResult {
|
|
123
149
|
results: Array<{
|
|
@@ -133,10 +159,42 @@ interface SimilarSearchResult {
|
|
|
133
159
|
createdAt: string;
|
|
134
160
|
}>;
|
|
135
161
|
}
|
|
162
|
+
interface FileResult {
|
|
163
|
+
contentType: string;
|
|
164
|
+
createdAt: string;
|
|
165
|
+
embeddingStatus: string | null;
|
|
166
|
+
key: string;
|
|
167
|
+
metadata: Record<string, string> | null;
|
|
168
|
+
size: number;
|
|
169
|
+
url: string | null;
|
|
170
|
+
}
|
|
171
|
+
interface ProfileCreateRequest {
|
|
172
|
+
bucket?: string;
|
|
173
|
+
fileKeys?: string[];
|
|
174
|
+
name?: string;
|
|
175
|
+
}
|
|
176
|
+
interface ProfileResult {
|
|
177
|
+
createdAt: string;
|
|
178
|
+
fileCount: number;
|
|
179
|
+
id: string;
|
|
180
|
+
name: string | null;
|
|
181
|
+
updatedAt: string;
|
|
182
|
+
}
|
|
183
|
+
interface ProfileFilesResult {
|
|
184
|
+
fileCount: number;
|
|
185
|
+
id: string;
|
|
186
|
+
}
|
|
187
|
+
interface TextSearchRequest {
|
|
188
|
+
bucket?: string;
|
|
189
|
+
limit?: number;
|
|
190
|
+
query: string;
|
|
191
|
+
}
|
|
136
192
|
declare class StowServer {
|
|
137
193
|
private readonly apiKey;
|
|
138
194
|
private readonly baseUrl;
|
|
139
195
|
private readonly bucket?;
|
|
196
|
+
private readonly timeout;
|
|
197
|
+
private readonly retries;
|
|
140
198
|
constructor(config: StowServerConfig | string);
|
|
141
199
|
/**
|
|
142
200
|
* Get the base URL for this instance (used by client SDK)
|
|
@@ -152,9 +210,14 @@ declare class StowServer {
|
|
|
152
210
|
*/
|
|
153
211
|
private withBucket;
|
|
154
212
|
/**
|
|
155
|
-
* Make an API request with
|
|
213
|
+
* Make an API request with retry, timeout, and error handling.
|
|
214
|
+
*
|
|
215
|
+
* - Retries on 429 (rate limit) and 5xx with exponential backoff (1s, 2s, 4s).
|
|
216
|
+
* - AbortController timeout (default 30s).
|
|
217
|
+
* - Consumer can pass `signal` in options to cancel.
|
|
156
218
|
*/
|
|
157
219
|
private request;
|
|
220
|
+
private sleep;
|
|
158
221
|
/**
|
|
159
222
|
* Upload a file directly from the server
|
|
160
223
|
*/
|
|
@@ -173,6 +236,8 @@ declare class StowServer {
|
|
|
173
236
|
uploadFromUrl(url: string, filename: string, options?: {
|
|
174
237
|
bucket?: string;
|
|
175
238
|
metadata?: Record<string, string>;
|
|
239
|
+
/** Headers to forward when fetching the URL (e.g. User-Agent, Referer) */
|
|
240
|
+
headers?: Record<string, string>;
|
|
176
241
|
}): Promise<UploadResult>;
|
|
177
242
|
/**
|
|
178
243
|
* Get a presigned URL for direct client-side upload.
|
|
@@ -215,6 +280,12 @@ declare class StowServer {
|
|
|
215
280
|
key: string;
|
|
216
281
|
metadata: Record<string, string>;
|
|
217
282
|
}>;
|
|
283
|
+
/**
|
|
284
|
+
* Get a single file by key
|
|
285
|
+
*/
|
|
286
|
+
getFile(key: string, options?: {
|
|
287
|
+
bucket?: string;
|
|
288
|
+
}): Promise<FileResult>;
|
|
218
289
|
/**
|
|
219
290
|
* Get a transform URL for an image.
|
|
220
291
|
*
|
|
@@ -271,8 +342,10 @@ declare class StowServer {
|
|
|
271
342
|
*/
|
|
272
343
|
get search(): {
|
|
273
344
|
similar: (params: SimilarSearchRequest) => Promise<SimilarSearchResult>;
|
|
345
|
+
text: (params: TextSearchRequest) => Promise<SimilarSearchResult>;
|
|
274
346
|
};
|
|
275
347
|
private searchSimilar;
|
|
348
|
+
private searchText;
|
|
276
349
|
/**
|
|
277
350
|
* Upload a file as a drop (quick share)
|
|
278
351
|
*
|
|
@@ -297,6 +370,21 @@ declare class StowServer {
|
|
|
297
370
|
* Delete a drop by ID
|
|
298
371
|
*/
|
|
299
372
|
deleteDrop(id: string): Promise<void>;
|
|
373
|
+
/**
|
|
374
|
+
* Profiles namespace for managing taste profiles
|
|
375
|
+
*/
|
|
376
|
+
get profiles(): {
|
|
377
|
+
create: (params?: ProfileCreateRequest) => Promise<ProfileResult>;
|
|
378
|
+
get: (id: string) => Promise<ProfileResult>;
|
|
379
|
+
delete: (id: string) => Promise<void>;
|
|
380
|
+
addFiles: (id: string, fileKeys: string[], bucket?: string) => Promise<ProfileFilesResult>;
|
|
381
|
+
removeFiles: (id: string, fileKeys: string[], bucket?: string) => Promise<ProfileFilesResult>;
|
|
382
|
+
};
|
|
383
|
+
private createProfile;
|
|
384
|
+
private getProfile;
|
|
385
|
+
private deleteProfile;
|
|
386
|
+
private addProfileFiles;
|
|
387
|
+
private removeProfileFiles;
|
|
300
388
|
}
|
|
301
389
|
|
|
302
|
-
export { type ConfirmUploadRequest, type Drop, type DropResult, type ListDropsResult, type ListFilesResult, type PresignRequest, type PresignResult, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TransformOptions, type UploadResult };
|
|
390
|
+
export { type ConfirmUploadRequest, type Drop, type DropResult, type FileResult, type ListDropsResult, type ListFilesResult, type PresignDedupeResult, type PresignNewResult, type PresignRequest, type PresignResult, type ProfileCreateRequest, type ProfileFilesResult, type ProfileResult, type SimilarSearchRequest, type SimilarSearchResult, StowError, StowServer, type StowServerConfig, type TextSearchRequest, type TransformOptions, type UploadResult };
|
package/dist/index.js
CHANGED
|
@@ -78,12 +78,24 @@ var listDropsSchema = import_zod.z.object({
|
|
|
78
78
|
drops: import_zod.z.array(dropSchema),
|
|
79
79
|
usage: import_zod.z.object({ bytes: import_zod.z.number(), limit: import_zod.z.number() })
|
|
80
80
|
});
|
|
81
|
-
var
|
|
81
|
+
var presignNewResultSchema = import_zod.z.object({
|
|
82
82
|
fileKey: import_zod.z.string(),
|
|
83
83
|
uploadUrl: import_zod.z.string(),
|
|
84
84
|
r2Key: import_zod.z.string(),
|
|
85
|
-
confirmUrl: import_zod.z.string()
|
|
85
|
+
confirmUrl: import_zod.z.string(),
|
|
86
|
+
dedupe: import_zod.z.literal(false).optional()
|
|
86
87
|
});
|
|
88
|
+
var presignDedupeResultSchema = import_zod.z.object({
|
|
89
|
+
dedupe: import_zod.z.literal(true),
|
|
90
|
+
key: import_zod.z.string(),
|
|
91
|
+
url: import_zod.z.string().nullable(),
|
|
92
|
+
size: import_zod.z.number(),
|
|
93
|
+
contentType: import_zod.z.string()
|
|
94
|
+
});
|
|
95
|
+
var presignResultSchema = import_zod.z.union([
|
|
96
|
+
presignDedupeResultSchema,
|
|
97
|
+
presignNewResultSchema
|
|
98
|
+
]);
|
|
87
99
|
var confirmResultSchema = import_zod.z.object({
|
|
88
100
|
key: import_zod.z.string(),
|
|
89
101
|
url: import_zod.z.string().nullable(),
|
|
@@ -91,18 +103,44 @@ var confirmResultSchema = import_zod.z.object({
|
|
|
91
103
|
contentType: import_zod.z.string(),
|
|
92
104
|
metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
|
|
93
105
|
});
|
|
106
|
+
var fileResultSchema = import_zod.z.object({
|
|
107
|
+
key: import_zod.z.string(),
|
|
108
|
+
size: import_zod.z.number(),
|
|
109
|
+
contentType: import_zod.z.string(),
|
|
110
|
+
url: import_zod.z.string().nullable(),
|
|
111
|
+
metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).nullable(),
|
|
112
|
+
embeddingStatus: import_zod.z.string().nullable(),
|
|
113
|
+
createdAt: import_zod.z.string()
|
|
114
|
+
});
|
|
115
|
+
var profileResultSchema = import_zod.z.object({
|
|
116
|
+
id: import_zod.z.string(),
|
|
117
|
+
name: import_zod.z.string().nullable(),
|
|
118
|
+
fileCount: import_zod.z.number(),
|
|
119
|
+
createdAt: import_zod.z.string(),
|
|
120
|
+
updatedAt: import_zod.z.string()
|
|
121
|
+
});
|
|
122
|
+
var profileFilesResultSchema = import_zod.z.object({
|
|
123
|
+
id: import_zod.z.string(),
|
|
124
|
+
fileCount: import_zod.z.number()
|
|
125
|
+
});
|
|
94
126
|
var StowServer = class {
|
|
95
127
|
apiKey;
|
|
96
128
|
baseUrl;
|
|
97
129
|
bucket;
|
|
130
|
+
timeout;
|
|
131
|
+
retries;
|
|
98
132
|
constructor(config) {
|
|
99
133
|
if (typeof config === "string") {
|
|
100
134
|
this.apiKey = config;
|
|
101
135
|
this.baseUrl = "https://app.stow.sh";
|
|
136
|
+
this.timeout = 3e4;
|
|
137
|
+
this.retries = 3;
|
|
102
138
|
} else {
|
|
103
139
|
this.apiKey = config.apiKey;
|
|
104
140
|
this.baseUrl = config.baseUrl || "https://app.stow.sh";
|
|
105
141
|
this.bucket = config.bucket;
|
|
142
|
+
this.timeout = config.timeout ?? 3e4;
|
|
143
|
+
this.retries = config.retries ?? 3;
|
|
106
144
|
}
|
|
107
145
|
}
|
|
108
146
|
/**
|
|
@@ -130,24 +168,66 @@ var StowServer = class {
|
|
|
130
168
|
return `${path}${sep}bucket=${encodeURIComponent(b)}`;
|
|
131
169
|
}
|
|
132
170
|
/**
|
|
133
|
-
* Make an API request with
|
|
171
|
+
* Make an API request with retry, timeout, and error handling.
|
|
172
|
+
*
|
|
173
|
+
* - Retries on 429 (rate limit) and 5xx with exponential backoff (1s, 2s, 4s).
|
|
174
|
+
* - AbortController timeout (default 30s).
|
|
175
|
+
* - Consumer can pass `signal` in options to cancel.
|
|
134
176
|
*/
|
|
135
177
|
async request(path, options, schema) {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
178
|
+
const maxAttempts = this.retries + 1;
|
|
179
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
182
|
+
if (options.signal) {
|
|
183
|
+
options.signal.addEventListener("abort", () => controller.abort());
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
187
|
+
...options,
|
|
188
|
+
signal: controller.signal,
|
|
189
|
+
headers: {
|
|
190
|
+
...options.headers,
|
|
191
|
+
"x-api-key": this.apiKey
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const data = await response.json();
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
const error = errorSchema.safeParse(data);
|
|
197
|
+
const message = error.success ? error.data.error : "Request failed";
|
|
198
|
+
const code = error.success ? error.data.code : void 0;
|
|
199
|
+
const isRetryable = response.status === 429 || response.status >= 500;
|
|
200
|
+
if (isRetryable && attempt < maxAttempts - 1) {
|
|
201
|
+
await this.sleep(1e3 * 2 ** attempt);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
throw new StowError(message, response.status, code);
|
|
205
|
+
}
|
|
206
|
+
return schema ? schema.parse(data) : data;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (err instanceof StowError) {
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
if (err instanceof DOMException || err instanceof Error && err.name === "AbortError") {
|
|
212
|
+
throw new StowError("Request timed out", 408, "TIMEOUT");
|
|
213
|
+
}
|
|
214
|
+
if (attempt < maxAttempts - 1) {
|
|
215
|
+
await this.sleep(1e3 * 2 ** attempt);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
throw new StowError(
|
|
219
|
+
err instanceof Error ? err.message : "Network error",
|
|
220
|
+
0,
|
|
221
|
+
"NETWORK_ERROR"
|
|
222
|
+
);
|
|
223
|
+
} finally {
|
|
224
|
+
clearTimeout(timeoutId);
|
|
141
225
|
}
|
|
142
|
-
});
|
|
143
|
-
const data = await response.json();
|
|
144
|
-
if (!response.ok) {
|
|
145
|
-
const error = errorSchema.safeParse(data);
|
|
146
|
-
const message = error.success ? error.data.error : "Request failed";
|
|
147
|
-
const code = error.success ? error.data.code : void 0;
|
|
148
|
-
throw new StowError(message, response.status, code);
|
|
149
226
|
}
|
|
150
|
-
|
|
227
|
+
throw new StowError("Max retries exceeded", 0, "MAX_RETRIES");
|
|
228
|
+
}
|
|
229
|
+
sleep(ms) {
|
|
230
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
151
231
|
}
|
|
152
232
|
/**
|
|
153
233
|
* Upload a file directly from the server
|
|
@@ -192,7 +272,8 @@ var StowServer = class {
|
|
|
192
272
|
body: JSON.stringify({
|
|
193
273
|
url,
|
|
194
274
|
filename,
|
|
195
|
-
...options?.metadata ? { metadata: options.metadata } : {}
|
|
275
|
+
...options?.metadata ? { metadata: options.metadata } : {},
|
|
276
|
+
...options?.headers ? { headers: options.headers } : {}
|
|
196
277
|
})
|
|
197
278
|
},
|
|
198
279
|
uploadResultSchema
|
|
@@ -215,7 +296,7 @@ var StowServer = class {
|
|
|
215
296
|
* 4. Client calls confirmUpload to finalize
|
|
216
297
|
*/
|
|
217
298
|
getPresignedUrl(request) {
|
|
218
|
-
const { filename, contentType, size, route, bucket, metadata } = request;
|
|
299
|
+
const { filename, contentType, size, route, bucket, metadata, contentHash } = request;
|
|
219
300
|
return this.request(
|
|
220
301
|
this.withBucket("/api/presign", bucket),
|
|
221
302
|
{
|
|
@@ -226,7 +307,8 @@ var StowServer = class {
|
|
|
226
307
|
contentType,
|
|
227
308
|
size,
|
|
228
309
|
route,
|
|
229
|
-
...metadata ? { metadata } : {}
|
|
310
|
+
...metadata ? { metadata } : {},
|
|
311
|
+
...contentHash ? { contentHash } : {}
|
|
230
312
|
})
|
|
231
313
|
},
|
|
232
314
|
presignResultSchema
|
|
@@ -237,7 +319,16 @@ var StowServer = class {
|
|
|
237
319
|
* This creates the file record in the database.
|
|
238
320
|
*/
|
|
239
321
|
confirmUpload(request) {
|
|
240
|
-
const {
|
|
322
|
+
const {
|
|
323
|
+
fileKey,
|
|
324
|
+
size,
|
|
325
|
+
contentType,
|
|
326
|
+
bucket,
|
|
327
|
+
metadata,
|
|
328
|
+
skipVerify,
|
|
329
|
+
deferKvSync,
|
|
330
|
+
contentHash
|
|
331
|
+
} = request;
|
|
241
332
|
return this.request(
|
|
242
333
|
this.withBucket("/api/presign/confirm", bucket),
|
|
243
334
|
{
|
|
@@ -247,7 +338,10 @@ var StowServer = class {
|
|
|
247
338
|
fileKey,
|
|
248
339
|
size,
|
|
249
340
|
contentType,
|
|
250
|
-
...metadata ? { metadata } : {}
|
|
341
|
+
...metadata ? { metadata } : {},
|
|
342
|
+
...skipVerify ? { skipVerify } : {},
|
|
343
|
+
...deferKvSync ? { deferKvSync } : {},
|
|
344
|
+
...contentHash ? { contentHash } : {}
|
|
251
345
|
})
|
|
252
346
|
},
|
|
253
347
|
confirmResultSchema
|
|
@@ -297,6 +391,17 @@ var StowServer = class {
|
|
|
297
391
|
body: JSON.stringify({ metadata })
|
|
298
392
|
});
|
|
299
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Get a single file by key
|
|
396
|
+
*/
|
|
397
|
+
async getFile(key, options) {
|
|
398
|
+
const path = `/api/files/${encodeURIComponent(key)}`;
|
|
399
|
+
return this.request(
|
|
400
|
+
this.withBucket(path, options?.bucket),
|
|
401
|
+
{ method: "GET" },
|
|
402
|
+
fileResultSchema
|
|
403
|
+
);
|
|
404
|
+
}
|
|
300
405
|
/**
|
|
301
406
|
* Get a transform URL for an image.
|
|
302
407
|
*
|
|
@@ -387,7 +492,8 @@ var StowServer = class {
|
|
|
387
492
|
*/
|
|
388
493
|
get search() {
|
|
389
494
|
return {
|
|
390
|
-
similar: (params) => this.searchSimilar(params)
|
|
495
|
+
similar: (params) => this.searchSimilar(params),
|
|
496
|
+
text: (params) => this.searchText(params)
|
|
391
497
|
};
|
|
392
498
|
}
|
|
393
499
|
searchSimilar(params) {
|
|
@@ -397,6 +503,18 @@ var StowServer = class {
|
|
|
397
503
|
body: JSON.stringify({
|
|
398
504
|
...params.fileKey ? { fileKey: params.fileKey } : {},
|
|
399
505
|
...params.vector ? { vector: params.vector } : {},
|
|
506
|
+
...params.profileId ? { profileId: params.profileId } : {},
|
|
507
|
+
...params.bucket ? { bucket: params.bucket } : {},
|
|
508
|
+
...params.limit ? { limit: params.limit } : {}
|
|
509
|
+
})
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
searchText(params) {
|
|
513
|
+
return this.request("/api/search/text", {
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: { "Content-Type": "application/json" },
|
|
516
|
+
body: JSON.stringify({
|
|
517
|
+
query: params.query,
|
|
400
518
|
...params.bucket ? { bucket: params.bucket } : {},
|
|
401
519
|
...params.limit ? { limit: params.limit } : {}
|
|
402
520
|
})
|
|
@@ -469,6 +587,76 @@ var StowServer = class {
|
|
|
469
587
|
method: "DELETE"
|
|
470
588
|
});
|
|
471
589
|
}
|
|
590
|
+
// ============================================================
|
|
591
|
+
// PROFILES - Taste/preference profiles from file collections
|
|
592
|
+
// ============================================================
|
|
593
|
+
/**
|
|
594
|
+
* Profiles namespace for managing taste profiles
|
|
595
|
+
*/
|
|
596
|
+
get profiles() {
|
|
597
|
+
return {
|
|
598
|
+
create: (params) => this.createProfile(params),
|
|
599
|
+
get: (id) => this.getProfile(id),
|
|
600
|
+
delete: (id) => this.deleteProfile(id),
|
|
601
|
+
addFiles: (id, fileKeys, bucket) => this.addProfileFiles(id, fileKeys, bucket),
|
|
602
|
+
removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket)
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
createProfile(params) {
|
|
606
|
+
return this.request(
|
|
607
|
+
"/api/profiles",
|
|
608
|
+
{
|
|
609
|
+
method: "POST",
|
|
610
|
+
headers: { "Content-Type": "application/json" },
|
|
611
|
+
body: JSON.stringify({
|
|
612
|
+
...params?.name ? { name: params.name } : {},
|
|
613
|
+
...params?.fileKeys ? { fileKeys: params.fileKeys } : {},
|
|
614
|
+
...params?.bucket ? { bucket: params.bucket } : {}
|
|
615
|
+
})
|
|
616
|
+
},
|
|
617
|
+
profileResultSchema
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
getProfile(id) {
|
|
621
|
+
return this.request(
|
|
622
|
+
`/api/profiles/${encodeURIComponent(id)}`,
|
|
623
|
+
{ method: "GET" },
|
|
624
|
+
profileResultSchema
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
async deleteProfile(id) {
|
|
628
|
+
await this.request(`/api/profiles/${encodeURIComponent(id)}`, {
|
|
629
|
+
method: "DELETE"
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
addProfileFiles(id, fileKeys, bucket) {
|
|
633
|
+
return this.request(
|
|
634
|
+
`/api/profiles/${encodeURIComponent(id)}/files`,
|
|
635
|
+
{
|
|
636
|
+
method: "POST",
|
|
637
|
+
headers: { "Content-Type": "application/json" },
|
|
638
|
+
body: JSON.stringify({
|
|
639
|
+
fileKeys,
|
|
640
|
+
...bucket ? { bucket } : {}
|
|
641
|
+
})
|
|
642
|
+
},
|
|
643
|
+
profileFilesResultSchema
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
removeProfileFiles(id, fileKeys, bucket) {
|
|
647
|
+
return this.request(
|
|
648
|
+
`/api/profiles/${encodeURIComponent(id)}/files`,
|
|
649
|
+
{
|
|
650
|
+
method: "DELETE",
|
|
651
|
+
headers: { "Content-Type": "application/json" },
|
|
652
|
+
body: JSON.stringify({
|
|
653
|
+
fileKeys,
|
|
654
|
+
...bucket ? { bucket } : {}
|
|
655
|
+
})
|
|
656
|
+
},
|
|
657
|
+
profileFilesResultSchema
|
|
658
|
+
);
|
|
659
|
+
}
|
|
472
660
|
};
|
|
473
661
|
// Annotate the CommonJS export names for ESM import in node:
|
|
474
662
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -53,12 +53,24 @@ var listDropsSchema = z.object({
|
|
|
53
53
|
drops: z.array(dropSchema),
|
|
54
54
|
usage: z.object({ bytes: z.number(), limit: z.number() })
|
|
55
55
|
});
|
|
56
|
-
var
|
|
56
|
+
var presignNewResultSchema = z.object({
|
|
57
57
|
fileKey: z.string(),
|
|
58
58
|
uploadUrl: z.string(),
|
|
59
59
|
r2Key: z.string(),
|
|
60
|
-
confirmUrl: z.string()
|
|
60
|
+
confirmUrl: z.string(),
|
|
61
|
+
dedupe: z.literal(false).optional()
|
|
61
62
|
});
|
|
63
|
+
var presignDedupeResultSchema = z.object({
|
|
64
|
+
dedupe: z.literal(true),
|
|
65
|
+
key: z.string(),
|
|
66
|
+
url: z.string().nullable(),
|
|
67
|
+
size: z.number(),
|
|
68
|
+
contentType: z.string()
|
|
69
|
+
});
|
|
70
|
+
var presignResultSchema = z.union([
|
|
71
|
+
presignDedupeResultSchema,
|
|
72
|
+
presignNewResultSchema
|
|
73
|
+
]);
|
|
62
74
|
var confirmResultSchema = z.object({
|
|
63
75
|
key: z.string(),
|
|
64
76
|
url: z.string().nullable(),
|
|
@@ -66,18 +78,44 @@ var confirmResultSchema = z.object({
|
|
|
66
78
|
contentType: z.string(),
|
|
67
79
|
metadata: z.record(z.string(), z.string()).optional()
|
|
68
80
|
});
|
|
81
|
+
var fileResultSchema = z.object({
|
|
82
|
+
key: z.string(),
|
|
83
|
+
size: z.number(),
|
|
84
|
+
contentType: z.string(),
|
|
85
|
+
url: z.string().nullable(),
|
|
86
|
+
metadata: z.record(z.string(), z.string()).nullable(),
|
|
87
|
+
embeddingStatus: z.string().nullable(),
|
|
88
|
+
createdAt: z.string()
|
|
89
|
+
});
|
|
90
|
+
var profileResultSchema = z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
name: z.string().nullable(),
|
|
93
|
+
fileCount: z.number(),
|
|
94
|
+
createdAt: z.string(),
|
|
95
|
+
updatedAt: z.string()
|
|
96
|
+
});
|
|
97
|
+
var profileFilesResultSchema = z.object({
|
|
98
|
+
id: z.string(),
|
|
99
|
+
fileCount: z.number()
|
|
100
|
+
});
|
|
69
101
|
var StowServer = class {
|
|
70
102
|
apiKey;
|
|
71
103
|
baseUrl;
|
|
72
104
|
bucket;
|
|
105
|
+
timeout;
|
|
106
|
+
retries;
|
|
73
107
|
constructor(config) {
|
|
74
108
|
if (typeof config === "string") {
|
|
75
109
|
this.apiKey = config;
|
|
76
110
|
this.baseUrl = "https://app.stow.sh";
|
|
111
|
+
this.timeout = 3e4;
|
|
112
|
+
this.retries = 3;
|
|
77
113
|
} else {
|
|
78
114
|
this.apiKey = config.apiKey;
|
|
79
115
|
this.baseUrl = config.baseUrl || "https://app.stow.sh";
|
|
80
116
|
this.bucket = config.bucket;
|
|
117
|
+
this.timeout = config.timeout ?? 3e4;
|
|
118
|
+
this.retries = config.retries ?? 3;
|
|
81
119
|
}
|
|
82
120
|
}
|
|
83
121
|
/**
|
|
@@ -105,24 +143,66 @@ var StowServer = class {
|
|
|
105
143
|
return `${path}${sep}bucket=${encodeURIComponent(b)}`;
|
|
106
144
|
}
|
|
107
145
|
/**
|
|
108
|
-
* Make an API request with
|
|
146
|
+
* Make an API request with retry, timeout, and error handling.
|
|
147
|
+
*
|
|
148
|
+
* - Retries on 429 (rate limit) and 5xx with exponential backoff (1s, 2s, 4s).
|
|
149
|
+
* - AbortController timeout (default 30s).
|
|
150
|
+
* - Consumer can pass `signal` in options to cancel.
|
|
109
151
|
*/
|
|
110
152
|
async request(path, options, schema) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
153
|
+
const maxAttempts = this.retries + 1;
|
|
154
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
155
|
+
const controller = new AbortController();
|
|
156
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
157
|
+
if (options.signal) {
|
|
158
|
+
options.signal.addEventListener("abort", () => controller.abort());
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
162
|
+
...options,
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
headers: {
|
|
165
|
+
...options.headers,
|
|
166
|
+
"x-api-key": this.apiKey
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const data = await response.json();
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const error = errorSchema.safeParse(data);
|
|
172
|
+
const message = error.success ? error.data.error : "Request failed";
|
|
173
|
+
const code = error.success ? error.data.code : void 0;
|
|
174
|
+
const isRetryable = response.status === 429 || response.status >= 500;
|
|
175
|
+
if (isRetryable && attempt < maxAttempts - 1) {
|
|
176
|
+
await this.sleep(1e3 * 2 ** attempt);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
throw new StowError(message, response.status, code);
|
|
180
|
+
}
|
|
181
|
+
return schema ? schema.parse(data) : data;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
if (err instanceof StowError) {
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
if (err instanceof DOMException || err instanceof Error && err.name === "AbortError") {
|
|
187
|
+
throw new StowError("Request timed out", 408, "TIMEOUT");
|
|
188
|
+
}
|
|
189
|
+
if (attempt < maxAttempts - 1) {
|
|
190
|
+
await this.sleep(1e3 * 2 ** attempt);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
throw new StowError(
|
|
194
|
+
err instanceof Error ? err.message : "Network error",
|
|
195
|
+
0,
|
|
196
|
+
"NETWORK_ERROR"
|
|
197
|
+
);
|
|
198
|
+
} finally {
|
|
199
|
+
clearTimeout(timeoutId);
|
|
116
200
|
}
|
|
117
|
-
});
|
|
118
|
-
const data = await response.json();
|
|
119
|
-
if (!response.ok) {
|
|
120
|
-
const error = errorSchema.safeParse(data);
|
|
121
|
-
const message = error.success ? error.data.error : "Request failed";
|
|
122
|
-
const code = error.success ? error.data.code : void 0;
|
|
123
|
-
throw new StowError(message, response.status, code);
|
|
124
201
|
}
|
|
125
|
-
|
|
202
|
+
throw new StowError("Max retries exceeded", 0, "MAX_RETRIES");
|
|
203
|
+
}
|
|
204
|
+
sleep(ms) {
|
|
205
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
126
206
|
}
|
|
127
207
|
/**
|
|
128
208
|
* Upload a file directly from the server
|
|
@@ -167,7 +247,8 @@ var StowServer = class {
|
|
|
167
247
|
body: JSON.stringify({
|
|
168
248
|
url,
|
|
169
249
|
filename,
|
|
170
|
-
...options?.metadata ? { metadata: options.metadata } : {}
|
|
250
|
+
...options?.metadata ? { metadata: options.metadata } : {},
|
|
251
|
+
...options?.headers ? { headers: options.headers } : {}
|
|
171
252
|
})
|
|
172
253
|
},
|
|
173
254
|
uploadResultSchema
|
|
@@ -190,7 +271,7 @@ var StowServer = class {
|
|
|
190
271
|
* 4. Client calls confirmUpload to finalize
|
|
191
272
|
*/
|
|
192
273
|
getPresignedUrl(request) {
|
|
193
|
-
const { filename, contentType, size, route, bucket, metadata } = request;
|
|
274
|
+
const { filename, contentType, size, route, bucket, metadata, contentHash } = request;
|
|
194
275
|
return this.request(
|
|
195
276
|
this.withBucket("/api/presign", bucket),
|
|
196
277
|
{
|
|
@@ -201,7 +282,8 @@ var StowServer = class {
|
|
|
201
282
|
contentType,
|
|
202
283
|
size,
|
|
203
284
|
route,
|
|
204
|
-
...metadata ? { metadata } : {}
|
|
285
|
+
...metadata ? { metadata } : {},
|
|
286
|
+
...contentHash ? { contentHash } : {}
|
|
205
287
|
})
|
|
206
288
|
},
|
|
207
289
|
presignResultSchema
|
|
@@ -212,7 +294,16 @@ var StowServer = class {
|
|
|
212
294
|
* This creates the file record in the database.
|
|
213
295
|
*/
|
|
214
296
|
confirmUpload(request) {
|
|
215
|
-
const {
|
|
297
|
+
const {
|
|
298
|
+
fileKey,
|
|
299
|
+
size,
|
|
300
|
+
contentType,
|
|
301
|
+
bucket,
|
|
302
|
+
metadata,
|
|
303
|
+
skipVerify,
|
|
304
|
+
deferKvSync,
|
|
305
|
+
contentHash
|
|
306
|
+
} = request;
|
|
216
307
|
return this.request(
|
|
217
308
|
this.withBucket("/api/presign/confirm", bucket),
|
|
218
309
|
{
|
|
@@ -222,7 +313,10 @@ var StowServer = class {
|
|
|
222
313
|
fileKey,
|
|
223
314
|
size,
|
|
224
315
|
contentType,
|
|
225
|
-
...metadata ? { metadata } : {}
|
|
316
|
+
...metadata ? { metadata } : {},
|
|
317
|
+
...skipVerify ? { skipVerify } : {},
|
|
318
|
+
...deferKvSync ? { deferKvSync } : {},
|
|
319
|
+
...contentHash ? { contentHash } : {}
|
|
226
320
|
})
|
|
227
321
|
},
|
|
228
322
|
confirmResultSchema
|
|
@@ -272,6 +366,17 @@ var StowServer = class {
|
|
|
272
366
|
body: JSON.stringify({ metadata })
|
|
273
367
|
});
|
|
274
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Get a single file by key
|
|
371
|
+
*/
|
|
372
|
+
async getFile(key, options) {
|
|
373
|
+
const path = `/api/files/${encodeURIComponent(key)}`;
|
|
374
|
+
return this.request(
|
|
375
|
+
this.withBucket(path, options?.bucket),
|
|
376
|
+
{ method: "GET" },
|
|
377
|
+
fileResultSchema
|
|
378
|
+
);
|
|
379
|
+
}
|
|
275
380
|
/**
|
|
276
381
|
* Get a transform URL for an image.
|
|
277
382
|
*
|
|
@@ -362,7 +467,8 @@ var StowServer = class {
|
|
|
362
467
|
*/
|
|
363
468
|
get search() {
|
|
364
469
|
return {
|
|
365
|
-
similar: (params) => this.searchSimilar(params)
|
|
470
|
+
similar: (params) => this.searchSimilar(params),
|
|
471
|
+
text: (params) => this.searchText(params)
|
|
366
472
|
};
|
|
367
473
|
}
|
|
368
474
|
searchSimilar(params) {
|
|
@@ -372,6 +478,18 @@ var StowServer = class {
|
|
|
372
478
|
body: JSON.stringify({
|
|
373
479
|
...params.fileKey ? { fileKey: params.fileKey } : {},
|
|
374
480
|
...params.vector ? { vector: params.vector } : {},
|
|
481
|
+
...params.profileId ? { profileId: params.profileId } : {},
|
|
482
|
+
...params.bucket ? { bucket: params.bucket } : {},
|
|
483
|
+
...params.limit ? { limit: params.limit } : {}
|
|
484
|
+
})
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
searchText(params) {
|
|
488
|
+
return this.request("/api/search/text", {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: { "Content-Type": "application/json" },
|
|
491
|
+
body: JSON.stringify({
|
|
492
|
+
query: params.query,
|
|
375
493
|
...params.bucket ? { bucket: params.bucket } : {},
|
|
376
494
|
...params.limit ? { limit: params.limit } : {}
|
|
377
495
|
})
|
|
@@ -444,6 +562,76 @@ var StowServer = class {
|
|
|
444
562
|
method: "DELETE"
|
|
445
563
|
});
|
|
446
564
|
}
|
|
565
|
+
// ============================================================
|
|
566
|
+
// PROFILES - Taste/preference profiles from file collections
|
|
567
|
+
// ============================================================
|
|
568
|
+
/**
|
|
569
|
+
* Profiles namespace for managing taste profiles
|
|
570
|
+
*/
|
|
571
|
+
get profiles() {
|
|
572
|
+
return {
|
|
573
|
+
create: (params) => this.createProfile(params),
|
|
574
|
+
get: (id) => this.getProfile(id),
|
|
575
|
+
delete: (id) => this.deleteProfile(id),
|
|
576
|
+
addFiles: (id, fileKeys, bucket) => this.addProfileFiles(id, fileKeys, bucket),
|
|
577
|
+
removeFiles: (id, fileKeys, bucket) => this.removeProfileFiles(id, fileKeys, bucket)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
createProfile(params) {
|
|
581
|
+
return this.request(
|
|
582
|
+
"/api/profiles",
|
|
583
|
+
{
|
|
584
|
+
method: "POST",
|
|
585
|
+
headers: { "Content-Type": "application/json" },
|
|
586
|
+
body: JSON.stringify({
|
|
587
|
+
...params?.name ? { name: params.name } : {},
|
|
588
|
+
...params?.fileKeys ? { fileKeys: params.fileKeys } : {},
|
|
589
|
+
...params?.bucket ? { bucket: params.bucket } : {}
|
|
590
|
+
})
|
|
591
|
+
},
|
|
592
|
+
profileResultSchema
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
getProfile(id) {
|
|
596
|
+
return this.request(
|
|
597
|
+
`/api/profiles/${encodeURIComponent(id)}`,
|
|
598
|
+
{ method: "GET" },
|
|
599
|
+
profileResultSchema
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
async deleteProfile(id) {
|
|
603
|
+
await this.request(`/api/profiles/${encodeURIComponent(id)}`, {
|
|
604
|
+
method: "DELETE"
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
addProfileFiles(id, fileKeys, bucket) {
|
|
608
|
+
return this.request(
|
|
609
|
+
`/api/profiles/${encodeURIComponent(id)}/files`,
|
|
610
|
+
{
|
|
611
|
+
method: "POST",
|
|
612
|
+
headers: { "Content-Type": "application/json" },
|
|
613
|
+
body: JSON.stringify({
|
|
614
|
+
fileKeys,
|
|
615
|
+
...bucket ? { bucket } : {}
|
|
616
|
+
})
|
|
617
|
+
},
|
|
618
|
+
profileFilesResultSchema
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
removeProfileFiles(id, fileKeys, bucket) {
|
|
622
|
+
return this.request(
|
|
623
|
+
`/api/profiles/${encodeURIComponent(id)}/files`,
|
|
624
|
+
{
|
|
625
|
+
method: "DELETE",
|
|
626
|
+
headers: { "Content-Type": "application/json" },
|
|
627
|
+
body: JSON.stringify({
|
|
628
|
+
fileKeys,
|
|
629
|
+
...bucket ? { bucket } : {}
|
|
630
|
+
})
|
|
631
|
+
},
|
|
632
|
+
profileFilesResultSchema
|
|
633
|
+
);
|
|
634
|
+
}
|
|
447
635
|
};
|
|
448
636
|
export {
|
|
449
637
|
StowError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howells/stow-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Server-side SDK for Stow file storage",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@stow/typescript-config": "workspace:*",
|
|
43
|
-
"@types/node": "^25.2.
|
|
44
|
-
"tsup": "^8.
|
|
45
|
-
"typescript": "^5.
|
|
46
|
-
"vitest": "^4.0.
|
|
43
|
+
"@types/node": "^25.2.3",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vitest": "^4.0.18",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
}
|
|
49
49
|
}
|