ts-procedures 3.0.2 → 3.2.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 +14 -3
- package/build/errors.js +40 -5
- package/build/errors.js.map +1 -1
- package/build/errors.test.js +82 -0
- package/build/errors.test.js.map +1 -1
- package/build/exports.d.ts +1 -0
- package/build/exports.js +1 -0
- package/build/exports.js.map +1 -1
- package/build/implementations/http/express-rpc/index.d.ts +6 -3
- package/build/implementations/http/express-rpc/index.js +18 -6
- package/build/implementations/http/express-rpc/index.js.map +1 -1
- package/build/implementations/http/express-rpc/index.test.js +153 -0
- package/build/implementations/http/express-rpc/index.test.js.map +1 -1
- package/build/implementations/http/express-rpc/types.d.ts +6 -23
- package/build/implementations/http/hono-rpc/index.d.ts +6 -3
- package/build/implementations/http/hono-rpc/index.js +18 -6
- package/build/implementations/http/hono-rpc/index.js.map +1 -1
- package/build/implementations/http/hono-rpc/index.test.js +153 -0
- package/build/implementations/http/hono-rpc/index.test.js.map +1 -1
- package/build/implementations/http/hono-rpc/types.d.ts +6 -23
- package/build/implementations/types.d.ts +32 -1
- package/build/index.js +15 -5
- package/build/index.js.map +1 -1
- package/build/index.test.js +102 -0
- package/build/index.test.js.map +1 -1
- package/build/schema/compute-schema.d.ts +3 -1
- package/build/schema/compute-schema.js +5 -2
- package/build/schema/compute-schema.js.map +1 -1
- package/build/stack-utils.d.ts +25 -0
- package/build/stack-utils.js +95 -0
- package/build/stack-utils.js.map +1 -0
- package/build/stack-utils.test.d.ts +1 -0
- package/build/stack-utils.test.js +80 -0
- package/build/stack-utils.test.js.map +1 -0
- package/package.json +1 -1
- package/src/errors.test.ts +110 -0
- package/src/errors.ts +49 -3
- package/src/exports.ts +1 -0
- package/src/implementations/http/express-rpc/index.test.ts +225 -0
- package/src/implementations/http/express-rpc/index.ts +39 -10
- package/src/implementations/http/express-rpc/types.ts +8 -25
- package/src/implementations/http/hono-rpc/README.md +82 -42
- package/src/implementations/http/hono-rpc/index.test.ts +225 -0
- package/src/implementations/http/hono-rpc/index.ts +40 -11
- package/src/implementations/http/hono-rpc/types.ts +8 -25
- package/src/implementations/types.ts +39 -1
- package/src/index.test.ts +126 -0
- package/src/index.ts +21 -5
- package/src/schema/compute-schema.ts +5 -0
- package/src/stack-utils.test.ts +94 -0
- package/src/stack-utils.ts +129 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal ts-procedures files that should be skipped when finding user code.
|
|
3
|
+
* Only skip the core library files, not test files or user code.
|
|
4
|
+
*/
|
|
5
|
+
const INTERNAL_FILES = [
|
|
6
|
+
'/index.ts',
|
|
7
|
+
'/index.js',
|
|
8
|
+
'/errors.ts',
|
|
9
|
+
'/errors.js',
|
|
10
|
+
'/stack-utils.ts',
|
|
11
|
+
'/stack-utils.js',
|
|
12
|
+
'/compute-schema.ts',
|
|
13
|
+
'/compute-schema.js',
|
|
14
|
+
'/parser.ts',
|
|
15
|
+
'/parser.js',
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Captures the stack trace at the call site and extracts the definition location.
|
|
19
|
+
* Finds the first stack frame outside of ts-procedures internal files.
|
|
20
|
+
*/
|
|
21
|
+
export function captureDefinitionInfo() {
|
|
22
|
+
const err = new Error();
|
|
23
|
+
const stack = err.stack;
|
|
24
|
+
if (!stack) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
const lines = stack.split('\n');
|
|
28
|
+
// Find the first frame that's not from ts-procedures internals
|
|
29
|
+
// Skip the first line (Error message) and frames from this module
|
|
30
|
+
let userFrame;
|
|
31
|
+
for (let i = 1; i < lines.length; i++) {
|
|
32
|
+
const rawLine = lines[i];
|
|
33
|
+
if (!rawLine)
|
|
34
|
+
continue;
|
|
35
|
+
const line = rawLine.trim();
|
|
36
|
+
// Skip empty or invalid frames
|
|
37
|
+
if (!line.startsWith('at ')) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Skip frames from ts-procedures internal source files
|
|
41
|
+
const isInternalFile = INTERNAL_FILES.some(file => line.includes(file));
|
|
42
|
+
if (isInternalFile) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Skip frames from ts-procedures in node_modules (when used as a dependency)
|
|
46
|
+
if (line.includes('/node_modules/ts-procedures/') || line.includes('\\node_modules\\ts-procedures\\')) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Skip internal node frames
|
|
50
|
+
if (line.includes('node:') || line.startsWith('at Module.') || line.startsWith('at Object.<anonymous> (node:')) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
userFrame = line;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (!userFrame) {
|
|
57
|
+
return { definitionStack: stack };
|
|
58
|
+
}
|
|
59
|
+
const definedAt = parseStackFrame(userFrame);
|
|
60
|
+
return {
|
|
61
|
+
definedAt,
|
|
62
|
+
definitionStack: stack,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parses a V8 stack frame line to extract file, line, and column info.
|
|
67
|
+
* Handles formats like:
|
|
68
|
+
* - "at Object.<anonymous> (/path/to/file.ts:10:5)"
|
|
69
|
+
* - "at functionName (/path/to/file.ts:10:5)"
|
|
70
|
+
* - "at /path/to/file.ts:10:5"
|
|
71
|
+
*/
|
|
72
|
+
function parseStackFrame(frame) {
|
|
73
|
+
// Match patterns like "(path:line:column)" or just "path:line:column"
|
|
74
|
+
const match = frame.match(/\(([^)]+):(\d+):(\d+)\)$/) || frame.match(/at ([^:]+):(\d+):(\d+)$/);
|
|
75
|
+
if (match && match[1] && match[2] && match[3]) {
|
|
76
|
+
return {
|
|
77
|
+
file: match[1],
|
|
78
|
+
line: parseInt(match[2], 10),
|
|
79
|
+
column: parseInt(match[3], 10),
|
|
80
|
+
raw: frame,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Formats definition info for appending to error stacks.
|
|
87
|
+
*/
|
|
88
|
+
export function formatDefinitionInfo(info, procedureName) {
|
|
89
|
+
if (!info.definedAt) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const { file, line, column } = info.definedAt;
|
|
93
|
+
return `\n--- Procedure "${procedureName}" defined at ---\n at ${file}:${line}:${column}`;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=stack-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-utils.js","sourceRoot":"","sources":["../src/stack-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { captureDefinitionInfo, formatDefinitionInfo } from './stack-utils.js';
|
|
3
|
+
describe('Stack Utils', () => {
|
|
4
|
+
describe('captureDefinitionInfo', () => {
|
|
5
|
+
test('returns definition info with definedAt', () => {
|
|
6
|
+
const info = captureDefinitionInfo();
|
|
7
|
+
// Should capture the call site in this test file
|
|
8
|
+
expect(info).toBeDefined();
|
|
9
|
+
expect(info.definitionStack).toBeDefined();
|
|
10
|
+
expect(typeof info.definitionStack).toBe('string');
|
|
11
|
+
});
|
|
12
|
+
test('definedAt contains file, line, column when available', () => {
|
|
13
|
+
const info = captureDefinitionInfo();
|
|
14
|
+
// The definedAt should be present since we're calling from user code (test file)
|
|
15
|
+
if (info.definedAt) {
|
|
16
|
+
expect(info.definedAt.file).toBeDefined();
|
|
17
|
+
expect(typeof info.definedAt.file).toBe('string');
|
|
18
|
+
expect(info.definedAt.line).toBeDefined();
|
|
19
|
+
expect(typeof info.definedAt.line).toBe('number');
|
|
20
|
+
expect(info.definedAt.line).toBeGreaterThan(0);
|
|
21
|
+
expect(info.definedAt.column).toBeDefined();
|
|
22
|
+
expect(typeof info.definedAt.column).toBe('number');
|
|
23
|
+
expect(info.definedAt.column).toBeGreaterThan(0);
|
|
24
|
+
expect(info.definedAt.raw).toBeDefined();
|
|
25
|
+
expect(typeof info.definedAt.raw).toBe('string');
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
test('definitionStack contains Error stack trace', () => {
|
|
29
|
+
const info = captureDefinitionInfo();
|
|
30
|
+
expect(info.definitionStack).toContain('Error');
|
|
31
|
+
expect(info.definitionStack).toContain('at ');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('formatDefinitionInfo', () => {
|
|
35
|
+
test('returns undefined when definedAt is not present', () => {
|
|
36
|
+
const info = {};
|
|
37
|
+
const result = formatDefinitionInfo(info, 'TestProcedure');
|
|
38
|
+
expect(result).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
test('returns formatted string when definedAt is present', () => {
|
|
41
|
+
const info = {
|
|
42
|
+
definedAt: {
|
|
43
|
+
file: '/app/procedures/test.ts',
|
|
44
|
+
line: 42,
|
|
45
|
+
column: 5,
|
|
46
|
+
raw: 'at Object.<anonymous> (/app/procedures/test.ts:42:5)',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const result = formatDefinitionInfo(info, 'TestProcedure');
|
|
50
|
+
expect(result).toBeDefined();
|
|
51
|
+
expect(result).toContain('--- Procedure "TestProcedure" defined at ---');
|
|
52
|
+
expect(result).toContain('/app/procedures/test.ts:42:5');
|
|
53
|
+
});
|
|
54
|
+
test('includes procedure name in formatted output', () => {
|
|
55
|
+
const info = {
|
|
56
|
+
definedAt: {
|
|
57
|
+
file: '/path/to/file.ts',
|
|
58
|
+
line: 10,
|
|
59
|
+
column: 3,
|
|
60
|
+
raw: 'at /path/to/file.ts:10:3',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const result = formatDefinitionInfo(info, 'MyCustomProcedure');
|
|
64
|
+
expect(result).toContain('"MyCustomProcedure"');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('integration with procedure creation', () => {
|
|
68
|
+
test('captures location from calling code', () => {
|
|
69
|
+
// Helper to simulate what happens in Create()
|
|
70
|
+
function simulateCreate() {
|
|
71
|
+
return captureDefinitionInfo();
|
|
72
|
+
}
|
|
73
|
+
const info = simulateCreate();
|
|
74
|
+
// Should have captured the location of the simulateCreate() call
|
|
75
|
+
expect(info).toBeDefined();
|
|
76
|
+
expect(info.definitionStack).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=stack-utils.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-utils.test.js","sourceRoot":"","sources":["../src/stack-utils.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
package/src/errors.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ProcedureValidationError,
|
|
5
5
|
ProcedureRegistrationError,
|
|
6
6
|
} from './errors.js'
|
|
7
|
+
import { DefinitionInfo } from './stack-utils.js'
|
|
7
8
|
|
|
8
9
|
describe('Error Classes', () => {
|
|
9
10
|
test('ProcedureError has correct properties', () => {
|
|
@@ -51,3 +52,112 @@ describe('Error Classes', () => {
|
|
|
51
52
|
expect(registrationErr.procedureName).toBe('Proc3')
|
|
52
53
|
})
|
|
53
54
|
})
|
|
55
|
+
|
|
56
|
+
describe('Error Classes - Definition Info', () => {
|
|
57
|
+
const mockDefinitionInfo: DefinitionInfo = {
|
|
58
|
+
definedAt: {
|
|
59
|
+
file: '/app/procedures/user.ts',
|
|
60
|
+
line: 25,
|
|
61
|
+
column: 3,
|
|
62
|
+
raw: 'at Object.<anonymous> (/app/procedures/user.ts:25:3)',
|
|
63
|
+
},
|
|
64
|
+
definitionStack: 'Error\n at Object.<anonymous> (/app/procedures/user.ts:25:3)',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test('ProcedureError includes definedAt when provided', () => {
|
|
68
|
+
const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
|
|
69
|
+
|
|
70
|
+
expect(err.definedAt).toBeDefined()
|
|
71
|
+
expect(err.definedAt?.file).toBe('/app/procedures/user.ts')
|
|
72
|
+
expect(err.definedAt?.line).toBe(25)
|
|
73
|
+
expect(err.definedAt?.column).toBe(3)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('ProcedureError includes definitionStack when provided', () => {
|
|
77
|
+
const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
|
|
78
|
+
|
|
79
|
+
expect(err.definitionStack).toBeDefined()
|
|
80
|
+
expect(err.definitionStack).toContain('/app/procedures/user.ts')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('ProcedureError works without definitionInfo (backward compat)', () => {
|
|
84
|
+
const err = new ProcedureError('TestProc', 'Something failed', { code: 123 })
|
|
85
|
+
|
|
86
|
+
expect(err.definedAt).toBeUndefined()
|
|
87
|
+
expect(err.definitionStack).toBeUndefined()
|
|
88
|
+
expect(err.procedureName).toBe('TestProc')
|
|
89
|
+
expect(err.message).toBe('Something failed')
|
|
90
|
+
expect(err.meta).toEqual({ code: 123 })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('ProcedureValidationError includes definedAt when provided', () => {
|
|
94
|
+
const err = new ProcedureValidationError('TestProc', 'Validation failed', [], mockDefinitionInfo)
|
|
95
|
+
|
|
96
|
+
expect(err.definedAt).toBeDefined()
|
|
97
|
+
expect(err.definedAt?.file).toBe('/app/procedures/user.ts')
|
|
98
|
+
expect(err.definedAt?.line).toBe(25)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('ProcedureValidationError works without definitionInfo (backward compat)', () => {
|
|
102
|
+
const err = new ProcedureValidationError('TestProc', 'Validation failed', [])
|
|
103
|
+
|
|
104
|
+
expect(err.definedAt).toBeUndefined()
|
|
105
|
+
expect(err.definitionStack).toBeUndefined()
|
|
106
|
+
expect(err.name).toBe('ProcedureValidationError')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('ProcedureRegistrationError includes definedAt when provided', () => {
|
|
110
|
+
const err = new ProcedureRegistrationError('TestProc', 'Registration failed', mockDefinitionInfo)
|
|
111
|
+
|
|
112
|
+
expect(err.definedAt).toBeDefined()
|
|
113
|
+
expect(err.definedAt?.file).toBe('/app/procedures/user.ts')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('ProcedureRegistrationError works without definitionInfo (backward compat)', () => {
|
|
117
|
+
const err = new ProcedureRegistrationError('TestProc', 'Registration failed')
|
|
118
|
+
|
|
119
|
+
expect(err.definedAt).toBeUndefined()
|
|
120
|
+
expect(err.definitionStack).toBeUndefined()
|
|
121
|
+
expect(err.name).toBe('ProcedureRegistrationError')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('getDefinitionLocation returns formatted location string', () => {
|
|
125
|
+
const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
|
|
126
|
+
|
|
127
|
+
const location = err.getDefinitionLocation()
|
|
128
|
+
|
|
129
|
+
expect(location).toBe('/app/procedures/user.ts:25:3')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('getDefinitionLocation returns undefined when no definedAt', () => {
|
|
133
|
+
const err = new ProcedureError('TestProc', 'Something failed')
|
|
134
|
+
|
|
135
|
+
const location = err.getDefinitionLocation()
|
|
136
|
+
|
|
137
|
+
expect(location).toBeUndefined()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('enhanced stack contains definition location', () => {
|
|
141
|
+
const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
|
|
142
|
+
|
|
143
|
+
expect(err.stack).toContain('--- Procedure "TestProc" defined at ---')
|
|
144
|
+
expect(err.stack).toContain('/app/procedures/user.ts:25:3')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('stack is not modified when no definitionInfo', () => {
|
|
148
|
+
const err = new ProcedureError('TestProc', 'Something failed')
|
|
149
|
+
|
|
150
|
+
expect(err.stack).toBeDefined()
|
|
151
|
+
expect(err.stack).not.toContain('--- Procedure')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('all error types enhance stack with definition location', () => {
|
|
155
|
+
const baseErr = new ProcedureError('Proc1', 'message', undefined, mockDefinitionInfo)
|
|
156
|
+
const validationErr = new ProcedureValidationError('Proc2', 'message', [], mockDefinitionInfo)
|
|
157
|
+
const registrationErr = new ProcedureRegistrationError('Proc3', 'message', mockDefinitionInfo)
|
|
158
|
+
|
|
159
|
+
expect(baseErr.stack).toContain('--- Procedure "Proc1" defined at ---')
|
|
160
|
+
expect(validationErr.stack).toContain('--- Procedure "Proc2" defined at ---')
|
|
161
|
+
expect(registrationErr.stack).toContain('--- Procedure "Proc3" defined at ---')
|
|
162
|
+
})
|
|
163
|
+
})
|
package/src/errors.ts
CHANGED
|
@@ -1,19 +1,58 @@
|
|
|
1
1
|
import { TSchemaValidationError } from './schema/parser.js'
|
|
2
|
+
import { DefinitionInfo, DefinitionLocation, formatDefinitionInfo } from './stack-utils.js'
|
|
2
3
|
|
|
3
4
|
export class ProcedureError extends Error {
|
|
4
5
|
cause?: unknown
|
|
6
|
+
readonly definedAt?: DefinitionLocation
|
|
7
|
+
readonly definitionStack?: string
|
|
5
8
|
|
|
6
9
|
constructor(
|
|
7
10
|
readonly procedureName: string,
|
|
8
11
|
readonly message: string,
|
|
9
12
|
readonly meta?: object,
|
|
13
|
+
// Used for error stack trace details
|
|
14
|
+
definitionInfo?: DefinitionInfo
|
|
10
15
|
) {
|
|
11
16
|
super(message)
|
|
12
17
|
this.name = 'ProcedureError'
|
|
13
18
|
|
|
19
|
+
if (definitionInfo) {
|
|
20
|
+
this.definedAt = definitionInfo.definedAt
|
|
21
|
+
this.definitionStack = definitionInfo.definitionStack
|
|
22
|
+
this.enhanceStack()
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
15
26
|
Object.setPrototypeOf(this, ProcedureError.prototype)
|
|
16
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns a formatted string showing where the procedure was defined.
|
|
31
|
+
*/
|
|
32
|
+
getDefinitionLocation(): string | undefined {
|
|
33
|
+
if (!this.definedAt) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
return `${this.definedAt.file}:${this.definedAt.line}:${this.definedAt.column}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Enhances the error stack with definition location information.
|
|
41
|
+
*/
|
|
42
|
+
private enhanceStack(): void {
|
|
43
|
+
if (!this.stack || !this.definedAt) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const definitionSection = formatDefinitionInfo(
|
|
48
|
+
{ definedAt: this.definedAt, definitionStack: this.definitionStack },
|
|
49
|
+
this.procedureName
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (definitionSection) {
|
|
53
|
+
this.stack = this.stack + definitionSection
|
|
54
|
+
}
|
|
55
|
+
}
|
|
17
56
|
}
|
|
18
57
|
|
|
19
58
|
export class ProcedureValidationError extends ProcedureError {
|
|
@@ -21,8 +60,10 @@ export class ProcedureValidationError extends ProcedureError {
|
|
|
21
60
|
readonly procedureName: string,
|
|
22
61
|
message: string,
|
|
23
62
|
readonly errors?: TSchemaValidationError[],
|
|
63
|
+
// Used for error stack trace details
|
|
64
|
+
definitionInfo?: DefinitionInfo
|
|
24
65
|
) {
|
|
25
|
-
super(procedureName, message)
|
|
66
|
+
super(procedureName, message, undefined, definitionInfo)
|
|
26
67
|
this.name = 'ProcedureValidationError'
|
|
27
68
|
|
|
28
69
|
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
|
@@ -31,8 +72,13 @@ export class ProcedureValidationError extends ProcedureError {
|
|
|
31
72
|
}
|
|
32
73
|
|
|
33
74
|
export class ProcedureRegistrationError extends ProcedureError {
|
|
34
|
-
constructor(
|
|
35
|
-
|
|
75
|
+
constructor(
|
|
76
|
+
readonly procedureName: string,
|
|
77
|
+
message: string,
|
|
78
|
+
// Used for error stack trace details
|
|
79
|
+
definitionInfo?: DefinitionInfo
|
|
80
|
+
) {
|
|
81
|
+
super(procedureName, message, undefined, definitionInfo)
|
|
36
82
|
this.name = 'ProcedureRegistrationError'
|
|
37
83
|
|
|
38
84
|
// https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
|
package/src/exports.ts
CHANGED
|
@@ -626,6 +626,231 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
626
626
|
})
|
|
627
627
|
})
|
|
628
628
|
|
|
629
|
+
// --------------------------------------------------------------------------
|
|
630
|
+
// extendProcedureDoc Tests
|
|
631
|
+
// --------------------------------------------------------------------------
|
|
632
|
+
describe('extendProcedureDoc', () => {
|
|
633
|
+
test('adds custom properties to generated documentation', () => {
|
|
634
|
+
const builder = new ExpressRPCAppBuilder()
|
|
635
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
636
|
+
|
|
637
|
+
RPC.Create('GetUser', { scope: 'users', version: 1 }, async () => ({ name: 'test' }))
|
|
638
|
+
|
|
639
|
+
builder.register(
|
|
640
|
+
RPC,
|
|
641
|
+
() => ({}),
|
|
642
|
+
({ base, procedure }) => ({
|
|
643
|
+
summary: `Get user endpoint`,
|
|
644
|
+
tags: ['users'],
|
|
645
|
+
operationId: procedure.name,
|
|
646
|
+
})
|
|
647
|
+
)
|
|
648
|
+
builder.build()
|
|
649
|
+
|
|
650
|
+
const doc = builder.docs[0]!
|
|
651
|
+
expect(doc).toHaveProperty('summary', 'Get user endpoint')
|
|
652
|
+
expect(doc).toHaveProperty('tags', ['users'])
|
|
653
|
+
expect(doc).toHaveProperty('operationId', 'GetUser')
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
test('receives correct base and procedure parameters', () => {
|
|
657
|
+
const extendFn = vi.fn(() => ({}))
|
|
658
|
+
const builder = new ExpressRPCAppBuilder()
|
|
659
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
660
|
+
|
|
661
|
+
const paramsSchema = v.object({ id: v.string() })
|
|
662
|
+
RPC.Create(
|
|
663
|
+
'GetItem',
|
|
664
|
+
{ scope: 'items', version: 2, schema: { params: paramsSchema } },
|
|
665
|
+
async () => ({})
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
builder.register(RPC, () => ({}), extendFn)
|
|
669
|
+
builder.build()
|
|
670
|
+
|
|
671
|
+
expect(extendFn).toHaveBeenCalledTimes(1)
|
|
672
|
+
const callArg = extendFn.mock.calls[0]![0 as any] as any
|
|
673
|
+
|
|
674
|
+
// Verify base properties
|
|
675
|
+
expect(callArg.base).toHaveProperty('name', 'GetItem')
|
|
676
|
+
expect(callArg.base).toHaveProperty('version', 2)
|
|
677
|
+
expect(callArg.base).toHaveProperty('scope', 'items')
|
|
678
|
+
expect(callArg.base).toHaveProperty('path', '/items/get-item/2')
|
|
679
|
+
expect(callArg.base).toHaveProperty('method', 'post')
|
|
680
|
+
expect(callArg.base.jsonSchema).toHaveProperty('body')
|
|
681
|
+
|
|
682
|
+
// Verify procedure properties
|
|
683
|
+
expect(callArg.procedure).toHaveProperty('name', 'GetItem')
|
|
684
|
+
expect(callArg.procedure).toHaveProperty('handler')
|
|
685
|
+
expect(callArg.procedure.config).toHaveProperty('scope', 'items')
|
|
686
|
+
expect(callArg.procedure.config).toHaveProperty('version', 2)
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
test('base properties take precedence over extended properties', () => {
|
|
690
|
+
const builder = new ExpressRPCAppBuilder()
|
|
691
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
692
|
+
|
|
693
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({}))
|
|
694
|
+
|
|
695
|
+
builder.register(
|
|
696
|
+
RPC,
|
|
697
|
+
() => ({}),
|
|
698
|
+
() => ({
|
|
699
|
+
name: 'OverriddenName',
|
|
700
|
+
path: '/overridden/path',
|
|
701
|
+
method: 'get',
|
|
702
|
+
customField: 'custom-value',
|
|
703
|
+
})
|
|
704
|
+
)
|
|
705
|
+
builder.build()
|
|
706
|
+
|
|
707
|
+
const doc = builder.docs[0]!
|
|
708
|
+
// Base properties should NOT be overridden
|
|
709
|
+
expect(doc.name).toBe('Test')
|
|
710
|
+
expect(doc.path).toBe('/test/test/1')
|
|
711
|
+
expect(doc.method).toBe('post')
|
|
712
|
+
// Custom field should be present
|
|
713
|
+
expect(doc).toHaveProperty('customField', 'custom-value')
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
test('different factories can have different extendProcedureDoc functions', () => {
|
|
717
|
+
const builder = new ExpressRPCAppBuilder()
|
|
718
|
+
|
|
719
|
+
const PublicRPC = Procedures<{}, RPCConfig>()
|
|
720
|
+
const AdminRPC = Procedures<{}, RPCConfig>()
|
|
721
|
+
|
|
722
|
+
PublicRPC.Create('GetPublic', { scope: 'public', version: 1 }, async () => ({}))
|
|
723
|
+
AdminRPC.Create('GetAdmin', { scope: 'admin', version: 1 }, async () => ({}))
|
|
724
|
+
|
|
725
|
+
builder
|
|
726
|
+
.register(
|
|
727
|
+
PublicRPC,
|
|
728
|
+
() => ({}),
|
|
729
|
+
() => ({
|
|
730
|
+
security: [],
|
|
731
|
+
tags: ['public'],
|
|
732
|
+
})
|
|
733
|
+
)
|
|
734
|
+
.register(
|
|
735
|
+
AdminRPC,
|
|
736
|
+
() => ({}),
|
|
737
|
+
() => ({
|
|
738
|
+
security: [{ bearerAuth: [] }],
|
|
739
|
+
tags: ['admin'],
|
|
740
|
+
})
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
builder.build()
|
|
744
|
+
|
|
745
|
+
const publicDoc = builder.docs.find((d) => d.name === 'GetPublic')!
|
|
746
|
+
const adminDoc = builder.docs.find((d) => d.name === 'GetAdmin')!
|
|
747
|
+
|
|
748
|
+
expect(publicDoc).toHaveProperty('security', [])
|
|
749
|
+
expect(publicDoc).toHaveProperty('tags', ['public'])
|
|
750
|
+
expect(adminDoc).toHaveProperty('security', [{ bearerAuth: [] }])
|
|
751
|
+
expect(adminDoc).toHaveProperty('tags', ['admin'])
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
test('extendProcedureDoc is called for each procedure in factory', () => {
|
|
755
|
+
const extendFn = vi.fn(() => ({ extended: true }))
|
|
756
|
+
const builder = new ExpressRPCAppBuilder()
|
|
757
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
758
|
+
|
|
759
|
+
RPC.Create('Method1', { scope: 'm1', version: 1 }, async () => ({}))
|
|
760
|
+
RPC.Create('Method2', { scope: 'm2', version: 1 }, async () => ({}))
|
|
761
|
+
RPC.Create('Method3', { scope: 'm3', version: 1 }, async () => ({}))
|
|
762
|
+
|
|
763
|
+
builder.register(RPC, () => ({}), extendFn)
|
|
764
|
+
builder.build()
|
|
765
|
+
|
|
766
|
+
expect(extendFn).toHaveBeenCalledTimes(3)
|
|
767
|
+
|
|
768
|
+
const procedureNames = extendFn.mock.calls.map((call: any) => call[0].procedure.name)
|
|
769
|
+
expect(procedureNames).toContain('Method1')
|
|
770
|
+
expect(procedureNames).toContain('Method2')
|
|
771
|
+
expect(procedureNames).toContain('Method3')
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
test('not providing extendProcedureDoc results in base documentation only', () => {
|
|
775
|
+
const builder = new ExpressRPCAppBuilder()
|
|
776
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
777
|
+
|
|
778
|
+
RPC.Create('Test', { scope: 'test', version: 1 }, async () => ({}))
|
|
779
|
+
|
|
780
|
+
builder.register(RPC, () => ({})) // No extendProcedureDoc
|
|
781
|
+
builder.build()
|
|
782
|
+
|
|
783
|
+
const doc = builder.docs[0]!
|
|
784
|
+
expect(doc.name).toBe('Test')
|
|
785
|
+
expect(doc.path).toBe('/test/test/1')
|
|
786
|
+
expect(doc.method).toBe('post')
|
|
787
|
+
// Should not have any extra properties
|
|
788
|
+
expect(Object.keys(doc)).toEqual(['name', 'version', 'scope', 'path', 'method', 'jsonSchema'])
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
test('extendProcedureDoc can access procedure config for conditional logic', () => {
|
|
792
|
+
const builder = new ExpressRPCAppBuilder()
|
|
793
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
794
|
+
|
|
795
|
+
RPC.Create('PublicEndpoint', { scope: 'public', version: 1 }, async () => ({}))
|
|
796
|
+
RPC.Create('PrivateEndpoint', { scope: 'private', version: 1 }, async () => ({}))
|
|
797
|
+
|
|
798
|
+
builder.register(
|
|
799
|
+
RPC,
|
|
800
|
+
() => ({}),
|
|
801
|
+
({ procedure }) => ({
|
|
802
|
+
isPublic: procedure.config.scope === 'public',
|
|
803
|
+
description: `This is a ${procedure.config.scope} endpoint`,
|
|
804
|
+
})
|
|
805
|
+
)
|
|
806
|
+
builder.build()
|
|
807
|
+
|
|
808
|
+
const publicDoc = builder.docs.find((d) => d.name === 'PublicEndpoint')!
|
|
809
|
+
const privateDoc = builder.docs.find((d) => d.name === 'PrivateEndpoint')!
|
|
810
|
+
|
|
811
|
+
expect(publicDoc).toHaveProperty('isPublic', true)
|
|
812
|
+
expect(publicDoc).toHaveProperty('description', 'This is a public endpoint')
|
|
813
|
+
expect(privateDoc).toHaveProperty('isPublic', false)
|
|
814
|
+
expect(privateDoc).toHaveProperty('description', 'This is a private endpoint')
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
test('extendProcedureDoc can use base jsonSchema for OpenAPI-style docs', () => {
|
|
818
|
+
const builder = new ExpressRPCAppBuilder()
|
|
819
|
+
const RPC = Procedures<{}, RPCConfig>()
|
|
820
|
+
|
|
821
|
+
const paramsSchema = v.object({ userId: v.string() })
|
|
822
|
+
const returnSchema = v.object({ name: v.string(), email: v.string() })
|
|
823
|
+
|
|
824
|
+
RPC.Create(
|
|
825
|
+
'GetUser',
|
|
826
|
+
{ scope: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } },
|
|
827
|
+
async () => ({ name: 'test', email: 'test@example.com' })
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
builder.register(
|
|
831
|
+
RPC,
|
|
832
|
+
() => ({}),
|
|
833
|
+
({ base }) => ({
|
|
834
|
+
requestBody: base.jsonSchema.body
|
|
835
|
+
? { content: { 'application/json': { schema: base.jsonSchema.body } } }
|
|
836
|
+
: undefined,
|
|
837
|
+
responses: {
|
|
838
|
+
200: base.jsonSchema.response
|
|
839
|
+
? { content: { 'application/json': { schema: base.jsonSchema.response } } }
|
|
840
|
+
: { description: 'Success' },
|
|
841
|
+
},
|
|
842
|
+
})
|
|
843
|
+
)
|
|
844
|
+
builder.build()
|
|
845
|
+
|
|
846
|
+
const doc = builder.docs[0] as any
|
|
847
|
+
expect(doc).toHaveProperty('requestBody')
|
|
848
|
+
expect(doc.requestBody).toHaveProperty('content')
|
|
849
|
+
expect(doc).toHaveProperty('responses')
|
|
850
|
+
expect(doc.responses).toHaveProperty('200')
|
|
851
|
+
})
|
|
852
|
+
})
|
|
853
|
+
|
|
629
854
|
// --------------------------------------------------------------------------
|
|
630
855
|
// Integration Test
|
|
631
856
|
// --------------------------------------------------------------------------
|