@contractspec/example.saas-boilerplate 0.0.0-canary-20260113170453

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 (226) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +188 -0
  2. package/.turbo/turbo-build.log +189 -0
  3. package/CHANGELOG.md +440 -0
  4. package/LICENSE +21 -0
  5. package/README.md +155 -0
  6. package/dist/billing/billing.entity.d.ts +61 -0
  7. package/dist/billing/billing.entity.d.ts.map +1 -0
  8. package/dist/billing/billing.entity.js +122 -0
  9. package/dist/billing/billing.entity.js.map +1 -0
  10. package/dist/billing/billing.enum.d.ts +16 -0
  11. package/dist/billing/billing.enum.d.ts.map +1 -0
  12. package/dist/billing/billing.enum.js +27 -0
  13. package/dist/billing/billing.enum.js.map +1 -0
  14. package/dist/billing/billing.event.d.ts +86 -0
  15. package/dist/billing/billing.event.d.ts.map +1 -0
  16. package/dist/billing/billing.event.js +153 -0
  17. package/dist/billing/billing.event.js.map +1 -0
  18. package/dist/billing/billing.handler.d.ts +82 -0
  19. package/dist/billing/billing.handler.d.ts.map +1 -0
  20. package/dist/billing/billing.handler.js +58 -0
  21. package/dist/billing/billing.handler.js.map +1 -0
  22. package/dist/billing/billing.operations.d.ts +166 -0
  23. package/dist/billing/billing.operations.d.ts.map +1 -0
  24. package/dist/billing/billing.operations.js +181 -0
  25. package/dist/billing/billing.operations.js.map +1 -0
  26. package/dist/billing/billing.presentation.d.ts +14 -0
  27. package/dist/billing/billing.presentation.d.ts.map +1 -0
  28. package/dist/billing/billing.presentation.js +59 -0
  29. package/dist/billing/billing.presentation.js.map +1 -0
  30. package/dist/billing/billing.schema.d.ts +201 -0
  31. package/dist/billing/billing.schema.d.ts.map +1 -0
  32. package/dist/billing/billing.schema.js +214 -0
  33. package/dist/billing/billing.schema.js.map +1 -0
  34. package/dist/billing/index.d.ts +8 -0
  35. package/dist/billing/index.js +9 -0
  36. package/dist/dashboard/dashboard.presentation.d.ts +14 -0
  37. package/dist/dashboard/dashboard.presentation.d.ts.map +1 -0
  38. package/dist/dashboard/dashboard.presentation.js +55 -0
  39. package/dist/dashboard/dashboard.presentation.js.map +1 -0
  40. package/dist/dashboard/index.d.ts +2 -0
  41. package/dist/dashboard/index.js +3 -0
  42. package/dist/docs/index.d.ts +1 -0
  43. package/dist/docs/index.js +1 -0
  44. package/dist/docs/saas-boilerplate.docblock.d.ts +1 -0
  45. package/dist/docs/saas-boilerplate.docblock.js +100 -0
  46. package/dist/docs/saas-boilerplate.docblock.js.map +1 -0
  47. package/dist/example.d.ts +7 -0
  48. package/dist/example.d.ts.map +1 -0
  49. package/dist/example.js +53 -0
  50. package/dist/example.js.map +1 -0
  51. package/dist/handlers/index.d.ts +4 -0
  52. package/dist/handlers/index.js +5 -0
  53. package/dist/handlers/saas.handlers.d.ts +68 -0
  54. package/dist/handlers/saas.handlers.d.ts.map +1 -0
  55. package/dist/handlers/saas.handlers.js +148 -0
  56. package/dist/handlers/saas.handlers.js.map +1 -0
  57. package/dist/index.d.ts +54 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +81 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/presentations/index.d.ts +17 -0
  62. package/dist/presentations/index.d.ts.map +1 -0
  63. package/dist/presentations/index.js +17 -0
  64. package/dist/presentations/index.js.map +1 -0
  65. package/dist/project/index.d.ts +8 -0
  66. package/dist/project/index.js +9 -0
  67. package/dist/project/project.entity.d.ts +40 -0
  68. package/dist/project/project.entity.d.ts.map +1 -0
  69. package/dist/project/project.entity.js +85 -0
  70. package/dist/project/project.entity.js.map +1 -0
  71. package/dist/project/project.enum.d.ts +16 -0
  72. package/dist/project/project.enum.d.ts.map +1 -0
  73. package/dist/project/project.enum.js +26 -0
  74. package/dist/project/project.enum.js.map +1 -0
  75. package/dist/project/project.event.d.ts +92 -0
  76. package/dist/project/project.event.d.ts.map +1 -0
  77. package/dist/project/project.event.js +165 -0
  78. package/dist/project/project.event.js.map +1 -0
  79. package/dist/project/project.handler.d.ts +72 -0
  80. package/dist/project/project.handler.d.ts.map +1 -0
  81. package/dist/project/project.handler.js +82 -0
  82. package/dist/project/project.handler.js.map +1 -0
  83. package/dist/project/project.operations.d.ts +419 -0
  84. package/dist/project/project.operations.d.ts.map +1 -0
  85. package/dist/project/project.operations.js +260 -0
  86. package/dist/project/project.operations.js.map +1 -0
  87. package/dist/project/project.presentation.d.ts +14 -0
  88. package/dist/project/project.presentation.d.ts.map +1 -0
  89. package/dist/project/project.presentation.js +65 -0
  90. package/dist/project/project.presentation.js.map +1 -0
  91. package/dist/project/project.schema.d.ts +235 -0
  92. package/dist/project/project.schema.d.ts.map +1 -0
  93. package/dist/project/project.schema.js +215 -0
  94. package/dist/project/project.schema.js.map +1 -0
  95. package/dist/saas-boilerplate.feature.d.ts +12 -0
  96. package/dist/saas-boilerplate.feature.d.ts.map +1 -0
  97. package/dist/saas-boilerplate.feature.js +208 -0
  98. package/dist/saas-boilerplate.feature.js.map +1 -0
  99. package/dist/seeders/index.d.ts +10 -0
  100. package/dist/seeders/index.d.ts.map +1 -0
  101. package/dist/seeders/index.js +19 -0
  102. package/dist/seeders/index.js.map +1 -0
  103. package/dist/settings/index.d.ts +3 -0
  104. package/dist/settings/index.js +4 -0
  105. package/dist/settings/settings.entity.d.ts +37 -0
  106. package/dist/settings/settings.entity.d.ts.map +1 -0
  107. package/dist/settings/settings.entity.js +78 -0
  108. package/dist/settings/settings.entity.js.map +1 -0
  109. package/dist/settings/settings.enum.d.ts +10 -0
  110. package/dist/settings/settings.enum.d.ts.map +1 -0
  111. package/dist/settings/settings.enum.js +21 -0
  112. package/dist/settings/settings.enum.js.map +1 -0
  113. package/dist/shared/mock-data.d.ts +86 -0
  114. package/dist/shared/mock-data.d.ts.map +1 -0
  115. package/dist/shared/mock-data.js +138 -0
  116. package/dist/shared/mock-data.js.map +1 -0
  117. package/dist/shared/overlay-types.d.ts +34 -0
  118. package/dist/shared/overlay-types.d.ts.map +1 -0
  119. package/dist/shared/overlay-types.js +0 -0
  120. package/dist/tests/operations.test-spec.d.ts +10 -0
  121. package/dist/tests/operations.test-spec.d.ts.map +1 -0
  122. package/dist/tests/operations.test-spec.js +123 -0
  123. package/dist/tests/operations.test-spec.js.map +1 -0
  124. package/dist/ui/SaasDashboard.d.ts +7 -0
  125. package/dist/ui/SaasDashboard.d.ts.map +1 -0
  126. package/dist/ui/SaasDashboard.js +298 -0
  127. package/dist/ui/SaasDashboard.js.map +1 -0
  128. package/dist/ui/SaasProjectList.d.ts +14 -0
  129. package/dist/ui/SaasProjectList.d.ts.map +1 -0
  130. package/dist/ui/SaasProjectList.js +76 -0
  131. package/dist/ui/SaasProjectList.js.map +1 -0
  132. package/dist/ui/SaasSettingsPanel.d.ts +7 -0
  133. package/dist/ui/SaasSettingsPanel.d.ts.map +1 -0
  134. package/dist/ui/SaasSettingsPanel.js +138 -0
  135. package/dist/ui/SaasSettingsPanel.js.map +1 -0
  136. package/dist/ui/hooks/index.d.ts +3 -0
  137. package/dist/ui/hooks/index.js +6 -0
  138. package/dist/ui/hooks/useProjectList.d.ts +34 -0
  139. package/dist/ui/hooks/useProjectList.d.ts.map +1 -0
  140. package/dist/ui/hooks/useProjectList.js +75 -0
  141. package/dist/ui/hooks/useProjectList.js.map +1 -0
  142. package/dist/ui/hooks/useProjectMutations.d.ts +28 -0
  143. package/dist/ui/hooks/useProjectMutations.d.ts.map +1 -0
  144. package/dist/ui/hooks/useProjectMutations.js +146 -0
  145. package/dist/ui/hooks/useProjectMutations.js.map +1 -0
  146. package/dist/ui/index.d.ts +14 -0
  147. package/dist/ui/index.js +15 -0
  148. package/dist/ui/modals/CreateProjectModal.d.ts +23 -0
  149. package/dist/ui/modals/CreateProjectModal.d.ts.map +1 -0
  150. package/dist/ui/modals/CreateProjectModal.js +139 -0
  151. package/dist/ui/modals/CreateProjectModal.js.map +1 -0
  152. package/dist/ui/modals/ProjectActionsModal.d.ts +38 -0
  153. package/dist/ui/modals/ProjectActionsModal.d.ts.map +1 -0
  154. package/dist/ui/modals/ProjectActionsModal.js +292 -0
  155. package/dist/ui/modals/ProjectActionsModal.js.map +1 -0
  156. package/dist/ui/modals/index.d.ts +3 -0
  157. package/dist/ui/modals/index.js +4 -0
  158. package/dist/ui/overlays/demo-overlays.d.ts +19 -0
  159. package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
  160. package/dist/ui/overlays/demo-overlays.js +70 -0
  161. package/dist/ui/overlays/demo-overlays.js.map +1 -0
  162. package/dist/ui/overlays/index.d.ts +2 -0
  163. package/dist/ui/overlays/index.js +3 -0
  164. package/dist/ui/renderers/index.d.ts +3 -0
  165. package/dist/ui/renderers/index.js +4 -0
  166. package/dist/ui/renderers/project-list.markdown.d.ts +31 -0
  167. package/dist/ui/renderers/project-list.markdown.d.ts.map +1 -0
  168. package/dist/ui/renderers/project-list.markdown.js +148 -0
  169. package/dist/ui/renderers/project-list.markdown.js.map +1 -0
  170. package/dist/ui/renderers/project-list.renderer.d.ts +9 -0
  171. package/dist/ui/renderers/project-list.renderer.d.ts.map +1 -0
  172. package/dist/ui/renderers/project-list.renderer.js +17 -0
  173. package/dist/ui/renderers/project-list.renderer.js.map +1 -0
  174. package/example.ts +1 -0
  175. package/package.json +135 -0
  176. package/src/billing/billing.entity.ts +158 -0
  177. package/src/billing/billing.enum.ts +23 -0
  178. package/src/billing/billing.event.ts +108 -0
  179. package/src/billing/billing.handler.ts +137 -0
  180. package/src/billing/billing.operations.ts +187 -0
  181. package/src/billing/billing.presentation.ts +56 -0
  182. package/src/billing/billing.schema.ts +133 -0
  183. package/src/billing/index.ts +64 -0
  184. package/src/dashboard/dashboard.presentation.ts +56 -0
  185. package/src/dashboard/index.ts +8 -0
  186. package/src/docs/index.ts +1 -0
  187. package/src/docs/saas-boilerplate.docblock.ts +98 -0
  188. package/src/example.ts +38 -0
  189. package/src/handlers/index.ts +23 -0
  190. package/src/handlers/saas.handlers.ts +300 -0
  191. package/src/index.ts +76 -0
  192. package/src/presentations/index.ts +36 -0
  193. package/src/project/index.ts +66 -0
  194. package/src/project/project.entity.ts +93 -0
  195. package/src/project/project.enum.ts +22 -0
  196. package/src/project/project.event.ts +128 -0
  197. package/src/project/project.handler.ts +168 -0
  198. package/src/project/project.operations.ts +272 -0
  199. package/src/project/project.presentation.ts +58 -0
  200. package/src/project/project.schema.ts +147 -0
  201. package/src/saas-boilerplate.feature.ts +113 -0
  202. package/src/seeders/index.ts +28 -0
  203. package/src/settings/index.ts +9 -0
  204. package/src/settings/settings.entity.ts +89 -0
  205. package/src/settings/settings.enum.ts +11 -0
  206. package/src/shared/mock-data.ts +110 -0
  207. package/src/shared/overlay-types.ts +39 -0
  208. package/src/tests/operations.test-spec.ts +109 -0
  209. package/src/ui/SaasDashboard.tsx +325 -0
  210. package/src/ui/SaasProjectList.tsx +113 -0
  211. package/src/ui/SaasSettingsPanel.tsx +96 -0
  212. package/src/ui/hooks/index.ts +10 -0
  213. package/src/ui/hooks/useProjectList.ts +95 -0
  214. package/src/ui/hooks/useProjectMutations.ts +166 -0
  215. package/src/ui/index.ts +18 -0
  216. package/src/ui/modals/CreateProjectModal.tsx +176 -0
  217. package/src/ui/modals/ProjectActionsModal.tsx +346 -0
  218. package/src/ui/modals/index.ts +2 -0
  219. package/src/ui/overlays/demo-overlays.ts +74 -0
  220. package/src/ui/overlays/index.ts +1 -0
  221. package/src/ui/renderers/index.ts +7 -0
  222. package/src/ui/renderers/project-list.markdown.ts +239 -0
  223. package/src/ui/renderers/project-list.renderer.tsx +22 -0
  224. package/tsconfig.json +10 -0
  225. package/tsconfig.tsbuildinfo +1 -0
  226. package/tsdown.config.js +7 -0
