@agentuity/core 0.0.60 → 0.0.61
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/error.d.ts +90 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +258 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +1 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -2
- package/dist/json.js.map +1 -1
- package/dist/services/_util.d.ts +2 -6
- package/dist/services/_util.d.ts.map +1 -1
- package/dist/services/_util.js +56 -8
- package/dist/services/_util.js.map +1 -1
- package/dist/services/adapter.d.ts +2 -1
- package/dist/services/adapter.d.ts.map +1 -1
- package/dist/services/exception.d.ts +20 -6
- package/dist/services/exception.d.ts.map +1 -1
- package/dist/services/exception.js +2 -11
- package/dist/services/exception.js.map +1 -1
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/keyvalue.d.ts.map +1 -1
- package/dist/services/keyvalue.js +5 -1
- package/dist/services/keyvalue.js.map +1 -1
- package/dist/services/objectstore.d.ts.map +1 -1
- package/dist/services/objectstore.js +65 -26
- package/dist/services/objectstore.js.map +1 -1
- package/dist/services/stream.d.ts.map +1 -1
- package/dist/services/stream.js +25 -9
- package/dist/services/stream.js.map +1 -1
- package/dist/services/vector.d.ts.map +1 -1
- package/dist/services/vector.js +48 -30
- package/dist/services/vector.js.map +1 -1
- package/dist/workbench-config.d.ts +38 -0
- package/dist/workbench-config.d.ts.map +1 -1
- package/dist/workbench-config.js +7 -5
- package/dist/workbench-config.js.map +1 -1
- package/package.json +1 -1
- package/src/__test__/error.test.ts +431 -0
- package/src/error.ts +384 -0
- package/src/index.ts +1 -0
- package/src/json.ts +2 -2
- package/src/services/__test__/vector.test.ts +20 -14
- package/src/services/_util.ts +56 -18
- package/src/services/adapter.ts +3 -1
- package/src/services/exception.ts +7 -9
- package/src/services/index.ts +0 -1
- package/src/services/keyvalue.ts +6 -1
- package/src/services/objectstore.ts +79 -44
- package/src/services/stream.ts +45 -11
- package/src/services/vector.ts +99 -30
- package/src/workbench-config.ts +16 -5
package/src/error.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
// NOTE: these ideas are borrowed from https://github.com/Effect-TS/effect
|
|
4
|
+
|
|
5
|
+
import util from 'node:util';
|
|
6
|
+
import { safeStringify } from './json';
|
|
7
|
+
|
|
8
|
+
type PlainObject = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
const _argsSym = Symbol('@@RichError:plainArgs');
|
|
11
|
+
const _causeSym = Symbol('@@RichError:cause');
|
|
12
|
+
const _metaSym = Symbol('@@RichError:meta'); // reserved for future use (non-enumerable)
|
|
13
|
+
const _structuredSym = Symbol.for('@@StructuredError');
|
|
14
|
+
|
|
15
|
+
const spacer = ' ';
|
|
16
|
+
|
|
17
|
+
export class RichError extends Error {
|
|
18
|
+
// private slots (non-enumerable)
|
|
19
|
+
private [_argsSym]?: PlainObject;
|
|
20
|
+
private [_causeSym]?: unknown;
|
|
21
|
+
private [_metaSym]?: PlainObject;
|
|
22
|
+
|
|
23
|
+
constructor(args?: PlainObject) {
|
|
24
|
+
const message = args?.message ?? undefined;
|
|
25
|
+
const cause = (args?.cause ?? undefined) as unknown;
|
|
26
|
+
|
|
27
|
+
// If platform supports error cause option, pass it to Error constructor
|
|
28
|
+
// (Node 16+ / modern engines)
|
|
29
|
+
if (cause !== undefined) {
|
|
30
|
+
super(message, { cause } as any);
|
|
31
|
+
} else {
|
|
32
|
+
super(message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// correct prototype chain when transpiled to older JS
|
|
36
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
|
|
38
|
+
// capture a clean stack (omit this constructor)
|
|
39
|
+
if (typeof (Error as any).captureStackTrace === 'function') {
|
|
40
|
+
(Error as any).captureStackTrace(this, new.target);
|
|
41
|
+
} else {
|
|
42
|
+
// fallback: ensure stack exists
|
|
43
|
+
if (!this.stack) {
|
|
44
|
+
this.stack = new Error(message).stack;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (args && typeof args === 'object') {
|
|
49
|
+
// copy all fields except cause and message (we keep them separate)
|
|
50
|
+
const { cause: _c, message: _m, ...rest } = args;
|
|
51
|
+
if (Object.keys(rest).length > 0) {
|
|
52
|
+
Object.assign(this, rest);
|
|
53
|
+
this[_argsSym] = rest;
|
|
54
|
+
}
|
|
55
|
+
if (cause !== undefined) {
|
|
56
|
+
this[_causeSym] = cause;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// hide internal symbols and meta (redefine non-enumerable)
|
|
60
|
+
Object.defineProperty(this, _argsSym, {
|
|
61
|
+
value: this[_argsSym],
|
|
62
|
+
enumerable: false,
|
|
63
|
+
writable: true,
|
|
64
|
+
});
|
|
65
|
+
Object.defineProperty(this, _causeSym, {
|
|
66
|
+
value: this[_causeSym],
|
|
67
|
+
enumerable: false,
|
|
68
|
+
writable: true,
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(this, _metaSym, {
|
|
71
|
+
value: this[_metaSym] ?? {},
|
|
72
|
+
enumerable: false,
|
|
73
|
+
writable: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Return the stored plain args (if any) */
|
|
78
|
+
get plainArgs(): PlainObject | undefined {
|
|
79
|
+
return this[_argsSym];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Return the cause (if any) */
|
|
83
|
+
get cause(): unknown | undefined {
|
|
84
|
+
return this[_causeSym];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Pretty, recursive string representation (follows cause chain). */
|
|
88
|
+
prettyPrint(space = 2): string {
|
|
89
|
+
const lines: string[] = [];
|
|
90
|
+
const visited = new Set<Error>();
|
|
91
|
+
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
93
|
+
let cur: Error | undefined = this;
|
|
94
|
+
let depth = 0;
|
|
95
|
+
while (cur && cur instanceof Error && !visited.has(cur)) {
|
|
96
|
+
const curAny = cur as any;
|
|
97
|
+
visited.add(cur);
|
|
98
|
+
const header = `${cur.name}${curAny._tag && curAny._tag !== cur.name ? ` (${String(curAny._tag)})` : ''}${depth === 0 ? '' : ' [cause]'}`;
|
|
99
|
+
const msg = cur.message !== undefined && cur.message !== '' ? `: ${cur.message}` : '';
|
|
100
|
+
lines.push(header + msg);
|
|
101
|
+
|
|
102
|
+
// include stack if present (limit to first line of stack header for brevity)
|
|
103
|
+
if (cur.stack) {
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(spacer + 'stack trace:');
|
|
106
|
+
const stackLines = String(cur.stack).split('\n').slice(1); // drop first line (it's message)
|
|
107
|
+
if (stackLines.length > 0) {
|
|
108
|
+
// indent stack
|
|
109
|
+
let s = stackLines.map((s) => spacer + spacer + s.trim());
|
|
110
|
+
if (s[s.length - 1].includes('processTicksAndRejections')) {
|
|
111
|
+
s = s.slice(0, s.length - 1);
|
|
112
|
+
}
|
|
113
|
+
lines.push(...s);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// include plain args as formatted output (if any)
|
|
118
|
+
if (curAny[_argsSym]) {
|
|
119
|
+
let argsStr = util.formatWithOptions(
|
|
120
|
+
{
|
|
121
|
+
depth: 10,
|
|
122
|
+
sorted: true,
|
|
123
|
+
showHidden: false,
|
|
124
|
+
showProxy: false,
|
|
125
|
+
maxArrayLength: 10,
|
|
126
|
+
maxStringLength: 80 - spacer.length * 2,
|
|
127
|
+
},
|
|
128
|
+
curAny[_argsSym]
|
|
129
|
+
);
|
|
130
|
+
argsStr = argsStr.replace(/^{/, '').replace(/}$/, '');
|
|
131
|
+
const jsonlines = argsStr
|
|
132
|
+
.split('\n')
|
|
133
|
+
.filter(Boolean)
|
|
134
|
+
.map((l: string) => spacer + spacer + l + '\n')
|
|
135
|
+
.join('');
|
|
136
|
+
lines.push('');
|
|
137
|
+
lines.push(spacer + 'context:\n' + jsonlines);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// include cause summary if non-Error (could be object)
|
|
141
|
+
const c: unknown = cur.cause ?? curAny[_causeSym];
|
|
142
|
+
if (c && !(c instanceof Error)) {
|
|
143
|
+
lines.push(spacer + 'cause: ' + safeStringify(c, space));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
cur = c instanceof Error ? c : undefined;
|
|
147
|
+
depth += 1;
|
|
148
|
+
if (cur) lines.push('-- caused by --');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return lines.join('\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
toJSON() {
|
|
155
|
+
// Represent as merged: args, public enumerable props, plus well-known fields
|
|
156
|
+
const output: any = {};
|
|
157
|
+
|
|
158
|
+
// include args first (non-enumerable original values)
|
|
159
|
+
if (this[_argsSym]) {
|
|
160
|
+
Object.assign(output, this[_argsSym]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// copy enumerable own props
|
|
164
|
+
for (const k of Object.keys(this)) {
|
|
165
|
+
// skip internal symbols (they are non-enumerable anyway)
|
|
166
|
+
output[k] = (this as any)[k];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// canonical fields
|
|
170
|
+
output.name = this.name;
|
|
171
|
+
if (this.message !== undefined) output.message = this.message;
|
|
172
|
+
if (this.stack !== undefined) output.stack = this.stack;
|
|
173
|
+
if (this.cause !== undefined) {
|
|
174
|
+
if (this.cause instanceof Error) {
|
|
175
|
+
output.cause = {
|
|
176
|
+
name: this.cause.name,
|
|
177
|
+
message: this.cause.message,
|
|
178
|
+
stack: (this.cause as any).stack,
|
|
179
|
+
};
|
|
180
|
+
} else {
|
|
181
|
+
output.cause = this.cause;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return output;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
toString(): string {
|
|
189
|
+
// default to pretty print with small indent
|
|
190
|
+
return this.prettyPrint(2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
[util.inspect.custom](_depth: number, _options: any) {
|
|
194
|
+
return this.prettyPrint(); // or some concise string/JSON
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type StructuredErrorConstructor<
|
|
199
|
+
Tag extends string,
|
|
200
|
+
Shape extends PlainObject,
|
|
201
|
+
HasDefault extends boolean,
|
|
202
|
+
> = {
|
|
203
|
+
new (
|
|
204
|
+
args?: Shape extends Record<string, never>
|
|
205
|
+
? HasDefault extends true
|
|
206
|
+
? { cause?: unknown }
|
|
207
|
+
: { message?: string; cause?: unknown }
|
|
208
|
+
: HasDefault extends true
|
|
209
|
+
? Shape & { cause?: unknown }
|
|
210
|
+
: Shape & { message?: string; cause?: unknown }
|
|
211
|
+
): RichError & { readonly _tag: Tag } & Readonly<Shape>;
|
|
212
|
+
readonly defaultMessage?: string;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* StructuredError factory with automatic tag inference and payload typing.
|
|
217
|
+
*
|
|
218
|
+
* Creates a custom error class with:
|
|
219
|
+
* - A readonly `_tag` property for runtime type discrimination
|
|
220
|
+
* - Automatic stack trace capture
|
|
221
|
+
* - Cause chaining support
|
|
222
|
+
* - Pretty printing with `prettyPrint()` and `toString()`
|
|
223
|
+
* - JSON serialization with `toJSON()`
|
|
224
|
+
*
|
|
225
|
+
* @template Tag - The literal string tag type (automatically inferred from the tag parameter)
|
|
226
|
+
* @param tag - The unique identifier for this error type (used as the error name)
|
|
227
|
+
* @param defaultMessage - Optional default message to use when no message is provided in args
|
|
228
|
+
* @returns A constructor function that can be called directly or with a shape generic
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* // Without shape (tag auto-inferred)
|
|
232
|
+
* const NotFound = StructuredError("NotFound")
|
|
233
|
+
* throw new NotFound({ id: 1, message: "nope", cause: someError })
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* // With typed shape (tag auto-inferred, shape explicitly typed)
|
|
237
|
+
* const ValidationError = StructuredError("ValidationError")<{ field: string; code: string }>()
|
|
238
|
+
* throw new ValidationError({ field: "email", code: "INVALID", message: "Invalid email" })
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* // With default message (message cannot be overridden)
|
|
242
|
+
* const UpgradeRequired = StructuredError("UpgradeRequired", "Upgrade required to access this feature")
|
|
243
|
+
* throw new UpgradeRequired({ feature: "advanced" }) // message is automatically set and cannot be changed
|
|
244
|
+
*/
|
|
245
|
+
type StructuredErrorFactory<
|
|
246
|
+
Tag extends string,
|
|
247
|
+
HasDefault extends boolean,
|
|
248
|
+
> = StructuredErrorConstructor<Tag, Record<string, never>, HasDefault> &
|
|
249
|
+
(<Shape extends PlainObject = Record<string, never>>() => StructuredErrorConstructor<
|
|
250
|
+
Tag,
|
|
251
|
+
Shape,
|
|
252
|
+
HasDefault
|
|
253
|
+
>);
|
|
254
|
+
|
|
255
|
+
export function StructuredError<const Tag extends string>(
|
|
256
|
+
tag: Tag,
|
|
257
|
+
defaultMessage: string
|
|
258
|
+
): StructuredErrorFactory<Tag, true>;
|
|
259
|
+
export function StructuredError<const Tag extends string>(
|
|
260
|
+
tag: Tag
|
|
261
|
+
): StructuredErrorFactory<Tag, false>;
|
|
262
|
+
export function StructuredError<const Tag extends string>(tag: Tag, defaultMessage?: string) {
|
|
263
|
+
function createErrorClass<
|
|
264
|
+
Shape extends PlainObject = Record<string, never>,
|
|
265
|
+
>(): StructuredErrorConstructor<
|
|
266
|
+
Tag,
|
|
267
|
+
Shape,
|
|
268
|
+
typeof defaultMessage extends string ? true : false
|
|
269
|
+
> {
|
|
270
|
+
// create a unique symbol for this tag's args storage so different factories don't clash
|
|
271
|
+
const tagArgsSym = Symbol.for(`@StructuredError:tag:${tag}`);
|
|
272
|
+
|
|
273
|
+
class Tagged extends RichError {
|
|
274
|
+
// runtime readonly property for tag
|
|
275
|
+
public readonly _tag: Tag = tag as Tag;
|
|
276
|
+
static readonly defaultMessage = defaultMessage;
|
|
277
|
+
|
|
278
|
+
constructor(args?: Shape & { message?: string; cause?: unknown }) {
|
|
279
|
+
// ensure `_tag` isn't copied from args accidentally
|
|
280
|
+
const safeArgs =
|
|
281
|
+
args && typeof args === 'object'
|
|
282
|
+
? (() => {
|
|
283
|
+
const { _tag: _discard, ...rest } = args as any;
|
|
284
|
+
return rest;
|
|
285
|
+
})()
|
|
286
|
+
: args;
|
|
287
|
+
|
|
288
|
+
// Apply default message if no message provided
|
|
289
|
+
const finalArgs =
|
|
290
|
+
safeArgs && typeof safeArgs === 'object'
|
|
291
|
+
? { ...safeArgs, message: safeArgs.message ?? defaultMessage }
|
|
292
|
+
: defaultMessage
|
|
293
|
+
? { message: defaultMessage }
|
|
294
|
+
: safeArgs;
|
|
295
|
+
|
|
296
|
+
super(finalArgs);
|
|
297
|
+
// name the class for nicer stacks
|
|
298
|
+
try {
|
|
299
|
+
Object.defineProperty(this, 'name', { value: tag, configurable: true });
|
|
300
|
+
} catch {
|
|
301
|
+
(this as any).name = tag;
|
|
302
|
+
}
|
|
303
|
+
// mark as StructuredError with brand symbol
|
|
304
|
+
Object.defineProperty(this, _structuredSym, {
|
|
305
|
+
value: true,
|
|
306
|
+
enumerable: false,
|
|
307
|
+
writable: false,
|
|
308
|
+
});
|
|
309
|
+
// store tag args symbol to hide anything specific to this factory (non-enumerable)
|
|
310
|
+
Object.defineProperty(this, tagArgsSym, {
|
|
311
|
+
value: safeArgs,
|
|
312
|
+
enumerable: false,
|
|
313
|
+
writable: true,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// set prototype name (works in many engines)
|
|
319
|
+
try {
|
|
320
|
+
Object.defineProperty(Tagged, 'name', { value: String(tag) });
|
|
321
|
+
} catch {
|
|
322
|
+
(Tagged as any).name = tag;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return Tagged as unknown as StructuredErrorConstructor<
|
|
326
|
+
Tag,
|
|
327
|
+
Shape,
|
|
328
|
+
typeof defaultMessage extends string ? true : false
|
|
329
|
+
>;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Create a callable constructor: can be used directly or called with generics
|
|
333
|
+
type WithShape = <
|
|
334
|
+
Shape extends Record<string, any> = Record<string, never>,
|
|
335
|
+
>() => StructuredErrorConstructor<
|
|
336
|
+
Tag,
|
|
337
|
+
Shape,
|
|
338
|
+
typeof defaultMessage extends string ? true : false
|
|
339
|
+
>;
|
|
340
|
+
type Result = StructuredErrorConstructor<
|
|
341
|
+
Tag,
|
|
342
|
+
Record<string, never>,
|
|
343
|
+
typeof defaultMessage extends string ? true : false
|
|
344
|
+
> &
|
|
345
|
+
WithShape;
|
|
346
|
+
|
|
347
|
+
const baseClass = createErrorClass<Record<string, never>>();
|
|
348
|
+
|
|
349
|
+
// Use a Proxy to intercept calls and return shaped classes
|
|
350
|
+
const callable = new Proxy(baseClass, {
|
|
351
|
+
apply(_target, _thisArg, _args) {
|
|
352
|
+
// When called as a function (for generic type application), return a new class
|
|
353
|
+
// This happens when TypeScript sees: StructuredError("Tag")<Shape>()
|
|
354
|
+
return createErrorClass();
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return callable as Result;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Returns true if the error passed is an instance of a StructuredObject
|
|
363
|
+
*
|
|
364
|
+
* @param err the error object
|
|
365
|
+
* @returns true if err is a StructuredError
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* const UpgradeRequired = StructuredError("UpgradeRequired", "Upgrade required to access this feature")
|
|
369
|
+
* try {
|
|
370
|
+
* throw UpgradeRequired();
|
|
371
|
+
* } catch (ex) {
|
|
372
|
+
* if (isStructuredError(ex)) {
|
|
373
|
+
* console.log(ex._tag);
|
|
374
|
+
* }
|
|
375
|
+
* }
|
|
376
|
+
*/
|
|
377
|
+
export function isStructuredError(err: unknown): err is RichError & { _tag: string } {
|
|
378
|
+
return (
|
|
379
|
+
typeof err === 'object' &&
|
|
380
|
+
err !== null &&
|
|
381
|
+
(_structuredSym in err || err instanceof RichError) &&
|
|
382
|
+
typeof (err as any)._tag === 'string'
|
|
383
|
+
);
|
|
384
|
+
}
|
package/src/index.ts
CHANGED
package/src/json.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @param obj - The object to stringify
|
|
4
4
|
* @returns JSON string representation
|
|
5
5
|
*/
|
|
6
|
-
export function safeStringify(obj: unknown): string {
|
|
6
|
+
export function safeStringify(obj: unknown, space?: number | string): string {
|
|
7
7
|
const visited = new WeakSet();
|
|
8
8
|
|
|
9
9
|
function replacer(_key: string, value: unknown): unknown {
|
|
@@ -22,5 +22,5 @@ export function safeStringify(obj: unknown): string {
|
|
|
22
22
|
return value;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
return JSON.stringify(obj, replacer);
|
|
25
|
+
return JSON.stringify(obj, replacer, space);
|
|
26
26
|
}
|
|
@@ -34,7 +34,7 @@ describe('VectorStorageService', () => {
|
|
|
34
34
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
35
35
|
|
|
36
36
|
await expect(service.upsert('my-vectors')).rejects.toThrow(
|
|
37
|
-
'
|
|
37
|
+
'Vector storage requires at least one document for this method'
|
|
38
38
|
);
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -43,7 +43,7 @@ describe('VectorStorageService', () => {
|
|
|
43
43
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
44
44
|
|
|
45
45
|
await expect(service.upsert('my-vectors', { key: '', document: 'text' })).rejects.toThrow(
|
|
46
|
-
'
|
|
46
|
+
'Vector storage requires each document to have a non-empty key'
|
|
47
47
|
);
|
|
48
48
|
});
|
|
49
49
|
|
|
@@ -53,7 +53,9 @@ describe('VectorStorageService', () => {
|
|
|
53
53
|
|
|
54
54
|
await expect(
|
|
55
55
|
service.upsert('my-vectors', { key: 'k1' } as unknown as VectorUpsertParams)
|
|
56
|
-
).rejects.toThrow(
|
|
56
|
+
).rejects.toThrow(
|
|
57
|
+
'Vector storage requires each document to have either embeddings or document property'
|
|
58
|
+
);
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
test('rejects empty embeddings array', async () => {
|
|
@@ -61,7 +63,7 @@ describe('VectorStorageService', () => {
|
|
|
61
63
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
62
64
|
|
|
63
65
|
await expect(service.upsert('my-vectors', { key: 'k1', embeddings: [] })).rejects.toThrow(
|
|
64
|
-
'
|
|
66
|
+
'Vector storage requires each embeddings property to have a non-empty array of numbers'
|
|
65
67
|
);
|
|
66
68
|
});
|
|
67
69
|
|
|
@@ -70,7 +72,7 @@ describe('VectorStorageService', () => {
|
|
|
70
72
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
71
73
|
|
|
72
74
|
await expect(service.upsert('my-vectors', { key: 'k1', document: ' ' })).rejects.toThrow(
|
|
73
|
-
'
|
|
75
|
+
'Vector storage requires each document property to have a non-empty string value'
|
|
74
76
|
);
|
|
75
77
|
});
|
|
76
78
|
});
|
|
@@ -90,7 +92,7 @@ describe('VectorStorageService', () => {
|
|
|
90
92
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
91
93
|
|
|
92
94
|
await expect(service.get('my-vectors', '')).rejects.toThrow(
|
|
93
|
-
'
|
|
95
|
+
'Vector storage key is required and must be a non-empty string'
|
|
94
96
|
);
|
|
95
97
|
});
|
|
96
98
|
});
|
|
@@ -119,7 +121,7 @@ describe('VectorStorageService', () => {
|
|
|
119
121
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
120
122
|
|
|
121
123
|
await expect(service.getMany('my-vectors', 'key1', '', 'key3')).rejects.toThrow(
|
|
122
|
-
'
|
|
124
|
+
'Vector storage requires all keys to be non-empty strings'
|
|
123
125
|
);
|
|
124
126
|
});
|
|
125
127
|
});
|
|
@@ -139,7 +141,7 @@ describe('VectorStorageService', () => {
|
|
|
139
141
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
140
142
|
|
|
141
143
|
await expect(service.search('my-vectors', { query: '' })).rejects.toThrow(
|
|
142
|
-
'
|
|
144
|
+
'Vector storage query property is required and must be a non-empty string'
|
|
143
145
|
);
|
|
144
146
|
});
|
|
145
147
|
|
|
@@ -148,7 +150,7 @@ describe('VectorStorageService', () => {
|
|
|
148
150
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
149
151
|
|
|
150
152
|
await expect(service.search('my-vectors', { query: 'test', limit: -5 })).rejects.toThrow(
|
|
151
|
-
'
|
|
153
|
+
'Vector storage limit property must be positive number'
|
|
152
154
|
);
|
|
153
155
|
});
|
|
154
156
|
|
|
@@ -157,7 +159,7 @@ describe('VectorStorageService', () => {
|
|
|
157
159
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
158
160
|
|
|
159
161
|
await expect(service.search('my-vectors', { query: 'test', limit: 0 })).rejects.toThrow(
|
|
160
|
-
'
|
|
162
|
+
'Vector storage limit property must be positive number'
|
|
161
163
|
);
|
|
162
164
|
});
|
|
163
165
|
|
|
@@ -167,7 +169,9 @@ describe('VectorStorageService', () => {
|
|
|
167
169
|
|
|
168
170
|
await expect(
|
|
169
171
|
service.search('my-vectors', { query: 'test', similarity: -0.1 })
|
|
170
|
-
).rejects.toThrow(
|
|
172
|
+
).rejects.toThrow(
|
|
173
|
+
'Vector storage similarity property must be a number between 0.0 and 1.0'
|
|
174
|
+
);
|
|
171
175
|
});
|
|
172
176
|
|
|
173
177
|
test('rejects similarity > 1', async () => {
|
|
@@ -176,7 +180,9 @@ describe('VectorStorageService', () => {
|
|
|
176
180
|
|
|
177
181
|
await expect(
|
|
178
182
|
service.search('my-vectors', { query: 'test', similarity: 1.5 })
|
|
179
|
-
).rejects.toThrow(
|
|
183
|
+
).rejects.toThrow(
|
|
184
|
+
'Vector storage similarity property must be a number between 0.0 and 1.0'
|
|
185
|
+
);
|
|
180
186
|
});
|
|
181
187
|
|
|
182
188
|
test('rejects invalid metadata', async () => {
|
|
@@ -188,7 +194,7 @@ describe('VectorStorageService', () => {
|
|
|
188
194
|
query: 'test',
|
|
189
195
|
metadata: null as unknown as Record<string, unknown>,
|
|
190
196
|
})
|
|
191
|
-
).rejects.toThrow('
|
|
197
|
+
).rejects.toThrow('Vector storage metadata property must be a valid object');
|
|
192
198
|
});
|
|
193
199
|
|
|
194
200
|
test('accepts boundary similarity values', async () => {
|
|
@@ -221,7 +227,7 @@ describe('VectorStorageService', () => {
|
|
|
221
227
|
const service = new VectorStorageService(baseUrl, adapter);
|
|
222
228
|
|
|
223
229
|
await expect(service.delete('my-vectors', 'key1', '', 'key3')).rejects.toThrow(
|
|
224
|
-
'
|
|
230
|
+
'Vector storage requires all keys to be non-empty strings'
|
|
225
231
|
);
|
|
226
232
|
});
|
|
227
233
|
|
package/src/services/_util.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { safeStringify } from '../json';
|
|
2
|
-
import type { Body } from './adapter';
|
|
2
|
+
import type { Body, HttpMethod } from './adapter';
|
|
3
3
|
import { ServiceException } from './exception';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Valid HTTP methods for service calls
|
|
7
|
-
*/
|
|
8
|
-
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
9
|
-
|
|
10
5
|
export const buildUrl = (
|
|
11
6
|
base: string,
|
|
12
7
|
path: string,
|
|
@@ -29,13 +24,26 @@ export async function toServiceException(
|
|
|
29
24
|
method: HttpMethod,
|
|
30
25
|
url: string,
|
|
31
26
|
response: Response
|
|
32
|
-
): Promise<ServiceException
|
|
27
|
+
): Promise<InstanceType<typeof ServiceException>> {
|
|
28
|
+
const sessionId = response.headers.get('x-session-id');
|
|
33
29
|
switch (response.status) {
|
|
34
30
|
case 401:
|
|
35
31
|
case 403:
|
|
36
|
-
return new ServiceException(
|
|
32
|
+
return new ServiceException({
|
|
33
|
+
message: 'Unauthorized',
|
|
34
|
+
method,
|
|
35
|
+
url,
|
|
36
|
+
statusCode: response.status,
|
|
37
|
+
sessionId,
|
|
38
|
+
});
|
|
37
39
|
case 404:
|
|
38
|
-
return new ServiceException(
|
|
40
|
+
return new ServiceException({
|
|
41
|
+
message: 'Not Found',
|
|
42
|
+
method,
|
|
43
|
+
url,
|
|
44
|
+
statusCode: response.status,
|
|
45
|
+
sessionId,
|
|
46
|
+
});
|
|
39
47
|
default:
|
|
40
48
|
}
|
|
41
49
|
const ct = response.headers.get('content-type');
|
|
@@ -43,24 +51,54 @@ export async function toServiceException(
|
|
|
43
51
|
try {
|
|
44
52
|
const payload = (await response.json()) as { message?: string; error?: string };
|
|
45
53
|
if (payload.error) {
|
|
46
|
-
return new ServiceException(
|
|
54
|
+
return new ServiceException({
|
|
55
|
+
message: payload.error,
|
|
56
|
+
method,
|
|
57
|
+
url,
|
|
58
|
+
statusCode: response.status,
|
|
59
|
+
sessionId,
|
|
60
|
+
});
|
|
47
61
|
}
|
|
48
62
|
if (payload.message) {
|
|
49
|
-
return new ServiceException(
|
|
63
|
+
return new ServiceException({
|
|
64
|
+
message: payload.message,
|
|
65
|
+
method,
|
|
66
|
+
url,
|
|
67
|
+
statusCode: response.status,
|
|
68
|
+
sessionId,
|
|
69
|
+
});
|
|
50
70
|
}
|
|
51
|
-
return new ServiceException(
|
|
71
|
+
return new ServiceException({
|
|
72
|
+
message: JSON.stringify(payload),
|
|
73
|
+
method,
|
|
74
|
+
url,
|
|
75
|
+
statusCode: response.status,
|
|
76
|
+
sessionId,
|
|
77
|
+
});
|
|
52
78
|
} catch {
|
|
53
79
|
/** don't worry */
|
|
54
80
|
}
|
|
55
81
|
}
|
|
56
82
|
try {
|
|
57
83
|
const body = await response.text();
|
|
58
|
-
return new ServiceException(
|
|
84
|
+
return new ServiceException({
|
|
85
|
+
message: body,
|
|
86
|
+
method,
|
|
87
|
+
url,
|
|
88
|
+
statusCode: response.status,
|
|
89
|
+
sessionId,
|
|
90
|
+
});
|
|
59
91
|
} catch {
|
|
60
92
|
/* fall through */
|
|
61
93
|
}
|
|
62
94
|
|
|
63
|
-
return new ServiceException(
|
|
95
|
+
return new ServiceException({
|
|
96
|
+
message: response.statusText,
|
|
97
|
+
method,
|
|
98
|
+
url,
|
|
99
|
+
statusCode: response.status,
|
|
100
|
+
sessionId,
|
|
101
|
+
});
|
|
64
102
|
}
|
|
65
103
|
|
|
66
104
|
const binaryContentType = 'application/octet-stream';
|
|
@@ -126,10 +164,10 @@ export async function fromResponse<T>(
|
|
|
126
164
|
if (contentType?.includes(binaryContentType)) {
|
|
127
165
|
return (await response.arrayBuffer()) as T;
|
|
128
166
|
}
|
|
129
|
-
throw new ServiceException(
|
|
130
|
-
`Unsupported content-type: ${contentType}`,
|
|
167
|
+
throw new ServiceException({
|
|
168
|
+
message: `Unsupported content-type: ${contentType}`,
|
|
131
169
|
method,
|
|
132
170
|
url,
|
|
133
|
-
response.status
|
|
134
|
-
);
|
|
171
|
+
statusCode: response.status,
|
|
172
|
+
});
|
|
135
173
|
}
|
package/src/services/adapter.ts
CHANGED
|
@@ -14,8 +14,10 @@ export type FetchResponse<T> = FetchErrorResponse | FetchSuccessResponse<T>;
|
|
|
14
14
|
|
|
15
15
|
export type Body = string | Buffer | ArrayBuffer | ReadableStream;
|
|
16
16
|
|
|
17
|
+
export type HttpMethod = 'GET' | 'PUT' | 'POST' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'PATCH';
|
|
18
|
+
|
|
17
19
|
export interface FetchRequest {
|
|
18
|
-
method:
|
|
20
|
+
method: HttpMethod;
|
|
19
21
|
body?: Body;
|
|
20
22
|
signal?: AbortSignal;
|
|
21
23
|
contentType?: string;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { StructuredError } from '../error';
|
|
2
|
+
import { HttpMethod } from './adapter';
|
|
3
|
+
|
|
4
|
+
export const ServiceException = StructuredError('ServiceException')<{
|
|
2
5
|
statusCode: number;
|
|
3
|
-
method:
|
|
6
|
+
method: HttpMethod;
|
|
4
7
|
url: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.method = method;
|
|
8
|
-
this.url = url;
|
|
9
|
-
this.statusCode = statusCode;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
8
|
+
sessionId?: string | null;
|
|
9
|
+
}>();
|
package/src/services/index.ts
CHANGED