@limitry/sdk 1.0.0-rc.6 → 1.0.0-rc.8
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 +98 -0
- package/README.md +15 -13
- package/dist/index.d.ts +35 -31
- package/dist/product/index.d.ts +4 -4
- package/dist/product/index.js +7 -3
- package/dist/product/limits.d.ts +59 -40
- package/dist/product/limits.js +70 -61
- package/dist/resources.gen.d.ts +35 -31
- package/dist/resources.gen.js +7 -7
- package/dist/v1.gen.d.ts +196 -167
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,104 @@
|
|
|
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.8 (2026-10-21)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Limits have `counting`: `fast` answers the instant it decides, and
|
|
10
|
+
`guaranteed` stores every count safely before answering, so the limit
|
|
11
|
+
never goes over, even if a server fails. A `total` balance defaults to
|
|
12
|
+
`guaranteed`, every other window to `fast`. Set it when creating or
|
|
13
|
+
updating a limit (`counting`, `--counting`), or in `limitry.json`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- - Creating a limit needs only who it is for, the amount and the window:
|
|
18
|
+
`limits.create({ subject: { kind: "user" }, amount: 100, window: "month" })`
|
|
19
|
+
(`limitry limits create --subject '{"kind":"user"}' --amount 100 --window month`).
|
|
20
|
+
`action` defaults to every action (`*`), `unit` to `units`, and `name`
|
|
21
|
+
to the sentence the limit reads as, such as "100 units per user per
|
|
22
|
+
month". If a limit already has that name, the API answers `409`: give
|
|
23
|
+
this one a `name`. `limitry.json` entries still need a `name`.
|
|
24
|
+
- A limit's input names who it is for as `subject: { kind, id }` (was
|
|
25
|
+
`subjectKind` and `subjectId`), the same shape the limit itself has.
|
|
26
|
+
- What a limit counts is fixed once it is created: its subject, action,
|
|
27
|
+
unit, window, `every`, `start` and `rolling`. Changing one answers
|
|
28
|
+
`400 invalid_request`: create a new limit and archive the old one (its
|
|
29
|
+
history stays). Name, amount, fail mode and enabled still change any
|
|
30
|
+
time. `limitry sync` refuses such a change until you rename the entry.
|
|
31
|
+
- A limit says when a check last reached it (`lastCheckedAt`), so you can
|
|
32
|
+
see a limit no check names.
|
|
33
|
+
- One name for each idea, everywhere: the API, both SDKs, the CLI, the MCP
|
|
34
|
+
tools, `limitry.json` and webhooks. Update your code when you upgrade:
|
|
35
|
+
|
|
36
|
+
- `hold()` is `reserve()`: it takes `cost` (was `estimate`), and the block
|
|
37
|
+
gets a `Reservation` (was `Held`; Python `Reservation` and
|
|
38
|
+
`AsyncReservation`), with the same `cost()` and `extend()`.
|
|
39
|
+
- A refusal throws `LimitReached` (was `LimitExceeded`), with `limitId`
|
|
40
|
+
(Python `limit_id`) and `reason` `limit_reached` or
|
|
41
|
+
`too_many_reservations`. When Limitry can't be reached and a limit fails
|
|
42
|
+
closed, you now get a separate `Unavailable` error. "Can't be reached"
|
|
43
|
+
also covers a `402 plan_limit_reached` and a `429` from Limitry's flood
|
|
44
|
+
guard, so each limit's fail mode decides instead of your code getting an
|
|
45
|
+
error.
|
|
46
|
+
- A check's answer lists its limits in `limits` (was `remaining`): read
|
|
47
|
+
`decision.limits[0].remaining`. Every answer that names a limit says
|
|
48
|
+
`limitId`: `reason.limitId`, `limits[].limitId`, `usage.standing`'s
|
|
49
|
+
`limits[].limitId`, and webhook payloads.
|
|
50
|
+
- The refusal code is `limit_reached` (was `limit_exceeded`), and the
|
|
51
|
+
webhook event is `limit.reached` (was `limit.exceeded`): subscribe your
|
|
52
|
+
endpoints to the new name.
|
|
53
|
+
- Usage says `refused` (was `denied`); `usage.subjects` no longer has
|
|
54
|
+
`over` (it is `refused > 0`).
|
|
55
|
+
- Credits: `limits.credits.create()` and `limits.credits.list()` at
|
|
56
|
+
`/v1/limits/{limitId}/credits` (were `limits.grants.*` at `…/grants`),
|
|
57
|
+
ids start `crd_` (were `grt_`), and the approval an agent asks for is
|
|
58
|
+
`limit.credit` (was `limit.grant`).
|
|
59
|
+
- A period that starts on the subject's billing date is
|
|
60
|
+
`start: "billing_date"` (was `"anchor"`), and `subjects.update()` takes
|
|
61
|
+
`billingDate` (was `anchor`); the subject says `billingDate` too.
|
|
62
|
+
- `usage.standing()` and `usage.query()` take one `subject`, written
|
|
63
|
+
`kind:id` (`user:usr_123`), instead of separate kind and id fields
|
|
64
|
+
(`limitry usage standing --subject user:usr_123`).
|
|
65
|
+
- A limit's id starts `lmt_` (was `lim_`); keys still start `lim_test_`
|
|
66
|
+
and `lim_live_`.
|
|
67
|
+
- `limitry sync --preview` (was `--dry-run`), and the API's `preview`
|
|
68
|
+
(was `dryRun`), in what you send and what comes back.
|
|
69
|
+
|
|
70
|
+
### Removed
|
|
71
|
+
|
|
72
|
+
- Limits no longer have `counting` (Fast or Guaranteed), and the CLI no
|
|
73
|
+
longer has `--counting`: every count is now saved before Limitry answers,
|
|
74
|
+
for every limit. Remove `counting` from your code and from
|
|
75
|
+
`limitry.json`; Limitry ignores it.
|
|
76
|
+
|
|
77
|
+
## 1.0.0-rc.7 (2026-10-19)
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- A refused check's `reason` has a `message`: the refusal in one sentence,
|
|
82
|
+
such as "Refused by AI budget: 3 cents left, resets
|
|
83
|
+
2026-11-01T00:00:00.000Z. Try again in 3600 s." `LimitExceeded` and
|
|
84
|
+
`limitry check` show it as the API writes it, so every client says the
|
|
85
|
+
same. Branch on `allowed` and `reason.code`, never on the message.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- - `held.cost()` refuses a cost the API can't commit (not a whole number
|
|
90
|
+
from 0 to 1,000,000,000): TypeScript throws `RangeError`, Python raises
|
|
91
|
+
`ValueError`, in your code. Before, the commit failed silently after
|
|
92
|
+
the work and its usage was never counted.
|
|
93
|
+
- TypeScript's `held.extend()` throws the API's error, such as for an
|
|
94
|
+
expired hold, as Python's already did. Both send the `ttlSeconds` you
|
|
95
|
+
give as it is, so `0` is refused rather than read as the default.
|
|
96
|
+
- Python checks, commits and releases now retry exactly as TypeScript's
|
|
97
|
+
do: a network error, `408`, `429` or `5xx`, at most twice, all within
|
|
98
|
+
the check's `timeout` (1.5 s) or 5 s for a settle. `standing()` retries
|
|
99
|
+
too, and reports an error answer that isn't JSON.
|
|
100
|
+
- `limitry check` and `limitry sync` refuse `--workspace` with an API key,
|
|
101
|
+
as every other command does, instead of ignoring it.
|
|
102
|
+
|
|
5
103
|
## 1.0.0-rc.6 (2026-10-19)
|
|
6
104
|
|
|
7
105
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @limitry/sdk
|
|
2
2
|
|
|
3
|
-
The Limitry TypeScript SDK puts a limit around costly work in one call: `guard` checks before your code runs, and `
|
|
3
|
+
The Limitry TypeScript SDK puts a limit around costly work in one call: `guard` checks before your code runs, and `reserve` reserves what the work will probably cost, then charges what it actually cost. Every API operation is a typed method too.
|
|
4
4
|
|
|
5
5
|
Limitry is the limits, credits and budgets layer for your software: give each of your users, teams, tenants, API keys or agents an allowance, and before any costly action one call tells your code whether it may happen.
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ key (`lim_test_…`) while you build.
|
|
|
19
19
|
it counts one AI call against a limit such as 100 a day:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { createClient,
|
|
22
|
+
import { createClient, LimitReached } from "@limitry/sdk";
|
|
23
23
|
|
|
24
24
|
const limitry = createClient(); // reads LIMITRY_API_KEY
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ try {
|
|
|
29
29
|
() => callModel(prompt),
|
|
30
30
|
);
|
|
31
31
|
} catch (error) {
|
|
32
|
-
if (error instanceof
|
|
32
|
+
if (error instanceof LimitReached) {
|
|
33
33
|
console.log(`Try again in ${error.retryAfterSeconds} seconds`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -38,26 +38,28 @@ try {
|
|
|
38
38
|
## Charge what it actually cost
|
|
39
39
|
|
|
40
40
|
When you only know the cost afterwards, such as an AI reply priced by
|
|
41
|
-
the token, `
|
|
42
|
-
and charges what it used. If your code throws, the
|
|
41
|
+
the token, `reserve` reserves what it will probably cost (5 cents here),
|
|
42
|
+
runs your code and charges what it used. If your code throws, the
|
|
43
|
+
reservation is released:
|
|
43
44
|
|
|
44
45
|
```ts
|
|
45
46
|
import { createClient } from "@limitry/sdk";
|
|
46
47
|
|
|
47
48
|
const limitry = createClient();
|
|
48
49
|
|
|
49
|
-
const answer = await limitry.
|
|
50
|
-
{ subject: { kind: "user", id: "usr_123" }, action: "ai-call",
|
|
51
|
-
async (
|
|
50
|
+
const answer = await limitry.reserve(
|
|
51
|
+
{ subject: { kind: "user", id: "usr_123" }, action: "ai-call", cost: 5 },
|
|
52
|
+
async (reservation) => {
|
|
52
53
|
const response = await callModel(prompt);
|
|
53
|
-
|
|
54
|
+
reservation.cost(priceCents(response.usage)); // your prices, in cents
|
|
54
55
|
return response;
|
|
55
56
|
},
|
|
56
57
|
);
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
If Limitry can't be reached, each
|
|
60
|
-
the work run, `closed`
|
|
60
|
+
A refusal throws `LimitReached`. If Limitry can't be reached, each
|
|
61
|
+
limit's fail mode decides: `open` lets the work run, `closed` throws
|
|
62
|
+
`Unavailable`.
|
|
61
63
|
|
|
62
64
|
## Everything else
|
|
63
65
|
|
|
@@ -69,9 +71,9 @@ import { createClient } from "@limitry/sdk";
|
|
|
69
71
|
|
|
70
72
|
const limitry = createClient();
|
|
71
73
|
|
|
74
|
+
// "100 units per user per day for ai-call"
|
|
72
75
|
await limitry.limits.create({
|
|
73
|
-
|
|
74
|
-
subjectKind: "user",
|
|
76
|
+
subject: { kind: "user" },
|
|
75
77
|
action: "ai-call",
|
|
76
78
|
amount: 100,
|
|
77
79
|
window: "day",
|
package/dist/index.d.ts
CHANGED
|
@@ -76,11 +76,12 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
76
76
|
create: (input: import("./resources.ts").Input<"/checks", "post">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
77
77
|
allowed: boolean;
|
|
78
78
|
id: string;
|
|
79
|
+
limits: components["schemas"]["CheckLimit"][];
|
|
79
80
|
reason: {
|
|
80
|
-
code: "
|
|
81
|
-
|
|
81
|
+
code: "limit_reached" | "too_many_reservations";
|
|
82
|
+
limitId: string | null;
|
|
83
|
+
message: string;
|
|
82
84
|
} | null;
|
|
83
|
-
remaining: components["schemas"]["CheckRemaining"][];
|
|
84
85
|
reservation?: {
|
|
85
86
|
expiresAt: string;
|
|
86
87
|
id: string;
|
|
@@ -99,15 +100,33 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
99
100
|
every: number;
|
|
100
101
|
failMode: "open" | "closed";
|
|
101
102
|
id: string;
|
|
103
|
+
lastCheckedAt: string | null;
|
|
102
104
|
name: string;
|
|
103
105
|
rolling: boolean;
|
|
104
|
-
start: "calendar" | "
|
|
106
|
+
start: "calendar" | "billing_date" | "first_use";
|
|
105
107
|
subject: components["schemas"]["LimitSubject"];
|
|
106
108
|
unit: string;
|
|
107
109
|
updatedAt: string;
|
|
108
110
|
version: number;
|
|
109
111
|
window: "minute" | "hour" | "day" | "week" | "month" | "year" | "total" | "concurrent";
|
|
110
112
|
}>;
|
|
113
|
+
credits: {
|
|
114
|
+
create: (limitId: string | number, input: import("./resources.ts").Input<"/limits/{limitId}/credits", "post">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
115
|
+
amount: number;
|
|
116
|
+
createdAt: string;
|
|
117
|
+
createdByUserId: string | null;
|
|
118
|
+
id: string;
|
|
119
|
+
limitId: string;
|
|
120
|
+
note: string;
|
|
121
|
+
subject: {
|
|
122
|
+
id: string;
|
|
123
|
+
kind: string;
|
|
124
|
+
};
|
|
125
|
+
}>;
|
|
126
|
+
list: (limitId: string | number, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
127
|
+
items: components["schemas"]["Credit"][];
|
|
128
|
+
}>;
|
|
129
|
+
};
|
|
111
130
|
get: (limitId: string | number, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
112
131
|
action: string;
|
|
113
132
|
amount: number;
|
|
@@ -117,32 +136,16 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
117
136
|
every: number;
|
|
118
137
|
failMode: "open" | "closed";
|
|
119
138
|
id: string;
|
|
139
|
+
lastCheckedAt: string | null;
|
|
120
140
|
name: string;
|
|
121
141
|
rolling: boolean;
|
|
122
|
-
start: "calendar" | "
|
|
142
|
+
start: "calendar" | "billing_date" | "first_use";
|
|
123
143
|
subject: components["schemas"]["LimitSubject"];
|
|
124
144
|
unit: string;
|
|
125
145
|
updatedAt: string;
|
|
126
146
|
version: number;
|
|
127
147
|
window: "minute" | "hour" | "day" | "week" | "month" | "year" | "total" | "concurrent";
|
|
128
148
|
}>;
|
|
129
|
-
grants: {
|
|
130
|
-
create: (limitId: string | number, input: import("./resources.ts").Input<"/limits/{limitId}/grants", "post">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
131
|
-
amount: number;
|
|
132
|
-
createdAt: string;
|
|
133
|
-
createdByUserId: string | null;
|
|
134
|
-
id: string;
|
|
135
|
-
limitId: string;
|
|
136
|
-
note: string;
|
|
137
|
-
subject: {
|
|
138
|
-
id: string;
|
|
139
|
-
kind: string;
|
|
140
|
-
};
|
|
141
|
-
}>;
|
|
142
|
-
list: (limitId: string | number, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
143
|
-
items: components["schemas"]["LimitGrant"][];
|
|
144
|
-
}>;
|
|
145
|
-
};
|
|
146
149
|
list: (input?: import("./resources.ts").Input<"/limits", "get">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
147
150
|
items: components["schemas"]["Limit"][];
|
|
148
151
|
nextCursor: string | null;
|
|
@@ -156,9 +159,10 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
156
159
|
every: number;
|
|
157
160
|
failMode: "open" | "closed";
|
|
158
161
|
id: string;
|
|
162
|
+
lastCheckedAt: string | null;
|
|
159
163
|
name: string;
|
|
160
164
|
rolling: boolean;
|
|
161
|
-
start: "calendar" | "
|
|
165
|
+
start: "calendar" | "billing_date" | "first_use";
|
|
162
166
|
subject: components["schemas"]["LimitSubject"];
|
|
163
167
|
unit: string;
|
|
164
168
|
updatedAt: string;
|
|
@@ -168,7 +172,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
168
172
|
sync: (input: import("./resources.ts").Input<"/limits/sync", "post">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
169
173
|
archived: string[];
|
|
170
174
|
created: string[];
|
|
171
|
-
|
|
175
|
+
preview: boolean;
|
|
172
176
|
unchanged: string[];
|
|
173
177
|
updated: {
|
|
174
178
|
changes: {
|
|
@@ -189,9 +193,10 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
189
193
|
every: number;
|
|
190
194
|
failMode: "open" | "closed";
|
|
191
195
|
id: string;
|
|
196
|
+
lastCheckedAt: string | null;
|
|
192
197
|
name: string;
|
|
193
198
|
rolling: boolean;
|
|
194
|
-
start: "calendar" | "
|
|
199
|
+
start: "calendar" | "billing_date" | "first_use";
|
|
195
200
|
subject: components["schemas"]["LimitSubject"];
|
|
196
201
|
unit: string;
|
|
197
202
|
updatedAt: string;
|
|
@@ -224,7 +229,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
224
229
|
};
|
|
225
230
|
subjects: {
|
|
226
231
|
get: (subjectKind: string | number, subjectId: string | number, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
227
|
-
|
|
232
|
+
billingDate: string | null;
|
|
228
233
|
subject: components["schemas"]["SubjectRef"];
|
|
229
234
|
}>;
|
|
230
235
|
reset: (subjectKind: string | number, subjectId: string | number, input?: import("./resources.ts").Input<"/subjects/{subjectKind}/{subjectId}/reset", "post">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
@@ -232,7 +237,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
232
237
|
subject: components["schemas"]["SubjectRef"];
|
|
233
238
|
}>;
|
|
234
239
|
update: (subjectKind: string | number, subjectId: string | number, input: import("./resources.ts").Input<"/subjects/{subjectKind}/{subjectId}", "patch">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
235
|
-
|
|
240
|
+
billingDate: string | null;
|
|
236
241
|
subject: components["schemas"]["SubjectRef"];
|
|
237
242
|
}>;
|
|
238
243
|
};
|
|
@@ -242,7 +247,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
242
247
|
limitId: string;
|
|
243
248
|
points: {
|
|
244
249
|
allowed: number;
|
|
245
|
-
|
|
250
|
+
refused: number;
|
|
246
251
|
start: string;
|
|
247
252
|
used: number;
|
|
248
253
|
}[];
|
|
@@ -252,7 +257,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
252
257
|
limits: {
|
|
253
258
|
action: string;
|
|
254
259
|
amount: number;
|
|
255
|
-
|
|
260
|
+
limitId: string;
|
|
256
261
|
name: string;
|
|
257
262
|
remaining: number;
|
|
258
263
|
resetsAt: string | null;
|
|
@@ -273,8 +278,7 @@ declare function createBaseClient(options: ClientOptions): import("openapi-fetch
|
|
|
273
278
|
}>;
|
|
274
279
|
subjects: (input: import("./resources.ts").Input<"/usage/subjects", "get">, options?: import("./resources.ts").RequestOptions) => Promise<{
|
|
275
280
|
items: {
|
|
276
|
-
|
|
277
|
-
over: boolean;
|
|
281
|
+
refused: number;
|
|
278
282
|
remaining: number;
|
|
279
283
|
subject: {
|
|
280
284
|
id: string;
|
package/dist/product/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PRODUCT-OWNED: Limitry's SDK helpers (fork-owned seam — docs/conventions.md
|
|
3
3
|
* "Fork-owned seams"): re-exported by the SDK entry and published with
|
|
4
|
-
* it. `guard` / `
|
|
4
|
+
* it. `guard` / `reserve` (docs/product/roadmap.md slice 6) sit on the client
|
|
5
5
|
* (`limitry.guard(…)`, the SDK's `productMethods` seam); `createLimits(client,
|
|
6
6
|
* options)` gives them with your own timeout or fail mode.
|
|
7
7
|
*/
|
|
8
8
|
import type { BaseClient } from "../index.ts";
|
|
9
|
-
export { createLimits,
|
|
10
|
-
/** Limitry's methods on the client: `limitry.guard(…)`, `limitry.
|
|
9
|
+
export { createLimits, LimitReached, Unavailable, type GuardRequest, type Limits, type LimitsOptions, type Reservation, type ReserveRequest, } from "./limits.ts";
|
|
10
|
+
/** Limitry's methods on the client: `limitry.guard(…)`, `limitry.reserve(…)`, `limitry.standing(…)`. */
|
|
11
11
|
export declare const productMethods: (client: BaseClient) => {
|
|
12
12
|
guard: <T>(request: import("./limits.ts").GuardRequest, fn: () => Promise<T>) => Promise<T>;
|
|
13
|
-
|
|
13
|
+
reserve: <T>(request: import("./limits.ts").ReserveRequest, fn: (reservation: import("./limits.ts").Reservation) => Promise<T>) => Promise<T>;
|
|
14
14
|
standing: (request: {
|
|
15
15
|
subject: {
|
|
16
16
|
kind: string;
|
package/dist/product/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createLimits } from "./limits.js";
|
|
2
|
-
export { createLimits,
|
|
3
|
-
/** Limitry's methods on the client: `limitry.guard(…)`, `limitry.
|
|
2
|
+
export { createLimits, LimitReached, Unavailable, } from "./limits.js";
|
|
3
|
+
/** Limitry's methods on the client: `limitry.guard(…)`, `limitry.reserve(…)`, `limitry.standing(…)`. */
|
|
4
4
|
export const productMethods = (client) => {
|
|
5
5
|
const limits = createLimits(client);
|
|
6
|
-
return {
|
|
6
|
+
return {
|
|
7
|
+
guard: limits.guard,
|
|
8
|
+
reserve: limits.reserve,
|
|
9
|
+
standing: limits.standing,
|
|
10
|
+
};
|
|
7
11
|
};
|
package/dist/product/limits.d.ts
CHANGED
|
@@ -13,33 +13,34 @@ import type { BaseClient, Schemas } from "../index.ts";
|
|
|
13
13
|
* );
|
|
14
14
|
*
|
|
15
15
|
* // Cost known afterwards (an AI reply's cents), or "at most N at
|
|
16
|
-
* // once":
|
|
17
|
-
* // released on failure.
|
|
18
|
-
* const answer = await limitry.
|
|
19
|
-
* { subject: { kind: "user", id: user.id }, action: "ai-call",
|
|
20
|
-
* async (
|
|
16
|
+
* // once": reserve what it will probably cost, do the work, commit
|
|
17
|
+
* // what it used — released on failure.
|
|
18
|
+
* const answer = await limitry.reserve(
|
|
19
|
+
* { subject: { kind: "user", id: user.id }, action: "ai-call", cost: 5 },
|
|
20
|
+
* async (reservation) => {
|
|
21
21
|
* const res = await callModel(prompt);
|
|
22
|
-
*
|
|
22
|
+
* reservation.cost(priceCents(res.usage));
|
|
23
23
|
* return res;
|
|
24
24
|
* },
|
|
25
25
|
* );
|
|
26
26
|
*
|
|
27
|
-
* A refusal throws `
|
|
28
|
-
* Limitry cannot be reached
|
|
29
|
-
* `timeoutMs
|
|
30
|
-
* `plan_limit_reached`)
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* `open` — the default, and the answer before any
|
|
34
|
-
* lets the work run unmetered.
|
|
27
|
+
* A refusal throws `LimitReached` (with `limitId` and
|
|
28
|
+
* `retryAfterSeconds`). When Limitry cannot be reached — an error, a 5xx,
|
|
29
|
+
* no answer within `timeoutMs`, the workspace past its plan's allowance
|
|
30
|
+
* (402 `plan_limit_reached`) or the edge's flood guard (429) — each
|
|
31
|
+
* limit's fail mode decides: the last decision's `failMode` for this
|
|
32
|
+
* subject and action is remembered in memory, and `closed` throws
|
|
33
|
+
* `Unavailable` while `open` — the default, and the answer before any
|
|
34
|
+
* decision was seen — lets the work run unmetered.
|
|
35
35
|
*
|
|
36
|
-
* Retries are safe (docs/product idempotency): each guard or
|
|
37
|
-
* one Idempotency-Key, which the client's own retries reuse, so a
|
|
38
|
-
* counts once however often it is sent. A
|
|
39
|
-
* have been
|
|
40
|
-
* check again with the same key — a replay, nothing
|
|
41
|
-
* releases the
|
|
42
|
-
* Commits and releases retry too
|
|
36
|
+
* Retries are safe (docs/product idempotency): each guard or reserve
|
|
37
|
+
* sends one Idempotency-Key, which the client's own retries reuse, so a
|
|
38
|
+
* check counts once however often it is sent. A reservation whose check
|
|
39
|
+
* timed out may have been made all the same: once the work is done,
|
|
40
|
+
* `reserve` sends the check again with the same key — a replay, nothing
|
|
41
|
+
* counted twice — and releases the reservation it finds, rather than
|
|
42
|
+
* leave it taken until it expires. Commits and releases retry too
|
|
43
|
+
* (committing or releasing twice answers the same).
|
|
43
44
|
*/
|
|
44
45
|
type Decision = Schemas["CheckDecision"];
|
|
45
46
|
type Subject = {
|
|
@@ -60,29 +61,47 @@ export type GuardRequest = {
|
|
|
60
61
|
/** Pools it also draws from (a team's, an org's). */
|
|
61
62
|
shared?: Subject[];
|
|
62
63
|
};
|
|
63
|
-
export type
|
|
64
|
+
export type ReserveRequest = {
|
|
64
65
|
subject: Subject;
|
|
65
66
|
action: string;
|
|
66
|
-
/** What it will probably use —
|
|
67
|
-
|
|
67
|
+
/** What it will probably use — reserved until you commit the actual cost. Default 1. */
|
|
68
|
+
cost?: number;
|
|
68
69
|
shared?: Subject[];
|
|
69
|
-
/** How long the
|
|
70
|
+
/** How long the reservation lasts if never committed or released (default 300, at most 3600). */
|
|
70
71
|
ttlSeconds?: number;
|
|
71
72
|
};
|
|
72
|
-
export type
|
|
73
|
-
/**
|
|
73
|
+
export type Reservation = {
|
|
74
|
+
/**
|
|
75
|
+
* The cost actually used — committed when the work returns. Default: the
|
|
76
|
+
* reserved cost. A whole number from 0 to 1,000,000,000, as the API
|
|
77
|
+
* commits: anything else throws a RangeError here, in your code, rather
|
|
78
|
+
* than a commit the API refuses after the work (whose usage would go
|
|
79
|
+
* uncounted).
|
|
80
|
+
*/
|
|
74
81
|
cost(units: number): void;
|
|
75
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Keep the reservation for longer work: it expires `ttlSeconds` from now
|
|
84
|
+
* (default 300, 1 to 3600). Throws the API's error, such as for a
|
|
85
|
+
* reservation that already expired.
|
|
86
|
+
*/
|
|
76
87
|
extend(ttlSeconds?: number): Promise<void>;
|
|
77
88
|
};
|
|
78
|
-
/**
|
|
79
|
-
export declare class
|
|
80
|
-
readonly reason: "
|
|
81
|
-
/** The limit that refused; null
|
|
82
|
-
readonly
|
|
89
|
+
/** Refused: a limit is reached, or the subject holds too many open reservations. */
|
|
90
|
+
export declare class LimitReached extends Error {
|
|
91
|
+
readonly reason: "limit_reached" | "too_many_reservations";
|
|
92
|
+
/** The limit that refused; null for too many open reservations. */
|
|
93
|
+
readonly limitId: string | null;
|
|
83
94
|
readonly retryAfterSeconds: number | null;
|
|
84
|
-
readonly decision: Decision
|
|
85
|
-
constructor(decision: Decision
|
|
95
|
+
readonly decision: Decision;
|
|
96
|
+
constructor(decision: Decision);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Limitry could not be reached (an error, a 5xx, a timeout, the plan's
|
|
100
|
+
* allowance used up, or the flood guard's 429) and a limit on this
|
|
101
|
+
* subject and action fails closed. With `open`, the work runs instead.
|
|
102
|
+
*/
|
|
103
|
+
export declare class Unavailable extends Error {
|
|
104
|
+
constructor();
|
|
86
105
|
}
|
|
87
106
|
export declare function createLimits(client: BaseClient, options?: LimitsOptions): {
|
|
88
107
|
/**
|
|
@@ -99,12 +118,12 @@ export declare function createLimits(client: BaseClient, options?: LimitsOptions
|
|
|
99
118
|
/** Checks `cost`, runs `fn` only if allowed, and returns its result. */
|
|
100
119
|
guard<T>(request: GuardRequest, fn: () => Promise<T>): Promise<T>;
|
|
101
120
|
/**
|
|
102
|
-
*
|
|
103
|
-
* (`
|
|
104
|
-
* `fn` throws, and rethrows. A concurrent limit's slot
|
|
105
|
-
* exactly as long as `fn` runs.
|
|
121
|
+
* Reserves `cost`, runs `fn`, then commits the cost it reported
|
|
122
|
+
* (`reservation.cost(n)`, default the reserved cost) — or releases the
|
|
123
|
+
* reservation if `fn` throws, and rethrows. A concurrent limit's slot
|
|
124
|
+
* is taken for exactly as long as `fn` runs.
|
|
106
125
|
*/
|
|
107
|
-
|
|
126
|
+
reserve<T>(request: ReserveRequest, fn: (reservation: Reservation) => Promise<T>): Promise<T>;
|
|
108
127
|
};
|
|
109
128
|
export type Limits = ReturnType<typeof createLimits>;
|
|
110
129
|
export {};
|