@@ -0,0 +1,31 @@
1
+ import { PresentationRenderer } from "@contractspec/lib.contracts";
2
+
3
+ //#region src/ui/renderers/project-list.markdown.d.ts
4
+
5
+ /**
6
+ * Markdown renderer for saas-boilerplate.project.list presentation
7
+ * Only handles ProjectListView component
8
+ */
9
+ declare const projectListMarkdownRenderer: PresentationRenderer<{
10
+ mimeType: string;
11
+ body: string;
12
+ }>;
13
+ /**
14
+ * Markdown renderer for saas-boilerplate.dashboard presentation
15
+ * Only handles SaasDashboard component
16
+ */
17
+ declare const saasDashboardMarkdownRenderer: PresentationRenderer<{
18
+ mimeType: string;
19
+ body: string;
20
+ }>;
21
+ /**
22
+ * Markdown renderer for saas-boilerplate.billing.settings presentation
23
+ * Only handles SubscriptionView component
24
+ */
25
+ declare const saasBillingMarkdownRenderer: PresentationRenderer<{
26
+ mimeType: string;
27
+ body: string;
28
+ }>;
29
+ //#endregion
30
+ export { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer };
31
+ //# sourceMappingURL=project-list.markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-list.markdown.d.ts","names":[],"sources":["../../../src/ui/renderers/project-list.markdown.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAsBa,6BAA6B;;;;;;;;cA6D7B,+BAA+B;;;;;;;;cA0F/B,6BAA6B"}
@@ -0,0 +1,148 @@
1
+ import { mockGetSubscriptionHandler, mockListProjectsHandler } from "@contractspec/example.saas-boilerplate/handlers";
2
+
3
+ //#region src/ui/renderers/project-list.markdown.ts
4
+ /**
5
+ * Markdown renderer for saas-boilerplate.project.list presentation
6
+ * Only handles ProjectListView component
7
+ */
8
+ const projectListMarkdownRenderer = {
9
+ target: "markdown",
10
+ render: async (desc, _ctx) => {
11
+ if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") throw new Error("projectListMarkdownRenderer: not ProjectListView");
12
+ const data = await mockListProjectsHandler({
13
+ limit: 20,
14
+ offset: 0
15
+ });
16
+ const items = data.projects ?? data.items ?? [];
17
+ const lines = [
18
+ "# Projects",
19
+ "",
20
+ `**Total**: ${data.total} projects`,
21
+ ""
22
+ ];
23
+ if (items.length === 0) lines.push("_No projects found._");
24
+ else {
25
+ lines.push("| Status | Project | Description |");
26
+ lines.push("|--------|---------|-------------|");
27
+ for (const project of items) {
28
+ const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "📦" : "⏸️";
29
+ lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
30
+ }
31
+ }
32
+ return {
33
+ mimeType: "text/markdown",
34
+ body: lines.join("\n")
35
+ };
36
+ }
37
+ };
38
+ /**
39
+ * Markdown renderer for saas-boilerplate.dashboard presentation
40
+ * Only handles SaasDashboard component
41
+ */
42
+ const saasDashboardMarkdownRenderer = {
43
+ target: "markdown",
44
+ render: async (desc, _ctx) => {
45
+ if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
46
+ const [projectsData, subscription] = await Promise.all([mockListProjectsHandler({ limit: 50 }), mockGetSubscriptionHandler()]);
47
+ const projects = projectsData.projects ?? [];
48
+ const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
49
+ const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
50
+ const lines = [
51
+ "# SaaS Dashboard",
52
+ "",
53
+ "> Organization overview and usage summary",
54
+ "",
55
+ "## Summary",
56
+ "",
57
+ "| Metric | Value |",
58
+ "|--------|-------|",
59
+ `| Total Projects | ${projectsData.total} |`,
60
+ `| Active Projects | ${activeProjects} |`,
61
+ `| Archived Projects | ${archivedProjects} |`,
62
+ `| Subscription Plan | ${subscription.planName} |`,
63
+ `| Subscription Status | ${subscription.status} |`,
64
+ "",
65
+ "## Projects",
66
+ ""
67
+ ];
68
+ if (projects.length === 0) lines.push("_No projects yet._");
69
+ else {
70
+ lines.push("| Status | Project | Description |");
71
+ lines.push("|--------|---------|-------------|");
72
+ for (const project of projects.slice(0, 10)) {
73
+ const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "📦" : "⏸️";
74
+ lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
75
+ }
76
+ if (projects.length > 10) lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
77
+ }
78
+ lines.push("");
79
+ lines.push("## Subscription");
80
+ lines.push("");
81
+ lines.push(`- **Plan**: ${subscription.planName}`);
82
+ lines.push(`- **Status**: ${subscription.status}`);
83
+ if (subscription.currentPeriodEnd) lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
84
+ return {
85
+ mimeType: "text/markdown",
86
+ body: lines.join("\n")
87
+ };
88
+ }
89
+ };
90
+ /**
91
+ * Markdown renderer for saas-boilerplate.billing.settings presentation
92
+ * Only handles SubscriptionView component
93
+ */
94
+ const saasBillingMarkdownRenderer = {
95
+ target: "markdown",
96
+ render: async (desc, _ctx) => {
97
+ if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
98
+ const subscription = await mockGetSubscriptionHandler();
99
+ const lines = [
100
+ "# Billing & Subscription",
101
+ "",
102
+ "> Current subscription details and billing information",
103
+ "",
104
+ "## Subscription Details",
105
+ "",
106
+ "| Property | Value |",
107
+ "|----------|-------|",
108
+ `| Plan | ${subscription.planName} |`,
109
+ `| Status | ${subscription.status} |`,
110
+ `| ID | ${subscription.id} |`,
111
+ `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
112
+ `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
113
+ ];
114
+ lines.push("");
115
+ lines.push("## Plan Limits");
116
+ lines.push("");
117
+ lines.push(`- **Projects**: ${subscription.limits.projects}`);
118
+ lines.push(`- **Users**: ${subscription.limits.users}`);
119
+ lines.push("");
120
+ lines.push("## Plan Features");
121
+ lines.push("");
122
+ if (subscription.planName.toLowerCase().includes("free")) {
123
+ lines.push("- ✅ Up to 3 projects");
124
+ lines.push("- ✅ Basic support");
125
+ lines.push("- ❌ Priority support");
126
+ lines.push("- ❌ Advanced analytics");
127
+ } else if (subscription.planName.toLowerCase().includes("pro")) {
128
+ lines.push("- ✅ Unlimited projects");
129
+ lines.push("- ✅ Priority support");
130
+ lines.push("- ✅ Advanced analytics");
131
+ lines.push("- ❌ Custom integrations");
132
+ } else {
133
+ lines.push("- ✅ Unlimited projects");
134
+ lines.push("- ✅ Priority support");
135
+ lines.push("- ✅ Advanced analytics");
136
+ lines.push("- ✅ Custom integrations");
137
+ lines.push("- ✅ Dedicated support");
138
+ }
139
+ return {
140
+ mimeType: "text/markdown",
141
+ body: lines.join("\n")
142
+ };
143
+ }
144
+ };
145
+
146
+ //#endregion
147
+ export { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer };
148
+ //# sourceMappingURL=project-list.markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-list.markdown.js","names":[],"sources":["../../../src/ui/renderers/project-list.markdown.ts"],"sourcesContent":["/**\n * Markdown renderer for SaaS Project List presentation\n *\n * Uses dynamic import to ensure correct build order.\n */\nimport type { PresentationRenderer } from '@contractspec/lib.contracts';\nimport {\n mockListProjectsHandler,\n mockGetSubscriptionHandler,\n} from '@contractspec/example.saas-boilerplate/handlers';\n\ninterface ProjectItem {\n id: string;\n name: string;\n status: string;\n description?: string;\n}\n\n/**\n * Markdown renderer for saas-boilerplate.project.list presentation\n * Only handles ProjectListView component\n */\nexport const projectListMarkdownRenderer: PresentationRenderer<{\n mimeType: string;\n body: string;\n}> = {\n target: 'markdown',\n render: async (desc, _ctx) => {\n // Only handle ProjectListView\n if (\n desc.source.type !== 'component' ||\n desc.source.componentKey !== 'ProjectListView'\n ) {\n throw new Error('projectListMarkdownRenderer: not ProjectListView');\n }\n\n const data = await mockListProjectsHandler({\n limit: 20,\n offset: 0,\n });\n\n // The example handler returns 'projects', not 'items'\n const items =\n (data as { projects?: ProjectItem[]; items?: ProjectItem[] }).projects ??\n (data as { items?: ProjectItem[] }).items ??\n [];\n\n const lines: string[] = [\n '# Projects',\n '',\n `**Total**: ${data.total} projects`,\n '',\n ];\n\n if (items.length === 0) {\n lines.push('_No projects found._');\n } else {\n lines.push('| Status | Project | Description |');\n lines.push('|--------|---------|-------------|');\n for (const project of items) {\n const status =\n project.status === 'ACTIVE'\n ? '✅'\n : project.status === 'ARCHIVED'\n ? '📦'\n : '⏸️';\n lines.push(\n `| ${status} | **${project.name}** | ${project.description ?? '-'} |`\n );\n }\n }\n\n return {\n mimeType: 'text/markdown',\n body: lines.join('\\n'),\n };\n },\n};\n\n/**\n * Markdown renderer for saas-boilerplate.dashboard presentation\n * Only handles SaasDashboard component\n */\nexport const saasDashboardMarkdownRenderer: PresentationRenderer<{\n mimeType: string;\n body: string;\n}> = {\n target: 'markdown',\n render: async (desc, _ctx) => {\n // Only handle SaasDashboard\n if (\n desc.source.type !== 'component' ||\n desc.source.componentKey !== 'SaasDashboard'\n ) {\n throw new Error('saasDashboardMarkdownRenderer: not SaasDashboard');\n }\n\n const [projectsData, subscription] = await Promise.all([\n mockListProjectsHandler({ limit: 50 }),\n mockGetSubscriptionHandler(),\n ]);\n\n const projects =\n (projectsData as { projects?: ProjectItem[] }).projects ?? [];\n const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;\n const archivedProjects = projects.filter(\n (p) => p.status === 'ARCHIVED'\n ).length;\n\n const lines: string[] = [\n '# SaaS Dashboard',\n '',\n '> Organization overview and usage summary',\n '',\n '## Summary',\n '',\n '| Metric | Value |',\n '|--------|-------|',\n `| Total Projects | ${projectsData.total} |`,\n `| Active Projects | ${activeProjects} |`,\n `| Archived Projects | ${archivedProjects} |`,\n `| Subscription Plan | ${subscription.planName} |`,\n `| Subscription Status | ${subscription.status} |`,\n '',\n '## Projects',\n '',\n ];\n\n if (projects.length === 0) {\n lines.push('_No projects yet._');\n } else {\n lines.push('| Status | Project | Description |');\n lines.push('|--------|---------|-------------|');\n for (const project of projects.slice(0, 10)) {\n const status =\n project.status === 'ACTIVE'\n ? '✅'\n : project.status === 'ARCHIVED'\n ? '📦'\n : '⏸️';\n lines.push(\n `| ${status} | **${project.name}** | ${project.description ?? '-'} |`\n );\n }\n if (projects.length > 10) {\n lines.push(\n `| ... | ... | _${projectsData.total - 10} more projects_ |`\n );\n }\n }\n\n lines.push('');\n lines.push('## Subscription');\n lines.push('');\n lines.push(`- **Plan**: ${subscription.planName}`);\n lines.push(`- **Status**: ${subscription.status}`);\n if (subscription.currentPeriodEnd) {\n lines.push(\n `- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`\n );\n }\n\n return {\n mimeType: 'text/markdown',\n body: lines.join('\\n'),\n };\n },\n};\n\n/**\n * Markdown renderer for saas-boilerplate.billing.settings presentation\n * Only handles SubscriptionView component\n */\nexport const saasBillingMarkdownRenderer: PresentationRenderer<{\n mimeType: string;\n body: string;\n}> = {\n target: 'markdown',\n render: async (desc, _ctx) => {\n // Only handle SubscriptionView\n if (\n desc.source.type !== 'component' ||\n desc.source.componentKey !== 'SubscriptionView'\n ) {\n throw new Error('saasBillingMarkdownRenderer: not SubscriptionView');\n }\n\n const subscription = await mockGetSubscriptionHandler();\n\n const lines: string[] = [\n '# Billing & Subscription',\n '',\n '> Current subscription details and billing information',\n '',\n '## Subscription Details',\n '',\n '| Property | Value |',\n '|----------|-------|',\n `| Plan | ${subscription.planName} |`,\n `| Status | ${subscription.status} |`,\n `| ID | ${subscription.id} |`,\n `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,\n `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`,\n ];\n\n lines.push('');\n lines.push('## Plan Limits');\n lines.push('');\n lines.push(`- **Projects**: ${subscription.limits.projects}`);\n lines.push(`- **Users**: ${subscription.limits.users}`);\n\n lines.push('');\n lines.push('## Plan Features');\n lines.push('');\n\n if (subscription.planName.toLowerCase().includes('free')) {\n lines.push('- ✅ Up to 3 projects');\n lines.push('- ✅ Basic support');\n lines.push('- ❌ Priority support');\n lines.push('- ❌ Advanced analytics');\n } else if (subscription.planName.toLowerCase().includes('pro')) {\n lines.push('- ✅ Unlimited projects');\n lines.push('- ✅ Priority support');\n lines.push('- ✅ Advanced analytics');\n lines.push('- ❌ Custom integrations');\n } else {\n lines.push('- ✅ Unlimited projects');\n lines.push('- ✅ Priority support');\n lines.push('- ✅ Advanced analytics');\n lines.push('- ✅ Custom integrations');\n lines.push('- ✅ Dedicated support');\n }\n\n return {\n mimeType: 'text/markdown',\n body: lines.join('\\n'),\n };\n },\n};\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { PresentationRenderer } from "@contractspec/lib.contracts";
2
+ import * as React from "react";
3
+
4
+ //#region src/ui/renderers/project-list.renderer.d.ts
5
+
6
+ declare const projectListReactRenderer: PresentationRenderer<React.ReactElement>;
7
+ //#endregion
8
+ export { projectListReactRenderer };
9
+ //# sourceMappingURL=project-list.renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-list.renderer.d.ts","names":[],"sources":["../../../src/ui/renderers/project-list.renderer.tsx"],"sourcesContent":[],"mappings":";;;;;cAOa,0BAA0B,qBAAqB,KAAA,CAAM"}
@@ -0,0 +1,17 @@
1
+ import { SaasProjectList } from "../SaasProjectList.js";
2
+ import "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/ui/renderers/project-list.renderer.tsx
6
+ const projectListReactRenderer = {
7
+ target: "react",
8
+ render: async (desc, _ctx) => {
9
+ if (desc.source.type !== "component") throw new Error("Invalid source type");
10
+ if (desc.source.componentKey !== "SaasProjectListView") throw new Error(`Unknown component: ${desc.source.componentKey}`);
11
+ return /* @__PURE__ */ jsx(SaasProjectList, {});
12
+ }
13
+ };
14
+
15
+ //#endregion
16
+ export { projectListReactRenderer };
17
+ //# sourceMappingURL=project-list.renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-list.renderer.js","names":[],"sources":["../../../src/ui/renderers/project-list.renderer.tsx"],"sourcesContent":["/**\n * React renderer for SaaS Project List presentation\n */\nimport * as React from 'react';\nimport type { PresentationRenderer } from '@contractspec/lib.contracts';\nimport { SaasProjectList } from '../SaasProjectList';\n\nexport const projectListReactRenderer: PresentationRenderer<React.ReactElement> =\n {\n target: 'react',\n render: async (desc, _ctx) => {\n if (desc.source.type !== 'component') {\n throw new Error('Invalid source type');\n }\n\n if (desc.source.componentKey !== 'SaasProjectListView') {\n throw new Error(`Unknown component: ${desc.source.componentKey}`);\n }\n\n return <SaasProjectList />;\n },\n };\n"],"mappings":";;;;;AAOA,MAAa,2BACX;CACE,QAAQ;CACR,QAAQ,OAAO,MAAM,SAAS;AAC5B,MAAI,KAAK,OAAO,SAAS,YACvB,OAAM,IAAI,MAAM,sBAAsB;AAGxC,MAAI,KAAK,OAAO,iBAAiB,sBAC/B,OAAM,IAAI,MAAM,sBAAsB,KAAK,OAAO,eAAe;AAGnE,SAAO,oBAAC,oBAAkB;;CAE7B"}
package/example.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from './src/example';
package/package.json ADDED
@@ -0,0 +1,135 @@
1
+ {
2
+ "name": "@contractspec/example.saas-boilerplate",
3
+ "version": "0.0.0-canary-20260113170453",
4
+ "description": "SaaS Boilerplate - Users, Orgs, Projects, Billing, Settings",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./billing": "./dist/billing/index.js",
10
+ "./billing/billing.entity": "./dist/billing/billing.entity.js",
11
+ "./billing/billing.enum": "./dist/billing/billing.enum.js",
12
+ "./billing/billing.event": "./dist/billing/billing.event.js",
13
+ "./billing/billing.handler": "./dist/billing/billing.handler.js",
14
+ "./billing/billing.operations": "./dist/billing/billing.operations.js",
15
+ "./billing/billing.presentation": "./dist/billing/billing.presentation.js",
16
+ "./billing/billing.schema": "./dist/billing/billing.schema.js",
17
+ "./dashboard": "./dist/dashboard/index.js",
18
+ "./dashboard/dashboard.presentation": "./dist/dashboard/dashboard.presentation.js",
19
+ "./docs": "./dist/docs/index.js",
20
+ "./docs/saas-boilerplate.docblock": "./dist/docs/saas-boilerplate.docblock.js",
21
+ "./example": "./dist/example.js",
22
+ "./handlers": "./dist/handlers/index.js",
23
+ "./handlers/saas.handlers": "./dist/handlers/saas.handlers.js",
24
+ "./presentations": "./dist/presentations/index.js",
25
+ "./project": "./dist/project/index.js",
26
+ "./project/project.entity": "./dist/project/project.entity.js",
27
+ "./project/project.enum": "./dist/project/project.enum.js",
28
+ "./project/project.event": "./dist/project/project.event.js",
29
+ "./project/project.handler": "./dist/project/project.handler.js",
30
+ "./project/project.operations": "./dist/project/project.operations.js",
31
+ "./project/project.presentation": "./dist/project/project.presentation.js",
32
+ "./project/project.schema": "./dist/project/project.schema.js",
33
+ "./saas-boilerplate.feature": "./dist/saas-boilerplate.feature.js",
34
+ "./seeders": "./dist/seeders/index.js",
35
+ "./settings": "./dist/settings/index.js",
36
+ "./settings/settings.entity": "./dist/settings/settings.entity.js",
37
+ "./settings/settings.enum": "./dist/settings/settings.enum.js",
38
+ "./shared/mock-data": "./dist/shared/mock-data.js",
39
+ "./shared/overlay-types": "./dist/shared/overlay-types.js",
40
+ "./tests/operations.test-spec": "./dist/tests/operations.test-spec.js",
41
+ "./ui": "./dist/ui/index.js",
42
+ "./ui/hooks": "./dist/ui/hooks/index.js",
43
+ "./ui/hooks/useProjectList": "./dist/ui/hooks/useProjectList.js",
44
+ "./ui/hooks/useProjectMutations": "./dist/ui/hooks/useProjectMutations.js",
45
+ "./ui/modals": "./dist/ui/modals/index.js",
46
+ "./ui/modals/CreateProjectModal": "./dist/ui/modals/CreateProjectModal.js",
47
+ "./ui/modals/ProjectActionsModal": "./dist/ui/modals/ProjectActionsModal.js",
48
+ "./ui/overlays": "./dist/ui/overlays/index.js",
49
+ "./ui/overlays/demo-overlays": "./dist/ui/overlays/demo-overlays.js",
50
+ "./ui/renderers": "./dist/ui/renderers/index.js",
51
+ "./ui/renderers/project-list.markdown": "./dist/ui/renderers/project-list.markdown.js",
52
+ "./ui/renderers/project-list.renderer": "./dist/ui/renderers/project-list.renderer.js",
53
+ "./ui/SaasDashboard": "./dist/ui/SaasDashboard.js",
54
+ "./ui/SaasProjectList": "./dist/ui/SaasProjectList.js",
55
+ "./ui/SaasSettingsPanel": "./dist/ui/SaasSettingsPanel.js",
56
+ "./*": "./*"
57
+ },
58
+ "scripts": {
59
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
60
+ "publish:pkg:canary": "bun publish:pkg --tag canary",
61
+ "build": "bun build:types && bun build:bundle",
62
+ "build:bundle": "tsdown",
63
+ "build:types": "tsc --noEmit",
64
+ "dev": "bun build:bundle --watch",
65
+ "clean": "rimraf dist .turbo",
66
+ "lint": "bun lint:fix",
67
+ "lint:fix": "eslint src --fix",
68
+ "lint:check": "eslint src",
69
+ "test": "bun test"
70
+ },
71
+ "dependencies": {
72
+ "@contractspec/lib.identity-rbac": "0.0.0-canary-20260113170453",
73
+ "@contractspec/lib.jobs": "0.0.0-canary-20260113170453",
74
+ "@contractspec/module.audit-trail": "0.0.0-canary-20260113170453",
75
+ "@contractspec/module.notifications": "0.0.0-canary-20260113170453",
76
+ "@contractspec/lib.contracts": "0.0.0-canary-20260113170453",
77
+ "@contractspec/lib.schema": "0.0.0-canary-20260113170453",
78
+ "@contractspec/lib.example-shared-ui": "0.0.0-canary-20260113170453",
79
+ "@contractspec/lib.design-system": "0.0.0-canary-20260113170453",
80
+ "@contractspec/lib.runtime-sandbox": "0.0.0-canary-20260113170453",
81
+ "react": "19.2.3",
82
+ "react-dom": "19.2.3"
83
+ },
84
+ "devDependencies": {
85
+ "@contractspec/tool.tsdown": "0.0.0-canary-20260113170453",
86
+ "@contractspec/tool.typescript": "0.0.0-canary-20260113170453",
87
+ "tsdown": "^0.19.0",
88
+ "typescript": "^5.9.3",
89
+ "@types/react": "^19.2.8",
90
+ "@types/react-dom": "^19.2.2"
91
+ },
92
+ "publishConfig": {
93
+ "exports": {
94
+ ".": "./dist/index.js",
95
+ "./billing": "./dist/billing/index.js",
96
+ "./billing/billing.contracts": "./dist/billing/billing.operations.js",
97
+ "./billing/billing.entity": "./dist/billing/billing.entity.js",
98
+ "./billing/billing.enum": "./dist/billing/billing.enum.js",
99
+ "./billing/billing.event": "./dist/billing/billing.event.js",
100
+ "./billing/billing.handler": "./dist/billing/billing.handler.js",
101
+ "./billing/billing.presentation": "./dist/billing/billing.presentation.js",
102
+ "./billing/billing.schema": "./dist/billing/billing.schema.js",
103
+ "./dashboard": "./dist/dashboard/index.js",
104
+ "./dashboard/dashboard.presentation": "./dist/dashboard/dashboard.presentation.js",
105
+ "./docs": "./dist/docs/index.js",
106
+ "./docs/saas-boilerplate.docblock": "./dist/docs/saas-boilerplate.docblock.js",
107
+ "./example": "./dist/example.js",
108
+ "./handlers": "./dist/handlers/index.js",
109
+ "./presentations": "./dist/presentations/index.js",
110
+ "./project": "./dist/project/index.js",
111
+ "./project/project.contracts": "./dist/project/project.operations.js",
112
+ "./project/project.entity": "./dist/project/project.entity.js",
113
+ "./project/project.enum": "./dist/project/project.enum.js",
114
+ "./project/project.event": "./dist/project/project.event.js",
115
+ "./project/project.handler": "./dist/project/project.handler.js",
116
+ "./project/project.presentation": "./dist/project/project.presentation.js",
117
+ "./project/project.schema": "./dist/project/project.schema.js",
118
+ "./saas-boilerplate.feature": "./dist/saas-boilerplate.feature.js",
119
+ "./settings": "./dist/settings/index.js",
120
+ "./settings/settings.entity": "./dist/settings/settings.entity.js",
121
+ "./settings/settings.enum": "./dist/settings/settings.enum.js",
122
+ "./shared/mock-data": "./dist/shared/mock-data.js",
123
+ "./*": "./*"
124
+ },
125
+ "registry": "https://registry.npmjs.org/",
126
+ "access": "public"
127
+ },
128
+ "license": "MIT",
129
+ "repository": {
130
+ "type": "git",
131
+ "url": "https://github.com/lssm-tech/contractspec.git",
132
+ "directory": "packages/examples/saas-boilerplate"
133
+ },
134
+ "homepage": "https://contractspec.io"
135
+ }
@@ -0,0 +1,158 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Subscription status enum for entities.
10
+ */
11
+ export const SubscriptionStatusEnum = defineEntityEnum({
12
+ name: 'SubscriptionStatus',
13
+ values: ['TRIALING', 'ACTIVE', 'PAST_DUE', 'CANCELED', 'PAUSED'] as const,
14
+ schema: 'saas_app',
15
+ description: 'Status of a subscription.',
16
+ });
17
+
18
+ /**
19
+ * Subscription entity - organization subscription info.
20
+ */
21
+ export const SubscriptionEntity = defineEntity({
22
+ name: 'Subscription',
23
+ description: 'Organization subscription/plan information.',
24
+ schema: 'saas_app',
25
+ map: 'subscription',
26
+ fields: {
27
+ id: field.id(),
28
+ organizationId: field.foreignKey({ isUnique: true }),
29
+
30
+ // Plan info
31
+ planId: field.string({ description: 'Plan identifier' }),
32
+ planName: field.string({ description: 'Plan display name' }),
33
+
34
+ // Status
35
+ status: field.enum('SubscriptionStatus'),
36
+
37
+ // Billing cycle
38
+ currentPeriodStart: field.dateTime(),
39
+ currentPeriodEnd: field.dateTime(),
40
+
41
+ // Trial
42
+ trialEndsAt: field.dateTime({ isOptional: true }),
43
+
44
+ // Cancellation
45
+ cancelAtPeriodEnd: field.boolean({ default: false }),
46
+ canceledAt: field.dateTime({ isOptional: true }),
47
+
48
+ // External reference
49
+ stripeSubscriptionId: field.string({ isOptional: true }),
50
+ stripeCustomerId: field.string({ isOptional: true }),
51
+
52
+ // Metadata
53
+ metadata: field.json({ isOptional: true }),
54
+
55
+ // Timestamps
56
+ createdAt: field.createdAt(),
57
+ updatedAt: field.updatedAt(),
58
+ },
59
+ enums: [SubscriptionStatusEnum],
60
+ });
61
+
62
+ /**
63
+ * BillingUsage entity - track feature usage.
64
+ */
65
+ export const BillingUsageEntity = defineEntity({
66
+ name: 'BillingUsage',
67
+ description: 'Track usage of metered features.',
68
+ schema: 'saas_app',
69
+ map: 'billing_usage',
70
+ fields: {
71
+ id: field.id(),
72
+ organizationId: field.foreignKey(),
73
+
74
+ // Feature
75
+ feature: field.string({
76
+ description: 'Feature being tracked (e.g., "api_calls", "storage_gb")',
77
+ }),
78
+
79
+ // Usage
80
+ quantity: field.int({ description: 'Usage quantity' }),
81
+ unit: field.string({
82
+ isOptional: true,
83
+ description: 'Unit of measurement',
84
+ }),
85
+
86
+ // Period
87
+ billingPeriod: field.string({
88
+ description: 'Billing period (e.g., "2024-01")',
89
+ }),
90
+
91
+ // Aggregation
92
+ recordedAt: field.dateTime({ description: 'When usage was recorded' }),
93
+
94
+ // Source
95
+ sourceId: field.string({
96
+ isOptional: true,
97
+ description: 'Source of usage (e.g., request ID)',
98
+ }),
99
+ sourceType: field.string({ isOptional: true }),
100
+
101
+ // Metadata
102
+ metadata: field.json({ isOptional: true }),
103
+ },
104
+ indexes: [
105
+ index.on(['organizationId', 'feature', 'billingPeriod']),
106
+ index.on(['organizationId', 'recordedAt']),
107
+ ],
108
+ });
109
+
110
+ /**
111
+ * UsageLimit entity - feature usage limits per plan.
112
+ */
113
+ export const UsageLimitEntity = defineEntity({
114
+ name: 'UsageLimit',
115
+ description: 'Usage limits per plan/organization.',
116
+ schema: 'saas_app',
117
+ map: 'usage_limit',
118
+ fields: {
119
+ id: field.id(),
120
+
121
+ // Scope
122
+ planId: field.string({
123
+ isOptional: true,
124
+ description: 'Plan this limit applies to',
125
+ }),
126
+ organizationId: field.string({
127
+ isOptional: true,
128
+ description: 'Org-specific override',
129
+ }),
130
+
131
+ // Limit
132
+ feature: field.string({ description: 'Feature being limited' }),
133
+ limit: field.int({ description: 'Maximum allowed usage' }),
134
+ resetPeriod: field.string({
135
+ default: '"monthly"',
136
+ description: 'When limit resets',
137
+ }),
138
+
139
+ // Soft/hard limit
140
+ isSoftLimit: field.boolean({
141
+ default: false,
142
+ description: 'Whether to warn vs block',
143
+ }),
144
+ overage: field.boolean({
145
+ default: false,
146
+ description: 'Whether overage is allowed',
147
+ }),
148
+ overageRate: field.float({
149
+ isOptional: true,
150
+ description: 'Cost per unit over limit',
151
+ }),
152
+
153
+ // Timestamps
154
+ createdAt: field.createdAt(),
155
+ updatedAt: field.updatedAt(),
156
+ },
157
+ indexes: [index.unique(['planId', 'feature'])],
158
+ });
@@ -0,0 +1,23 @@
1
+ import { defineEnum } from '@contractspec/lib.schema';
2
+
3
+ /**
4
+ * Subscription status enum for contract schemas.
5
+ * Note: Entity enum is defined separately in billing.entity.ts
6
+ */
7
+ export const SubscriptionStatusSchemaEnum = defineEnum('SubscriptionStatus', [
8
+ 'TRIALING',
9
+ 'ACTIVE',
10
+ 'PAST_DUE',
11
+ 'CANCELED',
12
+ 'PAUSED',
13
+ ]);
14
+
15
+ /**
16
+ * Feature access reason enum.
17
+ */
18
+ export const FeatureAccessReasonEnum = defineEnum('FeatureAccessReason', [
19
+ 'included',
20
+ 'limit_available',
21
+ 'limit_reached',
22
+ 'not_in_plan',
23
+ ]);