@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.
Files changed (39) hide show
  1. package/dist/health.d.ts +14 -18
  2. package/dist/health.d.ts.map +1 -1
  3. package/dist/health.js +71 -68
  4. package/dist/index.d.ts +4 -8
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +828 -9
  7. package/dist/node/health.js +72 -0
  8. package/dist/node/index.js +827 -0
  9. package/dist/node/runtime.js +208 -0
  10. package/dist/node/secrets/env-secret-provider.js +158 -0
  11. package/dist/node/secrets/gcp-secret-manager.js +346 -0
  12. package/dist/node/secrets/index.js +549 -0
  13. package/dist/node/secrets/manager.js +182 -0
  14. package/dist/node/secrets/provider.js +73 -0
  15. package/dist/runtime.d.ts +86 -90
  16. package/dist/runtime.d.ts.map +1 -1
  17. package/dist/runtime.js +204 -181
  18. package/dist/secrets/env-secret-provider.d.ts +20 -23
  19. package/dist/secrets/env-secret-provider.d.ts.map +1 -1
  20. package/dist/secrets/env-secret-provider.js +157 -80
  21. package/dist/secrets/gcp-secret-manager.d.ts +25 -28
  22. package/dist/secrets/gcp-secret-manager.d.ts.map +1 -1
  23. package/dist/secrets/gcp-secret-manager.js +339 -222
  24. package/dist/secrets/index.d.ts +5 -5
  25. package/dist/secrets/index.d.ts.map +1 -0
  26. package/dist/secrets/index.js +549 -5
  27. package/dist/secrets/manager.d.ts +32 -35
  28. package/dist/secrets/manager.d.ts.map +1 -1
  29. package/dist/secrets/manager.js +180 -101
  30. package/dist/secrets/provider.d.ts +42 -45
  31. package/dist/secrets/provider.d.ts.map +1 -1
  32. package/dist/secrets/provider.js +69 -54
  33. package/package.json +76 -30
  34. package/dist/health.js.map +0 -1
  35. package/dist/runtime.js.map +0 -1
  36. package/dist/secrets/env-secret-provider.js.map +0 -1
  37. package/dist/secrets/gcp-secret-manager.js.map +0 -1
  38. package/dist/secrets/manager.js.map +0 -1
  39. package/dist/secrets/provider.js.map +0 -1
