@hazeljs/prisma 0.2.0-beta.52 → 0.2.0-beta.54

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Prisma + HazelJS. Type-safe, no boilerplate.**
4
4
 
5
- Repository pattern, `@PrismaModel` decorator, DI integration. Full CRUD from your schema. Transactions, relations, pagination — the way you'd expect it to work.
5
+ Repository pattern, `@Repository` decorator, DI integration. Full CRUD from your schema. Transactions, relations, pagination — the way you'd expect it to work.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@hazeljs/prisma.svg)](https://www.npmjs.com/package/@hazeljs/prisma)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/prisma)](https://www.npmjs.com/package/@hazeljs/prisma)
@@ -12,13 +12,21 @@ Repository pattern, `@PrismaModel` decorator, DI integration. Full CRUD from you
12
12
 
13
13
  - 🎯 **Type-Safe Queries** - Full TypeScript support with Prisma
14
14
  - 🏗️ **Repository Pattern** - Clean data access layer
15
- - 🎨 **Decorator Support** - `@PrismaModel` decorator
15
+ - 🎨 **Decorator Support** - `@Repository` implies `@Injectable()` — one decorator does the job
16
16
  - 🔄 **Transaction Support** - Built-in transaction management
17
17
  - 📊 **Query Builder** - Fluent query interface
18
18
  - 🔌 **Dependency Injection** - Seamless DI integration
19
19
  - 🧪 **Testing Utilities** - Mock Prisma for testing
20
20
  - 📈 **Connection Pooling** - Automatic connection management
21
21
 
22
+ ## Decorator Convention
23
+
24
+ | Class type | Correct decorator |
25
+ |------------|------------------|
26
+ | Repository (`extends BaseRepository`) | `@Repository({ model: '...' })` — implies `@Injectable()` |
27
+ | Service (business logic) | `@Service()` |
28
+ | Controller | `@Controller(...)` |
29
+
22
30
  ## Installation
23
31
 
24
32
  ```bash
@@ -93,18 +101,18 @@ export class AppModule {}
93
101
 
94
102
  ### 5. Create Repository
95
103
 
104
+ `@Repository` implies `@Injectable()` — no need to add both decorators.
105
+
96
106
  ```typescript
97
- import { Injectable } from '@hazeljs/core';
98
- import { PrismaService, BaseRepository, PrismaModel } from '@hazeljs/prisma';
107
+ import { PrismaService, BaseRepository } from '@hazeljs/prisma';
108
+ import { Repository } from '@hazeljs/prisma';
99
109
 
