@contractspec/integration.runtime 1.57.0 → 1.58.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.
- package/dist/health.d.ts +14 -18
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +71 -68
- package/dist/index.d.ts +4 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +828 -9
- package/dist/node/health.js +72 -0
- package/dist/node/index.js +827 -0
- package/dist/node/runtime.js +208 -0
- package/dist/node/secrets/env-secret-provider.js +158 -0
- package/dist/node/secrets/gcp-secret-manager.js +346 -0
- package/dist/node/secrets/index.js +549 -0
- package/dist/node/secrets/manager.js +182 -0
- package/dist/node/secrets/provider.js +73 -0
- package/dist/runtime.d.ts +86 -90
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +204 -181
- package/dist/secrets/env-secret-provider.d.ts +20 -23
- package/dist/secrets/env-secret-provider.d.ts.map +1 -1
- package/dist/secrets/env-secret-provider.js +157 -80
- package/dist/secrets/gcp-secret-manager.d.ts +25 -28
- package/dist/secrets/gcp-secret-manager.d.ts.map +1 -1
- package/dist/secrets/gcp-secret-manager.js +339 -222
- package/dist/secrets/index.d.ts +5 -5
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +549 -5
- package/dist/secrets/manager.d.ts +32 -35
- package/dist/secrets/manager.d.ts.map +1 -1
- package/dist/secrets/manager.js +180 -101
- package/dist/secrets/provider.d.ts +42 -45
- package/dist/secrets/provider.d.ts.map +1 -1
- package/dist/secrets/provider.js +69 -54
- package/package.json +76 -30
- package/dist/health.js.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/secrets/env-secret-provider.js.map +0 -1
- package/dist/secrets/gcp-secret-manager.js.map +0 -1
- package/dist/secrets/manager.js.map +0 -1
- package/dist/secrets/provider.js.map +0 -1
package/dist/runtime.js
CHANGED
|
@@ -1,186 +1,209 @@
|
|
|
1
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/runtime.ts
|
|
3
|
+
import { performance } from "perf_hooks";
|
|
4
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
5
|
+
var DEFAULT_BACKOFF_MS = 250;
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
7
|
+
class IntegrationCallGuard {
|
|
8
|
+
secretProvider;
|
|
9
|
+
telemetry;
|
|
10
|
+
maxAttempts;
|
|
11
|
+
backoffMs;
|
|
12
|
+
shouldRetry;
|
|
13
|
+
sleep;
|
|
14
|
+
now;
|
|
15
|
+
constructor(secretProvider, options = {}) {
|
|
16
|
+
this.secretProvider = secretProvider;
|
|
17
|
+
this.telemetry = options.telemetry;
|
|
18
|
+
this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
|
|
19
|
+
this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;
|
|
20
|
+
this.shouldRetry = options.shouldRetry ?? ((error) => typeof error === "object" && error !== null && ("retryable" in error) && Boolean(error.retryable));
|
|
21
|
+
this.sleep = options.sleep ?? ((ms) => ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms)));
|
|
22
|
+
this.now = options.now ?? (() => new Date);
|
|
23
|
+
}
|
|
24
|
+
async executeWithGuards(slotId, operation, _input, resolvedConfig, executor) {
|
|
25
|
+
const integration = this.findIntegration(slotId, resolvedConfig);
|
|
26
|
+
if (!integration) {
|
|
27
|
+
return this.failure({
|
|
28
|
+
tenantId: resolvedConfig.tenantId,
|
|
29
|
+
appId: resolvedConfig.appId,
|
|
30
|
+
environment: resolvedConfig.environment,
|
|
31
|
+
blueprintName: resolvedConfig.blueprintName,
|
|
32
|
+
blueprintVersion: resolvedConfig.blueprintVersion,
|
|
33
|
+
configVersion: resolvedConfig.configVersion,
|
|
34
|
+
slotId,
|
|
35
|
+
operation
|
|
36
|
+
}, undefined, {
|
|
37
|
+
code: "SLOT_NOT_BOUND",
|
|
38
|
+
message: `Integration slot "${slotId}" is not bound for tenant "${resolvedConfig.tenantId}".`,
|
|
39
|
+
retryable: false
|
|
40
|
+
}, 0);
|
|
41
|
+
}
|
|
42
|
+
const status = integration.connection.status;
|
|
43
|
+
if (status === "disconnected" || status === "error") {
|
|
44
|
+
return this.failure(this.makeContext(slotId, operation, resolvedConfig), integration, {
|
|
45
|
+
code: "CONNECTION_NOT_READY",
|
|
46
|
+
message: `Integration connection "${integration.connection.meta.label}" is in status "${status}".`,
|
|
47
|
+
retryable: false
|
|
48
|
+
}, 0);
|
|
49
|
+
}
|
|
50
|
+
const secrets = await this.fetchSecrets(integration.connection);
|
|
51
|
+
let attempt = 0;
|
|
52
|
+
const started = performance.now();
|
|
53
|
+
while (attempt < this.maxAttempts) {
|
|
54
|
+
attempt += 1;
|
|
55
|
+
try {
|
|
56
|
+
const data = await executor(integration.connection, secrets);
|
|
57
|
+
const duration = performance.now() - started;
|
|
58
|
+
this.emitTelemetry(this.makeContext(slotId, operation, resolvedConfig), integration, "success", duration);
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
data,
|
|
62
|
+
metadata: {
|
|
63
|
+
latencyMs: duration,
|
|
64
|
+
connectionId: integration.connection.meta.id,
|
|
65
|
+
ownershipMode: integration.connection.ownershipMode,
|
|
66
|
+
attempts: attempt
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const duration = performance.now() - started;
|
|
71
|
+
this.emitTelemetry(this.makeContext(slotId, operation, resolvedConfig), integration, "error", duration, this.errorCodeFor(error), error instanceof Error ? error.message : String(error));
|
|
72
|
+
const retryable = this.shouldRetry(error, attempt);
|
|
73
|
+
if (!retryable || attempt >= this.maxAttempts) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
error: {
|
|
77
|
+
code: this.errorCodeFor(error),
|
|
78
|
+
message: error instanceof Error ? error.message : String(error),
|
|
79
|
+
retryable,
|
|
80
|
+
cause: error
|
|
81
|
+
},
|
|
82
|
+
metadata: {
|
|
83
|
+
latencyMs: duration,
|
|
84
|
+
connectionId: integration.connection.meta.id,
|
|
85
|
+
ownershipMode: integration.connection.ownershipMode,
|
|
86
|
+
attempts: attempt
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
await this.sleep(this.backoffMs);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: {
|
|
96
|
+
code: "UNKNOWN_ERROR",
|
|
97
|
+
message: "Integration call failed after retries.",
|
|
98
|
+
retryable: false
|
|
99
|
+
},
|
|
100
|
+
metadata: {
|
|
101
|
+
latencyMs: performance.now() - started,
|
|
102
|
+
connectionId: integration.connection.meta.id,
|
|
103
|
+
ownershipMode: integration.connection.ownershipMode,
|
|
104
|
+
attempts: this.maxAttempts
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
findIntegration(slotId, config) {
|
|
109
|
+
return config.integrations.find((integration) => integration.slot.slotId === slotId);
|
|
110
|
+
}
|
|
111
|
+
async fetchSecrets(connection) {
|
|
112
|
+
if (!this.secretProvider.canHandle(connection.secretRef)) {
|
|
113
|
+
throw new Error(`Secret provider "${this.secretProvider.id}" cannot handle reference "${connection.secretRef}".`);
|
|
114
|
+
}
|
|
115
|
+
const secret = await this.secretProvider.getSecret(connection.secretRef);
|
|
116
|
+
return this.parseSecret(secret);
|
|
117
|
+
}
|
|
118
|
+
parseSecret(secret) {
|
|
119
|
+
const text = new TextDecoder().decode(secret.data);
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(text);
|
|
122
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
123
|
+
const entries = Object.entries(parsed).filter(([, value]) => typeof value === "string" || typeof value === "number" || typeof value === "boolean");
|
|
124
|
+
return Object.fromEntries(entries.map(([key, value]) => [key, String(value)]));
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
return { secret: text };
|
|
128
|
+
}
|
|
129
|
+
emitTelemetry(context, integration, status, durationMs, errorCode, errorMessage) {
|
|
130
|
+
if (!this.telemetry || !integration)
|
|
131
|
+
return;
|
|
132
|
+
this.telemetry.record({
|
|
133
|
+
tenantId: context.tenantId,
|
|
134
|
+
appId: context.appId,
|
|
135
|
+
environment: context.environment,
|
|
136
|
+
slotId: context.slotId,
|
|
137
|
+
integrationKey: integration.connection.meta.integrationKey,
|
|
138
|
+
integrationVersion: integration.connection.meta.integrationVersion,
|
|
139
|
+
connectionId: integration.connection.meta.id,
|
|
140
|
+
status,
|
|
141
|
+
durationMs,
|
|
142
|
+
errorCode,
|
|
143
|
+
errorMessage,
|
|
144
|
+
occurredAt: this.now(),
|
|
145
|
+
metadata: {
|
|
146
|
+
blueprint: `${context.blueprintName}.v${context.blueprintVersion}`,
|
|
147
|
+
configVersion: context.configVersion,
|
|
148
|
+
operation: context.operation
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
failure(context, integration, error, attempts) {
|
|
153
|
+
if (integration) {
|
|
154
|
+
this.emitTelemetry(context, integration, "error", 0, error.code, error.message);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error,
|
|
159
|
+
metadata: {
|
|
160
|
+
latencyMs: 0,
|
|
161
|
+
connectionId: integration?.connection.meta.id ?? "unknown",
|
|
162
|
+
ownershipMode: integration?.connection.ownershipMode ?? "managed",
|
|
163
|
+
attempts
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
makeContext(slotId, operation, config) {
|
|
168
|
+
return {
|
|
169
|
+
tenantId: config.tenantId,
|
|
170
|
+
appId: config.appId,
|
|
171
|
+
environment: config.environment,
|
|
172
|
+
blueprintName: config.blueprintName,
|
|
173
|
+
blueprintVersion: config.blueprintVersion,
|
|
174
|
+
configVersion: config.configVersion,
|
|
175
|
+
slotId,
|
|
176
|
+
operation
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
errorCodeFor(error) {
|
|
180
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
|
|
181
|
+
return error.code;
|
|
182
|
+
}
|
|
183
|
+
return "PROVIDER_ERROR";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
171
186
|
function ensureConnectionReady(integration) {
|
|
172
|
-
|
|
173
|
-
|
|
187
|
+
const status = integration.connection.status;
|
|
188
|
+
if (status === "disconnected" || status === "error") {
|
|
189
|
+
throw new Error(`Integration connection "${integration.connection.meta.label}" is in status "${status}".`);
|
|
190
|
+
}
|
|
174
191
|
}
|
|
175
192
|
function connectionStatusLabel(status) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
switch (status) {
|
|
194
|
+
case "connected":
|
|
195
|
+
return "connected";
|
|
196
|
+
case "disconnected":
|
|
197
|
+
return "disconnected";
|
|
198
|
+
case "error":
|
|
199
|
+
return "error";
|
|
200
|
+
case "unknown":
|
|
201
|
+
default:
|
|
202
|
+
return "unknown";
|
|
203
|
+
}
|
|
182
204
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
205
|
+
export {
|
|
206
|
+
ensureConnectionReady,
|
|
207
|
+
connectionStatusLabel,
|
|
208
|
+
IntegrationCallGuard
|
|
209
|
+
};
|
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
import { SecretProvider, SecretReference, SecretRotationResult, SecretValue, SecretWritePayload } from
|
|
2
|
-
|
|
3
|
-
//#region src/secrets/env-secret-provider.d.ts
|
|
1
|
+
import type { SecretProvider, SecretReference, SecretRotationResult, SecretValue, SecretWritePayload } from './provider';
|
|
4
2
|
interface EnvSecretProviderOptions {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Optional map to alias secret references to environment variable names.
|
|
5
|
+
* Useful when referencing secrets from other providers (e.g. gcp://...)
|
|
6
|
+
* while still allowing local overrides.
|
|
7
|
+
*/
|
|
8
|
+
aliases?: Record<string, string>;
|
|
11
9
|
}
|
|
12
10
|
/**
|
|
13
11
|
* Environment-variable backed secret provider. Read-only by design.
|
|
14
12
|
* Allows overriding other secret providers by deriving environment variable
|
|
15
13
|
* names from secret references (or by using explicit aliases).
|
|
16
14
|
*/
|
|
17
|
-
declare class EnvSecretProvider implements SecretProvider {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
export declare class EnvSecretProvider implements SecretProvider {
|
|
16
|
+
readonly id = "env";
|
|
17
|
+
private readonly aliases;
|
|
18
|
+
constructor(options?: EnvSecretProviderOptions);
|
|
19
|
+
canHandle(reference: SecretReference): boolean;
|
|
20
|
+
getSecret(reference: SecretReference): Promise<SecretValue>;
|
|
21
|
+
setSecret(reference: SecretReference, _payload: SecretWritePayload): Promise<SecretRotationResult>;
|
|
22
|
+
rotateSecret(reference: SecretReference, _payload: SecretWritePayload): Promise<SecretRotationResult>;
|
|
23
|
+
deleteSecret(reference: SecretReference): Promise<void>;
|
|
24
|
+
private resolveEnvKey;
|
|
25
|
+
private deriveEnvKey;
|
|
26
|
+
private forbiddenError;
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
export { EnvSecretProvider };
|
|
28
|
+
export {};
|
|
32
29
|
//# sourceMappingURL=env-secret-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-secret-provider.d.ts","
|
|
1
|
+
{"version":3,"file":"env-secret-provider.d.ts","sourceRoot":"","sources":["../../src/secrets/env-secret-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAGpB,UAAU,wBAAwB;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,EAAE,SAAS;IAEpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAErC,OAAO,GAAE,wBAA6B;IAIlD,SAAS,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO;IAKxC,SAAS,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAgC3D,SAAS,CACb,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAI1B,YAAY,CAChB,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,oBAAoB,CAAC;IAI1B,YAAY,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,cAAc;CAWvB"}
|