@rulvar/store-sqlite 1.104.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 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, validateQuotaRules } from "@rulvar/core";
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
- validateQuotaRules(options.rules, "SqliteQuotaLimiterOptions.rules");
446
- this.rules = options.rules;
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.rules) {
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(ruleKey(rule), windowStart);
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.104.0",
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.104.0"
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.104.0"
31
+ "@rulvar/store-conformance": "1.105.0"
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",