node-autotask 0.17.0 → 0.19.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 (38) hide show
  1. package/ARCHITECTURE.md +25 -12
  2. package/CHANGELOG.md +574 -0
  3. package/MCP_TOOL_CATALOG.json +2 -2
  4. package/MCP_TOOL_MANIFEST.md +5 -5
  5. package/N8N_MCP_MANIFEST.json +2 -2
  6. package/N8N_MCP_MANIFEST.md +3 -3
  7. package/README.md +8 -4
  8. package/capabilities.json +1574 -1574
  9. package/dist/capabilities.cjs +2 -2
  10. package/dist/capabilities.js +1 -1
  11. package/dist/{chunk-UV2PECAL.js → chunk-4YKV7ORF.js} +2 -2
  12. package/dist/{chunk-NQVD2XLH.cjs → chunk-7RDR5DSI.cjs} +393 -393
  13. package/dist/{chunk-EGR2ZLYM.js → chunk-D5RAQ4DE.js} +2 -2
  14. package/dist/{chunk-5ZGXWS4E.cjs → chunk-JM52ZU7T.cjs} +1573 -1573
  15. package/dist/{chunk-HC6YWPIY.js → chunk-LB2X4X4B.js} +116 -24
  16. package/dist/{chunk-6SP6GJJR.js → chunk-NDK7WYWA.js} +1573 -1573
  17. package/dist/{chunk-4RJG73GY.cjs → chunk-QDYSHAU6.cjs} +177 -85
  18. package/dist/{chunk-DQIZUZWP.cjs → chunk-TIM75K3G.cjs} +135 -135
  19. package/dist/{index-BAFdPxjG.d.ts → index-DLugNSr7.d.ts} +59 -21
  20. package/dist/{index-CjC0oWZX.d.cts → index-aZ3tCh1h.d.cts} +59 -21
  21. package/dist/index.cjs +26 -22
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +8 -4
  25. package/dist/operations/index.cjs +178 -105
  26. package/dist/operations/index.d.cts +12 -3
  27. package/dist/operations/index.d.ts +12 -3
  28. package/dist/operations/index.js +77 -4
  29. package/dist/pool.cjs +8 -8
  30. package/dist/pool.d.cts +1 -1
  31. package/dist/pool.d.ts +1 -1
  32. package/dist/pool.js +4 -4
  33. package/dist/resources/index.cjs +4 -4
  34. package/dist/resources/index.d.cts +1 -1
  35. package/dist/resources/index.d.ts +1 -1
  36. package/dist/resources/index.js +3 -3
  37. package/docs/API.md +92 -26
  38. package/package.json +4 -2
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CAPABILITIES
3
- } from "./chunk-6SP6GJJR.js";
3
+ } from "./chunk-NDK7WYWA.js";
4
4
 
5
5
  // src/auth.ts
