ts-procedures 2.0.1 → 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 (40) hide show
  1. package/build/implementations/http/express-rpc/index.d.ts +40 -39
  2. package/build/implementations/http/express-rpc/index.js +40 -20
  3. package/build/implementations/http/express-rpc/index.js.map +1 -1
  4. package/build/implementations/http/express-rpc/index.test.js +138 -58
  5. package/build/implementations/http/express-rpc/index.test.js.map +1 -1
  6. package/build/implementations/http/express-rpc/types.d.ts +1 -1
  7. package/build/implementations/types.d.ts +1 -1
  8. package/package.json +1 -1
  9. package/src/implementations/http/express-rpc/README.md +49 -25
  10. package/src/implementations/http/express-rpc/index.test.ts +170 -58
  11. package/src/implementations/http/express-rpc/index.ts +74 -45
  12. package/src/implementations/http/express-rpc/types.ts +6 -2
  13. package/src/implementations/types.ts +1 -1
  14. package/build/implementations/http/client/index.d.ts +0 -1
  15. package/build/implementations/http/client/index.js +0 -2
  16. package/build/implementations/http/client/index.js.map +0 -1
  17. package/build/implementations/http/express/example/factories.d.ts +0 -97
  18. package/build/implementations/http/express/example/factories.js +0 -4
  19. package/build/implementations/http/express/example/factories.js.map +0 -1
  20. package/build/implementations/http/express/example/procedures/auth.d.ts +0 -1
  21. package/build/implementations/http/express/example/procedures/auth.js +0 -22
  22. package/build/implementations/http/express/example/procedures/auth.js.map +0 -1
  23. package/build/implementations/http/express/example/procedures/users.d.ts +0 -1
  24. package/build/implementations/http/express/example/procedures/users.js +0 -30
  25. package/build/implementations/http/express/example/procedures/users.js.map +0 -1
  26. package/build/implementations/http/express/example/server.d.ts +0 -3
  27. package/build/implementations/http/express/example/server.js +0 -49
  28. package/build/implementations/http/express/example/server.js.map +0 -1
  29. package/build/implementations/http/express/example/server.test.d.ts +0 -1
  30. package/build/implementations/http/express/example/server.test.js +0 -110
  31. package/build/implementations/http/express/example/server.test.js.map +0 -1
  32. package/build/implementations/http/express/index.d.ts +0 -35
  33. package/build/implementations/http/express/index.js +0 -75
  34. package/build/implementations/http/express/index.js.map +0 -1
  35. package/build/implementations/http/express/index.test.d.ts +0 -1
  36. package/build/implementations/http/express/index.test.js +0 -329
  37. package/build/implementations/http/express/index.test.js.map +0 -1
  38. package/build/implementations/http/express/types.d.ts +0 -17
  39. package/build/implementations/http/express/types.js +0 -2
  40. 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,46 +33,43 @@ 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;
54
64
  get app(): express.Express;
55
65
  get docs(): RPCHttpRouteDoc[];
56
66
  /**
57
- * Registers a procedure factory with its context resolver.
58
- * @param factory
59
- * @param contextResolver
67
+ * Registers a procedure factory with its context.
68
+ * @param factory - The procedure factory created by Procedures<Context, RPCConfig>()
69
+ * @param factoryContext - The context for procedure handlers. Can be a direct value,
70
+ * a sync function (req) => Context, or an async function (req) => Promise<Context>
60
71
  */
61
- register<TFactory extends ProceduresFactory>(factory: TFactory, contextResolver: (req: express.Request) => ExtractContext<TFactory>): this;
72
+ register<TFactory extends ProceduresFactory>(factory: TFactory, factoryContext: ExtractContext<TFactory> | ((req: express.Request) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>)): this;
62
73
  /**
63
74
  * Builds and returns the Express application with registered RPC routes.
64
75
  * @return express.Application
@@ -68,15 +79,5 @@ export declare class ExpressRPCAppBuilder {
68
79
  * Generates the RPC HTTP route for the given procedure.
69
80
  * @param procedure
70
81
  */
