@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.
Files changed (36) hide show
  1. package/dist/cli/generate-client.js +0 -0
  2. package/dist/cli/generate-schemas.d.ts.map +1 -1
  3. package/dist/cli/generate-schemas.js +30 -80
  4. package/dist/cli/templates.d.ts.map +1 -1
  5. package/dist/cli/templates.js +0 -4
  6. package/package.json +1 -1
  7. package/dist/cli/init-client.d.ts +0 -3
  8. package/dist/cli/init-client.d.ts.map +0 -1
  9. package/dist/cli/init-client.js +0 -133
  10. package/dist/client/install-tracking.d.ts +0 -18
  11. package/dist/client/install-tracking.d.ts.map +0 -1
  12. package/dist/client/install-tracking.js +0 -88
  13. package/dist/client/middleware.d.ts +0 -18
  14. package/dist/client/middleware.d.ts.map +0 -1
  15. package/dist/client/middleware.js +0 -71
  16. package/dist/client/sanitize-url.d.ts +0 -3
  17. package/dist/client/sanitize-url.d.ts.map +0 -1
  18. package/dist/client/sanitize-url.js +0 -14
  19. package/dist/server/express-middleware.d.ts +0 -41
  20. package/dist/server/express-middleware.d.ts.map +0 -1
  21. package/dist/server/express-middleware.js +0 -52
  22. package/dist/server/middleware.d.ts +0 -27
  23. package/dist/server/middleware.d.ts.map +0 -1
  24. package/dist/server/middleware.js +0 -55
  25. package/dist/server/validation.d.ts +0 -40
  26. package/dist/server/validation.d.ts.map +0 -1
  27. package/dist/server/validation.js +0 -45
  28. package/dist/shared/auth-middleware.d.ts +0 -45
  29. package/dist/shared/auth-middleware.d.ts.map +0 -1
  30. package/dist/shared/auth-middleware.js +0 -84
  31. package/dist/shared/safe-response.d.ts +0 -22
  32. package/dist/shared/safe-response.d.ts.map +0 -1
  33. package/dist/shared/safe-response.js +0 -41
  34. package/dist/shared/validation.d.ts +0 -40
  35. package/dist/shared/validation.d.ts.map +0 -1
  36. 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;AAMD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CA6GnG"}
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
- * 1. Runs openapi-zod-client to get Zod schemas for all components
43
- * 2. Parses the OpenAPI spec to find which path+method has a request body
44
- * 3. Outputs a validation map: "METHOD /path" -> Zod schema
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. Generate full zodios output to a temp file
111
- const zodClientBin = findZodClientBin();
112
- (0, child_process_1.execSync)(`"${zodClientBin}" "${specPath}" -o "${tmpFile}" --export-schemas`, { stdio: 'inherit' });
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
- raw = fs.readFileSync(tmpFile, 'utf-8');
110
+ ({ getZodClientTemplateContext } = require('openapi-zod-client'));
116
111
  }
117
112
  catch {
118
- throw new Error(`Failed to read generated file: ${tmpFile}`);
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 schemaSection = raw
127
- .substring(0, endpointsIdx)
128
- .split('\n')
129
- .filter((l) => !l.startsWith('import '))
130
- .join('\n')
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 [pathStr, methods] of Object.entries(spec.paths || {})) {
136
- for (const [method, operation] of Object.entries(methods)) {
137
- if (method === 'parameters')
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
- // Try to find a schema with matching suffix
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
- // 4. Build output file
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,CAOvC"}
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"}
@@ -157,9 +157,5 @@ dist/
157
157
  }
158
158
  function generateYarnRc() {
159
159
  return `nodeLinker: node-modules
160
-
161
- npmScopes:
162
- tetrascience:
163
- npmRegistryServer: "https://tetrascience.jfrog.io/artifactory/api/npm/ts-npm-virtual/"
164
160
  `;
165
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tetrascience-npm/request",
3
- "version": "0.2.0",
3
+ "version": "0.2.1-beta.112.2",
4
4
  "description": "Request tracking middleware for TetraScience services and data apps (client + server).",
5
5
  "license": "Apache-2.0",
6
6
  "packageManager": "yarn@4.1.0",
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=init-client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"init-client.d.ts","sourceRoot":"","sources":["../../src/cli/init-client.ts"],"names":[],"mappings":""}
@@ -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,3 +0,0 @@
1
- /** Extract pathname from a URL, stripping query string and hash. */
2
- export declare function sanitizeUrl(url: string): string;
3
- //# sourceMappingURL=sanitize-url.d.ts.map
@@ -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
- }