@happyvertical/smrt-subscriptions 0.30.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.
Files changed (59) hide show
  1. package/AGENTS.md +26 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/SubscriptionPlanCollection.d.ts +9 -0
  8. package/dist/collections/SubscriptionPlanCollection.d.ts.map +1 -0
  9. package/dist/collections/TenantSubscriptionCollection.d.ts +29 -0
  10. package/dist/collections/TenantSubscriptionCollection.d.ts.map +1 -0
  11. package/dist/collections/TenantUsageMetricCollection.d.ts +28 -0
  12. package/dist/collections/TenantUsageMetricCollection.d.ts.map +1 -0
  13. package/dist/collections/index.d.ts +4 -0
  14. package/dist/collections/index.d.ts.map +1 -0
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +1271 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/manifest.json +1291 -0
  20. package/dist/models/SubscriptionPlan.d.ts +30 -0
  21. package/dist/models/SubscriptionPlan.d.ts.map +1 -0
  22. package/dist/models/TenantSubscription.d.ts +59 -0
  23. package/dist/models/TenantSubscription.d.ts.map +1 -0
  24. package/dist/models/TenantUsageMetric.d.ts +35 -0
  25. package/dist/models/TenantUsageMetric.d.ts.map +1 -0
  26. package/dist/models/index.d.ts +4 -0
  27. package/dist/models/index.d.ts.map +1 -0
  28. package/dist/services/index.d.ts +5 -0
  29. package/dist/services/index.d.ts.map +1 -0
  30. package/dist/services/subscription-resolver.d.ts +96 -0
  31. package/dist/services/subscription-resolver.d.ts.map +1 -0
  32. package/dist/services/threshold-evaluator.d.ts +4 -0
  33. package/dist/services/threshold-evaluator.d.ts.map +1 -0
  34. package/dist/services/usage-meter.d.ts +32 -0
  35. package/dist/services/usage-meter.d.ts.map +1 -0
  36. package/dist/smrt-knowledge.json +883 -0
  37. package/dist/svelte/PlanPicker.svelte +82 -0
  38. package/dist/svelte/PlanPicker.svelte.d.ts +10 -0
  39. package/dist/svelte/PlanPicker.svelte.d.ts.map +1 -0
  40. package/dist/svelte/SubscriptionSummary.svelte +65 -0
  41. package/dist/svelte/SubscriptionSummary.svelte.d.ts +8 -0
  42. package/dist/svelte/SubscriptionSummary.svelte.d.ts.map +1 -0
  43. package/dist/svelte/UsageThresholds.svelte +71 -0
  44. package/dist/svelte/UsageThresholds.svelte.d.ts +8 -0
  45. package/dist/svelte/UsageThresholds.svelte.d.ts.map +1 -0
  46. package/dist/svelte/i18n.d.ts +5 -0
  47. package/dist/svelte/i18n.d.ts.map +1 -0
  48. package/dist/svelte/i18n.js +9 -0
  49. package/dist/svelte/index.d.ts +4 -0
  50. package/dist/svelte/index.d.ts.map +1 -0
  51. package/dist/svelte/index.js +3 -0
  52. package/dist/types.d.ts +175 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/utils.d.ts +59 -0
  57. package/dist/utils.d.ts.map +1 -0
  58. package/package.json +80 -0
  59. package/scripts/migrate-1454-drop-legacy-conflict-index.ts +110 -0
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ import type { SubscriptionPlan } from '../models/SubscriptionPlan.js';
3
+
4
+ let {
5
+ plans = [],
6
+ selectedPlanKey = null,
7
+ onSelect,
8
+ }: {
9
+ plans?: SubscriptionPlan[];
10
+ selectedPlanKey?: string | null;
11
+ onSelect?: (plan: SubscriptionPlan) => void;
12
+ } = $props();
13
+ </script>
14
+
15
+ <div class="smrt-plan-picker">
16
+ {#each plans as plan (plan.id)}
17
+ <button
18
+ aria-pressed={plan.planKey === selectedPlanKey}
19
+ class:selected={plan.planKey === selectedPlanKey}
20
+ class="smrt-plan-picker__plan"
21
+ type="button"
22
+ onclick={() => onSelect?.(plan)}
23
+ >
24
+ <span class="smrt-plan-picker__name">{plan.name}</span>
25
+ <span class="smrt-plan-picker__price">
26
+ {new Intl.NumberFormat(undefined, {
27
+ style: 'currency',
28
+ currency: plan.currency,
29
+ }).format(plan.priceAmount)}
30
+ <small>/ {plan.billingInterval}</small>
31
+ </span>
32
+ {#if plan.description}
33
+ <span class="smrt-plan-picker__description">{plan.description}</span>
34
+ {/if}
35
+ <span class="smrt-plan-picker__features">
36
+ {plan.getFeatureKeys().length} features
37
+ </span>
38
+ </button>
39
+ {/each}
40
+ </div>
41
+
42
+ <style>
43
+ .smrt-plan-picker {
44
+ display: grid;
45
+ gap: 0.75rem;
46
+ grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
47
+ }
48
+
49
+ .smrt-plan-picker__plan {
50
+ align-items: flex-start;
51
+ background: var(--smrt-color-surface, #fff);
52
+ border: 1px solid var(--smrt-color-outline-variant, #d8dde6);
53
+ border-radius: var(--smrt-radius-md, 8px);
54
+ color: inherit;
55
+ cursor: pointer;
56
+ display: grid;
57
+ gap: 0.45rem;
58
+ padding: 1rem;
59
+ text-align: left;
60
+ }
61
+
62
+ .smrt-plan-picker__plan.selected {
63
+ border-color: var(--smrt-color-primary, #2563eb);
64
+ box-shadow: 0 0 0 1px var(--smrt-color-primary, #2563eb);
65
+ }
66
+
67
+ .smrt-plan-picker__name {
68
+ font-weight: var(--smrt-typography-weight-bold, 650);
69
+ }
70
+
71
+ .smrt-plan-picker__price {
72
+ font-size: var(--smrt-typography-title-large-size, 1.25rem);
73
+ font-weight: var(--smrt-typography-weight-bold, 700);
74
+ }
75
+
76
+ .smrt-plan-picker__price small,
77
+ .smrt-plan-picker__description,
78
+ .smrt-plan-picker__features {
79
+ color: var(--smrt-color-on-surface-variant, #64748b);
80
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
81
+ }
82
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { SubscriptionPlan } from '../models/SubscriptionPlan.js';
2
+ type $$ComponentProps = {
3
+ plans?: SubscriptionPlan[];
4
+ selectedPlanKey?: string | null;
5
+ onSelect?: (plan: SubscriptionPlan) => void;
6
+ };
7
+ declare const PlanPicker: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type PlanPicker = ReturnType<typeof PlanPicker>;
9
+ export default PlanPicker;
10
+ //# sourceMappingURL=PlanPicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlanPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/PlanPicker.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAErE,KAAK,gBAAgB,GAAI;IACxB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC7C,CAAC;AAoCF,QAAA,MAAM,UAAU,sDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import type { EntitlementResolution } from '../types.js';
4
+ import { M } from './i18n.js';
5
+
6
+ let {
7
+ resolution = null,
8
+ }: {
9
+ resolution?: EntitlementResolution | null;
10
+ } = $props();
11
+
12
+ const { t } = useI18n();
13
+ </script>
14
+
15
+ <section class="smrt-subscription-summary">
16
+ <div>
17
+ <p class="smrt-subscription-summary__label">{t(M['subscriptions.summary.current_plan'])}</p>
18
+ <h2>{resolution?.planKey ?? t(M['subscriptions.summary.no_active_plan'])}</h2>
19
+ </div>
20
+ <dl>
21
+ <div>
22
+ <dt>Status</dt>
23
+ <dd>{resolution?.status ?? 'none'}</dd>
24
+ </div>
25
+ <div>
26
+ <dt>Features</dt>
27
+ <dd>{resolution?.featureKeys.length ?? 0}</dd>
28
+ </div>
29
+ <div>
30
+ <dt>Thresholds</dt>
31
+ <dd>{resolution?.thresholds.length ?? 0}</dd>
32
+ </div>
33
+ </dl>
34
+ </section>
35
+
36
+ <style>
37
+ .smrt-subscription-summary {
38
+ align-items: center;
39
+ display: flex;
40
+ gap: 1rem;
41
+ justify-content: space-between;
42
+ }
43
+
44
+ .smrt-subscription-summary h2,
45
+ .smrt-subscription-summary__label {
46
+ margin: 0;
47
+ }
48
+
49
+ .smrt-subscription-summary__label,
50
+ .smrt-subscription-summary dt {
51
+ color: var(--smrt-color-on-surface-variant, #64748b);
52
+ font-size: var(--smrt-typography-label-medium-size, 0.8rem);
53
+ }
54
+
55
+ .smrt-subscription-summary dl {
56
+ display: flex;
57
+ gap: 1rem;
58
+ margin: 0;
59
+ }
60
+
61
+ .smrt-subscription-summary dd {
62
+ font-weight: var(--smrt-typography-weight-bold, 650);
63
+ margin: 0;
64
+ }
65
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { EntitlementResolution } from '../types.js';
2
+ type $$ComponentProps = {
3
+ resolution?: EntitlementResolution | null;
4
+ };
5
+ declare const SubscriptionSummary: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SubscriptionSummary = ReturnType<typeof SubscriptionSummary>;
7
+ export default SubscriptionSummary;
8
+ //# sourceMappingURL=SubscriptionSummary.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubscriptionSummary.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/SubscriptionSummary.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGxD,KAAK,gBAAgB,GAAI;IACxB,UAAU,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAC3C,CAAC;AAsCF,QAAA,MAAM,mBAAmB,sDAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import type { ThresholdEvaluation } from '../types.js';
3
+
4
+ let {
5
+ evaluations = [],
6
+ }: {
7
+ evaluations?: ThresholdEvaluation[];
8
+ } = $props();
9
+ </script>
10
+
11
+ <div class="smrt-usage-thresholds">
12
+ {#each evaluations as evaluation (`${evaluation.threshold.metricKey}:${evaluation.usage.windowStart.toISOString()}`)}
13
+ <article
14
+ class:warn={evaluation.state === 'warn'}
15
+ class:blocked={evaluation.state === 'blocked'}
16
+ class="smrt-usage-thresholds__row"
17
+ >
18
+ <div class="smrt-usage-thresholds__header">
19
+ <strong>{evaluation.threshold.label ?? evaluation.threshold.metricKey}</strong>
20
+ <span>{evaluation.usage.quantity} / {evaluation.threshold.limit}</span>
21
+ </div>
22
+ <progress
23
+ aria-label={`${evaluation.threshold.label ?? evaluation.threshold.metricKey} usage`}
24
+ max="1"
25
+ value={Number.isFinite(evaluation.ratio)
26
+ ? Math.min(1, evaluation.ratio)
27
+ : 1}
28
+ ></progress>
29
+ <p>{evaluation.state}</p>
30
+ </article>
31
+ {/each}
32
+ </div>
33
+
34
+ <style>
35
+ .smrt-usage-thresholds {
36
+ display: grid;
37
+ gap: 0.75rem;
38
+ }
39
+
40
+ .smrt-usage-thresholds__row {
41
+ border: 1px solid var(--smrt-color-outline-variant, #d8dde6);
42
+ border-radius: var(--smrt-radius-md, 8px);
43
+ display: grid;
44
+ gap: 0.45rem;
45
+ padding: 0.875rem;
46
+ }
47
+
48
+ .smrt-usage-thresholds__row.warn {
49
+ border-color: var(--smrt-color-warning, #d97706);
50
+ }
51
+
52
+ .smrt-usage-thresholds__row.blocked {
53
+ border-color: var(--smrt-color-error, #dc2626);
54
+ }
55
+
56
+ .smrt-usage-thresholds__header {
57
+ align-items: center;
58
+ display: flex;
59
+ justify-content: space-between;
60
+ }
61
+
62
+ .smrt-usage-thresholds p {
63
+ color: var(--smrt-color-on-surface-variant, #64748b);
64
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
65
+ margin: 0;
66
+ }
67
+
68
+ .smrt-usage-thresholds progress {
69
+ inline-size: 100%;
70
+ }
71
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { ThresholdEvaluation } from '../types.js';
2
+ type $$ComponentProps = {
3
+ evaluations?: ThresholdEvaluation[];
4
+ };
5
+ declare const UsageThresholds: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type UsageThresholds = ReturnType<typeof UsageThresholds>;
7
+ export default UsageThresholds;
8
+ //# sourceMappingURL=UsageThresholds.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageThresholds.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/UsageThresholds.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEtD,KAAK,gBAAgB,GAAI;IACxB,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACrC,CAAC;AA4BF,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const M: {
2
+ readonly 'subscriptions.summary.current_plan': "subscriptions.summary.current_plan";
3
+ readonly 'subscriptions.summary.no_active_plan': "subscriptions.summary.no_active_plan";
4
+ };
5
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,CAAC;;;CAGZ,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * smrt-subscriptions UI message catalog (S13 #1418).
3
+ * Keys: `subscriptions.<component>.<descriptor>`.
4
+ */
5
+ import { defineMessages } from '@happyvertical/smrt-ui/i18n';
6
+ export const M = defineMessages({
7
+ 'subscriptions.summary.current_plan': 'Current plan',
8
+ 'subscriptions.summary.no_active_plan': 'No active plan',
9
+ });
@@ -0,0 +1,4 @@
1
+ export { default as PlanPicker } from './PlanPicker.svelte';
2
+ export { default as SubscriptionSummary } from './SubscriptionSummary.svelte';
3
+ export { default as UsageThresholds } from './UsageThresholds.svelte';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { default as PlanPicker } from './PlanPicker.svelte';
2
+ export { default as SubscriptionSummary } from './SubscriptionSummary.svelte';
3
+ export { default as UsageThresholds } from './UsageThresholds.svelte';
@@ -0,0 +1,175 @@
1
+ import { SmrtClassOptions } from '@happyvertical/smrt-core';
2
+ import { SubscriptionPlan } from './models/SubscriptionPlan.js';
3
+ import { TenantSubscription } from './models/TenantSubscription.js';
4
+ export type SubscriptionStatus = 'active' | 'canceled' | 'incomplete' | 'past_due' | 'trialing' | 'unpaid';
5
+ export type SubscriptionPlanStatus = 'active' | 'archived' | 'draft';
6
+ export type BillingInterval = 'day' | 'week' | 'month' | 'year';
7
+ export type ThresholdEnforcement = 'observe' | 'warn' | 'block';
8
+ export type ThresholdWindow = 'day' | 'week' | 'month' | 'year' | 'rolling';
9
+ export type SubscriberKind = 'tenant' | 'external';
10
+ /**
11
+ * Discriminated union identifying who a subscription/usage record is for.
12
+ *
13
+ * - `tenant`: the subscriber IS the owning tenant — preserves the existing
14
+ * single-tenant SaaS shape end-to-end.
15
+ * - `external`: the subscriber is a caller-defined identity (opaque to this
16
+ * package) scoped under an issuing `tenantId`. The `externalId` is a free-form
17
+ * string that the caller namespaces (e.g. `buyer-contact:abc123`). Use this
18
+ * for B2C buyers, anonymous-email subscribers, agent identities, etc.
19
+ *
20
+ * For both kinds, `tenantId` carries the owning/issuing tenant scope.
21
+ */
22
+ export type Subscriber = {
23
+ kind: 'tenant';
24
+ tenantId: string;
25
+ } | {
26
+ kind: 'external';
27
+ tenantId: string;
28
+ externalId: string;
29
+ };
30
+ export interface PlanFeatureGrant {
31
+ featureKey: string;
32
+ enabled?: boolean;
33
+ metadata?: Record<string, unknown>;
34
+ }
35
+ export interface PlanThreshold {
36
+ metricKey: string;
37
+ limit: number;
38
+ window: ThresholdWindow;
39
+ enforcement: ThresholdEnforcement;
40
+ label?: string;
41
+ warningRatio?: number;
42
+ metadata?: Record<string, unknown>;
43
+ }
44
+ export interface UsageWindow {
45
+ start: Date;
46
+ end: Date;
47
+ }
48
+ export interface UsageMetricRecord {
49
+ /**
50
+ * Owning/issuing tenant scope. For `subscriberKind: 'tenant'` records this is
51
+ * also the subscriber. For `'external'` records this is the issuer.
52
+ */
53
+ tenantId: string;
54
+ /**
55
+ * Defaults to `'tenant'` when omitted — preserves the historical single-tenant
56
+ * shape for existing callers.
57
+ */
58
+ subscriberKind?: SubscriberKind;
59
+ /**
60
+ * Required when `subscriberKind` is `'external'`. Caller-namespaced opaque
61
+ * identifier (e.g. `buyer-contact:abc123`).
62
+ */
63
+ subscriberExternalId?: string;
64
+ metricKey: string;
65
+ quantity: number;
66
+ windowStart: Date;
67
+ windowEnd: Date;
68
+ source?: string;
69
+ sourceId?: string;
70
+ dimensions?: Record<string, unknown>;
71
+ }
72
+ export interface UsageSummary {
73
+ /** Owning/issuing tenant scope. */
74
+ tenantId: string;
75
+ /** Subscriber kind this summary represents. Defaults to `'tenant'`. */
76
+ subscriberKind?: SubscriberKind;
77
+ /** Set when `subscriberKind === 'external'`. */
78
+ subscriberExternalId?: string;
79
+ metricKey: string;
80
+ quantity: number;
81
+ windowStart: Date;
82
+ windowEnd: Date;
83
+ }
84
+ export interface AiUsageSummary {
85
+ tenantId: string;
86
+ promptTokens: number;
87
+ completionTokens: number;
88
+ totalTokens: number;
89
+ estimatedCost: number;
90
+ requestCount: number;
91
+ windowStart: Date;
92
+ windowEnd: Date;
93
+ }
94
+ export interface ThresholdEvaluation {
95
+ threshold: PlanThreshold;
96
+ usage: UsageSummary;
97
+ ratio: number;
98
+ state: 'ok' | 'warn' | 'blocked';
99
+ allowed: boolean;
100
+ remaining: number;
101
+ }
102
+ export interface EntitlementResolution {
103
+ /** Issuing/owning tenant scope. */
104
+ tenantId: string;
105
+ /**
106
+ * The subscriber identity this resolution was computed for.
107
+ *
108
+ * Always populated at runtime by `SubscriptionResolver.resolveEntitlements`,
109
+ * but kept optional on the interface so downstream code that constructs
110
+ * or mocks `EntitlementResolution` (pre-#1454) continues to typecheck
111
+ * without code changes.
112
+ */
113
+ subscriber?: Subscriber;
114
+ planId: string | null;
115
+ planKey: string | null;
116
+ subscriptionId: string | null;
117
+ status: SubscriptionStatus | 'none';
118
+ featureKeys: string[];
119
+ thresholds: PlanThreshold[];
120
+ thresholdEvaluations: ThresholdEvaluation[];
121
+ allowed: boolean;
122
+ }
123
+ export interface EntitlementResolutionContext {
124
+ /**
125
+ * Current subscription for the requested subscriber. Set to `null` when a
126
+ * caller has already resolved that no current subscription exists.
127
+ */
128
+ subscription?: TenantSubscription | null;
129
+ /**
130
+ * Plan for `subscription.planId`. Set to `null` when the caller has already
131
+ * resolved the plan as absent or inactive.
132
+ */
133
+ plan?: SubscriptionPlan | null;
134
+ }
135
+ export interface SubscriptionResolverOptions {
136
+ now?: Date;
137
+ usageWindows?: Partial<Record<ThresholdWindow, UsageWindow>>;
138
+ /**
139
+ * Request-scoped context that lets repeated entitlement checks reuse an
140
+ * already-loaded subscription and plan instead of re-querying readers.
141
+ */
142
+ context?: EntitlementResolutionContext;
143
+ }
144
+ export interface UsageMeterOptions {
145
+ classOptions?: SmrtClassOptions;
146
+ }
147
+ export interface RecordUsageOptions extends UsageMetricRecord {
148
+ }
149
+ export interface SummarizeUsageOptions {
150
+ /** Owning/issuing tenant scope. */
151
+ tenantId: string;
152
+ /** Defaults to `'tenant'`. */
153
+ subscriberKind?: SubscriberKind;
154
+ /** Required when `subscriberKind === 'external'`. */
155
+ subscriberExternalId?: string;
156
+ metricKey: string;
157
+ window: UsageWindow;
158
+ }
159
+ export interface SummarizeUsageBatchOptions {
160
+ /** Owning/issuing tenant scope. */
161
+ tenantId: string;
162
+ /** Defaults to `'tenant'`. */
163
+ subscriberKind?: SubscriberKind;
164
+ /** Required when `subscriberKind === 'external'`. */
165
+ subscriberExternalId?: string;
166
+ metricKeys: string[];
167
+ window: UsageWindow;
168
+ }
169
+ export interface SummarizeAiUsageOptions {
170
+ tenantId: string;
171
+ window: UsageWindow;
172
+ }
173
+ export type JsonObject = Record<string, unknown>;
174
+ export type { SmrtClassOptions };
175
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ import { JsonObject, PlanFeatureGrant, PlanThreshold, Subscriber, SubscriberKind, ThresholdWindow, UsageWindow } from './types.js';
2
+ export declare function parseJsonArray<T>(value: string, fallback?: T[]): T[];
3
+ export declare function parseJsonObject<T extends JsonObject>(value: string, fallback: T): T;
4
+ export declare function stringifyJson(value: unknown): string;
5
+ export declare function normalizeFeatureGrants(grants: Array<string | PlanFeatureGrant>): PlanFeatureGrant[];
6
+ /**
7
+ * Coerce inputs from the legacy tenant-only API into a {@link Subscriber}.
8
+ *
9
+ * Existing callers that pass only `tenantId` (with optional `subscriberKind` /
10
+ * `subscriberExternalId` fields, e.g. on `RecordUsageOptions`) get normalized
11
+ * into the discriminated union exactly once at the boundary. This is the only
12
+ * place that contains "if kind is omitted, default to tenant" logic — every
13
+ * other site works with a typed `Subscriber`.
14
+ *
15
+ * Throws when:
16
+ * - `subscriberKind === 'external'` is requested without a non-empty
17
+ * `subscriberExternalId` (the XOR invariant is the whole point), or
18
+ * - the input carries a non-empty `subscriberExternalId` but the kind resolves
19
+ * to `'tenant'` (silent re-scoping would write buyer-scoped usage as tenant
20
+ * usage, which then disappears from external summaries).
21
+ */
22
+ export declare function normalizeSubscriber(input: {
23
+ tenantId: string;
24
+ subscriberKind?: SubscriberKind | null;
25
+ subscriberExternalId?: string | null;
26
+ }): Subscriber;
27
+ /**
28
+ * Project a {@link Subscriber} back onto column values for persistence or query
29
+ * filters. Counterpart to {@link normalizeSubscriber}.
30
+ */
31
+ export declare function subscriberToColumns(subscriber: Subscriber): {
32
+ tenantId: string;
33
+ subscriberKind: SubscriberKind;
34
+ subscriberExternalId: string;
35
+ };
36
+ /**
37
+ * Enforce the subscriber XOR invariant on a row's columns. Used by both the
38
+ * model constructors (catches construction-time mistakes) and the
39
+ * `validateBeforeSave` override (catches mutations applied via the
40
+ * generated PUT/PATCH update path that bypass the constructor).
41
+ *
42
+ * Defensive about types because both call sites can receive JSON or untyped
43
+ * fixture data via the generated REST/CLI surface — `null` arriving in place
44
+ * of `''` would otherwise slip past an `=== ''` check, land in the
45
+ * `(tenant_id, subscriber_kind, subscriber_external_id)` conflict key, and
46
+ * (on Postgres) not collide with other NULLs while `findCurrentForSubscriber`
47
+ * keeps querying by a string external id.
48
+ *
49
+ * @param modelName - Prepended to error messages so callers can tell whether
50
+ * the failure originated in `TenantSubscription` or `TenantUsageMetric`.
51
+ */
52
+ export declare function assertSubscriberInvariant(modelName: string, fields: {
53
+ subscriberKind: unknown;
54
+ subscriberExternalId: unknown;
55
+ }): void;
56
+ export declare function getWindowForThreshold(thresholdWindow: ThresholdWindow, now?: Date): UsageWindow;
57
+ export declare function getWindowKey(window: UsageWindow): string;
58
+ export declare function isValidThreshold(threshold: PlanThreshold): boolean;
59
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,cAAc,EAEd,eAAe,EACf,WAAW,EACZ,MAAM,YAAY,CAAC;AAepB,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,CAAC,EAAO,GAAG,CAAC,EAAE,CAWxE;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,UAAU,EAClD,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,GACV,CAAC,CAaH;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,gBAAgB,CAAC,GACvC,gBAAgB,EAAE,CAIpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,GAAG,UAAU,CAuCb;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAaA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IACN,cAAc,EAAE,OAAO,CAAC;IACxB,oBAAoB,EAAE,OAAO,CAAC;CAC/B,GACA,IAAI,CAmCN;AASD,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,eAAe,EAChC,GAAG,OAAa,GACf,WAAW,CAgBb;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAExD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAalE"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@happyvertical/smrt-subscriptions",
3
+ "version": "0.30.0",
4
+ "description": "Tenant subscriptions, entitlement resolution, usage thresholds, and subscription UI for SMRT",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "scripts",
11
+ "CLAUDE.md",
12
+ "AGENTS.md"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ },
19
+ "./manifest": "./dist/manifest.json",
20
+ "./manifest.json": "./dist/manifest.json",
21
+ "./svelte": {
22
+ "types": "./dist/svelte/index.d.ts",
23
+ "svelte": "./dist/svelte/index.js",
24
+ "import": "./dist/svelte/index.js"
25
+ }
26
+ },
27
+ "dependencies": {
28
+ "@happyvertical/sql": "^0.74.7",
29
+ "@happyvertical/smrt-ui": "0.30.0",
30
+ "@happyvertical/smrt-tenancy": "0.30.0",
31
+ "@happyvertical/smrt-core": "0.30.0"
32
+ },
33
+ "peerDependencies": {
34
+ "svelte": "^5.18.0"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "svelte": {
38
+ "optional": true
39
+ }
40
+ },
41
+ "devDependencies": {
42
+ "@sveltejs/package": "^2.5.7",
43
+ "@types/node": "24.10.9",
44
+ "svelte": "^5.18.0",
45
+ "svelte-check": "^4.3.5",
46
+ "typescript": "^5.9.3",
47
+ "vite": "^7.3.1",
48
+ "vitest": "^4.0.17",
49
+ "@happyvertical/smrt-vitest": "0.30.0"
50
+ },
51
+ "keywords": [
52
+ "smrt",
53
+ "subscriptions",
54
+ "billing",
55
+ "entitlements",
56
+ "multi-tenant"
57
+ ],
58
+ "author": "HappyVertical",
59
+ "license": "MIT",
60
+ "publishConfig": {
61
+ "registry": "https://registry.npmjs.org",
62
+ "access": "public"
63
+ },
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "https://github.com/happyvertical/smrt.git",
67
+ "directory": "packages/subscriptions"
68
+ },
69
+ "scripts": {
70
+ "build": "vite build --mode library && svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json",
71
+ "build:watch": "vite build --mode library --watch",
72
+ "check": "pnpm exec svelte-check --tsconfig ./tsconfig.svelte.json",
73
+ "typecheck": "tsc --noEmit && node ../../scripts/svelte-check-a11y.mjs --tsconfig ./tsconfig.svelte.json",
74
+ "clean": "rm -rf dist",
75
+ "dev": "vite dev",
76
+ "test": "vitest run",
77
+ "test:watch": "vitest",
78
+ "verify:pack": "node ../../scripts/verify-package-types-exports.js ."
79
+ }
80
+ }