@onebun/core 0.1.1 → 0.1.3

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.
Files changed (81) hide show
  1. package/README.md +233 -0
  2. package/package.json +1 -1
  3. package/src/{application.test.ts → application/application.test.ts} +125 -5
  4. package/src/{application.ts → application/application.ts} +239 -13
  5. package/src/application/index.ts +9 -0
  6. package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
  7. package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
  8. package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
  9. package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
  10. package/src/{decorators.ts → decorators/decorators.ts} +3 -2
  11. package/src/decorators/index.ts +15 -0
  12. package/src/docs-examples.test.ts +753 -0
  13. package/src/index.ts +50 -41
  14. package/src/module/index.ts +12 -0
  15. package/src/{module.test.ts → module/module.test.ts} +3 -2
  16. package/src/{module.ts → module/module.ts} +15 -8
  17. package/src/queue/adapters/index.ts +8 -0
  18. package/src/queue/adapters/memory.adapter.test.ts +405 -0
  19. package/src/queue/adapters/memory.adapter.ts +509 -0
  20. package/src/queue/adapters/redis.adapter.ts +673 -0
  21. package/src/queue/cron-expression.test.ts +145 -0
  22. package/src/queue/cron-expression.ts +115 -0
  23. package/src/queue/cron-parser.test.ts +185 -0
  24. package/src/queue/cron-parser.ts +287 -0
  25. package/src/queue/decorators.test.ts +292 -0
  26. package/src/queue/decorators.ts +493 -0
  27. package/src/queue/docs-examples.test.ts +449 -0
  28. package/src/queue/guards.test.ts +309 -0
  29. package/src/queue/guards.ts +307 -0
  30. package/src/queue/index.ts +118 -0
  31. package/src/queue/pattern-matcher.test.ts +191 -0
  32. package/src/queue/pattern-matcher.ts +252 -0
  33. package/src/queue/queue.service.ts +421 -0
  34. package/src/queue/scheduler.test.ts +235 -0
  35. package/src/queue/scheduler.ts +379 -0
  36. package/src/queue/types.ts +502 -0
  37. package/src/redis/index.ts +8 -0
  38. package/src/redis/redis-client.ts +502 -0
  39. package/src/redis/shared-redis.ts +231 -0
  40. package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
  41. package/src/service-client/index.ts +10 -0
  42. package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
  43. package/src/{service-client.ts → service-client/service-client.ts} +1 -1
  44. package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
  45. package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
  46. package/src/testing/index.ts +7 -0
  47. package/src/types.ts +84 -5
  48. package/src/websocket/index.ts +50 -0
  49. package/src/websocket/ws-base-gateway.test.ts +479 -0
  50. package/src/websocket/ws-base-gateway.ts +514 -0
  51. package/src/websocket/ws-client.test.ts +511 -0
  52. package/src/websocket/ws-client.ts +628 -0
  53. package/src/websocket/ws-client.types.ts +129 -0
  54. package/src/websocket/ws-decorators.test.ts +331 -0
  55. package/src/websocket/ws-decorators.ts +418 -0
  56. package/src/websocket/ws-guards.test.ts +334 -0
  57. package/src/websocket/ws-guards.ts +298 -0
  58. package/src/websocket/ws-handler.ts +658 -0
  59. package/src/websocket/ws-integration.test.ts +518 -0
  60. package/src/websocket/ws-pattern-matcher.test.ts +152 -0
  61. package/src/websocket/ws-pattern-matcher.ts +240 -0
  62. package/src/websocket/ws-service-definition.ts +224 -0
  63. package/src/websocket/ws-socketio-protocol.test.ts +344 -0
  64. package/src/websocket/ws-socketio-protocol.ts +567 -0
  65. package/src/websocket/ws-storage-memory.test.ts +246 -0
  66. package/src/websocket/ws-storage-memory.ts +222 -0
  67. package/src/websocket/ws-storage-redis.ts +302 -0
  68. package/src/websocket/ws-storage.ts +210 -0
  69. package/src/websocket/ws.types.ts +342 -0
  70. /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
  71. /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
  72. /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
  73. /package/src/{config.service.ts → module/config.service.ts} +0 -0
  74. /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
  75. /package/src/{controller.ts → module/controller.ts} +0 -0
  76. /package/src/{service.test.ts → module/service.test.ts} +0 -0
  77. /package/src/{service.ts → module/service.ts} +0 -0
  78. /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
  79. /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
  80. /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
  81. /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Shared Redis Provider
