@limitry/sdk 1.0.0-rc.5 → 1.0.0-rc.7

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/CHANGELOG.md CHANGED
@@ -2,11 +2,46 @@
2
2
 
3
3
  What changed in the Limitry TypeScript SDK (`@limitry/sdk`). Generated from the source on each release; newest first.
4
4
 
5
+ ## 1.0.0-rc.7 (2026-10-19)
6
+
7
+ ### Added
8
+
9
+ - A refused check's `reason` has a `message`: the refusal in one sentence,
10
+ such as "Refused by AI budget: 3 cents left, resets
11
+ 2026-11-01T00:00:00.000Z. Try again in 3600 s." `LimitExceeded` and
12
+ `limitry check` show it as the API writes it, so every client says the
13
+ same. Branch on `allowed` and `reason.code`, never on the message.
14
+
15
+ ### Fixed
16
+
17
+ - - `held.cost()` refuses a cost the API can't commit (not a whole number
18
+ from 0 to 1,000,000,000): TypeScript throws `RangeError`, Python raises
19
+ `ValueError`, in your code. Before, the commit failed silently after
20
+ the work and its usage was never counted.
21
+ - TypeScript's `held.extend()` throws the API's error, such as for an
22
+ expired hold, as Python's already did. Both send the `ttlSeconds` you
23
+ give as it is, so `0` is refused rather than read as the default.
24
+ - Python checks, commits and releases now retry exactly as TypeScript's
25
+ do: a network error, `408`, `429` or `5xx`, at most twice, all within
26
+ the check's `timeout` (1.5 s) or 5 s for a settle. `standing()` retries
27
+ too, and reports an error answer that isn't JSON.
28
+ - `limitry check` and `limitry sync` refuse `--workspace` with an API key,
29
+ as every other command does, instead of ignoring it.
30
+
31
+ ## 1.0.0-rc.6 (2026-10-19)
32
+
33
+ ### Changed
34
+
35
+ - `LimitExceeded` says which limit refused, by name, and when to try
36
+ again: "Refused by AI replies per day: 0 replies left, resets
37
+ 2026-10-20T00:00:00.000Z. Try again in 60 s." It used to show only the
38
+ limit's id. `error.limit` (`limit` in Python) is still the id.
39
+
5
40
  ## 1.0.0-rc.5 (2026-10-16)
6
41
 
7
42
  ### Changed
8
43
 
9
- - The TypeScript SDK's guide is at `/docs/typescript/` (beside `/docs/python/` and `/docs/cli/`); the old `/docs/sdk/` address redirects there.
44
+ - The TypeScript SDK's guide is at `/docs/typescript/`, beside `/docs/python/` and `/docs/cli/`.
10
45
 
11
46
  ## 1.0.0-rc.4 (2026-10-16)
12
47
 