71
- buildRpcHttpRouteDoc(procedure: TProcedureRegistration<any, RPCConfig>): RPCHttpRouteDoc;
72
- /**
73
- * Generates the RPC route path based on the RPC configuration.
74
- * The RPCConfig name can be a string or an array of strings to form nested paths.
75
- *
76
- * Example
77
- * name: ['string', 'string-string', 'string']
78
- * path: /rpc/string/string-string/string/version
79
- * @param config
80
- */
81
- makeRPCHttpRoutePath(config: RPCConfig): string;
82
+ private buildRpcHttpRouteDoc;
82
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 = [];
@@ -57,12 +82,13 @@ export class ExpressRPCAppBuilder {
57
82
  return this._docs;
58
83
  }
59
84
  /**
60
- * Registers a procedure factory with its context resolver.
61
- * @param factory
62
- * @param contextResolver
85
+ * Registers a procedure factory with its context.
86
+ * @param factory - The procedure factory created by Procedures<Context, RPCConfig>()
87
+ * @param factoryContext - The context for procedure handlers. Can be a direct value,
88
+ * a sync function (req) => Context, or an async function (req) => Promise<Context>
63
89
  */
64
- register(factory, contextResolver) {
65
- this.factories.push({ factory, contextResolver });
90
+ register(factory, factoryContext) {
91
+ this.factories.push({ factory, factoryContext });
66
92
  return this;
67
93
  }
68
94
  /**
@@ -70,13 +96,16 @@ export class ExpressRPCAppBuilder {
70
96
  * @return express.Application
71
97
  */
72
98
  build() {
73
- this.factories.forEach(({ factory, contextResolver }) => {
99
+ this.factories.forEach(({ factory, factoryContext }) => {
74
100
  factory.getProcedures().map((procedure) => {
75
101
  const route = this.buildRpcHttpRouteDoc(procedure);
76
102
  this._docs.push(route);
77
103
  this._app[route.method](route.path, async (req, res) => {
78
104
  try {
79
- res.json(await procedure.handler(contextResolver(req), req.body));
105
+ const context = typeof factoryContext === 'function'
106
+ ? await factoryContext(req)
107
+ : factoryContext;
108
+ res.json(await procedure.handler(context, req.body));
80
109
  if (this.config?.onSuccess) {
81
110
  this.config.onSuccess(procedure, req, res);
82
111
  }
@@ -109,7 +138,10 @@ export class ExpressRPCAppBuilder {
109
138
  */
110
139
  buildRpcHttpRouteDoc(procedure) {
111
140
  const { config } = procedure;
112
- const path = this.makeRPCHttpRoutePath(config);
141
+ const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
142
+ config,
143
+ prefix: this.config?.pathPrefix,
144
+ });
113
145
  const method = 'post'; // RPCs use POST method
114
146
  const jsonSchema = {};
115
147
  if (config.schema?.params) {
@@ -124,17 +156,5 @@ export class ExpressRPCAppBuilder {
124
156
  jsonSchema,
125
157
  };
126
158
  }
127
- /**
128
- * Generates the RPC route path based on the RPC configuration.
129
- * The RPCConfig name can be a string or an array of strings to form nested paths.
130
- *
131
- * Example
132
- * name: ['string', 'string-string', 'string']
133
- * path: /rpc/string/string-string/string/version
134
- * @param config
135
- */
136
- makeRPCHttpRoutePath(config) {
137
- return `/rpc/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`;
138
- }
139
159
  }
140
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":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAG7C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AA6B7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,oBAAoB;IAMV;IALrB;;;;OAIG;IACH,YAAqB,MAAmC;QAAnC,WAAM,GAAN,MAAM,CAA6B;QACtD,IAAI,MAAM,EAAE,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,2CAA2C;YAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;QAC/B,CAAC;QAED,IAAI,MAAM,EAAE,cAAc,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBAC/B,MAAM,CAAC,cAAe,CAAC,GAAG,CAAC,CAAA;gBAC3B,IAAI,EAAE,CAAA;YACR,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,MAAM,EAAE,YAAY,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBAC/B,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;oBACpB,MAAM,CAAC,YAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAChC,CAAC,CAAC,CAAA;gBACF,IAAI,EAAE,CAAA;YACR,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,MAAM,EAA0C;QACpF,MAAM,gBAAgB,GAAG,MAAM;YAC7B,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;YAClD,CAAC,CAAC,EAAE,CAAA;QAEN,OAAO,GAAG,gBAAgB,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;IAClH,CAAC;IAED;;;OAGG;IACH,oBAAoB,CAAC,MAAiB;QACpC,OAAO,oBAAoB,CAAC,oBAAoB,CAAC;YAC/C,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;SAChC,CAAC,CAAA;IACJ,CAAC;IAEO,SAAS,GAA8B,EAAE,CAAA;IAEzC,IAAI,GAAoB,OAAO,EAAE,CAAA;IACjC,KAAK,GAAsB,EAAE,CAAA;IAErC,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CACN,OAAiB,EACjB,cAE4F;QAE5F,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAA6B,CAAC,CAAA;QAC3E,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE;YACrD,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,SAAiD,EAAE,EAAE;gBAChF,MAAM,KAAK,GAAG,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAA;gBAElD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAEtB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;oBACrD,IAAI,CAAC;wBACH,MAAM,OAAO,GACX,OAAO,cAAc,KAAK,UAAU;4BAClC,CAAC,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC;4BAC3B,CAAC,CAAE,cAAiD,CAAA;wBAExD,GAAG,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;wBACpD,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;4BAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;wBAC5C,CAAC;wBACD,gCAAgC;wBAChC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;4BAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACjB,CAAC;oBACH,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;4BACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,KAAc,CAAC,CAAA;4BACtD,OAAM;wBACR,CAAC;wBACD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;4BAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACjB,CAAC;wBACD,gDAAgD;wBAChD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;4BACrB,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;wBAC/C,CAAC;oBACH,CAAC;gBACH,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED;;;OAGG;IACK,oBAAoB,CAAC,SAAiD;QAC5E,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;QAC5B,MAAM,IAAI,GAAG,oBAAoB,CAAC,oBAAoB,CAAC;YACrD,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;SAChC,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,CAAA,CAAC,uBAAuB;QAC7C,MAAM,UAAU,GAAyC,EAAE,CAAA;QAE3D,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YAC1B,UAAU,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QACxC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;YAC9B,UAAU,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAA;QAChD,CAAC;QAED,OAAO;YACL,IAAI;YACJ,MAAM;YACN,UAAU;SACX,CAAA;IACH,CAAC;CACF"}
@@ -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,13 +253,39 @@ 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
  });
210
- test('context resolver receives Express request object', async () => {
211
- const contextResolver = vi.fn((req) => ({
261
+ test('context can be a static object', async () => {
262
+ const factoryContext = { requestId: 'req-123' };
263
+ const builder = new ExpressRPCAppBuilder();
264
+ const RPC = Procedures();
265
+ RPC.Create('GetRequestId', { name: 'get-request-id', version: 1 }, async (ctx) => ({
266
+ id: ctx.requestId,
267
+ }));
268
+ builder.register(RPC, factoryContext);
269
+ const app = builder.build();
270
+ const res = await request(app).post('/get-request-id/1').send({});
271
+ expect(res.body).toEqual({ id: 'req-123' });
272
+ });
273
+ test('factoryContext can be async function', async () => {
274
+ const factoryContext = vi.fn(async () => {
275
+ return { requestId: 'req-456' };
276
+ });
277
+ const builder = new ExpressRPCAppBuilder();
278
+ const RPC = Procedures();
279
+ RPC.Create('GetRequestId', { name: 'get-request-id', version: 1 }, async (ctx) => ({
280
+ id: ctx.requestId,
281
+ }));
282
+ builder.register(RPC, factoryContext);
283
+ const app = builder.build();
284
+ await request(app).post('/get-request-id/1').send({});
285
+ expect(factoryContext).toHaveBeenCalledTimes(1);
286
+ });
287
+ test('factoryContext function receives Express request object', async () => {
288
+ const factoryContext = vi.fn((req) => ({
212
289
  authHeader: req.headers.authorization,
213
290
  }));
214
291
  const builder = new ExpressRPCAppBuilder();
@@ -216,12 +293,12 @@ describe('ExpressRPCAppBuilder', () => {
216
293
  RPC.Create('GetAuth', { name: 'get-auth', version: 1 }, async (ctx) => ({
217
294
  auth: ctx.authHeader,
218
295
  }));
219
- builder.register(RPC, contextResolver);
296
+ builder.register(RPC, factoryContext);
220
297
  const app = builder.build();
221
- await request(app).post('/rpc/get-auth/1').set('Authorization', 'Bearer token123').send({});
222
- expect(contextResolver).toHaveBeenCalledTimes(1);
223
- expect(contextResolver.mock.calls[0][0]).toHaveProperty('headers');
224
- expect(contextResolver.mock.calls[0][0].headers).toHaveProperty('authorization', 'Bearer token123');
298
+ await request(app).post('/get-auth/1').set('Authorization', 'Bearer token123').send({});
299
+ expect(factoryContext).toHaveBeenCalledTimes(1);
300
+ expect(factoryContext.mock.calls[0][0]).toHaveProperty('headers');
301
+ expect(factoryContext.mock.calls[0][0].headers).toHaveProperty('authorization', 'Bearer token123');
225
302
  });
226
303
  });
227
304
  // --------------------------------------------------------------------------
@@ -235,8 +312,8 @@ describe('ExpressRPCAppBuilder', () => {
235
312
  RPC.Create('MethodTwo', { name: 'method-two', version: 2 }, async () => ({ m: 2 }));
236
313
  builder.register(RPC, () => ({}));
237
314
  const app = builder.build();
238
- const res1 = await request(app).post('/rpc/method-one/1').send({});
239
- 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({});
240
317
  expect(res1.status).toBe(200);
241
318
  expect(res2.status).toBe(200);
242
319
  expect(res1.body).toEqual({ m: 1 });
@@ -257,8 +334,8 @@ describe('ExpressRPCAppBuilder', () => {
257
334
  builder.register(RPC, () => ({}));
258
335
  builder.build();
259
336
  expect(builder.docs).toHaveLength(2);
260
- expect(builder.docs[0].path).toBe('/rpc/method-one/1');
261
- 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');
262
339
  });
263
340
  test('passes request body to handler as params', async () => {
264
341
  const builder = new ExpressRPCAppBuilder();
@@ -266,7 +343,7 @@ describe('ExpressRPCAppBuilder', () => {
266
343
  RPC.Create('Echo', { name: 'echo', version: 1, schema: { params: v.object({ data: v.string() }) } }, async (ctx, params) => ({ received: params.data }));
267
344
  builder.register(RPC, () => ({}));
268
345
  const app = builder.build();
269
- 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' });
270
347
  expect(res.body).toEqual({ received: 'test-data' });
271
348
  });
272
349
  test('GET requests return 404 (RPC uses POST only)', async () => {
@@ -275,7 +352,7 @@ describe('ExpressRPCAppBuilder', () => {
275
352
  RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
276
353
  builder.register(RPC, () => ({}));
277
354
  const app = builder.build();
278
- const res = await request(app).get('/rpc/test/1');
355
+ const res = await request(app).get('/test/1');
279
356
  expect(res.status).toBe(404);
280
357
  });
281
358
  });
@@ -287,36 +364,36 @@ describe('ExpressRPCAppBuilder', () => {
287
364
  beforeEach(() => {
288
365
  builder = new ExpressRPCAppBuilder();
289
366
  });
290
- test("simple string: 'users' → /rpc/users/1", () => {
367
+ test("simple string: 'users' → /users/1", () => {
291
368
  const path = builder.makeRPCHttpRoutePath({ name: 'users', version: 1 });
292
- expect(path).toBe('/rpc/users/1');
369
+ expect(path).toBe('/users/1');
293
370
  });
294
- 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", () => {
295
372
  const path = builder.makeRPCHttpRoutePath({ name: ['users', 'get-by-id'], version: 1 });
296
- expect(path).toBe('/rpc/users/get-by-id/1');
373
+ expect(path).toBe('/users/get-by-id/1');
297
374
  });
298
- test("camelCase: 'getUserById' → /rpc/get-user-by-id/1", () => {
375
+ test("camelCase: 'getUserById' → /get-user-by-id/1", () => {
299
376
  const path = builder.makeRPCHttpRoutePath({ name: 'getUserById', version: 1 });
300
- expect(path).toBe('/rpc/get-user-by-id/1');
377
+ expect(path).toBe('/get-user-by-id/1');
301
378
  });
302
- test("PascalCase: 'GetUserById' → /rpc/get-user-by-id/1", () => {
379
+ test("PascalCase: 'GetUserById' → /get-user-by-id/1", () => {
303
380
  const path = builder.makeRPCHttpRoutePath({ name: 'GetUserById', version: 1 });
304
- expect(path).toBe('/rpc/get-user-by-id/1');
381
+ expect(path).toBe('/get-user-by-id/1');
305
382
  });
306
383
  test('version number included in path', () => {
307
384
  const pathV1 = builder.makeRPCHttpRoutePath({ name: 'test', version: 1 });
308
385
  const pathV2 = builder.makeRPCHttpRoutePath({ name: 'test', version: 2 });
309
386
  const pathV99 = builder.makeRPCHttpRoutePath({ name: 'test', version: 99 });
310
- expect(pathV1).toBe('/rpc/test/1');
311
- expect(pathV2).toBe('/rpc/test/2');
312
- expect(pathV99).toBe('/rpc/test/99');
387
+ expect(pathV1).toBe('/test/1');
388
+ expect(pathV2).toBe('/test/2');
389
+ expect(pathV99).toBe('/test/99');
313
390
  });
314
391
  test('handles mixed case in array segments', () => {
315
392
  const path = builder.makeRPCHttpRoutePath({
316
393
  name: ['UserModule', 'getActiveUsers'],
317
394
  version: 1,
318
395
  });
319
- expect(path).toBe('/rpc/user-module/get-active-users/1');
396
+ expect(path).toBe('/user-module/get-active-users/1');
320
397
  });
321
398
  });
322
399
  // --------------------------------------------------------------------------
@@ -331,10 +408,11 @@ describe('ExpressRPCAppBuilder', () => {
331
408
  const paramsSchema = v.object({ id: v.string() });
332
409
  const returnSchema = v.object({ name: v.string() });
333
410
  const RPC = Procedures();
334
- const { info } = RPC.Create('GetUser', { name: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } }, async () => ({ name: 'test' }));
335
- const procedure = RPC.getProcedures()[0];
336
- const doc = builder.buildRpcHttpRouteDoc(procedure);
337
- 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');
338
416
  expect(doc.method).toBe('post');
339
417
  expect(doc.jsonSchema.body).toBeDefined();
340
418
  expect(doc.jsonSchema.response).toBeDefined();
@@ -342,15 +420,17 @@ describe('ExpressRPCAppBuilder', () => {
342
420
  test('omits body schema when no params defined', () => {
343
421
  const RPC = Procedures();
344
422
  RPC.Create('NoParams', { name: 'no-params', version: 1 }, async () => ({ ok: true }));
345
- const procedure = RPC.getProcedures()[0];
346
- const doc = builder.buildRpcHttpRouteDoc(procedure);
423
+ builder.register(RPC, () => ({}));
424
+ builder.build();
425
+ const doc = builder.docs[0];
347
426
  expect(doc.jsonSchema.body).toBeUndefined();
348
427
  });
349
428
  test('omits response schema when no returnType defined', () => {
350
429
  const RPC = Procedures();
351
430
  RPC.Create('NoReturn', { name: 'no-return', version: 1, schema: { params: v.object({ x: v.number() }) } }, async () => ({}));
352
- const procedure = RPC.getProcedures()[0];
353
- const doc = builder.buildRpcHttpRouteDoc(procedure);
431
+ builder.register(RPC, () => ({}));
432
+ builder.build();
433
+ const doc = builder.docs[0];
354
434
  expect(doc.jsonSchema.body).toBeDefined();
355
435
  expect(doc.jsonSchema.response).toBeUndefined();
356
436
  });
@@ -358,9 +438,9 @@ describe('ExpressRPCAppBuilder', () => {
358
438
  const RPC = Procedures();
359
439
  RPC.Create('Test1', { name: 't1', version: 1 }, async () => ({}));
360
440
  RPC.Create('Test2', { name: 't2', version: 2 }, async () => ({}));
361
- const procedures = RPC.getProcedures();
362
- procedures.forEach((proc) => {
363
- const doc = builder.buildRpcHttpRouteDoc(proc);
441
+ builder.register(RPC, () => ({}));
442
+ builder.build();
443
+ builder.docs.forEach((doc) => {
364
444
  expect(doc.method).toBe('post');
365
445
  });
366
446
  });
@@ -406,21 +486,21 @@ describe('ExpressRPCAppBuilder', () => {
406
486
  }));
407
487
  const app = builder.build();
408
488
  // Test public endpoints
409
- const versionRes = await request(app).post('/rpc/system/version/1').send({});
489
+ const versionRes = await request(app).post('/system/version/1').send({});
410
490
  expect(versionRes.status).toBe(200);
411
491
  expect(versionRes.body).toEqual({ version: '1.0.0' });
412
- const healthRes = await request(app).post('/rpc/health/1').send({});
492
+ const healthRes = await request(app).post('/health/1').send({});
413
493
  expect(healthRes.status).toBe(200);
414
494
  expect(healthRes.body).toEqual({ status: 'ok' });
415
495
  // Test authenticated endpoints
416
496
  const profileRes = await request(app)
417
- .post('/rpc/users/profile/1')
497
+ .post('/users/profile/1')
418
498
  .set('X-User-Id', 'user-123')
419
499
  .send({});
420
500
  expect(profileRes.status).toBe(200);
421
501
  expect(profileRes.body).toEqual({ userId: 'user-123', source: 'auth' });
422
502
  const updateRes = await request(app)
423
- .post('/rpc/users/profile/2')
503
+ .post('/users/profile/2')
424
504
  .set('X-User-Id', 'user-456')
425
505
  .send({ name: 'John Doe' });
426
506
  expect(updateRes.status).toBe(200);
@@ -428,10 +508,10 @@ describe('ExpressRPCAppBuilder', () => {
428
508
  // Verify documentation
429
509
  expect(builder.docs).toHaveLength(4);
430
510
  const paths = builder.docs.map((d) => d.path);
431
- expect(paths).toContain('/rpc/system/version/1');
432
- expect(paths).toContain('/rpc/health/1');
433
- expect(paths).toContain('/rpc/users/profile/1');
434
- 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');
435
515
  // Verify hooks were called
436
516
  expect(events).toContain('request-start');
437
517
  expect(events).toContain('success:GetVersion');