@ts-contract/plugins 1.0.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Brimmer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # utils
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build utils` to build the library.
@@ -0,0 +1,5 @@
1
+ export * from './lib/path';
2
+ export * from './lib/validate';
3
+ export * from './lib/plugins/path';
4
+ export * from './lib/plugins/validate';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './lib/path';
2
+ export * from './lib/validate';
3
+ export * from './lib/plugins/path';
4
+ export * from './lib/plugins/validate';
@@ -0,0 +1,5 @@
1
+ import type { RouteDef, InferPathParams, InferQuery } from '@ts-contract/core';
2
+ type BuildPathArgs<R extends RouteDef> = InferPathParams<R> extends undefined ? InferQuery<R> extends undefined ? [] : [params?: undefined, query?: InferQuery<R>] : InferQuery<R> extends undefined ? [params: InferPathParams<R>] : [params: InferPathParams<R>, query?: InferQuery<R>];
3
+ export declare const buildPath: <R extends RouteDef>(route: R, ...args: BuildPathArgs<R>) => string;
4
+ export {};
5
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/lib/path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/E,KAAK,aAAa,CAAC,CAAC,SAAS,QAAQ,IACnC,eAAe,CAAC,CAAC,CAAC,SAAS,SAAS,GAChC,UAAU,CAAC,CAAC,CAAC,SAAS,SAAS,GAC7B,EAAE,GACF,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAC7C,UAAU,CAAC,CAAC,CAAC,SAAS,SAAS,GAC7B,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,GAC5B,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5D,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,QAAQ,EAC1C,OAAO,CAAC,EACR,GAAG,MAAM,aAAa,CAAC,CAAC,CAAC,KACxB,MA+BF,CAAC"}
@@ -0,0 +1,25 @@
1
+ export const buildPath = (route, ...args) => {
2
+ const [params, query] = args;
3
+ let path = route.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,12 @@
1
+ import type { RouteDef, InferPathParams, InferQuery, ContractPlugin } from '@ts-contract/core';
2
+ type BuildPathArgs<R extends RouteDef> = InferPathParams<R> extends undefined ? InferQuery<R> extends undefined ? [] : [params?: undefined, query?: InferQuery<R>] : InferQuery<R> extends undefined ? [params: InferPathParams<R>] : [params: InferPathParams<R>, query?: InferQuery<R>];
3
+ declare module '@ts-contract/core' {
4
+ interface PluginTypeRegistry<R> {
5
+ path: {
6
+ buildPath: R extends RouteDef ? (...args: BuildPathArgs<R>) => string : never;
7
+ };
8
+ }
9
+ }
10
+ export declare const pathPlugin: ContractPlugin<'path'>;
11
+ export {};
12
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/lib/plugins/path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,UAAU,EACV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAG3B,KAAK,aAAa,CAAC,CAAC,SAAS,QAAQ,IACnC,eAAe,CAAC,CAAC,CAAC,SAAS,SAAS,GAChC,UAAU,CAAC,CAAC,CAAC,SAAS,SAAS,GAC7B,EAAE,GACF,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAC7C,UAAU,CAAC,CAAC,CAAC,SAAS,SAAS,GAC7B,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,GAC5B,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5D,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAU,kBAAkB,CAAC,CAAC;QAC5B,IAAI,EAAE;YACJ,SAAS,EAAE,CAAC,SAAS,QAAQ,GACzB,CAAC,GAAG,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,MAAM,GACrC,KAAK,CAAC;SACX,CAAC;KACH;CACF;AAED,eAAO,MAAM,UAAU,EAAE,cAAc,CAAC,MAAM,CAS7C,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { buildPath } from '../path';
2
+ export const pathPlugin = {
3
+ name: 'path',
4
+ route: (route) => ({
5
+ buildPath: (...args) => buildPath(route, ...args),
6
+ }),
7
+ };
@@ -0,0 +1,14 @@
1
+ import type { RouteDef, InferPathParams, InferQuery, InferBody, InferHeaders, InferResponseBody, HttpStatusCodes, ContractPlugin } from '@ts-contract/core';
2
+ declare module '@ts-contract/core' {
3
+ interface PluginTypeRegistry<R> {
4
+ validate: {
5
+ validatePathParams: R extends RouteDef ? (params: unknown) => InferPathParams<R> : never;
6
+ validateQuery: R extends RouteDef ? (query: unknown) => InferQuery<R> : never;
7
+ validateBody: R extends RouteDef ? (body: unknown) => InferBody<R> : never;
8
+ validateResponse: R extends RouteDef ? <S extends keyof R['responses'] & HttpStatusCodes>(status: S, data: unknown) => InferResponseBody<R, S> : never;
9
+ validateHeaders: R extends RouteDef ? (headers: Record<string, unknown>) => InferHeaders<R> : never;
10
+ };
11
+ }
12
+ }
13
+ export declare const validatePlugin: ContractPlugin<'validate'>;
14
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/lib/plugins/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,cAAc,EACf,MAAM,mBAAmB,CAAC;AAS3B,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAU,kBAAkB,CAAC,CAAC;QAC5B,QAAQ,EAAE;YACR,kBAAkB,EAAE,CAAC,SAAS,QAAQ,GAClC,CAAC,MAAM,EAAE,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,GACvC,KAAK,CAAC;YACV,aAAa,EAAE,CAAC,SAAS,QAAQ,GAC7B,CAAC,KAAK,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,GACjC,KAAK,CAAC;YACV,YAAY,EAAE,CAAC,SAAS,QAAQ,GAC5B,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAC/B,KAAK,CAAC;YACV,gBAAgB,EAAE,CAAC,SAAS,QAAQ,GAChC,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,eAAe,EAC/C,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,OAAO,KACV,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,KAAK,CAAC;YACV,eAAe,EAAE,CAAC,SAAS,QAAQ,GAC/B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,GACrD,KAAK,CAAC;SACX,CAAC;KACH;CACF;AAED,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,UAAU,CAWrD,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { validatePathParams, validateQuery, validateBody, validateResponse, validateHeaders, } from '../validate';
2
+ export const validatePlugin = {
3
+ name: 'validate',
4
+ route: (route) => ({
5
+ validatePathParams: (params) => validatePathParams(route, params),
6
+ validateQuery: (query) => validateQuery(route, query),
7
+ validateBody: (body) => validateBody(route, body),
8
+ validateResponse: (status, data) => validateResponse(route, status, data),
9
+ validateHeaders: (headers) => validateHeaders(route, headers),
10
+ }),
11
+ };
@@ -0,0 +1,7 @@
1
+ import type { RouteDef, InferPathParams, InferQuery, InferBody, InferHeaders, InferResponseBody, HttpStatusCodes } from '@ts-contract/core';
2
+ export declare const validatePathParams: <R extends RouteDef>(route: R, params: unknown) => InferPathParams<R>;
3
+ export declare const validateQuery: <R extends RouteDef>(route: R, query: unknown) => InferQuery<R>;
4
+ export declare const validateBody: <R extends RouteDef>(route: R, body: unknown) => InferBody<R>;
5
+ export declare const validateResponse: <R extends RouteDef, S extends keyof R["responses"] & HttpStatusCodes>(route: R, status: S, data: unknown) => InferResponseBody<R, S>;
6
+ export declare const validateHeaders: <R extends RouteDef>(route: R, headers: Record<string, unknown>) => InferHeaders<R>;
7
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/lib/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAoB3B,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,QAAQ,EACnD,OAAO,CAAC,EACR,QAAQ,OAAO,KACd,eAAe,CAAC,CAAC,CASnB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,QAAQ,EAC9C,OAAO,CAAC,EACR,OAAO,OAAO,KACb,UAAU,CAAC,CAAC,CASd,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,QAAQ,EAC7C,OAAO,CAAC,EACR,MAAM,OAAO,KACZ,SAAS,CAAC,CAAC,CASb,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,CAAC,SAAS,QAAQ,EAClB,CAAC,SAAS,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,eAAe,EAEhD,OAAO,CAAC,EACR,QAAQ,CAAC,EACT,MAAM,OAAO,KACZ,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAYxB,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,QAAQ,EAChD,OAAO,CAAC,EACR,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,YAAY,CAAC,CAAC,CAahB,CAAC"}
@@ -0,0 +1,46 @@
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 validatePathParams = (route, params) => {
13
+ if (!route.pathParams) {
14
+ throw new Error(`Route "${route.path}" has no pathParams schema`);
15
+ }
16
+ return validateSchema(route.pathParams, params, `pathParams of ${route.path}`);
17
+ };
18
+ export const validateQuery = (route, query) => {
19
+ if (!route.query) {
20
+ throw new Error(`Route "${route.path}" has no query schema`);
21
+ }
22
+ return validateSchema(route.query, query, `query of ${route.path}`);
23
+ };
24
+ export const validateBody = (route, body) => {
25
+ if (!route.body) {
26
+ throw new Error(`Route "${route.path}" has no body schema`);
27
+ }
28
+ return validateSchema(route.body, body, `body of ${route.path}`);
29
+ };
30
+ export const validateResponse = (route, status, data) => {
31
+ const schema = route.responses[status];
32
+ if (!schema) {
33
+ throw new Error(`Route "${route.path}" has no response schema for status ${String(status)}`);
34
+ }
35
+ return validateSchema(schema, data, `response ${String(status)} of ${route.path}`);
36
+ };
37
+ export const validateHeaders = (route, headers) => {
38
+ if (!route.headers) {
39
+ throw new Error(`Route "${route.path}" has no headers schema`);
40
+ }
41
+ const result = {};
42
+ for (const [key, schema] of Object.entries(route.headers)) {
43
+ result[key] = validateSchema(schema, headers[key], `header "${key}" of ${route.path}`);
44
+ }
45
+ return result;
46
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ts-contract/plugins",
3
+ "version": "1.0.0-alpha.0",
4
+ "description": "Built-in plugins for path building and schema validation for ts-contract",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ "./package.json": "./package.json",
11
+ ".": {
12
+ "@ts-contract/source": "./src/index.ts",
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "!**/*.tsbuildinfo"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/mbrimmer83/ts-contract.git",
25
+ "directory": "packages/plugins"
26
+ },
27
+ "homepage": "https://github.com/mbrimmer83/ts-contract#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/mbrimmer83/ts-contract/issues"
30
+ },
31
+ "keywords": [
32
+ "typescript",
33
+ "contract",
34
+ "plugins",
35
+ "validation",
36
+ "path-building"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "tslib": "^2.3.0",
43
+ "@ts-contract/core": "1.0.0-alpha.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -b tsconfig.lib.json",
47
+ "test": "vitest run --config vitest.config.mts",
48
+ "lint": "eslint ."
49
+ }
50
+ }