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
@@ -3,6 +3,20 @@ import { TProcedureRegistration } from '../../../index.js';
3
3
  import { RPCConfig, RPCHttpRouteDoc } from '../../types.js';
4
4
  import { ExtractContext, ProceduresFactory } from './types.js';
5
5
  export type { RPCConfig, RPCHttpRouteDoc };
6
+ export type ExpressRPCAppBuilderConfig = {
7
+ /**
8
+ * An existing Express application instance to use.
9
+ * When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
10
+ * If not provided, a new instance will be created.
11
+ */
12
+ app?: express.Express;
13
+ /** Optional path prefix for all RPC routes. */
14
+ pathPrefix?: string;
15
+ onRequestStart?: (req: express.Request) => void;
16
+ onRequestEnd?: (req: express.Request, res: express.Response) => void;
17
+ onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void;
18
+ error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void;
19
+ };
6
20
  /**
7
21
  * Builder class for creating an Express application with RPC routes.
8
22
  *
@@ -19,35 +33,31 @@ export type { RPCConfig, RPCHttpRouteDoc };
19
33
  * const docs = rpcApp.docs; // RPC route documentation
20
34
  */
21
35
  export declare class ExpressRPCAppBuilder {
22
- readonly config?: {
23
- /**
24
- * An existing Express application instance to use.
25
- * When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
26
- * If not provided, a new instance will be created.
27
- */
28
- app?: express.Express;
29
- onRequestStart?: (req: express.Request) => void;
30
- onRequestEnd?: (req: express.Request, res: express.Response) => void;
31
- onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void;
32
- error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void;
33
- } | undefined;
36
+ readonly config?: ExpressRPCAppBuilderConfig | undefined;
34
37
  /**
35
38
  * Constructor for ExpressRPCAppBuilder.
36
39
  *
37
40
  * @param config
38
41
  */
39
- constructor(config?: {
40
- /**
41
- * An existing Express application instance to use.
42
- * When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
43
- * If not provided, a new instance will be created.
44
- */
45
- app?: express.Express;
46
- onRequestStart?: (req: express.Request) => void;
47
- onRequestEnd?: (req: express.Request, res: express.Response) => void;
48
- onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void;
49
- error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void;
50
- } | undefined);
42
+ constructor(config?: ExpressRPCAppBuilderConfig | undefined);
43
+ /**
44
+ * Generates the RPC route path based on the RPC configuration.
45
+ * The RPCConfig name can be a string or an array of strings to form nested paths.
46
+ *
47
+ * Example
48
+ * name: ['string', 'string-string', 'string']
49
+ * path: /string/string-string/string/version
50
+ * @param config
51
+ */
52
+ static makeRPCHttpRoutePath({ config, prefix }: {
53
+ prefix?: string;
54
+ config: RPCConfig;
55
+ }): string;
56
+ /**
57
+ * Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
58
+ * @param config - The RPC configuration
59
+ */
60
+ makeRPCHttpRoutePath(config: RPCConfig): string;
51
61
  private factories;
52
62
  private _app;
53
63
  private _docs;
@@ -69,15 +79,5 @@ export declare class ExpressRPCAppBuilder {
69
79
  * Generates the RPC HTTP route for the given procedure.
70
80
  * @param procedure
71
81
  */
72
- buildRpcHttpRouteDoc(procedure: TProcedureRegistration<any, RPCConfig>): RPCHttpRouteDoc;
73
- /**
74
- * Generates the RPC route path based on the RPC configuration.
75
- * The RPCConfig name can be a string or an array of strings to form nested paths.
76
- *
77
- * Example
78
- * name: ['string', 'string-string', 'string']
79
- * path: /rpc/string/string-string/string/version
80
- * @param config
81
- */
82
- makeRPCHttpRoutePath(config: RPCConfig): string;
82
+ private buildRpcHttpRouteDoc;
83
83
  }
@@ -47,6 +47,31 @@ export class ExpressRPCAppBuilder {
47
47
  });
48
48
  }
49
49
  }
