ts-procedures 2.1.0 → 2.1.1

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 (36) hide show
  1. package/build/implementations/http/express-rpc/index.d.ts +35 -35
  2. package/build/implementations/http/express-rpc/index.js +29 -13
  3. package/build/implementations/http/express-rpc/index.js.map +1 -1
  4. package/build/implementations/http/express-rpc/index.test.js +108 -54
  5. package/build/implementations/http/express-rpc/index.test.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/implementations/http/express-rpc/README.md +27 -13
  8. package/src/implementations/http/express-rpc/index.test.ts +128 -54
  9. package/src/implementations/http/express-rpc/index.ts +59 -38
  10. package/build/implementations/http/client/index.d.ts +0 -1
  11. package/build/implementations/http/client/index.js +0 -2
  12. package/build/implementations/http/client/index.js.map +0 -1
  13. package/build/implementations/http/express/example/factories.d.ts +0 -97
  14. package/build/implementations/http/express/example/factories.js +0 -4
  15. package/build/implementations/http/express/example/factories.js.map +0 -1
  16. package/build/implementations/http/express/example/procedures/auth.d.ts +0 -1
  17. package/build/implementations/http/express/example/procedures/auth.js +0 -22
  18. package/build/implementations/http/express/example/procedures/auth.js.map +0 -1
  19. package/build/implementations/http/express/example/procedures/users.d.ts +0 -1
  20. package/build/implementations/http/express/example/procedures/users.js +0 -30
  21. package/build/implementations/http/express/example/procedures/users.js.map +0 -1
  22. package/build/implementations/http/express/example/server.d.ts +0 -3
  23. package/build/implementations/http/express/example/server.js +0 -49
  24. package/build/implementations/http/express/example/server.js.map +0 -1
  25. package/build/implementations/http/express/example/server.test.d.ts +0 -1
  26. package/build/implementations/http/express/example/server.test.js +0 -110
  27. package/build/implementations/http/express/example/server.test.js.map +0 -1
  28. package/build/implementations/http/express/index.d.ts +0 -35
  29. package/build/implementations/http/express/index.js +0 -75
  30. package/build/implementations/http/express/index.js.map +0 -1
  31. package/build/implementations/http/express/index.test.d.ts +0 -1
  32. package/build/implementations/http/express/index.test.js +0 -329
  33. package/build/implementations/http/express/index.test.js.map +0 -1
  34. package/build/implementations/http/express/types.d.ts +0 -17
  35. package/build/implementations/http/express/types.js +0 -2
  36. package/build/implementations/http/express/types.js.map +0 -1
@@ -10,7 +10,7 @@ import { RPCConfig } from '../../types.js'
10
10
  * ExpressRPCAppBuilder Test Suite
11
11
  *
12
12
  * Tests the RPC-style Express integration for ts-procedures.
13
- * This builder creates POST routes at `/rpc/{name}/{version}` paths.
13
+ * This builder creates POST routes at `/{name}/{version}` paths (with optional pathPrefix).
14
14
  */
