@codemation/core-nodes 0.6.0 → 0.7.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/index.d.cts CHANGED
@@ -1398,6 +1398,15 @@ type HttpBodySpec = Readonly<{
1398
1398
  kind: "multipart";
1399
1399
  fields: Readonly<Record<string, string>>;
1400
1400
  binaries?: Readonly<Record<string, BinaryRef>>;
1401
+ }> | Readonly<{
1402
+ /**
1403
+ * Send raw bytes from a binary slot as the request body.
1404
+ * The binary attachment's `mimeType` is used as `Content-Type` unless
1405
+ * the request `headers` map already contains `content-type`.
1406
+ */
1407
+ kind: "binary";
1408
+ /** Key into `item.binary` to read the request body bytes from. */
1409
+ slot: string;
1401
1410
  }>;
1402
1411
  /**
1403
1412
  * Session interface that credential types implement.
@@ -1429,6 +1438,15 @@ type HttpRequestSpec = Readonly<{
1429
1438
  mode: "auto" | "always" | "never";
1430
1439
  binaryName: string;
1431
1440
  }>;
1441
+ /**
1442
+ * When set to `"binary"`, the response body is written to a binary slot
1443
+ * instead of being parsed as JSON/text. Overrides `download` mode.
1444
+ */
1445
+ responseFormat?: "json" | "text" | "binary";
1446
+ /** Binary slot name for the response body when `responseFormat === "binary"`. Defaults to `"response"`. */
1447
+ responseBinarySlot?: string;
1448
+ /** Maximum allowed response size in bytes (checked against Content-Length before allocating). Defaults to 100 MiB. */
1449
+ responseSizeCapBytes?: number;
1432
1450
  /** Execution context — needed for binary attach. */
1433
1451
  ctx: NodeExecutionContext<RunnableNodeConfig<unknown, unknown>>;
1434
1452
  }>;
@@ -1446,6 +1464,14 @@ type HttpRequestResult = Readonly<{
1446
1464
  json?: unknown;
1447
1465
  text?: string;
1448
1466
  bodyBinaryName?: string;
1467
+ /** Set when `responseFormat === "binary"`. Name of the binary slot the response body was written to. */
1468
+ binarySlot?: string;
1469
+ /** Set when `responseFormat === "binary"`. The MIME type of the stored response. */
1470
+ contentType?: string;
1471
+ /** Set when `responseFormat === "binary"`. Size in bytes of the stored response. */
1472
+ size?: number;
1473
+ /** Set when `responseFormat === "binary"`. Filename inferred from URL or Content-Disposition. */
1474
+ filename?: string;
1449
1475
  }>;
1450
1476
  //#endregion
1451
1477
  //#region src/credentials/ApiKeyCredentialType.d.ts
@@ -2300,6 +2326,7 @@ declare class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
2300
2326
  readonly outputPorts: readonly ["main"];
2301
2327
  execute(args: RunnableNodeExecuteArgs<HttpRequest<any, any>>): Promise<unknown>;
2302
2328
  private executeItem;
2329
+ private handleBinaryResponse;
2303
2330
  private resolveCredential;
2304
2331
  private resolveUrl;
2305
2332
  private asRecord;
@@ -2325,6 +2352,14 @@ type HttpRequestOutputJson = Readonly<{
2325
2352
  json?: unknown;
2326
2353
  text?: string;
2327
2354
  bodyBinaryName?: string;
2355
+ /** Set when `responseFormat === "binary"`. Name of the binary slot the response was stored in. */
2356
+ binarySlot?: string;
2357
+ /** Set when `responseFormat === "binary"`. MIME type of the stored response. */
2358
+ contentType?: string;
2359
+ /** Set when `responseFormat === "binary"`. Size in bytes of the stored response. */
2360
+ size?: number;
2361
+ /** Set when `responseFormat === "binary"`. Filename inferred from URL or Content-Disposition. */
2362
+ filename?: string;
2328
2363
  }>;
