@vibeorm/runtime 1.0.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 +74 -0
- package/package.json +47 -0
- package/src/adapter.ts +114 -0
- package/src/client.ts +2055 -0
- package/src/errors.ts +39 -0
- package/src/id-generators.ts +151 -0
- package/src/index.ts +36 -0
- package/src/lateral-join-builder.ts +759 -0
- package/src/query-builder.ts +1417 -0
- package/src/relation-loader.ts +489 -0
- package/src/types.ts +290 -0
- package/src/where-builder.ts +737 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VibeORM Validation Error
|
|
3
|
+
*
|
|
4
|
+
* Thrown when Zod validation fails for query inputs or outputs.
|
|
5
|
+
* Contains structured information about which model, operation, and
|
|
6
|
+
* direction (input/output) the validation failure occurred in.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class VibeValidationError extends Error {
|
|
10
|
+
readonly model: string;
|
|
11
|
+
readonly operation: string;
|
|
12
|
+
readonly direction: "input" | "output";
|
|
13
|
+
readonly zodError: unknown;
|
|
14
|
+
|
|
15
|
+
constructor(params: {
|
|
16
|
+
model: string;
|
|
17
|
+
operation: string;
|
|
18
|
+
direction: "input" | "output";
|
|
19
|
+
zodError: unknown;
|
|
20
|
+
}) {
|
|
21
|
+
const { model, operation, direction, zodError } = params;
|
|
22
|
+
const msg = `Validation failed for ${model}.${operation} (${direction}): ${formatZodError({ error: zodError })}`;
|
|
23
|
+
super(msg);
|
|
24
|
+
this.name = "VibeValidationError";
|
|
25
|
+
this.model = model;
|
|
26
|
+
this.operation = operation;
|
|
27
|
+
this.direction = direction;
|
|
28
|
+
this.zodError = zodError;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatZodError(params: { error: unknown }): string {
|
|
33
|
+
const { error } = params;
|
|
34
|
+
if (error && typeof error === "object" && "issues" in error) {
|
|
35
|
+
const issues = (error as { issues: Array<{ path: (string | number)[]; message: string }> }).issues;
|
|
36
|
+
return issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
37
|
+
}
|
|
38
|
+
return String(error);
|
|
39
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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 { VibeValidationError } from "./errors.ts";
|
|
12
|
+
export {
|
|
13
|
+
loadRelationsWithLateralJoin,
|
|
14
|
+
executeLateralJoinQuery,
|
|
15
|
+
} from "./lateral-join-builder.ts";
|
|
16
|
+
export { toSqlExecutor } from "./adapter.ts";
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
DatabaseAdapter,
|
|
20
|
+
QueryResult,
|
|
21
|
+
SqlExecutor,
|
|
22
|
+
} from "./adapter.ts";
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
VibeClientOptions,
|
|
26
|
+
ModelMeta,
|
|
27
|
+
ModelMetaMap,
|
|
28
|
+
ModelSchemas,
|
|
29
|
+
ValidationSchema,
|
|
30
|
+
QueryProfile,
|
|
31
|
+
RelationProfile,
|
|
32
|
+
ScalarFieldMeta,
|
|
33
|
+
RelationFieldMeta,
|
|
34
|
+
SqlQuery,
|
|
35
|
+
Operation,
|
|
36
|
+
} from "./types.ts";
|