@arnaudjnn/billing-tools 2.19.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapters/workos-org.d.ts +23 -0
- package/dist/adapters/workos-org.d.ts.map +1 -1
- package/dist/adapters/workos-org.js +4 -2
- package/dist/adapters/workos-org.js.map +1 -1
- package/dist/bound-api.d.ts +173 -0
- package/dist/bound-api.d.ts.map +1 -0
- package/dist/bound-api.js +152 -0
- package/dist/bound-api.js.map +1 -0
- package/dist/cli/commands.js +25 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/create-billing.d.ts +128 -41
- package/dist/create-billing.d.ts.map +1 -1
- package/dist/create-billing.js +24 -14
- package/dist/create-billing.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/entries/agent-auth.d.ts +5 -0
- package/dist/entries/agent-auth.d.ts.map +1 -0
- package/dist/entries/agent-auth.js +17 -0
- package/dist/entries/agent-auth.js.map +1 -0
- package/dist/entries/plans.d.ts +4 -0
- package/dist/entries/plans.d.ts.map +1 -0
- package/dist/entries/plans.js +36 -0
- package/dist/entries/plans.js.map +1 -0
- package/dist/entries/routes.d.ts +5 -0
- package/dist/entries/routes.d.ts.map +1 -0
- package/dist/entries/routes.js +17 -0
- package/dist/entries/routes.js.map +1 -0
- package/dist/entries/tools.d.ts +6 -0
- package/dist/entries/tools.d.ts.map +1 -0
- package/dist/entries/tools.js +20 -0
- package/dist/entries/tools.js.map +1 -0
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -15
- package/dist/index.js.map +1 -1
- package/dist/metering.d.ts +4 -3
- package/dist/metering.d.ts.map +1 -1
- package/dist/metering.js.map +1 -1
- package/dist/plan-model.d.ts +58 -3
- package/dist/plan-model.d.ts.map +1 -1
- package/dist/plan-model.js +61 -4
- package/dist/plan-model.js.map +1 -1
- package/dist/plans.d.ts +2 -2
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +1 -1
- package/dist/plans.js.map +1 -1
- package/dist/tools/billing.d.ts +2 -1
- package/dist/tools/billing.d.ts.map +1 -1
- package/dist/tools/billing.js +185 -64
- package/dist/tools/billing.js.map +1 -1
- package/dist/tools/management.d.ts +2 -1
- package/dist/tools/management.d.ts.map +1 -1
- package/dist/tools/management.js +210 -187
- package/dist/tools/management.js.map +1 -1
- package/dist/tools/register.d.ts +13 -1
- package/dist/tools/register.d.ts.map +1 -1
- package/dist/tools/register.js +28 -6
- package/dist/tools/register.js.map +1 -1
- package/dist/tools/subscription.d.ts +2 -1
- package/dist/tools/subscription.d.ts.map +1 -1
- package/dist/tools/subscription.js +115 -108
- package/dist/tools/subscription.js.map +1 -1
- package/dist/usage-ledger.d.ts +15 -37
- package/dist/usage-ledger.d.ts.map +1 -1
- package/dist/usage-ledger.js +24 -119
- package/dist/usage-ledger.js.map +1 -1
- package/dist/usage-scopes.d.ts +55 -0
- package/dist/usage-scopes.d.ts.map +1 -0
- package/dist/usage-scopes.js +284 -0
- package/dist/usage-scopes.js.map +1 -0
- package/dist/util/clearout.d.ts.map +1 -1
- package/dist/util/clearout.js +15 -3
- package/dist/util/clearout.js.map +1 -1
- package/package.json +21 -1
- package/dist/usage-counters.d.ts +0 -104
- package/dist/usage-counters.d.ts.map +0 -1
- package/dist/usage-counters.js +0 -214
- package/dist/usage-counters.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/tools/register.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAyB,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAgC,MAAM,mBAAmB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAoB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/tools/register.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { type PlanCatalog } from "../plans.js";
|
|
3
|
+
import { type ToolCapabilities } from "../plan-model.js";
|
|
3
4
|
import type { BillingAdapter, ResolvedConfig } from "../types.js";
|
|
4
5
|
export interface SubscriptionToolOptions {
|
|
5
6
|
plans: PlanCatalog;
|
|
@@ -10,5 +11,5 @@ export interface SubscriptionToolOptions {
|
|
|
10
11
|
* Defaults to the rates already on the subscription's items. */
|
|
11
12
|
taxRates?: (orgId: string) => Promise<string[]> | string[];
|
|
12
13
|
}
|
|
13
|
-
export declare function registerSubscriptionTools(server: McpServer, adapter: BillingAdapter, config: ResolvedConfig, opts: SubscriptionToolOptions): void;
|
|
14
|
+
export declare function registerSubscriptionTools(server: McpServer, adapter: BillingAdapter, config: ResolvedConfig, opts: SubscriptionToolOptions, caps?: ToolCapabilities): void;
|
|
14
15
|
//# sourceMappingURL=subscription.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/tools/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BlE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,WAAW,CAAC;IACnB;2CACuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;qEACiE;IACjE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CAC5D;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/tools/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BlE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,WAAW,CAAC;IACnB;2CACuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;qEACiE;IACjE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;CAC5D;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,uBAAuB,EAC7B,IAAI,GAAE,gBAAwC,QA4K/C"}
|
|
@@ -3,6 +3,7 @@ import { enforceAdmin } from "../auth.js";
|
|
|
3
3
|
import { stripeConfigured } from "../billing.js";
|
|
4
4
|
import { cancelPlan, changePlan, planActions, previewPlanChange, PlanChangeError, } from "../subscription.js";
|
|
5
5
|
import { normalizePlans } from "../plans.js";
|
|
6
|
+
import { ALL_TOOL_CAPABILITIES } from "../plan-model.js";
|
|
6
7
|
// The subscription lifecycle, as tools.
|
|
7
8
|
//
|
|
8
9
|
// This is the gap the QA pass found: changing plan was reachable from the app's
|
|
@@ -29,130 +30,136 @@ function toolError(e) {
|
|
|
29
30
|
}
|
|
30
31
|
return err(e instanceof Error ? e.message : String(e));
|
|
31
32
|
}
|
|
32
|
-
export function registerSubscriptionTools(server, adapter, config, opts) {
|
|
33
|
+
export function registerSubscriptionTools(server, adapter, config, opts, caps = ALL_TOOL_CAPABILITIES) {
|
|
33
34
|
const seatsSchema = z
|
|
34
35
|
.record(z.string(), z.number().int().min(0))
|
|
35
36
|
.optional()
|
|
36
37
|
.describe('Seats per seat type, e.g. {"standard": 3, "premium": 1}. Defaults to the plan minimum');
|
|
37
38
|
const rates = async (orgId) => (opts.taxRates ? await opts.taxRates(orgId) : undefined);
|
|
38
|
-
|
|
39
|
+
// The three tools that CHANGE a subscription need a plan a customer can move to
|
|
40
|
+
// without a salesperson. On a catalogue that is entirely free + quote-only there
|
|
41
|
+
// is no such move, and `change_plan` could only ever refuse — while `get_plan`
|
|
42
|
+
// below stays a useful read on any catalogue.
|
|
43
|
+
if (caps.lifecycle) {
|
|
44
|
+
server.tool("preview_plan_change", `What moving to a plan would cost, WITHOUT making the change.
|
|
39
45
|
|
|
40
46
|
Returns four numbers, and they mean different things: \`due_now\` is charged today
|
|
41
47
|
(zero unless proration is invoice_now), \`next_invoice_total\` is what the NEXT
|
|
42
48
|
invoice comes to including any deferred difference, \`recurring_total\` is the
|
|
43
49
|
steady-state price after that, and \`credit_applied\` is the unused part of the
|
|
44
|
-
current plan credited back. On a mid-cycle upgrade the next invoice is LARGER
|
|
50
|
+
current plan credited back. On a mid-cycle upgrade the next invoice is LARGER
|
|
45
51
|
than the plan price — quote it together with \`next_invoice_at\` so the customer
|
|
46
|
-
is told rather than surprised.
|
|
52
|
+
is told rather than surprised.
|
|
47
53
|
|
|
48
54
|
Quote this before change_plan, with the same \`proration\`: both come from the
|
|
49
55
|
same arithmetic, so the number shown is the number charged.`, {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
three: an upgrade applies immediately and is prorated, a downgrade is scheduled for
|
|
94
|
-
the end of the paid period (no refund, nothing lost early), and moving to the free
|
|
95
|
-
plan cancels at the period end. Moving back to the current plan while a cancellation
|
|
56
|
+
plan: z.string().describe("Target plan key, from list_plans"),
|
|
57
|
+
interval: z.enum(["monthly", "yearly"]).optional(),
|
|
58
|
+
seats: seatsSchema,
|
|
59
|
+
timing: z
|
|
60
|
+
.enum(["auto", "now", "period_end"])
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Default `auto`: upgrades apply now, downgrades at the period end"),
|
|
63
|
+
proration: z
|
|
64
|
+
.enum(["next_invoice", "invoice_now", "none"])
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Must match what you pass to change_plan — it decides whether the difference is billed today"),
|
|
67
|
+
}, async ({ plan, interval, seats, timing, proration }) => {
|
|
68
|
+
const auth = await enforceAdmin(adapter, "preview_plan_change");
|
|
69
|
+
if ("isError" in auth)
|
|
70
|
+
return auth;
|
|
71
|
+
if (!stripeConfigured())
|
|
72
|
+
return err("Billing is not configured (STRIPE_SECRET_KEY unset).");
|
|
73
|
+
try {
|
|
74
|
+
const p = await previewPlanChange(adapter, auth.orgId, {
|
|
75
|
+
plans: opts.plans,
|
|
76
|
+
to: { plan, interval, seats },
|
|
77
|
+
currency: config.currency,
|
|
78
|
+
timing,
|
|
79
|
+
proration,
|
|
80
|
+
taxRates: await rates(auth.orgId),
|
|
81
|
+
});
|
|
82
|
+
return json({
|
|
83
|
+
outcome: p.kind,
|
|
84
|
+
currency: p.currency,
|
|
85
|
+
due_now: p.dueNow,
|
|
86
|
+
next_invoice_total: p.nextInvoiceTotal,
|
|
87
|
+
recurring_total: p.recurringTotal,
|
|
88
|
+
credit_applied: p.credit,
|
|
89
|
+
effective_at: p.effectiveAt,
|
|
90
|
+
next_invoice_at: p.nextInvoiceAt,
|
|
91
|
+
lines: p.lines,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return toolError(e);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
server.tool("change_plan", `Move this workspace to another plan — up, down, or off. One entry point for all
|
|
99
|
+
three: an upgrade applies immediately and is prorated, a downgrade is scheduled for
|
|
100
|
+
the end of the paid period (no refund, nothing lost early), and moving to the free
|
|
101
|
+
plan cancels at the period end. Moving back to the current plan while a cancellation
|
|
96
102
|
is pending resumes it. Preview the cost first with preview_plan_change.`, {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
refunded and nothing is lost today — the workspace drops to the free plan when the
|
|
103
|
+
plan: z.string().describe("Target plan key, from list_plans"),
|
|
104
|
+
interval: z.enum(["monthly", "yearly"]).optional(),
|
|
105
|
+
seats: seatsSchema,
|
|
106
|
+
timing: z
|
|
107
|
+
.enum(["auto", "now", "period_end"])
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Default `auto`: upgrades now, downgrades at the period end"),
|
|
110
|
+
proration: z
|
|
111
|
+
.enum(["next_invoice", "invoice_now", "none"])
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Default `next_invoice`: the prorated difference lands on the next invoice"),
|
|
114
|
+
}, async ({ plan, interval, seats, timing, proration }) => {
|
|
115
|
+
const auth = await enforceAdmin(adapter, "change_plan");
|
|
116
|
+
if ("isError" in auth)
|
|
117
|
+
return auth;
|
|
118
|
+
if (!stripeConfigured())
|
|
119
|
+
return err("Billing is not configured (STRIPE_SECRET_KEY unset).");
|
|
120
|
+
try {
|
|
121
|
+
const r = await changePlan(adapter, auth.orgId, {
|
|
122
|
+
plans: opts.plans,
|
|
123
|
+
to: { plan, interval, seats },
|
|
124
|
+
config,
|
|
125
|
+
currency: config.currency,
|
|
126
|
+
timing,
|
|
127
|
+
proration,
|
|
128
|
+
returnUrl: opts.returnUrl,
|
|
129
|
+
taxRates: await rates(auth.orgId),
|
|
130
|
+
});
|
|
131
|
+
return json({
|
|
132
|
+
outcome: r.kind,
|
|
133
|
+
plan: r.plan,
|
|
134
|
+
status: r.status,
|
|
135
|
+
effective_at: r.effectiveAt,
|
|
136
|
+
subscription_id: r.subscriptionId,
|
|
137
|
+
// Only on the first-purchase path: there is nothing to change yet, so
|
|
138
|
+
// the caller has to send someone through Checkout.
|
|
139
|
+
...(r.kind === "checkout" ? { checkout_client_secret: r.clientSecret, checkout_session_id: r.sessionId } : {}),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return toolError(e);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
server.tool("cancel_plan", `Cancel the subscription at the end of the period already paid for. Nothing is
|
|
147
|
+
refunded and nothing is lost today — the workspace drops to the free plan when the
|
|
142
148
|
period ends. Reverse it before then with change_plan back to the current plan.`, {}, async () => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
const auth = await enforceAdmin(adapter, "cancel_plan");
|
|
150
|
+
if ("isError" in auth)
|
|
151
|
+
return auth;
|
|
152
|
+
if (!stripeConfigured())
|
|
153
|
+
return err("Billing is not configured (STRIPE_SECRET_KEY unset).");
|
|
154
|
+
try {
|
|
155
|
+
const r = await cancelPlan(adapter, auth.orgId, { plans: opts.plans, currency: config.currency });
|
|
156
|
+
return json({ outcome: r.kind, plan: r.plan, status: r.status, effective_at: r.effectiveAt });
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
return toolError(e);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
156
163
|
server.tool("get_plan", `The workspace's current plan: what it is on, whether a change is already
|
|
157
164
|
scheduled, and which moves are available (the plan above, the plan below, whether it
|
|
158
165
|
can be cancelled) — the same set the billing screen offers.`, {}, async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/tools/subscription.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EACL,UAAU,EACV,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/tools/subscription.ts"],"names":[],"mappings":"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"}
|
package/dist/usage-ledger.d.ts
CHANGED
|
@@ -82,6 +82,19 @@ export declare const USAGE_METER_EVENT = "billing_tools_usage";
|
|
|
82
82
|
export declare function stripeMeterUsageLedger(opts?: {
|
|
83
83
|
eventName?: string;
|
|
84
84
|
}): UsageLedger;
|
|
85
|
+
/**
|
|
86
|
+
* The meter id for `eventName`, creating the meter when it doesn't exist.
|
|
87
|
+
*
|
|
88
|
+
* NEVER THROWS — the same rule `defaultPaymentMethodConfig` follows, and for the
|
|
89
|
+
* same reason: this is reached from `ledger.record` on the hot path of every
|
|
90
|
+
* metered call, so a key without permission to create a meter must not take the
|
|
91
|
+
* product down. It degrades to `null` (windows read 0) and says so once, loudly,
|
|
92
|
+
* because that state is otherwise indistinguishable from no usage.
|
|
93
|
+
*/
|
|
94
|
+
export declare function meterIdFor(eventName: string, opts?: {
|
|
95
|
+
create?: boolean;
|
|
96
|
+
displayName?: string;
|
|
97
|
+
}): Promise<string | null>;
|
|
85
98
|
/**
|
|
86
99
|
* Ensure the usage meter exists, eagerly. Idempotent, like `ensurePlans`.
|
|
87
100
|
*
|
|
@@ -108,8 +121,8 @@ export declare function stripeUsageLedger(opts?: {
|
|
|
108
121
|
eventName?: string;
|
|
109
122
|
/**
|
|
110
123
|
* Where a per-CALLER window is read from. Default: the balance ledger (exact,
|
|
111
|
-
* per-member, wallet-funded only).
|
|
112
|
-
* per-member window is INCLUDED.
|
|
124
|
+
* per-member, wallet-funded only). `stripeScopeUsageLedger()` belongs here for
|
|
125
|
+
* a plan whose per-member window is INCLUDED — it sees both halves.
|
|
113
126
|
*/
|
|
114
127
|
perCaller?: UsageLedger;
|
|
115
128
|
/**
|
|
@@ -136,39 +149,4 @@ export declare function defaultUsageLedger(): UsageLedger;
|
|
|
136
149
|
* built, which is itself worth seeing.
|
|
137
150
|
*/
|
|
138
151
|
export declare function warnLedgerGaps(plans: PlanCatalog, ledger: UsageLedger): void;
|
|
139
|
-
/** The one method this needs from a driver. */
|
|
140
|
-
export interface SqlClient {
|
|
141
|
-
query<R = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{
|
|
142
|
-
rows: R[];
|
|
143
|
-
}>;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* The table, and the three indexes the two queries below actually use.
|
|
147
|
-
*
|
|
148
|
-
* Shipped rather than described because the UNIQUE index is PARTIAL, and that is
|
|
149
|
-
* not a detail a consumer should have to rediscover: `idempotency_key` is
|
|
150
|
-
* nullable, only non-null keys are unique, and Postgres refuses to infer a
|
|
151
|
-
* partial index for `ON CONFLICT` unless the predicate is repeated at the insert.
|
|
152
|
-
* Getting it wrong fails every insert with 42P10.
|
|
153
|
-
*
|
|
154
|
-
* Idempotent, so it is safe to run from a migration on every deploy.
|
|
155
|
-
*/
|
|
156
|
-
export declare const USAGE_EVENTS = "\nCREATE TABLE IF NOT EXISTS usage_events (\n id bigserial PRIMARY KEY,\n org_id text NOT NULL,\n customer_id text,\n action text NOT NULL,\n cost integer NOT NULL,\n -- Which allowance paid: pool | pack | wallet. Kept because \"included vs\n -- charged\" is unanswerable afterwards otherwise.\n funded text NOT NULL,\n caller_kind text,\n caller_id text,\n -- Makes a retried call a no-op rather than a double count.\n idempotency_key text,\n at timestamptz NOT NULL DEFAULT now()\n);\nCREATE INDEX IF NOT EXISTS usage_events_org_at_idx\n ON usage_events (org_id, at DESC);\nCREATE INDEX IF NOT EXISTS usage_events_org_caller_at_idx\n ON usage_events (org_id, caller_kind, caller_id, at DESC);\nCREATE UNIQUE INDEX IF NOT EXISTS usage_events_idempotency_idx\n ON usage_events (idempotency_key) WHERE idempotency_key IS NOT NULL;\n";
|
|
157
|
-
/** Create the table + indexes if they are missing. Call it from your migration;
|
|
158
|
-
* it is idempotent. Consumers who own their schema can read `USAGE_EVENTS`
|
|
159
|
-
* instead and paste it into their own migration tool. */
|
|
160
|
-
/** @deprecated Renamed to `USAGE_EVENTS` — the constant IS the table's shape, so
|
|
161
|
-
* the name says which table rather than which kind of string. Kept as an alias
|
|
162
|
-
* because a rename that breaks a consumer's migration script is not worth the
|
|
163
|
-
* tidiness. */
|
|
164
|
-
export declare const USAGE_EVENTS_DDL = "\nCREATE TABLE IF NOT EXISTS usage_events (\n id bigserial PRIMARY KEY,\n org_id text NOT NULL,\n customer_id text,\n action text NOT NULL,\n cost integer NOT NULL,\n -- Which allowance paid: pool | pack | wallet. Kept because \"included vs\n -- charged\" is unanswerable afterwards otherwise.\n funded text NOT NULL,\n caller_kind text,\n caller_id text,\n -- Makes a retried call a no-op rather than a double count.\n idempotency_key text,\n at timestamptz NOT NULL DEFAULT now()\n);\nCREATE INDEX IF NOT EXISTS usage_events_org_at_idx\n ON usage_events (org_id, at DESC);\nCREATE INDEX IF NOT EXISTS usage_events_org_caller_at_idx\n ON usage_events (org_id, caller_kind, caller_id, at DESC);\nCREATE UNIQUE INDEX IF NOT EXISTS usage_events_idempotency_idx\n ON usage_events (idempotency_key) WHERE idempotency_key IS NOT NULL;\n";
|
|
165
|
-
export declare function ensureUsageLedgerTable(client: SqlClient): Promise<void>;
|
|
166
|
-
/**
|
|
167
|
-
* Usage counted in Postgres: exact, attributable per caller, and summable over
|
|
168
|
-
* any window a plan declares (hour / day / week / month / cycle).
|
|
169
|
-
*
|
|
170
|
-
* `record` is on the hot path of every metered execution: one INSERT, no read,
|
|
171
|
-
* no transaction. `total` is one aggregate over an index-covered range.
|
|
172
|
-
*/
|
|
173
|
-
export declare function postgresUsageLedger(client: SqlClient): UsageLedger;
|
|
174
152
|
//# sourceMappingURL=usage-ledger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage-ledger.d.ts","sourceRoot":"","sources":["../src/usage-ledger.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAmBpG,uCAAuC;AACvC,MAAM,MAAM,aAAa;AACvB,oCAAoC;AAClC,MAAM;AACR,8BAA8B;GAC5B,MAAM;AACR,uDAAuD;GACrD,QAAQ,CAAC;AAEb,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,iCAAiC;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,mCAAmC;IACnC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,WAAW,CAWtD;AAED,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,WAAW,CA6Cb;
|
|
1
|
+
{"version":3,"file":"usage-ledger.d.ts","sourceRoot":"","sources":["../src/usage-ledger.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAmBpG,uCAAuC;AACvC,MAAM,MAAM,aAAa;AACvB,oCAAoC;AAClC,MAAM;AACR,8BAA8B;GAC5B,MAAM;AACR,uDAAuD;GACrD,QAAQ,CAAC;AAEb,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,iCAAiC;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,mCAAmC;IACnC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,WAAW,CAWtD;AAED,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,WAAW,CA6Cb;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmCxB;AAaD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAOhD;AAED;;oEAEoE;AACpE,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAkCD,wBAAgB,iBAAiB,CAC/B,IAAI,GAAE;IACJ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;CAClB,GACL,WAAW,CA8Bb;AAeD,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAwB5E"}
|
package/dist/usage-ledger.js
CHANGED
|
@@ -132,7 +132,7 @@ async function findMeter(eventName) {
|
|
|
132
132
|
* product down. It degrades to `null` (windows read 0) and says so once, loudly,
|
|
133
133
|
* because that state is otherwise indistinguishable from no usage.
|
|
134
134
|
*/
|
|
135
|
-
function meterIdFor(eventName, opts = {}) {
|
|
135
|
+
export function meterIdFor(eventName, opts = {}) {
|
|
136
136
|
const hit = meterIds.get(eventName);
|
|
137
137
|
if (hit)
|
|
138
138
|
return Promise.resolve(hit);
|
|
@@ -145,7 +145,7 @@ function meterIdFor(eventName, opts = {}) {
|
|
|
145
145
|
const resolve = (async () => {
|
|
146
146
|
try {
|
|
147
147
|
const found = (await findMeter(eventName)) ??
|
|
148
|
-
(opts.create === false ? null : await createMeter(eventName));
|
|
148
|
+
(opts.create === false ? null : await createMeter(eventName, opts.displayName));
|
|
149
149
|
// Only a real id is remembered. Caching the miss would poison the memo for
|
|
150
150
|
// the life of the process — including for `ensureMeters`, which probes with
|
|
151
151
|
// `create: false` and creates the meter itself immediately afterwards.
|
|
@@ -221,20 +221,27 @@ export function invalidateMeters() {
|
|
|
221
221
|
// per-member, but they only exist where money moved, so INCLUDED per-member
|
|
222
222
|
// usage is invisible to them.
|
|
223
223
|
//
|
|
224
|
-
// That last line
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
// lands). `createBilling` warns for exactly that combination rather than for any
|
|
228
|
-
// cap, which is the difference between "you need a database" and "you don't".
|
|
224
|
+
// That last line USED to be the honest limit of running with no store — a plan
|
|
225
|
+
// that both includes usage and meters it per member needed a database, and this
|
|
226
|
+
// library shipped three to fill the gap.
|
|
229
227
|
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
228
|
+
// It no longer is. `stripeScopeUsageLedger()` gives each usage scope a Stripe
|
|
229
|
+
// Customer of its own, which is the second grouping key Stripe otherwise withholds
|
|
230
|
+
// (a meter summary can only be filtered by customer — verified against the API),
|
|
231
|
+
// and reads the wallet-funded half from the debits, where it is exact and has no
|
|
232
|
+
// aggregation lag. So:
|
|
233
|
+
//
|
|
234
|
+
// stripeUsageLedger({ perCaller: stripeScopeUsageLedger() })
|
|
235
|
+
//
|
|
236
|
+
// counts every window this library can express, with no store anywhere. It is not
|
|
237
|
+
// the DEFAULT because it creates Stripe Customers as a side effect, which a
|
|
238
|
+
// consumer should opt into rather than discover; `warnLedgerGaps` and
|
|
239
|
+
// `checkPlansConfig` name it for any config that needs it.
|
|
233
240
|
export function stripeUsageLedger(opts = {}) {
|
|
234
241
|
const meter = opts.orgWide ?? stripeMeterUsageLedger({ eventName: opts.eventName });
|
|
235
242
|
const perCaller = opts.perCaller ?? stripeBalanceUsageLedger();
|
|
236
243
|
// Each axis inherits the leg that answers it, so `stripeUsageLedger({ perCaller:
|
|
237
|
-
//
|
|
244
|
+
// stripeScopeUsageLedger() })` reports full coverage and the bare composite reports
|
|
238
245
|
// the org axis only — which is exactly what each can do. A leg that declares
|
|
239
246
|
// nothing (a consumer's own) makes the composite silent too rather than making it
|
|
240
247
|
// guess: an invented `false` would fail a config that is perfectly wired.
|
|
@@ -301,114 +308,12 @@ export function warnLedgerGaps(plans, ledger) {
|
|
|
301
308
|
}
|
|
302
309
|
if (caller.length) {
|
|
303
310
|
say(`plans ${caller.map((m) => m.key).join(", ")} meter an INCLUDED window per member (a seat ` +
|
|
304
|
-
"pack, or a `scope: \"caller\"` rate limit),
|
|
305
|
-
"balance transaction carries the caller but only exists where money
|
|
306
|
-
"
|
|
307
|
-
"
|
|
308
|
-
|
|
311
|
+
"pack, or a `scope: \"caller\"` rate limit), and the wired ledger can only see calls the " +
|
|
312
|
+
"wallet paid for — a balance transaction carries the caller but only exists where money " +
|
|
313
|
+
"moved. That usage counts as 0, so a seat pack or a caller-scoped rate limit never " +
|
|
314
|
+
"applies. Use `stripeUsageLedger({ perCaller: stripeScopeUsageLedger() })`, which counts " +
|
|
315
|
+
"it in Stripe with no store by giving each usage scope a customer of its own — or pass a " +
|
|
316
|
+
"`ledger` of your own if you want the per-action history a store would keep.");
|
|
309
317
|
}
|
|
310
318
|
}
|
|
311
|
-
/**
|
|
312
|
-
* The table, and the three indexes the two queries below actually use.
|
|
313
|
-
*
|
|
314
|
-
* Shipped rather than described because the UNIQUE index is PARTIAL, and that is
|
|
315
|
-
* not a detail a consumer should have to rediscover: `idempotency_key` is
|
|
316
|
-
* nullable, only non-null keys are unique, and Postgres refuses to infer a
|
|
317
|
-
* partial index for `ON CONFLICT` unless the predicate is repeated at the insert.
|
|
318
|
-
* Getting it wrong fails every insert with 42P10.
|
|
319
|
-
*
|
|
320
|
-
* Idempotent, so it is safe to run from a migration on every deploy.
|
|
321
|
-
*/
|
|
322
|
-
export const USAGE_EVENTS = `
|
|
323
|
-
CREATE TABLE IF NOT EXISTS usage_events (
|
|
324
|
-
id bigserial PRIMARY KEY,
|
|
325
|
-
org_id text NOT NULL,
|
|
326
|
-
customer_id text,
|
|
327
|
-
action text NOT NULL,
|
|
328
|
-
cost integer NOT NULL,
|
|
329
|
-
-- Which allowance paid: pool | pack | wallet. Kept because "included vs
|
|
330
|
-
-- charged" is unanswerable afterwards otherwise.
|
|
331
|
-
funded text NOT NULL,
|
|
332
|
-
caller_kind text,
|
|
333
|
-
caller_id text,
|
|
334
|
-
-- Makes a retried call a no-op rather than a double count.
|
|
335
|
-
idempotency_key text,
|
|
336
|
-
at timestamptz NOT NULL DEFAULT now()
|
|
337
|
-
);
|
|
338
|
-
CREATE INDEX IF NOT EXISTS usage_events_org_at_idx
|
|
339
|
-
ON usage_events (org_id, at DESC);
|
|
340
|
-
CREATE INDEX IF NOT EXISTS usage_events_org_caller_at_idx
|
|
341
|
-
ON usage_events (org_id, caller_kind, caller_id, at DESC);
|
|
342
|
-
CREATE UNIQUE INDEX IF NOT EXISTS usage_events_idempotency_idx
|
|
343
|
-
ON usage_events (idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
344
|
-
`;
|
|
345
|
-
/** Create the table + indexes if they are missing. Call it from your migration;
|
|
346
|
-
* it is idempotent. Consumers who own their schema can read `USAGE_EVENTS`
|
|
347
|
-
* instead and paste it into their own migration tool. */
|
|
348
|
-
/** @deprecated Renamed to `USAGE_EVENTS` — the constant IS the table's shape, so
|
|
349
|
-
* the name says which table rather than which kind of string. Kept as an alias
|
|
350
|
-
* because a rename that breaks a consumer's migration script is not worth the
|
|
351
|
-
* tidiness. */
|
|
352
|
-
export const USAGE_EVENTS_DDL = USAGE_EVENTS;
|
|
353
|
-
export async function ensureUsageLedgerTable(client) {
|
|
354
|
-
await client.query(USAGE_EVENTS);
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Usage counted in Postgres: exact, attributable per caller, and summable over
|
|
358
|
-
* any window a plan declares (hour / day / week / month / cycle).
|
|
359
|
-
*
|
|
360
|
-
* `record` is on the hot path of every metered execution: one INSERT, no read,
|
|
361
|
-
* no transaction. `total` is one aggregate over an index-covered range.
|
|
362
|
-
*/
|
|
363
|
-
export function postgresUsageLedger(client) {
|
|
364
|
-
return {
|
|
365
|
-
// Every event is a row carrying both the caller and what funded it, so this is
|
|
366
|
-
// the one implementation that can answer either question.
|
|
367
|
-
covers: { orgIncluded: true, callerIncluded: true },
|
|
368
|
-
async record(e) {
|
|
369
|
-
// ON CONFLICT on the idempotency key, so a retried execution is counted
|
|
370
|
-
// once. Without a key (the common case) every row is a distinct event. The
|
|
371
|
-
// repeated WHERE is what makes the partial index inferable — see the DDL.
|
|
372
|
-
await client.query(`INSERT INTO usage_events
|
|
373
|
-
(org_id, customer_id, action, cost, funded, caller_kind, caller_id, idempotency_key, at)
|
|
374
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, to_timestamp($9 / 1000.0))
|
|
375
|
-
ON CONFLICT (idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING`, [
|
|
376
|
-
e.orgId,
|
|
377
|
-
e.customerId ?? null,
|
|
378
|
-
e.action,
|
|
379
|
-
e.cost,
|
|
380
|
-
e.funded,
|
|
381
|
-
e.caller?.kind ?? null,
|
|
382
|
-
e.caller?.id ?? null,
|
|
383
|
-
e.idempotencyKey ?? null,
|
|
384
|
-
e.at ?? Date.now(),
|
|
385
|
-
]);
|
|
386
|
-
},
|
|
387
|
-
async total(q) {
|
|
388
|
-
// [start, end): the half-open window this library defines everywhere, so an
|
|
389
|
-
// event on a boundary is counted by exactly one window and never by both.
|
|
390
|
-
const params = [q.orgId, q.start];
|
|
391
|
-
let sql = `SELECT COALESCE(SUM(cost), 0)::int AS total
|
|
392
|
-
FROM usage_events
|
|
393
|
-
WHERE org_id = $1
|
|
394
|
-
AND at >= to_timestamp($2 / 1000.0)`;
|
|
395
|
-
if (q.end != null) {
|
|
396
|
-
params.push(q.end);
|
|
397
|
-
sql += ` AND at < to_timestamp($${params.length} / 1000.0)`;
|
|
398
|
-
}
|
|
399
|
-
// A caller filter with no id means "all callers of this kind", which is how
|
|
400
|
-
// a shared API seat is measured: one window for every key in the org.
|
|
401
|
-
if (q.filter?.callerKind) {
|
|
402
|
-
params.push(q.filter.callerKind);
|
|
403
|
-
sql += ` AND caller_kind = $${params.length}`;
|
|
404
|
-
}
|
|
405
|
-
if (q.filter?.callerId) {
|
|
406
|
-
params.push(q.filter.callerId);
|
|
407
|
-
sql += ` AND caller_id = $${params.length}`;
|
|
408
|
-
}
|
|
409
|
-
const r = await client.query(sql, params);
|
|
410
|
-
return r.rows[0]?.total ?? 0;
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
319
|
//# sourceMappingURL=usage-ledger.js.map
|