@voltro/plugin-billing 0.33.0 → 0.34.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/CHANGELOG.md +1801 -0
- package/dist/index.d.ts +481 -14
- package/dist/index.js +876 -446
- package/dist/rpc.d.ts +18 -0
- package/dist/rpc.js +33 -11
- package/dist/types.d.ts +145 -0
- package/dist/types.js +8 -3
- package/package.json +6 -6
package/dist/rpc.d.ts
CHANGED
|
@@ -47,6 +47,24 @@ export declare const changeSeatsDescriptor: ActionProcedureDescriptor<"billing.c
|
|
|
47
47
|
currency: typeof Schema.String;
|
|
48
48
|
}>, typeof BillingError>;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* "Is this tenant entitled right now?" — the dunning-aware answer a billing
|
|
52
|
+
* banner needs. `entitled: false` means the grace period expired; `inGrace`
|
|
53
|
+
* with a `graceEndsAt` is the "your payment failed, you have until …" state.
|
|
54
|
+
* Timestamps are ISO strings (the descriptor's Schema is String, so a Date
|
|
55
|
+
* would fail encode at the boundary).
|
|
56
|
+
*/
|
|
57
|
+
export declare const entitlementStatusDescriptor: QueryProcedureDescriptor<"billing.entitlementStatus", Schema.Struct<{}>, Schema.Struct<{
|
|
58
|
+
plan: typeof Schema.String;
|
|
59
|
+
billedPlan: typeof Schema.String;
|
|
60
|
+
status: typeof Schema.String;
|
|
61
|
+
entitled: typeof Schema.Boolean;
|
|
62
|
+
inGrace: typeof Schema.Boolean;
|
|
63
|
+
graceEndsAt: Schema.NullOr<typeof Schema.String>;
|
|
64
|
+
lockedSince: Schema.NullOr<typeof Schema.String>;
|
|
65
|
+
lockout: typeof Schema.String;
|
|
66
|
+
}>, typeof BillingError>;
|
|
67
|
+
|
|
50
68
|
/** The provider's invoice history. `hostedUrl` / `pdfUrl` are the provider's
|
|
51
69
|
* own pages — we never render an invoice ourselves. */
|
|
52
70
|
export declare const invoicesDescriptor: QueryProcedureDescriptor<"billing.invoices", Schema.Struct<{}>, Schema.Struct<{
|
package/dist/rpc.js
CHANGED
|
@@ -28,7 +28,21 @@ var i = n({
|
|
|
28
28
|
cancelAt: t.NullOr(t.String)
|
|
29
29
|
})) }),
|
|
30
30
|
error: e
|
|
31
|
-
}), s =
|
|
31
|
+
}), s = r({
|
|
32
|
+
name: "billing.entitlementStatus",
|
|
33
|
+
input: t.Struct({}),
|
|
34
|
+
output: t.Struct({
|
|
35
|
+
plan: t.String,
|
|
36
|
+
billedPlan: t.String,
|
|
37
|
+
status: t.String,
|
|
38
|
+
entitled: t.Boolean,
|
|
39
|
+
inGrace: t.Boolean,
|
|
40
|
+
graceEndsAt: t.NullOr(t.String),
|
|
41
|
+
lockedSince: t.NullOr(t.String),
|
|
42
|
+
lockout: t.String
|
|
43
|
+
}),
|
|
44
|
+
error: e
|
|
45
|
+
}), c = n({
|
|
32
46
|
name: "billing.reportUsage",
|
|
33
47
|
input: t.Struct({
|
|
34
48
|
key: t.String,
|
|
@@ -36,20 +50,20 @@ var i = n({
|
|
|
36
50
|
}),
|
|
37
51
|
output: t.Struct({ ok: t.Boolean }),
|
|
38
52
|
error: e
|
|
39
|
-
}),
|
|
53
|
+
}), l = t.Struct({
|
|
40
54
|
plan: t.String,
|
|
41
55
|
quantity: t.Number,
|
|
42
56
|
prorationMinor: t.Number,
|
|
43
57
|
currency: t.String
|
|
44
|
-
}),
|
|
58
|
+
}), u = n({
|
|
45
59
|
name: "billing.previewChange",
|
|
46
60
|
input: t.Struct({
|
|
47
61
|
plan: t.optional(t.String),
|
|
48
62
|
quantity: t.optional(t.Number)
|
|
49
63
|
}),
|
|
50
|
-
output:
|
|
64
|
+
output: l,
|
|
51
65
|
error: e
|
|
52
|
-
}),
|
|
66
|
+
}), d = r({
|
|
53
67
|
name: "billing.invoices",
|
|
54
68
|
input: t.Struct({}),
|
|
55
69
|
output: t.Struct({ invoices: t.Array(t.Struct({
|
|
@@ -62,17 +76,17 @@ var i = n({
|
|
|
62
76
|
pdfUrl: t.NullOr(t.String)
|
|
63
77
|
})) }),
|
|
64
78
|
error: e
|
|
65
|
-
}),
|
|
79
|
+
}), f = n({
|
|
66
80
|
name: "billing.changePlan",
|
|
67
81
|
input: t.Struct({ plan: t.String }),
|
|
68
|
-
output:
|
|
82
|
+
output: l,
|
|
69
83
|
error: e
|
|
70
|
-
}),
|
|
84
|
+
}), p = n({
|
|
71
85
|
name: "billing.changeSeats",
|
|
72
86
|
input: t.Struct({ quantity: t.Number }),
|
|
73
|
-
output:
|
|
87
|
+
output: l,
|
|
74
88
|
error: e
|
|
75
|
-
}),
|
|
89
|
+
}), m = [
|
|
76
90
|
{
|
|
77
91
|
tag: "billing.startCheckout",
|
|
78
92
|
kind: "action",
|
|
@@ -97,6 +111,14 @@ var i = n({
|
|
|
97
111
|
name: "subscriptionDescriptor"
|
|
98
112
|
}
|
|
99
113
|
},
|
|
114
|
+
{
|
|
115
|
+
tag: "billing.entitlementStatus",
|
|
116
|
+
kind: "query",
|
|
117
|
+
import: {
|
|
118
|
+
module: "@voltro/plugin-billing/rpc",
|
|
119
|
+
name: "entitlementStatusDescriptor"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
100
122
|
{
|
|
101
123
|
tag: "billing.reportUsage",
|
|
102
124
|
kind: "action",
|
|
@@ -139,4 +161,4 @@ var i = n({
|
|
|
139
161
|
}
|
|
140
162
|
];
|
|
141
163
|
//#endregion
|
|
142
|
-
export {
|
|
164
|
+
export { m as billingRpcClientImports, f as changePlanDescriptor, p as changeSeatsDescriptor, s as entitlementStatusDescriptor, d as invoicesDescriptor, a as portalUrlDescriptor, u as previewChangeDescriptor, c as reportUsageDescriptor, i as startCheckoutDescriptor, o as subscriptionDescriptor };
|
package/dist/types.d.ts
CHANGED
|
@@ -39,26 +39,33 @@ export declare type BillingEvent = {
|
|
|
39
39
|
readonly currentPeriodStart?: Date | null;
|
|
40
40
|
readonly currentPeriodEnd: Date | null;
|
|
41
41
|
readonly cancelAt: Date | null;
|
|
42
|
+
/** When the PROVIDER emitted this — the out-of-order guard. See
|
|
43
|
+
* `occurredAt` on the union below. */
|
|
44
|
+
readonly occurredAt: Date | null;
|
|
42
45
|
} | {
|
|
43
46
|
readonly _tag: 'subscriptionCanceled';
|
|
44
47
|
readonly tenantId: string;
|
|
45
48
|
readonly providerSubscriptionId: string;
|
|
49
|
+
readonly occurredAt: Date | null;
|
|
46
50
|
} | {
|
|
47
51
|
readonly _tag: 'invoicePaid';
|
|
48
52
|
readonly tenantId: string;
|
|
49
53
|
readonly providerInvoiceId: string;
|
|
50
54
|
readonly amountMinor: number;
|
|
51
55
|
readonly currency: string;
|
|
56
|
+
readonly occurredAt: Date | null;
|
|
52
57
|
} | {
|
|
53
58
|
readonly _tag: 'invoicePaymentFailed';
|
|
54
59
|
readonly tenantId: string;
|
|
55
60
|
readonly providerInvoiceId: string;
|
|
56
61
|
readonly amountMinor: number;
|
|
57
62
|
readonly currency: string;
|
|
63
|
+
readonly occurredAt: Date | null;
|
|
58
64
|
} | {
|
|
59
65
|
readonly _tag: 'customerLinked';
|
|
60
66
|
readonly tenantId: string;
|
|
61
67
|
readonly providerCustomerId: string;
|
|
68
|
+
readonly occurredAt: Date | null;
|
|
62
69
|
};
|
|
63
70
|
|
|
64
71
|
/** The event tag union — handy for `onEvent` keys. */
|
|
@@ -105,6 +112,23 @@ export declare interface BillingProvider {
|
|
|
105
112
|
* is a support ticket.
|
|
106
113
|
*/
|
|
107
114
|
readonly previewSubscriptionChange: (input: SubscriptionUpdateInput) => Effect.Effect<SubscriptionChangeResult, BillingError>;
|
|
115
|
+
/**
|
|
116
|
+
* The provider's CURRENT view of a subscription — a direct read, not an
|
|
117
|
+
* event body.
|
|
118
|
+
*
|
|
119
|
+
* This is the method dunning refuses to lock a customer out without. An
|
|
120
|
+
* `invoice.payment_failed` can arrive after the retry that succeeded, a
|
|
121
|
+
* redelivery can arrive days late, and neither carries the state that
|
|
122
|
+
* matters — the subscription's status right now does. Returns null when the
|
|
123
|
+
* provider does not know the id.
|
|
124
|
+
*/
|
|
125
|
+
readonly fetchSubscription: (providerSubscriptionId: string) => Effect.Effect<ProviderSubscriptionState | null, BillingError>;
|
|
126
|
+
/**
|
|
127
|
+
* The billing contact the PROVIDER has on file, when it has one. Used as the
|
|
128
|
+
* dunning recipient of last resort — `dunning.resolveRecipient` wins when
|
|
129
|
+
* the app keeps its own. Optional: a provider may model no customer email.
|
|
130
|
+
*/
|
|
131
|
+
readonly customerEmail?: (providerCustomerId: string) => Effect.Effect<string | null, BillingError>;
|
|
108
132
|
/** The provider's invoice history. We mirror it for listing but never
|
|
109
133
|
* render an invoice ourselves — `hostedUrl` / `pdfUrl` are theirs. */
|
|
110
134
|
readonly listInvoices: (input: {
|
|
@@ -138,6 +162,31 @@ export declare interface BillingServiceShape {
|
|
|
138
162
|
readonly subscription: (tenantId: string) => Effect.Effect<Subscription | null, BillingError>;
|
|
139
163
|
/** Resolve the tenant's plan id; defaults to `'free'` when no row. */
|
|
140
164
|
readonly plan: (tenantId: string) => Effect.Effect<PlanId, BillingError>;
|
|
165
|
+
/**
|
|
166
|
+
* "Is this tenant entitled right now?" — the one truthful answer, dunning
|
|
167
|
+
* state included. A PURE read of the local row (no provider call, no write),
|
|
168
|
+
* so it is safe on a hot path: the grace clock is a column and the lockout
|
|
169
|
+
* is derived from it, never a flag some job had to remember to set.
|
|
170
|
+
*/
|
|
171
|
+
readonly entitlementStatus: (tenantId: string) => Effect.Effect<EntitlementStatus, BillingError>;
|
|
172
|
+
/**
|
|
173
|
+
* Re-derive dunning state for a tenant FROM THE PROVIDER, then fire whatever
|
|
174
|
+
* notification steps are now due (each at most once per episode).
|
|
175
|
+
*
|
|
176
|
+
* Runs automatically after every subscription/invoice event, which is what
|
|
177
|
+
* makes the provider's own retry cadence the schedule. Call it directly only
|
|
178
|
+
* to force a re-check (e.g. from a support tool). It is idempotent, it never
|
|
179
|
+
* charges anything, and it never asks the provider to retry.
|
|
180
|
+
*/
|
|
181
|
+
readonly reconcileDunning: (tenantId: string) => Effect.Effect<EntitlementStatus, BillingError>;
|
|
182
|
+
/**
|
|
183
|
+
* Reconcile every past-due tenant. Optional — the event path already covers
|
|
184
|
+
* the normal case; a sweep exists so a step configured for a moment the
|
|
185
|
+
* provider happens not to emit an event (and a reconcile missed during a
|
|
186
|
+
* provider outage) still lands. Wire it from your own `*.cron.tsx` if you
|
|
187
|
+
* want that; nothing schedules it for you. Returns the number reconciled.
|
|
188
|
+
*/
|
|
189
|
+
readonly dunningSweep: () => Effect.Effect<number, BillingError>;
|
|
141
190
|
/** Pure read — would `cost` units of `key` be allowed? No mutation. */
|
|
142
191
|
readonly checkEntitlement: (tenantId: string, key: string, cost: number) => Effect.Effect<EntitlementDecision, BillingError>;
|
|
143
192
|
/** Check + decrement, atomically. Fails `EntitlementExceeded` over-limit. */
|
|
@@ -251,6 +300,31 @@ declare const EntitlementExceeded_base: Schema.TaggedErrorClass<EntitlementExcee
|
|
|
251
300
|
/** An entitlement limit: a finite quota or the unbounded `'unlimited'`. */
|
|
252
301
|
export declare type EntitlementLimit = number | 'unlimited';
|
|
253
302
|
|
|
303
|
+
/** The truthful "is this tenant entitled right now" answer. */
|
|
304
|
+
export declare interface EntitlementStatus {
|
|
305
|
+
readonly tenantId: string;
|
|
306
|
+
/** The plan whose LIMITS apply right now — `'free'` under a hard lockout. */
|
|
307
|
+
readonly plan: PlanId;
|
|
308
|
+
/** The plan the tenant is subscribed to, lockout or not. */
|
|
309
|
+
readonly billedPlan: PlanId;
|
|
310
|
+
readonly status: SubscriptionStatus | 'none';
|
|
311
|
+
/**
|
|
312
|
+
* False ONLY when dunning has locked this tenant out. A canceled
|
|
313
|
+
* subscription is not a lockout — it is simply the free tier, and reporting
|
|
314
|
+
* it as "not entitled" would conflate "never paid" with "stopped paying".
|
|
315
|
+
*/
|
|
316
|
+
readonly entitled: boolean;
|
|
317
|
+
/** Past due, but still inside the grace window. */
|
|
318
|
+
readonly inGrace: boolean;
|
|
319
|
+
/** When grace runs out; null when the tenant is not past due — or when the
|
|
320
|
+
* provider has not confirmed the past-due yet, in which case the tenant is
|
|
321
|
+
* in grace with no expiry rather than locked. */
|
|
322
|
+
readonly graceEndsAt: Date | null;
|
|
323
|
+
/** Non-null iff locked out — the instant grace expired. */
|
|
324
|
+
readonly lockedSince: Date | null;
|
|
325
|
+
readonly lockout: LockoutMode;
|
|
326
|
+
}
|
|
327
|
+
|
|
254
328
|
export declare interface InvoiceRecord {
|
|
255
329
|
readonly providerInvoiceId: string;
|
|
256
330
|
readonly amountMinor: number;
|
|
@@ -262,6 +336,17 @@ export declare interface InvoiceRecord {
|
|
|
262
336
|
readonly pdfUrl: string | null;
|
|
263
337
|
}
|
|
264
338
|
|
|
339
|
+
/**
|
|
340
|
+
* What "locked" means once the grace period is over.
|
|
341
|
+
*
|
|
342
|
+
* - `'hard'` — entitlement limits fall to the `'free'` plan's. The app needs
|
|
343
|
+
* no new code: every existing `requireEntitlement` / `enforce` check
|
|
344
|
+
* starts answering with the free tier's numbers.
|
|
345
|
+
* - `'soft'` — limits stay on the paid plan; only `entitlementStatus()`
|
|
346
|
+
* reports the lockout, so the app decides what to withhold.
|
|
347
|
+
*/
|
|
348
|
+
export declare type LockoutMode = 'hard' | 'soft';
|
|
349
|
+
|
|
265
350
|
/** A monetary amount: integer minor units + ISO-4217 currency. */
|
|
266
351
|
export declare interface Money {
|
|
267
352
|
/** Integer minor units (cents). Never a float. */
|
|
@@ -296,6 +381,19 @@ export declare interface PortalInput {
|
|
|
296
381
|
readonly returnUrl: string;
|
|
297
382
|
}
|
|
298
383
|
|
|
384
|
+
/** The provider's CURRENT view of a subscription — read directly, not from an
|
|
385
|
+
* event body. `reconcileDunning` asks for this before it will lock anyone
|
|
386
|
+
* out. */
|
|
387
|
+
export declare interface ProviderSubscriptionState {
|
|
388
|
+
readonly status: SubscriptionStatus;
|
|
389
|
+
/** Null when the provider cannot map the price to one of our plans. */
|
|
390
|
+
readonly plan: PlanId | null;
|
|
391
|
+
readonly quantity: number;
|
|
392
|
+
readonly currentPeriodStart: Date | null;
|
|
393
|
+
readonly currentPeriodEnd: Date | null;
|
|
394
|
+
readonly cancelAt: Date | null;
|
|
395
|
+
}
|
|
396
|
+
|
|
299
397
|
/** A tenant's subscription row, normalized across providers. */
|
|
300
398
|
export declare interface Subscription {
|
|
301
399
|
readonly tenantId: string;
|
|
@@ -313,6 +411,27 @@ export declare interface Subscription {
|
|
|
313
411
|
readonly currentPeriodEnd: Date | null;
|
|
314
412
|
/** When the subscription is scheduled to cancel; null if not scheduled. */
|
|
315
413
|
readonly cancelAt: Date | null;
|
|
414
|
+
/**
|
|
415
|
+
* When this tenant went past due — the dunning grace clock, and the identity
|
|
416
|
+
* of the current dunning episode.
|
|
417
|
+
*
|
|
418
|
+
* Written ONLY by a provider-confirmed reconcile, never straight off a
|
|
419
|
+
* webhook: it is the value the lockout is derived from, and locking a
|
|
420
|
+
* customer out on an event body alone is the one mistake here with a
|
|
421
|
+
* real-world cost. Cleared the moment the provider reports the
|
|
422
|
+
* subscription healthy again, which is also what cancels the rest of the
|
|
423
|
+
* notification sequence.
|
|
424
|
+
*/
|
|
425
|
+
readonly pastDueSince: Date | null;
|
|
426
|
+
/**
|
|
427
|
+
* The provider-event timestamp the current `status` came from — the
|
|
428
|
+
* out-of-order guard. Provider webhooks are at-least-once AND unordered, so
|
|
429
|
+
* an event that is older than what the row already reflects is DROPPED
|
|
430
|
+
* rather than applied (a stale `active` must not un-do a `pastDue`, and a
|
|
431
|
+
* stale `pastDue` must not resurrect a resolved one). Null on rows written
|
|
432
|
+
* before any dated event.
|
|
433
|
+
*/
|
|
434
|
+
readonly statusEventAt: Date | null;
|
|
316
435
|
}
|
|
317
436
|
|
|
318
437
|
/** The outcome of a mid-cycle plan/seat change — the prorated settlement. */
|
|
@@ -337,6 +456,32 @@ export declare interface SubscriptionChangeResult {
|
|
|
337
456
|
readonly currentPeriodEnd: Date | null;
|
|
338
457
|
}
|
|
339
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Raised when dunning has locked a tenant out — the grace period after a
|
|
461
|
+
* provider-confirmed past-due has expired. Distinct from
|
|
462
|
+
* `EntitlementExceeded`: that one means "you used your quota", this one means
|
|
463
|
+
* "you did not pay". Registered via `errorSchemas`, so a client can branch on
|
|
464
|
+
* `_tag === 'SubscriptionLocked'` and route the user to the billing portal
|
|
465
|
+
* rather than showing a generic failure.
|
|
466
|
+
*
|
|
467
|
+
* Dates cross the wire as ISO strings — a `Schema.DateFromSelf` would not
|
|
468
|
+
* survive the JSON boundary the error union is encoded through.
|
|
469
|
+
*/
|
|
470
|
+
export declare class SubscriptionLocked extends SubscriptionLocked_base {
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
declare const SubscriptionLocked_base: Schema.TaggedErrorClass<SubscriptionLocked, "SubscriptionLocked", {
|
|
474
|
+
readonly _tag: Schema.tag<"SubscriptionLocked">;
|
|
475
|
+
} & {
|
|
476
|
+
tenantId: typeof Schema.String;
|
|
477
|
+
/** The subscription status at the time of the refusal (`'pastDue'`). */
|
|
478
|
+
status: typeof Schema.String;
|
|
479
|
+
/** ISO timestamp at which the grace period expired. */
|
|
480
|
+
lockedSince: typeof Schema.String;
|
|
481
|
+
/** `'hard'` (limits fell to the free plan) or `'soft'` (report only). */
|
|
482
|
+
lockout: typeof Schema.String;
|
|
483
|
+
}>;
|
|
484
|
+
|
|
340
485
|
export declare type SubscriptionStatus = 'active' | 'trialing' | 'pastDue' | 'canceled' | 'incomplete';
|
|
341
486
|
|
|
342
487
|
/** Apply a plan / seat change to the LIVE subscription at the provider. */
|
package/dist/types.js
CHANGED
|
@@ -4,11 +4,16 @@ var n = class extends t.TaggedError()("BillingError", {
|
|
|
4
4
|
source: t.String,
|
|
5
5
|
message: t.String,
|
|
6
6
|
transient: t.Boolean
|
|
7
|
-
}) {}, r = class extends t.TaggedError()("
|
|
7
|
+
}) {}, r = class extends t.TaggedError()("SubscriptionLocked", {
|
|
8
|
+
tenantId: t.String,
|
|
9
|
+
status: t.String,
|
|
10
|
+
lockedSince: t.String,
|
|
11
|
+
lockout: t.String
|
|
12
|
+
}) {}, i = class extends t.TaggedError()("EntitlementExceeded", {
|
|
8
13
|
entitlement: t.String,
|
|
9
14
|
limit: t.Number,
|
|
10
15
|
used: t.Number,
|
|
11
16
|
cost: t.Number
|
|
12
|
-
}) {},
|
|
17
|
+
}) {}, a = "free", o = class extends e.Tag("@voltro/plugin-billing/BillingService")() {};
|
|
13
18
|
//#endregion
|
|
14
|
-
export { n as BillingError,
|
|
19
|
+
export { n as BillingError, o as BillingService, a as DEFAULT_PLAN, i as EntitlementExceeded, r as SubscriptionLocked };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-billing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Billing on the official Stripe SDK — subscriptions, plans, entitlements and usage metering. Proration, failed-payment retries, tax, promotion codes, trials and the checkout seat stepper are Stripe's own; the plugin adds the entitlement engine, the enforce interceptor, typed BillingError/EntitlementExceeded, the /billing/webhook mount with Stripe's signature verification, the BillingService Tag, requireEntitlement(), and a browser-safe useStartCheckout() hook. Money is integer minor units end-to-end.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"node": ">=24.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@voltro/database": "0.
|
|
52
|
-
"@voltro/env": "0.
|
|
53
|
-
"@voltro/logger": "0.
|
|
54
|
-
"@voltro/plugin-webhooks": "0.
|
|
55
|
-
"@voltro/protocol": "0.
|
|
51
|
+
"@voltro/database": "0.34.0",
|
|
52
|
+
"@voltro/env": "0.34.0",
|
|
53
|
+
"@voltro/logger": "0.34.0",
|
|
54
|
+
"@voltro/plugin-webhooks": "0.34.0",
|
|
55
|
+
"@voltro/protocol": "0.34.0",
|
|
56
56
|
"stripe": "22.3.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|