6
6
  var AUTH_HEADERS = {
@@ -377,7 +377,8 @@ function parseVendorErrors(body) {
377
377
  var DETERMINISTIC_VENDOR_500_PHRASES = [
378
378
  /exceeds maximum length/i,
379
379
  /Missing Required Field/i,
380
- /Value does not exist for the required field/i
380
+ /Value does not exist for the required field/i,
381
+ /Unable to find .+ in the .+ Entity\./i
381
382
  ];
382
383
  function isDeterministicVendor500(entries) {
383
384
  if (entries === void 0) return false;
@@ -1535,6 +1536,7 @@ var DEFAULT_TIMEOUT_MS = 3e4;
1535
1536
  var DEFAULT_MAX_RETRIES = 3;
1536
1537
  var DEFAULT_MAINTENANCE_MAX_WAIT_MS = 36e5;
1537
1538
  var DEFAULT_MAINTENANCE_POLL_MS = 3e4;
1539
+ var MAINTENANCE_WINDOW_MAX_MS = 4 * 36e5;
1538
1540
  var DEFAULT_MAX_CONCURRENT = 10;
1539
1541
  var DEFAULT_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
1540
1542
  var DEFAULT_MAX_SCAN_RECORDS = 500;
@@ -14689,12 +14691,26 @@ function normalizeZoneBase(raw) {
14689
14691
  if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
14690
14692
  return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
14691
14693
  }
14692
- function buildQueryRequest(baseUrl, resourcePath, params) {
14693
- const queryPath = `${resourcePath}/query`;
14694
+ function buildQueryBody(params) {
14694
14695
  const conditions = normalizedConditions(params.filter);
14695
14696
  const filter = conditions === void 0 ? void 0 : buildFilter(conditions);
14696
- const includeFields = params.includeFields === void 0 ? void 0 : params.includeFields.includes("id") ? params.includeFields : [...params.includeFields, "id"];
14697
+ const includeFields = effectiveIncludeFields(params);
14698
+ const body = {};
14699
+ if (params.maxRecords !== void 0) body.MaxRecords = params.maxRecords;
14700
+ if (includeFields !== void 0) body.IncludeFields = includeFields;
14701
+ body.filter = filter === void 0 ? [] : filter.filter;
14702
+ return body;
14703
+ }
14704
+ function effectiveIncludeFields(params) {
14705
+ if (params.includeFields === void 0) return void 0;
14706
+ return params.includeFields.includes("id") ? params.includeFields : [...params.includeFields, "id"];
14707
+ }
14708
+ function buildQueryRequest(baseUrl, resourcePath, params) {
14709
+ const queryPath = `${resourcePath}/query`;
14697
14710
  if (params.via === "get") {
14711
+ const conditions = normalizedConditions(params.filter);
14712
+ const filter = conditions === void 0 ? void 0 : buildFilter(conditions);
14713
+ const includeFields = effectiveIncludeFields(params);
14698
14714
  const search = {};
14699
14715
  if (filter !== void 0) search.filter = filter.filter;
14700
14716
  if (params.maxRecords !== void 0) search.maxRecords = params.maxRecords;
@@ -14712,11 +14728,7 @@ function buildQueryRequest(baseUrl, resourcePath, params) {
14712
14728
  }
14713
14729
  return req;
14714
14730
  }
14715
- const body = {};
14716
- if (params.maxRecords !== void 0) body.MaxRecords = params.maxRecords;
14717
- if (includeFields !== void 0) body.IncludeFields = includeFields;
14718
- body.filter = filter === void 0 ? [] : filter.filter;
14719
- return { method: "POST", path: queryPath, body, idempotent: true };
14731
+ return { method: "POST", path: queryPath, body: buildQueryBody(params), idempotent: true };
14720
14732
  }
14721
14733
  var ambientSignal = new AsyncLocalStorage();
14722
14734
  function runWithSignal(signal, fn) {
@@ -14768,9 +14780,21 @@ function backoffDelay(attempt) {
14768
14780
  const exp = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
14769
14781
  return Math.floor(exp / 2 + Math.random() * (exp / 2));
14770
14782
  }
14771
- function isMaintenancePage(err) {
14772
- const snippet = typeof err.details?.bodySnippet === "string" ? err.details.bodySnippet : "";
14773
- return /maintenance|temporarily unavailable|service unavailable|outage|downtime|down for/i.test(snippet);
14783
+ var MAINTENANCE_PROBE_BYTES = 256;
14784
+ function maintenanceProbe(text) {
14785
+ return Buffer.from(text, "utf8").subarray(0, MAINTENANCE_PROBE_BYTES).toString("utf8");
14786
+ }
14787
+ function looksLikeMaintenanceText(text) {
14788
+ return /maintenance|temporarily unavailable|service unavailable|outage|downtime|down for/i.test(
14789
+ maintenanceProbe(text)
14790
+ );
14791
+ }
14792
+ function isHtmlLike(contentType, body) {
14793
+ if (contentType !== null && /text\/html|application\/xhtml/i.test(contentType)) return true;
14794
+ return body.trimStart().startsWith("<");
14795
+ }
14796
+ function maintenanceExhaustedAction(maxWaitMs, waits) {
14797
+ return `The zone stayed in a maintenance window for ${Math.round(maxWaitMs / 6e4)} min \u2014 the SDK waited and retried ${waits} time(s) and gave up. Re-run after the window (the observed median is about 2 h; the ceiling is 4 h \u2014 MAINTENANCE_WINDOW_MAX_MS), or raise maintenanceMaxWaitMs.`;
14774
14798
  }
14775
14799
  function parseRetryAfter(value) {
14776
14800
  if (value === null || value.length === 0) return void 0;
@@ -15206,6 +15230,7 @@ var HttpClient = class {
15206
15230
  const maintenancePollMs = opts.maintenancePollMs ?? this.maintenancePollMs;
15207
15231
  let firstMaintenanceAt;
15208
15232
  let maintenanceWaits = 0;
15233
+ let maintenanceBudgetSpent = false;
15209
15234
  const started = Date.now();
15210
15235
  let deadlineAt = started + timeoutMs;
15211
15236
  const callerSignal = opts.signal ?? ambientSignal.getStore();
@@ -15426,12 +15451,13 @@ var HttpClient = class {
15426
15451
  httpStatus: status,
15427
15452
  suggestedAction: "The zone returned a non-JSON 2xx body; verify baseUrl points at the REST API."
15428
15453
  });
15429
- if (err2.code === "UNEXPECTED_CONTENT_TYPE" && maintenanceMaxWaitMs > 0 && isMaintenancePage(err2)) {
15454
+ if (err2.code === "UNEXPECTED_CONTENT_TYPE" && maintenanceMaxWaitMs > 0 && looksLikeMaintenanceText(text)) {
15430
15455
  const now = Date.now();
15431
15456
  firstMaintenanceAt ??= now;
15432
15457
  const remaining = maintenanceMaxWaitMs - (now - firstMaintenanceAt);
15433
15458
  if (remaining > 0) {
15434
15459
  const wait = Math.min(maintenancePollMs, remaining);
15460
+ if (wait >= remaining) maintenanceBudgetSpent = true;
15435
15461
  maintenanceWaits += 1;
15436
15462
  this.logger.warn?.(
15437
15463
  `[autotask] maintenance window on ${method} ${opts.path}; waiting ${Math.round(wait / 1e3)}s (retry ${maintenanceWaits}, up to ${Math.round(maintenanceMaxWaitMs / 6e4)} min)`,
@@ -15442,6 +15468,7 @@ var HttpClient = class {
15442
15468
  await pause(wait);
15443
15469
  continue;
15444
15470
  }
15471
+ maintenanceBudgetSpent = true;
15445
15472
  }
15446
15473
  if (err2.code === "UNEXPECTED_CONTENT_TYPE" && retriable5xx && attempt < maxRetries) {
15447
15474
  const retryAfter = parseRetryAfter(res.headers.get("Retry-After"));
@@ -15456,17 +15483,27 @@ var HttpClient = class {
15456
15483
  continue;
15457
15484
  }
15458
15485
  }
15459
- if (maintenanceWaits > 0 && err2.code === "UNEXPECTED_CONTENT_TYPE") {
15486
+ const pageSnippet = typeof err2.details?.bodySnippet === "string" ? err2.details.bodySnippet : "";
15487
+ if (maintenanceWaits > 0) {
15460
15488
  err2.details = {
15461
15489
  ...err2.details ?? {},
15462
15490
  maintenance: {
15463
15491
  waits: maintenanceWaits,
15464
15492
  waitedMs: Date.now() - (firstMaintenanceAt ?? Date.now()),
15465
15493
  maxWaitMs: maintenanceMaxWaitMs,
15466
- exhausted: true
15494
+ exhausted: maintenanceBudgetSpent
15467
15495
  }
15468
15496
  };
15469
- err2.suggestedAction = `The zone stayed in a maintenance window for ${Math.round(maintenanceMaxWaitMs / 6e4)} min \u2014 the SDK waited and retried ${maintenanceWaits} time(s) and gave up. Re-run after the window (typically under 30 min), or raise maintenanceMaxWaitMs.`;
15497
+ if (maintenanceBudgetSpent) {
15498
+ err2.suggestedAction = maintenanceExhaustedAction(maintenanceMaxWaitMs, maintenanceWaits);
15499
+ }
15500
+ }
15501
+ if (err2.code === "UNEXPECTED_CONTENT_TYPE" && !looksLikeMaintenanceText(text) && isHtmlLike(res.headers.get("Content-Type"), text)) {
15502
+ err2.details = { ...err2.details ?? {}, maintenancePatternMiss: true };
15503
+ this.logger.debug?.(
15504
+ `[autotask] non-JSON body did not match the maintenance vocabulary on ${method} ${opts.path}: ${pageSnippet}`,
15505
+ correlationId
15506
+ );
15470
15507
  }
15471
15508
  attachEmptyReasonDetails(err2);
15472
15509
  throw err2;
@@ -15478,12 +15515,37 @@ var HttpClient = class {
15478
15515
  break;
15479
15516
  }
15480
15517
  let body;
15518
+ let nonJsonBody;
15481
15519
  if (text.length > 0) {
15482
15520
  try {
15483
15521
  body = JSON.parse(text);
15484
15522
  } catch {
15485
15523
  body = text;
15524
+ nonJsonBody = text;
15525
+ }
15526
+ }
15527
+ const nonJsonSnippet = nonJsonBody === void 0 ? void 0 : bodySnippet(nonJsonBody, this.redactPersonalData);
15528
+ const nonJsonIsMaintenance = nonJsonBody !== void 0 && looksLikeMaintenanceText(nonJsonBody);
15529
+ const nonJsonIsHtmlCandidate = nonJsonBody !== void 0 && isHtmlLike(res.headers.get("Content-Type"), nonJsonBody);
15530
+ const nonJsonMaintenanceMayWait = nonJsonIsMaintenance && retriable5xx;
15531
+ if (nonJsonBody !== void 0 && maintenanceMaxWaitMs > 0 && nonJsonMaintenanceMayWait) {
15532
+ const now = Date.now();
15533
+ firstMaintenanceAt ??= now;
15534
+ const remaining = maintenanceMaxWaitMs - (now - firstMaintenanceAt);
15535
+ if (remaining > 0) {
15536
+ const wait = Math.min(maintenancePollMs, remaining);
15537
+ if (wait >= remaining) maintenanceBudgetSpent = true;
15538
+ maintenanceWaits += 1;
15539
+ this.logger.warn?.(
15540
+ `[autotask] maintenance window on ${method} ${opts.path} (HTTP ${status}); waiting ${Math.round(wait / 1e3)}s (retry ${maintenanceWaits}, up to ${Math.round(maintenanceMaxWaitMs / 6e4)} min)`,
15541
+ correlationId
15542
+ );
15543
+ deadlineAt = Date.now() + wait + timeoutMs;
15544
+ armDeadline(deadlineAt - Date.now());
15545
+ await pause(wait);
15546
+ continue;
15486
15547
  }
15548
+ maintenanceBudgetSpent = true;
15487
15549
  }
15488
15550
  const deterministic500 = status >= 500 && isDeterministicVendor500(parseVendorErrors(body));
15489
15551
  const retryableStatus = status === 429 || status >= 500;
@@ -15526,6 +15588,32 @@ var HttpClient = class {
15526
15588
  credentialShapeExpectation(headers, this.auth)
15527
15589
  );
15528
15590
  err.correlationId = correlationId;
15591
+ if (maintenanceWaits > 0) {
15592
+ err.details = {
15593
+ ...err.details ?? {},
15594
+ maintenance: {
15595
+ waits: maintenanceWaits,
15596
+ waitedMs: Date.now() - (firstMaintenanceAt ?? Date.now()),
15597
+ maxWaitMs: maintenanceMaxWaitMs,
15598
+ exhausted: maintenanceBudgetSpent
15599
+ }
15600
+ };
15601
+ if (maintenanceBudgetSpent) {
15602
+ err.suggestedAction = maintenanceExhaustedAction(maintenanceMaxWaitMs, maintenanceWaits);
15603
+ }
15604
+ }
15605
+ if (nonJsonBody !== void 0) {
15606
+ if (nonJsonIsMaintenance && !retriable5xx) {
15607
+ err.details = { ...err.details ?? {}, maintenanceDetected: true };
15608
+ err.suggestedAction = `A maintenance-shaped page answered ${method} ${opts.path} (HTTP ${status}); it was NOT waited out or re-sent \u2014 a non-2xx edge page can arrive after the request was forwarded, so verify the record before re-issuing (only a GET or an explicitly idempotent-flagged call waits this window out).`;
15609
+ } else if (!nonJsonIsMaintenance && nonJsonIsHtmlCandidate) {
15610
+ err.details = { ...err.details ?? {}, maintenancePatternMiss: true };
15611
+ this.logger.debug?.(
15612
+ `[autotask] non-JSON body did not match the maintenance vocabulary on ${method} ${opts.path}: ${nonJsonSnippet}`,
15613
+ correlationId
15614
+ );
15615
+ }
15616
+ }
15529
15617
  if (status === 401 && zoneRetry !== void 0) {
15530
15618
  err.details = {
15531
15619
  ...err.details ?? {},
@@ -15982,6 +16070,7 @@ function applyFieldsFilter(projected, filter, row) {
15982
16070
  if (filter !== void 0) {
15983
16071
  const present = new Set(projected.map((f) => f.name));
15984
16072
  row.unmatchedFields = filter.filter((n) => !present.has(n));
16073
+ row.projected = true;
15985
16074
  }
15986
16075
  row.fields = kept;
15987
16076
  row.required = kept.filter((f) => f.isRequired).map((f) => f.name);
@@ -17042,7 +17131,7 @@ var BaseResource = class {
17042
17131
  // ------------------------------------------------------------------
17043
17132
  // Query walk (wired to the G3 walker; cursor-follow per the pinned wire)
17044
17133
  // ------------------------------------------------------------------
17045
- /** One page fetch: page 1 POSTs the QueryModel body; later pages POST the filter to the absolute cursor. */
17134
+ /** One page fetch: page 1 POSTs the QueryModel body; later pages POST the same body (`buildQueryBody`) to the absolute cursor (issue #118). */
17046
17135
  async fetchPage(params, page, pageSize, state) {
17047
17136
  const operation = `${this.config.key}.query`;
17048
17137
  if (page === 1) {
@@ -17058,8 +17147,7 @@ var BaseResource = class {
17058
17147
  operation
17059
17148
  });
17060
17149
  }
17061
- const conditions = normalizedConditions(params.filter);
17062
- const body = { filter: conditions === void 0 ? [] : buildFilter(conditions).filter };
17150
+ const body = buildQueryBody({ ...params, maxRecords: pageSize });
17063
17151
  const res = await this.http.request({
17064
17152
  method: "POST",
17065
17153
  path: cursorToPath(this.http.baseUrl, cursor),
@@ -17112,10 +17200,12 @@ var BaseResource = class {
17112
17200
  * complete-enough result. The client never owns the walk:
17113
17201
  * - `limit` = total records (default 500, hard max 2500 — above it throws
17114
17202
  * AutotaskConfigError; it is NOT a page size). The page-1 request uses
17115
- * the internal page size min(500, limit); follow-up pages POST the
17116
- * preserved filter to the absolute cursor verbatim (the pinned wire:
17117
- * MaxRecords/IncludeFields are baked into the cursor — never changed
17118
- * mid-walk), so the walk reuses `fetchPage` (the shared cursor machinery)
17203
+ * the internal page size min(500, limit) (the `latest` mechanism sizes
17204
+ * its walk from the matching count); every follow-up page POSTs the
17205
+ * SAME QueryModel body (`buildQueryBody`: MaxRecords, IncludeFields and
17206
+ * the preserved filter) to the absolute cursor (issue #118 — the vendor
17207
+ * honours IncludeFields there and ignores MaxRecords, serving its own
17208
+ * page size), so the walk reuses `fetchPage` (the shared cursor machinery)
17119
17209
  * with a per-walk closure state (walks never share state).
17120
17210
  * - The 1000-page guard still applies as a SOFT stop: a walk that would
17121
17211
  * fetch a 1001st page stops with `hasMore: true` (never a throw — this
@@ -18689,6 +18779,7 @@ export {
18689
18779
  DEFAULT_MAX_RETRIES,
18690
18780
  DEFAULT_MAINTENANCE_MAX_WAIT_MS,
18691
18781
  DEFAULT_MAINTENANCE_POLL_MS,
18782
+ MAINTENANCE_WINDOW_MAX_MS,
18692
18783
  DEFAULT_MAX_CONCURRENT,
18693
18784
  DEFAULT_MAX_RESPONSE_BYTES,
18694
18785
  DEFAULT_MAX_SCAN_RECORDS,
@@ -18734,6 +18825,7 @@ export {
18734
18825
  IDEMPOTENT_METHODS,
18735
18826
  buildUrl,
18736
18827
  cursorToPath,
18828
+ buildQueryBody,
18737
18829
  buildQueryRequest,
18738
18830
  runWithSignal,
18739
18831
  HttpClient,