@aloma.io/integration-sdk 3.8.68 → 3.8.70

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 (44) hide show
  1. package/FINDINGS-PLAT-47.md +253 -0
  2. package/OPENAPI_TO_CONNECTOR.md +4 -1
  3. package/build/internal/fetcher/fetcher.d.mts +8 -0
  4. package/build/internal/fetcher/fetcher.mjs +47 -3
  5. package/build/openapi-to-connector.mjs +5 -2
  6. package/build/transform/index.d.mts +2 -2
  7. package/build/transform/index.mjs +493 -78
  8. package/package.json +3 -3
  9. package/src/internal/fetcher/fetcher.mts +52 -3
  10. package/src/openapi-to-connector.mts +6 -2
  11. package/src/transform/index.mts +530 -96
  12. package/test/fetcher-query-params.test.mjs +294 -0
  13. package/test/fixtures/transform/basic/expected.json +9 -0
  14. package/test/fixtures/transform/basic/src/controller/abstract.mts +5 -0
  15. package/test/fixtures/transform/basic/src/controller/index.mts +30 -0
  16. package/test/fixtures/transform/basic/tsconfig.json +1 -0
  17. package/test/fixtures/transform/interface-param/src/controller/abstract.mts +5 -0
  18. package/test/fixtures/transform/interface-param/src/controller/index.mts +12 -0
  19. package/test/fixtures/transform/interface-param/tsconfig.json +1 -0
  20. package/test/fixtures/transform/overloads/expected.json +6 -0
  21. package/test/fixtures/transform/overloads/src/controller/abstract.mts +5 -0
  22. package/test/fixtures/transform/overloads/src/controller/index.mts +11 -0
  23. package/test/fixtures/transform/overloads/tsconfig.json +1 -0
  24. package/test/fixtures/transform/tdd-stub/expected.json +8 -0
  25. package/test/fixtures/transform/tdd-stub/src/controller/abstract.mts +6 -0
  26. package/test/fixtures/transform/tdd-stub/src/controller/index.mts +204 -0
  27. package/test/fixtures/transform/tdd-stub/tsconfig.json +1 -0
  28. package/test/fixtures/transform/two-exports/src/controller/abstract.mts +5 -0
  29. package/test/fixtures/transform/two-exports/src/controller/index.mts +9 -0
  30. package/test/fixtures/transform/two-exports/tsconfig.json +1 -0
  31. package/test/fixtures/transform/type-error/src/controller/abstract.mts +5 -0
  32. package/test/fixtures/transform/type-error/src/controller/index.mts +7 -0
  33. package/test/fixtures/transform/type-error/tsconfig.json +1 -0
  34. package/test/fixtures/transform/typed/expected.json +8 -0
  35. package/test/fixtures/transform/typed/src/controller/abstract.mts +5 -0
  36. package/test/fixtures/transform/typed/src/controller/index.mts +13 -0
  37. package/test/fixtures/transform/typed/tsconfig.json +1 -0
  38. package/test/fixtures/transform/visibility/expected.json +9 -0
  39. package/test/fixtures/transform/visibility/src/controller/abstract.mts +5 -0
  40. package/test/fixtures/transform/visibility/src/controller/index.mts +19 -0
  41. package/test/fixtures/transform/visibility/tsconfig.json +1 -0
  42. package/test/openapi-to-connector.test.mts +7 -0
  43. package/test/transform.test.mjs +68 -0
  44. package/tools/mutation-run.mjs +23 -5
