@ts-contract/plugins 1.0.0-alpha.0 → 1.0.0-alpha.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/README.md +213 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/plugins/websocket-path.d.ts +12 -0
- package/dist/lib/plugins/websocket-path.d.ts.map +1 -0
- package/dist/lib/plugins/websocket-path.js +7 -0
- package/dist/lib/plugins/websocket-validate.d.ts +14 -0
- package/dist/lib/plugins/websocket-validate.d.ts.map +1 -0
- package/dist/lib/plugins/websocket-validate.js +11 -0
- package/dist/lib/websocket-path.d.ts +5 -0
- package/dist/lib/websocket-path.d.ts.map +1 -0
- package/dist/lib/websocket-path.js +25 -0
- package/dist/lib/websocket-validate.d.ts +7 -0
- package/dist/lib/websocket-validate.d.ts.map +1 -0
- package/dist/lib/websocket-validate.js +47 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,216 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ts-contract/plugins
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Built-in plugins for path building and schema validation for ts-contract.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`@ts-contract/plugins` provides official plugins that extend ts-contract with commonly needed functionality:
|
|
8
|
+
|
|
9
|
+
- **Path Plugin** - Build URL paths with type-safe parameter substitution
|
|
10
|
+
- **Validate Plugin** - Validate request/response data against schemas
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ts-contract/plugins @ts-contract/core
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @ts-contract/plugins @ts-contract/core
|
|
18
|
+
# or
|
|
19
|
+
yarn add @ts-contract/plugins @ts-contract/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Plugins
|
|
23
|
+
|
|
24
|
+
### Path Plugin
|
|
25
|
+
|
|
26
|
+
Build type-safe URL paths with parameter substitution and query string generation.
|
|
27
|
+
|
|
28
|
+
#### Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { initContract } from '@ts-contract/core';
|
|
32
|
+
import { pathPlugin } from '@ts-contract/plugins';
|
|
33
|
+
import { contract } from './contract';
|
|
34
|
+
|
|
35
|
+
const api = initContract(contract).use(pathPlugin).build();
|
|
36
|
+
|
|
37
|
+
// Build path with parameters
|
|
38
|
+
const path = api.getUser.buildPath({ id: '123' });
|
|
39
|
+
// => "/users/123"
|
|
40
|
+
|
|
41
|
+
// Build path with query parameters
|
|
42
|
+
const path = api.listUsers.buildPath(undefined, { page: '1', limit: '10' });
|
|
43
|
+
// => "/users?page=1&limit=10"
|
|
44
|
+
|
|
45
|
+
// Build path with both
|
|
46
|
+
const path = api.searchUsers.buildPath(
|
|
47
|
+
{ category: 'active' },
|
|
48
|
+
{ sort: 'name' },
|
|
49
|
+
);
|
|
50
|
+
// => "/users/active?sort=name"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### API
|
|
54
|
+
|
|
55
|
+
**`buildPath(params?, query?)`**
|
|
56
|
+
|
|
57
|
+
Builds a URL path from the route definition.
|
|
58
|
+
|
|
59
|
+
- **params** - Path parameters (typed from contract)
|
|
60
|
+
- **query** - Query parameters (typed from contract)
|
|
61
|
+
- **Returns:** String URL path
|
|
62
|
+
|
|
63
|
+
### Validate Plugin
|
|
64
|
+
|
|
65
|
+
Validate request bodies and response data against your contract schemas.
|
|
66
|
+
|
|
67
|
+
#### Usage
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { initContract } from '@ts-contract/core';
|
|
71
|
+
import { validatePlugin } from '@ts-contract/plugins';
|
|
72
|
+
import { contract } from './contract';
|
|
73
|
+
|
|
74
|
+
const api = initContract(contract).use(validatePlugin).build();
|
|
75
|
+
|
|
76
|
+
// Validate request body
|
|
77
|
+
try {
|
|
78
|
+
const validatedBody = api.createUser.validateBody({
|
|
79
|
+
name: 'Alice',
|
|
80
|
+
email: 'alice@example.com',
|
|
81
|
+
});
|
|
82
|
+
// validatedBody is typed and validated
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Validation failed
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Validate response
|
|
88
|
+
try {
|
|
89
|
+
const validatedResponse = api.getUser.validateResponse(200, {
|
|
90
|
+
id: '123',
|
|
91
|
+
name: 'Alice',
|
|
92
|
+
email: 'alice@example.com',
|
|
93
|
+
});
|
|
94
|
+
// validatedResponse is typed and validated
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// Validation failed
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Validate path parameters
|
|
100
|
+
const validatedParams = api.getUser.validatePathParams({ id: '123' });
|
|
101
|
+
|
|
102
|
+
// Validate query parameters
|
|
103
|
+
const validatedQuery = api.listUsers.validateQuery({ page: '1', limit: '10' });
|
|
104
|
+
|
|
105
|
+
// Validate headers
|
|
106
|
+
const validatedHeaders = api.getUser.validateHeaders({
|
|
107
|
+
authorization: 'Bearer token',
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### API
|
|
112
|
+
|
|
113
|
+
**`validateBody(data)`**
|
|
114
|
+
|
|
115
|
+
Validates request body data against the route's body schema.
|
|
116
|
+
|
|
117
|
+
- **data** - Data to validate
|
|
118
|
+
- **Returns:** Validated and typed data
|
|
119
|
+
- **Throws:** Validation error if data is invalid
|
|
120
|
+
|
|
121
|
+
**`validateResponse(status, data)`**
|
|
122
|
+
|
|
123
|
+
Validates response data against the route's response schema for a specific status code.
|
|
124
|
+
|
|
125
|
+
- **status** - HTTP status code
|
|
126
|
+
- **data** - Data to validate
|
|
127
|
+
- **Returns:** Validated and typed data
|
|
128
|
+
- **Throws:** Validation error if data is invalid
|
|
129
|
+
|
|
130
|
+
**`validatePathParams(data)`**
|
|
131
|
+
|
|
132
|
+
Validates path parameters against the route's pathParams schema.
|
|
133
|
+
|
|
134
|
+
**`validateQuery(data)`**
|
|
135
|
+
|
|
136
|
+
Validates query parameters against the route's query schema.
|
|
137
|
+
|
|
138
|
+
**`validateHeaders(data)`**
|
|
139
|
+
|
|
140
|
+
Validates headers against the route's headers schema.
|
|
141
|
+
|
|
142
|
+
## Using Both Plugins
|
|
143
|
+
|
|
144
|
+
Combine plugins for full functionality:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { initContract } from '@ts-contract/core';
|
|
148
|
+
import { pathPlugin, validatePlugin } from '@ts-contract/plugins';
|
|
149
|
+
|
|
150
|
+
const api = initContract(contract).use(pathPlugin).use(validatePlugin).build();
|
|
151
|
+
|
|
152
|
+
// Use both plugin methods
|
|
153
|
+
const path = api.createUser.buildPath();
|
|
154
|
+
const validatedBody = api.createUser.validateBody({
|
|
155
|
+
name: 'Alice',
|
|
156
|
+
email: 'alice@example.com',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Make request
|
|
160
|
+
const response = await fetch(path, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
body: JSON.stringify(validatedBody),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const data = await response.json();
|
|
167
|
+
const validatedResponse = api.createUser.validateResponse(201, data);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Type Safety
|
|
171
|
+
|
|
172
|
+
All plugin methods are fully typed based on your contract:
|
|
173
|
+
|
|
174
|
+
- Path parameters are typed from `pathParams` schema
|
|
175
|
+
- Query parameters are typed from `query` schema
|
|
176
|
+
- Request body is typed from `body` schema
|
|
177
|
+
- Response data is typed from `responses` schema
|
|
178
|
+
- TypeScript will catch type errors at compile time
|
|
179
|
+
|
|
180
|
+
## Error Handling
|
|
181
|
+
|
|
182
|
+
Validation errors include detailed information:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
try {
|
|
186
|
+
api.createUser.validateBody({ name: 'Alice' }); // Missing email
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(error.message);
|
|
189
|
+
// Detailed validation error from schema library
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Schema Library Support
|
|
194
|
+
|
|
195
|
+
Works with any schema library that implements the [Standard Schema](https://github.com/standard-schema/standard-schema) specification:
|
|
196
|
+
|
|
197
|
+
- ✅ Zod
|
|
198
|
+
- ✅ Valibot
|
|
199
|
+
- ✅ Arktype
|
|
200
|
+
- ✅ And more...
|
|
201
|
+
|
|
202
|
+
## Documentation
|
|
203
|
+
|
|
204
|
+
For complete documentation and examples, visit:
|
|
205
|
+
|
|
206
|
+
- [Path Plugin Documentation](https://github.com/mbrimmer83/ts-contract/tree/main/docs/plugins/path-plugin.md)
|
|
207
|
+
- [Validate Plugin Documentation](https://github.com/mbrimmer83/ts-contract/tree/main/docs/plugins/validate-plugin.md)
|
|
208
|
+
- [Creating Custom Plugins](https://github.com/mbrimmer83/ts-contract/tree/main/docs/plugins/creating-custom-plugins.md)
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT
|
|
213
|
+
|
|
214
|
+
## Contributing
|
|
215
|
+
|
|
216
|
+
Contributions are welcome! Please see the [contributing guide](../../CONTRIBUTING.md) for details.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,8 @@ export * from './lib/path';
|
|
|
2
2
|
export * from './lib/validate';
|
|
3
3
|
export * from './lib/plugins/path';
|
|
4
4
|
export * from './lib/plugins/validate';
|
|
5
|
+
export * from './lib/websocket-path';
|
|
6
|
+
export * from './lib/websocket-validate';
|
|
7
|
+
export * from './lib/plugins/websocket-path';
|
|
8
|
+
export * from './lib/plugins/websocket-validate';
|
|
5
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,7 @@ export * from './lib/path';
|
|
|
2
2
|
export * from './lib/validate';
|
|
3
3
|
export * from './lib/plugins/path';
|
|
4
4
|
export * from './lib/plugins/validate';
|
|
5
|
+
export * from './lib/websocket-path';
|
|
6
|
+
export * from './lib/websocket-validate';
|
|
7
|
+
export * from './lib/plugins/websocket-path';
|
|
8
|
+
export * from './lib/plugins/websocket-validate';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WebSocketDef, InferWebSocketPathParams, InferWebSocketQuery, WebSocketPlugin } from '@ts-contract/core';
|
|
2
|
+
type BuildPathArgs<W extends WebSocketDef> = InferWebSocketPathParams<W> extends undefined ? InferWebSocketQuery<W> extends undefined ? [] : [params?: undefined, query?: InferWebSocketQuery<W>] : InferWebSocketQuery<W> extends undefined ? [params: InferWebSocketPathParams<W>] : [params: InferWebSocketPathParams<W>, query?: InferWebSocketQuery<W>];
|
|
3
|
+
declare module '@ts-contract/core' {
|
|
4
|
+
interface WebSocketPluginTypeRegistry<W> {
|
|
5
|
+
'websocket-path': {
|
|
6
|
+
buildPath: W extends WebSocketDef ? (...args: BuildPathArgs<W>) => string : never;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export declare const websocketPathPlugin: WebSocketPlugin<'websocket-path'>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=websocket-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-path.d.ts","sourceRoot":"","sources":["../../../src/lib/plugins/websocket-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAG3B,KAAK,aAAa,CAAC,CAAC,SAAS,YAAY,IACvC,wBAAwB,CAAC,CAAC,CAAC,SAAS,SAAS,GACzC,mBAAmB,CAAC,CAAC,CAAC,SAAS,SAAS,GACtC,EAAE,GACF,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,GACtD,mBAAmB,CAAC,CAAC,CAAC,SAAS,SAAS,GACtC,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC,GACrC,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9E,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAU,2BAA2B,CAAC,CAAC;QACrC,gBAAgB,EAAE;YAChB,SAAS,EAAE,CAAC,SAAS,YAAY,GAC7B,CAAC,GAAG,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,MAAM,GACrC,KAAK,CAAC;SACX,CAAC;KACH;CACF;AAED,eAAO,MAAM,mBAAmB,EAAE,eAAe,CAAC,gBAAgB,CASjE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { WebSocketDef, InferWebSocketPathParams, InferWebSocketQuery, InferWebSocketHeaders, InferClientMessage, InferServerMessage, WebSocketPlugin } from '@ts-contract/core';
|
|
2
|
+
declare module '@ts-contract/core' {
|
|
3
|
+
interface WebSocketPluginTypeRegistry<W> {
|
|
4
|
+
'websocket-validate': {
|
|
5
|
+
validateClientMessage: W extends WebSocketDef ? <E extends keyof W['clientMessages'] & string>(eventName: E, data: unknown) => InferClientMessage<W, E> : never;
|
|
6
|
+
validateServerMessage: W extends WebSocketDef ? <E extends keyof W['serverMessages'] & string>(eventName: E, data: unknown) => InferServerMessage<W, E> : never;
|
|
7
|
+
validatePathParams: W extends WebSocketDef ? (params: unknown) => InferWebSocketPathParams<W> : never;
|
|
8
|
+
validateQuery: W extends WebSocketDef ? (query: unknown) => InferWebSocketQuery<W> : never;
|
|
9
|
+
validateHeaders: W extends WebSocketDef ? (headers: Record<string, unknown>) => InferWebSocketHeaders<W> : never;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export declare const websocketValidatePlugin: WebSocketPlugin<'websocket-validate'>;
|
|
14
|
+
//# sourceMappingURL=websocket-validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-validate.d.ts","sourceRoot":"","sources":["../../../src/lib/plugins/websocket-validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAU,2BAA2B,CAAC,CAAC;QACrC,oBAAoB,EAAE;YACpB,qBAAqB,EAAE,CAAC,SAAS,YAAY,GACzC,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,gBAAgB,CAAC,GAAG,MAAM,EAC3C,SAAS,EAAE,CAAC,EACZ,IAAI,EAAE,OAAO,KACV,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,KAAK,CAAC;YACV,qBAAqB,EAAE,CAAC,SAAS,YAAY,GACzC,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,gBAAgB,CAAC,GAAG,MAAM,EAC3C,SAAS,EAAE,CAAC,EACZ,IAAI,EAAE,OAAO,KACV,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,KAAK,CAAC;YACV,kBAAkB,EAAE,CAAC,SAAS,YAAY,GACtC,CAAC,MAAM,EAAE,OAAO,KAAK,wBAAwB,CAAC,CAAC,CAAC,GAChD,KAAK,CAAC;YACV,aAAa,EAAE,CAAC,SAAS,YAAY,GACjC,CAAC,KAAK,EAAE,OAAO,KAAK,mBAAmB,CAAC,CAAC,CAAC,GAC1C,KAAK,CAAC;YACV,eAAe,EAAE,CAAC,SAAS,YAAY,GACnC,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,qBAAqB,CAAC,CAAC,CAAC,GAC9D,KAAK,CAAC;SACX,CAAC;KACH;CACF;AAED,eAAO,MAAM,uBAAuB,EAAE,eAAe,CAAC,oBAAoB,CAazE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { validateClientMessage, validateServerMessage, validateWebSocketPathParams, validateWebSocketQuery, validateWebSocketHeaders, } from '../websocket-validate';
|
|
2
|
+
export const websocketValidatePlugin = {
|
|
3
|
+
name: 'websocket-validate',
|
|
4
|
+
websocket: (def) => ({
|
|
5
|
+
validateClientMessage: (eventName, data) => validateClientMessage(def, eventName, data),
|
|
6
|
+
validateServerMessage: (eventName, data) => validateServerMessage(def, eventName, data),
|
|
7
|
+
validatePathParams: (params) => validateWebSocketPathParams(def, params),
|
|
8
|
+
validateQuery: (query) => validateWebSocketQuery(def, query),
|
|
9
|
+
validateHeaders: (headers) => validateWebSocketHeaders(def, headers),
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { WebSocketDef, InferWebSocketPathParams, InferWebSocketQuery } from '@ts-contract/core';
|
|
2
|
+
type BuildWebSocketPathArgs<W extends WebSocketDef> = InferWebSocketPathParams<W> extends undefined ? InferWebSocketQuery<W> extends undefined ? [] : [params?: undefined, query?: InferWebSocketQuery<W>] : InferWebSocketQuery<W> extends undefined ? [params: InferWebSocketPathParams<W>] : [params: InferWebSocketPathParams<W>, query?: InferWebSocketQuery<W>];
|
|
3
|
+
export declare const buildWebSocketPath: <W extends WebSocketDef>(def: W, ...args: BuildWebSocketPathArgs<W>) => string;
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=websocket-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-path.d.ts","sourceRoot":"","sources":["../../src/lib/websocket-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,KAAK,sBAAsB,CAAC,CAAC,SAAS,YAAY,IAChD,wBAAwB,CAAC,CAAC,CAAC,SAAS,SAAS,GACzC,mBAAmB,CAAC,CAAC,CAAC,SAAS,SAAS,GACtC,EAAE,GACF,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,GACtD,mBAAmB,CAAC,CAAC,CAAC,SAAS,SAAS,GACtC,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC,GACrC,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9E,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,YAAY,EACvD,KAAK,CAAC,EACN,GAAG,MAAM,sBAAsB,CAAC,CAAC,CAAC,KACjC,MA+BF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const buildWebSocketPath = (def, ...args) => {
|
|
2
|
+
const [params, query] = args;
|
|
3
|
+
let path = def.path;
|
|
4
|
+
if (params) {
|
|
5
|
+
path = path.replace(/:([^/]+)/g, (_, key) => {
|
|
6
|
+
if (!(key in params)) {
|
|
7
|
+
throw new Error(`Missing path parameter: ${key}`);
|
|
8
|
+
}
|
|
9
|
+
return encodeURIComponent(params[key]);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
if (query) {
|
|
13
|
+
const searchParams = new URLSearchParams();
|
|
14
|
+
for (const [key, value] of Object.entries(query)) {
|
|
15
|
+
if (value !== undefined && value !== null) {
|
|
16
|
+
searchParams.append(key, String(value));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const qs = searchParams.toString();
|
|
20
|
+
if (qs) {
|
|
21
|
+
path += `?${qs}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return path;
|
|
25
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WebSocketDef, InferWebSocketPathParams, InferWebSocketQuery, InferWebSocketHeaders, InferClientMessage, InferServerMessage } from '@ts-contract/core';
|
|
2
|
+
export declare const validateClientMessage: <W extends WebSocketDef, E extends keyof W["clientMessages"] & string>(def: W, eventName: E, data: unknown) => InferClientMessage<W, E>;
|
|
3
|
+
export declare const validateServerMessage: <W extends WebSocketDef, E extends keyof W["serverMessages"] & string>(def: W, eventName: E, data: unknown) => InferServerMessage<W, E>;
|
|
4
|
+
export declare const validateWebSocketPathParams: <W extends WebSocketDef>(def: W, params: unknown) => InferWebSocketPathParams<W>;
|
|
5
|
+
export declare const validateWebSocketQuery: <W extends WebSocketDef>(def: W, query: unknown) => InferWebSocketQuery<W>;
|
|
6
|
+
export declare const validateWebSocketHeaders: <W extends WebSocketDef>(def: W, headers: Record<string, unknown>) => InferWebSocketHeaders<W>;
|
|
7
|
+
//# sourceMappingURL=websocket-validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-validate.d.ts","sourceRoot":"","sources":["../../src/lib/websocket-validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAEnB,MAAM,mBAAmB,CAAC;AAoB3B,eAAO,MAAM,qBAAqB,GAChC,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,MAAM,CAAC,CAAC,gBAAgB,CAAC,GAAG,MAAM,EAE5C,KAAK,CAAC,EACN,WAAW,CAAC,EACZ,MAAM,OAAO,KACZ,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAYzB,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,MAAM,CAAC,CAAC,gBAAgB,CAAC,GAAG,MAAM,EAE5C,KAAK,CAAC,EACN,WAAW,CAAC,EACZ,MAAM,OAAO,KACZ,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAYzB,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,CAAC,SAAS,YAAY,EAChE,KAAK,CAAC,EACN,QAAQ,OAAO,KACd,wBAAwB,CAAC,CAAC,CAS5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,YAAY,EAC3D,KAAK,CAAC,EACN,OAAO,OAAO,KACb,mBAAmB,CAAC,CAAC,CASvB,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,CAAC,SAAS,YAAY,EAC7D,KAAK,CAAC,EACN,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,qBAAqB,CAAC,CAAC,CAazB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a value against a Standard Schema, throwing on failure
|
|
3
|
+
*/
|
|
4
|
+
const validateSchema = (schema, value, label) => {
|
|
5
|
+
const result = schema['~standard'].validate(value);
|
|
6
|
+
if ('issues' in result && result.issues) {
|
|
7
|
+
const messages = result.issues.map((i) => i.message).join(', ');
|
|
8
|
+
throw new Error(`Validation failed for ${label}: ${messages}`);
|
|
9
|
+
}
|
|
10
|
+
return result.value;
|
|
11
|
+
};
|
|
12
|
+
export const validateClientMessage = (def, eventName, data) => {
|
|
13
|
+
const schema = def.clientMessages[eventName];
|
|
14
|
+
if (!schema) {
|
|
15
|
+
throw new Error(`WebSocket "${def.path}" has no client message schema for event "${eventName}"`);
|
|
16
|
+
}
|
|
17
|
+
return validateSchema(schema, data, `client message "${eventName}" of ${def.path}`);
|
|
18
|
+
};
|
|
19
|
+
export const validateServerMessage = (def, eventName, data) => {
|
|
20
|
+
const schema = def.serverMessages[eventName];
|
|
21
|
+
if (!schema) {
|
|
22
|
+
throw new Error(`WebSocket "${def.path}" has no server message schema for event "${eventName}"`);
|
|
23
|
+
}
|
|
24
|
+
return validateSchema(schema, data, `server message "${eventName}" of ${def.path}`);
|
|
25
|
+
};
|
|
26
|
+
export const validateWebSocketPathParams = (def, params) => {
|
|
27
|
+
if (!def.pathParams) {
|
|
28
|
+
throw new Error(`WebSocket "${def.path}" has no pathParams schema`);
|
|
29
|
+
}
|
|
30
|
+
return validateSchema(def.pathParams, params, `pathParams of ${def.path}`);
|
|
31
|
+
};
|
|
32
|
+
export const validateWebSocketQuery = (def, query) => {
|
|
33
|
+
if (!def.query) {
|
|
34
|
+
throw new Error(`WebSocket "${def.path}" has no query schema`);
|
|
35
|
+
}
|
|
36
|
+
return validateSchema(def.query, query, `query of ${def.path}`);
|
|
37
|
+
};
|
|
38
|
+
export const validateWebSocketHeaders = (def, headers) => {
|
|
39
|
+
if (!def.headers) {
|
|
40
|
+
throw new Error(`WebSocket "${def.path}" has no headers schema`);
|
|
41
|
+
}
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [key, schema] of Object.entries(def.headers)) {
|
|
44
|
+
result[key] = validateSchema(schema, headers[key], `header "${key}" of ${def.path}`);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-contract/plugins",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
4
|
"description": "Built-in plugins for path building and schema validation for ts-contract",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"tslib": "^2.3.0",
|
|
43
|
-
"@ts-contract/core": "1.0.0-alpha.
|
|
43
|
+
"@ts-contract/core": "1.0.0-alpha.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -b tsconfig.lib.json",
|