@diegoaltoworks/chatter 0.24.0 → 0.26.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.
@@ -106,6 +106,37 @@ async function answerOnce({
106
106
  return completeOnce({ client, system, messages, temperature, model });
107
107
  }
108
108
 
109
+ // src/core/buckets.ts
110
+ function defaultBuckets(mode) {
111
+ return ["base", mode];
112
+ }
113
+ function isIdentified(sender) {
114
+ return typeof sender === "string" && sender.trim().length > 0;
115
+ }
116
+ function normalize(buckets) {
117
+ const out = [];
118
+ for (const raw of buckets) {
119
+ if (typeof raw !== "string") continue;
120
+ const bucket = raw.trim();
121
+ if (bucket && !out.includes(bucket)) out.push(bucket);
122
+ }
123
+ return out;
124
+ }
125
+ async function resolveBuckets({
126
+ mode,
127
+ sender,
128
+ bucketsFor
129
+ }) {
130
+ if (!bucketsFor) return void 0;
131
+ const identified = isIdentified(sender);
132
+ const requested = await bucketsFor(identified ? { mode, sender } : { mode });
133
+ if (!Array.isArray(requested)) return void 0;
134
+ const cleaned = normalize(requested);
135
+ if (identified) return cleaned;
136
+ const ceiling = new Set(defaultBuckets(mode));
137
+ return cleaned.filter((bucket) => ceiling.has(bucket));
138
+ }
139
+
109
140
  // src/core/prompts.ts
110
141
  var import_node_fs = require("node:fs");
111
142
  var import_node_path = require("node:path");
@@ -314,8 +345,19 @@ var VectorStore = class _VectorStore {
314
345
  }
315
346
  return dot / (Math.sqrt(na) * Math.sqrt(nb));
316
347
  }
