node-autotask 0.43.0 → 0.45.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 (57) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/MCP_TOOL_CATALOG.json +156 -1253
  3. package/MCP_TOOL_MANIFEST.md +154 -220
  4. package/MCP_TOOL_OVERRIDES.json +8 -62
  5. package/N8N_MCP_MANIFEST.json +128 -705
  6. package/N8N_MCP_MANIFEST.md +43 -63
  7. package/README.md +10 -28
  8. package/capabilities.json +15315 -22219
  9. package/dist/{base-CVx2VMSj.d.cts → base-BVoYDlVA.d.cts} +2 -1
  10. package/dist/{base-CVx2VMSj.d.ts → base-BVoYDlVA.d.ts} +2 -1
  11. package/dist/capabilities.cjs +2 -2
  12. package/dist/capabilities.d.cts +2 -1
  13. package/dist/capabilities.d.ts +2 -1
  14. package/dist/capabilities.js +1 -1
  15. package/dist/{chunk-5VHSLJMR.js → chunk-A6LHCJQP.js} +30 -9
  16. package/dist/{chunk-JY2BWYXY.js → chunk-ASMCTZO2.js} +15314 -22218
  17. package/dist/{chunk-TH7B5H4P.cjs → chunk-DC3PY4EF.cjs} +1839 -1407
  18. package/dist/{chunk-4EFXIE53.js → chunk-DXKXQHCX.js} +5 -5
  19. package/dist/{chunk-SK3GYRNA.cjs → chunk-JO6WI763.cjs} +143 -143
  20. package/dist/{chunk-Q23JULNN.js → chunk-NSW7JPVS.js} +11529 -13319
  21. package/dist/{chunk-KTEDPS4X.js → chunk-O4PIPUS4.js} +1583 -1151
  22. package/dist/{chunk-MJTKK3QG.cjs → chunk-SN4BMQ2N.cjs} +31 -10
  23. package/dist/{chunk-5BDUMDAQ.cjs → chunk-VXVYFJH3.cjs} +15314 -22218
  24. package/dist/{chunk-VFLAMMIJ.cjs → chunk-YMBVKVK4.cjs} +10604 -12394
  25. package/dist/{index-C7rMJ4IV.d.ts → index-C5g1H_hF.d.ts} +1080 -1288
  26. package/dist/{index-Cm_Xpt7X.d.cts → index-Db0SNnQn.d.cts} +1080 -1288
  27. package/dist/{index-C3KFMnRT.d.cts → index-c549LvOb.d.cts} +332 -331
  28. package/dist/{index-C3KFMnRT.d.ts → index-c549LvOb.d.ts} +332 -331
  29. package/dist/index.cjs +23 -23
  30. package/dist/index.d.cts +15 -41
  31. package/dist/index.d.ts +15 -41
  32. package/dist/index.js +8 -8
  33. package/dist/operations/index.cjs +5 -2234
  34. package/dist/operations/index.d.cts +51 -323
  35. package/dist/operations/index.d.ts +51 -323
  36. package/dist/operations/index.js +30 -2259
  37. package/dist/pool.cjs +9 -9
  38. package/dist/pool.d.cts +4 -3
  39. package/dist/pool.d.ts +4 -3
  40. package/dist/pool.js +5 -5
  41. package/dist/resources/index.cjs +5 -5
  42. package/dist/resources/index.d.cts +2 -2
  43. package/dist/resources/index.d.ts +2 -2
  44. package/dist/resources/index.js +8 -8
  45. package/dist/types/index.d.cts +1 -1
  46. package/dist/types/index.d.ts +1 -1
  47. package/dist/untrusted.cjs +2 -2
  48. package/dist/untrusted.d.cts +14 -5
  49. package/dist/untrusted.d.ts +14 -5
  50. package/dist/untrusted.js +1 -1
  51. package/docs/API.md +321 -334
  52. package/docs/LIVE-TESTING.md +4 -6
  53. package/docs/N8N-ADAPTER.md +26 -26
  54. package/examples/mcp/schemas.ts +2 -0
  55. package/examples/mcp/tools-read.ts +3 -2
  56. package/n8n-mcp.curation.json +3 -3
  57. package/package.json +10 -9
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
4
 
@@ -41,6 +41,9 @@
41
41
 
42
42
 
43
43
 
44
+ var _chunkYMBVKVK4cjs = require('../chunk-YMBVKVK4.cjs');
45
+ require('../chunk-VXVYFJH3.cjs');
46
+ require('../chunk-SN4BMQ2N.cjs');
44
47
 
45
48
 
46
49
 
@@ -70,2200 +73,12 @@
70
73
 
71
74
 
72
75
 
73
- var _chunkVFLAMMIJcjs = require('../chunk-VFLAMMIJ.cjs');
74
76
 
75
77
 
76
- var _chunk5BDUMDAQcjs = require('../chunk-5BDUMDAQ.cjs');
77
78
 
78
79
 
79
- var _chunkMJTKK3QGcjs = require('../chunk-MJTKK3QG.cjs');
80
80
 
81
- // src/operations/searchAcrossResources.ts
82
- var DEFAULT_LIMIT = 25;
83
- function resourceFor(client, key) {
84
- return client[_chunkVFLAMMIJcjs.camelKey.call(void 0, key)];
85
- }
86
- async function searchAcrossResources(client, query, opts = {}) {
87
- const text = query.trim();
88
- if (text === "") {
89
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("searchAcrossResources: query must be a non-empty string");
90
- }
91
- const keys = _nullishCoalesce(opts.resources, () => ( _chunkVFLAMMIJcjs.WORKFLOW_RESOURCES));
92
- if (keys.length === 0) {
93
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("searchAcrossResources: no resources requested");
94
- }
95
- const limit = _chunkVFLAMMIJcjs.clampPageSize.call(void 0, _nullishCoalesce(opts.limit, () => ( DEFAULT_LIMIT)));
96
- const totalLimit = opts.totalLimit;
97
- if (totalLimit !== void 0 && !(Number.isInteger(totalLimit) && totalLimit >= 1)) {
98
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("searchAcrossResources: totalLimit must be a positive integer");
99
- }
100
- const results = [];
101
- const skipped = [];
102
- const notSearched = [];
103
- let remaining = _nullishCoalesce(totalLimit, () => ( Infinity));
104
- let hitQuota = false;
105
- for (const key of keys) {
106
- const holder = resourceFor(client, key);
107
- if (holder === void 0 || typeof holder.search !== "function") {
108
- skipped.push(key);
109
- continue;
110
- }
111
- if (remaining <= 0) {
112
- notSearched.push(key);
113
- continue;
114
- }
115
- const ask = Math.min(limit, remaining);
116
- const matches = await holder.search(text, { limit: ask });
117
- const rows = Array.isArray(matches) ? matches.filter((m) => typeof m === "object" && m !== null).map((m) => m) : [];
118
- if (totalLimit !== void 0) rows.length = Math.min(rows.length, ask);
119
- results.push({ resource: key, matches: rows });
120
- remaining -= rows.length;
121
- if (rows.length === ask) hitQuota = true;
122
- }
123
- if (totalLimit === void 0) return { query: text, results, skipped };
124
- return { query: text, results, skipped, notSearched, truncated: notSearched.length > 0 || hitQuota };
125
- }
126
81
 