package/README.md CHANGED
@@ -91,9 +91,12 @@ idempotency key, so a retried request happens once.
91
91
  - [Discord](https://limitry.com/discord)
92
92
  - [Report a security issue](https://limitry.com/security/)
93
93
 
94
- ## About this package
94
+ ## Contributing
95
95
 
96
96
  This repository is generated from Limitry's source on each
97
97
  release, so pull requests are ported there and credited (see
98
98
  [CONTRIBUTING.md](https://github.com/limitry/limitry-typescript/blob/main/CONTRIBUTING.md)).
99
+
100
+ ## License
101
+
99
102
  Licensed under the Apache License 2.0.
package/dist/index.d.ts CHANGED
@@ -79,6 +79,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
79
79
  reason: {
80
80
  code: "limit_exceeded" | "too_many_reservations";
81
81
  limit: string | null;
82
+ message: string;
82
83
  } | null;
83
84
  remaining: components["schemas"]["CheckRemaining"][];
84
85
  reservation?: {
@@ -70,9 +70,18 @@ export type HoldRequest = {
70
70
  ttlSeconds?: number;
71
71
  };
72
72
  export type Held = {
73
- /** The cost actually used — committed when the work returns. Default: the estimate. */
73
+ /**
74
+ * The cost actually used — committed when the work returns. Default: the
75
+ * estimate. A whole number from 0 to 1,000,000,000, as the API commits:
76
+ * anything else throws a RangeError here, in your code, rather than a
77
+ * commit the API refuses after the work (whose usage would go uncounted).
78
+ */
74
79
  cost(units: number): void;
75
- /** Keep the hold for longer work: it expires `ttlSeconds` from now. */
80
+ /**
81
+ * Keep the hold for longer work: it expires `ttlSeconds` from now
82
+ * (default 300, 1 to 3600). Throws the API's error, such as for a hold
83
+ * that already expired.
84
+ */
76
85
  extend(ttlSeconds?: number): Promise<void>;
77
86
  };
78
87
  /** Not allowed: a limit is used up — or Limitry is unreachable and a limit fails closed. */
@@ -1,3 +1,12 @@
1
+ import { apiErrorFrom } from "../errors.js";
2
+ /** The largest cost a commit takes. */
3
+ const MAX_COST = 1_000_000_000;
4
+ function checkedCost(units) {
5
+ if (!Number.isInteger(units) || units < 0 || units > MAX_COST) {
6
+ throw new RangeError(`held.cost(${units}): the cost must be a whole number from 0 to ${MAX_COST}`);
7
+ }
8
+ return units;
9
+ }
1
10
  /** Not allowed: a limit is used up — or Limitry is unreachable and a limit fails closed. */
2
11
  export class LimitExceeded extends Error {
3
12
  reason;
@@ -9,9 +18,8 @@ export class LimitExceeded extends Error {
9
18
  const reason = decision?.reason?.code ?? "unavailable";
10
19
  super(reason === "unavailable"
11
20
  ? "Limitry could not be reached and a limit denies when unreachable"
12
- : reason === "too_many_reservations"
13
- ? "Too many open reservations for this subject"
14
- : `Limit ${decision?.reason?.limit ?? ""} exceeded`.trim());
21
+ : // The API's own sentence (reason.message), one wording everywhere.
22
+ (decision?.reason?.message ?? "Refused"));
15
23
  this.name = "LimitExceeded";
16
24
  this.reason = reason;
17
25
  this.limit = decision?.reason?.limit ?? null;
@@ -185,7 +193,12 @@ export function createLimits(client, options = {}) {
185
193
  };
186
194
  const idempotencyKey = crypto.randomUUID();
187
195
  const decision = await check(body, idempotencyKey);
188
- const unmetered = { cost: () => { }, extend: async () => { } };
196
+ const unmetered = {
197
+ // Checked even unmetered: the mistake shows whether or not
198
+ // Limitry answered.
199
+ cost: (units) => void checkedCost(units),
200
+ extend: async () => { },
201
+ };
189
202
  if (decision === UNREACHABLE || decision === TIMED_OUT) {
190
203
  const unheard = () => decision === TIMED_OUT
191
204
  ? releaseUnheard(body, idempotencyKey)
@@ -209,14 +222,17 @@ export function createLimits(client, options = {}) {
209
222
  let used = estimate;
210
223
  const held = {
211
224
  cost(units) {
212
- used = units;
225
+ used = checkedCost(units);
213
226
  },
214
227
  async extend(ttlSeconds) {
215
- await client.POST("/reservations/{reservationId}/extend", {
228
+ // Passed as given: the API refuses a bad one (0, past 3600).
229
+ const { error, response } = await client.POST("/reservations/{reservationId}/extend", {
216
230
  params: { path: { reservationId: id } },
217
- body: ttlSeconds ? { ttlSeconds } : {},
231
+ body: ttlSeconds === undefined ? {} : { ttlSeconds },
218
232
  signal: AbortSignal.timeout(timeoutMs),
219
233
  });
234
+ if (error !== undefined)
235
+ throw apiErrorFrom(error, response);
220
236
  },
221
237
  };
222
238
  let result;
@@ -8,6 +8,7 @@ export declare function resources(client: RawClient): {
8
8
  reason: {
9
9
  code: "limit_exceeded" | "too_many_reservations";
10
10
  limit: string | null;
11
+ message: string;
11
12
  } | null;
12
13
  remaining: import("./v1.gen.ts").components["schemas"]["CheckRemaining"][];
13
14
  reservation?: {
package/dist/v1.gen.d.ts CHANGED
@@ -778,6 +778,11 @@ export interface components {
778
778
  code: "limit_exceeded" | "too_many_reservations";
779
779
  /** @example lim_3f0c… */
780
780
  limit: string | null;
781
+ /**
782
+ * @description The refusal in one sentence, for people and logs: show it, never parse it (its wording may change). Branch on `allowed` and `code`.
783
+ * @example Refused by AI replies per day: 0 replies left, resets 2026-11-01T00:00:00.000Z. Try again in 3600 s.
784
+ */
785
+ message: string;
781
786
  } | null;
782
787
  /** @description Every limit that applied, and what is left in its window. */
783
788
  remaining: components["schemas"]["CheckRemaining"][];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limitry/sdk",
3
- "version": "1.0.0-rc.5",
3
+ "version": "1.0.0-rc.7",
4
4
  "description": "Limitry API client",
5
5
  "type": "module",
6
6
  "exports": {