@myapihq/sdk 2.19.2 → 2.20.1

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/dist/auth.js CHANGED
@@ -63,7 +63,11 @@ async function createClient(apiKey, orgId, input) {
63
63
  return (0, client_1.request)('POST', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey, input);
64
64
  }
65
65
  async function listClients(apiKey, orgId) {
66
- return (0, client_1.request)('GET', `${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey);
66
+ // Follows the cursor to the end — the endpoint pages now. The {clients: […]}
67
+ // wrapper was kept when it started paging so no caller had to change, which
68
+ // is why the rows need picking out one level down.
69
+ const clients = await (0, client_1.requestAll)(`${config_1.AUTH_BASE}/auth/orgs/${encodeURIComponent(orgId)}/clients`, apiKey, { select: d => (d?.clients ?? []) });
70
+ return { clients };
67
71
  }
68
72
  // Change a client's redirect URIs or name, keeping the SAME client_id and
69
73
  // secret.
package/dist/client.d.ts CHANGED
@@ -41,4 +41,29 @@ export interface Page<T> {
41
41
  next_cursor?: string;
42
42
  has_more?: boolean;
43
43
  }
44
+ /**
45
+ * Follow a keyset-paginated list to the end and return every row.
46
+ *
47
+ * The backend began paging these lists on 2026-08-20 (myapi-hq #17). Callers
48
+ * that were using `request` kept working and quietly started receiving only the
49
+ * first page — 50 rows where they used to get the whole collection, with
50
+ * nothing in the response to say so. A list that silently stops at 50 is worse
51
+ * than a slow one: the caller acts on a partial answer believing it is complete.
52
+ *
53
+ * So the default is unchanged behaviour — everything — and a caller who wants
54
+ * one page asks for it with requestPage.
55
+ *
56
+ * `maxPages` is a stop, not a limit: a server that returns has_more forever
57
+ * (or a cursor that does not advance) would otherwise spin here. Hitting it
58
+ * throws rather than returning a short list, because a truncated answer that
59
+ * looks complete is the failure this function exists to prevent.
60
+ */
61
+ export declare function requestAll<T>(url: string, apiKey?: string, opts?: RequestOptions & {
62
+ pageSize?: number;
63
+ maxPages?: number;
64
+ /** Pulls the array out of a wrapped payload — `{queues: […]}`, `{clients:
65
+ * […]}`. Those wrappers were kept when the endpoints started paging so no
66
+ * existing caller had to change, which means the rows are one level down. */
67
+ select?: (data: unknown) => T[];
68
+ }): Promise<T[]>;
44
69
  export declare function requestPage<T>(method: string, url: string, apiKey?: string, body?: unknown, extraHeaders?: Record<string, string>, opts?: RequestOptions): Promise<Page<T>>;
package/dist/client.js CHANGED
@@ -4,6 +4,7 @@ exports.MyApiError = void 0;
4
4
  exports.setUserAgent = setUserAgent;
5
5
  exports.toError = toError;
6
6
  exports.request = request;
7
+ exports.requestAll = requestAll;
7
8
  exports.requestPage = requestPage;
8
9
  const version_1 = require("./version");
9
10
  class MyApiError extends Error {
@@ -301,6 +302,43 @@ async function request(method, url, apiKey, body, extraHeaders, opts) {
301
302
  // For keyset-paginated endpoints (backend `envelope.WrapPage`): `data` is a
302
303
  // bare array and the cursor/has_more live under `meta`. `request` would drop
303
304
  // the cursor, so list endpoints with pagination must use this.
305
+ /**
306
+ * Follow a keyset-paginated list to the end and return every row.
307
+ *
308
+ * The backend began paging these lists on 2026-08-20 (myapi-hq #17). Callers
309
+ * that were using `request` kept working and quietly started receiving only the
310
+ * first page — 50 rows where they used to get the whole collection, with
311
+ * nothing in the response to say so. A list that silently stops at 50 is worse
312
+ * than a slow one: the caller acts on a partial answer believing it is complete.
313
+ *
314
+ * So the default is unchanged behaviour — everything — and a caller who wants
315
+ * one page asks for it with requestPage.
316
+ *
317
+ * `maxPages` is a stop, not a limit: a server that returns has_more forever
318
+ * (or a cursor that does not advance) would otherwise spin here. Hitting it
319
+ * throws rather than returning a short list, because a truncated answer that
320
+ * looks complete is the failure this function exists to prevent.
321
+ */
322
+ async function requestAll(url, apiKey, opts) {
323
+ const pageSize = opts?.pageSize ?? 200;
324
+ const maxPages = opts?.maxPages ?? 100;
325
+ const out = [];
326
+ let cursor;
327
+ for (let i = 0; i < maxPages; i++) {
328
+ const sep = url.includes('?') ? '&' : '?';
329
+ const qs = `limit=${pageSize}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`;
330
+ const page = await requestPage('GET', `${url}${sep}${qs}`, apiKey, undefined, undefined, opts);
331
+ out.push(...(opts?.select ? opts.select(page.data) : page.data));
332
+ if (!page.has_more || !page.next_cursor)
333
+ return out;
334
+ if (page.next_cursor === cursor) {
335
+ throw new MyApiError('pagination_stalled', 0, `${url} returned the same cursor twice — refusing to loop. Got ${out.length} rows.`);
336
+ }
337
+ cursor = page.next_cursor;
338
+ }
339
+ throw new MyApiError('pagination_runaway', 0, `${url} still reported more rows after ${maxPages} pages (${out.length} fetched). ` +
340
+ `Refusing to return a partial list as if it were complete.`);
341
+ }
304
342
  async function requestPage(method, url, apiKey, body, extraHeaders, opts) {
305
343
  const r = await requestFull(method, url, apiKey, body, extraHeaders, opts);
306
344
  const meta = (r.meta ?? {});
package/dist/container.js CHANGED
@@ -37,7 +37,10 @@ async function createContainer(apiKey, orgId, payload) {
37
37
  return (0, client_1.request)('POST', `${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers`, apiKey, payload);
38
38
  }
39
39
  async function listContainers(apiKey, orgId) {
40
- return (0, client_1.request)('GET', `${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers`, apiKey);
40
+ // Follows the cursor to the end: the backend began paging this list on
41
+ // 2026-08-20, and returning only the first page here would silently turn a
42
+ // complete answer into a partial one.
43
+ return (0, client_1.requestAll)(`${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers`, apiKey);
41
44
  }
42
45
  async function getContainer(apiKey, orgId, containerId) {
43
46
  return (0, client_1.request)('GET', `${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}`, apiKey);
@@ -154,3 +154,16 @@ export interface WidgetUpdate {
154
154
  export declare function updateWidget(apiKey: string, orgId: string, id: string, patch: WidgetUpdate): Promise<Widget>;
155
155
  export declare function listWidgets(apiKey: string, orgId: string): Promise<Widget[]>;
156
156
  export declare function revokeWidget(apiKey: string, orgId: string, id: string): Promise<void>;
157
+ /**
158
+ * Render a feedback item as a Playwright spec.
159
+ *
160
+ * Returns the spec SOURCE, not JSON: the endpoint answers `text/plain`, so this
161
+ * cannot go through `request()`, which parses every body as JSON and would
162
+ * throw `invalid_json_response` on a perfectly good test file.
163
+ *
164
+ * `base` overrides where the test runs. The report's own `page_url` is used by
165
+ * default; an item filed without one answers 422 `BASE_URL_REQUIRED`, because
166
+ * the generator has nowhere to point the browser and guessing would produce a
167
+ * spec that silently tests nothing.
168
+ */
169
+ export declare function getItemTest(apiKey: string, orgId: string, id: string, base?: string): Promise<string>;
package/dist/feedback.js CHANGED
@@ -9,6 +9,7 @@ exports.createWidget = createWidget;
9
9
  exports.updateWidget = updateWidget;
10
10
  exports.listWidgets = listWidgets;
11
11
  exports.revokeWidget = revokeWidget;
12
+ exports.getItemTest = getItemTest;
12
13
  const client_1 = require("./client");
13
14
  const config_1 = require("./config");
14
15
  // End-user feedback collected from a page, and the widget keys that let a
@@ -22,6 +23,8 @@ exports.EXPOSES = [
22
23
  'POST /feedback/orgs/{org_id}/items',
23
24
  'POST /feedback/orgs/{org_id}/items/{id}/resolve',
24
25
  'DELETE /feedback/orgs/{org_id}/items/{id}',
26
+ // Answers text/plain — a Playwright spec, not the JSON envelope.
27
+ 'GET /feedback/orgs/{org_id}/items/{id}/test',
25
28
  ];
26
29
  function orgBase(orgId) {
27
30
  return `${config_1.FEEDBACK_BASE}/feedback/orgs/${encodeURIComponent(orgId)}`;
@@ -84,11 +87,43 @@ async function listWidgets(apiKey, orgId) {
84
87
  // CLI reported "No widgets yet" for orgs that had widgets, four times, while
85
88
  // the HTTP API was answering correctly the whole time. Reported by ImmoPilot
86
89
  // on 2026-08-19; verified against widgetView in the backend.
87
- const res = await (0, client_1.request)('GET', `${orgBase(orgId)}/widgets`, apiKey);
88
- if (Array.isArray(res))
89
- return res; // tolerate a future bare array
90
- return res?.widgets ?? [];
90
+ // Follows the cursor to the end: the endpoint pages now, and one page
91
+ // returned as the whole list is the same silent-partial-answer bug in a new
92
+ // costume.
93
+ return (0, client_1.requestAll)(`${orgBase(orgId)}/widgets`, apiKey, {
94
+ select: d => (Array.isArray(d) ? d : (d?.widgets ?? [])),
95
+ });
91
96
  }
92
97
  async function revokeWidget(apiKey, orgId, id) {
93
98
  return (0, client_1.request)('DELETE', `${orgBase(orgId)}/widgets/${encodeURIComponent(id)}`, apiKey);
94
99
  }
100
+ /**
101
+ * Render a feedback item as a Playwright spec.
102
+ *
103
+ * Returns the spec SOURCE, not JSON: the endpoint answers `text/plain`, so this
104
+ * cannot go through `request()`, which parses every body as JSON and would
105
+ * throw `invalid_json_response` on a perfectly good test file.
106
+ *
107
+ * `base` overrides where the test runs. The report's own `page_url` is used by
108
+ * default; an item filed without one answers 422 `BASE_URL_REQUIRED`, because
109
+ * the generator has nowhere to point the browser and guessing would produce a
110
+ * spec that silently tests nothing.
111
+ */
112
+ async function getItemTest(apiKey, orgId, id, base) {
113
+ const qs = base ? `?base=${encodeURIComponent(base)}` : '';
114
+ const url = `${orgBase(orgId)}/items/${encodeURIComponent(id)}/test${qs}`;
115
+ const response = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
116
+ const raw = await response.text();
117
+ if (!response.ok) {
118
+ // Errors still come back as the JSON envelope, so parse only on failure.
119
+ let err;
120
+ try {
121
+ err = JSON.parse(raw)?.error;
122
+ }
123
+ catch { /* fall through to the code below */ }
124
+ const code = typeof err === 'object' ? (err?.code ?? 'unknown_error') : (err ?? 'unknown_error');
125
+ const detail = typeof err === 'object' ? err?.message : undefined;
126
+ throw new client_1.MyApiError(code, response.status, detail, typeof err === 'object' ? err : undefined);
127
+ }
128
+ return raw;
129
+ }
package/dist/function.js CHANGED
@@ -29,7 +29,10 @@ async function createFunction(apiKey, orgId, payload) {
29
29
  return (0, client_1.request)('POST', `${config_1.FUNCTION_BASE}/function/orgs/${encodeURIComponent(orgId)}/functions`, apiKey, payload);
30
30
  }
31
31
  async function listFunctions(apiKey, orgId) {
32
- return (0, client_1.request)('GET', `${config_1.FUNCTION_BASE}/function/orgs/${encodeURIComponent(orgId)}/functions`, apiKey);
32
+ // Follows the cursor to the end: the backend began paging this list on
33
+ // 2026-08-20, and returning only the first page here would silently turn a
34
+ // complete answer into a partial one.
35
+ return (0, client_1.requestAll)(`${config_1.FUNCTION_BASE}/function/orgs/${encodeURIComponent(orgId)}/functions`, apiKey);
33
36
  }
34
37
  async function getFunction(apiKey, orgId, fnId) {
35
38
  return (0, client_1.request)('GET', `${config_1.FUNCTION_BASE}/function/orgs/${encodeURIComponent(orgId)}/functions/${encodeURIComponent(fnId)}`, apiKey);
package/dist/hq.d.ts CHANGED
@@ -118,6 +118,7 @@ export declare function getBalance(apiKey: string): Promise<{
118
118
  has_payment_method: boolean;
119
119
  }>;
120
120
  export declare function getBillingHistory(apiKey: string): Promise<{
121
+ id: string;
121
122
  type: string;
122
123
  amount_display: string;
123
124
  status: string;
package/dist/hq.js CHANGED
@@ -164,7 +164,9 @@ async function getBalance(apiKey) {
164
164
  return (0, client_1.request)('GET', `${config_1.HQ_BASE}/hq/billing/balance`, apiKey);
165
165
  }
166
166
  async function getBillingHistory(apiKey) {
167
- return (0, client_1.request)('GET', `${config_1.HQ_BASE}/hq/billing/history`, apiKey);
167
+ // Follows the cursor to the end — the backend pages this list now, and one
168
+ // page returned as if it were the whole list is a silent partial answer.
169
+ return (0, client_1.requestAll)(`${config_1.HQ_BASE}/hq/billing/history`, apiKey);
168
170
  }
169
171
  // getBillingUsage rolls up spend by service for a window: the current
170
172
  // calendar month (default) or the trailing 30 days ('30d').
package/dist/queue.js CHANGED
@@ -37,8 +37,12 @@ async function createQueue(apiKey, orgId, opts) {
37
37
  return (0, client_1.request)('POST', queuesBase(orgId), apiKey, body);
38
38
  }
39
39
  async function listQueues(apiKey, orgId) {
40
- const res = await (0, client_1.request)('GET', queuesBase(orgId), apiKey);
41
- return res?.queues ?? [];
40
+ // Follows the cursor to the end: the backend began paging this list on
41
+ // 2026-08-20, and returning only the first page here would silently turn a
42
+ // complete answer into a partial one.
43
+ return (0, client_1.requestAll)(queuesBase(orgId), apiKey, {
44
+ select: d => (d?.queues ?? []),
45
+ });
42
46
  }
43
47
  async function getQueue(apiKey, orgId, name) {
44
48
  return (0, client_1.request)('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
package/dist/storage.js CHANGED
@@ -91,7 +91,10 @@ function signedUrlOf(res) {
91
91
  return res.signed_url ?? res.url;
92
92
  }
93
93
  async function listAssets(apiKey, orgId) {
94
- return (0, client_1.request)('GET', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets`, apiKey);
94
+ // Follows the cursor to the end: the backend began paging this list on
95
+ // 2026-08-20, and returning only the first page here would silently turn a
96
+ // complete answer into a partial one.
97
+ return (0, client_1.requestAll)(`${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets`, apiKey);
95
98
  }
96
99
  async function deleteAsset(apiKey, orgId, assetId) {
97
100
  return (0, client_1.request)('DELETE', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets/${encodeURIComponent(assetId)}`, apiKey);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.19.2";
1
+ export declare const SDK_VERSION = "2.20.1";
package/dist/version.js CHANGED
@@ -8,4 +8,4 @@ exports.SDK_VERSION = void 0;
8
8
  // Why a constant and not a package.json read: the SDK runs inside edge
9
9
  // functions (Cloudflare Workers), so it must not import node:fs. A literal
10
10
  // is the only version source that works in every runtime we ship to.
11
- exports.SDK_VERSION = '2.19.2';
11
+ exports.SDK_VERSION = '2.20.1';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/sdk",
3
3
  "license": "Apache-2.0",
4
- "version": "2.19.2",
4
+ "version": "2.20.1",
5
5
  "description": "TypeScript SDK for the MyAPI ecosystem",
6
6
  "repository": {
7
7
  "type": "git",