127
- // src/operations/resolveAny.ts
128
- function resourceFor2(client, key) {
129
- return client[_chunkVFLAMMIJcjs.camelKey.call(void 0, key)];
130
- }
131
- var FAULT_CATEGORIES = /* @__PURE__ */ new Set(["auth", "rate_limit", "server", "network", "timeout", "policy"]);
132
- function missCost(identifier) {
133
- if (typeof identifier === "number") return "direct";
134
- return /^\d+$/.test(identifier.trim()) ? "direct" : "server-filter";
135
- }
136
- async function resolveAny(client, identifier, opts = {}) {
137
- if (typeof identifier === "number") {
138
- _chunkVFLAMMIJcjs.parseIdentifier.call(void 0, identifier, "identifier");
139
- } else if (identifier.trim() === "") {
140
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("resolveAny: identifier must be non-empty");
141
- }
142
- const keys = _nullishCoalesce(opts.resources, () => ( _chunkVFLAMMIJcjs.WORKFLOW_RESOURCES));
143
- if (keys.length === 0) {
144
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("resolveAny: no resources requested");
145
- }
146
- const match = _nullishCoalesce(opts.match, () => ( "exact"));
147
- if (match !== "exact" && match !== "auto") {
148
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`resolveAny: match must be 'exact' or 'auto' (got ${JSON.stringify(match)})`);
149
- }
150
- const results = [];
151
- for (const key of keys) {
152
- const holder = resourceFor2(client, key);
153
- if (holder === void 0 || typeof holder.resolveDetailed !== "function") {
154
- results.push({
155
- resource: key,
156
- value: null,
157
- resolutionCost: missCost(identifier),
158
- scanned: 0,
159
- scanTruncated: false,
160
- candidates: [],
161
- error: "NO_RESOLVE"
162
- });
163
- continue;
164
- }
165
- try {
166
- const r = await holder.resolveDetailed(identifier);
167
- let outcome = {
168
- resource: key,
169
- value: _nullishCoalesce(r.value, () => ( null)),
170
- resolutionCost: r.resolutionCost,
171
- scanned: r.scanned,
172
- scanTruncated: r.scanTruncated,
173
- candidates: (_nullishCoalesce(r.candidates, () => ( []))).map((c) => c.id)
174
- };
175
- if (match === "auto" && outcome.value === null && typeof identifier === "string") {
176
- const hits = await holder.resolveContainsMatches(identifier);
177
- if (hits.length === 1) {
178
- outcome = { resource: key, value: hits[0], resolutionCost: "server-filter", scanned: 0, scanTruncated: false, candidates: [] };
179
- } else if (hits.length > 1) {
180
- outcome = {
181
- resource: key,
182
- value: null,
183
- resolutionCost: "server-filter",
184
- scanned: 0,
185
- scanTruncated: false,
186
- candidates: hits.map((h) => h.id).filter((id) => typeof id === "number"),
187
- error: "RESOLUTION_AMBIGUOUS"
188
- };
189
- }
190
- }
191
- results.push(outcome);
192
- } catch (err) {
193
- let code;
194
- let candidates = [];
195
- if (err instanceof _chunkVFLAMMIJcjs.AutotaskConfigError) {
196
- code = "CONFIG_ERROR";
197
- } else if (_chunkVFLAMMIJcjs.isAutotaskError.call(void 0, err)) {
198
- if (FAULT_CATEGORIES.has(err.category)) throw err;
199
- code = err.code;
200
- if (code === "RESOLUTION_AMBIGUOUS" && err.resourceIds !== void 0) {
201
- candidates = err.resourceIds;
202
- }
203
- } else {
204
- throw err;
205
- }
206
- results.push({
207
- resource: key,
208
- value: null,
209
- resolutionCost: missCost(identifier),
210
- scanned: 0,
211
- scanTruncated: code === "RESOLUTION_TRUNCATED",
212
- candidates,
213
- error: code
214
- });
215
- }
216
- }
217
- const matches = results.filter((r) => r.value !== null).map((r) => r.resource);
218
- return { identifier, results, matches };
219
- }
220
-
221
- // src/operations/aggregate.ts
222
- var AGGREGATE_TOP_MAX = 100;
223
- var AGGREGATE_DEFAULT_TOP = 25;
224
- var AGGREGATE_DEFAULT_LIMIT = _chunkVFLAMMIJcjs.BOUNDED_QUERY_MAX_LIMIT;
225
- var AGGREGATE_DATE_BUCKETS = ["month", "week", "year"];
226
- var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
227
- var DATETIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?(Z|[+-]\d{2}:?\d{2})?$/;
228
- function parseDateInstant(v) {
229
- if (typeof v !== "string") return null;
230
- let m = v.match(DATE_ONLY_RE);
231
- if (m) {
232
- return _nullishCoalesce(validUtc(Number(m[1]), Number(m[2]), Number(m[3])), () => ( null));
233
- }
234
- m = v.match(DATETIME_RE);
235
- if (m) {
236
- const t = validUtc(Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6]));
237
- if (t === null) return null;
238
- const zone = m[8];
239
- if (zone === void 0 || zone === "Z") return t;
240
- const sign = zone[0] === "-" ? -1 : 1;
241
- const hh = Number(zone.slice(1, 3));
242
- const mm = Number(zone.slice(zone.length - 2));
243
- return t - sign * (hh * 60 + mm) * 6e4;
244
- }
245
- return null;
246
- }
247
- function validUtc(y, mo, d, h = 0, mi = 0, s = 0) {
248
- const dt = new Date(Date.UTC(y, mo - 1, d, h, mi, s));
249
- if (y >= 0 && y < 100) dt.setUTCFullYear(y);
250
- if (dt.getUTCFullYear() !== y || dt.getUTCMonth() !== mo - 1 || dt.getUTCDate() !== d || dt.getUTCHours() !== h || dt.getUTCMinutes() !== mi || dt.getUTCSeconds() !== s) {
251
- return null;
252
- }
253
- return dt.getTime();
254
- }
255
- var pad2 = (n) => String(n).padStart(2, "0");
256
- function isoWeekLabel(ms) {
257
- const d = new Date(ms);
258
- const dow = d.getUTCDay() || 7;
259
- const S = d.getUTCFullYear() < 200 ? 100 : 0;
260
- const thu = new Date(Date.UTC(d.getUTCFullYear() + S, d.getUTCMonth(), d.getUTCDate() + (4 - dow)));
261
- const isoYear = thu.getUTCFullYear() - S;
262
- const week = Math.ceil(((thu.getTime() - Date.UTC(thu.getUTCFullYear(), 0, 1)) / 864e5 + 1) / 7);
263
- return `${isoYear}-W${pad2(week)}`;
264
- }
265
- function dateBucketLabel(value, bucket) {
266
- if (value === null || value === void 0) return null;
267
- const t = parseDateInstant(value);
268
- if (t === null) return value;
269
- const d = new Date(t);
270
- switch (bucket) {
271
- case "month":
272
- return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}`;
273
- case "year":
274
- return `${d.getUTCFullYear()}`;
275
- case "week":
276
- return isoWeekLabel(t);
277
- }
278
- }
279
- var QUERY_RESOURCE_KEYS = (() => {
280
- const s = /* @__PURE__ */ new Set();
281
- for (const rec of _chunk5BDUMDAQcjs.CAPABILITIES) if (rec.operation === "query") s.add(rec.resource);
282
- return s;
283
- })();
284
- var AGGREGATE_OPT_KEYS = ["groupBy", "sum", "top", "limit", "names", "acknowledgeUnscoped"];
285
- var OP_RE = /^([a-z0-9_]+)\.query$/;
286
- function num2(v) {
287
- return typeof v === "number" && Number.isFinite(v) ? v : 0;
288
- }
289
- function parseOperationKey(operation, problems) {
290
- if (typeof operation !== "string" || !OP_RE.test(operation)) {
291
- problems.push(
292
- `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`
293
- );
294
- return void 0;
295
- }
296
- const key = OP_RE.exec(operation)[1];
297
- if (!QUERY_RESOURCE_KEYS.has(key)) {
298
- problems.push(
299
- `operation "${operation}": "${key}" has no query row in the capability registry (116 query surfaces) \u2014 see autotask_catalog for the exact plan keys`
300
- );
301
- }
302
- return key;
303
- }
304
- function parseFilterConditions(filter, problems) {
305
- if (filter === void 0 || filter === null) {
306
- return [];
307
- }
308
- const conds = Array.isArray(filter) ? filter : filter.filter;
309
- if (!Array.isArray(conds)) {
310
- problems.push("filter must be the query-bag filter (a conditions array, or { filter: [...] })");
311
- return [];
312
- }
313
- if (conds.length === 0) {
314
- return [];
315
- }
316
- try {
317
- return _chunkVFLAMMIJcjs.buildFilter.call(void 0, conds).filter;
318
- } catch (err) {
319
- problems.push(err instanceof _chunkVFLAMMIJcjs.AutotaskConfigError ? err.message : `filter must be the query-bag filter (13 operators; caps 500 OR-conditions / 1 UDF / 8000 chars); ${String(err)}`);
320
- return [];
321
- }
322
- }
323
- function checkOptsBag(opts, problems) {
324
- if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
325
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
326
- `aggregate: opts must be the options object (got ${Array.isArray(opts) ? "an array" : typeof opts})`,
327
- { operation: "operations.aggregate" }
328
- );
329
- }
330
- for (const key of Object.keys(_nullishCoalesce(opts, () => ( {})))) {
331
- if (!AGGREGATE_OPT_KEYS.includes(key)) {
332
- problems.push(`unknown opts key "${key}" \u2014 the bag is closed: ${AGGREGATE_OPT_KEYS.join(", ")}`);
333
- }
334
- }
335
- if (_optionalChain([opts, 'optionalAccess', _2 => _2.names]) !== void 0 && typeof opts.names !== "boolean") problems.push(`names must be a boolean (got ${typeof opts.names})`);
336
- if (_optionalChain([opts, 'optionalAccess', _3 => _3.acknowledgeUnscoped]) !== void 0 && typeof opts.acknowledgeUnscoped !== "boolean") {
337
- problems.push(`acknowledgeUnscoped must be a boolean (got ${typeof opts.acknowledgeUnscoped})`);
338
- }
339
- }
340
- function checkSumFields(name, fields, problems) {
341
- if (fields === void 0) return [];
342
- if (!Array.isArray(fields) || fields.length === 0 || fields.some((f) => typeof f !== "string" || f.trim() === "")) {
343
- problems.push(`${name} must be a non-empty array of exact vendor field names (discover them via operations.fieldCatalog)`);
344
- return [];
345
- }
346
- return fields;
347
- }
348
- function parseGroupSpec(f, oracles, known, problems) {
349
- const { resourceKey, dateSets } = oracles;
350
- const at = f.indexOf("@");
351
- if (at === -1) return { name: f, base: f };
352
- const base = f.slice(0, at);
353
- const suffix = f.slice(at + 1);
354
- if (!AGGREGATE_DATE_BUCKETS.includes(suffix)) {
355
- problems.push(`groupBy: "${f}": unknown bucket suffix "${suffix}" \u2014 the closed set is month | week | year (the suffix applies to date-typed fields only)`);
356
- return void 0;
357
- }
358
- if (known !== void 0 && !known.includes(base)) return void 0;
359
- if (dateSets === void 0 || !dateSets.includes(base)) {
360
- problems.push(`groupBy: "${f}": "${base}" is not a date field of ${resourceKey} (the @month/@week/@year bucket suffix applies to date-typed fields of the resource's vendored model \u2014 exact vendor field names via operations.fieldCatalog)`);
361
- return void 0;
362
- }
363
- return { name: f, base, bucket: suffix };
364
- }
365
- function parseGroupBy(opts, oracles, known, problems) {
366
- const groupBy = [];
367
- if (opts.groupBy === void 0) return groupBy;
368
- if (!Array.isArray(opts.groupBy) || opts.groupBy.length === 0 || opts.groupBy.some((f) => typeof f !== "string" || f.trim() === "")) {
369
- problems.push("groupBy must be a non-empty array of exact vendor field names (a date-typed field may carry the closed bucket suffix @month | @week | @year \u2014 discover them via operations.fieldCatalog)");
370
- return groupBy;
371
- }
372
- for (const f of opts.groupBy) {
373
- const spec = parseGroupSpec(f, oracles, known, problems);
374
- if (spec !== void 0) groupBy.push(spec);
375
- }
376
- return groupBy;
377
- }
378
- function parseTopAndLimit(opts, problems) {
379
- let top = AGGREGATE_DEFAULT_TOP;
380
- if (opts.top !== void 0) {
381
- if (!Number.isInteger(opts.top)) {
382
- problems.push(`top must be an integer (got ${String(opts.top)})`);
383
- } else {
384
- top = Math.min(Math.max(opts.top, 1), AGGREGATE_TOP_MAX);
385
- }
386
- }
387
- let limit = AGGREGATE_DEFAULT_LIMIT;
388
- if (opts.limit !== void 0) {
389
- if (!Number.isInteger(opts.limit)) {
390
- problems.push(`limit must be an integer (got ${String(opts.limit)})`);
391
- } else {
392
- limit = Math.min(Math.max(opts.limit, 1), _chunkVFLAMMIJcjs.BOUNDED_QUERY_MAX_LIMIT);
393
- }
394
- }
395
- return { top, limit };
396
- }
397
- async function checkFieldNames(resourceKey, conditions, opts, problems) {
398
- const record = resourceKey === void 0 ? void 0 : _chunk5BDUMDAQcjs.CAPABILITIES.find((r) => r.resource === resourceKey && r.operation === "query");
399
- if (record === void 0) return void 0;
400
- const strings = (v) => Array.isArray(v) ? v.filter((f) => typeof f === "string" && f.trim() !== "") : [];
401
- const groupBy = strings(opts.groupBy);
402
- const found = await _chunkVFLAMMIJcjs.findUnknownFieldNames.call(void 0, record, void 0, {
403
- filter: _chunkVFLAMMIJcjs.collectFilterFieldNames.call(void 0, conditions, resourceKey),
404
- groupBy: groupBy.map((f) => f.split("@")[0]),
405
- sum: strings(opts.sum)
406
- });
407
- if (found === void 0) return void 0;
408
- const refuse2 = (key, entry, base) => {
409
- const named = entry === base ? `"${base}"` : `"${entry}": "${base}"`;
410
- problems.push(`${key}: ${named} 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; checked against ${found.authority})`);
411
- };
412
- for (const f of found.unknown["filter"]) refuse2("filter", f, f);
413
- for (const f of groupBy) {
414
- const base = f.split("@")[0];
415
- if (found.unknown["groupBy"].includes(base)) refuse2("groupBy", f, base);
416
- }
417
- for (const f of found.unknown["sum"]) refuse2("sum", f, f);
418
- return found.known;
419
- }
420
- async function validateAggregate(operation, filter, opts) {
421
- const problems = [];
422
- const resourceKey = parseOperationKey(operation, problems);
423
- const conditions = parseFilterConditions(filter, problems);
424
- checkOptsBag(opts, problems);
425
- const oracles = {
426
- resourceKey,
427
- dateSets: resourceKey !== void 0 ? _chunkVFLAMMIJcjs.MODEL_DATE_FIELDS[resourceKey] : void 0
428
- };
429
- const known = await checkFieldNames(resourceKey, conditions, opts, problems);
430
- const groupBy = parseGroupBy(opts, oracles, known, problems);
431
- const sum = checkSumFields("sum", opts.sum, problems);
432
- const { top, limit } = parseTopAndLimit(opts, problems);
433
- if (problems.length > 0) {
434
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
435
- `aggregate: ${problems.length} problem(s) found pre-request (zero wire was issued)
436
- - ` + problems.join("\n- "),
437
- { operation: "operations.aggregate" }
438
- );
439
- }
440
- if ((groupBy.length > 0 || sum.length > 0) && conditions.length === 0 && opts.acknowledgeUnscoped !== true) {
441
- throw new (0, _chunkVFLAMMIJcjs.ScopeRequiredError)(operation, { threshold: _chunkVFLAMMIJcjs.UNSCOPED_WALK_THRESHOLD }, "empty-filter");
442
- }
443
- return { resourceKey, conditions, groupBy, sum, top, limit };
444
- }
445
- function foldGroups(items, v) {
446
- const groups = /* @__PURE__ */ new Map();
447
- for (const item of items) {
448
- const key = {};
449
- const values = [];
450
- for (const spec of v.groupBy) {
451
- const raw = item[spec.base] === void 0 ? null : item[spec.base];
452
- const value = spec.bucket === void 0 ? raw : dateBucketLabel(raw, spec.bucket);
453
- key[spec.name] = value;
454
- values.push(value);
455
- }
456
- const keyOrder = JSON.stringify(values);
457
- let g = groups.get(keyOrder);
458
- if (g === void 0) {
459
- g = { key, keyOrder, count: 0, ...v.sum.length > 0 ? { sums: {} } : {} };
460
- groups.set(keyOrder, g);
461
- }
462
- g.count += 1;
463
- if (g.sums !== void 0) {
464
- for (const f of v.sum) g.sums[f] = (_nullishCoalesce(g.sums[f], () => ( 0))) + num2(item[f]);
465
- }
466
- }
467
- return [...groups.values()].sort((a, b) => b.count - a.count || (a.keyOrder < b.keyOrder ? -1 : a.keyOrder > b.keyOrder ? 1 : 0));
468
- }
469
- async function aggregate(client, operation, filter, opts = {}) {
470
- const v = await validateAggregate(operation, filter, opts);
471
- const holder = client[_chunkVFLAMMIJcjs.camelKey.call(void 0, v.resourceKey)];
472
- if (holder === void 0) {
473
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`aggregate: the client exposes no accessor for resource "${v.resourceKey}"`, {
474
- operation: "operations.aggregate",
475
- suggestedAction: "Rebuild the client from the SDK build that ships this registry (the resource set is pinned by the plan)."
476
- });
477
- }
478
- const wireFilter = v.conditions.length === 0 ? void 0 : v.conditions;
479
- if (v.groupBy.length === 0 && v.sum.length === 0) {
480
- const raw = await holder.count({ filter: wireFilter });
481
- const sentinel = typeof raw === "object" && raw !== null && "count" in raw ? raw : { count: raw };
482
- const result2 = { mode: "count", count: sentinel.count, blind: sentinel.blindSurface !== void 0, truncated: false };
483
- if (sentinel.blindSurface !== void 0) result2.blindSurface = sentinel.blindSurface;
484
- if (sentinel.emptyReason !== void 0) result2.emptyReason = sentinel.emptyReason;
485
- return result2;
486
- }
487
- const res = await holder.queryBounded({ filter: wireFilter, limit: v.limit });
488
- const sorted = foldGroups(res.items, v);
489
- const top = sorted.slice(0, v.top);
490
- const label = _chunkVFLAMMIJcjs.readNamesOpt.call(void 0, client, opts.names, "aggregate");
491
- const labelled = !label || v.groupBy.length === 0 ? void 0 : await _chunkVFLAMMIJcjs.labelRows.call(void 0, client, v.resourceKey, top.map((g) => g.key));
492
- const result = {
493
- mode: v.groupBy.length > 0 ? "group" : "sum",
494
- groups: top.map((g, i) => ({ key: _nullishCoalesce(_optionalChain([labelled, 'optionalAccess', _4 => _4.rows, 'access', _5 => _5[i]]), () => ( g.key)), count: g.count, ...g.sums !== void 0 ? { sums: g.sums } : {} })),
495
- totalGroups: sorted.length,
496
- groupsTruncated: sorted.length > top.length,
497
- totalScanned: res.fetched,
498
- hasMore: res.hasMore,
499
- truncated: res.hasMore
500
- // issue #287 (D6): the walk hit its limit — groups may be incomplete
501
- };
502
- if (res.blindSurface !== void 0) result.blindSurface = res.blindSurface;
503
- if (res.emptyReason !== void 0) result.emptyReason = res.emptyReason;
504
- if (res.relationNote !== void 0) result.relationNote = res.relationNote;
505
- if (_optionalChain([labelled, 'optionalAccess', _6 => _6.names]) !== void 0) result.names = labelled.names;
506
- return result;
507
- }
508
-
509
- // src/operations/revenueRollup.ts
510
- var OP = "operations.revenueRollup";
511
- var PAGE = 500;
512
- var CONCURRENCY = 3;
513
- var DEFAULT_BUDGET = 90;
514
- var DEFAULT_BUDGET_NON_CHARGEABLE = 150;
515
- var BUDGET_CAP = 200;
516
- var TOP_MAX = 100;
517
- var SCOPE_IDS_MAX = 100;
518
- var TENANT_HOURLY_LIMIT = 1e4;
519
- var DEFAULT_GROUP_BY = ["vertical", "contractType"];
520
- var DEFAULT_CONTRACT_TYPE_RE = /^\s*(recurring|fixed)\b/i;
521
- var CONTRACTS_PATH = "/V1.0/Contracts";
522
- var COMPANIES_PATH = "/V1.0/Companies";
523
- var BILLING_ITEMS_PATH = "/V1.0/BillingItems";
524
- var LINE_TYPE_FIELDS = ["billingItemType", "timeEntryID", "expenseItemID", ..._chunkVFLAMMIJcjs.CHARGE_LINE_FKS, ..._chunkVFLAMMIJcjs.SERVICE_LINE_FKS];
525
- var isLabour = (line) => _chunkVFLAMMIJcjs.billingLineType.call(void 0, line) === "labour";
526
- function picklistKey(c, dim, path, field, raw, missing) {
527
- const id = _chunkVFLAMMIJcjs.num.call(void 0, raw);
528
- if (id === 0) return { id: null, label: missing };
529
- const label = _optionalChain([c, 'access', _7 => _7.labels, 'access', _8 => _8[path], 'optionalAccess', _9 => _9.get, 'call', _10 => _10(field), 'optionalAccess', _11 => _11.get, 'call', _12 => _12(id)]);
530
- if (label === void 0) {
531
- c.unlabelled.add(`${dim}: ${field} value ${id} has no label on the live ${path} picklist (id-only)`);
532
- return { id };
533
- }
534
- return { id, label };
535
- }
536
- var DIMENSIONS = {
537
- vertical: {
538
- joins: { companies: ["marketSegmentID"] },
539
- picklist: COMPANIES_PATH,
540
- key: (c) => picklistKey(c, "vertical", COMPANIES_PATH, "marketSegmentID", _optionalChain([c, 'access', _13 => _13.company, 'optionalAccess', _14 => _14["marketSegmentID"]]), "unassigned")
541
- },
542
- contractType: {
543
- key: (c) => picklistKey(c, "contractType", CONTRACTS_PATH, "contractType", c.contract["contractType"], "none")
544
- },
545
- contractCategory: {
546
- key: (c) => picklistKey(c, "contractCategory", CONTRACTS_PATH, "contractCategory", c.contract["contractCategory"], "uncategorised")
547
- },
548
- company: {
549
- joins: { companies: ["companyName"] },
550
- key: (c) => ({ id: _chunkVFLAMMIJcjs.num.call(void 0, c.line["companyID"]), name: String(_nullishCoalesce(_optionalChain([c, 'access', _15 => _15.company, 'optionalAccess', _16 => _16["companyName"]]), () => ( ""))) })
551
- },
552
- month: {
553
- key: (c) => ({ id: c.invoice.month })
554
- },
555
- billingItemKind: {
556
- lineFields: LINE_TYPE_FIELDS,
557
- picklist: BILLING_ITEMS_PATH,
558
- key: (c) => _chunkVFLAMMIJcjs.num.call(void 0, c.line["billingItemType"]) === 0 ? { id: _chunkVFLAMMIJcjs.billingLineType.call(void 0, c.line), label: _chunkVFLAMMIJcjs.billingLineType.call(void 0, c.line) } : picklistKey(c, "billingItemKind", BILLING_ITEMS_PATH, "billingItemType", c.line["billingItemType"], "other")
559
- },
560
- contract: {
561
- joins: { contracts: ["contractName"] },
562
- key: (c) => ({ id: _chunkVFLAMMIJcjs.num.call(void 0, c.line["contractID"]), name: String(_nullishCoalesce(c.contract["contractName"], () => ( ""))) })
563
- },
564
- // Per line (no contract-pointer fallback): association → level2 (the Line of Business, `name`) + level1 (the division).
565
- lineOfBusiness: {
566
- lineFields: ["organizationalLevelAssociationID"],
567
- joins: { organizationalLevelAssociations: [], organizationalLevel1s: [], organizationalLevel2s: [] },
568
- unresolved: "unresolvedAssociations",
569
- key: (c) => {
570
- const id = _chunkVFLAMMIJcjs.num.call(void 0, c.line["organizationalLevelAssociationID"]);
571
- if (id === 0) return { id: null, label: "no LOB assigned" };
572
- const a = c.joined.organizationalLevelAssociations.get(id);
573
- const l1 = c.joined.organizationalLevel1s.get(_chunkVFLAMMIJcjs.num.call(void 0, _optionalChain([a, 'optionalAccess', _17 => _17["organizationalLevel1ID"]])));
574
- const l2 = c.joined.organizationalLevel2s.get(_chunkVFLAMMIJcjs.num.call(void 0, _optionalChain([a, 'optionalAccess', _18 => _18["organizationalLevel2ID"]])));
575
- if (l1 === void 0 || l2 === void 0) return "unresolvedAssociations";
576
- return { id, name: String(_nullishCoalesce(l2["name"], () => ( ""))), division: String(_nullishCoalesce(l1["name"], () => ( ""))) };
577
- }
578
- },
579
- // Labour lines only; a non-labour line (or a labour line with no time entry, e.g. an adjustment) is "n/a".
580
- resource: {
581
- lineFields: LINE_TYPE_FIELDS,
582
- joins: { timeEntries: [], resources: [] },
583
- unresolved: "unresolvedTimeEntries",
584
- key: (c) => {
585
- const te = _chunkVFLAMMIJcjs.num.call(void 0, c.line["timeEntryID"]);
586
- if (!isLabour(c.line) || te === 0) return { id: null, label: "n/a" };
587
- const id = _chunkVFLAMMIJcjs.num.call(void 0, _optionalChain([c, 'access', _19 => _19.joined, 'access', _20 => _20.timeEntries, 'access', _21 => _21.get, 'call', _22 => _22(te), 'optionalAccess', _23 => _23["resourceID"]]));
588
- const r = c.joined.resources.get(id);
589
- if (r === void 0) return "unresolvedTimeEntries";
590
- return { id, name: `${String(_nullishCoalesce(r["firstName"], () => ( "")))} ${String(_nullishCoalesce(r["lastName"], () => ( "")))}`.trim() };
591
- }
592
- },
593
- billingCode: {
594
- lineFields: ["billingCodeID"],
595
- joins: { billingCodes: [] },
596
- unresolved: "unresolvedBillingCodes",
597
- key: (c) => {
598
- const id = _chunkVFLAMMIJcjs.num.call(void 0, c.line["billingCodeID"]);
599
- if (id === 0) return { id: null, label: "none" };
600
- const b = c.joined.billingCodes.get(id);
601
- return b === void 0 ? "unresolvedBillingCodes" : { id, name: String(_nullishCoalesce(b["name"], () => ( ""))) };
602
- }
603
- }
604
- };
605
- var fk = (rows, field) => [...rows].map((r) => _chunkVFLAMMIJcjs.num.call(void 0, r[field]));
606
- var ONE_CHUNK = (idChunks) => Math.min(1, idChunks);
607
- var JOINS = {
608
- contracts: {
609
- make: (http) => new (0, _chunkVFLAMMIJcjs.ContractsResource)(http, PAGE),
610
- fields: ["id", "contractType", "contractCategory"],
611
- ids: (lines) => fk(lines, "contractID")
612
- },
613
- companies: {
614
- make: (http) => new (0, _chunkVFLAMMIJcjs.CompaniesResource)(http, PAGE),
615
- fields: ["id"],
616
- ids: (lines) => fk(lines, "companyID")
617
- },
618
- organizationalLevelAssociations: {
619
- make: (http) => new (0, _chunkVFLAMMIJcjs.OrganizationalLevelAssociationsResource)(http, PAGE),
620
- fields: ["id", "organizationalLevel1ID", "organizationalLevel2ID"],
621
- ids: (lines) => fk(lines, "organizationalLevelAssociationID")
622
- },
623
- organizationalLevel1s: {
624
- make: (http) => new (0, _chunkVFLAMMIJcjs.OrganizationalLevel1sResource)(http, PAGE),
625
- fields: ["id", "name"],
626
- ids: (_, j) => fk(j["organizationalLevelAssociations"].values(), "organizationalLevel1ID"),
627
- chunks: ONE_CHUNK
628
- },
629
- organizationalLevel2s: {
630
- make: (http) => new (0, _chunkVFLAMMIJcjs.OrganizationalLevel2sResource)(http, PAGE),
631
- fields: ["id", "name"],
632
- ids: (_, j) => fk(j["organizationalLevelAssociations"].values(), "organizationalLevel2ID"),
633
- chunks: ONE_CHUNK
634
- },
635
- timeEntries: {
636
- make: (http) => new (0, _chunkVFLAMMIJcjs.TimeEntriesResource)(http, PAGE),
637
- fields: ["id", "resourceID"],
638
- ids: (lines) => fk(lines.filter(isLabour), "timeEntryID"),
639
- // One time entry per labour line: the line count is the upper bound (labour share unknown pre-walk).
640
- chunks: (_, lines) => pages(lines)
641
- },
642
- resources: {
643
- make: (http, client) => new (0, _chunkVFLAMMIJcjs.ResourcesResource)(http, client.config, PAGE),
644
- fields: ["id", "firstName", "lastName"],
645
- ids: (_, j) => fk(j["timeEntries"].values(), "resourceID"),
646
- chunks: ONE_CHUNK
647
- },
648
- billingCodes: {
649
- make: (http) => new (0, _chunkVFLAMMIJcjs.BillingCodesResource)(http, PAGE),
650
- fields: ["id", "name"],
651
- ids: (lines) => fk(lines, "billingCodeID")
652
- }
653
- };
654
- var LINE_FIELDS = ["id", "invoiceID", "companyID", "contractID", "totalAmount", "internalCurrencyTotalAmount", "extendedPrice", "internalCurrencyExtendedPrice"];
655
- var OPT_KEYS = ["companyIDs", "ownerResourceID", "contractIDs", "tenantWide", "dateFrom", "dateTo", "contractTypes", "contractCategories", "groupBy", "top", "maxVendorCalls", "includeNonChargeable"];
656
- var NC_LINE_FIELDS = ["nonBillable", "quantity", ...LINE_TYPE_FIELDS];
657
- var SCOPE_KEYS = ["companyIDs", "ownerResourceID", "contractIDs", "tenantWide"];
658
- var isPosInt = (v) => Number.isSafeInteger(v) && v > 0;
659
- function checkIdList(name, v, problems) {
660
- if (!Array.isArray(v) || v.length === 0 || v.length > SCOPE_IDS_MAX || !v.every(isPosInt)) {
661
- problems.push(`${name} must be a non-empty array of at most ${SCOPE_IDS_MAX} positive integer ids`);
662
- return void 0;
663
- }
664
- return [...new Set(v)].sort((a, b) => a - b);
665
- }
666
- function checkSelector(name, v, problems) {
667
- if (v === void 0) return void 0;
668
- if (!Array.isArray(v) || v.length === 0 || !v.every((x) => isPosInt(x) || typeof x === "string" && x.trim() !== "")) {
669
- problems.push(`${name} must be a non-empty array of picklist ids or labels`);
670
- return void 0;
671
- }
672
- return v;
673
- }
674
- function checkInt(name, v, def, max, problems) {
675
- if (v === void 0) return def;
676
- if (!Number.isInteger(v) || v < 1 || v > max) {
677
- problems.push(`${name} must be an integer 1..${max} (got ${String(v)})`);
678
- return def;
679
- }
680
- return v;
681
- }
682
- function validate(opts) {
683
- if (typeof opts !== "object" || opts === null || Array.isArray(opts)) {
684
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("revenueRollup: opts must be the options object { <scope>, dateFrom, dateTo, ... } (zero wire was issued)", { operation: OP });
685
- }
686
- const o = opts;
687
- const problems = [];
688
- for (const k of Object.keys(o)) if (!OPT_KEYS.includes(k)) problems.push(`unknown opts key "${k}" \u2014 the bag is closed: ${OPT_KEYS.join(", ")}`);
689
- const given = SCOPE_KEYS.filter((k) => o[k] !== void 0);
690
- if (given.length !== 1) {
691
- problems.push(given.length === 0 ? "a scope is required: exactly one of companyIDs, ownerResourceID, contractIDs, or tenantWide: true" : `exactly one scope key is allowed (got ${given.join(", ")})`);
692
- }
693
- const scopeKind = _nullishCoalesce(given[0], () => ( "tenantWide"));
694
- let scopeIds;
695
- if (given.length === 1) {
696
- if (scopeKind === "companyIDs" || scopeKind === "contractIDs") scopeIds = checkIdList(scopeKind, o[scopeKind], problems);
697
- if (scopeKind === "ownerResourceID" && !isPosInt(o.ownerResourceID)) problems.push("ownerResourceID must be a positive integer resource id");
698
- if (scopeKind === "tenantWide" && o.tenantWide !== true) problems.push("tenantWide must be exactly true");
699
- }
700
- const { dateFrom, dateTo } = o;
701
- if (!_chunkVFLAMMIJcjs.isDateYMD.call(void 0, dateFrom) || !_chunkVFLAMMIJcjs.isDateYMD.call(void 0, dateTo)) {
702
- problems.push("dateFrom and dateTo are both required as YYYY-MM-DD (the window is inclusive, whole UTC days)");
703
- } else if (dateFrom > dateTo) {
704
- problems.push(`dateFrom ${dateFrom} is after dateTo ${dateTo}`);
705
- } else if (_chunkVFLAMMIJcjs.monthsInWindow.call(void 0, dateFrom, dateTo) > _chunkVFLAMMIJcjs.PROFITABILITY_MAX_WINDOW_MONTHS) {
706
- problems.push(`the window spans ${_chunkVFLAMMIJcjs.monthsInWindow.call(void 0, dateFrom, dateTo)} months; the cap is ${_chunkVFLAMMIJcjs.PROFITABILITY_MAX_WINDOW_MONTHS}`);
707
- }
708
- let groupBy = [...DEFAULT_GROUP_BY];
709
- if (o.groupBy !== void 0) {
710
- const g = o.groupBy;
711
- if (!Array.isArray(g) || g.length === 0 || new Set(g).size !== g.length) {
712
- problems.push(`groupBy must be a non-empty array of distinct dimensions: ${Object.keys(DIMENSIONS).join(", ")}`);
713
- } else {
714
- for (const d of g) if (!Object.hasOwn(DIMENSIONS, d)) problems.push(`unknown groupBy dimension "${String(d)}" \u2014 the dimensions are: ${Object.keys(DIMENSIONS).join(", ")}`);
715
- groupBy = g;
716
- }
717
- }
718
- const v = {
719
- dateFrom,
720
- dateTo,
721
- scopeKind,
722
- scopeIds,
723
- ownerResourceID: scopeKind === "ownerResourceID" ? o.ownerResourceID : void 0,
724
- contractTypes: checkSelector("contractTypes", o.contractTypes, problems),
725
- contractCategories: checkSelector("contractCategories", o.contractCategories, problems),
726
- groupBy,
727
- top: checkInt("top", o.top, TOP_MAX, TOP_MAX, problems),
728
- budget: checkInt("maxVendorCalls", o.maxVendorCalls, o.includeNonChargeable === true ? DEFAULT_BUDGET_NON_CHARGEABLE : DEFAULT_BUDGET, BUDGET_CAP, problems),
729
- nonChargeable: o.includeNonChargeable === true
730
- };
731
- if (o.includeNonChargeable !== void 0 && typeof o.includeNonChargeable !== "boolean") problems.push("includeNonChargeable must be a boolean");
732
- if (problems.length > 0) {
733
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`revenueRollup: ${problems.length} problem(s) found pre-request (zero wire was issued)
734
- - ${problems.join("\n- ")}`, { operation: OP });
735
- }
736
- return v;
737
- }
738
- function resolveSelector(name, sel, values) {
739
- const out = /* @__PURE__ */ new Map();
740
- const misses = [];
741
- for (const s of sel) {
742
- const hits = typeof s === "number" ? values.has(s) ? [s] : [] : [...values].filter(([, l]) => l.trim().toLowerCase() === s.trim().toLowerCase()).map(([id]) => id);
743
- if (hits.length === 0) misses.push(JSON.stringify(s));
744
- for (const h of hits) out.set(h, values.get(h));
745
- }
746
- if (misses.length > 0) throw unmatched(name, `${misses.join(", ")} matched no live value`, values);
747
- return out;
748
- }
749
- function unmatched(name, what, values) {
750
- const live = [...values].map(([id, l]) => `${id} ${l}`).join("; ") || "(the picklist serves no values)";
751
- return new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`revenueRollup: ${name}: ${what}. Live values (id label): ${live}`, {
752
- operation: OP,
753
- suggestedAction: `Pass ${name} as ids or exact labels from the live list.`
754
- });
755
- }
756
- var sortedRecord = (m) => Object.fromEntries([...m].sort((a, b) => a[0] - b[0]).map(([id, l]) => [String(id), l]));
757
- var chunk = (a) => {
758
- const out = [];
759
- for (let i = 0; i < a.length; i += PAGE) out.push(a.slice(i, i + PAGE));
760
- return out;
761
- };
762
- var byId = (rows) => [...new Map(rows.map((r) => [_chunkVFLAMMIJcjs.num.call(void 0, r["id"]), r])).values()];
763
- var cents = (v) => Math.round(_chunkVFLAMMIJcjs.num.call(void 0, v) * 100);
764
- var revC = (line) => cents(_chunkVFLAMMIJcjs.internalAmount.call(void 0, line, "internalCurrencyTotalAmount", "totalAmount"));
765
- var extC = (line) => cents(_chunkVFLAMMIJcjs.internalAmount.call(void 0, line, "internalCurrencyExtendedPrice", "extendedPrice"));
766
- var INTERNAL_FIELDS = ["internalCurrencyTotalAmount", "internalCurrencyExtendedPrice"];
767
- var lacksInternal = (line) => INTERNAL_FIELDS.some((k) => !Number.isFinite(line[k]));
768
- var money = (c) => c / 100;
769
- var pages = (n) => Math.ceil(n / PAGE);
770
- async function collect(res, filter, includeFields) {
771
- const out = [];
772
- for await (const r of res.query({ filter, includeFields, maxRecords: PAGE, names: false })) out.push(r);
773
- return out;
774
- }
775
- function blindCount() {
776
- return new (0, _chunkVFLAMMIJcjs.AutotaskError)("revenueRollup: a gate count came back blind (unknown, not zero); the roll-up cannot be sized", {
777
- code: "INVALID_RESPONSE",
778
- category: "server",
779
- operation: OP,
780
- details: { reason: "blind_count" },
781
- suggestedAction: "Check the API user can see invoices and billing items (operations.blindSurfaces), then retry."
782
- });
783
- }
784
- function countMismatch(what, streamed, counted, rows) {
785
- const rowNote = rows !== void 0 && rows !== streamed ? ` (${rows} rows)` : "";
786
- return new (0, _chunkVFLAMMIJcjs.AutotaskError)(`revenueRollup: the ${what} stream returned ${streamed} distinct ids${rowNote} but the gate count was ${counted} (the data changed mid-walk or a page dropped rows); the fold was discarded`, {
787
- code: "INVALID_RESPONSE",
788
- category: "server",
789
- operation: OP,
790
- details: { reason: "count_mismatch", stream: what, streamed, counted },
791
- suggestedAction: "Retry the call."
792
- });
793
- }
794
- function budgetError(message, details, suggestedAction) {
795
- return new (0, _chunkVFLAMMIJcjs.AutotaskError)(message, { code: "BUDGET_EXCEEDED", category: "policy", operation: OP, details, suggestedAction });
796
- }
797
- function lastMonthsFrom(dateTo, months) {
798
- return new Date(Date.UTC(Number(dateTo.slice(0, 4)), Number(dateTo.slice(5, 7)) - months, 1)).toISOString().slice(0, 10);
799
- }
800
- async function revenueRollup(client, opts) {
801
- const v = validate(opts);
802
- const raw = client.http;
803
- let calls = 0;
804
- let estimate = 0;
805
- const sizingMin = [.../* @__PURE__ */ new Set([CONTRACTS_PATH, ...v.groupBy.flatMap((d) => DIMENSIONS[d].picklist ? [DIMENSIONS[d].picklist] : [])])].length + (v.scopeKind === "ownerResourceID" ? 1 : 0) + 2 + (v.nonChargeable ? 1 : 0);
806
- const http = Object.create(raw);
807
- http.request = (o) => {
808
- if (calls >= v.budget && estimate === 0) {
809
- throw budgetError(
810
- `revenueRollup: the vendor-call budget of ${v.budget} is below the sizing overhead (labels, scope and count calls: at least ${sizingMin} for this call); the roll-up could not be sized and no stream was read. Retry with a higher maxVendorCalls (cap ${BUDGET_CAP}); the refusal then names the full estimate.`,
811
- { phase: "estimate", estimate: sizingMin, budget: v.budget, vendorCalls: calls, suggestions: [{ maxVendorCalls: sizingMin }] },
812
- `Pass maxVendorCalls >= ${sizingMin} (cap ${BUDGET_CAP}).`
813
- );
814
- }
815
- if (calls >= v.budget) {
816
- throw budgetError(
817
- `revenueRollup: the vendor-call budget of ${v.budget} was exhausted mid-walk (pre-walk estimate ${estimate}); the partial fold was discarded. Retry with a higher maxVendorCalls (cap ${BUDGET_CAP}) or a shorter window / a scope.`,
818
- { phase: "walk", budget: v.budget, vendorCalls: calls, estimate },
819
- `Pass maxVendorCalls > ${v.budget} (cap ${BUDGET_CAP}), a later dateFrom, or a scope.`
820
- );
821
- }
822
- calls += 1;
823
- return raw.request(o);
824
- };
825
- const groupDims = v.groupBy.map((d) => DIMENSIONS[d]);
826
- const paths = [.../* @__PURE__ */ new Set([CONTRACTS_PATH, ...groupDims.flatMap((d) => d.picklist ? [d.picklist] : [])])];
827
- const labels = {};
828
- for (const p of paths) labels[p] = await _chunkVFLAMMIJcjs.readPicklists.call(void 0, http, p, OP);
829
- const contractLabels = labels[CONTRACTS_PATH];
830
- const typeValues = _nullishCoalesce(contractLabels.get("contractType"), () => ( /* @__PURE__ */ new Map()));
831
- const inTypes = v.contractTypes ? resolveSelector("contractTypes", v.contractTypes, typeValues) : new Map([...typeValues].filter(([, l]) => DEFAULT_CONTRACT_TYPE_RE.test(l)));
832
- if (inTypes.size === 0) throw unmatched("contractTypes", "no live contract type label starts with 'Recurring' or 'Fixed' (the default)", typeValues);
833
- const inCategories = v.contractCategories ? resolveSelector("contractCategories", v.contractCategories, _nullishCoalesce(contractLabels.get("contractCategory"), () => ( /* @__PURE__ */ new Map()))) : void 0;
834
- let scopeIds = v.scopeIds;
835
- if (v.scopeKind === "ownerResourceID") {
836
- const owned = /* @__PURE__ */ new Set();
837
- for await (const c of new (0, _chunkVFLAMMIJcjs.CompaniesResource)(http, PAGE).query({ filter: [{ field: "ownerResourceID", op: "eq", value: v.ownerResourceID }], includeFields: ["id"], maxRecords: PAGE, names: false })) {
838
- const id = _chunkVFLAMMIJcjs.num.call(void 0, c.id);
839
- if (id > 0) owned.add(id);
840
- if (owned.size > SCOPE_IDS_MAX) break;
841
- }
842
- scopeIds = [...owned].sort((a, b) => a - b);
843
- if (scopeIds.length === 0 || scopeIds.length > SCOPE_IDS_MAX) {
844
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
845
- `revenueRollup: ownerResourceID ${v.ownerResourceID} resolves to ${scopeIds.length > SCOPE_IDS_MAX ? `more than ${SCOPE_IDS_MAX}` : 0} companies (companies.query; 1..${SCOPE_IDS_MAX} supported)`,
846
- { operation: OP, suggestedAction: "Pass companyIDs explicitly, or tenantWide: true." }
847
- );
848
- }
849
- }
850
- const scopeCond = scopeIds === void 0 ? [] : [{ field: v.scopeKind === "contractIDs" ? "contractID" : "companyID", op: "in", value: scopeIds }];
851
- const invoices = new (0, _chunkVFLAMMIJcjs.InvoicesResource)(http, PAGE);
852
- const billingItems = new (0, _chunkVFLAMMIJcjs.BillingItemsResource)(http, PAGE);
853
- const windowOn = (field) => [{ field, op: "gte", value: v.dateFrom }, { field, op: "lt", value: _chunkVFLAMMIJcjs.dayAfter.call(void 0, v.dateTo) }];
854
- const invoiceFilter = (voided2) => [...windowOn("invoiceDateTime"), { field: "isVoided", op: "eq", value: voided2 }];
855
- const withVoided = v.scopeKind !== "contractIDs";
856
- const voidedFilter = [...invoiceFilter(true), ...scopeIds !== void 0 && withVoided ? [{ field: "companyID", op: "in", value: scopeIds }] : []];
857
- const nInvoices = await invoices.count({ filter: invoiceFilter(false) });
858
- const nLines = await billingItems.count({ filter: [...windowOn("itemDate"), { field: "invoiceID", op: "exist" }, ...scopeCond] });
859
- const uninvoicedFilter = [{ field: "invoiceID", op: "notExist" }, ...windowOn("itemDate"), ...scopeCond];
860
- const nUninvoiced = v.nonChargeable ? await billingItems.count({ filter: uninvoicedFilter }) : 0;
861
- if (typeof nInvoices !== "number" || typeof nLines !== "number" || typeof nUninvoiced !== "number") throw blindCount();
862
- const joinsNeeded = Object.keys(JOINS).filter((j) => j === "contracts" || groupDims.some((d) => _optionalChain([d, 'access', _24 => _24.joins, 'optionalAccess', _25 => _25[j]])));
863
- const chunks = pages(nInvoices);
864
- const linePages = chunks + Math.max(0, pages(nLines) - 1) + chunks;
865
- const uninvoicedPages = v.nonChargeable ? Math.max(1, pages(nUninvoiced)) : 0;
866
- const idChunks = pages(Math.min(nLines + nUninvoiced, nInvoices));
867
- const joinChunks = joinsNeeded.reduce((s, j) => {
868
- const spec = JOINS[j];
869
- return s + ("chunks" in spec ? spec.chunks(idChunks, nLines + nUninvoiced) : idChunks);
870
- }, 0);
871
- const fixed = calls + (withVoided ? 1 : 0);
872
- estimate = fixed + Math.max(1, chunks) + linePages + uninvoicedPages + joinChunks;
873
- if (estimate > v.budget) throw refusal(v, estimate, fixed, scopeIds, joinsNeeded.length);
874
- const invoiceRows = await collect(invoices, invoiceFilter(false), ["id", "invoiceTotal", "invoiceDateTime", "companyID"]);
875
- const invoiceById = /* @__PURE__ */ new Map();
876
- for (const r of invoiceRows) {
877
- invoiceById.set(_chunkVFLAMMIJcjs.num.call(void 0, r["id"]), { total: cents(r["invoiceTotal"]), month: String(_nullishCoalesce(r["invoiceDateTime"], () => ( ""))).slice(0, 7), companyID: _chunkVFLAMMIJcjs.num.call(void 0, r["companyID"]) });
878
- }
879
- if (invoiceById.size !== nInvoices) throw countMismatch("invoice", invoiceById.size, nInvoices, invoiceRows.length);
880
- const voided = withVoided ? byId(await collect(invoices, voidedFilter, ["id", "invoiceTotal"])) : void 0;
881
- const lineFields = [.../* @__PURE__ */ new Set([...LINE_FIELDS, ...groupDims.flatMap((d) => _nullishCoalesce(d.lineFields, () => ( []))), ...v.nonChargeable ? NC_LINE_FIELDS : []])];
882
- const ids = [...invoiceById.keys()].sort((a, b) => a - b);
883
- const lines = (await client.batch(async (c) => {
884
- const filter = [{ field: "invoiceID", op: "in", value: c }, ...scopeCond];
885
- const n = await billingItems.count({ filter });
886
- if (typeof n !== "number") throw blindCount();
887
- const rows = byId(await collect(billingItems, filter, lineFields));
888
- if (rows.length !== n) throw countMismatch("line", rows.length, n);
889
- return rows;
890
- }, chunk(ids), { concurrency: CONCURRENCY })).flat();
891
- const orphan = lines.find((l) => !invoiceById.has(_chunkVFLAMMIJcjs.num.call(void 0, l["invoiceID"])));
892
- if (orphan !== void 0) {
893
- throw new (0, _chunkVFLAMMIJcjs.AutotaskError)(`revenueRollup: billing line ${_chunkVFLAMMIJcjs.num.call(void 0, orphan["id"])} belongs to invoice ${_chunkVFLAMMIJcjs.num.call(void 0, orphan["invoiceID"])}, which is not among the ${invoiceById.size} streamed invoices; the fold was discarded`, {
894
- code: "INVALID_RESPONSE",
895
- category: "server",
896
- operation: OP,
897
- details: { reason: "orphan_line", lineID: _chunkVFLAMMIJcjs.num.call(void 0, orphan["id"]), invoiceID: _chunkVFLAMMIJcjs.num.call(void 0, orphan["invoiceID"]) },
898
- suggestedAction: "Retry the call."
899
- });
900
- }
901
- const uninvoiced = v.nonChargeable ? byId(await collect(billingItems, uninvoicedFilter, [...lineFields, "itemDate"])) : [];
902
- if (v.nonChargeable && uninvoiced.length !== nUninvoiced) throw countMismatch("uninvoiced line", uninvoiced.length, nUninvoiced);
903
- const all = uninvoiced.length > 0 ? [...lines, ...uninvoiced] : lines;
904
- const joined = Object.fromEntries(Object.keys(JOINS).map((j) => [j, /* @__PURE__ */ new Map()]));
905
- for (const j of joinsNeeded) {
906
- const spec = JOINS[j];
907
- const res = spec.make(http, client);
908
- const fields = [.../* @__PURE__ */ new Set([...spec.fields, ...groupDims.flatMap((d) => _nullishCoalesce(_optionalChain([d, 'access', _26 => _26.joins, 'optionalAccess', _27 => _27[j]]), () => ( [])))])];
909
- const want = [...new Set(spec.ids(all, joined))].filter((id) => id > 0).sort((a, b) => a - b);
910
- const rows = await client.batch((c) => collect(res, [{ field: "id", op: "in", value: c }], fields), chunk(want), { concurrency: CONCURRENCY });
911
- for (const r of rows.flat()) joined[j].set(_chunkVFLAMMIJcjs.num.call(void 0, r["id"]), r);
912
- }
913
- return fold(v, { invoiceById, voided, lines, uninvoiced, joined, labels, inTypes, inCategories, scopeIds, calls, estimate });
914
- }
915
- function refusal(v, estimate, fixed, scopeIds, joinCount) {
916
- const months = _chunkVFLAMMIJcjs.monthsInWindow.call(void 0, v.dateFrom, v.dateTo);
917
- const variable = estimate - fixed;
918
- const fitMonths = Math.floor((v.budget - fixed) * months / variable);
919
- const suggestions = [];
920
- const text = [];
921
- let unestimated = false;
922
- const suggest = (s, t, estimated = false) => {
923
- suggestions.push(s);
924
- text.push(t);
925
- if (!estimated) unestimated = true;
926
- };
927
- if (estimate <= BUDGET_CAP) suggest({ maxVendorCalls: estimate }, `maxVendorCalls: ${estimate} (cap ${BUDGET_CAP})`, true);
928
- if (fitMonths >= 1 && fitMonths < months) {
929
- const dateFrom = lastMonthsFrom(v.dateTo, fitMonths);
930
- const est = fixed + Math.ceil(variable * fitMonths / months);
931
- suggest({ dateFrom, estimate: est }, `dateFrom: '${dateFrom}' (est. ${est})`, true);
932
- }
933
- if (v.scopeKind === "tenantWide") suggest({ companyIDs: "[...]" }, "companyIDs: [...] (or ownerResourceID)");
934
- if (estimate > BUDGET_CAP) {
935
- if (v.nonChargeable) suggest({ includeNonChargeable: false }, "includeNonChargeable: false");
936
- if (joinCount > 1) suggest({ groupBy: ["contractType"] }, "groupBy: ['contractType'] (fewer dimensions, fewer id-fetch calls)");
937
- if (v.scopeKind === "companyIDs" || v.scopeKind === "ownerResourceID") suggest({ contractIDs: "[...]" }, "contractIDs: [...] (one call per group of contracts)");
938
- const n = (_nullishCoalesce(scopeIds, () => ( []))).length;
939
- if (v.scopeKind === "companyIDs" && n > 1) suggest({ companyIDs: "[...]" }, "a shorter companyIDs list (one call per part)");
940
- if (v.scopeKind === "ownerResourceID" && n > 1) suggest({ companyIDs: "[...]" }, "companyIDs: a subset of the owner's companies (one call per part)");
941
- if (v.scopeKind === "contractIDs" && n > 1) suggest({ contractIDs: "[...]" }, "a shorter contractIDs list (one call per part)");
942
- }
943
- if (suggestions.length === 0) suggest({ maxVendorCalls: BUDGET_CAP }, `maxVendorCalls: ${BUDGET_CAP} (the cap; the estimate ${estimate} is above it, so this call will be refused at this estimate even at the cap; no narrower shape exists for this scope and window)`, true);
944
- const caveat = unestimated ? ". Narrowings without an estimate may still be refused: the invoice stream is tenant-wide in every scope, and the line stream costs at least one page and one count per chunk of 500 of those invoices, so a narrower scope or shape does not reduce that part" : "";
945
- const scope = v.scopeKind === "tenantWide" ? "tenantWide" : `${v.scopeKind} ${(_nullishCoalesce(scopeIds, () => ( []))).length} id(s)`;
946
- return budgetError(
947
- `revenueRollup: estimated ${estimate} vendor calls exceeds maxVendorCalls ${v.budget} (window ${v.dateFrom}..${v.dateTo}, ${scope}). Narrow with one of: ${text.join("; ")}${caveat}`,
948
- { phase: "estimate", estimate, budget: v.budget, suggestions },
949
- `Narrow with one of: ${text.join("; ")}${caveat}`
950
- );
951
- }
952
- var amt = () => ({ amount: 0, lines: 0 });
953
- var nc = () => ({ contractCovered: { value: 0, hours: 0, lines: 0 }, nonBillable: { value: 0, hours: 0, lines: 0 } });
954
- var add = (a, c) => {
955
- a.amount += c;
956
- a.lines += 1;
957
- };
958
- var outAmt = (a) => ({ amount: money(a.amount), lines: a.lines });
959
- var outNc = (n) => {
960
- const o = (k) => ({ value: money(n[k].value), hours: n[k].hours / 100, lines: n[k].lines });
961
- return { contractCovered: o("contractCovered"), nonBillable: o("nonBillable") };
962
- };
963
- var outRecord = (r) => Object.fromEntries([...r].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, a]) => [k, outAmt(a)]));
964
- function ncClass(line) {
965
- if (line["nonBillable"] === true || line["nonBillable"] === 1) return "nonBillable";
966
- return extC(line) > 0 && revC(line) === 0 ? "contractCovered" : void 0;
967
- }
968
- function ncAdd(n, k, line) {
969
- n[k].value += extC(line);
970
- if (isLabour(line)) n[k].hours += cents(line["quantity"]);
971
- n[k].lines += 1;
972
- }
973
- function compareIds(a, b) {
974
- for (let i = 0; i < a.length; i++) {
975
- const x = _nullishCoalesce(a[i], () => ( null));
976
- const y = _nullishCoalesce(b[i], () => ( null));
977
- if (x === y) continue;
978
- if (x === null) return -1;
979
- if (y === null) return 1;
980
- if (typeof x === "number" && typeof y === "number") return x - y;
981
- return String(x) < String(y) ? -1 : 1;
982
- }
983
- return 0;
984
- }
985
- function fold(v, f) {
986
- const contractLabels = f.labels[CONTRACTS_PATH];
987
- const bucketLabel = (field, fallback) => (t) => {
988
- const values = contractLabels.get(field);
989
- const l = _optionalChain([values, 'optionalAccess', _28 => _28.get, 'call', _29 => _29(t)]);
990
- if (l === void 0) return `${fallback} ${t}`;
991
- return [...values.values()].filter((x) => x === l).length > 1 ? `${l} (${t})` : l;
992
- };
993
- const typeLabel = bucketLabel("contractType", "type");
994
- const catLabel = bucketLabel("contractCategory", "category");
995
- const needCompany = v.groupBy.some((d) => _optionalChain([DIMENSIONS, 'access', _30 => _30[d], 'access', _31 => _31.joins, 'optionalAccess', _32 => _32.companies]));
996
- const byLob = v.groupBy.includes("lineOfBusiness");
997
- const unlabelled = /* @__PURE__ */ new Set();
998
- const uncontracted = amt();
999
- const unresolvedContracts = amt();
1000
- const unresolvedCompanies = amt();
1001
- const unresolvedJoins = new Map(v.groupBy.flatMap((d) => DIMENSIONS[d].unresolved ? [[DIMENSIONS[d].unresolved, amt()]] : []));
1002
- const unresolvedNc = amt();
1003
- const noPointer = amt();
1004
- const otherTypes = /* @__PURE__ */ new Map();
1005
- const otherCats = /* @__PURE__ */ new Map();
1006
- const cells = /* @__PURE__ */ new Map();
1007
- const allContracts = /* @__PURE__ */ new Set();
1008
- const lineTotals = /* @__PURE__ */ new Map();
1009
- const zeroTotal = amt();
1010
- const zeroIds = /* @__PURE__ */ new Set();
1011
- const locate = (line, invoice) => {
1012
- const contractID = _chunkVFLAMMIJcjs.num.call(void 0, line["contractID"]);
1013
- if (contractID === 0) return { bucket: uncontracted, unresolved: false };
1014
- const contract = f.joined.contracts.get(contractID);
1015
- if (contract === void 0) return { bucket: unresolvedContracts, unresolved: true };
1016
- const type = _chunkVFLAMMIJcjs.num.call(void 0, contract["contractType"]);
1017
- if (!f.inTypes.has(type)) return { bucket: slot(otherTypes, typeLabel(type)), unresolved: false };
1018
- const cat = _chunkVFLAMMIJcjs.num.call(void 0, contract["contractCategory"]);
1019
- if (f.inCategories !== void 0 && !f.inCategories.has(cat)) return { bucket: slot(otherCats, cat === 0 ? "uncategorised" : catLabel(cat)), unresolved: false };
1020
- const company = f.joined.companies.get(_chunkVFLAMMIJcjs.num.call(void 0, line["companyID"]));
1021
- if (needCompany && company === void 0) return { bucket: unresolvedCompanies, unresolved: true };
1022
- const ctx = { line, invoice, contract, company, joined: f.joined, labels: f.labels, unlabelled };
1023
- const keys = {};
1024
- const ids = [];
1025
- for (const d of v.groupBy) {
1026
- const k = DIMENSIONS[d].key(ctx);
1027
- if (typeof k === "string") return { bucket: unresolvedJoins.get(k), unresolved: true };
1028
- keys[d] = k;
1029
- ids.push(k.id);
1030
- }
1031
- const id = JSON.stringify(ids);
1032
- let cell = cells.get(id);
1033
- if (cell === void 0) {
1034
- cell = { keys, ids, revenue: 0, credits: 0, creditLines: 0, included: 0, contracts: /* @__PURE__ */ new Set(), items: 0, nc: nc() };
1035
- cells.set(id, cell);
1036
- }
1037
- return { cell };
1038
- };
1039
- for (const line of f.lines) {
1040
- const invoiceID = _chunkVFLAMMIJcjs.num.call(void 0, line["invoiceID"]);
1041
- const invoice = f.invoiceById.get(invoiceID);
1042
- lineTotals.set(invoiceID, (_nullishCoalesce(lineTotals.get(invoiceID), () => ( 0))) + cents(line["totalAmount"]));
1043
- const c = revC(line);
1044
- if (invoice.total === 0) {
1045
- add(zeroTotal, c);
1046
- zeroIds.add(invoiceID);
1047
- }
1048
- const at = locate(line, invoice);
1049
- if (!("cell" in at)) {
1050
- add(at.bucket, c);
1051
- if (at.unresolved && v.nonChargeable && ncClass(line)) add(unresolvedNc, extC(line));
1052
- continue;
1053
- }
1054
- const cell = at.cell;
1055
- cell.revenue += c;
1056
- if (c < 0) {
1057
- cell.credits += c;
1058
- cell.creditLines += 1;
1059
- }
1060
- cell.included += extC(line) - c;
1061
- cell.contracts.add(_chunkVFLAMMIJcjs.num.call(void 0, line["contractID"]));
1062
- cell.items += 1;
1063
- allContracts.add(_chunkVFLAMMIJcjs.num.call(void 0, line["contractID"]));
1064
- if (byLob && cell.keys.lineOfBusiness.id === null) add(noPointer, c);
1065
- const k = v.nonChargeable ? ncClass(line) : void 0;
1066
- if (k) ncAdd(cell.nc, k, line);
1067
- }
1068
- for (const line of f.uninvoiced) {
1069
- const k = ncClass(line);
1070
- if (!k) continue;
1071
- const at = locate(line, { total: 0, month: String(_nullishCoalesce(line["itemDate"], () => ( ""))).slice(0, 7), companyID: _chunkVFLAMMIJcjs.num.call(void 0, line["companyID"]) });
1072
- if ("cell" in at) ncAdd(at.cell.nc, k, line);
1073
- else if (at.unresolved) add(unresolvedNc, extC(line));
1074
- }
1075
- const sorted = [...cells.values()].sort((a, b) => b.revenue - a.revenue || compareIds(a.ids, b.ids));
1076
- const kept = sorted.slice(0, v.top);
1077
- const cut = sorted.slice(v.top);
1078
- const sum = (k) => sorted.reduce((s, x) => s + x[k], 0);
1079
- const ncTotal = nc();
1080
- for (const x of sorted) for (const k of ["contractCovered", "nonBillable"]) for (const m of ["value", "hours", "lines"]) ncTotal[k][m] += x.nc[k][m];
1081
- const reconciled = v.scopeKind === "contractIDs" ? [] : [...f.invoiceById].filter(([, inv]) => v.scopeKind === "tenantWide" || f.scopeIds.includes(inv.companyID));
1082
- let headerTotal = 0;
1083
- let lineTotal = 0;
1084
- let mismatched = 0;
1085
- for (const [id, inv] of reconciled) {
1086
- const lt = _nullishCoalesce(lineTotals.get(id), () => ( 0));
1087
- headerTotal += inv.total;
1088
- lineTotal += lt;
1089
- if (lt !== inv.total) mismatched += 1;
1090
- }
1091
- const blindSurfaces2 = [...unlabelled].sort();
1092
- const noInternal = [...f.lines, ...f.uninvoiced].filter(lacksInternal).length;
1093
- if (noInternal > 0) blindSurfaces2.push(`billing_items: ${noInternal} line(s) without internalCurrencyTotalAmount / internalCurrencyExtendedPrice; their local-currency totalAmount / extendedPrice was used instead`);
1094
- if (unresolvedCompanies.lines > 0) blindSurfaces2.push("companies.query: some line companies were not returned by the id-in fetch (the companies list surface can be blind to companies the API user does not own); their lines are in coverage.excluded.unresolvedCompanies");
1095
- if (v.scopeKind === "ownerResourceID") blindSurfaces2.push("companies.query: ownerResourceID was resolved through the companies list surface, which can be blind to companies the API user does not own; a company it hides is missing from the scope and its revenue is not counted (scope.ids lists the companies that were resolved)");
1096
- const notes = [
1097
- "basis: invoice date (invoiceDateTime) as whole UTC days; voided invoices excluded; revenue = internalCurrencyTotalAmount (ex-tax, internal currency); includedValue is informational and never in revenue",
1098
- "prepaid block-hour / retainer contracts: revenue is the billed purchase"
1099
- ];
1100
- notes.push("coverage.zeroTotalInvoices is informational: its lines are also folded into rows / the excluded buckets, so it is not part of coverage.excluded (revenue + the excluded line buckets reconcile to the whole)");
1101
- if (v.scopeKind === "contractIDs") {
1102
- notes.push("reconciliation is not run for a contractIDs scope (only the scoped contracts' lines are fetched)");
1103
- notes.push("coverage.excluded.voidedInvoices is omitted for a contractIDs scope: an invoice header cannot be attributed to a contract (zeroTotalInvoices counts the zero-total invoices carrying a scoped line)");
1104
- } else if (v.scopeKind !== "tenantWide") {
1105
- notes.push("scope applies to the line's companyID; reconciliation, voidedInvoices and the zero-total count cover the invoices whose header company is in scope (zero-total also counts an invoice carrying an in-scope line; a mismatch can mean a line billed to another company)");
1106
- }
1107
- if (v.nonChargeable) {
1108
- notes.push("nonChargeable is never added to revenue: contractCovered = billable lines with internalCurrencyExtendedPrice > 0 and internalCurrencyTotalAmount = 0, nonBillable = nonBillable lines; value = \u03A3 internalCurrencyExtendedPrice, hours = \u03A3 quantity on labour lines (Labor and Labor Adjustment). Invoiced lines are keyed on the invoice date; uninvoiced lines (invoiceID notExist) have no invoice date, so they are windowed and month-keyed on itemDate (whole UTC days). Uninvoiced lines outside the contract-type/category scope or uncontracted are not counted.");
1109
- }
1110
- for (const [id, inv] of f.invoiceById) {
1111
- if (inv.total === 0 && (v.scopeKind === "tenantWide" || v.scopeKind !== "contractIDs" && f.scopeIds.includes(inv.companyID))) zeroIds.add(id);
1112
- }
1113
- const result = {
1114
- window: { dateFrom: v.dateFrom, dateTo: v.dateTo, basis: "invoiceDate" },
1115
- scope: {
1116
- kind: v.scopeKind,
1117
- ...v.ownerResourceID !== void 0 ? { ownerResourceID: v.ownerResourceID } : {},
1118
- ...f.scopeIds !== void 0 ? { ids: f.scopeIds } : {}
1119
- },
1120
- groupBy: v.groupBy,
1121
- top: v.top,
1122
- rows: kept.map((x) => ({
1123
- keys: x.keys,
1124
- revenue: money(x.revenue),
1125
- credits: { amount: money(x.credits), lines: x.creditLines },
1126
- includedValue: money(x.included),
1127
- contracts: x.contracts.size,
1128
- items: x.items,
1129
- ...v.nonChargeable ? { nonChargeable: outNc(x.nc) } : {}
1130
- })),
1131
- ...cut.length > 0 ? { rowsTruncated: { rows: cut.length, revenue: money(cut.reduce((s, x) => s + x.revenue, 0)) } } : {},
1132
- totals: {
1133
- revenue: money(sum("revenue")),
1134
- credits: money(sum("credits")),
1135
- includedValue: money(sum("included")),
1136
- contracts: allContracts.size,
1137
- items: sum("items"),
1138
- ...v.nonChargeable ? { nonChargeable: outNc(ncTotal) } : {}
1139
- },
1140
- coverage: {
1141
- vendorCalls: f.calls,
1142
- budget: v.budget,
1143
- estimate: f.estimate,
1144
- tenantHourlyShare: `${(f.calls / TENANT_HOURLY_LIMIT * 100).toFixed(2)}% of ${TENANT_HOURLY_LIMIT.toLocaleString("en-US")}/h`,
1145
- contractTypeMap: sortedRecord(f.inTypes),
1146
- ...f.inCategories !== void 0 ? { contractCategoryMap: sortedRecord(f.inCategories) } : {},
1147
- reconciliation: { headerTotal: money(headerTotal), lineTotal: money(lineTotal), delta: money(lineTotal - headerTotal), invoicesChecked: reconciled.length, invoicesMismatched: mismatched },
1148
- zeroTotalInvoices: { count: zeroIds.size, ...outAmt(zeroTotal) },
1149
- excluded: {
1150
- ...f.voided !== void 0 ? { voidedInvoices: { count: f.voided.length, headerAmount: money(f.voided.reduce((s, r) => s + cents(r["invoiceTotal"]), 0)) } } : {},
1151
- uncontracted: outAmt(uncontracted),
1152
- otherContractTypes: outRecord(otherTypes),
1153
- otherContractCategories: outRecord(otherCats),
1154
- unresolvedContracts: outAmt(unresolvedContracts),
1155
- unresolvedCompanies: outAmt(unresolvedCompanies),
1156
- ...Object.fromEntries([...unresolvedJoins].map(([k, a]) => [k, outAmt(a)])),
1157
- ...v.nonChargeable ? { unresolvedNonChargeable: outAmt(unresolvedNc) } : {}
1158
- },
1159
- ...byLob ? {
1160
- lineOfBusiness: {
1161
- noPointer: outAmt(noPointer),
1162
- note: `LOB figures are floors: lines without a pointer are shown separately (the "no LOB assigned" row, id null). LOB is per billing line (no contract fallback), so a row's contracts = contracts with at least one line in that LOB, and per-LOB contract counts can sum to more than totals.contracts.`
1163
- }
1164
- } : {},
1165
- blindSurfaces: blindSurfaces2,
1166
- notes
1167
- }
1168
- };
1169
- return result;
1170
- }
1171
- function slot(m, k) {
1172
- const a = _nullishCoalesce(m.get(k), () => ( amt()));
1173
- m.set(k, a);
1174
- return a;
1175
- }
1176
-
1177
- // src/operations/relationshipMap.ts
1178
- async function relationshipMap(client, resourceKey) {
1179
- void client;
1180
- if (typeof resourceKey !== "string" || resourceKey.trim() === "") {
1181
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1182
- `relationshipMap: resourceKey must be a non-empty plan resource key string (e.g. 'tickets'; got ${typeof resourceKey === "string" ? "a blank string" : typeof resourceKey})`,
1183
- { suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog \u2014 e.g. tickets, companies, configuration_items, ticket_notes." }
1184
- );
1185
- }
1186
- const key = resourceKey.trim();
1187
- const entry = _chunkVFLAMMIJcjs.RELATIONSHIP_MAP[key];
1188
- if (entry === void 0) {
1189
- throw new (0, _chunkVFLAMMIJcjs.AutotaskError)(
1190
- `relationshipMap: unknown resource "${key}" \u2014 the plan has ${Object.keys(_chunkVFLAMMIJcjs.RELATIONSHIP_MAP).length} resources`,
1191
- {
1192
- code: "RESOURCE_UNKNOWN",
1193
- category: "validation",
1194
- suggestedAction: `Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items). Zone facts served are ${_chunkVFLAMMIJcjs.RELATIONSHIP_ZONE.label} (${_chunkVFLAMMIJcjs.RELATIONSHIP_ZONE.zone})-verified.`
1195
- }
1196
- );
1197
- }
1198
- return entry;
1199
- }
1200
-
1201
- // src/operations/blindSurfaces.ts
1202
- var BLIND_SURFACE_KINDS = [
1203
- "unowned-blind",
1204
- "fully-blind",
1205
- "unfiltered-blind",
1206
- "unknown"
1207
- ];
1208
- async function blindSurfaces(client, opts) {
1209
- void client;
1210
- if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
1211
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("blindSurfaces: opts must be an object { resource?: string, kind?: BlindSurfaceKind }");
1212
- }
1213
- const inv = _chunkVFLAMMIJcjs.BLIND_SURFACE_INVENTORY;
1214
- if (inv === null) {
1215
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("blindSurfaces: the reachability inventory is absent from the generated artifact \u2014 re-run the build");
1216
- }
1217
- let surfaces = inv.surfaces;
1218
- if (_optionalChain([opts, 'optionalAccess', _33 => _33.resource]) !== void 0) {
1219
- const key = typeof opts.resource === "string" ? opts.resource.trim() : "";
1220
- if (key === "") {
1221
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1222
- "blindSurfaces: opts.resource must be a non-empty plan resource key string (e.g. 'tickets')",
1223
- { suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog \u2014 e.g. tickets, companies, configuration_items." }
1224
- );
1225
- }
1226
- if (!inv.surfaces.some((row) => row.resource === key)) {
1227
- throw new (0, _chunkVFLAMMIJcjs.AutotaskError)(
1228
- `blindSurfaces: unknown resource "${key}" \u2014 the plan has ${Object.keys(_chunkVFLAMMIJcjs.RELATIONSHIP_MAP).length} resources`,
1229
- {
1230
- code: "RESOURCE_UNKNOWN",
1231
- category: "validation",
1232
- suggestedAction: `Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items). Zone facts served are ${inv.label} (${inv.zone})-verified.`
1233
- }
1234
- );
1235
- }
1236
- surfaces = surfaces.filter((row) => row.resource === key);
1237
- }
1238
- if (_optionalChain([opts, 'optionalAccess', _34 => _34.kind]) !== void 0) {
1239
- if (!BLIND_SURFACE_KINDS.includes(opts.kind)) {
1240
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1241
- `blindSurfaces: unknown kind "${String(opts.kind)}" \u2014 the valid classes are: ${BLIND_SURFACE_KINDS.join(", ")}`,
1242
- { suggestedAction: `Filter with one of: ${BLIND_SURFACE_KINDS.join(", ")} (or omit kind for the full 116-surface inventory).` }
1243
- );
1244
- }
1245
- surfaces = surfaces.filter((row) => row.kind === opts.kind);
1246
- }
1247
- return { ...inv, surfaces };
1248
- }
1249
-
1250
- // src/operations/probeReachability.ts
1251
- async function probeReachability(client, opts) {
1252
- if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
1253
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("probeReachability: opts must be an object { resources?: string[], limit?: 1|2, maxWireCalls?: number, confirmProbe?: true }");
1254
- }
1255
- const defs = _chunkVFLAMMIJcjs.REACHABILITY_PROBE_DEFAULTS;
1256
- if (_optionalChain([opts, 'optionalAccess', _35 => _35.confirmProbe]) !== true) {
1257
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1258
- "probeReachability: confirmProbe: true is required \u2014 the op deliberately fans out read wire across the tenant (one bounded query per surface); pass confirmProbe: true to run it",
1259
- { suggestedAction: `Re-call with { confirmProbe: true } (plus resources/limit/maxWireCalls as needed). For the ZERO-wire pinned inventory use operations.blindSurfaces.` }
1260
- );
1261
- }
1262
- const limit = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _36 => _36.limit]), () => ( defs.limitDefault));
1263
- if (!Number.isInteger(limit) || limit < 1 || limit > defs.limitMax) {
1264
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1265
- `probeReachability: limit must be an integer 1..${defs.limitMax} (got ${String(limit)}) \u2014 the rows-per-surface bound for the discovery read`
1266
- );
1267
- }
1268
- const cap = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _37 => _37.maxWireCalls]), () => ( defs.defaultCap));
1269
- if (!Number.isInteger(cap) || cap < 1) {
1270
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`probeReachability: maxWireCalls must be a positive integer (got ${String(cap)})`);
1271
- }
1272
- if (cap > defs.hardCap) {
1273
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1274
- `probeReachability: maxWireCalls ${cap} exceeds the hard wire cap of ${defs.hardCap} \u2014 the sweep is refused above it (never capped silently)`,
1275
- { suggestedAction: `Pass maxWireCalls \u2264 ${defs.hardCap} and a resources set no larger than the cap (the refused set is never truncated).` }
1276
- );
1277
- }
1278
- const inv = _chunkVFLAMMIJcjs.BLIND_SURFACE_INVENTORY;
1279
- if (inv === null) {
1280
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("probeReachability: the reachability inventory is absent from the generated artifact \u2014 re-run the build");
1281
- }
1282
- let targets;
1283
- if (_optionalChain([opts, 'optionalAccess', _38 => _38.resources]) === void 0) {
1284
- targets = [...defs.defaultTargets];
1285
- } else {
1286
- if (!Array.isArray(opts.resources) || opts.resources.length === 0) {
1287
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("probeReachability: opts.resources must be a non-empty string[] of plan resource keys (or omit it for the default non-data-full set)");
1288
- }
1289
- const seen = /* @__PURE__ */ new Set();
1290
- for (const r of opts.resources) {
1291
- if (typeof r !== "string" || r.trim() === "") {
1292
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`probeReachability: opts.resources must contain non-empty plan resource key strings (got ${typeof r === "string" && r.trim() === "" ? "a blank string" : typeof r})`);
1293
- }
1294
- const key = r.trim();
1295
- if (!inv.surfaces.some((row) => row.resource === key)) {
1296
- const surfaceCount = new Set(inv.surfaces.map((s) => s.resource)).size;
1297
- throw new (0, _chunkVFLAMMIJcjs.AutotaskError)(
1298
- `probeReachability: unknown resource "${key}" \u2014 the inventory has ${surfaceCount} surfaces`,
1299
- {
1300
- code: "RESOURCE_UNKNOWN",
1301
- category: "validation",
1302
- suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items)."
1303
- }
1304
- );
1305
- }
1306
- seen.add(key);
1307
- }
1308
- targets = [...seen];
1309
- }
1310
- if (targets.length > cap) {
1311
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1312
- `probeReachability: ${targets.length} requested surface(s) exceed the wire cap of ${cap} \u2014 the sweep is refused above its cap (never truncated)`,
1313
- { suggestedAction: `Raise maxWireCalls (\u2264 ${defs.hardCap}) to cover the set, or pass a smaller resources list.` }
1314
- );
1315
- }
1316
- const byResource = new Map(inv.surfaces.map((row) => [row.resource, row]));
1317
- const results = [];
1318
- for (const resource of targets.sort()) {
1319
- const row = byResource.get(resource);
1320
- if (row === void 0) continue;
1321
- const holder = client[_chunkVFLAMMIJcjs.camelKey.call(void 0, resource)];
1322
- const probeFn = _optionalChain([holder, 'optionalAccess', _39 => _39.queryBounded]);
1323
- if (typeof probeFn !== "function") {
1324
- results.push({
1325
- ...row,
1326
- unfilteredRows: null,
1327
- probe: { ok: false, wire: 0, error: { code: "RESOURCE_NO_QUERY_OP", message: `no queryBounded read exists on the client accessor for "${resource}" \u2014 nothing was probed` } }
1328
- });
1329
- continue;
1330
- }
1331
- try {
1332
- const res = await probeFn.call(holder, { limit });
1333
- const items = Array.isArray(_optionalChain([res, 'optionalAccess', _40 => _40.items])) ? res.items : [];
1334
- results.push({
1335
- ...row,
1336
- unfilteredRows: items.length,
1337
- probe: { ok: true, wire: 1, error: null }
1338
- });
1339
- } catch (err) {
1340
- const code = _chunkVFLAMMIJcjs.isAutotaskError.call(void 0, err) ? err.code : "NETWORK_ERROR";
1341
- const message = err instanceof Error ? err.message : String(err);
1342
- results.push({
1343
- ...row,
1344
- unfilteredRows: null,
1345
- probe: { ok: false, wire: 1, error: { code, message } }
1346
- });
1347
- }
1348
- }
1349
- const wireUsed = results.reduce((n, r) => n + r.probe.wire, 0);
1350
- return { results, wireUsed, cap, truncated: false };
1351
- }
1352
-
1353
- // src/operations/createGuidance.ts
1354
- var CREATE_BLOCKER_KINDS = [
1355
- "ok",
1356
- "blind-reference",
1357
- "blind-parent",
1358
- "picklist-unexposed",
1359
- "undocumented-ladder",
1360
- "resource-role-pair",
1361
- "delete-absent",
1362
- "non-mutable",
1363
- "unknown"
1364
- ];
1365
- async function createGuidance(client, opts) {
1366
- void client;
1367
- if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
1368
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1369
- "createGuidance: opts must be an object { resources?: string[], kind?: CreateBlockerKind }"
1370
- );
1371
- }
1372
- let servedRows = _chunkVFLAMMIJcjs.CREATE_GUIDANCE.rows.map((row) => ({
1373
- ...row,
1374
- // Issue #219 N4: one precedence rule, stated on every row (live-proven >
1375
- // zone > spec). The spec set is not available here (it is a LIVE read) —
1376
- // the helper is handed the plan's two shapes and names the source.
1377
- requiredFields: _chunkVFLAMMIJcjs.effectiveRequiredFields.call(void 0, void 0, _nullishCoalesce(_optionalChain([_chunkVFLAMMIJcjs.RELATIONSHIP_MAP, 'access', _41 => _41[row.resource], 'optionalAccess', _42 => _42.zoneFacts]), () => ( null)))
1378
- }));
1379
- if (_optionalChain([opts, 'optionalAccess', _43 => _43.resources]) !== void 0) {
1380
- if (!Array.isArray(opts.resources) || opts.resources.length === 0) {
1381
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("createGuidance: opts.resources must be a non-empty string[] of plan resource keys");
1382
- }
1383
- const wanted = /* @__PURE__ */ new Set();
1384
- for (const r of opts.resources) {
1385
- if (typeof r !== "string" || r.trim() === "") {
1386
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)("createGuidance: opts.resources must contain non-empty plan resource key strings");
1387
- }
1388
- const key = r.trim();
1389
- if (!_chunkVFLAMMIJcjs.CREATE_GUIDANCE.rows.some((row) => row.resource === key)) {
1390
- throw new (0, _chunkVFLAMMIJcjs.AutotaskError)(
1391
- `createGuidance: unknown resource "${key}" \u2014 the catalog has ${_chunkVFLAMMIJcjs.CREATE_GUIDANCE.rows.length} create entities`,
1392
- {
1393
- code: "RESOURCE_UNKNOWN",
1394
- category: "validation",
1395
- suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, projects, opportunities)."
1396
- }
1397
- );
1398
- }
1399
- wanted.add(key);
1400
- }
1401
- servedRows = servedRows.filter((row) => wanted.has(row.resource));
1402
- }
1403
- if (_optionalChain([opts, 'optionalAccess', _44 => _44.kind]) !== void 0) {
1404
- if (!CREATE_BLOCKER_KINDS.includes(opts.kind)) {
1405
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1406
- `createGuidance: unknown kind "${String(opts.kind)}" \u2014 the valid kinds are: ${CREATE_BLOCKER_KINDS.join(", ")}`
1407
- );
1408
- }
1409
- servedRows = servedRows.filter((row) => _optionalChain([row, 'access', _45 => _45.blocker, 'optionalAccess', _46 => _46.kind]) === opts.kind);
1410
- }
1411
- return {
1412
- zone: _chunkVFLAMMIJcjs.CREATE_GUIDANCE.zone,
1413
- label: _chunkVFLAMMIJcjs.CREATE_GUIDANCE.label,
1414
- verifiedAt: _chunkVFLAMMIJcjs.CREATE_GUIDANCE.verifiedAt,
1415
- rows: servedRows,
1416
- counts: _chunkVFLAMMIJcjs.CREATE_GUIDANCE.counts
1417
- };
1418
- }
1419
-
1420
- // src/operations/registryIndex.ts
1421
- var OPERATION_BY_ID = new Map(_chunk5BDUMDAQcjs.CAPABILITIES.map((r) => [r.id, r]));
1422
-
1423
- // src/operations/sanitiseResult.ts
1424
- async function sanitiseResult(client, result, operation) {
1425
- void client;
1426
- if (typeof operation !== "string" || operation === "") {
1427
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1428
- 'sanitiseResult: operation must be a non-empty registry operation key (e.g. "tickets.query")',
1429
- { suggestedAction: "Pass the operation key the result came from \u2014 the registry id, not the resource key." }
1430
- );
1431
- }
1432
- const record = OPERATION_BY_ID.get(operation);
1433
- if (record === void 0) {
1434
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1435
- `sanitiseResult: unknown operation "${operation.slice(0, 60)}" \u2014 it is not in the capability registry`,
1436
- { operation, suggestedAction: "List the registry (the ./capabilities subpath) for the exact operation key." }
1437
- );
1438
- }
1439
- const fields = _optionalChain([record, 'access', _47 => _47.metadata, 'optionalAccess', _48 => _48.untrustedFields]);
1440
- if (!Array.isArray(fields) || fields.length === 0) return result;
1441
- return _chunkMJTKK3QGcjs.deepMarkUntrusted.call(void 0, result, fields);
1442
- }
1443
-
1444
- // src/operations/recovery.ts
1445
- var RECOVERY_CONFIRM = "operations.recovery";
1446
- var RECOVERY_DEFAULT_LIMIT = 25;
1447
- var RECOVERY_MAX_LIMIT = 100;
1448
- function configError(message, operation, suggestedAction) {
1449
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(message, { operation, suggestedAction });
1450
- }
1451
- function parseMatchId(record) {
1452
- const raw = record.id;
1453
- if (typeof raw === "number") {
1454
- if (Number.isInteger(raw) && raw >= 0 && raw <= Number.MAX_SAFE_INTEGER) return { id: raw, unsafe: false };
1455
- return { id: null, unsafe: true };
1456
- }
1457
- if (typeof raw === "string" && /^\d+$/.test(raw.trim())) {
1458
- const n = Number(raw.trim());
1459
- if (Number.isSafeInteger(n)) return { id: n, unsafe: false };
1460
- return { id: null, unsafe: true };
1461
- }
1462
- return { id: null, unsafe: true };
1463
- }
1464
- function validateRecoveryInput(input, operation) {
1465
- if (!_chunkVFLAMMIJcjs.isRecord.call(void 0, input)) {
1466
- configError(
1467
- `recovery: input must be an object { resource, marker, ... } (got ${typeof input})`,
1468
- operation,
1469
- "Pass { resource: '<plan key>', marker: '<the marker the write stamped>' } \u2014 e.g. { resource: 'companies', marker: 'FULL100W-1791110339' }."
1470
- );
1471
- }
1472
- const resource = typeof input.resource === "string" ? input.resource.trim() : "";
1473
- if (resource === "") {
1474
- configError(
1475
- `recovery: input.resource must be a non-empty plan resource key string (e.g. 'companies')`,
1476
- operation,
1477
- "Pass the exact plan resource key (snake_case) from autotask_catalog \u2014 e.g. companies, tickets, configuration_items."
1478
- );
1479
- }
1480
- const marker = typeof input.marker === "string" ? input.marker : "";
1481
- if (marker.trim() === "") {
1482
- configError(
1483
- "recovery: input.marker must be a non-empty string \u2014 the marker the write stamped into a name-like field",
1484
- operation,
1485
- "Stamp a deterministic marker into the record at create time (e.g. `FULL100W-${Date.now()}`) and pass it here; a guess is not a marker."
1486
- );
1487
- }
1488
- const action = _nullishCoalesce(input.action, () => ( "find"));
1489
- if (action !== "find" && action !== "delete") {
1490
- configError(
1491
- `recovery: input.action must be 'find' (default) or 'delete' (got ${JSON.stringify(String(input.action))})`,
1492
- operation,
1493
- "Pass action: 'delete' for the destructive path (dry-run-first; the apply requires the confirm string)."
1494
- );
1495
- }
1496
- return { resource, marker, action };
1497
- }
1498
- function resolveRecoveryTarget(client, resource, operation) {
1499
- const entry = _chunkVFLAMMIJcjs.RELATIONSHIP_MAP[resource];
1500
- if (entry === void 0) {
1501
- throw new (0, _chunkVFLAMMIJcjs.AutotaskError)(
1502
- `recovery: unknown resource "${resource}" \u2014 the plan has ${Object.keys(_chunkVFLAMMIJcjs.RELATIONSHIP_MAP).length} resources`,
1503
- {
1504
- code: "RESOURCE_UNKNOWN",
1505
- category: "validation",
1506
- operation,
1507
- suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items)."
1508
- }
1509
- );
1510
- }
1511
- const holder = client[_chunkVFLAMMIJcjs.camelKey.call(void 0, resource)];
1512
- if (!holder || typeof holder.queryBounded !== "function") {
1513
- configError(
1514
- `recovery: ${resource} exposes no bounded query surface \u2014 the marker search cannot run`,
1515
- operation,
1516
- "Use a resource with a query surface, or address the record by id directly (get/fetchMany)."
1517
- );
1518
- }
1519
- return { entry, holder };
1520
- }
1521
- function resolveMarkerField(input, resource, operation) {
1522
- const field = input.field !== void 0 ? String(input.field) : _chunkVFLAMMIJcjs.MARKER_FIELDS[resource];
1523
- if (typeof field !== "string" || field.trim() === "") {
1524
- configError(
1525
- `recovery: ${resource} has no plan markerField and no input.field was passed \u2014 the marker search needs a name-like field to filter on`,
1526
- operation,
1527
- "Pass input.field \u2014 a name-like vendor field the marker was stamped into (see the resource model in autotask_describe / autotask_catalog)."
1528
- );
1529
- }
1530
- return field;
1531
- }
1532
- function resolveParentId(input, entry, resource, operation) {
1533
- if (input.parentId === void 0) return void 0;
1534
- if (!Number.isInteger(input.parentId) || input.parentId < 0) {
1535
- configError(
1536
- `recovery: input.parentId must be a non-negative integer (got ${String(input.parentId)})`,
1537
- operation,
1538
- "Pass the parent record id (e.g. the ticket id for a ticket note)."
1539
- );
1540
- }
1541
- if (entry.parent === null || entry.parent.field === null) {
1542
- configError(
1543
- `recovery: ${resource} is not a parent-scoped child resource \u2014 input.parentId does not apply`,
1544
- operation,
1545
- `Drop input.parentId (the search runs unscoped), or pass the child resource that IS scoped under ${resource}.`
1546
- );
1547
- }
1548
- return input.parentId;
1549
- }
1550
- function resolveExtraFilter(input, operation) {
1551
- const extra = [];
1552
- if (input.filter === void 0) return extra;
1553
- if (!Array.isArray(input.filter)) {
1554
- configError(
1555
- `recovery: input.filter must be an array of leaf filter conditions (got ${typeof input.filter})`,
1556
- operation,
1557
- "Pass e.g. [{ field: 'status', op: 'eq', value: 'open' }] \u2014 leaf conditions only."
1558
- );
1559
- }
1560
- for (const c of input.filter) {
1561
- if (!_chunkVFLAMMIJcjs.isRecord.call(void 0, c) || typeof c.op !== "string" || c.op === "and" || c.op === "or") {
1562
- configError(
1563
- "recovery: input.filter accepts only plain leaf conditions ({ field, op, value }) \u2014 no grouping",
1564
- operation,
1565
- "Pass flat leaf conditions; grouping operators are not supported here."
1566
- );
1567
- }
1568
- extra.push(c);
1569
- }
1570
- return extra;
1571
- }
1572
- function resolveLimit(input, operation) {
1573
- if (input.limit === void 0) return RECOVERY_DEFAULT_LIMIT;
1574
- if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > RECOVERY_MAX_LIMIT) {
1575
- configError(
1576
- `recovery: input.limit must be an integer 1..${RECOVERY_MAX_LIMIT} (got ${String(input.limit)})`,
1577
- operation,
1578
- `Raise the marker precision instead of the cap, or pass limit up to ${RECOVERY_MAX_LIMIT}.`
1579
- );
1580
- }
1581
- return input.limit;
1582
- }
1583
- async function applyRecoveryDeletes(holder, matches, parentId, resource, operation) {
1584
- const deleteMethod = holder.delete;
1585
- if (deleteMethod === void 0) {
1586
- throw new (0, _chunkVFLAMMIJcjs.DeleteBoundaryError)(
1587
- `recovery: ${resource} has no delete method on the client \u2014 the boundary is marked available in the plan but the typed delete surface is missing`,
1588
- {
1589
- operation,
1590
- suggestedAction: "Report the registry drift (plan says deletable, client exposes no delete) \u2014 or clean up via the Autotask UI.",
1591
- details: { resource }
1592
- }
1593
- );
1594
- }
1595
- const deleted = [];
1596
- for (const match of matches) {
1597
- const id = match.id;
1598
- let verify = "error";
1599
- let code;
1600
- try {
1601
- if (parentId !== void 0) {
1602
- await deleteMethod.call(holder, parentId, id, { dryRun: false });
1603
- } else {
1604
- await deleteMethod.call(holder, id, { dryRun: false });
1605
- }
1606
- try {
1607
- if (parentId !== void 0) {
1608
- await holder.get.call(holder, parentId, id);
1609
- verify = "still-present";
1610
- } else {
1611
- await holder.get.call(holder, id);
1612
- verify = "still-present";
1613
- }
1614
- } catch (err) {
1615
- if (err instanceof _chunkVFLAMMIJcjs.NotFoundError) verify = "not-found";
1616
- else {
1617
- verify = "error";
1618
- code = _chunkVFLAMMIJcjs.isAutotaskError.call(void 0, err) ? err.code : "SERVER_ERROR";
1619
- }
1620
- }
1621
- } catch (err) {
1622
- code = _chunkVFLAMMIJcjs.isAutotaskError.call(void 0, err) ? err.code : "SERVER_ERROR";
1623
- }
1624
- deleted.push({ id, verified: verify === "not-found", code });
1625
- }
1626
- return deleted;
1627
- }
1628
- function collectMatches(items, field) {
1629
- const matches = [];
1630
- const unsafeIds = [];
1631
- items.forEach((record, idx) => {
1632
- const parsed = parseMatchId(record);
1633
- if (parsed.unsafe) unsafeIds.push(idx);
1634
- const value = record[field];
1635
- matches.push({
1636
- id: parsed.id,
1637
- value: typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : null
1638
- });
1639
- });
1640
- return { matches, unsafeIds };
1641
- }
1642
- async function recovery(client, input, opts = {}) {
1643
- const operation = RECOVERY_CONFIRM;
1644
- const { resource, marker, action } = validateRecoveryInput(input, operation);
1645
- const { entry, holder } = resolveRecoveryTarget(client, resource, operation);
1646
- const field = resolveMarkerField(input, resource, operation);
1647
- const parentId = resolveParentId(input, entry, resource, operation);
1648
- const extra = resolveExtraFilter(input, operation);
1649
- const limit = resolveLimit(input, operation);
1650
- const boundaryNote = _chunkVFLAMMIJcjs.DELETE_BOUNDARY_NOTES[resource];
1651
- const boundary = {
1652
- available: boundaryNote === void 0,
1653
- reason: _nullishCoalesce(boundaryNote, () => ( null)),
1654
- authority: null
1655
- };
1656
- const cleanup = boundaryNote === void 0 ? null : {
1657
- manual: true,
1658
- note: `the vendor defines no DELETE for ${resource} \u2014 remove the record(s) in the Autotask UI (UI-only cleanup; the API 405s the method). ${boundaryNote}`
1659
- };
1660
- const isDelete = action === "delete";
1661
- if (isDelete && boundaryNote !== void 0) {
1662
- throw new (0, _chunkVFLAMMIJcjs.DeleteBoundaryError)(
1663
- `recovery: the vendor defines no DELETE for ${resource} \u2014 the delete cannot be issued through the API (zero wire was issued)`,
1664
- {
1665
- operation,
1666
- suggestedAction: cleanup.note,
1667
- details: { resource, note: boundaryNote }
1668
- }
1669
- );
1670
- }
1671
- const dryRun = _optionalChain([opts, 'optionalAccess', _49 => _49.dryRun]) !== false;
1672
- const confirm = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _50 => _50.confirm]), () => ( input.confirm));
1673
- if (isDelete && !dryRun && confirm !== RECOVERY_CONFIRM) {
1674
- throw new (0, _chunkVFLAMMIJcjs.ConfirmationRequiredError)(
1675
- `recovery: action 'delete' apply is destructive and requires its confirmation flag: pass confirm: "${RECOVERY_CONFIRM}"`,
1676
- {
1677
- operation: RECOVERY_CONFIRM,
1678
- suggestedAction: "Pass confirm equal to the operation key to acknowledge the deliberate act \u2014 or keep the dry run (omit dryRun: false) and read the match set first."
1679
- }
1680
- );
1681
- }
1682
- const conditions = [
1683
- { field, op: "contains", value: marker },
1684
- ...parentId !== void 0 && entry.parent !== null && entry.parent.field !== null ? [{ field: entry.parent.field, op: "eq", value: parentId }] : [],
1685
- ...extra
1686
- ];
1687
- const query = await holder.queryBounded({ filter: conditions, limit });
1688
- if (query.hasMore) {
1689
- const wire = isDelete ? "DELETE wire" : "mutating wire";
1690
- throw new (0, _chunkVFLAMMIJcjs.RecoveryTruncatedError)(
1691
- `recovery: the marker search on ${resource} TRIPPED its match cap (${limit}) \u2014 more matches exist than were returned (zero ${wire} was issued)`,
1692
- {
1693
- operation,
1694
- suggestedAction: `Narrow the search (a more precise marker, an input.filter condition, input.parentId) or pass a higher limit (max ${RECOVERY_MAX_LIMIT}).`,
1695
- details: { resource, scanned: query.fetched, limit }
1696
- }
1697
- );
1698
- }
1699
- const { matches, unsafeIds } = collectMatches(query.items, field);
1700
- const unsafeId = unsafeIds.length > 0;
1701
- const base = {
1702
- resource,
1703
- marker,
1704
- field,
1705
- action,
1706
- matches,
1707
- scanned: query.fetched,
1708
- truncated: false,
1709
- delete: boundary,
1710
- unsafeId,
1711
- cleanup,
1712
- dryRun: void 0
1713
- };
1714
- if (!isDelete) return base;
1715
- if (dryRun) {
1716
- return { ...base, simulated: true, dryRun: true };
1717
- }
1718
- if (unsafeId) {
1719
- throw new (0, _chunkVFLAMMIJcjs.MarkerUnsafeIdError)(
1720
- `recovery: ${unsafeIds.length} of ${matches.length} match(es) on ${resource} have ids beyond Number.MAX_SAFE_INTEGER \u2014 they CANNOT be addressed through the SDK's typed delete surface (zero DELETE wire was issued)`,
1721
- {
1722
- operation,
1723
- suggestedAction: cleanup ? cleanup.note : "Remove the unaddressable record(s) in the Autotask UI, then re-run the recovery (the safe matches are addressed one by one).",
1724
- details: { resource, unsafeMatches: unsafeIds.length, totalMatches: matches.length }
1725
- }
1726
- );
1727
- }
1728
- const deleted = await applyRecoveryDeletes(holder, matches, parentId, resource, operation);
1729
- return { ...base, deleted, dryRun: false };
1730
- }
1731
-
1732
- // src/operations/invoke/modes.ts
1733
- function needsConfirmation(record) {
1734
- return record.effect === "destructive" || (_nullishCoalesce(record.flags, () => ( []))).includes("requiresApproval");
1735
- }
1736
- function refuse(message, operation, suggestedAction) {
1737
- throw new (0, _chunkVFLAMMIJcjs.ConfirmationRequiredError)(message, { operation, suggestedAction });
1738
- }
1739
- var MODE_BY_EFFECT = {
1740
- read: "read",
1741
- write: "write",
1742
- destructive: "delete"
1743
- };
1744
- var MODE_TOOL = {
1745
- read: "autotask_read",
1746
- write: "autotask_write",
1747
- delete: "autotask_delete"
1748
- };
1749
- var MODE_SDK = {
1750
- read: "invokeRead",
1751
- write: "invokeWrite",
1752
- delete: "invokeDelete"
1753
- };
1754
- var MODE_EFFECT = {
1755
- read: "read",
1756
- write: "write",
1757
- delete: "destructive"
1758
- };
1759
- function crossModeRefusal(operation, effect, mode) {
1760
- const actual = MODE_BY_EFFECT[effect];
1761
- const subject = effect === "write" ? "a write" : effect === "destructive" ? "destructive" : "a read";
1762
- const target = actual === "read" ? `Route it through the read mode (${MODE_SDK[actual]} / ${MODE_TOOL[actual]}).` : actual === "write" ? `Route it through the write mode (${MODE_SDK[actual]} / ${MODE_TOOL[actual]} \u2014 dry-run first).` : `Deletes have their own tool (${MODE_TOOL[actual]}) and require confirm: "${operation}".`;
1763
- return `invoke: "${operation}" is ${subject} (effect=${effect}) \u2014 refused on the ${mode} path (${MODE_TOOL[mode]} routes effect=${MODE_EFFECT[mode]} operations only). ${target}`;
1764
- }
1765
-
1766
- // src/operations/invoke/validate.ts
1767
- function isSafeNonNegativeInteger(value) {
1768
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1769
- }
1770
- function looseTypeOk(type, value) {
1771
- switch (type) {
1772
- case "number":
1773
- return isSafeNonNegativeInteger(value) || typeof value === "string" && /^\d+$/.test(value);
1774
- case "string":
1775
- return typeof value === "string";
1776
- case "string | number":
1777
- return typeof value === "string" || isSafeNonNegativeInteger(value);
1778
- case "number[]":
1779
- return Array.isArray(value);
1780
- case "string[]":
1781
- return Array.isArray(value) && value.every((v) => typeof v === "string");
1782
- case "object":
1783
- return _chunkVFLAMMIJcjs.isRecord.call(void 0, value);
1784
- default:
1785
- return true;
1786
- }
1787
- }
1788
- function declaredArgList(record) {
1789
- const schema = _nullishCoalesce(record.inputSchema, () => ( {}));
1790
- const names = Object.keys(schema);
1791
- if (names.length === 0) return "no positional args";
1792
- return names.map((n) => `${n}: ${schema[n].type}`).join(", ");
1793
- }
1794
- function resolveDispatchArgs(record, argsIn) {
1795
- const operation = record.id;
1796
- const bad = (message) => {
1797
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`invoke: "${operation}" ${message}`, { operation });
1798
- };
1799
- if (argsIn === void 0 || argsIn === null) return [];
1800
- if (Array.isArray(argsIn)) return argsIn;
1801
- const byName = argsIn;
1802
- if (!_chunkVFLAMMIJcjs.isRecord.call(void 0, byName) || ![Object.prototype, null].includes(Object.getPrototypeOf(byName))) {
1803
- return bad(`args must be a positional array or a plain object of named inputs (declared: ${declaredArgList(record)})`);
1804
- }
1805
- const schema = _nullishCoalesce(record.inputSchema, () => ( {}));
1806
- const names = Object.keys(schema);
1807
- const unknown = Object.keys(byName).filter((k) => !names.includes(k));
1808
- if (unknown.length > 0) bad(`has no input named ${unknown.slice(0, 10).map((k) => `"${k}"`).join(", ")}${unknown.length > 10 ? ` (and ${unknown.length - 10} more)` : ""} (declared: ${declaredArgList(record)})`);
1809
- const missing = names.filter((n) => schema[n].required && byName[n] === void 0);
1810
- if (missing.length > 0) bad(`requires named input ${missing.map((k) => `"${k}"`).join(", ")} (declared: ${declaredArgList(record)})`);
1811
- const last = names.reduce((at, n, i) => byName[n] === void 0 ? at : i, -1);
1812
- return names.slice(0, last + 1).map((n) => byName[n]);
1813
- }
1814
- function validateDispatchArgs(record, argsIn) {
1815
- const args = _nullishCoalesce(argsIn, () => ( []));
1816
- const operation = record.id;
1817
- const schema = _nullishCoalesce(record.inputSchema, () => ( {}));
1818
- const names = Object.keys(schema);
1819
- if (args.length > names.length) {
1820
- const bagName = names.find((n) => schema[n].properties);
1821
- const surplus = args[names.length];
1822
- const bagHint = bagName && surplus !== null && typeof surplus === "object" && !Array.isArray(surplus) ? ` \u2014 options go inside the single ${bagName} object` : "";
1823
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1824
- `invoke: "${operation}" declares ${names.length} positional arg(s) (${declaredArgList(record)}) but ${args.length} were passed \u2014 extra args are refused${bagHint}`,
1825
- {
1826
- operation,
1827
- suggestedAction: `Pass exactly the declared args in order (${declaredArgList(record)}); dryRun/confirm/lastModifiedDate travel through the invoke options, never the args.`
1828
- }
1829
- );
1830
- }
1831
- const required = names.filter((n) => schema[n].required);
1832
- if (args.length < required.length) {
1833
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1834
- `invoke: "${operation}" requires ${required.length} positional arg(s) (${required.map((n) => `${n}: ${schema[n].type}`).join(", ")}) but only ${args.length} were passed \u2014 declared args: ${declaredArgList(record)}`,
1835
- { operation }
1836
- );
1837
- }
1838
- for (let i = 0; i < args.length; i += 1) {
1839
- const value = args[i];
1840
- if (value === void 0) continue;
1841
- const name = names[i];
1842
- const spec = schema[name];
1843
- if (!looseTypeOk(spec.type, value)) {
1844
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1845
- `invoke: "${operation}" arg ${i + 1} is declared ${name}: ${spec.type} \u2014 the value has the wrong type (pass a ${spec.type} value)`,
1846
- { operation }
1847
- );
1848
- }
1849
- }
1850
- }
1851
- function foldFlattenedOpts(record, toolArgs) {
1852
- if (!_chunkVFLAMMIJcjs.isRecord.call(void 0, toolArgs)) {
1853
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(`invoke: "${record.id}" tool args must be a plain object of named inputs`, { operation: record.id });
1854
- }
1855
- const bagKeys = (_nullishCoalesce(_optionalChain([record, 'access', _51 => _51.inputSchema, 'optionalAccess', _52 => _52.opts, 'optionalAccess', _53 => _53.properties]), () => ( []))).map((p) => p.name);
1856
- const flat = Object.keys(toolArgs).filter((k) => bagKeys.includes(k) && toolArgs[k] !== void 0 && toolArgs[k] !== null);
1857
- const folded = { ...toolArgs };
1858
- for (const k of bagKeys) delete folded[k];
1859
- if (folded.opts === null) delete folded.opts;
1860
- if (flat.length === 0) return folded;
1861
- if (folded.opts !== void 0) {
1862
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1863
- `invoke: "${record.id}" was given both opts and flattened option(s) ${flat.map((k) => `"${k}"`).join(", ")} \u2014 pass one form`,
1864
- { operation: record.id }
1865
- );
1866
- }
1867
- return { ...folded, opts: Object.fromEntries(flat.map((k) => [k, toolArgs[k]])) };
1868
- }
1869
-
1870
- // src/operations/invoke/inputSets.ts
1871
- var PARAMS_BAG_KEYS = {
1872
- query: ["filter", "limit", "includeFields", "via", "latest", "noteChars", "fieldCheck", "names", "acknowledgeUnscoped", "walk"],
1873
- // No registry row DECLARES `queryAll` today (a plan decision: the raw walk is
1874
- // called directly), so this entry never fires. It is KEPT so that a future
1875
- // `queryAll` row — which `invoke` WOULD dispatch via holder[operation] —
1876
- // cannot silently open the bag (the keys are the vendor `QueryParams` set
1877
- // the raw method accepts).
1878
- queryAll: ["filter", "maxRecords", "includeFields", "maxTotalRecords", "via"],
1879
- // issue #145: the count bag carries the fieldCheck opt-out — the #140
1880
- // refusal's advised remedy (before, the closed bag refused the very key it
1881
- // advised, and the remedy looped into a second CONFIG_ERROR).
1882
- count: ["filter", "fieldCheck"]
1883
- };
1884
- var BAG_KEY_SYNONYMS = {
1885
- filters: "filter",
1886
- last: "latest",
1887
- // issue #59: the expected typo class (honoured only on `query`)
1888
- lmit: "limit",
1889
- page: "limit",
1890
- pageSize: "limit",
1891
- cursor: "limit",
1892
- maxrecords: "maxRecords",
1893
- fieldsPreset: "includeFields"
1894
- // issue #287: the preset was removed — the refusal points at the explicit includeFields
1895
- };
1896
- function levenshtein(a, b) {
1897
- if (a === b) return 0;
1898
- const prev = new Array(b.length + 1);
1899
- const cur = new Array(b.length + 1);
1900
- for (let j = 0; j <= b.length; j += 1) prev[j] = j;
1901
- for (let i = 1; i <= a.length; i += 1) {
1902
- cur[0] = i;
1903
- for (let j = 1; j <= b.length; j += 1) {
1904
- cur[j] = Math.min((_nullishCoalesce(prev[j], () => ( 0))) + 1, (_nullishCoalesce(cur[j - 1], () => ( 0))) + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
1905
- }
1906
- for (let j = 0; j <= b.length; j += 1) prev[j] = cur[j];
1907
- }
1908
- return _nullishCoalesce(prev[b.length], () => ( 0));
1909
- }
1910
- function suggestForKey(key, valid) {
1911
- const synonym = BAG_KEY_SYNONYMS[key.toLowerCase()];
1912
- if (synonym !== void 0 && valid.includes(synonym)) return synonym;
1913
- let best = null;
1914
- for (const v of valid) {
1915
- const dist = levenshtein(key.toLowerCase(), v.toLowerCase());
1916
- if (best === null || dist < best.dist) best = { key: v, dist };
1917
- }
1918
- if (best !== null && best.dist <= 3) return best.key;
1919
- return null;
1920
- }
1921
- function keySuggestions(unknown, valid) {
1922
- return unknown.map((k) => [k, suggestForKey(k, valid)]).filter((pair) => pair[1] !== null).map(([k, s]) => `"${k}" \u2192 "${s}"`);
1923
- }
1924
- function validateParamsBag(record, args) {
1925
- if (record.inputSchemaStrict !== true) return;
1926
- const valid = PARAMS_BAG_KEYS[record.operation];
1927
- if (valid === void 0) return;
1928
- const bag = _optionalChain([args, 'optionalAccess', _54 => _54[0]]);
1929
- if (bag === void 0 || bag === null || !_chunkVFLAMMIJcjs.isRecord.call(void 0, bag)) return;
1930
- const unknown = Object.keys(bag).filter((k) => !valid.includes(k));
1931
- if (unknown.length === 0) return;
1932
- const suggestions = keySuggestions(unknown, valid);
1933
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1934
- `invoke: "${record.id}" params bag has unknown key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the bag is closed (valid keys: ${valid.join(", ")})`,
1935
- {
1936
- operation: record.id,
1937
- suggestedAction: suggestions.length > 0 ? `Fix the key name (${suggestions.join(", ")}); the full bag contract is in the operation's inputSchema (autotask_describe).` : `Pass only the valid keys (${valid.join(", ")}); the full bag contract is in the operation's inputSchema (autotask_describe).`
1938
- }
1939
- );
1940
- }
1941
- var INVOKE_OPTION_KEYS = ["dryRun", "confirm", "lastModifiedDate", "maxRetries", "fresh", "mode"];
1942
- function validateInvokeOptions(opts, operation) {
1943
- if (opts === void 0 || opts === null) return;
1944
- const unknown = Object.keys(opts).filter((k) => !INVOKE_OPTION_KEYS.includes(k));
1945
- if (unknown.length === 0) return;
1946
- const suggestions = keySuggestions(unknown, ["dryRun", "confirm", "lastModifiedDate", "maxRetries", "fresh"]);
1947
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1948
- `invoke: "${operation}" has unknown option key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the options set is closed (valid: dryRun, confirm, lastModifiedDate, maxRetries, fresh; "mode" is internal to the mode wrappers)`,
1949
- {
1950
- operation,
1951
- suggestedAction: suggestions.length > 0 ? `Fix the option name (${suggestions.join(", ")}).` : `Pass only dryRun / confirm / lastModifiedDate / maxRetries / fresh.`
1952
- }
1953
- );
1954
- }
1955
- function unknownKeysRefusal(raw, validKeys, subject, operation) {
1956
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
1957
- const unknown = Object.keys(raw).filter((k) => !validKeys.includes(k));
1958
- if (unknown.length === 0) return null;
1959
- const suggestions = keySuggestions(unknown, [...validKeys]);
1960
- return new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
1961
- `unknown ${subject} ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the ${subject} set is closed (valid keys: ${validKeys.join(", ")})`,
1962
- {
1963
- ...operation !== void 0 ? { operation } : {},
1964
- suggestedAction: suggestions.length > 0 ? operation === void 0 ? `Fix the ${subject} name (${suggestions.join(", ")}); the tool's full contract is in the tool's inputSchema, served by the MCP client's list_tools.` : `Fix the ${subject} name (${suggestions.join(", ")}); the full contract is in the tool's inputSchema (autotask_describe).` : `Pass only the valid keys (${validKeys.join(", ")}).`
1965
- }
1966
- );
1967
- }
1968
-
1969
- // src/operations/scopeGuard.ts
1970
- var PROPAGATE_CODES = /* @__PURE__ */ new Set([
1971
- "ABORTED",
1972
- "UNAUTHORIZED",
1973
- "FORBIDDEN",
1974
- "HOST_POLICY_DENIED",
1975
- "RATE_LIMITED",
1976
- "RATE_LIMITED_COOLDOWN"
1977
- ]);
1978
- function hasIdScope(filter) {
1979
- if (Array.isArray(filter)) return filter.some(hasIdScope);
1980
- if (filter === null || typeof filter !== "object") return false;
1981
- const n = filter;
1982
- const op = typeof n.op === "string" ? n.op.toLowerCase() : "";
1983
- if (Array.isArray(n.items)) return op === "and" && n.items.some(hasIdScope);
1984
- if (Array.isArray(n.filter)) return hasIdScope(n.filter);
1985
- if (typeof n.field !== "string" || !(n.field === "id" || /ID$/.test(n.field))) return false;
1986
- const real = (v) => v !== void 0 && v !== null && v !== "";
1987
- const list = Array.isArray(n.value) ? n.value : typeof n.value === "string" ? n.value.split(",").map((t) => t.trim()) : [];
1988
- return op === "eq" && real(n.value) || op === "in" && list.some(real);
1989
- }
1990
- async function guardUnscopedQueryWalk(holder, operation, bag) {
1991
- const filter = _optionalChain([bag, 'optionalAccess', _55 => _55["filter"]]);
1992
- const limit = _optionalChain([bag, 'optionalAccess', _56 => _56["limit"]]);
1993
- const latest = _optionalChain([bag, 'optionalAccess', _57 => _57["latest"]]);
1994
- const walk = typeof _optionalChain([bag, 'optionalAccess', _58 => _58["walk"]]) === "object" && _optionalChain([bag, 'optionalAccess', _59 => _59["walk"]]) !== null ? _optionalChain([bag, 'optionalAccess', _60 => _60["walk"]]) : void 0;
1995
- const maxRows = typeof _optionalChain([walk, 'optionalAccess', _61 => _61.maxRows]) === "number" ? walk.maxRows : void 0;
1996
- const effectiveLimit = typeof limit === "number" ? limit : _chunkVFLAMMIJcjs.BOUNDED_QUERY_DEFAULT_LIMIT;
1997
- let walked = typeof latest === "number" ? Math.max(effectiveLimit, latest) : effectiveLimit;
1998
- if (maxRows !== void 0) walked = Math.min(walked, maxRows);
1999
- if (walked <= _chunkVFLAMMIJcjs.UNSCOPED_WALK_THRESHOLD || hasIdScope(filter)) return {};
2000
- let conditions;
2001
- try {
2002
- conditions = _chunkVFLAMMIJcjs.normalizedConditions.call(void 0, filter);
2003
- } catch (e) {
2004
- return {};
2005
- }
2006
- let counted;
2007
- try {
2008
- counted = await holder.count({ filter: conditions === void 0 ? void 0 : conditions });
2009
- } catch (err) {
2010
- const code = _optionalChain([err, 'optionalAccess', _62 => _62.code]);
2011
- if (typeof code === "string" && PROPAGATE_CODES.has(code)) throw err;
2012
- const label = typeof code === "string" && /^[A-Z][A-Z_]{2,40}$/.test(code) ? ` (${code})` : "";
2013
- return { note: `scope check skipped: the pre-flight count failed${label}; this unscoped walk was not guarded.` };
2014
- }
2015
- const sentinel = typeof counted === "object" && counted !== null && "count" in counted ? counted : { count: counted };
2016
- if (sentinel.blindSurface !== void 0) {
2017
- return { note: "scope check skipped: the count surface is blind (a 0 is not a true zero); this unscoped walk was not guarded." };
2018
- }
2019
- const count = typeof sentinel.count === "number" && Number.isFinite(sentinel.count) && sentinel.count >= 0 ? sentinel.count : void 0;
2020
- if (count === void 0) {
2021
- return { note: "scope check skipped: the pre-flight count was unreadable; this unscoped walk was not guarded." };
2022
- }
2023
- if (count > _chunkVFLAMMIJcjs.UNSCOPED_WALK_THRESHOLD) throw new (0, _chunkVFLAMMIJcjs.ScopeRequiredError)(operation, { threshold: _chunkVFLAMMIJcjs.UNSCOPED_WALK_THRESHOLD, count });
2024
- return {};
2025
- }
2026
-
2027
- // src/operations/invoke/dispatch.ts
2028
- var INVOKE_MAX_INLINE_BYTES = 1024 * 1024;
2029
- function bufferResult(buf) {
2030
- if (buf.byteLength <= INVOKE_MAX_INLINE_BYTES) {
2031
- return { encoding: "base64", byteLength: buf.byteLength, truncated: false, data: buf.toString("base64") };
2032
- }
2033
- return {
2034
- encoding: "base64",
2035
- byteLength: buf.byteLength,
2036
- truncated: true,
2037
- note: `Body exceeds the ${INVOKE_MAX_INLINE_BYTES}-byte inline limit and was not returned; call the resource's download method directly.`
2038
- };
2039
- }
2040
- function resolveRecord(operation) {
2041
- const record = OPERATION_BY_ID.get(operation);
2042
- if (record === void 0) {
2043
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2044
- `invoke: unknown operation "${operation}" \u2014 it is not in the capability registry`,
2045
- { operation, suggestedAction: "List the registry (the ./capabilities subpath) for the exact operation key." }
2046
- );
2047
- }
2048
- return record;
2049
- }
2050
- function governPreRequest(record, operation, opts) {
2051
- validateInvokeOptions(opts, operation);
2052
- const mode = opts.mode;
2053
- if (mode !== void 0 && MODE_BY_EFFECT[record.effect] !== mode) {
2054
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(crossModeRefusal(operation, record.effect, mode), { operation });
2055
- }
2056
- if (opts.dryRun !== void 0 && typeof opts.dryRun !== "boolean") {
2057
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2058
- `invoke: dryRun must be a boolean (got ${typeof opts.dryRun}) \u2014 "${operation}" was not issued`,
2059
- { operation, suggestedAction: "Pass dryRun: true (simulate) or dryRun: false (apply) as a real boolean." }
2060
- );
2061
- }
2062
- if (opts.fresh !== void 0) {
2063
- if (typeof opts.fresh !== "boolean") {
2064
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2065
- `invoke: fresh must be a boolean (got ${typeof opts.fresh}) \u2014 "${operation}" was not issued`,
2066
- { operation, suggestedAction: "Pass fresh: true (bypass the read cache) or omit it." }
2067
- );
2068
- }
2069
- if (record.effect !== "read" || record.kind !== "primitive" || record.operation !== "get" || !("opts" in (_nullishCoalesce(record.inputSchema, () => ( {}))))) {
2070
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2071
- `invoke: fresh applies only to a single-record "<resource>.get" read \u2014 "${operation}" was not issued`,
2072
- { operation, suggestedAction: "Drop the fresh option (only <resource>.get consults the read cache per call)." }
2073
- );
2074
- }
2075
- }
2076
- if (record.effect === "read" && opts.dryRun === true) {
2077
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2078
- `invoke: "${operation}" is a read \u2014 dryRun is not applicable (reads issue their request as-is)`,
2079
- { operation }
2080
- );
2081
- }
2082
- if (needsConfirmation(record) && opts.confirm !== operation) {
2083
- refuse(
2084
- `invoke: "${operation}" is ${record.effect}${(_nullishCoalesce(record.flags, () => ( []))).includes("requiresApproval") ? " approval-gated" : ""} and requires its confirmation flag: pass confirm: "${operation}"`,
2085
- operation,
2086
- "Pass confirm equal to the operation key to acknowledge the deliberate act."
2087
- );
2088
- }
2089
- }
2090
- function operationsHolder(client) {
2091
- return {
2092
- resolveAny: (identifier, opts) => resolveAny(client, identifier, opts),
2093
- searchAcrossResources: (query, opts) => searchAcrossResources(client, query, opts),
2094
- fetchMany: (items, opts) => _chunkVFLAMMIJcjs.fetchMany.call(void 0, client, items, opts),
2095
- // Issue #62: the generic aggregate + the cheap count — the
2096
- // groupBy/sum-absent mode is ONE vendor /query/count (the S12-C blind
2097
- // sentinel rides through); the group/sum modes walk the named
2098
- // <resource>.query op via the house bounded walk and fold client-side
2099
- // (truncation honesty: hasMore/totalScanned — no silent partial sums).
2100
- aggregate: (operation, filter, opts) => aggregate(client, operation, filter, opts),
2101
- // Issue #234: revenue by dimension in one budgeted call (refuses over budget, never partial).
2102
- revenueRollup: (opts) => revenueRollup(client, opts),
2103
- // Issue #59 PR-B: the bulk fetch by ids — query-first chunked `id in`
2104
- // reads + the per-id `get` fallback (blind-surface-safe; per-item
2105
- // typed outcomes reuse the fetchMany shapes).
2106
- fetchByIds: (items, opts) => _chunkVFLAMMIJcjs.fetchByIds.call(void 0, client, items, opts),
2107
- // Issue #65: opt-in resource name expansion — per-id resources.get
2108
- // reads ONLY (never an id-in query — the resources table can be blind
2109
- // to count/query while per-id reads work); blind-marked rows for ids
2110
- // the API user cannot resolve; the read cache (default-on).
2111
- expandResourceNames: (ids, opts) => _chunkVFLAMMIJcjs.expandResourceNames.call(void 0, client, ids, opts),
2112
- relationshipMap: (resourceKey) => relationshipMap(client, resourceKey),
2113
- // Issue #11: the served blind-surface inventory (zero wire) + the
2114
- // bounded caller-opted reachability sweep (read-only, confirmProbe-
2115
- // required — the op's own pre-request gates keep it deliberate).
2116
- blindSurfaces: (opts) => blindSurfaces(client, opts),
2117
- probeReachability: (opts) => probeReachability(client, opts),
2118
- // Issue #12: the served zone-fact create-guidance catalog (zero wire).
2119
- createGuidance: (opts) => createGuidance(client, opts),
2120
- // Issue #202: ExecuteCommand deep links — pure, zero wire.
2121
- uiLink: (input) => _chunkVFLAMMIJcjs.uiLink.call(void 0, client, input),
2122
- // Issue #287 (D8): the projection-layer untrusted-content helper — pure,
2123
- // zero wire (the registry record's metadata.untrustedFields + the deep
2124
- // walker from src/untrusted.ts; an unknown operation refuses).
2125
- sanitiseResult: (result, operation) => sanitiseResult(client, result, operation),
2126
- // Issue #31: the opt-in live field-metadata channel — N bounded
2127
- // entityInformation GETs (the op's own cap refuses over-cap fan-outs
2128
- // pre-request; per-resource failures are recorded on the row and
2129
- // never abort the call).
2130
- fieldCatalog: (opts) => _chunkVFLAMMIJcjs.fieldCatalog.call(void 0, client, opts),
2131
- // Issue #9: the one-call recovery/cleanup op — effect write in the
2132
- // registry (the governor's dry-run-first applies to it); the op's own
2133
- // confirm gate makes the destructive action deliberate (the governor
2134
- // forwards the caller's confirm through the trailing bag below).
2135
- recovery: (input, opts) => recovery(client, input, opts)
2136
- };
2137
- }
2138
- function resolveHolder(client, record, operation) {
2139
- const clientBag = client;
2140
- let holder = clientBag[_chunkVFLAMMIJcjs.camelKey.call(void 0, record.resource)];
2141
- if (typeof holder === "function") {
2142
- holder = _nullishCoalesce(clientBag[`${_chunkVFLAMMIJcjs.camelKey.call(void 0, record.resource)}Resource`], () => ( holder));
2143
- }
2144
- if (holder === void 0 && record.resource === "operations") {
2145
- holder = operationsHolder(client);
2146
- }
2147
- if (holder === void 0) {
2148
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2149
- `invoke: the client exposes no accessor for resource "${record.resource}"`,
2150
- { operation }
2151
- );
2152
- }
2153
- return holder;
2154
- }
2155
- var BAG_RIDING_ARGS = {
2156
- createIfNotExists: ["dedupFields"],
2157
- moveConfigurationItem: ["destinationCompanyId"]
2158
- };
2159
- function buildMutationOpts(opts, isWrite) {
2160
- const dryRun = isWrite ? opts.dryRun !== false : false;
2161
- const mutationOpts = { dryRun };
2162
- if (isWrite && opts.lastModifiedDate !== void 0) {
2163
- mutationOpts.lastModifiedDate = opts.lastModifiedDate;
2164
- }
2165
- if (isWrite && opts.maxRetries !== void 0) {
2166
- mutationOpts.maxRetries = opts.maxRetries;
2167
- }
2168
- if (isWrite && opts.confirm !== void 0) {
2169
- mutationOpts.confirm = opts.confirm;
2170
- }
2171
- return mutationOpts;
2172
- }
2173
- function buildCallArgs(record, args, mutationOpts, isWrite) {
2174
- const declaredNames = Object.keys(_nullishCoalesce(record.inputSchema, () => ( {})));
2175
- const bagRiding = new Set(_nullishCoalesce(BAG_RIDING_ARGS[record.operation], () => ( [])));
2176
- let positional = args;
2177
- for (const argName of _nullishCoalesce(BAG_RIDING_ARGS[record.operation], () => ( []))) {
2178
- const at = declaredNames.indexOf(argName);
2179
- if (at >= 0 && args.length > at) {
2180
- mutationOpts[argName] = args[at];
2181
- positional = positional.slice(0, at);
2182
- }
2183
- }
2184
- const callArgs = [...positional];
2185
- if (isWrite) {
2186
- const declaredPositional = declaredNames.filter((n) => !bagRiding.has(n));
2187
- while (callArgs.length < declaredPositional.length) callArgs.push(void 0);
2188
- callArgs.push(mutationOpts);
2189
- }
2190
- return callArgs;
2191
- }
2192
- function appendFresh(record, callArgs) {
2193
- const at = Object.keys(_nullishCoalesce(record.inputSchema, () => ( {}))).indexOf("opts");
2194
- while (callArgs.length < at) callArgs.push(void 0);
2195
- callArgs[at] = { fresh: true };
2196
- return callArgs;
2197
- }
2198
- function validateFreshSpelling(record, args) {
2199
- if (record.kind !== "primitive" || record.operation !== "get") return;
2200
- const at = Object.keys(_nullishCoalesce(record.inputSchema, () => ( {}))).indexOf("opts");
2201
- const bag = at < 0 ? void 0 : args[at];
2202
- if (bag === void 0 || bag === null) return;
2203
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2204
- `invoke: "${record.id}" takes no positional options bag \u2014 the fresh spelling is the fresh invoke option (pass { fresh: true } in the invoke options, not as a method argument)`,
2205
- {
2206
- operation: record.id,
2207
- suggestedAction: 'Pass fresh: true as an invoke option: invoke(client, "<resource>.get", [id], { fresh: true }). The typed client.<resource>.get(id, { fresh: true }) method keeps its own bag.'
2208
- }
2209
- );
2210
- }
2211
- var PRE_REQUEST_VALIDATORS = [
2212
- (c) => governPreRequest(c.record, c.operation, c.opts),
2213
- (c) => validateDispatchArgs(c.record, c.args),
2214
- (c) => validateParamsBag(c.record, c.args),
2215
- (c) => validateFreshSpelling(c.record, c.args),
2216
- (c) => c.boundedQuery || c.countedQuery ? _chunkVFLAMMIJcjs.validateQueryFieldNames.call(void 0, c.record, c.holder, c.args) : Promise.resolve(void 0)
2217
- ];
2218
- async function runPreRequestPass(ctx) {
2219
- for (const step of PRE_REQUEST_VALIDATORS) await step(ctx);
2220
- }
2221
- async function invokeMode(client, operation, argsIn = [], opts = {}) {
2222
- const record = resolveRecord(operation);
2223
- const args = resolveDispatchArgs(record, argsIn);
2224
- const boundedQuery = record.kind === "primitive" && record.operation === "query";
2225
- const countedQuery = record.kind === "primitive" && record.operation === "count";
2226
- const holder = resolveHolder(client, record, operation);
2227
- await runPreRequestPass({ record, operation, args, opts, client, holder, boundedQuery, countedQuery });
2228
- const labelQuery = boundedQuery && _chunkVFLAMMIJcjs.readNamesOpt.call(void 0, client, _optionalChain([args, 'access', _63 => _63[0], 'optionalAccess', _64 => _64["names"]]), `invoke: "${record.id}" params bag`);
2229
- const methodName = boundedQuery ? "queryBounded" : record.operation;
2230
- const method = holder[methodName];
2231
- if (typeof method !== "function") {
2232
- throw new (0, _chunkVFLAMMIJcjs.AutotaskConfigError)(
2233
- `invoke: "${record.resource}.${methodName}" is not a callable method on the client`,
2234
- { operation }
2235
- );
2236
- }
2237
- const isWrite = record.effect !== "read";
2238
- const mutationOpts = buildMutationOpts(opts, isWrite);
2239
- const callArgs = buildCallArgs(record, args, mutationOpts, isWrite);
2240
- if (opts.fresh === true) appendFresh(record, callArgs);
2241
- if (boundedQuery) _chunkVFLAMMIJcjs.validateWalkBag.call(void 0, _optionalChain([args, 'access', _65 => _65[0], 'optionalAccess', _66 => _66["walk"]]), `invoke: "${record.id}"`, { operation: record.id });
2242
- let scopeNote;
2243
- if (boundedQuery && _optionalChain([args, 'access', _67 => _67[0], 'optionalAccess', _68 => _68["acknowledgeUnscoped"]]) !== true) {
2244
- scopeNote = (await guardUnscopedQueryWalk(holder, record.id, args[0])).note;
2245
- }
2246
- const result = await method.apply(holder, callArgs);
2247
- if (labelQuery && Array.isArray(_optionalChain([result, 'optionalAccess', _69 => _69.items]))) {
2248
- const bounded = result;
2249
- const labelled = await _chunkVFLAMMIJcjs.labelRows.call(void 0, client, record.resource, bounded.items);
2250
- return { ...bounded, items: labelled.rows, ...labelled.names !== void 0 ? { names: labelled.names } : {}, ...scopeNote !== void 0 ? { scopeNote } : {} };
2251
- }
2252
- const finished = Buffer.isBuffer(result) ? bufferResult(result) : result;
2253
- return scopeNote !== void 0 && finished !== null && typeof finished === "object" ? { ...finished, scopeNote } : finished;
2254
- }
2255
- async function invoke(client, operation, args = [], opts = {}) {
2256
- return invokeMode(client, operation, args, { ...opts, mode: void 0 });
2257
- }
2258
- async function invokeRead(client, operation, args = [], opts = {}) {
2259
- return invokeMode(client, operation, args, { ...opts, mode: "read" });
2260
- }
2261
- async function invokeWrite(client, operation, args = [], opts = {}) {
2262
- return invokeMode(client, operation, args, { ...opts, mode: "write" });
2263
- }
2264
- async function invokeDelete(client, operation, args = [], opts = {}) {
2265
- return invokeMode(client, operation, args, { ...opts, mode: "delete" });
2266
- }
2267
82
 
