@vericto/sdk 0.0.0-stage → 0.2.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.
@@ -0,0 +1,600 @@
1
+ 'use strict';
2
+
3
+ // src/dialects.ts
4
+ var KNOWN_DIALECTS = ["postgres", "mysql", "oracle", "mssql", "snowflake", "sqlite"];
5
+ var ALIASES = {
6
+ postgres: "postgres",
7
+ postgresql: "postgres",
8
+ pg: "postgres",
9
+ mysql: "mysql",
10
+ mysql2: "mysql",
11
+ mariadb: "mysql",
12
+ oracle: "oracle",
13
+ oracledb: "oracle",
14
+ mssql: "mssql",
15
+ sqlserver: "mssql",
16
+ tedious: "mssql",
17
+ snowflake: "snowflake",
18
+ sqlite: "sqlite",
19
+ sqlite3: "sqlite",
20
+ "better-sqlite3": "sqlite"
21
+ };
22
+ function normalizeDialect(name) {
23
+ const key = name.trim().toLowerCase();
24
+ return ALIASES[key] ?? key;
25
+ }
26
+
27
+ // src/errors.ts
28
+ var VerictoError = class extends Error {
29
+ constructor(message) {
30
+ super(message);
31
+ this.name = new.target.name;
32
+ }
33
+ };
34
+ var VerictoConfigError = class extends VerictoError {
35
+ status;
36
+ code;
37
+ constructor(message, options = {}) {
38
+ super(message);
39
+ this.status = options.status;
40
+ this.code = options.code;
41
+ }
42
+ };
43
+ var VerictoUnsupportedDialectError = class extends VerictoConfigError {
44
+ dialect;
45
+ supported;
46
+ constructor(dialect, supported, apiUrl) {
47
+ const listed = supported.length ? supported.join(", ") : "none reported";
48
+ super(
49
+ `The Vericto API at ${apiUrl} does not support the '${dialect}' dialect (supported: ${listed}). No SQL was executed. Upgrade the server, or use a supported dialect.`,
50
+ { code: "UNSUPPORTED_DIALECT" }
51
+ );
52
+ this.dialect = dialect;
53
+ this.supported = supported;
54
+ }
55
+ };
56
+ var VerictoUnavailableError = class extends VerictoError {
57
+ status;
58
+ code;
59
+ constructor(message, options = {}) {
60
+ super(message);
61
+ this.status = options.status;
62
+ this.code = options.code;
63
+ }
64
+ };
65
+ var VerictoQuotaExceededError = class extends VerictoUnavailableError {
66
+ resetsAt;
67
+ upgradeUrl;
68
+ constructor(message, options = {}) {
69
+ super(message, { status: 429, code: "QUOTA_EXCEEDED" });
70
+ this.resetsAt = options.resetsAt;
71
+ this.upgradeUrl = options.upgradeUrl;
72
+ }
73
+ };
74
+ var VerictoBlockedError = class extends VerictoError {
75
+ verdict;
76
+ sql;
77
+ constructor(verdict, sql) {
78
+ let message = `Query blocked by ${verdict.ruleCode ?? "a Vericto rule"}`;
79
+ if (verdict.severity) message += ` (severity ${verdict.severity})`;
80
+ if (verdict.message) message += `: ${verdict.message}`;
81
+ if (verdict.suggestedFix) message += `. Suggested fix: ${verdict.suggestedFix}`;
82
+ super(message);
83
+ this.verdict = verdict;
84
+ this.sql = sql;
85
+ }
86
+ get findings() {
87
+ return this.verdict.findings;
88
+ }
89
+ get suggestedFix() {
90
+ return this.verdict.suggestedFix;
91
+ }
92
+ };
93
+
94
+ // src/verdict.ts
95
+ var SERVER_DECISIONS = /* @__PURE__ */ new Set(["ALLOWED", "FLAGGED", "MONITORED", "PARSE_ERROR", "BLOCKED"]);
96
+ function sqlToExecute(verdict) {
97
+ return verdict.rewrittenQuery ?? verdict.sql;
98
+ }
99
+ function isMasked(verdict) {
100
+ return Boolean(verdict.rewrittenQuery);
101
+ }
102
+ function verdictFromResponse(body, sql, dialect) {
103
+ const raw = (typeof body.decision === "string" ? body.decision : "").toUpperCase();
104
+ const decision = SERVER_DECISIONS.has(raw) ? raw : "BLOCKED";
105
+ const ruleCode = optStr(body.rule_code);
106
+ const severity = optStr(body.severity);
107
+ const suggestedFix = optStr(body.suggested_safe_query);
108
+ let message = optStr(body.error);
109
+ if (message && ruleCode && message === `Query blocked by rule ${ruleCode}`) message = void 0;
110
+ return {
111
+ decision,
112
+ sql,
113
+ dialect,
114
+ ruleCode,
115
+ severity,
116
+ message,
117
+ suggestedFix,
118
+ // A blocked statement never runs, so it has nothing to rewrite.
119
+ rewrittenQuery: decision === "BLOCKED" ? void 0 : optStr(body.rewritten_query),
120
+ parseError: optStr(body.parse_error),
121
+ findings: findingsOf(body, ruleCode, severity, suggestedFix),
122
+ sensitiveColumns: Array.isArray(body.sensitive_columns) ? body.sensitive_columns : [],
123
+ accessDenied: Array.isArray(body.access_denied) ? body.access_denied : [],
124
+ eventId: optStr(body.event_id),
125
+ rulesetVersion: optStr(body.ruleset_version),
126
+ cached: false,
127
+ raw: body
128
+ };
129
+ }
130
+ function unevaluatedVerdict(sql, dialect, reason) {
131
+ return {
132
+ decision: "UNEVALUATED",
133
+ sql,
134
+ dialect,
135
+ message: reason,
136
+ findings: [],
137
+ sensitiveColumns: [],
138
+ accessDenied: [],
139
+ cached: false,
140
+ raw: {}
141
+ };
142
+ }
143
+ function optStr(value) {
144
+ if (value === null || value === void 0 || value === "") return void 0;
145
+ return typeof value === "string" ? value : JSON.stringify(value);
146
+ }
147
+ function findingsOf(body, ruleCode, severity, suggestedFix) {
148
+ const listed = body.findings ?? body.violations;
149
+ if (Array.isArray(listed)) {
150
+ const out = [];
151
+ for (const item of listed) {
152
+ if (!item || typeof item !== "object") continue;
153
+ const f = item;
154
+ const code = optStr(f.rule_code ?? f.code);
155
+ if (!code) continue;
156
+ out.push({
157
+ ruleCode: code,
158
+ severity: optStr(f.severity),
159
+ ruleId: optStr(f.rule_id),
160
+ astNodePath: optStr(f.ast_node_path),
161
+ suggestedFix: optStr(f.suggested_safe_query ?? f.suggested_fix)
162
+ });
163
+ }
164
+ if (out.length) return out;
165
+ }
166
+ if (!ruleCode) return [];
167
+ return [
168
+ {
169
+ ruleCode,
170
+ severity,
171
+ ruleId: optStr(body.rule_id),
172
+ astNodePath: optStr(body.ast_node_path),
173
+ suggestedFix
174
+ }
175
+ ];
176
+ }
177
+
178
+ // src/version.ts
179
+ var VERSION = "0.2.0";
180
+
181
+ // src/runtime-api.ts
182
+ var DEFAULT_API_URL = "https://api.vericto.com";
183
+ var EVALUATE_PATH = "/api/v1/proxy/query";
184
+ var CONFIG_PATH = "/api/v1/meta/config";
185
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
186
+ var RETRYABLE = /* @__PURE__ */ new Set([500, 502, 503, 504]);
187
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
188
+ var MAX_BACKOFF_MS = 2e3;
189
+ var RuntimeApiEvaluator = class {
190
+ apiUrl;
191
+ databaseId;
192
+ timeoutMs;
193
+ maxRetries;
194
+ #apiKey;
195
+ #backoffMs;
196
+ #dialectsTtlMs;
197
+ #fetch;
198
+ #dialects;
199
+ #dialectsAt = 0;
200
+ #dialectsPending;
201
+ constructor(options = {}) {
202
+ const env = options.env ?? (typeof process !== "undefined" ? process.env : {});
203
+ const key = (options.apiKey ?? env.VERICTO_API_KEY ?? "").trim();
204
+ if (!key) {
205
+ throw new VerictoConfigError(
206
+ "No Vericto API key: pass apiKey or set VERICTO_API_KEY (a vtro_... key with the proxy:evaluate scope)."
207
+ );
208
+ }
209
+ if (!key.startsWith("vtro_")) {
210
+ throw new VerictoConfigError("The Vericto API key must start with 'vtro_' (the value itself is not shown).");
211
+ }
212
+ this.#apiKey = key;
213
+ const db = (options.databaseId ?? env.VERICTO_DATABASE_ID ?? "").trim();
214
+ if (!db) {
215
+ throw new VerictoConfigError(
216
+ "No Vericto database id: pass databaseId or set VERICTO_DATABASE_ID (the id of the database registered in your Vericto workspace)."
217
+ );
218
+ }
219
+ if (!UUID_RE.test(db)) {
220
+ throw new VerictoConfigError(`The Vericto database id must be a UUID, got ${JSON.stringify(db.slice(0, 64))}.`);
221
+ }
222
+ this.databaseId = db.toLowerCase();
223
+ const url = ((options.apiUrl ?? env.VERICTO_API_URL ?? "").trim() || DEFAULT_API_URL).replace(/\/+$/, "");
224
+ let parsed;
225
+ try {
226
+ parsed = new URL(url);
227
+ } catch {
228
+ throw new VerictoConfigError(`Invalid Vericto API URL ${JSON.stringify(url)}: expected https://host.`);
229
+ }
230
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
231
+ throw new VerictoConfigError(`Invalid Vericto API URL ${JSON.stringify(url)}: expected https://host.`);
232
+ }
233
+ if (parsed.protocol === "http:" && !LOCAL_HOSTS.has(parsed.hostname)) {
234
+ throw new VerictoConfigError(`Refusing to send the API key over plain HTTP to ${parsed.hostname}: use https://.`);
235
+ }
236
+ this.apiUrl = url;
237
+ let timeoutMs = options.timeoutMs;
238
+ if (timeoutMs === void 0) {
239
+ const fromEnv = (env.VERICTO_TIMEOUT ?? "").trim();
240
+ timeoutMs = fromEnv ? Number(fromEnv) * 1e3 : 5e3;
241
+ if (!Number.isFinite(timeoutMs)) {
242
+ throw new VerictoConfigError(`VERICTO_TIMEOUT must be a number of seconds, got ${JSON.stringify(fromEnv)}.`);
243
+ }
244
+ }
245
+ if (!(timeoutMs > 0)) throw new VerictoConfigError("timeoutMs must be greater than 0.");
246
+ const maxRetries = options.maxRetries ?? 2;
247
+ if (!(maxRetries >= 0)) throw new VerictoConfigError("maxRetries cannot be negative.");
248
+ this.timeoutMs = timeoutMs;
249
+ this.maxRetries = Math.floor(maxRetries);
250
+ this.#backoffMs = Math.max(0, options.backoffMs ?? 200);
251
+ this.#dialectsTtlMs = options.dialectsTtlMs ?? 6e5;
252
+ const f = options.fetch ?? globalThis.fetch;
253
+ if (!f) throw new VerictoConfigError("No fetch implementation: Node 18+ is required, or pass options.fetch.");
254
+ this.#fetch = f;
255
+ }
256
+ toString() {
257
+ return `RuntimeApiEvaluator(apiUrl=${this.apiUrl}, databaseId=${this.databaseId})`;
258
+ }
259
+ toJSON() {
260
+ return { apiUrl: this.apiUrl, databaseId: this.databaseId };
261
+ }
262
+ async evaluate(sql, dialect) {
263
+ const payload = JSON.stringify({ query: sql, database_id: this.databaseId, dialect });
264
+ let lastError = "no attempt made";
265
+ let retryAfterMs;
266
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
267
+ if (attempt > 0) await sleep(this.#delay(attempt, retryAfterMs));
268
+ retryAfterMs = void 0;
269
+ let response;
270
+ try {
271
+ response = await this.#request(EVALUATE_PATH, {
272
+ method: "POST",
273
+ headers: { ...this.#headers(), "Content-Type": "application/json", "X-API-Key": this.#apiKey },
274
+ body: payload
275
+ });
276
+ } catch (err) {
277
+ lastError = `${errorName(err)} while calling ${this.apiUrl}`;
278
+ continue;
279
+ }
280
+ const status = response.status;
281
+ const body = await jsonBody(response);
282
+ if (status === 200 || status === 403 && (typeof body.decision === "string" ? body.decision : "").toUpperCase() === "BLOCKED") {
283
+ if (!("decision" in body)) {
284
+ lastError = `HTTP ${status} without a decision from ${this.apiUrl}`;
285
+ continue;
286
+ }
287
+ return verdictFromResponse(body, sql, dialect);
288
+ }
289
+ const error = body.error && typeof body.error === "object" ? body.error : {};
290
+ const code = typeof error.code === "string" && error.code ? error.code : void 0;
291
+ const message = typeof error.message === "string" && error.message ? error.message : void 0;
292
+ if (status === 429 && code === "QUOTA_EXCEEDED") {
293
+ const resetsAt = typeof error.resets_at === "string" ? error.resets_at : void 0;
294
+ throw new VerictoQuotaExceededError(
295
+ `The Vericto workspace has used its monthly query quota${resetsAt ? `; it resets at ${resetsAt}` : ""}.`,
296
+ { resetsAt, upgradeUrl: typeof error.upgrade_url === "string" ? error.upgrade_url : void 0 }
297
+ );
298
+ }
299
+ if (status === 429 || RETRYABLE.has(status)) {
300
+ lastError = `HTTP ${status}${code ? ` ${code}` : ""} from ${this.apiUrl}`;
301
+ retryAfterMs = retryAfter(response.headers.get("retry-after"), error.retry_after_seconds);
302
+ continue;
303
+ }
304
+ throw await this.#configError(status, code, message, dialect);
305
+ }
306
+ throw new VerictoUnavailableError(
307
+ `Vericto did not return a verdict after ${this.maxRetries + 1} attempt(s): ${lastError}.`
308
+ );
309
+ }
310
+ /**
311
+ * The dialects the server evaluates (`GET /api/v1/meta/config`), cached. `undefined`
312
+ * when the server cannot be asked; the evaluation request then decides.
313
+ */
314
+ async supportedDialects() {
315
+ if (this.#dialects && Date.now() - this.#dialectsAt < this.#dialectsTtlMs) return this.#dialects;
316
+ this.#dialectsPending ??= this.#fetchDialects().finally(() => {
317
+ this.#dialectsPending = void 0;
318
+ });
319
+ return this.#dialectsPending;
320
+ }
321
+ async #fetchDialects() {
322
+ try {
323
+ const response = await this.#request(CONFIG_PATH, { method: "GET", headers: this.#headers() });
324
+ if (response.status !== 200) return this.#dialects;
325
+ const listed = (await jsonBody(response)).dialects;
326
+ if (!Array.isArray(listed)) return this.#dialects;
327
+ const ids = listed.map((d) => d && typeof d === "object" ? d.id : void 0).filter((id) => typeof id === "string" && id.length > 0);
328
+ this.#dialects = ids;
329
+ this.#dialectsAt = Date.now();
330
+ return ids;
331
+ } catch {
332
+ return this.#dialects;
333
+ }
334
+ }
335
+ #headers() {
336
+ return { Accept: "application/json", "User-Agent": `vericto-node/${VERSION}` };
337
+ }
338
+ async #request(path, init) {
339
+ const controller = new AbortController();
340
+ const timer = setTimeout(() => {
341
+ controller.abort();
342
+ }, this.timeoutMs);
343
+ try {
344
+ return await this.#fetch(this.apiUrl + path, { ...init, redirect: "manual", signal: controller.signal });
345
+ } finally {
346
+ clearTimeout(timer);
347
+ }
348
+ }
349
+ #delay(attempt, retryAfterMs) {
350
+ if (retryAfterMs !== void 0) return Math.min(retryAfterMs, MAX_BACKOFF_MS);
351
+ const base = this.#backoffMs * 2 ** (attempt - 1);
352
+ return Math.min(MAX_BACKOFF_MS, base * (0.5 + Math.random() / 2));
353
+ }
354
+ async #configError(status, code, message, dialect) {
355
+ if (status === 401) {
356
+ return new VerictoConfigError("Vericto rejected the API key (HTTP 401). Check VERICTO_API_KEY.", {
357
+ status,
358
+ code
359
+ });
360
+ }
361
+ if (status === 403) {
362
+ return new VerictoConfigError(
363
+ `Vericto refused the request (HTTP 403${code ? `, ${code}` : ""}): ${message ?? "the key lacks the proxy:evaluate scope or this database"}.`,
364
+ { status, code }
365
+ );
366
+ }
367
+ if (status === 400) {
368
+ const supported = await this.supportedDialects();
369
+ if (supported && !supported.includes(dialect)) {
370
+ return new VerictoUnsupportedDialectError(dialect, supported, this.apiUrl);
371
+ }
372
+ }
373
+ return new VerictoConfigError(
374
+ `Vericto rejected the request (HTTP ${status}${code ? `, ${code}` : ""})${message ? `: ${message}` : "."}`,
375
+ { status, code }
376
+ );
377
+ }
378
+ };
379
+ async function jsonBody(response) {
380
+ try {
381
+ const text = await response.text();
382
+ const parsed = text ? JSON.parse(text) : {};
383
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
384
+ } catch {
385
+ return {};
386
+ }
387
+ }
388
+ function retryAfter(header, bodyValue) {
389
+ for (const value of [header, bodyValue]) {
390
+ if (value === null || value === void 0 || value === "") continue;
391
+ const seconds = Number(value);
392
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
393
+ }
394
+ return void 0;
395
+ }
396
+ function errorName(err) {
397
+ if (err instanceof Error) return err.name === "AbortError" ? "Timeout" : err.name;
398
+ return "Error";
399
+ }
400
+ function sleep(ms) {
401
+ return new Promise((resolve) => setTimeout(resolve, ms));
402
+ }
403
+
404
+ // src/guard.ts
405
+ var DEFAULT_LOGGER = {
406
+ info: () => {
407
+ },
408
+ warn: (m) => {
409
+ console.warn(m);
410
+ },
411
+ error: (m) => {
412
+ console.error(m);
413
+ }
414
+ };
415
+ var SILENT = { info: () => {
416
+ }, warn: () => {
417
+ }, error: () => {
418
+ } };
419
+ var Vericto = class {
420
+ evaluator;
421
+ failMode;
422
+ onVerdict;
423
+ #logger;
424
+ #cache;
425
+ constructor(options = {}) {
426
+ this.evaluator = options.evaluator ?? new RuntimeApiEvaluator(options);
427
+ const env = options.env ?? (typeof process !== "undefined" ? process.env : {});
428
+ const mode = (options.failMode ?? env.VERICTO_FAIL_MODE ?? "closed").trim().toLowerCase();
429
+ if (mode !== "closed" && mode !== "open") {
430
+ throw new VerictoConfigError(`failMode must be 'closed' or 'open', got ${JSON.stringify(mode)}.`);
431
+ }
432
+ this.failMode = mode;
433
+ const ttl = options.cacheTtlMs ?? 0;
434
+ const size = options.cacheSize ?? 1024;
435
+ if (ttl < 0 || size < 1) throw new VerictoConfigError("cacheTtlMs must be >= 0 and cacheSize >= 1.");
436
+ this.#cache = ttl > 0 ? new VerdictCache(ttl, size) : void 0;
437
+ this.onVerdict = options.onVerdict;
438
+ this.#logger = options.logger === false ? SILENT : options.logger ?? DEFAULT_LOGGER;
439
+ }
440
+ /**
441
+ * Fail fast if the server reports that it does not evaluate `dialect`. Resolves the
442
+ * normalized dialect id. When the server cannot be asked, nothing is thrown here.
443
+ */
444
+ async requireDialect(dialect) {
445
+ const name = normalizeDialect(dialect);
446
+ const supported = await this.evaluator.supportedDialects();
447
+ if (supported && !supported.includes(name)) {
448
+ throw new VerictoUnsupportedDialectError(name, supported, this.evaluator.apiUrl ?? "the configured evaluator");
449
+ }
450
+ return name;
451
+ }
452
+ /**
453
+ * Evaluate `sql` and resolve the verdict, whatever it is. Does not apply the fail
454
+ * mode and does not reject on `BLOCKED`: use it to ask, and `enforce` to guard.
455
+ */
456
+ async check(sql, dialect) {
457
+ if (typeof sql !== "string") {
458
+ throw new TypeError(`vericto: expected the SQL text as a string, got ${typeof sql}.`);
459
+ }
460
+ const name = await this.requireDialect(dialect);
461
+ const hit = this.#cache?.get(sql, name);
462
+ if (hit) return hit;
463
+ const verdict = await this.evaluator.evaluate(sql, name);
464
+ this.#cache?.put(verdict);
465
+ return verdict;
466
+ }
467
+ /**
468
+ * Evaluate `sql` and apply the decision: reject with `VerictoBlockedError` on
469
+ * `BLOCKED`; log `FLAGGED`/`MONITORED`/allowed `PARSE_ERROR`; apply the fail mode
470
+ * when Vericto cannot answer. Run `sqlToExecute(verdict)` (the masked rewrite, if any).
471
+ */
472
+ async enforce(sql, dialect) {
473
+ let verdict;
474
+ try {
475
+ verdict = await this.check(sql, dialect);
476
+ } catch (err) {
477
+ if (!(err instanceof VerictoUnavailableError)) throw err;
478
+ if (this.failMode === "closed") {
479
+ this.#logger.error(`vericto: no verdict, statement not executed (failMode=closed): ${err.message}`);
480
+ throw err;
481
+ }
482
+ verdict = unevaluatedVerdict(sql, normalizeDialect(dialect), err.message);
483
+ this.#logger.warn(`vericto: no verdict, statement executed unevaluated (failMode=open): ${err.message}`);
484
+ emitFailOpenWarning(err.message);
485
+ this.#notify(verdict);
486
+ return verdict;
487
+ }
488
+ this.#notify(verdict);
489
+ this.#log(verdict);
490
+ if (verdict.decision === "BLOCKED") throw new VerictoBlockedError(verdict, sql);
491
+ return verdict;
492
+ }
493
+ /** `enforce`, resolving the SQL text to execute. */
494
+ async prepare(sql, dialect) {
495
+ return sqlToExecute(await this.enforce(sql, dialect));
496
+ }
497
+ /** Enforce a batch in order; a block anywhere rejects before the caller runs any of it. */
498
+ async enforceAll(statements, dialect) {
499
+ const out = [];
500
+ for (const statement of statements) out.push(await this.enforce(statement, dialect));
501
+ return out;
502
+ }
503
+ clearCache() {
504
+ this.#cache?.clear();
505
+ }
506
+ #notify(verdict) {
507
+ if (!this.onVerdict) return;
508
+ try {
509
+ this.onVerdict(verdict);
510
+ } catch (err) {
511
+ this.#logger.error(`vericto: onVerdict callback threw: ${err instanceof Error ? err.message : String(err)}`);
512
+ }
513
+ }
514
+ #log(verdict) {
515
+ const extra = `rule=${verdict.ruleCode ?? "-"} severity=${verdict.severity ?? "-"} event_id=${verdict.eventId ?? "-"}`;
516
+ switch (verdict.decision) {
517
+ case "BLOCKED":
518
+ this.#logger.warn(`vericto: statement BLOCKED ${extra}`);
519
+ break;
520
+ case "FLAGGED":
521
+ this.#logger.warn(`vericto: statement FLAGGED ${extra}`);
522
+ break;
523
+ case "PARSE_ERROR":
524
+ this.#logger.warn(`vericto: statement not parsed, allowed by workspace policy ${extra}`);
525
+ break;
526
+ case "MONITORED":
527
+ this.#logger.info(`vericto: statement MONITORED ${extra}`);
528
+ break;
529
+ }
530
+ if (isMasked(verdict)) this.#logger.info(`vericto: running the masked rewrite ${extra}`);
531
+ }
532
+ };
533
+ function emitFailOpenWarning(reason) {
534
+ if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
535
+ process.emitWarning(`Vericto could not evaluate the statement and failMode is 'open', so it ran unchecked: ${reason}`, {
536
+ type: "VerictoFailOpenWarning",
537
+ code: "VERICTO_FAIL_OPEN"
538
+ });
539
+ }
540
+ }
541
+ var VerdictCache = class {
542
+ #ttl;
543
+ #size;
544
+ #items = /* @__PURE__ */ new Map();
545
+ #ruleset;
546
+ constructor(ttl, size) {
547
+ this.#ttl = ttl;
548
+ this.#size = size;
549
+ }
550
+ get(sql, dialect) {
551
+ const key = `${dialect}\0${sql}`;
552
+ const item = this.#items.get(key);
553
+ if (!item) return void 0;
554
+ if (Date.now() - item.at > this.#ttl) {
555
+ this.#items.delete(key);
556
+ return void 0;
557
+ }
558
+ this.#items.delete(key);
559
+ this.#items.set(key, item);
560
+ return { ...item.verdict, cached: true };
561
+ }
562
+ put(verdict) {
563
+ if (verdict.decision === "UNEVALUATED") return;
564
+ if (verdict.rulesetVersion && verdict.rulesetVersion !== this.#ruleset) {
565
+ if (this.#ruleset !== void 0) this.#items.clear();
566
+ this.#ruleset = verdict.rulesetVersion;
567
+ }
568
+ const key = `${verdict.dialect}\0${verdict.sql}`;
569
+ this.#items.delete(key);
570
+ this.#items.set(key, { at: Date.now(), verdict });
571
+ while (this.#items.size > this.#size) {
572
+ const oldest = this.#items.keys().next().value;
573
+ if (oldest === void 0) break;
574
+ this.#items.delete(oldest);
575
+ }
576
+ }
577
+ clear() {
578
+ this.#items.clear();
579
+ }
580
+ };
581
+
582
+ exports.CONFIG_PATH = CONFIG_PATH;
583
+ exports.DEFAULT_API_URL = DEFAULT_API_URL;
584
+ exports.EVALUATE_PATH = EVALUATE_PATH;
585
+ exports.KNOWN_DIALECTS = KNOWN_DIALECTS;
586
+ exports.RuntimeApiEvaluator = RuntimeApiEvaluator;
587
+ exports.VERSION = VERSION;
588
+ exports.Vericto = Vericto;
589
+ exports.VerictoBlockedError = VerictoBlockedError;
590
+ exports.VerictoConfigError = VerictoConfigError;
591
+ exports.VerictoError = VerictoError;
592
+ exports.VerictoQuotaExceededError = VerictoQuotaExceededError;
593
+ exports.VerictoUnavailableError = VerictoUnavailableError;
594
+ exports.VerictoUnsupportedDialectError = VerictoUnsupportedDialectError;
595
+ exports.isMasked = isMasked;
596
+ exports.normalizeDialect = normalizeDialect;
597
+ exports.sqlToExecute = sqlToExecute;
598
+ exports.verdictFromResponse = verdictFromResponse;
599
+ //# sourceMappingURL=chunk-HFL7W4WC.cjs.map
600
+ //# sourceMappingURL=chunk-HFL7W4WC.cjs.map