15
15
  describe('ExpressRPCAppBuilder', () => {
16
16
  // --------------------------------------------------------------------------
@@ -31,7 +31,7 @@ describe('ExpressRPCAppBuilder', () => {
31
31
  const app = builder.build()
32
32
 
33
33
  // JSON body should be parsed automatically
34
- const res = await request(app).post('/rpc/echo/1').send({ message: 'hello' })
34
+ const res = await request(app).post('/echo/1').send({ message: 'hello' })
35
35
 
36
36
  expect(res.status).toBe(200)
37
37
  expect(res.body).toEqual({ message: 'hello' })
@@ -54,7 +54,7 @@ describe('ExpressRPCAppBuilder', () => {
54
54
 
55
55
  // Without json middleware, body won't be parsed (req.body is undefined)
56
56
  const res = await request(app)
57
- .post('/rpc/echo/1')
57
+ .post('/echo/1')
58
58
  .set('Content-Type', 'application/json')
59
59
  .send(JSON.stringify({ message: 'hello' }))
60
60
 
@@ -76,6 +76,77 @@ describe('ExpressRPCAppBuilder', () => {
76
76
  })
77
77
  })
78
78
 
79
+ // --------------------------------------------------------------------------
80
+ // pathPrefix Option Tests
81
+ // --------------------------------------------------------------------------
82
+ describe('pathPrefix option', () => {
83
+ test('uses custom pathPrefix for all routes', async () => {
84
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api/v1' })
85
+ const RPC = Procedures<{}, RPCConfig>()
86
+
87
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
88
+
89
+ builder.register(RPC, () => ({}))
90
+ const app = builder.build()
91
+
92
+ const res = await request(app).post('/api/v1/test/1').send({})
93
+ expect(res.status).toBe(200)
94
+ expect(res.body).toEqual({ ok: true })
95
+ })
96
+
97
+ test('pathPrefix without leading slash gets normalized', async () => {
98
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: 'custom' })
99
+ const RPC = Procedures<{}, RPCConfig>()
100
+
101
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
102
+
103
+ builder.register(RPC, () => ({}))
104
+ const app = builder.build()
105
+
106
+ const res = await request(app).post('/custom/test/1').send({})
107
+ expect(res.status).toBe(200)
108
+ })
109
+
110
+ test('no prefix when pathPrefix not specified', async () => {
111
+ const builder = new ExpressRPCAppBuilder()
112
+ const RPC = Procedures<{}, RPCConfig>()
113
+
114
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
115
+
116
+ builder.register(RPC, () => ({}))
117
+ const app = builder.build()
118
+
119
+ const res = await request(app).post('/test/1').send({})
120
+ expect(res.status).toBe(200)
121
+ })
122
+
123
+ test('pathPrefix appears in generated docs', () => {
124
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api' })
125
+ const RPC = Procedures<{}, RPCConfig>()
126
+
127
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({}))
128
+
129
+ builder.register(RPC, () => ({}))
130
+ builder.build()
131
+
132
+ expect(builder.docs[0]!.path).toBe('/api/test/1')
133
+ })
134
+
135
+ test('pathPrefix /rpc restores original behavior', async () => {
136
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' })
137
+ const RPC = Procedures<{}, RPCConfig>()
138
+
139
+ RPC.Create('Users', { name: 'users', version: 1 }, async () => ({ users: [] }))
140
+
141
+ builder.register(RPC, () => ({}))
142
+ const app = builder.build()
143
+
144
+ const res = await request(app).post('/rpc/users/1').send({})
145
+ expect(res.status).toBe(200)
146
+ expect(builder.docs[0]!.path).toBe('/rpc/users/1')
147
+ })
148
+ })
149
+
79
150
  // --------------------------------------------------------------------------
80
151
  // Lifecycle Hooks Tests
81
152
  // --------------------------------------------------------------------------
@@ -90,11 +161,11 @@ describe('ExpressRPCAppBuilder', () => {
90
161
  builder.register(RPC, () => ({}))
91
162
  const app = builder.build()
92
163
 
93
- await request(app).post('/rpc/test/1').send({})
164
+ await request(app).post('/test/1').send({})
94
165
 
95
166
  expect(onRequestStart).toHaveBeenCalledTimes(1)
96
167
  expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('method', 'POST')
97
- expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('path', '/rpc/test/1')
168
+ expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('path', '/test/1')
98
169
  })
99
170
 
100
171
  test('onRequestEnd is called after response finishes', async () => {
@@ -107,7 +178,7 @@ describe('ExpressRPCAppBuilder', () => {
107
178
  builder.register(RPC, () => ({}))
108
179
  const app = builder.build()
109
180
 
110
- await request(app).post('/rpc/test/1').send({})
181
+ await request(app).post('/test/1').send({})
111
182
 
112
183
  expect(onRequestEnd).toHaveBeenCalledTimes(1)
113
184
  expect(onRequestEnd.mock.calls[0]![0]).toHaveProperty('method', 'POST')
@@ -124,7 +195,7 @@ describe('ExpressRPCAppBuilder', () => {
124
195
  builder.register(RPC, () => ({}))
125
196
  const app = builder.build()
126
197
 
127
- await request(app).post('/rpc/test/1').send({})
198
+ await request(app).post('/test/1').send({})
128
199
 
129
200
  expect(onSuccess).toHaveBeenCalledTimes(1)
130
201
  expect(onSuccess.mock.calls[0]![0]).toHaveProperty('name', 'Test')
@@ -142,7 +213,7 @@ describe('ExpressRPCAppBuilder', () => {
142
213
  builder.register(RPC, () => ({}))
143
214
  const app = builder.build()
144
215
 
145
- await request(app).post('/rpc/test/1').send({})
216
+ await request(app).post('/test/1').send({})
146
217
 
147
218
  expect(onSuccess).not.toHaveBeenCalled()
148
219
  })
@@ -165,7 +236,7 @@ describe('ExpressRPCAppBuilder', () => {
165
236
  builder.register(RPC, () => ({}))
166
237
  const app = builder.build()
167
238
 
168
- await request(app).post('/rpc/test/1').send({})
239
+ await request(app).post('/test/1').send({})
169
240
 
170
241
  expect(order).toEqual(['start', 'handler', 'success', 'end'])
171
242
  })
@@ -190,7 +261,7 @@ describe('ExpressRPCAppBuilder', () => {
190
261
  builder.register(RPC, () => ({}))
191
262
  const app = builder.build()
192
263
 
193
- const res = await request(app).post('/rpc/test/1').send({})
264
+ const res = await request(app).post('/test/1').send({})
194
265
 
195
266
  expect(errorHandler).toHaveBeenCalledTimes(1)
196
267
  expect(errorHandler.mock.calls[0]![0]).toHaveProperty('name', 'Test')
@@ -211,7 +282,7 @@ describe('ExpressRPCAppBuilder', () => {
211
282
  builder.register(RPC, () => ({}))
212
283
  const app = builder.build()
213
284
 
214
- const res = await request(app).post('/rpc/test/1').send({})
285
+ const res = await request(app).post('/test/1').send({})
215
286
 
216
287
  // Default error handler returns error message in JSON body
217
288
  expect(res.body).toHaveProperty('error')
@@ -231,7 +302,7 @@ describe('ExpressRPCAppBuilder', () => {
231
302
  builder.register(RPC, () => ({}))
232
303
  const app = builder.build()
233
304
 
234
- const res = await request(app).post('/rpc/test/1').send({})
305
+ const res = await request(app).post('/test/1').send({})
235
306
 
236
307
  // Unhandled exceptions are caught and returned as error response
237
308
  expect(res.body).toHaveProperty('error')
@@ -276,8 +347,8 @@ describe('ExpressRPCAppBuilder', () => {
276
347
 
277
348
  const app = builder.build()
278
349
 
279
- const publicRes = await request(app).post('/rpc/public/1').send({})
280
- const privateRes = await request(app).post('/rpc/private/1').send({})
350
+ const publicRes = await request(app).post('/public/1').send({})
351
+ const privateRes = await request(app).post('/private/1').send({})
281
352
 
282
353
  expect(publicRes.body).toEqual({ isPublic: true })
283
354
  expect(privateRes.body).toEqual({ isPrivate: true })
@@ -296,7 +367,7 @@ describe('ExpressRPCAppBuilder', () => {
296
367
  builder.register(RPC, factoryContext)
297
368
  const app = builder.build()
298
369
 
299
- const res = await request(app).post('/rpc/get-request-id/1').send({})
370
+ const res = await request(app).post('/get-request-id/1').send({})
300
371
 
301
372
  expect(res.body).toEqual({ id: 'req-123' })
302
373
  })
@@ -316,7 +387,7 @@ describe('ExpressRPCAppBuilder', () => {
316
387
  builder.register(RPC, factoryContext)
317
388
  const app = builder.build()
318
389
 
319
- await request(app).post('/rpc/get-request-id/1').send({})
390
+ await request(app).post('/get-request-id/1').send({})
320
391
 
321
392
  expect(factoryContext).toHaveBeenCalledTimes(1)
322
393
  })
@@ -336,7 +407,7 @@ describe('ExpressRPCAppBuilder', () => {
336
407
  builder.register(RPC, factoryContext)
337
408
  const app = builder.build()
338
409
 
339
- await request(app).post('/rpc/get-auth/1').set('Authorization', 'Bearer token123').send({})
410
+ await request(app).post('/get-auth/1').set('Authorization', 'Bearer token123').send({})
340
411
 
341
412
  expect(factoryContext).toHaveBeenCalledTimes(1)
342
413
  expect(factoryContext.mock.calls[0]![0]).toHaveProperty('headers')
@@ -361,8 +432,8 @@ describe('ExpressRPCAppBuilder', () => {
361
432
  builder.register(RPC, () => ({}))
362
433
  const app = builder.build()
363
434
 
364
- const res1 = await request(app).post('/rpc/method-one/1').send({})
365
- const res2 = await request(app).post('/rpc/method-two/2').send({})
435
+ const res1 = await request(app).post('/method-one/1').send({})
436
+ const res2 = await request(app).post('/method-two/2').send({})
366
437
 
367
438
  expect(res1.status).toBe(200)
368
439
  expect(res2.status).toBe(200)
@@ -391,8 +462,8 @@ describe('ExpressRPCAppBuilder', () => {
391
462
  builder.build()
392
463
 
393
464
  expect(builder.docs).toHaveLength(2)
394
- expect(builder.docs[0]!.path).toBe('/rpc/method-one/1')
395
- expect(builder.docs[1]!.path).toBe('/rpc/nested/method/2')
465
+ expect(builder.docs[0]!.path).toBe('/method-one/1')
466
+ expect(builder.docs[1]!.path).toBe('/nested/method/2')
396
467
  })
397
468
 
398
469
  test('passes request body to handler as params', async () => {
@@ -408,7 +479,7 @@ describe('ExpressRPCAppBuilder', () => {
408
479
  builder.register(RPC, () => ({}))
409
480
  const app = builder.build()
410
481
 
411
- const res = await request(app).post('/rpc/echo/1').send({ data: 'test-data' })
482
+ const res = await request(app).post('/echo/1').send({ data: 'test-data' })
412
483
 
413
484
  expect(res.body).toEqual({ received: 'test-data' })
414
485
  })
@@ -422,7 +493,7 @@ describe('ExpressRPCAppBuilder', () => {
422
493
  builder.register(RPC, () => ({}))
423
494
  const app = builder.build()
424
495
 
425
- const res = await request(app).get('/rpc/test/1')
496
+ const res = await request(app).get('/test/1')
426
497
 
427
498
  expect(res.status).toBe(404)
428
499
  })
@@ -438,24 +509,24 @@ describe('ExpressRPCAppBuilder', () => {
438
509
  builder = new ExpressRPCAppBuilder()
439
510
  })
440
511
 
441
- test("simple string: 'users' → /rpc/users/1", () => {
512
+ test("simple string: 'users' → /users/1", () => {
442
513
  const path = builder.makeRPCHttpRoutePath({ name: 'users', version: 1 })
443
- expect(path).toBe('/rpc/users/1')
514
+ expect(path).toBe('/users/1')
444
515
  })
445
516
 
446
- test("array name: ['users', 'get-by-id'] → /rpc/users/get-by-id/1", () => {
517
+ test("array name: ['users', 'get-by-id'] → /users/get-by-id/1", () => {
447
518
  const path = builder.makeRPCHttpRoutePath({ name: ['users', 'get-by-id'], version: 1 })
448
- expect(path).toBe('/rpc/users/get-by-id/1')
519
+ expect(path).toBe('/users/get-by-id/1')
449
520
  })
450
521
 
451
- test("camelCase: 'getUserById' → /rpc/get-user-by-id/1", () => {
522
+ test("camelCase: 'getUserById' → /get-user-by-id/1", () => {
452
523
  const path = builder.makeRPCHttpRoutePath({ name: 'getUserById', version: 1 })
453
- expect(path).toBe('/rpc/get-user-by-id/1')
524
+ expect(path).toBe('/get-user-by-id/1')
454
525
  })
455
526
 
456
- test("PascalCase: 'GetUserById' → /rpc/get-user-by-id/1", () => {
527
+ test("PascalCase: 'GetUserById' → /get-user-by-id/1", () => {
457
528
  const path = builder.makeRPCHttpRoutePath({ name: 'GetUserById', version: 1 })
458
- expect(path).toBe('/rpc/get-user-by-id/1')
529
+ expect(path).toBe('/get-user-by-id/1')
459
530
  })
460
531
 
461
532
  test('version number included in path', () => {
@@ -463,9 +534,9 @@ describe('ExpressRPCAppBuilder', () => {
463
534
  const pathV2 = builder.makeRPCHttpRoutePath({ name: 'test', version: 2 })
464
535
  const pathV99 = builder.makeRPCHttpRoutePath({ name: 'test', version: 99 })
465
536
 
466
- expect(pathV1).toBe('/rpc/test/1')
467
- expect(pathV2).toBe('/rpc/test/2')
468
- expect(pathV99).toBe('/rpc/test/99')
537
+ expect(pathV1).toBe('/test/1')
538
+ expect(pathV2).toBe('/test/2')
539
+ expect(pathV99).toBe('/test/99')
469
540
  })
470
541
 
471
542
  test('handles mixed case in array segments', () => {
@@ -473,7 +544,7 @@ describe('ExpressRPCAppBuilder', () => {
473
544
  name: ['UserModule', 'getActiveUsers'],
474
545
  version: 1,
475
546
  })
476
- expect(path).toBe('/rpc/user-module/get-active-users/1')
547
+ expect(path).toBe('/user-module/get-active-users/1')
477
548
  })
478
549
  })
479
550
 
@@ -492,16 +563,17 @@ describe('ExpressRPCAppBuilder', () => {
492
563
  const returnSchema = v.object({ name: v.string() })
493
564
 
494
565
  const RPC = Procedures<{}, RPCConfig>()
495
- const { info } = RPC.Create(
566
+ RPC.Create(
496
567
  'GetUser',
497
568
  { name: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } },
498
569
  async () => ({ name: 'test' })
499
570
  )
500
571
 
501
- const procedure = RPC.getProcedures()[0]!
502
- const doc = builder.buildRpcHttpRouteDoc(procedure)
572
+ builder.register(RPC, () => ({}))
573
+ builder.build()
503
574
 
504
- expect(doc.path).toBe('/rpc/users/1')
575
+ const doc = builder.docs[0]!
576
+ expect(doc.path).toBe('/users/1')
505
577
  expect(doc.method).toBe('post')
506
578
  expect(doc.jsonSchema.body).toBeDefined()
507
579
  expect(doc.jsonSchema.response).toBeDefined()
@@ -511,9 +583,10 @@ describe('ExpressRPCAppBuilder', () => {
511
583
  const RPC = Procedures<{}, RPCConfig>()
512
584
  RPC.Create('NoParams', { name: 'no-params', version: 1 }, async () => ({ ok: true }))
513
585
 
514
- const procedure = RPC.getProcedures()[0]!
515
- const doc = builder.buildRpcHttpRouteDoc(procedure)
586
+ builder.register(RPC, () => ({}))
587
+ builder.build()
516
588
 
589
+ const doc = builder.docs[0]!
517
590
  expect(doc.jsonSchema.body).toBeUndefined()
518
591
  })
519
592
 
@@ -525,9 +598,10 @@ describe('ExpressRPCAppBuilder', () => {
525
598
  async () => ({})
526
599
  )
527
600
 
528
- const procedure = RPC.getProcedures()[0]!
529
- const doc = builder.buildRpcHttpRouteDoc(procedure)
601
+ builder.register(RPC, () => ({}))
602
+ builder.build()
530
603
 
604
+ const doc = builder.docs[0]!
531
605
  expect(doc.jsonSchema.body).toBeDefined()
532
606
  expect(doc.jsonSchema.response).toBeUndefined()
533
607
  })
@@ -537,10 +611,10 @@ describe('ExpressRPCAppBuilder', () => {
537
611
  RPC.Create('Test1', { name: 't1', version: 1 }, async () => ({}))
538
612
  RPC.Create('Test2', { name: 't2', version: 2 }, async () => ({}))
539
613
 
540
- const procedures = RPC.getProcedures()
614
+ builder.register(RPC, () => ({}))
615
+ builder.build()
541
616
 
542
- procedures.forEach((proc) => {
543
- const doc = builder.buildRpcHttpRouteDoc(proc)
617
+ builder.docs.forEach((doc) => {
544
618
  expect(doc.method).toBe('post')
545
619
  })
546
620
  })
@@ -608,24 +682,24 @@ describe('ExpressRPCAppBuilder', () => {
608
682
  const app = builder.build()
609
683
 
610
684
  // Test public endpoints
611
- const versionRes = await request(app).post('/rpc/system/version/1').send({})
685
+ const versionRes = await request(app).post('/system/version/1').send({})
612
686
  expect(versionRes.status).toBe(200)
613
687
  expect(versionRes.body).toEqual({ version: '1.0.0' })
614
688
 
615
- const healthRes = await request(app).post('/rpc/health/1').send({})
689
+ const healthRes = await request(app).post('/health/1').send({})
616
690
  expect(healthRes.status).toBe(200)
617
691
  expect(healthRes.body).toEqual({ status: 'ok' })
618
692
 
619
693
  // Test authenticated endpoints
620
694
  const profileRes = await request(app)
621
- .post('/rpc/users/profile/1')
695
+ .post('/users/profile/1')
622
696
  .set('X-User-Id', 'user-123')
623
697
  .send({})
624
698
  expect(profileRes.status).toBe(200)
625
699
  expect(profileRes.body).toEqual({ userId: 'user-123', source: 'auth' })
626
700
 
627
701
  const updateRes = await request(app)
628
- .post('/rpc/users/profile/2')
702
+ .post('/users/profile/2')
629
703
  .set('X-User-Id', 'user-456')
630
704
  .send({ name: 'John Doe' })
631
705
  expect(updateRes.status).toBe(200)
@@ -635,10 +709,10 @@ describe('ExpressRPCAppBuilder', () => {
635
709
  expect(builder.docs).toHaveLength(4)
636
710
 
637
711
  const paths = builder.docs.map((d) => d.path)
638
- expect(paths).toContain('/rpc/system/version/1')
639
- expect(paths).toContain('/rpc/health/1')
640
- expect(paths).toContain('/rpc/users/profile/1')
641
- expect(paths).toContain('/rpc/users/profile/2')
712
+ expect(paths).toContain('/system/version/1')
713
+ expect(paths).toContain('/health/1')
714
+ expect(paths).toContain('/users/profile/1')
715
+ expect(paths).toContain('/users/profile/2')
642
716
 
643
717
  // Verify hooks were called
644
718
  expect(events).toContain('request-start')
@@ -6,6 +6,31 @@ import { castArray } from 'es-toolkit/compat'
6
6
  import { ExpressFactoryItem, ExtractContext, ProceduresFactory } from './types.js'
7
7
 
8
8
  export type { RPCConfig, RPCHttpRouteDoc }
9
+
10
+ export type ExpressRPCAppBuilderConfig = {
11
+ /**
12
+ * An existing Express application instance to use.
13
+ * When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
14
+ * If not provided, a new instance will be created.
15
+ */
16
+ app?: express.Express
17
+ /** Optional path prefix for all RPC routes. */
18
+ pathPrefix?: string
19
+ onRequestStart?: (req: express.Request) => void
20
+ onRequestEnd?: (req: express.Request, res: express.Response) => void
21
+ onSuccess?: (
22
+ procedure: TProcedureRegistration,
23
+ req: express.Request,
24
+ res: express.Response
25
+ ) => void
26
+ error?: (
27
+ procedure: TProcedureRegistration,
28
+ req: express.Request,
29
+ res: express.Response,
30
+ error: Error
31
+ ) => void
32
+ }
33
+
9
34
  /**
10
35
  * Builder class for creating an Express application with RPC routes.
11
36
  *
@@ -27,29 +52,7 @@ export class ExpressRPCAppBuilder {
27
52
  *
28
53
  * @param config
29
54
  */
30
- constructor(
31
- readonly config?: {
32
- /**
33
- * An existing Express application instance to use.
34
- * When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
35
- * If not provided, a new instance will be created.
36
- */
37
- app?: express.Express
38
- onRequestStart?: (req: express.Request) => void
39
- onRequestEnd?: (req: express.Request, res: express.Response) => void
40
- onSuccess?: (
41
- procedure: TProcedureRegistration,
42
- req: express.Request,
43
- res: express.Response
44
- ) => void
45
- error?: (
46
- procedure: TProcedureRegistration,
47
- req: express.Request,
48
- res: express.Response,
49
- error: Error
50
- ) => void
51
- }
52
- ) {
55
+ constructor(readonly config?: ExpressRPCAppBuilderConfig) {
53
56
  if (config?.app) {
54
57
  this._app = config.app
55
58
  } else {
@@ -74,6 +77,34 @@ export class ExpressRPCAppBuilder {
74
77
  }
75
78
  }
76
79
 
80
+ /**
81
+ * Generates the RPC route path based on the RPC configuration.
82
+ * The RPCConfig name can be a string or an array of strings to form nested paths.
83
+ *
84
+ * Example
85
+ * name: ['string', 'string-string', 'string']
86
+ * path: /string/string-string/string/version
87
+ * @param config
88
+ */
89
+ static makeRPCHttpRoutePath({ config, prefix }: { prefix?: string; config: RPCConfig }) {
90
+ const normalizedPrefix = prefix
91
+ ? (prefix.startsWith('/') ? prefix : `/${prefix}`)
92
+ : ''
93
+
94
+ return `${normalizedPrefix}/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`
95
+ }
96
+
97
+ /**
98
+ * Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
99
+ * @param config - The RPC configuration
100
+ */
101
+ makeRPCHttpRoutePath(config: RPCConfig): string {
102
+ return ExpressRPCAppBuilder.makeRPCHttpRoutePath({
103
+ config,
104
+ prefix: this.config?.pathPrefix,
105
+ })
106
+ }
107
+
77
108
  private factories: ExpressFactoryItem<any>[] = []
78
109
 
79
110
  private _app: express.Express = express()
@@ -153,9 +184,12 @@ export class ExpressRPCAppBuilder {
153
184
  * Generates the RPC HTTP route for the given procedure.
154
185
  * @param procedure
155
186
  */
156
- buildRpcHttpRouteDoc(procedure: TProcedureRegistration<any, RPCConfig>): RPCHttpRouteDoc {
187
+ private buildRpcHttpRouteDoc(procedure: TProcedureRegistration<any, RPCConfig>): RPCHttpRouteDoc {
157
188
  const { config } = procedure
158
- const path = this.makeRPCHttpRoutePath(config)
189
+ const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
190
+ config,
191
+ prefix: this.config?.pathPrefix,
192
+ })
159
193
  const method = 'post' // RPCs use POST method
160
194
  const jsonSchema: { body?: object; response?: object } = {}
161
195
 
@@ -172,17 +206,4 @@ export class ExpressRPCAppBuilder {
172
206
  jsonSchema,
173
207
  }
174
208
  }
175
-
176
- /**
177
- * Generates the RPC route path based on the RPC configuration.
178
- * The RPCConfig name can be a string or an array of strings to form nested paths.
179
- *
180
- * Example
181
- * name: ['string', 'string-string', 'string']
182
- * path: /rpc/string/string-string/string/version
183
- * @param config
184
- */
185
- makeRPCHttpRoutePath(config: RPCConfig) {
186
- return `/rpc/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`
187
- }
188
209
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/client/index.ts"],"names":[],"mappings":""}
@@ -1,97 +0,0 @@
1
- export interface ProceduresContext {
2
- headers: Record<string, string>;
3
- ipAddress: string;
4
- requestId: string;
5
- }
6
- export interface ProtectedContext extends ProceduresContext {
7
- userId: string;
8
- }
9
- export interface PublicContext extends ProceduresContext {
10
- userId?: undefined;
11
- }
12
- export declare const ProtectedFactory: {
13
- getProcedures: () => import("../../../../index.js").TProcedureRegistration<ProtectedContext, HTTPRouteConfig>[];
14
- Create: <TName extends string, TParams, TReturnType>(name: TName, config: {
15
- description?: string;
16
- schema?: {
17
- params?: TParams | undefined;
18
- returnType?: TReturnType | undefined;
19
- } | undefined;
20
- } & HTTPRouteConfig, handler: (ctx: {
21
- userId: string;
22
- headers: Record<string, string>;
23
- ipAddress: string;
24
- requestId: string;
25
- error: (message: string, meta?: object) => import("../../../../errors.js").ProcedureError;
26
- }, params: import("../../../../exports.js").TSchemaLib<TParams>) => Promise<import("../../../../exports.js").TSchemaLib<TReturnType>>) => { [K in TName]: (ctx: {
27
- userId: string;
28
- headers: Record<string, string>;
29
- ipAddress: string;
30
- requestId: string;
31
- }, params: import("../../../../exports.js").TSchemaLib<TParams>) => Promise<import("../../../../exports.js").TSchemaLib<TReturnType>>; } & {
32
- procedure: (ctx: {
33
- userId: string;
34
- headers: Record<string, string>;
35
- ipAddress: string;
36
- requestId: string;
37
- }, params: import("../../../../exports.js").TSchemaLib<TParams>) => Promise<import("../../../../exports.js").TSchemaLib<TReturnType>>;
38
- info: {
39
- name: TName;
40
- description?: string;
41
- schema: {
42
- params?: TParams | undefined;
43
- returnType?: TReturnType | undefined;
44
- };
45
- validation?: {
46
- params?: ((params: any) => {
47
- errors?: any[];
48
- }) | undefined;
49
- } | undefined;
50
- } & HTTPRouteConfig;
51
- };
52
- };
53
- export declare const PublicFactory: {
54
- getProcedures: () => import("../../../../index.js").TProcedureRegistration<PublicContext, HTTPRouteConfig>[];
55
- Create: <TName extends string, TParams, TReturnType>(name: TName, config: {
56
- description?: string;
57
- schema?: {
58
- params?: TParams | undefined;
59
- returnType?: TReturnType | undefined;
60
- } | undefined;
61
- } & HTTPRouteConfig, handler: (ctx: {
62
- userId?: undefined | undefined;
63
- headers: Record<string, string>;
64
- ipAddress: string;
65
- requestId: string;
66
- error: (message: string, meta?: object) => import("../../../../errors.js").ProcedureError;
67
- }, params: import("../../../../exports.js").TSchemaLib<TParams>) => Promise<import("../../../../exports.js").TSchemaLib<TReturnType>>) => { [K in TName]: (ctx: {
68
- userId?: undefined | undefined;
69
- headers: Record<string, string>;
70
- ipAddress: string;
71
- requestId: string;
72
- }, params: import("../../../../exports.js").TSchemaLib<TParams>) => Promise<import("../../../../exports.js").TSchemaLib<TReturnType>>; } & {
73
- procedure: (ctx: {
74
- userId?: undefined | undefined;
75
- headers: Record<string, string>;
76
- ipAddress: string;
77
- requestId: string;
78
- }, params: import("../../../../exports.js").TSchemaLib<TParams>) => Promise<import("../../../../exports.js").TSchemaLib<TReturnType>>;
79
- info: {
80
- name: TName;
81
- description?: string;
82
- schema: {
83
- params?: TParams | undefined;
84
- returnType?: TReturnType | undefined;
85
- };
86
- validation?: {
87
- params?: ((params: any) => {
88
- errors?: any[];
89
- }) | undefined;
90
- } | undefined;
91
- } & HTTPRouteConfig;
92
- };
93
- };
94
- export interface HTTPRouteConfig {
95
- path: string;
96
- method: 'get' | 'post' | 'put' | 'delete' | 'patch';
97
- }
@@ -1,4 +0,0 @@
1
- import { Procedures } from '../../../../index.js';
2
- export const ProtectedFactory = Procedures();
3
- export const PublicFactory = Procedures();
4
- //# sourceMappingURL=factories.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"factories.js","sourceRoot":"","sources":["../../../../../src/implementations/http/express/example/factories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAiBjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,EAAoC,CAAA;AAE9E,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,EAAiC,CAAA"}