2268
83
 
2269
84
 
@@ -2272,48 +87,4 @@ async function invokeDelete(client, operation, args = [], opts = {}) {
2272
87
 
2273
88
 
2274
89
 
2275
-
2276
-
2277
-
2278
-
2279
-
2280
-
2281
-
2282
-
2283
-
2284
-
2285
-
2286
-
2287
-
2288
-
2289
-
2290
-
2291
-
2292
-
2293
-
2294
-
2295
-
2296
-
2297
-
2298
-
2299
-
2300
-
2301
-
2302
-
2303
-
2304
-
2305
-
2306
-
2307
-
2308
-
2309
-
2310
-
2311
-
2312
-
2313
-
2314
-
2315
-
2316
-
2317
-
2318
-
2319
- exports.AGGREGATE_DATE_BUCKETS = AGGREGATE_DATE_BUCKETS; exports.AGGREGATE_DEFAULT_LIMIT = AGGREGATE_DEFAULT_LIMIT; exports.AGGREGATE_DEFAULT_TOP = AGGREGATE_DEFAULT_TOP; exports.AGGREGATE_TOP_MAX = AGGREGATE_TOP_MAX; exports.BLIND_SURFACE_KINDS = BLIND_SURFACE_KINDS; exports.CREATE_BLOCKER_KINDS = CREATE_BLOCKER_KINDS; exports.EXPAND_RESOURCE_NAMES_MAX_IDS = _chunkVFLAMMIJcjs.EXPAND_RESOURCE_NAMES_MAX_IDS; exports.FETCH_BY_IDS_CHUNK_SIZE = _chunkVFLAMMIJcjs.FETCH_BY_IDS_CHUNK_SIZE; exports.FETCH_BY_IDS_MAX_ITEMS = _chunkVFLAMMIJcjs.FETCH_BY_IDS_MAX_ITEMS; exports.FETCH_MANY_MAX_ITEMS = _chunkVFLAMMIJcjs.FETCH_MANY_MAX_ITEMS; exports.FIELD_CACHE_KINDS = _chunkVFLAMMIJcjs.FIELD_CACHE_KINDS; exports.FIELD_CATALOG_HONESTY_NOTE = _chunkVFLAMMIJcjs.FIELD_CATALOG_HONESTY_NOTE; exports.INVOKE_OPTION_KEYS = INVOKE_OPTION_KEYS; exports.MAX_FIELD_CATALOG_WIRE = _chunkVFLAMMIJcjs.MAX_FIELD_CATALOG_WIRE; exports.MIN_FIELD_CATALOG_MAX_BYTES = _chunkVFLAMMIJcjs.MIN_FIELD_CATALOG_MAX_BYTES; exports.PARAMS_BAG_KEYS = PARAMS_BAG_KEYS; exports.PICKLIST_VALUE_NOT_SERVED_NOTE = _chunkVFLAMMIJcjs.PICKLIST_VALUE_NOT_SERVED_NOTE; exports.RECOVERY_CONFIRM = RECOVERY_CONFIRM; exports.RECOVERY_DEFAULT_LIMIT = RECOVERY_DEFAULT_LIMIT; exports.RECOVERY_MAX_LIMIT = RECOVERY_MAX_LIMIT; exports.UI_LINK_ENTITIES = _chunkVFLAMMIJcjs.UI_LINK_ENTITIES; exports.WORKFLOW_RESOURCES = _chunkVFLAMMIJcjs.WORKFLOW_RESOURCES; exports.aggregate = aggregate; exports.blindSurfaces = blindSurfaces; exports.buildUiLink = _chunkVFLAMMIJcjs.buildUiLink; exports.createGuidance = createGuidance; exports.dateBucketLabel = dateBucketLabel; exports.effectiveRequiredFields = _chunkVFLAMMIJcjs.effectiveRequiredFields; exports.expandResourceNames = _chunkVFLAMMIJcjs.expandResourceNames; exports.fetchByIds = _chunkVFLAMMIJcjs.fetchByIds; exports.fetchMany = _chunkVFLAMMIJcjs.fetchMany; exports.fieldCatalog = _chunkVFLAMMIJcjs.fieldCatalog; exports.foldFlattenedOpts = foldFlattenedOpts; exports.invoke = invoke; exports.invokeDelete = invokeDelete; exports.invokeRead = invokeRead; exports.invokeWrite = invokeWrite; exports.needsConfirmation = needsConfirmation; exports.probeReachability = probeReachability; exports.recovery = recovery; exports.relationshipMap = relationshipMap; exports.resolveAny = resolveAny; exports.revenueRollup = revenueRollup; exports.sanitiseResult = sanitiseResult; exports.searchAcrossResources = searchAcrossResources; exports.uiLink = _chunkVFLAMMIJcjs.uiLink; exports.unknownKeysRefusal = unknownKeysRefusal; exports.validateDispatchArgs = validateDispatchArgs; exports.validateFetchManyItems = _chunkVFLAMMIJcjs.validateFetchManyItems; exports.validateInvokeOptions = validateInvokeOptions; exports.validateParamsBag = validateParamsBag;
90
+ exports.AGGREGATE_DATE_BUCKETS = _chunkYMBVKVK4cjs.AGGREGATE_DATE_BUCKETS; exports.AGGREGATE_DEFAULT_LIMIT = _chunkYMBVKVK4cjs.AGGREGATE_DEFAULT_LIMIT; exports.AGGREGATE_DEFAULT_TOP = _chunkYMBVKVK4cjs.AGGREGATE_DEFAULT_TOP; exports.AGGREGATE_TOP_MAX = _chunkYMBVKVK4cjs.AGGREGATE_TOP_MAX; exports.CREATE_BLOCKER_KINDS = _chunkYMBVKVK4cjs.CREATE_BLOCKER_KINDS; exports.EXPAND_RESOURCE_NAMES_MAX_IDS = _chunkYMBVKVK4cjs.EXPAND_RESOURCE_NAMES_MAX_IDS; exports.FETCH_BY_IDS_CHUNK_SIZE = _chunkYMBVKVK4cjs.FETCH_BY_IDS_CHUNK_SIZE; exports.FETCH_BY_IDS_MAX_ITEMS = _chunkYMBVKVK4cjs.FETCH_BY_IDS_MAX_ITEMS; exports.FIELD_CACHE_KINDS = _chunkYMBVKVK4cjs.FIELD_CACHE_KINDS; exports.FIELD_CATALOG_HONESTY_NOTE = _chunkYMBVKVK4cjs.FIELD_CATALOG_HONESTY_NOTE; exports.INVOKE_OPTION_KEYS = _chunkYMBVKVK4cjs.INVOKE_OPTION_KEYS; exports.MAX_FIELD_CATALOG_WIRE = _chunkYMBVKVK4cjs.MAX_FIELD_CATALOG_WIRE; exports.MIN_FIELD_CATALOG_MAX_BYTES = _chunkYMBVKVK4cjs.MIN_FIELD_CATALOG_MAX_BYTES; exports.PARAMS_BAG_KEYS = _chunkYMBVKVK4cjs.PARAMS_BAG_KEYS; exports.PICKLIST_VALUE_NOT_SERVED_NOTE = _chunkYMBVKVK4cjs.PICKLIST_VALUE_NOT_SERVED_NOTE; exports.UI_LINK_ENTITIES = _chunkYMBVKVK4cjs.UI_LINK_ENTITIES; exports.WORKFLOW_RESOURCES = _chunkYMBVKVK4cjs.WORKFLOW_RESOURCES; exports.aggregate = _chunkYMBVKVK4cjs.aggregate; exports.buildUiLink = _chunkYMBVKVK4cjs.buildUiLink; exports.createGuidance = _chunkYMBVKVK4cjs.createGuidance; exports.dateBucketLabel = _chunkYMBVKVK4cjs.dateBucketLabel; exports.effectiveRequiredFields = _chunkYMBVKVK4cjs.effectiveRequiredFields; exports.expandResourceNames = _chunkYMBVKVK4cjs.expandResourceNames; exports.fetchByIds = _chunkYMBVKVK4cjs.fetchByIds; exports.fieldCatalog = _chunkYMBVKVK4cjs.fieldCatalog; exports.foldFlattenedOpts = _chunkYMBVKVK4cjs.foldFlattenedOpts; exports.invoke = _chunkYMBVKVK4cjs.invoke; exports.invokeDelete = _chunkYMBVKVK4cjs.invokeDelete; exports.invokeRead = _chunkYMBVKVK4cjs.invokeRead; exports.invokeWrite = _chunkYMBVKVK4cjs.invokeWrite; exports.needsConfirmation = _chunkYMBVKVK4cjs.needsConfirmation; exports.relationshipMap = _chunkYMBVKVK4cjs.relationshipMap; exports.resolveAny = _chunkYMBVKVK4cjs.resolveAny; exports.resolveStaff = _chunkYMBVKVK4cjs.resolveStaff; exports.revenueRollup = _chunkYMBVKVK4cjs.revenueRollup; exports.sanitiseResult = _chunkYMBVKVK4cjs.sanitiseResult; exports.searchAcrossResources = _chunkYMBVKVK4cjs.searchAcrossResources; exports.uiLink = _chunkYMBVKVK4cjs.uiLink; exports.unknownKeysRefusal = _chunkYMBVKVK4cjs.unknownKeysRefusal; exports.validateDispatchArgs = _chunkYMBVKVK4cjs.validateDispatchArgs; exports.validateInvokeOptions = _chunkYMBVKVK4cjs.validateInvokeOptions; exports.validateParamsBag = _chunkYMBVKVK4cjs.validateParamsBag;