@sapporta/rest-open-api 3.52.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/.babelrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "presets": [
3
+ [
4
+ "@nrwl/js/babel",
5
+ {
6
+ "useBuiltIns": "usage"
7
+ }
8
+ ]
9
+ ]
10
+ }
package/.eslintrc.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": ["../../../.eslintrc.json"],
3
+ "ignorePatterns": ["!**/*"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7
+ "rules": {
8
+ "@typescript-eslint/no-unused-vars": ["warn"],
9
+ "@typescript-eslint/no-explicit-any": ["warn"]
10
+ }
11
+ },
12
+ {
13
+ "files": ["*.ts", "*.tsx"],
14
+ "rules": {}
15
+ },
16
+ {
17
+ "files": ["*.js", "*.jsx"],
18
+ "rules": {}
19
+ }
20
+ ]
21
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @sapporta/rest-open-api
2
+
3
+ Initial Sapporta-maintained fork of `@ts-rest/open-api` from the upstream v4 branch.
4
+
5
+ This package removes the Anatine OpenAPI dependency and supports caller-provided Zod 4 schema transformers.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @sapporta/rest-open-api
2
+
3
+ Sapporta-maintained OpenAPI generator forked from `@ts-rest/open-api` v4.
4
+
5
+ This package supports Sapporta's Zod 4 contract usage and requires callers to provide a schema transformer. It does not depend on `@anatine/zod-openapi`.
6
+
7
+ Build with:
8
+
9
+ ```bash
10
+ pnpm nx build sapporta-rest-open-api
11
+ ```
12
+
13
+ Test with:
14
+
15
+ ```bash
16
+ pnpm nx test sapporta-rest-open-api
17
+ ```
package/jest.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ /* eslint-disable */
2
+ export default {
3
+ displayName: 'sapporta-rest-open-api',
4
+ preset: '../../../jest.preset.js',
5
+ globals: {},
6
+ transform: {
7
+ '^.+\\.[tj]s$': [
8
+ 'ts-jest',
9
+ {
10
+ tsconfig: '<rootDir>/tsconfig.spec.json',
11
+ },
12
+ ],
13
+ },
14
+ moduleFileExtensions: ['ts', 'js', 'html'],
15
+ testEnvironment: 'node',
16
+ };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@sapporta/rest-open-api",
3
+ "version": "3.52.1",
4
+ "description": "Sapporta-maintained ts-rest OpenAPI generator for Zod 4 contracts",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/sapporta/sapporta-rest.git",
8
+ "directory": "libs/ts-rest/open-api"
9
+ },
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "@sapporta/rest-core": "workspace:*",
13
+ "openapi3-ts": "^2.0.2"
14
+ },
15
+ "peerDependencies": {
16
+ "zod": "^4.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "zod": "^4.0.0"
20
+ }
21
+ }
package/project.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "sapporta-rest-open-api",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/ts-rest/open-api/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "build": {
8
+ "executor": "@nx/rollup:rollup",
9
+ "outputs": ["{options.outputPath}"],
10
+ "options": {
11
+ "project": "libs/ts-rest/open-api/package.json",
12
+ "outputPath": "dist/libs/ts-rest/open-api",
13
+ "main": "libs/ts-rest/open-api/src/index.ts",
14
+ "tsConfig": "libs/ts-rest/open-api/tsconfig.lib.json",
15
+ "assets": [
16
+ {
17
+ "glob": "CHANGELOG.md",
18
+ "input": "libs/ts-rest/open-api",
19
+ "output": "."
20
+ },
21
+ {
22
+ "glob": "README.md",
23
+ "input": ".",
24
+ "output": "."
25
+ }
26
+ ],
27
+ "format": ["esm", "cjs"],
28
+ "compiler": "tsc",
29
+ "rollupConfig": "tools/scripts/rollup.config.js",
30
+ "generateExportsField": true,
31
+ "updateBuildableProjectDepsInPackageJson": true
32
+ }
33
+ },
34
+ "lint": {
35
+ "executor": "@nx/eslint:lint",
36
+ "outputs": ["{options.outputFile}"],
37
+ "options": {
38
+ "lintFilePatterns": ["libs/ts-rest/open-api/**/*.ts"],
39
+ "ignorePath": ".gitignore"
40
+ }
41
+ },
42
+ "test": {
43
+ "executor": "@nx/jest:jest",
44
+ "outputs": ["{workspaceRoot}/coverage/libs/ts-rest/open-api"],
45
+ "options": {
46
+ "jestConfig": "libs/ts-rest/open-api/jest.config.ts"
47
+ }
48
+ }
49
+ },
50
+ "tags": []
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './lib/ts-rest-open-api';
2
+ export {
3
+ SchemaTransformer,
4
+ SchemaTransformerAsync,
5
+ SchemaTransformerSync,
6
+ } from './lib/types';
@@ -0,0 +1,217 @@
1
+ import { AppRouter, isAppRoute, isAppRouteOtherResponse } from '@sapporta/rest-core';
2
+ import {
3
+ AsyncAndSyncHelper,
4
+ GetAsyncFunction,
5
+ GetSyncFunction,
6
+ PathSchemaResults,
7
+ RouterPath,
8
+ SchemaTransformerAsync,
9
+ SchemaTransformerSync,
10
+ } from './types';
11
+ import { getPathParameterSchema } from './parsers/path-params';
12
+ import { getHeaderParameterSchema } from './parsers/headers';
13
+ import { getQueryParameterSchema } from './parsers/query-params';
14
+ import { getBodySchema } from './parsers/body';
15
+ import { SchemaObject } from 'openapi3-ts';
16
+
17
+ type PerformContractTraversalHelper = AsyncAndSyncHelper<
18
+ {
19
+ contract: AppRouter;
20
+ jsonQuery: boolean;
21
+ },
22
+ {
23
+ transformSchema: SchemaTransformerSync;
24
+ },
25
+ {
26
+ transformSchema: SchemaTransformerAsync;
27
+ },
28
+ Array<RouterPath & { schemaResults: PathSchemaResults }>
29
+ >;
30
+
31
+ /**
32
+ * Recursively step through the router and get all the individual routes with their paths etc.
33
+ */
34
+ const getPathsFromRouter = (
35
+ router: AppRouter,
36
+ pathHistory?: string[],
37
+ ): RouterPath[] => {
38
+ const paths: RouterPath[] = [];
39
+
40
+ Object.keys(router).forEach((key) => {
41
+ const value = router[key];
42
+
43
+ if (isAppRoute(value)) {
44
+ const pathWithPathParams = value.path.replace(/:(\w+)/g, '{$1}');
45
+
46
+ paths.push({
47
+ id: key,
48
+ path: pathWithPathParams,
49
+ route: value,
50
+ paths: pathHistory ?? [],
51
+ });
52
+ } else {
53
+ paths.push(...getPathsFromRouter(value, [...(pathHistory ?? []), key]));
54
+ }
55
+ });
56
+
57
+ return paths;
58
+ };
59
+
60
+ const syncFunc: GetSyncFunction<PerformContractTraversalHelper> = ({
61
+ contract,
62
+ transformSchema,
63
+ jsonQuery,
64
+ }) => {
65
+ const paths = getPathsFromRouter(contract);
66
+
67
+ const results: Array<RouterPath & { schemaResults: PathSchemaResults }> = [];
68
+
69
+ for (const path of paths) {
70
+ const concatenatedPath = [...path.paths, path.id].join('.');
71
+
72
+ const pathParams = getPathParameterSchema.sync({
73
+ transformSchema,
74
+ appRoute: path.route,
75
+ id: path.id,
76
+ concatenatedPath,
77
+ });
78
+
79
+ const headerParams = getHeaderParameterSchema.sync({
80
+ transformSchema,
81
+ appRoute: path.route,
82
+ id: path.id,
83
+ concatenatedPath,
84
+ });
85
+
86
+ const querySchema = getQueryParameterSchema.sync({
87
+ transformSchema,
88
+ appRoute: path.route,
89
+ id: path.id,
90
+ concatenatedPath,
91
+ jsonQuery: !!jsonQuery,
92
+ });
93
+
94
+ const bodySchema = getBodySchema.sync({
95
+ transformSchema,
96
+ appRoute: path.route,
97
+ id: path.id,
98
+ concatenatedPath,
99
+ });
100
+
101
+ const responses: Record<string, SchemaObject> = {};
102
+ for (const [statusCode, _response] of Object.entries(
103
+ path.route.responses,
104
+ )) {
105
+ const schemaValidator = isAppRouteOtherResponse(_response)
106
+ ? _response.body
107
+ : _response;
108
+
109
+ const responseSchema = transformSchema({
110
+ schema: schemaValidator,
111
+ appRoute: path.route,
112
+ id: path.id,
113
+ concatenatedPath,
114
+ type: 'response',
115
+ });
116
+
117
+ if (responseSchema) {
118
+ responses[statusCode] = responseSchema;
119
+ }
120
+ }
121
+
122
+ results.push({
123
+ ...path,
124
+ schemaResults: {
125
+ path: pathParams,
126
+ headers: headerParams,
127
+ query: querySchema,
128
+ body: bodySchema,
129
+ responses,
130
+ },
131
+ });
132
+ }
133
+
134
+ return results;
135
+ };
136
+
137
+ const asyncFunc: GetAsyncFunction<PerformContractTraversalHelper> = async ({
138
+ contract,
139
+ transformSchema,
140
+ jsonQuery,
141
+ }) => {
142
+ const paths = getPathsFromRouter(contract);
143
+
144
+ const results: Array<RouterPath & { schemaResults: PathSchemaResults }> = [];
145
+
146
+ for (const path of paths) {
147
+ const concatenatedPath = [...path.paths, path.id].join('.');
148
+
149
+ const pathParams = await getPathParameterSchema.async({
150
+ transformSchema,
151
+ appRoute: path.route,
152
+ id: path.id,
153
+ concatenatedPath,
154
+ });
155
+
156
+ const headerParams = await getHeaderParameterSchema.async({
157
+ transformSchema,
158
+ appRoute: path.route,
159
+ id: path.id,
160
+ concatenatedPath,
161
+ });
162
+
163
+ const querySchema = await getQueryParameterSchema.async({
164
+ transformSchema,
165
+ appRoute: path.route,
166
+ id: path.id,
167
+ concatenatedPath,
168
+ jsonQuery: !!jsonQuery,
169
+ });
170
+
171
+ const bodySchema = await getBodySchema.async({
172
+ transformSchema,
173
+ appRoute: path.route,
174
+ id: path.id,
175
+ concatenatedPath,
176
+ });
177
+
178
+ const responses: Record<string, SchemaObject> = {};
179
+ for (const [statusCode, _response] of Object.entries(
180
+ path.route.responses,
181
+ )) {
182
+ const schemaValidator = isAppRouteOtherResponse(_response)
183
+ ? _response.body
184
+ : _response;
185
+
186
+ const responseSchema = await transformSchema({
187
+ schema: schemaValidator,
188
+ appRoute: path.route,
189
+ id: path.id,
190
+ concatenatedPath,
191
+ type: 'response',
192
+ });
193
+
194
+ if (responseSchema) {
195
+ responses[statusCode] = responseSchema;
196
+ }
197
+ }
198
+
199
+ results.push({
200
+ ...path,
201
+ schemaResults: {
202
+ path: pathParams,
203
+ headers: headerParams,
204
+ query: querySchema,
205
+ body: bodySchema,
206
+ responses,
207
+ },
208
+ });
209
+ }
210
+
211
+ return results;
212
+ };
213
+
214
+ export const performContractTraversal: PerformContractTraversalHelper = {
215
+ sync: syncFunc,
216
+ async: asyncFunc,
217
+ };
@@ -0,0 +1,71 @@
1
+ import { AppRoute } from '@sapporta/rest-core';
2
+ import {
3
+ AsyncAndSyncHelper,
4
+ GetAsyncFunction,
5
+ GetSyncFunction,
6
+ SchemaTransformerAsync,
7
+ SchemaTransformerSync,
8
+ } from '../types';
9
+ import { SchemaObject } from 'openapi3-ts';
10
+
11
+ type GetBodySchemaHelper = AsyncAndSyncHelper<
12
+ {
13
+ appRoute: AppRoute;
14
+ id: string;
15
+ concatenatedPath: string;
16
+ },
17
+ {
18
+ transformSchema: SchemaTransformerSync;
19
+ },
20
+ {
21
+ transformSchema: SchemaTransformerAsync;
22
+ },
23
+ SchemaObject | null
24
+ >;
25
+
26
+ const syncFunc: GetSyncFunction<GetBodySchemaHelper> = ({
27
+ transformSchema,
28
+ appRoute,
29
+ id,
30
+ concatenatedPath,
31
+ }) => {
32
+ const schema = 'body' in appRoute ? appRoute.body : undefined;
33
+
34
+ const transformedSchema = transformSchema({
35
+ schema,
36
+ appRoute,
37
+ id,
38
+ concatenatedPath,
39
+ type: 'body',
40
+ });
41
+
42
+ if (!transformedSchema) {
43
+ return null;
44
+ }
45
+
46
+ return transformedSchema;
47
+ };
48
+
49
+ const asyncFunc: GetAsyncFunction<GetBodySchemaHelper> = async ({
50
+ transformSchema,
51
+ appRoute,
52
+ id,
53
+ concatenatedPath,
54
+ }) => {
55
+ const schema = 'body' in appRoute ? appRoute.body : undefined;
56
+
57
+ const transformedSchema = await transformSchema({
58
+ schema,
59
+ appRoute,
60
+ id,
61
+ concatenatedPath,
62
+ type: 'body',
63
+ });
64
+
65
+ return transformedSchema;
66
+ };
67
+
68
+ export const getBodySchema: GetBodySchemaHelper = {
69
+ sync: syncFunc,
70
+ async: asyncFunc,
71
+ };
@@ -0,0 +1,163 @@
1
+ import { AppRoute, isStandardSchema } from '@sapporta/rest-core';
2
+ import {
3
+ AsyncAndSyncHelper,
4
+ GetAsyncFunction,
5
+ GetSyncFunction,
6
+ SchemaTransformerAsync,
7
+ SchemaTransformerSync,
8
+ } from '../types';
9
+ import { ParameterObject } from 'openapi3-ts';
10
+ import { schemaObjectToParameters, schemaToParameter } from './utils';
11
+
12
+ type GetHeaderParameterHelper = AsyncAndSyncHelper<
13
+ {
14
+ appRoute: AppRoute;
15
+ id: string;
16
+ concatenatedPath: string;
17
+ },
18
+ {
19
+ transformSchema: SchemaTransformerSync;
20
+ },
21
+ {
22
+ transformSchema: SchemaTransformerAsync;
23
+ },
24
+ Array<ParameterObject>
25
+ >;
26
+
27
+ const syncFunc: GetSyncFunction<GetHeaderParameterHelper> = ({
28
+ transformSchema,
29
+ appRoute,
30
+ id,
31
+ concatenatedPath,
32
+ }) => {
33
+ const schema = appRoute.headers;
34
+
35
+ if (schema === null) {
36
+ return [];
37
+ }
38
+
39
+ if (schema === undefined) {
40
+ return [];
41
+ }
42
+
43
+ if (typeof schema === 'symbol') {
44
+ return [];
45
+ }
46
+
47
+ if (isStandardSchema(schema)) {
48
+ const transformedSchema = transformSchema({
49
+ schema,
50
+ appRoute,
51
+ id,
52
+ concatenatedPath,
53
+ type: 'header',
54
+ });
55
+
56
+ if (!transformedSchema) {
57
+ return [];
58
+ }
59
+
60
+ return schemaObjectToParameters(transformedSchema, 'header');
61
+ }
62
+
63
+ const parameters: ParameterObject[] = [];
64
+
65
+ for (const [key, subSchema] of Object.entries(schema)) {
66
+ if (isStandardSchema(subSchema)) {
67
+ const transformedSchema = transformSchema({
68
+ schema: subSchema,
69
+ appRoute,
70
+ id,
71
+ concatenatedPath,
72
+ type: 'header',
73
+ });
74
+
75
+ if (!transformedSchema) {
76
+ return [];
77
+ }
78
+
79
+ parameters.push(...schemaObjectToParameters(transformedSchema, 'header'));
80
+ }
81
+ }
82
+
83
+ return parameters;
84
+ };
85
+
86
+ const asyncFunc: GetAsyncFunction<GetHeaderParameterHelper> = async ({
87
+ transformSchema,
88
+ appRoute,
89
+ id,
90
+ concatenatedPath,
91
+ }) => {
92
+ const schema = appRoute.headers;
93
+
94
+ if (schema === null) {
95
+ return [];
96
+ }
97
+
98
+ if (schema === undefined) {
99
+ return [];
100
+ }
101
+
102
+ if (typeof schema === 'symbol') {
103
+ return [];
104
+ }
105
+
106
+ if (isStandardSchema(schema)) {
107
+ const transformedSchema = await transformSchema({
108
+ schema,
109
+ appRoute,
110
+ id,
111
+ concatenatedPath,
112
+ type: 'header',
113
+ });
114
+
115
+ if (!transformedSchema) {
116
+ return [];
117
+ }
118
+
119
+ return schemaObjectToParameters(transformedSchema, 'header');
120
+ }
121
+
122
+ const parameters: ParameterObject[] = [];
123
+
124
+ for (const [key, subSchema] of Object.entries(schema)) {
125
+ if (isStandardSchema(subSchema)) {
126
+ const transformedSchema = await transformSchema({
127
+ schema: subSchema,
128
+ appRoute,
129
+ id,
130
+ concatenatedPath,
131
+ type: 'header',
132
+ });
133
+
134
+ if (!transformedSchema) {
135
+ continue;
136
+ }
137
+
138
+ const validateEmptyResult = subSchema['~standard'].validate(undefined);
139
+
140
+ if (validateEmptyResult instanceof Promise) {
141
+ throw new Error('Schema validation must be synchronous');
142
+ }
143
+
144
+ const isRequired = Boolean(validateEmptyResult.issues?.length);
145
+
146
+ const asParameter = schemaToParameter(
147
+ transformedSchema,
148
+ 'header',
149
+ isRequired,
150
+ key,
151
+ false,
152
+ );
153
+ parameters.push(asParameter);
154
+ }
155
+ }
156
+
157
+ return parameters;
158
+ };
159
+
160
+ export const getHeaderParameterSchema: GetHeaderParameterHelper = {
161
+ sync: syncFunc,
162
+ async: asyncFunc,
163
+ };
@@ -0,0 +1,5 @@
1
+ export { getPathParameterSchema } from './path-params';
2
+ export { getHeaderParameterSchema } from './headers';
3
+ export { getQueryParameterSchema } from './query-params';
4
+ export { getBodySchema } from './body';
5
+ export * from './utils';