node-autotask 0.8.3 → 0.10.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 (84) hide show
  1. package/ARCHITECTURE.md +13 -10
  2. package/MCP_TOOL_CATALOG.json +733 -251
  3. package/MCP_TOOL_MANIFEST.md +162 -133
  4. package/MCP_TOOL_OVERRIDES.json +2 -2
  5. package/N8N_MCP_MANIFEST.json +88 -25
  6. package/N8N_MCP_MANIFEST.md +26 -24
  7. package/README.md +12 -4
  8. package/capabilities.json +12503 -9316
  9. package/dist/capabilities.cjs +2 -3
  10. package/dist/capabilities.js +1 -2
  11. package/dist/{chunk-4WJCXIY2.js → chunk-3VB6N44U.js} +1141 -2977
  12. package/dist/{chunk-32AKZMY4.js → chunk-6KFP5HV3.js} +12502 -9316
  13. package/dist/chunk-DMMMWO6A.js +0 -0
  14. package/dist/chunk-FLB2X6O5.cjs +1 -0
  15. package/dist/{chunk-GYI4ZQID.js → chunk-HWC2WPX2.js} +4781 -89
  16. package/dist/chunk-IDPB4N2Q.cjs +1 -0
  17. package/dist/{chunk-F6YSQRZV.js → chunk-NVJ2PTN3.js} +7 -7
  18. package/dist/{chunk-5UGSQLLO.cjs → chunk-QUM74WLQ.cjs} +136 -136
  19. package/dist/{chunk-LAKEIYET.cjs → chunk-S7VMY5KN.cjs} +1468 -3304
  20. package/dist/chunk-UPQIUWOS.js +0 -0
  21. package/dist/{chunk-KNTUX3RR.cjs → chunk-WTVD2JZN.cjs} +12502 -9316
  22. package/dist/{chunk-V3XPLTIR.cjs → chunk-WTVLHMZA.cjs} +4784 -92
  23. package/dist/{index-B6JeThrY.d.ts → index-BOyh7S65.d.cts} +870 -79
  24. package/dist/{index-B0I2bGtx.d.cts → index-DGgMD8aq.d.ts} +870 -79
  25. package/dist/{index-DOCRp_83.d.cts → index-cQ6CRexR.d.cts} +158 -90
  26. package/dist/{index-DOCRp_83.d.ts → index-cQ6CRexR.d.ts} +158 -90
  27. package/dist/index.cjs +37 -22
  28. package/dist/index.d.cts +46 -5
  29. package/dist/index.d.ts +46 -5
  30. package/dist/index.js +48 -33
  31. package/dist/operations/index.cjs +480 -359
  32. package/dist/operations/index.d.cts +195 -16
  33. package/dist/operations/index.d.ts +195 -16
  34. package/dist/operations/index.js +370 -249
  35. package/dist/pool.cjs +36 -12
  36. package/dist/pool.d.cts +2 -3
  37. package/dist/pool.d.ts +2 -3
  38. package/dist/pool.js +33 -9
  39. package/dist/resources/index.cjs +5 -6
  40. package/dist/resources/index.d.cts +2 -2
  41. package/dist/resources/index.d.ts +2 -2
  42. package/dist/resources/index.js +4 -5
  43. package/dist/types/index.cjs +1 -2
  44. package/dist/types/index.d.cts +1 -1
  45. package/dist/types/index.d.ts +1 -1
  46. package/dist/types/index.js +1 -2
  47. package/dist/untrusted.cjs +0 -1
  48. package/dist/untrusted.js +0 -1
  49. package/docs/API.md +693 -48
  50. package/docs/N8N-ADAPTER.md +4 -4
  51. package/examples/basic.ts +1 -1
  52. package/package.json +5 -3
  53. package/dist/capabilities.cjs.map +0 -1
  54. package/dist/capabilities.js.map +0 -1
  55. package/dist/chunk-32AKZMY4.js.map +0 -1
  56. package/dist/chunk-4WJCXIY2.js.map +0 -1
  57. package/dist/chunk-5UGSQLLO.cjs.map +0 -1
  58. package/dist/chunk-CXM35TEC.cjs +0 -1
  59. package/dist/chunk-CXM35TEC.cjs.map +0 -1
  60. package/dist/chunk-F6YSQRZV.js.map +0 -1
  61. package/dist/chunk-GIVD2CQ3.js +0 -1
  62. package/dist/chunk-GIVD2CQ3.js.map +0 -1
  63. package/dist/chunk-GYI4ZQID.js.map +0 -1
  64. package/dist/chunk-KNTUX3RR.cjs.map +0 -1
  65. package/dist/chunk-LAKEIYET.cjs.map +0 -1
  66. package/dist/chunk-MHBMSBTA.js +0 -1
  67. package/dist/chunk-MHBMSBTA.js.map +0 -1
  68. package/dist/chunk-NZUFSSIZ.cjs +0 -1
  69. package/dist/chunk-NZUFSSIZ.cjs.map +0 -1
  70. package/dist/chunk-V3XPLTIR.cjs.map +0 -1
  71. package/dist/client-DHmr0Oyx.d.ts +0 -203
  72. package/dist/client-Q6NoqinP.d.cts +0 -203
  73. package/dist/index.cjs.map +0 -1
  74. package/dist/index.js.map +0 -1
  75. package/dist/operations/index.cjs.map +0 -1
  76. package/dist/operations/index.js.map +0 -1
  77. package/dist/pool.cjs.map +0 -1
  78. package/dist/pool.js.map +0 -1
  79. package/dist/resources/index.cjs.map +0 -1
  80. package/dist/resources/index.js.map +0 -1
  81. package/dist/types/index.cjs.map +0 -1
  82. package/dist/types/index.js.map +0 -1
  83. package/dist/untrusted.cjs.map +0 -1
  84. package/dist/untrusted.js.map +0 -1
