@openlimiter/core 0.1.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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +9 -0
- package/dist/cache.d.ts +76 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +326 -0
- package/dist/cache.js.map +1 -0
- package/dist/forecast.d.ts +3 -0
- package/dist/forecast.d.ts.map +1 -0
- package/dist/forecast.js +25 -0
- package/dist/forecast.js.map +1 -0
- package/dist/format.d.ts +15 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +22 -0
- package/dist/format.js.map +1 -0
- package/dist/freshness.d.ts +3 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +15 -0
- package/dist/freshness.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/merge.d.ts +12 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +40 -0
- package/dist/merge.js.map +1 -0
- package/dist/normalizer.d.ts +5 -0
- package/dist/normalizer.d.ts.map +1 -0
- package/dist/normalizer.js +127 -0
- package/dist/normalizer.js.map +1 -0
- package/dist/policy.d.ts +3 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +83 -0
- package/dist/policy.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/dist/forecast.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function forecast(previous, current) {
|
|
2
|
+
if (previous.provider !== current.provider ||
|
|
3
|
+
previous.meter !== current.meter ||
|
|
4
|
+
previous.unit !== "PERCENT" ||
|
|
5
|
+
current.unit !== "PERCENT" ||
|
|
6
|
+
!Number.isFinite(previous.value) ||
|
|
7
|
+
!Number.isFinite(current.value) ||
|
|
8
|
+
previous.value < 0 ||
|
|
9
|
+
previous.value > 100 ||
|
|
10
|
+
current.value < 0 ||
|
|
11
|
+
current.value > 100)
|
|
12
|
+
return null;
|
|
13
|
+
const elapsedHours = (Date.parse(current.observedAt) - Date.parse(previous.observedAt)) / 3_600_000;
|
|
14
|
+
if (!Number.isFinite(elapsedHours) || elapsedHours <= 0)
|
|
15
|
+
return null;
|
|
16
|
+
const burnRatePerHour = (current.value - previous.value) / elapsedHours;
|
|
17
|
+
if (!Number.isFinite(burnRatePerHour) || burnRatePerHour <= 0)
|
|
18
|
+
return null;
|
|
19
|
+
const remaining = Math.max(0, 100 - current.value);
|
|
20
|
+
return {
|
|
21
|
+
burnRatePerHour,
|
|
22
|
+
hoursToExhaustion: remaining === 0 ? 0 : remaining / burnRatePerHour
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=forecast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forecast.js","sourceRoot":"","sources":["../src/forecast.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,QAAQ,CAAC,QAAkB,EAAE,OAAiB;IAC5D,IACE,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ;QACtC,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK;QAChC,QAAQ,CAAC,IAAI,KAAK,SAAS;QAC3B,OAAO,CAAC,IAAI,KAAK,SAAS;QAC1B,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAChC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;QAC/B,QAAQ,CAAC,KAAK,GAAG,CAAC;QAClB,QAAQ,CAAC,KAAK,GAAG,GAAG;QACpB,OAAO,CAAC,KAAK,GAAG,CAAC;QACjB,OAAO,CAAC,KAAK,GAAG,GAAG;QACnB,OAAO,IAAI,CAAC;IACd,MAAM,YAAY,GAChB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC;IACjF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrE,MAAM,eAAe,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC;IACxE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,eAAe,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACnD,OAAO;QACL,eAAe;QACf,iBAAiB,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,eAAe;KACrE,CAAC;AACJ,CAAC"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display helpers that never round a usage value upward.
|
|
3
|
+
*
|
|
4
|
+
* Over reporting a quota would tell a human that a cap was reached when it was
|
|
5
|
+
* not, so every rendered number is truncated toward zero instead of rounded.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Truncate a number to a fixed number of fraction digits and render it.
|
|
9
|
+
*
|
|
10
|
+
* The scaled value is first re read at twelve significant digits so that binary
|
|
11
|
+
* floating point noise (84.25 times 100 is 8425.000000000001) cannot push a
|
|
12
|
+
* value across a digit boundary in either direction.
|
|
13
|
+
*/
|
|
14
|
+
export declare function floorFixed(value: number, fractionDigits: number): string;
|
|
15
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAKxE"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display helpers that never round a usage value upward.
|
|
3
|
+
*
|
|
4
|
+
* Over reporting a quota would tell a human that a cap was reached when it was
|
|
5
|
+
* not, so every rendered number is truncated toward zero instead of rounded.
|
|
6
|
+
*/
|
|
7
|
+
const PRECISION_DIGITS = 12;
|
|
8
|
+
/**
|
|
9
|
+
* Truncate a number to a fixed number of fraction digits and render it.
|
|
10
|
+
*
|
|
11
|
+
* The scaled value is first re read at twelve significant digits so that binary
|
|
12
|
+
* floating point noise (84.25 times 100 is 8425.000000000001) cannot push a
|
|
13
|
+
* value across a digit boundary in either direction.
|
|
14
|
+
*/
|
|
15
|
+
export function floorFixed(value, fractionDigits) {
|
|
16
|
+
if (!Number.isFinite(value))
|
|
17
|
+
return (0).toFixed(fractionDigits);
|
|
18
|
+
const scale = 10 ** fractionDigits;
|
|
19
|
+
const scaled = Number((value * scale).toPrecision(PRECISION_DIGITS));
|
|
20
|
+
return (Math.floor(scaled) / scale).toFixed(fractionDigits);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,cAAsB;IAC9D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,EAAE,IAAI,cAAc,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAOhD,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,aAAa,CAOf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function parseInstant(value) {
|
|
2
|
+
const parsed = Date.parse(value);
|
|
3
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
4
|
+
}
|
|
5
|
+
export function freshness(observedAt, expiresAt, now) {
|
|
6
|
+
const observed = parseInstant(observedAt);
|
|
7
|
+
const expires = parseInstant(expiresAt);
|
|
8
|
+
const current = parseInstant(now);
|
|
9
|
+
if (observed === null || expires === null || current === null)
|
|
10
|
+
return "unknown";
|
|
11
|
+
if (expires < observed || observed > current)
|
|
12
|
+
return "unknown";
|
|
13
|
+
return current <= expires ? "fresh" : "stale";
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=freshness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.js","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"AAEA,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,UAAkB,EAClB,SAAiB,EACjB,GAAW;IAEX,MAAM,QAAQ,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAChF,IAAI,OAAO,GAAG,QAAQ,IAAI,QAAQ,GAAG,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/D,OAAO,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;AAChD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./cache.js";
|
|
2
|
+
export * from "./forecast.js";
|
|
3
|
+
export * from "./format.js";
|
|
4
|
+
export * from "./freshness.js";
|
|
5
|
+
export * from "./merge.js";
|
|
6
|
+
export * from "./normalizer.js";
|
|
7
|
+
export * from "./policy.js";
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./cache.js";
|
|
2
|
+
export * from "./forecast.js";
|
|
3
|
+
export * from "./format.js";
|
|
4
|
+
export * from "./freshness.js";
|
|
5
|
+
export * from "./merge.js";
|
|
6
|
+
export * from "./normalizer.js";
|
|
7
|
+
export * from "./policy.js";
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/merge.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snapshot } from "./types.js";
|
|
2
|
+
/** Hard ceiling on how many snapshots one cache file may carry. */
|
|
3
|
+
export declare const MAX_CACHE_ENTRIES = 64;
|
|
4
|
+
/**
|
|
5
|
+
* Combine cached snapshots with freshly observed ones.
|
|
6
|
+
*
|
|
7
|
+
* One provider and meter pair holds exactly one snapshot. An incoming snapshot
|
|
8
|
+
* replaces the cached snapshot for that pair, every other cached row survives,
|
|
9
|
+
* and the result stays bounded by dropping the least recently observed rows.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeSnapshots(existing: readonly Snapshot[], incoming: readonly Snapshot[], limit?: number): Snapshot[];
|
|
12
|
+
//# sourceMappingURL=merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAqBpC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,KAAK,SAAoB,GACxB,QAAQ,EAAE,CAUZ"}
|
package/dist/merge.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Hard ceiling on how many snapshots one cache file may carry. */
|
|
2
|
+
export const MAX_CACHE_ENTRIES = 64;
|
|
3
|
+
/*
|
|
4
|
+
* A provider code and a meter name are both closed uppercase identifiers with
|
|
5
|
+
* no spaces, so joining them with one space cannot collide.
|
|
6
|
+
*/
|
|
7
|
+
function identity(snapshot) {
|
|
8
|
+
return snapshot.provider + " " + snapshot.meter;
|
|
9
|
+
}
|
|
10
|
+
function compareIdentity(left, right) {
|
|
11
|
+
const leftKey = identity(left);
|
|
12
|
+
const rightKey = identity(right);
|
|
13
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
14
|
+
}
|
|
15
|
+
function observedMilliseconds(snapshot) {
|
|
16
|
+
const parsed = Date.parse(snapshot.observedAt);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Combine cached snapshots with freshly observed ones.
|
|
21
|
+
*
|
|
22
|
+
* One provider and meter pair holds exactly one snapshot. An incoming snapshot
|
|
23
|
+
* replaces the cached snapshot for that pair, every other cached row survives,
|
|
24
|
+
* and the result stays bounded by dropping the least recently observed rows.
|
|
25
|
+
*/
|
|
26
|
+
export function mergeSnapshots(existing, incoming, limit = MAX_CACHE_ENTRIES) {
|
|
27
|
+
const byIdentity = new Map();
|
|
28
|
+
for (const snapshot of existing)
|
|
29
|
+
byIdentity.set(identity(snapshot), snapshot);
|
|
30
|
+
for (const snapshot of incoming)
|
|
31
|
+
byIdentity.set(identity(snapshot), snapshot);
|
|
32
|
+
const merged = [...byIdentity.values()];
|
|
33
|
+
if (merged.length <= limit)
|
|
34
|
+
return merged.sort(compareIdentity);
|
|
35
|
+
return merged
|
|
36
|
+
.sort((left, right) => observedMilliseconds(right) - observedMilliseconds(left))
|
|
37
|
+
.slice(0, limit)
|
|
38
|
+
.sort(compareIdentity);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;GAGG;AACH,SAAS,QAAQ,CAAC,QAAkB;IAClC,OAAO,QAAQ,CAAC,QAAQ,GAAG,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC;AAClD,CAAC;AAED,SAAS,eAAe,CAAC,IAAc,EAAE,KAAe;IACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAkB;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA6B,EAC7B,QAA6B,EAC7B,KAAK,GAAG,iBAAiB;IAEzB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC/C,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9E,KAAK,MAAM,QAAQ,IAAI,QAAQ;QAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9E,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChE,OAAO,MAAM;SACV,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;SAC/E,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;SACf,IAAI,CAAC,eAAe,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type RawMeter, type Snapshot } from "./types.js";
|
|
2
|
+
export declare function normalizeMeter(raw: RawMeter): Snapshot | null;
|
|
3
|
+
export declare function normalizeMeters(rawMeters: readonly RawMeter[]): Snapshot[];
|
|
4
|
+
export declare function canonicalJson(value: unknown): string;
|
|
5
|
+
//# sourceMappingURL=normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,QAAQ,EAKd,MAAM,YAAY,CAAC;AAuEpB,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAmC7D;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAK1E;AAUD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { PROVIDER_CODES } from "./types.js";
|
|
2
|
+
const units = new Set(["PERCENT", "CREDITS", "TOKENS", "REQUESTS"]);
|
|
3
|
+
const sources = new Set([
|
|
4
|
+
"native_payload",
|
|
5
|
+
"documented_api",
|
|
6
|
+
"internal_payload",
|
|
7
|
+
"authenticated_page",
|
|
8
|
+
"manual_entry"
|
|
9
|
+
]);
|
|
10
|
+
const precisions = new Set(["exact", "estimated", "manual"]);
|
|
11
|
+
const providerCodes = new Set(PROVIDER_CODES);
|
|
12
|
+
const safeMeter = /^[A-Z][A-Z0-9_]{0,31}$/u;
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function isIsoInstant(value) {
|
|
17
|
+
if (typeof value !== "string")
|
|
18
|
+
return false;
|
|
19
|
+
const parsed = Date.parse(value);
|
|
20
|
+
return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
|
|
21
|
+
}
|
|
22
|
+
function normalizeWindow(value) {
|
|
23
|
+
if (!isRecord(value))
|
|
24
|
+
return null;
|
|
25
|
+
const kind = value["kind"];
|
|
26
|
+
if (kind !== "rolling" &&
|
|
27
|
+
kind !== "fixed" &&
|
|
28
|
+
kind !== "lifetime" &&
|
|
29
|
+
kind !== "unknown")
|
|
30
|
+
return null;
|
|
31
|
+
const duration = value["durationSeconds"];
|
|
32
|
+
if (duration === undefined)
|
|
33
|
+
return { kind };
|
|
34
|
+
if (typeof duration !== "number" ||
|
|
35
|
+
!Number.isInteger(duration) ||
|
|
36
|
+
duration <= 0 ||
|
|
37
|
+
duration > 31_536_000)
|
|
38
|
+
return null;
|
|
39
|
+
return { kind, durationSeconds: duration };
|
|
40
|
+
}
|
|
41
|
+
function normalizeLabels(value) {
|
|
42
|
+
if (!isRecord(value) || value["verification"] !== "UNVERIFIED")
|
|
43
|
+
return null;
|
|
44
|
+
const credentialOrigin = value["credentialOrigin"];
|
|
45
|
+
const dataInterfaceStatus = value["dataInterfaceStatus"];
|
|
46
|
+
const automationRisk = value["automationRisk"];
|
|
47
|
+
if (credentialOrigin !== "official-local-tool" &&
|
|
48
|
+
credentialOrigin !== "user-key" &&
|
|
49
|
+
credentialOrigin !== "browser-session" &&
|
|
50
|
+
credentialOrigin !== "user-entered")
|
|
51
|
+
return null;
|
|
52
|
+
if (dataInterfaceStatus !== "native-statusline-payload" &&
|
|
53
|
+
dataInterfaceStatus !== "documented-api" &&
|
|
54
|
+
dataInterfaceStatus !== "internal-endpoint" &&
|
|
55
|
+
dataInterfaceStatus !== "authenticated-scrape" &&
|
|
56
|
+
dataInterfaceStatus !== "manual")
|
|
57
|
+
return null;
|
|
58
|
+
if (automationRisk !== "low" && automationRisk !== "high")
|
|
59
|
+
return null;
|
|
60
|
+
return {
|
|
61
|
+
credentialOrigin,
|
|
62
|
+
dataInterfaceStatus,
|
|
63
|
+
automationRisk,
|
|
64
|
+
verification: "UNVERIFIED"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function normalizeMeter(raw) {
|
|
68
|
+
if (typeof raw.provider !== "string" || !providerCodes.has(raw.provider))
|
|
69
|
+
return null;
|
|
70
|
+
if (typeof raw.meter !== "string" || !safeMeter.test(raw.meter))
|
|
71
|
+
return null;
|
|
72
|
+
if (typeof raw.value !== "number" || !Number.isFinite(raw.value) || raw.value < 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (typeof raw.unit !== "string" || !units.has(raw.unit))
|
|
76
|
+
return null;
|
|
77
|
+
if (raw.unit === "PERCENT" && raw.value > 100)
|
|
78
|
+
return null;
|
|
79
|
+
if (raw.value > 1_000_000_000_000)
|
|
80
|
+
return null;
|
|
81
|
+
const window = normalizeWindow(raw.window);
|
|
82
|
+
const labels = normalizeLabels(raw.labels);
|
|
83
|
+
if (window === null || labels === null)
|
|
84
|
+
return null;
|
|
85
|
+
if (raw.resetAt !== null && !isIsoInstant(raw.resetAt))
|
|
86
|
+
return null;
|
|
87
|
+
if (typeof raw.source !== "string" || !sources.has(raw.source)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (typeof raw.precision !== "string" ||
|
|
91
|
+
!precisions.has(raw.precision))
|
|
92
|
+
return null;
|
|
93
|
+
if (!isIsoInstant(raw.observedAt) || !isIsoInstant(raw.expiresAt))
|
|
94
|
+
return null;
|
|
95
|
+
if (Date.parse(raw.expiresAt) < Date.parse(raw.observedAt))
|
|
96
|
+
return null;
|
|
97
|
+
return {
|
|
98
|
+
provider: raw.provider,
|
|
99
|
+
meter: raw.meter,
|
|
100
|
+
value: raw.value,
|
|
101
|
+
unit: raw.unit,
|
|
102
|
+
window,
|
|
103
|
+
resetAt: raw.resetAt,
|
|
104
|
+
source: raw.source,
|
|
105
|
+
precision: raw.precision,
|
|
106
|
+
observedAt: raw.observedAt,
|
|
107
|
+
expiresAt: raw.expiresAt,
|
|
108
|
+
labels
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function normalizeMeters(rawMeters) {
|
|
112
|
+
return rawMeters.flatMap((raw) => {
|
|
113
|
+
const normalized = normalizeMeter(raw);
|
|
114
|
+
return normalized === null ? [] : [normalized];
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function sortDeep(value) {
|
|
118
|
+
if (Array.isArray(value))
|
|
119
|
+
return value.map(sortDeep);
|
|
120
|
+
if (!isRecord(value))
|
|
121
|
+
return value;
|
|
122
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortDeep(value[key])]));
|
|
123
|
+
}
|
|
124
|
+
export function canonicalJson(value) {
|
|
125
|
+
return JSON.stringify(sortDeep(value));
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=normalizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizer.js","sourceRoot":"","sources":["../src/normalizer.ts"],"names":[],"mappings":"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"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,MAAM,EAGX,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AAsCpB,wBAAgB,WAAW,CACzB,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,GAAG,EAAE,MAAM,EACX,iBAAiB,GAAE,SAAS,YAAY,EAAmB,GAC1D,MAAM,CAqDR"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { freshness } from "./freshness.js";
|
|
2
|
+
import { PROVIDER_CODES } from "./types.js";
|
|
3
|
+
function reasonFor(percent) {
|
|
4
|
+
if (percent >= 100)
|
|
5
|
+
return "AT_CAP";
|
|
6
|
+
if (percent >= 80)
|
|
7
|
+
return "NEAR_CAP";
|
|
8
|
+
return "HEALTHY";
|
|
9
|
+
}
|
|
10
|
+
const priority = {
|
|
11
|
+
UNKNOWN: 0,
|
|
12
|
+
HEALTHY: 1,
|
|
13
|
+
NEAR_CAP: 2,
|
|
14
|
+
AT_CAP: 3
|
|
15
|
+
};
|
|
16
|
+
function recommendationFor(known) {
|
|
17
|
+
const fresh = known.filter((provider) => provider.state === "fresh");
|
|
18
|
+
if (fresh.length === 0) {
|
|
19
|
+
return { code: "NONE", provider: null, reason: "NO_FRESH_DATA" };
|
|
20
|
+
}
|
|
21
|
+
const usable = fresh.filter((provider) => provider.usagePercent < 80);
|
|
22
|
+
if (usable.length === 0) {
|
|
23
|
+
return { code: "NONE", provider: null, reason: "NO_HEALTHY_PROVIDER" };
|
|
24
|
+
}
|
|
25
|
+
let best = usable[0];
|
|
26
|
+
for (const candidate of usable.slice(1)) {
|
|
27
|
+
if (candidate.usagePercent < best.usagePercent)
|
|
28
|
+
best = candidate;
|
|
29
|
+
}
|
|
30
|
+
const tied = usable.filter((candidate) => candidate.usagePercent === best.usagePercent).length > 1;
|
|
31
|
+
return {
|
|
32
|
+
code: "PREFER",
|
|
33
|
+
provider: best.provider,
|
|
34
|
+
reason: tied ? "ORDERED_TIE_BREAK" : "LOWEST_USAGE"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function buildAdvice(snapshots, now, expectedProviders = PROVIDER_CODES) {
|
|
38
|
+
const known = [];
|
|
39
|
+
for (const provider of expectedProviders) {
|
|
40
|
+
const candidates = snapshots
|
|
41
|
+
.filter((snapshot) => snapshot.provider === provider && snapshot.unit === "PERCENT")
|
|
42
|
+
.map((snapshot) => ({
|
|
43
|
+
snapshot,
|
|
44
|
+
state: freshness(snapshot.observedAt, snapshot.expiresAt, now)
|
|
45
|
+
}))
|
|
46
|
+
.filter((entry) => entry.state !== "unknown")
|
|
47
|
+
.sort((left, right) => right.snapshot.value - left.snapshot.value);
|
|
48
|
+
const worst = candidates[0];
|
|
49
|
+
if (worst !== undefined) {
|
|
50
|
+
known.push({
|
|
51
|
+
provider,
|
|
52
|
+
state: worst.state,
|
|
53
|
+
usagePercent: worst.snapshot.value,
|
|
54
|
+
resetAt: worst.snapshot.resetAt
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (known.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
inject: false,
|
|
61
|
+
reason: "UNKNOWN",
|
|
62
|
+
recommendation: {
|
|
63
|
+
code: "NONE",
|
|
64
|
+
provider: null,
|
|
65
|
+
reason: "NO_KNOWN_PROVIDER"
|
|
66
|
+
},
|
|
67
|
+
providers: [],
|
|
68
|
+
unknownProviders: [...expectedProviders]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const unknownProviders = expectedProviders.filter((provider) => !known.some((entry) => entry.provider === provider));
|
|
72
|
+
const reason = known
|
|
73
|
+
.map((entry) => reasonFor(entry.usagePercent))
|
|
74
|
+
.reduce((worst, current) => priority[current] > priority[worst] ? current : worst, "HEALTHY");
|
|
75
|
+
return {
|
|
76
|
+
inject: true,
|
|
77
|
+
reason,
|
|
78
|
+
recommendation: recommendationFor(known),
|
|
79
|
+
providers: known,
|
|
80
|
+
unknownProviders
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export declare const PROVIDER_CODES: readonly ["CLAUDE", "OPENROUTER", "CODEX", "ANTIGRAVITY", "OPENCODE", "MANUAL"];
|
|
2
|
+
export type ProviderCode = (typeof PROVIDER_CODES)[number];
|
|
3
|
+
export type SnapshotState = "fresh" | "stale" | "unknown";
|
|
4
|
+
export type SnapshotUnit = "PERCENT" | "CREDITS" | "TOKENS" | "REQUESTS";
|
|
5
|
+
export type SnapshotPrecision = "exact" | "estimated" | "manual";
|
|
6
|
+
export type SnapshotSource = "native_payload" | "documented_api" | "internal_payload" | "authenticated_page" | "manual_entry";
|
|
7
|
+
export interface SnapshotWindow {
|
|
8
|
+
kind: "rolling" | "fixed" | "lifetime" | "unknown";
|
|
9
|
+
durationSeconds?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ConnectorLabels {
|
|
12
|
+
credentialOrigin: "official-local-tool" | "user-key" | "browser-session" | "user-entered";
|
|
13
|
+
dataInterfaceStatus: "native-statusline-payload" | "documented-api" | "internal-endpoint" | "authenticated-scrape" | "manual";
|
|
14
|
+
automationRisk: "low" | "high";
|
|
15
|
+
verification: "UNVERIFIED";
|
|
16
|
+
}
|
|
17
|
+
export interface Snapshot {
|
|
18
|
+
provider: ProviderCode;
|
|
19
|
+
meter: string;
|
|
20
|
+
value: number;
|
|
21
|
+
unit: SnapshotUnit;
|
|
22
|
+
window: SnapshotWindow;
|
|
23
|
+
resetAt: string | null;
|
|
24
|
+
source: SnapshotSource;
|
|
25
|
+
precision: SnapshotPrecision;
|
|
26
|
+
observedAt: string;
|
|
27
|
+
expiresAt: string;
|
|
28
|
+
labels: ConnectorLabels;
|
|
29
|
+
}
|
|
30
|
+
export interface RawMeter {
|
|
31
|
+
provider: unknown;
|
|
32
|
+
meter: unknown;
|
|
33
|
+
value: unknown;
|
|
34
|
+
unit: unknown;
|
|
35
|
+
window: unknown;
|
|
36
|
+
resetAt: unknown;
|
|
37
|
+
source: unknown;
|
|
38
|
+
precision: unknown;
|
|
39
|
+
observedAt: unknown;
|
|
40
|
+
expiresAt: unknown;
|
|
41
|
+
labels: unknown;
|
|
42
|
+
}
|
|
43
|
+
export interface ConnectorReadContext {
|
|
44
|
+
payload?: unknown;
|
|
45
|
+
now: string;
|
|
46
|
+
environment: Readonly<Record<string, string | undefined>>;
|
|
47
|
+
}
|
|
48
|
+
export type ConnectorResult = {
|
|
49
|
+
ok: true;
|
|
50
|
+
meters: readonly RawMeter[];
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: "unknown" | "unavailable" | "not_configured";
|
|
54
|
+
};
|
|
55
|
+
export interface ConnectorContract {
|
|
56
|
+
readonly id: Lowercase<ProviderCode>;
|
|
57
|
+
readonly displayName: string;
|
|
58
|
+
readonly labels: ConnectorLabels;
|
|
59
|
+
detect(environment: Readonly<Record<string, string | undefined>>): boolean;
|
|
60
|
+
read(context: ConnectorReadContext): Promise<ConnectorResult>;
|
|
61
|
+
}
|
|
62
|
+
export type AdviceReason = "HEALTHY" | "NEAR_CAP" | "AT_CAP" | "UNKNOWN";
|
|
63
|
+
export type AdviceRecommendation = {
|
|
64
|
+
code: "PREFER";
|
|
65
|
+
provider: ProviderCode;
|
|
66
|
+
reason: "LOWEST_USAGE" | "ORDERED_TIE_BREAK";
|
|
67
|
+
} | {
|
|
68
|
+
code: "NONE";
|
|
69
|
+
provider: null;
|
|
70
|
+
reason: "NO_KNOWN_PROVIDER" | "NO_FRESH_DATA" | "NO_HEALTHY_PROVIDER";
|
|
71
|
+
};
|
|
72
|
+
export interface AdviceProvider {
|
|
73
|
+
provider: ProviderCode;
|
|
74
|
+
state: "fresh" | "stale";
|
|
75
|
+
usagePercent: number;
|
|
76
|
+
resetAt: string | null;
|
|
77
|
+
}
|
|
78
|
+
export interface Advice {
|
|
79
|
+
inject: boolean;
|
|
80
|
+
reason: AdviceReason;
|
|
81
|
+
recommendation: AdviceRecommendation;
|
|
82
|
+
providers: readonly AdviceProvider[];
|
|
83
|
+
unknownProviders: readonly ProviderCode[];
|
|
84
|
+
}
|
|
85
|
+
export interface Forecast {
|
|
86
|
+
burnRatePerHour: number;
|
|
87
|
+
hoursToExhaustion: number | null;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,QAAQ;IACR,YAAY;IACZ,OAAO;IACP,aAAa;IACb,UAAU;IACV,QAAQ;CACA,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openlimiter/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared types, quota models, cache handling, and forecasts for OpenLimiter.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"NOTICE"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/lucaswebsystems/openlimiter.git",
|
|
24
|
+
"directory": "packages/core"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://openlimiter.com",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/lucaswebsystems/openlimiter/issues"
|
|
29
|
+
},
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"author": "Lucas Costa (https://lucaswebsystems.com)",
|
|
32
|
+
"keywords": [
|
|
33
|
+
"ai",
|
|
34
|
+
"quota",
|
|
35
|
+
"usage",
|
|
36
|
+
"meter",
|
|
37
|
+
"agents",
|
|
38
|
+
"claude",
|
|
39
|
+
"codex"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=24"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
50
|
+
}
|
|
51
|
+
}
|