50
+ /**
51
+ * Generates the RPC route path based on the RPC configuration.
52
+ * The RPCConfig name can be a string or an array of strings to form nested paths.
53
+ *
54
+ * Example
55
+ * name: ['string', 'string-string', 'string']
56
+ * path: /string/string-string/string/version
57
+ * @param config
58
+ */
59
+ static makeRPCHttpRoutePath({ config, prefix }) {
60
+ const normalizedPrefix = prefix
61
+ ? (prefix.startsWith('/') ? prefix : `/${prefix}`)
62
+ : '';
63
+ return `${normalizedPrefix}/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`;
64
+ }
65
+ /**
66
+ * Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
67
+ * @param config - The RPC configuration
68
+ */
69
+ makeRPCHttpRoutePath(config) {
70
+ return ExpressRPCAppBuilder.makeRPCHttpRoutePath({
71
+ config,
72
+ prefix: this.config?.pathPrefix,
73
+ });
74
+ }
50
75
  factories = [];
51
76
  _app = express();
52
77
  _docs = [];
@@ -113,7 +138,10 @@ export class ExpressRPCAppBuilder {
113
138
  */
114
139
  buildRpcHttpRouteDoc(procedure) {
115
140
  const { config } = procedure;
116
- const path = this.makeRPCHttpRoutePath(config);
141
+ const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
142
+ config,
143
+ prefix: this.config?.pathPrefix,
144
+ });
117
145
  const method = 'post'; // RPCs use POST method
118
146
  const jsonSchema = {};
119
147
  if (config.schema?.params) {
@@ -128,17 +156,5 @@ export class ExpressRPCAppBuilder {
128
156
  jsonSchema,
129
157
  };
130
158
  }
131
- /**
132
- * Generates the RPC route path based on the RPC configuration.
133
- * The RPCConfig name can be a string or an array of strings to form nested paths.
134
- *
135
- * Example
136
- * name: ['string', 'string-string', 'string']
137
- * path: /rpc/string/string-string/string/version
138
- * @param config
139
- */
140
- makeRPCHttpRoutePath(config) {
141
- return `/rpc/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`;
142
- }
143
159
  }
144
160
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/express-rpc/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/express-rpc/index.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,7 @@ import { ExpressRPCAppBuilder } from './index.js';
8
8
  * ExpressRPCAppBuilder Test Suite
9
9
  *
10
10
  * Tests the RPC-style Express integration for ts-procedures.
11
- * This builder creates POST routes at `/rpc/{name}/{version}` paths.
11
+ * This builder creates POST routes at `/{name}/{version}` paths (with optional pathPrefix).
12
12
  */