317
- // Retrieve top-k across allowed buckets; compute similarity in app (simple & portable).
348
+ /**
349
+ * Retrieve top-k across allowed buckets; compute similarity in app (simple
350
+ * & portable).
351
+ *
352
+ * `allowed` is a bucket-name list rather than the {@link Bucket} union: the
353
+ * `chunks` table constrains nothing, so a deployment that ingests its own
354
+ * buckets can query them (`build` only writes and prunes the three the
355
+ * knowledge loader walks). Names are bound as query parameters, never
356
+ * interpolated. An empty list retrieves nothing, and short-circuits before
357
+ * the embedding call.
358
+ */
318
359
  async query(q, k = 6, allowed = ["base"]) {
360
+ if (allowed.length === 0) return [];
319
361
  const qv = (await this.client.embeddings.create({ model: EMB_MODEL, input: [q] })).data[0].embedding;
320
362
  const placeholders = allowed.map(() => "?").join(",");
321
363
  const res = await this.db.execute({
@@ -560,7 +602,8 @@ async function createMCPServer(config) {
560
602
  if (!lastUserMsg) {
561
603
  throw new Error("No user message found in conversation");
562
604
  }
563
- const ctx = await store.query(lastUserMsg.content, 6, ["base", "public"]);
605
+ const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor }) ?? defaultBuckets("public");
606
+ const ctx = await store.query(lastUserMsg.content, 6, buckets);
564
607
  const system = [
565
608
  prompts.baseSystemRules,
566
609
  prompts.publicPersona,
@@ -646,7 +689,8 @@ ${ctx.join("\n\n")}`
646
689
  if (!lastUserMsg) {
647
690
  throw new Error("No user message found in conversation");
648
691
  }
649
- const ctx = await store.query(lastUserMsg.content, 8, ["base", "private"]);
692
+ const buckets = await resolveBuckets({ mode: "private", bucketsFor: config.bucketsFor }) ?? defaultBuckets("private");
693
+ const ctx = await store.query(lastUserMsg.content, 8, buckets);
650
694
  const system = [
651
695
  prompts.baseSystemRules,
652
696
  prompts.privatePersona,
@@ -72,6 +72,37 @@ async function answerOnce({
72
72
  return completeOnce({ client, system, messages, temperature, model });
73
73
  }
74
74
 
75
+ // src/core/buckets.ts
76
+ function defaultBuckets(mode) {
77
+ return ["base", mode];
78
+ }
79
+ function isIdentified(sender) {
80
+ return typeof sender === "string" && sender.trim().length > 0;
81
+ }
82
+ function normalize(buckets) {
83
+ const out = [];
84
+ for (const raw of buckets) {
85
+ if (typeof raw !== "string") continue;
86
+ const bucket = raw.trim();
87
+ if (bucket && !out.includes(bucket)) out.push(bucket);
88
+ }
89
+ return out;
90
+ }
91
+ async function resolveBuckets({
92
+ mode,
93
+ sender,
94
+ bucketsFor
95
+ }) {
96
+ if (!bucketsFor) return void 0;
97
+ const identified = isIdentified(sender);
98
+ const requested = await bucketsFor(identified ? { mode, sender } : { mode });
99
+ if (!Array.isArray(requested)) return void 0;
100
+ const cleaned = normalize(requested);
101
+ if (identified) return cleaned;
102
+ const ceiling = new Set(defaultBuckets(mode));
103
+ return cleaned.filter((bucket) => ceiling.has(bucket));
104
+ }
105
+
75
106
  // src/core/prompts.ts
76
107
  import { readFileSync } from "node:fs";
77
108
  import { join } from "node:path";
@@ -280,8 +311,19 @@ var VectorStore = class _VectorStore {
280
311
  }
281
312
  return dot / (Math.sqrt(na) * Math.sqrt(nb));
282
313
  }
283
- // Retrieve top-k across allowed buckets; compute similarity in app (simple & portable).
314
+ /**
315
+ * Retrieve top-k across allowed buckets; compute similarity in app (simple
316
+ * & portable).
317
+ *
318
+ * `allowed` is a bucket-name list rather than the {@link Bucket} union: the
319
+ * `chunks` table constrains nothing, so a deployment that ingests its own
320
+ * buckets can query them (`build` only writes and prunes the three the
321
+ * knowledge loader walks). Names are bound as query parameters, never
322
+ * interpolated. An empty list retrieves nothing, and short-circuits before
323
+ * the embedding call.
324
+ */
284
325
  async query(q, k = 6, allowed = ["base"]) {
326
+ if (allowed.length === 0) return [];
285
327
  const qv = (await this.client.embeddings.create({ model: EMB_MODEL, input: [q] })).data[0].embedding;
286
328
  const placeholders = allowed.map(() => "?").join(",");
287
329
  const res = await this.db.execute({
@@ -526,7 +568,8 @@ async function createMCPServer(config) {
526
568
  if (!lastUserMsg) {
527
569
  throw new Error("No user message found in conversation");
528
570
  }
529
- const ctx = await store.query(lastUserMsg.content, 6, ["base", "public"]);
571
+ const buckets = await resolveBuckets({ mode: "public", bucketsFor: config.bucketsFor }) ?? defaultBuckets("public");
572
+ const ctx = await store.query(lastUserMsg.content, 6, buckets);
530
573
  const system = [
531
574
  prompts.baseSystemRules,
532
575
  prompts.publicPersona,
@@ -612,7 +655,8 @@ ${ctx.join("\n\n")}`
612
655
  if (!lastUserMsg) {
613
656
  throw new Error("No user message found in conversation");
614
657
  }
615
- const ctx = await store.query(lastUserMsg.content, 8, ["base", "private"]);
658
+ const buckets = await resolveBuckets({ mode: "private", bucketsFor: config.bucketsFor }) ?? defaultBuckets("private");
659
+ const ctx = await store.query(lastUserMsg.content, 8, buckets);
616
660
  const system = [
617
661
  prompts.baseSystemRules,
618
662
  prompts.privatePersona,
@@ -1,5 +1,15 @@
1
1
  import type { Context, Next } from "hono";
2
2
  import type { ChatterConfig } from "../types";
3
+ /**
4
+ * The verified JWT subject attached by {@link createJWTMiddleware}, or
5
+ * `undefined` when the request carried no identity (unauthenticated route, or
6
+ * a token whose payload had no `sub`).
7
+ *
8
+ * Surfaces use this as the sender identity for per-request policy such as
9
+ * retrieval bucket gating, where "no identity" must mean anonymous rather
10
+ * than an empty-string user.
11
+ */
12
+ export declare function jwtSubject(c: Context): string | undefined;
3
13
  /**
4
14
  * Create JWT authentication middleware for private API endpoints
5
15
  * Supports JWKS and public key PEM verification
@@ -1 +1 @@
1
- {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/middleware/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAO9C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,OAwBZ,OAAO,QAAQ,IAAI;;8BAa/D;AAGD,eAAO,MAAM,iBAAiB,MAhBe,OAAO,QAAQ,IAAI;;6BAgBS,CAAC"}
1
+ {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/middleware/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAO9C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,OAwBZ,OAAO,QAAQ,IAAI;;8BAa/D;AAGD,eAAO,MAAM,iBAAiB,MAhBe,OAAO,QAAQ,IAAI;;6BAgBS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/routes/demo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAsHnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,8EAiHlD"}
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../../src/routes/demo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAsHnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,8EAqHlD"}
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/routes/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAAE,KAAK,eAAe,EAAkC,MAAM,kBAAkB,CAAC;AAGxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,GAAG,IAAI,CA8BxE;AASD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EAgIpD"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/routes/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,OAAO,EAAE,KAAK,eAAe,EAAkC,MAAM,kBAAkB,CAAC;AAGxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,EAAE,GAAG,IAAI,CA8BxE;AASD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EAwIpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/routes/private.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,8EA6DrD"}
1
+ {"version":3,"file":"private.d.ts","sourceRoot":"","sources":["../../src/routes/private.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,8EAoErD"}
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/routes/public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAQ5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EAsEpD"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/routes/public.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AASnD,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,8EA0EpD"}
package/dist/types.d.ts CHANGED
@@ -8,6 +8,7 @@ import type { Client as LibsqlClient } from "@libsql/client";
8
8
  import type { Hono } from "hono";
9
9
  import type OpenAI from "openai";
10
10
  import type { AnswerFn } from "./core/answer";
11
+ import type { BucketsFor } from "./core/buckets";
11
12
  import type { PromptLoader } from "./core/prompts";
12
13
  import type { VectorStore } from "./core/retrieval";
13
14
  /**
@@ -156,6 +157,21 @@ export interface ChatterConfig {
156
157
  * errors. Unset: the built-in OpenAI completion is used.
157
158
  */
158
159
  answerFn?: AnswerFn;
160
+ /**
161
+ * Decides which knowledge buckets each chat turn may retrieve from, given
162
+ * the pipeline mode and — where the surface knows it — the sender's
163
+ * identity. Use it for role-gated knowledge: entitle a signed-in operator
164
+ * to buckets an anonymous visitor cannot see.
165
+ *
166
+ * Consulted by the chat routes, the OpenAI-compatible endpoints, the MCP
167
+ * chat tools and the demo route. Return `undefined` to keep the mode
168
+ * defaults (`base` plus the mode's own bucket). For a caller the surface
169
+ * could not identify, the answer is filtered down to those same defaults —
170
+ * the hook can narrow retrieval for an unnamed caller but never widen it.
171
+ *
172
+ * Unset: the mode defaults apply everywhere.
173
+ */
174
+ bucketsFor?: BucketsFor;
159
175
  /** Custom route handler for advanced use cases. May be async — see {@link CustomRoutes} */
160
176
  customRoutes?: CustomRoutes;
161
177
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Usage metering — daily spend guards for anything that costs money per call.
3
+ *
4
+ * Published as the `@diegoaltoworks/chatter/usage` subpath so the core install
5
+ * stays untouched by it. The limiter itself is pure; the Turso store is the
6
+ * shipped persistence binding and needs `@libsql/client`, which chatter
7
+ * already expects for retrieval.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ export type { DailyLimitCheck, DailyLimiter, DailyLimiterDeps, DailyLimitReason, DailyLimitsConfig, DailyLimitsStore, } from "./limits";
12
+ export { createDailyLimiter, GLOBAL_LIMIT_KEY, pickDailyLimit, utcDayKey } from "./limits";
13
+ export { createTursoUsageStore } from "./tursoStore";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/usage/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/usage/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GLOBAL_LIMIT_KEY: () => GLOBAL_LIMIT_KEY,
24
+ createDailyLimiter: () => createDailyLimiter,
25
+ createTursoUsageStore: () => createTursoUsageStore,
26
+ pickDailyLimit: () => pickDailyLimit,
27
+ utcDayKey: () => utcDayKey
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/usage/limits.ts
32
+ var GLOBAL_LIMIT_KEY = "global";
33
+ function utcDayKey(now) {
34
+ return new Date(now).toISOString().slice(0, 10);
35
+ }
36
+ function createDailyLimiter(config, deps) {
37
+ const now = deps.now ?? Date.now;
38
+ return {
39
+ async checkAndReserve(key) {
40
+ const day = utcDayKey(now());
41
+ const perKeyCount = await deps.store.incrementAndGet("key", key, day);
42
+ if (perKeyCount > config.perKeyDailyLimit) {
43
+ return { allowed: false, reason: "per-key" };
44
+ }
45
+ const globalCount = await deps.store.incrementAndGet("global", GLOBAL_LIMIT_KEY, day);
46
+ if (globalCount > config.globalDailyLimit) {
47
+ return { allowed: false, reason: "global" };
48
+ }
49
+ return { allowed: true, reason: null };
50
+ }
51
+ };
52
+ }
53
+ function pickDailyLimit(value, fallback) {
54
+ const trimmed = value?.trim();
55
+ if (trimmed === void 0 || trimmed === "") return fallback;
56
+ const parsed = Number(trimmed);
57
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
58
+ }
59
+
60
+ // src/usage/tursoStore.ts
61
+ var schemaReady = /* @__PURE__ */ new WeakMap();
62
+ function ensureUsageSchema(client, tableName) {
63
+ let perTable = schemaReady.get(client);
64
+ if (!perTable) {
65
+ perTable = /* @__PURE__ */ new Map();
66
+ schemaReady.set(client, perTable);
67
+ }
68
+ const tables = perTable;
69
+ let ready = tables.get(tableName);
70
+ if (!ready) {
71
+ ready = client.execute(
72
+ `CREATE TABLE IF NOT EXISTS ${tableName} (
73
+ scope TEXT NOT NULL,
74
+ key TEXT NOT NULL,
75
+ day TEXT NOT NULL,
76
+ count INTEGER NOT NULL DEFAULT 0,
77
+ PRIMARY KEY (scope, key, day)
78
+ )`
79
+ ).catch((error) => {
80
+ tables.delete(tableName);
81
+ throw error;
82
+ });
83
+ tables.set(tableName, ready);
84
+ }
85
+ return ready;
86
+ }
87
+ var VALID_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
88
+ function createTursoUsageStore(client, tableName) {
89
+ if (!VALID_TABLE_NAME.test(tableName)) {
90
+ throw new Error(`Invalid usage store table name: ${tableName}`);
91
+ }
92
+ return {
93
+ async incrementAndGet(scope, key, day) {
94
+ await ensureUsageSchema(client, tableName);
95
+ const result = await client.execute({
96
+ sql: `INSERT INTO ${tableName} (scope, key, day, count) VALUES (?, ?, ?, 1)
97
+ ON CONFLICT(scope, key, day) DO UPDATE SET count = count + 1
98
+ RETURNING count`,
99
+ args: [scope, key, day]
100
+ });
101
+ const row = result.rows[0];
102
+ if (!row) {
103
+ throw new Error(`${tableName} returned no row from upsert`);
104
+ }
105
+ return Number(row.count);
106
+ }
107
+ };
108
+ }
109
+ // Annotate the CommonJS export names for ESM import in node:
110
+ 0 && (module.exports = {
111
+ GLOBAL_LIMIT_KEY,
112
+ createDailyLimiter,
113
+ createTursoUsageStore,
114
+ pickDailyLimit,
115
+ utcDayKey
116
+ });
@@ -0,0 +1,85 @@
1
+ // src/usage/limits.ts
2
+ var GLOBAL_LIMIT_KEY = "global";
3
+ function utcDayKey(now) {
4
+ return new Date(now).toISOString().slice(0, 10);
5
+ }
6
+ function createDailyLimiter(config, deps) {
7
+ const now = deps.now ?? Date.now;
8
+ return {
9
+ async checkAndReserve(key) {
10
+ const day = utcDayKey(now());
11
+ const perKeyCount = await deps.store.incrementAndGet("key", key, day);
12
+ if (perKeyCount > config.perKeyDailyLimit) {
13
+ return { allowed: false, reason: "per-key" };
14
+ }
15
+ const globalCount = await deps.store.incrementAndGet("global", GLOBAL_LIMIT_KEY, day);
16
+ if (globalCount > config.globalDailyLimit) {
17
+ return { allowed: false, reason: "global" };
18
+ }
19
+ return { allowed: true, reason: null };
20
+ }
21
+ };
22
+ }
23
+ function pickDailyLimit(value, fallback) {
24
+ const trimmed = value?.trim();
25
+ if (trimmed === void 0 || trimmed === "") return fallback;
26
+ const parsed = Number(trimmed);
27
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
28
+ }
29
+
30
+ // src/usage/tursoStore.ts
31
+ var schemaReady = /* @__PURE__ */ new WeakMap();
32
+ function ensureUsageSchema(client, tableName) {
33
+ let perTable = schemaReady.get(client);
34
+ if (!perTable) {
35
+ perTable = /* @__PURE__ */ new Map();
36
+ schemaReady.set(client, perTable);
37
+ }
38
+ const tables = perTable;
39
+ let ready = tables.get(tableName);
40
+ if (!ready) {
41
+ ready = client.execute(
42
+ `CREATE TABLE IF NOT EXISTS ${tableName} (
43
+ scope TEXT NOT NULL,
44
+ key TEXT NOT NULL,
45
+ day TEXT NOT NULL,
46
+ count INTEGER NOT NULL DEFAULT 0,
47
+ PRIMARY KEY (scope, key, day)
48
+ )`
49
+ ).catch((error) => {
50
+ tables.delete(tableName);
51
+ throw error;
52
+ });
53
+ tables.set(tableName, ready);
54
+ }
55
+ return ready;
56
+ }
57
+ var VALID_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
58
+ function createTursoUsageStore(client, tableName) {
59
+ if (!VALID_TABLE_NAME.test(tableName)) {
60
+ throw new Error(`Invalid usage store table name: ${tableName}`);
61
+ }
62
+ return {
63
+ async incrementAndGet(scope, key, day) {
64
+ await ensureUsageSchema(client, tableName);
65
+ const result = await client.execute({
66
+ sql: `INSERT INTO ${tableName} (scope, key, day, count) VALUES (?, ?, ?, 1)
67
+ ON CONFLICT(scope, key, day) DO UPDATE SET count = count + 1
68
+ RETURNING count`,
69
+ args: [scope, key, day]
70
+ });
71
+ const row = result.rows[0];
72
+ if (!row) {
73
+ throw new Error(`${tableName} returned no row from upsert`);
74
+ }
75
+ return Number(row.count);
76
+ }
77
+ };
78
+ }
79
+ export {
80
+ GLOBAL_LIMIT_KEY,
81
+ createDailyLimiter,
82
+ createTursoUsageStore,
83
+ pickDailyLimit,
84
+ utcDayKey
85
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Daily spend guards — the counting half of "this feature costs money, cap it".
3
+ *
4
+ * Framework-free and storage-free: this module knows how to decide, never how
5
+ * to persist. A host supplies a {@link DailyLimitsStore} (see ./tursoStore for
6
+ * the shipped one) and instantiates one limiter per metered resource — image
7
+ * generation, voice minutes, anything with a per-unit price — by giving each
8
+ * its own store table. Nothing here is chat-specific.
9
+ */
10
+ /** The two caps every metered resource is subject to. */
11
+ export interface DailyLimitsConfig {
12
+ /** Units one key may reserve per UTC day. */
13
+ perKeyDailyLimit: number;
14
+ /** Units all keys together may reserve per UTC day. */
15
+ globalDailyLimit: number;
16
+ }
17
+ /** The key the global counter is stored under — that scope has no caller key. */
18
+ export declare const GLOBAL_LIMIT_KEY = "global";
19
+ /** Which cap blocked the request, or null when it was allowed. */
20
+ export type DailyLimitReason = "per-key" | "global" | null;
21
+ export interface DailyLimitCheck {
22
+ allowed: boolean;
23
+ reason: DailyLimitReason;
24
+ }
25
+ /**
26
+ * Structural dependency — a host implements this against its own storage.
27
+ *
28
+ * Must increment and read back atomically. Deployments run multiple instances,
29
+ * so a read-then-write from this process would race with another instance's
30
+ * and both would see room under a cap that only had space for one.
31
+ */
32
+ export interface DailyLimitsStore {
33
+ /** Increments the counter for (scope, key, day) and returns the count after incrementing. */
34
+ incrementAndGet(scope: "key" | "global", key: string, day: string): Promise<number>;
35
+ }
36
+ /** The UTC calendar day counters roll over on, e.g. "2026-08-01". */
37
+ export declare function utcDayKey(now: number): string;
38
+ export interface DailyLimiterDeps {
39
+ store: DailyLimitsStore;
40
+ /** Clock, injectable so day-rollover tests don't depend on real time. */
41
+ now?: () => number;
42
+ }
43
+ export interface DailyLimiter {
44
+ /**
45
+ * Reserves one unit for `key`, subject to both daily caps.
46
+ *
47
+ * Call this exactly once per logical request: the increment is permanent and
48
+ * unconditional (there is no release path), so a duplicate call — or a retry
49
+ * after a transient failure — permanently burns a second unit of quota for
50
+ * one request never delivered. Blocked calls still count: a call that trips
51
+ * the per-key cap still consumes one of that key's daily units, and (by
52
+ * design — see below) a call that trips the global cap after passing the
53
+ * per-key check also consumes one of that key's units, for nothing.
54
+ *
55
+ * The per-key counter increments before the global one, so a key blocked at
56
+ * its own cap never touches the shared global budget. The mirror case is
57
+ * accepted, not fixed: once the global cap is exhausted for the day, every
58
+ * further caller still burns one of their own per-key units on the way to
59
+ * being told no. This favours guarding against the more common cost risk
60
+ * (one key hammering the endpoint) over the rarer one (the global cap
61
+ * saturating and clipping everyone's personal quota).
62
+ *
63
+ * Reserve before spending, not after: the ordering is what makes the cap a
64
+ * cap. Callers with a cache should check it first, so a cache hit costs
65
+ * neither money nor quota.
66
+ */
67
+ checkAndReserve(key: string): Promise<DailyLimitCheck>;
68
+ }
69
+ export declare function createDailyLimiter(config: DailyLimitsConfig, deps: DailyLimiterDeps): DailyLimiter;
70
+ /**
71
+ * Reads a cap out of loosely-typed configuration (an env var, a JSON field).
72
+ *
73
+ * A configured non-negative integer wins over the default; anything else —
74
+ * unset, blank, negative, fractional, unparseable — falls back. A malformed
75
+ * cap must not silently become `NaN`, which compares false against every
76
+ * count and would disable the guard entirely.
77
+ *
78
+ * Blank is trimmed before parsing rather than compared raw: `Number(" ")` is
79
+ * `0`, so a config value that is whitespace would otherwise be read as a cap
80
+ * of zero and switch the feature off entirely instead of falling back.
81
+ *
82
+ * This is the supported way to build a {@link DailyLimitsConfig}. The limiter
83
+ * itself does not validate its caps, so a `NaN` reaching it from somewhere
84
+ * else fails open.
85
+ */
86
+ export declare function pickDailyLimit(value: string | undefined, fallback: number): number;
87
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/usage/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,WAAW,CAAC;AAEzC,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6FAA6F;IAC7F,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrF;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,gBAAgB,CAAC;IACxB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACxD;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,gBAAgB,GACrB,YAAY,CAoBd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKlF"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Daily limiter tests.
3
+ *
4
+ * Two properties matter more than the arithmetic and are asserted directly:
5
+ * the *order* of the two increments (a key blocked at its own cap must not
6
+ * burn shared budget), and the reserve-once semantics (every call counts,
7
+ * including blocked ones — there is no release path to lean on).
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=limits.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.test.d.ts","sourceRoot":"","sources":["../../src/usage/limits.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Turso/libsql-backed {@link DailyLimitsStore}.
3
+ *
4
+ * Schema and upsert only — error typing and wrapping stay with the caller,
5
+ * because each metered feature wants its own error contract and this layer
6
+ * should not invent one for them.
7
+ *
8
+ * `@libsql/client` is imported for its type alone, so this module adds no
9
+ * runtime dependency to anyone importing `./usage` for the pure limiter.
10
+ */
11
+ import type { Client } from "@libsql/client";
12
+ import type { DailyLimitsStore } from "./limits";
13
+ /**
14
+ * Creates a store that counts into `tableName` on `client`.
15
+ *
16
+ * The upsert is a single atomic statement returning the post-increment count,
17
+ * so concurrent instances each get a distinct number and the cap holds across
18
+ * a multi-instance deployment. Table creation is idempotent and happens once
19
+ * per (client, table).
20
+ *
21
+ * Give each metered resource its own table — sharing one would make separate
22
+ * features compete for a single global counter.
23
+ */
24
+ export declare function createTursoUsageStore(client: Client, tableName: string): DailyLimitsStore;
25
+ //# sourceMappingURL=tursoStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tursoStore.d.ts","sourceRoot":"","sources":["../../src/usage/tursoStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAyCjD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAuBzF"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Turso usage store tests.
3
+ *
4
+ * Run against a real in-memory libsql client rather than a fake, because the
5
+ * properties worth proving here are SQL-level: the upsert is atomic, it hands
6
+ * every concurrent caller a distinct number, and table creation is idempotent.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=tursoStore.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tursoStore.test.d.ts","sourceRoot":"","sources":["../../src/usage/tursoStore.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}