@vibeorm/runtime 1.3.0 → 2.0.0-alpha.10
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 +50 -107
- package/dist/adapter.d.ts +250 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/bulk-upsert.d.ts +282 -0
- package/dist/bulk-upsert.d.ts.map +1 -0
- package/dist/client.d.ts +200 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/codecs.d.ts +170 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/computed.d.ts +43 -0
- package/dist/computed.d.ts.map +1 -0
- package/dist/db-now.d.ts +41 -0
- package/dist/db-now.d.ts.map +1 -0
- package/dist/diagnostics/index.d.ts +12 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/insight.d.ts +63 -0
- package/dist/diagnostics/insight.d.ts.map +1 -0
- package/dist/diagnostics/plan.d.ts +88 -0
- package/dist/diagnostics/plan.d.ts.map +1 -0
- package/dist/diagnostics/preview.d.ts +43 -0
- package/dist/diagnostics/preview.d.ts.map +1 -0
- package/dist/diagnostics/types.d.ts +223 -0
- package/dist/diagnostics/types.d.ts.map +1 -0
- package/dist/diagnostics/workload.d.ts +32 -0
- package/dist/diagnostics/workload.d.ts.map +1 -0
- package/dist/extensions.d.ts +102 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13070 -0
- package/dist/index.js.map +43 -0
- package/dist/keyset-iterator.d.ts +73 -0
- package/dist/keyset-iterator.d.ts.map +1 -0
- package/dist/keyset.d.ts +121 -0
- package/dist/keyset.d.ts.map +1 -0
- package/dist/model-meta.d.ts +200 -0
- package/dist/model-meta.d.ts.map +1 -0
- package/dist/nested-writes.d.ts +67 -0
- package/dist/nested-writes.d.ts.map +1 -0
- package/dist/policy-operation.d.ts +14 -0
- package/dist/policy-operation.d.ts.map +1 -0
- package/dist/policy.d.ts +17 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/query-builder.d.ts +271 -0
- package/dist/query-builder.d.ts.map +1 -0
- package/dist/relation-key.d.ts +23 -0
- package/dist/relation-key.d.ts.map +1 -0
- package/dist/relation-loader.d.ts +46 -0
- package/dist/relation-loader.d.ts.map +1 -0
- package/dist/relation-plan.d.ts +141 -0
- package/dist/relation-plan.d.ts.map +1 -0
- package/dist/render-cache.d.ts +48 -0
- package/dist/render-cache.d.ts.map +1 -0
- package/dist/rls-context.d.ts +14 -0
- package/dist/rls-context.d.ts.map +1 -0
- package/dist/rls-readiness.d.ts +114 -0
- package/dist/rls-readiness.d.ts.map +1 -0
- package/dist/scoped.d.ts +104 -0
- package/dist/scoped.d.ts.map +1 -0
- package/dist/strict-args.d.ts +47 -0
- package/dist/strict-args.d.ts.map +1 -0
- package/dist/telemetry/collector.d.ts +53 -0
- package/dist/telemetry/collector.d.ts.map +1 -0
- package/dist/telemetry/config.d.ts +53 -0
- package/dist/telemetry/config.d.ts.map +1 -0
- package/dist/telemetry/fingerprint.d.ts +38 -0
- package/dist/telemetry/fingerprint.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/recorder.d.ts +93 -0
- package/dist/telemetry/recorder.d.ts.map +1 -0
- package/dist/telemetry/statement.d.ts +53 -0
- package/dist/telemetry/statement.d.ts.map +1 -0
- package/dist/telemetry/types.d.ts +265 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/validators.d.ts +61 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/views.d.ts +97 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/write-scope.d.ts +14 -0
- package/dist/write-scope.d.ts.map +1 -0
- package/package.json +33 -26
- package/src/adapter.ts +0 -146
- package/src/client.ts +0 -2172
- package/src/coerce.ts +0 -184
- package/src/count-loader.ts +0 -152
- package/src/errors.ts +0 -492
- package/src/id-generators.ts +0 -151
- package/src/index.ts +0 -55
- package/src/lateral-join-builder.ts +0 -1053
- package/src/query-builder.ts +0 -1832
- package/src/relation-loader.ts +0 -534
- package/src/retry.ts +0 -183
- package/src/types.ts +0 -317
- package/src/view.ts +0 -629
- package/src/where-builder.ts +0 -772
package/src/errors.ts
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VibeORM Error Hierarchy
|
|
3
|
-
*
|
|
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
|
-
| "VALUE_OUT_OF_RANGE"
|
|
55
|
-
| "NOT_FOUND"
|
|
56
|
-
| "VALIDATION_ERROR"
|
|
57
|
-
| "UNKNOWN_REQUEST_ERROR";
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Error codes for transient infrastructure failures.
|
|
61
|
-
* The operation is valid but the infrastructure failed — retrying may fix it.
|
|
62
|
-
*/
|
|
63
|
-
export type VibeTransientErrorCode =
|
|
64
|
-
| "CONNECTION_ERROR"
|
|
65
|
-
| "DEADLOCK"
|
|
66
|
-
| "SERIALIZATION_FAILURE"
|
|
67
|
-
| "STATEMENT_TIMEOUT"
|
|
68
|
-
| "TOO_MANY_CONNECTIONS"
|
|
69
|
-
| "UNKNOWN_TRANSIENT_ERROR";
|
|
70
|
-
|
|
71
|
-
/** Union of all VibeORM error codes. */
|
|
72
|
-
export type VibeErrorCode = VibeRequestErrorCode | VibeTransientErrorCode;
|
|
73
|
-
|
|
74
|
-
// ─── Error Meta ──────────────────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Structured metadata attached to every VibeORM error.
|
|
78
|
-
* Fields are populated when available from the PostgreSQL error protocol
|
|
79
|
-
* or from the application-level context (model name, operation, etc.).
|
|
80
|
-
*/
|
|
81
|
-
export type VibeErrorMeta = {
|
|
82
|
-
/** VibeORM model name (e.g. "User", "Post"). Set for app-level errors. */
|
|
83
|
-
model?: string;
|
|
84
|
-
/** The field that caused the error (extracted from PG detail when possible). */
|
|
85
|
-
field?: string;
|
|
86
|
-
/** PostgreSQL constraint name (e.g. "User_email_key"). */
|
|
87
|
-
constraint?: string;
|
|
88
|
-
/** PostgreSQL table name from the error (e.g. "User"). */
|
|
89
|
-
table?: string;
|
|
90
|
-
/** PostgreSQL column name from the error. */
|
|
91
|
-
column?: string;
|
|
92
|
-
/** PostgreSQL schema name from the error (e.g. "public"). */
|
|
93
|
-
schema?: string;
|
|
94
|
-
/** Human-readable detail from PostgreSQL (e.g. 'Key (email)=(x@y.com) already exists.'). */
|
|
95
|
-
detail?: string;
|
|
96
|
-
/** The VibeORM operation that triggered this error (e.g. "create", "update"). */
|
|
97
|
-
operation?: string;
|
|
98
|
-
/** Validation direction — only set on VibeValidationError. */
|
|
99
|
-
direction?: "input" | "output";
|
|
100
|
-
/** Raw Zod error object — only set on VibeValidationError. */
|
|
101
|
-
zodError?: unknown;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// ─── Base Class ──────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Abstract base class for all VibeORM errors.
|
|
108
|
-
* Use `instanceof VibeError` to catch any error originating from VibeORM.
|
|
109
|
-
*/
|
|
110
|
-
export abstract class VibeError extends Error {
|
|
111
|
-
abstract readonly code: VibeErrorCode;
|
|
112
|
-
readonly meta: VibeErrorMeta;
|
|
113
|
-
|
|
114
|
-
constructor(params: { message: string; meta?: VibeErrorMeta; cause?: Error }) {
|
|
115
|
-
super(params.message, { cause: params.cause });
|
|
116
|
-
this.name = "VibeError";
|
|
117
|
-
this.meta = params.meta ?? {};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ─── Request Error (deterministic) ──────────────────────────────
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Deterministic request error — the operation itself is invalid.
|
|
125
|
-
* Same input will always produce the same failure.
|
|
126
|
-
*
|
|
127
|
-
* Covers: constraint violations, not-found, validation, and
|
|
128
|
-
* unrecognized database errors that aren't transient.
|
|
129
|
-
*
|
|
130
|
-
* Use `error.code` to narrow the specific failure type.
|
|
131
|
-
*/
|
|
132
|
-
export class VibeRequestError extends VibeError {
|
|
133
|
-
readonly code: VibeRequestErrorCode;
|
|
134
|
-
|
|
135
|
-
constructor(params: {
|
|
136
|
-
code: VibeRequestErrorCode;
|
|
137
|
-
message: string;
|
|
138
|
-
meta?: VibeErrorMeta;
|
|
139
|
-
cause?: Error;
|
|
140
|
-
}) {
|
|
141
|
-
super({ message: params.message, meta: params.meta, cause: params.cause });
|
|
142
|
-
this.name = "VibeRequestError";
|
|
143
|
-
this.code = params.code;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ─── Validation Error (subclass of Request) ─────────────────────
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Zod validation error — thrown when input or output data fails schema validation.
|
|
151
|
-
*
|
|
152
|
-
* Subclass of VibeRequestError, so `instanceof VibeRequestError` catches it.
|
|
153
|
-
* Use `instanceof VibeValidationError` to narrow specifically to validation failures.
|
|
154
|
-
*
|
|
155
|
-
* Preserves backward-compatible fields: model, operation, direction, zodError.
|
|
156
|
-
*/
|
|
157
|
-
export class VibeValidationError extends VibeRequestError {
|
|
158
|
-
readonly model: string;
|
|
159
|
-
readonly operation: string;
|
|
160
|
-
readonly direction: "input" | "output";
|
|
161
|
-
readonly zodError: unknown;
|
|
162
|
-
|
|
163
|
-
constructor(params: {
|
|
164
|
-
model: string;
|
|
165
|
-
operation: string;
|
|
166
|
-
direction: "input" | "output";
|
|
167
|
-
zodError: unknown;
|
|
168
|
-
}) {
|
|
169
|
-
const { model, operation, direction, zodError } = params;
|
|
170
|
-
const msg = `Validation failed for ${model}.${operation} (${direction}): ${formatZodError({ error: zodError })}`;
|
|
171
|
-
super({
|
|
172
|
-
code: "VALIDATION_ERROR",
|
|
173
|
-
message: msg,
|
|
174
|
-
meta: { model, operation, direction, zodError },
|
|
175
|
-
});
|
|
176
|
-
this.name = "VibeValidationError";
|
|
177
|
-
this.model = model;
|
|
178
|
-
this.operation = operation;
|
|
179
|
-
this.direction = direction;
|
|
180
|
-
this.zodError = zodError;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ─── Transient Error (retryable) ────────────────────────────────
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Transient infrastructure error — the operation is valid but the
|
|
188
|
-
* infrastructure failed. Retrying the same operation may succeed.
|
|
189
|
-
*
|
|
190
|
-
* Covers: connection errors, deadlocks, serialization failures,
|
|
191
|
-
* statement timeouts, and pool exhaustion.
|
|
192
|
-
*
|
|
193
|
-
* `retryable` is always `true` on this class.
|
|
194
|
-
*/
|
|
195
|
-
export class VibeTransientError extends VibeError {
|
|
196
|
-
readonly code: VibeTransientErrorCode;
|
|
197
|
-
readonly retryable = true as const;
|
|
198
|
-
|
|
199
|
-
constructor(params: {
|
|
200
|
-
code: VibeTransientErrorCode;
|
|
201
|
-
message: string;
|
|
202
|
-
meta?: VibeErrorMeta;
|
|
203
|
-
cause?: Error;
|
|
204
|
-
}) {
|
|
205
|
-
super({ message: params.message, meta: params.meta, cause: params.cause });
|
|
206
|
-
this.name = "VibeTransientError";
|
|
207
|
-
this.code = params.code;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ─── SQLSTATE → VibeError Mapping ───────────────────────────────
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* SQLSTATE code ranges for transient (retryable) errors.
|
|
215
|
-
* Class 08 = connection, Class 40 = transaction rollback,
|
|
216
|
-
* 57014 = query_canceled (statement_timeout), 53300 = too_many_connections.
|
|
217
|
-
*/
|
|
218
|
-
const TRANSIENT_CODE_MAP: Record<string, VibeTransientErrorCode> = {
|
|
219
|
-
"08000": "CONNECTION_ERROR",
|
|
220
|
-
"08001": "CONNECTION_ERROR",
|
|
221
|
-
"08003": "CONNECTION_ERROR",
|
|
222
|
-
"08004": "CONNECTION_ERROR",
|
|
223
|
-
"08006": "CONNECTION_ERROR",
|
|
224
|
-
"08007": "CONNECTION_ERROR",
|
|
225
|
-
"08P01": "CONNECTION_ERROR",
|
|
226
|
-
"40P01": "DEADLOCK",
|
|
227
|
-
"40001": "SERIALIZATION_FAILURE",
|
|
228
|
-
"57014": "STATEMENT_TIMEOUT",
|
|
229
|
-
"53300": "TOO_MANY_CONNECTIONS",
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* SQLSTATE codes for constraint violation errors (Class 23).
|
|
234
|
-
*/
|
|
235
|
-
const CONSTRAINT_CODE_MAP: Record<string, VibeRequestErrorCode> = {
|
|
236
|
-
"23505": "UNIQUE_CONSTRAINT",
|
|
237
|
-
"23503": "FOREIGN_KEY_VIOLATION",
|
|
238
|
-
"23502": "NOT_NULL_VIOLATION",
|
|
239
|
-
"23514": "CHECK_CONSTRAINT",
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* SQLSTATE Class 22 — data exception. We map the specific codes we want to
|
|
244
|
-
* surface as actionable errors. 22003 is the canonical "value out of range
|
|
245
|
-
* for type" code; the most common way users hit it is inserting a `BigInt`
|
|
246
|
-
* value larger than 2^63-1 into a `bigint` column (Bug 9).
|
|
247
|
-
*/
|
|
248
|
-
const DATA_EXCEPTION_CODE_MAP: Record<string, VibeRequestErrorCode> = {
|
|
249
|
-
"22003": "VALUE_OUT_OF_RANGE",
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Extract a field name from a PostgreSQL detail string.
|
|
254
|
-
*
|
|
255
|
-
* Examples:
|
|
256
|
-
* - 'Key (email)=(x@y.com) already exists.' → "email"
|
|
257
|
-
* - 'Failing row contains (1, null, ...).' → undefined
|
|
258
|
-
* - 'Key (author_id)=(999) is not present in table "User".' → "author_id"
|
|
259
|
-
*/
|
|
260
|
-
function extractFieldFromDetail(params: { detail: string }): string | undefined {
|
|
261
|
-
const match = params.detail.match(/Key \(([^)]+)\)/);
|
|
262
|
-
return match ? match[1] : undefined;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Shape of a PostgreSQL protocol error as exposed by both bun:sql and node-postgres.
|
|
267
|
-
*
|
|
268
|
-
* Note: bun:sql puts the SQLSTATE code in `errno` (e.g. "23505") while `code`
|
|
269
|
-
* contains a Node.js-style string (e.g. "ERR_POSTGRES_SERVER_ERROR").
|
|
270
|
-
* node-postgres puts the SQLSTATE code in `code` directly.
|
|
271
|
-
*/
|
|
272
|
-
type PgProtocolError = {
|
|
273
|
-
code: string;
|
|
274
|
-
errno?: string;
|
|
275
|
-
message: string;
|
|
276
|
-
detail?: string;
|
|
277
|
-
hint?: string;
|
|
278
|
-
constraint?: string;
|
|
279
|
-
table?: string;
|
|
280
|
-
column?: string | number;
|
|
281
|
-
schema?: string;
|
|
282
|
-
severity?: string;
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Check whether a raw error object looks like a PostgreSQL protocol error.
|
|
287
|
-
* Both bun:sql (PostgresError) and node-postgres (DatabaseError) expose
|
|
288
|
-
* error fields from the PostgreSQL wire protocol. The SQLSTATE code is in
|
|
289
|
-
* `errno` (bun:sql) or `code` (node-postgres).
|
|
290
|
-
*/
|
|
291
|
-
function isPgError(error: unknown): error is PgProtocolError {
|
|
292
|
-
if (error === null || typeof error !== "object" || !("message" in error)) return false;
|
|
293
|
-
const e = error as Record<string, unknown>;
|
|
294
|
-
// node-postgres: has `code` as a 5-char SQLSTATE string
|
|
295
|
-
// bun:sql: has `errno` as a SQLSTATE string + `severity`
|
|
296
|
-
return (
|
|
297
|
-
(typeof e.code === "string" && /^[0-9A-Z]{5}$/.test(e.code)) ||
|
|
298
|
-
(typeof e.errno === "string" && typeof e.severity === "string")
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Extract the SQLSTATE code from a PgProtocolError.
|
|
304
|
-
* bun:sql stores it in `errno`, node-postgres stores it in `code`.
|
|
305
|
-
*/
|
|
306
|
-
function getSqlStateCode(error: PgProtocolError): string {
|
|
307
|
-
// bun:sql: errno contains the actual SQLSTATE code (e.g. "23505")
|
|
308
|
-
if (error.errno && /^[0-9A-Z]{5}$/.test(error.errno)) {
|
|
309
|
-
return error.errno;
|
|
310
|
-
}
|
|
311
|
-
// node-postgres: code contains the SQLSTATE code
|
|
312
|
-
if (/^[0-9A-Z]{5}$/.test(error.code)) {
|
|
313
|
-
return error.code;
|
|
314
|
-
}
|
|
315
|
-
return error.code;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Check whether a raw error looks like a client-side connection error
|
|
320
|
-
* (e.g. ECONNREFUSED, ENOTFOUND, ETIMEDOUT) that doesn't have a SQLSTATE code.
|
|
321
|
-
*/
|
|
322
|
-
function isConnectionError(err: unknown): boolean {
|
|
323
|
-
if (err === null || typeof err !== "object") return false;
|
|
324
|
-
const anyErr = err as Record<string, unknown>;
|
|
325
|
-
|
|
326
|
-
// Node.js system errors from net/dns
|
|
327
|
-
if (typeof anyErr.code === "string") {
|
|
328
|
-
const code = anyErr.code;
|
|
329
|
-
if (
|
|
330
|
-
code === "ECONNREFUSED" ||
|
|
331
|
-
code === "ECONNRESET" ||
|
|
332
|
-
code === "ENOTFOUND" ||
|
|
333
|
-
code === "ETIMEDOUT" ||
|
|
334
|
-
code === "EPIPE"
|
|
335
|
-
) {
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// bun:sql connection-level errors often have specific message patterns
|
|
341
|
-
const msg = typeof anyErr.message === "string" ? anyErr.message : "";
|
|
342
|
-
if (
|
|
343
|
-
msg.includes("connection refused") ||
|
|
344
|
-
msg.includes("Connection terminated") ||
|
|
345
|
-
msg.includes("Connection lost") ||
|
|
346
|
-
msg.includes("connect ECONNREFUSED") ||
|
|
347
|
-
msg.includes("the database system is starting up")
|
|
348
|
-
) {
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return false;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Normalize a raw database error into a structured VibeORM error.
|
|
357
|
-
*
|
|
358
|
-
* Both bun:sql and node-postgres expose the PostgreSQL ErrorResponse fields
|
|
359
|
-
* (code, detail, constraint, table, column, schema, severity), so this
|
|
360
|
-
* function is adapter-agnostic.
|
|
361
|
-
*
|
|
362
|
-
* If the error is already a VibeError, it is returned as-is.
|
|
363
|
-
*
|
|
364
|
-
* @param error - The raw error from the database driver.
|
|
365
|
-
* @param model - Optional VibeORM model name for context.
|
|
366
|
-
* @param operation - Optional operation name for context.
|
|
367
|
-
*/
|
|
368
|
-
export function normalizeError(params: {
|
|
369
|
-
error: unknown;
|
|
370
|
-
model?: string;
|
|
371
|
-
operation?: string;
|
|
372
|
-
}): VibeRequestError | VibeTransientError {
|
|
373
|
-
const { error, model, operation } = params;
|
|
374
|
-
|
|
375
|
-
// Already a VibeError — return as-is (don't double-wrap)
|
|
376
|
-
if (error instanceof VibeError) {
|
|
377
|
-
return error as VibeRequestError | VibeTransientError;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const cause = error instanceof Error ? error : new Error(String(error));
|
|
381
|
-
|
|
382
|
-
// ─── PostgreSQL protocol error (has SQLSTATE code) ───────────
|
|
383
|
-
if (isPgError(error)) {
|
|
384
|
-
const pgErr = error;
|
|
385
|
-
const pgCode = getSqlStateCode(pgErr);
|
|
386
|
-
const meta: VibeErrorMeta = {
|
|
387
|
-
model,
|
|
388
|
-
operation,
|
|
389
|
-
constraint: pgErr.constraint,
|
|
390
|
-
table: pgErr.table,
|
|
391
|
-
column: typeof pgErr.column === "string" ? pgErr.column : undefined,
|
|
392
|
-
schema: pgErr.schema,
|
|
393
|
-
detail: pgErr.detail,
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
// Extract field name from detail when available
|
|
397
|
-
if (pgErr.detail) {
|
|
398
|
-
const field = extractFieldFromDetail({ detail: pgErr.detail });
|
|
399
|
-
if (field) meta.field = field;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Check transient errors first (Class 08, 40, 57014, 53300)
|
|
403
|
-
const transientCode = TRANSIENT_CODE_MAP[pgCode];
|
|
404
|
-
if (transientCode) {
|
|
405
|
-
return new VibeTransientError({
|
|
406
|
-
code: transientCode,
|
|
407
|
-
message: pgErr.message,
|
|
408
|
-
meta,
|
|
409
|
-
cause,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Check constraint violations (Class 23)
|
|
414
|
-
const constraintCode = CONSTRAINT_CODE_MAP[pgCode];
|
|
415
|
-
if (constraintCode) {
|
|
416
|
-
return new VibeRequestError({
|
|
417
|
-
code: constraintCode,
|
|
418
|
-
message: pgErr.message,
|
|
419
|
-
meta,
|
|
420
|
-
cause,
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Data exception (Class 22): e.g. value out of range for the column type.
|
|
425
|
-
// Specifically: 22003 is what users see when a BigInt value overflows the
|
|
426
|
-
// signed-int64 limit on a `bigint` column. We surface a targeted hint for
|
|
427
|
-
// that case because the raw PG message ("value out of range for type
|
|
428
|
-
// bigint") is easy to misread as a driver bug. Bug 9.
|
|
429
|
-
const dataExceptionCode = DATA_EXCEPTION_CODE_MAP[pgCode];
|
|
430
|
-
if (dataExceptionCode) {
|
|
431
|
-
const isBigIntRange =
|
|
432
|
-
dataExceptionCode === "VALUE_OUT_OF_RANGE" &&
|
|
433
|
-
/out of range for type bigint/i.test(pgErr.message);
|
|
434
|
-
const message = isBigIntRange
|
|
435
|
-
? `${pgErr.message} (PostgreSQL bigint is signed int64, max 0x7fffffffffffffff = 2^63-1. For unsigned 64-bit values, store as String or Decimal.)`
|
|
436
|
-
: pgErr.message;
|
|
437
|
-
return new VibeRequestError({
|
|
438
|
-
code: dataExceptionCode,
|
|
439
|
-
message,
|
|
440
|
-
meta,
|
|
441
|
-
cause,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Check transient by SQLSTATE class prefix
|
|
446
|
-
if (pgCode.startsWith("08") || pgCode.startsWith("40")) {
|
|
447
|
-
return new VibeTransientError({
|
|
448
|
-
code: pgCode.startsWith("08") ? "CONNECTION_ERROR" : "UNKNOWN_TRANSIENT_ERROR",
|
|
449
|
-
message: pgErr.message,
|
|
450
|
-
meta,
|
|
451
|
-
cause,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Unrecognized SQLSTATE — treat as request error
|
|
456
|
-
return new VibeRequestError({
|
|
457
|
-
code: "UNKNOWN_REQUEST_ERROR",
|
|
458
|
-
message: pgErr.message,
|
|
459
|
-
meta,
|
|
460
|
-
cause,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// ─── Client-side connection error (no SQLSTATE) ──────────────
|
|
465
|
-
if (isConnectionError(error)) {
|
|
466
|
-
return new VibeTransientError({
|
|
467
|
-
code: "CONNECTION_ERROR",
|
|
468
|
-
message: cause.message,
|
|
469
|
-
meta: { model, operation },
|
|
470
|
-
cause,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// ─── Unknown error — treat as request error ──────────────────
|
|
475
|
-
return new VibeRequestError({
|
|
476
|
-
code: "UNKNOWN_REQUEST_ERROR",
|
|
477
|
-
message: cause.message,
|
|
478
|
-
meta: { model, operation },
|
|
479
|
-
cause,
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// ─── Helpers ─────────────────────────────────────────────────────
|
|
484
|
-
|
|
485
|
-
function formatZodError(params: { error: unknown }): string {
|
|
486
|
-
const { error } = params;
|
|
487
|
-
if (error && typeof error === "object" && "issues" in error) {
|
|
488
|
-
const issues = (error as { issues: Array<{ path: (string | number)[]; message: string }> }).issues;
|
|
489
|
-
return issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
490
|
-
}
|
|
491
|
-
return String(error);
|
|
492
|
-
}
|
package/src/id-generators.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zero-dependency, cryptographically secure ID generators.
|
|
3
|
-
*
|
|
4
|
-
* Used by the runtime to auto-generate values for fields with
|
|
5
|
-
* @default(uuid()), @default(cuid()), @default(nanoid()), @default(ulid()).
|
|
6
|
-
*
|
|
7
|
-
* All implementations use crypto.getRandomValues() for security.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// ─── UUID v4 ──────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Generate a UUID v4 string.
|
|
14
|
-
* Uses crypto.randomUUID() when available (Node 19+, Bun, Deno, browsers),
|
|
15
|
-
* falls back to a manual implementation.
|
|
16
|
-
*/
|
|
17
|
-
export function generateUuid(): string {
|
|
18
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
19
|
-
return crypto.randomUUID();
|
|
20
|
-
}
|
|
21
|
-
// Manual fallback using crypto.getRandomValues
|
|
22
|
-
const bytes = new Uint8Array(16);
|
|
23
|
-
crypto.getRandomValues(bytes);
|
|
24
|
-
// Set version (4) and variant (RFC 4122)
|
|
25
|
-
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
|
|
26
|
-
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
27
|
-
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
28
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ─── CUID2 ────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Generate a CUID2-compatible string.
|
|
35
|
-
*
|
|
36
|
-
* CUID2 is a secure, collision-resistant ID optimized for horizontal scaling.
|
|
37
|
-
* This is a simplified implementation that produces IDs with the same properties:
|
|
38
|
-
* - Starts with a letter (for HTML element IDs and DB compatibility)
|
|
39
|
-
* - 24 characters long (default CUID2 length)
|
|
40
|
-
* - Cryptographically random
|
|
41
|
-
*
|
|
42
|
-
* Uses a base-36 encoding of random bytes with a letter prefix.
|
|
43
|
-
*/
|
|
44
|
-
const CUID_LENGTH = 24;
|
|
45
|
-
const CUID_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
46
|
-
|
|
47
|
-
export function generateCuid(): string {
|
|
48
|
-
// First char is always a letter
|
|
49
|
-
const firstByte = new Uint8Array(1);
|
|
50
|
-
crypto.getRandomValues(firstByte);
|
|
51
|
-
const prefix = CUID_ALPHABET[firstByte[0]! % 26]!;
|
|
52
|
-
|
|
53
|
-
// Remaining chars from random bytes encoded as base36
|
|
54
|
-
// We need enough random bytes to produce CUID_LENGTH - 1 base36 chars
|
|
55
|
-
// Each byte gives ~1.29 base36 chars, so we need ceil((CUID_LENGTH-1)/1.29) ≈ 18 bytes
|
|
56
|
-
const bytes = new Uint8Array(32);
|
|
57
|
-
crypto.getRandomValues(bytes);
|
|
58
|
-
|
|
59
|
-
let result = prefix;
|
|
60
|
-
// Convert bytes to a big number string in base36
|
|
61
|
-
let carry = 0n;
|
|
62
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
63
|
-
carry = (carry << 8n) | BigInt(bytes[i]!);
|
|
64
|
-
}
|
|
65
|
-
const base36 = carry.toString(36);
|
|
66
|
-
result += base36.slice(0, CUID_LENGTH - 1).padStart(CUID_LENGTH - 1, "a");
|
|
67
|
-
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ─── NanoID ───────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate a NanoID-compatible string.
|
|
75
|
-
*
|
|
76
|
-
* NanoID uses a URL-safe alphabet (A-Za-z0-9_-) and produces 21-character IDs.
|
|
77
|
-
* This is a high-performance implementation using bit masking for uniform distribution.
|
|
78
|
-
*/
|
|
79
|
-
const NANOID_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
80
|
-
const NANOID_LENGTH = 21;
|
|
81
|
-
const NANOID_MASK = 63; // 0x3F — 6 bits, matching alphabet size of 64
|
|
82
|
-
|
|
83
|
-
export function generateNanoid(): string {
|
|
84
|
-
const bytes = new Uint8Array(NANOID_LENGTH);
|
|
85
|
-
crypto.getRandomValues(bytes);
|
|
86
|
-
|
|
87
|
-
let id = "";
|
|
88
|
-
for (let i = 0; i < NANOID_LENGTH; i++) {
|
|
89
|
-
id += NANOID_ALPHABET[bytes[i]! & NANOID_MASK]!;
|
|
90
|
-
}
|
|
91
|
-
return id;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ─── ULID ─────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Generate a ULID (Universally Unique Lexicographically Sortable Identifier).
|
|
98
|
-
*
|
|
99
|
-
* Format: 10 chars timestamp (48-bit ms since epoch) + 16 chars randomness (80-bit)
|
|
100
|
-
* Encoding: Crockford's Base32 (0-9A-HJKMNP-TV-Z)
|
|
101
|
-
* Total: 26 characters, lexicographically sortable by creation time.
|
|
102
|
-
*/
|
|
103
|
-
const ULID_ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
104
|
-
const ULID_ENCODING_LEN = 32;
|
|
105
|
-
|
|
106
|
-
function encodeTime(params: { now: number; len: number }): string {
|
|
107
|
-
let { now, len } = params;
|
|
108
|
-
let str = "";
|
|
109
|
-
for (; len > 0; len--) {
|
|
110
|
-
str = ULID_ENCODING[now % ULID_ENCODING_LEN]! + str;
|
|
111
|
-
now = Math.floor(now / ULID_ENCODING_LEN);
|
|
112
|
-
}
|
|
113
|
-
return str;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function encodeRandom(params: { len: number }): string {
|
|
117
|
-
const { len } = params;
|
|
118
|
-
const bytes = new Uint8Array(len);
|
|
119
|
-
crypto.getRandomValues(bytes);
|
|
120
|
-
let str = "";
|
|
121
|
-
for (let i = 0; i < len; i++) {
|
|
122
|
-
str += ULID_ENCODING[bytes[i]! % ULID_ENCODING_LEN]!;
|
|
123
|
-
}
|
|
124
|
-
return str;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function generateUlid(): string {
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
return encodeTime({ now, len: 10 }) + encodeRandom({ len: 16 });
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ─── Dispatcher ───────────────────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Generate an ID value based on the default kind.
|
|
136
|
-
* Returns undefined for kinds that are handled at the DB level (autoincrement, now, literal).
|
|
137
|
-
*/
|
|
138
|
-
export function generateDefault(params: { kind: string }): string | undefined {
|
|
139
|
-
switch (params.kind) {
|
|
140
|
-
case "uuid":
|
|
141
|
-
return generateUuid();
|
|
142
|
-
case "cuid":
|
|
143
|
-
return generateCuid();
|
|
144
|
-
case "nanoid":
|
|
145
|
-
return generateNanoid();
|
|
146
|
-
case "ulid":
|
|
147
|
-
return generateUlid();
|
|
148
|
-
default:
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vibeorm/runtime — Driver-agnostic VibeORM client runtime.
|
|
3
|
-
*
|
|
4
|
-
* This package contains the core query engine, SQL builders, and relation
|
|
5
|
-
* loading strategies. It does NOT include any database driver — use one
|
|
6
|
-
* of the adapter packages (@vibeorm/adapter-bun, @vibeorm/adapter-pg)
|
|
7
|
-
* to connect to PostgreSQL.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export { createClient } from "./client.ts";
|
|
11
|
-
export {
|
|
12
|
-
VibeError,
|
|
13
|
-
VibeRequestError,
|
|
14
|
-
VibeTransientError,
|
|
15
|
-
VibeValidationError,
|
|
16
|
-
normalizeError,
|
|
17
|
-
} from "./errors.ts";
|
|
18
|
-
export {
|
|
19
|
-
loadRelationsWithLateralJoin,
|
|
20
|
-
executeLateralJoinQuery,
|
|
21
|
-
} from "./lateral-join-builder.ts";
|
|
22
|
-
export { toSqlExecutor } from "./adapter.ts";
|
|
23
|
-
export { withRetry } from "./retry.ts";
|
|
24
|
-
export { ViewResult, createView } from "./view.ts";
|
|
25
|
-
export type { ViewDefinition } from "./view.ts";
|
|
26
|
-
export type { RetryOptions } from "./retry.ts";
|
|
27
|
-
export { PgArray } from "./types.ts";
|
|
28
|
-
|
|
29
|
-
export type {
|
|
30
|
-
DatabaseAdapter,
|
|
31
|
-
QueryResult,
|
|
32
|
-
SqlExecutor,
|
|
33
|
-
TransactionOptions,
|
|
34
|
-
} from "./adapter.ts";
|
|
35
|
-
|
|
36
|
-
export type {
|
|
37
|
-
VibeErrorCode,
|
|
38
|
-
VibeRequestErrorCode,
|
|
39
|
-
VibeTransientErrorCode,
|
|
40
|
-
VibeErrorMeta,
|
|
41
|
-
} from "./errors.ts";
|
|
42
|
-
|
|
43
|
-
export type {
|
|
44
|
-
VibeClientOptions,
|
|
45
|
-
ModelMeta,
|
|
46
|
-
ModelMetaMap,
|
|
47
|
-
ModelSchemas,
|
|
48
|
-
ValidationSchema,
|
|
49
|
-
QueryProfile,
|
|
50
|
-
RelationProfile,
|
|
51
|
-
ScalarFieldMeta,
|
|
52
|
-
RelationFieldMeta,
|
|
53
|
-
SqlQuery,
|
|
54
|
-
Operation,
|
|
55
|
-
} from "./types.ts";
|