@plures/praxis 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/FRAMEWORK.md +420 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1310 -0
  4. package/dist/adapters/cli.d.ts +43 -0
  5. package/dist/adapters/cli.d.ts.map +1 -0
  6. package/dist/adapters/cli.js +126 -0
  7. package/dist/adapters/cli.js.map +1 -0
  8. package/dist/cli/commands/auth.d.ts +26 -0
  9. package/dist/cli/commands/auth.d.ts.map +1 -0
  10. package/dist/cli/commands/auth.js +233 -0
  11. package/dist/cli/commands/auth.js.map +1 -0
  12. package/dist/cli/commands/cloud.d.ts +27 -0
  13. package/dist/cli/commands/cloud.d.ts.map +1 -0
  14. package/dist/cli/commands/cloud.js +232 -0
  15. package/dist/cli/commands/cloud.js.map +1 -0
  16. package/dist/cli/commands/generate.d.ts +25 -0
  17. package/dist/cli/commands/generate.d.ts.map +1 -0
  18. package/dist/cli/commands/generate.js +168 -0
  19. package/dist/cli/commands/generate.js.map +1 -0
  20. package/dist/cli/index.d.ts +8 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +179 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cloud/auth.d.ts +51 -0
  25. package/dist/cloud/auth.d.ts.map +1 -0
  26. package/dist/cloud/auth.js +194 -0
  27. package/dist/cloud/auth.js.map +1 -0
  28. package/dist/cloud/billing.d.ts +184 -0
  29. package/dist/cloud/billing.d.ts.map +1 -0
  30. package/dist/cloud/billing.js +179 -0
  31. package/dist/cloud/billing.js.map +1 -0
  32. package/dist/cloud/client.d.ts +39 -0
  33. package/dist/cloud/client.d.ts.map +1 -0
  34. package/dist/cloud/client.js +176 -0
  35. package/dist/cloud/client.js.map +1 -0
  36. package/dist/cloud/index.d.ts +44 -0
  37. package/dist/cloud/index.d.ts.map +1 -0
  38. package/dist/cloud/index.js +44 -0
  39. package/dist/cloud/index.js.map +1 -0
  40. package/dist/cloud/marketplace.d.ts +166 -0
  41. package/dist/cloud/marketplace.d.ts.map +1 -0
  42. package/dist/cloud/marketplace.js +159 -0
  43. package/dist/cloud/marketplace.js.map +1 -0
  44. package/dist/cloud/provisioning.d.ts +110 -0
  45. package/dist/cloud/provisioning.d.ts.map +1 -0
  46. package/dist/cloud/provisioning.js +148 -0
  47. package/dist/cloud/provisioning.js.map +1 -0
  48. package/dist/cloud/relay/endpoints.d.ts +62 -0
  49. package/dist/cloud/relay/endpoints.d.ts.map +1 -0
  50. package/dist/cloud/relay/endpoints.js +217 -0
  51. package/dist/cloud/relay/endpoints.js.map +1 -0
  52. package/dist/cloud/relay/health/index.d.ts +5 -0
  53. package/dist/cloud/relay/health/index.d.ts.map +1 -0
  54. package/dist/cloud/relay/health/index.js +9 -0
  55. package/dist/cloud/relay/health/index.js.map +1 -0
  56. package/dist/cloud/relay/stats/index.d.ts +5 -0
  57. package/dist/cloud/relay/stats/index.d.ts.map +1 -0
  58. package/dist/cloud/relay/stats/index.js +9 -0
  59. package/dist/cloud/relay/stats/index.js.map +1 -0
  60. package/dist/cloud/relay/sync/index.d.ts +5 -0
  61. package/dist/cloud/relay/sync/index.d.ts.map +1 -0
  62. package/dist/cloud/relay/sync/index.js +9 -0
  63. package/dist/cloud/relay/sync/index.js.map +1 -0
  64. package/dist/cloud/relay/usage/index.d.ts +5 -0
  65. package/dist/cloud/relay/usage/index.d.ts.map +1 -0
  66. package/dist/cloud/relay/usage/index.js +9 -0
  67. package/dist/cloud/relay/usage/index.js.map +1 -0
  68. package/dist/cloud/sponsors.d.ts +81 -0
  69. package/dist/cloud/sponsors.d.ts.map +1 -0
  70. package/dist/cloud/sponsors.js +130 -0
  71. package/dist/cloud/sponsors.js.map +1 -0
  72. package/dist/cloud/types.d.ts +169 -0
  73. package/dist/cloud/types.d.ts.map +1 -0
  74. package/dist/cloud/types.js +7 -0
  75. package/dist/cloud/types.js.map +1 -0
  76. package/dist/components/index.d.ts +43 -0
  77. package/dist/components/index.d.ts.map +1 -0
  78. package/dist/components/index.js +17 -0
  79. package/dist/components/index.js.map +1 -0
  80. package/dist/core/actors.d.ts +95 -0
  81. package/dist/core/actors.d.ts.map +1 -0
  82. package/dist/core/actors.js +158 -0
  83. package/dist/core/actors.js.map +1 -0
  84. package/dist/core/component/generator.d.ts +122 -0
  85. package/dist/core/component/generator.d.ts.map +1 -0
  86. package/dist/core/component/generator.js +307 -0
  87. package/dist/core/component/generator.js.map +1 -0
  88. package/dist/core/engine.d.ts +92 -0
  89. package/dist/core/engine.d.ts.map +1 -0
  90. package/dist/core/engine.js +199 -0
  91. package/dist/core/engine.js.map +1 -0
  92. package/dist/core/introspection.d.ts +141 -0
  93. package/dist/core/introspection.d.ts.map +1 -0
  94. package/dist/core/introspection.js +208 -0
  95. package/dist/core/introspection.js.map +1 -0
  96. package/dist/core/logic/generator.d.ts +76 -0
  97. package/dist/core/logic/generator.d.ts.map +1 -0
  98. package/dist/core/logic/generator.js +339 -0
  99. package/dist/core/logic/generator.js.map +1 -0
  100. package/dist/core/pluresdb/generator.d.ts +58 -0
  101. package/dist/core/pluresdb/generator.d.ts.map +1 -0
  102. package/dist/core/pluresdb/generator.js +162 -0
  103. package/dist/core/pluresdb/generator.js.map +1 -0
  104. package/dist/core/protocol.d.ts +121 -0
  105. package/dist/core/protocol.d.ts.map +1 -0
  106. package/dist/core/protocol.js +46 -0
  107. package/dist/core/protocol.js.map +1 -0
  108. package/dist/core/rules.d.ts +120 -0
  109. package/dist/core/rules.d.ts.map +1 -0
  110. package/dist/core/rules.js +81 -0
  111. package/dist/core/rules.js.map +1 -0
  112. package/dist/core/schema/loader.d.ts +47 -0
  113. package/dist/core/schema/loader.d.ts.map +1 -0
  114. package/dist/core/schema/loader.js +189 -0
  115. package/dist/core/schema/loader.js.map +1 -0
  116. package/dist/core/schema/normalize.d.ts +72 -0
  117. package/dist/core/schema/normalize.d.ts.map +1 -0
  118. package/dist/core/schema/normalize.js +190 -0
  119. package/dist/core/schema/normalize.js.map +1 -0
  120. package/dist/core/schema/types.d.ts +370 -0
  121. package/dist/core/schema/types.d.ts.map +1 -0
  122. package/dist/core/schema/types.js +161 -0
  123. package/dist/core/schema/types.js.map +1 -0
  124. package/dist/dsl/index.d.ts +152 -0
  125. package/dist/dsl/index.d.ts.map +1 -0
  126. package/dist/dsl/index.js +132 -0
  127. package/dist/dsl/index.js.map +1 -0
  128. package/dist/dsl.d.ts +124 -0
  129. package/dist/dsl.d.ts.map +1 -0
  130. package/dist/dsl.js +130 -0
  131. package/dist/dsl.js.map +1 -0
  132. package/dist/examples/advanced-todo/index.d.ts +55 -0
  133. package/dist/examples/advanced-todo/index.d.ts.map +1 -0
  134. package/dist/examples/advanced-todo/index.js +222 -0
  135. package/dist/examples/advanced-todo/index.js.map +1 -0
  136. package/dist/examples/auth-basic/index.d.ts +17 -0
  137. package/dist/examples/auth-basic/index.d.ts.map +1 -0
  138. package/dist/examples/auth-basic/index.js +122 -0
  139. package/dist/examples/auth-basic/index.js.map +1 -0
  140. package/dist/examples/cart/index.d.ts +19 -0
  141. package/dist/examples/cart/index.d.ts.map +1 -0
  142. package/dist/examples/cart/index.js +202 -0
  143. package/dist/examples/cart/index.js.map +1 -0
  144. package/dist/examples/hero-ecommerce/index.d.ts +39 -0
  145. package/dist/examples/hero-ecommerce/index.d.ts.map +1 -0
  146. package/dist/examples/hero-ecommerce/index.js +506 -0
  147. package/dist/examples/hero-ecommerce/index.js.map +1 -0
  148. package/dist/examples/svelte-counter/index.d.ts +31 -0
  149. package/dist/examples/svelte-counter/index.d.ts.map +1 -0
  150. package/dist/examples/svelte-counter/index.js +123 -0
  151. package/dist/examples/svelte-counter/index.js.map +1 -0
  152. package/dist/flows.d.ts +125 -0
  153. package/dist/flows.d.ts.map +1 -0
  154. package/dist/flows.js +160 -0
  155. package/dist/flows.js.map +1 -0
  156. package/dist/index.d.ts +67 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +59 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/integrations/pluresdb.d.ts +56 -0
  161. package/dist/integrations/pluresdb.d.ts.map +1 -0
  162. package/dist/integrations/pluresdb.js +46 -0
  163. package/dist/integrations/pluresdb.js.map +1 -0
  164. package/dist/integrations/svelte.d.ts +306 -0
  165. package/dist/integrations/svelte.d.ts.map +1 -0
  166. package/dist/integrations/svelte.js +447 -0
  167. package/dist/integrations/svelte.js.map +1 -0
  168. package/dist/registry.d.ts +94 -0
  169. package/dist/registry.d.ts.map +1 -0
  170. package/dist/registry.js +181 -0
  171. package/dist/registry.js.map +1 -0
  172. package/dist/runtime/terminal-adapter.d.ts +105 -0
  173. package/dist/runtime/terminal-adapter.d.ts.map +1 -0
  174. package/dist/runtime/terminal-adapter.js +113 -0
  175. package/dist/runtime/terminal-adapter.js.map +1 -0
  176. package/dist/step.d.ts +34 -0
  177. package/dist/step.d.ts.map +1 -0
  178. package/dist/step.js +111 -0
  179. package/dist/step.js.map +1 -0
  180. package/dist/types.d.ts +63 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +6 -0
  183. package/dist/types.js.map +1 -0
  184. package/docs/MONETIZATION.md +394 -0
  185. package/docs/TERMINAL_NODE.md +588 -0
  186. package/docs/guides/canvas.md +389 -0
  187. package/docs/guides/getting-started.md +347 -0
  188. package/docs/guides/history-state-pattern.md +618 -0
  189. package/docs/guides/orchestration.md +617 -0
  190. package/docs/guides/parallel-state-pattern.md +767 -0
  191. package/docs/guides/svelte-integration.md +691 -0
  192. package/package.json +96 -0
  193. package/src/__tests__/actors.test.ts +270 -0
  194. package/src/__tests__/billing.test.ts +175 -0
  195. package/src/__tests__/cloud.test.ts +247 -0
  196. package/src/__tests__/dsl.test.ts +154 -0
  197. package/src/__tests__/edge-cases.test.ts +475 -0
  198. package/src/__tests__/engine.test.ts +137 -0
  199. package/src/__tests__/generators.test.ts +270 -0
  200. package/src/__tests__/introspection.test.ts +321 -0
  201. package/src/__tests__/protocol.test.ts +40 -0
  202. package/src/__tests__/provisioning.test.ts +162 -0
  203. package/src/__tests__/schema.test.ts +241 -0
  204. package/src/__tests__/svelte-integration.test.ts +431 -0
  205. package/src/__tests__/terminal-node.test.ts +352 -0
  206. package/src/adapters/cli.ts +175 -0
  207. package/src/cli/commands/auth.ts +271 -0
  208. package/src/cli/commands/cloud.ts +281 -0
  209. package/src/cli/commands/generate.ts +225 -0
  210. package/src/cli/index.ts +190 -0
  211. package/src/cloud/README.md +383 -0
  212. package/src/cloud/auth.ts +245 -0
  213. package/src/cloud/billing.ts +336 -0
  214. package/src/cloud/client.ts +221 -0
  215. package/src/cloud/index.ts +121 -0
  216. package/src/cloud/marketplace.ts +303 -0
  217. package/src/cloud/provisioning.ts +254 -0
  218. package/src/cloud/relay/endpoints.ts +307 -0
  219. package/src/cloud/relay/health/function.json +17 -0
  220. package/src/cloud/relay/health/index.ts +10 -0
  221. package/src/cloud/relay/host.json +15 -0
  222. package/src/cloud/relay/local.settings.json +8 -0
  223. package/src/cloud/relay/stats/function.json +17 -0
  224. package/src/cloud/relay/stats/index.ts +10 -0
  225. package/src/cloud/relay/sync/function.json +17 -0
  226. package/src/cloud/relay/sync/index.ts +10 -0
  227. package/src/cloud/relay/usage/function.json +17 -0
  228. package/src/cloud/relay/usage/index.ts +10 -0
  229. package/src/cloud/sponsors.ts +213 -0
  230. package/src/cloud/types.ts +198 -0
  231. package/src/components/README.md +125 -0
  232. package/src/components/TerminalNode.svelte +457 -0
  233. package/src/components/index.ts +46 -0
  234. package/src/core/actors.ts +205 -0
  235. package/src/core/component/generator.ts +432 -0
  236. package/src/core/engine.ts +243 -0
  237. package/src/core/introspection.ts +329 -0
  238. package/src/core/logic/generator.ts +420 -0
  239. package/src/core/pluresdb/generator.ts +229 -0
  240. package/src/core/protocol.ts +132 -0
  241. package/src/core/rules.ts +167 -0
  242. package/src/core/schema/loader.ts +247 -0
  243. package/src/core/schema/normalize.ts +322 -0
  244. package/src/core/schema/types.ts +557 -0
  245. package/src/dsl/index.ts +218 -0
  246. package/src/dsl.ts +214 -0
  247. package/src/examples/advanced-todo/App.svelte +506 -0
  248. package/src/examples/advanced-todo/README.md +371 -0
  249. package/src/examples/advanced-todo/index.ts +309 -0
  250. package/src/examples/auth-basic/index.ts +163 -0
  251. package/src/examples/cart/index.ts +259 -0
  252. package/src/examples/hero-ecommerce/index.ts +657 -0
  253. package/src/examples/svelte-counter/index.ts +168 -0
  254. package/src/flows.ts +268 -0
  255. package/src/index.ts +154 -0
  256. package/src/integrations/pluresdb.ts +93 -0
  257. package/src/integrations/svelte.ts +617 -0
  258. package/src/registry.ts +223 -0
  259. package/src/runtime/terminal-adapter.ts +175 -0
  260. package/src/step.ts +151 -0
  261. package/src/types.ts +70 -0
  262. package/templates/basic-app/README.md +147 -0
  263. package/templates/fullstack-app/README.md +279 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * GitHub Marketplace API Client
