ts-procedures 2.1.0 → 3.0.0
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.
- package/build/errors.d.ts +2 -1
- package/build/errors.js +3 -2
- package/build/errors.js.map +1 -1
- package/build/errors.test.js +40 -0
- package/build/errors.test.js.map +1 -0
- package/build/implementations/http/express-rpc/index.d.ts +36 -35
- package/build/implementations/http/express-rpc/index.js +29 -13
- package/build/implementations/http/express-rpc/index.js.map +1 -1
- package/build/implementations/http/express-rpc/index.test.js +146 -92
- package/build/implementations/http/express-rpc/index.test.js.map +1 -1
- package/build/implementations/http/hono-rpc/index.d.ts +83 -0
- package/build/implementations/http/hono-rpc/index.js +148 -0
- package/build/implementations/http/hono-rpc/index.js.map +1 -0
- package/build/implementations/http/hono-rpc/index.test.js +647 -0
- package/build/implementations/http/hono-rpc/index.test.js.map +1 -0
- package/build/implementations/http/hono-rpc/types.d.ts +28 -0
- package/build/implementations/http/hono-rpc/types.js.map +1 -0
- package/build/implementations/types.d.ts +1 -1
- package/build/index.d.ts +12 -0
- package/build/index.js +29 -7
- package/build/index.js.map +1 -1
- package/build/index.test.js +65 -0
- package/build/index.test.js.map +1 -1
- package/build/schema/parser.js +3 -0
- package/build/schema/parser.js.map +1 -1
- package/build/schema/parser.test.js +18 -0
- package/build/schema/parser.test.js.map +1 -1
- package/package.json +8 -2
- package/src/errors.test.ts +53 -0
- package/src/errors.ts +4 -2
- package/src/implementations/http/README.md +172 -0
- package/src/implementations/http/express-rpc/README.md +151 -228
- package/src/implementations/http/express-rpc/index.test.ts +167 -93
- package/src/implementations/http/express-rpc/index.ts +67 -38
- package/src/implementations/http/hono-rpc/README.md +293 -0
- package/src/implementations/http/hono-rpc/index.test.ts +847 -0
- package/src/implementations/http/hono-rpc/index.ts +202 -0
- package/src/implementations/http/hono-rpc/types.ts +33 -0
- package/src/implementations/types.ts +2 -1
- package/src/index.test.ts +83 -0
- package/src/index.ts +34 -8
- package/src/schema/parser.test.ts +26 -0
- package/src/schema/parser.ts +5 -1
- package/build/implementations/http/client/index.js +0 -2
- package/build/implementations/http/client/index.js.map +0 -1
- package/build/implementations/http/express/example/factories.d.ts +0 -97
- package/build/implementations/http/express/example/factories.js +0 -4
- package/build/implementations/http/express/example/factories.js.map +0 -1
- package/build/implementations/http/express/example/procedures/auth.d.ts +0 -1
- package/build/implementations/http/express/example/procedures/auth.js +0 -22
- package/build/implementations/http/express/example/procedures/auth.js.map +0 -1
- package/build/implementations/http/express/example/procedures/users.d.ts +0 -1
- package/build/implementations/http/express/example/procedures/users.js +0 -30
- package/build/implementations/http/express/example/procedures/users.js.map +0 -1
- package/build/implementations/http/express/example/server.d.ts +0 -3
- package/build/implementations/http/express/example/server.js +0 -49
- package/build/implementations/http/express/example/server.js.map +0 -1
- package/build/implementations/http/express/example/server.test.d.ts +0 -1
- package/build/implementations/http/express/example/server.test.js +0 -110
- package/build/implementations/http/express/example/server.test.js.map +0 -1
- package/build/implementations/http/express/index.d.ts +0 -35
- package/build/implementations/http/express/index.js +0 -75
- package/build/implementations/http/express/index.js.map +0 -1
- package/build/implementations/http/express/index.test.js +0 -329
- package/build/implementations/http/express/index.test.js.map +0 -1
- package/build/implementations/http/express/types.d.ts +0 -17
- package/build/implementations/http/express/types.js.map +0 -1
- /package/build/{implementations/http/client/index.d.ts → errors.test.d.ts} +0 -0
- /package/build/implementations/http/{express → hono-rpc}/index.test.d.ts +0 -0
- /package/build/implementations/http/{express → hono-rpc}/types.js +0 -0
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { PublicFactory, ProtectedFactory } from './factories.js';
|
|
3
|
-
import { registerExpressRoutes } from '../index.js';
|
|
4
|
-
// Import procedures for their side effects (they register with the factories)
|
|
5
|
-
import './procedures/auth.js';
|
|
6
|
-
import './procedures/users.js';
|
|
7
|
-
const authTokenService = (authToken) => {
|
|
8
|
-
// Dummy implementation - replace with real token validation logic
|
|
9
|
-
return {
|
|
10
|
-
isValid: authToken === 'valid-token',
|
|
11
|
-
userId: authToken === 'valid-token' ? 'user-123' : null,
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
export const app = express();
|
|
15
|
-
app.use(express.json());
|
|
16
|
-
registerExpressRoutes(app, {
|
|
17
|
-
getContext: async (req, res) => {
|
|
18
|
-
return {
|
|
19
|
-
ipAddress: '',
|
|
20
|
-
requestId: '',
|
|
21
|
-
headers: {},
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
}, PublicFactory.getProcedures());
|
|
25
|
-
registerExpressRoutes(app, {
|
|
26
|
-
getContext: async (req, res) => {
|
|
27
|
-
const authServiceResult = authTokenService(req.headers['authorization']?.toString() || '');
|
|
28
|
-
if (authServiceResult.isValid) {
|
|
29
|
-
return {
|
|
30
|
-
userId: authServiceResult.userId,
|
|
31
|
-
ipAddress: '',
|
|
32
|
-
requestId: '',
|
|
33
|
-
headers: {}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
res.status(401).json({
|
|
37
|
-
error: 'Unauthorized'
|
|
38
|
-
});
|
|
39
|
-
throw new Error('Unauthorized');
|
|
40
|
-
}
|
|
41
|
-
}, ProtectedFactory.getProcedures());
|
|
42
|
-
// Only start server when run directly (not when imported for testing)
|
|
43
|
-
const isMainModule = import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, '/') || '');
|
|
44
|
-
if (isMainModule) {
|
|
45
|
-
app.listen(4841, () => {
|
|
46
|
-
console.log('Server is running on http://localhost:4841');
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=server.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../../src/implementations/http/express/example/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EACL,aAAa,EACb,gBAAgB,EACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,8EAA8E;AAC9E,OAAO,sBAAsB,CAAA;AAC7B,OAAO,uBAAuB,CAAA;AAE9B,MAAM,gBAAgB,GAAG,CAAC,SAAiB,EAAE,EAAE;IAC7C,kEAAkE;IAClE,OAAO;QACL,OAAO,EAAE,SAAS,KAAK,aAAa;QACpC,MAAM,EAAE,SAAS,KAAK,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;KACxD,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAA;AAC5B,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;AAEvB,qBAAqB,CACnB,GAAG,EACH;IACE,UAAU,EAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5B,OAAO;YACL,SAAS,EAAC,EAAE;YACZ,SAAS,EAAC,EAAE;YACZ,OAAO,EAAC,EAAE;SACX,CAAA;IACH,CAAC;CACF,EACD,aAAa,CAAC,aAAa,EAAE,CAC9B,CAAA;AAED,qBAAqB,CACnB,GAAG,EACH;IACE,UAAU,EAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5B,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QAE1F,IAAI,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO;gBACL,MAAM,EAAC,iBAAiB,CAAC,MAAO;gBAChC,SAAS,EAAC,EAAE;gBACZ,SAAS,EAAC,EAAE;gBACZ,OAAO,EAAC,EAAE;aACX,CAAA;QACH,CAAC;QAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE,cAAc;SACtB,CAAC,CAAA;QACF,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;IACjC,CAAC;CACF,EACD,gBAAgB,CAAC,aAAa,EAAE,CACjC,CAAA;AAED,sEAAsE;AACtE,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;AACzF,IAAI,YAAY,EAAE,CAAC;IACjB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import supertest from 'supertest';
|
|
3
|
-
import { app } from './server.js';
|
|
4
|
-
describe('Express Server Tests', () => {
|
|
5
|
-
describe('Public Endpoints', () => {
|
|
6
|
-
describe('POST /authenticate', () => {
|
|
7
|
-
it('should return token with valid credentials', async () => {
|
|
8
|
-
const response = await supertest(app)
|
|
9
|
-
.post('/authenticate')
|
|
10
|
-
.send({ username: 'john', password: 'secret123' });
|
|
11
|
-
expect(response.status).toBe(200);
|
|
12
|
-
expect(response.body).toEqual({ token: 'fake-jwt-token' });
|
|
13
|
-
});
|
|
14
|
-
it('should return 422 when username is missing', async () => {
|
|
15
|
-
const response = await supertest(app).post('/authenticate').send({ password: 'secret123' });
|
|
16
|
-
expect(response.status).toBe(422);
|
|
17
|
-
expect(response.body).toHaveProperty('error');
|
|
18
|
-
});
|
|
19
|
-
it('should return 422 when username is too short (minLength: 3)', async () => {
|
|
20
|
-
const response = await supertest(app)
|
|
21
|
-
.post('/authenticate')
|
|
22
|
-
.send({ username: 'ab', password: 'secret123' });
|
|
23
|
-
expect(response.status).toBe(422);
|
|
24
|
-
expect(response.body).toHaveProperty('error');
|
|
25
|
-
});
|
|
26
|
-
it('should return 422 when password is missing', async () => {
|
|
27
|
-
const response = await supertest(app).post('/authenticate').send({ username: 'john' });
|
|
28
|
-
expect(response.status).toBe(422);
|
|
29
|
-
expect(response.body).toHaveProperty('error');
|
|
30
|
-
});
|
|
31
|
-
it('should return 422 when password is too short (minLength: 6)', async () => {
|
|
32
|
-
const response = await supertest(app)
|
|
33
|
-
.post('/authenticate')
|
|
34
|
-
.send({ username: 'john', password: '12345' });
|
|
35
|
-
expect(response.status).toBe(422);
|
|
36
|
-
expect(response.body).toHaveProperty('error');
|
|
37
|
-
});
|
|
38
|
-
it('should return 422 when body is empty', async () => {
|
|
39
|
-
const response = await supertest(app).post('/authenticate').send({});
|
|
40
|
-
expect(response.status).toBe(422);
|
|
41
|
-
expect(response.body).toHaveProperty('error');
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
describe('Protected Endpoints', () => {
|
|
46
|
-
describe('GET /users/user-profile/:id', () => {
|
|
47
|
-
it('should return 401 when no authorization header is provided', async () => {
|
|
48
|
-
const response = await supertest(app).get('/users/user-profile/123');
|
|
49
|
-
expect(response.status).toBe(401);
|
|
50
|
-
expect(response.body).toEqual({ error: 'Unauthorized' });
|
|
51
|
-
});
|
|
52
|
-
it('should return 401 when authorization token is invalid', async () => {
|
|
53
|
-
const response = await supertest(app)
|
|
54
|
-
.get('/users/user-profile/123')
|
|
55
|
-
.set('Authorization', 'invalid-token');
|
|
56
|
-
expect(response.status).toBe(401);
|
|
57
|
-
expect(response.body).toEqual({ error: 'Unauthorized' });
|
|
58
|
-
});
|
|
59
|
-
it('should return user profile with valid token', async () => {
|
|
60
|
-
const response = await supertest(app)
|
|
61
|
-
.get('/users/user-profile/123')
|
|
62
|
-
.set('Authorization', 'valid-token');
|
|
63
|
-
expect(response.status).toBe(200);
|
|
64
|
-
expect(response.body).toEqual({
|
|
65
|
-
user: {
|
|
66
|
-
id: '123',
|
|
67
|
-
name: 'Jane Doe',
|
|
68
|
-
email: 'test@gmail.com',
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
it('should return user profile with different id from path', async () => {
|
|
73
|
-
const response = await supertest(app)
|
|
74
|
-
.get('/users/user-profile/abc')
|
|
75
|
-
.set('Authorization', 'valid-token');
|
|
76
|
-
expect(response.status).toBe(200);
|
|
77
|
-
expect(response.body).toEqual({
|
|
78
|
-
user: {
|
|
79
|
-
id: 'abc',
|
|
80
|
-
name: 'Jane Doe',
|
|
81
|
-
email: 'test@gmail.com',
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe('Response Structure', () => {
|
|
88
|
-
it('authenticate returns correct token structure', async () => {
|
|
89
|
-
const response = await supertest(app)
|
|
90
|
-
.post('/authenticate')
|
|
91
|
-
.send({ username: 'testuser', password: 'password123' });
|
|
92
|
-
expect(response.status).toBe(200);
|
|
93
|
-
expect(response.body).toHaveProperty('token');
|
|
94
|
-
expect(typeof response.body.token).toBe('string');
|
|
95
|
-
});
|
|
96
|
-
it('user profile returns correct nested user object with id, name, email', async () => {
|
|
97
|
-
const response = await supertest(app)
|
|
98
|
-
.get('/users/user-profile/test-id')
|
|
99
|
-
.set('Authorization', 'valid-token');
|
|
100
|
-
expect(response.status).toBe(200);
|
|
101
|
-
expect(response.body).toHaveProperty('user');
|
|
102
|
-
expect(response.body.user).toHaveProperty('id', 'test-id');
|
|
103
|
-
expect(response.body.user).toHaveProperty('name');
|
|
104
|
-
expect(response.body.user).toHaveProperty('email');
|
|
105
|
-
expect(typeof response.body.user.name).toBe('string');
|
|
106
|
-
expect(typeof response.body.user.email).toBe('string');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
//# sourceMappingURL=server.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../../../../src/implementations/http/express/example/server.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { TProcedureRegistration } from '../../../index.js';
|
|
3
|
-
import { ExpressHttpRouteHelpersContext } from './types.js';
|
|
4
|
-
/**
|
|
5
|
-
* Maps path parameters from Express route to an object
|
|
6
|
-
*
|
|
7
|
-
* Path /users/:id with /users/123 => { id: '123' }
|
|
8
|
-
*
|
|
9
|
-
* @param path
|
|
10
|
-
* @param url
|
|
11
|
-
*/
|
|
12
|
-
export declare function mapPathParamsToObject(path: string, url: string): Record<string, string>;
|
|
13
|
-
/**
|
|
14
|
-
* A convenience function to register multiple procedures as Express routes.
|
|
15
|
-
*
|
|
16
|
-
* Provide the Express app, a context generator function, and an array of procedure registrations.
|
|
17
|
-
*
|
|
18
|
-
* @param app
|
|
19
|
-
* @param callbacks
|
|
20
|
-
* @param procedures
|
|
21
|
-
*/
|
|
22
|
-
export declare function registerExpressRoutes<ProceduresContext>(app: express.Application, callbacks: {
|
|
23
|
-
/** Get procedure factory context for handler */
|
|
24
|
-
getContext: (req: express.Request, res: express.Response) => Promise<ProceduresContext & ExpressHttpRouteHelpersContext>;
|
|
25
|
-
/** Optional error handler for procedure handler errors */
|
|
26
|
-
onHandlerError?: (error: Error, req: express.Request, res: express.Response) => void;
|
|
27
|
-
/** Optional validation error handler */
|
|
28
|
-
onValidationError?: (errors: Array<{
|
|
29
|
-
message: string;
|
|
30
|
-
path: string[];
|
|
31
|
-
}>, req: express.Request, res: express.Response) => void;
|
|
32
|
-
}, procedures: Array<TProcedureRegistration<ProceduresContext, {
|
|
33
|
-
method: 'get' | 'post' | 'patch' | 'delete' | 'put';
|
|
34
|
-
path: string;
|
|
35
|
-
}>>): void;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maps path parameters from Express route to an object
|
|
3
|
-
*
|
|
4
|
-
* Path /users/:id with /users/123 => { id: '123' }
|
|
5
|
-
*
|
|
6
|
-
* @param path
|
|
7
|
-
* @param url
|
|
8
|
-
*/
|
|
9
|
-
export function mapPathParamsToObject(path, url) {
|
|
10
|
-
const urlObj = new URL(url, 'http://localhost'); // Base URL is required but irrelevant here
|
|
11
|
-
const pathSegments = path.split('/').filter(Boolean);
|
|
12
|
-
const urlSegments = urlObj.pathname.split('/').filter(Boolean);
|
|
13
|
-
const params = {};
|
|
14
|
-
pathSegments.forEach((segment, index) => {
|
|
15
|
-
if (segment.startsWith(':')) {
|
|
16
|
-
const paramName = segment.slice(1);
|
|
17
|
-
params[paramName] = urlSegments[index] || '';
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
return params;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Combines path, query, and body parameters from an Express request into a single object
|
|
24
|
-
* @param req
|
|
25
|
-
*/
|
|
26
|
-
function getAllParamsFromExpressRequest(req) {
|
|
27
|
-
const pathParams = mapPathParamsToObject(req.route.path, req.url);
|
|
28
|
-
const queryParams = req.query || {};
|
|
29
|
-
const bodyParams = req.body || {};
|
|
30
|
-
return { ...pathParams, ...queryParams, ...bodyParams };
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* A convenience function to register multiple procedures as Express routes.
|
|
34
|
-
*
|
|
35
|
-
* Provide the Express app, a context generator function, and an array of procedure registrations.
|
|
36
|
-
*
|
|
37
|
-
* @param app
|
|
38
|
-
* @param callbacks
|
|
39
|
-
* @param procedures
|
|
40
|
-
*/
|
|
41
|
-
export function registerExpressRoutes(app, callbacks,
|
|
42
|
-
// The procedure extended config must include method and path
|
|
43
|
-
procedures) {
|
|
44
|
-
procedures.forEach(({ handler, config }) => {
|
|
45
|
-
const { method, path } = config;
|
|
46
|
-
app[method](path, async (req, res) => {
|
|
47
|
-
const context = await callbacks.getContext(req, res);
|
|
48
|
-
const allParams = getAllParamsFromExpressRequest(req);
|
|
49
|
-
if (config.validation?.params) {
|
|
50
|
-
const { errors } = config.validation.params(allParams);
|
|
51
|
-
if (errors && errors.length > 0) {
|
|
52
|
-
if (callbacks.onValidationError) {
|
|
53
|
-
callbacks.onValidationError(errors, req, res);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
res.status(422).json({ error: 'Validation Error', details: errors });
|
|
57
|
-
}
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
const result = await handler(context, allParams);
|
|
63
|
-
res.json(result);
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (callbacks.onHandlerError) {
|
|
67
|
-
callbacks.onHandlerError(error, req, res);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
res.status(500).json({ error: error.message });
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/express/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import supertest from 'supertest';
|
|
3
|
-
import express from 'express';
|
|
4
|
-
import { registerExpressRoutes, mapPathParamsToObject } from './index.js';
|
|
5
|
-
import { Procedures } from '../../../index.js';
|
|
6
|
-
import { Type } from 'typebox';
|
|
7
|
-
describe('mapPathParamsToObject', () => {
|
|
8
|
-
it('extracts single path param', () => {
|
|
9
|
-
const result = mapPathParamsToObject('/users/:id', '/users/123');
|
|
10
|
-
expect(result).toEqual({ id: '123' });
|
|
11
|
-
});
|
|
12
|
-
it('extracts multiple path params', () => {
|
|
13
|
-
const result = mapPathParamsToObject('/users/:userId/posts/:postId', '/users/42/posts/99');
|
|
14
|
-
expect(result).toEqual({ userId: '42', postId: '99' });
|
|
15
|
-
});
|
|
16
|
-
it('returns empty object when no params', () => {
|
|
17
|
-
const result = mapPathParamsToObject('/users', '/users');
|
|
18
|
-
expect(result).toEqual({});
|
|
19
|
-
});
|
|
20
|
-
it('handles trailing slashes', () => {
|
|
21
|
-
const result = mapPathParamsToObject('/users/:id/', '/users/123/');
|
|
22
|
-
expect(result).toEqual({ id: '123' });
|
|
23
|
-
});
|
|
24
|
-
it('handles missing segment with empty string', () => {
|
|
25
|
-
const result = mapPathParamsToObject('/users/:id/:name', '/users/123');
|
|
26
|
-
expect(result).toEqual({ id: '123', name: '' });
|
|
27
|
-
});
|
|
28
|
-
it('ignores query string in URL', () => {
|
|
29
|
-
const result = mapPathParamsToObject('/users/:id', '/users/123?foo=bar');
|
|
30
|
-
expect(result).toEqual({ id: '123' });
|
|
31
|
-
});
|
|
32
|
-
it('handles nested paths with params', () => {
|
|
33
|
-
const result = mapPathParamsToObject('/api/v1/users/:id/profile', '/api/v1/users/abc/profile');
|
|
34
|
-
expect(result).toEqual({ id: 'abc' });
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
describe('registerExpressRoutes', () => {
|
|
38
|
-
let app;
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
app = express();
|
|
41
|
-
app.use(express.json());
|
|
42
|
-
});
|
|
43
|
-
describe('Route Registration', () => {
|
|
44
|
-
it('registers GET route and returns JSON response', async () => {
|
|
45
|
-
const { Create, getProcedures } = Procedures();
|
|
46
|
-
Create('GetUsers', { method: 'get', path: '/users' }, async () => ({
|
|
47
|
-
users: ['alice', 'bob'],
|
|
48
|
-
}));
|
|
49
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
50
|
-
const response = await supertest(app).get('/users');
|
|
51
|
-
expect(response.status).toBe(200);
|
|
52
|
-
expect(response.body).toEqual({ users: ['alice', 'bob'] });
|
|
53
|
-
});
|
|
54
|
-
it('registers POST route and returns JSON response', async () => {
|
|
55
|
-
const { Create, getProcedures } = Procedures();
|
|
56
|
-
Create('CreateUser', {
|
|
57
|
-
method: 'post',
|
|
58
|
-
path: '/users',
|
|
59
|
-
schema: {
|
|
60
|
-
params: Type.Object({ name: Type.Optional(Type.String()) }),
|
|
61
|
-
},
|
|
62
|
-
}, async (_ctx, params) => ({ created: true, name: params?.name }));
|
|
63
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
64
|
-
const response = await supertest(app).post('/users').send({ name: 'charlie' });
|
|
65
|
-
expect(response.status).toBe(200);
|
|
66
|
-
expect(response.body).toEqual({ created: true, name: 'charlie' });
|
|
67
|
-
});
|
|
68
|
-
it('registers PUT route', async () => {
|
|
69
|
-
const { Create, getProcedures } = Procedures();
|
|
70
|
-
Create('UpdateUser', {
|
|
71
|
-
method: 'put',
|
|
72
|
-
path: '/users/:id',
|
|
73
|
-
schema: {
|
|
74
|
-
params: Type.Object({ id: Type.Optional(Type.String()) }),
|
|
75
|
-
},
|
|
76
|
-
}, async (_ctx, params) => ({ updated: true, id: params?.id }));
|
|
77
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
78
|
-
const response = await supertest(app).put('/users/123').send({ name: 'updated' });
|
|
79
|
-
expect(response.status).toBe(200);
|
|
80
|
-
expect(response.body).toEqual({ updated: true, id: '123' });
|
|
81
|
-
});
|
|
82
|
-
it('registers PATCH route', async () => {
|
|
83
|
-
const { Create, getProcedures } = Procedures();
|
|
84
|
-
Create('PatchUser', {
|
|
85
|
-
method: 'patch',
|
|
86
|
-
path: '/users/:id',
|
|
87
|
-
schema: {
|
|
88
|
-
params: Type.Object({ id: Type.Optional(Type.String()) }),
|
|
89
|
-
},
|
|
90
|
-
}, async (_ctx, params) => ({ patched: true, id: params?.id }));
|
|
91
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
92
|
-
const response = await supertest(app).patch('/users/456').send({ status: 'active' });
|
|
93
|
-
expect(response.status).toBe(200);
|
|
94
|
-
expect(response.body).toEqual({ patched: true, id: '456' });
|
|
95
|
-
});
|
|
96
|
-
it('registers DELETE route', async () => {
|
|
97
|
-
const { Create, getProcedures } = Procedures();
|
|
98
|
-
Create('DeleteUser', {
|
|
99
|
-
method: 'delete',
|
|
100
|
-
path: '/users/:id',
|
|
101
|
-
schema: {
|
|
102
|
-
params: Type.Object({ id: Type.Optional(Type.String()) }),
|
|
103
|
-
},
|
|
104
|
-
}, async (_ctx, params) => ({ deleted: true, id: params?.id }));
|
|
105
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
106
|
-
const response = await supertest(app).delete('/users/789');
|
|
107
|
-
expect(response.status).toBe(200);
|
|
108
|
-
expect(response.body).toEqual({ deleted: true, id: '789' });
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
describe('Parameter Handling', () => {
|
|
112
|
-
it('passes path params to handler', async () => {
|
|
113
|
-
const { Create, getProcedures } = Procedures();
|
|
114
|
-
Create('GetUser', {
|
|
115
|
-
method: 'get',
|
|
116
|
-
path: '/users/:userId',
|
|
117
|
-
schema: {
|
|
118
|
-
params: Type.Object({ userId: Type.Optional(Type.String()) }),
|
|
119
|
-
},
|
|
120
|
-
}, async (_ctx, params) => ({ userId: params?.userId }));
|
|
121
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
122
|
-
const response = await supertest(app).get('/users/abc123');
|
|
123
|
-
expect(response.body).toEqual({ userId: 'abc123' });
|
|
124
|
-
});
|
|
125
|
-
it('passes query params to handler', async () => {
|
|
126
|
-
const { Create, getProcedures } = Procedures();
|
|
127
|
-
Create('SearchUsers', {
|
|
128
|
-
method: 'get',
|
|
129
|
-
path: '/users',
|
|
130
|
-
schema: {
|
|
131
|
-
params: Type.Object({
|
|
132
|
-
q: Type.Optional(Type.String()),
|
|
133
|
-
limit: Type.Optional(Type.String()),
|
|
134
|
-
}),
|
|
135
|
-
},
|
|
136
|
-
}, async (_ctx, params) => ({ query: params?.q, limit: params?.limit }));
|
|
137
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
138
|
-
const response = await supertest(app).get('/users?q=test&limit=10');
|
|
139
|
-
expect(response.body).toEqual({ query: 'test', limit: '10' });
|
|
140
|
-
});
|
|
141
|
-
it('passes body params to handler', async () => {
|
|
142
|
-
const { Create, getProcedures } = Procedures();
|
|
143
|
-
Create('CreatePost', {
|
|
144
|
-
method: 'post',
|
|
145
|
-
path: '/posts',
|
|
146
|
-
schema: {
|
|
147
|
-
params: Type.Object({
|
|
148
|
-
title: Type.Optional(Type.String()),
|
|
149
|
-
content: Type.Optional(Type.String()),
|
|
150
|
-
}),
|
|
151
|
-
},
|
|
152
|
-
}, async (_ctx, params) => ({ title: params?.title, content: params?.content }));
|
|
153
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
154
|
-
const response = await supertest(app)
|
|
155
|
-
.post('/posts')
|
|
156
|
-
.send({ title: 'Hello', content: 'World' });
|
|
157
|
-
expect(response.body).toEqual({ title: 'Hello', content: 'World' });
|
|
158
|
-
});
|
|
159
|
-
it('merges path, query, and body params', async () => {
|
|
160
|
-
const { Create, getProcedures } = Procedures();
|
|
161
|
-
Create('UpdatePost', {
|
|
162
|
-
method: 'put',
|
|
163
|
-
path: '/users/:userId/posts/:postId',
|
|
164
|
-
schema: {
|
|
165
|
-
params: Type.Object({
|
|
166
|
-
userId: Type.Optional(Type.String()),
|
|
167
|
-
postId: Type.Optional(Type.String()),
|
|
168
|
-
filter: Type.Optional(Type.String()),
|
|
169
|
-
title: Type.Optional(Type.String()),
|
|
170
|
-
}),
|
|
171
|
-
},
|
|
172
|
-
}, async (_ctx, params) => ({
|
|
173
|
-
userId: params?.userId,
|
|
174
|
-
postId: params?.postId,
|
|
175
|
-
filter: params?.filter,
|
|
176
|
-
title: params?.title,
|
|
177
|
-
}));
|
|
178
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
179
|
-
const response = await supertest(app)
|
|
180
|
-
.put('/users/u1/posts/p2?filter=active')
|
|
181
|
-
.send({ title: 'Updated Title' });
|
|
182
|
-
expect(response.body).toEqual({
|
|
183
|
-
userId: 'u1',
|
|
184
|
-
postId: 'p2',
|
|
185
|
-
filter: 'active',
|
|
186
|
-
title: 'Updated Title',
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
describe('Context Generation', () => {
|
|
191
|
-
it('calls getContext with req and res', async () => {
|
|
192
|
-
const getContext = vi.fn().mockResolvedValue({ user: 'testuser' });
|
|
193
|
-
const { Create, getProcedures } = Procedures();
|
|
194
|
-
Create('GetData', { method: 'get', path: '/data' }, async () => ({ ok: true }));
|
|
195
|
-
registerExpressRoutes(app, { getContext }, getProcedures());
|
|
196
|
-
await supertest(app).get('/data');
|
|
197
|
-
expect(getContext).toHaveBeenCalledTimes(1);
|
|
198
|
-
expect(getContext).toHaveBeenCalledWith(expect.objectContaining({ method: 'GET' }), expect.objectContaining({ statusCode: 200 }));
|
|
199
|
-
});
|
|
200
|
-
it('passes context to handler', async () => {
|
|
201
|
-
const { Create, getProcedures } = Procedures();
|
|
202
|
-
Create('GetProfile', { method: 'get', path: '/profile' }, async (ctx) => ({
|
|
203
|
-
user: ctx.user,
|
|
204
|
-
permissions: ctx.permissions,
|
|
205
|
-
}));
|
|
206
|
-
registerExpressRoutes(app, {
|
|
207
|
-
getContext: async () => ({
|
|
208
|
-
user: 'alice',
|
|
209
|
-
permissions: ['read', 'write'],
|
|
210
|
-
}),
|
|
211
|
-
}, getProcedures());
|
|
212
|
-
const response = await supertest(app).get('/profile');
|
|
213
|
-
expect(response.body).toEqual({
|
|
214
|
-
user: 'alice',
|
|
215
|
-
permissions: ['read', 'write'],
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
describe('Validation Error Handling', () => {
|
|
220
|
-
it('returns 422 with default handler when validation fails', async () => {
|
|
221
|
-
const { Create, getProcedures } = Procedures();
|
|
222
|
-
Create('CreateItem', {
|
|
223
|
-
method: 'post',
|
|
224
|
-
path: '/items',
|
|
225
|
-
schema: {
|
|
226
|
-
params: Type.Object({
|
|
227
|
-
name: Type.String({ minLength: 1 }),
|
|
228
|
-
quantity: Type.Number({ minimum: 1 }),
|
|
229
|
-
}),
|
|
230
|
-
},
|
|
231
|
-
}, async (_ctx, params) => ({ created: params }));
|
|
232
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
233
|
-
const response = await supertest(app).post('/items').send({ name: '', quantity: 0 });
|
|
234
|
-
expect(response.status).toBe(422);
|
|
235
|
-
expect(response.body.error).toBe('Validation Error');
|
|
236
|
-
expect(response.body.details).toBeDefined();
|
|
237
|
-
expect(Array.isArray(response.body.details)).toBe(true);
|
|
238
|
-
});
|
|
239
|
-
it('calls custom onValidationError callback', async () => {
|
|
240
|
-
const onValidationError = vi.fn((errors, _req, res) => {
|
|
241
|
-
res.status(400).json({ custom: 'validation error', errors });
|
|
242
|
-
});
|
|
243
|
-
const { Create, getProcedures } = Procedures();
|
|
244
|
-
Create('ValidateItem', {
|
|
245
|
-
method: 'post',
|
|
246
|
-
path: '/validate',
|
|
247
|
-
schema: {
|
|
248
|
-
params: Type.Object({
|
|
249
|
-
required: Type.String(),
|
|
250
|
-
}),
|
|
251
|
-
},
|
|
252
|
-
}, async () => ({ ok: true }));
|
|
253
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }), onValidationError }, getProcedures());
|
|
254
|
-
const response = await supertest(app).post('/validate').send({});
|
|
255
|
-
expect(onValidationError).toHaveBeenCalledTimes(1);
|
|
256
|
-
expect(response.status).toBe(400);
|
|
257
|
-
expect(response.body.custom).toBe('validation error');
|
|
258
|
-
});
|
|
259
|
-
it('does not call handler when validation fails', async () => {
|
|
260
|
-
const handlerFn = vi.fn().mockResolvedValue({ ok: true });
|
|
261
|
-
const { Create, getProcedures } = Procedures();
|
|
262
|
-
Create('NoCall', {
|
|
263
|
-
method: 'post',
|
|
264
|
-
path: '/nocall',
|
|
265
|
-
schema: {
|
|
266
|
-
params: Type.Object({
|
|
267
|
-
required: Type.String(),
|
|
268
|
-
}),
|
|
269
|
-
},
|
|
270
|
-
}, handlerFn);
|
|
271
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
272
|
-
await supertest(app).post('/nocall').send({});
|
|
273
|
-
expect(handlerFn).not.toHaveBeenCalled();
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
describe('Handler Error Handling', () => {
|
|
277
|
-
it('returns 500 with default handler when handler throws', async () => {
|
|
278
|
-
const { Create, getProcedures } = Procedures();
|
|
279
|
-
Create('ThrowError', { method: 'get', path: '/error' }, async () => {
|
|
280
|
-
throw new Error('Something went wrong');
|
|
281
|
-
});
|
|
282
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
283
|
-
const response = await supertest(app).get('/error');
|
|
284
|
-
expect(response.status).toBe(500);
|
|
285
|
-
expect(response.body.error).toContain('Something went wrong');
|
|
286
|
-
});
|
|
287
|
-
it('calls custom onHandlerError callback', async () => {
|
|
288
|
-
const onHandlerError = vi.fn((error, _req, res) => {
|
|
289
|
-
res.status(503).json({ custom: 'error handler', message: error.message });
|
|
290
|
-
});
|
|
291
|
-
const { Create, getProcedures } = Procedures();
|
|
292
|
-
Create('CustomError', { method: 'get', path: '/custom-error' }, async () => {
|
|
293
|
-
throw new Error('Custom failure');
|
|
294
|
-
});
|
|
295
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }), onHandlerError }, getProcedures());
|
|
296
|
-
const response = await supertest(app).get('/custom-error');
|
|
297
|
-
expect(onHandlerError).toHaveBeenCalledTimes(1);
|
|
298
|
-
expect(response.status).toBe(503);
|
|
299
|
-
expect(response.body).toEqual({
|
|
300
|
-
custom: 'error handler',
|
|
301
|
-
message: 'Custom failure',
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
describe('Success Response', () => {
|
|
306
|
-
it('returns handler result as JSON', async () => {
|
|
307
|
-
const { Create, getProcedures } = Procedures();
|
|
308
|
-
Create('ComplexResult', { method: 'get', path: '/complex' }, async () => ({
|
|
309
|
-
nested: {
|
|
310
|
-
data: [1, 2, 3],
|
|
311
|
-
metadata: { count: 3 },
|
|
312
|
-
},
|
|
313
|
-
status: 'success',
|
|
314
|
-
}));
|
|
315
|
-
registerExpressRoutes(app, { getContext: async () => ({ user: 'test' }) }, getProcedures());
|
|
316
|
-
const response = await supertest(app).get('/complex');
|
|
317
|
-
expect(response.status).toBe(200);
|
|
318
|
-
expect(response.headers['content-type']).toMatch(/application\/json/);
|
|
319
|
-
expect(response.body).toEqual({
|
|
320
|
-
nested: {
|
|
321
|
-
data: [1, 2, 3],
|
|
322
|
-
metadata: { count: 3 },
|
|
323
|
-
},
|
|
324
|
-
status: 'success',
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
//# sourceMappingURL=index.test.js.map
|