@contractspec/integration.runtime 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ // @bun
2
+ // src/transport/auth-resolver.ts
3
+ import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
4
+ import {
5
+ buildAuthHeaders,
6
+ refreshOAuth2Token,
7
+ isOAuth2TokenExpired
8
+ } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
9
+ async function resolveAuth(options) {
10
+ const authConfig = findAuthConfig(options.supportedAuthMethods, options.activeAuthMethod);
11
+ if (!authConfig) {
12
+ return { headers: {}, tokenRefreshed: false };
13
+ }
14
+ if (authConfig.type === "oauth2" && options.oauth2State) {
15
+ if (isOAuth2TokenExpired(options.oauth2State)) {
16
+ const clientId = options.secrets.clientId ?? "";
17
+ const clientSecret = options.secrets.clientSecret ?? "";
18
+ try {
19
+ const newState = await refreshOAuth2Token(authConfig, options.oauth2State, { clientId, clientSecret }, options.fetchFn);
20
+ const mergedSecrets2 = {
21
+ ...options.secrets,
22
+ accessToken: newState.accessToken
23
+ };
24
+ return {
25
+ headers: buildAuthHeaders(authConfig, mergedSecrets2),
26
+ tokenRefreshed: true,
27
+ updatedOAuth2State: newState
28
+ };
29
+ } catch {
30
+ return {
31
+ headers: buildAuthHeaders(authConfig, options.secrets),
32
+ tokenRefreshed: false
33
+ };
34
+ }
35
+ }
36
+ const mergedSecrets = {
37
+ ...options.secrets,
38
+ accessToken: options.oauth2State.accessToken
39
+ };
40
+ return {
41
+ headers: buildAuthHeaders(authConfig, mergedSecrets),
42
+ tokenRefreshed: false
43
+ };
44
+ }
45
+ return {
46
+ headers: buildAuthHeaders(authConfig, options.secrets),
47
+ tokenRefreshed: false
48
+ };
49
+ }
50
+ export {
51
+ resolveAuth
52
+ };
@@ -0,0 +1,3 @@
1
+ export * from './transport-factory';
2
+ export * from './auth-resolver';
3
+ export * from './version-negotiator';
@@ -0,0 +1,163 @@
1
+ // @bun
2
+ // src/transport/transport-factory.ts
3
+ import { findTransportConfig } from "@contractspec/lib.contracts-integrations/integrations/transport";
4
+
5
+ class RestTransportClient {
6
+ config;
7
+ authHeaders;
8
+ fetchFn;
9
+ type = "rest";
10
+ constructor(config, authHeaders = {}, fetchFn = globalThis.fetch) {
11
+ this.config = config;
12
+ this.authHeaders = authHeaders;
13
+ this.fetchFn = fetchFn;
14
+ }
15
+ async request(method, path, options) {
16
+ const url = new URL(path, this.config.baseUrl ?? "https://localhost");
17
+ if (options?.queryParams) {
18
+ for (const [key, value] of Object.entries(options.queryParams)) {
19
+ url.searchParams.set(key, value);
20
+ }
21
+ }
22
+ const headers = {
23
+ ...this.config.defaultHeaders,
24
+ ...this.authHeaders,
25
+ ...options?.headers
26
+ };
27
+ if (options?.body && !headers["Content-Type"]) {
28
+ headers["Content-Type"] = "application/json";
29
+ }
30
+ const response = await this.fetchFn(url.toString(), {
31
+ method,
32
+ headers,
33
+ body: options?.body ? JSON.stringify(options.body) : undefined,
34
+ signal: options?.signal
35
+ });
36
+ const responseHeaders = {};
37
+ response.headers.forEach((value, key) => {
38
+ responseHeaders[key] = value;
39
+ });
40
+ const data = await response.json().catch(() => null);
41
+ let rateLimitRemaining;
42
+ let rateLimitReset;
43
+ if (this.config.rateLimitHeaders) {
44
+ const remaining = responseHeaders[this.config.rateLimitHeaders.remaining];
45
+ const reset = responseHeaders[this.config.rateLimitHeaders.reset];
46
+ if (remaining)
47
+ rateLimitRemaining = Number(remaining);
48
+ if (reset)
49
+ rateLimitReset = Number(reset);
50
+ }
51
+ return {
52
+ data,
53
+ status: response.status,
54
+ headers: responseHeaders,
55
+ rateLimitRemaining,
56
+ rateLimitReset
57
+ };
58
+ }
59
+ }
60
+ function createTransportClient(transports, targetType, authHeaders = {}, fetchFn) {
61
+ const config = findTransportConfig(transports, targetType);
62
+ if (!config)
63
+ return;
64
+ switch (config.type) {
65
+ case "rest":
66
+ return new RestTransportClient(config, authHeaders, fetchFn);
67
+ case "mcp":
68
+ case "webhook":
69
+ case "sdk":
70
+ return;
71
+ default:
72
+ return;
73
+ }
74
+ }
75
+
76
+ // src/transport/auth-resolver.ts
77
+ import { findAuthConfig } from "@contractspec/lib.contracts-integrations/integrations/auth";
78
+ import {
79
+ buildAuthHeaders,
80
+ refreshOAuth2Token,
81
+ isOAuth2TokenExpired
82
+ } from "@contractspec/lib.contracts-integrations/integrations/auth-helpers";
83
+ async function resolveAuth(options) {
84
+ const authConfig = findAuthConfig(options.supportedAuthMethods, options.activeAuthMethod);
85
+ if (!authConfig) {
86
+ return { headers: {}, tokenRefreshed: false };
87
+ }
88
+ if (authConfig.type === "oauth2" && options.oauth2State) {
89
+ if (isOAuth2TokenExpired(options.oauth2State)) {
90
+ const clientId = options.secrets.clientId ?? "";
91
+ const clientSecret = options.secrets.clientSecret ?? "";
92
+ try {
93
+ const newState = await refreshOAuth2Token(authConfig, options.oauth2State, { clientId, clientSecret }, options.fetchFn);
94
+ const mergedSecrets2 = {
95
+ ...options.secrets,
96
+ accessToken: newState.accessToken
97
+ };
98
+ return {
99
+ headers: buildAuthHeaders(authConfig, mergedSecrets2),
100
+ tokenRefreshed: true,
101
+ updatedOAuth2State: newState
102
+ };
103
+ } catch {
104
+ return {
105
+ headers: buildAuthHeaders(authConfig, options.secrets),
106
+ tokenRefreshed: false
107
+ };
108
+ }
109
+ }
110
+ const mergedSecrets = {
111
+ ...options.secrets,
112
+ accessToken: options.oauth2State.accessToken
113
+ };
114
+ return {
115
+ headers: buildAuthHeaders(authConfig, mergedSecrets),
116
+ tokenRefreshed: false
117
+ };
118
+ }
119
+ return {
120
+ headers: buildAuthHeaders(authConfig, options.secrets),
121
+ tokenRefreshed: false
122
+ };
123
+ }
124
+
125
+ // src/transport/version-negotiator.ts
126
+ import {
127
+ resolveApiVersion,
128
+ isVersionDeprecated
129
+ } from "@contractspec/lib.contracts-integrations/integrations/versioning";
130
+ function negotiateVersion(policy, connectionOverride) {
131
+ if (!policy) {
132
+ return {
133
+ resolvedVersion: undefined,
134
+ deprecated: false,
135
+ versionHeaders: {},
136
+ versionQueryParams: {}
137
+ };
138
+ }
139
+ const version = resolveApiVersion(policy, connectionOverride);
140
+ const deprecated = version ? isVersionDeprecated(policy, version) : false;
141
+ const versionHeaders = {};
142
+ const versionQueryParams = {};
143
+ if (version) {
144
+ if (policy.versionHeader) {
145
+ versionHeaders[policy.versionHeader] = version;
146
+ }
147
+ if (policy.versionQueryParam) {
148
+ versionQueryParams[policy.versionQueryParam] = version;
149
+ }
150
+ }
151
+ return {
152
+ resolvedVersion: version,
153
+ deprecated,
154
+ versionHeaders,
155
+ versionQueryParams
156
+ };
157
+ }
158
+ export {
159
+ resolveAuth,
160
+ negotiateVersion,
161
+ createTransportClient,
162
+ RestTransportClient
163
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Factory for creating transport clients from integration transport configs.
3
+ */
4
+ import type { IntegrationTransportConfig, IntegrationTransportType, RestTransportConfig } from '@contractspec/lib.contracts-integrations/integrations/transport';
5
+ export interface TransportClient {
6
+ readonly type: IntegrationTransportType;
7
+ request<T>(method: string, path: string, options?: TransportRequestOptions): Promise<TransportResponse<T>>;
8
+ }
9
+ export interface TransportRequestOptions {
10
+ body?: unknown;
11
+ headers?: Record<string, string>;
12
+ queryParams?: Record<string, string>;
13
+ timeoutMs?: number;
14
+ signal?: AbortSignal;
15
+ }
16
+ export interface TransportResponse<T> {
17
+ data: T;
18
+ status: number;
19
+ headers: Record<string, string>;
20
+ rateLimitRemaining?: number;
21
+ rateLimitReset?: number;
22
+ }
23
+ export declare class RestTransportClient implements TransportClient {
24
+ private readonly config;
25
+ private readonly authHeaders;
26
+ private readonly fetchFn;
27
+ readonly type: "rest";
28
+ constructor(config: RestTransportConfig, authHeaders?: Record<string, string>, fetchFn?: typeof globalThis.fetch);
29
+ request<T>(method: string, path: string, options?: TransportRequestOptions): Promise<TransportResponse<T>>;
30
+ }
31
+ export declare function createTransportClient(transports: IntegrationTransportConfig[], targetType: IntegrationTransportType, authHeaders?: Record<string, string>, fetchFn?: typeof globalThis.fetch): TransportClient | undefined;
@@ -0,0 +1,78 @@
1
+ // @bun
2
+ // src/transport/transport-factory.ts
3
+ import { findTransportConfig } from "@contractspec/lib.contracts-integrations/integrations/transport";
4
+
5
+ class RestTransportClient {
6
+ config;
7
+ authHeaders;
8
+ fetchFn;
9
+ type = "rest";
10
+ constructor(config, authHeaders = {}, fetchFn = globalThis.fetch) {
11
+ this.config = config;
12
+ this.authHeaders = authHeaders;
13
+ this.fetchFn = fetchFn;
14
+ }
15
+ async request(method, path, options) {
16
+ const url = new URL(path, this.config.baseUrl ?? "https://localhost");
17
+ if (options?.queryParams) {
18
+ for (const [key, value] of Object.entries(options.queryParams)) {
19
+ url.searchParams.set(key, value);
20
+ }
21
+ }
22
+ const headers = {
23
+ ...this.config.defaultHeaders,
24
+ ...this.authHeaders,
25
+ ...options?.headers
26
+ };
27
+ if (options?.body && !headers["Content-Type"]) {
28
+ headers["Content-Type"] = "application/json";
29
+ }
30
+ const response = await this.fetchFn(url.toString(), {
31
+ method,
32
+ headers,
33
+ body: options?.body ? JSON.stringify(options.body) : undefined,
34
+ signal: options?.signal
35
+ });
36
+ const responseHeaders = {};
37
+ response.headers.forEach((value, key) => {
38
+ responseHeaders[key] = value;
39
+ });
40
+ const data = await response.json().catch(() => null);
41
+ let rateLimitRemaining;
42
+ let rateLimitReset;
43
+ if (this.config.rateLimitHeaders) {
44
+ const remaining = responseHeaders[this.config.rateLimitHeaders.remaining];
45
+ const reset = responseHeaders[this.config.rateLimitHeaders.reset];
46
+ if (remaining)
47
+ rateLimitRemaining = Number(remaining);
48
+ if (reset)
49
+ rateLimitReset = Number(reset);
50
+ }
51
+ return {
52
+ data,
53
+ status: response.status,
54
+ headers: responseHeaders,
55
+ rateLimitRemaining,
56
+ rateLimitReset
57
+ };
58
+ }
59
+ }
60
+ function createTransportClient(transports, targetType, authHeaders = {}, fetchFn) {
61
+ const config = findTransportConfig(transports, targetType);
62
+ if (!config)
63
+ return;
64
+ switch (config.type) {
65
+ case "rest":
66
+ return new RestTransportClient(config, authHeaders, fetchFn);
67
+ case "mcp":
68
+ case "webhook":
69
+ case "sdk":
70
+ return;
71
+ default:
72
+ return;
73
+ }
74
+ }
75
+ export {
76
+ createTransportClient,
77
+ RestTransportClient
78
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Negotiates and applies API versioning for integration requests.
3
+ */
4
+ import type { IntegrationVersionPolicy } from '@contractspec/lib.contracts-integrations/integrations/versioning';
5
+ export interface VersionNegotiationResult {
6
+ resolvedVersion: string | undefined;
7
+ deprecated: boolean;
8
+ versionHeaders: Record<string, string>;
9
+ versionQueryParams: Record<string, string>;
10
+ }
11
+ /**
12
+ * Negotiate the API version and produce headers/params to include in requests.
13
+ */
14
+ export declare function negotiateVersion(policy: IntegrationVersionPolicy | undefined, connectionOverride?: string): VersionNegotiationResult;
@@ -0,0 +1,37 @@
1
+ // @bun
2
+ // src/transport/version-negotiator.ts
3
+ import {
4
+ resolveApiVersion,
5
+ isVersionDeprecated
6
+ } from "@contractspec/lib.contracts-integrations/integrations/versioning";
7
+ function negotiateVersion(policy, connectionOverride) {
8
+ if (!policy) {
9
+ return {
10
+ resolvedVersion: undefined,
11
+ deprecated: false,
12
+ versionHeaders: {},
13
+ versionQueryParams: {}
14
+ };
15
+ }
16
+ const version = resolveApiVersion(policy, connectionOverride);
17
+ const deprecated = version ? isVersionDeprecated(policy, version) : false;
18
+ const versionHeaders = {};
19
+ const versionQueryParams = {};
20
+ if (version) {
21
+ if (policy.versionHeader) {
22
+ versionHeaders[policy.versionHeader] = version;
23
+ }
24
+ if (policy.versionQueryParam) {
25
+ versionQueryParams[policy.versionQueryParam] = version;
26
+ }
27
+ }
28
+ return {
29
+ resolvedVersion: version,
30
+ deprecated,
31
+ versionHeaders,
32
+ versionQueryParams
33
+ };
34
+ }
35
+ export {
36
+ negotiateVersion
37
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/integration.runtime",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Runtime integration with secret management",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -31,19 +31,19 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@contractspec/lib.contracts-spec": "3.0.0",
35
- "@contractspec/lib.contracts-integrations": "3.0.0",
36
- "@contractspec/lib.logger": "3.0.0",
34
+ "@contractspec/lib.contracts-spec": "3.2.0",
35
+ "@contractspec/lib.contracts-integrations": "3.2.0",
36
+ "@contractspec/lib.logger": "3.2.0",
37
37
  "@google-cloud/secret-manager": "^6.1.1",
38
38
  "google-gax": "^5.0.0"
39
39
  },
40
40
  "optionalDependencies": {
41
- "pg": "^8.19.0"
41
+ "pg": "^8.20.0"
42
42
  },
43
43
  "devDependencies": {
44
- "@contractspec/tool.typescript": "3.0.0",
44
+ "@contractspec/tool.typescript": "3.2.0",
45
45
  "typescript": "^5.9.3",
46
- "@contractspec/tool.bun": "3.0.0"
46
+ "@contractspec/tool.bun": "3.2.0"
47
47
  },
48
48
  "exports": {
49
49
  ".": {
@@ -201,6 +201,36 @@
201
201
  "bun": "./dist/secrets/provider.js",
202
202
  "node": "./dist/node/secrets/provider.js",
203
203
  "default": "./dist/secrets/provider.js"
204
+ },
205
+ "./transport": {
206
+ "types": "./dist/transport/index.d.ts",
207
+ "bun": "./dist/transport/index.js",
208
+ "node": "./dist/node/transport/index.js",
209
+ "default": "./dist/transport/index.js"
210
+ },
211
+ "./transport/auth-resolver": {
212
+ "types": "./dist/transport/auth-resolver.d.ts",
213
+ "bun": "./dist/transport/auth-resolver.js",
214
+ "node": "./dist/node/transport/auth-resolver.js",
215
+ "default": "./dist/transport/auth-resolver.js"
216
+ },
217
+ "./transport/index": {
218
+ "types": "./dist/transport/index.d.ts",
219
+ "bun": "./dist/transport/index.js",
220
+ "node": "./dist/node/transport/index.js",
221
+ "default": "./dist/transport/index.js"
222
+ },
223
+ "./transport/transport-factory": {
224
+ "types": "./dist/transport/transport-factory.d.ts",
225
+ "bun": "./dist/transport/transport-factory.js",
226
+ "node": "./dist/node/transport/transport-factory.js",
227
+ "default": "./dist/transport/transport-factory.js"
228
+ },
229
+ "./transport/version-negotiator": {
230
+ "types": "./dist/transport/version-negotiator.d.ts",
231
+ "bun": "./dist/transport/version-negotiator.js",
232
+ "node": "./dist/node/transport/version-negotiator.js",
233
+ "default": "./dist/transport/version-negotiator.js"
204
234
  }
205
235
  },
206
236
  "publishConfig": {
@@ -361,6 +391,36 @@
361
391
  "bun": "./dist/secrets/provider.js",
362
392
  "node": "./dist/node/secrets/provider.js",
363
393
  "default": "./dist/secrets/provider.js"
394
+ },
395
+ "./transport": {
396
+ "types": "./dist/transport/index.d.ts",
397
+ "bun": "./dist/transport/index.js",
398
+ "node": "./dist/node/transport/index.js",
399
+ "default": "./dist/transport/index.js"
400
+ },
401
+ "./transport/auth-resolver": {
402
+ "types": "./dist/transport/auth-resolver.d.ts",
403
+ "bun": "./dist/transport/auth-resolver.js",
404
+ "node": "./dist/node/transport/auth-resolver.js",
405
+ "default": "./dist/transport/auth-resolver.js"
406
+ },
407
+ "./transport/index": {
408
+ "types": "./dist/transport/index.d.ts",
409
+ "bun": "./dist/transport/index.js",
410
+ "node": "./dist/node/transport/index.js",
411
+ "default": "./dist/transport/index.js"
412
+ },
413
+ "./transport/transport-factory": {
414
+ "types": "./dist/transport/transport-factory.d.ts",
415
+ "bun": "./dist/transport/transport-factory.js",
416
+ "node": "./dist/node/transport/transport-factory.js",
417
+ "default": "./dist/transport/transport-factory.js"
418
+ },
419
+ "./transport/version-negotiator": {
420
+ "types": "./dist/transport/version-negotiator.d.ts",
421
+ "bun": "./dist/transport/version-negotiator.js",
422
+ "node": "./dist/node/transport/version-negotiator.js",
423
+ "default": "./dist/transport/version-negotiator.js"
364
424
  }
365
425
  },
366
426
  "registry": "https://registry.npmjs.org/"