@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.
- package/LICENSE +75 -0
- package/README.md +426 -0
- package/bin/drm3.js +255 -0
- package/dist/alerts-bell.d.ts +7 -0
- package/dist/alerts-bell.d.ts.map +1 -0
- package/dist/alerts-bell.js +118 -0
- package/dist/alerts-bell.js.map +1 -0
- package/dist/alerts-fetch.d.ts +41 -0
- package/dist/alerts-fetch.d.ts.map +1 -0
- package/dist/alerts-fetch.js +60 -0
- package/dist/alerts-fetch.js.map +1 -0
- package/dist/alerts-styles.d.ts +4 -0
- package/dist/alerts-styles.d.ts.map +1 -0
- package/dist/alerts-styles.js +49 -0
- package/dist/alerts-styles.js.map +1 -0
- package/dist/alerts.d.ts +3 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.global.js +206 -0
- package/dist/alerts.global.text.js +2 -0
- package/dist/alerts.js +21 -0
- package/dist/alerts.js.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/keys-canonical.d.ts +7 -0
- package/dist/keys-canonical.d.ts.map +1 -0
- package/dist/keys-canonical.js +57 -0
- package/dist/keys-canonical.js.map +1 -0
- package/dist/keys-generate.d.ts +39 -0
- package/dist/keys-generate.d.ts.map +1 -0
- package/dist/keys-generate.js +39 -0
- package/dist/keys-generate.js.map +1 -0
- package/dist/keys-verify.d.ts +62 -0
- package/dist/keys-verify.d.ts.map +1 -0
- package/dist/keys-verify.js +68 -0
- package/dist/keys-verify.js.map +1 -0
- package/dist/keys.d.ts +4 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +22 -0
- package/dist/keys.js.map +1 -0
- package/dist/meter/adapter.d.ts +35 -0
- package/dist/meter/adapter.d.ts.map +1 -0
- package/dist/meter/adapter.js +0 -0
- package/dist/meter/adapter.js.map +1 -0
- package/dist/meter/cascade.d.ts +56 -0
- package/dist/meter/cascade.d.ts.map +1 -0
- package/dist/meter/cascade.js +64 -0
- package/dist/meter/cascade.js.map +1 -0
- package/dist/meter/client.d.ts +78 -0
- package/dist/meter/client.d.ts.map +1 -0
- package/dist/meter/client.js +84 -0
- package/dist/meter/client.js.map +1 -0
- package/dist/meter/core.d.ts +37 -0
- package/dist/meter/core.d.ts.map +1 -0
- package/dist/meter/core.js +49 -0
- package/dist/meter/core.js.map +1 -0
- package/dist/meter/gate-client.d.ts +94 -0
- package/dist/meter/gate-client.d.ts.map +1 -0
- package/dist/meter/gate-client.js +77 -0
- package/dist/meter/gate-client.js.map +1 -0
- package/dist/meter/gate-decide.d.ts +61 -0
- package/dist/meter/gate-decide.d.ts.map +1 -0
- package/dist/meter/gate-decide.js +54 -0
- package/dist/meter/gate-decide.js.map +1 -0
- package/dist/meter/gate.d.ts +10 -0
- package/dist/meter/gate.d.ts.map +1 -0
- package/dist/meter/gate.js +10 -0
- package/dist/meter/gate.js.map +1 -0
- package/dist/meter/index.d.ts +28 -0
- package/dist/meter/index.d.ts.map +1 -0
- package/dist/meter/index.js +28 -0
- package/dist/meter/index.js.map +1 -0
- package/dist/meter/meters-buckets.d.ts +56 -0
- package/dist/meter/meters-buckets.d.ts.map +1 -0
- package/dist/meter/meters-buckets.js +111 -0
- package/dist/meter/meters-buckets.js.map +1 -0
- package/dist/meter/meters-charge.d.ts +13 -0
- package/dist/meter/meters-charge.d.ts.map +1 -0
- package/dist/meter/meters-charge.js +76 -0
- package/dist/meter/meters-charge.js.map +1 -0
- package/dist/meter/meters.d.ts +63 -0
- package/dist/meter/meters.d.ts.map +1 -0
- package/dist/meter/meters.js +44 -0
- package/dist/meter/meters.js.map +1 -0
- package/dist/meter/plans.d.ts +63 -0
- package/dist/meter/plans.d.ts.map +1 -0
- package/dist/meter/plans.js +114 -0
- package/dist/meter/plans.js.map +1 -0
- package/dist/meter/pricebook.d.ts +32 -0
- package/dist/meter/pricebook.d.ts.map +1 -0
- package/dist/meter/pricebook.js +38 -0
- package/dist/meter/pricebook.js.map +1 -0
- package/dist/meter/resolve.d.ts +103 -0
- package/dist/meter/resolve.d.ts.map +1 -0
- package/dist/meter/resolve.js +100 -0
- package/dist/meter/resolve.js.map +1 -0
- package/dist/meter/sink.d.ts +26 -0
- package/dist/meter/sink.d.ts.map +1 -0
- package/dist/meter/sink.js +9 -0
- package/dist/meter/sink.js.map +1 -0
- package/dist/meter/sql/d1.d.ts +32 -0
- package/dist/meter/sql/d1.d.ts.map +1 -0
- package/dist/meter/sql/d1.js +41 -0
- package/dist/meter/sql/d1.js.map +1 -0
- package/dist/meter/sql/driver.d.ts +34 -0
- package/dist/meter/sql/driver.d.ts.map +1 -0
- package/dist/meter/sql/driver.js +17 -0
- package/dist/meter/sql/driver.js.map +1 -0
- package/dist/meter/sql/postgres.d.ts +23 -0
- package/dist/meter/sql/postgres.d.ts.map +1 -0
- package/dist/meter/sql/postgres.js +36 -0
- package/dist/meter/sql/postgres.js.map +1 -0
- package/dist/meter/sql/schema.d.ts +13 -0
- package/dist/meter/sql/schema.d.ts.map +1 -0
- package/dist/meter/sql/schema.js +32 -0
- package/dist/meter/sql/schema.js.map +1 -0
- package/dist/meter/sql/sql-adapter.d.ts +36 -0
- package/dist/meter/sql/sql-adapter.d.ts.map +1 -0
- package/dist/meter/sql/sql-adapter.js +124 -0
- package/dist/meter/sql/sql-adapter.js.map +1 -0
- package/dist/meter/sql/statements.d.ts +30 -0
- package/dist/meter/sql/statements.d.ts.map +1 -0
- package/dist/meter/sql/statements.js +24 -0
- package/dist/meter/sql/statements.js.map +1 -0
- package/dist/meter/types.d.ts +48 -0
- package/dist/meter/types.d.ts.map +1 -0
- package/dist/meter/types.js +8 -0
- package/dist/meter/types.js.map +1 -0
- package/dist/meter/units.d.ts +33 -0
- package/dist/meter/units.d.ts.map +1 -0
- package/dist/meter/units.js +45 -0
- package/dist/meter/units.js.map +1 -0
- package/dist/meter/wallet-buckets.d.ts +63 -0
- package/dist/meter/wallet-buckets.d.ts.map +1 -0
- package/dist/meter/wallet-buckets.js +131 -0
- package/dist/meter/wallet-buckets.js.map +1 -0
- package/dist/meter/wallet-charge.d.ts +15 -0
- package/dist/meter/wallet-charge.d.ts.map +1 -0
- package/dist/meter/wallet-charge.js +64 -0
- package/dist/meter/wallet-charge.js.map +1 -0
- package/dist/meter/wallet.d.ts +68 -0
- package/dist/meter/wallet.d.ts.map +1 -0
- package/dist/meter/wallet.js +47 -0
- package/dist/meter/wallet.js.map +1 -0
- package/dist/receipts-api.d.ts +5 -0
- package/dist/receipts-api.d.ts.map +1 -0
- package/dist/receipts-api.js +117 -0
- package/dist/receipts-api.js.map +1 -0
- package/dist/receipts-chain.d.ts +3 -0
- package/dist/receipts-chain.d.ts.map +1 -0
- package/dist/receipts-chain.js +121 -0
- package/dist/receipts-chain.js.map +1 -0
- package/dist/receipts-modal.d.ts +14 -0
- package/dist/receipts-modal.d.ts.map +1 -0
- package/dist/receipts-modal.js +100 -0
- package/dist/receipts-modal.js.map +1 -0
- package/dist/receipts-registry.d.ts +4 -0
- package/dist/receipts-registry.d.ts.map +1 -0
- package/dist/receipts-registry.js +54 -0
- package/dist/receipts-registry.js.map +1 -0
- package/dist/receipts-render.d.ts +9 -0
- package/dist/receipts-render.d.ts.map +1 -0
- package/dist/receipts-render.js +83 -0
- package/dist/receipts-render.js.map +1 -0
- package/dist/receipts-types.d.ts +61 -0
- package/dist/receipts-types.d.ts.map +1 -0
- package/dist/receipts-types.js +12 -0
- package/dist/receipts-types.js.map +1 -0
- package/dist/receipts-ui.d.ts +10 -0
- package/dist/receipts-ui.d.ts.map +1 -0
- package/dist/receipts-ui.js +103 -0
- package/dist/receipts-ui.js.map +1 -0
- package/dist/receipts-verify.d.ts +8 -0
- package/dist/receipts-verify.d.ts.map +1 -0
- package/dist/receipts-verify.js +113 -0
- package/dist/receipts-verify.js.map +1 -0
- package/dist/receipts.d.ts +9 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.global.js +587 -0
- package/dist/receipts.global.text.js +2 -0
- package/dist/receipts.js +28 -0
- package/dist/receipts.js.map +1 -0
- package/dist/sso/index.d.ts +4 -0
- package/dist/sso/index.d.ts.map +1 -0
- package/dist/sso/index.js +10 -0
- package/dist/sso/index.js.map +1 -0
- package/dist/sso/jwt.d.ts +29 -0
- package/dist/sso/jwt.d.ts.map +1 -0
- package/dist/sso/jwt.js +73 -0
- package/dist/sso/jwt.js.map +1 -0
- package/dist/sso/launch.d.ts +70 -0
- package/dist/sso/launch.d.ts.map +1 -0
- package/dist/sso/launch.js +75 -0
- package/dist/sso/launch.js.map +1 -0
- package/dist/sso/revocation.d.ts +23 -0
- package/dist/sso/revocation.d.ts.map +1 -0
- package/dist/sso/revocation.js +53 -0
- package/dist/sso/revocation.js.map +1 -0
- package/dist/switcher.d.ts +22 -0
- package/dist/switcher.d.ts.map +1 -0
- package/dist/switcher.js +174 -0
- package/dist/switcher.js.map +1 -0
- package/dist/x402/chain.d.ts +49 -0
- package/dist/x402/chain.d.ts.map +1 -0
- package/dist/x402/chain.js +33 -0
- package/dist/x402/chain.js.map +1 -0
- package/dist/x402/crypto.d.ts +25 -0
- package/dist/x402/crypto.d.ts.map +1 -0
- package/dist/x402/crypto.js +74 -0
- package/dist/x402/crypto.js.map +1 -0
- package/dist/x402/eip712.d.ts +20 -0
- package/dist/x402/eip712.d.ts.map +1 -0
- package/dist/x402/eip712.js +49 -0
- package/dist/x402/eip712.js.map +1 -0
- package/dist/x402/envelope.d.ts +45 -0
- package/dist/x402/envelope.d.ts.map +1 -0
- package/dist/x402/envelope.js +50 -0
- package/dist/x402/envelope.js.map +1 -0
- package/dist/x402/index.d.ts +41 -0
- package/dist/x402/index.d.ts.map +1 -0
- package/dist/x402/index.js +41 -0
- package/dist/x402/index.js.map +1 -0
- package/dist/x402/verify.d.ts +51 -0
- package/dist/x402/verify.d.ts.map +1 -0
- package/dist/x402/verify.js +110 -0
- package/dist/x402/verify.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** available = balance - held, floored at 0 (a hold never makes it negative). */
|
|
2
|
+
export function available(balance, held) {
|
|
3
|
+
const a = balance - held;
|
|
4
|
+
return a > 0n ? a : 0n;
|
|
5
|
+
}
|
|
6
|
+
/** Can a hold of `amount` be placed against this scope? The Visa pre-auth check. */
|
|
7
|
+
export function canHold(balance, held, amount) {
|
|
8
|
+
return amount >= 0n && balance - held >= amount;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The Visa guarantee: you are never charged more than you reserved.
|
|
12
|
+
* charged = min(quotedActual, held). Always.
|
|
13
|
+
*/
|
|
14
|
+
export function captureAmount(held, quotedActual) {
|
|
15
|
+
const q = quotedActual < 0n ? 0n : quotedActual;
|
|
16
|
+
return q < held ? q : held;
|
|
17
|
+
}
|
|
18
|
+
/** Whether a reservation is in a state that can still be captured/voided. */
|
|
19
|
+
export function isOpen(state) {
|
|
20
|
+
return state === "held";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Decide the result of capturing a held reservation. Returns the next
|
|
24
|
+
* reservation shape + how much to debit balance + how much hold to release.
|
|
25
|
+
* Pure: caller persists. Capturing a non-open reservation is a no-op echo
|
|
26
|
+
* (idempotent at the state-machine level; storage enforces idem by key too).
|
|
27
|
+
*/
|
|
28
|
+
export function applyCapture(r, quotedActual, idemKey) {
|
|
29
|
+
if (!isOpen(r.state)) {
|
|
30
|
+
return { reservation: r, debit: 0n, releaseHold: 0n };
|
|
31
|
+
}
|
|
32
|
+
const charged = captureAmount(r.held, quotedActual);
|
|
33
|
+
return {
|
|
34
|
+
reservation: { ...r, state: "captured", charged, captureIdemKey: idemKey ?? r.captureIdemKey },
|
|
35
|
+
debit: charged, // balance -= charged
|
|
36
|
+
releaseHold: r.held, // the whole hold leaves `held`; `charged` of it became spend
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Voiding/expiring an open reservation releases the entire hold, charges nothing. */
|
|
40
|
+
export function applyRelease(r, state) {
|
|
41
|
+
if (!isOpen(r.state))
|
|
42
|
+
return { reservation: r, releaseHold: 0n };
|
|
43
|
+
return { reservation: { ...r, state, charged: 0n }, releaseHold: r.held };
|
|
44
|
+
}
|
|
45
|
+
/** Has a reservation passed its TTL and is still holding credits? */
|
|
46
|
+
export function isReclaimable(r, now) {
|
|
47
|
+
return r.state === "held" && r.expiresAt <= now;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/meter/core.ts"],"names":[],"mappings":"AAQA,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,OAAgB,EAAE,IAAa;IACvD,MAAM,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACzB,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,OAAO,CAAC,OAAgB,EAAE,IAAa,EAAE,MAAe;IACtE,OAAO,MAAM,IAAI,EAAE,IAAI,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa,EAAE,YAAqB;IAChE,MAAM,CAAC,GAAG,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;IAChD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7B,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,MAAM,CAAC,KAAuB;IAC5C,OAAO,KAAK,KAAK,MAAM,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,CAAc,EAAE,YAAqB,EAAE,OAAgB;IAClF,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IACxD,CAAC;IACD,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACpD,OAAO;QACL,WAAW,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,IAAI,CAAC,CAAC,cAAc,EAAE;QAC9F,KAAK,EAAE,OAAO,EAAE,qBAAqB;QACrC,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,6DAA6D;KACnF,CAAC;AACJ,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,CAAc,EAAE,KAA2B;IACtE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IACjE,OAAO,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5E,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,CAAc,EAAE,GAAW;IACvD,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gate-client - the bus→authority client over HTTP.
|
|
3
|
+
*
|
|
4
|
+
* The bus has no ledger; it calls the queue's internal credit endpoints. bigint
|
|
5
|
+
* amounts cross the wire as strings (JSON has no bigint). The endpoints are
|
|
6
|
+
* internal-keyed (X-Internal-Key). The queue implements them over MeterStore.
|
|
7
|
+
*/
|
|
8
|
+
import type { Demand } from "./resolve.js";
|
|
9
|
+
/** `POST /api/internal/meter/preflight` - resolve a consumer's inf_ key to its
|
|
10
|
+
* tenant + credits + ban state in one call, so a direct-call surface (the bus)
|
|
11
|
+
* can run `decide()` before serving. */
|
|
12
|
+
export interface PreflightResult {
|
|
13
|
+
/** The resolved tenant, or null = invalid/revoked key (→ unauthenticated). */
|
|
14
|
+
readonly tenant: string | null;
|
|
15
|
+
/** Spendable µcredits, or null = ledger error (fail-open signal). */
|
|
16
|
+
readonly available: bigint | null;
|
|
17
|
+
readonly suspended: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** `GET /api/internal/meter/available?tenant=&scope=` */
|
|
20
|
+
export interface AvailableResult {
|
|
21
|
+
/** Spendable µcredits, or null = the authority hit a ledger error. null is a
|
|
22
|
+
* fail-open SIGNAL, not zero: the caller decides whether to serve through it. */
|
|
23
|
+
readonly available: bigint | null;
|
|
24
|
+
readonly suspended: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** `POST /api/internal/meter/charge` - the token-based inference charge the queue
|
|
27
|
+
* prices through its own matrix (the bus knows tokens, the queue owns the rate).
|
|
28
|
+
* This is the shape the queue implements today and the bus's hot path. */
|
|
29
|
+
export interface InferenceChargeWire {
|
|
30
|
+
readonly tenant: string;
|
|
31
|
+
readonly scope: string;
|
|
32
|
+
readonly idemKey?: string;
|
|
33
|
+
readonly provider?: string;
|
|
34
|
+
readonly model?: string;
|
|
35
|
+
readonly tokensIn: number;
|
|
36
|
+
readonly tokensOut: number;
|
|
37
|
+
}
|
|
38
|
+
/** `POST /api/internal/meter/charge` (Demand form) - a pre-resolved feature/credit
|
|
39
|
+
* demand priced by the CALLER. For app-level feature metering; the queue's
|
|
40
|
+
* inference endpoint uses the token form above. */
|
|
41
|
+
export interface ChargeRequestWire {
|
|
42
|
+
readonly tenant: string;
|
|
43
|
+
readonly scope: string;
|
|
44
|
+
readonly idemKey?: string;
|
|
45
|
+
readonly demand: {
|
|
46
|
+
readonly feature: string;
|
|
47
|
+
readonly units?: string;
|
|
48
|
+
readonly creditPricePerUnit: string;
|
|
49
|
+
} | {
|
|
50
|
+
readonly ucredits: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export interface ChargeResult {
|
|
54
|
+
readonly charged: bigint;
|
|
55
|
+
readonly overdraft: bigint;
|
|
56
|
+
readonly replay: boolean;
|
|
57
|
+
readonly byBucket: Readonly<Record<string, bigint>>;
|
|
58
|
+
}
|
|
59
|
+
export interface MeterClientOptions {
|
|
60
|
+
/** Queue base URL, e.g. https://queue.drm3.network */
|
|
61
|
+
readonly baseUrl: string;
|
|
62
|
+
/** Shared internal key (X-Internal-Key), same secret the queue checks. */
|
|
63
|
+
readonly internalKey: string;
|
|
64
|
+
/** Injectable fetch (Bun/Workers/Node global by default). */
|
|
65
|
+
readonly fetch?: typeof fetch;
|
|
66
|
+
}
|
|
67
|
+
/** Thin typed client the bus (and any non-CF surface) uses to gate + meter against
|
|
68
|
+
* the queue's one ledger. Stateless; one per surface. */
|
|
69
|
+
export declare class MeterClient {
|
|
70
|
+
private readonly base;
|
|
71
|
+
private readonly key;
|
|
72
|
+
private readonly f;
|
|
73
|
+
constructor(opts: MeterClientOptions);
|
|
74
|
+
/** Read spendable credits + ban state for the credits plane of `decide()`.
|
|
75
|
+
* `available` is null when the authority hit a ledger error (the bus fails open). */
|
|
76
|
+
available(tenant: string, scope?: string): Promise<AvailableResult>;
|
|
77
|
+
/** Resolve a direct caller's inf_ key → tenant + credits + ban, for `decide()`.
|
|
78
|
+
* The bus's first call on a consumer request. tenant=null ⇒ unauthenticated. */
|
|
79
|
+
preflight(key: string): Promise<PreflightResult>;
|
|
80
|
+
private postCharge;
|
|
81
|
+
/** Meter one direct inference request: the QUEUE prices provider/model/tokens
|
|
82
|
+
* through its matrix and debits the one ledger. This is the bus's hot path -
|
|
83
|
+
* call it AFTER the response with the real token counts. Idempotent on idemKey. */
|
|
84
|
+
chargeInference(tenant: string, usage: {
|
|
85
|
+
provider?: string;
|
|
86
|
+
model?: string;
|
|
87
|
+
tokensIn: number;
|
|
88
|
+
tokensOut: number;
|
|
89
|
+
}, idemKey?: string, scope?: string): Promise<ChargeResult>;
|
|
90
|
+
/** Debit a pre-priced demand (caller-priced feature/credit metering). For app
|
|
91
|
+
* surfaces; the queue's inference endpoint uses chargeInference. Idempotent. */
|
|
92
|
+
charge(tenant: string, demand: Demand, idemKey?: string, scope?: string): Promise<ChargeResult>;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=gate-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-client.d.ts","sourceRoot":"","sources":["../../src/meter/gate-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const wireDemand = (d) => "feature" in d
|
|
2
|
+
? { feature: d.feature, units: (d.units ?? 1n).toString(), creditPricePerUnit: d.creditPricePerUnit.toString() }
|
|
3
|
+
: { ucredits: d.ucredits.toString() };
|
|
4
|
+
/** Thin typed client the bus (and any non-CF surface) uses to gate + meter against
|
|
5
|
+
* the queue's one ledger. Stateless; one per surface. */
|
|
6
|
+
export class MeterClient {
|
|
7
|
+
base;
|
|
8
|
+
key;
|
|
9
|
+
f;
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.base = opts.baseUrl.replace(/\/+$/, "");
|
|
12
|
+
this.key = opts.internalKey;
|
|
13
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
14
|
+
if (!f)
|
|
15
|
+
throw new Error("MeterClient: no fetch available; pass opts.fetch");
|
|
16
|
+
this.f = f;
|
|
17
|
+
}
|
|
18
|
+
/** Read spendable credits + ban state for the credits plane of `decide()`.
|
|
19
|
+
* `available` is null when the authority hit a ledger error (the bus fails open). */
|
|
20
|
+
async available(tenant, scope = "") {
|
|
21
|
+
const u = `${this.base}/api/internal/meter/available?tenant=${encodeURIComponent(tenant)}&scope=${encodeURIComponent(scope)}`;
|
|
22
|
+
const res = await this.f(u, { headers: { "X-Internal-Key": this.key } });
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
throw new Error(`meter available ${res.status}`);
|
|
25
|
+
const j = (await res.json());
|
|
26
|
+
return { available: j.available == null ? null : BigInt(j.available), suspended: Boolean(j.suspended) };
|
|
27
|
+
}
|
|
28
|
+
/** Resolve a direct caller's inf_ key → tenant + credits + ban, for `decide()`.
|
|
29
|
+
* The bus's first call on a consumer request. tenant=null ⇒ unauthenticated. */
|
|
30
|
+
async preflight(key) {
|
|
31
|
+
const res = await this.f(`${this.base}/api/internal/meter/preflight`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "X-Internal-Key": this.key, "content-type": "application/json" },
|
|
34
|
+
body: JSON.stringify({ key }),
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
throw new Error(`meter preflight ${res.status}`);
|
|
38
|
+
const j = (await res.json());
|
|
39
|
+
return { tenant: j.tenant ?? null, available: j.available == null ? null : BigInt(j.available), suspended: Boolean(j.suspended) };
|
|
40
|
+
}
|
|
41
|
+
async postCharge(body) {
|
|
42
|
+
const res = await this.f(`${this.base}/api/internal/meter/charge`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "X-Internal-Key": this.key, "content-type": "application/json" },
|
|
45
|
+
body: JSON.stringify(body),
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok)
|
|
48
|
+
throw new Error(`meter charge ${res.status}`);
|
|
49
|
+
const j = (await res.json());
|
|
50
|
+
const byBucket = {};
|
|
51
|
+
for (const [k, v] of Object.entries(j.byBucket ?? {}))
|
|
52
|
+
byBucket[k] = BigInt(v);
|
|
53
|
+
return { charged: BigInt(j.charged ?? "0"), overdraft: BigInt(j.overdraft ?? "0"), replay: Boolean(j.replay), byBucket };
|
|
54
|
+
}
|
|
55
|
+
/** Meter one direct inference request: the QUEUE prices provider/model/tokens
|
|
56
|
+
* through its matrix and debits the one ledger. This is the bus's hot path -
|
|
57
|
+
* call it AFTER the response with the real token counts. Idempotent on idemKey. */
|
|
58
|
+
async chargeInference(tenant, usage, idemKey, scope = "") {
|
|
59
|
+
const body = {
|
|
60
|
+
tenant,
|
|
61
|
+
scope,
|
|
62
|
+
idemKey,
|
|
63
|
+
provider: usage.provider,
|
|
64
|
+
model: usage.model,
|
|
65
|
+
tokensIn: Math.max(0, Math.trunc(usage.tokensIn || 0)),
|
|
66
|
+
tokensOut: Math.max(0, Math.trunc(usage.tokensOut || 0)),
|
|
67
|
+
};
|
|
68
|
+
return this.postCharge(body);
|
|
69
|
+
}
|
|
70
|
+
/** Debit a pre-priced demand (caller-priced feature/credit metering). For app
|
|
71
|
+
* surfaces; the queue's inference endpoint uses chargeInference. Idempotent. */
|
|
72
|
+
async charge(tenant, demand, idemKey, scope = "") {
|
|
73
|
+
const body = { tenant, scope, idemKey, demand: wireDemand(demand) };
|
|
74
|
+
return this.postCharge(body);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=gate-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-client.js","sourceRoot":"","sources":["../../src/meter/gate-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gate-decide - the shared admission decision as a pure function.
|
|
3
|
+
*
|
|
4
|
+
* DRM3 has TWO metered front doors onto ONE ledger: the async queue and the
|
|
5
|
+
* synchronous bus. A consumer (human or service) may call either directly. Both
|
|
6
|
+
* must answer the same two questions before serving, in the same order:
|
|
7
|
+
*
|
|
8
|
+
* 1. MAY you? identity + entitlement → the access plane
|
|
9
|
+
* 2. CAN you? credits available → the credits plane
|
|
10
|
+
*
|
|
11
|
+
* `decide()` is that answer as a pure function over already-resolved facts, so the
|
|
12
|
+
* queue and the bus return identical typed denials (the four reason codes). Each
|
|
13
|
+
* surface assembles the GateInput from what it knows: authentication + app claims
|
|
14
|
+
* + unlimited come from the SSO token / tenant record; `available` comes from the
|
|
15
|
+
* ledger (the queue reads it locally; the bus fetches it via MeterClient).
|
|
16
|
+
*
|
|
17
|
+
* The double-charge trap. The queue drains TO the bus using the service key, so a
|
|
18
|
+
* request can reach the bus already metered. A metered surface therefore classes
|
|
19
|
+
* its caller: a CONSUMER is gated + metered; an UPSTREAM (the queue, an already-
|
|
20
|
+
* metered hop) passes straight through - provenance only, no second charge. Use
|
|
21
|
+
* `callerClass()` with the surface's own trusted-key set.
|
|
22
|
+
*/
|
|
23
|
+
export type DenyReason = "unauthenticated" | "not_authorized_app" | "no_credits" | "suspended";
|
|
24
|
+
/** Already-resolved facts about one request. Each surface fills these in. */
|
|
25
|
+
export interface GateInput {
|
|
26
|
+
/** A valid identity was presented (SSO token / tenant key). */
|
|
27
|
+
readonly authenticated: boolean;
|
|
28
|
+
/** Banned (revocation feed). The one punitive state - checked before entitlement. */
|
|
29
|
+
readonly suspended: boolean;
|
|
30
|
+
/** Holds the product/app claim for what's being called. */
|
|
31
|
+
readonly entitled: boolean;
|
|
32
|
+
/** An unlimited gate for this call → serve and LOG the cost, never debit. */
|
|
33
|
+
readonly unlimited: boolean;
|
|
34
|
+
/** Spendable µcredits now (allowances + purse). Ignored when unlimited. */
|
|
35
|
+
readonly available: bigint;
|
|
36
|
+
}
|
|
37
|
+
export type GateDecision =
|
|
38
|
+
/** Serve. `meter` is false for an unlimited grant (log the cost, don't debit). */
|
|
39
|
+
{
|
|
40
|
+
readonly allow: true;
|
|
41
|
+
readonly meter: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Refuse, with the typed reason the surface renders. */
|
|
44
|
+
| {
|
|
45
|
+
readonly allow: false;
|
|
46
|
+
readonly reason: DenyReason;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The shared admission decision. Order matters: a banned account hears "suspended"
|
|
50
|
+
* even where it lacks a claim; an entitled account with no credits hears "no_credits",
|
|
51
|
+
* not "not authorized". Identity → ban → access → unlimited → credits.
|
|
52
|
+
*/
|
|
53
|
+
export declare function decide(g: GateInput): GateDecision;
|
|
54
|
+
export type CallerClass = "consumer" | "upstream";
|
|
55
|
+
/**
|
|
56
|
+
* Classify a caller of a metered surface. An UPSTREAM presented a key in the
|
|
57
|
+
* surface's trusted set (e.g. the bus trusting the queue's BUS_API_KEY) → it has
|
|
58
|
+
* already metered, so pass through. Anything else is a CONSUMER to gate + meter.
|
|
59
|
+
*/
|
|
60
|
+
export declare function callerClass(presentedKey: string | null | undefined, upstreamKeys: Iterable<string>): CallerClass;
|
|
61
|
+
//# sourceMappingURL=gate-decide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-decide.d.ts","sourceRoot":"","sources":["../../src/meter/gate-decide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,YAAY,GAAG,WAAW,CAAC;AAE/F,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY;AACtB,kFAAkF;AAChF;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE;AACnD,yDAAyD;GACvD;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,SAAS,GAAG,YAAY,CAOjD;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;AAElD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,WAAW,CAIhH"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gate-decide - the shared admission decision as a pure function.
|
|
3
|
+
*
|
|
4
|
+
* DRM3 has TWO metered front doors onto ONE ledger: the async queue and the
|
|
5
|
+
* synchronous bus. A consumer (human or service) may call either directly. Both
|
|
6
|
+
* must answer the same two questions before serving, in the same order:
|
|
7
|
+
*
|
|
8
|
+
* 1. MAY you? identity + entitlement → the access plane
|
|
9
|
+
* 2. CAN you? credits available → the credits plane
|
|
10
|
+
*
|
|
11
|
+
* `decide()` is that answer as a pure function over already-resolved facts, so the
|
|
12
|
+
* queue and the bus return identical typed denials (the four reason codes). Each
|
|
13
|
+
* surface assembles the GateInput from what it knows: authentication + app claims
|
|
14
|
+
* + unlimited come from the SSO token / tenant record; `available` comes from the
|
|
15
|
+
* ledger (the queue reads it locally; the bus fetches it via MeterClient).
|
|
16
|
+
*
|
|
17
|
+
* The double-charge trap. The queue drains TO the bus using the service key, so a
|
|
18
|
+
* request can reach the bus already metered. A metered surface therefore classes
|
|
19
|
+
* its caller: a CONSUMER is gated + metered; an UPSTREAM (the queue, an already-
|
|
20
|
+
* metered hop) passes straight through - provenance only, no second charge. Use
|
|
21
|
+
* `callerClass()` with the surface's own trusted-key set.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The shared admission decision. Order matters: a banned account hears "suspended"
|
|
25
|
+
* even where it lacks a claim; an entitled account with no credits hears "no_credits",
|
|
26
|
+
* not "not authorized". Identity → ban → access → unlimited → credits.
|
|
27
|
+
*/
|
|
28
|
+
export function decide(g) {
|
|
29
|
+
if (!g.authenticated)
|
|
30
|
+
return { allow: false, reason: "unauthenticated" };
|
|
31
|
+
if (g.suspended)
|
|
32
|
+
return { allow: false, reason: "suspended" };
|
|
33
|
+
if (!g.entitled)
|
|
34
|
+
return { allow: false, reason: "not_authorized_app" };
|
|
35
|
+
if (g.unlimited)
|
|
36
|
+
return { allow: true, meter: false }; // all-you-can-eat: serve, log only
|
|
37
|
+
if (g.available <= 0n)
|
|
38
|
+
return { allow: false, reason: "no_credits" };
|
|
39
|
+
return { allow: true, meter: true };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Classify a caller of a metered surface. An UPSTREAM presented a key in the
|
|
43
|
+
* surface's trusted set (e.g. the bus trusting the queue's BUS_API_KEY) → it has
|
|
44
|
+
* already metered, so pass through. Anything else is a CONSUMER to gate + meter.
|
|
45
|
+
*/
|
|
46
|
+
export function callerClass(presentedKey, upstreamKeys) {
|
|
47
|
+
if (!presentedKey)
|
|
48
|
+
return "consumer";
|
|
49
|
+
for (const k of upstreamKeys)
|
|
50
|
+
if (k && k === presentedKey)
|
|
51
|
+
return "upstream";
|
|
52
|
+
return "consumer";
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=gate-decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-decide.js","sourceRoot":"","sources":["../../src/meter/gate-decide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAwBH;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,CAAY;IACjC,IAAI,CAAC,CAAC,CAAC,aAAa;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IACzE,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC9D,IAAI,CAAC,CAAC,CAAC,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACvE,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,mCAAmC;IAC1F,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACrE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACtC,CAAC;AAID;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,YAAuC,EAAE,YAA8B;IACjG,IAAI,CAAC,YAAY;QAAE,OAAO,UAAU,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,YAAY;QAAE,IAAI,CAAC,IAAI,CAAC,KAAK,YAAY;YAAE,OAAO,UAAU,CAAC;IAC7E,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gate - the shared admission decision + the bus→authority client.
|
|
3
|
+
*
|
|
4
|
+
* Barrel over two concerns:
|
|
5
|
+
* - gate-decide.ts: the pure `decide()` / `callerClass()` admission logic.
|
|
6
|
+
* - gate-client.ts: the `MeterClient` HTTP client onto the queue's ledger.
|
|
7
|
+
*/
|
|
8
|
+
export { decide, callerClass, type DenyReason, type GateInput, type GateDecision, type CallerClass } from "./gate-decide.js";
|
|
9
|
+
export { MeterClient, type PreflightResult, type AvailableResult, type InferenceChargeWire, type ChargeRequestWire, type ChargeResult, type MeterClientOptions, } from "./gate-client.js";
|
|
10
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/meter/gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC7H,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gate - the shared admission decision + the bus→authority client.
|
|
3
|
+
*
|
|
4
|
+
* Barrel over two concerns:
|
|
5
|
+
* - gate-decide.ts: the pure `decide()` / `callerClass()` admission logic.
|
|
6
|
+
* - gate-client.ts: the `MeterClient` HTTP client onto the queue's ledger.
|
|
7
|
+
*/
|
|
8
|
+
export { decide, callerClass } from "./gate-decide.js";
|
|
9
|
+
export { MeterClient, } from "./gate-client.js";
|
|
10
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/meter/gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAwE,MAAM,kBAAkB,CAAC;AAC7H,OAAO,EACL,WAAW,GAOZ,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @drm3/sdk/meter - a non-custodial usage-metering ledger.
|
|
3
|
+
* authorize → capture → void → settle. Pure core, pluggable storage + settlement,
|
|
4
|
+
* no overdraft by construction. An external ledger only appears behind SettlementSink.
|
|
5
|
+
*
|
|
6
|
+
* Wallet is the multi-bucket layer on top: allowances (daily/weekly/monthly,
|
|
7
|
+
* refill-to-cap, burned first) over the existing ledger as the overdraftable
|
|
8
|
+
* purse (burned last). Purely additive - single-balance consumers are unaffected.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./types.js";
|
|
11
|
+
export * from "./core.js";
|
|
12
|
+
export { cascade, type CascadeNode, type CascadeDraw, type CascadeResult } from "./cascade.js";
|
|
13
|
+
export { resolveCharge, creditCost, type MeterState, type Demand, type MeterDraw, type ResolveResult, } from "./resolve.js";
|
|
14
|
+
export { MeterStore, CREDITS_METER, type MeterBucketDef, type MeterStoreOptions } from "./meters.js";
|
|
15
|
+
export { PlanStore, type PlanDef, type PlanStoreOptions } from "./plans.js";
|
|
16
|
+
export { decide, callerClass, MeterClient, type DenyReason, type GateInput, type GateDecision, type CallerClass, type AvailableResult, type PreflightResult, type ChargeRequestWire, type InferenceChargeWire, type ChargeResult as RemoteChargeResult, type MeterClientOptions, } from "./gate.js";
|
|
17
|
+
export { PriceBook, type Rate } from "./pricebook.js";
|
|
18
|
+
export { CREDIT, CREDIT_DECIMALS, formatCredits, parseCredits } from "./units.js";
|
|
19
|
+
export { type StorageAdapter, InMemoryAdapter } from "./adapter.js";
|
|
20
|
+
export { SqlStorageAdapter } from "./sql/sql-adapter.js";
|
|
21
|
+
export { ddl } from "./sql/schema.js";
|
|
22
|
+
export { type SqlDriver, type Dialect, toBig, toPgPlaceholders } from "./sql/driver.js";
|
|
23
|
+
export { type D1Like, d1Driver, d1Adapter } from "./sql/d1.js";
|
|
24
|
+
export { type PgLike, pgDriver, pgAdapter } from "./sql/postgres.js";
|
|
25
|
+
export { type SettlementSink, type SettlementBatch, NullSink } from "./sink.js";
|
|
26
|
+
export { CreditsClient, type ClientOptions, type AuthorizeInput, type CaptureInput } from "./client.js";
|
|
27
|
+
export { Wallet, type Cadence, type BucketDef, type BucketBalance, type WalletBalance, type ChargeResult, type WalletOptions, } from "./wallet.js";
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/meter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/F,OAAO,EACL,aAAa,EACb,UAAU,EACV,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,YAAY,IAAI,kBAAkB,EACvC,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,SAAS,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAE,KAAK,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,KAAK,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxG,OAAO,EACL,MAAM,EACN,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @drm3/sdk/meter - a non-custodial usage-metering ledger.
|
|
3
|
+
* authorize → capture → void → settle. Pure core, pluggable storage + settlement,
|
|
4
|
+
* no overdraft by construction. An external ledger only appears behind SettlementSink.
|
|
5
|
+
*
|
|
6
|
+
* Wallet is the multi-bucket layer on top: allowances (daily/weekly/monthly,
|
|
7
|
+
* refill-to-cap, burned first) over the existing ledger as the overdraftable
|
|
8
|
+
* purse (burned last). Purely additive - single-balance consumers are unaffected.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./types.js";
|
|
11
|
+
export * from "./core.js";
|
|
12
|
+
export { cascade } from "./cascade.js";
|
|
13
|
+
export { resolveCharge, creditCost, } from "./resolve.js";
|
|
14
|
+
export { MeterStore, CREDITS_METER } from "./meters.js";
|
|
15
|
+
export { PlanStore } from "./plans.js";
|
|
16
|
+
export { decide, callerClass, MeterClient, } from "./gate.js";
|
|
17
|
+
export { PriceBook } from "./pricebook.js";
|
|
18
|
+
export { CREDIT, CREDIT_DECIMALS, formatCredits, parseCredits } from "./units.js";
|
|
19
|
+
export { InMemoryAdapter } from "./adapter.js";
|
|
20
|
+
export { SqlStorageAdapter } from "./sql/sql-adapter.js";
|
|
21
|
+
export { ddl } from "./sql/schema.js";
|
|
22
|
+
export { toBig, toPgPlaceholders } from "./sql/driver.js";
|
|
23
|
+
export { d1Driver, d1Adapter } from "./sql/d1.js";
|
|
24
|
+
export { pgDriver, pgAdapter } from "./sql/postgres.js";
|
|
25
|
+
export { NullSink } from "./sink.js";
|
|
26
|
+
export { CreditsClient } from "./client.js";
|
|
27
|
+
export { Wallet, } from "./wallet.js";
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/meter/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,OAAO,EAA0D,MAAM,cAAc,CAAC;AAC/F,OAAO,EACL,aAAa,EACb,UAAU,GAKX,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,aAAa,EAA+C,MAAM,aAAa,CAAC;AACrG,OAAO,EAAE,SAAS,EAAuC,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,GAWZ,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,SAAS,EAAa,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClF,OAAO,EAAuB,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAgC,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAe,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAe,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAA6C,QAAQ,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EAAE,aAAa,EAA8D,MAAM,aAAa,CAAC;AACxG,OAAO,EACL,MAAM,GAOP,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* meters-buckets - the persisted-cascade config layer: types, DDL, refill.
|
|
3
|
+
*
|
|
4
|
+
* Free functions over a SqlDriver for the credits_bucket table's config/refill
|
|
5
|
+
* half. The load + resolve + charge round trip lives in meters-charge.ts; the
|
|
6
|
+
* MeterStore class (meters.ts) composes both.
|
|
7
|
+
*/
|
|
8
|
+
import type { SqlDriver } from "./sql/driver.js";
|
|
9
|
+
import type { Cadence } from "./wallet.js";
|
|
10
|
+
export declare const GLOBAL = "";
|
|
11
|
+
/** The terminal meter's key. Always sorted last so it is the overdraftable buck-stop. */
|
|
12
|
+
export declare const CREDITS_METER = "credits";
|
|
13
|
+
/** Config for one bucket of one meter. The (meter, bucket) pair is unique per
|
|
14
|
+
* (tenant, scope). */
|
|
15
|
+
export interface MeterBucketDef {
|
|
16
|
+
/** Meter key: a feature ('smart_query') or the terminal 'credits' meter. */
|
|
17
|
+
meter: string;
|
|
18
|
+
/** Bucket name within the meter: 'daily' | 'monthly' | 'buyup'. */
|
|
19
|
+
bucket: string;
|
|
20
|
+
/** Refill ceiling (µcredits for credits, calls for a feature). */
|
|
21
|
+
cap: bigint;
|
|
22
|
+
/** Auto-refill cadence. 'none' = dormant (defined, never auto-refills). */
|
|
23
|
+
cadence: Cadence;
|
|
24
|
+
/** Billing unit, audit/display: 'call' for a feature, 'credit' for credits. */
|
|
25
|
+
unit: string;
|
|
26
|
+
/** Burn order WITHIN the meter (daily < monthly < buyup). */
|
|
27
|
+
priority: number;
|
|
28
|
+
/** Burn order ACROSS meters. Feature meters low; the credits meter must be
|
|
29
|
+
* highest so it sorts terminal/last. */
|
|
30
|
+
meterPriority: number;
|
|
31
|
+
/** Carries forward across epochs (buyups true; daily/monthly false → reset-to-cap). */
|
|
32
|
+
carry?: boolean;
|
|
33
|
+
/** May overdraft (go negative). Only the credits buyup is true. */
|
|
34
|
+
overdraft?: boolean;
|
|
35
|
+
/** Optional upstream allocation this bucket is backed by (e.g. 'venice'). */
|
|
36
|
+
source?: string;
|
|
37
|
+
}
|
|
38
|
+
/** Period key for a cadence at an instant (UTC). Refill fires when it differs from
|
|
39
|
+
* the stored key. (Local copy - keeps this module independent of Wallet.) */
|
|
40
|
+
export declare function periodKey(cadence: Cadence, nowMs: number): string;
|
|
41
|
+
/** Idempotent DDL: the generalized bucket table + a charge idempotency guard. */
|
|
42
|
+
export declare function init(d: SqlDriver): Promise<void>;
|
|
43
|
+
/** Define / update a bucket's config. Sets balance to cap on first insert so a
|
|
44
|
+
* freshly-defined allowance starts full; an existing bucket keeps its balance
|
|
45
|
+
* (the next refill() applies the cap). */
|
|
46
|
+
export declare function setBucket(d: SqlDriver, tenant: string, scope: string, def: MeterBucketDef): Promise<void>;
|
|
47
|
+
/** Refill every non-carry bucket whose epoch has rolled, reset-to-cap. Buyups
|
|
48
|
+
* (carry=1) never auto-refill. Returns the topped (meter,bucket) pairs. */
|
|
49
|
+
export declare function refill(d: SqlDriver, now: () => number, tenant: string, scope: string): Promise<{
|
|
50
|
+
topped: string[];
|
|
51
|
+
granted: bigint;
|
|
52
|
+
}>;
|
|
53
|
+
/** Add µcredits to the credits buyup (Stripe deposit / admin top-up). Nets
|
|
54
|
+
* against any overdraft automatically. */
|
|
55
|
+
export declare function deposit(d: SqlDriver, tenant: string, scope: string, ucredits: bigint): Promise<void>;
|
|
56
|
+
//# sourceMappingURL=meters-buckets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meters-buckets.d.ts","sourceRoot":"","sources":["../../src/meter/meters-buckets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,yFAAyF;AACzF,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC;uBACuB;AACvB,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB;6CACyC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;8EAC8E;AAC9E,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CASjE;AAED,iFAAiF;AACjF,wBAAsB,IAAI,CAAC,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BtD;AAED;;2CAE2C;AAC3C,wBAAsB,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/G;AAED;4EAC4E;AAC5E,wBAAsB,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2B3I;AAED;2CAC2C;AAC3C,wBAAsB,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS1G"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { toBig } from "./sql/driver.js";
|
|
2
|
+
export const GLOBAL = "";
|
|
3
|
+
/** The terminal meter's key. Always sorted last so it is the overdraftable buck-stop. */
|
|
4
|
+
export const CREDITS_METER = "credits";
|
|
5
|
+
/** Period key for a cadence at an instant (UTC). Refill fires when it differs from
|
|
6
|
+
* the stored key. (Local copy - keeps this module independent of Wallet.) */
|
|
7
|
+
export function periodKey(cadence, nowMs) {
|
|
8
|
+
if (cadence === "none")
|
|
9
|
+
return "none";
|
|
10
|
+
const d = new Date(nowMs);
|
|
11
|
+
const iso = d.toISOString();
|
|
12
|
+
if (cadence === "monthly")
|
|
13
|
+
return iso.slice(0, 7);
|
|
14
|
+
if (cadence === "daily")
|
|
15
|
+
return iso.slice(0, 10);
|
|
16
|
+
const start = Date.UTC(d.getUTCFullYear(), 0, 1);
|
|
17
|
+
const week = Math.floor((nowMs - start) / (7 * 24 * 3600 * 1000));
|
|
18
|
+
return `${d.getUTCFullYear()}-W${week}`;
|
|
19
|
+
}
|
|
20
|
+
/** Idempotent DDL: the generalized bucket table + a charge idempotency guard. */
|
|
21
|
+
export async function init(d) {
|
|
22
|
+
const INT = d.dialect === "postgres" ? "BIGINT" : "INTEGER";
|
|
23
|
+
await d.exec(`CREATE TABLE IF NOT EXISTS credits_bucket (
|
|
24
|
+
tenant_id TEXT NOT NULL,
|
|
25
|
+
scope TEXT NOT NULL,
|
|
26
|
+
meter TEXT NOT NULL,
|
|
27
|
+
bucket TEXT NOT NULL,
|
|
28
|
+
balance ${INT} NOT NULL DEFAULT 0,
|
|
29
|
+
cap ${INT} NOT NULL DEFAULT 0,
|
|
30
|
+
cadence TEXT NOT NULL DEFAULT 'none',
|
|
31
|
+
unit TEXT NOT NULL DEFAULT 'credit',
|
|
32
|
+
priority ${INT} NOT NULL DEFAULT 100,
|
|
33
|
+
meter_priority ${INT} NOT NULL DEFAULT 100,
|
|
34
|
+
carry ${INT} NOT NULL DEFAULT 0,
|
|
35
|
+
overdraft ${INT} NOT NULL DEFAULT 0,
|
|
36
|
+
source TEXT,
|
|
37
|
+
period TEXT,
|
|
38
|
+
refilled_at ${INT},
|
|
39
|
+
PRIMARY KEY (tenant_id, scope, meter, bucket)
|
|
40
|
+
)`);
|
|
41
|
+
await d.exec(`CREATE TABLE IF NOT EXISTS bucket_txn (
|
|
42
|
+
idem_key TEXT PRIMARY KEY,
|
|
43
|
+
tenant_id TEXT NOT NULL,
|
|
44
|
+
scope TEXT NOT NULL,
|
|
45
|
+
charged ${INT} NOT NULL,
|
|
46
|
+
at ${INT} NOT NULL
|
|
47
|
+
)`);
|
|
48
|
+
}
|
|
49
|
+
/** Define / update a bucket's config. Sets balance to cap on first insert so a
|
|
50
|
+
* freshly-defined allowance starts full; an existing bucket keeps its balance
|
|
51
|
+
* (the next refill() applies the cap). */
|
|
52
|
+
export async function setBucket(d, tenant, scope, def) {
|
|
53
|
+
const s = scope || GLOBAL;
|
|
54
|
+
const existing = await d.all("SELECT bucket FROM credits_bucket WHERE tenant_id=? AND scope=? AND meter=? AND bucket=?", [
|
|
55
|
+
tenant,
|
|
56
|
+
s,
|
|
57
|
+
def.meter,
|
|
58
|
+
def.bucket,
|
|
59
|
+
]);
|
|
60
|
+
const carry = def.carry ? 1 : 0;
|
|
61
|
+
const overdraft = def.overdraft ? 1 : 0;
|
|
62
|
+
if (existing.length) {
|
|
63
|
+
await d.run("UPDATE credits_bucket SET cap=?, cadence=?, unit=?, priority=?, meter_priority=?, carry=?, overdraft=?, source=? WHERE tenant_id=? AND scope=? AND meter=? AND bucket=?", [def.cap, def.cadence, def.unit, def.priority, def.meterPriority, carry, overdraft, def.source ?? null, tenant, s, def.meter, def.bucket]);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
await d.run("INSERT INTO credits_bucket (tenant_id, scope, meter, bucket, balance, cap, cadence, unit, priority, meter_priority, carry, overdraft, source) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [tenant, s, def.meter, def.bucket, def.cap, def.cap, def.cadence, def.unit, def.priority, def.meterPriority, carry, overdraft, def.source ?? null]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Refill every non-carry bucket whose epoch has rolled, reset-to-cap. Buyups
|
|
70
|
+
* (carry=1) never auto-refill. Returns the topped (meter,bucket) pairs. */
|
|
71
|
+
export async function refill(d, now, tenant, scope) {
|
|
72
|
+
const s = scope || GLOBAL;
|
|
73
|
+
const nowMs = now();
|
|
74
|
+
const rows = await d.all("SELECT meter, bucket, cap, cadence, carry, period FROM credits_bucket WHERE tenant_id=? AND scope=?", [tenant, s]);
|
|
75
|
+
const topped = [];
|
|
76
|
+
let granted = 0n;
|
|
77
|
+
for (const r of rows) {
|
|
78
|
+
if (r.cadence === "none" || toBig(r.carry) !== 0n)
|
|
79
|
+
continue; // dormant or a carry buyup
|
|
80
|
+
const key = periodKey(r.cadence, nowMs);
|
|
81
|
+
if (r.period === key)
|
|
82
|
+
continue;
|
|
83
|
+
const cap = toBig(r.cap);
|
|
84
|
+
await d.run("UPDATE credits_bucket SET balance=?, period=?, refilled_at=? WHERE tenant_id=? AND scope=? AND meter=? AND bucket=?", [
|
|
85
|
+
cap,
|
|
86
|
+
key,
|
|
87
|
+
nowMs,
|
|
88
|
+
tenant,
|
|
89
|
+
s,
|
|
90
|
+
r.meter,
|
|
91
|
+
r.bucket,
|
|
92
|
+
]);
|
|
93
|
+
topped.push(`${r.meter}.${r.bucket}`);
|
|
94
|
+
granted += cap;
|
|
95
|
+
}
|
|
96
|
+
return { topped, granted };
|
|
97
|
+
}
|
|
98
|
+
/** Add µcredits to the credits buyup (Stripe deposit / admin top-up). Nets
|
|
99
|
+
* against any overdraft automatically. */
|
|
100
|
+
export async function deposit(d, tenant, scope, ucredits) {
|
|
101
|
+
if (ucredits <= 0n)
|
|
102
|
+
return;
|
|
103
|
+
const s = scope || GLOBAL;
|
|
104
|
+
await d.run("UPDATE credits_bucket SET balance = balance + ? WHERE tenant_id=? AND scope=? AND meter=? AND bucket='buyup'", [
|
|
105
|
+
ucredits,
|
|
106
|
+
tenant,
|
|
107
|
+
s,
|
|
108
|
+
CREDITS_METER,
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=meters-buckets.js.map
|