2329
2364
  /**
2330
2365
  * The built-in HTTP request credential type IDs accepted by the `HttpRequest` node.
@@ -2359,6 +2394,27 @@ declare class HttpRequest<TInputJson$1 = Readonly<{
2359
2394
  credentialSlot?: string;
2360
2395
  binaryName?: string;
2361
2396
  downloadMode?: HttpRequestDownloadMode;
2397
+ /**
2398
+ * Controls how the response body is handled.
2399
+ * - `"json"` / `"text"`: existing behaviour (parse + emit on `item.json`).
2400
+ * - `"binary"`: read the response as raw bytes and store via `ctx.binary.attach`.
2401
+ * The output JSON contains `{ status, headers, binarySlot, contentType, size, filename }`
2402
+ * but NOT the raw bytes. Use `responseBinarySlot` to name the slot (default `"response"`).
2403
+ *
2404
+ * When omitted, the existing `downloadMode` logic applies (backward-compatible).
2405
+ */
2406
+ responseFormat?: "json" | "text" | "binary";
2407
+ /**
2408
+ * Name of the binary slot to write the response body into when `responseFormat === "binary"`.
2409
+ * Defaults to `"response"`.
2410
+ */
2411
+ responseBinarySlot?: string;
2412
+ /**
2413
+ * Maximum response size in bytes for binary mode. Checked against the `Content-Length`
2414
+ * response header before allocating memory. Defaults to 100 MiB (104857600).
2415
+ * Requests whose `Content-Length` exceeds this cap are rejected before the body is read.
2416
+ */
2417
+ responseSizeCapBytes?: number;
2362
2418
  id?: string;
2363
2419
  }>;
2364
2420
  readonly retryPolicy: RetryPolicySpec;
@@ -2392,6 +2448,27 @@ declare class HttpRequest<TInputJson$1 = Readonly<{
2392
2448
  credentialSlot?: string;
2393
2449
  binaryName?: string;
2394
2450
  downloadMode?: HttpRequestDownloadMode;
2451
+ /**
2452
+ * Controls how the response body is handled.
2453
+ * - `"json"` / `"text"`: existing behaviour (parse + emit on `item.json`).
2454
+ * - `"binary"`: read the response as raw bytes and store via `ctx.binary.attach`.
2455
+ * The output JSON contains `{ status, headers, binarySlot, contentType, size, filename }`
2456
+ * but NOT the raw bytes. Use `responseBinarySlot` to name the slot (default `"response"`).
2457
+ *
2458
+ * When omitted, the existing `downloadMode` logic applies (backward-compatible).
2459
+ */
2460
+ responseFormat?: "json" | "text" | "binary";
2461
+ /**
2462
+ * Name of the binary slot to write the response body into when `responseFormat === "binary"`.
2463
+ * Defaults to `"response"`.
2464
+ */
2465
+ responseBinarySlot?: string;
2466
+ /**
2467
+ * Maximum response size in bytes for binary mode. Checked against the `Content-Length`
2468
+ * response header before allocating memory. Defaults to 100 MiB (104857600).
2469
+ * Requests whose `Content-Length` exceeds this cap are rejected before the body is read.
2470
+ */
2471
+ responseSizeCapBytes?: number;
2395
2472
  id?: string;
2396
2473
  }>, retryPolicy?: RetryPolicySpec);
2397
2474
  get id(): string | undefined;
@@ -2399,6 +2476,9 @@ declare class HttpRequest<TInputJson$1 = Readonly<{
2399
2476
  get urlField(): string;
2400
2477
  get binaryName(): string;
2401
2478
  get downloadMode(): HttpRequestDownloadMode;
2479
+ get responseFormat(): "json" | "text" | "binary" | undefined;
2480
+ get responseBinarySlot(): string;
2481
+ get responseSizeCapBytes(): number;
2402
2482
  getCredentialRequirements(): ReadonlyArray<CredentialRequirement>;
2403
2483
  }