package/dist/runtime.js CHANGED
@@ -1,186 +1,209 @@
1
- import { performance } from "node:perf_hooks";
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
- //#region src/runtime.ts
4
- const DEFAULT_MAX_ATTEMPTS = 3;
5
- const DEFAULT_BACKOFF_MS = 250;
6
- var IntegrationCallGuard = class {
7
- telemetry;
8
- maxAttempts;
9
- backoffMs;
10
- shouldRetry;
11
- sleep;
12
- now;
13
- constructor(secretProvider, options = {}) {
14
- this.secretProvider = secretProvider;
15
- this.telemetry = options.telemetry;
16
- this.maxAttempts = Math.max(1, options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS);
17
- this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;
18
- this.shouldRetry = options.shouldRetry ?? ((error) => typeof error === "object" && error !== null && "retryable" in error && Boolean(error.retryable));
19
- this.sleep = options.sleep ?? ((ms) => ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms)));
20
- this.now = options.now ?? (() => /* @__PURE__ */ new Date());
21
- }
22
- async executeWithGuards(slotId, operation, _input, resolvedConfig, executor) {
23
- const integration = this.findIntegration(slotId, resolvedConfig);
24
- if (!integration) return this.failure({
25
- tenantId: resolvedConfig.tenantId,
26
- appId: resolvedConfig.appId,
27
- environment: resolvedConfig.environment,
28
- blueprintName: resolvedConfig.blueprintName,
29
- blueprintVersion: resolvedConfig.blueprintVersion,
30
- configVersion: resolvedConfig.configVersion,
31
- slotId,
32
- operation
33
- }, void 0, {
34
- code: "SLOT_NOT_BOUND",
35
- message: `Integration slot "${slotId}" is not bound for tenant "${resolvedConfig.tenantId}".`,
36
- retryable: false
37
- }, 0);
38
- const status = integration.connection.status;
39
- if (status === "disconnected" || status === "error") return this.failure(this.makeContext(slotId, operation, resolvedConfig), integration, {
40
- code: "CONNECTION_NOT_READY",
41
- message: `Integration connection "${integration.connection.meta.label}" is in status "${status}".`,
42
- retryable: false
43
- }, 0);
44
- const secrets = await this.fetchSecrets(integration.connection);
45
- let attempt = 0;
46
- const started = performance.now();
47
- while (attempt < this.maxAttempts) {
48
- attempt += 1;
49
- try {
50
- const data = await executor(integration.connection, secrets);
51
- const duration = performance.now() - started;
52
- this.emitTelemetry(this.makeContext(slotId, operation, resolvedConfig), integration, "success", duration);
53
- return {
54
- success: true,
55
- data,
56
- metadata: {
57
- latencyMs: duration,
58
- connectionId: integration.connection.meta.id,
59
- ownershipMode: integration.connection.ownershipMode,
60
- attempts: attempt
61
- }
62
- };
63
- } catch (error) {
64
- const duration = performance.now() - started;
65
- this.emitTelemetry(this.makeContext(slotId, operation, resolvedConfig), integration, "error", duration, this.errorCodeFor(error), error instanceof Error ? error.message : String(error));
66
- const retryable = this.shouldRetry(error, attempt);
67
- if (!retryable || attempt >= this.maxAttempts) return {
68
- success: false,
69
- error: {
70
- code: this.errorCodeFor(error),
71
- message: error instanceof Error ? error.message : String(error),
72
- retryable,
73
- cause: error
74
- },
75
- metadata: {
76
- latencyMs: duration,
77
- connectionId: integration.connection.meta.id,
78
- ownershipMode: integration.connection.ownershipMode,
79
- attempts: attempt
80
- }
81
- };
82
- await this.sleep(this.backoffMs);
83
- }
84
- }
85
- return {
86
- success: false,
87
- error: {
88
- code: "UNKNOWN_ERROR",
89
- message: "Integration call failed after retries.",
90
- retryable: false
91
- },
92
- metadata: {
93
- latencyMs: performance.now() - started,
94
- connectionId: integration.connection.meta.id,
95
- ownershipMode: integration.connection.ownershipMode,
96
- attempts: this.maxAttempts
97
- }
98
- };
99
- }
100
- findIntegration(slotId, config) {
101
- return config.integrations.find((integration) => integration.slot.slotId === slotId);
102
- }
103
- async fetchSecrets(connection) {
104
- if (!this.secretProvider.canHandle(connection.secretRef)) throw new Error(`Secret provider "${this.secretProvider.id}" cannot handle reference "${connection.secretRef}".`);
105
- const secret = await this.secretProvider.getSecret(connection.secretRef);
106
- return this.parseSecret(secret);
107
- }
108
- parseSecret(secret) {
109
- const text = new TextDecoder().decode(secret.data);
110
- try {
111
- const parsed = JSON.parse(text);
112
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
113
- const entries = Object.entries(parsed).filter(([, value]) => typeof value === "string" || typeof value === "number" || typeof value === "boolean");
114
- return Object.fromEntries(entries.map(([key, value]) => [key, String(value)]));
115
- }
116
- } catch {}
117
- return { secret: text };
118
- }
119
- emitTelemetry(context, integration, status, durationMs, errorCode, errorMessage) {
120
- if (!this.telemetry || !integration) return;
121
- this.telemetry.record({
122
- tenantId: context.tenantId,
123
- appId: context.appId,
124
- environment: context.environment,
125
- slotId: context.slotId,
126
- integrationKey: integration.connection.meta.integrationKey,
127
- integrationVersion: integration.connection.meta.integrationVersion,
128
- connectionId: integration.connection.meta.id,
129
- status,
130
- durationMs,
131
- errorCode,
132
- errorMessage,
133
- occurredAt: this.now(),
134
- metadata: {
135
- blueprint: `${context.blueprintName}.v${context.blueprintVersion}`,
136
- configVersion: context.configVersion,
137
- operation: context.operation
138
- }
139
- });
140
- }
141
- failure(context, integration, error, attempts) {
142
- if (integration) this.emitTelemetry(context, integration, "error", 0, error.code, error.message);
143
- return {
144
- success: false,
145
- error,
146
- metadata: {
147
- latencyMs: 0,
148
- connectionId: integration?.connection.meta.id ?? "unknown",
149
- ownershipMode: integration?.connection.ownershipMode ?? "managed",
150
- attempts
151
- }
152
- };
153
- }
154
- makeContext(slotId, operation, config) {
155
- return {
156
- tenantId: config.tenantId,
157
- appId: config.appId,
158
- environment: config.environment,
159
- blueprintName: config.blueprintName,
160
- blueprintVersion: config.blueprintVersion,
161
- configVersion: config.configVersion,
162
- slotId,
163
- operation
164
- };
165
- }
166
- errorCodeFor(error) {
167
- if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") return error.code;
168
- return "PROVIDER_ERROR";
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
- const status = integration.connection.status;
173
- if (status === "disconnected" || status === "error") throw new Error(`Integration connection "${integration.connection.meta.label}" is in status "${status}".`);
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
- switch (status) {
177
- case "connected": return "connected";
178
- case "disconnected": return "disconnected";
179
- case "error": return "error";
180
- default: return "unknown";
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
- //#endregion
185
- export { IntegrationCallGuard, connectionStatusLabel, ensureConnectionReady };
186
- //# sourceMappingURL=runtime.js.map
205
+ export {
206
+ ensureConnectionReady,
207
+ connectionStatusLabel,
208
+ IntegrationCallGuard
209
+ };
@@ -1,32 +1,29 @@
1
- import { SecretProvider, SecretReference, SecretRotationResult, SecretValue, SecretWritePayload } from "./provider.js";
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
- * Optional map to alias secret references to environment variable names.
7
- * Useful when referencing secrets from other providers (e.g. gcp://...)
8
- * while still allowing local overrides.
9
- */
10
- aliases?: Record<string, string>;
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
- readonly id = "env";
19
- private readonly aliases;
20
- constructor(options?: EnvSecretProviderOptions);
21
- canHandle(reference: SecretReference): boolean;
22
- getSecret(reference: SecretReference): Promise<SecretValue>;
23
- setSecret(reference: SecretReference, _payload: SecretWritePayload): Promise<SecretRotationResult>;
24
- rotateSecret(reference: SecretReference, _payload: SecretWritePayload): Promise<SecretRotationResult>;
25
- deleteSecret(reference: SecretReference): Promise<void>;
26
- private resolveEnvKey;
27
- private deriveEnvKey;
28
- private forbiddenError;
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
- //#endregion
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","names":[],"sources":["../../src/secrets/env-secret-provider.ts"],"mappings":";;;UASU,wBAAA;;AAHU;;;;EASlB,OAAA,GAAU,MAAA;AAAA;;;;;;cAQC,iBAAA,YAA6B,cAAA;EAAA,SAC/B,EAAA;EAAA,iBAEQ,OAAA;cAEL,OAAA,GAAS,wBAAA;EAIrB,SAAA,CAAU,SAAA,EAAW,eAAA;EAKf,SAAA,CAAU,SAAA,EAAW,eAAA,GAAkB,OAAA,CAAQ,WAAA;EAgC/C,SAAA,CACJ,SAAA,EAAW,eAAA,EACX,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,oBAAA;EAIL,YAAA,CACJ,SAAA,EAAW,eAAA,EACX,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,oBAAA;EAIL,YAAA,CAAa,SAAA,EAAW,eAAA,GAAkB,OAAA;EAAA,QAIxC,aAAA;EAAA,QA6BA,YAAA;EAAA,QAcA,cAAA;AAAA"}
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"}