lago-javascript-client 1.46.1 → 1.47.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/README.md +38 -0
- package/esm/logging_rate_limit_observer.d.ts +33 -0
- package/esm/logging_rate_limit_observer.js +43 -0
- package/esm/mod.d.ts +4 -2
- package/esm/mod.js +3 -2
- package/esm/openapi/client.d.ts +26 -2
- package/esm/openapi/client.js +1 -1
- package/esm/openapi/webhooks.d.ts +22385 -0
- package/esm/openapi/webhooks.js +5 -0
- package/esm/rate_limit_fetch.d.ts +15 -0
- package/esm/rate_limit_fetch.js +37 -2
- package/esm/rate_limit_headers.d.ts +26 -0
- package/esm/rate_limit_headers.js +24 -0
- package/esm/webhook_types.d.ts +68 -0
- package/esm/webhook_types.js +23 -0
- package/package.json +1 -1
- package/script/logging_rate_limit_observer.d.ts +33 -0
- package/script/logging_rate_limit_observer.js +47 -0
- package/script/mod.d.ts +4 -2
- package/script/mod.js +6 -1
- package/script/openapi/client.d.ts +26 -2
- package/script/openapi/client.js +1 -1
- package/script/openapi/webhooks.d.ts +22385 -0
- package/script/openapi/webhooks.js +6 -0
- package/script/rate_limit_fetch.d.ts +15 -0
- package/script/rate_limit_fetch.js +36 -1
- package/script/rate_limit_headers.d.ts +26 -0
- package/script/rate_limit_headers.js +27 -1
- package/script/webhook_types.d.ts +68 -0
- package/script/webhook_types.js +24 -0
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { type RateLimitInfo } from "./rate_limit_headers.js";
|
|
2
|
+
/**
|
|
3
|
+
* Callback invoked after every successful response with parsed rate limit
|
|
4
|
+
* headers. Use this to build observability around the rate limit (warn at
|
|
5
|
+
* thresholds, emit metrics, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Errors thrown by the callback are caught and logged so they cannot break
|
|
8
|
+
* the underlying request flow.
|
|
9
|
+
*/
|
|
10
|
+
export type RateLimitInfoCallback = (info: RateLimitInfo) => void;
|
|
1
11
|
/**
|
|
2
12
|
* Configuration for rate limit fetch behavior
|
|
3
13
|
*/
|
|
@@ -8,6 +18,11 @@ export interface RateLimitFetchConfig {
|
|
|
8
18
|
retryOnRateLimit?: boolean;
|
|
9
19
|
/** Maximum delay in milliseconds before a retry (default: 20000) */
|
|
10
20
|
maxRetryDelay?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Optional callback invoked after every successful (non-429) response
|
|
23
|
+
* with parsed rate limit headers. See `RateLimitInfoCallback`.
|
|
24
|
+
*/
|
|
25
|
+
onRateLimitInfo?: RateLimitInfoCallback;
|
|
11
26
|
}
|
|
12
27
|
/**
|
|
13
28
|
* Creates a fetch wrapper that handles rate limiting with automatic retry
|
|
@@ -11,6 +11,7 @@ function createRateLimitFetch(baseFetch, config = {}) {
|
|
|
11
11
|
const maxRetries = config.maxRetries ?? 3;
|
|
12
12
|
const retryOnRateLimit = config.retryOnRateLimit ?? true;
|
|
13
13
|
const maxRetryDelay = config.maxRetryDelay ?? 20_000;
|
|
14
|
+
const onRateLimitInfo = config.onRateLimitInfo;
|
|
14
15
|
return async function rateLimitFetch(input, init) {
|
|
15
16
|
let lastError;
|
|
16
17
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -34,7 +35,12 @@ function createRateLimitFetch(baseFetch, config = {}) {
|
|
|
34
35
|
await sleep(waitMs);
|
|
35
36
|
continue; // Retry
|
|
36
37
|
}
|
|
37
|
-
// Success
|
|
38
|
+
// Success: emit observability info. Non-2xx, non-429 responses are
|
|
39
|
+
// returned as-is without invoking the callback because their headers
|
|
40
|
+
// do not carry useful rate limit context.
|
|
41
|
+
if (onRateLimitInfo && response.ok) {
|
|
42
|
+
emitRateLimitInfo(onRateLimitInfo, response, input, init);
|
|
43
|
+
}
|
|
38
44
|
return response;
|
|
39
45
|
}
|
|
40
46
|
catch (error) {
|
|
@@ -52,6 +58,35 @@ function createRateLimitFetch(baseFetch, config = {}) {
|
|
|
52
58
|
};
|
|
53
59
|
}
|
|
54
60
|
exports.createRateLimitFetch = createRateLimitFetch;
|
|
61
|
+
/**
|
|
62
|
+
* Invoke the configured callback with parsed rate limit info, swallowing any
|
|
63
|
+
* exception so a buggy observer cannot break the request.
|
|
64
|
+
*/
|
|
65
|
+
function emitRateLimitInfo(callback, response, input, init) {
|
|
66
|
+
try {
|
|
67
|
+
// Honor the method on a Request input when init.method is undefined
|
|
68
|
+
// (a valid fetch signature: fetch(new Request(url, { method: 'POST' }))).
|
|
69
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
70
|
+
const url = requestUrl(input);
|
|
71
|
+
const info = (0, rate_limit_headers_js_1.parseRateLimitInfo)(response.headers, method, url);
|
|
72
|
+
if (info === null)
|
|
73
|
+
return;
|
|
74
|
+
callback(info);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
// Never let observability break the request flow.
|
|
78
|
+
// deno-lint-ignore no-console
|
|
79
|
+
console.warn("Lago: onRateLimitInfo callback raised:", err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function requestUrl(input) {
|
|
83
|
+
if (typeof input === "string")
|
|
84
|
+
return input;
|
|
85
|
+
if (input instanceof URL)
|
|
86
|
+
return input.toString();
|
|
87
|
+
// Request
|
|
88
|
+
return input.url;
|
|
89
|
+
}
|
|
55
90
|
/**
|
|
56
91
|
* Calculate wait time before retry
|
|
57
92
|
* Uses the exact reset time from headers if available, otherwise exponential backoff
|
|
@@ -6,7 +6,33 @@ export interface RateLimitHeaders {
|
|
|
6
6
|
remaining: number | null;
|
|
7
7
|
reset: number | null;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Parsed rate limit headers plus the request context they came from.
|
|
11
|
+
*
|
|
12
|
+
* Delivered to the `onRateLimitInfo` callback after every successful request
|
|
13
|
+
* so callers can build observability around the rate limit (warn at thresholds,
|
|
14
|
+
* emit metrics, etc.).
|
|
15
|
+
*/
|
|
16
|
+
export interface RateLimitInfo extends RateLimitHeaders {
|
|
17
|
+
/** HTTP method of the call (GET, POST, ...). */
|
|
18
|
+
method: string;
|
|
19
|
+
/** Request URL. */
|
|
20
|
+
url: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns the fraction of the rate limit currently used in [0.0, 1.0],
|
|
24
|
+
* or `null` when the headers aren't usable (missing limit, zero limit,
|
|
25
|
+
* missing remaining).
|
|
26
|
+
*/
|
|
27
|
+
export declare function rateLimitUsagePct(info: RateLimitHeaders): number | null;
|
|
9
28
|
/**
|
|
10
29
|
* Extract rate limit information from response headers
|
|
11
30
|
*/
|
|
12
31
|
export declare function parseRateLimitHeaders(headers: Headers): RateLimitHeaders;
|
|
32
|
+
/**
|
|
33
|
+
* Returns a `RateLimitInfo` (headers + request context), or `null` when no
|
|
34
|
+
* `x-ratelimit-*` headers are present (e.g. self-hosted Lago instance with
|
|
35
|
+
* limits disabled). Useful for skipping observability emission entirely when
|
|
36
|
+
* there's nothing to report.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseRateLimitInfo(headers: Headers, method: string, url: string): RateLimitInfo | null;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseRateLimitHeaders = void 0;
|
|
3
|
+
exports.parseRateLimitInfo = exports.parseRateLimitHeaders = exports.rateLimitUsagePct = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the fraction of the rate limit currently used in [0.0, 1.0],
|
|
6
|
+
* or `null` when the headers aren't usable (missing limit, zero limit,
|
|
7
|
+
* missing remaining).
|
|
8
|
+
*/
|
|
9
|
+
function rateLimitUsagePct(info) {
|
|
10
|
+
if (info.limit == null || info.remaining == null || info.limit <= 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return 1 - info.remaining / info.limit;
|
|
14
|
+
}
|
|
15
|
+
exports.rateLimitUsagePct = rateLimitUsagePct;
|
|
4
16
|
/**
|
|
5
17
|
* Extract rate limit information from response headers
|
|
6
18
|
*/
|
|
@@ -12,6 +24,20 @@ function parseRateLimitHeaders(headers) {
|
|
|
12
24
|
};
|
|
13
25
|
}
|
|
14
26
|
exports.parseRateLimitHeaders = parseRateLimitHeaders;
|
|
27
|
+
/**
|
|
28
|
+
* Returns a `RateLimitInfo` (headers + request context), or `null` when no
|
|
29
|
+
* `x-ratelimit-*` headers are present (e.g. self-hosted Lago instance with
|
|
30
|
+
* limits disabled). Useful for skipping observability emission entirely when
|
|
31
|
+
* there's nothing to report.
|
|
32
|
+
*/
|
|
33
|
+
function parseRateLimitInfo(headers, method, url) {
|
|
34
|
+
const parsed = parseRateLimitHeaders(headers);
|
|
35
|
+
if (parsed.limit == null && parsed.remaining == null && parsed.reset == null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return { ...parsed, method, url };
|
|
39
|
+
}
|
|
40
|
+
exports.parseRateLimitInfo = parseRateLimitInfo;
|
|
15
41
|
/**
|
|
16
42
|
* Helper to parse a header value as a number
|
|
17
43
|
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { webhooks } from "./openapi/webhooks.js";
|
|
2
|
+
/** Extract the `application/json` body of a webhook's POST request. */
|
|
3
|
+
type _PayloadOf<K extends keyof webhooks> = webhooks[K] extends {
|
|
4
|
+
post: {
|
|
5
|
+
requestBody?: {
|
|
6
|
+
content: {
|
|
7
|
+
"application/json": infer P;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
} ? P : never;
|
|
12
|
+
/** Extract the `webhook_type` literal (e.g. `"alert.triggered"`) from a payload. */
|
|
13
|
+
type _WebhookTypeOf<K extends keyof webhooks> = _PayloadOf<K> extends {
|
|
14
|
+
webhook_type: infer T;
|
|
15
|
+
} ? T extends string ? T : never : never;
|
|
16
|
+
/**
|
|
17
|
+
* Map of webhook event name to its typed payload.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import type { LagoWebhookPayloads } from "lago-javascript-client";
|
|
22
|
+
*
|
|
23
|
+
* app.post("/webhooks", (req, res) => {
|
|
24
|
+
* const event = req.body as LagoWebhookPayloads["alert.triggered"];
|
|
25
|
+
* console.log(event.triggered_alert.external_customer_id);
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type LagoWebhookPayloads = {
|
|
30
|
+
[K in keyof webhooks as _WebhookTypeOf<K>]: _PayloadOf<K>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Discriminated union of every webhook payload. Narrow with the `webhook_type`
|
|
34
|
+
* field in a `switch` to get a fully typed branch.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import type { LagoWebhookPayload } from "lago-javascript-client";
|
|
39
|
+
*
|
|
40
|
+
* function handle(event: LagoWebhookPayload) {
|
|
41
|
+
* switch (event.webhook_type) {
|
|
42
|
+
* case "alert.triggered":
|
|
43
|
+
* // event is narrowed; event.triggered_alert is fully typed
|
|
44
|
+
* break;
|
|
45
|
+
* case "invoice.created":
|
|
46
|
+
* // event.invoice is fully typed
|
|
47
|
+
* break;
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export type LagoWebhookPayload = LagoWebhookPayloads[keyof LagoWebhookPayloads];
|
|
53
|
+
/**
|
|
54
|
+
* Union of every `webhook_type` string emitted by Lago, e.g.
|
|
55
|
+
* `"alert.triggered" | "invoice.created" | ...`.
|
|
56
|
+
*/
|
|
57
|
+
export type LagoWebhookType = keyof LagoWebhookPayloads;
|
|
58
|
+
/**
|
|
59
|
+
* Helper to look up a single payload type by its `webhook_type` string.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* import type { WebhookOf } from "lago-javascript-client";
|
|
64
|
+
* type InvoiceCreated = WebhookOf<"invoice.created">;
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export type WebhookOf<T extends LagoWebhookType> = LagoWebhookPayloads[T];
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// User-facing TypeScript types for Lago webhook payloads.
|
|
3
|
+
//
|
|
4
|
+
// Backed by ./openapi/webhooks.ts, which is auto-generated from the Lago
|
|
5
|
+
// OpenAPI 3.1 spec (`webhooks:` key) via `deno task generate:webhooks`.
|
|
6
|
+
// This file is hand-written and is safe across regenerations.
|
|
7
|
+
//
|
|
8
|
+
// The generator output keys events by their snake_case operationId
|
|
9
|
+
// (e.g. `alert_triggered`). We re-key here by the dotted `webhook_type`
|
|
10
|
+
// literal (e.g. `alert.triggered`) so that the public API matches the
|
|
11
|
+
// string customers actually see on the wire.
|
|
12
|
+
//
|
|
13
|
+
// Note on nested types: the payload types here are produced by
|
|
14
|
+
// `openapi-typescript`, while the SDK's component exports (e.g.
|
|
15
|
+
// `CustomerObject`, `InvoiceObjectExtended`) are produced by
|
|
16
|
+
// `swagger-typescript-api`. The two generators interpret the same schema
|
|
17
|
+
// slightly differently for nullable fields, so a value typed as
|
|
18
|
+
// `LagoWebhookPayloads['customer.created']['customer']` is not always
|
|
19
|
+
// structurally equal to `CustomerObject` from the main SDK exports. Both
|
|
20
|
+
// are valid views of the same wire shape; the openapi-typescript view is
|
|
21
|
+
// the more accurate one for what actually arrives over the network. If
|
|
22
|
+
// you pass webhook objects into helpers typed with SDK components, a
|
|
23
|
+
// narrowing cast may be required.
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|