@tetrascience-npm/request 0.2.0-beta.106.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 (81) hide show
  1. package/README.md +169 -0
  2. package/client.d.ts +1 -0
  3. package/client.js +1 -0
  4. package/dist/cli/generate-client.d.ts +3 -0
  5. package/dist/cli/generate-client.d.ts.map +1 -0
  6. package/dist/cli/generate-client.js +208 -0
  7. package/dist/cli/generate-schemas.d.ts +7 -0
  8. package/dist/cli/generate-schemas.d.ts.map +1 -0
  9. package/dist/cli/generate-schemas.js +210 -0
  10. package/dist/cli/templates.d.ts +27 -0
  11. package/dist/cli/templates.d.ts.map +1 -0
  12. package/dist/cli/templates.js +165 -0
  13. package/dist/client/console-logger.d.ts +10 -0
  14. package/dist/client/console-logger.d.ts.map +1 -0
  15. package/dist/client/console-logger.js +42 -0
  16. package/dist/client/index.d.ts +4 -0
  17. package/dist/client/index.d.ts.map +1 -0
  18. package/dist/client/index.js +19 -0
  19. package/dist/client/install-middleware.d.ts +31 -0
  20. package/dist/client/install-middleware.d.ts.map +1 -0
  21. package/dist/client/install-middleware.js +117 -0
  22. package/dist/client/types.d.ts +13 -0
  23. package/dist/client/types.d.ts.map +1 -0
  24. package/dist/client/types.js +2 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +17 -0
  28. package/dist/server/index.d.ts +5 -0
  29. package/dist/server/index.d.ts.map +1 -0
  30. package/dist/server/index.js +24 -0
  31. package/dist/server/request-context.d.ts +27 -0
  32. package/dist/server/request-context.d.ts.map +1 -0
  33. package/dist/server/request-context.js +42 -0
  34. package/dist/server/request-middleware.d.ts +14 -0
  35. package/dist/server/request-middleware.d.ts.map +1 -0
  36. package/dist/server/request-middleware.js +82 -0
  37. package/dist/server/types.d.ts +13 -0
  38. package/dist/server/types.d.ts.map +1 -0
  39. package/dist/server/types.js +2 -0
  40. package/dist/shared/client-types.d.ts +63 -0
  41. package/dist/shared/client-types.d.ts.map +1 -0
  42. package/dist/shared/client-types.js +7 -0
  43. package/dist/shared/constants.d.ts +7 -0
  44. package/dist/shared/constants.d.ts.map +1 -0
  45. package/dist/shared/constants.js +9 -0
  46. package/dist/shared/generate-request-id.d.ts +10 -0
  47. package/dist/shared/generate-request-id.d.ts.map +1 -0
  48. package/dist/shared/generate-request-id.js +21 -0
  49. package/dist/shared/index.d.ts +8 -0
  50. package/dist/shared/index.d.ts.map +1 -0
  51. package/dist/shared/index.js +29 -0
  52. package/dist/shared/middleware/auth.d.ts +39 -0
  53. package/dist/shared/middleware/auth.d.ts.map +1 -0
  54. package/dist/shared/middleware/auth.js +164 -0
  55. package/dist/shared/middleware/default.d.ts +29 -0
  56. package/dist/shared/middleware/default.d.ts.map +1 -0
  57. package/dist/shared/middleware/default.js +67 -0
  58. package/dist/shared/middleware/index.d.ts +6 -0
  59. package/dist/shared/middleware/index.d.ts.map +1 -0
  60. package/dist/shared/middleware/index.js +21 -0
  61. package/dist/shared/middleware/safe-response.d.ts +22 -0
  62. package/dist/shared/middleware/safe-response.d.ts.map +1 -0
  63. package/dist/shared/middleware/safe-response.js +41 -0
  64. package/dist/shared/middleware/tracing.d.ts +23 -0
  65. package/dist/shared/middleware/tracing.d.ts.map +1 -0
  66. package/dist/shared/middleware/tracing.js +67 -0
  67. package/dist/shared/middleware/utils.d.ts +4 -0
  68. package/dist/shared/middleware/utils.d.ts.map +1 -0
  69. package/dist/shared/middleware/utils.js +13 -0
  70. package/dist/shared/middleware/validation.d.ts +43 -0
  71. package/dist/shared/middleware/validation.d.ts.map +1 -0
  72. package/dist/shared/middleware/validation.js +42 -0
  73. package/dist/shared/sanitize-url.d.ts +3 -0
  74. package/dist/shared/sanitize-url.d.ts.map +1 -0
  75. package/dist/shared/sanitize-url.js +12 -0
  76. package/dist/shared/types.d.ts +10 -0
  77. package/dist/shared/types.d.ts.map +1 -0
  78. package/dist/shared/types.js +2 -0
  79. package/package.json +98 -0
  80. package/server.d.ts +1 -0
  81. package/server.js +1 -0
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /**
3
+ * Shared template functions used by init-client and generate-client CLIs.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deriveServiceName = deriveServiceName;
7
+ exports.generateTsConfig = generateTsConfig;
8
+ exports.generateIndexTs = generateIndexTs;
9
+ exports.generateClientPackageJson = generateClientPackageJson;
10
+ exports.generateClientGitIgnore = generateClientGitIgnore;
11
+ exports.generateYarnRc = generateYarnRc;
12
+ /**
13
+ * Derive a PascalCase service name from the npm package name.
14
+ * @example "@tetrascience/data-apps-client" -> "DataApps"
15
+ * @example "@tetrascience/pipeline-client" -> "Pipeline"
16
+ */
17
+ function deriveServiceName(packageName) {
18
+ const bare = packageName.replace(/^@[^/]+\//, '').replace(/-client$/, '');
19
+ return bare
20
+ .split('-')
21
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
22
+ .join('');
23
+ }
24
+ function generateTsConfig() {
25
+ const config = {
26
+ compilerOptions: {
27
+ target: 'ES2020',
28
+ module: 'node16',
29
+ moduleResolution: 'node16',
30
+ lib: ['ES2020'],
31
+ declaration: true,
32
+ strict: true,
33
+ esModuleInterop: true,
34
+ skipLibCheck: true,
35
+ outDir: 'dist',
36
+ rootDir: '.',
37
+ },
38
+ include: ['index.ts', 'generated/**/*.ts'],
39
+ exclude: ['node_modules', 'dist'],
40
+ };
41
+ return JSON.stringify(config, null, 2) + '\n';
42
+ }
43
+ /**
44
+ * PascalCase a variant name for use as a type name.
45
+ * @example "internal" -> "Internal", "public-api" -> "PublicApi"
46
+ */
47
+ function pascalCase(s) {
48
+ return s
49
+ .split(/[-_]/)
50
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
51
+ .join('');
52
+ }
53
+ /**
54
+ * Generate index.ts for a client package.
55
+ *
56
+ * @param serviceName - PascalCase service name (e.g. "DataApps")
57
+ * @param specVariants - null for single-spec, or an array of variant names (e.g. ["internal", "public"])
58
+ */
59
+ function generateIndexTs(serviceName, specVariants) {
60
+ const factoryName = `create${serviceName}Client`;
61
+ const typeName = `${serviceName}Client`;
62
+ const optionsName = `${serviceName}ClientOptions`;
63
+ if (!specVariants || specVariants.length === 0) {
64
+ // Single-spec template
65
+ return `import { createClient, applyDefaultMiddleware } from '@tetrascience-npm/request'
66
+ import type { ServiceClientOptions } from '@tetrascience-npm/request'
67
+ import type { paths } from './generated/schema'
68
+ import { requestBodySchemas } from './generated/request-schemas'
69
+
70
+ export type * from './generated/schema'
71
+ export type { InternalAuth, DirectAuth, ServiceClientOptions } from '@tetrascience-npm/request'
72
+
73
+ export type ${optionsName} = ServiceClientOptions
74
+
75
+ export function ${factoryName}(options: ${optionsName}) {
76
+ const client = createClient<paths>({
77
+ baseUrl: options.baseUrl,
78
+ headers: options.headers,
79
+ })
80
+
81
+ applyDefaultMiddleware(client, options, requestBodySchemas)
82
+
83
+ return client
84
+ }
85
+
86
+ export type ${typeName} = ReturnType<typeof ${factoryName}>
87
+ `;
88
+ }
89
+ // Multi-spec template
90
+ const pathImports = specVariants
91
+ .map((v) => `import type { paths as ${pascalCase(v)}Paths } from './generated/${v}.schema'`)
92
+ .join('\n');
93
+ const schemaImports = specVariants
94
+ .map((v) => `import { requestBodySchemas as ${v}Schemas } from './generated/${v}.request-schemas'`)
95
+ .join('\n');
96
+ const pathTypes = specVariants.map((v) => `${pascalCase(v)}Paths`);
97
+ const pathUnion = pathTypes.join(' | ');
98
+ const defaultPath = pathTypes[0];
99
+ const pathExports = specVariants.map((v) => ` ${pascalCase(v)}Paths`).join(',\n');
100
+ const schemaMerge = specVariants.map((v) => `...${v}Schemas`).join(', ');
101
+ return `import { createClient, applyDefaultMiddleware } from '@tetrascience-npm/request'
102
+ import type { ServiceClientOptions } from '@tetrascience-npm/request'
103
+ ${pathImports}
104
+ ${schemaImports}
105
+
106
+ export type {
107
+ ${pathExports},
108
+ }
109
+ export type { InternalAuth, DirectAuth, ServiceClientOptions } from '@tetrascience-npm/request'
110
+
111
+ const allSchemas = { ${schemaMerge} }
112
+
113
+ export type ${optionsName} = ServiceClientOptions
114
+
115
+ export function ${factoryName}<P extends ${pathUnion} = ${defaultPath}>(options: ${optionsName}) {
116
+ const client = createClient<P>({
117
+ baseUrl: options.baseUrl,
118
+ headers: options.headers,
119
+ })
120
+
121
+ applyDefaultMiddleware(client, options, allSchemas)
122
+
123
+ return client
124
+ }
125
+
126
+ export type ${typeName}<P extends ${pathUnion} = ${defaultPath}> = ReturnType<typeof ${factoryName}<P>>
127
+ `;
128
+ }
129
+ function generateClientPackageJson(config) {
130
+ const pkg = {
131
+ name: config.packageName,
132
+ version: config.version,
133
+ description: config.description || `Typed client for the ${config.serviceName} service API`,
134
+ main: 'dist/index.js',
135
+ types: 'dist/index.d.ts',
136
+ files: ['dist'],
137
+ scripts: {
138
+ build: 'tsc --project tsconfig.build.json',
139
+ prepack: 'yarn build',
140
+ },
141
+ dependencies: {
142
+ '@tetrascience-npm/request': config.middlewareVersion,
143
+ 'openapi-fetch': '^0.17.0',
144
+ zod: '^3.24.0',
145
+ },
146
+ devDependencies: {
147
+ typescript: '^5.7.3',
148
+ },
149
+ };
150
+ return JSON.stringify(pkg, null, 2) + '\n';
151
+ }
152
+ function generateClientGitIgnore() {
153
+ return `node_modules/
154
+ dist/
155
+ .yarn/
156
+ `;
157
+ }
158
+ function generateYarnRc() {
159
+ return `nodeLinker: node-modules
160
+
161
+ npmScopes:
162
+ tetrascience:
163
+ npmRegistryServer: "https://tetrascience.jfrog.io/artifactory/api/npm/ts-npm-virtual/"
164
+ `;
165
+ }
@@ -0,0 +1,10 @@
1
+ import type { RequestTrackingLogger } from '../shared/types';
2
+ import type { ConsoleLoggerOptions } from './types';
3
+ /**
4
+ * Create a structured console logger for request tracking.
5
+ *
6
+ * Outputs structured log entries to the browser console, including the
7
+ * requestId from metadata for correlation with backend logs.
8
+ */
9
+ export declare function createConsoleLogger(options?: ConsoleLoggerOptions): RequestTrackingLogger;
10
+ //# sourceMappingURL=console-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-logger.d.ts","sourceRoot":"","sources":["../../src/client/console-logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,SAAS,CAAC;AAKlD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,qBAAqB,CA8BzF"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createConsoleLogger = createConsoleLogger;
4
+ const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
5
+ /**
6
+ * Create a structured console logger for request tracking.
7
+ *
8
+ * Outputs structured log entries to the browser console, including the
9
+ * requestId from metadata for correlation with backend logs.
10
+ */
11
+ function createConsoleLogger(options) {
12
+ const minLevel = LOG_LEVELS[options?.level ?? 'info'];
13
+ const prefix = options?.prefix;
14
+ const formatMessage = (message) => (prefix ? `[${prefix}] ${message}` : message);
15
+ const shouldLog = (level) => LOG_LEVELS[level] >= minLevel;
16
+ const log = (method, message, meta) => {
17
+ if (meta !== undefined) {
18
+ console[method](formatMessage(message), meta);
19
+ }
20
+ else {
21
+ console[method](formatMessage(message));
22
+ }
23
+ };
24
+ return {
25
+ debug(message, meta) {
26
+ if (shouldLog('debug'))
27
+ log('debug', message, meta);
28
+ },
29
+ info(message, meta) {
30
+ if (shouldLog('info'))
31
+ log('info', message, meta);
32
+ },
33
+ warn(message, meta) {
34
+ if (shouldLog('warn'))
35
+ log('warn', message, meta);
36
+ },
37
+ error(message, meta) {
38
+ if (shouldLog('error'))
39
+ log('error', message, meta);
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './console-logger';
3
+ export * from './install-middleware';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./console-logger"), exports);
19
+ __exportStar(require("./install-middleware"), exports);
@@ -0,0 +1,31 @@
1
+ import type { ClientTrackingOptions } from './types';
2
+ /**
3
+ * Install request middleware on the global `fetch` function.
4
+ *
5
+ * Automatically injects `ts-request-id` and `ts-session-id` headers
6
+ * on every outgoing request. Session ID is persisted in a cookie
7
+ * with a 30-minute sliding expiry.
8
+ *
9
+ * Safe to call multiple times — subsequent calls return the existing
10
+ * uninstall function without re-patching (important for Module Federation
11
+ * where multiple micro-frontends share `globalThis`).
12
+ *
13
+ * **Limitations:**
14
+ * - Only patches `fetch()`. Libraries using `XMLHttpRequest` or `axios`
15
+ * are not covered.
16
+ * - Code that captures `window.fetch` before this runs (e.g.
17
+ * `const myFetch = window.fetch` at import time) will hold a
18
+ * reference to the unpatched fetch.
19
+ *
20
+ * @returns An uninstall function that restores the original `fetch`
21
+ * and clears the session cookie.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { installRequestMiddleware } from '@tetrascience-npm/request/client'
26
+ *
27
+ * installRequestMiddleware()
28
+ * ```
29
+ */
30
+ export declare function installRequestMiddleware(options?: ClientTrackingOptions): () => void;
31
+ //# sourceMappingURL=install-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-middleware.d.ts","sourceRoot":"","sources":["../../src/client/install-middleware.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAqCnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,IAAI,CA+DpF"}
@@ -0,0 +1,117 @@
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
+ const sanitize_url_1 = require("../shared/sanitize-url");
7
+ const SESSION_COOKIE_MAX_AGE = 30 * 60; // 30 minutes, refreshed on each request
8
+ const INSTALLED_KEY = '__ts_request_middleware_installed__';
9
+ /**
10
+ * Get or create a session ID from cookies.
11
+ * Creates a new UUID and stores it as a cookie if not already present.
12
+ * Refreshes the cookie expiry on each call (sliding window).
13
+ *
14
+ * The cookie is written without a Domain attribute — the server's
15
+ * createRequestMiddleware() sets the correct cross-subdomain Domain
16
+ * (from COOKIE_DOMAIN env var) on the response. The browser-set cookie
17
+ * is just a fallback for the first request before the server responds.
18
+ */
19
+ function getOrCreateSessionId() {
20
+ const match = document.cookie.match(new RegExp(`(?:^|; )${constants_1.SESSION_ID_HEADER}=([^;]*)`));
21
+ if (match) {
22
+ // Refresh expiry
23
+ document.cookie = `${constants_1.SESSION_ID_HEADER}=${match[1]}; path=/; max-age=${SESSION_COOKIE_MAX_AGE}; SameSite=Lax; Secure`;
24
+ return match[1];
25
+ }
26
+ const sessionId = (0, generate_request_id_1.generateRequestId)();
27
+ document.cookie = `${constants_1.SESSION_ID_HEADER}=${sessionId}; path=/; max-age=${SESSION_COOKIE_MAX_AGE}; SameSite=Lax; Secure`;
28
+ return sessionId;
29
+ }
30
+ /**
31
+ * Clear the session cookie.
32
+ */
33
+ function clearSessionCookie() {
34
+ if (typeof document !== 'undefined') {
35
+ document.cookie = `${constants_1.SESSION_ID_HEADER}=; path=/; max-age=0; SameSite=Lax; Secure`;
36
+ }
37
+ }
38
+ /**
39
+ * Install request middleware on the global `fetch` function.
40
+ *
41
+ * Automatically injects `ts-request-id` and `ts-session-id` headers
42
+ * on every outgoing request. Session ID is persisted in a cookie
43
+ * with a 30-minute sliding expiry.
44
+ *
45
+ * Safe to call multiple times — subsequent calls return the existing
46
+ * uninstall function without re-patching (important for Module Federation
47
+ * where multiple micro-frontends share `globalThis`).
48
+ *
49
+ * **Limitations:**
50
+ * - Only patches `fetch()`. Libraries using `XMLHttpRequest` or `axios`
51
+ * are not covered.
52
+ * - Code that captures `window.fetch` before this runs (e.g.
53
+ * `const myFetch = window.fetch` at import time) will hold a
54
+ * reference to the unpatched fetch.
55
+ *
56
+ * @returns An uninstall function that restores the original `fetch`
57
+ * and clears the session cookie.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { installRequestMiddleware } from '@tetrascience-npm/request/client'
62
+ *
63
+ * installRequestMiddleware()
64
+ * ```
65
+ */
66
+ function installRequestMiddleware(options) {
67
+ // Guard against double-install (Module Federation, multiple micro-frontends)
68
+ const existing = globalThis[INSTALLED_KEY];
69
+ if (existing) {
70
+ return existing.uninstall;
71
+ }
72
+ const { logger } = options ?? {};
73
+ const originalFetch = globalThis.fetch;
74
+ globalThis.fetch = (input, init) => {
75
+ // Merge headers: start from the Request's headers (if input is a Request),
76
+ // then layer on init?.headers, then add tracking headers.
77
+ const inputHeaders = input instanceof Request ? input.headers : undefined;
78
+ const headers = new Headers(inputHeaders);
79
+ if (init?.headers) {
80
+ new Headers(init.headers).forEach((value, key) => headers.set(key, value));
81
+ }
82
+ if (!headers.has(constants_1.REQUEST_ID_HEADER)) {
83
+ headers.set(constants_1.REQUEST_ID_HEADER, (0, generate_request_id_1.generateRequestId)());
84
+ }
85
+ const requestId = headers.get(constants_1.REQUEST_ID_HEADER);
86
+ if (!headers.has(constants_1.SESSION_ID_HEADER) && typeof document !== 'undefined') {
87
+ headers.set(constants_1.SESSION_ID_HEADER, getOrCreateSessionId());
88
+ }
89
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
90
+ logger?.debug('Outgoing request', {
91
+ requestId,
92
+ path: (0, sanitize_url_1.sanitizeUrl)(url),
93
+ });
94
+ return originalFetch(input, { ...init, headers }).then((response) => {
95
+ logger?.debug('Response received', {
96
+ requestId,
97
+ path: (0, sanitize_url_1.sanitizeUrl)(url),
98
+ status: response.status,
99
+ });
100
+ return response;
101
+ }, (error) => {
102
+ logger?.error('Request failed', {
103
+ requestId,
104
+ path: (0, sanitize_url_1.sanitizeUrl)(url),
105
+ error: error instanceof Error ? error.message : String(error),
106
+ });
107
+ throw error;
108
+ });
109
+ };
110
+ const uninstall = () => {
111
+ globalThis.fetch = originalFetch;
112
+ clearSessionCookie();
113
+ delete globalThis[INSTALLED_KEY];
114
+ };
115
+ globalThis[INSTALLED_KEY] = { uninstall };
116
+ return uninstall;
117
+ }
@@ -0,0 +1,13 @@
1
+ import type { RequestTrackingLogger } from '../shared/types';
2
+ export interface ConsoleLoggerOptions {
3
+ /** Prefix prepended to all log messages. */
4
+ prefix?: string;
5
+ /** Minimum log level. Defaults to 'info'. */
6
+ level?: 'debug' | 'info' | 'warn' | 'error';
7
+ }
8
+ /** Options for installRequestTracking (browser global fetch patching). */
9
+ export interface ClientTrackingOptions {
10
+ /** If provided, logs each outgoing request/response with the request ID. */
11
+ logger?: RequestTrackingLogger;
12
+ }
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,iBAAiB,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACpC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC5C;AAED,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACrC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAC/B"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export * from './shared';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./shared"), exports);
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './request-context';
3
+ export * from './request-middleware';
4
+ export { REQUEST_ID_HEADER } from '../shared/constants';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AAIrC,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.REQUEST_ID_HEADER = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ __exportStar(require("./request-context"), exports);
20
+ __exportStar(require("./request-middleware"), exports);
21
+ // Constant re-exported for convenience (commonly used with request context)
22
+ // eslint-disable-next-line no-restricted-syntax
23
+ var constants_1 = require("../shared/constants");
24
+ Object.defineProperty(exports, "REQUEST_ID_HEADER", { enumerable: true, get: function () { return constants_1.REQUEST_ID_HEADER; } });
@@ -0,0 +1,27 @@
1
+ import type { RequestContext } from './types';
2
+ /**
3
+ * Get the current request context.
4
+ * Returns an empty object if no context has been set.
5
+ */
6
+ export declare function getRequestContext(): Partial<RequestContext>;
7
+ /**
8
+ * Run a function within a request context. The context is scoped to the
9
+ * callback and its async descendants, providing proper isolation.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * app.use((req, res, next) => {
14
+ * runWithRequestContext({ requestId }, () => next())
15
+ * })
16
+ * ```
17
+ */
18
+ export declare function runWithRequestContext<T>(context: RequestContext, fn: () => T): T;
19
+ /**
20
+ * Run a function with no request context. Useful for testing isolation.
21
+ */
22
+ export declare function runWithoutRequestContext<T>(fn: () => T): T;
23
+ /**
24
+ * Get the request ID from the current context, or generate a new UUID.
25
+ */
26
+ export declare function getRequestId(): string;
27
+ //# sourceMappingURL=request-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/server/request-context.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAI5C;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAE3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEhF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE1D;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRequestContext = getRequestContext;
4
+ exports.runWithRequestContext = runWithRequestContext;
5
+ exports.runWithoutRequestContext = runWithoutRequestContext;
6
+ exports.getRequestId = getRequestId;
7
+ const async_hooks_1 = require("async_hooks");
8
+ const crypto_1 = require("crypto");
9
+ const asyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
10
+ /**
11
+ * Get the current request context.
12
+ * Returns an empty object if no context has been set.
13
+ */
14
+ function getRequestContext() {
15
+ return asyncLocalStorage.getStore() ?? {};
16
+ }
17
+ /**
18
+ * Run a function within a request context. The context is scoped to the
19
+ * callback and its async descendants, providing proper isolation.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * app.use((req, res, next) => {
24
+ * runWithRequestContext({ requestId }, () => next())
25
+ * })
26
+ * ```
27
+ */
28
+ function runWithRequestContext(context, fn) {
29
+ return asyncLocalStorage.run(context, fn);
30
+ }
31
+ /**
32
+ * Run a function with no request context. Useful for testing isolation.
33
+ */
34
+ function runWithoutRequestContext(fn) {
35
+ return asyncLocalStorage.exit(fn);
36
+ }
37
+ /**
38
+ * Get the request ID from the current context, or generate a new UUID.
39
+ */
40
+ function getRequestId() {
41
+ return getRequestContext().requestId ?? (0, crypto_1.randomUUID)();
42
+ }
@@ -0,0 +1,14 @@
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
+ }
7
+ interface ServerResponse {
8
+ setHeader?(name: string, value: string): void;
9
+ }
10
+ type NextFunction = () => void;
11
+ type ExpressMiddleware = (req: IncomingRequest, res: ServerResponse, next: NextFunction) => void;
12
+ export declare function createRequestMiddleware(): ExpressMiddleware;
13
+ export {};
14
+ //# sourceMappingURL=request-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-middleware.d.ts","sourceRoot":"","sources":["../../src/server/request-middleware.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,UAAU,eAAe;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;CACvD;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;AAwDjG,wBAAgB,uBAAuB,IAAI,iBAAiB,CAyB3D"}
@@ -0,0 +1,82 @@
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
+ * Parse a named value from the Cookie header.
10
+ */
11
+ function getCookie(req, name) {
12
+ const header = req.headers.cookie;
13
+ if (!header)
14
+ return undefined;
15
+ const match = header.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
16
+ return match?.[1];
17
+ }
18
+ /**
19
+ * Express middleware that sets up request context for tracing and auth.
20
+ *
21
+ * Reads from incoming headers and cookies:
22
+ * - `ts-request-id` — generates UUID if missing
23
+ * - `ts-session-id` — generates UUID if missing, sets cookie for persistence
24
+ * - `x-org-slug` — from header or cookie
25
+ * - `ts-auth-token` — from header or cookie
26
+ *
27
+ * Parses cookies internally — no need for `cookie-parser`.
28
+ *
29
+ * All downstream code can access the context via `getRequestContext()`.
30
+ * Generated clients auto-resolve auth and tracing from this context.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import express from 'express';
35
+ * import { createRequestMiddleware } from '@tetrascience-npm/request/server';
36
+ *
37
+ * const app = express();
38
+ * app.use(createRequestMiddleware());
39
+ * ```
40
+ */
41
+ /**
42
+ * Derive cookie domain from env vars (same logic as TDP's getCookieDomain).
43
+ * COOKIE_DOMAIN is set on data apps; SERVICE_URI on the TDP web service.
44
+ */
45
+ function deriveServerCookieDomain() {
46
+ const cookieDomain = process.env.COOKIE_DOMAIN;
47
+ if (cookieDomain)
48
+ return cookieDomain.startsWith('.') ? cookieDomain : '.' + cookieDomain;
49
+ const serviceUri = process.env.SERVICE_URI;
50
+ if (serviceUri) {
51
+ try {
52
+ return '.' + new URL(serviceUri).hostname;
53
+ }
54
+ catch {
55
+ // fall through
56
+ }
57
+ }
58
+ return undefined;
59
+ }
60
+ function createRequestMiddleware() {
61
+ const cookieDomain = deriveServerCookieDomain();
62
+ return (req, res, next) => {
63
+ const requestId = req.headers[constants_1.REQUEST_ID_HEADER] || (0, crypto_1.randomUUID)();
64
+ // Session: header → cookie → generate
65
+ let sessionId = req.headers[constants_1.SESSION_ID_HEADER]
66
+ || getCookie(req, constants_1.SESSION_ID_HEADER);
67
+ if (!sessionId) {
68
+ sessionId = (0, crypto_1.randomUUID)();
69
+ }
70
+ // Always refresh the session cookie (sliding expiry)
71
+ let setCookie = `${constants_1.SESSION_ID_HEADER}=${sessionId}; Path=/; Max-Age=${SESSION_COOKIE_MAX_AGE}; SameSite=Lax; Secure`;
72
+ if (cookieDomain)
73
+ setCookie += `; Domain=${cookieDomain}`;
74
+ res.setHeader?.('Set-Cookie', setCookie);
75
+ // Auth: header → cookie (optional, may not be present for background jobs)
76
+ const orgSlug = req.headers[constants_1.ORG_SLUG_HEADER]
77
+ || getCookie(req, constants_1.ORG_SLUG_HEADER);
78
+ const authToken = req.headers[constants_1.AUTH_TOKEN_HEADER]
79
+ || getCookie(req, constants_1.AUTH_TOKEN_HEADER);
80
+ (0, request_context_1.runWithRequestContext)({ requestId, sessionId, orgSlug, authToken }, () => next());
81
+ };
82
+ }