2404
2484
  //#endregion
package/dist/index.d.ts CHANGED
@@ -1398,6 +1398,15 @@ type HttpBodySpec = Readonly<{
1398
1398
  kind: "multipart";
1399
1399
  fields: Readonly<Record<string, string>>;
1400
1400
  binaries?: Readonly<Record<string, BinaryRef>>;
1401
+ }> | Readonly<{
1402
+ /**
1403
+ * Send raw bytes from a binary slot as the request body.
1404
+ * The binary attachment's `mimeType` is used as `Content-Type` unless
1405
+ * the request `headers` map already contains `content-type`.
1406
+ */
1407
+ kind: "binary";
1408
+ /** Key into `item.binary` to read the request body bytes from. */
1409
+ slot: string;
1401
1410
  }>;
1402
1411
  /**
1403
1412
  * Session interface that credential types implement.
@@ -1429,6 +1438,15 @@ type HttpRequestSpec = Readonly<{
1429
1438
  mode: "auto" | "always" | "never";
1430
1439
  binaryName: string;
1431
1440
  }>;
1441
+ /**
1442
+ * When set to `"binary"`, the response body is written to a binary slot
1443
+ * instead of being parsed as JSON/text. Overrides `download` mode.
1444
+ */
1445
+ responseFormat?: "json" | "text" | "binary";
1446
+ /** Binary slot name for the response body when `responseFormat === "binary"`. Defaults to `"response"`. */
1447
+ responseBinarySlot?: string;
1448
+ /** Maximum allowed response size in bytes (checked against Content-Length before allocating). Defaults to 100 MiB. */
1449
+ responseSizeCapBytes?: number;
1432
1450
  /** Execution context — needed for binary attach. */
1433
1451
  ctx: NodeExecutionContext<RunnableNodeConfig<unknown, unknown>>;
1434
1452
  }>;
@@ -1446,6 +1464,14 @@ type HttpRequestResult = Readonly<{
1446
1464
  json?: unknown;
1447
1465
  text?: string;
1448
1466
  bodyBinaryName?: string;
1467
+ /** Set when `responseFormat === "binary"`. Name of the binary slot the response body was written to. */
1468
+ binarySlot?: string;
1469
+ /** Set when `responseFormat === "binary"`. The MIME type of the stored response. */
1470
+ contentType?: string;
1471
+ /** Set when `responseFormat === "binary"`. Size in bytes of the stored response. */
1472
+ size?: number;
1473
+ /** Set when `responseFormat === "binary"`. Filename inferred from URL or Content-Disposition. */
1474
+ filename?: string;
1449
1475
  }>;
1450
1476
  //#endregion
1451
1477
  //#region src/credentials/ApiKeyCredentialType.d.ts
