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