13
13
  describe('ExpressRPCAppBuilder', () => {
14
14
  // --------------------------------------------------------------------------
@@ -22,7 +22,7 @@ describe('ExpressRPCAppBuilder', () => {
22
22
  builder.register(RPC, () => ({ userId: '123' }));
23
23
  const app = builder.build();
24
24
  // JSON body should be parsed automatically
25
- const res = await request(app).post('/rpc/echo/1').send({ message: 'hello' });
25
+ const res = await request(app).post('/echo/1').send({ message: 'hello' });
26
26
  expect(res.status).toBe(200);
27
27
  expect(res.body).toEqual({ message: 'hello' });
28
28
  });
@@ -36,7 +36,7 @@ describe('ExpressRPCAppBuilder', () => {
36
36
  const app = builder.build();
37
37
  // Without json middleware, body won't be parsed (req.body is undefined)
38
38
  const res = await request(app)
39
- .post('/rpc/echo/1')
39
+ .post('/echo/1')
40
40
  .set('Content-Type', 'application/json')
41
41
  .send(JSON.stringify({ message: 'hello' }));
42
42
  // Request body is undefined since json middleware wasn't added
@@ -55,6 +55,57 @@ describe('ExpressRPCAppBuilder', () => {
55
55
  });
56
56
  });
57
57
  // --------------------------------------------------------------------------
58
+ // pathPrefix Option Tests
59
+ // --------------------------------------------------------------------------
60
+ describe('pathPrefix option', () => {
61
+ test('uses custom pathPrefix for all routes', async () => {
62
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api/v1' });
63
+ const RPC = Procedures();
64
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
65
+ builder.register(RPC, () => ({}));
66
+ const app = builder.build();
67
+ const res = await request(app).post('/api/v1/test/1').send({});
68
+ expect(res.status).toBe(200);
69
+ expect(res.body).toEqual({ ok: true });
70
+ });
71
+ test('pathPrefix without leading slash gets normalized', async () => {
72
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: 'custom' });
73
+ const RPC = Procedures();
74
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
75
+ builder.register(RPC, () => ({}));
76
+ const app = builder.build();
77
+ const res = await request(app).post('/custom/test/1').send({});
78
+ expect(res.status).toBe(200);
79
+ });
80
+ test('no prefix when pathPrefix not specified', async () => {
81
+ const builder = new ExpressRPCAppBuilder();
82
+ const RPC = Procedures();
83
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
84
+ builder.register(RPC, () => ({}));
85
+ const app = builder.build();
86
+ const res = await request(app).post('/test/1').send({});
87
+ expect(res.status).toBe(200);
88
+ });
89
+ test('pathPrefix appears in generated docs', () => {
90
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api' });
91
+ const RPC = Procedures();
92
+ RPC.Create('Test', { name: 'test', version: 1 }, async () => ({}));
93
+ builder.register(RPC, () => ({}));
94
+ builder.build();
95
+ expect(builder.docs[0].path).toBe('/api/test/1');
96
+ });
97
+ test('pathPrefix /rpc restores original behavior', async () => {
98
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' });
99
+ const RPC = Procedures();
100
+ RPC.Create('Users', { name: 'users', version: 1 }, async () => ({ users: [] }));
101
+ builder.register(RPC, () => ({}));
102
+ const app = builder.build();
103
+ const res = await request(app).post('/rpc/users/1').send({});
104
+ expect(res.status).toBe(200);
105
+ expect(builder.docs[0].path).toBe('/rpc/users/1');
106
+ });
107
+ });
108
+ // --------------------------------------------------------------------------
58
109
  // Lifecycle Hooks Tests
59
110
  // --------------------------------------------------------------------------
