@veloxts/orm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 VeloxTS Framework Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,566 @@
1
+ # @veloxts/orm
2
+
3
+ Laravel-inspired Prisma wrapper with enhanced developer experience for the VeloxTS framework.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @veloxts/orm @prisma/client
9
+ # or
10
+ pnpm add @veloxts/orm @prisma/client
11
+ ```
12
+
13
+ Note: `@prisma/client` is a peer dependency. You'll also need the `prisma` CLI as a dev dependency:
14
+
15
+ ```bash
16
+ npm install -D prisma
17
+ # or
18
+ pnpm add -D prisma
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Initialize Prisma
24
+
25
+ ```bash
26
+ npx prisma init
27
+ ```
28
+
29
+ This creates a `prisma` directory with a `schema.prisma` file.
30
+
31
+ ### 2. Define Your Schema
32
+
33
+ Edit `prisma/schema.prisma`:
34
+
35
+ ```prisma
36
+ datasource db {
37
+ provider = "postgresql"
38
+ url = env("DATABASE_URL")
39
+ }
40
+
41
+ generator client {
42
+ provider = "prisma-client-js"
43
+ }
44
+
45
+ model User {
46
+ id String @id @default(uuid())
47
+ name String
48
+ email String @unique
49
+ createdAt DateTime @default(now())
50
+ updatedAt DateTime @updatedAt
51
+ }
52
+ ```
53
+
54
+ ### 3. Generate Prisma Client
55
+
56
+ ```bash
57
+ npx prisma generate
58
+ ```
59
+
60
+ ### 4. Integrate with VeloxTS
61
+
62
+ ```typescript
63
+ import { createVeloxApp } from '@veloxts/core';
64
+ import { createDatabasePlugin } from '@veloxts/orm';
65
+ import { PrismaClient } from '@prisma/client';
66
+
67
+ // Create Prisma client
68
+ const prisma = new PrismaClient();
69
+
70
+ // Create VeloxTS app
71
+ const app = await createVeloxApp({
72
+ port: 3000,
73
+ logger: true,
74
+ });
75
+
76
+ // Register database plugin
77
+ await app.register(createDatabasePlugin({ client: prisma }));
78
+
79
+ // Start server
80
+ await app.start();
81
+ console.log(`Server running on ${app.address}`);
82
+ ```
83
+
84
+ ## Core API
85
+
86
+ ### `createDatabasePlugin(config)`
87
+
88
+ Creates a VeloxTS plugin that integrates Prisma with automatic lifecycle management.
89
+
90
+ **Configuration:**
91
+
92
+ ```typescript
93
+ interface OrmPluginConfig {
94
+ client: PrismaClient; // Your Prisma client instance
95
+ connect?: boolean; // Auto-connect on startup (default: true)
96
+ disconnect?: boolean; // Auto-disconnect on shutdown (default: true)
97
+ }
98
+ ```
99
+
100
+ **Example:**
101
+
102
+ ```typescript
103
+ import { createDatabasePlugin } from '@veloxts/orm';
104
+ import { PrismaClient } from '@prisma/client';
105
+
106
+ const prisma = new PrismaClient({
107
+ log: ['query', 'error', 'warn'],
108
+ });
109
+
110
+ const dbPlugin = createDatabasePlugin({
111
+ client: prisma,
112
+ connect: true, // Connect when app starts
113
+ disconnect: true, // Disconnect on graceful shutdown
114
+ });
115
+
116
+ await app.register(dbPlugin);
117
+ ```
118
+
119
+ ### Context Extension
120
+
121
+ The database plugin extends the VeloxTS context with a `db` property:
122
+
123
+ ```typescript
124
+ // Type definition is automatically available
125
+ declare module '@veloxts/core' {
126
+ interface BaseContext {
127
+ db: PrismaClient;
128
+ }
129
+ }
130
+
131
+ // Use in procedures
132
+ export const userProcedures = defineProcedures('users', {
133
+ getUser: procedure()
134
+ .input(z.object({ id: z.string().uuid() }))
135
+ .output(UserSchema)
136
+ .query(async ({ input, ctx }) => {
137
+ // ctx.db is fully typed as PrismaClient
138
+ const user = await ctx.db.user.findUnique({
139
+ where: { id: input.id },
140
+ });
141
+
142
+ if (!user) {
143
+ throw new NotFoundError('User', input.id);
144
+ }
145
+
146
+ return user;
147
+ }),
148
+ });
149
+ ```
150
+
151
+ ## Database Queries
152
+
153
+ The `ctx.db` object provides full access to Prisma's query API:
154
+
155
+ ### Finding Records
156
+
157
+ ```typescript
158
+ // Find unique record
159
+ const user = await ctx.db.user.findUnique({
160
+ where: { id: userId },
161
+ });
162
+
163
+ // Find unique or throw
164
+ const user = await ctx.db.user.findUniqueOrThrow({
165
+ where: { id: userId },
166
+ });
167
+
168
+ // Find first matching record
169
+ const user = await ctx.db.user.findFirst({
170
+ where: { email: 'alice@example.com' },
171
+ });
172
+
173
+ // Find many with filters
174
+ const users = await ctx.db.user.findMany({
175
+ where: {
176
+ email: { contains: '@example.com' },
177
+ createdAt: { gte: new Date('2025-01-01') },
178
+ },
179
+ orderBy: { createdAt: 'desc' },
180
+ take: 10,
181
+ skip: 0,
182
+ });
183
+ ```
184
+
185
+ ### Creating Records
186
+
187
+ ```typescript
188
+ // Create single record
189
+ const user = await ctx.db.user.create({
190
+ data: {
191
+ name: 'Alice',
192
+ email: 'alice@example.com',
193
+ },
194
+ });
195
+
196
+ // Create with relations
197
+ const post = await ctx.db.post.create({
198
+ data: {
199
+ title: 'Hello World',
200
+ content: 'My first post',
201
+ author: {
202
+ connect: { id: userId },
203
+ },
204
+ },
205
+ include: { author: true }, // Include related data
206
+ });
207
+ ```
208
+
209
+ ### Updating Records
210
+
211
+ ```typescript
212
+ // Update single record
213
+ const user = await ctx.db.user.update({
214
+ where: { id: userId },
215
+ data: { name: 'Alice Smith' },
216
+ });
217
+
218
+ // Update many
219
+ const result = await ctx.db.user.updateMany({
220
+ where: { email: { contains: '@old-domain.com' } },
221
+ data: { email: 'migrated@new-domain.com' },
222
+ });
223
+ console.log(`Updated ${result.count} users`);
224
+
225
+ // Upsert (update or create)
226
+ const user = await ctx.db.user.upsert({
227
+ where: { email: 'alice@example.com' },
228
+ update: { name: 'Alice Updated' },
229
+ create: {
230
+ name: 'Alice',
231
+ email: 'alice@example.com',
232
+ },
233
+ });
234
+ ```
235
+
236
+ ### Deleting Records
237
+
238
+ ```typescript
239
+ // Delete single record
240
+ const user = await ctx.db.user.delete({
241
+ where: { id: userId },
242
+ });
243
+
244
+ // Delete many
245
+ const result = await ctx.db.user.deleteMany({
246
+ where: { createdAt: { lt: new Date('2024-01-01') } },
247
+ });
248
+ console.log(`Deleted ${result.count} users`);
249
+ ```
250
+
251
+ ### Aggregations and Counting
252
+
253
+ ```typescript
254
+ // Count records
255
+ const count = await ctx.db.user.count({
256
+ where: { email: { contains: '@example.com' } },
257
+ });
258
+
259
+ // Aggregate
260
+ const stats = await ctx.db.post.aggregate({
261
+ _count: true,
262
+ _avg: { views: true },
263
+ _sum: { views: true },
264
+ where: { published: true },
265
+ });
266
+ ```
267
+
268
+ ### Transactions
269
+
270
+ ```typescript
271
+ // Transaction with array of operations
272
+ const [user, post] = await ctx.db.$transaction([
273
+ ctx.db.user.create({ data: { name: 'Alice', email: 'alice@example.com' } }),
274
+ ctx.db.post.create({ data: { title: 'Hello', content: 'World' } }),
275
+ ]);
276
+
277
+ // Interactive transaction
278
+ const result = await ctx.db.$transaction(async (tx) => {
279
+ const user = await tx.user.create({
280
+ data: { name: 'Bob', email: 'bob@example.com' },
281
+ });
282
+
283
+ const post = await tx.post.create({
284
+ data: {
285
+ title: 'Bob\'s Post',
286
+ content: 'Hello from Bob',
287
+ authorId: user.id,
288
+ },
289
+ });
290
+
291
+ return { user, post };
292
+ });
293
+ ```
294
+
295
+ ## Database Migrations
296
+
297
+ VeloxTS provides CLI commands for managing database migrations via Prisma:
298
+
299
+ ### Development Workflow
300
+
301
+ ```bash
302
+ # Create migration after schema changes
303
+ npx prisma migrate dev --name add_user_table
304
+
305
+ # Apply migrations
306
+ velox migrate
307
+
308
+ # Force push schema (dev only, skips migrations)
309
+ velox migrate --force
310
+
311
+ # Reset database (WARNING: deletes all data)
312
+ npx prisma migrate reset
313
+ ```
314
+
315
+ ### Production Workflow
316
+
317
+ ```bash
318
+ # Deploy pending migrations
319
+ velox migrate --deploy
320
+
321
+ # Or use Prisma directly
322
+ npx prisma migrate deploy
323
+ ```
324
+
325
+ ## Manual Connection Management
326
+
327
+ For advanced use cases, you can manually manage connections:
328
+
329
+ ```typescript
330
+ import { createDatabase } from '@veloxts/orm';
331
+ import { PrismaClient } from '@prisma/client';
332
+
333
+ const db = createDatabase({
334
+ client: new PrismaClient(),
335
+ });
336
+
337
+ // Manually connect
338
+ await db.connect();
339
+ console.log('Connected:', db.isConnected);
340
+
341
+ // Use the client
342
+ const users = await db.client.user.findMany();
343
+
344
+ // Check connection status
345
+ const status = db.getStatus();
346
+ console.log(status);
347
+ // {
348
+ // state: 'connected',
349
+ // connectedAt: Date,
350
+ // disconnectedAt: null,
351
+ // errorCount: 0,
352
+ // lastError: null
353
+ // }
354
+
355
+ // Manually disconnect
356
+ await db.disconnect();
357
+ ```
358
+
359
+ ## Practical Examples
360
+
361
+ ### CRUD Procedures
362
+
363
+ Complete example of CRUD operations:
364
+
365
+ ```typescript
366
+ import { defineProcedures, procedure } from '@veloxts/router';
367
+ import { z, paginationInputSchema } from '@veloxts/validation';
368
+ import { NotFoundError } from '@veloxts/core';
369
+
370
+ const UserSchema = z.object({
371
+ id: z.string().uuid(),
372
+ name: z.string(),
373
+ email: z.string().email(),
374
+ createdAt: z.string().datetime(),
375
+ updatedAt: z.string().datetime(),
376
+ });
377
+
378
+ export const userProcedures = defineProcedures('users', {
379
+ // GET /users/:id
380
+ getUser: procedure()
381
+ .input(z.object({ id: z.string().uuid() }))
382
+ .output(UserSchema)
383
+ .query(async ({ input, ctx }) => {
384
+ const user = await ctx.db.user.findUnique({
385
+ where: { id: input.id },
386
+ });
387
+
388
+ if (!user) {
389
+ throw new NotFoundError('User', input.id);
390
+ }
391
+
392
+ return user;
393
+ }),
394
+
395
+ // GET /users
396
+ listUsers: procedure()
397
+ .input(paginationInputSchema)
398
+ .output(z.object({
399
+ data: z.array(UserSchema),
400
+ meta: z.object({
401
+ page: z.number(),
402
+ limit: z.number(),
403
+ total: z.number(),
404
+ }),
405
+ }))
406
+ .query(async ({ input, ctx }) => {
407
+ const skip = (input.page - 1) * input.limit;
408
+
409
+ const [data, total] = await Promise.all([
410
+ ctx.db.user.findMany({
411
+ skip,
412
+ take: input.limit,
413
+ orderBy: { createdAt: 'desc' },
414
+ }),
415
+ ctx.db.user.count(),
416
+ ]);
417
+
418
+ return {
419
+ data,
420
+ meta: { page: input.page, limit: input.limit, total },
421
+ };
422
+ }),
423
+
424
+ // POST /users
425
+ createUser: procedure()
426
+ .input(z.object({
427
+ name: z.string().min(1),
428
+ email: z.string().email(),
429
+ }))
430
+ .output(UserSchema)
431
+ .mutation(async ({ input, ctx }) => {
432
+ return ctx.db.user.create({ data: input });
433
+ }),
434
+
435
+ // PUT /users/:id (deferred to v1.1+)
436
+ // DELETE /users/:id (deferred to v1.1+)
437
+ });
438
+ ```
439
+
440
+ ### Relationships
441
+
442
+ Working with related data:
443
+
444
+ ```typescript
445
+ // Schema with relations
446
+ model User {
447
+ id String @id @default(uuid())
448
+ name String
449
+ posts Post[]
450
+ }
451
+
452
+ model Post {
453
+ id String @id @default(uuid())
454
+ title String
455
+ authorId String
456
+ author User @relation(fields: [authorId], references: [id])
457
+ }
458
+
459
+ // Query with relations
460
+ const userProcedures = defineProcedures('users', {
461
+ getUserWithPosts: procedure()
462
+ .input(z.object({ id: z.string().uuid() }))
463
+ .query(async ({ input, ctx }) => {
464
+ return ctx.db.user.findUnique({
465
+ where: { id: input.id },
466
+ include: {
467
+ posts: {
468
+ orderBy: { createdAt: 'desc' },
469
+ take: 10,
470
+ },
471
+ },
472
+ });
473
+ }),
474
+ });
475
+ ```
476
+
477
+ ## Configuration Best Practices
478
+
479
+ ### Environment Variables
480
+
481
+ Store connection strings in `.env`:
482
+
483
+ ```env
484
+ DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
485
+ ```
486
+
487
+ ### Production Setup
488
+
489
+ ```typescript
490
+ const prisma = new PrismaClient({
491
+ log: process.env.NODE_ENV === 'development'
492
+ ? ['query', 'error', 'warn']
493
+ : ['error'],
494
+ errorFormat: 'minimal',
495
+ });
496
+
497
+ const dbPlugin = createDatabasePlugin({
498
+ client: prisma,
499
+ connect: true,
500
+ disconnect: true,
501
+ });
502
+ ```
503
+
504
+ ### Connection Pooling
505
+
506
+ Prisma handles connection pooling automatically. Configure in `schema.prisma`:
507
+
508
+ ```prisma
509
+ datasource db {
510
+ provider = "postgresql"
511
+ url = env("DATABASE_URL")
512
+ // Connection pool settings via URL parameters:
513
+ // ?connection_limit=10&pool_timeout=20
514
+ }
515
+ ```
516
+
517
+ ## Error Handling
518
+
519
+ The ORM plugin integrates with VeloxTS's error handling:
520
+
521
+ ```typescript
522
+ import { NotFoundError, VeloxError } from '@veloxts/core';
523
+
524
+ try {
525
+ const user = await ctx.db.user.findUniqueOrThrow({
526
+ where: { id: userId },
527
+ });
528
+ } catch (error) {
529
+ if (error.code === 'P2025') {
530
+ // Prisma "Record not found" error
531
+ throw new NotFoundError('User', userId);
532
+ }
533
+ throw new VeloxError('Database error', 500);
534
+ }
535
+ ```
536
+
537
+ ## MVP Limitations
538
+
539
+ The current v0.1.0 release focuses on core functionality:
540
+
541
+ **Included:**
542
+ - Prisma client integration
543
+ - Context extension (`ctx.db`)
544
+ - Connection lifecycle management
545
+ - Basic migration commands
546
+
547
+ **Deferred to v1.1+:**
548
+ - Database seeding utilities
549
+ - Migration rollback via CLI
550
+ - Query logging middleware
551
+ - Advanced connection pooling configuration
552
+
553
+ ## Related Packages
554
+
555
+ - [@veloxts/core](/packages/core) - Core framework with context system
556
+ - [@veloxts/router](/packages/router) - Procedure definitions using database queries
557
+ - [@veloxts/validation](/packages/validation) - Schema validation for inputs/outputs
558
+ - [@veloxts/cli](/packages/cli) - CLI with `velox migrate` command
559
+
560
+ ## TypeScript Support
561
+
562
+ All exports are fully typed with comprehensive JSDoc documentation. The package includes type definitions and declaration maps for excellent IDE support.
563
+
564
+ ## License
565
+
566
+ MIT
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Database client wrapper for Prisma
3
+ *
4
+ * Provides lifecycle management and connection state tracking
5
+ * for Prisma clients in a type-safe manner.
6
+ *
7
+ * @module client
8
+ */
9
+ import type { ConnectionStatus, DatabaseClient, DatabaseWrapperConfig } from './types.js';
10
+ /**
11
+ * Wrapped database client with connection lifecycle management
12
+ *
13
+ * @template TClient - Type of the underlying Prisma client
14
+ */
15
+ export interface Database<TClient extends DatabaseClient> {
16
+ /**
17
+ * The underlying Prisma client instance
18
+ *
19
+ * Use this for all database queries. The client is always available,
20
+ * but queries will fail if not connected.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const db = createDatabase({ client: prisma });
25
+ * await db.connect();
26
+ *
27
+ * const users = await db.client.user.findMany();
28
+ * ```
29
+ */
30
+ readonly client: TClient;
31
+ /**
32
+ * Current connection status
33
+ */
34
+ readonly status: ConnectionStatus;
35
+ /**
36
+ * Whether the database is connected
37
+ *
38
+ * Convenience getter equivalent to `status.isConnected`
39
+ */
40
+ readonly isConnected: boolean;
41
+ /**
42
+ * Establishes connection to the database
43
+ *
44
+ * @throws {VeloxError} If already connected or connection fails
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const db = createDatabase({ client: prisma });
49
+ * await db.connect();
50
+ * console.log(db.isConnected); // true
51
+ * ```
52
+ */
53
+ connect(): Promise<void>;
54
+ /**
55
+ * Disconnects from the database
56
+ *
57
+ * @throws {VeloxError} If not connected or disconnection fails
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * await db.disconnect();
62
+ * console.log(db.isConnected); // false
63
+ * ```
64
+ */
65
+ disconnect(): Promise<void>;
66
+ }
67
+ /**
68
+ * Creates a database wrapper with connection lifecycle management
69
+ *
70
+ * This wrapper provides:
71
+ * - Connection state tracking
72
+ * - Controlled connect/disconnect methods
73
+ * - Type-safe access to the underlying client
74
+ *
75
+ * The client is NOT automatically connected - call `connect()` explicitly
76
+ * or use `createDatabasePlugin` for automatic lifecycle management.
77
+ *
78
+ * @template TClient - Type of the Prisma client
79
+ * @param config - Database configuration with client instance
80
+ * @returns Database wrapper with lifecycle management
81
+ *
82
+ * @throws {VeloxError} If config is invalid or client doesn't implement DatabaseClient
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * import { PrismaClient } from '@prisma/client';
87
+ * import { createDatabase } from '@veloxts/orm';
88
+ *
89
+ * const prisma = new PrismaClient();
90
+ * const db = createDatabase({ client: prisma });
91
+ *
92
+ * // Manual connection management
93
+ * await db.connect();
94
+ * const users = await db.client.user.findMany();
95
+ * await db.disconnect();
96
+ * ```
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * // Check connection status
101
+ * const db = createDatabase({ client: prisma });
102
+ *
103
+ * console.log(db.isConnected); // false
104
+ * console.log(db.status.state); // 'disconnected'
105
+ *
106
+ * await db.connect();
107
+ *
108
+ * console.log(db.isConnected); // true
109
+ * console.log(db.status.state); // 'connected'
110
+ * console.log(db.status.connectedAt); // Date
111
+ * ```
112
+ */
113
+ export declare function createDatabase<TClient extends DatabaseClient>(config: DatabaseWrapperConfig<TClient>): Database<TClient>;
114
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAEV,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAOpB;;;;GAIG;AACH,MAAM,WAAW,QAAQ,CAAC,OAAO,SAAS,cAAc;IACtD;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAEzB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAElC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B;;;;;;;;;;;OAWG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB;;;;;;;;;;OAUG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,cAAc,EAC3D,MAAM,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACrC,QAAQ,CAAC,OAAO,CAAC,CAgInB"}