@fluxsoft/fluxvector 0.1.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/README.md +234 -0
- package/dist/index.cjs +495 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +337 -0
- package/dist/index.d.ts +337 -0
- package/dist/index.js +480 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var FluxVectorError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
requestId;
|
|
6
|
+
constructor(message, status, code = "unknown_error", requestId) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "FluxVectorError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.requestId = requestId;
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var AuthenticationError = class extends FluxVectorError {
|
|
16
|
+
constructor(message = "Invalid or missing API key", requestId) {
|
|
17
|
+
super(message, 401, "authentication_error", requestId);
|
|
18
|
+
this.name = "AuthenticationError";
|
|
19
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var NotFoundError = class extends FluxVectorError {
|
|
23
|
+
constructor(message = "Resource not found", requestId) {
|
|
24
|
+
super(message, 404, "not_found", requestId);
|
|
25
|
+
this.name = "NotFoundError";
|
|
26
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var RateLimitError = class extends FluxVectorError {
|
|
30
|
+
retryAfter;
|
|
31
|
+
constructor(message = "Rate limit exceeded", retryAfter, requestId) {
|
|
32
|
+
super(message, 429, "rate_limit_exceeded", requestId);
|
|
33
|
+
this.name = "RateLimitError";
|
|
34
|
+
this.retryAfter = retryAfter;
|
|
35
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var ValidationError = class extends FluxVectorError {
|
|
39
|
+
constructor(message = "Validation failed", requestId) {
|
|
40
|
+
super(message, 422, "validation_error", requestId);
|
|
41
|
+
this.name = "ValidationError";
|
|
42
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var ServerError = class extends FluxVectorError {
|
|
46
|
+
constructor(message = "Internal server error", status = 500, requestId) {
|
|
47
|
+
super(message, status, "server_error", requestId);
|
|
48
|
+
this.name = "ServerError";
|
|
49
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/client.ts
|
|
54
|
+
var DEFAULT_BASE_URL = "https://fluxvector.dev";
|
|
55
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
56
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
57
|
+
var RETRY_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
58
|
+
var HttpClient = class {
|
|
59
|
+
config;
|
|
60
|
+
constructor(config) {
|
|
61
|
+
this.config = {
|
|
62
|
+
apiKey: config.apiKey,
|
|
63
|
+
baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
64
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
65
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async request(options) {
|
|
69
|
+
const url = this.buildUrl(options.path, options.query);
|
|
70
|
+
const headers = {
|
|
71
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"User-Agent": "@fluxsoft/fluxvector/0.1.0"
|
|
74
|
+
};
|
|
75
|
+
const init = {
|
|
76
|
+
method: options.method,
|
|
77
|
+
headers,
|
|
78
|
+
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
79
|
+
signal: AbortSignal.timeout(this.config.timeout)
|
|
80
|
+
};
|
|
81
|
+
let lastError;
|
|
82
|
+
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
83
|
+
if (attempt > 0) {
|
|
84
|
+
await this.sleep(this.getRetryDelay(attempt, lastError));
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetch(url, init);
|
|
88
|
+
if (response.ok) {
|
|
89
|
+
return await response.json();
|
|
90
|
+
}
|
|
91
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
92
|
+
if (!RETRY_STATUS_CODES.has(response.status) || attempt === this.config.maxRetries) {
|
|
93
|
+
throw this.buildError(response.status, await this.safeJson(response), requestId);
|
|
94
|
+
}
|
|
95
|
+
lastError = this.buildError(response.status, await this.safeJson(response), requestId);
|
|
96
|
+
if (response.status === 429) {
|
|
97
|
+
const retryAfter = response.headers.get("retry-after");
|
|
98
|
+
if (retryAfter && lastError instanceof RateLimitError) {
|
|
99
|
+
lastError.retryAfter = Number(retryAfter);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof FluxVectorError) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
if (attempt === this.config.maxRetries) {
|
|
107
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
108
|
+
throw new FluxVectorError("Request timed out", 0, "timeout");
|
|
109
|
+
}
|
|
110
|
+
throw new FluxVectorError(
|
|
111
|
+
error instanceof Error ? error.message : "Unknown network error",
|
|
112
|
+
0,
|
|
113
|
+
"network_error"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw lastError ?? new FluxVectorError("Request failed after retries", 0, "retry_exhausted");
|
|
120
|
+
}
|
|
121
|
+
buildUrl(path, query) {
|
|
122
|
+
const url = new URL(`${this.config.baseUrl}${path}`);
|
|
123
|
+
if (query) {
|
|
124
|
+
for (const [key, value] of Object.entries(query)) {
|
|
125
|
+
if (value !== void 0) {
|
|
126
|
+
url.searchParams.set(key, String(value));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return url.toString();
|
|
131
|
+
}
|
|
132
|
+
buildError(status, body, requestId) {
|
|
133
|
+
const message = body?.message ?? body?.error ?? `Request failed with status ${status}`;
|
|
134
|
+
switch (status) {
|
|
135
|
+
case 401:
|
|
136
|
+
return new AuthenticationError(message, requestId);
|
|
137
|
+
case 404:
|
|
138
|
+
return new NotFoundError(message, requestId);
|
|
139
|
+
case 422:
|
|
140
|
+
return new ValidationError(message, requestId);
|
|
141
|
+
case 429: {
|
|
142
|
+
const retryAfter = body?.retry_after;
|
|
143
|
+
return new RateLimitError(message, retryAfter, requestId);
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
if (status >= 500) {
|
|
147
|
+
return new ServerError(message, status, requestId);
|
|
148
|
+
}
|
|
149
|
+
return new FluxVectorError(message, status, "api_error", requestId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async safeJson(response) {
|
|
153
|
+
try {
|
|
154
|
+
return await response.json();
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
getRetryDelay(attempt, lastError) {
|
|
160
|
+
if (lastError instanceof RateLimitError && lastError.retryAfter) {
|
|
161
|
+
return lastError.retryAfter * 1e3;
|
|
162
|
+
}
|
|
163
|
+
const base = Math.min(500 * Math.pow(2, attempt - 1), 4e3);
|
|
164
|
+
const jitter = Math.random() * base * 0.5;
|
|
165
|
+
return base + jitter;
|
|
166
|
+
}
|
|
167
|
+
sleep(ms) {
|
|
168
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/resources/collections.ts
|
|
173
|
+
var Collections = class {
|
|
174
|
+
constructor(client) {
|
|
175
|
+
this.client = client;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create a new collection.
|
|
179
|
+
*/
|
|
180
|
+
async create(params) {
|
|
181
|
+
return this.client.request({
|
|
182
|
+
method: "POST",
|
|
183
|
+
path: "/v1/collections",
|
|
184
|
+
body: params
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* List all collections with cursor-based pagination.
|
|
189
|
+
*/
|
|
190
|
+
async list(params) {
|
|
191
|
+
return this.client.request({
|
|
192
|
+
method: "GET",
|
|
193
|
+
path: "/v1/collections",
|
|
194
|
+
query: {
|
|
195
|
+
cursor: params?.cursor,
|
|
196
|
+
limit: params?.limit
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get a collection by name.
|
|
202
|
+
*/
|
|
203
|
+
async get(name) {
|
|
204
|
+
return this.client.request({
|
|
205
|
+
method: "GET",
|
|
206
|
+
path: `/v1/collections/${encodeURIComponent(name)}`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Delete a collection by name.
|
|
211
|
+
*/
|
|
212
|
+
async delete(name) {
|
|
213
|
+
return this.client.request({
|
|
214
|
+
method: "DELETE",
|
|
215
|
+
path: `/v1/collections/${encodeURIComponent(name)}`
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/resources/vectors.ts
|
|
221
|
+
var MAX_UPSERT_BATCH_SIZE = 1e3;
|
|
222
|
+
var Vectors = class {
|
|
223
|
+
constructor(client) {
|
|
224
|
+
this.client = client;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Upsert vectors into a collection.
|
|
228
|
+
* Automatically chunks into batches of 1000 vectors.
|
|
229
|
+
*/
|
|
230
|
+
async upsert(collection, vectors) {
|
|
231
|
+
if (vectors.length <= MAX_UPSERT_BATCH_SIZE) {
|
|
232
|
+
return this.client.request({
|
|
233
|
+
method: "POST",
|
|
234
|
+
path: "/v1/vectors/upsert",
|
|
235
|
+
body: { collection, vectors }
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
let totalUpserted = 0;
|
|
239
|
+
for (let i = 0; i < vectors.length; i += MAX_UPSERT_BATCH_SIZE) {
|
|
240
|
+
const chunk = vectors.slice(i, i + MAX_UPSERT_BATCH_SIZE);
|
|
241
|
+
const result = await this.client.request({
|
|
242
|
+
method: "POST",
|
|
243
|
+
path: "/v1/vectors/upsert",
|
|
244
|
+
body: { collection, vectors: chunk }
|
|
245
|
+
});
|
|
246
|
+
totalUpserted += result.upserted;
|
|
247
|
+
}
|
|
248
|
+
return { upserted: totalUpserted };
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Query vectors by text or vector with optional filtering.
|
|
252
|
+
*/
|
|
253
|
+
async query(params) {
|
|
254
|
+
return this.client.request({
|
|
255
|
+
method: "POST",
|
|
256
|
+
path: "/v1/vectors/query",
|
|
257
|
+
body: {
|
|
258
|
+
collection: params.collection,
|
|
259
|
+
text: params.text,
|
|
260
|
+
vector: params.vector,
|
|
261
|
+
top_k: params.top_k,
|
|
262
|
+
filter: params.filter,
|
|
263
|
+
include_metadata: params.include_metadata,
|
|
264
|
+
include_text: params.include_text,
|
|
265
|
+
mode: params.mode
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Delete vectors by IDs or filter.
|
|
271
|
+
*/
|
|
272
|
+
async delete(collection, options) {
|
|
273
|
+
return this.client.request({
|
|
274
|
+
method: "POST",
|
|
275
|
+
path: "/v1/vectors/delete",
|
|
276
|
+
body: {
|
|
277
|
+
collection,
|
|
278
|
+
ids: options.ids,
|
|
279
|
+
filter: options.filter
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Fetch vectors by their IDs.
|
|
285
|
+
*/
|
|
286
|
+
async fetch(collection, ids) {
|
|
287
|
+
return this.client.request({
|
|
288
|
+
method: "GET",
|
|
289
|
+
path: "/v1/vectors/fetch",
|
|
290
|
+
query: {
|
|
291
|
+
collection,
|
|
292
|
+
ids: ids.join(",")
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/resources/search.ts
|
|
299
|
+
var Search = class {
|
|
300
|
+
constructor(client) {
|
|
301
|
+
this.client = client;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Semantic search across a collection.
|
|
305
|
+
* Returns matching results ranked by relevance.
|
|
306
|
+
*/
|
|
307
|
+
async query(collection, text, options) {
|
|
308
|
+
const response = await this.client.request({
|
|
309
|
+
method: "POST",
|
|
310
|
+
path: "/v1/search",
|
|
311
|
+
body: {
|
|
312
|
+
collection,
|
|
313
|
+
text,
|
|
314
|
+
top_k: options?.topK,
|
|
315
|
+
filter: options?.filter,
|
|
316
|
+
mode: options?.mode
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
return response.results;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Semantic search with full response (includes usage and timing).
|
|
323
|
+
*/
|
|
324
|
+
async queryWithMeta(collection, text, options) {
|
|
325
|
+
return this.client.request({
|
|
326
|
+
method: "POST",
|
|
327
|
+
path: "/v1/search",
|
|
328
|
+
body: {
|
|
329
|
+
collection,
|
|
330
|
+
text,
|
|
331
|
+
top_k: options?.topK,
|
|
332
|
+
filter: options?.filter,
|
|
333
|
+
mode: options?.mode
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// src/resources/embeddings.ts
|
|
340
|
+
var Embeddings = class {
|
|
341
|
+
constructor(client) {
|
|
342
|
+
this.client = client;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Generate an embedding for a single text.
|
|
346
|
+
*/
|
|
347
|
+
async create(text) {
|
|
348
|
+
return this.client.request({
|
|
349
|
+
method: "POST",
|
|
350
|
+
path: "/v1/embed",
|
|
351
|
+
body: { text }
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Generate embeddings for multiple texts in a single request.
|
|
356
|
+
*/
|
|
357
|
+
async createBatch(texts) {
|
|
358
|
+
return this.client.request({
|
|
359
|
+
method: "POST",
|
|
360
|
+
path: "/v1/embed/batch",
|
|
361
|
+
body: { texts }
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/resources/api-keys.ts
|
|
367
|
+
var ApiKeys = class {
|
|
368
|
+
constructor(client) {
|
|
369
|
+
this.client = client;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Create a new API key.
|
|
373
|
+
*/
|
|
374
|
+
async create(params) {
|
|
375
|
+
return this.client.request({
|
|
376
|
+
method: "POST",
|
|
377
|
+
path: "/v1/api-keys",
|
|
378
|
+
body: params
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* List all API keys.
|
|
383
|
+
*/
|
|
384
|
+
async list() {
|
|
385
|
+
return this.client.request({
|
|
386
|
+
method: "GET",
|
|
387
|
+
path: "/v1/api-keys"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Update an API key's name.
|
|
392
|
+
*/
|
|
393
|
+
async update(id, params) {
|
|
394
|
+
return this.client.request({
|
|
395
|
+
method: "PATCH",
|
|
396
|
+
path: `/v1/api-keys/${encodeURIComponent(id)}`,
|
|
397
|
+
body: params
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Delete an API key.
|
|
402
|
+
*/
|
|
403
|
+
async delete(id) {
|
|
404
|
+
return this.client.request({
|
|
405
|
+
method: "DELETE",
|
|
406
|
+
path: `/v1/api-keys/${encodeURIComponent(id)}`
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// src/resources/usage.ts
|
|
412
|
+
var Usage = class {
|
|
413
|
+
constructor(client) {
|
|
414
|
+
this.client = client;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get current usage overview for the billing period.
|
|
418
|
+
*/
|
|
419
|
+
async get() {
|
|
420
|
+
return this.client.request({
|
|
421
|
+
method: "GET",
|
|
422
|
+
path: "/v1/usage"
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Get historical usage data.
|
|
427
|
+
*/
|
|
428
|
+
async history(params) {
|
|
429
|
+
return this.client.request({
|
|
430
|
+
method: "GET",
|
|
431
|
+
path: "/v1/usage/history",
|
|
432
|
+
query: {
|
|
433
|
+
days: params?.days
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/index.ts
|
|
440
|
+
var FluxVector = class {
|
|
441
|
+
collections;
|
|
442
|
+
vectors;
|
|
443
|
+
embeddings;
|
|
444
|
+
apiKeys;
|
|
445
|
+
usage;
|
|
446
|
+
searchResource;
|
|
447
|
+
constructor(config) {
|
|
448
|
+
if (!config.apiKey) {
|
|
449
|
+
throw new Error("FluxVector: apiKey is required");
|
|
450
|
+
}
|
|
451
|
+
const client = new HttpClient({
|
|
452
|
+
apiKey: config.apiKey,
|
|
453
|
+
baseUrl: config.baseUrl,
|
|
454
|
+
maxRetries: config.maxRetries,
|
|
455
|
+
timeout: config.timeout
|
|
456
|
+
});
|
|
457
|
+
this.collections = new Collections(client);
|
|
458
|
+
this.vectors = new Vectors(client);
|
|
459
|
+
this.searchResource = new Search(client);
|
|
460
|
+
this.embeddings = new Embeddings(client);
|
|
461
|
+
this.apiKeys = new ApiKeys(client);
|
|
462
|
+
this.usage = new Usage(client);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Semantic search across a collection.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* const results = await fv.search('products', 'comfortable shoes', {
|
|
469
|
+
* topK: 5,
|
|
470
|
+
* filter: { price: { $lt: 100 } },
|
|
471
|
+
* });
|
|
472
|
+
*/
|
|
473
|
+
async search(collection, text, options) {
|
|
474
|
+
return this.searchResource.query(collection, text, options);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export { ApiKeys, AuthenticationError, Collections, Embeddings, FluxVector, FluxVectorError, HttpClient, NotFoundError, RateLimitError, Search, ServerError, Usage, ValidationError, Vectors };
|
|
479
|
+
//# sourceMappingURL=index.js.map
|
|
480
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts","../src/resources/collections.ts","../src/resources/vectors.ts","../src/resources/search.ts","../src/resources/embeddings.ts","../src/resources/api-keys.ts","../src/resources/usage.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export class FluxVectorError extends Error {\n readonly status: number;\n readonly code: string;\n readonly requestId: string | undefined;\n\n constructor(\n message: string,\n status: number,\n code: string = 'unknown_error',\n requestId?: string,\n ) {\n super(message);\n this.name = 'FluxVectorError';\n this.status = status;\n this.code = code;\n this.requestId = requestId;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class AuthenticationError extends FluxVectorError {\n constructor(message: string = 'Invalid or missing API key', requestId?: string) {\n super(message, 401, 'authentication_error', requestId);\n this.name = 'AuthenticationError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class NotFoundError extends FluxVectorError {\n constructor(message: string = 'Resource not found', requestId?: string) {\n super(message, 404, 'not_found', requestId);\n this.name = 'NotFoundError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class RateLimitError extends FluxVectorError {\n readonly retryAfter: number | undefined;\n\n constructor(message: string = 'Rate limit exceeded', retryAfter?: number, requestId?: string) {\n super(message, 429, 'rate_limit_exceeded', requestId);\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ValidationError extends FluxVectorError {\n constructor(message: string = 'Validation failed', requestId?: string) {\n super(message, 422, 'validation_error', requestId);\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ServerError extends FluxVectorError {\n constructor(message: string = 'Internal server error', status: number = 500, requestId?: string) {\n super(message, status, 'server_error', requestId);\n this.name = 'ServerError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import type { RequestOptions } from './types.js';\nimport {\n FluxVectorError,\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n ServerError,\n} from './errors.js';\n\nconst DEFAULT_BASE_URL = 'https://fluxvector.dev';\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_TIMEOUT = 30_000;\nconst RETRY_STATUS_CODES = new Set([429, 500, 502, 503, 504]);\n\ninterface ClientConfig {\n apiKey: string;\n baseUrl: string;\n maxRetries: number;\n timeout: number;\n}\n\nexport class HttpClient {\n private readonly config: ClientConfig;\n\n constructor(config: {\n apiKey: string;\n baseUrl?: string;\n maxRetries?: number;\n timeout?: number;\n }) {\n this.config = {\n apiKey: config.apiKey,\n baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, ''),\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n };\n }\n\n async request<T>(options: RequestOptions): Promise<T> {\n const url = this.buildUrl(options.path, options.query);\n const headers: Record<string, string> = {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n 'User-Agent': '@fluxsoft/fluxvector/0.1.0',\n };\n\n const init: RequestInit = {\n method: options.method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n signal: AbortSignal.timeout(this.config.timeout),\n };\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n if (attempt > 0) {\n await this.sleep(this.getRetryDelay(attempt, lastError));\n }\n\n try {\n const response = await fetch(url, init);\n\n if (response.ok) {\n return await response.json() as T;\n }\n\n const requestId = response.headers.get('x-request-id') ?? undefined;\n\n if (!RETRY_STATUS_CODES.has(response.status) || attempt === this.config.maxRetries) {\n throw this.buildError(response.status, await this.safeJson(response), requestId);\n }\n\n lastError = this.buildError(response.status, await this.safeJson(response), requestId);\n if (response.status === 429) {\n const retryAfter = response.headers.get('retry-after');\n if (retryAfter && lastError instanceof RateLimitError) {\n (lastError as RateLimitError & { retryAfter: number | undefined }).retryAfter = Number(retryAfter);\n }\n }\n } catch (error) {\n if (error instanceof FluxVectorError) {\n throw error;\n }\n\n if (attempt === this.config.maxRetries) {\n if (error instanceof DOMException && error.name === 'TimeoutError') {\n throw new FluxVectorError('Request timed out', 0, 'timeout');\n }\n throw new FluxVectorError(\n error instanceof Error ? error.message : 'Unknown network error',\n 0,\n 'network_error',\n );\n }\n\n lastError = error instanceof Error ? error : new Error(String(error));\n }\n }\n\n throw lastError ?? new FluxVectorError('Request failed after retries', 0, 'retry_exhausted');\n }\n\n private buildUrl(path: string, query?: Record<string, string | number | undefined>): string {\n const url = new URL(`${this.config.baseUrl}${path}`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n }\n\n private buildError(\n status: number,\n body: Record<string, unknown> | null,\n requestId: string | undefined,\n ): FluxVectorError {\n const message = (body?.message as string | undefined)\n ?? (body?.error as string | undefined)\n ?? `Request failed with status ${status}`;\n\n switch (status) {\n case 401:\n return new AuthenticationError(message, requestId);\n case 404:\n return new NotFoundError(message, requestId);\n case 422:\n return new ValidationError(message, requestId);\n case 429: {\n const retryAfter = body?.retry_after as number | undefined;\n return new RateLimitError(message, retryAfter, requestId);\n }\n default:\n if (status >= 500) {\n return new ServerError(message, status, requestId);\n }\n return new FluxVectorError(message, status, 'api_error', requestId);\n }\n }\n\n private async safeJson(response: Response): Promise<Record<string, unknown> | null> {\n try {\n return await response.json() as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n private getRetryDelay(attempt: number, lastError?: Error): number {\n if (lastError instanceof RateLimitError && lastError.retryAfter) {\n return lastError.retryAfter * 1000;\n }\n // Exponential backoff: 500ms, 1s, 2s + jitter\n const base = Math.min(500 * Math.pow(2, attempt - 1), 4000);\n const jitter = Math.random() * base * 0.5;\n return base + jitter;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { HttpClient } from '../client.js';\nimport type {\n Collection,\n CreateCollectionParams,\n ListCollectionsParams,\n ListCollectionsResponse,\n} from '../types.js';\n\nexport class Collections {\n constructor(private readonly client: HttpClient) {}\n\n /**\n * Create a new collection.\n */\n async create(params: CreateCollectionParams): Promise<Collection> {\n return this.client.request<Collection>({\n method: 'POST',\n path: '/v1/collections',\n body: params,\n });\n }\n\n /**\n * List all collections with cursor-based pagination.\n */\n async list(params?: ListCollectionsParams): Promise<ListCollectionsResponse> {\n return this.client.request<ListCollectionsResponse>({\n method: 'GET',\n path: '/v1/collections',\n query: {\n cursor: params?.cursor,\n limit: params?.limit,\n },\n });\n }\n\n /**\n * Get a collection by name.\n */\n async get(name: string): Promise<Collection> {\n return this.client.request<Collection>({\n method: 'GET',\n path: `/v1/collections/${encodeURIComponent(name)}`,\n });\n }\n\n /**\n * Delete a collection by name.\n */\n async delete(name: string): Promise<{ deleted: true }> {\n return this.client.request<{ deleted: true }>({\n method: 'DELETE',\n path: `/v1/collections/${encodeURIComponent(name)}`,\n });\n }\n}\n","import type { HttpClient } from '../client.js';\nimport type {\n VectorInput,\n UpsertResponse,\n QueryParams,\n QueryResponse,\n MetadataFilter,\n FetchVectorsResponse,\n} from '../types.js';\n\nconst MAX_UPSERT_BATCH_SIZE = 1000;\n\nexport class Vectors {\n constructor(private readonly client: HttpClient) {}\n\n /**\n * Upsert vectors into a collection.\n * Automatically chunks into batches of 1000 vectors.\n */\n async upsert(collection: string, vectors: VectorInput[]): Promise<UpsertResponse> {\n if (vectors.length <= MAX_UPSERT_BATCH_SIZE) {\n return this.client.request<UpsertResponse>({\n method: 'POST',\n path: '/v1/vectors/upsert',\n body: { collection, vectors },\n });\n }\n\n let totalUpserted = 0;\n\n for (let i = 0; i < vectors.length; i += MAX_UPSERT_BATCH_SIZE) {\n const chunk = vectors.slice(i, i + MAX_UPSERT_BATCH_SIZE);\n const result = await this.client.request<UpsertResponse>({\n method: 'POST',\n path: '/v1/vectors/upsert',\n body: { collection, vectors: chunk },\n });\n totalUpserted += result.upserted;\n }\n\n return { upserted: totalUpserted };\n }\n\n /**\n * Query vectors by text or vector with optional filtering.\n */\n async query(params: QueryParams): Promise<QueryResponse> {\n return this.client.request<QueryResponse>({\n method: 'POST',\n path: '/v1/vectors/query',\n body: {\n collection: params.collection,\n text: params.text,\n vector: params.vector,\n top_k: params.top_k,\n filter: params.filter,\n include_metadata: params.include_metadata,\n include_text: params.include_text,\n mode: params.mode,\n },\n });\n }\n\n /**\n * Delete vectors by IDs or filter.\n */\n async delete(\n collection: string,\n options: { ids?: string[]; filter?: MetadataFilter },\n ): Promise<{ deleted: true }> {\n return this.client.request<{ deleted: true }>({\n method: 'POST',\n path: '/v1/vectors/delete',\n body: {\n collection,\n ids: options.ids,\n filter: options.filter,\n },\n });\n }\n\n /**\n * Fetch vectors by their IDs.\n */\n async fetch(collection: string, ids: string[]): Promise<FetchVectorsResponse> {\n return this.client.request<FetchVectorsResponse>({\n method: 'GET',\n path: '/v1/vectors/fetch',\n query: {\n collection,\n ids: ids.join(','),\n },\n });\n }\n}\n","import type { HttpClient } from '../client.js';\nimport type { MetadataFilter, QueryResult, SearchResponse } from '../types.js';\n\nexport interface SearchOptions {\n topK?: number;\n filter?: MetadataFilter;\n mode?: string;\n}\n\nexport class Search {\n constructor(private readonly client: HttpClient) {}\n\n /**\n * Semantic search across a collection.\n * Returns matching results ranked by relevance.\n */\n async query(\n collection: string,\n text: string,\n options?: SearchOptions,\n ): Promise<QueryResult[]> {\n const response = await this.client.request<SearchResponse>({\n method: 'POST',\n path: '/v1/search',\n body: {\n collection,\n text,\n top_k: options?.topK,\n filter: options?.filter,\n mode: options?.mode,\n },\n });\n return response.results;\n }\n\n /**\n * Semantic search with full response (includes usage and timing).\n */\n async queryWithMeta(\n collection: string,\n text: string,\n options?: SearchOptions,\n ): Promise<SearchResponse> {\n return this.client.request<SearchResponse>({\n method: 'POST',\n path: '/v1/search',\n body: {\n collection,\n text,\n top_k: options?.topK,\n filter: options?.filter,\n mode: options?.mode,\n },\n });\n }\n}\n","import type { HttpClient } from '../client.js';\nimport type { EmbedResponse, EmbedBatchResponse } from '../types.js';\n\nexport class Embeddings {\n constructor(private readonly client: HttpClient) {}\n\n /**\n * Generate an embedding for a single text.\n */\n async create(text: string): Promise<EmbedResponse> {\n return this.client.request<EmbedResponse>({\n method: 'POST',\n path: '/v1/embed',\n body: { text },\n });\n }\n\n /**\n * Generate embeddings for multiple texts in a single request.\n */\n async createBatch(texts: string[]): Promise<EmbedBatchResponse> {\n return this.client.request<EmbedBatchResponse>({\n method: 'POST',\n path: '/v1/embed/batch',\n body: { texts },\n });\n }\n}\n","import type { HttpClient } from '../client.js';\nimport type {\n ApiKey,\n CreateApiKeyParams,\n ListApiKeysResponse,\n UpdateApiKeyParams,\n} from '../types.js';\n\nexport class ApiKeys {\n constructor(private readonly client: HttpClient) {}\n\n /**\n * Create a new API key.\n */\n async create(params: CreateApiKeyParams): Promise<ApiKey> {\n return this.client.request<ApiKey>({\n method: 'POST',\n path: '/v1/api-keys',\n body: params,\n });\n }\n\n /**\n * List all API keys.\n */\n async list(): Promise<ListApiKeysResponse> {\n return this.client.request<ListApiKeysResponse>({\n method: 'GET',\n path: '/v1/api-keys',\n });\n }\n\n /**\n * Update an API key's name.\n */\n async update(id: string, params: UpdateApiKeyParams): Promise<ApiKey> {\n return this.client.request<ApiKey>({\n method: 'PATCH',\n path: `/v1/api-keys/${encodeURIComponent(id)}`,\n body: params,\n });\n }\n\n /**\n * Delete an API key.\n */\n async delete(id: string): Promise<{ deleted: true }> {\n return this.client.request<{ deleted: true }>({\n method: 'DELETE',\n path: `/v1/api-keys/${encodeURIComponent(id)}`,\n });\n }\n}\n","import type { HttpClient } from '../client.js';\nimport type {\n UsageOverview,\n UsageHistoryParams,\n UsageHistoryResponse,\n} from '../types.js';\n\nexport class Usage {\n constructor(private readonly client: HttpClient) {}\n\n /**\n * Get current usage overview for the billing period.\n */\n async get(): Promise<UsageOverview> {\n return this.client.request<UsageOverview>({\n method: 'GET',\n path: '/v1/usage',\n });\n }\n\n /**\n * Get historical usage data.\n */\n async history(params?: UsageHistoryParams): Promise<UsageHistoryResponse> {\n return this.client.request<UsageHistoryResponse>({\n method: 'GET',\n path: '/v1/usage/history',\n query: {\n days: params?.days,\n },\n });\n }\n}\n","import { HttpClient } from './client.js';\nimport { Collections } from './resources/collections.js';\nimport { Vectors } from './resources/vectors.js';\nimport { Search } from './resources/search.js';\nimport type { SearchOptions } from './resources/search.js';\nimport { Embeddings } from './resources/embeddings.js';\nimport { ApiKeys } from './resources/api-keys.js';\nimport { Usage } from './resources/usage.js';\nimport type { FluxVectorConfig, QueryResult } from './types.js';\n\nexport class FluxVector {\n readonly collections: Collections;\n readonly vectors: Vectors;\n readonly embeddings: Embeddings;\n readonly apiKeys: ApiKeys;\n readonly usage: Usage;\n\n private readonly searchResource: Search;\n\n constructor(config: FluxVectorConfig) {\n if (!config.apiKey) {\n throw new Error('FluxVector: apiKey is required');\n }\n\n const client = new HttpClient({\n apiKey: config.apiKey,\n baseUrl: config.baseUrl,\n maxRetries: config.maxRetries,\n timeout: config.timeout,\n });\n\n this.collections = new Collections(client);\n this.vectors = new Vectors(client);\n this.searchResource = new Search(client);\n this.embeddings = new Embeddings(client);\n this.apiKeys = new ApiKeys(client);\n this.usage = new Usage(client);\n }\n\n /**\n * Semantic search across a collection.\n *\n * @example\n * const results = await fv.search('products', 'comfortable shoes', {\n * topK: 5,\n * filter: { price: { $lt: 100 } },\n * });\n */\n async search(\n collection: string,\n text: string,\n options?: SearchOptions,\n ): Promise<QueryResult[]> {\n return this.searchResource.query(collection, text, options);\n }\n}\n\n// Re-export everything\nexport { HttpClient } from './client.js';\nexport { Collections } from './resources/collections.js';\nexport { Vectors } from './resources/vectors.js';\nexport { Search } from './resources/search.js';\nexport type { SearchOptions } from './resources/search.js';\nexport { Embeddings } from './resources/embeddings.js';\nexport { ApiKeys } from './resources/api-keys.js';\nexport { Usage } from './resources/usage.js';\n\nexport {\n FluxVectorError,\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n ServerError,\n} from './errors.js';\n\nexport type {\n FluxVectorConfig,\n FilterOperator,\n FilterValue,\n FilterCondition,\n MetadataFilter,\n CreateCollectionParams,\n Collection,\n ListCollectionsParams,\n ListCollectionsResponse,\n VectorInput,\n Vector,\n UpsertResponse,\n QueryParams,\n QueryResult,\n QueryResponse,\n DeleteVectorsParams,\n FetchVectorsParams,\n FetchVectorsResponse,\n SearchParams,\n SearchResponse,\n EmbedResponse,\n EmbedBatchResponse,\n CreateApiKeyParams,\n ApiKey,\n UpdateApiKeyParams,\n ListApiKeysResponse,\n UsageInfo,\n UsageOverview,\n UsageHistoryParams,\n UsageHistoryEntry,\n UsageHistoryResponse,\n} from './types.js';\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluxsoft/fluxvector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for FluxVector — semantic search API by FluxSoft Labs",
|
|
5
|
+
"author": "FluxSoft Technologies, LLC",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/index.d.cts",
|
|
19
|
+
"default": "./dist/index.cjs"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"lint": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "echo skip"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"msw": "^2.6.0",
|
|
38
|
+
"tsup": "^8.3.0",
|
|
39
|
+
"typescript": "^5.7.0",
|
|
40
|
+
"vitest": "^2.1.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"fluxvector",
|
|
47
|
+
"vector",
|
|
48
|
+
"semantic-search",
|
|
49
|
+
"embeddings",
|
|
50
|
+
"ai",
|
|
51
|
+
"sdk",
|
|
52
|
+
"fluxsoft"
|
|
53
|
+
],
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/fluxsoft-labs/fluxvector-js"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://fluxsoftlabs.com/fluxvector",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/fluxsoft-labs/fluxvector-js/issues"
|
|
61
|
+
}
|
|
62
|
+
}
|