@@ -0,0 +1,253 @@
1
+ Issue: PLAT-47
2
+
3
+ # PLAT-47: the SDK Fetcher sends `fetchOptions.params` as the query string
4
+
5
+ Run 20261026-104553, on `origin/main` 70a9898 (`@aloma.io/integration-sdk` 3.8.69). The package version is unchanged.
6
+
7
+ ## Update, run 20261026-111752: the fix is committed (e8f0fbb)
8
+
9
+ After `integration-sdk/node_modules` was reinstalled from the lockfile (lockfile unchanged), `yarn build` exits 0 and
10
+ `yarn test` passes in full: 5 mocha files and 8 standalone scripts, 0 failed, with fetcher-query-params at 19/19. The
11
+ stashed work was the same, byte for byte, as `.aeh/runs/20261026-110540/unfinished.patch`. Test, code and docs were
12
+ committed together as e8f0fbb, "PLAT-47: the Fetcher sends fetchOptions.params as the query string". The gate passed
13
+ every check on tree 370dfe838730: secret-files, secrets, static, dependencies, tdd, model and probe. The sections
14
+ below are the history of the earlier refusals. Their "not committed" status no longer holds.
15
+
16
+ ## Status (runs 20261026-104553 and 20261026-110540, superseded): the fix is written and verified in the box but NOT committed (gate blocker)
17
+
18
+ The box's commit gate refused the code commit:
19
+
20
+ tdd fail no test command is configured for this project in the harness (config/tdd.json),
21
+ so a code change cannot be accepted
22
+
23
+ The gate's test command (`AEH_TDD`) is set from the Mac only; the box cannot change it.
24
+
25
+ - **Committed on this branch:** this FINDINGS file only. The gate's test-first check needs no test command for a
26
+ docs-only commit.
27
+ - **Not committed:** `src/internal/fetcher/fetcher.mts`, `test/fetcher-query-params.test.mjs`,
28
+ `tools/mutation-run.mjs` and the `OPENAPI_TO_CONNECTOR.md` edit. They are left in the box's working tree, and saved
29
+ as one patch at `.aeh/runs/20261026-104553/PLAT-47-fix.patch`.
30
+ - **Why the test was not committed on its own:** the gate requires the new test in the same commit as the code
31
+ change, so that it can see red then green.
32
+
33
+ **To land it:**
34
+
35
+ 1. Configure the gate's test command for this project. For example, `cd integration-sdk && yarn build && node
36
+ test/fetcher-query-params.test.mjs`, or the full `yarn test`. The tester also needs `integration-sdk/node_modules`
37
+ linked (`link` in the tdd settings), because the tester runs with no network.
38
+ 2. Then apply the patch (`git apply .aeh/runs/20261026-104553/PLAT-47-fix.patch`), review it and commit it as
39
+ "PLAT-47: Fetcher sends fetchOptions.params as the query string".
40
+
41
+ Everything below describes that patch.
42
+
43
+ ## Run 20261026-110540: second attempt, refused again by the gate (tdd, green run fails)
44
+
45
+ With the gate's test command now configured, this run tried to land the same patch (stash `plat47-fetcher-fix`, checked
46
+ byte-identical to `.aeh/runs/20261026-104553/PLAT-47-fix.patch`, which is also saved as
47
+ `.aeh/runs/20261026-110540/unfinished.patch`). It is still **not committed**. It stays in the box's working tree.
48
+
49
+ What happened, in order:
50
+
51
+ 1. **Test alone, then code (the task's plan).** The test file was committed alone as `401b1cf` (review warn: no
52
+ probe for the red step). The code and docs commit was then refused:
53
+
54
+ tdd fail non-test code changed (integration-sdk/src/internal/fetcher/fetcher.mts,
55
+ integration-sdk/tools/mutation-run.mjs) with no new or changed test: write a test that fails on the
56
+ current code first
57
+
58
+ 2. **Following the gate: test and code in one commit.** `401b1cf` was reverted by an ordinary commit, with no history
59
+ rewrite because it may already be published. Then test, code and docs were staged together and reviewed. That
60
+ commit was refused, and this is the gate's exact reason (review of tree `ea44c6244395`):
61
+
62
+ tdd fail the tests fail after the change (exit 2)
63
+ --- green run:
64
+ warning You don't appear to have an internet connection. Try the --offline flag to use the cache for registry queries.
65
+ error Command failed with exit code 2.
66
+
67
+ Every other check on that tree passed: secret-files, secrets, static, dependencies, the gate's own model review
68
+ ("No findings"), and the probe. The probe ran in the gate's tester and gave 19/19 for
69
+ `test/fetcher-query-params.test.mjs` on the fixed tree.
70
+
71
+ **Likely cause (environment, not the change):** the box cannot confirm this, because it cannot see the gate's test
72
+ command or its tester. In the box, `yarn build` (tsc) exits 2 on missing packages only:
73
+
74
+ src/openapi-to-connector.mts: Cannot find module 'zod'
75
+ src/testing/fake-aloma.mts: Cannot find module 'ws'
76
+ src/transform/index.mts: Cannot find module 'comment-parser'
77
+
78
+ `node_modules` here is incomplete, and `yarn install --frozen-lockfile` fails (registry proxy 403; `yarn.lock`
79
+ unchanged). `yarn test` then fails 8 of 13 files, every one with `ERR_MODULE_NOT_FOUND` (ws, zod, brace-expansion).
80
+ **The same 8 fail on HEAD without the patch**, and the new fetcher test passes 19/19. If the gate's command is
81
+ `yarn build && yarn test` and its linked `node_modules` is the same incomplete set, it fails before and after any
82
+ change, so no code commit can pass the tdd check.
83
+
84
+ **To land it (Mac side):** give the gate's tester a complete `node_modules` for `integration-sdk` (zod 4.6.6, ws,
85
+ comment-parser, brace-expansion, at the locked versions), or set its test command to one that runs offline. Then commit
86
+ test + `fetcher.mts` + `mutation-run.mjs` + `OPENAPI_TO_CONNECTOR.md` in **one** commit: the gate needs the new test
87
+ in the same commit as the code change.
88
+
89
+ ## The change
90
+
91
+ `src/internal/fetcher/fetcher.mts`:
92
+
93
+ - `toQuery(params)` turns `params` into a query string with `URLSearchParams`:
94
+ - `undefined` and `null` values (and array items) are skipped;
95
+ - numbers and booleans are stringified (`String(value)`), strings are sent as they are;
96
+ - arrays become repeated keys (`properties=a&properties=b`);
97
+ - a nested object (or an object inside an array) is refused with
98
+ `params.<key> is an object; only strings, numbers, booleans and arrays of them are sent`;
99
+ - a `params` that is not an object is refused (`params must be an object`);
100
+ - a `URLSearchParams` instance is sent as it is. Without this, `Object.entries` of it is empty and it would be
101
+ silently dropped, the same kind of bug as PLAT-47.
102
+ - `withQuery(url, query)` appends the query: `?` if the url has none, `&` if it already has one (nothing if it ends in
103
+ `?` or `&`), before any `#fragment`. An empty query returns the url unchanged, so no params, `{}` or all-undefined
104
+ params give exactly today's URL, byte for byte.
105
+ - In `fetch()`, the full URL is built after `customize` and after the `//` collapse, into a local `fullURL` only.
106
+ `url` and `options0` are untouched, and `options0` keeps `params`. The caller's `params` object is never mutated.
107
+ Retries go through `onError` → `fetch` → `customize` with the same `url` and `options0`, so a retry rebuilds the
108
+ same URL with the query exactly once.
109
+ - Only the object handed to native `fetch` drops `params` (`const {params, ...init} = options0`). Headers, body,
110
+ method, timeout, `customize`, `onResponse`, `unwrap` and the retry policy are unchanged.
111
+ - A refused `params` throws a private `ParamsError`, which the `catch` rethrows at once. Without that the error would
112
+ be retried (500 ms each) as if it were a network error.
113
+ - **Logging:** the failed-attempt log `console.log(theURL, e)` now logs `theURL.split('?')[0]`, the URL without
114
+ its query string. Query values may hold ids or emails. The params query is never in `theURL` (it lives only in
115
+ `fullURL`), and a query already written into `url` is now stripped from the log too.
116
+ - **Typing:** `params?: {[key: string]: any} | URLSearchParams` is added, with JSDoc, to the typed `fetch` options, so
117
+ hand-written connectors using `getClient()` see it. `build/internal/fetcher/fetcher.d.mts` declares it.
118
+ - **OAuth:** `OAuthFetcher` does not override `fetch()` and builds no URL of its own, so it inherits the behaviour.
119
+ One test proves it, with the Bearer header still set. The OAuth token calls (`make-oauth.mts`, `finish-oauth.mts`)
120
+ are separate requests and are not touched.
121
+
122
+ `OPENAPI_TO_CONNECTOR.md` (Parameter Mapping) now says the Fetcher sends `params` and how values are serialised.
123
+
124
+ `tools/mutation-run.mjs` has Fetcher mutants F01 to F12. They run `test/fetcher-query-params.test.mjs` instead of
125
+ mocha, and the runner checks that file's baseline too. `test/MUTATION.md` was **not** regenerated, because the runner
126
+ cannot run in this box (see below).
127
+
128
+ ## Array form chosen, and why
129
+
130
+ **Repeated keys** (`properties=a&properties=b`).
131
+
132
+ - connector-hubspot-private-v2 and the HubSpot OpenAPI spec are not in this box (no network, and no HubSpot spec under
133
+ `examples/`), so the connector's generated code could not be read here.
134
+ - The generator (`openapi-to-connector.mts`) copies raw values into `fetchOptions.params` and ignores `style` and
135
+ `explode`. A generator that ignores them is implicitly assuming the OpenAPI default for query parameters, which is
136
+ `style: form, explode: true`, i.e. repeated keys.
137
+ - The task notes that HubSpot accepts repeated keys for `properties` on CRM v3 GET endpoints, and that form is the
138
+ stated fallback.
139
+ - **Doubt:** not checked against HubSpot's live documentation from the box. Mutant F03 (comma-joined) shows the tests
140
+ pin the choice, so switching later is a one-line change plus test updates.
141
+
142
+ **Nested objects are refused, not JSON-encoded.** The generator emits no serialisation for objects and has no
143
+ `deepObject` or JSON intent. Sending `[object Object]` silently is the worst outcome, and guessing a JSON or
144
+ `a[b]=c` encoding would silently be wrong for some vendors. A clear error names the key instead.
145
+
146
+ ## Tests (`test/fetcher-query-params.test.mjs`, standalone script)
147
+
148
+ The test uses a stubbed `globalThis.fetch`, plus one real local HTTP server on 127.0.0.1. Nothing leaves the test
149
+ process. "Today" means the unchanged 70a9898 source, built and run with the same test file.
150
+
151
+ | # | Test | Rule | Today | After |
152
+ |---|------|------|-------|-------|
153
+ | 1 | numbers, strings and booleans are stringified into the query string | 1 | FAIL (query missing) | pass |
154
+ | 2 | query parameters already in the url are kept | 1 | FAIL (`bar=2` missing) | pass |
155
+ | 3 | undefined and null values are skipped (`0` and `''` kept) | 1 | FAIL (query missing) | pass |
156
+ | 4 | arrays become repeated keys | 1 | FAIL (query missing) | pass |
157
+ | 5 | undefined and null array items are skipped, other items stringified | 1 | FAIL (query missing) | pass |
158
+ | 6 | nested objects are refused with a clear error, nothing is sent, not retried | 1 | FAIL (no error) | pass |
159
+ | 7 | objects inside arrays are refused too | 1 | FAIL (no error) | pass |
160
+ | 8 | values are encoded by URLSearchParams and a `//` inside a value is not collapsed | 1 | FAIL (query missing) | pass |
161
+ | 9 | a URLSearchParams instance is sent as it is | 1 | FAIL (query missing) | pass |
162
+ | 10 | without baseUrl the query is appended to the url as given | 1 | FAIL (query missing) | pass |
163
+ | 11 | params is not passed on to global fetch; headers, body and method unchanged | 2 | FAIL (`params` reaches fetch) | pass |
164
+ | 12 | the caller's options and params objects are not mutated | 2 | pass (guard) | pass |
165
+ | 13 | customize runs as before and can change url and params | 2 | FAIL (query missing) | pass |
166
+ | 14 | a retried request sends the same url with the query string exactly once | 2 | FAIL (query missing) | pass |
167
+ | 15 | a failed attempt logs the url without its query string (the query is still sent) | 2 | FAIL (query not sent) | pass |
168
+ | 16 | no params, `{}`, all-undefined or `undefined` params give exactly the old url | 3 | pass (guard) | pass |
169
+ | 17 | the OAuth fetcher sends the query string and its Bearer token | 2 | FAIL (query missing) | pass |
170
+ | 18 | a local HTTP server receives the query string through the real global fetch | 1 | FAIL (request line has no query) | pass |
171
+ | 19 | params is part of the typed fetch options (`fetcher.d.mts`) | typing | FAIL (not declared) | pass |
172
+
173
+ Today: 17 fail, 2 pass. After: 19/19 pass.
174
+
175
+ Tests 12 and 16 pass on today's code by design. Rule 3 says today's URL must not change, and today's code never
176
+ mutates the caller, so these two guard against regressions. Mutants F04, F05 and F10 show they bite.
177
+
178
+ A typing check, done by hand outside the repo, compiled a hand-written call with `params: {limit, properties: [...],
179
+ archived}` and with a `URLSearchParams`, and a `// @ts-expect-error` confirmed that `params: 'a=1'` is a type error.
180
+
181
+ ## Mutation results
182
+
183
+ Run by hand with a driver that reads the F rows from `tools/mutation-run.mjs`. For each mutant it applies the change
184
+ to `src/`, rebuilds with tsc, runs the test file and restores the source.
185
+
186
+ | # | Mutant | Result | Killed by (failing tests) |
187
+ |---|--------|--------|---------------------------|
188
+ | F01 | params handling removed (`fullURL = theURL`) | killed | 15 tests (every query test) |
189
+ | F02 | array handling dropped | killed | arrays, array items, retry, OAuth, local server |
190
+ | F03 | arrays comma-joined instead of repeated keys | killed | arrays, array items, objects in arrays, retry, OAuth, local server |
191
+ | F04 | undefined not skipped (`=== null`) | killed | undefined/null skipped, array items, old url |
192
+ | F05 | null not skipped (`=== undefined`) | killed | undefined/null skipped, array items, old url |
193
+ | F06 | nested objects sent instead of refused | killed | nested objects refused, objects in arrays |
194
+ | F07 | refused params retried | killed | nested objects refused (timing) |
195
+ | F08 | always `?` even when the url has a query | killed | existing query kept, log test |
196
+ | F09 | `params` passed on to native fetch | killed | params not passed on, retry, OAuth |
197
+ | F10 | `delete options0.params` (the map's original approach) | killed | retry keeps the query |
198
+ | F11 | log the full URL with its query | killed | log test |
199
+ | F12 | URLSearchParams instance dropped | killed | URLSearchParams test |
200
+
201
+ **12/12 killed.** That covers the three mutants the task asks for: params handling removed (F01), array handling
202
+ dropped (F02), undefined not skipped (F04).
203
+
204
+ ## Build and full suite: what could and could not be run in this box
205
+
206
+ **Doubt, for Patrick to re-run on the Mac:** this box could not install the repo's lockfile.
207
+
208
+ - `yarn install --frozen-lockfile` fails: the egress proxy answers 403 for registry.yarnpkg.com.
209
+ - The offline yarn cache lacks exact locked versions of `zod@4.6.6`, `ws@8.21.2`, `qs@6.15.2`, `body-parser@1.20.9`,
210
+ `brace-expansion@2.1.7`, `serialize-javascript@7.0.5`, `chai@4.5.0`, `comment-parser@1.4.1` and a few
211
+ ts-node/mocha-type dependencies.
212
+ - Installing other versions would change the dependencies, which needs Patrick's agreement, so it was not done.
213
+ - What was done: the exact locked versions that *are* in the local cache (typescript 5.2.2, @types/node 18.19.39,
214
+ prettier, mocha's own package and others) were copied into the git-ignored `node_modules/`. Nothing was downloaded,
215
+ `yarn.lock` and `package.json` are unchanged (lockfile sha256 checked before and after).
216
+
217
+ Results with that partial install:
218
+
219
+ - **`yarn build` (tsc):** the same three errors before and after the change, all `TS2307 Cannot find module` for the
220
+ uninstalled `zod`, `ws` and `comment-parser`. No new error. tsc still emits `build/`.
221
+ - **`node tools/run-tests.mjs`:** 8 of 13 entries fail both before and after the change, every one with
222
+ `Cannot find module/package` (`brace-expansion` for mocha; `ws` or `zod` for the standalone scripts). The new
223
+ `test/fetcher-query-params.test.mjs` is the only added entry, and it passes.
224
+ - **Not run here:** the mocha suite (including `harness.test.mjs` "records the url the Fetcher really sends", which
225
+ pins the path-only URL and should be unaffected by rule 3), the generator and scenario tests, and
226
+ `yarn test:mutation` (M01 to M32 plus the new F rows).
227
+
228
+ Please run `yarn install --frozen-lockfile && yarn build && yarn test && yarn test:mutation` on the Mac, and commit
229
+ the regenerated `test/MUTATION.md`.
230
+
231
+ ## Follow-ups (not changed in this run)
232
+
233
+ - **Generator, dotted query names:** a name such as `a.b` becomes a nested option object in the method signature,
234
+ but the body emits `fetchOptions.params.a.b = options.a.b`. That throws at runtime when `fetchOptions.params.a` is
235
+ undefined (`openapi-to-connector.mts`, query params loop). It should emit `fetchOptions.params['a.b'] = ...`.
236
+ - **Generator, serialisation:** it ignores `style` and `explode` and emits raw values for null, arrays and objects. The
237
+ Fetcher now applies one fixed rule (form, explode true; objects refused). Specs that use `explode: false` (comma
238
+ form), `deepObject` or `pipeDelimited` would need the generator to pre-serialise, or to pass a per-parameter style.
239
+ - **Generator, headers and cookies:** `in: header` and `in: cookie` parameters are dropped by the generator (from the
240
+ map), separately from this issue.
241
+ - **Retry re-runs `customize`:** a non-idempotent `customize` (for example one that appends to `url`) is still applied
242
+ twice on retry. That is unchanged and out of scope; the params query is safe because it never enters `url`.
243
+ - **VendorMock string matchers** compare the full URL including the query. Connector tests that now send a query
244
+ must use a regexp or function matcher, or the exact query string.
245
+
246
+ ## Rollout risks to check before release
247
+
248
+ - A connector whose `customize` already works around PLAT-47 by appending `params` to `options.url` itself (the
249
+ workaround noted in the map) would now send every parameter twice. Patrick checked 29 active connectors and only
250
+ connector-hubspot-private-v2 uses `params`. A grep of its `customize` for `params` or `URLSearchParams` before
251
+ release settles it.
252
+ - A call that passes an object value in `params` used to have it silently dropped; it now fails with a clear error and
253
+ sends nothing. This is intended (rule 1), but it is a visible behaviour change.
@@ -253,7 +253,10 @@ The script generates method names using the following priority:
253
253
  ## Parameter Mapping
254
254
 
255
255
  - **Path parameters**: Mapped to `args.paramName`
256
- - **Query parameters**: Mapped to `args.paramName`
256
+ - **Query parameters**: Mapped to `args.paramName`, copied into `fetchOptions.params`; the SDK Fetcher sends them as
257
+ the query string. `undefined` and `null` values are skipped, numbers and booleans are stringified,
258
+ arrays become repeated keys (`properties=a&properties=b`, the OpenAPI default `style: form, explode: true`), and
259
+ nested objects are refused with an error. Values are encoded by `URLSearchParams`; a query already in the url is kept.
257
260
  - **Request body**: Mapped to `args.body`
258
261
  - **Headers**: Mapped to `args.headerName`
259
262
 
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
2
3
  /**
3
4
  * http request fetcher
4
5
  */
@@ -41,5 +42,12 @@ export default class Fetcher {
41
42
  * request body like "hello world" or {hello: "world"}
42
43
  */
43
44
  body?: any;
45
+ /**
46
+ * query parameters like {limit: 10, properties: ["a", "b"]}, appended to the url;
47
+ * undefined and null are skipped, arrays become repeated keys, objects are refused
48
+ */
49
+ params?: {
50
+ [key: string]: any;
51
+ } | URLSearchParams;
44
52
  }, retries?: number, args?: any): Promise<any>;
45
53
  }