@@ -2,36 +2,38 @@ import {
2
2
  AutotaskConfigError,
3
3
  AutotaskError,
4
4
  BLIND_SURFACE_INVENTORY,
5
+ BOUNDED_QUERY_MAX_LIMIT,
5
6
  CREATE_GUIDANCE,
6
7
  ConfirmationRequiredError,
7
8
  DELETE_BOUNDARY_NOTES,
8
9
  DeleteBoundaryError,
10
+ EXPAND_RESOURCE_NAMES_MAX_IDS,
9
11
  FIELD_CACHE_KINDS,
12
+ FIELD_CATALOG_HONESTY_NOTE,
10
13
  MARKER_FIELDS,
14
+ MAX_FIELD_CATALOG_WIRE,
11
15
  MarkerUnsafeIdError,
12
16
  NotFoundError,
17
+ PICKLIST_VALUE_NOT_SERVED_NOTE,
13
18
  REACHABILITY_PROBE_DEFAULTS,
14
19
  RELATIONSHIP_MAP,
15
20
  RELATIONSHIP_ZONE,
16
21
  RecoveryTruncatedError,
22
+ WORKFLOW_RESOURCES,
23
+ buildFilter,
24
+ buildQueryRequest,
17
25
  camelKey,
18
26
  clampPageSize,
27
+ expandResourceNames,
28
+ fieldCatalog,
19
29
  isAutotaskError,
20
30
  isRecord,
21
- parseIdentifier
22
- } from "../chunk-GYI4ZQID.js";
31
+ parseIdentifier,
32
+ unwrapItems
33
+ } from "../chunk-HWC2WPX2.js";
23
34
  import {
24
35
  CAPABILITIES
25
- } from "../chunk-32AKZMY4.js";
26
-
27
- // src/operations/types.ts
28
- var WORKFLOW_RESOURCES = [
29
- "companies",
30
- "tickets",
31
- "contacts",
32
- "configuration_items",
33
- "projects"
34
- ];
36
+ } from "../chunk-6KFP5HV3.js";
35
37
 
36
38
  // src/operations/searchAcrossResources.ts
37
39
  var DEFAULT_LIMIT = 25;
