@contractspec/example.saas-boilerplate 3.8.9 → 3.8.11
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 +155 -155
- package/CHANGELOG.md +34 -0
- package/dist/billing/billing.entity.js +1 -113
- package/dist/billing/billing.enum.js +1 -19
- package/dist/billing/billing.event.js +1 -90
- package/dist/billing/billing.handler.js +1 -148
- package/dist/billing/billing.operations.js +1 -278
- package/dist/billing/billing.presentation.js +1 -55
- package/dist/billing/billing.schema.js +1 -121
- package/dist/billing/index.js +1 -691
- package/dist/browser/billing/billing.entity.js +1 -113
- package/dist/browser/billing/billing.enum.js +1 -19
- package/dist/browser/billing/billing.event.js +1 -90
- package/dist/browser/billing/billing.handler.js +1 -148
- package/dist/browser/billing/billing.operations.js +1 -278
- package/dist/browser/billing/billing.presentation.js +1 -55
- package/dist/browser/billing/billing.schema.js +1 -121
- package/dist/browser/billing/index.js +1 -691
- package/dist/browser/dashboard/dashboard.presentation.js +1 -55
- package/dist/browser/dashboard/index.js +1 -55
- package/dist/browser/docs/index.js +5 -49
- package/dist/browser/docs/saas-boilerplate.docblock.js +5 -49
- package/dist/browser/example.js +1 -39
- package/dist/browser/handlers/index.js +2 -358
- package/dist/browser/handlers/saas.handlers.js +2 -134
- package/dist/browser/index.js +9 -3591
- package/dist/browser/presentations/index.js +1 -299
- package/dist/browser/project/index.js +1 -793
- package/dist/browser/project/project.entity.js +1 -77
- package/dist/browser/project/project.enum.js +1 -18
- package/dist/browser/project/project.event.js +1 -103
- package/dist/browser/project/project.handler.js +1 -178
- package/dist/browser/project/project.operations.js +1 -372
- package/dist/browser/project/project.presentation.js +1 -180
- package/dist/browser/project/project.schema.js +1 -134
- package/dist/browser/saas-boilerplate.feature.js +1 -304
- package/dist/browser/seeders/index.js +2 -20
- package/dist/browser/settings/index.js +1 -75
- package/dist/browser/settings/settings.entity.js +1 -74
- package/dist/browser/settings/settings.enum.js +1 -11
- package/dist/browser/shared/mock-data.js +1 -104
- package/dist/browser/tests/operations.test-spec.js +1 -112
- package/dist/browser/ui/SaasDashboard.js +1 -1239
- package/dist/browser/ui/SaasDashboard.visualizations.js +1 -249
- package/dist/browser/ui/SaasProjectList.js +1 -162
- package/dist/browser/ui/SaasSettingsPanel.js +1 -145
- package/dist/browser/ui/hooks/index.js +1 -159
- package/dist/browser/ui/hooks/useProjectList.js +1 -66
- package/dist/browser/ui/hooks/useProjectMutations.js +1 -91
- package/dist/browser/ui/index.js +5 -2077
- package/dist/browser/ui/modals/CreateProjectModal.js +1 -153
- package/dist/browser/ui/modals/ProjectActionsModal.js +1 -335
- package/dist/browser/ui/modals/index.js +1 -487
- package/dist/browser/ui/overlays/demo-overlays.js +1 -61
- package/dist/browser/ui/overlays/index.js +1 -61
- package/dist/browser/ui/renderers/index.js +5 -901
- package/dist/browser/ui/renderers/project-list.markdown.js +5 -725
- package/dist/browser/ui/renderers/project-list.renderer.js +1 -177
- package/dist/browser/visualizations/catalog.js +1 -155
- package/dist/browser/visualizations/index.js +1 -217
- package/dist/browser/visualizations/selectors.js +1 -210
- package/dist/dashboard/dashboard.presentation.js +1 -55
- package/dist/dashboard/index.js +1 -55
- package/dist/docs/index.js +5 -49
- package/dist/docs/saas-boilerplate.docblock.js +5 -49
- package/dist/example.js +1 -39
- package/dist/handlers/index.js +2 -358
- package/dist/handlers/saas.handlers.js +2 -134
- package/dist/index.js +9 -3591
- package/dist/node/billing/billing.entity.js +1 -113
- package/dist/node/billing/billing.enum.js +1 -19
- package/dist/node/billing/billing.event.js +1 -90
- package/dist/node/billing/billing.handler.js +1 -148
- package/dist/node/billing/billing.operations.js +1 -278
- package/dist/node/billing/billing.presentation.js +1 -55
- package/dist/node/billing/billing.schema.js +1 -121
- package/dist/node/billing/index.js +1 -691
- package/dist/node/dashboard/dashboard.presentation.js +1 -55
- package/dist/node/dashboard/index.js +1 -55
- package/dist/node/docs/index.js +5 -49
- package/dist/node/docs/saas-boilerplate.docblock.js +5 -49
- package/dist/node/example.js +1 -39
- package/dist/node/handlers/index.js +2 -358
- package/dist/node/handlers/saas.handlers.js +2 -134
- package/dist/node/index.js +9 -3591
- package/dist/node/presentations/index.js +1 -299
- package/dist/node/project/index.js +1 -793
- package/dist/node/project/project.entity.js +1 -77
- package/dist/node/project/project.enum.js +1 -18
- package/dist/node/project/project.event.js +1 -103
- package/dist/node/project/project.handler.js +1 -178
- package/dist/node/project/project.operations.js +1 -372
- package/dist/node/project/project.presentation.js +1 -180
- package/dist/node/project/project.schema.js +1 -134
- package/dist/node/saas-boilerplate.feature.js +1 -304
- package/dist/node/seeders/index.js +2 -20
- package/dist/node/settings/index.js +1 -75
- package/dist/node/settings/settings.entity.js +1 -74
- package/dist/node/settings/settings.enum.js +1 -11
- package/dist/node/shared/mock-data.js +1 -104
- package/dist/node/tests/operations.test-spec.js +1 -112
- package/dist/node/ui/SaasDashboard.js +1 -1239
- package/dist/node/ui/SaasDashboard.visualizations.js +1 -249
- package/dist/node/ui/SaasProjectList.js +1 -162
- package/dist/node/ui/SaasSettingsPanel.js +1 -145
- package/dist/node/ui/hooks/index.js +1 -159
- package/dist/node/ui/hooks/useProjectList.js +1 -66
- package/dist/node/ui/hooks/useProjectMutations.js +1 -91
- package/dist/node/ui/index.js +5 -2077
- package/dist/node/ui/modals/CreateProjectModal.js +1 -153
- package/dist/node/ui/modals/ProjectActionsModal.js +1 -335
- package/dist/node/ui/modals/index.js +1 -487
- package/dist/node/ui/overlays/demo-overlays.js +1 -61
- package/dist/node/ui/overlays/index.js +1 -61
- package/dist/node/ui/renderers/index.js +5 -901
- package/dist/node/ui/renderers/project-list.markdown.js +5 -725
- package/dist/node/ui/renderers/project-list.renderer.js +1 -177
- package/dist/node/visualizations/catalog.js +1 -155
- package/dist/node/visualizations/index.js +1 -217
- package/dist/node/visualizations/selectors.js +1 -210
- package/dist/presentations/index.js +1 -299
- package/dist/project/index.js +1 -793
- package/dist/project/project.entity.js +1 -77
- package/dist/project/project.enum.js +1 -18
- package/dist/project/project.event.js +1 -103
- package/dist/project/project.handler.js +1 -178
- package/dist/project/project.operations.js +1 -372
- package/dist/project/project.presentation.js +1 -180
- package/dist/project/project.schema.js +1 -134
- package/dist/saas-boilerplate.feature.js +1 -304
- package/dist/seeders/index.js +2 -20
- package/dist/settings/index.js +1 -75
- package/dist/settings/settings.entity.js +1 -74
- package/dist/settings/settings.enum.js +1 -11
- package/dist/shared/mock-data.js +1 -104
- package/dist/tests/operations.test-spec.js +1 -112
- package/dist/ui/SaasDashboard.js +1 -1239
- package/dist/ui/SaasDashboard.visualizations.js +1 -249
- package/dist/ui/SaasProjectList.js +1 -162
- package/dist/ui/SaasSettingsPanel.js +1 -145
- package/dist/ui/hooks/index.js +1 -159
- package/dist/ui/hooks/useProjectList.js +1 -66
- package/dist/ui/hooks/useProjectMutations.js +1 -91
- package/dist/ui/index.js +5 -2077
- package/dist/ui/modals/CreateProjectModal.js +1 -153
- package/dist/ui/modals/ProjectActionsModal.js +1 -335
- package/dist/ui/modals/index.js +1 -487
- package/dist/ui/overlays/demo-overlays.js +1 -61
- package/dist/ui/overlays/index.js +1 -61
- package/dist/ui/renderers/index.js +5 -901
- package/dist/ui/renderers/project-list.markdown.js +5 -725
- package/dist/ui/renderers/project-list.renderer.js +1 -177
- package/dist/visualizations/catalog.js +1 -155
- package/dist/visualizations/index.js +1 -217
- package/dist/visualizations/selectors.js +1 -210
- package/package.json +12 -12
|
@@ -1,691 +1 @@
|
|
|
1
|
-
// src/billing/billing.entity.ts
|
|
2
|
-
import {
|
|
3
|
-
defineEntity,
|
|
4
|
-
defineEntityEnum,
|
|
5
|
-
field,
|
|
6
|
-
index
|
|
7
|
-
} from "@contractspec/lib.schema";
|
|
8
|
-
var SubscriptionStatusEnum = defineEntityEnum({
|
|
9
|
-
name: "SubscriptionStatus",
|
|
10
|
-
values: ["TRIALING", "ACTIVE", "PAST_DUE", "CANCELED", "PAUSED"],
|
|
11
|
-
schema: "saas_app",
|
|
12
|
-
description: "Status of a subscription."
|
|
13
|
-
});
|
|
14
|
-
var SubscriptionEntity = defineEntity({
|
|
15
|
-
name: "Subscription",
|
|
16
|
-
description: "Organization subscription/plan information.",
|
|
17
|
-
schema: "saas_app",
|
|
18
|
-
map: "subscription",
|
|
19
|
-
fields: {
|
|
20
|
-
id: field.id(),
|
|
21
|
-
organizationId: field.foreignKey({ isUnique: true }),
|
|
22
|
-
planId: field.string({ description: "Plan identifier" }),
|
|
23
|
-
planName: field.string({ description: "Plan display name" }),
|
|
24
|
-
status: field.enum("SubscriptionStatus"),
|
|
25
|
-
currentPeriodStart: field.dateTime(),
|
|
26
|
-
currentPeriodEnd: field.dateTime(),
|
|
27
|
-
trialEndsAt: field.dateTime({ isOptional: true }),
|
|
28
|
-
cancelAtPeriodEnd: field.boolean({ default: false }),
|
|
29
|
-
canceledAt: field.dateTime({ isOptional: true }),
|
|
30
|
-
stripeSubscriptionId: field.string({ isOptional: true }),
|
|
31
|
-
stripeCustomerId: field.string({ isOptional: true }),
|
|
32
|
-
metadata: field.json({ isOptional: true }),
|
|
33
|
-
createdAt: field.createdAt(),
|
|
34
|
-
updatedAt: field.updatedAt()
|
|
35
|
-
},
|
|
36
|
-
enums: [SubscriptionStatusEnum]
|
|
37
|
-
});
|
|
38
|
-
var BillingUsageEntity = defineEntity({
|
|
39
|
-
name: "BillingUsage",
|
|
40
|
-
description: "Track usage of metered features.",
|
|
41
|
-
schema: "saas_app",
|
|
42
|
-
map: "billing_usage",
|
|
43
|
-
fields: {
|
|
44
|
-
id: field.id(),
|
|
45
|
-
organizationId: field.foreignKey(),
|
|
46
|
-
feature: field.string({
|
|
47
|
-
description: 'Feature being tracked (e.g., "api_calls", "storage_gb")'
|
|
48
|
-
}),
|
|
49
|
-
quantity: field.int({ description: "Usage quantity" }),
|
|
50
|
-
unit: field.string({
|
|
51
|
-
isOptional: true,
|
|
52
|
-
description: "Unit of measurement"
|
|
53
|
-
}),
|
|
54
|
-
billingPeriod: field.string({
|
|
55
|
-
description: 'Billing period (e.g., "2024-01")'
|
|
56
|
-
}),
|
|
57
|
-
recordedAt: field.dateTime({ description: "When usage was recorded" }),
|
|
58
|
-
sourceId: field.string({
|
|
59
|
-
isOptional: true,
|
|
60
|
-
description: "Source of usage (e.g., request ID)"
|
|
61
|
-
}),
|
|
62
|
-
sourceType: field.string({ isOptional: true }),
|
|
63
|
-
metadata: field.json({ isOptional: true })
|
|
64
|
-
},
|
|
65
|
-
indexes: [
|
|
66
|
-
index.on(["organizationId", "feature", "billingPeriod"]),
|
|
67
|
-
index.on(["organizationId", "recordedAt"])
|
|
68
|
-
]
|
|
69
|
-
});
|
|
70
|
-
var UsageLimitEntity = defineEntity({
|
|
71
|
-
name: "UsageLimit",
|
|
72
|
-
description: "Usage limits per plan/organization.",
|
|
73
|
-
schema: "saas_app",
|
|
74
|
-
map: "usage_limit",
|
|
75
|
-
fields: {
|
|
76
|
-
id: field.id(),
|
|
77
|
-
planId: field.string({
|
|
78
|
-
isOptional: true,
|
|
79
|
-
description: "Plan this limit applies to"
|
|
80
|
-
}),
|
|
81
|
-
organizationId: field.string({
|
|
82
|
-
isOptional: true,
|
|
83
|
-
description: "Org-specific override"
|
|
84
|
-
}),
|
|
85
|
-
feature: field.string({ description: "Feature being limited" }),
|
|
86
|
-
limit: field.int({ description: "Maximum allowed usage" }),
|
|
87
|
-
resetPeriod: field.string({
|
|
88
|
-
default: '"monthly"',
|
|
89
|
-
description: "When limit resets"
|
|
90
|
-
}),
|
|
91
|
-
isSoftLimit: field.boolean({
|
|
92
|
-
default: false,
|
|
93
|
-
description: "Whether to warn vs block"
|
|
94
|
-
}),
|
|
95
|
-
overage: field.boolean({
|
|
96
|
-
default: false,
|
|
97
|
-
description: "Whether overage is allowed"
|
|
98
|
-
}),
|
|
99
|
-
overageRate: field.float({
|
|
100
|
-
isOptional: true,
|
|
101
|
-
description: "Cost per unit over limit"
|
|
102
|
-
}),
|
|
103
|
-
createdAt: field.createdAt(),
|
|
104
|
-
updatedAt: field.updatedAt()
|
|
105
|
-
},
|
|
106
|
-
indexes: [index.unique(["planId", "feature"])]
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// src/billing/billing.enum.ts
|
|
110
|
-
import { defineEnum } from "@contractspec/lib.schema";
|
|
111
|
-
var SubscriptionStatusSchemaEnum = defineEnum("SubscriptionStatus", [
|
|
112
|
-
"TRIALING",
|
|
113
|
-
"ACTIVE",
|
|
114
|
-
"PAST_DUE",
|
|
115
|
-
"CANCELED",
|
|
116
|
-
"PAUSED"
|
|
117
|
-
]);
|
|
118
|
-
var FeatureAccessReasonEnum = defineEnum("FeatureAccessReason", [
|
|
119
|
-
"included",
|
|
120
|
-
"limit_available",
|
|
121
|
-
"limit_reached",
|
|
122
|
-
"not_in_plan"
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
// src/billing/billing.event.ts
|
|
126
|
-
import { defineEvent } from "@contractspec/lib.contracts-spec";
|
|
127
|
-
import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
|
|
128
|
-
var UsageRecordedPayload = defineSchemaModel({
|
|
129
|
-
name: "UsageRecordedPayload",
|
|
130
|
-
description: "Payload when feature usage is recorded",
|
|
131
|
-
fields: {
|
|
132
|
-
organizationId: {
|
|
133
|
-
type: ScalarTypeEnum.String_unsecure(),
|
|
134
|
-
isOptional: false
|
|
135
|
-
},
|
|
136
|
-
feature: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
137
|
-
quantity: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
138
|
-
billingPeriod: {
|
|
139
|
-
type: ScalarTypeEnum.String_unsecure(),
|
|
140
|
-
isOptional: false
|
|
141
|
-
},
|
|
142
|
-
recordedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
var UsageLimitReachedPayload = defineSchemaModel({
|
|
146
|
-
name: "UsageLimitReachedPayload",
|
|
147
|
-
description: "Payload when usage limit is reached",
|
|
148
|
-
fields: {
|
|
149
|
-
organizationId: {
|
|
150
|
-
type: ScalarTypeEnum.String_unsecure(),
|
|
151
|
-
isOptional: false
|
|
152
|
-
},
|
|
153
|
-
feature: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
154
|
-
limit: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
155
|
-
currentUsage: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
156
|
-
reachedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
var SubscriptionChangedPayload = defineSchemaModel({
|
|
160
|
-
name: "SubscriptionChangedPayload",
|
|
161
|
-
description: "Payload when subscription status changes",
|
|
162
|
-
fields: {
|
|
163
|
-
organizationId: {
|
|
164
|
-
type: ScalarTypeEnum.String_unsecure(),
|
|
165
|
-
isOptional: false
|
|
166
|
-
},
|
|
167
|
-
previousPlan: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
168
|
-
newPlan: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
169
|
-
previousStatus: {
|
|
170
|
-
type: ScalarTypeEnum.String_unsecure(),
|
|
171
|
-
isOptional: true
|
|
172
|
-
},
|
|
173
|
-
newStatus: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
174
|
-
changedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
var UsageRecordedEvent = defineEvent({
|
|
178
|
-
meta: {
|
|
179
|
-
key: "billing.usage.recorded",
|
|
180
|
-
version: "1.0.0",
|
|
181
|
-
description: "Feature usage has been recorded.",
|
|
182
|
-
stability: "stable",
|
|
183
|
-
owners: ["@saas-team"],
|
|
184
|
-
tags: ["billing", "usage", "recorded"]
|
|
185
|
-
},
|
|
186
|
-
payload: UsageRecordedPayload
|
|
187
|
-
});
|
|
188
|
-
var UsageLimitReachedEvent = defineEvent({
|
|
189
|
-
meta: {
|
|
190
|
-
key: "billing.limit.reached",
|
|
191
|
-
version: "1.0.0",
|
|
192
|
-
description: "Usage limit has been reached for a feature.",
|
|
193
|
-
stability: "stable",
|
|
194
|
-
owners: ["@saas-team"],
|
|
195
|
-
tags: ["billing", "limit", "reached"]
|
|
196
|
-
},
|
|
197
|
-
payload: UsageLimitReachedPayload
|
|
198
|
-
});
|
|
199
|
-
var SubscriptionChangedEvent = defineEvent({
|
|
200
|
-
meta: {
|
|
201
|
-
key: "billing.subscription.changed",
|
|
202
|
-
version: "1.0.0",
|
|
203
|
-
description: "Subscription status has changed.",
|
|
204
|
-
stability: "stable",
|
|
205
|
-
owners: ["@saas-team"],
|
|
206
|
-
tags: ["billing", "subscription", "changed"]
|
|
207
|
-
},
|
|
208
|
-
payload: SubscriptionChangedPayload
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// src/shared/mock-data.ts
|
|
212
|
-
var MOCK_PROJECTS = [
|
|
213
|
-
{
|
|
214
|
-
id: "proj-1",
|
|
215
|
-
name: "Marketing Website",
|
|
216
|
-
description: "Main company website redesign project",
|
|
217
|
-
slug: "marketing-website",
|
|
218
|
-
organizationId: "demo-org",
|
|
219
|
-
createdBy: "user-1",
|
|
220
|
-
status: "ACTIVE",
|
|
221
|
-
isPublic: false,
|
|
222
|
-
tags: ["marketing", "website", "redesign"],
|
|
223
|
-
createdAt: new Date("2024-01-15T10:00:00Z"),
|
|
224
|
-
updatedAt: new Date("2024-03-20T14:30:00Z")
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
id: "proj-2",
|
|
228
|
-
name: "Mobile App v2",
|
|
229
|
-
description: "Next generation mobile application",
|
|
230
|
-
slug: "mobile-app-v2",
|
|
231
|
-
organizationId: "demo-org",
|
|
232
|
-
createdBy: "user-2",
|
|
233
|
-
status: "ACTIVE",
|
|
234
|
-
isPublic: false,
|
|
235
|
-
tags: ["mobile", "app", "v2"],
|
|
236
|
-
createdAt: new Date("2024-02-01T09:00:00Z"),
|
|
237
|
-
updatedAt: new Date("2024-04-05T11:15:00Z")
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
id: "proj-3",
|
|
241
|
-
name: "API Integration",
|
|
242
|
-
description: "Third-party API integration project",
|
|
243
|
-
slug: "api-integration",
|
|
244
|
-
organizationId: "demo-org",
|
|
245
|
-
createdBy: "user-1",
|
|
246
|
-
status: "DRAFT",
|
|
247
|
-
isPublic: false,
|
|
248
|
-
tags: ["api", "integration"],
|
|
249
|
-
createdAt: new Date("2024-03-10T08:00:00Z"),
|
|
250
|
-
updatedAt: new Date("2024-03-10T08:00:00Z")
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
id: "proj-4",
|
|
254
|
-
name: "Analytics Dashboard",
|
|
255
|
-
description: "Internal analytics and reporting dashboard",
|
|
256
|
-
slug: "analytics-dashboard",
|
|
257
|
-
organizationId: "demo-org",
|
|
258
|
-
createdBy: "user-3",
|
|
259
|
-
status: "ARCHIVED",
|
|
260
|
-
isPublic: true,
|
|
261
|
-
tags: ["analytics", "dashboard", "reporting"],
|
|
262
|
-
createdAt: new Date("2023-10-01T12:00:00Z"),
|
|
263
|
-
updatedAt: new Date("2024-02-28T16:45:00Z")
|
|
264
|
-
}
|
|
265
|
-
];
|
|
266
|
-
var MOCK_SUBSCRIPTION = {
|
|
267
|
-
id: "sub-1",
|
|
268
|
-
organizationId: "demo-org",
|
|
269
|
-
planId: "pro",
|
|
270
|
-
planName: "Professional",
|
|
271
|
-
status: "ACTIVE",
|
|
272
|
-
currentPeriodStart: new Date("2024-04-01T00:00:00Z"),
|
|
273
|
-
currentPeriodEnd: new Date("2024-05-01T00:00:00Z"),
|
|
274
|
-
limits: {
|
|
275
|
-
projects: 25,
|
|
276
|
-
users: 10,
|
|
277
|
-
storage: 50,
|
|
278
|
-
apiCalls: 1e5
|
|
279
|
-
},
|
|
280
|
-
usage: {
|
|
281
|
-
projects: 4,
|
|
282
|
-
users: 5,
|
|
283
|
-
storage: 12.5,
|
|
284
|
-
apiCalls: 45230
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
var MOCK_USAGE_SUMMARY = {
|
|
288
|
-
organizationId: "demo-org",
|
|
289
|
-
period: "current_month",
|
|
290
|
-
apiCalls: {
|
|
291
|
-
total: 45230,
|
|
292
|
-
limit: 1e5,
|
|
293
|
-
percentUsed: 45.23
|
|
294
|
-
},
|
|
295
|
-
storage: {
|
|
296
|
-
totalGb: 12.5,
|
|
297
|
-
limitGb: 50,
|
|
298
|
-
percentUsed: 25
|
|
299
|
-
},
|
|
300
|
-
activeProjects: 4,
|
|
301
|
-
activeUsers: 5,
|
|
302
|
-
breakdown: [
|
|
303
|
-
{ date: "2024-04-01", apiCalls: 3200, storageGb: 12.1 },
|
|
304
|
-
{ date: "2024-04-02", apiCalls: 2800, storageGb: 12.2 },
|
|
305
|
-
{ date: "2024-04-03", apiCalls: 4100, storageGb: 12.3 },
|
|
306
|
-
{ date: "2024-04-04", apiCalls: 3600, storageGb: 12.4 },
|
|
307
|
-
{ date: "2024-04-05", apiCalls: 3800, storageGb: 12.5 }
|
|
308
|
-
]
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// src/billing/billing.handler.ts
|
|
312
|
-
async function mockGetSubscriptionHandler() {
|
|
313
|
-
return MOCK_SUBSCRIPTION;
|
|
314
|
-
}
|
|
315
|
-
async function mockGetUsageSummaryHandler(input) {
|
|
316
|
-
return {
|
|
317
|
-
...MOCK_USAGE_SUMMARY,
|
|
318
|
-
period: input.period ?? "current_month"
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
async function mockRecordUsageHandler(input) {
|
|
322
|
-
const currentUsage = MOCK_USAGE_SUMMARY.apiCalls.total;
|
|
323
|
-
const newTotal = currentUsage + input.quantity;
|
|
324
|
-
return {
|
|
325
|
-
recorded: true,
|
|
326
|
-
newTotal
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
async function mockCheckFeatureAccessHandler(input) {
|
|
330
|
-
const { feature } = input;
|
|
331
|
-
const featureMap = {
|
|
332
|
-
custom_domains: {
|
|
333
|
-
allowed: true
|
|
334
|
-
},
|
|
335
|
-
api_access: {
|
|
336
|
-
allowed: true,
|
|
337
|
-
currentUsage: MOCK_USAGE_SUMMARY.apiCalls.total,
|
|
338
|
-
limit: MOCK_USAGE_SUMMARY.apiCalls.limit
|
|
339
|
-
},
|
|
340
|
-
advanced_analytics: {
|
|
341
|
-
allowed: false,
|
|
342
|
-
reason: "FEATURE_NOT_INCLUDED"
|
|
343
|
-
},
|
|
344
|
-
unlimited_projects: {
|
|
345
|
-
allowed: false,
|
|
346
|
-
reason: "PLAN_LIMIT",
|
|
347
|
-
currentUsage: MOCK_SUBSCRIPTION.usage.projects,
|
|
348
|
-
limit: MOCK_SUBSCRIPTION.limits.projects
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
return featureMap[feature] ?? { allowed: true };
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// src/billing/billing.schema.ts
|
|
355
|
-
import { defineSchemaModel as defineSchemaModel2, ScalarTypeEnum as ScalarTypeEnum2 } from "@contractspec/lib.schema";
|
|
356
|
-
var SubscriptionModel = defineSchemaModel2({
|
|
357
|
-
name: "Subscription",
|
|
358
|
-
description: "Organization subscription details",
|
|
359
|
-
fields: {
|
|
360
|
-
id: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
361
|
-
organizationId: {
|
|
362
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
363
|
-
isOptional: false
|
|
364
|
-
},
|
|
365
|
-
planId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
366
|
-
planName: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
367
|
-
status: { type: SubscriptionStatusSchemaEnum, isOptional: false },
|
|
368
|
-
currentPeriodStart: { type: ScalarTypeEnum2.DateTime(), isOptional: false },
|
|
369
|
-
currentPeriodEnd: { type: ScalarTypeEnum2.DateTime(), isOptional: false },
|
|
370
|
-
trialEndsAt: { type: ScalarTypeEnum2.DateTime(), isOptional: true },
|
|
371
|
-
cancelAtPeriodEnd: { type: ScalarTypeEnum2.Boolean(), isOptional: false }
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
var UsageSummaryModel = defineSchemaModel2({
|
|
375
|
-
name: "UsageSummary",
|
|
376
|
-
description: "Usage summary for a feature",
|
|
377
|
-
fields: {
|
|
378
|
-
feature: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
379
|
-
used: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
380
|
-
limit: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
381
|
-
unit: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
382
|
-
percentage: { type: ScalarTypeEnum2.Float_unsecure(), isOptional: true }
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
var RecordUsageInputModel = defineSchemaModel2({
|
|
386
|
-
name: "RecordUsageInput",
|
|
387
|
-
description: "Input for recording feature usage",
|
|
388
|
-
fields: {
|
|
389
|
-
feature: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
390
|
-
quantity: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
391
|
-
sourceId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
392
|
-
sourceType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
393
|
-
metadata: { type: ScalarTypeEnum2.JSONObject(), isOptional: true }
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
var RecordUsageOutputModel = defineSchemaModel2({
|
|
397
|
-
name: "RecordUsageOutput",
|
|
398
|
-
description: "Output for recording feature usage",
|
|
399
|
-
fields: {
|
|
400
|
-
recorded: { type: ScalarTypeEnum2.Boolean(), isOptional: false },
|
|
401
|
-
currentUsage: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
402
|
-
limit: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: true },
|
|
403
|
-
limitReached: { type: ScalarTypeEnum2.Boolean(), isOptional: false }
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
var UsageRecordedPayloadModel = defineSchemaModel2({
|
|
407
|
-
name: "UsageRecordedPayload",
|
|
408
|
-
description: "Payload for usage.recorded event",
|
|
409
|
-
fields: {
|
|
410
|
-
feature: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
411
|
-
quantity: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false }
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
var GetUsageSummaryInputModel = defineSchemaModel2({
|
|
415
|
-
name: "GetUsageSummaryInput",
|
|
416
|
-
description: "Input for getting usage summary",
|
|
417
|
-
fields: {
|
|
418
|
-
billingPeriod: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true }
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
var GetUsageSummaryOutputModel = defineSchemaModel2({
|
|
422
|
-
name: "GetUsageSummaryOutput",
|
|
423
|
-
description: "Output for usage summary",
|
|
424
|
-
fields: {
|
|
425
|
-
billingPeriod: {
|
|
426
|
-
type: ScalarTypeEnum2.String_unsecure(),
|
|
427
|
-
isOptional: false
|
|
428
|
-
},
|
|
429
|
-
usage: { type: UsageSummaryModel, isArray: true, isOptional: false }
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
var CheckFeatureAccessInputModel = defineSchemaModel2({
|
|
433
|
-
name: "CheckFeatureAccessInput",
|
|
434
|
-
description: "Input for checking feature access",
|
|
435
|
-
fields: {
|
|
436
|
-
feature: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false }
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
var CheckFeatureAccessOutputModel = defineSchemaModel2({
|
|
440
|
-
name: "CheckFeatureAccessOutput",
|
|
441
|
-
description: "Output for feature access check",
|
|
442
|
-
fields: {
|
|
443
|
-
hasAccess: { type: ScalarTypeEnum2.Boolean(), isOptional: false },
|
|
444
|
-
reason: { type: FeatureAccessReasonEnum, isOptional: true },
|
|
445
|
-
upgradeUrl: { type: ScalarTypeEnum2.URL(), isOptional: true }
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
// src/billing/billing.operations.ts
|
|
450
|
-
import { defineCommand, defineQuery } from "@contractspec/lib.contracts-spec";
|
|
451
|
-
var OWNERS = ["@example.saas-boilerplate"];
|
|
452
|
-
var GetSubscriptionContract = defineQuery({
|
|
453
|
-
meta: {
|
|
454
|
-
key: "saas.billing.subscription.get",
|
|
455
|
-
version: "1.0.0",
|
|
456
|
-
stability: "stable",
|
|
457
|
-
owners: [...OWNERS],
|
|
458
|
-
tags: ["saas", "billing", "subscription"],
|
|
459
|
-
description: "Get organization subscription status.",
|
|
460
|
-
goal: "Show current plan and billing status.",
|
|
461
|
-
context: "Billing page, plan upgrade prompts."
|
|
462
|
-
},
|
|
463
|
-
io: {
|
|
464
|
-
input: null,
|
|
465
|
-
output: SubscriptionModel
|
|
466
|
-
},
|
|
467
|
-
policy: {
|
|
468
|
-
auth: "user"
|
|
469
|
-
},
|
|
470
|
-
acceptance: {
|
|
471
|
-
scenarios: [
|
|
472
|
-
{
|
|
473
|
-
key: "get-subscription-happy-path",
|
|
474
|
-
given: ["Organization has active subscription"],
|
|
475
|
-
when: ["User requests subscription status"],
|
|
476
|
-
then: ["Subscription details are returned"]
|
|
477
|
-
}
|
|
478
|
-
],
|
|
479
|
-
examples: [
|
|
480
|
-
{
|
|
481
|
-
key: "get-basic",
|
|
482
|
-
input: null,
|
|
483
|
-
output: {
|
|
484
|
-
plan: "pro",
|
|
485
|
-
status: "active",
|
|
486
|
-
currentPeriodEnd: "2025-02-01T00:00:00Z"
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
]
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
var RecordUsageContract = defineCommand({
|
|
493
|
-
meta: {
|
|
494
|
-
key: "saas.billing.usage.record",
|
|
495
|
-
version: "1.0.0",
|
|
496
|
-
stability: "stable",
|
|
497
|
-
owners: [...OWNERS],
|
|
498
|
-
tags: ["saas", "billing", "usage"],
|
|
499
|
-
description: "Record usage of a metered feature.",
|
|
500
|
-
goal: "Track feature usage for billing.",
|
|
501
|
-
context: "Called by services when metered features are used."
|
|
502
|
-
},
|
|
503
|
-
io: {
|
|
504
|
-
input: RecordUsageInputModel,
|
|
505
|
-
output: RecordUsageOutputModel
|
|
506
|
-
},
|
|
507
|
-
policy: {
|
|
508
|
-
auth: "user"
|
|
509
|
-
},
|
|
510
|
-
sideEffects: {
|
|
511
|
-
emits: [
|
|
512
|
-
{
|
|
513
|
-
key: "billing.usage.recorded",
|
|
514
|
-
version: "1.0.0",
|
|
515
|
-
when: "Usage is recorded",
|
|
516
|
-
payload: UsageRecordedPayloadModel
|
|
517
|
-
}
|
|
518
|
-
]
|
|
519
|
-
},
|
|
520
|
-
acceptance: {
|
|
521
|
-
scenarios: [
|
|
522
|
-
{
|
|
523
|
-
key: "record-usage-happy-path",
|
|
524
|
-
given: ["Organization exists"],
|
|
525
|
-
when: ["System records feature usage"],
|
|
526
|
-
then: ["Usage is recorded"]
|
|
527
|
-
}
|
|
528
|
-
],
|
|
529
|
-
examples: [
|
|
530
|
-
{
|
|
531
|
-
key: "record-api-call",
|
|
532
|
-
input: { feature: "api_calls", quantity: 1, idempotencyKey: "abc-123" },
|
|
533
|
-
output: { recorded: true, currentUsage: 100 }
|
|
534
|
-
}
|
|
535
|
-
]
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
var GetUsageSummaryContract = defineQuery({
|
|
539
|
-
meta: {
|
|
540
|
-
key: "saas.billing.usage.summary",
|
|
541
|
-
version: "1.0.0",
|
|
542
|
-
stability: "stable",
|
|
543
|
-
owners: [...OWNERS],
|
|
544
|
-
tags: ["saas", "billing", "usage"],
|
|
545
|
-
description: "Get usage summary for the current billing period.",
|
|
546
|
-
goal: "Show usage vs limits.",
|
|
547
|
-
context: "Billing page, usage dashboards."
|
|
548
|
-
},
|
|
549
|
-
io: {
|
|
550
|
-
input: GetUsageSummaryInputModel,
|
|
551
|
-
output: GetUsageSummaryOutputModel
|
|
552
|
-
},
|
|
553
|
-
policy: {
|
|
554
|
-
auth: "user"
|
|
555
|
-
},
|
|
556
|
-
acceptance: {
|
|
557
|
-
scenarios: [
|
|
558
|
-
{
|
|
559
|
-
key: "get-usage-happy-path",
|
|
560
|
-
given: ["Organization has usage history"],
|
|
561
|
-
when: ["User requests usage summary"],
|
|
562
|
-
then: ["Usage metrics are returned"]
|
|
563
|
-
}
|
|
564
|
-
],
|
|
565
|
-
examples: [
|
|
566
|
-
{
|
|
567
|
-
key: "get-current-usage",
|
|
568
|
-
input: { period: "current" },
|
|
569
|
-
output: { features: [{ name: "api_calls", used: 100, limit: 1000 }] }
|
|
570
|
-
}
|
|
571
|
-
]
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
var CheckFeatureAccessContract = defineQuery({
|
|
575
|
-
meta: {
|
|
576
|
-
key: "saas.billing.feature.check",
|
|
577
|
-
version: "1.0.0",
|
|
578
|
-
stability: "stable",
|
|
579
|
-
owners: [...OWNERS],
|
|
580
|
-
tags: ["saas", "billing", "feature"],
|
|
581
|
-
description: "Check if organization has access to a feature.",
|
|
582
|
-
goal: "Gate features based on plan/usage.",
|
|
583
|
-
context: "Feature access checks, upgrade prompts."
|
|
584
|
-
},
|
|
585
|
-
io: {
|
|
586
|
-
input: CheckFeatureAccessInputModel,
|
|
587
|
-
output: CheckFeatureAccessOutputModel
|
|
588
|
-
},
|
|
589
|
-
policy: {
|
|
590
|
-
auth: "user"
|
|
591
|
-
},
|
|
592
|
-
acceptance: {
|
|
593
|
-
scenarios: [
|
|
594
|
-
{
|
|
595
|
-
key: "check-access-granted",
|
|
596
|
-
given: ["Organization is on Pro plan"],
|
|
597
|
-
when: ["User checks access to Pro feature"],
|
|
598
|
-
then: ["Access is granted"]
|
|
599
|
-
}
|
|
600
|
-
],
|
|
601
|
-
examples: [
|
|
602
|
-
{
|
|
603
|
-
key: "check-advanced-reports",
|
|
604
|
-
input: { feature: "advanced_reports" },
|
|
605
|
-
output: { hasAccess: true, reason: "Included in Pro plan" }
|
|
606
|
-
}
|
|
607
|
-
]
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
// src/billing/billing.presentation.ts
|
|
612
|
-
import {
|
|
613
|
-
definePresentation,
|
|
614
|
-
StabilityEnum
|
|
615
|
-
} from "@contractspec/lib.contracts-spec";
|
|
616
|
-
var SubscriptionPresentation = definePresentation({
|
|
617
|
-
meta: {
|
|
618
|
-
key: "saas.billing.subscription",
|
|
619
|
-
version: "1.0.0",
|
|
620
|
-
title: "Subscription Status",
|
|
621
|
-
description: "Subscription status with plan info, limits, and current usage",
|
|
622
|
-
domain: "saas-boilerplate",
|
|
623
|
-
owners: ["@saas-team"],
|
|
624
|
-
tags: ["billing", "subscription"],
|
|
625
|
-
stability: StabilityEnum.Beta,
|
|
626
|
-
goal: "View subscription plan and status",
|
|
627
|
-
context: "Billing section"
|
|
628
|
-
},
|
|
629
|
-
source: {
|
|
630
|
-
type: "component",
|
|
631
|
-
framework: "react",
|
|
632
|
-
componentKey: "SubscriptionView"
|
|
633
|
-
},
|
|
634
|
-
targets: ["react", "markdown"],
|
|
635
|
-
policy: {
|
|
636
|
-
flags: ["saas.billing.enabled"]
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
var UsageDashboardPresentation = definePresentation({
|
|
640
|
-
meta: {
|
|
641
|
-
key: "saas.billing.usage",
|
|
642
|
-
version: "1.0.0",
|
|
643
|
-
title: "Usage Dashboard",
|
|
644
|
-
description: "Usage metrics and breakdown by resource type",
|
|
645
|
-
domain: "saas-boilerplate",
|
|
646
|
-
owners: ["@saas-team"],
|
|
647
|
-
tags: ["billing", "usage", "metrics"],
|
|
648
|
-
stability: StabilityEnum.Beta,
|
|
649
|
-
goal: "Monitor feature usage and limits",
|
|
650
|
-
context: "Billing section"
|
|
651
|
-
},
|
|
652
|
-
source: {
|
|
653
|
-
type: "component",
|
|
654
|
-
framework: "react",
|
|
655
|
-
componentKey: "UsageDashboardView"
|
|
656
|
-
},
|
|
657
|
-
targets: ["react", "markdown"],
|
|
658
|
-
policy: {
|
|
659
|
-
flags: ["saas.billing.enabled"]
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
export {
|
|
663
|
-
mockRecordUsageHandler,
|
|
664
|
-
mockGetUsageSummaryHandler,
|
|
665
|
-
mockGetSubscriptionHandler,
|
|
666
|
-
mockCheckFeatureAccessHandler,
|
|
667
|
-
UsageSummaryModel,
|
|
668
|
-
UsageRecordedPayloadModel,
|
|
669
|
-
UsageRecordedEvent,
|
|
670
|
-
UsageLimitReachedEvent,
|
|
671
|
-
UsageLimitEntity,
|
|
672
|
-
UsageDashboardPresentation,
|
|
673
|
-
SubscriptionStatusSchemaEnum,
|
|
674
|
-
SubscriptionStatusEnum,
|
|
675
|
-
SubscriptionPresentation,
|
|
676
|
-
SubscriptionModel,
|
|
677
|
-
SubscriptionEntity,
|
|
678
|
-
SubscriptionChangedEvent,
|
|
679
|
-
RecordUsageOutputModel,
|
|
680
|
-
RecordUsageInputModel,
|
|
681
|
-
RecordUsageContract,
|
|
682
|
-
GetUsageSummaryOutputModel,
|
|
683
|
-
GetUsageSummaryInputModel,
|
|
684
|
-
GetUsageSummaryContract,
|
|
685
|
-
GetSubscriptionContract,
|
|
686
|
-
FeatureAccessReasonEnum,
|
|
687
|
-
CheckFeatureAccessOutputModel,
|
|
688
|
-
CheckFeatureAccessInputModel,
|
|
689
|
-
CheckFeatureAccessContract,
|
|
690
|
-
BillingUsageEntity
|
|
691
|
-
};
|
|
1
|
+
import{defineEntity as z,defineEntityEnum as K,field as g,index as w}from"@contractspec/lib.schema";var U=K({name:"SubscriptionStatus",values:["TRIALING","ACTIVE","PAST_DUE","CANCELED","PAUSED"],schema:"saas_app",description:"Status of a subscription."}),N=z({name:"Subscription",description:"Organization subscription/plan information.",schema:"saas_app",map:"subscription",fields:{id:g.id(),organizationId:g.foreignKey({isUnique:!0}),planId:g.string({description:"Plan identifier"}),planName:g.string({description:"Plan display name"}),status:g.enum("SubscriptionStatus"),currentPeriodStart:g.dateTime(),currentPeriodEnd:g.dateTime(),trialEndsAt:g.dateTime({isOptional:!0}),cancelAtPeriodEnd:g.boolean({default:!1}),canceledAt:g.dateTime({isOptional:!0}),stripeSubscriptionId:g.string({isOptional:!0}),stripeCustomerId:g.string({isOptional:!0}),metadata:g.json({isOptional:!0}),createdAt:g.createdAt(),updatedAt:g.updatedAt()},enums:[U]}),t=z({name:"BillingUsage",description:"Track usage of metered features.",schema:"saas_app",map:"billing_usage",fields:{id:g.id(),organizationId:g.foreignKey(),feature:g.string({description:'Feature being tracked (e.g., "api_calls", "storage_gb")'}),quantity:g.int({description:"Usage quantity"}),unit:g.string({isOptional:!0,description:"Unit of measurement"}),billingPeriod:g.string({description:'Billing period (e.g., "2024-01")'}),recordedAt:g.dateTime({description:"When usage was recorded"}),sourceId:g.string({isOptional:!0,description:"Source of usage (e.g., request ID)"}),sourceType:g.string({isOptional:!0}),metadata:g.json({isOptional:!0})},indexes:[w.on(["organizationId","feature","billingPeriod"]),w.on(["organizationId","recordedAt"])]}),R=z({name:"UsageLimit",description:"Usage limits per plan/organization.",schema:"saas_app",map:"usage_limit",fields:{id:g.id(),planId:g.string({isOptional:!0,description:"Plan this limit applies to"}),organizationId:g.string({isOptional:!0,description:"Org-specific override"}),feature:g.string({description:"Feature being limited"}),limit:g.int({description:"Maximum allowed usage"}),resetPeriod:g.string({default:'"monthly"',description:"When limit resets"}),isSoftLimit:g.boolean({default:!1,description:"Whether to warn vs block"}),overage:g.boolean({default:!1,description:"Whether overage is allowed"}),overageRate:g.float({isOptional:!0,description:"Cost per unit over limit"}),createdAt:g.createdAt(),updatedAt:g.updatedAt()},indexes:[w.unique(["planId","feature"])]});import{defineEnum as W}from"@contractspec/lib.schema";var D=W("SubscriptionStatus",["TRIALING","ACTIVE","PAST_DUE","CANCELED","PAUSED"]),G=W("FeatureAccessReason",["included","limit_available","limit_reached","not_in_plan"]);import{defineEvent as J}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as V,ScalarTypeEnum as x}from"@contractspec/lib.schema";var _=V({name:"UsageRecordedPayload",description:"Payload when feature usage is recorded",fields:{organizationId:{type:x.String_unsecure(),isOptional:!1},feature:{type:x.String_unsecure(),isOptional:!1},quantity:{type:x.Int_unsecure(),isOptional:!1},billingPeriod:{type:x.String_unsecure(),isOptional:!1},recordedAt:{type:x.DateTime(),isOptional:!1}}}),O=V({name:"UsageLimitReachedPayload",description:"Payload when usage limit is reached",fields:{organizationId:{type:x.String_unsecure(),isOptional:!1},feature:{type:x.String_unsecure(),isOptional:!1},limit:{type:x.Int_unsecure(),isOptional:!1},currentUsage:{type:x.Int_unsecure(),isOptional:!1},reachedAt:{type:x.DateTime(),isOptional:!1}}}),h=V({name:"SubscriptionChangedPayload",description:"Payload when subscription status changes",fields:{organizationId:{type:x.String_unsecure(),isOptional:!1},previousPlan:{type:x.String_unsecure(),isOptional:!0},newPlan:{type:x.String_unsecure(),isOptional:!1},previousStatus:{type:x.String_unsecure(),isOptional:!0},newStatus:{type:x.String_unsecure(),isOptional:!1},changedAt:{type:x.DateTime(),isOptional:!1}}}),M=J({meta:{key:"billing.usage.recorded",version:"1.0.0",description:"Feature usage has been recorded.",stability:"stable",owners:["@saas-team"],tags:["billing","usage","recorded"]},payload:_}),r=J({meta:{key:"billing.limit.reached",version:"1.0.0",description:"Usage limit has been reached for a feature.",stability:"stable",owners:["@saas-team"],tags:["billing","limit","reached"]},payload:O}),p=J({meta:{key:"billing.subscription.changed",version:"1.0.0",description:"Subscription status has changed.",stability:"stable",owners:["@saas-team"],tags:["billing","subscription","changed"]},payload:h});var Fg=[{id:"proj-1",name:"Marketing Website",description:"Main company website redesign project",slug:"marketing-website",organizationId:"demo-org",createdBy:"user-1",status:"ACTIVE",isPublic:!1,tags:["marketing","website","redesign"],createdAt:new Date("2024-01-15T10:00:00Z"),updatedAt:new Date("2024-03-20T14:30:00Z")},{id:"proj-2",name:"Mobile App v2",description:"Next generation mobile application",slug:"mobile-app-v2",organizationId:"demo-org",createdBy:"user-2",status:"ACTIVE",isPublic:!1,tags:["mobile","app","v2"],createdAt:new Date("2024-02-01T09:00:00Z"),updatedAt:new Date("2024-04-05T11:15:00Z")},{id:"proj-3",name:"API Integration",description:"Third-party API integration project",slug:"api-integration",organizationId:"demo-org",createdBy:"user-1",status:"DRAFT",isPublic:!1,tags:["api","integration"],createdAt:new Date("2024-03-10T08:00:00Z"),updatedAt:new Date("2024-03-10T08:00:00Z")},{id:"proj-4",name:"Analytics Dashboard",description:"Internal analytics and reporting dashboard",slug:"analytics-dashboard",organizationId:"demo-org",createdBy:"user-3",status:"ARCHIVED",isPublic:!0,tags:["analytics","dashboard","reporting"],createdAt:new Date("2023-10-01T12:00:00Z"),updatedAt:new Date("2024-02-28T16:45:00Z")}],j={id:"sub-1",organizationId:"demo-org",planId:"pro",planName:"Professional",status:"ACTIVE",currentPeriodStart:new Date("2024-04-01T00:00:00Z"),currentPeriodEnd:new Date("2024-05-01T00:00:00Z"),limits:{projects:25,users:10,storage:50,apiCalls:1e5},usage:{projects:4,users:5,storage:12.5,apiCalls:45230}},F={organizationId:"demo-org",period:"current_month",apiCalls:{total:45230,limit:1e5,percentUsed:45.23},storage:{totalGb:12.5,limitGb:50,percentUsed:25},activeProjects:4,activeUsers:5,breakdown:[{date:"2024-04-01",apiCalls:3200,storageGb:12.1},{date:"2024-04-02",apiCalls:2800,storageGb:12.2},{date:"2024-04-03",apiCalls:4100,storageGb:12.3},{date:"2024-04-04",apiCalls:3600,storageGb:12.4},{date:"2024-04-05",apiCalls:3800,storageGb:12.5}]};async function y(){return j}async function T(q){return{...F,period:q.period??"current_month"}}async function u(q){return{recorded:!0,newTotal:F.apiCalls.total+q.quantity}}async function E(q){let{feature:I}=q;return{custom_domains:{allowed:!0},api_access:{allowed:!0,currentUsage:F.apiCalls.total,limit:F.apiCalls.limit},advanced_analytics:{allowed:!1,reason:"FEATURE_NOT_INCLUDED"},unlimited_projects:{allowed:!1,reason:"PLAN_LIMIT",currentUsage:j.usage.projects,limit:j.limits.projects}}[I]??{allowed:!0}}import{defineSchemaModel as H,ScalarTypeEnum as k}from"@contractspec/lib.schema";var X=H({name:"Subscription",description:"Organization subscription details",fields:{id:{type:k.String_unsecure(),isOptional:!1},organizationId:{type:k.String_unsecure(),isOptional:!1},planId:{type:k.String_unsecure(),isOptional:!1},planName:{type:k.String_unsecure(),isOptional:!1},status:{type:D,isOptional:!1},currentPeriodStart:{type:k.DateTime(),isOptional:!1},currentPeriodEnd:{type:k.DateTime(),isOptional:!1},trialEndsAt:{type:k.DateTime(),isOptional:!0},cancelAtPeriodEnd:{type:k.Boolean(),isOptional:!1}}}),Y=H({name:"UsageSummary",description:"Usage summary for a feature",fields:{feature:{type:k.String_unsecure(),isOptional:!1},used:{type:k.Int_unsecure(),isOptional:!1},limit:{type:k.Int_unsecure(),isOptional:!0},unit:{type:k.String_unsecure(),isOptional:!0},percentage:{type:k.Float_unsecure(),isOptional:!0}}}),Z=H({name:"RecordUsageInput",description:"Input for recording feature usage",fields:{feature:{type:k.String_unsecure(),isOptional:!1},quantity:{type:k.Int_unsecure(),isOptional:!1},sourceId:{type:k.String_unsecure(),isOptional:!0},sourceType:{type:k.String_unsecure(),isOptional:!0},metadata:{type:k.JSONObject(),isOptional:!0}}}),$=H({name:"RecordUsageOutput",description:"Output for recording feature usage",fields:{recorded:{type:k.Boolean(),isOptional:!1},currentUsage:{type:k.Int_unsecure(),isOptional:!1},limit:{type:k.Int_unsecure(),isOptional:!0},limitReached:{type:k.Boolean(),isOptional:!1}}}),B=H({name:"UsageRecordedPayload",description:"Payload for usage.recorded event",fields:{feature:{type:k.String_unsecure(),isOptional:!1},quantity:{type:k.Int_unsecure(),isOptional:!1}}}),L=H({name:"GetUsageSummaryInput",description:"Input for getting usage summary",fields:{billingPeriod:{type:k.String_unsecure(),isOptional:!0}}}),o=H({name:"GetUsageSummaryOutput",description:"Output for usage summary",fields:{billingPeriod:{type:k.String_unsecure(),isOptional:!1},usage:{type:Y,isArray:!0,isOptional:!1}}}),s=H({name:"CheckFeatureAccessInput",description:"Input for checking feature access",fields:{feature:{type:k.String_unsecure(),isOptional:!1}}}),Q=H({name:"CheckFeatureAccessOutput",description:"Output for feature access check",fields:{hasAccess:{type:k.Boolean(),isOptional:!1},reason:{type:G,isOptional:!0},upgradeUrl:{type:k.URL(),isOptional:!0}}});import{defineCommand as f,defineQuery as b}from"@contractspec/lib.contracts-spec";var v=["@example.saas-boilerplate"],m=b({meta:{key:"saas.billing.subscription.get",version:"1.0.0",stability:"stable",owners:[...v],tags:["saas","billing","subscription"],description:"Get organization subscription status.",goal:"Show current plan and billing status.",context:"Billing page, plan upgrade prompts."},io:{input:null,output:X},policy:{auth:"user"},acceptance:{scenarios:[{key:"get-subscription-happy-path",given:["Organization has active subscription"],when:["User requests subscription status"],then:["Subscription details are returned"]}],examples:[{key:"get-basic",input:null,output:{plan:"pro",status:"active",currentPeriodEnd:"2025-02-01T00:00:00Z"}}]}}),S=f({meta:{key:"saas.billing.usage.record",version:"1.0.0",stability:"stable",owners:[...v],tags:["saas","billing","usage"],description:"Record usage of a metered feature.",goal:"Track feature usage for billing.",context:"Called by services when metered features are used."},io:{input:Z,output:$},policy:{auth:"user"},sideEffects:{emits:[{key:"billing.usage.recorded",version:"1.0.0",when:"Usage is recorded",payload:B}]},acceptance:{scenarios:[{key:"record-usage-happy-path",given:["Organization exists"],when:["System records feature usage"],then:["Usage is recorded"]}],examples:[{key:"record-api-call",input:{feature:"api_calls",quantity:1,idempotencyKey:"abc-123"},output:{recorded:!0,currentUsage:100}}]}}),c=b({meta:{key:"saas.billing.usage.summary",version:"1.0.0",stability:"stable",owners:[...v],tags:["saas","billing","usage"],description:"Get usage summary for the current billing period.",goal:"Show usage vs limits.",context:"Billing page, usage dashboards."},io:{input:L,output:o},policy:{auth:"user"},acceptance:{scenarios:[{key:"get-usage-happy-path",given:["Organization has usage history"],when:["User requests usage summary"],then:["Usage metrics are returned"]}],examples:[{key:"get-current-usage",input:{period:"current"},output:{features:[{name:"api_calls",used:100,limit:1000}]}}]}}),d=b({meta:{key:"saas.billing.feature.check",version:"1.0.0",stability:"stable",owners:[...v],tags:["saas","billing","feature"],description:"Check if organization has access to a feature.",goal:"Gate features based on plan/usage.",context:"Feature access checks, upgrade prompts."},io:{input:s,output:Q},policy:{auth:"user"},acceptance:{scenarios:[{key:"check-access-granted",given:["Organization is on Pro plan"],when:["User checks access to Pro feature"],then:["Access is granted"]}],examples:[{key:"check-advanced-reports",input:{feature:"advanced_reports"},output:{hasAccess:!0,reason:"Included in Pro plan"}}]}});import{definePresentation as A,StabilityEnum as C}from"@contractspec/lib.contracts-spec";var i=A({meta:{key:"saas.billing.subscription",version:"1.0.0",title:"Subscription Status",description:"Subscription status with plan info, limits, and current usage",domain:"saas-boilerplate",owners:["@saas-team"],tags:["billing","subscription"],stability:C.Beta,goal:"View subscription plan and status",context:"Billing section"},source:{type:"component",framework:"react",componentKey:"SubscriptionView"},targets:["react","markdown"],policy:{flags:["saas.billing.enabled"]}}),n=A({meta:{key:"saas.billing.usage",version:"1.0.0",title:"Usage Dashboard",description:"Usage metrics and breakdown by resource type",domain:"saas-boilerplate",owners:["@saas-team"],tags:["billing","usage","metrics"],stability:C.Beta,goal:"Monitor feature usage and limits",context:"Billing section"},source:{type:"component",framework:"react",componentKey:"UsageDashboardView"},targets:["react","markdown"],policy:{flags:["saas.billing.enabled"]}});export{u as mockRecordUsageHandler,T as mockGetUsageSummaryHandler,y as mockGetSubscriptionHandler,E as mockCheckFeatureAccessHandler,Y as UsageSummaryModel,B as UsageRecordedPayloadModel,M as UsageRecordedEvent,r as UsageLimitReachedEvent,R as UsageLimitEntity,n as UsageDashboardPresentation,D as SubscriptionStatusSchemaEnum,U as SubscriptionStatusEnum,i as SubscriptionPresentation,X as SubscriptionModel,N as SubscriptionEntity,p as SubscriptionChangedEvent,$ as RecordUsageOutputModel,Z as RecordUsageInputModel,S as RecordUsageContract,o as GetUsageSummaryOutputModel,L as GetUsageSummaryInputModel,c as GetUsageSummaryContract,m as GetSubscriptionContract,G as FeatureAccessReasonEnum,Q as CheckFeatureAccessOutputModel,s as CheckFeatureAccessInputModel,d as CheckFeatureAccessContract,t as BillingUsageEntity};
|