60
111
  describe('lifecycle hooks', () => {
@@ -65,10 +116,10 @@ describe('ExpressRPCAppBuilder', () => {
65
116
  RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
66
117
  builder.register(RPC, () => ({}));
67
118
  const app = builder.build();
68
- await request(app).post('/rpc/test/1').send({});
119
+ await request(app).post('/test/1').send({});
69
120
  expect(onRequestStart).toHaveBeenCalledTimes(1);
70
121
  expect(onRequestStart.mock.calls[0][0]).toHaveProperty('method', 'POST');
71
- expect(onRequestStart.mock.calls[0][0]).toHaveProperty('path', '/rpc/test/1');
122
+ expect(onRequestStart.mock.calls[0][0]).toHaveProperty('path', '/test/1');
72
123
  });
73
124
  test('onRequestEnd is called after response finishes', async () => {
74
125
  const onRequestEnd = vi.fn();
@@ -77,7 +128,7 @@ describe('ExpressRPCAppBuilder', () => {
77
128
  RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
78
129
  builder.register(RPC, () => ({}));
79
130
  const app = builder.build();
80
- await request(app).post('/rpc/test/1').send({});
131
+ await request(app).post('/test/1').send({});
81
132
  expect(onRequestEnd).toHaveBeenCalledTimes(1);
82
133
  expect(onRequestEnd.mock.calls[0][0]).toHaveProperty('method', 'POST');
83
134
  expect(onRequestEnd.mock.calls[0][1]).toHaveProperty('statusCode', 200);
@@ -89,7 +140,7 @@ describe('ExpressRPCAppBuilder', () => {
89
140
  RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
90
141
  builder.register(RPC, () => ({}));
91
142
  const app = builder.build();
92
- await request(app).post('/rpc/test/1').send({});
143
+ await request(app).post('/test/1').send({});
93
144
  expect(onSuccess).toHaveBeenCalledTimes(1);
94
145
  expect(onSuccess.mock.calls[0][0]).toHaveProperty('name', 'Test');
95
146
  });
@@ -102,7 +153,7 @@ describe('ExpressRPCAppBuilder', () => {
102
153
  });
103
154
  builder.register(RPC, () => ({}));
104
155
  const app = builder.build();
105
- await request(app).post('/rpc/test/1').send({});
156
+ await request(app).post('/test/1').send({});
106
157
  expect(onSuccess).not.toHaveBeenCalled();
107
158
  });
108
159
  test('hooks execute in correct order: start → handler → success → end', async () => {
@@ -119,7 +170,7 @@ describe('ExpressRPCAppBuilder', () => {
119
170
  });
120
171
  builder.register(RPC, () => ({}));
121
172
  const app = builder.build();
122
- await request(app).post('/rpc/test/1').send({});
173
+ await request(app).post('/test/1').send({});
123
174
  expect(order).toEqual(['start', 'handler', 'success', 'end']);
124
175
  });
125
176
  });
@@ -138,7 +189,7 @@ describe('ExpressRPCAppBuilder', () => {
138
189
  });
139
190
  builder.register(RPC, () => ({}));
140
191
  const app = builder.build();
141
- const res = await request(app).post('/rpc/test/1').send({});
192
+ const res = await request(app).post('/test/1').send({});
142
193
  expect(errorHandler).toHaveBeenCalledTimes(1);
143
194
  expect(errorHandler.mock.calls[0][0]).toHaveProperty('name', 'Test');
144
195
  expect(errorHandler.mock.calls[0][3]).toBeInstanceOf(Error);
@@ -154,7 +205,7 @@ describe('ExpressRPCAppBuilder', () => {
154
205
  });
155
206
  builder.register(RPC, () => ({}));
156
207
  const app = builder.build();
157
- const res = await request(app).post('/rpc/test/1').send({});
208
+ const res = await request(app).post('/test/1').send({});
158
209
  // Default error handler returns error message in JSON body
159
210
  expect(res.body).toHaveProperty('error');
160
211
  expect(res.body.error).toContain('Something went wrong');
@@ -169,7 +220,7 @@ describe('ExpressRPCAppBuilder', () => {
169
220
  });
170
221
  builder.register(RPC, () => ({}));
171
222
  const app = builder.build();
172
- const res = await request(app).post('/rpc/test/1').send({});
223
+ const res = await request(app).post('/test/1').send({});
173
224
  // Unhandled exceptions are caught and returned as error response
174
225
  expect(res.body).toHaveProperty('error');
175
226
  });
@@ -202,8 +253,8 @@ describe('ExpressRPCAppBuilder', () => {
202
253
  .register(PublicRPC, () => ({ public: true }))
203
254
  .register(PrivateRPC, () => ({ private: true }));
204
255
  const app = builder.build();
205
- const publicRes = await request(app).post('/rpc/public/1').send({});
206
- const privateRes = await request(app).post('/rpc/private/1').send({});
256
+ const publicRes = await request(app).post('/public/1').send({});
257
+ const privateRes = await request(app).post('/private/1').send({});
207
258
  expect(publicRes.body).toEqual({ isPublic: true });
208
259
  expect(privateRes.body).toEqual({ isPrivate: true });
209
260
  });
@@ -216,7 +267,7 @@ describe('ExpressRPCAppBuilder', () => {
216
267
  }));
217
268
  builder.register(RPC, factoryContext);
218
269
  const app = builder.build();
219
- const res = await request(app).post('/rpc/get-request-id/1').send({});
270
+ const res = await request(app).post('/get-request-id/1').send({});
220
271
  expect(res.body).toEqual({ id: 'req-123' });
221
272
  });