@@ -2300,6 +2326,7 @@ declare class HttpRequestNode implements RunnableNode<HttpRequest<any, any>> {
2300
2326
  readonly outputPorts: readonly ["main"];
2301
2327
  execute(args: RunnableNodeExecuteArgs<HttpRequest<any, any>>): Promise<unknown>;
2302
2328
  private executeItem;
2329
+ private handleBinaryResponse;
2303
2330
  private resolveCredential;
2304
2331
  private resolveUrl;
2305
2332
  private asRecord;
@@ -2325,6 +2352,14 @@ type HttpRequestOutputJson = Readonly<{
2325
2352
  json?: unknown;
2326
2353
  text?: string;
2327
2354
  bodyBinaryName?: string;
2355
+ /** Set when `responseFormat === "binary"`. Name of the binary slot the response was stored in. */
2356
+ binarySlot?: string;
2357
+ /** Set when `responseFormat === "binary"`. MIME type of the stored response. */
2358
+ contentType?: string;
2359
+ /** Set when `responseFormat === "binary"`. Size in bytes of the stored response. */
2360
+ size?: number;
2361
+ /** Set when `responseFormat === "binary"`. Filename inferred from URL or Content-Disposition. */
2362
+ filename?: string;
2328
2363
  }>;
2329
2364
  /**
2330
2365
  * The built-in HTTP request credential type IDs accepted by the `HttpRequest` node.
@@ -2359,6 +2394,27 @@ declare class HttpRequest<TInputJson$1 = Readonly<{
2359
2394
  credentialSlot?: string;
2360
2395
  binaryName?: string;
2361
2396
  downloadMode?: HttpRequestDownloadMode;
2397
+ /**
2398
+ * Controls how the response body is handled.
2399
+ * - `"json"` / `"text"`: existing behaviour (parse + emit on `item.json`).
2400
+ * - `"binary"`: read the response as raw bytes and store via `ctx.binary.attach`.
2401
+ * The output JSON contains `{ status, headers, binarySlot, contentType, size, filename }`
2402
+ * but NOT the raw bytes. Use `responseBinarySlot` to name the slot (default `"response"`).
2403
+ *
2404
+ * When omitted, the existing `downloadMode` logic applies (backward-compatible).
2405
+ */
2406
+ responseFormat?: "json" | "text" | "binary";
2407
+ /**
2408
+ * Name of the binary slot to write the response body into when `responseFormat === "binary"`.
2409
+ * Defaults to `"response"`.
2410
+ */
2411
+ responseBinarySlot?: string;
2412
+ /**
2413
+ * Maximum response size in bytes for binary mode. Checked against the `Content-Length`
2414
+ * response header before allocating memory. Defaults to 100 MiB (104857600).
2415
+ * Requests whose `Content-Length` exceeds this cap are rejected before the body is read.
2416
+ */
2417
+ responseSizeCapBytes?: number;
2362
2418
  id?: string;
2363
2419
  }>;
2364
2420
  readonly retryPolicy: RetryPolicySpec;
@@ -2392,6 +2448,27 @@ declare class HttpRequest<TInputJson$1 = Readonly<{
2392
2448
  credentialSlot?: string;
2393
2449
  binaryName?: string;
2394
2450
  downloadMode?: HttpRequestDownloadMode;
2451
+ /**
2452
+ * Controls how the response body is handled.
2453
+ * - `"json"` / `"text"`: existing behaviour (parse + emit on `item.json`).
2454
+ * - `"binary"`: read the response as raw bytes and store via `ctx.binary.attach`.
2455
+ * The output JSON contains `{ status, headers, binarySlot, contentType, size, filename }`
2456
+ * but NOT the raw bytes. Use `responseBinarySlot` to name the slot (default `"response"`).
2457
+ *
2458
+ * When omitted, the existing `downloadMode` logic applies (backward-compatible).
2459
+ */
2460
+ responseFormat?: "json" | "text" | "binary";
2461
+ /**
2462
+ * Name of the binary slot to write the response body into when `responseFormat === "binary"`.
2463
+ * Defaults to `"response"`.
2464
+ */
2465
+ responseBinarySlot?: string;
2466
+ /**
2467
+ * Maximum response size in bytes for binary mode. Checked against the `Content-Length`
2468
+ * response header before allocating memory. Defaults to 100 MiB (104857600).
2469
+ * Requests whose `Content-Length` exceeds this cap are rejected before the body is read.
2470
+ */
2471
+ responseSizeCapBytes?: number;
2395
2472
  id?: string;
2396
2473
  }>, retryPolicy?: RetryPolicySpec);
2397
2474
  get id(): string | undefined;
@@ -2399,6 +2476,9 @@ declare class HttpRequest<TInputJson$1 = Readonly<{
2399
2476
  get urlField(): string;
2400
2477
  get binaryName(): string;
2401
2478
  get downloadMode(): HttpRequestDownloadMode;
2479
+ get responseFormat(): "json" | "text" | "binary" | undefined;
2480
+ get responseBinarySlot(): string;
2481
+ get responseSizeCapBytes(): number;
2402
2482
  getCredentialRequirements(): ReadonlyArray<CredentialRequirement>;
2403
2483
  }
