@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 @@
|
|
|
1
|
+
{"version":3,"file":"meters-buckets.js","sourceRoot":"","sources":["../../src/meter/meters-buckets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Demand, type MeterState, type ResolveResult } from "./resolve.js";
|
|
2
|
+
import { type SqlDriver } from "./sql/driver.js";
|
|
3
|
+
/** Load this (tenant, scope) as the ordered `MeterState[]` resolveCharge wants:
|
|
4
|
+
* feature meters by meter_priority, the credits meter forced terminal/last. */
|
|
5
|
+
export declare function loadMeters(d: SqlDriver, tenant: string, scope: string): Promise<MeterState[]>;
|
|
6
|
+
/** Charge a demand: load → resolveCharge → persist the draws. Idempotent on
|
|
7
|
+
* idemKey across the whole charge. Floored buckets decrement under a
|
|
8
|
+
* `balance >= amount` guard; the terminal credits buyup decrements unguarded
|
|
9
|
+
* (it is the one node allowed to go negative). */
|
|
10
|
+
export declare function charge(d: SqlDriver, now: () => number, tenant: string, scope: string, demand: Demand, idemKey?: string): Promise<ResolveResult & {
|
|
11
|
+
replay: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=meters-charge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meters-charge.d.ts","sourceRoot":"","sources":["../../src/meter/meters-charge.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAiB,MAAM,cAAc,CAAC;AAC/F,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,iBAAiB,CAAC;AAiBxD;gFACgF;AAChF,wBAAsB,UAAU,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAwBnG;AAED;;;mDAGmD;AACnD,wBAAsB,MAAM,CAC1B,CAAC,EAAE,SAAS,EACZ,GAAG,EAAE,MAAM,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CA4C9C"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CREDITS_METER, GLOBAL } from "./meters-buckets.js";
|
|
2
|
+
import { resolveCharge } from "./resolve.js";
|
|
3
|
+
import { toBig } from "./sql/driver.js";
|
|
4
|
+
/** Load this (tenant, scope) as the ordered `MeterState[]` resolveCharge wants:
|
|
5
|
+
* feature meters by meter_priority, the credits meter forced terminal/last. */
|
|
6
|
+
export async function loadMeters(d, tenant, scope) {
|
|
7
|
+
const s = scope || GLOBAL;
|
|
8
|
+
const rows = await d.all("SELECT meter, bucket, balance, unit, priority, meter_priority FROM credits_bucket WHERE tenant_id=? AND scope=? ORDER BY meter_priority ASC, priority ASC", [tenant, s]);
|
|
9
|
+
const order = [];
|
|
10
|
+
const byMeter = new Map();
|
|
11
|
+
for (const r of rows) {
|
|
12
|
+
let m = byMeter.get(r.meter);
|
|
13
|
+
if (!m) {
|
|
14
|
+
m = { unit: r.unit || "credit", buckets: [] };
|
|
15
|
+
byMeter.set(r.meter, m);
|
|
16
|
+
order.push(r.meter);
|
|
17
|
+
}
|
|
18
|
+
m.buckets.push({ key: r.bucket, balance: toBig(r.balance) });
|
|
19
|
+
}
|
|
20
|
+
// Credits meter is always terminal: drop it from its sorted slot and re-append.
|
|
21
|
+
const featureKeys = order.filter((k) => k !== CREDITS_METER);
|
|
22
|
+
const ordered = byMeter.has(CREDITS_METER) ? [...featureKeys, CREDITS_METER] : featureKeys;
|
|
23
|
+
return ordered.map((key) => {
|
|
24
|
+
const m = byMeter.get(key);
|
|
25
|
+
return { key, unit: m.unit, buckets: m.buckets };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Charge a demand: load → resolveCharge → persist the draws. Idempotent on
|
|
29
|
+
* idemKey across the whole charge. Floored buckets decrement under a
|
|
30
|
+
* `balance >= amount` guard; the terminal credits buyup decrements unguarded
|
|
31
|
+
* (it is the one node allowed to go negative). */
|
|
32
|
+
export async function charge(d, now, tenant, scope, demand, idemKey) {
|
|
33
|
+
const s = scope || GLOBAL;
|
|
34
|
+
const empty = { meters: [], creditsCharged: 0n, overdraft: 0n, shortfall: 0n, byBucket: {} };
|
|
35
|
+
if (idemKey) {
|
|
36
|
+
const nowMs = now();
|
|
37
|
+
const guard = await d.run("INSERT INTO bucket_txn (idem_key, tenant_id, scope, charged, at) VALUES (?,?,?,?,?) ON CONFLICT (idem_key) DO NOTHING", [
|
|
38
|
+
idemKey,
|
|
39
|
+
tenant,
|
|
40
|
+
s,
|
|
41
|
+
0,
|
|
42
|
+
nowMs,
|
|
43
|
+
]);
|
|
44
|
+
if (guard.rowsAffected === 0)
|
|
45
|
+
return { ...empty, replay: true };
|
|
46
|
+
}
|
|
47
|
+
const meters = await loadMeters(d, tenant, s);
|
|
48
|
+
if (meters.length === 0)
|
|
49
|
+
return { ...empty, replay: false };
|
|
50
|
+
const r = resolveCharge(meters, demand);
|
|
51
|
+
for (const [key, amount] of Object.entries(r.byBucket)) {
|
|
52
|
+
if (amount <= 0n)
|
|
53
|
+
continue;
|
|
54
|
+
const dot = key.indexOf(".");
|
|
55
|
+
const meter = key.slice(0, dot);
|
|
56
|
+
const bucket = key.slice(dot + 1);
|
|
57
|
+
// Floor guard first; if it loses (would go negative), this is the overdraft
|
|
58
|
+
// bucket - re-apply unguarded so the terminal credits buyup can go negative.
|
|
59
|
+
const guarded = await d.run("UPDATE credits_bucket SET balance = balance - ? WHERE tenant_id=? AND scope=? AND meter=? AND bucket=? AND balance >= ?", [
|
|
60
|
+
amount,
|
|
61
|
+
tenant,
|
|
62
|
+
s,
|
|
63
|
+
meter,
|
|
64
|
+
bucket,
|
|
65
|
+
amount,
|
|
66
|
+
]);
|
|
67
|
+
if (guarded.rowsAffected === 0) {
|
|
68
|
+
await d.run("UPDATE credits_bucket SET balance = balance - ? WHERE tenant_id=? AND scope=? AND meter=? AND bucket=?", [amount, tenant, s, meter, bucket]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (idemKey) {
|
|
72
|
+
await d.run("UPDATE bucket_txn SET charged=? WHERE idem_key=?", [r.creditsCharged, idemKey]);
|
|
73
|
+
}
|
|
74
|
+
return { ...r, replay: false };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=meters-charge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meters-charge.js","sourceRoot":"","sources":["../../src/meter/meters-charge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MeterStore - the persisted cascade. The schema half of the entitlement model.
|
|
3
|
+
*
|
|
4
|
+
* `resolve.ts` is pure math over an in-memory `MeterState[]`. This is its storage
|
|
5
|
+
* shape: ONE generalized bucket table that carries the META dimension the flat
|
|
6
|
+
* `credits_allowance` lacks, so a tenant can hold feature meters (smart_query,
|
|
7
|
+
* tts, …) AND the terminal credits meter, each a cascade of daily/monthly/buyup
|
|
8
|
+
* buckets:
|
|
9
|
+
*
|
|
10
|
+
* credits_bucket(tenant, scope, meter, bucket → balance, cap, cadence, unit,
|
|
11
|
+
* priority, meter_priority, carry, overdraft, …)
|
|
12
|
+
*
|
|
13
|
+
* `loadMeters()` reads the rows into the exact `MeterState[]` order `resolveCharge`
|
|
14
|
+
* wants (feature meters by meter_priority, the credits meter terminal/last).
|
|
15
|
+
* `charge()` loads, resolves, and writes the draws back idempotently. Refill is
|
|
16
|
+
* reset-to-cap for the non-carry buckets whose epoch rolled.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately SELF-CONTAINED: it does not read or write the live `credits_ledger`
|
|
19
|
+
* the queue ships today. Wiring the two is a later, coordinated step; this proves
|
|
20
|
+
* the round trip first. Pure integer (µcredit) bigint, single-conditional-UPDATE
|
|
21
|
+
* atomicity (same row-lock pattern as the rest of the package).
|
|
22
|
+
*
|
|
23
|
+
* This file is the barrel: the class composes meters-buckets.ts (config, refill,
|
|
24
|
+
* deposit) and meters-charge.ts (load, resolve, charge), and re-exports the API.
|
|
25
|
+
*/
|
|
26
|
+
import type { Demand, MeterState, ResolveResult } from "./resolve.js";
|
|
27
|
+
import type { SqlDriver } from "./sql/driver.js";
|
|
28
|
+
export { CREDITS_METER, type MeterBucketDef } from "./meters-buckets.js";
|
|
29
|
+
import type { MeterBucketDef } from "./meters-buckets.js";
|
|
30
|
+
export interface MeterStoreOptions {
|
|
31
|
+
now?: () => number;
|
|
32
|
+
}
|
|
33
|
+
export declare class MeterStore {
|
|
34
|
+
private readonly d;
|
|
35
|
+
private readonly now;
|
|
36
|
+
constructor(driver: SqlDriver, opts?: MeterStoreOptions);
|
|
37
|
+
/** Idempotent DDL: the generalized bucket table + a charge idempotency guard. */
|
|
38
|
+
init(): Promise<void>;
|
|
39
|
+
/** Define / update a bucket's config. Sets balance to cap on first insert so a
|
|
40
|
+
* freshly-defined allowance starts full; an existing bucket keeps its balance
|
|
41
|
+
* (the next refill() applies the cap). */
|
|
42
|
+
setBucket(tenant: string, scope: string, def: MeterBucketDef): Promise<void>;
|
|
43
|
+
/** Load this (tenant, scope) as the ordered `MeterState[]` resolveCharge wants:
|
|
44
|
+
* feature meters by meter_priority, the credits meter forced terminal/last. */
|
|
45
|
+
loadMeters(tenant: string, scope: string): Promise<MeterState[]>;
|
|
46
|
+
/** Refill every non-carry bucket whose epoch has rolled, reset-to-cap. Buyups
|
|
47
|
+
* (carry=1) never auto-refill. Returns the topped (meter,bucket) pairs. */
|
|
48
|
+
refill(tenant: string, scope: string): Promise<{
|
|
49
|
+
topped: string[];
|
|
50
|
+
granted: bigint;
|
|
51
|
+
}>;
|
|
52
|
+
/** Add µcredits to the credits buyup (Stripe deposit / admin top-up). Nets
|
|
53
|
+
* against any overdraft automatically. */
|
|
54
|
+
deposit(tenant: string, scope: string, ucredits: bigint): Promise<void>;
|
|
55
|
+
/** Charge a demand: load → resolveCharge → persist the draws. Idempotent on
|
|
56
|
+
* idemKey across the whole charge. Floored buckets decrement under a
|
|
57
|
+
* `balance >= amount` guard; the terminal credits buyup decrements unguarded
|
|
58
|
+
* (it is the one node allowed to go negative). */
|
|
59
|
+
charge(tenant: string, scope: string, demand: Demand, idemKey?: string): Promise<ResolveResult & {
|
|
60
|
+
replay: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=meters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meters.d.ts","sourceRoot":"","sources":["../../src/meter/meters.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,MAAM,EAAE,SAAS,EAAE,IAAI,GAAE,iBAAsB;IAK3D,iFAAiF;IAC3E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;+CAE2C;IACrC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlF;oFACgF;IAC1E,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAItE;gFAC4E;IACtE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAI3F;+CAC2C;IACrC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E;;;uDAGmD;IAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CAG5H"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as buckets from "./meters-buckets.js";
|
|
2
|
+
import * as chargeMod from "./meters-charge.js";
|
|
3
|
+
export { CREDITS_METER } from "./meters-buckets.js";
|
|
4
|
+
export class MeterStore {
|
|
5
|
+
d;
|
|
6
|
+
now;
|
|
7
|
+
constructor(driver, opts = {}) {
|
|
8
|
+
this.d = driver;
|
|
9
|
+
this.now = opts.now ?? (() => Date.now());
|
|
10
|
+
}
|
|
11
|
+
/** Idempotent DDL: the generalized bucket table + a charge idempotency guard. */
|
|
12
|
+
async init() {
|
|
13
|
+
return buckets.init(this.d);
|
|
14
|
+
}
|
|
15
|
+
/** Define / update a bucket's config. Sets balance to cap on first insert so a
|
|
16
|
+
* freshly-defined allowance starts full; an existing bucket keeps its balance
|
|
17
|
+
* (the next refill() applies the cap). */
|
|
18
|
+
async setBucket(tenant, scope, def) {
|
|
19
|
+
return buckets.setBucket(this.d, tenant, scope, def);
|
|
20
|
+
}
|
|
21
|
+
/** Load this (tenant, scope) as the ordered `MeterState[]` resolveCharge wants:
|
|
22
|
+
* feature meters by meter_priority, the credits meter forced terminal/last. */
|
|
23
|
+
async loadMeters(tenant, scope) {
|
|
24
|
+
return chargeMod.loadMeters(this.d, tenant, scope);
|
|
25
|
+
}
|
|
26
|
+
/** Refill every non-carry bucket whose epoch has rolled, reset-to-cap. Buyups
|
|
27
|
+
* (carry=1) never auto-refill. Returns the topped (meter,bucket) pairs. */
|
|
28
|
+
async refill(tenant, scope) {
|
|
29
|
+
return buckets.refill(this.d, this.now, tenant, scope);
|
|
30
|
+
}
|
|
31
|
+
/** Add µcredits to the credits buyup (Stripe deposit / admin top-up). Nets
|
|
32
|
+
* against any overdraft automatically. */
|
|
33
|
+
async deposit(tenant, scope, ucredits) {
|
|
34
|
+
return buckets.deposit(this.d, tenant, scope, ucredits);
|
|
35
|
+
}
|
|
36
|
+
/** Charge a demand: load → resolveCharge → persist the draws. Idempotent on
|
|
37
|
+
* idemKey across the whole charge. Floored buckets decrement under a
|
|
38
|
+
* `balance >= amount` guard; the terminal credits buyup decrements unguarded
|
|
39
|
+
* (it is the one node allowed to go negative). */
|
|
40
|
+
async charge(tenant, scope, demand, idemKey) {
|
|
41
|
+
return chargeMod.charge(this.d, this.now, tenant, scope, demand, idemKey);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=meters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meters.js","sourceRoot":"","sources":["../../src/meter/meters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AA6BhD,OAAO,EAAE,aAAa,EAAuB,MAAM,qBAAqB,CAAC;AAOzE,MAAM,OAAO,UAAU;IACJ,CAAC,CAAY;IACb,GAAG,CAAe;IAEnC,YAAY,MAAiB,EAAE,OAA0B,EAAE;QACzD,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC;QAChB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,IAAI;QACR,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED;;+CAE2C;IAC3C,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,KAAa,EAAE,GAAmB;QAChE,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED;oFACgF;IAChF,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,KAAa;QAC5C,OAAO,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IAED;gFAC4E;IAC5E,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,KAAa;QACxC,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACzD,CAAC;IAED;+CAC2C;IAC3C,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,KAAa,EAAE,QAAgB;QAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC1D,CAAC;IAED;;;uDAGmD;IACnD,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc,EAAE,OAAgB;QAC1E,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;CACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlanStore - a plan is a named cascade config; the tier catalog is data.
|
|
3
|
+
*
|
|
4
|
+
* `MeterStore` holds one tenant's live cascade (their `credits_bucket` rows). A
|
|
5
|
+
* PLAN is the template those rows are stamped from: a named bundle of bucket
|
|
6
|
+
* configs (caps, cadences, priorities) for the feature meters and the credits
|
|
7
|
+
* meter. Assigning a plan to a tenant MATERIALIZES it - writes their bucket rows
|
|
8
|
+
* via MeterStore.setBucket, so a fresh allowance starts full and re-assigning
|
|
9
|
+
* re-stamps the config. This is where pricing lives: a TIER *is* its plan, and
|
|
10
|
+
* changing a cap is changing the price. No new math - plans only choose which
|
|
11
|
+
* cascade a tenant runs; resolveCharge does the rest.
|
|
12
|
+
*
|
|
13
|
+
* plan(id, name, description)
|
|
14
|
+
* plan_bucket(plan, meter, bucket → cap, cadence, unit, priority, …) -- the config
|
|
15
|
+
* tenant_plan(tenant, scope → plan_id, assigned_at) -- who's on what
|
|
16
|
+
*
|
|
17
|
+
* Self-contained like MeterStore; the admin portal CRUDs these, and the queue
|
|
18
|
+
* assigns one when provisioning a tenant.
|
|
19
|
+
*/
|
|
20
|
+
import type { MeterBucketDef, MeterStore } from "./meters.js";
|
|
21
|
+
import { type SqlDriver } from "./sql/driver.js";
|
|
22
|
+
/** A named plan: metadata + the bucket configs it confers. */
|
|
23
|
+
export interface PlanDef {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
/** The cascade this plan stamps onto a tenant (feature meters + credits). */
|
|
28
|
+
buckets: MeterBucketDef[];
|
|
29
|
+
}
|
|
30
|
+
export interface PlanStoreOptions {
|
|
31
|
+
now?: () => number;
|
|
32
|
+
}
|
|
33
|
+
export declare class PlanStore {
|
|
34
|
+
private readonly d;
|
|
35
|
+
private readonly now;
|
|
36
|
+
constructor(driver: SqlDriver, opts?: PlanStoreOptions);
|
|
37
|
+
/** Idempotent DDL: the plan catalog + its bucket configs + tenant assignments. */
|
|
38
|
+
init(): Promise<void>;
|
|
39
|
+
/** Upsert a plan and REPLACE its bucket set (so removing a bucket from the def
|
|
40
|
+
* removes it from the plan). Does not touch any tenant already on the plan; a
|
|
41
|
+
* re-assign re-materializes. */
|
|
42
|
+
setPlan(plan: PlanDef): Promise<void>;
|
|
43
|
+
/** The bucket configs for a plan, as MeterBucketDefs ready to materialize. */
|
|
44
|
+
bucketsFor(planId: string): Promise<MeterBucketDef[]>;
|
|
45
|
+
/** Full plan (metadata + buckets), or null if absent. */
|
|
46
|
+
getPlan(planId: string): Promise<PlanDef | null>;
|
|
47
|
+
/** Plan catalog (metadata only). */
|
|
48
|
+
listPlans(): Promise<{
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description: string | null;
|
|
52
|
+
}[]>;
|
|
53
|
+
/** The plan a tenant is currently assigned, or null. */
|
|
54
|
+
assignedPlan(tenant: string, scope?: string): Promise<string | null>;
|
|
55
|
+
/** Assign a plan to a tenant and MATERIALIZE it: record the assignment and stamp
|
|
56
|
+
* the plan's buckets onto the tenant via MeterStore.setBucket (fresh allowances
|
|
57
|
+
* start full; existing buckets keep their balance, config re-stamped). Throws on
|
|
58
|
+
* an unknown plan. Returns how many buckets were materialized. */
|
|
59
|
+
assign(store: MeterStore, tenant: string, scope: string, planId: string): Promise<{
|
|
60
|
+
materialized: number;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=plans.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../../src/meter/plans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,KAAK,SAAS,EAAS,MAAM,iBAAiB,CAAC;AAKxD,8DAA8D;AAC9D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAeD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,MAAM,EAAE,SAAS,EAAE,IAAI,GAAE,gBAAqB;IAK1D,kFAAkF;IAC5E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC3B;;qCAEiC;IAC3B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3C,8EAA8E;IACxE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAmB3D,yDAAyD;IACnD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAOtD,oCAAoC;IAC9B,SAAS,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IAItF,wDAAwD;IAClD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAK1E;;;uEAGmE;IAC7D,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAiBlH"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { toBig } from "./sql/driver.js";
|
|
2
|
+
const GLOBAL = "";
|
|
3
|
+
export class PlanStore {
|
|
4
|
+
d;
|
|
5
|
+
now;
|
|
6
|
+
constructor(driver, opts = {}) {
|
|
7
|
+
this.d = driver;
|
|
8
|
+
this.now = opts.now ?? (() => Date.now());
|
|
9
|
+
}
|
|
10
|
+
/** Idempotent DDL: the plan catalog + its bucket configs + tenant assignments. */
|
|
11
|
+
async init() {
|
|
12
|
+
const INT = this.d.dialect === "postgres" ? "BIGINT" : "INTEGER";
|
|
13
|
+
await this.d.exec(`CREATE TABLE IF NOT EXISTS plan (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
description TEXT
|
|
17
|
+
)`);
|
|
18
|
+
await this.d.exec(`CREATE TABLE IF NOT EXISTS plan_bucket (
|
|
19
|
+
plan TEXT NOT NULL,
|
|
20
|
+
meter TEXT NOT NULL,
|
|
21
|
+
bucket TEXT NOT NULL,
|
|
22
|
+
cap ${INT} NOT NULL DEFAULT 0,
|
|
23
|
+
cadence TEXT NOT NULL DEFAULT 'none',
|
|
24
|
+
unit TEXT NOT NULL DEFAULT 'credit',
|
|
25
|
+
priority ${INT} NOT NULL DEFAULT 100,
|
|
26
|
+
meter_priority ${INT} NOT NULL DEFAULT 100,
|
|
27
|
+
carry ${INT} NOT NULL DEFAULT 0,
|
|
28
|
+
overdraft ${INT} NOT NULL DEFAULT 0,
|
|
29
|
+
source TEXT,
|
|
30
|
+
PRIMARY KEY (plan, meter, bucket)
|
|
31
|
+
)`);
|
|
32
|
+
await this.d.exec(`CREATE TABLE IF NOT EXISTS tenant_plan (
|
|
33
|
+
tenant_id TEXT NOT NULL,
|
|
34
|
+
scope TEXT NOT NULL,
|
|
35
|
+
plan_id TEXT NOT NULL,
|
|
36
|
+
assigned_at ${INT} NOT NULL,
|
|
37
|
+
PRIMARY KEY (tenant_id, scope)
|
|
38
|
+
)`);
|
|
39
|
+
}
|
|
40
|
+
/** Upsert a plan and REPLACE its bucket set (so removing a bucket from the def
|
|
41
|
+
* removes it from the plan). Does not touch any tenant already on the plan; a
|
|
42
|
+
* re-assign re-materializes. */
|
|
43
|
+
async setPlan(plan) {
|
|
44
|
+
const exists = await this.d.all("SELECT id FROM plan WHERE id=?", [plan.id]);
|
|
45
|
+
if (exists.length) {
|
|
46
|
+
await this.d.run("UPDATE plan SET name=?, description=? WHERE id=?", [plan.name, plan.description ?? null, plan.id]);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
await this.d.run("INSERT INTO plan (id, name, description) VALUES (?,?,?)", [plan.id, plan.name, plan.description ?? null]);
|
|
50
|
+
}
|
|
51
|
+
await this.d.run("DELETE FROM plan_bucket WHERE plan=?", [plan.id]);
|
|
52
|
+
for (const b of plan.buckets) {
|
|
53
|
+
await this.d.run("INSERT INTO plan_bucket (plan, meter, bucket, cap, cadence, unit, priority, meter_priority, carry, overdraft, source) VALUES (?,?,?,?,?,?,?,?,?,?,?)", [plan.id, b.meter, b.bucket, b.cap, b.cadence, b.unit, b.priority, b.meterPriority, b.carry ? 1 : 0, b.overdraft ? 1 : 0, b.source ?? null]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The bucket configs for a plan, as MeterBucketDefs ready to materialize. */
|
|
57
|
+
async bucketsFor(planId) {
|
|
58
|
+
const rows = await this.d.all("SELECT meter, bucket, cap, cadence, unit, priority, meter_priority, carry, overdraft, source FROM plan_bucket WHERE plan=? ORDER BY meter_priority ASC, priority ASC", [planId]);
|
|
59
|
+
return rows.map((r) => ({
|
|
60
|
+
meter: r.meter,
|
|
61
|
+
bucket: r.bucket,
|
|
62
|
+
cap: toBig(r.cap),
|
|
63
|
+
cadence: r.cadence,
|
|
64
|
+
unit: r.unit,
|
|
65
|
+
priority: Number(toBig(r.priority)),
|
|
66
|
+
meterPriority: Number(toBig(r.meter_priority)),
|
|
67
|
+
carry: toBig(r.carry) !== 0n,
|
|
68
|
+
overdraft: toBig(r.overdraft) !== 0n,
|
|
69
|
+
source: r.source ?? undefined,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
/** Full plan (metadata + buckets), or null if absent. */
|
|
73
|
+
async getPlan(planId) {
|
|
74
|
+
const rows = await this.d.all("SELECT id, name, description FROM plan WHERE id=?", [planId]);
|
|
75
|
+
const p = rows[0];
|
|
76
|
+
if (!p)
|
|
77
|
+
return null;
|
|
78
|
+
return { id: p.id, name: p.name, description: p.description ?? undefined, buckets: await this.bucketsFor(planId) };
|
|
79
|
+
}
|
|
80
|
+
/** Plan catalog (metadata only). */
|
|
81
|
+
async listPlans() {
|
|
82
|
+
return this.d.all("SELECT id, name, description FROM plan ORDER BY id");
|
|
83
|
+
}
|
|
84
|
+
/** The plan a tenant is currently assigned, or null. */
|
|
85
|
+
async assignedPlan(tenant, scope = GLOBAL) {
|
|
86
|
+
const rows = await this.d.all("SELECT plan_id FROM tenant_plan WHERE tenant_id=? AND scope=?", [tenant, scope || GLOBAL]);
|
|
87
|
+
return rows[0]?.plan_id ?? null;
|
|
88
|
+
}
|
|
89
|
+
/** Assign a plan to a tenant and MATERIALIZE it: record the assignment and stamp
|
|
90
|
+
* the plan's buckets onto the tenant via MeterStore.setBucket (fresh allowances
|
|
91
|
+
* start full; existing buckets keep their balance, config re-stamped). Throws on
|
|
92
|
+
* an unknown plan. Returns how many buckets were materialized. */
|
|
93
|
+
async assign(store, tenant, scope, planId) {
|
|
94
|
+
const defs = await this.bucketsFor(planId);
|
|
95
|
+
if (defs.length === 0) {
|
|
96
|
+
const known = await this.getPlan(planId);
|
|
97
|
+
if (!known)
|
|
98
|
+
throw new Error(`assign: unknown plan '${planId}'`);
|
|
99
|
+
}
|
|
100
|
+
const s = scope || GLOBAL;
|
|
101
|
+
const now = this.now();
|
|
102
|
+
const existing = await this.d.all("SELECT tenant_id FROM tenant_plan WHERE tenant_id=? AND scope=?", [tenant, s]);
|
|
103
|
+
if (existing.length) {
|
|
104
|
+
await this.d.run("UPDATE tenant_plan SET plan_id=?, assigned_at=? WHERE tenant_id=? AND scope=?", [planId, now, tenant, s]);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
await this.d.run("INSERT INTO tenant_plan (tenant_id, scope, plan_id, assigned_at) VALUES (?,?,?,?)", [tenant, s, planId, now]);
|
|
108
|
+
}
|
|
109
|
+
for (const def of defs)
|
|
110
|
+
await store.setBucket(tenant, s, def);
|
|
111
|
+
return { materialized: defs.length };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=plans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plans.js","sourceRoot":"","sources":["../../src/meter/plans.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Credits } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Converts any metered unit into integer µcredits (base units). Inference
|
|
4
|
+
* tokens, a DB query, a refinement run, bytes of egress, compute-ms - all just
|
|
5
|
+
* units with a rate.
|
|
6
|
+
*
|
|
7
|
+
* Rates are integers in µcredits, never floats, and a quote is `rate × qty`
|
|
8
|
+
* (+ optional flat base) - pure integer arithmetic. So a quote is ALWAYS an
|
|
9
|
+
* exact whole number of µcredits: rounding cannot occur. Author rates against
|
|
10
|
+
* the `CREDIT` scale, e.g. `{ perUnit: CREDIT / 10_000n }` = 1 credit / 10k
|
|
11
|
+
* units = 100 µcredits/unit. Pick ratios that divide CREDIT cleanly (they do
|
|
12
|
+
* for any sane price) and there is never anything to round.
|
|
13
|
+
*/
|
|
14
|
+
import { CREDIT } from "./units.js";
|
|
15
|
+
export interface Rate {
|
|
16
|
+
/** µcredits charged per unit of work - an INTEGER, never a float.
|
|
17
|
+
* Author against CREDIT, e.g. `CREDIT / 1_000n` (0.001 credit/unit). */
|
|
18
|
+
perUnit: bigint;
|
|
19
|
+
/** optional flat µcredits added per quote (e.g. a base request fee). */
|
|
20
|
+
base?: bigint;
|
|
21
|
+
}
|
|
22
|
+
export declare class PriceBook {
|
|
23
|
+
private rates;
|
|
24
|
+
constructor(initial?: Record<string, Rate>);
|
|
25
|
+
set(unit: string, rate: Rate): this;
|
|
26
|
+
has(unit: string): boolean;
|
|
27
|
+
/** Quote `qty` of `unit` as integer µcredits - exact, never rounded.
|
|
28
|
+
* Unknown unit or qty ≤ 0 → 0n (caller may guard). */
|
|
29
|
+
quote(unit: string, qty: number): Credits;
|
|
30
|
+
}
|
|
31
|
+
export { CREDIT };
|
|
32
|
+
//# sourceMappingURL=pricebook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricebook.d.ts","sourceRoot":"","sources":["../../src/meter/pricebook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,MAAM,WAAW,IAAI;IACnB;6EACyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAA2B;gBAE5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC;IAI1C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI;IAKnC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;2DACuD;IACvD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;CAK1C;AAED,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts any metered unit into integer µcredits (base units). Inference
|
|
3
|
+
* tokens, a DB query, a refinement run, bytes of egress, compute-ms - all just
|
|
4
|
+
* units with a rate.
|
|
5
|
+
*
|
|
6
|
+
* Rates are integers in µcredits, never floats, and a quote is `rate × qty`
|
|
7
|
+
* (+ optional flat base) - pure integer arithmetic. So a quote is ALWAYS an
|
|
8
|
+
* exact whole number of µcredits: rounding cannot occur. Author rates against
|
|
9
|
+
* the `CREDIT` scale, e.g. `{ perUnit: CREDIT / 10_000n }` = 1 credit / 10k
|
|
10
|
+
* units = 100 µcredits/unit. Pick ratios that divide CREDIT cleanly (they do
|
|
11
|
+
* for any sane price) and there is never anything to round.
|
|
12
|
+
*/
|
|
13
|
+
import { CREDIT } from "./units.js";
|
|
14
|
+
export class PriceBook {
|
|
15
|
+
rates = new Map();
|
|
16
|
+
constructor(initial) {
|
|
17
|
+
if (initial)
|
|
18
|
+
for (const [unit, rate] of Object.entries(initial))
|
|
19
|
+
this.rates.set(unit, rate);
|
|
20
|
+
}
|
|
21
|
+
set(unit, rate) {
|
|
22
|
+
this.rates.set(unit, rate);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
has(unit) {
|
|
26
|
+
return this.rates.has(unit);
|
|
27
|
+
}
|
|
28
|
+
/** Quote `qty` of `unit` as integer µcredits - exact, never rounded.
|
|
29
|
+
* Unknown unit or qty ≤ 0 → 0n (caller may guard). */
|
|
30
|
+
quote(unit, qty) {
|
|
31
|
+
const r = this.rates.get(unit);
|
|
32
|
+
if (!r || qty <= 0)
|
|
33
|
+
return 0n;
|
|
34
|
+
return (r.base ?? 0n) + r.perUnit * BigInt(Math.trunc(qty));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export { CREDIT };
|
|
38
|
+
//# sourceMappingURL=pricebook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricebook.js","sourceRoot":"","sources":["../../src/meter/pricebook.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAUpC,MAAM,OAAO,SAAS;IACZ,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IAExC,YAAY,OAA8B;QACxC,IAAI,OAAO;YAAE,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9F,CAAC;IAED,GAAG,CAAC,IAAY,EAAE,IAAU;QAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;2DACuD;IACvD,KAAK,CAAC,IAAY,EAAE,GAAW;QAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAC9B,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC;CACF;AAED,OAAO,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
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 { type CascadeDraw, type CascadeNode } from "./cascade.js";
|
|
35
|
+
/** One meter in the wallet cascade. Its buckets are the inner cascade, ordered
|
|
36
|
+
* (daily → monthly → buyup). The LAST meter in a wallet is the terminal credits
|
|
37
|
+
* meter; every earlier meter is a feature meter that floors and spills. */
|
|
38
|
+
export interface MeterState {
|
|
39
|
+
/** Feature key (e.g. 'smart_query'), or the terminal meter's key (e.g. 'credits'). */
|
|
40
|
+
readonly key: string;
|
|
41
|
+
/** Billing unit, for the audit/display only ('call', 'credit', …). The math is
|
|
42
|
+
* unit-agnostic; conversion happens via the demand's `creditPricePerUnit`. */
|
|
43
|
+
readonly unit: string;
|
|
44
|
+
/** Ordered buckets - daily, monthly, buyup. The inner cascade draws these. */
|
|
45
|
+
readonly buckets: readonly CascadeNode[];
|
|
46
|
+
}
|
|
47
|
+
/** What a request wants to consume. Either a feature call (drawn from that
|
|
48
|
+
* feature's meter, overflow priced into credits) or a raw credit charge. */
|
|
49
|
+
export type Demand = {
|
|
50
|
+
/** The feature meter this call consumes. */
|
|
51
|
+
readonly feature: string;
|
|
52
|
+
/** Calls this request consumes. Default 1. */
|
|
53
|
+
readonly units?: bigint;
|
|
54
|
+
/** µcredits one unmet call costs - the price that converts overflow (and the
|
|
55
|
+
* whole call when the feature has no meter) into credit demand. */
|
|
56
|
+
readonly creditPricePerUnit: bigint;
|
|
57
|
+
} | {
|
|
58
|
+
/** µcredits to charge directly to the credits meter (generic inference). */
|
|
59
|
+
readonly ucredits: bigint;
|
|
60
|
+
};
|
|
61
|
+
/** Per-meter outcome, in resolution order. */
|
|
62
|
+
export interface MeterDraw {
|
|
63
|
+
readonly key: string;
|
|
64
|
+
readonly unit: string;
|
|
65
|
+
/** Per-bucket draws within this meter. */
|
|
66
|
+
readonly draws: readonly CascadeDraw[];
|
|
67
|
+
/** Units this meter covered (in its own unit: calls for a feature, µcredits for credits). */
|
|
68
|
+
readonly applied: bigint;
|
|
69
|
+
/** Units this meter could NOT cover and passed on (converted to credits for a
|
|
70
|
+
* feature meter; unmet credit demand for a non-overdrafting credits meter). */
|
|
71
|
+
readonly overflow: bigint;
|
|
72
|
+
}
|
|
73
|
+
export interface ResolveResult {
|
|
74
|
+
/** Per-meter breakdown, resolution order: feature meter (if any), then credits. */
|
|
75
|
+
readonly meters: readonly MeterDraw[];
|
|
76
|
+
/** µcredits actually drawn from the credits meter (allowances + buyup overdraft). */
|
|
77
|
+
readonly creditsCharged: bigint;
|
|
78
|
+
/** New negative incurred on the credits buyup this charge (>= 0). 0 unless the
|
|
79
|
+
* buyup overdrafted. */
|
|
80
|
+
readonly overdraft: bigint;
|
|
81
|
+
/** Unmet credit demand - always 0 when the terminal buyup overdrafts (default). */
|
|
82
|
+
readonly shortfall: bigint;
|
|
83
|
+
/** Flat per-bucket map keyed `meter.bucket` (nonzero draws only), for the audit row. */
|
|
84
|
+
readonly byBucket: Readonly<Record<string, bigint>>;
|
|
85
|
+
}
|
|
86
|
+
/** The credit cost of a demand, independent of any balance or meter - the meter
|
|
87
|
+
* reading that is ALWAYS logged (accounting ≠ debiting). For a feature call this
|
|
88
|
+
* is `units × price` even when an allowance will cover it for free; for a credit
|
|
89
|
+
* demand it is the amount itself. */
|
|
90
|
+
export declare function creditCost(demand: Demand): bigint;
|
|
91
|
+
/**
|
|
92
|
+
* Draw a demand across the wallet's meters. Pure: input meters are never mutated.
|
|
93
|
+
*
|
|
94
|
+
* @param meters ordered meters - feature meters first, the terminal credits meter LAST.
|
|
95
|
+
* @param demand a feature call or a raw credit charge.
|
|
96
|
+
* @param opts.terminalOverdraft whether the credits buyup may go negative (default
|
|
97
|
+
* true - the production rail buys-back-up). false makes the whole wallet floor
|
|
98
|
+
* and report the unmet amount as `shortfall` (useful for a dry-run gate check).
|
|
99
|
+
*/
|
|
100
|
+
export declare function resolveCharge(meters: readonly MeterState[], demand: Demand, opts?: {
|
|
101
|
+
terminalOverdraft?: boolean;
|
|
102
|
+
}): ResolveResult;
|
|
103
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/meter/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAW,MAAM,cAAc,CAAC;AAE3E;;4EAE4E;AAC5E,MAAM,WAAW,UAAU;IACzB,sFAAsF;IACtF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;mFAC+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;CAC1C;AAED;6EAC6E;AAC7E,MAAM,MAAM,MAAM,GACd;IACE,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;wEACoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC,GACD;IACE,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEN,8CAA8C;AAC9C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;oFACgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,qFAAqF;IACrF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;6BACyB;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAID;;;sCAGsC;AACtC,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGjD;AAQD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,aAAa,CAwCtI"}
|