222
273
  test('factoryContext can be async function', async () => {
@@ -230,7 +281,7 @@ describe('ExpressRPCAppBuilder', () => {
230
281
  }));
231
282
  builder.register(RPC, factoryContext);
232
283
  const app = builder.build();
233
- await request(app).post('/rpc/get-request-id/1').send({});
284
+ await request(app).post('/get-request-id/1').send({});
234
285
  expect(factoryContext).toHaveBeenCalledTimes(1);
235
286
  });
236
287
  test('factoryContext function receives Express request object', async () => {
@@ -244,7 +295,7 @@ describe('ExpressRPCAppBuilder', () => {
244
295
  }));
245
296
  builder.register(RPC, factoryContext);
246
297
  const app = builder.build();
247
- await request(app).post('/rpc/get-auth/1').set('Authorization', 'Bearer token123').send({});
298
+ await request(app).post('/get-auth/1').set('Authorization', 'Bearer token123').send({});
248
299
  expect(factoryContext).toHaveBeenCalledTimes(1);
249
300
  expect(factoryContext.mock.calls[0][0]).toHaveProperty('headers');
250
301
  expect(factoryContext.mock.calls[0][0].headers).toHaveProperty('authorization', 'Bearer token123');
@@ -261,8 +312,8 @@ describe('ExpressRPCAppBuilder', () => {
261
312
  RPC.Create('MethodTwo', { name: 'method-two', version: 2 }, async () => ({ m: 2 }));
262
313
  builder.register(RPC, () => ({}));
263
314
  const app = builder.build();
264
- const res1 = await request(app).post('/rpc/method-one/1').send({});
265
- const res2 = await request(app).post('/rpc/method-two/2').send({});
315
+ const res1 = await request(app).post('/method-one/1').send({});
316
+ const res2 = await request(app).post('/method-two/2').send({});
266
317
  expect(res1.status).toBe(200);
267
318
  expect(res2.status).toBe(200);
268
319
  expect(res1.body).toEqual({ m: 1 });
@@ -283,8 +334,8 @@ describe('ExpressRPCAppBuilder', () => {
283
334
  builder.register(RPC, () => ({}));
284
335
  builder.build();
285
336
  expect(builder.docs).toHaveLength(2);
286
- expect(builder.docs[0].path).toBe('/rpc/method-one/1');
287
- expect(builder.docs[1].path).toBe('/rpc/nested/method/2');
337
+ expect(builder.docs[0].path).toBe('/method-one/1');
338
+ expect(builder.docs[1].path).toBe('/nested/method/2');
288
339
  });
289
340
  test('passes request body to handler as params', async () => {
290
341
  const builder = new ExpressRPCAppBuilder();
@@ -292,7 +343,7 @@ describe('ExpressRPCAppBuilder', () => {
292
343
  RPC.Create('Echo', { name: 'echo', version: 1, schema: { params: v.object({ data: v.string() }) } }, async (ctx, params) => ({ received: params.data }));
293
344
  builder.register(RPC, () => ({}));
294
345
  const app = builder.build();
295
- const res = await request(app).post('/rpc/echo/1').send({ data: 'test-data' });
346
+ const res = await request(app).post('/echo/1').send({ data: 'test-data' });
296
347
  expect(res.body).toEqual({ received: 'test-data' });
297
348
  });
298
349
  test('GET requests return 404 (RPC uses POST only)', async () => {
@@ -301,7 +352,7 @@ describe('ExpressRPCAppBuilder', () => {
301
352
  RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
302
353
  builder.register(RPC, () => ({}));
303
354
  const app = builder.build();
304
- const res = await request(app).get('/rpc/test/1');
355
+ const res = await request(app).get('/test/1');
305
356
  expect(res.status).toBe(404);
306
357
  });
307
358
  });
@@ -313,36 +364,36 @@ describe('ExpressRPCAppBuilder', () => {
313
364
  beforeEach(() => {
314
365
  builder = new ExpressRPCAppBuilder();
315
366
  });
316
- test("simple string: 'users' → /rpc/users/1", () => {
367
+ test("simple string: 'users' → /users/1", () => {
317
368
  const path = builder.makeRPCHttpRoutePath({ name: 'users', version: 1 });
318
- expect(path).toBe('/rpc/users/1');
369
+ expect(path).toBe('/users/1');
319
370
  });
320
- test("array name: ['users', 'get-by-id'] → /rpc/users/get-by-id/1", () => {
371
+ test("array name: ['users', 'get-by-id'] → /users/get-by-id/1", () => {
321
372
  const path = builder.makeRPCHttpRoutePath({ name: ['users', 'get-by-id'], version: 1 });
322
- expect(path).toBe('/rpc/users/get-by-id/1');
373
+ expect(path).toBe('/users/get-by-id/1');
323
374
  });
324
- test("camelCase: 'getUserById' → /rpc/get-user-by-id/1", () => {
375
+ test("camelCase: 'getUserById' → /get-user-by-id/1", () => {
325
376
  const path = builder.makeRPCHttpRoutePath({ name: 'getUserById', version: 1 });
326
- expect(path).toBe('/rpc/get-user-by-id/1');
377
+ expect(path).toBe('/get-user-by-id/1');
327
378
  });
328
- test("PascalCase: 'GetUserById' → /rpc/get-user-by-id/1", () => {
379
+ test("PascalCase: 'GetUserById' → /get-user-by-id/1", () => {
329
380
  const path = builder.makeRPCHttpRoutePath({ name: 'GetUserById', version: 1 });
330
- expect(path).toBe('/rpc/get-user-by-id/1');
381
+ expect(path).toBe('/get-user-by-id/1');
331
382
  });
332
383
  test('version number included in path', () => {
333
384
  const pathV1 = builder.makeRPCHttpRoutePath({ name: 'test', version: 1 });
334
385
  const pathV2 = builder.makeRPCHttpRoutePath({ name: 'test', version: 2 });
335
386
  const pathV99 = builder.makeRPCHttpRoutePath({ name: 'test', version: 99 });
336
- expect(pathV1).toBe('/rpc/test/1');
337
- expect(pathV2).toBe('/rpc/test/2');
338
- expect(pathV99).toBe('/rpc/test/99');
387
+ expect(pathV1).toBe('/test/1');
388
+ expect(pathV2).toBe('/test/2');
389
+ expect(pathV99).toBe('/test/99');
339
390
  });
340
391
  test('handles mixed case in array segments', () => {
341
392
  const path = builder.makeRPCHttpRoutePath({
342
393
  name: ['UserModule', 'getActiveUsers'],
343
394
  version: 1,
344
395
  });
345
- expect(path).toBe('/rpc/user-module/get-active-users/1');
396
+ expect(path).toBe('/user-module/get-active-users/1');
346
397
  });
347
398
  });
348
399
  // --------------------------------------------------------------------------
@@ -357,10 +408,11 @@ describe('ExpressRPCAppBuilder', () => {
357
408
  const paramsSchema = v.object({ id: v.string() });
358
409
  const returnSchema = v.object({ name: v.string() });
359
410
  const RPC = Procedures();
360
- const { info } = RPC.Create('GetUser', { name: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } }, async () => ({ name: 'test' }));
361
- const procedure = RPC.getProcedures()[0];
362
- const doc = builder.buildRpcHttpRouteDoc(procedure);
363
- expect(doc.path).toBe('/rpc/users/1');
411
+ RPC.Create('GetUser', { name: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } }, async () => ({ name: 'test' }));
412
+ builder.register(RPC, () => ({}));
413
+ builder.build();
414
+ const doc = builder.docs[0];
415
+ expect(doc.path).toBe('/users/1');
364
416
  expect(doc.method).toBe('post');
365
417
  expect(doc.jsonSchema.body).toBeDefined();
366
418
  expect(doc.jsonSchema.response).toBeDefined();
@@ -368,15 +420,17 @@ describe('ExpressRPCAppBuilder', () => {
368
420
  test('omits body schema when no params defined', () => {
369
421
  const RPC = Procedures();
370
422
  RPC.Create('NoParams', { name: 'no-params', version: 1 }, async () => ({ ok: true }));
371
- const procedure = RPC.getProcedures()[0];
372
- const doc = builder.buildRpcHttpRouteDoc(procedure);
423
+ builder.register(RPC, () => ({}));
424
+ builder.build();
425
+ const doc = builder.docs[0];
373
426
  expect(doc.jsonSchema.body).toBeUndefined();
374
427
  });
375
428
  test('omits response schema when no returnType defined', () => {
376
429
  const RPC = Procedures();
377
430
  RPC.Create('NoReturn', { name: 'no-return', version: 1, schema: { params: v.object({ x: v.number() }) } }, async () => ({}));
378
- const procedure = RPC.getProcedures()[0];
379
- const doc = builder.buildRpcHttpRouteDoc(procedure);
431
+ builder.register(RPC, () => ({}));
432
+ builder.build();
433
+ const doc = builder.docs[0];
380
434
  expect(doc.jsonSchema.body).toBeDefined();
381
435
  expect(doc.jsonSchema.response).toBeUndefined();
382
436
  });
@@ -384,9 +438,9 @@ describe('ExpressRPCAppBuilder', () => {
384
438
  const RPC = Procedures();
385
439
  RPC.Create('Test1', { name: 't1', version: 1 }, async () => ({}));
386
440
  RPC.Create('Test2', { name: 't2', version: 2 }, async () => ({}));
387
- const procedures = RPC.getProcedures();
388
- procedures.forEach((proc) => {
389
- const doc = builder.buildRpcHttpRouteDoc(proc);
441
+ builder.register(RPC, () => ({}));
442
+ builder.build();
443
+ builder.docs.forEach((doc) => {
390
444
  expect(doc.method).toBe('post');
391
445
  });
392
446
  });
@@ -432,21 +486,21 @@ describe('ExpressRPCAppBuilder', () => {
432
486
  }));
433
487
  const app = builder.build();
434
488
  // Test public endpoints
435
- const versionRes = await request(app).post('/rpc/system/version/1').send({});
489
+ const versionRes = await request(app).post('/system/version/1').send({});
436
490
  expect(versionRes.status).toBe(200);
437
491
  expect(versionRes.body).toEqual({ version: '1.0.0' });
438
- const healthRes = await request(app).post('/rpc/health/1').send({});
492
+ const healthRes = await request(app).post('/health/1').send({});
439
493
  expect(healthRes.status).toBe(200);
440
494
  expect(healthRes.body).toEqual({ status: 'ok' });
441
495
  // Test authenticated endpoints
442
496
  const profileRes = await request(app)
443
- .post('/rpc/users/profile/1')
497
+ .post('/users/profile/1')
444
498
  .set('X-User-Id', 'user-123')
445
499
  .send({});
446
500
  expect(profileRes.status).toBe(200);
447
501
  expect(profileRes.body).toEqual({ userId: 'user-123', source: 'auth' });
448
502
  const updateRes = await request(app)
449
- .post('/rpc/users/profile/2')
503
+ .post('/users/profile/2')
450
504
  .set('X-User-Id', 'user-456')
451
505
  .send({ name: 'John Doe' });
452
506
  expect(updateRes.status).toBe(200);
@@ -454,10 +508,10 @@ describe('ExpressRPCAppBuilder', () => {
454
508
  // Verify documentation
455
509
  expect(builder.docs).toHaveLength(4);
456
510
  const paths = builder.docs.map((d) => d.path);
457
- expect(paths).toContain('/rpc/system/version/1');
458
- expect(paths).toContain('/rpc/health/1');
459
- expect(paths).toContain('/rpc/users/profile/1');
460
- expect(paths).toContain('/rpc/users/profile/2');
511
+ expect(paths).toContain('/system/version/1');
512
+ expect(paths).toContain('/health/1');
513
+ expect(paths).toContain('/users/profile/1');
514
+ expect(paths).toContain('/users/profile/2');
461
515
  // Verify hooks were called
462
516
  expect(events).toContain('request-start');
463
517
  expect(events).toContain('success:GetVersion');