@superdurable/connectors-sdk 0.1.1 → 0.3.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,316 @@
1
+ import {
2
+ validateMutationDefinition,
3
+ validateQueryDefinition
4
+ } from "./chunk-32YJXBE4.js";
5
+ import {
6
+ createCall
7
+ } from "./chunk-QDMBAWXD.js";
8
+ import {
9
+ CallIDSchema,
10
+ ConnectionRefSchema,
11
+ ConnectorConfigurationUISchema,
12
+ DefectBranchID,
13
+ OperationRefSchema,
14
+ ReceiptSchema,
15
+ UncertainBranchID,
16
+ defaultConnectorStepExecuteRetryPolicy
17
+ } from "./chunk-BQGYGCOQ.js";
18
+ import {
19
+ FailureSchema,
20
+ RetryError
21
+ } from "./chunk-IHG6ADQG.js";
22
+
23
+ // src/run_operation.ts
24
+ async function runQuery(operation, call, input) {
25
+ if (!operation) return defectResult(void 0, call, "connector query is required");
26
+ const definition = operation.definition();
27
+ try {
28
+ validateQueryDefinition(definition);
29
+ } catch (error) {
30
+ return defectResult(definition, call, `invalid query definition: ${safeErrorMessage(error)}`);
31
+ }
32
+ const invalidCall = validateCall(call, definition.operation);
33
+ if (invalidCall) return defectResult(definition, call, invalidCall);
34
+ const attempt = await operation.invoke(call, input);
35
+ return resultFromAttempt(definition, call, attempt, "query");
36
+ }
37
+ async function runMutation(operation, call, input) {
38
+ if (!operation) return defectResult(void 0, call, "connector mutation is required");
39
+ const definition = operation.definition();
40
+ try {
41
+ validateMutationDefinition(definition);
42
+ } catch (error) {
43
+ return defectResult(definition, call, `invalid mutation definition: ${safeErrorMessage(error)}`);
44
+ }
45
+ const invalidCall = validateCall(call, definition.operation);
46
+ if (invalidCall) return defectResult(definition, call, invalidCall);
47
+ const chosenKey = operation.idempotencyKey(call.id, input) || call.id;
48
+ if (!chosenKey.trim()) return defectResult(definition, call, "mutation returned an invalid idempotency key");
49
+ const mutationCall = { ...call, context: call.context, idempotencyKey: chosenKey };
50
+ Object.defineProperty(mutationCall, "context", { value: call.context, enumerable: false });
51
+ for (const method of ["reportProgress", "writeText", "hasProgressStream", "hasTextStream"]) Object.defineProperty(mutationCall, method, { value: call[method], enumerable: false });
52
+ const attempt = await operation.invoke(mutationCall, input);
53
+ return resultFromAttempt(definition, mutationCall, attempt, "mutation");
54
+ }
55
+ function resultFromAttempt(definition, call, attempt, operationKind) {
56
+ if (!attempt || typeof attempt !== "object") return defectResult(definition, call, `${operationKind} returned an invalid attempt`);
57
+ if (attempt.kind === "retry") {
58
+ const failure = FailureSchema.safeParse(attempt.failure);
59
+ if (!failure.success || !Number.isSafeInteger(attempt.retryAfterMs) || attempt.retryAfterMs < 0) return defectResult(definition, call, `${operationKind} returned an invalid retry attempt`);
60
+ throw new RetryError(failure.data, attempt.retryAfterMs);
61
+ }
62
+ if (attempt.kind === "branch" || operationKind === "mutation" && attempt.kind === "uncertain") {
63
+ const branch = attempt.kind === "uncertain" ? UncertainBranchID : attempt.branch;
64
+ if (!definition.branches.some((declared) => declared.id === branch)) {
65
+ return defectResult(definition, call, attempt.kind === "uncertain" ? "mutation returned uncertainty without declaring the uncertain branch" : `${operationKind} returned an unknown branch`);
66
+ }
67
+ const failure = "failure" in attempt && attempt.failure !== void 0 ? FailureSchema.safeParse(attempt.failure) : void 0;
68
+ if (failure && !failure.success) return defectResult(definition, call, `${operationKind} returned an invalid failure`);
69
+ if (attempt.kind === "uncertain" && !failure) return defectResult(definition, call, "mutation returned an invalid uncertainty failure");
70
+ let receipt;
71
+ try {
72
+ receipt = completeReceipt(attempt.receipt, call);
73
+ } catch (error) {
74
+ return defectResult(definition, call, safeErrorMessage(error));
75
+ }
76
+ const result = { branch, value: attempt.value, receipt };
77
+ if (failure?.success) result.failure = failure.data;
78
+ return result;
79
+ }
80
+ return defectResult(definition, call, `${operationKind} returned an invalid attempt`);
81
+ }
82
+ function completeReceipt(draft, call) {
83
+ if (draft.callId && draft.callId !== call.id) throw new TypeError("operation returned a receipt for another call");
84
+ if (draft.idempotencyKey && draft.idempotencyKey !== call.idempotencyKey) throw new TypeError("operation returned a receipt with another idempotency key");
85
+ return ReceiptSchema.parse({ ...draft, callId: call.id, ...call.idempotencyKey ? { idempotencyKey: call.idempotencyKey } : {}, provider: draft.provider || call.operation.connectorId, observedAt: draft.observedAt && draft.observedAt !== "0001-01-01T00:00:00Z" ? draft.observedAt : (/* @__PURE__ */ new Date()).toISOString() });
86
+ }
87
+ function validateCall(call, operation) {
88
+ if (!call) return "connector call is required";
89
+ if (!CallIDSchema.safeParse(call.id).success) return "call ID must be a UUID";
90
+ if (!ConnectionRefSchema.safeParse(call.connection).success) return "connection provider and name are required";
91
+ if (!OperationRefSchema.safeParse(call.operation).success || call.operation.connectorId !== operation.connectorId || call.operation.operationId !== operation.operationId) return "connector call operation does not match the definition";
92
+ return void 0;
93
+ }
94
+ function defectResult(definition, call, message) {
95
+ const operation = definition?.operation;
96
+ const failure = { kind: "LOCAL_DEFECT", provider: operation?.connectorId || "connector", operation: operation?.operationId || "invoke", message };
97
+ const receipt = call && CallIDSchema.safeParse(call.id).success ? completeReceipt({}, call) : void 0;
98
+ return { branch: DefectBranchID, value: void 0, receipt, failure };
99
+ }
100
+ function safeErrorMessage(error) {
101
+ return error instanceof Error ? error.message : "invalid value";
102
+ }
103
+
104
+ // src/step.ts
105
+ import { StepRef, forceFail, goTo, retryAfter } from "@superdurable/dex";
106
+ function goToBranch(branch, target) {
107
+ return target ? { branch, target } : { branch };
108
+ }
109
+ function stepRef(stepType) {
110
+ return new StepRef(stepType);
111
+ }
112
+ var QueryStep = class {
113
+ inputCodec;
114
+ #configuration;
115
+ #options;
116
+ #ui;
117
+ #targets;
118
+ #optional;
119
+ #identity;
120
+ /** Validate static wiring before registering the Step in a Flow. */
121
+ constructor(configuration) {
122
+ if (!configuration?.operation) throw new TypeError("query operation is required");
123
+ const definition = configuration.operation.definition();
124
+ validateQueryDefinition(definition);
125
+ validateFactoryConfiguration(configuration);
126
+ const routes = validateBranchTargets(definition.branches, configuration.branches);
127
+ this.#configuration = configuration;
128
+ this.#options = mergeStepOptions(definition.stepDefaults, configuration.stepOptionsOverride);
129
+ this.#ui = ConnectorConfigurationUISchema.parse(configuration.configurationUI ?? { units: [] });
130
+ this.#targets = routes.targets;
131
+ this.#optional = routes.optional;
132
+ this.#identity = { operation: definition.operation, connection: configuration.connection };
133
+ if (configuration.inputCodec) this.inputCodec = configuration.inputCodec;
134
+ }
135
+ /** Stable Dex Step type. */
136
+ getStepType() {
137
+ return this.#configuration.stepType;
138
+ }
139
+ /** Isolated Execute options, including connector retry defaults. */
140
+ getStepOptions() {
141
+ return cloneStepOptions(this.#options);
142
+ }
143
+ /** Group metadata for Connector Studio. */
144
+ annotations() {
145
+ return { ...this.#configuration.annotations };
146
+ }
147
+ /** Isolated connector UI composition. */
148
+ configurationUI() {
149
+ return structuredClone(this.#ui);
150
+ }
151
+ /** Invoke, persist the optional terminal Attribute, and route the selected branch. */
152
+ async execute(context, input) {
153
+ const operationInput = this.#configuration.mapToOperationInput(input);
154
+ const call = createCall(context, this.#identity.connection, this.#identity.operation, {
155
+ ...this.#configuration.progressStream ? { progressStream: this.#configuration.progressStream } : {},
156
+ ...this.#configuration.textStream ? { textStream: this.#configuration.textStream } : {},
157
+ ...this.#configuration.textOptions ? { textOptions: this.#configuration.textOptions } : {}
158
+ });
159
+ let result;
160
+ try {
161
+ result = await runQuery(this.#configuration.operation, call, operationInput);
162
+ } catch (error) {
163
+ throwDexRetry(error);
164
+ }
165
+ if (this.#configuration.resultAttribute) context.setAttribute(this.#configuration.resultAttribute, result);
166
+ return routeBranch(this.#targets, this.#optional, result, this.#identity, "query");
167
+ }
168
+ };
169
+ var MutationStep = class {
170
+ inputCodec;
171
+ #configuration;
172
+ #options;
173
+ #ui;
174
+ #targets;
175
+ #optional;
176
+ #identity;
177
+ /** Validate static wiring before registering the Step in a Flow. */
178
+ constructor(configuration) {
179
+ if (!configuration?.operation) throw new TypeError("mutation operation is required");
180
+ const definition = configuration.operation.definition();
181
+ validateMutationDefinition(definition);
182
+ validateFactoryConfiguration(configuration);
183
+ const routes = validateBranchTargets(definition.branches, configuration.branches);
184
+ this.#configuration = configuration;
185
+ this.#options = mergeStepOptions(definition.stepDefaults, configuration.stepOptionsOverride);
186
+ this.#ui = ConnectorConfigurationUISchema.parse(configuration.configurationUI ?? { units: [] });
187
+ this.#targets = routes.targets;
188
+ this.#optional = routes.optional;
189
+ this.#identity = { operation: definition.operation, connection: configuration.connection };
190
+ if (configuration.inputCodec) this.inputCodec = configuration.inputCodec;
191
+ }
192
+ /** Stable Dex Step type. */
193
+ getStepType() {
194
+ return this.#configuration.stepType;
195
+ }
196
+ /** Isolated Execute options, including connector retry defaults. */
197
+ getStepOptions() {
198
+ return cloneStepOptions(this.#options);
199
+ }
200
+ /** Group metadata for Connector Studio. */
201
+ annotations() {
202
+ return { ...this.#configuration.annotations };
203
+ }
204
+ /** Isolated connector UI composition. */
205
+ configurationUI() {
206
+ return structuredClone(this.#ui);
207
+ }
208
+ /** Invoke, persist the optional terminal Attribute, and route the selected branch. */
209
+ async execute(context, input) {
210
+ const operationInput = this.#configuration.mapToOperationInput(input);
211
+ const call = createCall(context, this.#identity.connection, this.#identity.operation, {
212
+ ...this.#configuration.progressStream ? { progressStream: this.#configuration.progressStream } : {},
213
+ ...this.#configuration.textStream ? { textStream: this.#configuration.textStream } : {},
214
+ ...this.#configuration.textOptions ? { textOptions: this.#configuration.textOptions } : {}
215
+ });
216
+ let result;
217
+ try {
218
+ result = await runMutation(this.#configuration.operation, call, operationInput);
219
+ } catch (error) {
220
+ throwDexRetry(error);
221
+ }
222
+ if (this.#configuration.resultAttribute) context.setAttribute(this.#configuration.resultAttribute, result);
223
+ return routeBranch(this.#targets, this.#optional, result, this.#identity, "mutation");
224
+ }
225
+ };
226
+ function newQueryStep(configuration) {
227
+ return new QueryStep(configuration);
228
+ }
229
+ function newMutationStep(configuration) {
230
+ return new MutationStep(configuration);
231
+ }
232
+ function validateFactoryConfiguration(configuration) {
233
+ if (!configuration.stepType?.trim()) throw new TypeError("stable Step type is required");
234
+ if (!/^[a-z][a-z0-9-]*$/.test(configuration.annotations?.groupId ?? "") || !configuration.annotations.groupLabel?.trim() || !configuration.annotations.explanation?.trim()) throw new TypeError("Step group, group label, and explanation are required");
235
+ ConnectionRefSchema.parse(configuration.connection);
236
+ if (typeof configuration.mapToOperationInput !== "function") throw new TypeError("mapToOperationInput is required");
237
+ }
238
+ function validateBranchTargets(definitions, routes) {
239
+ const expected = new Map(definitions.map((branch) => [branch.id, branch]));
240
+ const targets = /* @__PURE__ */ new Map();
241
+ const optional = new Set(definitions.filter((branch) => branch.optional).map((branch) => branch.id));
242
+ for (const route of routes) {
243
+ if (!expected.has(route.branch)) throw new TypeError(`branch target ${route.branch} is not declared by the operation`);
244
+ if (!route.target) {
245
+ if (!optional.has(route.branch)) throw new TypeError(`branch target ${route.branch} must name a Step`);
246
+ continue;
247
+ }
248
+ if (route.target instanceof StepRef && !route.target.stepType.trim()) throw new TypeError(`branch target ${route.branch} must name a Step`);
249
+ if (typeof route.target === "object" && !(route.target instanceof StepRef) && !route.target.getStepType().trim()) throw new TypeError(`branch target ${route.branch} must name a Step`);
250
+ if (targets.has(route.branch)) throw new TypeError(`branch target ${route.branch} is duplicated`);
251
+ targets.set(route.branch, route.target);
252
+ }
253
+ for (const branch of definitions) if (!branch.optional && !targets.has(branch.id)) throw new TypeError(`branch target ${branch.id} is required`);
254
+ return { targets, optional };
255
+ }
256
+ function mergeStepOptions(defaults, override) {
257
+ if (override && Object.keys(override).some((key) => key.startsWith("waitFor") && override[key] !== void 0)) throw new TypeError("execute-only connector Step rejects WaitFor options");
258
+ const merged = {
259
+ ...override?.executeMethodTimeoutMs || defaults.executeMethodTimeoutMs ? { executeMethodTimeoutMs: override?.executeMethodTimeoutMs || defaults.executeMethodTimeoutMs } : {},
260
+ ...override?.heartbeatTimeoutMs || defaults.heartbeatTimeoutMs ? { heartbeatTimeoutMs: override?.heartbeatTimeoutMs || defaults.heartbeatTimeoutMs } : {},
261
+ executeRetry: { ...override?.executeRetry ?? defaults.executeRetry ?? defaultConnectorStepExecuteRetryPolicy() },
262
+ ...override?.executeDurability || (defaults.executeDurability !== "default" ? defaults.executeDurability : void 0) ? { executeDurability: override?.executeDurability ?? defaults.executeDurability } : {},
263
+ ...override?.executeFailure ? { executeFailure: override.executeFailure } : {},
264
+ ...override?.executeLockAttributes ? { executeLockAttributes: override.executeLockAttributes } : {},
265
+ ...override?.executeLoadAttributeMaps ? { executeLoadAttributeMaps: override.executeLoadAttributeMaps } : {},
266
+ ...override?.executeLoadAttributeMapInstances ? { executeLoadAttributeMapInstances: override.executeLoadAttributeMapInstances } : {},
267
+ ...override?.executeLoadChannels ? { executeLoadChannels: override.executeLoadChannels } : {},
268
+ ...override?.executeLoadChannelMaps ? { executeLoadChannelMaps: override.executeLoadChannelMaps } : {},
269
+ ...override?.executeLoadChannelMapInstances ? { executeLoadChannelMapInstances: override.executeLoadChannelMapInstances } : {}
270
+ };
271
+ return cloneStepOptions(merged);
272
+ }
273
+ function cloneStepOptions(options) {
274
+ return {
275
+ ...options,
276
+ ...options.executeRetry ? { executeRetry: { ...options.executeRetry } } : {},
277
+ ...options.executeLockAttributes ? { executeLockAttributes: [...options.executeLockAttributes] } : {},
278
+ ...options.executeLoadAttributeMaps ? { executeLoadAttributeMaps: [...options.executeLoadAttributeMaps] } : {},
279
+ ...options.executeLoadAttributeMapInstances ? { executeLoadAttributeMapInstances: [...options.executeLoadAttributeMapInstances] } : {},
280
+ ...options.executeLoadChannels ? { executeLoadChannels: [...options.executeLoadChannels] } : {},
281
+ ...options.executeLoadChannelMaps ? { executeLoadChannelMaps: [...options.executeLoadChannelMaps] } : {},
282
+ ...options.executeLoadChannelMapInstances ? { executeLoadChannelMapInstances: [...options.executeLoadChannelMapInstances] } : {}
283
+ };
284
+ }
285
+ function routeBranch(targets, optional, result, identity, operationKind) {
286
+ const target = targets.get(result.branch);
287
+ if (target) return goTo(target, result);
288
+ if (optional.has(result.branch)) {
289
+ let reason = `${identity.operation.connectorId} ${identity.operation.operationId} (connection ${identity.connection.name}) selected branch ${result.branch} with no target`;
290
+ if (result.failure) reason += `: ${result.failure.kind}: ${boundedFailureMessage(result.failure.message)}`;
291
+ return forceFail(reason);
292
+ }
293
+ throw new TypeError(`${operationKind} result selected unconfigured branch ${result.branch}`);
294
+ }
295
+ function boundedFailureMessage(message) {
296
+ const bytes = Buffer.from(message);
297
+ if (bytes.length <= 1024) return message;
298
+ let end = 1024;
299
+ while (end > 0 && (bytes[end] & 192) === 128) end--;
300
+ return `${bytes.subarray(0, end).toString("utf8")} (truncated)`;
301
+ }
302
+ function throwDexRetry(error) {
303
+ if (error instanceof RetryError && error.retryAfterMs >= 1e3) throw retryAfter(Math.floor(error.retryAfterMs / 1e3), error);
304
+ throw error;
305
+ }
306
+
307
+ export {
308
+ runQuery,
309
+ runMutation,
310
+ goToBranch,
311
+ stepRef,
312
+ QueryStep,
313
+ MutationStep,
314
+ newQueryStep,
315
+ newMutationStep
316
+ };
@@ -2,7 +2,7 @@ import {
2
2
  ConnectionRefSchema,
3
3
  OperationRefSchema,
4
4
  deriveCallID
5
- } from "./chunk-SOXYPIMO.js";
5
+ } from "./chunk-BQGYGCOQ.js";
6
6
 
7
7
  // src/credentials.ts
8
8
  function createCall(context, connection, operation, options = {}) {
@@ -43,7 +43,7 @@ function isLinkLocalIPAddressLiteral(hostname) {
43
43
  }
44
44
  function isAlwaysRefusedIPAddressLiteral(hostname) {
45
45
  const address = unbracketHostname(hostname);
46
- return address === "0.0.0.0" || address === "::" || address.toLowerCase() === "fd00:ec2::254" || contains(linkLocal, address) || contains(multicast, address);
46
+ return address === "0.0.0.0" || address === "255.255.255.255" || address === "::" || address.toLowerCase() === "fd00:ec2::254" || contains(linkLocal, address) || contains(multicast, address);
47
47
  }
48
48
  function isAllowedProviderIPAddress(address, protocol, policy) {
49
49
  if (isIP(unbracketHostname(address)) === 0 || isAlwaysRefusedIPAddressLiteral(address)) return false;
@@ -92,6 +92,13 @@ var ProviderTransportError = class extends Error {
92
92
  }
93
93
  isProvablyNotSent;
94
94
  };
95
+ var ProviderStalledError = class extends Error {
96
+ /** Construct a sanitized no-byte timeout failure. */
97
+ constructor() {
98
+ super("provider sent no bytes within the stall timeout");
99
+ this.name = "ProviderStalledError";
100
+ }
101
+ };
95
102
  function parseRetryAfter(value, now = /* @__PURE__ */ new Date()) {
96
103
  const trimmed = value.trim();
97
104
  if (/^[0-9]+$/.test(trimmed)) {
@@ -153,7 +160,10 @@ function readErrorTokens(body, pointers) {
153
160
  }
154
161
  async function sendProviderRequest(request) {
155
162
  if (!Number.isSafeInteger(request.maxResponseBytes) || request.maxResponseBytes < 0) throw new RangeError("response body limit must be a non-negative integer");
156
- if (request.addressPolicy !== "publicHTTPS" && request.addressPolicy !== "privateNetworkHTTP") throw new AddressRefusal("address policy is invalid");
163
+ if (request.maxErrorResponseBytes !== void 0 && (!Number.isSafeInteger(request.maxErrorResponseBytes) || request.maxErrorResponseBytes < 0)) throw new RangeError("error body limit must be a non-negative integer");
164
+ if (request.onBodyLimit !== void 0 && request.onBodyLimit !== "destroy" && request.onBodyLimit !== "drain") throw new TypeError("response body limit action is invalid");
165
+ if (request.onBodyLimit === "drain" && !(request.timeoutMs && request.timeoutMs > 0) && !(request.stallTimeoutMs && request.stallTimeoutMs > 0) && !request.signal) throw new TypeError("draining requires a timeout or abort signal");
166
+ if (request.addressPolicy !== "publicHTTPS" && request.addressPolicy !== "privateNetworkHTTP" && request.addressPolicy !== "unrestricted") throw new AddressRefusal("address policy is invalid");
157
167
  let parsed;
158
168
  try {
159
169
  parsed = new URL(request.url);
@@ -172,7 +182,8 @@ async function sendProviderRequest(request) {
172
182
  } catch {
173
183
  throw new ProviderTransportError(true);
174
184
  }
175
- if (addresses.length === 0 || addresses.some(({ address }) => !isAllowedProviderIPAddress(address, parsed.protocol, request.addressPolicy))) {
185
+ const restrictedPolicy = request.addressPolicy === "unrestricted" ? void 0 : request.addressPolicy;
186
+ if (addresses.length === 0 || restrictedPolicy && addresses.some(({ address }) => !isAllowedProviderIPAddress(address, parsed.protocol, restrictedPolicy))) {
176
187
  throw new AddressRefusal("resolved address violates policy");
177
188
  }
178
189
  const selected = addresses[0];
@@ -186,15 +197,70 @@ async function sendProviderRequest(request) {
186
197
  return new Promise((resolve, reject) => {
187
198
  let hasConnected = false;
188
199
  let hasWrittenRequest = false;
200
+ let hasReceivedResponse = false;
189
201
  let clientRequest;
202
+ let responseStallTimer;
190
203
  try {
191
204
  clientRequest = (parsed.protocol === "https:" ? https : http).request(parsed, options, (response) => {
192
- void readBoundedBody(response, request.maxResponseBytes).then((body) => {
193
- resolve({ status: response.statusCode ?? 0, headers: response.headers, body });
194
- }).catch((error) => {
195
- response.destroy();
196
- reject(error instanceof BodyTooLargeError ? new BodyTooLargeError(response.statusCode ?? 0, response.headers) : new ProviderTransportError(false));
197
- });
205
+ hasReceivedResponse = true;
206
+ if (responseStallTimer) clearTimeout(responseStallTimer);
207
+ const chunks = [];
208
+ let size = 0;
209
+ let isBodyTooLarge = false;
210
+ let stallTimer;
211
+ const observeProgress = () => {
212
+ if (stallTimer) clearTimeout(stallTimer);
213
+ if (request.stallTimeoutMs && request.stallTimeoutMs > 0) stallTimer = setTimeout(() => response.destroy(new ProviderStalledError()), request.stallTimeoutMs);
214
+ };
215
+ observeProgress();
216
+ void (async () => {
217
+ try {
218
+ for await (const chunk of response) {
219
+ observeProgress();
220
+ if (isBodyTooLarge) continue;
221
+ const isSuccess = (response.statusCode ?? 0) >= 200 && (response.statusCode ?? 0) <= 299;
222
+ const limit = isSuccess ? request.maxResponseBytes : request.maxErrorResponseBytes ?? request.maxResponseBytes;
223
+ if (!isSuccess && request.maxErrorResponseBytes !== void 0 && size + chunk.length > limit) {
224
+ chunks.push(chunk.subarray(0, Math.max(0, limit - size)));
225
+ response.destroy();
226
+ resolve({ status: response.statusCode ?? 0, headers: response.headers, body: Buffer.concat(chunks) });
227
+ return;
228
+ }
229
+ if (request.onBodyLimit === "drain" && size + chunk.length > limit) {
230
+ const remainingBytes = limit - size;
231
+ isBodyTooLarge = true;
232
+ chunks.length = 0;
233
+ if (remainingBytes > 0) request.onResponseChunk?.(chunk.subarray(0, remainingBytes), response.statusCode ?? 0, response.headers);
234
+ continue;
235
+ }
236
+ const shouldContinue = request.onResponseChunk?.(chunk, response.statusCode ?? 0, response.headers);
237
+ if (shouldContinue === false) {
238
+ response.destroy();
239
+ resolve({ status: response.statusCode ?? 0, headers: response.headers, body: Buffer.concat(chunks) });
240
+ return;
241
+ }
242
+ size += chunk.length;
243
+ if (size > limit) throw new BodyTooLargeError(response.statusCode ?? 0, response.headers);
244
+ chunks.push(chunk);
245
+ }
246
+ if (isBodyTooLarge) {
247
+ reject(new BodyTooLargeError(response.statusCode ?? 0, response.headers));
248
+ return;
249
+ }
250
+ resolve({ status: response.statusCode ?? 0, headers: response.headers, body: Buffer.concat(chunks) });
251
+ } catch (error) {
252
+ response.destroy();
253
+ if (isBodyTooLarge) {
254
+ reject(new BodyTooLargeError(response.statusCode ?? 0, response.headers));
255
+ } else if (request.ignoreErrorBodyReadFailure && ((response.statusCode ?? 0) < 200 || (response.statusCode ?? 0) >= 300)) {
256
+ resolve({ status: response.statusCode ?? 0, headers: response.headers, body: Buffer.concat(chunks) });
257
+ } else {
258
+ reject(error instanceof BodyTooLargeError || error instanceof ProviderStalledError ? error : new ProviderTransportError(false));
259
+ }
260
+ } finally {
261
+ if (stallTimer) clearTimeout(stallTimer);
262
+ }
263
+ })();
198
264
  });
199
265
  } catch {
200
266
  reject(new ProviderTransportError(true));
@@ -202,14 +268,18 @@ async function sendProviderRequest(request) {
202
268
  }
203
269
  clientRequest.on("socket", (socket) => {
204
270
  if (!socket.connecting) hasConnected = true;
205
- socket.once(parsed.protocol === "https:" ? "secureConnect" : "connect", () => {
271
+ else socket.once(parsed.protocol === "https:" ? "secureConnect" : "connect", () => {
206
272
  hasConnected = true;
207
273
  });
208
274
  });
209
275
  clientRequest.on("finish", () => {
210
276
  hasWrittenRequest = true;
211
277
  });
212
- clientRequest.on("error", () => reject(new ProviderTransportError(!hasConnected && !hasWrittenRequest)));
278
+ clientRequest.on("error", (error) => {
279
+ if (responseStallTimer) clearTimeout(responseStallTimer);
280
+ if (!hasReceivedResponse) reject(error instanceof ProviderStalledError ? error : new ProviderTransportError(!hasConnected && !hasWrittenRequest));
281
+ });
282
+ if (request.stallTimeoutMs && request.stallTimeoutMs > 0) responseStallTimer = setTimeout(() => clientRequest.destroy(new ProviderStalledError()), request.stallTimeoutMs);
213
283
  try {
214
284
  clientRequest.end(request.body);
215
285
  } catch {
@@ -242,6 +312,7 @@ export {
242
312
  BodyTooLargeError,
243
313
  AddressRefusal,
244
314
  ProviderTransportError,
315
+ ProviderStalledError,
245
316
  parseRetryAfter,
246
317
  validateBaseURL,
247
318
  isHeaderSafeCredential,
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
- import { F as Failure, B as BranchID, C as Call, M as MutationAttempt, Q as QueryAttempt, U as UncertainBranchID, a as CallID } from '../credentials-vrvW0d4j.js';
3
- import { Q as QueryDefinition, M as Mutation, a as MutationDefinition, b as Query } from '../operation-Ca8rfacf.js';
2
+ import { F as Failure, B as BranchID, M as MutationAttempt, Q as QueryAttempt, U as UncertainBranchID, C as CallID } from '../types-D6De3sry.js';
3
+ import { C as Call } from '../credentials-JqG97kJ5.js';
4
+ import { Q as QueryDefinition, M as Mutation, a as MutationDefinition, b as Query } from '../operation-CASLOg86.js';
4
5
  import '@superdurable/dex';
5
6
 
6
7
  /** One answered mock call, including input only in test memory. */
@@ -1,10 +1,9 @@
1
1
  import {
2
2
  validateMutationDefinition,
3
3
  validateQueryDefinition
4
- } from "../chunk-2NBM7P4T.js";
4
+ } from "../chunk-32YJXBE4.js";
5
5
  import {
6
6
  DefectBranchID,
7
- FailureSchema,
8
7
  UncertainBranchID,
9
8
  newMutationBranch,
10
9
  newMutationRetry,
@@ -12,7 +11,10 @@ import {
12
11
  newQueryBranch,
13
12
  newQueryRetry,
14
13
  zeroReceipt
15
- } from "../chunk-SOXYPIMO.js";
14
+ } from "../chunk-BQGYGCOQ.js";
15
+ import {
16
+ FailureSchema
17
+ } from "../chunk-IHG6ADQG.js";
16
18
 
17
19
  // src/connectormock/index.ts
18
20
  import { readFile } from "fs/promises";
@@ -0,0 +1,102 @@
1
+ import { C as CallID, d as ConnectionRef, O as OperationRef } from './types-D6De3sry.js';
2
+ import { AsyncContext, Stream, BufferedTextStreamOptions } from '@superdurable/dex';
3
+
4
+ /** Provider-safe progress before Dex identity and sequence are added. */
5
+ interface Progress {
6
+ /** Provider-specific stable phase. */ phase: string;
7
+ /** Optional safe progress text. */ message?: string;
8
+ /** Optional inclusive fraction from zero to one. */ percent?: number;
9
+ /** Optional provider-owned monotonic sequence. */ providerSequence?: number;
10
+ }
11
+ /** Structured best-effort Dex Stream message. */
12
+ interface ProgressUpdate extends Progress {
13
+ /** Stable connector call ID. */ callId: CallID;
14
+ /** One-based Dex Execute attempt. */ attempt: number;
15
+ /** Connector-assigned monotonic sequence within this attempt. */ sequence: number;
16
+ }
17
+ /** Optional application-owned Streams for one connector invocation. */
18
+ interface CallProgressOptions {
19
+ /** Registered structured progress Stream. */ progressStream?: Stream<ProgressUpdate>;
20
+ /** Registered string Stream for ordered buffered text. */ textStream?: Stream<string>;
21
+ /** Flush interval and soft UTF-8 batch limit for text. */ textOptions?: BufferedTextStreamOptions;
22
+ }
23
+ /** Provider-safe call metadata with its live, non-serializable Dex Step context. */
24
+ interface Call {
25
+ /** Active Dex Execute context; Step factories attach it as a non-enumerable field. */ context: AsyncContext;
26
+ /** Stable identity across retries. */ id: CallID;
27
+ /** Provider deduplication key, when available. */ idempotencyKey?: string;
28
+ /** Logical named connection. */ connection: ConnectionRef;
29
+ /** Manifest operation identity. */ operation: OperationRef;
30
+ /** Append structured progress when a Stream is configured. */ reportProgress(progress: Progress): void;
31
+ /** Append text in order when a buffered Stream is configured. */ writeText(value: string): void;
32
+ /** Whether structured progress output is configured. */ hasProgressStream(): boolean;
33
+ /** Whether buffered text output is configured. */ hasTextStream(): boolean;
34
+ }
35
+ /** Bind a live Dex Execute context to a stable, provider-safe call identity. */
36
+ declare function createCall(context: AsyncContext, connection: ConnectionRef, operation: OperationRef, options?: CallProgressOptions): Call;
37
+ /** Resolve a credential at call time outside durable Flow state. */
38
+ interface CredentialProvider<C> {
39
+ /** Return current credentials, honoring cancellation. */
40
+ resolve(call: Call, signal?: AbortSignal): Promise<C>;
41
+ }
42
+ /** Resolve credentials outside a Dex Step, such as for a Trigger source. */
43
+ interface ContextCredentialProvider<C> extends CredentialProvider<C> {
44
+ /** Return current credentials under an explicit cancellation signal. */
45
+ resolveContext(call: Call, signal: AbortSignal): Promise<C>;
46
+ }
47
+ /** Current private material and decision time for a refresh driver. */
48
+ interface CredentialRefreshState<C> {
49
+ /** Current connector-specific credential. */ credentials: C;
50
+ /** Known access credential expiry. */ expiresAt?: Date;
51
+ /** Captured decision time. */ now: Date;
52
+ }
53
+ /** Complete replacement credential and mandatory access expiry. */
54
+ interface CredentialRefreshResult<C> {
55
+ /** Complete replacement, retaining omitted renewal material. */ credentials: C;
56
+ /** Replacement access expiry. */ expiresAt: Date;
57
+ }
58
+ /** Connector-specific provider refresh contract; persistence belongs to the host. */
59
+ interface CredentialRefreshDriver<C> {
60
+ /** Decide deterministically whether a credential needs provider renewal. */
61
+ refreshRequired(state: CredentialRefreshState<C>): boolean;
62
+ /** Make one provider refresh request after the host admits it durably. */
63
+ refresh(state: CredentialRefreshState<C>, signal: AbortSignal): Promise<CredentialRefreshResult<C>>;
64
+ }
65
+ /** Host adapter that refreshes and persists a connection atomically. */
66
+ interface RefreshingCredentialProvider<C> extends CredentialProvider<C> {
67
+ /** Serialize refresh for one connection, reload state, and persist replacement. */
68
+ resolveWithRefresh(call: Call, driver: CredentialRefreshDriver<C>, signal?: AbortSignal): Promise<C>;
69
+ }
70
+ /** Host adapter for one explicit provider authentication rejection. */
71
+ interface RejectedCredentialRefreshingProvider<C> {
72
+ /** Force one coordinated refresh after the provider rejects an access token. */
73
+ resolveAfterRejection(call: Call, driver: CredentialRefreshDriver<C>, signal?: AbortSignal): Promise<C>;
74
+ }
75
+ /** Resolve through host refresh support, then context-aware or ordinary lookup. */
76
+ declare function resolveCredential<C>(provider: CredentialProvider<C>, call: Call, driver: CredentialRefreshDriver<C>, signal?: AbortSignal): Promise<C>;
77
+ /** Force one coordinated refresh after a provider authentication rejection. */
78
+ declare function resolveCredentialAfterRejection<C>(provider: CredentialProvider<C>, call: Call, driver: CredentialRefreshDriver<C>, signal?: AbortSignal): Promise<C>;
79
+ /** In-memory named credentials for examples and tests; never persisted by this class. */
80
+ declare class StaticCredentialProvider<C> implements RefreshingCredentialProvider<C> {
81
+ #private;
82
+ /** Capture all connection values during construction. */
83
+ constructor(entries: Iterable<readonly [ConnectionRef, C]>);
84
+ /** Return the configured credential or reject a missing connection. */
85
+ resolve(call: Call): Promise<C>;
86
+ /** Static credentials never expire and therefore do not call the driver. */
87
+ resolveWithRefresh(call: Call): Promise<C>;
88
+ }
89
+ /** Wrap a secret that cannot be serialized or printed. */
90
+ declare class SecretString {
91
+ #private;
92
+ /** Hold one private value outside default stringification and inspection. */
93
+ constructor(value: string);
94
+ /** Reveal only at the provider request boundary. */
95
+ reveal(): string;
96
+ /** Return a fixed redaction for accidental string formatting. */
97
+ toString(): string;
98
+ /** Reject accidental JSON persistence. */
99
+ toJSON(): never;
100
+ }
101
+
102
+ export { type Call as C, type ProgressUpdate as P, type RefreshingCredentialProvider as R, SecretString as S, type CallProgressOptions as a, type ContextCredentialProvider as b, type CredentialProvider as c, type CredentialRefreshDriver as d, type CredentialRefreshResult as e, type CredentialRefreshState as f, type Progress as g, type RejectedCredentialRefreshingProvider as h, StaticCredentialProvider as i, createCall as j, resolveCredentialAfterRejection as k, resolveCredential as r };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { C as Call, b as MutationResult, c as QueryResult, d as ConnectorConfigurationUI, e as ConnectionRef, P as ProgressUpdate } from './credentials-vrvW0d4j.js';
2
- export { f as BranchDefinition, g as BranchDefinitionSchema, B as BranchID, a as CallID, h as CallIDNamespace, i as CallIDSchema, j as CallProgressOptions, k as ConnectionRefSchema, l as ConnectorConfigurationRef, m as ConnectorConfigurationRefSchema, n as ConnectorConfigurationUISchema, o as ConnectorUIBinding, p as ConnectorUIBindingSchema, q as ConnectorUIUnit, r as ConnectorUIUnitSchema, s as ContextCredentialProvider, t as CredentialProvider, u as CredentialRefreshDriver, v as CredentialRefreshResult, w as CredentialRefreshState, D as DefectBranchID, F as Failure, x as FailureKind, y as FailureKindSchema, z as FailureSchema, I as IdempotencyKey, M as MutationAttempt, A as MutationDefinitionWire, E as MutationDefinitionWireSchema, G as MutationResultSchema, O as OperationRef, H as OperationRefSchema, J as Progress, Q as QueryAttempt, K as QueryDefinitionWire, L as QueryDefinitionWireSchema, N as QueryResultSchema, R as ReauthorizationRequiredError, S as Receipt, T as ReceiptDraft, V as ReceiptSchema, W as RefreshingCredentialProvider, X as RejectedCredentialRefreshingProvider, Y as RetryError, Z as RetryPolicy, _ as SecretString, $ as StaticCredentialProvider, a0 as StepDefaultsWire, a1 as StepDefaultsWireSchema, U as UncertainBranchID, a2 as createCall, a3 as defaultConnectorStepExecuteRetryPolicy, a4 as defaultIdempotencyKey, a5 as deriveCallID, a6 as newMutationBranch, a7 as newMutationRetry, a8 as newMutationUncertain, a9 as newQueryBranch, aa as newQueryRetry, ab as resolveCredential, ac as resolveCredentialAfterRejection, ad as retryPolicyToGoWire, ae as zeroReceipt } from './credentials-vrvW0d4j.js';
3
- import { M as Mutation, b as Query } from './operation-Ca8rfacf.js';
4
- export { a as MutationDefinition, Q as QueryDefinition, S as StepDefaults, v as validateMutationDefinition, c as validateQueryDefinition } from './operation-Ca8rfacf.js';
1
+ import { a as MutationResult, b as QueryResult, c as ConnectorConfigurationUI, d as ConnectionRef } from './types-D6De3sry.js';
2
+ export { e as BranchDefinition, f as BranchDefinitionSchema, B as BranchID, C as CallID, g as CallIDNamespace, h as CallIDSchema, i as ConnectionRefSchema, j as ConnectorConfigurationRef, k as ConnectorConfigurationRefSchema, l as ConnectorConfigurationUISchema, m as ConnectorUIBinding, n as ConnectorUIBindingSchema, o as ConnectorUIUnit, p as ConnectorUIUnitSchema, D as DefectBranchID, F as Failure, q as FailureKind, r as FailureKindSchema, s as FailureSchema, I as IdempotencyKey, M as MutationAttempt, t as MutationDefinitionWire, u as MutationDefinitionWireSchema, v as MutationResultSchema, O as OperationRef, w as OperationRefSchema, Q as QueryAttempt, x as QueryDefinitionWire, y as QueryDefinitionWireSchema, z as QueryResultSchema, R as ReauthorizationRequiredError, A as Receipt, E as ReceiptDraft, G as ReceiptSchema, H as RetryError, J as RetryPolicy, S as StepDefaultsWire, K as StepDefaultsWireSchema, U as UncertainBranchID, L as defaultConnectorStepExecuteRetryPolicy, N as defaultIdempotencyKey, P as deriveCallID, T as newMutationBranch, V as newMutationRetry, W as newMutationUncertain, X as newQueryBranch, Y as newQueryRetry, Z as retryPolicyToGoWire, _ as zeroReceipt } from './types-D6De3sry.js';
3
+ import { C as Call, P as ProgressUpdate } from './credentials-JqG97kJ5.js';
4
+ export { a as CallProgressOptions, b as ContextCredentialProvider, c as CredentialProvider, d as CredentialRefreshDriver, e as CredentialRefreshResult, f as CredentialRefreshState, g as Progress, R as RefreshingCredentialProvider, h as RejectedCredentialRefreshingProvider, S as SecretString, i as StaticCredentialProvider, j as createCall, r as resolveCredential, k as resolveCredentialAfterRejection } from './credentials-JqG97kJ5.js';
5
+ import { M as Mutation, b as Query } from './operation-CASLOg86.js';
6
+ export { a as MutationDefinition, Q as QueryDefinition, S as StepDefaults, v as validateMutationDefinition, c as validateQueryDefinition } from './operation-CASLOg86.js';
5
7
  import { StepTarget, Step, Codec, Attribute, Stream, BufferedTextStreamOptions, StepOptions, AsyncContext, StepDecision, StepRef } from '@superdurable/dex';
6
8
  import 'zod';
7
9