@spaceinvoices/js-sdk 10.1.4 → 10.2.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.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Guarded "fetch all pages" helper for SDK list methods.
3
+ *
4
+ * Every generated list method (`sdk.invoices.list`, `sdk.expenses.list`, ...) returns one page
5
+ * at a time. Hand-rolling a `while (has_more) fetch(next_cursor)` loop around it is easy to get
6
+ * subtly wrong - a stale or misspelled cursor parameter means the loop never advances, which is
7
+ * what sent a customer's hand-rolled loop into an 18+ hour, ~565,000-request outage against
8
+ * production on 2026-09-18.
9
+ *
10
+ * `listAll()` walks every page of a list method for you and yields one item at a time:
11
+ *
12
+ * ```ts
13
+ * for await (const item of listAll(sdk.invoices.list, { limit: 100 })) {
14
+ * console.log(item.id);
15
+ * }
16
+ * ```
17
+ *
18
+ * It is a standalone function rather than a method on the list function itself (e.g. NOT
19
+ * `sdk.invoices.list.listAll(...)`) because it is hand-written, not generated - see
20
+ * `packages/js-sdk/scripts/generate-sdk.ts` for why the equivalent generator-based approach was
21
+ * reverted (a generated `utils.ts` change and a `generate-sdk.ts`-only change can't both satisfy
22
+ * the release guard's file classification in one bounded change).
23
+ *
24
+ * `options` (including any scoping like `entity_id`/`account_id`) is re-sent on every page
25
+ * request, not only the first - dropping it after page one would silently cross-scope later
26
+ * pages to the wrong entity/account.
27
+ *
28
+ * Three guards, all of which throw a distinguishable error instead of ever silently returning a
29
+ * partial result labelled as complete:
30
+ * - a page-count safety limit (`MAX_LIST_ALL_PAGES`)
31
+ * - a non-advancing cursor (the API returning the same `next_cursor` it was just given - the
32
+ * exact failure shape behind the 2026-09-18 incident)
33
+ * - a response with no pagination envelope: either a bare array, or an object whose `data` is an
34
+ * array but which has no `pagination` object at all (e.g. `{ data, has_more }` instead of
35
+ * `{ data, pagination: { next_cursor } }`). Both shapes mean the endpoint does not support
36
+ * cursor-based pagination the way `listAll()` expects; treating either as "one page, done"
37
+ * would silently truncate a real, larger result set.
38
+ */
39
+ /** Safety limit: maximum pages fetched before `listAll()` fails closed instead of looping forever. */
40
+ export declare const MAX_LIST_ALL_PAGES = 1000;
41
+ type ListItemOf<TMethod> = TMethod extends (options?: any) => Promise<infer R> ? R extends {
42
+ data: (infer Item)[];
43
+ } ? Item : never : never;
44
+ /**
45
+ * Iterate every page of a list method, yielding one item at a time until the API reports no
46
+ * further pages (`pagination.next_cursor` is null/absent).
47
+ *
48
+ * @param method A wrapped SDK list method, e.g. `sdk.invoices.list` or the module-level
49
+ * `invoices.list` from `@spaceinvoices/js-sdk/sdk`.
50
+ * @param options Forwarded on every page request (with `next_cursor` overridden per page).
51
+ * @param label Optional human-readable name for this method, used only to make thrown errors
52
+ * easier to trace back to a call site; omit it and the error still identifies the failure
53
+ * mode, just not the endpoint name (see the exported errors' messages).
54
+ */
55
+ export declare function listAll<TMethod extends (options?: any) => Promise<any>>(method: TMethod, options?: Parameters<TMethod>[0], label?: string): AsyncGenerator<ListItemOf<TMethod>, void, undefined>;
56
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Guarded "fetch all pages" helper for SDK list methods.
3
+ *
4
+ * Every generated list method (`sdk.invoices.list`, `sdk.expenses.list`, ...) returns one page
5
+ * at a time. Hand-rolling a `while (has_more) fetch(next_cursor)` loop around it is easy to get
6
+ * subtly wrong - a stale or misspelled cursor parameter means the loop never advances, which is
7
+ * what sent a customer's hand-rolled loop into an 18+ hour, ~565,000-request outage against
8
+ * production on 2026-09-18.
9
+ *
10
+ * `listAll()` walks every page of a list method for you and yields one item at a time:
11
+ *
12
+ * ```ts
13
+ * for await (const item of listAll(sdk.invoices.list, { limit: 100 })) {
14
+ * console.log(item.id);
15
+ * }
16
+ * ```
17
+ *
18
+ * It is a standalone function rather than a method on the list function itself (e.g. NOT
19
+ * `sdk.invoices.list.listAll(...)`) because it is hand-written, not generated - see
20
+ * `packages/js-sdk/scripts/generate-sdk.ts` for why the equivalent generator-based approach was
21
+ * reverted (a generated `utils.ts` change and a `generate-sdk.ts`-only change can't both satisfy
22
+ * the release guard's file classification in one bounded change).
23
+ *
24
+ * `options` (including any scoping like `entity_id`/`account_id`) is re-sent on every page
25
+ * request, not only the first - dropping it after page one would silently cross-scope later
26
+ * pages to the wrong entity/account.
27
+ *
28
+ * Three guards, all of which throw a distinguishable error instead of ever silently returning a
29
+ * partial result labelled as complete:
30
+ * - a page-count safety limit (`MAX_LIST_ALL_PAGES`)
31
+ * - a non-advancing cursor (the API returning the same `next_cursor` it was just given - the
32
+ * exact failure shape behind the 2026-09-18 incident)
33
+ * - a response with no pagination envelope: either a bare array, or an object whose `data` is an
34
+ * array but which has no `pagination` object at all (e.g. `{ data, has_more }` instead of
35
+ * `{ data, pagination: { next_cursor } }`). Both shapes mean the endpoint does not support
36
+ * cursor-based pagination the way `listAll()` expects; treating either as "one page, done"
37
+ * would silently truncate a real, larger result set.
38
+ */
39
+ /** Safety limit: maximum pages fetched before `listAll()` fails closed instead of looping forever. */
40
+ export declare const MAX_LIST_ALL_PAGES = 1000;
41
+ type ListItemOf<TMethod> = TMethod extends (options?: any) => Promise<infer R> ? R extends {
42
+ data: (infer Item)[];
43
+ } ? Item : never : never;
44
+ /**
45
+ * Iterate every page of a list method, yielding one item at a time until the API reports no
46
+ * further pages (`pagination.next_cursor` is null/absent).
47
+ *
48
+ * @param method A wrapped SDK list method, e.g. `sdk.invoices.list` or the module-level
49
+ * `invoices.list` from `@spaceinvoices/js-sdk/sdk`.
50
+ * @param options Forwarded on every page request (with `next_cursor` overridden per page).
51
+ * @param label Optional human-readable name for this method, used only to make thrown errors
52
+ * easier to trace back to a call site; omit it and the error still identifies the failure
53
+ * mode, just not the endpoint name (see the exported errors' messages).
54
+ */
55
+ export declare function listAll<TMethod extends (options?: any) => Promise<any>>(method: TMethod, options?: Parameters<TMethod>[0], label?: string): AsyncGenerator<ListItemOf<TMethod>, void, undefined>;
56
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaceinvoices/js-sdk",
3
- "version": "10.1.4",
3
+ "version": "10.2.0",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Space Invoices API",
5
5
  "author": "Space Invoices <support@spaceinvoices.com>",
6
6
  "license": "MIT",