3
+ *
4
+ * Client for GitHub Marketplace SaaS integration (preparatory).
5
+ */
6
+
7
+ import type { Subscription } from "./billing.js";
8
+ import { SubscriptionTier, SubscriptionStatus, BillingProvider, TIER_LIMITS } from "./billing.js";
9
+
10
+ /**
11
+ * GitHub Marketplace plan
12
+ */
13
+ export interface MarketplacePlan {
14
+ /**
15
+ * Plan ID
16
+ */
17
+ id: number;
18
+
19
+ /**
20
+ * Plan name
21
+ */
22
+ name: string;
23
+
24
+ /**
25
+ * Plan description
26
+ */
27
+ description: string;
28
+
29
+ /**
30
+ * Monthly price in cents
31
+ */
32
+ monthlyPriceInCents: number;
33
+
34
+ /**
35
+ * Yearly price in cents
36
+ */
37
+ yearlyPriceInCents: number;
38
+
39
+ /**
40
+ * Price model
41
+ */
42
+ priceModel: "FLAT_RATE" | "PER_UNIT";
43
+
44
+ /**
45
+ * Whether this plan has a free trial
46
+ */
47
+ hasFreeTrial: boolean;
48
+
49
+ /**
50
+ * Unit name (for per-unit pricing)
51
+ */
52
+ unitName?: string;
53
+
54
+ /**
55
+ * Bullets (features list)
56
+ */
57
+ bullets: string[];
58
+ }
59
+
60
+ /**
61
+ * Marketplace account
62
+ */
63
+ export interface MarketplaceAccount {
64
+ /**
65
+ * Account ID
66
+ */
67
+ id: number;
68
+
69
+ /**
70
+ * Account login
71
+ */
72
+ login: string;
73
+
74
+ /**
75
+ * Account type
76
+ */
77
+ type: "User" | "Organization";
78
+
79
+ /**
80
+ * Plan
81
+ */
82
+ plan: MarketplacePlan;
83
+
84
+ /**
85
+ * Whether account is on free trial
86
+ */
87
+ onFreeTrial: boolean;
88
+
89
+ /**
90
+ * Free trial ends on (if applicable)
91
+ */
92
+ freeTrialEndsOn?: string;
93
+
94
+ /**
95
+ * Next billing date
96
+ */
97
+ nextBillingDate?: string;
98
+ }
99
+
100
+ /**
101
+ * Marketplace webhook event
102
+ */
103
+ export interface MarketplaceWebhookEvent {
104
+ /**
105
+ * Action type
106
+ */
107
+ action: "purchased" | "cancelled" | "changed" | "pending_change" | "pending_change_cancelled";
108
+
109
+ /**
110
+ * Effective date
111
+ */
112
+ effectiveDate?: string;
113
+
114
+ /**
115
+ * Marketplace purchase
116
+ */
117
+ marketplacePurchase: {
118
+ account: MarketplaceAccount;
119
+ billingCycle: "monthly" | "yearly";
120
+ unitCount?: number;
121
+ onFreeTrial: boolean;
122
+ freeTrialEndsOn?: string;
123
+ nextBillingDate?: string;
124
+ };
125
+
126
+ /**
127
+ * Previous plan (for changes)
128
+ */
129
+ previousMarketplacePurchase?: {
130
+ account: MarketplaceAccount;
131
+ billingCycle: "monthly" | "yearly";
132
+ unitCount?: number;
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Praxis Cloud Marketplace plans configuration
138
+ */
139
+ export const MARKETPLACE_PLANS = {
140
+ solo: {
141
+ name: "Praxis Cloud Solo",
142
+ description: "For individual developers",
143
+ monthlyPriceInCents: 500, // $5/month
144
+ yearlyPriceInCents: 5000, // $50/year (2 months free)
145
+ features: [
146
+ "50,000 syncs/month",
147
+ "1 GB storage",
148
+ "10 apps/projects",
149
+ "Standard support",
150
+ ],
151
+ },
152
+ team: {
153
+ name: "Praxis Cloud Team",
154
+ description: "For small teams",
155
+ monthlyPriceInCents: 2000, // $20/month
156
+ yearlyPriceInCents: 20000, // $200/year (2 months free)
157
+ features: [
158
+ "500,000 syncs/month",
159
+ "10 GB storage",
160
+ "50 apps/projects",
161
+ "Up to 10 team members",
162
+ "Standard support",
163
+ ],
164
+ },
165
+ enterprise: {
166
+ name: "Praxis Cloud Enterprise",
167
+ description: "For large teams and organizations",
168
+ monthlyPriceInCents: 5000, // $50/month
169
+ yearlyPriceInCents: 50000, // $500/year (2 months free)
170
+ features: [
171
+ "5,000,000 syncs/month",
172
+ "100 GB storage",
173
+ "1,000 apps/projects",
174
+ "Unlimited team members",
175
+ "Priority support",
176
+ "SLA guarantees",
177
+ ],
178
+ },
179
+ };
180
+
181
+ /**
182
+ * GitHub Marketplace API client
183
+ */
184
+ export class GitHubMarketplaceClient {
185
+ private token: string;
186
+
187
+ constructor(token: string) {
188
+ this.token = token;
189
+ }
190
+
191
+ /**
192
+ * Get accounts for the authenticated user
193
+ */
194
+ async getAccounts(): Promise<MarketplaceAccount[]> {
195
+ try {
196
+ const response = await fetch(
197
+ "https://api.github.com/marketplace_listing/accounts",
198
+ {
199
+ headers: {
200
+ Authorization: `Bearer ${this.token}`,
201
+ Accept: "application/vnd.github.v3+json",
202
+ },
203
+ }
204
+ );
205
+
206
+ if (!response.ok) {
207
+ throw new Error(`GitHub API error: ${response.statusText}`);
208
+ }
209
+
210
+ return await response.json() as MarketplaceAccount[];
211
+ } catch (error) {
212
+ console.error("Failed to get marketplace accounts:", error);
213
+ return [];
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Get subscription from marketplace account
219
+ */
220
+ async getSubscription(accountId: number): Promise<Subscription | null> {
221
+ const accounts = await this.getAccounts();
222
+ const account = accounts.find((a) => a.id === accountId);
223
+
224
+ if (!account) {
225
+ return null;
226
+ }
227
+
228
+ // Map plan to tier
229
+ let tier = SubscriptionTier.FREE;
230
+ if (account.plan.monthlyPriceInCents >= 5000) {
231
+ tier = SubscriptionTier.ENTERPRISE;
232
+ } else if (account.plan.monthlyPriceInCents >= 2000) {
233
+ tier = SubscriptionTier.TEAM;
234
+ } else if (account.plan.monthlyPriceInCents >= 500) {
235
+ tier = SubscriptionTier.SOLO;
236
+ }
237
+
238
+ return {
239
+ tier,
240
+ status: SubscriptionStatus.ACTIVE,
241
+ provider: BillingProvider.MARKETPLACE,
242
+ marketplacePlanId: account.plan.id,
243
+ startDate: account.onFreeTrial && account.freeTrialEndsOn
244
+ ? new Date(account.freeTrialEndsOn).getTime()
245
+ : Date.now(),
246
+ periodEnd: account.nextBillingDate
247
+ ? new Date(account.nextBillingDate).getTime()
248
+ : undefined,
249
+ autoRenew: true,
250
+ limits: TIER_LIMITS[tier],
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Handle marketplace webhook event
256
+ */
257
+ handleWebhookEvent(event: MarketplaceWebhookEvent): {
258
+ userId: number;
259
+ userLogin: string;
260
+ subscription: Subscription;
261
+ } | null {
262
+ const account = event.marketplacePurchase.account;
263
+
264
+ if (event.action === "cancelled") {
265
+ // Handle cancellation
266
+ return null;
267
+ }
268
+
269
+ // Map plan to tier
270
+ let tier = SubscriptionTier.FREE;
271
+ if (account.plan.monthlyPriceInCents >= 5000) {
272
+ tier = SubscriptionTier.ENTERPRISE;
273
+ } else if (account.plan.monthlyPriceInCents >= 2000) {
274
+ tier = SubscriptionTier.TEAM;
275
+ } else if (account.plan.monthlyPriceInCents >= 500) {
276
+ tier = SubscriptionTier.SOLO;
277
+ }
278
+
279
+ return {
280
+ userId: account.id,
281
+ userLogin: account.login,
282
+ subscription: {
283
+ tier,
284
+ status: SubscriptionStatus.ACTIVE,
285
+ provider: BillingProvider.MARKETPLACE,
286
+ marketplacePlanId: account.plan.id,
287
+ startDate: Date.now(),
288
+ periodEnd: event.marketplacePurchase.nextBillingDate
289
+ ? new Date(event.marketplacePurchase.nextBillingDate).getTime()
290
+ : undefined,
291
+ autoRenew: true,
292
+ limits: TIER_LIMITS[tier],
293
+ },
294
+ };
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Create a GitHub Marketplace client
300
+ */
301
+ export function createMarketplaceClient(token: string): GitHubMarketplaceClient {
302
+ return new GitHubMarketplaceClient(token);
303
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Auto-Provisioning
3
+ *
4
+ * Automatic tenant/storage provisioning based on GitHub identity.
5
+ */
6
+
7
+ import type { GitHubUser } from "./types.js";
8
+ import type { Subscription } from "./billing.js";
9
+
10
+ /**
11
+ * Tenant information
12
+ */
13
+ export interface Tenant {
14
+ /**
15
+ * Tenant ID (derived from GitHub user/org)
16
+ */
17
+ id: string;
18
+
19
+ /**
20
+ * GitHub user ID
21
+ */
22
+ githubUserId: number;
23
+
24
+ /**
25
+ * GitHub login (username or org name)
26
+ */
27
+ githubLogin: string;
28
+
29
+ /**
30
+ * Tenant type
31
+ */
32
+ type: "user" | "organization";
33
+
34
+ /**
35
+ * Subscription
36
+ */
37
+ subscription: Subscription;
38
+
39
+ /**
40
+ * Storage namespace
41
+ */
42
+ storageNamespace: string;
43
+
44
+ /**
45
+ * Creation timestamp
46
+ */
47
+ createdAt: number;
48
+
49
+ /**
50
+ * Last accessed timestamp
51
+ */
52
+ lastAccessedAt: number;
53
+ }
54
+
55
+ /**
56
+ * Generate a storage namespace from GitHub login
57
+ *
58
+ * Namespace format: gh-{login}-{hash}
59
+ * This ensures uniqueness and follows Azure Blob Storage naming rules.
60
+ */
61
+ export function generateStorageNamespace(githubLogin: string, userId: number): string {
62
+ // Sanitize login: lowercase, replace non-alphanumeric with hyphens
63
+ const sanitized = githubLogin.toLowerCase().replace(/[^a-z0-9]/g, "-");
64
+
65
+ // Create a simple hash from user ID for uniqueness
66
+ const hash = userId.toString(36).padStart(6, "0");
67
+
68
+ // Combine with prefix
69
+ return `gh-${sanitized}-${hash}`;
70
+ }
71
+
72
+ /**
73
+ * Generate tenant ID from GitHub user
74
+ */
75
+ export function generateTenantId(githubUser: GitHubUser): string {
76
+ return `github-${githubUser.id}`;
77
+ }
78
+
79
+ /**
80
+ * Create a tenant from GitHub user
81
+ */
82
+ export function createTenant(
83
+ githubUser: GitHubUser,
84
+ subscription: Subscription
85
+ ): Tenant {
86
+ const tenantId = generateTenantId(githubUser);
87
+ const storageNamespace = generateStorageNamespace(
88
+ githubUser.login,
89
+ githubUser.id
90
+ );
91
+
92
+ return {
93
+ id: tenantId,
94
+ githubUserId: githubUser.id,
95
+ githubLogin: githubUser.login,
96
+ type: "user", // Could be "organization" if checking org membership
97
+ subscription,
98
+ storageNamespace,
99
+ createdAt: Date.now(),
100
+ lastAccessedAt: Date.now(),
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Validate storage namespace
106
+ *
107
+ * Ensures namespace follows Azure Blob Storage naming rules:
108
+ * - 3-63 characters
109
+ * - lowercase letters, numbers, and hyphens only
110
+ * - must start with letter or number
111
+ * - no consecutive hyphens
112
+ */
113
+ export function validateStorageNamespace(namespace: string): {
114
+ valid: boolean;
115
+ error?: string;
116
+ } {
117
+ if (namespace.length < 3 || namespace.length > 63) {
118
+ return {
119
+ valid: false,
120
+ error: "Namespace must be 3-63 characters",
121
+ };
122
+ }
123
+
124
+ if (!/^[a-z0-9]/.test(namespace)) {
125
+ return {
126
+ valid: false,
127
+ error: "Namespace must start with a letter or number",
128
+ };
129
+ }
130
+
131
+ if (!/^[a-z0-9-]+$/.test(namespace)) {
132
+ return {
133
+ valid: false,
134
+ error: "Namespace can only contain lowercase letters, numbers, and hyphens",
135
+ };
136
+ }
137
+
138
+ if (/--/.test(namespace)) {
139
+ return {
140
+ valid: false,
141
+ error: "Namespace cannot contain consecutive hyphens",
142
+ };
143
+ }
144
+
145
+ return { valid: true };
146
+ }
147
+
148
+ /**
149
+ * Get storage container name for an app
150
+ */
151
+ export function getAppStorageContainer(
152
+ tenantNamespace: string,
153
+ appId: string
154
+ ): string {
155
+ // Sanitize app ID
156
+ const sanitizedAppId = appId.toLowerCase().replace(/[^a-z0-9]/g, "-");
157
+ return `${tenantNamespace}-${sanitizedAppId}`;
158
+ }
159
+
160
+ /**
161
+ * Provisioning result
162
+ */
163
+ export interface ProvisioningResult {
164
+ /**
165
+ * Whether provisioning was successful
166
+ */
167
+ success: boolean;
168
+
169
+ /**
170
+ * Tenant (if successful)
171
+ */
172
+ tenant?: Tenant;
173
+
174
+ /**
175
+ * Error message (if failed)
176
+ */
177
+ error?: string;
178
+ }
179
+
180
+ /**
181
+ * Provision a new tenant
182
+ *
183
+ * This would typically:
184
+ * 1. Create storage containers
185
+ * 2. Set up access policies
186
+ * 3. Initialize tenant metadata
187
+ * 4. Register with billing system
188
+ */
189
+ export async function provisionTenant(
190
+ githubUser: GitHubUser,
191
+ subscription: Subscription
192
+ ): Promise<ProvisioningResult> {
193
+ try {
194
+ const tenant = createTenant(githubUser, subscription);
195
+
196
+ // Validate storage namespace
197
+ const validation = validateStorageNamespace(tenant.storageNamespace);
198
+ if (!validation.valid) {
199
+ return {
200
+ success: false,
201
+ error: validation.error,
202
+ };
203
+ }
204
+
205
+ // TODO: In production, this would:
206
+ // 1. Create Azure Blob Storage container
207
+ // 2. Set up access policies
208
+ // 3. Store tenant metadata in database
209
+ // 4. Send welcome email
210
+ // 5. Log provisioning event
211
+
212
+ console.log(`Provisioned tenant: ${tenant.id}`);
213
+ console.log(`Storage namespace: ${tenant.storageNamespace}`);
214
+
215
+ return {
216
+ success: true,
217
+ tenant,
218
+ };
219
+ } catch (error) {
220
+ return {
221
+ success: false,
222
+ error: error instanceof Error ? error.message : String(error),
223
+ };
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Get or create tenant
229
+ *
230
+ * Checks if tenant exists, creates if not.
231
+ */
232
+ export async function getOrCreateTenant(
233
+ githubUser: GitHubUser,
234
+ subscription: Subscription,
235
+ tenantLookup: (id: string) => Promise<Tenant | null>
236
+ ): Promise<Tenant> {
237
+ const tenantId = generateTenantId(githubUser);
238
+
239
+ // Try to get existing tenant
240
+ const existing = await tenantLookup(tenantId);
241
+ if (existing) {
242
+ // Update last accessed time
243
+ existing.lastAccessedAt = Date.now();
244
+ return existing;
245
+ }
246
+
247
+ // Provision new tenant
248
+ const result = await provisionTenant(githubUser, subscription);
249
+ if (!result.success || !result.tenant) {
250
+ throw new Error(`Failed to provision tenant: ${result.error}`);
251
+ }
252
+
253
+ return result.tenant;
254
+ }