ts-procedures 2.1.1 → 3.0.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 (46) hide show
  1. package/build/errors.d.ts +2 -1
  2. package/build/errors.js +3 -2
  3. package/build/errors.js.map +1 -1
  4. package/build/errors.test.d.ts +1 -0
  5. package/build/errors.test.js +40 -0
  6. package/build/errors.test.js.map +1 -0
  7. package/build/implementations/http/express-rpc/index.d.ts +3 -2
  8. package/build/implementations/http/express-rpc/index.js +6 -6
  9. package/build/implementations/http/express-rpc/index.js.map +1 -1
  10. package/build/implementations/http/express-rpc/index.test.js +93 -93
  11. package/build/implementations/http/express-rpc/index.test.js.map +1 -1
  12. package/build/implementations/http/hono-rpc/index.d.ts +83 -0
  13. package/build/implementations/http/hono-rpc/index.js +148 -0
  14. package/build/implementations/http/hono-rpc/index.js.map +1 -0
  15. package/build/implementations/http/hono-rpc/index.test.d.ts +1 -0
  16. package/build/implementations/http/hono-rpc/index.test.js +647 -0
  17. package/build/implementations/http/hono-rpc/index.test.js.map +1 -0
  18. package/build/implementations/http/hono-rpc/types.d.ts +28 -0
  19. package/build/implementations/http/hono-rpc/types.js +2 -0
  20. package/build/implementations/http/hono-rpc/types.js.map +1 -0
  21. package/build/implementations/types.d.ts +1 -1
  22. package/build/index.d.ts +12 -0
  23. package/build/index.js +29 -7
  24. package/build/index.js.map +1 -1
  25. package/build/index.test.js +65 -0
  26. package/build/index.test.js.map +1 -1
  27. package/build/schema/parser.js +3 -0
  28. package/build/schema/parser.js.map +1 -1
  29. package/build/schema/parser.test.js +18 -0
  30. package/build/schema/parser.test.js.map +1 -1
  31. package/package.json +10 -5
  32. package/src/errors.test.ts +53 -0
  33. package/src/errors.ts +4 -2
  34. package/src/implementations/http/README.md +172 -0
  35. package/src/implementations/http/express-rpc/README.md +151 -242
  36. package/src/implementations/http/express-rpc/index.test.ts +93 -93
  37. package/src/implementations/http/express-rpc/index.ts +15 -7
  38. package/src/implementations/http/hono-rpc/README.md +293 -0
  39. package/src/implementations/http/hono-rpc/index.test.ts +847 -0
  40. package/src/implementations/http/hono-rpc/index.ts +202 -0
  41. package/src/implementations/http/hono-rpc/types.ts +33 -0
  42. package/src/implementations/types.ts +2 -1
  43. package/src/index.test.ts +83 -0
  44. package/src/index.ts +34 -8
  45. package/src/schema/parser.test.ts +26 -0
  46. package/src/schema/parser.ts +5 -1