@@ -1,4 +1,42 @@
1
1
  import { unwrap } from '../util/index.mjs';
2
+ class ParamsError extends Error {
3
+ }
4
+ /**
5
+ * build a query string from request params: undefined and null are skipped, arrays become repeated keys,
6
+ * objects are refused; values are encoded by URLSearchParams
7
+ */
8
+ const toQuery = (params) => {
9
+ if (params == null)
10
+ return '';
11
+ if (params instanceof URLSearchParams)
12
+ return params.toString();
13
+ if (typeof params !== 'object' || Array.isArray(params))
14
+ throw new ParamsError('params must be an object');
15
+ const query = new URLSearchParams();
16
+ const add = (key, value) => {
17
+ if (value == null)
18
+ return;
19
+ if (typeof value === 'object') {
20
+ throw new ParamsError(`params.${key} is an object; only strings, numbers, booleans and arrays of them are sent`);
21
+ }
22
+ query.append(key, String(value));
23
+ };
24
+ for (const [key, value] of Object.entries(params)) {
25
+ if (Array.isArray(value))
26
+ value.forEach((item) => add(key, item));
27
+ else
28
+ add(key, value);
29
+ }
30
+ return query.toString();
31
+ };
32
+ const withQuery = (url, query) => {
33
+ if (!query)
34
+ return url;
35
+ const hash = url.indexOf('#');
36
+ const base = hash < 0 ? url : url.slice(0, hash);
37
+ const sep = !base.includes('?') ? '?' : /[?&]$/.test(base) ? '' : '&';
38
+ return base + sep + query + (hash < 0 ? '' : url.slice(hash));
39
+ };
2
40
  /**
3
41
  * http request fetcher
4
42
  */