@@ -259,23 +261,23 @@ var MODEL_FIELD_SETS = {
259
261
 
260
262
  // src/operations/fetchMany.ts
261
263
  var FETCH_MANY_MAX_ITEMS = 20;
262
- function validateFetchManyItems(items, cap) {
264
+ function validateFetchManyItems(items, cap, opName = "fetchMany", capRef = `FETCH_MANY_MAX_ITEMS = ${FETCH_MANY_MAX_ITEMS}`, validation) {
263
265
  if (!Array.isArray(items)) {
264
266
  throw new AutotaskConfigError(
265
- `fetchMany: items must be the items ARRAY \u2014 one positional arg, [{ resource: string, id: number, fields?: string[] }] (got ${Array.isArray(items) ? "an array" : typeof items})`,
267
+ `${opName}: items must be the items ARRAY \u2014 one positional arg, [{ resource: string, id: number, fields?: string[] }] (got ${Array.isArray(items) ? "an array" : typeof items})`,
266
268
  {
267
269
  suggestedAction: 'Pass args = [[{ resource: "tickets", id: 123 }, { resource: "projects", id: 456 }]] \u2014 the items array is the single positional argument.'
268
270
  }
269
271
  );
270
272
  }
271
273
  if (items.length === 0) {
272
- throw new AutotaskConfigError("fetchMany: items must not be empty (a zero-item batch is a shape error, not a no-op)", {
274
+ throw new AutotaskConfigError(`${opName}: items must not be empty (a zero-item batch is a shape error, not a no-op)`, {
273
275
  suggestedAction: "Pass at least one { resource, id } item \u2014 or use the resource's query op for a filtered read."
274
276
  });
275
277
  }
276
278
  if (items.length > cap) {
277
279
  throw new AutotaskConfigError(
278
- `fetchMany: ${items.length} items over the ${cap}-item cap (FETCH_MANY_MAX_ITEMS = ${FETCH_MANY_MAX_ITEMS}) \u2014 zero items were fetched (pre-request)`,
280
+ `${opName}: ${items.length} items over the ${cap}-item cap (${capRef}) \u2014 zero items were fetched (pre-request)`,
279
281
  {
280
282
  suggestedAction: `Split the batch into groups of <= ${cap} items \u2014 or, for more than ${cap} records / a filtered set, use the resource's bounded query op (<resource>.query).`
281
283
  }
@@ -327,21 +329,24 @@ function validateFetchManyItems(items, cap) {
327
329
  }
328
330
  if (item.fields !== void 0) {
329
331
  if (!Array.isArray(item.fields) || item.fields.some((f) => typeof f !== "string")) {
330
- problems.push(`${at}.fields: must be an array of exact vendor field names (discover them via ${resourceKey}.getFieldInfo)`);
332
+ problems.push(`${at}.fields: must be an array of exact vendor field names (discover them via operations.fieldCatalog)`);
331
333
  continue;
332
334
  }
333
- for (const f of item.fields) {
334
- if (!fields.includes(f)) {
335
- problems.push(
336
- `${at}.fields: "${f}" is not a field of ${resourceKey} \u2014 a hallucinated field is a shape error, not a silent projection-miss (exact vendor field names via ${resourceKey}.getFieldInfo)`
337
- );
335
+ if (validation?.fieldCheck !== false) {
336
+ const authoritative = validation?.fieldSetsByResource?.get(resourceKey) ?? fields;
337
+ for (const f of item.fields) {
338
+ if (!authoritative.includes(f)) {
339
+ problems.push(
340
+ `${at}.fields: "${f}" is not a field of ${resourceKey} \u2014 a hallucinated field is a shape error, not a silent projection-miss (exact vendor field names via operations.fieldCatalog)`
341
+ );
342
+ }
338
343
  }
339
344
  }
340
345
  }
341
346
  }
342
347
  if (problems.length > 0) {
343
348
  throw new AutotaskConfigError(
344
- `fetchMany: ${problems.length} problem(s) found pre-request (zero wire was issued)
349
+ `${opName}: ${problems.length} problem(s) found pre-request (zero wire was issued)
345
350
  - ` + problems.join("\n- "),
346
351
  { suggestedAction: "Fix every item above and call again \u2014 validation covers the whole batch up-front (one round, zero wire)." }
347
352
  );
@@ -360,8 +365,38 @@ async function fetchMany(client, items, opts = {}) {
360
365
  throw new AutotaskConfigError(`fetchMany: maxItems must be an integer >= 1 (got ${String(opts.maxItems)})`);
361
366
  }
362
367
  }
368
+ if (opts.liveFields !== void 0 && typeof opts.liveFields !== "boolean") {
369
+ throw new AutotaskConfigError(`fetchMany: liveFields must be a boolean (got ${typeof opts.liveFields})`);
370
+ }
363
371
  const cap = Math.min(FETCH_MANY_MAX_ITEMS, opts.maxItems ?? FETCH_MANY_MAX_ITEMS);
364
- validateFetchManyItems(items, cap);
372
+ if (opts.liveFields === true) {
373
+ validateFetchManyItems(items, cap, void 0, void 0, { fieldCheck: false });
374
+ const liveFieldSets = /* @__PURE__ */ new Map();
375
+ for (const item of items) {
376
+ const key = item.resource;
377
+ if (liveFieldSets.has(key)) continue;
378
+ const holder = client[camelKey(key)];
379
+ if (holder === void 0) {
380
+ throw new AutotaskConfigError(`fetchMany: the client exposes no accessor for resource "${key}"`, {
381
+ suggestedAction: "Rebuild the client from the SDK build that ships this registry (the resource set is pinned by the plan)."
382
+ });
383
+ }
384
+ try {
385
+ const rows = await holder.getFieldInfo();
386
+ const names = [];
387
+ for (const row of rows) {
388
+ if (typeof row.name === "string") names.push(row.name);
389
+ }
390
+ liveFieldSets.set(key, names.length > 0 ? names : null);
391
+ } catch (err) {
392
+ if (!isAutotaskError(err) || err.code === "ABORTED") throw err;
393
+ liveFieldSets.set(key, null);
394
+ }
395
+ }
396
+ validateFetchManyItems(items, cap, void 0, void 0, { fieldSetsByResource: liveFieldSets });
397
+ } else {
398
+ validateFetchManyItems(items, cap);
399
+ }
365
400
  const results = [];
366
401
  for (const item of items) {
367
402
  const key = item.resource;
@@ -391,6 +426,286 @@ async function fetchMany(client, items, opts = {}) {
391
426
  return { results, ok, failed: results.length - ok };
392
427
  }
393
428
 
429
+ // src/operations/aggregate.ts
430
+ var AGGREGATE_TOP_MAX = 100;
431
+ var AGGREGATE_DEFAULT_TOP = 25;
432
+ var AGGREGATE_DEFAULT_LIMIT = BOUNDED_QUERY_MAX_LIMIT;
433
+ var QUERY_RESOURCE_KEYS = (() => {
434
+ const s = /* @__PURE__ */ new Set();
435
+ for (const rec of CAPABILITIES) if (rec.operation === "query") s.add(rec.resource);
436
+ return s;
437
+ })();
438
+ var AGGREGATE_OPT_KEYS = ["groupBy", "sum", "top", "limit"];
439
+ var OP_RE = /^([a-z0-9_]+)\.query$/;
440
+ function num(v) {
441
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
442
+ }
443
+ function validateAggregate(operation, filter, opts) {
444
+ const problems = [];
445
+ let resourceKey;
446
+ if (typeof operation !== "string" || !OP_RE.test(operation)) {
447
+ problems.push(
448
+ `operation must be exactly "<planResourceKey>.query" (the resource's bounded query op \u2014 e.g. "billing_items.query"); a special op, a ".count" row, or any other verb is not an aggregate surface \u2014 see autotask_catalog for the exact plan keys`
449
+ );
450
+ } else {
451
+ const key = OP_RE.exec(operation)[1];
452
+ resourceKey = key;
453
+ if (!QUERY_RESOURCE_KEYS.has(key)) {
454
+ problems.push(
455
+ `operation "${operation}": "${key}" has no query row in the capability registry (116 query surfaces) \u2014 see autotask_catalog for the exact plan keys`
456
+ );
457
+ }
458
+ }
459
+ let conditions = [];
460
+ if (filter === void 0 || filter === null) {
461
+ conditions = [];
462
+ } else {
463
+ const conds = Array.isArray(filter) ? filter : filter.filter;
464
+ if (!Array.isArray(conds)) {
465
+ problems.push("filter must be the query-bag filter (a conditions array, or { filter: [...] })");
466
+ } else if (conds.length === 0) {
467
+ conditions = [];
468
+ } else {
469
+ try {
470
+ conditions = buildFilter(conds).filter;
471
+ } catch (err) {
472
+ problems.push(err instanceof AutotaskConfigError ? err.message : `filter must be the query-bag filter (13 operators; caps 500 OR-conditions / 1 UDF / 8000 chars); ${String(err)}`);
473
+ }
474
+ }
475
+ }
476
+ if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
477
+ throw new AutotaskConfigError(
478
+ `aggregate: opts must be the options object (got ${Array.isArray(opts) ? "an array" : typeof opts})`,
479
+ { operation: "operations.aggregate" }
480
+ );
481
+ }
482
+ for (const key of Object.keys(opts ?? {})) {
483
+ if (!AGGREGATE_OPT_KEYS.includes(key)) {
484
+ problems.push(`unknown opts key "${key}" \u2014 the bag is closed: ${AGGREGATE_OPT_KEYS.join(", ")}`);
485
+ }
486
+ }
487
+ const fieldSets = resourceKey !== void 0 ? MODEL_FIELD_SETS[resourceKey] : void 0;
488
+ const checkFields = (name, fields) => {
489
+ if (fields === void 0) return [];
490
+ if (!Array.isArray(fields) || fields.length === 0 || fields.some((f) => typeof f !== "string" || f.trim() === "")) {
491
+ problems.push(`${name} must be a non-empty array of exact vendor field names (discover them via operations.fieldCatalog)`);
492
+ return [];
493
+ }
494
+ const out = [];
495
+ for (const f of fields) {
496
+ if (fieldSets === void 0 || !fieldSets.includes(f)) {
497
+ problems.push(`${name}: "${f}" is not a field of ${resourceKey} \u2014 a hallucinated field is a shape error, not a silent fold-miss (exact vendor field names via operations.fieldCatalog)`);
498
+ } else {
499
+ out.push(f);
500
+ }
501
+ }
502
+ return out;
503
+ };
504
+ const groupBy = checkFields("groupBy", opts.groupBy);
505
+ const sum = checkFields("sum", opts.sum);
506
+ let top = AGGREGATE_DEFAULT_TOP;
507
+ if (opts.top !== void 0) {
508
+ if (!Number.isInteger(opts.top)) {
509
+ problems.push(`top must be an integer (got ${String(opts.top)})`);
510
+ } else {
511
+ top = Math.min(Math.max(opts.top, 1), AGGREGATE_TOP_MAX);
512
+ }
513
+ }
514
+ let limit = AGGREGATE_DEFAULT_LIMIT;
515
+ if (opts.limit !== void 0) {
516
+ if (!Number.isInteger(opts.limit)) {
517
+ problems.push(`limit must be an integer (got ${String(opts.limit)})`);
518
+ } else {
519
+ limit = Math.min(Math.max(opts.limit, 1), BOUNDED_QUERY_MAX_LIMIT);
520
+ }
521
+ }
522
+ if (problems.length > 0) {
523
+ throw new AutotaskConfigError(
524
+ `aggregate: ${problems.length} problem(s) found pre-request (zero wire was issued)
525
+ - ` + problems.join("\n- "),
526
+ { operation: "operations.aggregate" }
527
+ );
528
+ }
529
+ return { resourceKey, conditions, groupBy, sum, top, limit };
530
+ }
531
+ async function aggregate(client, operation, filter, opts = {}) {
532
+ const v = validateAggregate(operation, filter, opts);
533
+ const holder = client[camelKey(v.resourceKey)];
534
+ if (holder === void 0) {
535
+ throw new AutotaskConfigError(`aggregate: the client exposes no accessor for resource "${v.resourceKey}"`, {
536
+ operation: "operations.aggregate",
537
+ suggestedAction: "Rebuild the client from the SDK build that ships this registry (the resource set is pinned by the plan)."
538
+ });
539
+ }
540
+ const wireFilter = v.conditions.length === 0 ? void 0 : v.conditions;
541
+ if (v.groupBy.length === 0 && v.sum.length === 0) {
542
+ const raw = await holder.count({ filter: wireFilter });
543
+ const sentinel = typeof raw === "object" && raw !== null && "count" in raw ? raw : { count: raw };
544
+ const result2 = { mode: "count", count: sentinel.count, blind: sentinel.blindSurface !== void 0 };
545
+ if (sentinel.blindSurface !== void 0) result2.blindSurface = sentinel.blindSurface;
546
+ if (sentinel.emptyReason !== void 0) result2.emptyReason = sentinel.emptyReason;
547
+ return result2;
548
+ }
549
+ const res = await holder.queryBounded({ filter: wireFilter, limit: v.limit });
550
+ const groups = /* @__PURE__ */ new Map();
551
+ for (const item of res.items) {
552
+ const key = {};
553
+ const values = [];
554
+ for (const f of v.groupBy) {
555
+ const value = item[f] === void 0 ? null : item[f];
556
+ key[f] = value;
557
+ values.push(value);
558
+ }
559
+ const keyOrder = JSON.stringify(values);
560
+ let g = groups.get(keyOrder);
561
+ if (g === void 0) {
562
+ g = { key, keyOrder, count: 0, ...v.sum.length > 0 ? { sums: {} } : {} };
563
+ groups.set(keyOrder, g);
564
+ }
565
+ g.count += 1;
566
+ if (g.sums !== void 0) {
567
+ for (const f of v.sum) g.sums[f] = (g.sums[f] ?? 0) + num(item[f]);
568
+ }
569
+ }
570
+ const sorted = [...groups.values()].sort((a, b) => b.count - a.count || (a.keyOrder < b.keyOrder ? -1 : a.keyOrder > b.keyOrder ? 1 : 0));
571
+ const top = sorted.slice(0, v.top);
572
+ const result = {
573
+ mode: v.groupBy.length > 0 ? "group" : "sum",
574
+ groups: top.map((g) => ({ key: g.key, count: g.count, ...g.sums !== void 0 ? { sums: g.sums } : {} })),
575
+ totalGroups: sorted.length,
576
+ groupsTruncated: sorted.length > top.length,
577
+ totalScanned: res.fetched,
578
+ hasMore: res.hasMore
579
+ };
580
+ if (res.blindSurface !== void 0) result.blindSurface = res.blindSurface;
581
+ if (res.emptyReason !== void 0) result.emptyReason = res.emptyReason;
582
+ return result;
583
+ }
584
+
585
+ // src/operations/fetchByIds.ts
586
+ var FETCH_BY_IDS_MAX_ITEMS = 500;
587
+ var FETCH_BY_IDS_CHUNK_SIZE = 250;
588
+ function project2(value, fields) {
589
+ const out = {};
590
+ for (const f of fields) {
591
+ if (f in value) out[f] = value[f];
592
+ }
593
+ return out;
594
+ }
595
+ function resourcePathOf(key, holder) {
596
+ const path = holder?.resourcePath;
597
+ if (typeof path !== "string" || path === "") {
598
+ throw new AutotaskConfigError(`fetchByIds: the client exposes no ${key} resource path (registry drift)`, {
599
+ suggestedAction: "Rebuild the client from the SDK build that ships this registry (the resource set is pinned by the plan)."
600
+ });
601
+ }
602
+ return path;
603
+ }
604
+ async function fetchByIds(client, items, opts = {}) {
605
+ if (opts.maxItems !== void 0) {
606
+ if (!Number.isInteger(opts.maxItems) || opts.maxItems < 1) {
607
+ throw new AutotaskConfigError(`fetchByIds: maxItems must be an integer >= 1 (got ${String(opts.maxItems)})`);
608
+ }
609
+ }
610
+ const cap = Math.min(FETCH_BY_IDS_MAX_ITEMS, opts.maxItems ?? FETCH_BY_IDS_MAX_ITEMS);
611
+ validateFetchManyItems(items, cap, "fetchByIds", `FETCH_BY_IDS_MAX_ITEMS = ${FETCH_BY_IDS_MAX_ITEMS}`);
612
+ const groups = /* @__PURE__ */ new Map();
613
+ const canonical = [];
614
+ for (let i = 0; i < items.length; i += 1) {
615
+ const item = items[i];
616
+ const id = parseIdentifier(item.id, `items[${i}].id`);
617
+ canonical[i] = id;
618
+ const g = groups.get(item.resource) ?? { indices: [], fields: /* @__PURE__ */ new Set(), anyFields: false };
619
+ g.indices.push(i);
620
+ if (item.fields !== void 0) {
621
+ g.anyFields = true;
622
+ for (const f of item.fields) g.fields.add(f);
623
+ }
624
+ groups.set(item.resource, g);
625
+ }
626
+ const byKey = /* @__PURE__ */ new Map();
627
+ for (const [key, group] of groups) {
628
+ const holder = client[camelKey(key)];
629
+ if (holder === void 0) {
630
+ throw new AutotaskConfigError(`fetchByIds: the client exposes no accessor for resource "${key}"`, {
631
+ suggestedAction: "Rebuild the client from the SDK build that ships this registry (the resource set is pinned by the plan)."
632
+ });
633
+ }
634
+ const resourcePath = resourcePathOf(key, holder);
635
+ const unique = [];
636
+ const seen = /* @__PURE__ */ new Set();
637
+ for (const i of group.indices) {
638
+ const k = String(canonical[i]);
639
+ if (!seen.has(k)) {
640
+ seen.add(k);
641
+ unique.push(canonical[i]);
642
+ }
643
+ }
644
+ const includeFields = group.anyFields && group.indices.every((i) => items[i].fields !== void 0) ? [...group.fields] : void 0;
645
+ const kOf = (id) => `${key}:${String(id)}`;
646
+ for (let c = 0; c < unique.length; c += FETCH_BY_IDS_CHUNK_SIZE) {
647
+ const chunk = unique.slice(c, c + FETCH_BY_IDS_CHUNK_SIZE);
648
+ const chunkKeys = new Set(chunk.map((id) => String(id)));
649
+ try {
650
+ const req = buildQueryRequest(client.http.baseUrl, resourcePath, {
651
+ filter: [{ field: "id", op: "in", value: chunk }],
652
+ maxRecords: FETCH_BY_IDS_CHUNK_SIZE,
653
+ includeFields
654
+ });
655
+ const res = await client.http.request({ ...req, operation: `${key}.fetchByIds` });
656
+ const { items: rows } = unwrapItems(res, `${key}.fetchByIds`);
657
+ for (const row of rows) {
658
+ const k = String(row.id);
659
+ if (!chunkKeys.has(k)) continue;
660
+ const q = kOf(k);
661
+ if (!byKey.has(q)) byKey.set(q, { found: true, value: row });
662
+ }
663
+ } catch (err) {
664
+ if (isAutotaskError(err) && err.code !== "ABORTED") {
665
+ const e = { code: err.code, message: err.message };
666
+ if (err.httpStatus !== void 0) e.httpStatus = err.httpStatus;
667
+ for (const u of chunk) {
668
+ const q = kOf(u);
669
+ if (!byKey.has(q)) byKey.set(q, { found: false, value: null, error: e });
670
+ }
671
+ continue;
672
+ }
673
+ throw err;
674
+ }
675
+ }
676
+ for (const id of unique) {
677
+ const k = kOf(id);
678
+ if (byKey.has(k)) continue;
679
+ try {
680
+ const value = await holder.get(id);
681
+ byKey.set(k, { found: true, value });
682
+ } catch (err) {
683
+ if (err instanceof NotFoundError) {
684
+ byKey.set(k, { found: false, value: null, error: { code: "NOT_FOUND", message: err.message } });
685
+ } else if (isAutotaskError(err) && err.code !== "ABORTED") {
686
+ const e = { code: err.code, message: err.message };
687
+ if (err.httpStatus !== void 0) e.httpStatus = err.httpStatus;
688
+ byKey.set(k, { found: false, value: null, error: e });
689
+ } else {
690
+ throw err;
691
+ }
692
+ }
693
+ }
694
+ }
695
+ const results = [];
696
+ for (let i = 0; i < items.length; i += 1) {
697
+ const item = items[i];
698
+ const id = canonical[i];
699
+ const out = byKey.get(`${item.resource}:${String(id)}`);
700
+ const value = out.found ? item.fields !== void 0 ? project2(out.value, item.fields) : out.value : null;
701
+ results.push(
702
+ out.found ? { resource: item.resource, id, found: true, value } : { resource: item.resource, id, found: false, value: null, error: out.error }
703
+ );
704
+ }
705
+ const ok = results.filter((r) => r.found).length;
706
+ return { results, ok, failed: results.length - ok };
707
+ }
708
+
394
709
  // src/operations/relationshipMap.ts
395
710
  async function relationshipMap(client, resourceKey) {
396
711
  void client;
@@ -628,229 +943,6 @@ async function createGuidance(client, opts) {
628
943
  };
629
944
  }
630
945
 
631
- // src/operations/fieldCatalog.ts
632
- var MAX_FIELD_CATALOG_WIRE = 60;
633
- var FIELD_CATALOG_HONESTY_NOTE = "Live field metadata reports the SHAPE of a field (existence, requiredness, type, maxLength, read-only, queryable, picklist-ness) and serves picklist VALUES where the vendor exposes them. It cannot resolve a picklist VALUE the vendor does not expose to this API user (the W-A4 wall \u2014 e.g. the attachments Type picklist): retrying the catalog will not surface a withheld value; that gap is vendor-side, not a client defect.";
634
- var FIELD_CATALOG_OPT_KEYS = ["resources", "include", "fresh"];
635
- function projectFieldRow(raw) {
636
- const r = typeof raw === "object" && raw !== null ? raw : {};
637
- const name = typeof r.name === "string" ? r.name : "";
638
- const type = typeof r.dataType === "string" ? r.dataType : typeof r.type === "string" ? r.type : "";
639
- return {
640
- name,
641
- ...typeof r.label === "string" && r.label !== "" ? { label: r.label } : {},
642
- type,
643
- isRequired: r.isRequired === true,
644
- isReadOnly: r.isReadOnly === true,
645
- isQueryable: r.isQueryable === true,
646
- isPickList: r.isPickList === true,
647
- maxLength: typeof r.length === "number" ? r.length : null,
648
- referenceEntityType: typeof r.referenceEntityType === "string" ? r.referenceEntityType : ""
649
- };
650
- }
651
- function exposesPicklistValues(raw) {
652
- const r = typeof raw === "object" && raw !== null ? raw : {};
653
- return Array.isArray(r.picklistValues) && r.picklistValues.length > 0;
654
- }
655
- function capabilitySets() {
656
- const fieldOps = /* @__PURE__ */ new Map();
657
- const udfOps = /* @__PURE__ */ new Map();
658
- const resources = /* @__PURE__ */ new Set();
659
- for (const rec of CAPABILITIES) {
660
- if (rec.resource === "operations") continue;
661
- resources.add(rec.resource);
662
- if (rec.operation === "getFieldInfo") fieldOps.set(rec.resource, rec);
663
- if (rec.operation === "getUdfInfo") udfOps.set(rec.resource, rec);
664
- }
665
- return { fieldOps, udfOps, resources };
666
- }
667
- function apiEntityFor(rec) {
668
- const m = /^GET \/V1\.0\/([^/]+)\/entityInformation\/fields$/.exec(rec.endpoint ?? "");
669
- if (m === null) {
670
- throw new AutotaskConfigError(
671
- `fieldCatalog: the plan's getFieldInfo endpoint for "${rec.resource}" has no /V1.0/<Entity>/entityInformation/fields shape (${rec.endpoint}) \u2014 plan defect`,
672
- { operation: "operations.fieldCatalog" }
673
- );
674
- }
675
- return m[1];
676
- }
677
- async function fieldCatalog(client, opts) {
678
- if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
679
- throw new AutotaskConfigError(
680
- "fieldCatalog: opts must be an object { resources?: string[], include?: (info|fields|udf)[], fresh?: boolean }"
681
- );
682
- }
683
- for (const key of Object.keys(opts ?? {})) {
684
- if (!FIELD_CATALOG_OPT_KEYS.includes(key)) {
685
- throw new AutotaskConfigError(
686
- `fieldCatalog: unknown opts key "${key}" \u2014 the bag is closed: ${FIELD_CATALOG_OPT_KEYS.join(", ")}`,
687
- { suggestedAction: "Drop the unknown key (autotask_describe(operations.fieldCatalog) shows the exact bag)." }
688
- );
689
- }
690
- }
691
- const { fieldOps, udfOps, resources: allResources } = capabilitySets();
692
- const includeRaw = opts?.include ?? ["fields"];
693
- if (!Array.isArray(includeRaw) || includeRaw.length === 0) {
694
- throw new AutotaskConfigError("fieldCatalog: opts.include must be a non-empty array of (info|fields|udf)");
695
- }
696
- const include = [];
697
- for (const kind of includeRaw) {
698
- if (typeof kind !== "string" || !FIELD_CACHE_KINDS.includes(kind)) {
699
- throw new AutotaskConfigError(
700
- `fieldCatalog: unknown include kind "${String(kind)}" \u2014 the valid kinds are: ${FIELD_CACHE_KINDS.join(", ")}`,
701
- { suggestedAction: `Filter with one or more of: ${FIELD_CACHE_KINDS.join(", ")} (default ['fields']).` }
702
- );
703
- }
704
- if (!include.includes(kind)) include.push(kind);
705
- }
706
- const resourcesRaw = opts?.resources ?? WORKFLOW_RESOURCES;
707
- if (!Array.isArray(resourcesRaw) || resourcesRaw.length === 0) {
708
- throw new AutotaskConfigError("fieldCatalog: opts.resources must be a non-empty array of plan resource keys");
709
- }
710
- const targets = [];
711
- for (const r of resourcesRaw) {
712
- if (typeof r !== "string" || r.trim() === "") {
713
- throw new AutotaskConfigError(
714
- `fieldCatalog: opts.resources must contain non-empty plan resource key strings (got ${typeof r === "string" && r.trim() === "" ? "a blank string" : typeof r})`
715
- );
716
- }
717
- const key = r.trim();
718
- const fieldOp = fieldOps.get(key);
719
- if (fieldOp === void 0) {
720
- if (allResources.has(key)) {
721
- throw new AutotaskConfigError(
722
- `fieldCatalog: resource "${key}" has no field catalog \u2014 the vendor defines the entityInformation trio for the ${fieldOps.size} plan entity resources only`,
723
- { suggestedAction: `Pass one of the ${fieldOps.size} field-capable plan keys (list them with autotask_catalog).` }
724
- );
725
- }
726
- throw new AutotaskError(
727
- `fieldCatalog: unknown resource "${key}" \u2014 the plan has ${fieldOps.size} field-capable resources`,
728
- {
729
- code: "RESOURCE_UNKNOWN",
730
- category: "validation",
731
- suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items)."
732
- }
733
- );
734
- }
735
- if (!targets.includes(key)) targets.push(key);
736
- }
737
- const cost = targets.length * include.length;
738
- if (cost > MAX_FIELD_CATALOG_WIRE) {
739
- throw new AutotaskConfigError(
740
- `fieldCatalog: ${targets.length} resource(s) \xD7 ${include.length} kind(s) = ${cost} metadata reads exceed the wire cap of ${MAX_FIELD_CATALOG_WIRE} \u2014 the call is refused above its cap (never truncated)`,
741
- { suggestedAction: `Pass a smaller resources list or a narrower include set so the product stays \u2264 ${MAX_FIELD_CATALOG_WIRE}.` }
742
- );
743
- }
744
- const cache = client.http.fieldCache;
745
- const fresh = opts?.fresh === true;
746
- const zone = client.config.zone ?? "unknown";
747
- const rows = [];
748
- let wireUsed = 0;
749
- let anyPicklistValues = false;
750
- for (const resource of targets) {
751
- const fieldOp = fieldOps.get(resource);
752
- if (fieldOp === void 0) continue;
753
- const apiEntity = apiEntityFor(fieldOp);
754
- const holder = client[camelKey(resource)];
755
- const row = {
756
- resource,
757
- apiEntity,
758
- udfCapable: udfOps.has(resource),
759
- required: [],
760
- readOnly: [],
761
- queryable: [],
762
- picklistFields: [],
763
- cache: { hit: false, age: null },
764
- wire: 0,
765
- error: null
766
- };
767
- const fetches = {
768
- info: () => holder?.getEntityInfo?.() ?? Promise.reject(new AutotaskError(`fieldCatalog: the client exposes no getEntityInfo read for "${resource}"`, { code: "INVALID_RESPONSE", category: "server" })),
769
- fields: () => holder?.getFieldInfo?.() ?? Promise.reject(new AutotaskError(`fieldCatalog: the client exposes no getFieldInfo read for "${resource}"`, { code: "INVALID_RESPONSE", category: "server" })),
770
- udf: () => holder?.getUdfInfo?.() ?? Promise.reject(new AutotaskError(`fieldCatalog: the client exposes no getUdfInfo read for "${resource}"`, { code: "INVALID_RESPONSE", category: "server" }))
771
- };
772
- if (fresh) cache?.invalidate(resource);
773
- let allCached = true;
774
- const ages = [];
775
- for (const kind of include) {
776
- const peek = cache !== void 0 && !fresh ? cache.peek(resource, kind) : { hit: false, age: null };
777
- if (peek.hit !== true) allCached = false;
778
- if (peek.hit === true && cache !== void 0) {
779
- const value2 = cache.get(resource, kind);
780
- if (kind === "info") row.info = value2 ?? null;
781
- else {
782
- const list = Array.isArray(value2) ? value2 : [];
783
- const projected = list.map((f) => projectFieldRow(f));
784
- if (kind === "fields") row.fields = projected;
785
- else row.udf = projected;
786
- for (const f of list) if (exposesPicklistValues(f)) anyPicklistValues = true;
787
- if (kind === "fields") {
788
- row.required = projected.filter((f) => f.isRequired).map((f) => f.name);
789
- row.readOnly = projected.filter((f) => f.isReadOnly).map((f) => f.name);
790
- row.queryable = projected.filter((f) => f.isQueryable).map((f) => f.name);
791
- row.picklistFields = projected.filter((f) => f.isPickList).map((f) => f.name);
792
- }
793
- }
794
- if (peek.age !== null) ages.push(peek.age);
795
- continue;
796
- }
797
- let value;
798
- try {
799
- value = await fetches[kind]();
800
- } catch (err) {
801
- wireUsed += 1;
802
- row.wire += 1;
803
- if (row.error === null) {
804
- const code = isAutotaskError(err) ? err.code : "NETWORK_ERROR";
805
- const message = err instanceof Error ? err.message : String(err);
806
- row.error = { code, message };
807
- }
808
- continue;
809
- }
810
- wireUsed += 1;
811
- row.wire += 1;
812
- if (kind === "info") {
813
- row.info = value ?? null;
814
- } else {
815
- const list = Array.isArray(value) ? value : [];
816
- const projected = list.map((f) => projectFieldRow(f));
817
- if (kind === "fields") row.fields = projected;
818
- else row.udf = projected;
819
- for (const f of list) if (exposesPicklistValues(f)) anyPicklistValues = true;
820
- if (kind === "fields") {
821
- row.required = projected.filter((f) => f.isRequired).map((f) => f.name);
822
- row.readOnly = projected.filter((f) => f.isReadOnly).map((f) => f.name);
823
- row.queryable = projected.filter((f) => f.isQueryable).map((f) => f.name);
824
- row.picklistFields = projected.filter((f) => f.isPickList).map((f) => f.name);
825
- }
826
- }
827
- }
828
- row.cache = allCached ? { hit: true, age: ages.length > 0 ? Math.max(...ages) : 0 } : { hit: false, age: null };
829
- rows.push(row);
830
- }
831
- return {
832
- zone,
833
- label: zone,
834
- cached: cache !== void 0,
835
- ttlMs: cache?.ttlMs ?? null,
836
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
837
- rows,
838
- wireUsed,
839
- cap: MAX_FIELD_CATALOG_WIRE,
840
- truncated: false,
841
- counts: {
842
- resources: targets.length,
843
- fieldCapableResources: fieldOps.size,
844
- udfCapableResources: udfOps.size,
845
- cached: rows.filter((r) => r.cache.hit === true).length
846
- },
847
- honesty: {
848
- picklistValues: anyPicklistValues ? "partial" : "unexposed",
849
- note: FIELD_CATALOG_HONESTY_NOTE
850
- }
851
- };
852
- }
853
-
854
946
  // src/operations/recovery.ts
855
947
  var RECOVERY_CONFIRM = "operations.recovery";
856
948
  var RECOVERY_DEFAULT_LIMIT = 25;
@@ -1211,12 +1303,14 @@ function validateDispatchArgs(record, argsIn) {
1211
1303
  }
1212
1304
  }
1213
1305
  var PARAMS_BAG_KEYS = {
1214
- query: ["filter", "limit", "includeFields", "via"],
1306
+ query: ["filter", "limit", "includeFields", "via", "latest", "noteChars", "fieldsPreset"],
1215
1307
  queryAll: ["filter", "maxRecords", "includeFields", "maxTotalRecords", "via"],
1216
1308
  count: ["filter"]
1217
1309
  };
1218
1310
  var BAG_KEY_SYNONYMS = {
1219
1311
  filters: "filter",
1312
+ last: "latest",
1313
+ // issue #59: the expected typo class (honoured only on `query`)
1220
1314
  lmit: "limit",
1221
1315
  page: "limit",
1222
1316
  pageSize: "limit",
@@ -1330,11 +1424,29 @@ async function invokeMode(client, operation, argsIn = [], opts = {}) {
1330
1424
  validateParamsBag(record, args);
1331
1425
  const boundedQuery = record.kind === "primitive" && record.operation === "query";
1332
1426
  let holder = client[camelKey(record.resource)];
1427
+ if (typeof holder === "function") {
1428
+ holder = client[`${camelKey(record.resource)}Resource`] ?? holder;
1429
+ }
1333
1430
  if (holder === void 0 && record.resource === "operations") {
1334
1431
  holder = {
1335
1432
  resolveAny: (identifier, opts2) => resolveAny(client, identifier, opts2),
1336
1433
  searchAcrossResources: (query, opts2) => searchAcrossResources(client, query, opts2),
1337
1434
  fetchMany: (items, opts2) => fetchMany(client, items, opts2),
1435
+ // Issue #62: the generic aggregate + the cheap count — the
1436
+ // groupBy/sum-absent mode is ONE vendor /query/count (the S12-C blind
1437
+ // sentinel rides through); the group/sum modes walk the named
1438
+ // <resource>.query op via the house bounded walk and fold client-side
1439
+ // (truncation honesty: hasMore/totalScanned — no silent partial sums).
1440
+ aggregate: (operation2, filter, opts2) => aggregate(client, operation2, filter, opts2),
1441
+ // Issue #59 PR-B: the bulk fetch by ids — query-first chunked `id in`
1442
+ // reads + the per-id `get` fallback (blind-surface-safe; per-item
1443
+ // typed outcomes reuse the fetchMany shapes).
1444
+ fetchByIds: (items, opts2) => fetchByIds(client, items, opts2),
1445
+ // Issue #65: opt-in resource name expansion — per-id resources.get
1446
+ // reads ONLY (never an id-in query — the resources table can be blind
1447
+ // to count/query while per-id reads work); blind-marked rows for ids
1448
+ // the API user cannot resolve; the name cache (default-on).
1449
+ expandResourceNames: (ids, opts2) => expandResourceNames(client, ids, opts2),
1338
1450
  relationshipMap: (resourceKey) => relationshipMap(client, resourceKey),
1339
1451
  // Issue #11: the served blind-surface inventory (zero wire) + the
1340
1452
  // bounded caller-opted reachability sweep (read-only, confirmProbe-
@@ -1425,20 +1537,30 @@ async function invokeDelete(client, operation, args = [], opts = {}) {
1425
1537
  return invokeMode(client, operation, args, { ...opts, mode: "delete" });
1426
1538
  }
1427
1539
  export {
1540
+ AGGREGATE_DEFAULT_LIMIT,
1541
+ AGGREGATE_DEFAULT_TOP,
1542
+ AGGREGATE_TOP_MAX,
1428
1543
  BLIND_SURFACE_KINDS,
1429
1544
  CREATE_BLOCKER_KINDS,
1545
+ EXPAND_RESOURCE_NAMES_MAX_IDS,
1546
+ FETCH_BY_IDS_CHUNK_SIZE,
1547
+ FETCH_BY_IDS_MAX_ITEMS,
1430
1548
  FETCH_MANY_MAX_ITEMS,
1431
1549
  FIELD_CACHE_KINDS,
1432
1550
  FIELD_CATALOG_HONESTY_NOTE,
1433
1551
  INVOKE_OPTION_KEYS,
1434
1552
  MAX_FIELD_CATALOG_WIRE,
1435
1553
  PARAMS_BAG_KEYS,
1554
+ PICKLIST_VALUE_NOT_SERVED_NOTE,
1436
1555
  RECOVERY_CONFIRM,
1437
1556
  RECOVERY_DEFAULT_LIMIT,
1438
1557
  RECOVERY_MAX_LIMIT,
1439
1558
  WORKFLOW_RESOURCES,
1559
+ aggregate,
1440
1560
  blindSurfaces,
1441
1561
  createGuidance,
1562
+ expandResourceNames,
1563
+ fetchByIds,
1442
1564
  fetchMany,
1443
1565
  fieldCatalog,
1444
1566
  invoke,
@@ -1457,4 +1579,3 @@ export {
1457
1579
  validateInvokeOptions,
1458
1580
  validateParamsBag
1459
1581
  };
1460
- //# sourceMappingURL=index.js.map