@rulvar/store-sqlite 1.103.0 → 1.105.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/dist/index.d.ts +4 -0
- package/dist/index.js +12 -19
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -150,6 +150,10 @@ interface SqliteQuotaLimiterOptions {
|
|
|
150
150
|
declare class SqliteQuotaLimiter implements QuotaLimiter {
|
|
151
151
|
private readonly db;
|
|
152
152
|
private readonly rules;
|
|
153
|
+
/** Matching order for the denial fold: canonical rule-key order
|
|
154
|
+
* (RV608), so permuted identical sets produce byte-identical
|
|
155
|
+
* refusals. Telemetry keeps the declared order. */
|
|
156
|
+
private readonly ordered;
|
|
153
157
|
private readonly now;
|
|
154
158
|
constructor(options: SqliteQuotaLimiterOptions);
|
|
155
159
|
reserve(request: QuotaReservationRequest): Promise<QuotaDecision>;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync } from "node:sqlite";
|
|
2
|
-
import { ConfigError, JournalOrderViolation, LeaseHeldError, QUOTA_WINDOW_MS, mergeQuotaDenial, metaMatchesFilter, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches,
|
|
2
|
+
import { ConfigError, JournalOrderViolation, LeaseHeldError, QUOTA_WINDOW_MS, mergeQuotaDenial, metaMatchesFilter, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleKey, quotaRuleMatches, snapshotQuotaRules } from "@rulvar/core";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
//#region src/store.ts
|
|
5
5
|
/**
|
|
@@ -406,19 +406,6 @@ function sleepSync(ms) {
|
|
|
406
406
|
}
|
|
407
407
|
const wallClock = Date.now.bind(globalThis);
|
|
408
408
|
/**
|
|
409
|
-
* The canonical bucket key of one rule: a fixed-field-order JSON of
|
|
410
|
-
* its content, identical across processes for identical rules.
|
|
411
|
-
*/
|
|
412
|
-
function ruleKey(rule) {
|
|
413
|
-
return JSON.stringify({
|
|
414
|
-
provider: rule.provider ?? null,
|
|
415
|
-
model: rule.model ?? null,
|
|
416
|
-
tenant: rule.tenant ?? null,
|
|
417
|
-
requestsPerMinute: rule.requestsPerMinute ?? null,
|
|
418
|
-
tokensPerMinute: rule.tokensPerMinute ?? null
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
409
|
* The cross-process reference implementation of the core QuotaLimiter
|
|
423
410
|
* SPI: engine processes pointing instances at ONE database file (this
|
|
424
411
|
* store's file or its own) enforce one global provider quota.
|
|
@@ -439,11 +426,18 @@ function ruleKey(rule) {
|
|
|
439
426
|
var SqliteQuotaLimiter = class {
|
|
440
427
|
db;
|
|
441
428
|
rules;
|
|
429
|
+
/** Matching order for the denial fold: canonical rule-key order
|
|
430
|
+
* (RV608), so permuted identical sets produce byte-identical
|
|
431
|
+
* refusals. Telemetry keeps the declared order. */
|
|
432
|
+
ordered;
|
|
442
433
|
now;
|
|
443
434
|
constructor(options) {
|
|
444
435
|
if (typeof options.path !== "string" || options.path === "") throw new ConfigError("SqliteQuotaLimiterOptions.path must be a nonempty string");
|
|
445
|
-
|
|
446
|
-
this.
|
|
436
|
+
this.rules = snapshotQuotaRules(options.rules, "SqliteQuotaLimiterOptions.rules");
|
|
437
|
+
this.ordered = this.rules.map((rule) => ({
|
|
438
|
+
rule,
|
|
439
|
+
key: quotaRuleKey(rule)
|
|
440
|
+
})).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
447
441
|
this.now = options.now ?? wallClock;
|
|
448
442
|
this.db = new DatabaseSync(options.path);
|
|
449
443
|
const schema = `
|
|
@@ -482,9 +476,8 @@ var SqliteQuotaLimiter = class {
|
|
|
482
476
|
const read = this.db.prepare("SELECT requests, tokens FROM quota_buckets WHERE rule_key = ? AND window_start = ?");
|
|
483
477
|
const matched = [];
|
|
484
478
|
let denial;
|
|
485
|
-
for (const rule of this.
|
|
479
|
+
for (const { rule, key } of this.ordered) {
|
|
486
480
|
if (!quotaRuleMatches(rule, request)) continue;
|
|
487
|
-
const key = ruleKey(rule);
|
|
488
481
|
matched.push(key);
|
|
489
482
|
const verdict = quotaRuleAdmission(rule, read.get(key, windowStart) ?? {
|
|
490
483
|
requests: 0,
|
|
@@ -545,7 +538,7 @@ var SqliteQuotaLimiter = class {
|
|
|
545
538
|
const windowStart = at - at % QUOTA_WINDOW_MS;
|
|
546
539
|
const read = this.db.prepare("SELECT requests, tokens FROM quota_buckets WHERE rule_key = ? AND window_start = ?");
|
|
547
540
|
return this.rules.map((rule) => {
|
|
548
|
-
const row = read.get(
|
|
541
|
+
const row = read.get(quotaRuleKey(rule), windowStart);
|
|
549
542
|
return {
|
|
550
543
|
rule,
|
|
551
544
|
windowStart,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/store-sqlite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.105.0",
|
|
4
4
|
"description": "Rulvar SQLite store implementing JournalStore and LeasableStore with a fencing epoch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.105.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.1",
|
|
29
29
|
"tsdown": "^0.22.14",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/store-conformance": "1.
|
|
31
|
+
"@rulvar/store-conformance": "1.105.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|