@drm3/sdk 0.1.2

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.
Files changed (228) hide show
  1. package/LICENSE +75 -0
  2. package/README.md +426 -0
  3. package/bin/drm3.js +255 -0
  4. package/dist/alerts-bell.d.ts +7 -0
  5. package/dist/alerts-bell.d.ts.map +1 -0
  6. package/dist/alerts-bell.js +118 -0
  7. package/dist/alerts-bell.js.map +1 -0
  8. package/dist/alerts-fetch.d.ts +41 -0
  9. package/dist/alerts-fetch.d.ts.map +1 -0
  10. package/dist/alerts-fetch.js +60 -0
  11. package/dist/alerts-fetch.js.map +1 -0
  12. package/dist/alerts-styles.d.ts +4 -0
  13. package/dist/alerts-styles.d.ts.map +1 -0
  14. package/dist/alerts-styles.js +49 -0
  15. package/dist/alerts-styles.js.map +1 -0
  16. package/dist/alerts.d.ts +3 -0
  17. package/dist/alerts.d.ts.map +1 -0
  18. package/dist/alerts.global.js +206 -0
  19. package/dist/alerts.global.text.js +2 -0
  20. package/dist/alerts.js +21 -0
  21. package/dist/alerts.js.map +1 -0
  22. package/dist/index.d.ts +64 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +117 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/keys-canonical.d.ts +7 -0
  27. package/dist/keys-canonical.d.ts.map +1 -0
  28. package/dist/keys-canonical.js +57 -0
  29. package/dist/keys-canonical.js.map +1 -0
  30. package/dist/keys-generate.d.ts +39 -0
  31. package/dist/keys-generate.d.ts.map +1 -0
  32. package/dist/keys-generate.js +39 -0
  33. package/dist/keys-generate.js.map +1 -0
  34. package/dist/keys-verify.d.ts +62 -0
  35. package/dist/keys-verify.d.ts.map +1 -0
  36. package/dist/keys-verify.js +68 -0
  37. package/dist/keys-verify.js.map +1 -0
  38. package/dist/keys.d.ts +4 -0
  39. package/dist/keys.d.ts.map +1 -0
  40. package/dist/keys.js +22 -0
  41. package/dist/keys.js.map +1 -0
  42. package/dist/meter/adapter.d.ts +35 -0
  43. package/dist/meter/adapter.d.ts.map +1 -0
  44. package/dist/meter/adapter.js +0 -0
  45. package/dist/meter/adapter.js.map +1 -0
  46. package/dist/meter/cascade.d.ts +56 -0
  47. package/dist/meter/cascade.d.ts.map +1 -0
  48. package/dist/meter/cascade.js +64 -0
  49. package/dist/meter/cascade.js.map +1 -0
  50. package/dist/meter/client.d.ts +78 -0
  51. package/dist/meter/client.d.ts.map +1 -0
  52. package/dist/meter/client.js +84 -0
  53. package/dist/meter/client.js.map +1 -0
  54. package/dist/meter/core.d.ts +37 -0
  55. package/dist/meter/core.d.ts.map +1 -0
  56. package/dist/meter/core.js +49 -0
  57. package/dist/meter/core.js.map +1 -0
  58. package/dist/meter/gate-client.d.ts +94 -0
  59. package/dist/meter/gate-client.d.ts.map +1 -0
  60. package/dist/meter/gate-client.js +77 -0
  61. package/dist/meter/gate-client.js.map +1 -0
  62. package/dist/meter/gate-decide.d.ts +61 -0
  63. package/dist/meter/gate-decide.d.ts.map +1 -0
  64. package/dist/meter/gate-decide.js +54 -0
  65. package/dist/meter/gate-decide.js.map +1 -0
  66. package/dist/meter/gate.d.ts +10 -0
  67. package/dist/meter/gate.d.ts.map +1 -0
  68. package/dist/meter/gate.js +10 -0
  69. package/dist/meter/gate.js.map +1 -0
  70. package/dist/meter/index.d.ts +28 -0
  71. package/dist/meter/index.d.ts.map +1 -0
  72. package/dist/meter/index.js +28 -0
  73. package/dist/meter/index.js.map +1 -0
  74. package/dist/meter/meters-buckets.d.ts +56 -0
  75. package/dist/meter/meters-buckets.d.ts.map +1 -0
  76. package/dist/meter/meters-buckets.js +111 -0
  77. package/dist/meter/meters-buckets.js.map +1 -0
  78. package/dist/meter/meters-charge.d.ts +13 -0
  79. package/dist/meter/meters-charge.d.ts.map +1 -0
  80. package/dist/meter/meters-charge.js +76 -0
  81. package/dist/meter/meters-charge.js.map +1 -0
  82. package/dist/meter/meters.d.ts +63 -0
  83. package/dist/meter/meters.d.ts.map +1 -0
  84. package/dist/meter/meters.js +44 -0
  85. package/dist/meter/meters.js.map +1 -0
  86. package/dist/meter/plans.d.ts +63 -0
  87. package/dist/meter/plans.d.ts.map +1 -0
  88. package/dist/meter/plans.js +114 -0
  89. package/dist/meter/plans.js.map +1 -0
  90. package/dist/meter/pricebook.d.ts +32 -0
  91. package/dist/meter/pricebook.d.ts.map +1 -0
  92. package/dist/meter/pricebook.js +38 -0
  93. package/dist/meter/pricebook.js.map +1 -0
  94. package/dist/meter/resolve.d.ts +103 -0
  95. package/dist/meter/resolve.d.ts.map +1 -0
  96. package/dist/meter/resolve.js +100 -0
  97. package/dist/meter/resolve.js.map +1 -0
  98. package/dist/meter/sink.d.ts +26 -0
  99. package/dist/meter/sink.d.ts.map +1 -0
  100. package/dist/meter/sink.js +9 -0
  101. package/dist/meter/sink.js.map +1 -0
  102. package/dist/meter/sql/d1.d.ts +32 -0
  103. package/dist/meter/sql/d1.d.ts.map +1 -0
  104. package/dist/meter/sql/d1.js +41 -0
  105. package/dist/meter/sql/d1.js.map +1 -0
  106. package/dist/meter/sql/driver.d.ts +34 -0
  107. package/dist/meter/sql/driver.d.ts.map +1 -0
  108. package/dist/meter/sql/driver.js +17 -0
  109. package/dist/meter/sql/driver.js.map +1 -0
  110. package/dist/meter/sql/postgres.d.ts +23 -0
  111. package/dist/meter/sql/postgres.d.ts.map +1 -0
  112. package/dist/meter/sql/postgres.js +36 -0
  113. package/dist/meter/sql/postgres.js.map +1 -0
  114. package/dist/meter/sql/schema.d.ts +13 -0
  115. package/dist/meter/sql/schema.d.ts.map +1 -0
  116. package/dist/meter/sql/schema.js +32 -0
  117. package/dist/meter/sql/schema.js.map +1 -0
  118. package/dist/meter/sql/sql-adapter.d.ts +36 -0
  119. package/dist/meter/sql/sql-adapter.d.ts.map +1 -0
  120. package/dist/meter/sql/sql-adapter.js +124 -0
  121. package/dist/meter/sql/sql-adapter.js.map +1 -0
  122. package/dist/meter/sql/statements.d.ts +30 -0
  123. package/dist/meter/sql/statements.d.ts.map +1 -0
  124. package/dist/meter/sql/statements.js +24 -0
  125. package/dist/meter/sql/statements.js.map +1 -0
  126. package/dist/meter/types.d.ts +48 -0
  127. package/dist/meter/types.d.ts.map +1 -0
  128. package/dist/meter/types.js +8 -0
  129. package/dist/meter/types.js.map +1 -0
  130. package/dist/meter/units.d.ts +33 -0
  131. package/dist/meter/units.d.ts.map +1 -0
  132. package/dist/meter/units.js +45 -0
  133. package/dist/meter/units.js.map +1 -0
  134. package/dist/meter/wallet-buckets.d.ts +63 -0
  135. package/dist/meter/wallet-buckets.d.ts.map +1 -0
  136. package/dist/meter/wallet-buckets.js +131 -0
  137. package/dist/meter/wallet-buckets.js.map +1 -0
  138. package/dist/meter/wallet-charge.d.ts +15 -0
  139. package/dist/meter/wallet-charge.d.ts.map +1 -0
  140. package/dist/meter/wallet-charge.js +64 -0
  141. package/dist/meter/wallet-charge.js.map +1 -0
  142. package/dist/meter/wallet.d.ts +68 -0
  143. package/dist/meter/wallet.d.ts.map +1 -0
  144. package/dist/meter/wallet.js +47 -0
  145. package/dist/meter/wallet.js.map +1 -0
  146. package/dist/receipts-api.d.ts +5 -0
  147. package/dist/receipts-api.d.ts.map +1 -0
  148. package/dist/receipts-api.js +117 -0
  149. package/dist/receipts-api.js.map +1 -0
  150. package/dist/receipts-chain.d.ts +3 -0
  151. package/dist/receipts-chain.d.ts.map +1 -0
  152. package/dist/receipts-chain.js +121 -0
  153. package/dist/receipts-chain.js.map +1 -0
  154. package/dist/receipts-modal.d.ts +14 -0
  155. package/dist/receipts-modal.d.ts.map +1 -0
  156. package/dist/receipts-modal.js +100 -0
  157. package/dist/receipts-modal.js.map +1 -0
  158. package/dist/receipts-registry.d.ts +4 -0
  159. package/dist/receipts-registry.d.ts.map +1 -0
  160. package/dist/receipts-registry.js +54 -0
  161. package/dist/receipts-registry.js.map +1 -0
  162. package/dist/receipts-render.d.ts +9 -0
  163. package/dist/receipts-render.d.ts.map +1 -0
  164. package/dist/receipts-render.js +83 -0
  165. package/dist/receipts-render.js.map +1 -0
  166. package/dist/receipts-types.d.ts +61 -0
  167. package/dist/receipts-types.d.ts.map +1 -0
  168. package/dist/receipts-types.js +12 -0
  169. package/dist/receipts-types.js.map +1 -0
  170. package/dist/receipts-ui.d.ts +10 -0
  171. package/dist/receipts-ui.d.ts.map +1 -0
  172. package/dist/receipts-ui.js +103 -0
  173. package/dist/receipts-ui.js.map +1 -0
  174. package/dist/receipts-verify.d.ts +8 -0
  175. package/dist/receipts-verify.d.ts.map +1 -0
  176. package/dist/receipts-verify.js +113 -0
  177. package/dist/receipts-verify.js.map +1 -0
  178. package/dist/receipts.d.ts +9 -0
  179. package/dist/receipts.d.ts.map +1 -0
  180. package/dist/receipts.global.js +587 -0
  181. package/dist/receipts.global.text.js +2 -0
  182. package/dist/receipts.js +28 -0
  183. package/dist/receipts.js.map +1 -0
  184. package/dist/sso/index.d.ts +4 -0
  185. package/dist/sso/index.d.ts.map +1 -0
  186. package/dist/sso/index.js +10 -0
  187. package/dist/sso/index.js.map +1 -0
  188. package/dist/sso/jwt.d.ts +29 -0
  189. package/dist/sso/jwt.d.ts.map +1 -0
  190. package/dist/sso/jwt.js +73 -0
  191. package/dist/sso/jwt.js.map +1 -0
  192. package/dist/sso/launch.d.ts +70 -0
  193. package/dist/sso/launch.d.ts.map +1 -0
  194. package/dist/sso/launch.js +75 -0
  195. package/dist/sso/launch.js.map +1 -0
  196. package/dist/sso/revocation.d.ts +23 -0
  197. package/dist/sso/revocation.d.ts.map +1 -0
  198. package/dist/sso/revocation.js +53 -0
  199. package/dist/sso/revocation.js.map +1 -0
  200. package/dist/switcher.d.ts +22 -0
  201. package/dist/switcher.d.ts.map +1 -0
  202. package/dist/switcher.js +174 -0
  203. package/dist/switcher.js.map +1 -0
  204. package/dist/x402/chain.d.ts +49 -0
  205. package/dist/x402/chain.d.ts.map +1 -0
  206. package/dist/x402/chain.js +33 -0
  207. package/dist/x402/chain.js.map +1 -0
  208. package/dist/x402/crypto.d.ts +25 -0
  209. package/dist/x402/crypto.d.ts.map +1 -0
  210. package/dist/x402/crypto.js +74 -0
  211. package/dist/x402/crypto.js.map +1 -0
  212. package/dist/x402/eip712.d.ts +20 -0
  213. package/dist/x402/eip712.d.ts.map +1 -0
  214. package/dist/x402/eip712.js +49 -0
  215. package/dist/x402/eip712.js.map +1 -0
  216. package/dist/x402/envelope.d.ts +45 -0
  217. package/dist/x402/envelope.d.ts.map +1 -0
  218. package/dist/x402/envelope.js +50 -0
  219. package/dist/x402/envelope.js.map +1 -0
  220. package/dist/x402/index.d.ts +41 -0
  221. package/dist/x402/index.d.ts.map +1 -0
  222. package/dist/x402/index.js +41 -0
  223. package/dist/x402/index.js.map +1 -0
  224. package/dist/x402/verify.d.ts +51 -0
  225. package/dist/x402/verify.d.ts.map +1 -0
  226. package/dist/x402/verify.js +110 -0
  227. package/dist/x402/verify.js.map +1 -0
  228. package/package.json +100 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * resolve - the OUTER cascade: a wallet is a cascade of meters.