2404
2484
  //#endregion
package/dist/index.js CHANGED
@@ -295,7 +295,8 @@ var HttpRequestExecutor = class {
295
295
  ...credentialDelta.query ?? {}
296
296
  };
297
297
  const encodedBody = await this.bodyBuilder.build(spec.body, item, spec.ctx);
298
- if (encodedBody && encodedBody.contentType) mergedHeaders["content-type"] = encodedBody.contentType;
298
+ const hasExplicitContentType = Object.keys(mergedHeaders).some((k) => k.toLowerCase() === "content-type");
299
+ if (encodedBody && encodedBody.contentType && !hasExplicitContentType) mergedHeaders["content-type"] = encodedBody.contentType;
299
300
  return {
300
301
  url: this.urlBuilder.build(spec.url, mergedQuery),
301
302
  init: {
@@ -394,21 +395,7 @@ var HttpBodyBuilder = class {
394
395
  if (attachment) {
395
396
  const readResult = await ctx.binary.openReadStream(attachment);
396
397
  if (readResult) {
397
- const reader = readResult.body.getReader();
398
- const chunks = [];
399
- let done = false;
400
- while (!done) {
401
- const result = await reader.read();
402
- done = result.done;
403
- if (result.value) chunks.push(result.value);
404
- }
405
- const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
406
- const merged = new Uint8Array(totalLength);
407
- let offset = 0;
408
- for (const chunk of chunks) {
409
- merged.set(chunk, offset);
410
- offset += chunk.length;
411
- }
398
+ const merged = await this.readStreamToBuffer(readResult.body);
412
399
  const blob = new Blob([merged], { type: attachment.mimeType });
413
400
  formData.append(fieldName, blob, attachment.filename ?? binaryRef);
414
401
  }
@@ -419,6 +406,34 @@ var HttpBodyBuilder = class {
419
406
  contentType: ""
420
407
  };
421
408
  }
409
+ if (spec.kind === "binary") {
410
+ const attachment = item.binary?.[spec.slot];
411
+ if (!attachment) throw new Error(`HttpRequest bodyFormat "binary": no binary attachment found at slot "${spec.slot}". Ensure a previous node attached binary data at that slot.`);
412
+ const readResult = await ctx.binary.openReadStream(attachment);
413
+ if (!readResult) throw new Error(`HttpRequest bodyFormat "binary": could not open read stream for slot "${spec.slot}".`);
414
+ return {
415
+ body: readResult.body,
416
+ contentType: attachment.mimeType
417
+ };
418
+ }
419
+ }
420
+ async readStreamToBuffer(stream) {
421
+ const reader = stream.getReader();
422
+ const chunks = [];
423
+ let done = false;
424
+ while (!done) {
425
+ const result = await reader.read();
426
+ done = result.done;
427
+ if (result.value) chunks.push(result.value);
428
+ }
429
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
430
+ const merged = new Uint8Array(new ArrayBuffer(totalLength));
431
+ let offset = 0;
432
+ for (const chunk of chunks) {
433
+ merged.set(chunk, offset);
434
+ offset += chunk.length;
435
+ }
436
+ return merged;
422
437
  }
423
438
  };
424
439
 
@@ -6512,12 +6527,16 @@ let HttpRequestNode = class HttpRequestNode$1 {
6512
6527
  mode: ctx.config.downloadMode,
6513
6528
  binaryName: ctx.config.binaryName
6514
6529
  },
6530
+ responseFormat: ctx.config.responseFormat,
6531
+ responseBinarySlot: ctx.config.responseBinarySlot,
6532
+ responseSizeCapBytes: ctx.config.responseSizeCapBytes,
6515
6533
  ctx
6516
6534
  };
6517
6535
  const { url: resolvedUrl, init } = await new HttpRequestExecutor(globalThis.fetch, new HttpBodyBuilder(), new HttpUrlBuilder()).buildRequest(spec, item);
