node-autotask 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +15 -8
- package/CHANGELOG.md +590 -0
- package/MCP_TOOL_CATALOG.json +6 -6
- package/MCP_TOOL_MANIFEST.md +128 -128
- package/N8N_MCP_MANIFEST.json +44 -9
- package/N8N_MCP_MANIFEST.md +8 -8
- package/README.md +8 -4
- package/capabilities.json +1941 -1829
- package/capabilities.schema.json +6 -0
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-VV2UUW2J.js → chunk-2T77J2ZM.js} +1940 -1828
- package/dist/{chunk-5O5EPYBO.cjs → chunk-FLYTKJKV.cjs} +1940 -1828
- package/dist/{chunk-VYUZZIEE.js → chunk-GA6UCX3Z.js} +292 -58
- package/dist/{chunk-W2WSZOZA.js → chunk-JEJUFXHX.js} +2 -2
- package/dist/{chunk-WRWX6WIK.cjs → chunk-L6V66V22.cjs} +360 -126
- package/dist/{chunk-G564CHXM.cjs → chunk-RMLGOCGA.cjs} +135 -135
- package/dist/{chunk-HXOWEYV3.js → chunk-XSYJB4IW.js} +3 -3
- package/dist/{chunk-7I3F3AJJ.cjs → chunk-YH5CPRDN.cjs} +394 -394
- package/dist/{index-D7x3MkLK.d.cts → index-AN-NjLi9.d.cts} +126 -21
- package/dist/{index-BqPY04ZG.d.ts → index-CvLLsOCP.d.ts} +126 -21
- package/dist/index.cjs +24 -22
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -4
- package/dist/operations/index.cjs +184 -136
- package/dist/operations/index.d.cts +2 -2
- package/dist/operations/index.d.ts +2 -2
- package/dist/operations/index.js +79 -31
- package/dist/pool.cjs +8 -8
- package/dist/pool.d.cts +1 -1
- package/dist/pool.d.ts +1 -1
- package/dist/pool.js +4 -4
- package/dist/resources/index.cjs +4 -4
- package/dist/resources/index.d.cts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +3 -3
- package/docs/API.md +126 -40
- package/package.json +2 -1
package/docs/API.md
CHANGED
|
@@ -93,7 +93,7 @@ auto-retry on all legs — plus a concurrency guard (`maxConcurrent`, default 10
|
|
|
93
93
|
allows <= 10 concurrent), one correlation id per request (surfaced on every
|
|
94
94
|
error and to `onAudit`), and the opt-in client-side hourly budget tracker
|
|
95
95
|
(`rateLimit.perHour`, default off — a sliding one-hour window that waits for a slot within the call deadline, else throws `RateLimitError` with `retryAfter`; the API allows 10,000 req/hour). The tracker is PER PROCESS: it counts only this client's attempts and cannot see other instances or integrations on the tenant (`thresholdInformation.get()` is the only cross-integration signal; the local count is `client.hourlyBudget()`, section 3.2.1). Under the `agent` config profile the exhaustion `suggestedAction` says stop or narrow the query — never raise the limit.
|
|
96
|
-
Redirects are never followed (a 3xx throws `REDIRECT_BLOCKED`, so auth headers cannot leave the configured origin). Retry waits are capped at 60 s and never outlast the call deadline: a larger `Retry-After` (or one that exceeds the remaining budget) throws the `RateLimitError` immediately. `RequestOptions` adds `signal` (caller `AbortSignal`, combined with the deadline; aborting throws `AutotaskAbortError`, code `ABORTED`, and cancels retry sleeps) and `idempotent` (retry a non-GET method on 5xx/network errors; set on the `/query` and `/query/count` POST reads; on a mutation it is the issue #105 opt-back into the pre-#105 auto-retry — never set on real writes by the SDK). `client.withSignal(signal, () => ...)` applies a caller `AbortSignal` to every request (and retry wait) made inside the callback — the way to propagate MCP request cancellation through `invoke` without a signal parameter on each method. A real write clears the read cache. A non-JSON 2xx body is typed by the response `Content-Type` (section 2.1): a non-JSON-ish header throws the retryable `UNEXPECTED_CONTENT_TYPE` (the edge/maintenance page — a maintenance notice is waited out up to `maintenanceMaxWaitMs`, default 1 h, for reads and writes; otherwise a GET or idempotent-flagged call retries and a write makes one call
|
|
96
|
+
Redirects are never followed (a 3xx throws `REDIRECT_BLOCKED`, so auth headers cannot leave the configured origin). Retry waits are capped at 60 s and never outlast the call deadline: a larger `Retry-After` (or one that exceeds the remaining budget) throws the `RateLimitError` immediately. `RequestOptions` adds `signal` (caller `AbortSignal`, combined with the deadline; aborting throws `AutotaskAbortError`, code `ABORTED`, and cancels retry sleeps) and `idempotent` (retry a non-GET method on 5xx/network errors; set on the `/query` and `/query/count` POST reads; on a mutation it is the issue #105 opt-back into the pre-#105 auto-retry — never set on real writes by the SDK). `client.withSignal(signal, () => ...)` applies a caller `AbortSignal` to every request (and retry wait) made inside the callback — the way to propagate MCP request cancellation through `invoke` without a signal parameter on each method. A real write clears the read cache. A non-JSON 2xx body is typed by the response `Content-Type` (section 2.1): a non-JSON-ish header throws the retryable `UNEXPECTED_CONTENT_TYPE` (the edge/maintenance page — a maintenance notice is waited out up to `maintenanceMaxWaitMs`, default 1 h, for reads and writes; the same wait recognises the page at any status for calls safe to re-send (a GET or an explicitly idempotent-flagged call — an unflagged mutation makes ONE send with `details.maintenanceDetected`): section 2.1; otherwise a GET or idempotent-flagged call retries and a write makes one call); a JSON-ish or ABSENT header keeps `INVALID_RESPONSE` (the #13/W-A7 shape-drift key); JSON under any other header still decodes (the decode attempt is unconditional). A blank 2xx on a call that expects a result (every method but `DELETE` by default — `RequestOptions.expectBody`) throws the typed `EMPTY_RESPONSE` (`DELETE` and 204 keep returning `undefined`). A response above `maxResponseBytes` throws `RESPONSE_TOO_LARGE`; HTTP 412 maps to `ConflictError` (category `conflict`).
|
|
97
97
|
Per-tenant 429 cooldown gate (`cooldown`, ON by default): once a 429 surfaces from the transport (retries spent, a `Retry-After` too long to wait out, or a non-retriable method), the client records a cooldown deadline for that tenant (lowercased `UserName` + lowercased host — impersonation does not change the key), and every further call for that tenant fails LOCALLY — before the semaphore, the hourly limiter and fetch, with zero upstream calls — with a `RateLimitError` (`code: 'RATE_LIMITED_COOLDOWN'`, `notSent: true`, `retryAfter` = remaining seconds; no credentials appear in the message) until the deadline passes. Duration is `Retry-After` clamped to `cooldown.maxMs` (default 15 minutes), or `cooldown.defaultMs` (default 60 s) when the header is missing or unparseable; the thread-threshold 429 (vendor code `Itgenatr005`, which clears in seconds) arms only a short window: min(`Retry-After`, 10 s), default 5 s. A 2xx from a call that got through clears the gate early; expiry is lazy (the first call after the deadline deletes the entry and proceeds); a hostile `Retry-After` (huge, zero, negative, non-numeric) is clamped to the cap or falls back to the default, so a lock-out is always bounded. `thresholdInformation.get()` bypasses the gate so a throttled tenant can still read its own headroom (a 2xx from it clears the gate; a 429 re-arms it). The default store is the in-process in-memory map shared by every client instance (capped at 1024 entries, evicting the earliest deadline first — failing open); `cooldown: { enabled: false }` disables the gate and `cooldown.store` injects a custom `CooldownStore` (the interface leaves room for a shared/Redis-backed store). Gated calls emit an audit event marked `notSent` and do not count toward `rateLimit.perHour`. Limits worth knowing: the gate is per process — other processes and integrations on the same tenant are invisible to it (`thresholdInformation` is the only cross-integration signal); it can mask early recovery if the vendor window clears before `Retry-After`; a process that loads both the ESM and the CJS build gets two default stores (the default store is deliberately not anchored on `globalThis`) — inject one shared `CooldownStore` if you need cross-build sharing.
|
|
98
98
|
|
|
99
99
|
Credential-shaped fields are redacted in audit payloads by default.
|
|
@@ -259,7 +259,7 @@ status → what the vendor does that triggers it.
|
|
|
259
259
|
| `RateLimitError` | `RATE_LIMITED` | rate_limit | 429 | hourly/secondly budget exceeded — `retryAfter` from `Retry-After` — retryable |
|
|
260
260
|
| `RateLimitError` | `RATE_LIMITED_COOLDOWN` | rate_limit | — (call not sent) | per-tenant cooldown after a surfaced 429 (issue #6) — `notSent: true`, `retryAfter` = remaining seconds — not retryable before the deadline |
|
|
261
261
|
| `ServerError` | `SERVER_ERROR` | server | 5xx | vendor 5xx — retryable — EXCEPT a deterministic field-validation 500 (a known phrase, `isDeterministicVendor500`): same class/code/status, `retryable: false` + `details.deterministic: true` (issue #23) |
|
|
262
|
-
| `UnexpectedContentTypeError` | `UNEXPECTED_CONTENT_TYPE` | server | 2xx (non-JSON body + a non-JSON-ish `Content-Type`) | the edge serves a non-JSON page (maintenance) — the vendor never processed the request; `details = { httpStatus, contentType, bodySnippet (256 B) }` — retryable (advisory). A
|
|
262
|
+
| `UnexpectedContentTypeError` | `UNEXPECTED_CONTENT_TYPE` | server | 2xx (non-JSON body + a non-JSON-ish `Content-Type`) | the edge serves a non-JSON page (maintenance) — the vendor never processed the request; `details = { httpStatus, contentType, bodySnippet (256 B) }` — retryable (advisory). A non-JSON `2xx` maintenance page is waited out up to `maintenanceMaxWaitMs` (default 1 h) for reads AND writes; at `4xx`/`5xx` the page is waited out only for calls safe to re-send (GET / explicitly idempotent-flagged) — an unflagged mutation makes one send and carries `details.maintenanceDetected` (issue #114); a wait that ran is reported on the surfaced error as `details.maintenance` (section 2.1) |
|
|
263
263
|
| `EmptyResponseError` | `EMPTY_RESPONSE` | server | 2xx (empty body, `expectBody`) | a blank 2xx on a call that expects a result (non-DELETE by default — section 2.1); `details = { httpStatus, method }` — retryable for non-DELETE (advisory: the loop never retries 2xx) |
|
|
264
264
|
| `CircuitBreakerError` | `CIRCUIT_BREAKER_OPEN` | policy | — (call not sent) | the host circuit breaker (issue #26, opt-in) is open — refused LOCALLY before the request (zero upstream calls, no hourly stamp) — `notSent: true`, `retryAfter` = remaining open seconds — **retryable: true** (the deliberate divergence from #6's not-retryable cooldown refusal: the open period is bounded and advertised; section 3.2.2) |
|
|
265
265
|
| `ResolutionError` | `RESOLUTION_AMBIGUOUS` / `RESOLUTION_TRUNCATED` / `NOT_FOUND` | resolution | — | `resolve`: multiple exact matches (candidates in `resourceIds`, labeled in `details.idRefs`); a cap-tripped bounded client scan (`resolution.scanOnMiss` only; never a silent `null`); a numeric-id miss |
|
|
@@ -342,21 +342,41 @@ transport `RequestOptions` flag) or raises `maxRetries`. An
|
|
|
342
342
|
contract break is not transient.
|
|
343
343
|
|
|
344
344
|
**Maintenance-window auto-retry (weekly window).** Autotask runs a weekly
|
|
345
|
-
maintenance window
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
`
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
a
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
345
|
+
maintenance window during which the edge answers an HTML page before the
|
|
346
|
+
application ever sees the request. The transport recognises that page from the
|
|
347
|
+
**first 256 bytes** of its body at any status (`2xx`, `4xx`, `5xx`) — issue
|
|
348
|
+
#114: the edge picks the status line, and a 503/404 HTML page used to burn the
|
|
349
|
+
retry budget or surface as `NOT_FOUND`. The gate is the body: a **JSON** body
|
|
350
|
+
never enters (a JSON 503 is the application answering; it keeps the normal
|
|
351
|
+
rules, and a write still makes one send). When the page reads as a maintenance
|
|
352
|
+
notice:
|
|
353
|
+
|
|
354
|
+
- a non-JSON **`2xx`** page is waited out and retried for **reads and writes**
|
|
355
|
+
(a 2xx HTML page is edge-served before execution — the 0.16.0 premise);
|
|
356
|
+
- a non-JSON **`4xx`/`5xx`** page is waited out and retried only for calls that
|
|
357
|
+
are **safe to re-send** (`retriable5xx`: a `GET` or an explicitly
|
|
358
|
+
`idempotent`-flagged call). An UNFLAGGED mutation answered with such a page
|
|
359
|
+
makes **exactly one send** and surfaces the status's own error with
|
|
360
|
+
`details.maintenanceDetected = true` and a verify-before-reissuing action — a
|
|
361
|
+
proxy can serve that page AFTER forwarding the write, so the outcome is
|
|
362
|
+
unknown (the §2.2 write rule is intact).
|
|
363
|
+
|
|
364
|
+
The wait polls `maintenancePollMs` (default 30 s) for up to
|
|
365
|
+
`maintenanceMaxWaitMs` (default 1 hour) from the first such response. The
|
|
366
|
+
observed ceiling is 4 h (`MAINTENANCE_WINDOW_MAX_MS`; 19 live windows:
|
|
367
|
+
30–240 min, median ≈ 2 h) — an unattended worker should pass
|
|
368
|
+
`maintenanceMaxWaitMs: MAINTENANCE_WINDOW_MAX_MS`. The wait does not consume
|
|
369
|
+
`maxRetries`, each post-wait attempt gets a fresh `timeoutMs`, and a caller
|
|
370
|
+
`AbortSignal` interrupts the wait. A non-JSON HTML-ish page that does not look
|
|
371
|
+
like a maintenance notice (for example a wrong `baseUrl` serving a web site, or
|
|
372
|
+
a stock proxy page) is surfaced at once with
|
|
373
|
+
`details.maintenancePatternMiss = true` and the 256-byte snippet logged at
|
|
374
|
+
`debug` (issue #114 — the next occurrence supplies the wording). When a wait
|
|
375
|
+
ran, the surfaced error always carries
|
|
376
|
+
`details.maintenance = { waits, waitedMs, maxWaitMs, exhausted }` (`exhausted`
|
|
377
|
+
is true when the budget was spent; a wording change on a later page surfaces
|
|
378
|
+
that page's own error with `exhausted: false`). Disable with
|
|
379
|
+
`maintenanceMaxWaitMs: 0` (config or per call).
|
|
360
380
|
Zone discovery rides the same transport, so a startup failure during
|
|
361
381
|
maintenance is recognisable by the code. `details.bodySnippet` is the first
|
|
362
382
|
256 BYTES of the raw body — TRUNCATION ONLY (the name-based redaction is a
|
|
@@ -491,7 +511,7 @@ Host form: `https://<name>.autotask.net/atservicesrest` (lowercase
|
|
|
491
511
|
| `pageSize` | 10 | 500 (vendor max) | page size for query walks; `maxRecords` on the wire |
|
|
492
512
|
| `timeoutMs` | 30 000 | — | whole-call budget, incl. retries |
|
|
493
513
|
| `maxRetries` | 3 | — | the client's retry budget, overridable per call (`InvokeOptions.maxRetries` / `MutationOpts.maxRetries`; `0` = exactly one send per request the call performs). The budget caps three legs (issues #105/#109): the 429 leg (retried on GET/PUT/DELETE + flagged calls — unchanged) and the 5xx/network + non-JSON 2xx legs (GET + flagged only) |
|
|
494
|
-
| `maintenanceMaxWaitMs` | 3 600 000 (1 h) | — | **maintenance-window auto-retry** (weekly Autotask window): when the
|
|
514
|
+
| `maintenanceMaxWaitMs` | 3 600 000 (1 h) | — | **maintenance-window auto-retry** (weekly Autotask window): when the first 256 bytes of a non-JSON body read as a maintenance notice, the transport waits `maintenancePollMs` and retries for up to this long from the FIRST such page. A `2xx` page is waited out for reads AND writes (edge-served before execution); a `4xx`/`5xx` page only for calls safe to re-send (GET / explicitly idempotent-flagged) — an unflagged mutation makes ONE send and carries `details.maintenanceDetected` (a proxy can serve the page after forwarding). JSON bodies never enter. Does not consume `maxRetries`; each post-wait attempt gets a fresh `timeoutMs`. `0` disables the wait. A non-matching HTML-ish page stamps `details.maintenancePatternMiss`. Observed ceiling 4 h: `MAINTENANCE_WINDOW_MAX_MS` (19 windows: 30–240 min, median ≈ 2 h) |
|
|
495
515
|
| `maintenancePollMs` | 30 000 | — | poll interval while waiting out a maintenance page |
|
|
496
516
|
| `maxConcurrent` | 10 | 10 (vendor) | concurrency guard |
|
|
497
517
|
| `profile` | `library` | — | `library` = today's defaults (changes nothing); `agent` = the conservative `AGENT_PROFILE` limits for LLM-driven workloads (below). Resolution: explicit config > profile > library default |
|
|
@@ -752,6 +772,30 @@ carry paging parameters.
|
|
|
752
772
|
`limit >= latest` (above that the returned count is governed by
|
|
753
773
|
`latest`; `limit` is the blind-branch walk window). `latest` is
|
|
754
774
|
query-verb-ONLY (`queryAll` / `count` bags are unchanged).
|
|
775
|
+
**The count-probe cache (issue #127).** The walk's count probes (the
|
|
776
|
+
initial `count(F)` + every `id gt T` threshold probe) are cached per
|
|
777
|
+
client in a short-TTL in-process store: 30 s TTL, 4096-entry memory
|
|
778
|
+
guard, keyed by (resource plan key, canonical filter serialisation).
|
|
779
|
+
A re-asked "top N" within 30 s makes ZERO extra vendor count calls
|
|
780
|
+
(full-set AND threshold mode). A real write through the SAME client
|
|
781
|
+
clears the cache — and a probe still in flight across the write is
|
|
782
|
+
DISCARDED by the cache's invalidation epoch (issue #139: its pre-write
|
|
783
|
+
answer must not re-anchor a stale top-N within the TTL — the next
|
|
784
|
+
`latest` re-probes). Never cached: the blind-surface / pinned-empty
|
|
785
|
+
sentinels (the plan-pinned stamps stay fresh and re-derive on every
|
|
786
|
+
call), errors, and anything outside the probe path — the plain `count`
|
|
787
|
+
op and `operations.aggregate` call the vendor count directly and
|
|
788
|
+
promise fresh totals. A write made ELSEWHERE (another client, the
|
|
789
|
+
Autotask UI) shows up once the 30 s TTL expires — that is the
|
|
790
|
+
documented snapshot trade. The truncation guard covers the
|
|
791
|
+
growth-of-total direction; a stale-LOW total within the TTL is NOT
|
|
792
|
+
left to it: when a full-set walk truncates against a cached total,
|
|
793
|
+
the SDK re-probes the total ONCE fresh (invalidating the entry) and
|
|
794
|
+
continues the walk to a larger fresh total — the
|
|
795
|
+
`LATEST_WINDOW_INCOMPLETE` refusal stands only when the fresh probe
|
|
796
|
+
confirms the cached value (bounded: at most one extra count probe per
|
|
797
|
+
walk). A total of `0` is NEVER cached, so a repeated call after an
|
|
798
|
+
out-of-band insert re-probes instead of returning a false empty.
|
|
755
799
|
- **`noteChars: N` (issue #61) — the opt-in per-field free-text cap.**
|
|
756
800
|
`N` (integer >= 1, pre-request validated, zero wire) caps EVERY
|
|
757
801
|
string value in the resource's untrusted-class free-text fields
|
|
@@ -938,12 +982,24 @@ the mode pinned (§7.4). The governor rules (pinned):
|
|
|
938
982
|
the SDK; the caller passes `limit` as the TOTAL cap (default 500, max
|
|
939
983
|
2500) and never owns a stream. `queryAll` keeps its raw behavior (the
|
|
940
984
|
unbounded walk; the catalog text steers the caller away for normal work);
|
|
941
|
-
- a query's `includeFields` NAMES
|
|
942
|
-
#119b): the
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
985
|
+
- a query's `includeFields` NAMES and its `filter` condition FIELD NAMES are
|
|
986
|
+
validated **pre-request** (issues #119b + #125): the authority is the
|
|
987
|
+
entity's LIVE field metadata (the vendored spec when that read faults —
|
|
988
|
+
the resource's plan-pinned live-only fields, `metadata.liveOnlyFields`,
|
|
989
|
+
are exempt from the vendored refusal: the live entity carries them and
|
|
990
|
+
the vendor model omits them (e.g. `time_entries.postedDate` /
|
|
991
|
+
`postedOnTime`); a truly unknown name is still refused), every unknown
|
|
992
|
+
name is named in one typed `CONFIG_ERROR` before the query POST (zero
|
|
993
|
+
query wire — the cold fieldCache may cost the one authority metadata GET
|
|
994
|
+
first), and `fieldCheck: false` in the query bag disables BOTH name
|
|
995
|
+
checks (the `string[]` / non-empty shape check and the filter grammar
|
|
996
|
+
always run). The `count` verb's bag (closed to `filter` + the
|
|
997
|
+
`fieldCheck` opt-out — issue #145) is name-checked the same way
|
|
998
|
+
(issue #140 — the documented unchecked scope is the direct
|
|
999
|
+
`queryBounded` / `queryAll` client paths AND the `operations.aggregate`
|
|
1000
|
+
filter, which is grammar-checked only — its groupBy/sum field names ARE
|
|
1001
|
+
checked; name-checking the aggregate filter is a separate follow-up, the
|
|
1002
|
+
op's own field whitelist being the different authority);
|
|
947
1003
|
- `dryRun` must be a real boolean: anything else (`'true'`, `0`, `null`) is
|
|
948
1004
|
refused before any request — only an explicit `false` applies a write;
|
|
949
1005
|
- `<resource>.createIfNotExists` takes its match fields as an optional
|
|
@@ -1444,6 +1500,16 @@ drift). Records exist only for plan rows that are implemented/tested; the
|
|
|
1444
1500
|
plan (`capabilities.plan.json`) is the single source of truth and the
|
|
1445
1501
|
registry is its compiled form.
|
|
1446
1502
|
|
|
1503
|
+
**Consumer-defaulted arguments (issue #115).** A manifest CONSUMER may fill an
|
|
1504
|
+
argument this registry marks required when the caller omits it — the manifest
|
|
1505
|
+
describes the SDK operation, not the consumer's tool contract. The one
|
|
1506
|
+
instance today is `contracts.profitability`: the SDK requires
|
|
1507
|
+
`dateFrom`/`dateTo`, and iit-mcp-autotask defaults both to the last full
|
|
1508
|
+
calendar month (UTC) when the caller omits them. The served
|
|
1509
|
+
`metadata.args`/`metadata.usage` and the record's
|
|
1510
|
+
`inputSchema.opts.description` state the caveat; a consumer that trusts the
|
|
1511
|
+
raw manifest should apply the same contract.
|
|
1512
|
+
|
|
1447
1513
|
### 9.1 Generated artifact #5: `src/relationships.ts` (the relationship map + zone facts)
|
|
1448
1514
|
|
|
1449
1515
|
The regen chain's 5th generated module (plan S12 §2.4: the 4 generated
|
|
@@ -1962,7 +2028,9 @@ into one predicted-failure-free attempt.
|
|
|
1962
2028
|
- **`time_entries.postedWithin(from, to, opts?)`** — the working posted-state
|
|
1963
2029
|
window read: an inclusive UTC `dateWorked` window (`from`/`to` are
|
|
1964
2030
|
`YYYY-MM-DD` date strings — the S1c guard forbids Date positionals), ONE
|
|
1965
|
-
bounded query (`limit` 1-500, default
|
|
2031
|
+
bounded query (`limit` 1-500, default 500 — the hard cap, issue #128: a
|
|
2032
|
+
window with more than 500 matching rows needs `time_entries.query` with the
|
|
2033
|
+
same `dateWorked` filter, its 2500-row walk cap) returning
|
|
1966
2034
|
`{ items, postedCount, unpostedCount, window }`; `opts.posted` narrows
|
|
1967
2035
|
(true = posted rows only, false = unposted rows only, omitted = both
|
|
1968
2036
|
counted in one pass) and `ticketID` / `resourceID` narrow further.
|
|
@@ -1978,7 +2046,7 @@ into one predicted-failure-free attempt.
|
|
|
1978
2046
|
the posted/unposted counts then cover the returned rows ONLY (at most
|
|
1979
2047
|
`limit`); re-query with a wider window or a narrowing filter to reconcile
|
|
1980
2048
|
the totals. `time_entries.dateWorkedWindow(from, to, opts?)` is the
|
|
1981
|
-
|
|
2049
|
+
unambiguous-named alias: the same wire, the same result (incl. `truncated`),
|
|
1982
2050
|
its own op identity (plan row / MCP tool
|
|
1983
2051
|
`autotask_timeEntry_date_worked_window` / operation label) — the name
|
|
1984
2052
|
names the `dateWorked` field the window is on; `postedWithin` keeps its
|
|
@@ -2618,8 +2686,11 @@ additive, and mirrors the family:
|
|
|
2618
2686
|
with the identical contract, so the whole window family is uniformly
|
|
2619
2687
|
timezone-anchored (default UTC unchanged).
|
|
2620
2688
|
- **Shape and wire** — each op is exactly ONE bounded query (`MaxRecords`
|
|
2621
|
-
1-500, default
|
|
2622
|
-
|
|
2689
|
+
1-500, default 500 — the hard cap, issue #128: a call without `limit` takes
|
|
2690
|
+
the first 500 rows in id order; a window with more than 500 matching rows
|
|
2691
|
+
needs the bounded query op with the same date-window filter — its 2500-row
|
|
2692
|
+
walk cap; the `itemDate` / `invoiceDateTime` `gte`/`lte` filter rides the
|
|
2693
|
+
wire), and the result is the plain `items[]` array — no counts
|
|
2623
2694
|
(the time-entry family shape).
|
|
2624
2695
|
|
|
2625
2696
|
### 11.23 Free-text caps and includeFields presets (issue #61)
|
|
@@ -2653,19 +2724,34 @@ query-verb-ONLY and zero-wire or vendor-column-selection-only:
|
|
|
2653
2724
|
billing item to its company / ticket / project / time entry / contract
|
|
2654
2725
|
service / contract / billing code / expense item, plus the date and the
|
|
2655
2726
|
amount fields — the "common narrow read" the issue's example targets.
|
|
2656
|
-
- **`fieldCheck: false` — the
|
|
2657
|
-
#
|
|
2658
|
-
name
|
|
2659
|
-
metadata (`getFieldInfo()` through
|
|
2660
|
-
11.14 — warm = zero wire), degrading
|
|
2661
|
-
(`src/field-sets.ts`) when the metadata read faults;
|
|
2662
|
-
authority the check is skipped. The live set wins when
|
|
2663
|
-
live-only field (`time_entries.postedDate`) is accepted and
|
|
2664
|
-
vendored-only field the tenant no longer has is refused.
|
|
2665
|
-
|
|
2727
|
+
- **`fieldCheck: false` — the field-NAME checks' opt-out (issues #119b +
|
|
2728
|
+
#125).** By default the bounded `query` op validates every `includeFields`
|
|
2729
|
+
name AND every `filter` condition field name BEFORE the query POST: the
|
|
2730
|
+
authority is the entity's LIVE field metadata (`getFieldInfo()` through
|
|
2731
|
+
the cache-aware channel of section 11.14 — warm = zero wire), degrading
|
|
2732
|
+
to the vendored spec (`src/field-sets.ts`) when the metadata read faults;
|
|
2733
|
+
with neither an authority the check is skipped. The live set wins when
|
|
2734
|
+
present — a live-only field (`time_entries.postedDate`) is accepted and
|
|
2735
|
+
a vendored-only field the tenant no longer has is refused. Under
|
|
2736
|
+
degradation the resource's plan-pinned live-only fields (the record's
|
|
2737
|
+
`metadata.liveOnlyFields` — the live entity carries them and the vendor
|
|
2738
|
+
model omits them: `time_entries.postedDate` / `postedOnTime`, the W-D3
|
|
2739
|
+
spec gap) are EXEMPT from the vendored refusal, so a caller-composed
|
|
2740
|
+
`postedDate` filter degrades to the pre-#125 behaviour (the query reaches
|
|
2741
|
+
the vendor and works on a zone that honours the field); a name unknown to
|
|
2742
|
+
BOTH the vendored spec and the pinned set is still refused. Every unknown
|
|
2743
|
+
name is named in ONE typed `CONFIG_ERROR` (zero query wire — the cold
|
|
2744
|
+
fieldCache may cost the one authority metadata GET first), with
|
|
2666
2745
|
`fieldCheck: false` as the escape hatch and `operations.fieldCatalog` as
|
|
2667
|
-
the discovery pointer. The shape
|
|
2668
|
-
always
|
|
2746
|
+
the discovery pointer. The shape checks (`string[]` non-empty
|
|
2747
|
+
`includeFields`; the filter grammar) always run. The `count` verb's bag
|
|
2748
|
+
(closed to `filter` + the `fieldCheck` opt-out — issue #145) is
|
|
2749
|
+
name-checked the same way (issue #140 — the documented unchecked scope
|
|
2750
|
+
is the direct `queryBounded` / `queryAll` client paths AND the
|
|
2751
|
+
`operations.aggregate` filter, which is grammar-checked only — its
|
|
2752
|
+
groupBy/sum field names ARE checked; name-checking the aggregate filter
|
|
2753
|
+
is a separate follow-up, the op's own field whitelist being the
|
|
2754
|
+
different authority).
|
|
2669
2755
|
- **The live check (the AC test for the presets).** Each preset's fields
|
|
2670
2756
|
are asserted to EXIST on the live resource against the LIVE field
|
|
2671
2757
|
catalogue (`operations.fieldCatalog` — the entityInformation/fields
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-autotask",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Fully-typed TypeScript SDK for the Autotask (Datto) PSA REST API. Zero runtime dependencies. Built for building MCP servers and integrations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"bin",
|
|
88
88
|
"dist",
|
|
89
89
|
"README.md",
|
|
90
|
+
"CHANGELOG.md",
|
|
90
91
|
"LICENSE",
|
|
91
92
|
"ARCHITECTURE.md",
|
|
92
93
|
"capabilities.json",
|