3
+ *
4
+ * The inner `cascade()` draws across the BUCKETS of one meter (daily → monthly →
5
+ * buyup). This layer draws across the METERS of a wallet: the feature meters
6
+ * first (most-specific), then the generic credits meter as the terminal fallback.
7
+ * It is the same floor-and-spill idea one level up, with ONE subtlety the inner
8
+ * cascade doesn't have - the UNIT SEAM.
9
+ *
10
+ * Feature meters count CALLS. The credits meter counts µCREDITS. So when a
11
+ * feature call spills past its own meter (the allowance is exhausted), it does
12
+ * not carry "calls" into the credits meter - it converts: each unmet call costs
13
+ * `creditPricePerUnit` µcredits, and that credit demand flows into the credits
14
+ * cascade. "Use the feature allowance first because it's more specific; only
15
+ * what it can't cover costs credits."
16
+ *
17
+ * Resolution for one request:
18
+ * 1. Feature demand + a matching feature meter? Draw the calls from its buckets
19
+ * (daily → monthly → buyup), FLOORED (overdraft is terminal-only, so a
20
+ * feature buyup floors and spills rather than going negative). The unmet
21
+ * calls convert to a credit demand.
22
+ * 2. Feature demand + NO matching meter? The whole call bills credits directly
23
+ * (units × creditPricePerUnit) - features without an allowance just cost
24
+ * credits.
25
+ * 3. Generic credit demand? That µcredit amount IS the credit demand.
26
+ * 4. Draw the credit demand from the credits meter's buckets (daily → monthly →
27
+ * buyup), with the buyup OVERDRAFTING (it is the one terminal node).
28
+ *
29
+ * Gating (entitlement, "unlimited") and the always-on cost accounting live ABOVE
30
+ * this: see `creditCost()` for the cost that is logged on every call regardless of
31
+ * what (if anything) is debited. This module is pure integer math - no I/O, no
32
+ * storage, no clock. The DB-backed Wallet composes it.
33
+ */
34
+ import { cascade } from "./cascade.js";
35
+ const pos = (x) => (x > 0n ? x : 0n);
36
+ /** The credit cost of a demand, independent of any balance or meter - the meter
37
+ * reading that is ALWAYS logged (accounting ≠ debiting). For a feature call this
38
+ * is `units × price` even when an allowance will cover it for free; for a credit
39
+ * demand it is the amount itself. */
40
+ export function creditCost(demand) {
41
+ if ("feature" in demand)
42
+ return pos(demand.units ?? 1n) * pos(demand.creditPricePerUnit);
43
+ return pos(demand.ucredits);
44
+ }
45
+ function tally(byBucket, meterKey, draws) {
46
+ for (const d of draws) {
47
+ if (d.amount > 0n)
48
+ byBucket[`${meterKey}.${d.key}`] = (byBucket[`${meterKey}.${d.key}`] ?? 0n) + d.amount;
49
+ }
50
+ }
51
+ /**
52
+ * Draw a demand across the wallet's meters. Pure: input meters are never mutated.
53
+ *
54
+ * @param meters ordered meters - feature meters first, the terminal credits meter LAST.
55
+ * @param demand a feature call or a raw credit charge.
56
+ * @param opts.terminalOverdraft whether the credits buyup may go negative (default
57
+ * true - the production rail buys-back-up). false makes the whole wallet floor
58
+ * and report the unmet amount as `shortfall` (useful for a dry-run gate check).
59
+ */
60
+ export function resolveCharge(meters, demand, opts = {}) {
61
+ if (meters.length === 0)
62
+ throw new Error("resolveCharge: need at least the terminal credits meter");
63
+ const lastIdx = meters.length - 1;
64
+ const credits = meters[lastIdx];
65
+ const meterDraws = [];
66
+ const byBucket = {};
67
+ let creditsDemand;
68
+ if ("feature" in demand) {
69
+ const units = pos(demand.units ?? 1n);
70
+ // A feature meter only counts if it is not the terminal credits meter itself.
71
+ const fm = meters.find((m, i) => i !== lastIdx && m.key === demand.feature);
72
+ if (fm) {
73
+ // Floor (terminalOverdraft:false): even a feature buyup floors and spills -
74
+ // overdraft is terminal-only, reserved for the credits buyup.
75
+ const r = cascade(fm.buckets, units, { terminalOverdraft: false });
76
+ meterDraws.push({ key: fm.key, unit: fm.unit, draws: r.draws, applied: r.applied, overflow: r.shortfall });
77
+ tally(byBucket, fm.key, r.draws);
78
+ creditsDemand = r.shortfall * pos(demand.creditPricePerUnit); // unmet calls → credits
79
+ }
80
+ else {
81
+ // No allowance meter for this feature → the whole call bills credits.
82
+ creditsDemand = units * pos(demand.creditPricePerUnit);
83
+ }
84
+ }
85
+ else {
86
+ creditsDemand = pos(demand.ucredits);
87
+ }
88
+ const terminalOverdraft = opts.terminalOverdraft ?? true;
89
+ const cr = cascade(credits.buckets, creditsDemand, { terminalOverdraft });
90
+ meterDraws.push({ key: credits.key, unit: credits.unit, draws: cr.draws, applied: cr.applied, overflow: cr.shortfall });
91
+ tally(byBucket, credits.key, cr.draws);
92
+ return {
93
+ meters: meterDraws,
94
+ creditsCharged: cr.applied,
95
+ overdraft: cr.overdraft,
96
+ shortfall: cr.shortfall,
97
+ byBucket,
98
+ };
99
+ }
100
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/meter/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SettlementSink - where net spend goes at settlement time. The ONLY place a
3
+ * chain can ever appear. Default NullSink = pure off-chain; nothing leaves the
4
+ * box. A Base/Stellar sink is a later drop-in with the same shape. Settlement is
5
+ * triggered lazily (low-water-mark / batch threshold / schedule), never per call.
6
+ */
7
+ import type { App, Credits, Tenant } from "./types.js";
8
+ export interface SettlementBatch {
9
+ tenant: Tenant;
10
+ app: App;
11
+ /** net credits spent in this batch */
12
+ netDebit: Credits;
13
+ /** opaque batch reference; settlement is idempotent by this */
14
+ batchRef: string;
15
+ /** ids of the captured reservations rolled up */
16
+ captureIds: string[];
17
+ }
18
+ export interface SettlementSink {
19
+ settle(batch: SettlementBatch): Promise<void>;
20
+ }
21
+ /** Records batches in memory, sends nothing external. The off-chain default. */
22
+ export declare class NullSink implements SettlementSink {
23
+ readonly batches: SettlementBatch[];
24
+ settle(batch: SettlementBatch): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.d.ts","sourceRoot":"","sources":["../../src/meter/sink.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,GAAG,CAAC;IACT,sCAAsC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,gFAAgF;AAChF,qBAAa,QAAS,YAAW,cAAc;IAC7C,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,CAAM;IACzC,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;CAI9C"}
@@ -0,0 +1,9 @@
1
+ /** Records batches in memory, sends nothing external. The off-chain default. */
2
+ export class NullSink {
3
+ batches = [];
4
+ settle(batch) {
5
+ this.batches.push(batch);
6
+ return Promise.resolve();
7
+ }
8
+ }
9
+ //# sourceMappingURL=sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink.js","sourceRoot":"","sources":["../../src/meter/sink.ts"],"names":[],"mappings":"AAuBA,gFAAgF;AAChF,MAAM,OAAO,QAAQ;IACV,OAAO,GAAsB,EAAE,CAAC;IACzC,MAAM,CAAC,KAAsB;QAC3B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ import type { SqlDriver } from "./driver.js";
2
+ /**
3
+ * D1 driver + adapter. Cloudflare D1 speaks SQLite, so it consumes the adapter's
4
+ * `?` placeholders and `MAX(x, 0)` scalar verbatim. Bind bigints as Numbers -
5
+ * D1 columns are 64-bit INTEGER and credits stay well within Number's safe range
6
+ * for any realistic balance; reads come back as Number and `toBig` re-widens them.
7
+ *
8
+ * Usage in a Worker:
9
+ * const meter = new CreditsClient({ adapter: await d1Adapter(env.DB) });
10
+ */
11
+ import { SqlStorageAdapter } from "./sql-adapter.js";
12
+ /** The slice of Cloudflare's D1Database we use - declared locally so the package
13
+ * carries no @cloudflare/workers-types dependency. */
14
+ export interface D1Like {
15
+ prepare(sql: string): {
16
+ bind(...params: unknown[]): {
17
+ run(): Promise<{
18
+ meta: {
19
+ changes: number;
20
+ };
21
+ }>;
22
+ all<T = unknown>(): Promise<{
23
+ results: T[];
24
+ }>;
25
+ };
26
+ };
27
+ exec(sql: string): Promise<unknown>;
28
+ }
29
+ export declare function d1Driver(db: D1Like): SqlDriver;
30
+ /** Build + initialize a meter storage adapter over a Cloudflare D1 binding. */
31
+ export declare function d1Adapter(db: D1Like): Promise<SqlStorageAdapter>;
32
+ //# sourceMappingURL=d1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../../src/meter/sql/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C;;;;;;;;GAQG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;uDACuD;AACvD,MAAM,WAAW,MAAM;IACrB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QACpB,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG;YAC1B,GAAG,IAAI,OAAO,CAAC;gBAAE,IAAI,EAAE;oBAAE,OAAO,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAC,CAAC;YAC9C,GAAG,CAAC,CAAC,GAAG,OAAO,KAAK,OAAO,CAAC;gBAAE,OAAO,EAAE,CAAC,EAAE,CAAA;aAAE,CAAC,CAAC;SAC/C,CAAC;KACH,CAAC;IACF,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrC;AAID,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAsB9C;AAED,+EAA+E;AAC/E,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAItE"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * D1 driver + adapter. Cloudflare D1 speaks SQLite, so it consumes the adapter's
3
+ * `?` placeholders and `MAX(x, 0)` scalar verbatim. Bind bigints as Numbers -
4
+ * D1 columns are 64-bit INTEGER and credits stay well within Number's safe range
5
+ * for any realistic balance; reads come back as Number and `toBig` re-widens them.
6
+ *
7
+ * Usage in a Worker:
8
+ * const meter = new CreditsClient({ adapter: await d1Adapter(env.DB) });
9
+ */
10
+ import { SqlStorageAdapter } from "./sql-adapter.js";
11
+ const bindable = (params) => params.map((p) => (typeof p === "bigint" ? Number(p) : p));
12
+ export function d1Driver(db) {
13
+ return {
14
+ dialect: "sqlite",
15
+ async all(sql, params = []) {
16
+ const { results } = await db
17
+ .prepare(sql)
18
+ .bind(...bindable(params))
19
+ .all();
20
+ return results;
21
+ },
22
+ async run(sql, params = []) {
23
+ const { meta } = await db
24
+ .prepare(sql)
25
+ .bind(...bindable(params))
26
+ .run();
27
+ return { rowsAffected: meta.changes };
28
+ },
29
+ async exec(sql) {
30
+ // D1.exec runs a single statement; the DDL is split into single statements.
31
+ await db.prepare(sql).bind().run();
32
+ },
33
+ };
34
+ }
35
+ /** Build + initialize a meter storage adapter over a Cloudflare D1 binding. */
36
+ export async function d1Adapter(db) {
37
+ const a = new SqlStorageAdapter(d1Driver(db));
38
+ await a.init();
39
+ return a;
40
+ }
41
+ //# sourceMappingURL=d1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.js","sourceRoot":"","sources":["../../../src/meter/sql/d1.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAcrD,MAAM,QAAQ,GAAG,CAAC,MAA0B,EAAa,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEvH,MAAM,UAAU,QAAQ,CAAC,EAAU;IACjC,OAAO;QACL,OAAO,EAAE,QAAQ;QACjB,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,SAA6B,EAAE;YACvD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;iBACzB,OAAO,CAAC,GAAG,CAAC;iBACZ,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;iBACzB,GAAG,EAAK,CAAC;YACZ,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAA6B,EAAE;YACpD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE;iBACtB,OAAO,CAAC,GAAG,CAAC;iBACZ,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;iBACzB,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACxC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAW;YACpB,4EAA4E;YAC5E,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAU;IACxC,MAAM,CAAC,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACf,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SqlDriver - the thin seam between the dialect-neutral SqlStorageAdapter and a
3
+ * concrete database (Cloudflare D1, node:sqlite, node-postgres, PGlite, …).
4
+ *
5
+ * The adapter writes SQL with `?` positional placeholders and dialect-neutral
6
+ * scalar-max (`MAX(x, 0)` for SQLite, rewritten to `GREATEST(x, 0)` by the
7
+ * adapter via `dialect`). A Postgres driver rewrites `?` → `$1, $2, …` and
8
+ * stringifies bigint params (pg has no native bigint binding). A SQLite driver
9
+ * passes `?` and bigint through unchanged.
10
+ *
11
+ * Drivers expose exactly three verbs. There is no interactive transaction here:
12
+ * every multi-row invariant in the adapter is enforced by a single conditional
13
+ * UPDATE whose row count tells us whether we won the race, so the engine's own
14
+ * row lock is the atomicity boundary - identical on SQLite and Postgres, and
15
+ * the one thing D1 (no interactive transactions) can also do.
16
+ */
17
+ export type Dialect = "sqlite" | "postgres";
18
+ export interface SqlDriver {
19
+ readonly dialect: Dialect;
20
+ /** Run a SELECT; return all rows as plain objects. */
21
+ all<T = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
22
+ /** Run an INSERT/UPDATE/DELETE; return how many rows it changed. */
23
+ run(sql: string, params?: readonly unknown[]): Promise<{
24
+ rowsAffected: number;
25
+ }>;
26
+ /** Run DDL (no params, may contain a single statement). */
27
+ exec(sql: string): Promise<void>;
28
+ }
29
+ /** Coerce a DB integer (number | bigint | numeric-string | null) to bigint. */
30
+ export declare function toBig(v: unknown): bigint;
31
+ /** Rewrite `?` placeholders to Postgres `$1, $2, …`. Ignores `?` inside literals
32
+ * - the adapter never embeds a literal `?`, so a plain counter is safe here. */
33
+ export declare function toPgPlaceholders(sql: string): string;
34
+ //# sourceMappingURL=driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../../src/meter/sql/driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,sDAAsD;IACtD,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACzF,oEAAoE;IACpE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjF,2DAA2D;IAC3D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,+EAA+E;AAC/E,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAKxC;AAED;iFACiF;AACjF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpD"}
@@ -0,0 +1,17 @@
1
+ /** Coerce a DB integer (number | bigint | numeric-string | null) to bigint. */
2
+ export function toBig(v) {
3
+ if (typeof v === "bigint")
4
+ return v;
5
+ if (typeof v === "number")
6
+ return BigInt(Math.trunc(v));
7
+ if (typeof v === "string" && v.length > 0)
8
+ return BigInt(v);
9
+ return 0n;
10
+ }
11
+ /** Rewrite `?` placeholders to Postgres `$1, $2, …`. Ignores `?` inside literals
12
+ * - the adapter never embeds a literal `?`, so a plain counter is safe here. */
13
+ export function toPgPlaceholders(sql) {
14
+ let i = 0;
15
+ return sql.replace(/\?/g, () => `$${++i}`);
16
+ }
17
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/meter/sql/driver.ts"],"names":[],"mappings":"AA4BA,+EAA+E;AAC/E,MAAM,UAAU,KAAK,CAAC,CAAU;IAC9B,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;iFACiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type SqlDriver } from "./driver.js";
2
+ /**
3
+ * Postgres driver + adapter. Rewrites `?` → `$1, $2, …`, swaps `MAX(x, 0)` for
4
+ * `GREATEST(x, 0)` (handled in the adapter via dialect), and stringifies bigint
5
+ * params (node-postgres has no native bigint binding; BIGINT comes back as a
6
+ * numeric string, which `toBig` parses exactly - no precision loss).
7
+ *
8
+ * Works with anything exposing node-postgres's `query(text, values)` shape: a
9
+ * `pg.Pool`, a `pg.Client`, or PGlite. Usage:
10
+ * const meter = new CreditsClient({ adapter: await pgAdapter(pool) });
11
+ */
12
+ import { SqlStorageAdapter } from "./sql-adapter.js";
13
+ /** The slice of a node-postgres connection we use. PGlite matches this too. */
14
+ export interface PgLike {
15
+ query(text: string, values?: unknown[]): Promise<{
16
+ rows: unknown[];
17
+ rowCount: number | null;
18
+ }>;
19
+ }
20
+ export declare function pgDriver(pool: PgLike): SqlDriver;
21
+ /** Build + initialize a meter storage adapter over a Postgres connection/pool. */
22
+ export declare function pgAdapter(pool: PgLike): Promise<SqlStorageAdapter>;
23
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../../src/meter/sql/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,aAAa,CAAC;AAC/D;;;;;;;;;GASG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,+EAA+E;AAC/E,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAChG;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAehD;AAED,kFAAkF;AAClF,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAIxE"}
@@ -0,0 +1,36 @@
1
+ import { toPgPlaceholders } from "./driver.js";
2
+ /**
3
+ * Postgres driver + adapter. Rewrites `?` → `$1, $2, …`, swaps `MAX(x, 0)` for
4
+ * `GREATEST(x, 0)` (handled in the adapter via dialect), and stringifies bigint
5
+ * params (node-postgres has no native bigint binding; BIGINT comes back as a
6
+ * numeric string, which `toBig` parses exactly - no precision loss).
7
+ *
8
+ * Works with anything exposing node-postgres's `query(text, values)` shape: a
9
+ * `pg.Pool`, a `pg.Client`, or PGlite. Usage:
10
+ * const meter = new CreditsClient({ adapter: await pgAdapter(pool) });
11
+ */
12
+ import { SqlStorageAdapter } from "./sql-adapter.js";
13
+ const bindable = (params) => params.map((p) => (typeof p === "bigint" ? p.toString() : p));
14
+ export function pgDriver(pool) {
15
+ return {
16
+ dialect: "postgres",
17
+ async all(sql, params = []) {
18
+ const { rows } = await pool.query(toPgPlaceholders(sql), bindable(params));
19
+ return rows;
20
+ },
21
+ async run(sql, params = []) {
22
+ const { rowCount } = await pool.query(toPgPlaceholders(sql), bindable(params));
23
+ return { rowsAffected: rowCount ?? 0 };
24
+ },
25
+ async exec(sql) {
26
+ await pool.query(sql);
27
+ },
28
+ };
29
+ }
30
+ /** Build + initialize a meter storage adapter over a Postgres connection/pool. */
31
+ export async function pgAdapter(pool) {
32
+ const a = new SqlStorageAdapter(pgDriver(pool));
33
+ await a.init();
34
+ return a;
35
+ }
36
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../../src/meter/sql/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/D;;;;;;;;;GASG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAOrD,MAAM,QAAQ,GAAG,CAAC,MAA0B,EAAa,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1H,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,SAA6B,EAAE;YACvD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YAC3E,OAAO,IAAW,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAA6B,EAAE;YACpD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/E,OAAO,EAAE,YAAY,EAAE,QAAQ,IAAI,CAAC,EAAE,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAW;YACpB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IAC1C,MAAM,CAAC,GAAG,IAAI,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACf,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Portable DDL for the two-table ledger, identical in shape on SQLite/D1 and
3
+ * Postgres. Only the integer type differs (SQLite INTEGER is already 64-bit;
4
+ * Postgres wants BIGINT). Credits are whole integers mirroring the on-chain
5
+ * uint64 - never floats. created_at/expires_at are epoch milliseconds.
6
+ *
7
+ * - credits_ledger(tenant_id, app, balance, held) - app="" is the GLOBAL bucket.
8
+ * - credits_reservation(id, …, capture_idem_key) - UNIQUE idem key so a retried
9
+ * capture can never double-charge.
10
+ */
11
+ import type { Dialect } from "./driver.js";
12
+ export declare function ddl(dialect: Dialect): string[];
13
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/meter/sql/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CA8B9C"}
@@ -0,0 +1,32 @@
1
+ export function ddl(dialect) {
2
+ const INT = dialect === "postgres" ? "BIGINT" : "INTEGER";
3
+ return [
4
+ `CREATE TABLE IF NOT EXISTS credits_ledger (
5
+ tenant_id TEXT NOT NULL,
6
+ app TEXT NOT NULL,
7
+ balance ${INT} NOT NULL DEFAULT 0,
8
+ held ${INT} NOT NULL DEFAULT 0,
9
+ PRIMARY KEY (tenant_id, app)
10
+ )`,
11
+ `CREATE TABLE IF NOT EXISTS credits_reservation (
12
+ id TEXT PRIMARY KEY,
13
+ tenant_id TEXT NOT NULL,
14
+ app TEXT NOT NULL,
15
+ held ${INT} NOT NULL,
16
+ charged ${INT} NOT NULL DEFAULT 0,
17
+ state TEXT NOT NULL,
18
+ created_at ${INT} NOT NULL,
19
+ expires_at ${INT} NOT NULL,
20
+ capture_idem_key TEXT
21
+ )`,
22
+ // A retried capture can never double-charge: the idem key is globally unique.
23
+ // Partial index so the many NULLs (captures without a key) don't collide.
24
+ `CREATE UNIQUE INDEX IF NOT EXISTS ux_resv_idem
25
+ ON credits_reservation (capture_idem_key)
26
+ WHERE capture_idem_key IS NOT NULL`,
27
+ // The reclaim sweep scans held holds past their TTL.
28
+ `CREATE INDEX IF NOT EXISTS ix_resv_reclaim
29
+ ON credits_reservation (state, expires_at)`,
30
+ ];
31
+ }
32
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/meter/sql/schema.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,GAAG,CAAC,OAAgB;IAClC,MAAM,GAAG,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1D,OAAO;QACL;;;mBAGe,GAAG;mBACH,GAAG;;OAEf;QACH;;;;0BAIsB,GAAG;0BACH,GAAG;;0BAEH,GAAG;0BACH,GAAG;;OAEtB;QACH,8EAA8E;QAC9E,0EAA0E;QAC1E;;0CAEsC;QACtC,qDAAqD;QACrD;kDAC8C;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * SqlStorageAdapter - a single, dialect-neutral StorageAdapter that runs
3
+ * identically on SQLite/D1 and Postgres. It is the SQL sibling of the in-memory
4
+ * reference and passes the very same conformance suite.
5
+ *
6
+ * Atomicity without interactive transactions (D1 has none): every state change
7
+ * is a *conditional* UPDATE whose row count is the verdict. `placeHold` makes the
8
+ * affordability check and the decrement one write, so parallel holds against one
9
+ * balance serialize on that row and only as many as the balance covers succeed;
10
+ * capture/void/expire flip a reservation out of 'held' and the row count says who
11
+ * won. The SQL itself lives in `statements.ts`.
12
+ */
13
+ import type { StorageAdapter } from "../adapter.js";
14
+ import { type App, type Balance, type Credits, type Reservation, type Result, type Tenant } from "../types.js";
15
+ import { type SqlDriver } from "./driver.js";
16
+ export declare class SqlStorageAdapter implements StorageAdapter {
17
+ private readonly d;
18
+ private readonly q;
19
+ private readied?;
20
+ constructor(driver: SqlDriver);
21
+ /** Create the tables/indexes. Idempotent; safe to call on every cold start. */
22
+ init(): Promise<void>;
23
+ private ensureLedger;
24
+ fund(tenant: Tenant, app: App, amount: Credits): Promise<void>;
25
+ hold(r: Reservation): Promise<boolean>;
26
+ capture(id: string, quotedActual: Credits, idemKey?: string): Promise<Result<{
27
+ charged: Credits;
28
+ }>>;
29
+ voidHold(id: string): Promise<Result<Record<never, never>>>;
30
+ private release;
31
+ balance(tenant: Tenant, app: App): Promise<Balance>;
32
+ reclaimExpired(now: number): Promise<number>;
33
+ getReservation(id: string): Promise<Reservation | undefined>;
34
+ private getRow;
35
+ }
36
+ //# sourceMappingURL=sql-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-adapter.d.ts","sourceRoot":"","sources":["../../../src/meter/sql/sql-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAyB,KAAK,MAAM,EAAE,KAAK,MAAM,EAAO,MAAM,aAAa,CAAC;AAC3I,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,aAAa,CAAC;AAgBpD,qBAAa,iBAAkB,YAAW,cAAc;IACtD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAC,CAAgB;gBAEpB,MAAM,EAAE,SAAS;IAK7B,+EAA+E;IACzE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,OAAO,CAAC,YAAY;IAId,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9D,IAAI,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAetC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAuBzG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;YAI7C,OAAO;IASf,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnD,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAS5C,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;YAgBpD,MAAM;CAIrB"}
@@ -0,0 +1,124 @@
1
+ import { available, captureAmount } from "../core.js";
2
+ import { err } from "../types.js";
3
+ import { toBig } from "./driver.js";
4
+ import { ddl } from "./schema.js";
5
+ import { buildStatements } from "./statements.js";
6
+ export class SqlStorageAdapter {
7
+ d;
8
+ q;
9
+ readied;
10
+ constructor(driver) {
11
+ this.d = driver;
12
+ this.q = buildStatements(driver.dialect === "postgres" ? "GREATEST" : "MAX");
13
+ }
14
+ /** Create the tables/indexes. Idempotent; safe to call on every cold start. */
15
+ async init() {
16
+ if (!this.readied) {
17
+ this.readied = (async () => {
18
+ for (const stmt of ddl(this.d.dialect))
19
+ await this.d.exec(stmt);
20
+ })();
21
+ }
22
+ return this.readied;
23
+ }
24
+ ensureLedger(tenant, app) {
25
+ return this.d.run(this.q.ensureLedger, [tenant, app]);
26
+ }
27
+ async fund(tenant, app, amount) {
28
+ if (amount <= 0n)
29
+ return;
30
+ await this.ensureLedger(tenant, app);
31
+ await this.d.run(this.q.fund, [amount, tenant, app]);
32
+ }
33
+ async hold(r) {
34
+ if (r.held < 0n)
35
+ return false;
36
+ await this.ensureLedger(r.tenant, r.app);
37
+ // Insert the reservation first so a crash before the ledger write leaves an
38
+ // orphan that reclaimExpired cleans up harmlessly (held floor protects it),
39
+ // never a phantom hold no one can see.
40
+ await this.d.run(this.q.insertReservation, [r.id, r.tenant, r.app, r.held, r.createdAt, r.expiresAt]);
41
+ const placed = await this.d.run(this.q.placeHold, [r.held, r.tenant, r.app, r.held]);
42
+ if (placed.rowsAffected === 1)
43
+ return true;
44
+ // Insufficient funds: drop the speculative reservation (its id is private
45
+ // until we return true, so nothing else can have referenced it).
46
+ await this.d.run(this.q.dropReservation, [r.id]);
47
+ return false;
48
+ }
49
+ async capture(id, quotedActual, idemKey) {
50
+ const row = await this.getRow(id);
51
+ if (!row)
52
+ return err("RESERVATION_NOT_FOUND", `no reservation ${id}`);
53
+ // Idempotent replay: a retried capture (same key) returns the original charge.
54
+ if (idemKey) {
55
+ const prior = await this.d.all(this.q.selectByIdem, [idemKey]);
56
+ if (prior.length)
57
+ return { ok: true, charged: toBig(prior[0]?.charged) };
58
+ }
59
+ if (row.state !== "held")
60
+ return err("ALREADY_FINALIZED", `reservation ${id} is ${row.state}`);
61
+ const charged = captureAmount(toBig(row.held), quotedActual);
62
+ const won = await this.d.run(this.q.captureReservation, [charged, idemKey ?? null, id]);
63
+ if (won.rowsAffected !== 1) {
64
+ // Lost the race to a concurrent capture/void. Resolve as replay or final.
65
+ const now = await this.getRow(id);
66
+ if (idemKey && now?.capture_idem_key === idemKey)
67
+ return { ok: true, charged: toBig(now.charged) };
68
+ return err("ALREADY_FINALIZED", `reservation ${id} is ${now?.state ?? "gone"}`);
69
+ }
70
+ await this.d.run(this.q.applyLedgerCapture, [charged, toBig(row.held), row.tenant_id, row.app]);
71
+ return { ok: true, charged };
72
+ }
73
+ voidHold(id) {
74
+ return this.release(id, "voided");
75
+ }
76
+ async release(id, to) {
77
+ const row = await this.getRow(id);
78
+ if (!row)
79
+ return err("RESERVATION_NOT_FOUND", `no reservation ${id}`);
80
+ const won = await this.d.run(this.q.releaseReservation, [to, id]);
81
+ if (won.rowsAffected !== 1)
82
+ return err("ALREADY_FINALIZED", `reservation ${id} is not held`);
83
+ await this.d.run(this.q.releaseLedgerHold, [toBig(row.held), row.tenant_id, row.app]);
84
+ return { ok: true };
85
+ }
86
+ async balance(tenant, app) {
87
+ const rows = await this.d.all(this.q.selectBalance, [tenant, app]);
88
+ if (!rows.length)
89
+ return { balance: 0n, held: 0n, available: 0n };
90
+ const balance = toBig(rows[0]?.balance);
91
+ const held = toBig(rows[0]?.held);
92
+ return { balance, held, available: available(balance, held) };
93
+ }
94
+ async reclaimExpired(now) {
95
+ const stale = await this.d.all(this.q.selectExpired, [now]);
96
+ let n = 0;
97
+ for (const { id } of stale) {
98
+ if ((await this.release(id, "expired")).ok)
99
+ n++;
100
+ }
101
+ return n;
102
+ }
103
+ async getReservation(id) {
104
+ const row = await this.getRow(id);
105
+ if (!row)
106
+ return undefined;
107
+ return {
108
+ id: row.id,
109
+ tenant: row.tenant_id,
110
+ app: row.app,
111
+ held: toBig(row.held),
112
+ charged: toBig(row.charged),
113
+ state: row.state,
114
+ createdAt: Number(toBig(row.created_at)),
115
+ expiresAt: Number(toBig(row.expires_at)),
116
+ ...(row.capture_idem_key != null ? { captureIdemKey: row.capture_idem_key } : {}),
117
+ };
118
+ }
119
+ async getRow(id) {
120
+ const rows = await this.d.all(this.q.selectById, [id]);
121
+ return rows[0];
122
+ }
123
+ }
124
+ //# sourceMappingURL=sql-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-adapter.js","sourceRoot":"","sources":["../../../src/meter/sql/sql-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Every SQL statement the adapter issues, in one place, with `?` positional
3
+ * placeholders (the Postgres driver rewrites these to `$n`). Only two statements
4
+ * vary by dialect - the scalar floor on hold release is `MAX(x, 0)` on SQLite/D1
5
+ * and `GREATEST(x, 0)` on Postgres - so the set is built from that one keyword.
6
+ *
7
+ * The floor is a belt-and-braces guard: in normal operation `held ≥ release`
8
+ * always, but a crash-orphaned reservation reclaimed after its TTL must never be
9
+ * able to drive `held` negative.
10
+ */
11
+ export interface Statements {
12
+ ensureLedger: string;
13
+ fund: string;
14
+ insertReservation: string;
15
+ /** The atomic gate: place the hold only if the scope can cover it. */
16
+ placeHold: string;
17
+ dropReservation: string;
18
+ selectByIdem: string;
19
+ /** Flip a held reservation to captured; row count tells us if we won the race. */
20
+ captureReservation: string;
21
+ applyLedgerCapture: string;
22
+ /** Flip a held reservation to voided/expired; row count tells us if we won. */
23
+ releaseReservation: string;
24
+ releaseLedgerHold: string;
25
+ selectBalance: string;
26
+ selectExpired: string;
27
+ selectById: string;
28
+ }
29
+ export declare function buildStatements(greatest: "MAX" | "GREATEST"): Statements;
30
+ //# sourceMappingURL=statements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../../src/meter/sql/statements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,GAAG,UAAU,GAAG,UAAU,CAsBxE"}