3
+ *
4
+ * Singleton provider for sharing Redis connection across multiple consumers
5
+ * (cache, websocket, etc.)
6
+ */
7
+
8
+ import {
9
+ Context,
10
+ Effect,
11
+ Layer,
12
+ } from 'effect';
13
+
14
+ import { RedisClient, type RedisClientOptions } from './redis-client';
15
+
16
+ /**
17
+ * Options for shared Redis connection
18
+ */
19
+ export interface SharedRedisOptions {
20
+ /** Redis connection URL */
21
+ url: string;
22
+ /** Key prefix for all operations */
23
+ keyPrefix?: string;
24
+ /** Enable automatic reconnection */
25
+ reconnect?: boolean;
26
+ /** Enable TLS */
27
+ tls?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Singleton provider for shared Redis connection
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // Configure at app startup
36
+ * SharedRedisProvider.configure({
37
+ * url: 'redis://localhost:6379',
38
+ * keyPrefix: 'myapp:',
39
+ * });
40
+ *
41
+ * // Get shared client
42
+ * const client = await SharedRedisProvider.getClient();
43
+ *
44
+ * // Use in cache
45
+ * const cache = new RedisCache({ useSharedClient: true });
46
+ *
47
+ * // Use in WebSocket storage
48
+ * const wsStorage = new RedisWsStorage(await SharedRedisProvider.getClient());
49
+ * ```
50
+ */
51
+ export class SharedRedisProvider {
52
+ private static instance: RedisClient | null = null;
53
+ private static options: SharedRedisOptions | null = null;
54
+ private static connecting: Promise<RedisClient> | null = null;
55
+
56
+ /**
57
+ * Configure the shared Redis connection
58
+ * Must be called before getClient()
59
+ */
60
+ static configure(options: SharedRedisOptions): void {
61
+ // Note: If already connected, configuration will apply to new connections only
62
+ SharedRedisProvider.options = options;
63
+ }
64
+
65
+ /**
66
+ * Get the shared Redis client (creates connection if needed)
67
+ */
68
+ static async getClient(): Promise<RedisClient> {
69
+ // Return existing instance
70
+ if (SharedRedisProvider.instance?.isConnected()) {
71
+ return SharedRedisProvider.instance;
72
+ }
73
+
74
+ // Wait for existing connection attempt
75
+ if (SharedRedisProvider.connecting) {
76
+ return await SharedRedisProvider.connecting;
77
+ }
78
+
79
+ // Check configuration
80
+ if (!SharedRedisProvider.options) {
81
+ throw new Error(
82
+ 'SharedRedisProvider not configured. Call SharedRedisProvider.configure() first.',
83
+ );
84
+ }
85
+
86
+ // Create new connection
87
+ SharedRedisProvider.connecting = SharedRedisProvider.createConnection();
88
+
89
+ try {
90
+ SharedRedisProvider.instance = await SharedRedisProvider.connecting;
91
+
92
+ return SharedRedisProvider.instance;
93
+ } finally {
94
+ SharedRedisProvider.connecting = null;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Create a new Redis connection
100
+ */
101
+ private static async createConnection(): Promise<RedisClient> {
102
+ const options = SharedRedisProvider.options!;
103
+ const client = new RedisClient({
104
+ url: options.url,
105
+ keyPrefix: options.keyPrefix,
106
+ reconnect: options.reconnect,
107
+ tls: options.tls,
108
+ });
109
+
110
+ await client.connect();
111
+
112
+ return client;
113
+ }
114
+
115
+ /**
116
+ * Disconnect the shared client
117
+ */
118
+ static async disconnect(): Promise<void> {
119
+ if (SharedRedisProvider.instance) {
120
+ await SharedRedisProvider.instance.disconnect();
121
+ SharedRedisProvider.instance = null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Check if shared client is connected
127
+ */
128
+ static isConnected(): boolean {
129
+ return SharedRedisProvider.instance?.isConnected() ?? false;
130
+ }
131
+
132
+ /**
133
+ * Check if shared Redis is configured
134
+ */
135
+ static isConfigured(): boolean {
136
+ return SharedRedisProvider.options !== null;
137
+ }
138
+
139
+ /**
140
+ * Create a standalone Redis client (not shared)
141
+ * Useful for isolated scenarios
142
+ */
143
+ static createClient(options?: Partial<SharedRedisOptions>): RedisClient {
144
+ const baseOptions: Partial<SharedRedisOptions> = SharedRedisProvider.options || {};
145
+ const finalOptions: RedisClientOptions = {
146
+ url: options?.url || baseOptions.url || '',
147
+ keyPrefix: options?.keyPrefix ?? baseOptions.keyPrefix,
148
+ reconnect: options?.reconnect ?? baseOptions.reconnect ?? true,
149
+ tls: options?.tls ?? baseOptions.tls,
150
+ };
151
+
152
+ if (!finalOptions.url) {
153
+ throw new Error('Redis URL is required');
154
+ }
155
+
156
+ return new RedisClient(finalOptions);
157
+ }
158
+
159
+ /**
160
+ * Reset provider state (mainly for testing)
161
+ */
162
+ static async reset(): Promise<void> {
163
+ await SharedRedisProvider.disconnect();
164
+ SharedRedisProvider.options = null;
165
+ }
166
+ }
167
+
168
+ // ============================================================================
169
+ // Effect.js Integration
170
+ // ============================================================================
171
+
172
+ /**
173
+ * Effect.js Tag for shared Redis service
174
+ */
175
+ export class SharedRedisService extends Context.Tag('SharedRedisService')<
176
+ SharedRedisService,
177
+ RedisClient
178
+ >() {}
179
+
180
+ /**
181
+ * Create Effect.js Layer for shared Redis
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const redisLayer = makeSharedRedisLayer({
186
+ * url: 'redis://localhost:6379',
187
+ * keyPrefix: 'myapp:',
188
+ * });
189
+ *
190
+ * const program = pipe(
191
+ * SharedRedisService,
192
+ * Effect.flatMap(redis => redis.get('key')),
193
+ * Effect.provide(redisLayer),
194
+ * );
195
+ * ```
196
+ */
197
+ export function makeSharedRedisLayer(
198
+ options: SharedRedisOptions,
199
+ ): Layer.Layer<SharedRedisService> {
200
+ return Layer.scoped(
201
+ SharedRedisService,
202
+ Effect.gen(function* () {
203
+ // Configure shared provider
204
+ SharedRedisProvider.configure(options);
205
+
206
+ // Get client
207
+ const client = yield* Effect.promise(() => SharedRedisProvider.getClient());
208
+
209
+ // Return client with cleanup
210
+ yield* Effect.addFinalizer(() =>
211
+ Effect.promise(async () => {
212
+ // Note: We don't disconnect shared client on scope close
213
+ // because other consumers might still be using it
214
+ }),
215
+ );
216
+
217
+ return client;
218
+ }),
219
+ );
220
+ }
221
+
222
+ /**
223
+ * Get shared Redis client as Effect
224
+ */
225
+ export const getSharedRedis = Effect.gen(function* () {
226
+ if (!SharedRedisProvider.isConfigured()) {
227
+ return yield* Effect.fail(new Error('SharedRedisProvider not configured'));
228
+ }
229
+
230
+ return yield* Effect.promise(() => SharedRedisProvider.getClient());
231
+ });
@@ -1,4 +1,4 @@
1
- import type { EnvOverrides, EnvOverrideValue } from './multi-service.types';
1
+ import type { EnvOverrides, EnvOverrideValue } from '../application/multi-service.types';
2
2
 
3
3
  import {
4
4
  type EnvLoadOptions,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Service Client Module
3
+ *
4
+ * Type-safe service definitions and clients for inter-service communication.
5
+ */
6
+
7
+ export * from './service-client.types';
8
+ export * from './service-definition';
9
+ export * from './service-client';
10
+ export * from './env-resolver';
@@ -15,10 +15,11 @@ import {
15
15
  Post,
16
16
  Body,
17
17
  Query,
18
- } from './decorators';
18
+ } from '../decorators/decorators';
19
+ import { HttpMethod } from '../types';
20
+
19
21
  import { createServiceClient, getServiceUrl } from './service-client';
20
22
  import { createServiceDefinition } from './service-definition';
21
- import { HttpMethod } from './types';
22
23
 
23
24
  // Test controller with various parameter types
24
25
  @Controller('/users')
@@ -7,7 +7,7 @@ import type {
7
7
 
8
8
  import { HttpClient, type HttpMethod as RequestsHttpMethod } from '@onebun/requests';
9
9
 
10
- import { ParamType } from './types';
10
+ import { ParamType } from '../types';
11
11
 
12
12
  /**
13
13
  * Build request parameters from endpoint metadata and arguments
@@ -11,9 +11,10 @@ import {
11
11
  Param,
12
12
  Post,
13
13
  Body,
14
- } from './decorators';
14
+ } from '../decorators/decorators';
15
+ import { HttpMethod } from '../types';
16
+
15
17
  import { createServiceDefinition } from './service-definition';
16
- import { HttpMethod } from './types';
17
18
 
18
19
  // Test controller
19
20
  @Controller('/users')
@@ -1,5 +1,5 @@
1
- import { getControllerMetadata, getModuleMetadata } from './decorators';
2
- import { type HttpMethod, type ParamMetadata } from './types';
1
+ import { getControllerMetadata, getModuleMetadata } from '../decorators/decorators';
2
+ import { type HttpMethod, type ParamMetadata } from '../types';
3
3
 
4
4
  /**
5
5
  * Metadata for a single endpoint
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Testing Utilities
3
+ *
4
+ * Helpers for testing OneBun applications.
5
+ */
6
+
7
+ export * from './test-utils';
package/src/types.ts CHANGED
@@ -6,7 +6,7 @@ import type { Logger } from '@onebun/logger';
6
6
  /**
7
7
  * Base interface for all OneBun services
8
8
  */
9
- export interface Service {
9
+ export interface ServiceInterface {
10
10
  readonly [key: string]: unknown;
11
11
  }
12
12
 
@@ -17,7 +17,7 @@ export interface ModuleProviders {
17
17
  /**
18
18
  * Services to provide
19
19
  */
20
- providers?: Service[];
20
+ providers?: ServiceInterface[];
21
21
 
22
22
  /**
23
23
  * Controllers to include
@@ -27,7 +27,7 @@ export interface ModuleProviders {
27
27
  /**
28
28
  * Modules to import
29
29
  */
30
- imports?: Module[];
30
+ imports?: ModuleInstance[];
31
31
 
32
32
  /**
33
33
  * Services to export to parent modules
@@ -36,9 +36,9 @@ export interface ModuleProviders {
36
36
  }
37
37
 
38
38
  /**
39
- * Module interface
39
+ * Module instance interface
40
40
  */
41
- export interface Module {
41
+ export interface ModuleInstance {
42
42
  /**
43
43
  * Setup the module
44
44
  */
@@ -266,6 +266,85 @@ export interface ApplicationOptions {
266
266
  batchTimeout?: number;
267
267
  };
268
268
  };
269
+
270
+ /**
271
+ * WebSocket configuration
272
+ */
273
+ websocket?: WebSocketApplicationOptions;
274
+
275
+ /**
276
+ * Queue configuration
277
+ */
278
+ queue?: QueueApplicationOptions;
279
+
280
+ /**
281
+ * Enable graceful shutdown on SIGTERM/SIGINT
282
+ * When enabled, the application will cleanly shutdown on process signals,
283
+ * including closing shared Redis connections.
284
+ * Set to false to disable automatic signal handling.
285
+ * @defaultValue true
286
+ */
287
+ gracefulShutdown?: boolean;
288
+ }
289
+
290
+ /**
291
+ * Queue adapter type for application configuration
292
+ */
293
+ export type QueueAdapterType = 'memory' | 'redis';
294
+
295
+ /**
296
+ * Queue configuration for OneBunApplication
297
+ */
298
+ export interface QueueApplicationOptions {
299
+ /** Enable/disable queue (default: auto - enabled if handlers exist) */
300
+ enabled?: boolean;
301
+ /** Adapter type or custom adapter instance */
302
+ adapter?: QueueAdapterType;
303
+ /** Redis-specific options (only used when adapter is 'redis') */
304
+ redis?: {
305
+ /** Use shared Redis provider instead of dedicated connection */
306
+ useSharedProvider?: boolean;
307
+ /** Redis connection URL (required if not using shared provider) */
308
+ url?: string;
309
+ /** Key prefix for Redis keys */
310
+ prefix?: string;
311
+ };
312
+ }
313
+
314
+ /**
315
+ * WebSocket storage type
316
+ */
317
+ export type WsStorageType = 'memory' | 'redis';
318
+
319
+ /**
320
+ * WebSocket storage options
321
+ */
322
+ export interface WsStorageOptions {
323
+ /** Storage type */
324
+ type: WsStorageType;
325
+ /** Redis-specific options */
326
+ redis?: {
327
+ /** Redis connection URL */
328
+ url: string;
329
+ /** Key prefix for Redis keys */
330
+ prefix?: string;
331
+ };
332
+ }
333
+
334
+ /**
335
+ * WebSocket configuration for OneBunApplication
336
+ */
337
+ export interface WebSocketApplicationOptions {
338
+ /** Enable/disable WebSocket (default: auto - enabled if gateways exist) */
339
+ enabled?: boolean;
340
+ /** Storage options */
341
+ storage?: WsStorageOptions;
342
+ /** Ping interval in milliseconds for heartbeat (socket.io) */
343
+ pingInterval?: number;
344
+ /** Ping timeout in milliseconds (socket.io) */
345
+ pingTimeout?: number;
346
+ /** Maximum payload size in bytes */
347
+ maxPayload?: number;
269
348
  }
270
349
 
271
350
  /**
@@ -0,0 +1,50 @@
1
+ /**
2
+ * WebSocket Module
3
+ *
4
+ * WebSocket gateway system with Socket.IO protocol support.
5
+ */
6
+
7
+ // Types (excluding duplicates that come from ws-client.types)
8
+ // Note: WsServer type is exported from ws.types.ts but WsServer decorator is from ws-decorators.ts
9
+ // We export the type as WsServerType to avoid conflict with the decorator
10
+ export {
11
+ WsHandlerType,
12
+ WsParamType,
13
+ isWsMessage,
14
+ isWsHandlerResponse,
15
+ isWsClientData,
16
+ isWsRoom,
17
+ type WsClientData,
18
+ type WsAuthData,
19
+ type WsRoom,
20
+ type GatewayMetadata,
21
+ type WsHandlerMetadata,
22
+ type WsParamMetadata,
23
+ type WebSocketGatewayOptions,
24
+ type WsMessage,
25
+ type WsHandlerResponse,
26
+ type PatternMatch,
27
+ type WsExecutionContext,
28
+ type WsGuard,
29
+ // Export WsServer type as WsServerType to avoid conflict with WsServer decorator
30
+ type WsServer as WsServerType,
31
+ } from './ws.types';
32
+
33
+ export * from './ws-client.types';
34
+
35
+ // Core
36
+ export * from './ws-base-gateway';
37
+ export * from './ws-handler';
38
+ export * from './ws-decorators';
39
+ export * from './ws-guards';
40
+ export * from './ws-pattern-matcher';
41
+ export * from './ws-socketio-protocol';
42
+
43
+ // Storage
44
+ export * from './ws-storage';
45
+ export * from './ws-storage-memory';
46
+ export * from './ws-storage-redis';
47
+
48
+ // Service definition & client
49
+ export * from './ws-service-definition';
50
+ export * from './ws-client';