@@ -23,7 +23,7 @@ describe('ExpressRPCAppBuilder', () => {
23
23
 
24
24
  RPC.Create(
25
25
  'Echo',
26
- { name: 'echo', version: 1, schema: { params: v.object({ message: v.string() }) } },
26
+ { scope: 'echo', version: 1, schema: { params: v.object({ message: v.string() }) } },
27
27
  async (ctx, params) => params
28
28
  )
29
29
 
@@ -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('/echo/1').send({ message: 'hello' })
34
+ const res = await request(app).post('/echo/echo/1').send({ message: 'hello' })
35
35
 
36
36
  expect(res.status).toBe(200)
37
37
  expect(res.body).toEqual({ message: 'hello' })
@@ -45,7 +45,7 @@ describe('ExpressRPCAppBuilder', () => {
45
45
 
46
46
  RPC.Create(
47
47
  'Echo',
48
- { name: 'echo', version: 1, schema: { params: v.object({ message: v.string() }) } },
48
+ { scope: 'echo', version: 1, schema: { params: v.object({ message: v.string() }) } },
49
49
  async (ctx, params) => ({ received: params })
50
50
  )
51
51
 
@@ -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('/echo/1')
57
+ .post('/echo/echo/1')
58
58
  .set('Content-Type', 'application/json')
59
59
  .send(JSON.stringify({ message: 'hello' }))
60
60
 
@@ -84,12 +84,12 @@ describe('ExpressRPCAppBuilder', () => {
84
84
  const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api/v1' })
85
85
  const RPC = Procedures<{}, RPCConfig>()
86
86
 
87
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
87
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
88
88
 
89
89
  builder.register(RPC, () => ({}))
90
90
  const app = builder.build()
91
91
 
92
- const res = await request(app).post('/api/v1/test/1').send({})
92
+ const res = await request(app).post('/api/v1/test/test/1').send({})
93
93
  expect(res.status).toBe(200)
94
94
  expect(res.body).toEqual({ ok: true })
95
95
  })
@@ -98,12 +98,12 @@ describe('ExpressRPCAppBuilder', () => {
98
98
  const builder = new ExpressRPCAppBuilder({ pathPrefix: 'custom' })
99
99
  const RPC = Procedures<{}, RPCConfig>()
100
100
 
101
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
101
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
102
102
 
103
103
  builder.register(RPC, () => ({}))
104
104
  const app = builder.build()
105
105
 
106
- const res = await request(app).post('/custom/test/1').send({})
106
+ const res = await request(app).post('/custom/test/test/1').send({})
107
107
  expect(res.status).toBe(200)
108
108
  })
109
109
 
@@ -111,12 +111,12 @@ describe('ExpressRPCAppBuilder', () => {
111
111
  const builder = new ExpressRPCAppBuilder()
112
112
  const RPC = Procedures<{}, RPCConfig>()
113
113
 
114
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
114
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
115
115
 
116
116
  builder.register(RPC, () => ({}))
117
117
  const app = builder.build()
118
118
 
119
- const res = await request(app).post('/test/1').send({})
119
+ const res = await request(app).post('/test/test/1').send({})
120
120
  expect(res.status).toBe(200)
121
121
  })
122
122
 
@@ -124,26 +124,26 @@ describe('ExpressRPCAppBuilder', () => {
124
124
  const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api' })
125
125
  const RPC = Procedures<{}, RPCConfig>()
126
126
 
127
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({}))
127
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({}))
128
128
 
129
129
  builder.register(RPC, () => ({}))
130
130
  builder.build()
131
131
 
132
- expect(builder.docs[0]!.path).toBe('/api/test/1')
132
+ expect(builder.docs[0]!.path).toBe('/api/test/test/1')
133
133
  })
134
134
 
135
135
  test('pathPrefix /rpc restores original behavior', async () => {
136
136
  const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' })
137
137
  const RPC = Procedures<{}, RPCConfig>()
138
138
 
139
- RPC.Create('Users', { name: 'users', version: 1 }, async () => ({ users: [] }))
139
+ RPC.Create('Users', { scope: 'users', version: 1 }, async () => ({ users: [] }))
140
140
 
141
141
  builder.register(RPC, () => ({}))
142
142
  const app = builder.build()
143
143
 
144
- const res = await request(app).post('/rpc/users/1').send({})
144
+ const res = await request(app).post('/rpc/users/users/1').send({})
145
145
  expect(res.status).toBe(200)
146
- expect(builder.docs[0]!.path).toBe('/rpc/users/1')
146
+ expect(builder.docs[0]!.path).toBe('/rpc/users/users/1')
147
147
  })
148
148
  })
149
149
 
@@ -156,16 +156,16 @@ describe('ExpressRPCAppBuilder', () => {
156
156
  const builder = new ExpressRPCAppBuilder({ onRequestStart })
157
157
  const RPC = Procedures<{}, RPCConfig>()
158
158
 
159
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
159
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
160
160
 
161
161
  builder.register(RPC, () => ({}))
162
162
  const app = builder.build()
163
163
 
164
- await request(app).post('/test/1').send({})
164
+ await request(app).post('/test/test/1').send({})
165
165
 
166
166
  expect(onRequestStart).toHaveBeenCalledTimes(1)
167
167
  expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('method', 'POST')
168
- expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('path', '/test/1')
168
+ expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('path', '/test/test/1')
169
169
  })
170
170
 