100
- @Injectable()
101
- @PrismaModel('User')
102
- export class UserRepository extends BaseRepository {
110
+ @Repository({ model: 'user' })
111
+ export class UserRepository extends BaseRepository<User> {
103
112
  constructor(prisma: PrismaService) {
104
- super(prisma);
113
+ super(prisma, 'user');
105
114
  }
106
115
 
107
- // Custom methods
108
116
  async findByEmail(email: string) {
109
117
  return this.prisma.user.findUnique({ where: { email } });
110
118
  }
@@ -118,17 +126,22 @@ export class UserRepository extends BaseRepository {
118
126
  }
119
127
  ```
120
128
 
121
- ### 6. Use in Service
129
+ ### 6. Use in a Service
130
+
131
+ Use `@Service` for service classes — not `@Injectable`.
122
132
 
123
133
  ```typescript
124
- import { Injectable } from '@hazeljs/core';
134
+ import { Service } from '@hazeljs/core';
135
+ import { InjectRepository } from '@hazeljs/prisma';
125
136
 
126
- @Injectable()
137
+ @Service()
127
138
  export class UserService {
128
- constructor(private userRepository: UserRepository) {}
139
+ constructor(
140
+ @InjectRepository() private readonly userRepository: UserRepository,
141
+ ) {}
129
142
 
130
143
  async create(data: { email: string; name: string }) {
131
- return this.userRepository.create({ data });
144
+ return this.userRepository.create(data);
132
145
  }
133
146
 
134
147
  async findAll() {
@@ -136,18 +149,15 @@ export class UserService {
136
149
  }
137
150
 
138
151
  async findOne(id: string) {
139
- return this.userRepository.findUnique({ where: { id } });
152
+ return this.userRepository.findOne({ id });
140
153
  }
141
154
 
142
- async update(id: string, data: any) {
143
- return this.userRepository.update({
144
- where: { id },
145
- data,
146
- });
155
+ async update(id: string, data: Partial<User>) {
156
+ return this.userRepository.update({ id }, data);
147
157
  }
148
158
 
149
159
  async delete(id: string) {
150
- return this.userRepository.delete({ where: { id } });
160
+ return this.userRepository.delete({ id });
151
161
  }
152
162
  }
153
163
  ```
@@ -157,52 +167,47 @@ export class UserService {
157
167
  The `BaseRepository` provides common CRUD operations:
158
168
 
159
169
  ```typescript
160
- class BaseRepository {
170
+ class BaseRepository<T> {
161
171
  // Create
162
- create(args: Prisma.UserCreateArgs): Promise<User>;
163
- createMany(args: Prisma.UserCreateManyArgs): Promise<Prisma.BatchPayload>;
172
+ create(data: Omit<T, 'id'>): Promise<T>;
164
173
 
165
174
  // Read
166
- findUnique(args: Prisma.UserFindUniqueArgs): Promise<User | null>;
167
- findFirst(args: Prisma.UserFindFirstArgs): Promise<User | null>;
168
- findMany(args?: Prisma.UserFindManyArgs): Promise<User[]>;
169
- count(args?: Prisma.UserCountArgs): Promise<number>;
175
+ findMany(): Promise<T[]>;
176
+ findOne(where: WhereUniqueInput): Promise<T | null>;
177
+ count(args?: unknown): Promise<number>;
170
178
 
171
179
  // Update
172
- update(args: Prisma.UserUpdateArgs): Promise<User>;
173
- updateMany(args: Prisma.UserUpdateManyArgs): Promise<Prisma.BatchPayload>;
174
- upsert(args: Prisma.UserUpsertArgs): Promise<User>;
180
+ update(where: WhereUniqueInput, data: UpdateInput): Promise<T>;
175
181
 
176
182
  // Delete
177
- delete(args: Prisma.UserDeleteArgs): Promise<User>;
178
- deleteMany(args?: Prisma.UserDeleteManyArgs): Promise<Prisma.BatchPayload>;
183
+ delete(where: WhereUniqueInput): Promise<T>;
179
184
  }
180
185
  ```
181
186
 
182
187
  ## Transactions
183
188
 
184
- ### Using PrismaService
189
+ ### Using PrismaService directly
185
190
 
186
191
  ```typescript
187
- @Injectable()
192
+ import { Service } from '@hazeljs/core';
193
+ import { PrismaService } from '@hazeljs/prisma';
194
+
195
+ @Service()
188
196
  export class TransferService {
189
- constructor(private prisma: PrismaService) {}
197
+ constructor(private readonly prisma: PrismaService) {}
190
198
 
191
199
  async transfer(fromId: string, toId: string, amount: number) {
192
200
  return this.prisma.$transaction(async (tx) => {
193
- // Deduct from sender
194
201
  await tx.account.update({
195
202
  where: { id: fromId },
196
203
  data: { balance: { decrement: amount } },
197
204
  });
198
205
 
199
- // Add to receiver
200
206
  await tx.account.update({
201
207
  where: { id: toId },
202
208
  data: { balance: { increment: amount } },
203
209
  });
204
210
 
205
- // Create transaction record
206
211
  return tx.transaction.create({
207
212
  data: { fromId, toId, amount },
208
213
  });
@@ -211,28 +216,26 @@ export class TransferService {
211
216
  }
212
217
  ```
213
218
 
214
- ### Using Repository
219
+ ### Using Repositories inside a transaction
215
220
 
216
221
  ```typescript
217
- @Injectable()
222
+ import { Service } from '@hazeljs/core';
223
+ import { PrismaService } from '@hazeljs/prisma';
224
+
225
+ @Service()
218
226
  export class OrderService {
219
227
  constructor(
220
- private orderRepository: OrderRepository,
221
- private inventoryRepository: InventoryRepository,
222
- private prisma: PrismaService
228
+ private readonly orderRepository: OrderRepository,
229
+ private readonly inventoryRepository: InventoryRepository,
230
+ private readonly prisma: PrismaService,
223
231
  ) {}
224
232
 
225
233
  async createOrder(userId: string, items: OrderItem[]) {
226
234
  return this.prisma.$transaction(async (tx) => {
227
- // Create order
228
235
  const order = await tx.order.create({
229
- data: {
230
- userId,
231
- items: { create: items },
232
- },
236
+ data: { userId, items: { create: items } },
233
237
  });
234
238
 
235
- // Update inventory
236
239
  for (const item of items) {
237
240
  await tx.inventory.update({
238
241
  where: { productId: item.productId },
@@ -251,8 +254,12 @@ export class OrderService {
251
254
  ### Relations
252
255
 
253
256
  ```typescript
254
- @Injectable()
255
- export class UserRepository extends BaseRepository {
257
+ @Repository({ model: 'user' })
258
+ export class UserRepository extends BaseRepository<User> {
259
+ constructor(prisma: PrismaService) {
260
+ super(prisma, 'user');
261
+ }
262
+
256
263
  async findWithRelations(id: string) {
257
264
  return this.prisma.user.findUnique({
258
265
  where: { id },
@@ -271,28 +278,22 @@ export class UserRepository extends BaseRepository {
271
278
  ### Pagination
272
279
 
273
280
  ```typescript
274
- @Injectable()
275
- export class PostRepository extends BaseRepository {
281
+ @Repository({ model: 'post' })
282
+ export class PostRepository extends BaseRepository<Post> {
283
+ constructor(prisma: PrismaService) {
284
+ super(prisma, 'post');
285
+ }
286
+
276
287
  async findPaginated(page: number, limit: number) {
277
288
  const skip = (page - 1) * limit;
278
-
279
289
  const [posts, total] = await Promise.all([
280
- this.prisma.post.findMany({
281
- skip,
282
- take: limit,
283
- orderBy: { createdAt: 'desc' },
284
- }),
290
+ this.prisma.post.findMany({ skip, take: limit, orderBy: { createdAt: 'desc' } }),
285
291
  this.prisma.post.count(),
286
292
  ]);
287
293
 
288
294
  return {
289
295
  data: posts,
290
- meta: {
291
- page,
292
- limit,
293
- total,
294
- totalPages: Math.ceil(total / limit),
295
- },
296
+ meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
296
297
  };
297
298
  }
298
299
  }
@@ -301,8 +302,12 @@ export class PostRepository extends BaseRepository {
301
302
  ### Filtering
302
303
 
303
304
  ```typescript
304
- @Injectable()
305
- export class ProductRepository extends BaseRepository {
305
+ @Repository({ model: 'product' })
306
+ export class ProductRepository extends BaseRepository<Product> {
307
+ constructor(prisma: PrismaService) {
308
+ super(prisma, 'product');
309
+ }
310
+
306
311
  async search(query: string, filters: {
307
312
  category?: string;
308
313
  minPrice?: number;
@@ -312,12 +317,10 @@ export class ProductRepository extends BaseRepository {
312
317
  return this.prisma.product.findMany({
313
318
  where: {
314
319
  AND: [
315
- {
316
- OR: [
317
- { name: { contains: query, mode: 'insensitive' } },
318
- { description: { contains: query, mode: 'insensitive' } },
319
- ],
320
- },
320
+ { OR: [
321
+ { name: { contains: query, mode: 'insensitive' } },
322
+ { description: { contains: query, mode: 'insensitive' } },
323
+ ]},
321
324
  filters.category ? { category: filters.category } : {},
322
325
  filters.minPrice ? { price: { gte: filters.minPrice } } : {},
323
326
  filters.maxPrice ? { price: { lte: filters.maxPrice } } : {},
@@ -332,9 +335,12 @@ export class ProductRepository extends BaseRepository {
332
335
  ### Aggregations
333
336
 
334
337
  ```typescript
335
- @Injectable()
336
- export class AnalyticsRepository {
337
- constructor(private prisma: PrismaService) {}
338
+ import { Service } from '@hazeljs/core';
339
+ import { PrismaService } from '@hazeljs/prisma';
340
+
341
+ @Service()
342
+ export class AnalyticsService {
343
+ constructor(private readonly prisma: PrismaService) {}
338
344
 
339
345
  async getOrderStats() {
340
346
  return this.prisma.order.aggregate({
@@ -365,18 +371,18 @@ export class AnalyticsRepository {
365
371
  Add Prisma middleware for logging, soft deletes, etc:
366
372
 
367
373
  ```typescript
374
+ import { Injectable } from '@hazeljs/core';
375
+ import { PrismaClient } from '@prisma/client';
376
+
368
377
  @Injectable()
369
378
  export class PrismaService extends PrismaClient {
370
379
  constructor() {
371
380
  super();
372
381
 
373
- // Logging middleware
374
382
  this.$use(async (params, next) => {
375
383
  const before = Date.now();
376
384
  const result = await next(params);
377
- const after = Date.now();
378
-
379
- console.log(`Query ${params.model}.${params.action} took ${after - before}ms`);
385
+ console.log(`Query ${params.model}.${params.action} took ${Date.now() - before}ms`);
380
386
  return result;
381
387
  });
382
388
 
@@ -386,16 +392,6 @@ export class PrismaService extends PrismaClient {
386
392
  params.action = 'update';
387
393
  params.args['data'] = { deletedAt: new Date() };
388
394
  }
389
-
390
- if (params.action === 'deleteMany') {
391
- params.action = 'updateMany';
392
- if (params.args.data != undefined) {
393
- params.args.data['deletedAt'] = new Date();
394
- } else {
395
- params.args['data'] = { deletedAt: new Date() };
396
- }
397
- }
398
-
399
395
  return next(params);
400
396
  });
401
397
  }
@@ -411,41 +407,18 @@ import { PrismaClient } from '@prisma/client';
411
407
  const prisma = new PrismaClient();
412
408
 
413
409
  async function main() {
414
- // Create users
415
- const alice = await prisma.user.create({
410
+ await prisma.user.create({
416
411
  data: {
417
412
  email: 'alice@example.com',
418
413
  name: 'Alice',
419
- posts: {
420
- create: [
421
- {
422
- title: 'First Post',
423
- content: 'Hello World!',
424
- published: true,
425
- },
426
- ],
427
- },
428
- },
429
- });
430
-
431
- const bob = await prisma.user.create({
432
- data: {
433
- email: 'bob@example.com',
434
- name: 'Bob',
414
+ posts: { create: [{ title: 'First Post', content: 'Hello World!', published: true }] },
435
415
  },
436
416
  });
437
-
438
- console.log({ alice, bob });
439
417
  }
440
418
 
441
419
  main()
442
- .catch((e) => {
443
- console.error(e);
444
- process.exit(1);
445
- })
446
- .finally(async () => {
447
- await prisma.$disconnect();
448
- });
420
+ .catch((e) => { console.error(e); process.exit(1); })
421
+ .finally(() => prisma.$disconnect());
449
422
  ```
450
423
 
451
424
  Add to `package.json`:
@@ -464,114 +437,22 @@ Run seed:
464
437
  npx prisma db seed
465
438
  ```
466
439
 
467
- ## Testing
468
-
469
- ### Mock Prisma Service
470
-
471
- ```typescript
472
- import { TestingModule } from '@hazeljs/core';
473
- import { UserService } from './user.service';
474
- import { UserRepository } from './user.repository';
475
-
476
- describe('UserService', () => {
477
- let service: UserService;
478
- let repository: UserRepository;
479
-
480
- const mockPrisma = {
481
- user: {
482
- create: jest.fn(),
483
- findMany: jest.fn(),
484
- findUnique: jest.fn(),
485
- update: jest.fn(),
486
- delete: jest.fn(),
487
- },
488
- };
489
-
490
- beforeEach(async () => {
491
- const module = await TestingModule.create({
492
- providers: [
493
- UserService,
494
- {
495
- provide: UserRepository,
496
- useValue: {
497
- create: mockPrisma.user.create,
498
- findMany: mockPrisma.user.findMany,
499
- findUnique: mockPrisma.user.findUnique,
500
- update: mockPrisma.user.update,
501
- delete: mockPrisma.user.delete,
502
- },
503
- },
504
- ],
505
- });
506
-
507
- service = module.get(UserService);
508
- repository = module.get(UserRepository);
509
- });
510
-
511
- it('should create a user', async () => {
512
- const userData = { email: 'test@example.com', name: 'Test' };
513
- mockPrisma.user.create.mockResolvedValue({ id: '1', ...userData });
514
-
515
- const result = await service.create(userData);
516
-
517
- expect(result).toEqual({ id: '1', ...userData });
518
- expect(mockPrisma.user.create).toHaveBeenCalledWith({ data: userData });
519
- });
520
- });
521
- ```
522
-
523
- ## Best Practices
524
-
525
- 1. **Use Repositories** - Encapsulate data access logic
526
- 2. **Type Safety** - Leverage Prisma's generated types
527
- 3. **Transactions** - Use transactions for related operations
528
- 4. **Indexes** - Add indexes for frequently queried fields
529
- 5. **Migrations** - Always use migrations, never modify schema directly
530
- 6. **Connection Pooling** - Configure appropriate pool size
531
- 7. **Error Handling** - Handle Prisma errors gracefully
532
- 8. **Soft Deletes** - Implement soft deletes with middleware
533
-
534
440
  ## Migration Commands
535
441
 
536
442
  ```bash
537
- # Create migration
538
443
  npx prisma migrate dev --name add_user_role
539
-
540
- # Apply migrations
541
444
  npx prisma migrate deploy
542
-
543
- # Reset database
544
445
  npx prisma migrate reset
545
-
546
- # Generate client
547
446
  npx prisma generate
548
-
549
- # Open Prisma Studio
550
447
  npx prisma studio
551
448
  ```
552
449
 
553
- ## Examples
554
-
555
- See the [examples](../../example/src/prisma) directory for complete working examples.
556
-
557
- ## Testing
558
-
559
- ```bash
560
- npm test
561
- ```
562
-
563
- ## Contributing
450
+ ## Links
564
451
 
565
- Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
452
+ - [TypeORM docs](https://www.prisma.io/docs)
453
+ - [HazelJS](https://hazeljs.com)
454
+ - [GitHub](https://github.com/hazel-js/hazeljs)
566
455
 
567
456
  ## License
568
457
 
569
458
  Apache 2.0 © [HazelJS](https://hazeljs.com)
570
-
571
- ## Links
572
-
573
- - [Documentation](https://hazeljs.com/docs/packages/prisma)
574
- - [Prisma Docs](https://www.prisma.io/docs)
575
- - [GitHub](https://github.com/hazel-js/hazeljs)
576
- - [Issues](https://github.com/hazel-js/hazeljs/issues)
577
- - [Discord](https://discord.gg/hazeljs)
@@ -1,5 +1,5 @@
1
1
  import { RepositoryOptions } from '@hazeljs/core';
2
2
  import 'reflect-metadata';
3
- export declare function Repository(options: RepositoryOptions): ClassDecorator;
3
+ export declare function Repository(options: RepositoryOptions | string): ClassDecorator;
4
4
  export declare function InjectRepository(): ParameterDecorator;
5
5
  //# sourceMappingURL=repository.decorator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"repository.decorator.d.ts","sourceRoot":"","sources":["../src/repository.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,kBAAkB,CAAC;AAE1B,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAIrE;AAED,wBAAgB,gBAAgB,IAAI,kBAAkB,CA0BrD"}
1
+ {"version":3,"file":"repository.decorator.d.ts","sourceRoot":"","sources":["../src/repository.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,kBAAkB,CAAC;AAE1B,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,GAAG,cAAc,CAW9E;AAED,wBAAgB,gBAAgB,IAAI,kBAAkB,CA0BrD"}
@@ -5,7 +5,14 @@ exports.InjectRepository = InjectRepository;
5
5
  require("reflect-metadata");
6
6
  function Repository(options) {
7
7
  return function (target) {
8
- Reflect.defineMetadata('hazel:repository', options, target);
8
+ const opts = typeof options === 'string' ? { model: options } : options;
9
+ Reflect.defineMetadata('hazel:repository', opts, target);
10
+ // Implicitly mark the class as injectable — @Injectable() is not needed separately.
11
+ // Write metadata directly to avoid the ClassDecorator `Function` type constraint.
12
+ Reflect.defineMetadata('hazel:injectable', opts.scope ? { scope: opts.scope } : {}, target);
13
+ if (opts.scope) {
14
+ Reflect.defineMetadata('hazel:scope', opts.scope, target);
15
+ }
9
16
  };
10
17
  }
11
18
  function InjectRepository() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/prisma",
3
- "version": "0.2.0-beta.52",
3
+ "version": "0.2.0-beta.54",
4
4
  "description": "Prisma ORM integration for HazelJS framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,5 +50,5 @@
50
50
  "peerDependencies": {
51
51
  "@hazeljs/core": ">=0.2.0-beta.0"
52
52
  },
53
- "gitHead": "416ae971cf8fc45ab53df8cd6f43ad40439ef2f2"
53
+ "gitHead": "c593ce33447cdc62d7bd2386cc2db47840292fcb"
54
54
  }