@@ -55,6 +93,8 @@ export default class Fetcher {
55
93
  url = options0.url;
56
94
  delete options0.url;
57
95
  theURL = !baseUrl ? url : `${baseUrl?.endsWith('/') ? baseUrl : baseUrl + '/'}${url}`.replace(/\/\/+/gi, '/');
96
+ // after the slash collapse, so encoded values are not altered; options0 keeps params for retries
97
+ const fullURL = withQuery(theURL, toQuery(options0.params));
58
98
  if (!options0?.headers || !options0?.headers?.Accept) {
59
99
  options0.headers = {
60
100
  ...options0.headers,
@@ -74,8 +114,9 @@ export default class Fetcher {
74
114
  options0.body = JSON.stringify(options0.body);
75
115
  }
76
116
  const timeout = Math.min(options0?.timeout || 30 * 60 * 1000, 30 * 60 * 1000);
77
- const ret = await fetch(theURL, {
78
- ...options0,
117
+ const { params, ...init } = options0;
118
+ const ret = await fetch(fullURL, {
119
+ ...init,
79
120
  signal: AbortSignal.timeout(timeout),
80
121
  });
81
122
  const status = await ret.status;
@@ -94,6 +135,8 @@ export default class Fetcher {
94
135
  return unwrap(ret, options0);
95
136
  }
96
137
  catch (e) {
138
+ if (e instanceof ParamsError)
139
+ throw e;
97
140
  // too many requests
98
141
  if (e.status === 429) {
99
142
  return local.onError(e, url, options0, retries, args, true);
@@ -103,7 +146,8 @@ export default class Fetcher {
103
146
  throw e;
104
147
  }
105
148
  --retries;
106
- console.log(theURL, e);
149
+ // without the query string: its values may hold ids or emails
150
+ console.log(theURL.split('?')[0], e);
107
151
  if (retries <= 0)
108
152
  throw e;
109
153
  return local.onError(e, url, options0, retries, args);
@@ -97,10 +97,13 @@ export class OpenAPIToConnector {
97
97
  const validationResult = OpenAPISchema.safeParse(parsed);
98
98
  if (!validationResult.success) {
99
99
  // Check if the errors are just about missing 'type' fields in schemas
100
- const criticalErrors = validationResult.error.errors.filter((err) => {
100
+ // zod 4 reports .issues (zod 3's .errors is gone), and a missing field is an invalid_type issue whose message no
101
+ // longer reads 'Required'
102
+ const isMissing = (err) => err.message.includes('Required') || (err.code === 'invalid_type' && /received undefined/.test(err.message));
103
+ const criticalErrors = validationResult.error.issues.filter((err) => {
101
104
  const path = err.path.join('.');
102
105
  // Allow missing 'type' in schema definitions as many OpenAPI specs don't include it
103
- return !path.includes('components.schemas') || !err.message.includes('Required');
106
+ return !path.includes('components.schemas') || !isMissing(err);
104
107
  });
105
108
  if (criticalErrors.length > 0) {
106
109
  const errors = criticalErrors.map((err) => `${err.path.join('.')}: ${err.message}`).join(', ');
@@ -1,5 +1,5 @@
1
- declare const _default: (path: string) => Promise<{
2
- text: any;
1
+ declare const _default: (file: string) => Promise<{
2
+ text: string;
3
3
  methods: string[];
4
4
  }>;
5
5
  export default _default;