@tetrascience-npm/request 0.2.0 → 0.2.1-beta.112.2
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/dist/cli/generate-client.js +0 -0
- package/dist/cli/generate-schemas.d.ts.map +1 -1
- package/dist/cli/generate-schemas.js +30 -80
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +0 -4
- package/package.json +1 -1
- package/dist/cli/init-client.d.ts +0 -3
- package/dist/cli/init-client.d.ts.map +0 -1
- package/dist/cli/init-client.js +0 -133
- package/dist/client/install-tracking.d.ts +0 -18
- package/dist/client/install-tracking.d.ts.map +0 -1
- package/dist/client/install-tracking.js +0 -88
- package/dist/client/middleware.d.ts +0 -18
- package/dist/client/middleware.d.ts.map +0 -1
- package/dist/client/middleware.js +0 -71
- package/dist/client/sanitize-url.d.ts +0 -3
- package/dist/client/sanitize-url.d.ts.map +0 -1
- package/dist/client/sanitize-url.js +0 -14
- package/dist/server/express-middleware.d.ts +0 -41
- package/dist/server/express-middleware.d.ts.map +0 -1
- package/dist/server/express-middleware.js +0 -52
- package/dist/server/middleware.d.ts +0 -27
- package/dist/server/middleware.d.ts.map +0 -1
- package/dist/server/middleware.js +0 -55
- package/dist/server/validation.d.ts +0 -40
- package/dist/server/validation.d.ts.map +0 -1
- package/dist/server/validation.js +0 -45
- package/dist/shared/auth-middleware.d.ts +0 -45
- package/dist/shared/auth-middleware.d.ts.map +0 -1
- package/dist/shared/auth-middleware.js +0 -84
- package/dist/shared/safe-response.d.ts +0 -22
- package/dist/shared/safe-response.d.ts.map +0 -1
- package/dist/shared/safe-response.js +0 -41
- package/dist/shared/validation.d.ts +0 -40
- package/dist/shared/validation.d.ts.map +0 -1
- package/dist/shared/validation.js +0 -39
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-schemas.d.ts","sourceRoot":"","sources":["../../src/cli/generate-schemas.ts"],"names":[],"mappings":";AA8DA;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5C;
|
|
1
|
+
{"version":3,"file":"generate-schemas.d.ts","sourceRoot":"","sources":["../../src/cli/generate-schemas.ts"],"names":[],"mappings":";AA8DA;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAkEnG"}
|
|
@@ -39,14 +39,14 @@ exports.generateRequestSchemas = generateRequestSchemas;
|
|
|
39
39
|
/**
|
|
40
40
|
* Generates Zod request-body schemas from an OpenAPI spec.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* Uses openapi-zod-client's programmatic API to:
|
|
43
|
+
* 1. Parse the OpenAPI spec into structured schema + endpoint data
|
|
44
|
+
* 2. Extract request body schema mappings from endpoint definitions
|
|
45
|
+
* 3. Output a validation map: "METHOD /path" -> Zod schema
|
|
45
46
|
*
|
|
46
47
|
* Usage:
|
|
47
48
|
* generate-request-schemas --spec <path-to-openapi-yaml> --out <output-dir>
|
|
48
49
|
*/
|
|
49
|
-
const child_process_1 = require("child_process");
|
|
50
50
|
const fs = __importStar(require("fs"));
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
52
|
function parseArgs() {
|
|
@@ -100,103 +100,53 @@ function findBin(name) {
|
|
|
100
100
|
console.error(`Error: ${name} not found. Install it as a devDependency: yarn add -D ${name}`);
|
|
101
101
|
process.exit(1);
|
|
102
102
|
}
|
|
103
|
-
function findZodClientBin() {
|
|
104
|
-
return findBin('openapi-zod-client');
|
|
105
|
-
}
|
|
106
103
|
function generateRequestSchemas(specPath, outDir, outFileName) {
|
|
107
|
-
const tmpFile = path.join(outDir, '.zod-raw.ts');
|
|
108
104
|
const outFile = path.join(outDir, outFileName || 'request-schemas.ts');
|
|
109
105
|
fs.mkdirSync(outDir, { recursive: true });
|
|
110
|
-
// 1.
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
let raw;
|
|
106
|
+
// 1. Load OpenAPI spec and get structured schema/endpoint data via programmatic API
|
|
107
|
+
const spec = loadYaml(specPath);
|
|
108
|
+
let getZodClientTemplateContext;
|
|
114
109
|
try {
|
|
115
|
-
|
|
110
|
+
({ getZodClientTemplateContext } = require('openapi-zod-client'));
|
|
116
111
|
}
|
|
117
112
|
catch {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// Extract schema section (everything before `const endpoints = makeApi(`)
|
|
121
|
-
const endpointsIdx = raw.indexOf('const endpoints = makeApi(');
|
|
122
|
-
if (endpointsIdx === -1) {
|
|
123
|
-
fs.unlinkSync(tmpFile);
|
|
124
|
-
throw new Error('Could not find endpoints definition in generated file');
|
|
113
|
+
console.error('Error: openapi-zod-client is required for schema generation. Install it as a devDependency: yarn add -D openapi-zod-client');
|
|
114
|
+
process.exit(1);
|
|
125
115
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.trim();
|
|
132
|
-
// 2. Parse the OpenAPI spec to find request body -> schema name mappings
|
|
133
|
-
const spec = loadYaml(specPath);
|
|
116
|
+
const ctx = getZodClientTemplateContext(spec, { shouldExportAllSchemas: true });
|
|
117
|
+
// 2. Build "METHOD /path" -> schema name map from endpoint definitions
|
|
118
|
+
// openapi-zod-client converts path params to Express format (:id),
|
|
119
|
+
// but openapi-fetch's schemaPath uses OpenAPI format ({id}).
|
|
120
|
+
const toOpenApiPath = (p) => p.replace(/:(\w+)/g, '{$1}');
|
|
134
121
|
const bodyMap = {};
|
|
135
|
-
for (const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
continue;
|
|
139
|
-
const op = operation;
|
|
140
|
-
const requestBody = op.requestBody;
|
|
141
|
-
if (!requestBody?.content?.['application/json']?.schema)
|
|
142
|
-
continue;
|
|
143
|
-
const schema = requestBody.content['application/json'].schema;
|
|
144
|
-
const key = `${method.toUpperCase()} ${pathStr}`;
|
|
145
|
-
if (schema.$ref) {
|
|
146
|
-
const schemaName = schema.$ref.split('/').pop();
|
|
147
|
-
bodyMap[key] = schemaName;
|
|
148
|
-
}
|
|
149
|
-
else if (schema.type === 'array' && schema.items?.$ref) {
|
|
150
|
-
const opId = op.operationId?.replace(/-/g, '_');
|
|
151
|
-
if (opId)
|
|
152
|
-
bodyMap[key] = `${opId}_Body`;
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
const opId = op.operationId?.replace(/-/g, '_');
|
|
156
|
-
if (opId)
|
|
157
|
-
bodyMap[key] = `${opId}_Body`;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// 3. Verify all referenced schema names exist in the generated code
|
|
162
|
-
const exportedSchemaNames = [...raw.matchAll(/^const (\w+)\s*=\s*z[.\s]/gm)].map((m) => m[1]);
|
|
163
|
-
for (const [key, schemaName] of Object.entries(bodyMap)) {
|
|
164
|
-
if (raw.includes(`const ${schemaName} =`) || raw.includes(`const ${schemaName}=`)) {
|
|
122
|
+
for (const endpoint of ctx.endpoints) {
|
|
123
|
+
const bodyParam = endpoint.parameters.find((p) => p.type === 'Body');
|
|
124
|
+
if (!bodyParam)
|
|
165
125
|
continue;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const parts = schemaName.split('_');
|
|
169
|
-
const resourceHint = parts.slice(1, -1).join('_');
|
|
170
|
-
const fallback = exportedSchemaNames.find((name) => name.endsWith('_Body') && name.includes(resourceHint));
|
|
171
|
-
if (fallback) {
|
|
172
|
-
console.log(` info: ${key}: "${schemaName}" -> "${fallback}" (shared schema)`);
|
|
173
|
-
bodyMap[key] = fallback;
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
console.warn(` warn: Schema "${schemaName}" for ${key} not found — skipping`);
|
|
177
|
-
delete bodyMap[key];
|
|
178
|
-
}
|
|
126
|
+
const key = `${endpoint.method.toUpperCase()} ${toOpenApiPath(endpoint.path)}`;
|
|
127
|
+
bodyMap[key] = bodyParam.schema;
|
|
179
128
|
}
|
|
180
|
-
//
|
|
129
|
+
// 3. Build output file — schemas are already in dependency order (topologically sorted)
|
|
181
130
|
const lines = [
|
|
182
131
|
'// AUTO-GENERATED — do not edit. Run `generate-request-schemas` to regenerate.',
|
|
183
132
|
"import { z } from 'zod'",
|
|
184
133
|
'',
|
|
185
|
-
schemaSection,
|
|
186
|
-
'',
|
|
187
|
-
'/**',
|
|
188
|
-
' * Map of "METHOD /path" -> Zod schema for request body validation.',
|
|
189
|
-
' * Used by the request validation middleware.',
|
|
190
|
-
' */',
|
|
191
|
-
'export const requestBodySchemas: Record<string, z.ZodTypeAny> = {',
|
|
192
134
|
];
|
|
135
|
+
for (const [name, code] of Object.entries(ctx.schemas)) {
|
|
136
|
+
lines.push(`const ${name} = ${code}`);
|
|
137
|
+
}
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push('/**');
|
|
140
|
+
lines.push(' * Map of "METHOD /path" -> Zod schema for request body validation.');
|
|
141
|
+
lines.push(' * Used by the request validation middleware.');
|
|
142
|
+
lines.push(' */');
|
|
143
|
+
lines.push('export const requestBodySchemas: Record<string, z.ZodTypeAny> = {');
|
|
193
144
|
for (const [key, schemaName] of Object.entries(bodyMap)) {
|
|
194
145
|
lines.push(` '${key}': ${schemaName},`);
|
|
195
146
|
}
|
|
196
147
|
lines.push('}');
|
|
197
148
|
lines.push('');
|
|
198
149
|
fs.writeFileSync(outFile, lines.join('\n'));
|
|
199
|
-
fs.unlinkSync(tmpFile);
|
|
200
150
|
console.log(`\nGenerated ${outFile}`);
|
|
201
151
|
console.log(` ${Object.keys(bodyMap).length} request body schemas mapped:`);
|
|
202
152
|
for (const [key, schemaName] of Object.entries(bodyMap)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/cli/templates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC;AAaD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,MAAM,CA4E3F;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC1B,GAAG,MAAM,CAsBT;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAKhD;AAED,wBAAgB,cAAc,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/cli/templates.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC;AAaD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,MAAM,CA4E3F;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC1B,GAAG,MAAM,CAsBT;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAKhD;AAED,wBAAgB,cAAc,IAAI,MAAM,CAGvC"}
|
package/dist/cli/templates.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init-client.d.ts","sourceRoot":"","sources":["../../src/cli/init-client.ts"],"names":[],"mappings":""}
|
package/dist/cli/init-client.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
/**
|
|
38
|
-
* @deprecated Use `generate-service-client` instead.
|
|
39
|
-
*
|
|
40
|
-
* Scaffolds a typed OpenAPI client directory for a TetraScience service.
|
|
41
|
-
*
|
|
42
|
-
* Usage:
|
|
43
|
-
* init-service-client --name @tetrascience/my-service-client --spec ../openapi/spec.yaml [--out client]
|
|
44
|
-
*/
|
|
45
|
-
const fs = __importStar(require("fs"));
|
|
46
|
-
const path = __importStar(require("path"));
|
|
47
|
-
const templates_1 = require("./templates");
|
|
48
|
-
function parseArgs() {
|
|
49
|
-
const args = process.argv.slice(2);
|
|
50
|
-
let packageName;
|
|
51
|
-
let specPath;
|
|
52
|
-
let outDir = 'client';
|
|
53
|
-
for (let i = 0; i < args.length; i++) {
|
|
54
|
-
if (args[i] === '--name' && args[i + 1]) {
|
|
55
|
-
packageName = args[++i];
|
|
56
|
-
}
|
|
57
|
-
else if (args[i] === '--spec' && args[i + 1]) {
|
|
58
|
-
specPath = args[++i];
|
|
59
|
-
}
|
|
60
|
-
else if (args[i] === '--out' && args[i + 1]) {
|
|
61
|
-
outDir = args[++i];
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (!packageName) {
|
|
65
|
-
console.error('Error: --name <@tetrascience/my-service-client> is required');
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
if (!specPath) {
|
|
69
|
-
console.error('Error: --spec <relative-path-to-openapi-yaml> is required');
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
return { packageName, specPath, outDir };
|
|
73
|
-
}
|
|
74
|
-
function generatePackageJson(packageName, specPath) {
|
|
75
|
-
const pkg = {
|
|
76
|
-
name: packageName,
|
|
77
|
-
version: '1.0.0',
|
|
78
|
-
description: `Typed client for the ${(0, templates_1.deriveServiceName)(packageName)} service API`,
|
|
79
|
-
main: 'dist/index.js',
|
|
80
|
-
types: 'dist/index.d.ts',
|
|
81
|
-
files: ['dist'],
|
|
82
|
-
scripts: {
|
|
83
|
-
generate: `openapi-typescript ${specPath} -o generated/schema.ts`,
|
|
84
|
-
'generate:schemas': `generate-request-schemas --spec ${specPath} --out generated`,
|
|
85
|
-
build: 'yarn generate && yarn generate:schemas && tsc --project tsconfig.build.json',
|
|
86
|
-
},
|
|
87
|
-
dependencies: {
|
|
88
|
-
'@tetrascience/request-middleware': '^0.2.0',
|
|
89
|
-
'openapi-fetch': '^0.17.0',
|
|
90
|
-
zod: '^3.24.0',
|
|
91
|
-
},
|
|
92
|
-
devDependencies: {
|
|
93
|
-
'@types/js-yaml': '^4.0.9',
|
|
94
|
-
'js-yaml': '^4.1.1',
|
|
95
|
-
'openapi-typescript': '^7.6.0',
|
|
96
|
-
'openapi-zod-client': '^1.18.3',
|
|
97
|
-
'ts-node': '^10.9.2',
|
|
98
|
-
typescript: '^5.7.3',
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
return JSON.stringify(pkg, null, 2) + '\n';
|
|
102
|
-
}
|
|
103
|
-
function main() {
|
|
104
|
-
console.warn('WARNING: init-service-client is deprecated. Use generate-service-client instead.\n');
|
|
105
|
-
const { packageName, specPath, outDir } = parseArgs();
|
|
106
|
-
const resolvedOut = path.resolve(outDir);
|
|
107
|
-
const serviceName = (0, templates_1.deriveServiceName)(packageName);
|
|
108
|
-
if (fs.existsSync(resolvedOut) && fs.readdirSync(resolvedOut).length > 0) {
|
|
109
|
-
console.error(`Error: output directory "${outDir}" already exists and is not empty`);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
fs.mkdirSync(resolvedOut, { recursive: true });
|
|
113
|
-
fs.mkdirSync(path.join(resolvedOut, 'generated'), { recursive: true });
|
|
114
|
-
const files = {
|
|
115
|
-
'package.json': generatePackageJson(packageName, specPath),
|
|
116
|
-
'tsconfig.build.json': (0, templates_1.generateTsConfig)(),
|
|
117
|
-
'index.ts': (0, templates_1.generateIndexTs)(serviceName),
|
|
118
|
-
'.gitignore': (0, templates_1.generateClientGitIgnore)(),
|
|
119
|
-
};
|
|
120
|
-
for (const [name, content] of Object.entries(files)) {
|
|
121
|
-
const filePath = path.join(resolvedOut, name);
|
|
122
|
-
fs.writeFileSync(filePath, content);
|
|
123
|
-
console.log(` created ${path.relative(process.cwd(), filePath)}`);
|
|
124
|
-
}
|
|
125
|
-
console.log(`\nScaffolded ${packageName} in ${outDir}/`);
|
|
126
|
-
console.log(`\nNext steps:`);
|
|
127
|
-
console.log(` cd ${outDir}`);
|
|
128
|
-
console.log(` yarn install`);
|
|
129
|
-
console.log(` yarn build`);
|
|
130
|
-
}
|
|
131
|
-
if (require.main === module) {
|
|
132
|
-
main();
|
|
133
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ClientTrackingOptions } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Install request tracking on the global `fetch` function.
|
|
4
|
-
*
|
|
5
|
-
* Every `fetch()` call will automatically get a `ts-request-id` header
|
|
6
|
-
* and optional logging. Returns an uninstall function to restore the
|
|
7
|
-
* original `fetch`.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import {installRequestMiddleware, createConsoleLogger} from '@tetrascience/request-middleware/client';
|
|
12
|
-
*
|
|
13
|
-
* installRequestMiddleware({logger: createConsoleLogger({prefix: 'my-app'})});
|
|
14
|
-
* // All fetch() calls now include ts-request-id
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
export declare function installRequestMiddleware(options?: ClientTrackingOptions): () => void;
|
|
18
|
-
//# sourceMappingURL=install-tracking.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"install-tracking.d.ts","sourceRoot":"","sources":["../../src/client/install-tracking.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,SAAS,CAAC;AA8BnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAoDpF"}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.installRequestMiddleware = installRequestMiddleware;
|
|
4
|
-
const constants_1 = require("../shared/constants");
|
|
5
|
-
const generate_request_id_1 = require("../shared/generate-request-id");
|
|
6
|
-
function sanitizeUrl(url) {
|
|
7
|
-
try {
|
|
8
|
-
return new URL(url).pathname;
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return url.split(/[?#]/)[0];
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
const SESSION_COOKIE_MAX_AGE = 30 * 60; // 30 minutes, refreshed on each request
|
|
15
|
-
/**
|
|
16
|
-
* Get or create a session ID from cookies.
|
|
17
|
-
* Creates a new UUID and stores it as a cookie if not already present.
|
|
18
|
-
* Refreshes the cookie expiry on each call (sliding window).
|
|
19
|
-
*/
|
|
20
|
-
function getOrCreateSessionId() {
|
|
21
|
-
const match = document.cookie.match(new RegExp(`(?:^|; )${constants_1.SESSION_ID_HEADER}=([^;]*)`));
|
|
22
|
-
if (match) {
|
|
23
|
-
// Refresh expiry
|
|
24
|
-
document.cookie = `${constants_1.SESSION_ID_HEADER}=${match[1]}; path=/; max-age=${SESSION_COOKIE_MAX_AGE}; SameSite=Lax`;
|
|
25
|
-
return match[1];
|
|
26
|
-
}
|
|
27
|
-
const sessionId = (0, generate_request_id_1.generateRequestId)();
|
|
28
|
-
document.cookie = `${constants_1.SESSION_ID_HEADER}=${sessionId}; path=/; max-age=${SESSION_COOKIE_MAX_AGE}; SameSite=Lax`;
|
|
29
|
-
return sessionId;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Install request tracking on the global `fetch` function.
|
|
33
|
-
*
|
|
34
|
-
* Every `fetch()` call will automatically get a `ts-request-id` header
|
|
35
|
-
* and optional logging. Returns an uninstall function to restore the
|
|
36
|
-
* original `fetch`.
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```ts
|
|
40
|
-
* import {installRequestMiddleware, createConsoleLogger} from '@tetrascience/request-middleware/client';
|
|
41
|
-
*
|
|
42
|
-
* installRequestMiddleware({logger: createConsoleLogger({prefix: 'my-app'})});
|
|
43
|
-
* // All fetch() calls now include ts-request-id
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
function installRequestMiddleware(options) {
|
|
47
|
-
const { logger } = options ?? {};
|
|
48
|
-
const originalFetch = globalThis.fetch;
|
|
49
|
-
globalThis.fetch = (input, init) => {
|
|
50
|
-
// Merge headers: start from the Request's headers (if input is a Request),
|
|
51
|
-
// then layer on init?.headers, then add tracking headers.
|
|
52
|
-
const inputHeaders = input instanceof Request ? input.headers : undefined;
|
|
53
|
-
const headers = new Headers(inputHeaders);
|
|
54
|
-
if (init?.headers) {
|
|
55
|
-
new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
56
|
-
}
|
|
57
|
-
if (!headers.has(constants_1.REQUEST_ID_HEADER)) {
|
|
58
|
-
headers.set(constants_1.REQUEST_ID_HEADER, (0, generate_request_id_1.generateRequestId)());
|
|
59
|
-
}
|
|
60
|
-
const requestId = headers.get(constants_1.REQUEST_ID_HEADER);
|
|
61
|
-
if (!headers.has(constants_1.SESSION_ID_HEADER) && typeof document !== 'undefined') {
|
|
62
|
-
headers.set(constants_1.SESSION_ID_HEADER, getOrCreateSessionId());
|
|
63
|
-
}
|
|
64
|
-
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
65
|
-
logger?.debug('Outgoing request', {
|
|
66
|
-
requestId,
|
|
67
|
-
path: sanitizeUrl(url),
|
|
68
|
-
});
|
|
69
|
-
return originalFetch(input, { ...init, headers }).then((response) => {
|
|
70
|
-
logger?.debug('Response received', {
|
|
71
|
-
requestId,
|
|
72
|
-
path: sanitizeUrl(url),
|
|
73
|
-
status: response.status,
|
|
74
|
-
});
|
|
75
|
-
return response;
|
|
76
|
-
}, (error) => {
|
|
77
|
-
logger?.error('Request failed', {
|
|
78
|
-
requestId,
|
|
79
|
-
path: sanitizeUrl(url),
|
|
80
|
-
error: error instanceof Error ? error.message : String(error),
|
|
81
|
-
});
|
|
82
|
-
throw error;
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
return () => {
|
|
86
|
-
globalThis.fetch = originalFetch;
|
|
87
|
-
};
|
|
88
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from 'openapi-fetch';
|
|
2
|
-
import type { ClientTrackingOptions } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* Create request tracking middleware for openapi-fetch clients (browser).
|
|
5
|
-
*
|
|
6
|
-
* Composes tracing (request ID injection) with request/response logging.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* import createClient from 'openapi-fetch';
|
|
11
|
-
* import {createRequestTrackingMiddleware} from '@tetrascience/request-middleware/client';
|
|
12
|
-
*
|
|
13
|
-
* const client = createClient<paths>({baseUrl: '/api'});
|
|
14
|
-
* client.use(createRequestTrackingMiddleware());
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
export declare function createRequestTrackingMiddleware(options?: ClientTrackingOptions): Middleware;
|
|
18
|
-
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/client/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAK9C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAWnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,UAAU,CAkD3F"}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRequestTrackingMiddleware = createRequestTrackingMiddleware;
|
|
4
|
-
const constants_1 = require("../shared/constants");
|
|
5
|
-
const tracing_1 = require("../shared/middleware/tracing");
|
|
6
|
-
const sanitize_url_1 = require("./sanitize-url");
|
|
7
|
-
/**
|
|
8
|
-
* Read ts-session-id from cookies if available (browser only).
|
|
9
|
-
*/
|
|
10
|
-
function getSessionIdFromCookie() {
|
|
11
|
-
if (typeof document === 'undefined')
|
|
12
|
-
return undefined;
|
|
13
|
-
const match = document.cookie.match(new RegExp(`(?:^|; )${constants_1.SESSION_ID_HEADER}=([^;]*)`));
|
|
14
|
-
return match?.[1];
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Create request tracking middleware for openapi-fetch clients (browser).
|
|
18
|
-
*
|
|
19
|
-
* Composes tracing (request ID injection) with request/response logging.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* import createClient from 'openapi-fetch';
|
|
24
|
-
* import {createRequestTrackingMiddleware} from '@tetrascience/request-middleware/client';
|
|
25
|
-
*
|
|
26
|
-
* const client = createClient<paths>({baseUrl: '/api'});
|
|
27
|
-
* client.use(createRequestTrackingMiddleware());
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
function createRequestTrackingMiddleware(options) {
|
|
31
|
-
const { logger } = options ?? {};
|
|
32
|
-
const tracingMiddleware = (0, tracing_1.createTracingMiddleware)({
|
|
33
|
-
requestId: options?.requestId,
|
|
34
|
-
sessionId: options?.sessionId ?? getSessionIdFromCookie,
|
|
35
|
-
});
|
|
36
|
-
return {
|
|
37
|
-
onRequest(params) {
|
|
38
|
-
// Delegate header injection to shared tracing middleware
|
|
39
|
-
tracingMiddleware.onRequest(params);
|
|
40
|
-
const { request, schemaPath } = params;
|
|
41
|
-
const requestId = request.headers.get(constants_1.REQUEST_ID_HEADER);
|
|
42
|
-
logger?.debug('Outgoing request', {
|
|
43
|
-
requestId,
|
|
44
|
-
method: request.method,
|
|
45
|
-
path: (0, sanitize_url_1.sanitizeUrl)(request.url),
|
|
46
|
-
schemaPath,
|
|
47
|
-
});
|
|
48
|
-
return request;
|
|
49
|
-
},
|
|
50
|
-
onResponse({ request, response, schemaPath }) {
|
|
51
|
-
const requestId = request.headers.get(constants_1.REQUEST_ID_HEADER);
|
|
52
|
-
logger?.debug('Response received', {
|
|
53
|
-
requestId,
|
|
54
|
-
method: request.method,
|
|
55
|
-
path: (0, sanitize_url_1.sanitizeUrl)(request.url),
|
|
56
|
-
schemaPath,
|
|
57
|
-
status: response.status,
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
onError({ request, error, schemaPath }) {
|
|
61
|
-
const requestId = request.headers.get(constants_1.REQUEST_ID_HEADER);
|
|
62
|
-
logger?.error('Request failed', {
|
|
63
|
-
requestId,
|
|
64
|
-
method: request.method,
|
|
65
|
-
path: (0, sanitize_url_1.sanitizeUrl)(request.url),
|
|
66
|
-
schemaPath,
|
|
67
|
-
error: error instanceof Error ? error.message : String(error),
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize-url.d.ts","sourceRoot":"","sources":["../../src/client/sanitize-url.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ/C"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sanitizeUrl = sanitizeUrl;
|
|
4
|
-
/** Extract pathname from a URL, stripping query string and hash. */
|
|
5
|
-
function sanitizeUrl(url) {
|
|
6
|
-
try {
|
|
7
|
-
// Try absolute URL first
|
|
8
|
-
return new URL(url).pathname;
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
// Relative URL — strip query string and hash manually
|
|
12
|
-
return url.split(/[?#]/)[0];
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Express-compatible types so we don't depend on @types/express.
|
|
3
|
-
*/
|
|
4
|
-
interface IncomingRequest {
|
|
5
|
-
headers: Record<string, string | string[] | undefined>;
|
|
6
|
-
cookies?: Record<string, string>;
|
|
7
|
-
}
|
|
8
|
-
interface ServerResponse {
|
|
9
|
-
setHeader?(name: string, value: string): void;
|
|
10
|
-
}
|
|
11
|
-
type NextFunction = () => void;
|
|
12
|
-
type ExpressMiddleware = (req: IncomingRequest, res: ServerResponse, next: NextFunction) => void;
|
|
13
|
-
/**
|
|
14
|
-
* Express middleware that sets up request context for tracing and auth.
|
|
15
|
-
*
|
|
16
|
-
* Reads from incoming headers and cookies:
|
|
17
|
-
* - `ts-request-id` — generates UUID if missing
|
|
18
|
-
* - `ts-session-id` — generates UUID if missing, sets cookie for persistence
|
|
19
|
-
* - `x-org-slug` — from header or cookie
|
|
20
|
-
* - `ts-auth-token` — from header or cookie
|
|
21
|
-
*
|
|
22
|
-
* All downstream code can access the context via `getRequestContext()`.
|
|
23
|
-
* Generated clients auto-resolve auth and tracing from this context.
|
|
24
|
-
*
|
|
25
|
-
* Requires `cookie-parser` middleware to run before this middleware
|
|
26
|
-
* so that `req.cookies` is populated.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```ts
|
|
30
|
-
* import express from 'express';
|
|
31
|
-
* import cookieParser from 'cookie-parser';
|
|
32
|
-
* import { createRequestMiddleware } from '@tetrascience/request-middleware/server';
|
|
33
|
-
*
|
|
34
|
-
* const app = express();
|
|
35
|
-
* app.use(cookieParser());
|
|
36
|
-
* app.use(createRequestMiddleware());
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare function createRequestMiddleware(): ExpressMiddleware;
|
|
40
|
-
export {};
|
|
41
|
-
//# sourceMappingURL=express-middleware.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"express-middleware.d.ts","sourceRoot":"","sources":["../../src/server/express-middleware.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,UAAU,eAAe;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AACD,UAAU,cAAc;IACvB,SAAS,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AACD,KAAK,YAAY,GAAG,MAAM,IAAI,CAAC;AAC/B,KAAK,iBAAiB,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;AAIjG;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAwB3D"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRequestMiddleware = createRequestMiddleware;
|
|
4
|
-
const crypto_1 = require("crypto");
|
|
5
|
-
const constants_1 = require("../shared/constants");
|
|
6
|
-
const request_context_1 = require("./request-context");
|
|
7
|
-
const SESSION_COOKIE_MAX_AGE = 30 * 60; // 30 minutes, matching the browser cookie
|
|
8
|
-
/**
|
|
9
|
-
* Express middleware that sets up request context for tracing and auth.
|
|
10
|
-
*
|
|
11
|
-
* Reads from incoming headers and cookies:
|
|
12
|
-
* - `ts-request-id` — generates UUID if missing
|
|
13
|
-
* - `ts-session-id` — generates UUID if missing, sets cookie for persistence
|
|
14
|
-
* - `x-org-slug` — from header or cookie
|
|
15
|
-
* - `ts-auth-token` — from header or cookie
|
|
16
|
-
*
|
|
17
|
-
* All downstream code can access the context via `getRequestContext()`.
|
|
18
|
-
* Generated clients auto-resolve auth and tracing from this context.
|
|
19
|
-
*
|
|
20
|
-
* Requires `cookie-parser` middleware to run before this middleware
|
|
21
|
-
* so that `req.cookies` is populated.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* import express from 'express';
|
|
26
|
-
* import cookieParser from 'cookie-parser';
|
|
27
|
-
* import { createRequestMiddleware } from '@tetrascience/request-middleware/server';
|
|
28
|
-
*
|
|
29
|
-
* const app = express();
|
|
30
|
-
* app.use(cookieParser());
|
|
31
|
-
* app.use(createRequestMiddleware());
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
function createRequestMiddleware() {
|
|
35
|
-
return (req, res, next) => {
|
|
36
|
-
const requestId = req.headers[constants_1.REQUEST_ID_HEADER] || (0, crypto_1.randomUUID)();
|
|
37
|
-
// Session: header → cookie → generate
|
|
38
|
-
let sessionId = req.headers[constants_1.SESSION_ID_HEADER]
|
|
39
|
-
|| req.cookies?.[constants_1.SESSION_ID_HEADER];
|
|
40
|
-
if (!sessionId) {
|
|
41
|
-
sessionId = (0, crypto_1.randomUUID)();
|
|
42
|
-
}
|
|
43
|
-
// Always refresh the session cookie (sliding expiry)
|
|
44
|
-
res.setHeader?.('Set-Cookie', `${constants_1.SESSION_ID_HEADER}=${sessionId}; Path=/; Max-Age=${SESSION_COOKIE_MAX_AGE}; HttpOnly; SameSite=Lax`);
|
|
45
|
-
// Auth: header → cookie (optional, may not be present for background jobs)
|
|
46
|
-
const orgSlug = req.headers[constants_1.ORG_SLUG_HEADER]
|
|
47
|
-
|| req.cookies?.[constants_1.ORG_SLUG_HEADER];
|
|
48
|
-
const authToken = req.headers[constants_1.AUTH_TOKEN_HEADER]
|
|
49
|
-
|| req.cookies?.[constants_1.AUTH_TOKEN_HEADER];
|
|
50
|
-
(0, request_context_1.runWithRequestContext)({ requestId, sessionId, orgSlug, authToken }, () => next());
|
|
51
|
-
};
|
|
52
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from 'openapi-fetch';
|
|
2
|
-
import type { RequestTrackingMiddlewareOptions } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* Create openapi-fetch middleware that injects TetraScience service headers
|
|
5
|
-
* on every outgoing request.
|
|
6
|
-
*
|
|
7
|
-
* Reads request context from AsyncLocalStorage to propagate:
|
|
8
|
-
* - ts-request-id (from context or generates new UUID)
|
|
9
|
-
* - ts-initiating-service-name
|
|
10
|
-
* - ts-internal-api-key
|
|
11
|
-
* - x-org-slug (passthrough from incoming request context)
|
|
12
|
-
* - ts-auth-token (passthrough from incoming request context)
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import createClient from 'openapi-fetch';
|
|
17
|
-
* import {createRequestTrackingMiddleware} from '@tetrascience/request-middleware/server';
|
|
18
|
-
*
|
|
19
|
-
* const client = createClient<paths>({baseUrl: env.DATA_APPS_ENDPOINT});
|
|
20
|
-
* client.use(createRequestTrackingMiddleware({
|
|
21
|
-
* serviceName: 'my-service',
|
|
22
|
-
* internalApiKey: process.env.INTERNAL_API_KEY,
|
|
23
|
-
* }));
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export declare function createRequestTrackingMiddleware(options: RequestTrackingMiddlewareOptions): Middleware;
|
|
27
|
-
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAU9C,OAAO,KAAK,EAAC,gCAAgC,EAAC,MAAM,SAAS,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,gCAAgC,GAAG,UAAU,CA+BrG"}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRequestTrackingMiddleware = createRequestTrackingMiddleware;
|
|
4
|
-
const constants_1 = require("../shared/constants");
|
|
5
|
-
const request_context_1 = require("./request-context");
|
|
6
|
-
/**
|
|
7
|
-
* Create openapi-fetch middleware that injects TetraScience service headers
|
|
8
|
-
* on every outgoing request.
|
|
9
|
-
*
|
|
10
|
-
* Reads request context from AsyncLocalStorage to propagate:
|
|
11
|
-
* - ts-request-id (from context or generates new UUID)
|
|
12
|
-
* - ts-initiating-service-name
|
|
13
|
-
* - ts-internal-api-key
|
|
14
|
-
* - x-org-slug (passthrough from incoming request context)
|
|
15
|
-
* - ts-auth-token (passthrough from incoming request context)
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```ts
|
|
19
|
-
* import createClient from 'openapi-fetch';
|
|
20
|
-
* import {createRequestTrackingMiddleware} from '@tetrascience/request-middleware/server';
|
|
21
|
-
*
|
|
22
|
-
* const client = createClient<paths>({baseUrl: env.DATA_APPS_ENDPOINT});
|
|
23
|
-
* client.use(createRequestTrackingMiddleware({
|
|
24
|
-
* serviceName: 'my-service',
|
|
25
|
-
* internalApiKey: process.env.INTERNAL_API_KEY,
|
|
26
|
-
* }));
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
function createRequestTrackingMiddleware(options) {
|
|
30
|
-
const { serviceName, internalApiKey, getRequestId: getReqId = request_context_1.getRequestId } = options;
|
|
31
|
-
return {
|
|
32
|
-
onRequest({ request }) {
|
|
33
|
-
const context = (0, request_context_1.getRequestContext)();
|
|
34
|
-
const requestId = getReqId();
|
|
35
|
-
if (!request.headers.has(constants_1.REQUEST_ID_HEADER)) {
|
|
36
|
-
request.headers.set(constants_1.REQUEST_ID_HEADER, requestId);
|
|
37
|
-
}
|
|
38
|
-
if (!request.headers.has(constants_1.INITIATING_SERVICE_NAME_HEADER)) {
|
|
39
|
-
request.headers.set(constants_1.INITIATING_SERVICE_NAME_HEADER, serviceName);
|
|
40
|
-
}
|
|
41
|
-
const apiKey = typeof internalApiKey === 'function' ? internalApiKey() : internalApiKey;
|
|
42
|
-
if (apiKey && !request.headers.has(constants_1.INTERNAL_API_KEY_HEADER)) {
|
|
43
|
-
request.headers.set(constants_1.INTERNAL_API_KEY_HEADER, apiKey);
|
|
44
|
-
}
|
|
45
|
-
// Passthrough from incoming request context
|
|
46
|
-
if (context.orgSlug && !request.headers.has(constants_1.ORG_SLUG_HEADER)) {
|
|
47
|
-
request.headers.set(constants_1.ORG_SLUG_HEADER, context.orgSlug);
|
|
48
|
-
}
|
|
49
|
-
if (context.authToken && !request.headers.has(constants_1.AUTH_TOKEN_HEADER)) {
|
|
50
|
-
request.headers.set(constants_1.AUTH_TOKEN_HEADER, context.authToken);
|
|
51
|
-
}
|
|
52
|
-
return request;
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from 'openapi-fetch';
|
|
2
|
-
/**
|
|
3
|
-
* Any object with a `.parse()` method, compatible with both Zod 3 and Zod 4.
|
|
4
|
-
*/
|
|
5
|
-
export interface Parseable {
|
|
6
|
-
parse(data: unknown): unknown;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* A map of "METHOD /path" → schema for request body validation.
|
|
10
|
-
*
|
|
11
|
-
* Values must have a `.parse()` method (any Zod version works).
|
|
12
|
-
*
|
|
13
|
-
* Keys use the OpenAPI path template format, e.g.:
|
|
14
|
-
* "POST /v1/dataapps/apps/manage"
|
|
15
|
-
* "PUT /v1/dataapps/apps/{id}/labels"
|
|
16
|
-
*/
|
|
17
|
-
export type RequestBodySchemaMap = Record<string, Parseable>;
|
|
18
|
-
export interface RequestValidationMiddlewareOptions {
|
|
19
|
-
/** Map of "METHOD /path" → Zod schema for request body validation. */
|
|
20
|
-
schemas: RequestBodySchemaMap;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Create openapi-fetch middleware that validates request bodies against
|
|
24
|
-
* Zod schemas before the request is sent over the wire.
|
|
25
|
-
*
|
|
26
|
-
* If validation fails, a ZodError is thrown and the request is never sent.
|
|
27
|
-
* Endpoints without a matching schema are passed through without validation.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* import createClient from 'openapi-fetch';
|
|
32
|
-
* import {createRequestValidationMiddleware} from '@tetrascience/request-middleware/server';
|
|
33
|
-
* import {requestBodySchemas} from './generated/request-schemas';
|
|
34
|
-
*
|
|
35
|
-
* const client = createClient<paths>({baseUrl: '...'});
|
|
36
|
-
* client.use(createRequestValidationMiddleware({schemas: requestBodySchemas}));
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare function createRequestValidationMiddleware(options: RequestValidationMiddlewareOptions): Middleware;
|
|
40
|
-
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/server/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE7D,MAAM,WAAW,kCAAkC;IAClD,sEAAsE;IACtE,OAAO,EAAE,oBAAoB,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,kCAAkC,GAAG,UAAU,CA2BzG"}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRequestValidationMiddleware = createRequestValidationMiddleware;
|
|
4
|
-
/**
|
|
5
|
-
* Create openapi-fetch middleware that validates request bodies against
|
|
6
|
-
* Zod schemas before the request is sent over the wire.
|
|
7
|
-
*
|
|
8
|
-
* If validation fails, a ZodError is thrown and the request is never sent.
|
|
9
|
-
* Endpoints without a matching schema are passed through without validation.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* import createClient from 'openapi-fetch';
|
|
14
|
-
* import {createRequestValidationMiddleware} from '@tetrascience/request-middleware/server';
|
|
15
|
-
* import {requestBodySchemas} from './generated/request-schemas';
|
|
16
|
-
*
|
|
17
|
-
* const client = createClient<paths>({baseUrl: '...'});
|
|
18
|
-
* client.use(createRequestValidationMiddleware({schemas: requestBodySchemas}));
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
function createRequestValidationMiddleware(options) {
|
|
22
|
-
const { schemas } = options;
|
|
23
|
-
return {
|
|
24
|
-
async onRequest({ request, schemaPath }) {
|
|
25
|
-
const key = `${request.method} ${schemaPath}`;
|
|
26
|
-
const schema = schemas[key];
|
|
27
|
-
if (!schema)
|
|
28
|
-
return request;
|
|
29
|
-
const cloned = request.clone();
|
|
30
|
-
const rawText = await cloned.text();
|
|
31
|
-
if (rawText === '') {
|
|
32
|
-
return request;
|
|
33
|
-
}
|
|
34
|
-
let body;
|
|
35
|
-
try {
|
|
36
|
-
body = JSON.parse(rawText);
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
throw err;
|
|
40
|
-
}
|
|
41
|
-
schema.parse(body);
|
|
42
|
-
return request;
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from 'openapi-fetch';
|
|
2
|
-
import type { InternalAuth, DirectAuth, TracingOptions } from './client-types';
|
|
3
|
-
/**
|
|
4
|
-
* Create middleware that injects tracing headers: request ID, session ID,
|
|
5
|
-
* and service name.
|
|
6
|
-
*
|
|
7
|
-
* All values are optional. `requestId` falls back to a new UUID if not
|
|
8
|
-
* provided. Values can be static strings or functions resolved per-request.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* client.use(createTracingMiddleware({
|
|
13
|
-
* requestId: () => getRequestId(),
|
|
14
|
-
* sessionId: () => getRequestContext().sessionId,
|
|
15
|
-
* serviceName: 'my-service',
|
|
16
|
-
* }))
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
export declare function createTracingMiddleware(options?: TracingOptions): Middleware;
|
|
20
|
-
/**
|
|
21
|
-
* Create middleware that injects internal (service-to-service) auth headers.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* client.use(createInternalAuthMiddleware({
|
|
26
|
-
* internalApiKey: process.env.INTERNAL_API_KEY,
|
|
27
|
-
* orgSlug: () => getRequestContext().orgSlug,
|
|
28
|
-
* authToken: () => getRequestContext().authToken,
|
|
29
|
-
* }))
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export declare function createInternalAuthMiddleware(auth: InternalAuth): Middleware;
|
|
33
|
-
/**
|
|
34
|
-
* Create middleware that injects direct (user/browser) auth headers.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* client.use(createDirectAuthMiddleware({
|
|
39
|
-
* token: jwt,
|
|
40
|
-
* orgSlug: 'my-org',
|
|
41
|
-
* }))
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export declare function createDirectAuthMiddleware(auth: DirectAuth): Middleware;
|
|
45
|
-
//# sourceMappingURL=auth-middleware.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-middleware.d.ts","sourceRoot":"","sources":["../../src/shared/auth-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAC,YAAY,EAAE,UAAU,EAAE,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAsB7E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,UAAU,CAS5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,YAAY,GAAG,UAAU,CAS3E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAQvE"}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createTracingMiddleware = createTracingMiddleware;
|
|
4
|
-
exports.createInternalAuthMiddleware = createInternalAuthMiddleware;
|
|
5
|
-
exports.createDirectAuthMiddleware = createDirectAuthMiddleware;
|
|
6
|
-
const constants_1 = require("./constants");
|
|
7
|
-
const generate_request_id_1 = require("./generate-request-id");
|
|
8
|
-
function resolve(value) {
|
|
9
|
-
return typeof value === 'function' ? value() : value;
|
|
10
|
-
}
|
|
11
|
-
function setIfAbsent(request, header, value) {
|
|
12
|
-
const resolved = resolve(value);
|
|
13
|
-
if (resolved && !request.headers.has(header)) {
|
|
14
|
-
request.headers.set(header, resolved);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Create middleware that injects tracing headers: request ID, session ID,
|
|
19
|
-
* and service name.
|
|
20
|
-
*
|
|
21
|
-
* All values are optional. `requestId` falls back to a new UUID if not
|
|
22
|
-
* provided. Values can be static strings or functions resolved per-request.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* client.use(createTracingMiddleware({
|
|
27
|
-
* requestId: () => getRequestId(),
|
|
28
|
-
* sessionId: () => getRequestContext().sessionId,
|
|
29
|
-
* serviceName: 'my-service',
|
|
30
|
-
* }))
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
function createTracingMiddleware(options) {
|
|
34
|
-
return {
|
|
35
|
-
onRequest({ request }) {
|
|
36
|
-
setIfAbsent(request, constants_1.REQUEST_ID_HEADER, resolve(options?.requestId) ?? (0, generate_request_id_1.generateRequestId)());
|
|
37
|
-
setIfAbsent(request, constants_1.SESSION_ID_HEADER, options?.sessionId);
|
|
38
|
-
setIfAbsent(request, constants_1.INITIATING_SERVICE_NAME_HEADER, options?.serviceName);
|
|
39
|
-
return request;
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Create middleware that injects internal (service-to-service) auth headers.
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```ts
|
|
48
|
-
* client.use(createInternalAuthMiddleware({
|
|
49
|
-
* internalApiKey: process.env.INTERNAL_API_KEY,
|
|
50
|
-
* orgSlug: () => getRequestContext().orgSlug,
|
|
51
|
-
* authToken: () => getRequestContext().authToken,
|
|
52
|
-
* }))
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
function createInternalAuthMiddleware(auth) {
|
|
56
|
-
return {
|
|
57
|
-
onRequest({ request }) {
|
|
58
|
-
setIfAbsent(request, constants_1.INTERNAL_API_KEY_HEADER, auth.internalApiKey);
|
|
59
|
-
setIfAbsent(request, constants_1.ORG_SLUG_HEADER, auth.orgSlug);
|
|
60
|
-
setIfAbsent(request, constants_1.AUTH_TOKEN_HEADER, auth.authToken);
|
|
61
|
-
return request;
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Create middleware that injects direct (user/browser) auth headers.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```ts
|
|
70
|
-
* client.use(createDirectAuthMiddleware({
|
|
71
|
-
* token: jwt,
|
|
72
|
-
* orgSlug: 'my-org',
|
|
73
|
-
* }))
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
function createDirectAuthMiddleware(auth) {
|
|
77
|
-
return {
|
|
78
|
-
onRequest({ request }) {
|
|
79
|
-
setIfAbsent(request, constants_1.AUTH_TOKEN_HEADER, auth.token);
|
|
80
|
-
setIfAbsent(request, constants_1.ORG_SLUG_HEADER, auth.orgSlug);
|
|
81
|
-
return request;
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from 'openapi-fetch';
|
|
2
|
-
/**
|
|
3
|
-
* Create openapi-fetch middleware that prevents crashes when the server
|
|
4
|
-
* returns a non-JSON body (e.g. plain text "success") on a successful response.
|
|
5
|
-
*
|
|
6
|
-
* openapi-fetch's default parser calls `response.json()` which throws on
|
|
7
|
-
* non-JSON bodies. This middleware detects that case and wraps the body in
|
|
8
|
-
* a `{ message: "<text>" }` JSON envelope so parsing succeeds.
|
|
9
|
-
*
|
|
10
|
-
* 204 No Content responses are left untouched (no body to parse).
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import createClient from 'openapi-fetch';
|
|
15
|
-
* import {createSafeResponseMiddleware} from '@tetrascience/request-middleware';
|
|
16
|
-
*
|
|
17
|
-
* const client = createClient<paths>({baseUrl: '...'});
|
|
18
|
-
* client.use(createSafeResponseMiddleware());
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export declare function createSafeResponseMiddleware(): Middleware;
|
|
22
|
-
//# sourceMappingURL=safe-response.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"safe-response.d.ts","sourceRoot":"","sources":["../../src/shared/safe-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAE9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,4BAA4B,IAAI,UAAU,CAkBzD"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSafeResponseMiddleware = createSafeResponseMiddleware;
|
|
4
|
-
/**
|
|
5
|
-
* Create openapi-fetch middleware that prevents crashes when the server
|
|
6
|
-
* returns a non-JSON body (e.g. plain text "success") on a successful response.
|
|
7
|
-
*
|
|
8
|
-
* openapi-fetch's default parser calls `response.json()` which throws on
|
|
9
|
-
* non-JSON bodies. This middleware detects that case and wraps the body in
|
|
10
|
-
* a `{ message: "<text>" }` JSON envelope so parsing succeeds.
|
|
11
|
-
*
|
|
12
|
-
* 204 No Content responses are left untouched (no body to parse).
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import createClient from 'openapi-fetch';
|
|
17
|
-
* import {createSafeResponseMiddleware} from '@tetrascience/request-middleware';
|
|
18
|
-
*
|
|
19
|
-
* const client = createClient<paths>({baseUrl: '...'});
|
|
20
|
-
* client.use(createSafeResponseMiddleware());
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
function createSafeResponseMiddleware() {
|
|
24
|
-
return {
|
|
25
|
-
async onResponse({ response }) {
|
|
26
|
-
const contentType = response.headers.get('content-type') || '';
|
|
27
|
-
const isJson = contentType.includes('/json') || contentType.includes('+json');
|
|
28
|
-
if (response.ok && response.status !== 204 && !isJson) {
|
|
29
|
-
const text = await response.clone().text();
|
|
30
|
-
const headers = new Headers(response.headers);
|
|
31
|
-
headers.set('content-type', 'application/json');
|
|
32
|
-
return new Response(JSON.stringify({ message: text }), {
|
|
33
|
-
status: response.status,
|
|
34
|
-
statusText: response.statusText,
|
|
35
|
-
headers,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return undefined;
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { Middleware } from 'openapi-fetch';
|
|
2
|
-
/**
|
|
3
|
-
* Any object with a `.parse()` method, compatible with both Zod 3 and Zod 4.
|
|
4
|
-
*/
|
|
5
|
-
export interface Parseable {
|
|
6
|
-
parse(data: unknown): unknown;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* A map of "METHOD /path" → schema for request body validation.
|
|
10
|
-
*
|
|
11
|
-
* Values must have a `.parse()` method (any Zod version works).
|
|
12
|
-
*
|
|
13
|
-
* Keys use the OpenAPI path template format, e.g.:
|
|
14
|
-
* "POST /v1/dataapps/apps/manage"
|
|
15
|
-
* "PUT /v1/dataapps/apps/{id}/labels"
|
|
16
|
-
*/
|
|
17
|
-
export type RequestBodySchemaMap = Record<string, Parseable>;
|
|
18
|
-
export interface RequestValidationMiddlewareOptions {
|
|
19
|
-
/** Map of "METHOD /path" → Zod schema for request body validation. */
|
|
20
|
-
schemas: RequestBodySchemaMap;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Create openapi-fetch middleware that validates request bodies against
|
|
24
|
-
* Zod schemas before the request is sent over the wire.
|
|
25
|
-
*
|
|
26
|
-
* If validation fails, a ZodError is thrown and the request is never sent.
|
|
27
|
-
* Endpoints without a matching schema are passed through without validation.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* import createClient from 'openapi-fetch';
|
|
32
|
-
* import {createRequestValidationMiddleware} from '@tetrascience/request-middleware';
|
|
33
|
-
* import {requestBodySchemas} from './generated/request-schemas';
|
|
34
|
-
*
|
|
35
|
-
* const client = createClient<paths>({baseUrl: '...'});
|
|
36
|
-
* client.use(createRequestValidationMiddleware({schemas: requestBodySchemas}));
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare function createRequestValidationMiddleware(options: RequestValidationMiddlewareOptions): Middleware;
|
|
40
|
-
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/shared/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE7D,MAAM,WAAW,kCAAkC;IAClD,sEAAsE;IACtE,OAAO,EAAE,oBAAoB,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,kCAAkC,GAAG,UAAU,CAqBzG"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createRequestValidationMiddleware = createRequestValidationMiddleware;
|
|
4
|
-
/**
|
|
5
|
-
* Create openapi-fetch middleware that validates request bodies against
|
|
6
|
-
* Zod schemas before the request is sent over the wire.
|
|
7
|
-
*
|
|
8
|
-
* If validation fails, a ZodError is thrown and the request is never sent.
|
|
9
|
-
* Endpoints without a matching schema are passed through without validation.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* import createClient from 'openapi-fetch';
|
|
14
|
-
* import {createRequestValidationMiddleware} from '@tetrascience/request-middleware';
|
|
15
|
-
* import {requestBodySchemas} from './generated/request-schemas';
|
|
16
|
-
*
|
|
17
|
-
* const client = createClient<paths>({baseUrl: '...'});
|
|
18
|
-
* client.use(createRequestValidationMiddleware({schemas: requestBodySchemas}));
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
function createRequestValidationMiddleware(options) {
|
|
22
|
-
const { schemas } = options;
|
|
23
|
-
return {
|
|
24
|
-
async onRequest({ request, schemaPath }) {
|
|
25
|
-
const key = `${request.method} ${schemaPath}`;
|
|
26
|
-
const schema = schemas[key];
|
|
27
|
-
if (!schema)
|
|
28
|
-
return request;
|
|
29
|
-
const cloned = request.clone();
|
|
30
|
-
const rawText = await cloned.text();
|
|
31
|
-
if (rawText === '') {
|
|
32
|
-
return request;
|
|
33
|
-
}
|
|
34
|
-
const body = JSON.parse(rawText);
|
|
35
|
-
schema.parse(body);
|
|
36
|
-
return request;
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|