6518
6536
  const response = await globalThis.fetch(resolvedUrl, init);
6519
6537
  const headers = this.readHeaders(response.headers);
6520
6538
  const mimeType = this.resolveMimeType(headers);
6539
+ if (ctx.config.responseFormat === "binary") return await this.handleBinaryResponse(response, resolvedUrl, headers, mimeType, ctx);
6521
6540
  const binaryName = ctx.config.binaryName;
6522
6541
  if (this.shouldAttachBody(ctx.config.downloadMode, mimeType)) {
6523
6542
  const outputJson = {
@@ -6561,6 +6580,36 @@ let HttpRequestNode = class HttpRequestNode$1 {
6561
6580
  ...text !== void 0 ? { text } : {}
6562
6581
  } };
6563
6582
  }
6583
+ async handleBinaryResponse(response, resolvedUrl, headers, mimeType, ctx) {
6584
+ const slotName = ctx.config.responseBinarySlot;
6585
+ const sizeCap = ctx.config.responseSizeCapBytes;
6586
+ const contentLengthHeader = headers["content-length"];
6587
+ if (contentLengthHeader) {
6588
+ const declaredSize = parseInt(contentLengthHeader, 10);
6589
+ if (!isNaN(declaredSize) && declaredSize > sizeCap) throw new Error(`HttpRequest responseFormat "binary": response Content-Length (${declaredSize} bytes) exceeds responseSizeCapBytes (${sizeCap} bytes).`);
6590
+ }
6591
+ const filename = this.resolveFilename(resolvedUrl, headers);
6592
+ const attachment = await ctx.binary.attach({
6593
+ name: slotName,
6594
+ body: response.body ? response.body : new Uint8Array(await response.arrayBuffer()),
6595
+ mimeType,
6596
+ filename
6597
+ });
6598
+ let outputItem = { json: {
6599
+ url: resolvedUrl,
6600
+ method: ctx.config.method,
6601
+ ok: response.ok,
6602
+ status: response.status,
6603
+ statusText: response.statusText,
6604
+ headers,
6605
+ binarySlot: slotName,
6606
+ contentType: mimeType,
6607
+ size: attachment.size,
6608
+ ...filename !== void 0 ? { filename } : {}
6609
+ } };
6610
+ outputItem = ctx.binary.withAttachment(outputItem, slotName, attachment);
6611
+ return outputItem;
6612
+ }
6564
6613
  async resolveCredential(ctx) {
6565
6614
  const slotKey = ctx.config.args.credentialSlot;
6566
6615
  if (!slotKey) return;
@@ -6632,6 +6681,8 @@ const HTTP_REQUEST_ACCEPTED_CREDENTIAL_TYPES = [
6632
6681
  basicAuthCredentialType.definition.typeId,
6633
6682
  oauth2ClientCredentialsType.definition.typeId
6634
6683
  ];
6684
+ /** Default maximum response size for binary mode: 100 MiB. */
6685
+ const DEFAULT_RESPONSE_SIZE_CAP_BYTES = 100 * 1024 * 1024;
6635
6686
  var HttpRequest = class {
6636
6687
  kind = "node";
6637
6688
  type = HttpRequestNode;
@@ -6657,6 +6708,15 @@ var HttpRequest = class {
6657
6708
  get downloadMode() {
6658
6709
  return this.args.downloadMode ?? "auto";
6659
6710
  }
6711
+ get responseFormat() {
6712
+ return this.args.responseFormat;
6713
+ }
6714
+ get responseBinarySlot() {
6715
+ return this.args.responseBinarySlot ?? "response";
6716
+ }
6717
+ get responseSizeCapBytes() {
6718
+ return this.args.responseSizeCapBytes ?? DEFAULT_RESPONSE_SIZE_CAP_BYTES;
6719
+ }
6660
6720
  getCredentialRequirements() {
6661
6721
  if (!this.args.credentialSlot) return [];
6662
6722
  return [{