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
|
@@ -10,7 +10,7 @@ import { RPCConfig } from '../../types.js'
|
|
|
10
10
|
* ExpressRPCAppBuilder Test Suite
|
|
11
11
|
*
|
|
12
12
|
* Tests the RPC-style Express integration for ts-procedures.
|
|
13
|
-
* This builder creates POST routes at `/
|
|
13
|
+
* This builder creates POST routes at `/{name}/{version}` paths (with optional pathPrefix).
|
|
14
14
|
*/
|
|
15
15
|
describe('ExpressRPCAppBuilder', () => {
|
|
16
16
|
// --------------------------------------------------------------------------
|
|
@@ -23,7 +23,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
23
23
|
|
|
24
24
|
RPC.Create(
|
|
25
25
|
'Echo',
|
|
26
|
-
{
|
|
26
|
+
{ scope: 'echo', version: 1, schema: { params: v.object({ message: v.string() }) } },
|
|
27
27
|
async (ctx, params) => params
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
31
31
|
const app = builder.build()
|
|
32
32
|
|
|
33
33
|
// JSON body should be parsed automatically
|
|
34
|
-
const res = await request(app).post('/
|
|
34
|
+
const res = await request(app).post('/echo/echo/1').send({ message: 'hello' })
|
|
35
35
|
|
|
36
36
|
expect(res.status).toBe(200)
|
|
37
37
|
expect(res.body).toEqual({ message: 'hello' })
|
|
@@ -45,7 +45,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
45
45
|
|
|
46
46
|
RPC.Create(
|
|
47
47
|
'Echo',
|
|
48
|
-
{
|
|
48
|
+
{ scope: 'echo', version: 1, schema: { params: v.object({ message: v.string() }) } },
|
|
49
49
|
async (ctx, params) => ({ received: params })
|
|
50
50
|
)
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
54
54
|
|
|
55
55
|
// Without json middleware, body won't be parsed (req.body is undefined)
|
|
56
56
|
const res = await request(app)
|
|
57
|
-
.post('/
|
|
57
|
+
.post('/echo/echo/1')
|
|
58
58
|
.set('Content-Type', 'application/json')
|
|
59
59
|
.send(JSON.stringify({ message: 'hello' }))
|
|
60
60
|
|
|
@@ -76,6 +76,77 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
+
// --------------------------------------------------------------------------
|
|
80
|
+
// pathPrefix Option Tests
|
|
81
|
+
// --------------------------------------------------------------------------
|
|
82
|
+
describe('pathPrefix option', () => {
|
|
83
|
+
test('uses custom pathPrefix for all routes', async () => {
|
|
84
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api/v1' })
|
|
85
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
86
|
+
|
|
87
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
88
|
+
|
|
89
|
+
builder.register(RPC, () => ({}))
|
|
90
|
+
const app = builder.build()
|
|
91
|
+
|
|
92
|
+
const res = await request(app).post('/api/v1/test/test/1').send({})
|
|
93
|
+
expect(res.status).toBe(200)
|
|
94
|
+
expect(res.body).toEqual({ ok: true })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('pathPrefix without leading slash gets normalized', async () => {
|
|
98
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: 'custom' })
|
|
99
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
100
|
+
|
|
101
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
102
|
+
|
|
103
|
+
builder.register(RPC, () => ({}))
|
|
104
|
+
const app = builder.build()
|
|
105
|
+
|
|
106
|
+
const res = await request(app).post('/custom/test/test/1').send({})
|
|
107
|
+
expect(res.status).toBe(200)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('no prefix when pathPrefix not specified', async () => {
|
|
111
|
+
const builder = new ExpressRPCAppBuilder()
|
|
112
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
113
|
+
|
|
114
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
115
|
+
|
|
116
|
+
builder.register(RPC, () => ({}))
|
|
117
|
+
const app = builder.build()
|
|
118
|
+
|
|
119
|
+
const res = await request(app).post('/test/test/1').send({})
|
|
120
|
+
expect(res.status).toBe(200)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('pathPrefix appears in generated docs', () => {
|
|
124
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api' })
|
|
125
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
126
|
+
|
|
127
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({}))
|
|
128
|
+
|
|
129
|
+
builder.register(RPC, () => ({}))
|
|
130
|
+
builder.build()
|
|
131
|
+
|
|
132
|
+
expect(builder.docs[0]!.path).toBe('/api/test/test/1')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('pathPrefix /rpc restores original behavior', async () => {
|
|
136
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' })
|
|
137
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
138
|
+
|
|
139
|
+
RPC.Create('Users', { scope: 'users', version: 1 }, async () => ({ users: [] }))
|
|
140
|
+
|
|
141
|
+
builder.register(RPC, () => ({}))
|
|
142
|
+
const app = builder.build()
|
|
143
|
+
|
|
144
|
+
const res = await request(app).post('/rpc/users/users/1').send({})
|
|
145
|
+
expect(res.status).toBe(200)
|
|
146
|
+
expect(builder.docs[0]!.path).toBe('/rpc/users/users/1')
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
79
150
|
// --------------------------------------------------------------------------
|
|
80
151
|
// Lifecycle Hooks Tests
|
|
81
152
|
// --------------------------------------------------------------------------
|
|
@@ -85,16 +156,16 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
85
156
|
const builder = new ExpressRPCAppBuilder({ onRequestStart })
|
|
86
157
|
const RPC = Procedures<{}, RPCConfig>()
|
|
87
158
|
|
|
88
|
-
RPC.Create('Test', {
|
|
159
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
89
160
|
|
|
90
161
|
builder.register(RPC, () => ({}))
|
|
91
162
|
const app = builder.build()
|
|
92
163
|
|
|
93
|
-
await request(app).post('/
|
|
164
|
+
await request(app).post('/test/test/1').send({})
|
|
94
165
|
|
|
95
166
|
expect(onRequestStart).toHaveBeenCalledTimes(1)
|
|
96
167
|
expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('method', 'POST')
|
|
97
|
-
expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('path', '/
|
|
168
|
+
expect(onRequestStart.mock.calls[0]![0]).toHaveProperty('path', '/test/test/1')
|
|
98
169
|
})
|
|
99
170
|
|
|
100
171
|
test('onRequestEnd is called after response finishes', async () => {
|
|
@@ -102,12 +173,12 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
102
173
|
const builder = new ExpressRPCAppBuilder({ onRequestEnd })
|
|
103
174
|
const RPC = Procedures<{}, RPCConfig>()
|
|
104
175
|
|
|
105
|
-
RPC.Create('Test', {
|
|
176
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
106
177
|
|
|
107
178
|
builder.register(RPC, () => ({}))
|
|
108
179
|
const app = builder.build()
|
|
109
180
|
|
|
110
|
-
await request(app).post('/
|
|
181
|
+
await request(app).post('/test/test/1').send({})
|
|
111
182
|
|
|
112
183
|
expect(onRequestEnd).toHaveBeenCalledTimes(1)
|
|
113
184
|
expect(onRequestEnd.mock.calls[0]![0]).toHaveProperty('method', 'POST')
|
|
@@ -119,12 +190,12 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
119
190
|
const builder = new ExpressRPCAppBuilder({ onSuccess })
|
|
120
191
|
const RPC = Procedures<{}, RPCConfig>()
|
|
121
192
|
|
|
122
|
-
RPC.Create('Test', {
|
|
193
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
123
194
|
|
|
124
195
|
builder.register(RPC, () => ({}))
|
|
125
196
|
const app = builder.build()
|
|
126
197
|
|
|
127
|
-
await request(app).post('/
|
|
198
|
+
await request(app).post('/test/test/1').send({})
|
|
128
199
|
|
|
129
200
|
expect(onSuccess).toHaveBeenCalledTimes(1)
|
|
130
201
|
expect(onSuccess.mock.calls[0]![0]).toHaveProperty('name', 'Test')
|
|
@@ -135,14 +206,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
135
206
|
const builder = new ExpressRPCAppBuilder({ onSuccess })
|
|
136
207
|
const RPC = Procedures<{}, RPCConfig>()
|
|
137
208
|
|
|
138
|
-
RPC.Create('Test', {
|
|
209
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
|
|
139
210
|
throw new Error('Handler error')
|
|
140
211
|
})
|
|
141
212
|
|
|
142
213
|
builder.register(RPC, () => ({}))
|
|
143
214
|
const app = builder.build()
|
|
144
215
|
|
|
145
|
-
await request(app).post('/
|
|
216
|
+
await request(app).post('/test/test/1').send({})
|
|
146
217
|
|
|
147
218
|
expect(onSuccess).not.toHaveBeenCalled()
|
|
148
219
|
})
|
|
@@ -157,7 +228,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
157
228
|
})
|
|
158
229
|
const RPC = Procedures<{}, RPCConfig>()
|
|
159
230
|
|
|
160
|
-
RPC.Create('Test', {
|
|
231
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
|
|
161
232
|
order.push('handler')
|
|
162
233
|
return { ok: true }
|
|
163
234
|
})
|
|
@@ -165,7 +236,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
165
236
|
builder.register(RPC, () => ({}))
|
|
166
237
|
const app = builder.build()
|
|
167
238
|
|
|
168
|
-
await request(app).post('/
|
|
239
|
+
await request(app).post('/test/test/1').send({})
|
|
169
240
|
|
|
170
241
|
expect(order).toEqual(['start', 'handler', 'success', 'end'])
|
|
171
242
|
})
|
|
@@ -183,14 +254,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
183
254
|
const builder = new ExpressRPCAppBuilder({ error: errorHandler })
|
|
184
255
|
const RPC = Procedures<{}, RPCConfig>()
|
|
185
256
|
|
|
186
|
-
RPC.Create('Test', {
|
|
257
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
|
|
187
258
|
throw new Error('Test error')
|
|
188
259
|
})
|
|
189
260
|
|
|
190
261
|
builder.register(RPC, () => ({}))
|
|
191
262
|
const app = builder.build()
|
|
192
263
|
|
|
193
|
-
const res = await request(app).post('/
|
|
264
|
+
const res = await request(app).post('/test/test/1').send({})
|
|
194
265
|
|
|
195
266
|
expect(errorHandler).toHaveBeenCalledTimes(1)
|
|
196
267
|
expect(errorHandler.mock.calls[0]![0]).toHaveProperty('name', 'Test')
|
|
@@ -204,14 +275,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
204
275
|
const builder = new ExpressRPCAppBuilder()
|
|
205
276
|
const RPC = Procedures<{}, RPCConfig>()
|
|
206
277
|
|
|
207
|
-
RPC.Create('Test', {
|
|
278
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
|
|
208
279
|
throw new Error('Something went wrong')
|
|
209
280
|
})
|
|
210
281
|
|
|
211
282
|
builder.register(RPC, () => ({}))
|
|
212
283
|
const app = builder.build()
|
|
213
284
|
|
|
214
|
-
const res = await request(app).post('/
|
|
285
|
+
const res = await request(app).post('/test/test/1').send({})
|
|
215
286
|
|
|
216
287
|
// Default error handler returns error message in JSON body
|
|
217
288
|
expect(res.body).toHaveProperty('error')
|
|
@@ -222,7 +293,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
222
293
|
const builder = new ExpressRPCAppBuilder()
|
|
223
294
|
const RPC = Procedures<{}, RPCConfig>()
|
|
224
295
|
|
|
225
|
-
RPC.Create('Test', {
|
|
296
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => {
|
|
226
297
|
// Simulate unhandled exception
|
|
227
298
|
const obj: any = null
|
|
228
299
|
return obj.property // This will throw
|
|
@@ -231,7 +302,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
231
302
|
builder.register(RPC, () => ({}))
|
|
232
303
|
const app = builder.build()
|
|
233
304
|
|
|
234
|
-
const res = await request(app).post('/
|
|
305
|
+
const res = await request(app).post('/test/test/1').send({})
|
|
235
306
|
|
|
236
307
|
// Unhandled exceptions are caught and returned as error response
|
|
237
308
|
expect(res.body).toHaveProperty('error')
|
|
@@ -262,11 +333,11 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
262
333
|
const PublicRPC = Procedures<{ public: true }, RPCConfig>()
|
|
263
334
|
const PrivateRPC = Procedures<{ private: true }, RPCConfig>()
|
|
264
335
|
|
|
265
|
-
PublicRPC.Create('PublicMethod', {
|
|
336
|
+
PublicRPC.Create('PublicMethod', { scope: 'public', version: 1 }, async (ctx) => ({
|
|
266
337
|
isPublic: ctx.public,
|
|
267
338
|
}))
|
|
268
339
|
|
|
269
|
-
PrivateRPC.Create('PrivateMethod', {
|
|
340
|
+
PrivateRPC.Create('PrivateMethod', { scope: 'private', version: 1 }, async (ctx) => ({
|
|
270
341
|
isPrivate: ctx.private,
|
|
271
342
|
}))
|
|
272
343
|
|
|
@@ -276,8 +347,8 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
276
347
|
|
|
277
348
|
const app = builder.build()
|
|
278
349
|
|
|
279
|
-
const publicRes = await request(app).post('/
|
|
280
|
-
const privateRes = await request(app).post('/
|
|
350
|
+
const publicRes = await request(app).post('/public/public-method/1').send({})
|
|
351
|
+
const privateRes = await request(app).post('/private/private-method/1').send({})
|
|
281
352
|
|
|
282
353
|
expect(publicRes.body).toEqual({ isPublic: true })
|
|
283
354
|
expect(privateRes.body).toEqual({ isPrivate: true })
|
|
@@ -289,14 +360,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
289
360
|
const builder = new ExpressRPCAppBuilder()
|
|
290
361
|
const RPC = Procedures<{ requestId: string }, RPCConfig>()
|
|
291
362
|
|
|
292
|
-
RPC.Create('GetRequestId', {
|
|
363
|
+
RPC.Create('GetRequestId', { scope: 'get-request-id', version: 1 }, async (ctx) => ({
|
|
293
364
|
id: ctx.requestId,
|
|
294
365
|
}))
|
|
295
366
|
|
|
296
367
|
builder.register(RPC, factoryContext)
|
|
297
368
|
const app = builder.build()
|
|
298
369
|
|
|
299
|
-
const res = await request(app).post('/
|
|
370
|
+
const res = await request(app).post('/get-request-id/get-request-id/1').send({})
|
|
300
371
|
|
|
301
372
|
expect(res.body).toEqual({ id: 'req-123' })
|
|
302
373
|
})
|
|
@@ -309,14 +380,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
309
380
|
const builder = new ExpressRPCAppBuilder()
|
|
310
381
|
const RPC = Procedures<{ requestId: string }, RPCConfig>()
|
|
311
382
|
|
|
312
|
-
RPC.Create('GetRequestId', {
|
|
383
|
+
RPC.Create('GetRequestId', { scope: 'get-request-id', version: 1 }, async (ctx) => ({
|
|
313
384
|
id: ctx.requestId,
|
|
314
385
|
}))
|
|
315
386
|
|
|
316
387
|
builder.register(RPC, factoryContext)
|
|
317
388
|
const app = builder.build()
|
|
318
389
|
|
|
319
|
-
await request(app).post('/
|
|
390
|
+
await request(app).post('/get-request-id/get-request-id/1').send({})
|
|
320
391
|
|
|
321
392
|
expect(factoryContext).toHaveBeenCalledTimes(1)
|
|
322
393
|
})
|
|
@@ -329,14 +400,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
329
400
|
const builder = new ExpressRPCAppBuilder()
|
|
330
401
|
const RPC = Procedures<{ authHeader?: string }, RPCConfig>()
|
|
331
402
|
|
|
332
|
-
RPC.Create('GetAuth', {
|
|
403
|
+
RPC.Create('GetAuth', { scope: 'get-auth', version: 1 }, async (ctx) => ({
|
|
333
404
|
auth: ctx.authHeader,
|
|
334
405
|
}))
|
|
335
406
|
|
|
336
407
|
builder.register(RPC, factoryContext)
|
|
337
408
|
const app = builder.build()
|
|
338
409
|
|
|
339
|
-
await request(app).post('/
|
|
410
|
+
await request(app).post('/get-auth/get-auth/1').set('Authorization', 'Bearer token123').send({})
|
|
340
411
|
|
|
341
412
|
expect(factoryContext).toHaveBeenCalledTimes(1)
|
|
342
413
|
expect(factoryContext.mock.calls[0]![0]).toHaveProperty('headers')
|
|
@@ -355,14 +426,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
355
426
|
const builder = new ExpressRPCAppBuilder()
|
|
356
427
|
const RPC = Procedures<{}, RPCConfig>()
|
|
357
428
|
|
|
358
|
-
RPC.Create('MethodOne', {
|
|
359
|
-
RPC.Create('MethodTwo', {
|
|
429
|
+
RPC.Create('MethodOne', { scope: 'method-one', version: 1 }, async () => ({ m: 1 }))
|
|
430
|
+
RPC.Create('MethodTwo', { scope: 'method-two', version: 2 }, async () => ({ m: 2 }))
|
|
360
431
|
|
|
361
432
|
builder.register(RPC, () => ({}))
|
|
362
433
|
const app = builder.build()
|
|
363
434
|
|
|
364
|
-
const res1 = await request(app).post('/
|
|
365
|
-
const res2 = await request(app).post('/
|
|
435
|
+
const res1 = await request(app).post('/method-one/method-one/1').send({})
|
|
436
|
+
const res2 = await request(app).post('/method-two/method-two/2').send({})
|
|
366
437
|
|
|
367
438
|
expect(res1.status).toBe(200)
|
|
368
439
|
expect(res2.status).toBe(200)
|
|
@@ -382,8 +453,8 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
382
453
|
const builder = new ExpressRPCAppBuilder()
|
|
383
454
|
const RPC = Procedures<{}, RPCConfig>()
|
|
384
455
|
|
|
385
|
-
RPC.Create('MethodOne', {
|
|
386
|
-
RPC.Create('MethodTwo', {
|
|
456
|
+
RPC.Create('MethodOne', { scope: 'method-one', version: 1 }, async () => ({}))
|
|
457
|
+
RPC.Create('MethodTwo', { scope: ['nested', 'method'], version: 2 }, async () => ({}))
|
|
387
458
|
|
|
388
459
|
expect(builder.docs).toHaveLength(0)
|
|
389
460
|
|
|
@@ -391,8 +462,8 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
391
462
|
builder.build()
|
|
392
463
|
|
|
393
464
|
expect(builder.docs).toHaveLength(2)
|
|
394
|
-
expect(builder.docs[0]!.path).toBe('/
|
|
395
|
-
expect(builder.docs[1]!.path).toBe('/
|
|
465
|
+
expect(builder.docs[0]!.path).toBe('/method-one/method-one/1')
|
|
466
|
+
expect(builder.docs[1]!.path).toBe('/nested/method/method-two/2')
|
|
396
467
|
})
|
|
397
468
|
|
|
398
469
|
test('passes request body to handler as params', async () => {
|
|
@@ -401,14 +472,14 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
401
472
|
|
|
402
473
|
RPC.Create(
|
|
403
474
|
'Echo',
|
|
404
|
-
{
|
|
475
|
+
{ scope: 'echo', version: 1, schema: { params: v.object({ data: v.string() }) } },
|
|
405
476
|
async (ctx, params) => ({ received: params.data })
|
|
406
477
|
)
|
|
407
478
|
|
|
408
479
|
builder.register(RPC, () => ({}))
|
|
409
480
|
const app = builder.build()
|
|
410
481
|
|
|
411
|
-
const res = await request(app).post('/
|
|
482
|
+
const res = await request(app).post('/echo/echo/1').send({ data: 'test-data' })
|
|
412
483
|
|
|
413
484
|
expect(res.body).toEqual({ received: 'test-data' })
|
|
414
485
|
})
|
|
@@ -417,12 +488,12 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
417
488
|
const builder = new ExpressRPCAppBuilder()
|
|
418
489
|
const RPC = Procedures<{}, RPCConfig>()
|
|
419
490
|
|
|
420
|
-
RPC.Create('Test', {
|
|
491
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({ ok: true }))
|
|
421
492
|
|
|
422
493
|
builder.register(RPC, () => ({}))
|
|
423
494
|
const app = builder.build()
|
|
424
495
|
|
|
425
|
-
const res = await request(app).get('/
|
|
496
|
+
const res = await request(app).get('/test/test/1')
|
|
426
497
|
|
|
427
498
|
expect(res.status).toBe(404)
|
|
428
499
|
})
|
|
@@ -438,42 +509,42 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
438
509
|
builder = new ExpressRPCAppBuilder()
|
|
439
510
|
})
|
|
440
511
|
|
|
441
|
-
test("simple
|
|
442
|
-
const path = builder.makeRPCHttpRoutePath({
|
|
443
|
-
expect(path).toBe('/
|
|
512
|
+
test("simple scope with procedure name: 'users' + 'GetUser' → /users/get-user/1", () => {
|
|
513
|
+
const path = builder.makeRPCHttpRoutePath('GetUser', { scope: 'users', version: 1 })
|
|
514
|
+
expect(path).toBe('/users/get-user/1')
|
|
444
515
|
})
|
|
445
516
|
|
|
446
|
-
test("array name: ['users', '
|
|
447
|
-
const path = builder.makeRPCHttpRoutePath({
|
|
448
|
-
expect(path).toBe('/
|
|
517
|
+
test("array scope with procedure name: ['users', 'profile'] + 'GetById' → /users/profile/get-by-id/1", () => {
|
|
518
|
+
const path = builder.makeRPCHttpRoutePath('GetById', { scope: ['users', 'profile'], version: 1 })
|
|
519
|
+
expect(path).toBe('/users/profile/get-by-id/1')
|
|
449
520
|
})
|
|
450
521
|
|
|
451
|
-
test("camelCase: '
|
|
452
|
-
const path = builder.makeRPCHttpRoutePath({
|
|
453
|
-
expect(path).toBe('/
|
|
522
|
+
test("camelCase procedure name: 'users' + 'getProfile' → /users/get-profile/1", () => {
|
|
523
|
+
const path = builder.makeRPCHttpRoutePath('getProfile', { scope: 'users', version: 1 })
|
|
524
|
+
expect(path).toBe('/users/get-profile/1')
|
|
454
525
|
})
|
|
455
526
|
|
|
456
|
-
test("PascalCase: '
|
|
457
|
-
const path = builder.makeRPCHttpRoutePath({
|
|
458
|
-
expect(path).toBe('/
|
|
527
|
+
test("PascalCase procedure name: 'users' + 'UpdateProfile' → /users/update-profile/1", () => {
|
|
528
|
+
const path = builder.makeRPCHttpRoutePath('UpdateProfile', { scope: 'users', version: 1 })
|
|
529
|
+
expect(path).toBe('/users/update-profile/1')
|
|
459
530
|
})
|
|
460
531
|
|
|
461
532
|
test('version number included in path', () => {
|
|
462
|
-
const pathV1 = builder.makeRPCHttpRoutePath({
|
|
463
|
-
const pathV2 = builder.makeRPCHttpRoutePath({
|
|
464
|
-
const pathV99 = builder.makeRPCHttpRoutePath({
|
|
533
|
+
const pathV1 = builder.makeRPCHttpRoutePath('Test', { scope: 'test', version: 1 })
|
|
534
|
+
const pathV2 = builder.makeRPCHttpRoutePath('Test', { scope: 'test', version: 2 })
|
|
535
|
+
const pathV99 = builder.makeRPCHttpRoutePath('Test', { scope: 'test', version: 99 })
|
|
465
536
|
|
|
466
|
-
expect(pathV1).toBe('/
|
|
467
|
-
expect(pathV2).toBe('/
|
|
468
|
-
expect(pathV99).toBe('/
|
|
537
|
+
expect(pathV1).toBe('/test/test/1')
|
|
538
|
+
expect(pathV2).toBe('/test/test/2')
|
|
539
|
+
expect(pathV99).toBe('/test/test/99')
|
|
469
540
|
})
|
|
470
541
|
|
|
471
542
|
test('handles mixed case in array segments', () => {
|
|
472
|
-
const path = builder.makeRPCHttpRoutePath({
|
|
473
|
-
|
|
543
|
+
const path = builder.makeRPCHttpRoutePath('ListUsers', {
|
|
544
|
+
scope: ['UserModule', 'getActiveUsers'],
|
|
474
545
|
version: 1,
|
|
475
546
|
})
|
|
476
|
-
expect(path).toBe('/
|
|
547
|
+
expect(path).toBe('/user-module/get-active-users/list-users/1')
|
|
477
548
|
})
|
|
478
549
|
})
|
|
479
550
|
|
|
@@ -492,16 +563,17 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
492
563
|
const returnSchema = v.object({ name: v.string() })
|
|
493
564
|
|
|
494
565
|
const RPC = Procedures<{}, RPCConfig>()
|
|
495
|
-
|
|
566
|
+
RPC.Create(
|
|
496
567
|
'GetUser',
|
|
497
|
-
{
|
|
568
|
+
{ scope: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } },
|
|
498
569
|
async () => ({ name: 'test' })
|
|
499
570
|
)
|
|
500
571
|
|
|
501
|
-
|
|
502
|
-
|
|
572
|
+
builder.register(RPC, () => ({}))
|
|
573
|
+
builder.build()
|
|
503
574
|
|
|
504
|
-
|
|
575
|
+
const doc = builder.docs[0]!
|
|
576
|
+
expect(doc.path).toBe('/users/get-user/1')
|
|
505
577
|
expect(doc.method).toBe('post')
|
|
506
578
|
expect(doc.jsonSchema.body).toBeDefined()
|
|
507
579
|
expect(doc.jsonSchema.response).toBeDefined()
|
|
@@ -509,11 +581,12 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
509
581
|
|
|
510
582
|
test('omits body schema when no params defined', () => {
|
|
511
583
|
const RPC = Procedures<{}, RPCConfig>()
|
|
512
|
-
RPC.Create('NoParams', {
|
|
584
|
+
RPC.Create('NoParams', { scope: 'no-params', version: 1 }, async () => ({ ok: true }))
|
|
513
585
|
|
|
514
|
-
|
|
515
|
-
|
|
586
|
+
builder.register(RPC, () => ({}))
|
|
587
|
+
builder.build()
|
|
516
588
|
|
|
589
|
+
const doc = builder.docs[0]!
|
|
517
590
|
expect(doc.jsonSchema.body).toBeUndefined()
|
|
518
591
|
})
|
|
519
592
|
|
|
@@ -521,26 +594,27 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
521
594
|
const RPC = Procedures<{}, RPCConfig>()
|
|
522
595
|
RPC.Create(
|
|
523
596
|
'NoReturn',
|
|
524
|
-
{
|
|
597
|
+
{ scope: 'no-return', version: 1, schema: { params: v.object({ x: v.number() }) } },
|
|
525
598
|
async () => ({})
|
|
526
599
|
)
|
|
527
600
|
|
|
528
|
-
|
|
529
|
-
|
|
601
|
+
builder.register(RPC, () => ({}))
|
|
602
|
+
builder.build()
|
|
530
603
|
|
|
604
|
+
const doc = builder.docs[0]!
|
|
531
605
|
expect(doc.jsonSchema.body).toBeDefined()
|
|
532
606
|
expect(doc.jsonSchema.response).toBeUndefined()
|
|
533
607
|
})
|
|
534
608
|
|
|
535
609
|
test("method is always 'post'", () => {
|
|
536
610
|
const RPC = Procedures<{}, RPCConfig>()
|
|
537
|
-
RPC.Create('Test1', {
|
|
538
|
-
RPC.Create('Test2', {
|
|
611
|
+
RPC.Create('Test1', { scope: 't1', version: 1 }, async () => ({}))
|
|
612
|
+
RPC.Create('Test2', { scope: 't2', version: 2 }, async () => ({}))
|
|
539
613
|
|
|
540
|
-
|
|
614
|
+
builder.register(RPC, () => ({}))
|
|
615
|
+
builder.build()
|
|
541
616
|
|
|
542
|
-
|
|
543
|
-
const doc = builder.buildRpcHttpRouteDoc(proc)
|
|
617
|
+
builder.docs.forEach((doc) => {
|
|
544
618
|
expect(doc.method).toBe('post')
|
|
545
619
|
})
|
|
546
620
|
})
|
|
@@ -560,11 +634,11 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
560
634
|
const AuthRPC = Procedures<AuthContext, RPCConfig>()
|
|
561
635
|
|
|
562
636
|
// Create public procedures
|
|
563
|
-
PublicRPC.Create('GetVersion', {
|
|
637
|
+
PublicRPC.Create('GetVersion', { scope: ['system', 'version'], version: 1 }, async () => ({
|
|
564
638
|
version: '1.0.0',
|
|
565
639
|
}))
|
|
566
640
|
|
|
567
|
-
PublicRPC.Create('HealthCheck', {
|
|
641
|
+
PublicRPC.Create('HealthCheck', { scope: 'health', version: 1 }, async () => ({
|
|
568
642
|
status: 'ok',
|
|
569
643
|
}))
|
|
570
644
|
|
|
@@ -572,7 +646,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
572
646
|
AuthRPC.Create(
|
|
573
647
|
'GetProfile',
|
|
574
648
|
{
|
|
575
|
-
|
|
649
|
+
scope: ['users', 'profile'],
|
|
576
650
|
version: 1,
|
|
577
651
|
schema: { returnType: v.object({ userId: v.string(), source: v.string() }) },
|
|
578
652
|
},
|
|
@@ -582,7 +656,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
582
656
|
AuthRPC.Create(
|
|
583
657
|
'UpdateProfile',
|
|
584
658
|
{
|
|
585
|
-
|
|
659
|
+
scope: ['users', 'profile'],
|
|
586
660
|
version: 2,
|
|
587
661
|
schema: { params: v.object({ name: v.string() }) },
|
|
588
662
|
},
|
|
@@ -608,24 +682,24 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
608
682
|
const app = builder.build()
|
|
609
683
|
|
|
610
684
|
// Test public endpoints
|
|
611
|
-
const versionRes = await request(app).post('/
|
|
685
|
+
const versionRes = await request(app).post('/system/version/get-version/1').send({})
|
|
612
686
|
expect(versionRes.status).toBe(200)
|
|
613
687
|
expect(versionRes.body).toEqual({ version: '1.0.0' })
|
|
614
688
|
|
|
615
|
-
const healthRes = await request(app).post('/
|
|
689
|
+
const healthRes = await request(app).post('/health/health-check/1').send({})
|
|
616
690
|
expect(healthRes.status).toBe(200)
|
|
617
691
|
expect(healthRes.body).toEqual({ status: 'ok' })
|
|
618
692
|
|
|
619
693
|
// Test authenticated endpoints
|
|
620
694
|
const profileRes = await request(app)
|
|
621
|
-
.post('/
|
|
695
|
+
.post('/users/profile/get-profile/1')
|
|
622
696
|
.set('X-User-Id', 'user-123')
|
|
623
697
|
.send({})
|
|
624
698
|
expect(profileRes.status).toBe(200)
|
|
625
699
|
expect(profileRes.body).toEqual({ userId: 'user-123', source: 'auth' })
|
|
626
700
|
|
|
627
701
|
const updateRes = await request(app)
|
|
628
|
-
.post('/
|
|
702
|
+
.post('/users/profile/update-profile/2')
|
|
629
703
|
.set('X-User-Id', 'user-456')
|
|
630
704
|
.send({ name: 'John Doe' })
|
|
631
705
|
expect(updateRes.status).toBe(200)
|
|
@@ -635,10 +709,10 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
635
709
|
expect(builder.docs).toHaveLength(4)
|
|
636
710
|
|
|
637
711
|
const paths = builder.docs.map((d) => d.path)
|
|
638
|
-
expect(paths).toContain('/
|
|
639
|
-
expect(paths).toContain('/
|
|
640
|
-
expect(paths).toContain('/
|
|
641
|
-
expect(paths).toContain('/
|
|
712
|
+
expect(paths).toContain('/system/version/get-version/1')
|
|
713
|
+
expect(paths).toContain('/health/health-check/1')
|
|
714
|
+
expect(paths).toContain('/users/profile/get-profile/1')
|
|
715
|
+
expect(paths).toContain('/users/profile/update-profile/2')
|
|
642
716
|
|
|
643
717
|
// Verify hooks were called
|
|
644
718
|
expect(events).toContain('request-start')
|