@contractspec/example.saas-boilerplate 3.7.5 → 3.7.7
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/.turbo/turbo-build.log +8 -8
- package/AGENTS.md +50 -27
- package/CHANGELOG.md +16 -0
- package/README.md +64 -144
- package/dist/billing/billing.event.js +1 -1
- package/dist/billing/index.d.ts +6 -6
- package/dist/billing/index.js +1 -1
- package/dist/browser/billing/billing.event.js +1 -1
- package/dist/browser/billing/index.js +1 -1
- package/dist/browser/index.js +931 -932
- package/dist/browser/project/index.js +209 -209
- package/dist/browser/project/project.event.js +1 -1
- package/dist/browser/ui/SaasDashboard.js +45 -45
- package/dist/browser/ui/SaasProjectList.js +7 -7
- package/dist/browser/ui/SaasSettingsPanel.js +12 -12
- package/dist/browser/ui/hooks/index.js +2 -2
- package/dist/browser/ui/hooks/useProjectList.js +1 -1
- package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
- package/dist/browser/ui/index.js +483 -484
- package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
- package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/browser/ui/modals/index.js +23 -23
- package/dist/browser/ui/renderers/index.js +112 -112
- package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
- package/dist/handlers/index.d.ts +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +931 -932
- package/dist/node/billing/billing.event.js +1 -1
- package/dist/node/billing/index.js +1 -1
- package/dist/node/index.js +931 -932
- package/dist/node/project/index.js +209 -209
- package/dist/node/project/project.event.js +1 -1
- package/dist/node/ui/SaasDashboard.js +45 -45
- package/dist/node/ui/SaasProjectList.js +7 -7
- package/dist/node/ui/SaasSettingsPanel.js +12 -12
- package/dist/node/ui/hooks/index.js +2 -2
- package/dist/node/ui/hooks/useProjectList.js +1 -1
- package/dist/node/ui/hooks/useProjectMutations.js +1 -1
- package/dist/node/ui/index.js +483 -484
- package/dist/node/ui/modals/CreateProjectModal.js +10 -10
- package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/node/ui/modals/index.js +23 -23
- package/dist/node/ui/renderers/index.js +112 -112
- package/dist/node/ui/renderers/project-list.renderer.js +7 -7
- package/dist/presentations/index.d.ts +1 -1
- package/dist/project/index.d.ts +7 -7
- package/dist/project/index.js +209 -209
- package/dist/project/project.event.js +1 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/ui/SaasDashboard.js +45 -45
- package/dist/ui/SaasProjectList.js +7 -7
- package/dist/ui/SaasSettingsPanel.js +12 -12
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +2 -2
- package/dist/ui/hooks/useProjectList.d.ts +5 -0
- package/dist/ui/hooks/useProjectList.js +1 -1
- package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
- package/dist/ui/hooks/useProjectMutations.js +1 -1
- package/dist/ui/index.d.ts +4 -4
- package/dist/ui/index.js +483 -484
- package/dist/ui/modals/CreateProjectModal.js +10 -10
- package/dist/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/ui/modals/index.js +23 -23
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +112 -112
- package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
- package/dist/ui/renderers/project-list.renderer.js +7 -7
- package/package.json +14 -14
- package/src/billing/billing.entity.ts +132 -132
- package/src/billing/billing.enum.ts +9 -9
- package/src/billing/billing.event.ts +71 -71
- package/src/billing/billing.handler.ts +87 -87
- package/src/billing/billing.operations.ts +158 -158
- package/src/billing/billing.presentation.ts +45 -45
- package/src/billing/billing.schema.ts +76 -76
- package/src/billing/index.ts +43 -48
- package/src/dashboard/dashboard.presentation.ts +45 -45
- package/src/dashboard/index.ts +2 -2
- package/src/docs/saas-boilerplate.docblock.ts +43 -43
- package/src/example.ts +32 -32
- package/src/handlers/index.ts +9 -9
- package/src/handlers/saas.handlers.ts +250 -249
- package/src/index.ts +40 -41
- package/src/presentations/index.ts +18 -20
- package/src/project/index.ts +45 -50
- package/src/project/project.entity.ts +68 -68
- package/src/project/project.enum.ts +8 -8
- package/src/project/project.event.ts +79 -79
- package/src/project/project.handler.ts +103 -103
- package/src/project/project.operations.ts +236 -236
- package/src/project/project.presentation.ts +46 -46
- package/src/project/project.schema.ts +90 -90
- package/src/saas-boilerplate.feature.ts +100 -100
- package/src/seeders/index.ts +20 -20
- package/src/settings/index.ts +2 -3
- package/src/settings/settings.entity.ts +65 -65
- package/src/settings/settings.enum.ts +4 -4
- package/src/shared/mock-data.ts +92 -92
- package/src/shared/overlay-types.ts +23 -23
- package/src/tests/operations.test-spec.ts +96 -96
- package/src/ui/SaasDashboard.tsx +270 -270
- package/src/ui/SaasProjectList.tsx +90 -90
- package/src/ui/SaasSettingsPanel.tsx +84 -84
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useProjectList.ts +69 -68
- package/src/ui/hooks/useProjectMutations.ts +144 -143
- package/src/ui/index.ts +8 -12
- package/src/ui/modals/CreateProjectModal.tsx +154 -154
- package/src/ui/modals/ProjectActionsModal.tsx +321 -321
- package/src/ui/overlays/demo-overlays.ts +49 -49
- package/src/ui/renderers/index.ts +5 -4
- package/src/ui/renderers/project-list.markdown.ts +204 -204
- package/src/ui/renderers/project-list.renderer.tsx +14 -13
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
|
@@ -5,133 +5,133 @@ import { MOCK_SUBSCRIPTION, MOCK_USAGE_SUMMARY } from '../shared/mock-data';
|
|
|
5
5
|
|
|
6
6
|
// Types inferred from contract schemas
|
|
7
7
|
export interface Subscription {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
id: string;
|
|
9
|
+
organizationId: string;
|
|
10
|
+
planId: string;
|
|
11
|
+
planName: string;
|
|
12
|
+
status: 'ACTIVE' | 'TRIALING' | 'PAST_DUE' | 'CANCELED' | 'UNPAID';
|
|
13
|
+
currentPeriodStart: Date;
|
|
14
|
+
currentPeriodEnd: Date;
|
|
15
|
+
limits: {
|
|
16
|
+
projects: number;
|
|
17
|
+
users: number;
|
|
18
|
+
storage: number;
|
|
19
|
+
apiCalls: number;
|
|
20
|
+
};
|
|
21
|
+
usage: {
|
|
22
|
+
projects: number;
|
|
23
|
+
users: number;
|
|
24
|
+
storage: number;
|
|
25
|
+
apiCalls: number;
|
|
26
|
+
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface UsageSummary {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
organizationId: string;
|
|
31
|
+
period: string;
|
|
32
|
+
apiCalls: {
|
|
33
|
+
total: number;
|
|
34
|
+
limit: number;
|
|
35
|
+
percentUsed: number;
|
|
36
|
+
};
|
|
37
|
+
storage: {
|
|
38
|
+
totalGb: number;
|
|
39
|
+
limitGb: number;
|
|
40
|
+
percentUsed: number;
|
|
41
|
+
};
|
|
42
|
+
activeProjects: number;
|
|
43
|
+
activeUsers: number;
|
|
44
|
+
breakdown: {
|
|
45
|
+
date: string;
|
|
46
|
+
apiCalls: number;
|
|
47
|
+
storageGb: number;
|
|
48
|
+
}[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export interface RecordUsageInput {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
metric: string;
|
|
53
|
+
quantity: number;
|
|
54
|
+
timestamp?: Date;
|
|
55
|
+
metadata?: Record<string, unknown>;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export interface CheckFeatureAccessInput {
|
|
59
|
-
|
|
59
|
+
feature: string;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export interface CheckFeatureAccessOutput {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
allowed: boolean;
|
|
64
|
+
reason?:
|
|
65
|
+
| 'PLAN_LIMIT'
|
|
66
|
+
| 'FEATURE_NOT_INCLUDED'
|
|
67
|
+
| 'QUOTA_EXCEEDED'
|
|
68
|
+
| 'SUBSCRIPTION_INACTIVE';
|
|
69
|
+
currentUsage?: number;
|
|
70
|
+
limit?: number;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Mock handler for GetSubscriptionContract.
|
|
75
75
|
*/
|
|
76
76
|
export async function mockGetSubscriptionHandler(): Promise<Subscription> {
|
|
77
|
-
|
|
77
|
+
return MOCK_SUBSCRIPTION;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Mock handler for GetUsageSummaryContract.
|
|
82
82
|
*/
|
|
83
83
|
export async function mockGetUsageSummaryHandler(input: {
|
|
84
|
-
|
|
84
|
+
period?: string;
|
|
85
85
|
}): Promise<UsageSummary> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
return {
|
|
87
|
+
...MOCK_USAGE_SUMMARY,
|
|
88
|
+
period: input.period ?? 'current_month',
|
|
89
|
+
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* Mock handler for RecordUsageContract.
|
|
94
94
|
*/
|
|
95
95
|
export async function mockRecordUsageHandler(
|
|
96
|
-
|
|
96
|
+
input: RecordUsageInput
|
|
97
97
|
): Promise<{ recorded: boolean; newTotal: number }> {
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const currentUsage = MOCK_USAGE_SUMMARY.apiCalls.total;
|
|
99
|
+
const newTotal = currentUsage + input.quantity;
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
return {
|
|
102
|
+
recorded: true,
|
|
103
|
+
newTotal,
|
|
104
|
+
};
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Mock handler for CheckFeatureAccessContract.
|
|
109
109
|
*/
|
|
110
110
|
export async function mockCheckFeatureAccessHandler(
|
|
111
|
-
|
|
111
|
+
input: CheckFeatureAccessInput
|
|
112
112
|
): Promise<CheckFeatureAccessOutput> {
|
|
113
|
-
|
|
113
|
+
const { feature } = input;
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
115
|
+
const featureMap: Record<string, CheckFeatureAccessOutput> = {
|
|
116
|
+
custom_domains: {
|
|
117
|
+
allowed: true,
|
|
118
|
+
},
|
|
119
|
+
api_access: {
|
|
120
|
+
allowed: true,
|
|
121
|
+
currentUsage: MOCK_USAGE_SUMMARY.apiCalls.total,
|
|
122
|
+
limit: MOCK_USAGE_SUMMARY.apiCalls.limit,
|
|
123
|
+
},
|
|
124
|
+
advanced_analytics: {
|
|
125
|
+
allowed: false,
|
|
126
|
+
reason: 'FEATURE_NOT_INCLUDED',
|
|
127
|
+
},
|
|
128
|
+
unlimited_projects: {
|
|
129
|
+
allowed: false,
|
|
130
|
+
reason: 'PLAN_LIMIT',
|
|
131
|
+
currentUsage: MOCK_SUBSCRIPTION.usage.projects,
|
|
132
|
+
limit: MOCK_SUBSCRIPTION.limits.projects,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
return featureMap[feature] ?? { allowed: true };
|
|
137
137
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineCommand, defineQuery } from '@contractspec/lib.contracts-spec';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
CheckFeatureAccessInputModel,
|
|
4
|
+
CheckFeatureAccessOutputModel,
|
|
5
|
+
GetUsageSummaryInputModel,
|
|
6
|
+
GetUsageSummaryOutputModel,
|
|
7
|
+
RecordUsageInputModel,
|
|
8
|
+
RecordUsageOutputModel,
|
|
9
|
+
SubscriptionModel,
|
|
10
|
+
UsageRecordedPayloadModel,
|
|
11
11
|
} from './billing.schema';
|
|
12
12
|
|
|
13
13
|
const OWNERS = ['@example.saas-boilerplate'] as const;
|
|
@@ -16,172 +16,172 @@ const OWNERS = ['@example.saas-boilerplate'] as const;
|
|
|
16
16
|
* Get subscription status.
|
|
17
17
|
*/
|
|
18
18
|
export const GetSubscriptionContract = defineQuery({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
19
|
+
meta: {
|
|
20
|
+
key: 'saas.billing.subscription.get',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
stability: 'stable',
|
|
23
|
+
owners: [...OWNERS],
|
|
24
|
+
tags: ['saas', 'billing', 'subscription'],
|
|
25
|
+
description: 'Get organization subscription status.',
|
|
26
|
+
goal: 'Show current plan and billing status.',
|
|
27
|
+
context: 'Billing page, plan upgrade prompts.',
|
|
28
|
+
},
|
|
29
|
+
io: {
|
|
30
|
+
input: null,
|
|
31
|
+
output: SubscriptionModel,
|
|
32
|
+
},
|
|
33
|
+
policy: {
|
|
34
|
+
auth: 'user',
|
|
35
|
+
},
|
|
36
|
+
acceptance: {
|
|
37
|
+
scenarios: [
|
|
38
|
+
{
|
|
39
|
+
key: 'get-subscription-happy-path',
|
|
40
|
+
given: ['Organization has active subscription'],
|
|
41
|
+
when: ['User requests subscription status'],
|
|
42
|
+
then: ['Subscription details are returned'],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
examples: [
|
|
46
|
+
{
|
|
47
|
+
key: 'get-basic',
|
|
48
|
+
input: null,
|
|
49
|
+
output: {
|
|
50
|
+
plan: 'pro',
|
|
51
|
+
status: 'active',
|
|
52
|
+
currentPeriodEnd: '2025-02-01T00:00:00Z',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Record feature usage.
|
|
61
61
|
*/
|
|
62
62
|
export const RecordUsageContract = defineCommand({
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
63
|
+
meta: {
|
|
64
|
+
key: 'saas.billing.usage.record',
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
stability: 'stable',
|
|
67
|
+
owners: [...OWNERS],
|
|
68
|
+
tags: ['saas', 'billing', 'usage'],
|
|
69
|
+
description: 'Record usage of a metered feature.',
|
|
70
|
+
goal: 'Track feature usage for billing.',
|
|
71
|
+
context: 'Called by services when metered features are used.',
|
|
72
|
+
},
|
|
73
|
+
io: {
|
|
74
|
+
input: RecordUsageInputModel,
|
|
75
|
+
output: RecordUsageOutputModel,
|
|
76
|
+
},
|
|
77
|
+
policy: {
|
|
78
|
+
auth: 'user',
|
|
79
|
+
},
|
|
80
|
+
sideEffects: {
|
|
81
|
+
emits: [
|
|
82
|
+
{
|
|
83
|
+
key: 'billing.usage.recorded',
|
|
84
|
+
version: '1.0.0',
|
|
85
|
+
when: 'Usage is recorded',
|
|
86
|
+
payload: UsageRecordedPayloadModel,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
acceptance: {
|
|
91
|
+
scenarios: [
|
|
92
|
+
{
|
|
93
|
+
key: 'record-usage-happy-path',
|
|
94
|
+
given: ['Organization exists'],
|
|
95
|
+
when: ['System records feature usage'],
|
|
96
|
+
then: ['Usage is recorded'],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
examples: [
|
|
100
|
+
{
|
|
101
|
+
key: 'record-api-call',
|
|
102
|
+
input: { feature: 'api_calls', quantity: 1, idempotencyKey: 'abc-123' },
|
|
103
|
+
output: { recorded: true, currentUsage: 100 },
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Get usage summary.
|
|
111
111
|
*/
|
|
112
112
|
export const GetUsageSummaryContract = defineQuery({
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
113
|
+
meta: {
|
|
114
|
+
key: 'saas.billing.usage.summary',
|
|
115
|
+
version: '1.0.0',
|
|
116
|
+
stability: 'stable',
|
|
117
|
+
owners: [...OWNERS],
|
|
118
|
+
tags: ['saas', 'billing', 'usage'],
|
|
119
|
+
description: 'Get usage summary for the current billing period.',
|
|
120
|
+
goal: 'Show usage vs limits.',
|
|
121
|
+
context: 'Billing page, usage dashboards.',
|
|
122
|
+
},
|
|
123
|
+
io: {
|
|
124
|
+
input: GetUsageSummaryInputModel,
|
|
125
|
+
output: GetUsageSummaryOutputModel,
|
|
126
|
+
},
|
|
127
|
+
policy: {
|
|
128
|
+
auth: 'user',
|
|
129
|
+
},
|
|
130
|
+
acceptance: {
|
|
131
|
+
scenarios: [
|
|
132
|
+
{
|
|
133
|
+
key: 'get-usage-happy-path',
|
|
134
|
+
given: ['Organization has usage history'],
|
|
135
|
+
when: ['User requests usage summary'],
|
|
136
|
+
then: ['Usage metrics are returned'],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
key: 'get-current-usage',
|
|
142
|
+
input: { period: 'current' },
|
|
143
|
+
output: { features: [{ name: 'api_calls', used: 100, limit: 1000 }] },
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Check feature access.
|
|
151
151
|
*/
|
|
152
152
|
export const CheckFeatureAccessContract = defineQuery({
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
153
|
+
meta: {
|
|
154
|
+
key: 'saas.billing.feature.check',
|
|
155
|
+
version: '1.0.0',
|
|
156
|
+
stability: 'stable',
|
|
157
|
+
owners: [...OWNERS],
|
|
158
|
+
tags: ['saas', 'billing', 'feature'],
|
|
159
|
+
description: 'Check if organization has access to a feature.',
|
|
160
|
+
goal: 'Gate features based on plan/usage.',
|
|
161
|
+
context: 'Feature access checks, upgrade prompts.',
|
|
162
|
+
},
|
|
163
|
+
io: {
|
|
164
|
+
input: CheckFeatureAccessInputModel,
|
|
165
|
+
output: CheckFeatureAccessOutputModel,
|
|
166
|
+
},
|
|
167
|
+
policy: {
|
|
168
|
+
auth: 'user',
|
|
169
|
+
},
|
|
170
|
+
acceptance: {
|
|
171
|
+
scenarios: [
|
|
172
|
+
{
|
|
173
|
+
key: 'check-access-granted',
|
|
174
|
+
given: ['Organization is on Pro plan'],
|
|
175
|
+
when: ['User checks access to Pro feature'],
|
|
176
|
+
then: ['Access is granted'],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
examples: [
|
|
180
|
+
{
|
|
181
|
+
key: 'check-advanced-reports',
|
|
182
|
+
input: { feature: 'advanced_reports' },
|
|
183
|
+
output: { hasAccess: true, reason: 'Included in Pro plan' },
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
187
|
});
|
|
@@ -1,59 +1,59 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
definePresentation,
|
|
3
|
+
StabilityEnum,
|
|
4
4
|
} from '@contractspec/lib.contracts-spec';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Presentation for subscription overview.
|
|
8
8
|
*/
|
|
9
9
|
export const SubscriptionPresentation = definePresentation({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
meta: {
|
|
11
|
+
key: 'saas.billing.subscription',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
title: 'Subscription Status',
|
|
14
|
+
description:
|
|
15
|
+
'Subscription status with plan info, limits, and current usage',
|
|
16
|
+
domain: 'saas-boilerplate',
|
|
17
|
+
owners: ['@saas-team'],
|
|
18
|
+
tags: ['billing', 'subscription'],
|
|
19
|
+
stability: StabilityEnum.Beta,
|
|
20
|
+
goal: 'View subscription plan and status',
|
|
21
|
+
context: 'Billing section',
|
|
22
|
+
},
|
|
23
|
+
source: {
|
|
24
|
+
type: 'component',
|
|
25
|
+
framework: 'react',
|
|
26
|
+
componentKey: 'SubscriptionView',
|
|
27
|
+
},
|
|
28
|
+
targets: ['react', 'markdown'],
|
|
29
|
+
policy: {
|
|
30
|
+
flags: ['saas.billing.enabled'],
|
|
31
|
+
},
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Presentation for usage dashboard.
|
|
36
36
|
*/
|
|
37
37
|
export const UsageDashboardPresentation = definePresentation({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
meta: {
|
|
39
|
+
key: 'saas.billing.usage',
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
title: 'Usage Dashboard',
|
|
42
|
+
description: 'Usage metrics and breakdown by resource type',
|
|
43
|
+
domain: 'saas-boilerplate',
|
|
44
|
+
owners: ['@saas-team'],
|
|
45
|
+
tags: ['billing', 'usage', 'metrics'],
|
|
46
|
+
stability: StabilityEnum.Beta,
|
|
47
|
+
goal: 'Monitor feature usage and limits',
|
|
48
|
+
context: 'Billing section',
|
|
49
|
+
},
|
|
50
|
+
source: {
|
|
51
|
+
type: 'component',
|
|
52
|
+
framework: 'react',
|
|
53
|
+
componentKey: 'UsageDashboardView',
|
|
54
|
+
},
|
|
55
|
+
targets: ['react', 'markdown'],
|
|
56
|
+
policy: {
|
|
57
|
+
flags: ['saas.billing.enabled'],
|
|
58
|
+
},
|
|
59
59
|
});
|