@ooneex/service 0.0.1 → 0.0.4

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
@@ -1 +1,505 @@
1
1
  # @ooneex/service
2
+
3
+ A service layer decorator and base interface for business logic encapsulation in TypeScript applications. This package provides the foundation for creating injectable service classes with dependency injection support through the Ooneex container.
4
+
5
+ ![Browser](https://img.shields.io/badge/Browser-Compatible-green?style=flat-square&logo=googlechrome)
6
+ ![Bun](https://img.shields.io/badge/Bun-Compatible-orange?style=flat-square&logo=bun)
7
+ ![Deno](https://img.shields.io/badge/Deno-Compatible-blue?style=flat-square&logo=deno)
8
+ ![Node.js](https://img.shields.io/badge/Node.js-Compatible-green?style=flat-square&logo=node.js)
9
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)
10
+ ![MIT License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)
11
+
12
+ ## Features
13
+
14
+ ✅ **Service Decorator** - Register services with the DI container using decorators
15
+
16
+ ✅ **Interface Contract** - Standard interface for service implementations
17
+
18
+ ✅ **Scope Control** - Configure singleton, transient, or request-scoped services
19
+
20
+ ✅ **Type-Safe** - Full TypeScript support with proper type definitions
21
+
22
+ ✅ **Container Integration** - Seamless integration with @ooneex/container
23
+
24
+ ✅ **Zero Boilerplate** - Simple decorator-based registration
25
+
26
+ ## Installation
27
+
28
+ ### Bun
29
+ ```bash
30
+ bun add @ooneex/service
31
+ ```
32
+
33
+ ### pnpm
34
+ ```bash
35
+ pnpm add @ooneex/service
36
+ ```
37
+
38
+ ### Yarn
39
+ ```bash
40
+ yarn add @ooneex/service
41
+ ```
42
+
43
+ ### npm
44
+ ```bash
45
+ npm install @ooneex/service
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Basic Service
51
+
52
+ ```typescript
53
+ import { decorator, type IService } from '@ooneex/service';
54
+
55
+ interface SendEmailData {
56
+ to: string;
57
+ subject: string;
58
+ body: string;
59
+ }
60
+
61
+ @decorator.service()
62
+ class EmailService implements IService<SendEmailData> {
63
+ public async execute(data?: SendEmailData): Promise<void> {
64
+ if (!data) return;
65
+
66
+ await this.sendEmail(data.to, data.subject, data.body);
67
+ }
68
+
69
+ private async sendEmail(to: string, subject: string, body: string): Promise<void> {
70
+ // Email sending logic
71
+ console.log(`Sending email to ${to}: ${subject}`);
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Resolving Services
77
+
78
+ ```typescript
79
+ import { container } from '@ooneex/container';
80
+ import { EmailService } from './services/EmailService';
81
+
82
+ // Service is automatically registered by the decorator
83
+ const emailService = container.get(EmailService);
84
+
85
+ await emailService.execute({
86
+ to: 'user@example.com',
87
+ subject: 'Welcome!',
88
+ body: 'Thank you for signing up.'
89
+ });
90
+ ```
91
+
92
+ ### Service with Dependencies
93
+
94
+ ```typescript
95
+ import { decorator, type IService } from '@ooneex/service';
96
+ import { container } from '@ooneex/container';
97
+ import type { ILogger } from '@ooneex/logger';
98
+ import type { ICache } from '@ooneex/cache';
99
+
100
+ interface ProcessOrderData {
101
+ orderId: string;
102
+ items: Array<{ productId: string; quantity: number }>;
103
+ }
104
+
105
+ @decorator.service()
106
+ class OrderProcessingService implements IService<ProcessOrderData> {
107
+ private readonly logger = container.get<ILogger>('logger');
108
+ private readonly cache = container.get<ICache>('cache');
109
+
110
+ public async execute(data?: ProcessOrderData): Promise<void> {
111
+ if (!data) return;
112
+
113
+ this.logger.info('Processing order', { orderId: data.orderId });
114
+
115
+ // Process order items
116
+ for (const item of data.items) {
117
+ await this.processItem(item);
118
+ }
119
+
120
+ // Cache the result
121
+ await this.cache.set(`order:${data.orderId}:status`, 'processed', 3600);
122
+
123
+ this.logger.success('Order processed', { orderId: data.orderId });
124
+ }
125
+
126
+ private async processItem(item: { productId: string; quantity: number }): Promise<void> {
127
+ // Item processing logic
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Transient Services
133
+
134
+ ```typescript
135
+ import { decorator, type IService } from '@ooneex/service';
136
+ import { EContainerScope } from '@ooneex/container';
137
+
138
+ @decorator.service(EContainerScope.Transient)
139
+ class RequestContextService implements IService {
140
+ private readonly requestId = crypto.randomUUID();
141
+
142
+ public async execute(): Promise<void> {
143
+ console.log(`Request ID: ${this.requestId}`);
144
+ }
145
+
146
+ public getRequestId(): string {
147
+ return this.requestId;
148
+ }
149
+ }
150
+
151
+ // Each resolution creates a new instance
152
+ const service1 = container.get(RequestContextService);
153
+ const service2 = container.get(RequestContextService);
154
+ console.log(service1.getRequestId() !== service2.getRequestId()); // true
155
+ ```
156
+
157
+ ### Request-Scoped Services
158
+
159
+ ```typescript
160
+ import { decorator, type IService } from '@ooneex/service';
161
+ import { EContainerScope } from '@ooneex/container';
162
+
163
+ @decorator.service(EContainerScope.Request)
164
+ class UserSessionService implements IService {
165
+ private userId: string | null = null;
166
+
167
+ public async execute(): Promise<void> {
168
+ // Initialize session
169
+ }
170
+
171
+ public setUserId(id: string): void {
172
+ this.userId = id;
173
+ }
174
+
175
+ public getUserId(): string | null {
176
+ return this.userId;
177
+ }
178
+ }
179
+ ```
180
+
181
+ ## API Reference
182
+
183
+ ### Decorators
184
+
185
+ #### `@decorator.service(scope?)`
186
+
187
+ Decorator to register a service class with the DI container.
188
+
189
+ **Parameters:**
190
+ - `scope` - Container scope (default: `EContainerScope.Singleton`)
191
+ - `Singleton` - Single instance shared across all requests
192
+ - `Transient` - New instance created on every resolution
193
+ - `Request` - New instance per request context
194
+
195
+ **Example:**
196
+ ```typescript
197
+ import { decorator } from '@ooneex/service';
198
+ import { EContainerScope } from '@ooneex/container';
199
+
200
+ // Singleton (default)
201
+ @decorator.service()
202
+ class MySingletonService {}
203
+
204
+ // Transient
205
+ @decorator.service(EContainerScope.Transient)
206
+ class MyTransientService {}
207
+
208
+ // Request-scoped
209
+ @decorator.service(EContainerScope.Request)
210
+ class MyRequestService {}
211
+ ```
212
+
213
+ ### Interfaces
214
+
215
+ #### `IService<T>`
216
+
217
+ Interface for service implementations.
218
+
219
+ ```typescript
220
+ interface IService<T = Record<string, unknown>> {
221
+ execute: (data?: T) => Promise<void>;
222
+ }
223
+ ```
224
+
225
+ **Type Parameter:**
226
+ - `T` - The type of data the service accepts (default: `Record<string, unknown>`)
227
+
228
+ **Methods:**
229
+
230
+ ##### `execute(data?: T): Promise<void>`
231
+
232
+ Execute the service's main logic.
233
+
234
+ **Parameters:**
235
+ - `data` - Optional data to process
236
+
237
+ **Returns:** Promise that resolves when execution completes
238
+
239
+ ### Types
240
+
241
+ #### `ServiceClassType`
242
+
243
+ Type for service class constructors.
244
+
245
+ ```typescript
246
+ type ServiceClassType = new (...args: any[]) => IService;
247
+ ```
248
+
249
+ ## Advanced Usage
250
+
251
+ ### Service Composition
252
+
253
+ ```typescript
254
+ import { decorator, type IService } from '@ooneex/service';
255
+ import { container } from '@ooneex/container';
256
+
257
+ interface CreateUserData {
258
+ email: string;
259
+ name: string;
260
+ }
261
+
262
+ @decorator.service()
263
+ class ValidationService {
264
+ public validateEmail(email: string): boolean {
265
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
266
+ }
267
+ }
268
+
269
+ @decorator.service()
270
+ class NotificationService implements IService<{ userId: string; message: string }> {
271
+ public async execute(data?: { userId: string; message: string }): Promise<void> {
272
+ if (!data) return;
273
+ // Send notification
274
+ }
275
+ }
276
+
277
+ @decorator.service()
278
+ class UserCreationService implements IService<CreateUserData> {
279
+ private readonly validationService = container.get(ValidationService);
280
+ private readonly notificationService = container.get(NotificationService);
281
+
282
+ public async execute(data?: CreateUserData): Promise<void> {
283
+ if (!data) return;
284
+
285
+ // Validate
286
+ if (!this.validationService.validateEmail(data.email)) {
287
+ throw new Error('Invalid email');
288
+ }
289
+
290
+ // Create user
291
+ const userId = await this.createUser(data);
292
+
293
+ // Notify
294
+ await this.notificationService.execute({
295
+ userId,
296
+ message: 'Welcome to our platform!'
297
+ });
298
+ }
299
+
300
+ private async createUser(data: CreateUserData): Promise<string> {
301
+ // User creation logic
302
+ return crypto.randomUUID();
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### Service with Controller Integration
308
+
309
+ ```typescript
310
+ import { Route } from '@ooneex/routing';
311
+ import { decorator, type IService } from '@ooneex/service';
312
+ import { container } from '@ooneex/container';
313
+ import type { IController, ContextType } from '@ooneex/controller';
314
+
315
+ interface SubscriptionData {
316
+ userId: string;
317
+ plan: 'basic' | 'premium' | 'enterprise';
318
+ }
319
+
320
+ @decorator.service()
321
+ class SubscriptionService implements IService<SubscriptionData> {
322
+ public async execute(data?: SubscriptionData): Promise<void> {
323
+ if (!data) return;
324
+ await this.processSubscription(data.userId, data.plan);
325
+ }
326
+
327
+ private async processSubscription(userId: string, plan: string): Promise<void> {
328
+ // Subscription logic
329
+ }
330
+ }
331
+
332
+ @Route.http({
333
+ name: 'api.subscriptions.create',
334
+ path: '/api/subscriptions',
335
+ method: 'POST',
336
+ description: 'Create a subscription'
337
+ })
338
+ class SubscriptionController implements IController {
339
+ private readonly subscriptionService = container.get(SubscriptionService);
340
+
341
+ public async index(context: ContextType): Promise<IResponse> {
342
+ const { plan } = context.payload;
343
+ const userId = context.user?.id;
344
+
345
+ if (!userId) {
346
+ return context.response.exception('Unauthorized', { status: 401 });
347
+ }
348
+
349
+ await this.subscriptionService.execute({ userId, plan });
350
+
351
+ return context.response.json({ success: true });
352
+ }
353
+ }
354
+ ```
355
+
356
+ ### Error Handling in Services
357
+
358
+ ```typescript
359
+ import { decorator, type IService } from '@ooneex/service';
360
+ import { Exception } from '@ooneex/exception';
361
+
362
+ interface PaymentData {
363
+ amount: number;
364
+ currency: string;
365
+ cardToken: string;
366
+ }
367
+
368
+ class PaymentException extends Exception {
369
+ constructor(message: string, public readonly code: string) {
370
+ super(message, { status: 400, data: { code } });
371
+ this.name = 'PaymentException';
372
+ }
373
+ }
374
+
375
+ @decorator.service()
376
+ class PaymentService implements IService<PaymentData> {
377
+ public async execute(data?: PaymentData): Promise<void> {
378
+ if (!data) {
379
+ throw new PaymentException('Payment data required', 'MISSING_DATA');
380
+ }
381
+
382
+ if (data.amount <= 0) {
383
+ throw new PaymentException('Invalid amount', 'INVALID_AMOUNT');
384
+ }
385
+
386
+ try {
387
+ await this.processPayment(data);
388
+ } catch (error) {
389
+ throw new PaymentException('Payment processing failed', 'PROCESSING_ERROR');
390
+ }
391
+ }
392
+
393
+ private async processPayment(data: PaymentData): Promise<void> {
394
+ // Payment processing logic
395
+ }
396
+ }
397
+ ```
398
+
399
+ ### Generic Service Pattern
400
+
401
+ ```typescript
402
+ import { decorator, type IService } from '@ooneex/service';
403
+
404
+ interface CrudData<T> {
405
+ action: 'create' | 'read' | 'update' | 'delete';
406
+ entity: T;
407
+ id?: string;
408
+ }
409
+
410
+ @decorator.service()
411
+ class GenericCrudService<T extends { id?: string }> implements IService<CrudData<T>> {
412
+ constructor(private readonly entityName: string) {}
413
+
414
+ public async execute(data?: CrudData<T>): Promise<void> {
415
+ if (!data) return;
416
+
417
+ switch (data.action) {
418
+ case 'create':
419
+ await this.create(data.entity);
420
+ break;
421
+ case 'read':
422
+ await this.read(data.id);
423
+ break;
424
+ case 'update':
425
+ await this.update(data.id, data.entity);
426
+ break;
427
+ case 'delete':
428
+ await this.delete(data.id);
429
+ break;
430
+ }
431
+ }
432
+
433
+ private async create(entity: T): Promise<void> {
434
+ console.log(`Creating ${this.entityName}:`, entity);
435
+ }
436
+
437
+ private async read(id?: string): Promise<void> {
438
+ console.log(`Reading ${this.entityName}:`, id);
439
+ }
440
+
441
+ private async update(id?: string, entity?: T): Promise<void> {
442
+ console.log(`Updating ${this.entityName}:`, id, entity);
443
+ }
444
+
445
+ private async delete(id?: string): Promise<void> {
446
+ console.log(`Deleting ${this.entityName}:`, id);
447
+ }
448
+ }
449
+ ```
450
+
451
+ ### Testing Services
452
+
453
+ ```typescript
454
+ import { describe, test, expect, beforeEach } from 'bun:test';
455
+ import { container } from '@ooneex/container';
456
+ import { EmailService } from './EmailService';
457
+
458
+ describe('EmailService', () => {
459
+ let emailService: EmailService;
460
+
461
+ beforeEach(() => {
462
+ emailService = container.get(EmailService);
463
+ });
464
+
465
+ test('should execute without data', async () => {
466
+ await expect(emailService.execute()).resolves.toBeUndefined();
467
+ });
468
+
469
+ test('should execute with valid data', async () => {
470
+ await expect(
471
+ emailService.execute({
472
+ to: 'test@example.com',
473
+ subject: 'Test',
474
+ body: 'Test body'
475
+ })
476
+ ).resolves.toBeUndefined();
477
+ });
478
+ });
479
+ ```
480
+
481
+ ## License
482
+
483
+ This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
484
+
485
+ ## Contributing
486
+
487
+ Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
488
+
489
+ ### Development Setup
490
+
491
+ 1. Clone the repository
492
+ 2. Install dependencies: `bun install`
493
+ 3. Run tests: `bun run test`
494
+ 4. Build the project: `bun run build`
495
+
496
+ ### Guidelines
497
+
498
+ - Write tests for new features
499
+ - Follow the existing code style
500
+ - Update documentation for API changes
501
+ - Ensure all tests pass before submitting PR
502
+
503
+ ---
504
+
505
+ Made with ❤️ by the Ooneex team
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
+ import { EContainerScope } from "@ooneex/container";
1
2
  type ServiceClassType = new (...args: any[]) => IService;
2
- interface IService {
3
- execute: () => Promise<void>;
3
+ interface IService<T = Record<string, unknown>> {
4
+ execute: (data?: T) => Promise<void>;
4
5
  }
5
- export { ServiceClassType, IService };
6
+ declare const decorator: {
7
+ service: (scope?: EContainerScope) => (target: ServiceClassType) => void;
8
+ };
9
+ export { decorator, ServiceClassType, IService };
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
+ import{container as o,EContainerScope as t}from"@ooneex/container";var i={service:(e=t.Singleton)=>{return(r)=>{o.add(r,e)}}};export{i as decorator};
1
2
 
2
- //# debugId=BC4C8D5165931A0D64756E2164756E21
3
+ //# debugId=D88AC999764E45C964756E2164756E21
package/dist/index.js.map CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": [],
3
+ "sources": ["src/decorators.ts"],
4
4
  "sourcesContent": [
5
+ "import { container, EContainerScope } from \"@ooneex/container\";\nimport type { ServiceClassType } from \"./types\";\n\nexport const decorator = {\n service: (scope: EContainerScope = EContainerScope.Singleton) => {\n return (target: ServiceClassType): void => {\n container.add(target, scope);\n };\n },\n};\n"
5
6
  ],
6
- "mappings": "",
7
- "debugId": "BC4C8D5165931A0D64756E2164756E21",
7
+ "mappings": "AAAA,oBAAS,qBAAW,0BAGb,IAAM,EAAY,CACvB,QAAS,CAAC,EAAyB,EAAgB,YAAc,CAC/D,MAAO,CAAC,IAAmC,CACzC,EAAU,IAAI,EAAQ,CAAK,GAGjC",
8
+ "debugId": "D88AC999764E45C964756E2164756E21",
8
9
  "names": []
9
10
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ooneex/service",
3
- "description": "",
4
- "version": "0.0.1",
3
+ "description": "Service layer decorator and base classes for business logic encapsulation with dependency injection support",
4
+ "version": "0.0.4",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -22,13 +22,23 @@
22
22
  },
23
23
  "license": "MIT",
24
24
  "scripts": {
25
+ "test": "bun test tests",
25
26
  "build": "bunup",
26
27
  "lint": "tsgo --noEmit && bunx biome lint",
27
- "publish:prod": "bun publish --tolerate-republish --access public",
28
- "publish:pack": "bun pm pack --destination ./dist",
29
- "publish:dry": "bun publish --dry-run"
28
+ "publish": "bun publish --access public || true"
29
+ },
30
+ "dependencies": {
31
+ "@ooneex/container": "0.0.2"
30
32
  },
31
- "dependencies": {},
32
33
  "devDependencies": {},
33
- "peerDependencies": {}
34
+ "keywords": [
35
+ "bun",
36
+ "business-logic",
37
+ "decorator",
38
+ "layer",
39
+ "ooneex",
40
+ "service",
41
+ "services",
42
+ "typescript"
43
+ ]
34
44
  }