171
171
  test('onRequestEnd is called after response finishes', async () => {
@@ -173,12 +173,12 @@ describe('ExpressRPCAppBuilder', () => {
173
173
  const builder = new ExpressRPCAppBuilder({ onRequestEnd })
174
174
  const RPC = Procedures<{}, RPCConfig>()
175
175
 
176
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
176
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
177
177
 
178
178
  builder.register(RPC, () => ({}))
179
179
  const app = builder.build()
180
180
 
181
- await request(app).post('/test/1').send({})
181
+ await request(app).post('/test/test/1').send({})
182
182
 
183
183
  expect(onRequestEnd).toHaveBeenCalledTimes(1)
184
184
  expect(onRequestEnd.mock.calls[0]![0]).toHaveProperty('method', 'POST')
@@ -190,12 +190,12 @@ describe('ExpressRPCAppBuilder', () => {
190
190
  const builder = new ExpressRPCAppBuilder({ onSuccess })
191
191
  const RPC = Procedures<{}, RPCConfig>()
192
192
 
193
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
193
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
194
194
 
195
195
  builder.register(RPC, () => ({}))
196
196
  const app = builder.build()
197
197
 
198
- await request(app).post('/test/1').send({})
198
+ await request(app).post('/test/test/1').send({})
199
199
 
200
200
  expect(onSuccess).toHaveBeenCalledTimes(1)
201
201
  expect(onSuccess.mock.calls[0]![0]).toHaveProperty('name', 'Test')
@@ -206,14 +206,14 @@ describe('ExpressRPCAppBuilder', () => {
206
206
  const builder = new ExpressRPCAppBuilder({ onSuccess })
207
207
  const RPC = Procedures<{}, RPCConfig>()
208
208
 
209
- RPC.Create('Test', { name: 'test', version: 1 }, async () => {
209
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
210
210
  throw new Error('Handler error')
211
211
  })
212
212
 
213
213
  builder.register(RPC, () => ({}))
214
214
  const app = builder.build()
215
215
 
216
- await request(app).post('/test/1').send({})
216
+ await request(app).post('/test/test/1').send({})
217
217
 
218
218
  expect(onSuccess).not.toHaveBeenCalled()
219
219
  })
@@ -228,7 +228,7 @@ describe('ExpressRPCAppBuilder', () => {
228
228
  })
229
229
  const RPC = Procedures<{}, RPCConfig>()
230
230
 
231
- RPC.Create('Test', { name: 'test', version: 1 }, async () => {
231
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
232
232
  order.push('handler')
233
233
  return { ok: true }
234
234
  })
@@ -236,7 +236,7 @@ describe('ExpressRPCAppBuilder', () => {
236
236
  builder.register(RPC, () => ({}))
237
237
  const app = builder.build()
238
238
 
239
- await request(app).post('/test/1').send({})
239
+ await request(app).post('/test/test/1').send({})
240
240
 
241
241
  expect(order).toEqual(['start', 'handler', 'success', 'end'])
242
242
  })
@@ -254,14 +254,14 @@ describe('ExpressRPCAppBuilder', () => {
254
254
  const builder = new ExpressRPCAppBuilder({ error: errorHandler })
255
255
  const RPC = Procedures<{}, RPCConfig>()
256
256
 
257
- RPC.Create('Test', { name: 'test', version: 1 }, async () => {
257
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
258
258
  throw new Error('Test error')
259
259
  })
260
260
 
261
261
  builder.register(RPC, () => ({}))
262
262
  const app = builder.build()
263
263
 
264
- const res = await request(app).post('/test/1').send({})
264
+ const res = await request(app).post('/test/test/1').send({})
265
265
 
266
266
  expect(errorHandler).toHaveBeenCalledTimes(1)
267
267
  expect(errorHandler.mock.calls[0]![0]).toHaveProperty('name', 'Test')
@@ -275,14 +275,14 @@ describe('ExpressRPCAppBuilder', () => {
275
275
  const builder = new ExpressRPCAppBuilder()
276
276
  const RPC = Procedures<{}, RPCConfig>()
277
277
 
278
- RPC.Create('Test', { name: 'test', version: 1 }, async () => {
278
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
279
279
  throw new Error('Something went wrong')
280
280
  })
281
281
 
282
282
  builder.register(RPC, () => ({}))
283
283
  const app = builder.build()
284
284
 
285
- const res = await request(app).post('/test/1').send({})
285
+ const res = await request(app).post('/test/test/1').send({})
286
286
 
287
287
  // Default error handler returns error message in JSON body
288
288
  expect(res.body).toHaveProperty('error')
@@ -293,7 +293,7 @@ describe('ExpressRPCAppBuilder', () => {
293
293
  const builder = new ExpressRPCAppBuilder()
294
294
  const RPC = Procedures<{}, RPCConfig>()
295
295
 
296
- RPC.Create('Test', { name: 'test', version: 1 }, async () => {
296
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
297
297
  // Simulate unhandled exception
298
298
  const obj: any = null
299
299
  return obj.property // This will throw
@@ -302,7 +302,7 @@ describe('ExpressRPCAppBuilder', () => {
302
302
  builder.register(RPC, () => ({}))
303
303
  const app = builder.build()
304
304
 
305
- const res = await request(app).post('/test/1').send({})
305
+ const res = await request(app).post('/test/test/1').send({})
306
306
 
307
307
  // Unhandled exceptions are caught and returned as error response
308
308
  expect(res.body).toHaveProperty('error')
@@ -333,11 +333,11 @@ describe('ExpressRPCAppBuilder', () => {
333
333
  const PublicRPC = Procedures<{ public: true }, RPCConfig>()
334
334
  const PrivateRPC = Procedures<{ private: true }, RPCConfig>()
335
335
 
336
- PublicRPC.Create('PublicMethod', { name: 'public', version: 1 }, async (ctx) => ({
336
+ PublicRPC.Create('PublicMethod', { scope: 'public', version: 1 }, async (ctx) => ({
337
337
  isPublic: ctx.public,
338
338
  }))
339
339
 
340
- PrivateRPC.Create('PrivateMethod', { name: 'private', version: 1 }, async (ctx) => ({
340
+ PrivateRPC.Create('PrivateMethod', { scope: 'private', version: 1 }, async (ctx) => ({
341
341
  isPrivate: ctx.private,
342
342
  }))
343
343
 
@@ -347,8 +347,8 @@ describe('ExpressRPCAppBuilder', () => {
347
347
 
348
348
  const app = builder.build()
349
349
 
350
- const publicRes = await request(app).post('/public/1').send({})
351
- const privateRes = await request(app).post('/private/1').send({})
350
+ const publicRes = await request(app).post('/public/public-method/1').send({})
351
+ const privateRes = await request(app).post('/private/private-method/1').send({})
352
352
 
353
353
  expect(publicRes.body).toEqual({ isPublic: true })
354
354
  expect(privateRes.body).toEqual({ isPrivate: true })
@@ -360,14 +360,14 @@ describe('ExpressRPCAppBuilder', () => {
360
360
  const builder = new ExpressRPCAppBuilder()
361
361
  const RPC = Procedures<{ requestId: string }, RPCConfig>()
362
362
 
363
- RPC.Create('GetRequestId', { name: 'get-request-id', version: 1 }, async (ctx) => ({
363
+ RPC.Create('GetRequestId', { scope: 'get-request-id', version: 1 }, async (ctx) => ({
364
364
  id: ctx.requestId,
365
365
  }))
366
366
 
367
367
  builder.register(RPC, factoryContext)
368
368
  const app = builder.build()
369
369
 
370
- const res = await request(app).post('/get-request-id/1').send({})
370
+ const res = await request(app).post('/get-request-id/get-request-id/1').send({})
371
371
 
372
372
  expect(res.body).toEqual({ id: 'req-123' })
373
373
  })
@@ -380,14 +380,14 @@ describe('ExpressRPCAppBuilder', () => {
380
380
  const builder = new ExpressRPCAppBuilder()
381
381
  const RPC = Procedures<{ requestId: string }, RPCConfig>()
382
382
 
383
- RPC.Create('GetRequestId', { name: 'get-request-id', version: 1 }, async (ctx) => ({
383
+ RPC.Create('GetRequestId', { scope: 'get-request-id', version: 1 }, async (ctx) => ({
384
384
  id: ctx.requestId,
385
385
  }))
386
386
 
387
387
  builder.register(RPC, factoryContext)
388
388
  const app = builder.build()
389
389
 
390
- await request(app).post('/get-request-id/1').send({})
390
+ await request(app).post('/get-request-id/get-request-id/1').send({})
391
391
 
392
392
  expect(factoryContext).toHaveBeenCalledTimes(1)
393
393
  })
@@ -400,14 +400,14 @@ describe('ExpressRPCAppBuilder', () => {
400
400
  const builder = new ExpressRPCAppBuilder()
401
401
  const RPC = Procedures<{ authHeader?: string }, RPCConfig>()
402
402
 
403
- RPC.Create('GetAuth', { name: 'get-auth', version: 1 }, async (ctx) => ({
403
+ RPC.Create('GetAuth', { scope: 'get-auth', version: 1 }, async (ctx) => ({
404
404
  auth: ctx.authHeader,
405
405
  }))
406
406
 
407
407
  builder.register(RPC, factoryContext)
408
408
  const app = builder.build()
409
409
 
410
- await request(app).post('/get-auth/1').set('Authorization', 'Bearer token123').send({})
410
+ await request(app).post('/get-auth/get-auth/1').set('Authorization', 'Bearer token123').send({})
411
411
 
412
412
  expect(factoryContext).toHaveBeenCalledTimes(1)
413
413
  expect(factoryContext.mock.calls[0]![0]).toHaveProperty('headers')
@@ -426,14 +426,14 @@ describe('ExpressRPCAppBuilder', () => {
426
426
  const builder = new ExpressRPCAppBuilder()
427
427
  const RPC = Procedures<{}, RPCConfig>()
428
428
 
429
- RPC.Create('MethodOne', { name: 'method-one', version: 1 }, async () => ({ m: 1 }))
430
- RPC.Create('MethodTwo', { name: 'method-two', version: 2 }, async () => ({ m: 2 }))
429
+ RPC.Create('MethodOne', { scope: 'method-one', version: 1 }, async () => ({ m: 1 }))
430
+ RPC.Create('MethodTwo', { scope: 'method-two', version: 2 }, async () => ({ m: 2 }))
431
431
 
432
432
  builder.register(RPC, () => ({}))
433
433
  const app = builder.build()
434
434
 
435
- const res1 = await request(app).post('/method-one/1').send({})
436
- const res2 = await request(app).post('/method-two/2').send({})
435
+ const res1 = await request(app).post('/method-one/method-one/1').send({})
436
+ const res2 = await request(app).post('/method-two/method-two/2').send({})
437
437
 
438
438
  expect(res1.status).toBe(200)
439
439
  expect(res2.status).toBe(200)
@@ -453,8 +453,8 @@ describe('ExpressRPCAppBuilder', () => {
453
453
  const builder = new ExpressRPCAppBuilder()
454
454
  const RPC = Procedures<{}, RPCConfig>()
455
455
 
456
- RPC.Create('MethodOne', { name: 'method-one', version: 1 }, async () => ({}))
457
- RPC.Create('MethodTwo', { name: ['nested', 'method'], version: 2 }, async () => ({}))
456
+ RPC.Create('MethodOne', { scope: 'method-one', version: 1 }, async () => ({}))
457
+ RPC.Create('MethodTwo', { scope: ['nested', 'method'], version: 2 }, async () => ({}))
458
458
 
459
459
  expect(builder.docs).toHaveLength(0)
460
460
 
@@ -462,8 +462,8 @@ describe('ExpressRPCAppBuilder', () => {
462
462
  builder.build()
463
463
 
464
464
  expect(builder.docs).toHaveLength(2)
465
- expect(builder.docs[0]!.path).toBe('/method-one/1')
466
- expect(builder.docs[1]!.path).toBe('/nested/method/2')
465
+ expect(builder.docs[0]!.path).toBe('/method-one/method-one/1')
466
+ expect(builder.docs[1]!.path).toBe('/nested/method/method-two/2')
467
467
  })
468
468
 
469
469
  test('passes request body to handler as params', async () => {
@@ -472,14 +472,14 @@ describe('ExpressRPCAppBuilder', () => {
472
472
 
473
473
  RPC.Create(
474
474
  'Echo',
475
- { name: 'echo', version: 1, schema: { params: v.object({ data: v.string() }) } },
475
+ { scope: 'echo', version: 1, schema: { params: v.object({ data: v.string() }) } },
476
476
  async (ctx, params) => ({ received: params.data })
477
477
  )
478
478
 
479
479
  builder.register(RPC, () => ({}))
480
480
  const app = builder.build()
481
481
 
482
- const res = await request(app).post('/echo/1').send({ data: 'test-data' })
482
+ const res = await request(app).post('/echo/echo/1').send({ data: 'test-data' })
483
483
 
484
484
  expect(res.body).toEqual({ received: 'test-data' })
485
485
  })
@@ -488,12 +488,12 @@ describe('ExpressRPCAppBuilder', () => {
488
488
  const builder = new ExpressRPCAppBuilder()
489
489
  const RPC = Procedures<{}, RPCConfig>()
490
490
 
491
- RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }))
491
+ RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
492
492
 
493
493
  builder.register(RPC, () => ({}))
494
494
  const app = builder.build()
495
495
 
496
- const res = await request(app).get('/test/1')
496
+ const res = await request(app).get('/test/test/1')
497
497
 
498
498
  expect(res.status).toBe(404)
499
499
  })
@@ -509,42 +509,42 @@ describe('ExpressRPCAppBuilder', () => {
509
509
  builder = new ExpressRPCAppBuilder()
510
510
  })
511
511
 
512
- test("simple string: 'users' → /users/1", () => {
513
- const path = builder.makeRPCHttpRoutePath({ name: 'users', version: 1 })
514
- expect(path).toBe('/users/1')
512
+ test("simple scope with procedure name: 'users' + 'GetUser' → /users/get-user/1", () => {
513
+ const path = builder.makeRPCHttpRoutePath('GetUser', { scope: 'users', version: 1 })
514
+ expect(path).toBe('/users/get-user/1')
515
515
  })
516
516
 
517
- test("array name: ['users', 'get-by-id'] → /users/get-by-id/1", () => {
518
- const path = builder.makeRPCHttpRoutePath({ name: ['users', 'get-by-id'], version: 1 })
519
- expect(path).toBe('/users/get-by-id/1')
517
+ test("array scope with procedure name: ['users', 'profile'] + 'GetById' → /users/profile/get-by-id/1", () => {
518
+ const path = builder.makeRPCHttpRoutePath('GetById', { scope: ['users', 'profile'], version: 1 })
519
+ expect(path).toBe('/users/profile/get-by-id/1')
520
520
  })
521
521
 
522
- test("camelCase: 'getUserById' → /get-user-by-id/1", () => {
523
- const path = builder.makeRPCHttpRoutePath({ name: 'getUserById', version: 1 })
524
- expect(path).toBe('/get-user-by-id/1')
522
+ test("camelCase procedure name: 'users' + 'getProfile' → /users/get-profile/1", () => {
523
+ const path = builder.makeRPCHttpRoutePath('getProfile', { scope: 'users', version: 1 })
524
+ expect(path).toBe('/users/get-profile/1')
525
525
  })
526
526
 
527
- test("PascalCase: 'GetUserById' → /get-user-by-id/1", () => {
528
- const path = builder.makeRPCHttpRoutePath({ name: 'GetUserById', version: 1 })
529
- expect(path).toBe('/get-user-by-id/1')
527
+ test("PascalCase procedure name: 'users' + 'UpdateProfile' → /users/update-profile/1", () => {
528
+ const path = builder.makeRPCHttpRoutePath('UpdateProfile', { scope: 'users', version: 1 })
529
+ expect(path).toBe('/users/update-profile/1')
530
530
  })
531
531
 
532
532
  test('version number included in path', () => {
533
- const pathV1 = builder.makeRPCHttpRoutePath({ name: 'test', version: 1 })
534
- const pathV2 = builder.makeRPCHttpRoutePath({ name: 'test', version: 2 })
535
- const pathV99 = builder.makeRPCHttpRoutePath({ name: 'test', version: 99 })
533
+ const pathV1 = builder.makeRPCHttpRoutePath('Test', { scope: 'test', version: 1 })
534
+ const pathV2 = builder.makeRPCHttpRoutePath('Test', { scope: 'test', version: 2 })
535
+ const pathV99 = builder.makeRPCHttpRoutePath('Test', { scope: 'test', version: 99 })
536
536
 
537
- expect(pathV1).toBe('/test/1')
538
- expect(pathV2).toBe('/test/2')
539
- expect(pathV99).toBe('/test/99')
537
+ expect(pathV1).toBe('/test/test/1')
538
+ expect(pathV2).toBe('/test/test/2')
539
+ expect(pathV99).toBe('/test/test/99')
540
540
  })
541
541
 
542
542
  test('handles mixed case in array segments', () => {
543
- const path = builder.makeRPCHttpRoutePath({
544
- name: ['UserModule', 'getActiveUsers'],
543
+ const path = builder.makeRPCHttpRoutePath('ListUsers', {
544
+ scope: ['UserModule', 'getActiveUsers'],
545
545
  version: 1,
546
546
  })
547
- expect(path).toBe('/user-module/get-active-users/1')
547
+ expect(path).toBe('/user-module/get-active-users/list-users/1')
548
548
  })
549
549
  })
550
550
 
@@ -565,7 +565,7 @@ describe('ExpressRPCAppBuilder', () => {
565
565
  const RPC = Procedures<{}, RPCConfig>()
566
566
  RPC.Create(
567
567
  'GetUser',
568
- { name: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } },
568
+ { scope: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } },
569
569
  async () => ({ name: 'test' })
570
570
  )
571
571
 
@@ -573,7 +573,7 @@ describe('ExpressRPCAppBuilder', () => {
573
573
  builder.build()
574
574
 
575
575
  const doc = builder.docs[0]!
576
- expect(doc.path).toBe('/users/1')
576
+ expect(doc.path).toBe('/users/get-user/1')
577
577
  expect(doc.method).toBe('post')
578
578
  expect(doc.jsonSchema.body).toBeDefined()
579
579
  expect(doc.jsonSchema.response).toBeDefined()
@@ -581,7 +581,7 @@ describe('ExpressRPCAppBuilder', () => {
581
581
 
582
582
  test('omits body schema when no params defined', () => {
583
583
  const RPC = Procedures<{}, RPCConfig>()
584
- RPC.Create('NoParams', { name: 'no-params', version: 1 }, async () => ({ ok: true }))
584
+ RPC.Create('NoParams', { scope: 'no-params', version: 1 }, async () => ({ ok: true }))
585
585
 
586
586
  builder.register(RPC, () => ({}))
587
587
  builder.build()
@@ -594,7 +594,7 @@ describe('ExpressRPCAppBuilder', () => {
594
594
  const RPC = Procedures<{}, RPCConfig>()
595
595
  RPC.Create(
596
596
  'NoReturn',
597
- { name: 'no-return', version: 1, schema: { params: v.object({ x: v.number() }) } },
597
+ { scope: 'no-return', version: 1, schema: { params: v.object({ x: v.number() }) } },
598
598
  async () => ({})
599
599
  )
600
600
 
@@ -608,8 +608,8 @@ describe('ExpressRPCAppBuilder', () => {
608
608
 
609
609
  test("method is always 'post'", () => {
610
610
  const RPC = Procedures<{}, RPCConfig>()
611
- RPC.Create('Test1', { name: 't1', version: 1 }, async () => ({}))
612
- RPC.Create('Test2', { name: 't2', version: 2 }, async () => ({}))
611
+ RPC.Create('Test1', { scope: 't1', version: 1 }, async () => ({}))
612
+ RPC.Create('Test2', { scope: 't2', version: 2 }, async () => ({}))
613
613
 
614
614
  builder.register(RPC, () => ({}))
615
615
  builder.build()
@@ -634,11 +634,11 @@ describe('ExpressRPCAppBuilder', () => {
634
634
  const AuthRPC = Procedures<AuthContext, RPCConfig>()
635
635
 
636
636
  // Create public procedures
637
- PublicRPC.Create('GetVersion', { name: ['system', 'version'], version: 1 }, async () => ({
637
+ PublicRPC.Create('GetVersion', { scope: ['system', 'version'], version: 1 }, async () => ({
638
638
  version: '1.0.0',
639
639
  }))
640
640
 
641
- PublicRPC.Create('HealthCheck', { name: 'health', version: 1 }, async () => ({
641
+ PublicRPC.Create('HealthCheck', { scope: 'health', version: 1 }, async () => ({
642
642
  status: 'ok',
643
643
  }))
644
644
 
@@ -646,7 +646,7 @@ describe('ExpressRPCAppBuilder', () => {
646
646
  AuthRPC.Create(
647
647
  'GetProfile',
648
648
  {
649
- name: ['users', 'profile'],
649
+ scope: ['users', 'profile'],
650
650
  version: 1,
651
651
  schema: { returnType: v.object({ userId: v.string(), source: v.string() }) },
652
652
  },
@@ -656,7 +656,7 @@ describe('ExpressRPCAppBuilder', () => {
656
656
  AuthRPC.Create(
657
657
  'UpdateProfile',
658
658
  {
659
- name: ['users', 'profile'],
659
+ scope: ['users', 'profile'],
660
660
  version: 2,
661
661
  schema: { params: v.object({ name: v.string() }) },
662
662
  },
@@ -682,24 +682,24 @@ describe('ExpressRPCAppBuilder', () => {
682
682
  const app = builder.build()
683
683
 
684
684
  // Test public endpoints
685
- const versionRes = await request(app).post('/system/version/1').send({})
685
+ const versionRes = await request(app).post('/system/version/get-version/1').send({})
686
686
  expect(versionRes.status).toBe(200)
687
687
  expect(versionRes.body).toEqual({ version: '1.0.0' })
688
688
 
689
- const healthRes = await request(app).post('/health/1').send({})
689
+ const healthRes = await request(app).post('/health/health-check/1').send({})
690
690
  expect(healthRes.status).toBe(200)
691
691
  expect(healthRes.body).toEqual({ status: 'ok' })
692
692
 
693
693
  // Test authenticated endpoints
694
694
  const profileRes = await request(app)
695
- .post('/users/profile/1')
695
+ .post('/users/profile/get-profile/1')
696
696
  .set('X-User-Id', 'user-123')
697
697
  .send({})
698
698
  expect(profileRes.status).toBe(200)
699
699
  expect(profileRes.body).toEqual({ userId: 'user-123', source: 'auth' })
700
700
 
701
701
  const updateRes = await request(app)
702
- .post('/users/profile/2')
702
+ .post('/users/profile/update-profile/2')
703
703
  .set('X-User-Id', 'user-456')
704
704
  .send({ name: 'John Doe' })
705
705
  expect(updateRes.status).toBe(200)
@@ -709,10 +709,10 @@ describe('ExpressRPCAppBuilder', () => {
709
709
  expect(builder.docs).toHaveLength(4)
710
710
 
711
711
  const paths = builder.docs.map((d) => d.path)
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')
712
+ expect(paths).toContain('/system/version/get-version/1')
713
+ expect(paths).toContain('/health/health-check/1')
714
+ expect(paths).toContain('/users/profile/get-profile/1')
715
+ expect(paths).toContain('/users/profile/update-profile/2')
716
716
 
717
717
  // Verify hooks were called
718
718
  expect(events).toContain('request-start')
@@ -86,20 +86,27 @@ export class ExpressRPCAppBuilder {
86
86
  * path: /string/string-string/string/version
87
87
  * @param config
88
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()}`
89
+ static makeRPCHttpRoutePath({
90
+ name,
91
+ config,
92
+ prefix,
93
+ }: {
94
+ name: string
95
+ prefix?: string
96
+ config: RPCConfig
97
+ }) {
98
+ const normalizedPrefix = prefix ? (prefix.startsWith('/') ? prefix : `/${prefix}`) : ''
99
+
100
+ return `${normalizedPrefix}/${castArray(config.scope).map(kebabCase).join('/')}/${kebabCase(name)}/${String(config.version).trim()}`
95
101
  }
96
102
 
97
103
  /**
98
104
  * Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
99
105
  * @param config - The RPC configuration
100
106
  */
101
- makeRPCHttpRoutePath(config: RPCConfig): string {
107
+ makeRPCHttpRoutePath(name: string, config: RPCConfig): string {
102
108
  return ExpressRPCAppBuilder.makeRPCHttpRoutePath({
109
+ name,
103
110
  config,
104
111
  prefix: this.config?.pathPrefix,
105
112
  })
@@ -187,6 +194,7 @@ export class ExpressRPCAppBuilder {
187
194
  private buildRpcHttpRouteDoc(procedure: TProcedureRegistration<any, RPCConfig>): RPCHttpRouteDoc {
188
195
  const { config } = procedure
189
196
  const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
197
+ name: procedure.name,
190
198
  config,
191
199
  prefix: this.config?.pathPrefix,
192
200
  })