@vibeorm/runtime 1.0.1 → 1.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 +19 -0
- package/package.json +1 -1
- package/src/adapter.ts +33 -1
- package/src/client.ts +88 -33
- package/src/errors.ts +427 -6
- package/src/index.ts +15 -1
- package/src/lateral-join-builder.ts +157 -81
- package/src/query-builder.ts +573 -194
- package/src/relation-loader.ts +54 -20
- package/src/types.ts +25 -0
- package/src/where-builder.ts +56 -21
package/src/errors.ts
CHANGED
|
@@ -1,12 +1,159 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VibeORM
|
|
2
|
+
* VibeORM Error Hierarchy
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* All VibeORM errors extend the abstract VibeError base class, split into
|
|
5
|
+
* two concrete branches:
|
|
6
|
+
*
|
|
7
|
+
* - VibeRequestError — deterministic failures caused by invalid data or
|
|
8
|
+
* violated constraints. Running the same operation again will produce
|
|
9
|
+
* the same error. Includes: unique constraint, FK violation, not-null,
|
|
10
|
+
* check constraint, not-found, and validation errors.
|
|
11
|
+
*
|
|
12
|
+
* - VibeTransientError — transient infrastructure failures where retrying
|
|
13
|
+
* the same operation may succeed. Includes: connection errors, deadlocks,
|
|
14
|
+
* serialization failures, statement timeouts, and pool exhaustion.
|
|
15
|
+
*
|
|
16
|
+
* VibeValidationError (Zod validation) is a subclass of VibeRequestError,
|
|
17
|
+
* so `instanceof VibeRequestError` catches both constraint violations AND
|
|
18
|
+
* validation errors. Use `instanceof VibeValidationError` to narrow.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { VibeRequestError, VibeTransientError, VibeError } from "@vibeorm/runtime";
|
|
23
|
+
*
|
|
24
|
+
* try {
|
|
25
|
+
* await db.user.create({ data: { email: "taken@example.com" } });
|
|
26
|
+
* } catch (error) {
|
|
27
|
+
* if (error instanceof VibeRequestError) {
|
|
28
|
+
* if (error.code === "UNIQUE_CONSTRAINT") {
|
|
29
|
+
* console.log(error.meta.constraint); // "User_email_key"
|
|
30
|
+
* console.log(error.meta.detail); // 'Key (email)=(taken@example.com) already exists.'
|
|
31
|
+
* return { error: "Email already taken" };
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* if (error instanceof VibeTransientError) {
|
|
35
|
+
* // error.retryable is always true
|
|
36
|
+
* return retry(() => db.user.create({ ... }));
|
|
37
|
+
* }
|
|
38
|
+
* throw error;
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// ─── Error Codes ─────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Error codes for deterministic request failures.
|
|
47
|
+
* The operation itself is invalid — changing the data would fix it.
|
|
48
|
+
*/
|
|
49
|
+
export type VibeRequestErrorCode =
|
|
50
|
+
| "UNIQUE_CONSTRAINT"
|
|
51
|
+
| "FOREIGN_KEY_VIOLATION"
|
|
52
|
+
| "NOT_NULL_VIOLATION"
|
|
53
|
+
| "CHECK_CONSTRAINT"
|
|
54
|
+
| "NOT_FOUND"
|
|
55
|
+
| "VALIDATION_ERROR"
|
|
56
|
+
| "UNKNOWN_REQUEST_ERROR";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Error codes for transient infrastructure failures.
|
|
60
|
+
* The operation is valid but the infrastructure failed — retrying may fix it.
|
|
61
|
+
*/
|
|
62
|
+
export type VibeTransientErrorCode =
|
|
63
|
+
| "CONNECTION_ERROR"
|
|
64
|
+
| "DEADLOCK"
|
|
65
|
+
| "SERIALIZATION_FAILURE"
|
|
66
|
+
| "STATEMENT_TIMEOUT"
|
|
67
|
+
| "TOO_MANY_CONNECTIONS"
|
|
68
|
+
| "UNKNOWN_TRANSIENT_ERROR";
|
|
69
|
+
|
|
70
|
+
/** Union of all VibeORM error codes. */
|
|
71
|
+
export type VibeErrorCode = VibeRequestErrorCode | VibeTransientErrorCode;
|
|
72
|
+
|
|
73
|
+
// ─── Error Meta ──────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Structured metadata attached to every VibeORM error.
|
|
77
|
+
* Fields are populated when available from the PostgreSQL error protocol
|
|
78
|
+
* or from the application-level context (model name, operation, etc.).
|
|
79
|
+
*/
|
|
80
|
+
export type VibeErrorMeta = {
|
|
81
|
+
/** VibeORM model name (e.g. "User", "Post"). Set for app-level errors. */
|
|
82
|
+
model?: string;
|
|
83
|
+
/** The field that caused the error (extracted from PG detail when possible). */
|
|
84
|
+
field?: string;
|
|
85
|
+
/** PostgreSQL constraint name (e.g. "User_email_key"). */
|
|
86
|
+
constraint?: string;
|
|
87
|
+
/** PostgreSQL table name from the error (e.g. "User"). */
|
|
88
|
+
table?: string;
|
|
89
|
+
/** PostgreSQL column name from the error. */
|
|
90
|
+
column?: string;
|
|
91
|
+
/** PostgreSQL schema name from the error (e.g. "public"). */
|
|
92
|
+
schema?: string;
|
|
93
|
+
/** Human-readable detail from PostgreSQL (e.g. 'Key (email)=(x@y.com) already exists.'). */
|
|
94
|
+
detail?: string;
|
|
95
|
+
/** The VibeORM operation that triggered this error (e.g. "create", "update"). */
|
|
96
|
+
operation?: string;
|
|
97
|
+
/** Validation direction — only set on VibeValidationError. */
|
|
98
|
+
direction?: "input" | "output";
|
|
99
|
+
/** Raw Zod error object — only set on VibeValidationError. */
|
|
100
|
+
zodError?: unknown;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ─── Base Class ──────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Abstract base class for all VibeORM errors.
|
|
107
|
+
* Use `instanceof VibeError` to catch any error originating from VibeORM.
|
|
108
|
+
*/
|
|
109
|
+
export abstract class VibeError extends Error {
|
|
110
|
+
abstract readonly code: VibeErrorCode;
|
|
111
|
+
readonly meta: VibeErrorMeta;
|
|
112
|
+
|
|
113
|
+
constructor(params: { message: string; meta?: VibeErrorMeta; cause?: Error }) {
|
|
114
|
+
super(params.message, { cause: params.cause });
|
|
115
|
+
this.name = "VibeError";
|
|
116
|
+
this.meta = params.meta ?? {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Request Error (deterministic) ──────────────────────────────
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Deterministic request error — the operation itself is invalid.
|
|
124
|
+
* Same input will always produce the same failure.
|
|
125
|
+
*
|
|
126
|
+
* Covers: constraint violations, not-found, validation, and
|
|
127
|
+
* unrecognized database errors that aren't transient.
|
|
128
|
+
*
|
|
129
|
+
* Use `error.code` to narrow the specific failure type.
|
|
7
130
|
*/
|
|
131
|
+
export class VibeRequestError extends VibeError {
|
|
132
|
+
readonly code: VibeRequestErrorCode;
|
|
133
|
+
|
|
134
|
+
constructor(params: {
|
|
135
|
+
code: VibeRequestErrorCode;
|
|
136
|
+
message: string;
|
|
137
|
+
meta?: VibeErrorMeta;
|
|
138
|
+
cause?: Error;
|
|
139
|
+
}) {
|
|
140
|
+
super({ message: params.message, meta: params.meta, cause: params.cause });
|
|
141
|
+
this.name = "VibeRequestError";
|
|
142
|
+
this.code = params.code;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Validation Error (subclass of Request) ─────────────────────
|
|
8
147
|
|
|
9
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Zod validation error — thrown when input or output data fails schema validation.
|
|
150
|
+
*
|
|
151
|
+
* Subclass of VibeRequestError, so `instanceof VibeRequestError` catches it.
|
|
152
|
+
* Use `instanceof VibeValidationError` to narrow specifically to validation failures.
|
|
153
|
+
*
|
|
154
|
+
* Preserves backward-compatible fields: model, operation, direction, zodError.
|
|
155
|
+
*/
|
|
156
|
+
export class VibeValidationError extends VibeRequestError {
|
|
10
157
|
readonly model: string;
|
|
11
158
|
readonly operation: string;
|
|
12
159
|
readonly direction: "input" | "output";
|
|
@@ -20,7 +167,11 @@ export class VibeValidationError extends Error {
|
|
|
20
167
|
}) {
|
|
21
168
|
const { model, operation, direction, zodError } = params;
|
|
22
169
|
const msg = `Validation failed for ${model}.${operation} (${direction}): ${formatZodError({ error: zodError })}`;
|
|
23
|
-
super(
|
|
170
|
+
super({
|
|
171
|
+
code: "VALIDATION_ERROR",
|
|
172
|
+
message: msg,
|
|
173
|
+
meta: { model, operation, direction, zodError },
|
|
174
|
+
});
|
|
24
175
|
this.name = "VibeValidationError";
|
|
25
176
|
this.model = model;
|
|
26
177
|
this.operation = operation;
|
|
@@ -29,6 +180,276 @@ export class VibeValidationError extends Error {
|
|
|
29
180
|
}
|
|
30
181
|
}
|
|
31
182
|
|
|
183
|
+
// ─── Transient Error (retryable) ────────────────────────────────
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Transient infrastructure error — the operation is valid but the
|
|
187
|
+
* infrastructure failed. Retrying the same operation may succeed.
|
|
188
|
+
*
|
|
189
|
+
* Covers: connection errors, deadlocks, serialization failures,
|
|
190
|
+
* statement timeouts, and pool exhaustion.
|
|
191
|
+
*
|
|
192
|
+
* `retryable` is always `true` on this class.
|
|
193
|
+
*/
|
|
194
|
+
export class VibeTransientError extends VibeError {
|
|
195
|
+
readonly code: VibeTransientErrorCode;
|
|
196
|
+
readonly retryable = true as const;
|
|
197
|
+
|
|
198
|
+
constructor(params: {
|
|
199
|
+
code: VibeTransientErrorCode;
|
|
200
|
+
message: string;
|
|
201
|
+
meta?: VibeErrorMeta;
|
|
202
|
+
cause?: Error;
|
|
203
|
+
}) {
|
|
204
|
+
super({ message: params.message, meta: params.meta, cause: params.cause });
|
|
205
|
+
this.name = "VibeTransientError";
|
|
206
|
+
this.code = params.code;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ─── SQLSTATE → VibeError Mapping ───────────────────────────────
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* SQLSTATE code ranges for transient (retryable) errors.
|
|
214
|
+
* Class 08 = connection, Class 40 = transaction rollback,
|
|
215
|
+
* 57014 = query_canceled (statement_timeout), 53300 = too_many_connections.
|
|
216
|
+
*/
|
|
217
|
+
const TRANSIENT_CODE_MAP: Record<string, VibeTransientErrorCode> = {
|
|
218
|
+
"08000": "CONNECTION_ERROR",
|
|
219
|
+
"08001": "CONNECTION_ERROR",
|
|
220
|
+
"08003": "CONNECTION_ERROR",
|
|
221
|
+
"08004": "CONNECTION_ERROR",
|
|
222
|
+
"08006": "CONNECTION_ERROR",
|
|
223
|
+
"08007": "CONNECTION_ERROR",
|
|
224
|
+
"08P01": "CONNECTION_ERROR",
|
|
225
|
+
"40P01": "DEADLOCK",
|
|
226
|
+
"40001": "SERIALIZATION_FAILURE",
|
|
227
|
+
"57014": "STATEMENT_TIMEOUT",
|
|
228
|
+
"53300": "TOO_MANY_CONNECTIONS",
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* SQLSTATE codes for constraint violation errors (Class 23).
|
|
233
|
+
*/
|
|
234
|
+
const CONSTRAINT_CODE_MAP: Record<string, VibeRequestErrorCode> = {
|
|
235
|
+
"23505": "UNIQUE_CONSTRAINT",
|
|
236
|
+
"23503": "FOREIGN_KEY_VIOLATION",
|
|
237
|
+
"23502": "NOT_NULL_VIOLATION",
|
|
238
|
+
"23514": "CHECK_CONSTRAINT",
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Extract a field name from a PostgreSQL detail string.
|
|
243
|
+
*
|
|
244
|
+
* Examples:
|
|
245
|
+
* - 'Key (email)=(x@y.com) already exists.' → "email"
|
|
246
|
+
* - 'Failing row contains (1, null, ...).' → undefined
|
|
247
|
+
* - 'Key (author_id)=(999) is not present in table "User".' → "author_id"
|
|
248
|
+
*/
|
|
249
|
+
function extractFieldFromDetail(params: { detail: string }): string | undefined {
|
|
250
|
+
const match = params.detail.match(/Key \(([^)]+)\)/);
|
|
251
|
+
return match ? match[1] : undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Shape of a PostgreSQL protocol error as exposed by both bun:sql and node-postgres.
|
|
256
|
+
*
|
|
257
|
+
* Note: bun:sql puts the SQLSTATE code in `errno` (e.g. "23505") while `code`
|
|
258
|
+
* contains a Node.js-style string (e.g. "ERR_POSTGRES_SERVER_ERROR").
|
|
259
|
+
* node-postgres puts the SQLSTATE code in `code` directly.
|
|
260
|
+
*/
|
|
261
|
+
type PgProtocolError = {
|
|
262
|
+
code: string;
|
|
263
|
+
errno?: string;
|
|
264
|
+
message: string;
|
|
265
|
+
detail?: string;
|
|
266
|
+
hint?: string;
|
|
267
|
+
constraint?: string;
|
|
268
|
+
table?: string;
|
|
269
|
+
column?: string | number;
|
|
270
|
+
schema?: string;
|
|
271
|
+
severity?: string;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check whether a raw error object looks like a PostgreSQL protocol error.
|
|
276
|
+
* Both bun:sql (PostgresError) and node-postgres (DatabaseError) expose
|
|
277
|
+
* error fields from the PostgreSQL wire protocol. The SQLSTATE code is in
|
|
278
|
+
* `errno` (bun:sql) or `code` (node-postgres).
|
|
279
|
+
*/
|
|
280
|
+
function isPgError(error: unknown): error is PgProtocolError {
|
|
281
|
+
if (error === null || typeof error !== "object" || !("message" in error)) return false;
|
|
282
|
+
const e = error as Record<string, unknown>;
|
|
283
|
+
// node-postgres: has `code` as a 5-char SQLSTATE string
|
|
284
|
+
// bun:sql: has `errno` as a SQLSTATE string + `severity`
|
|
285
|
+
return (
|
|
286
|
+
(typeof e.code === "string" && /^[0-9A-Z]{5}$/.test(e.code)) ||
|
|
287
|
+
(typeof e.errno === "string" && typeof e.severity === "string")
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Extract the SQLSTATE code from a PgProtocolError.
|
|
293
|
+
* bun:sql stores it in `errno`, node-postgres stores it in `code`.
|
|
294
|
+
*/
|
|
295
|
+
function getSqlStateCode(error: PgProtocolError): string {
|
|
296
|
+
// bun:sql: errno contains the actual SQLSTATE code (e.g. "23505")
|
|
297
|
+
if (error.errno && /^[0-9A-Z]{5}$/.test(error.errno)) {
|
|
298
|
+
return error.errno;
|
|
299
|
+
}
|
|
300
|
+
// node-postgres: code contains the SQLSTATE code
|
|
301
|
+
if (/^[0-9A-Z]{5}$/.test(error.code)) {
|
|
302
|
+
return error.code;
|
|
303
|
+
}
|
|
304
|
+
return error.code;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check whether a raw error looks like a client-side connection error
|
|
309
|
+
* (e.g. ECONNREFUSED, ENOTFOUND, ETIMEDOUT) that doesn't have a SQLSTATE code.
|
|
310
|
+
*/
|
|
311
|
+
function isConnectionError(err: unknown): boolean {
|
|
312
|
+
if (err === null || typeof err !== "object") return false;
|
|
313
|
+
const anyErr = err as Record<string, unknown>;
|
|
314
|
+
|
|
315
|
+
// Node.js system errors from net/dns
|
|
316
|
+
if (typeof anyErr.code === "string") {
|
|
317
|
+
const code = anyErr.code;
|
|
318
|
+
if (
|
|
319
|
+
code === "ECONNREFUSED" ||
|
|
320
|
+
code === "ECONNRESET" ||
|
|
321
|
+
code === "ENOTFOUND" ||
|
|
322
|
+
code === "ETIMEDOUT" ||
|
|
323
|
+
code === "EPIPE"
|
|
324
|
+
) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// bun:sql connection-level errors often have specific message patterns
|
|
330
|
+
const msg = typeof anyErr.message === "string" ? anyErr.message : "";
|
|
331
|
+
if (
|
|
332
|
+
msg.includes("connection refused") ||
|
|
333
|
+
msg.includes("Connection terminated") ||
|
|
334
|
+
msg.includes("Connection lost") ||
|
|
335
|
+
msg.includes("connect ECONNREFUSED") ||
|
|
336
|
+
msg.includes("the database system is starting up")
|
|
337
|
+
) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Normalize a raw database error into a structured VibeORM error.
|
|
346
|
+
*
|
|
347
|
+
* Both bun:sql and node-postgres expose the PostgreSQL ErrorResponse fields
|
|
348
|
+
* (code, detail, constraint, table, column, schema, severity), so this
|
|
349
|
+
* function is adapter-agnostic.
|
|
350
|
+
*
|
|
351
|
+
* If the error is already a VibeError, it is returned as-is.
|
|
352
|
+
*
|
|
353
|
+
* @param error - The raw error from the database driver.
|
|
354
|
+
* @param model - Optional VibeORM model name for context.
|
|
355
|
+
* @param operation - Optional operation name for context.
|
|
356
|
+
*/
|
|
357
|
+
export function normalizeError(params: {
|
|
358
|
+
error: unknown;
|
|
359
|
+
model?: string;
|
|
360
|
+
operation?: string;
|
|
361
|
+
}): VibeRequestError | VibeTransientError {
|
|
362
|
+
const { error, model, operation } = params;
|
|
363
|
+
|
|
364
|
+
// Already a VibeError — return as-is (don't double-wrap)
|
|
365
|
+
if (error instanceof VibeError) {
|
|
366
|
+
return error as VibeRequestError | VibeTransientError;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
370
|
+
|
|
371
|
+
// ─── PostgreSQL protocol error (has SQLSTATE code) ───────────
|
|
372
|
+
if (isPgError(error)) {
|
|
373
|
+
const pgErr = error;
|
|
374
|
+
const pgCode = getSqlStateCode(pgErr);
|
|
375
|
+
const meta: VibeErrorMeta = {
|
|
376
|
+
model,
|
|
377
|
+
operation,
|
|
378
|
+
constraint: pgErr.constraint,
|
|
379
|
+
table: pgErr.table,
|
|
380
|
+
column: typeof pgErr.column === "string" ? pgErr.column : undefined,
|
|
381
|
+
schema: pgErr.schema,
|
|
382
|
+
detail: pgErr.detail,
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Extract field name from detail when available
|
|
386
|
+
if (pgErr.detail) {
|
|
387
|
+
const field = extractFieldFromDetail({ detail: pgErr.detail });
|
|
388
|
+
if (field) meta.field = field;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check transient errors first (Class 08, 40, 57014, 53300)
|
|
392
|
+
const transientCode = TRANSIENT_CODE_MAP[pgCode];
|
|
393
|
+
if (transientCode) {
|
|
394
|
+
return new VibeTransientError({
|
|
395
|
+
code: transientCode,
|
|
396
|
+
message: pgErr.message,
|
|
397
|
+
meta,
|
|
398
|
+
cause,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Check constraint violations (Class 23)
|
|
403
|
+
const constraintCode = CONSTRAINT_CODE_MAP[pgCode];
|
|
404
|
+
if (constraintCode) {
|
|
405
|
+
return new VibeRequestError({
|
|
406
|
+
code: constraintCode,
|
|
407
|
+
message: pgErr.message,
|
|
408
|
+
meta,
|
|
409
|
+
cause,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Check transient by SQLSTATE class prefix
|
|
414
|
+
if (pgCode.startsWith("08") || pgCode.startsWith("40")) {
|
|
415
|
+
return new VibeTransientError({
|
|
416
|
+
code: pgCode.startsWith("08") ? "CONNECTION_ERROR" : "UNKNOWN_TRANSIENT_ERROR",
|
|
417
|
+
message: pgErr.message,
|
|
418
|
+
meta,
|
|
419
|
+
cause,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Unrecognized SQLSTATE — treat as request error
|
|
424
|
+
return new VibeRequestError({
|
|
425
|
+
code: "UNKNOWN_REQUEST_ERROR",
|
|
426
|
+
message: pgErr.message,
|
|
427
|
+
meta,
|
|
428
|
+
cause,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ─── Client-side connection error (no SQLSTATE) ──────────────
|
|
433
|
+
if (isConnectionError(error)) {
|
|
434
|
+
return new VibeTransientError({
|
|
435
|
+
code: "CONNECTION_ERROR",
|
|
436
|
+
message: cause.message,
|
|
437
|
+
meta: { model, operation },
|
|
438
|
+
cause,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ─── Unknown error — treat as request error ──────────────────
|
|
443
|
+
return new VibeRequestError({
|
|
444
|
+
code: "UNKNOWN_REQUEST_ERROR",
|
|
445
|
+
message: cause.message,
|
|
446
|
+
meta: { model, operation },
|
|
447
|
+
cause,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
452
|
+
|
|
32
453
|
function formatZodError(params: { error: unknown }): string {
|
|
33
454
|
const { error } = params;
|
|
34
455
|
if (error && typeof error === "object" && "issues" in error) {
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export { createClient } from "./client.ts";
|
|
11
|
-
export {
|
|
11
|
+
export {
|
|
12
|
+
VibeError,
|
|
13
|
+
VibeRequestError,
|
|
14
|
+
VibeTransientError,
|
|
15
|
+
VibeValidationError,
|
|
16
|
+
normalizeError,
|
|
17
|
+
} from "./errors.ts";
|
|
12
18
|
export {
|
|
13
19
|
loadRelationsWithLateralJoin,
|
|
14
20
|
executeLateralJoinQuery,
|
|
@@ -19,8 +25,16 @@ export type {
|
|
|
19
25
|
DatabaseAdapter,
|
|
20
26
|
QueryResult,
|
|
21
27
|
SqlExecutor,
|
|
28
|
+
TransactionOptions,
|
|
22
29
|
} from "./adapter.ts";
|
|
23
30
|
|
|
31
|
+
export type {
|
|
32
|
+
VibeErrorCode,
|
|
33
|
+
VibeRequestErrorCode,
|
|
34
|
+
VibeTransientErrorCode,
|
|
35
|
+
VibeErrorMeta,
|
|
36
|
+
} from "./errors.ts";
|
|
37
|
+
|
|
24
38
|
export type {
|
|
25
39
|
VibeClientOptions,
|
|
26
40
|
ModelMeta,
|