@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.
- package/FRAMEWORK.md +420 -0
- package/LICENSE +21 -0
- package/README.md +1310 -0
- package/dist/adapters/cli.d.ts +43 -0
- package/dist/adapters/cli.d.ts.map +1 -0
- package/dist/adapters/cli.js +126 -0
- package/dist/adapters/cli.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +26 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +233 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/cloud.d.ts +27 -0
- package/dist/cli/commands/cloud.d.ts.map +1 -0
- package/dist/cli/commands/cloud.js +232 -0
- package/dist/cli/commands/cloud.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +25 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +168 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +179 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cloud/auth.d.ts +51 -0
- package/dist/cloud/auth.d.ts.map +1 -0
- package/dist/cloud/auth.js +194 -0
- package/dist/cloud/auth.js.map +1 -0
- package/dist/cloud/billing.d.ts +184 -0
- package/dist/cloud/billing.d.ts.map +1 -0
- package/dist/cloud/billing.js +179 -0
- package/dist/cloud/billing.js.map +1 -0
- package/dist/cloud/client.d.ts +39 -0
- package/dist/cloud/client.d.ts.map +1 -0
- package/dist/cloud/client.js +176 -0
- package/dist/cloud/client.js.map +1 -0
- package/dist/cloud/index.d.ts +44 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +44 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/cloud/marketplace.d.ts +166 -0
- package/dist/cloud/marketplace.d.ts.map +1 -0
- package/dist/cloud/marketplace.js +159 -0
- package/dist/cloud/marketplace.js.map +1 -0
- package/dist/cloud/provisioning.d.ts +110 -0
- package/dist/cloud/provisioning.d.ts.map +1 -0
- package/dist/cloud/provisioning.js +148 -0
- package/dist/cloud/provisioning.js.map +1 -0
- package/dist/cloud/relay/endpoints.d.ts +62 -0
- package/dist/cloud/relay/endpoints.d.ts.map +1 -0
- package/dist/cloud/relay/endpoints.js +217 -0
- package/dist/cloud/relay/endpoints.js.map +1 -0
- package/dist/cloud/relay/health/index.d.ts +5 -0
- package/dist/cloud/relay/health/index.d.ts.map +1 -0
- package/dist/cloud/relay/health/index.js +9 -0
- package/dist/cloud/relay/health/index.js.map +1 -0
- package/dist/cloud/relay/stats/index.d.ts +5 -0
- package/dist/cloud/relay/stats/index.d.ts.map +1 -0
- package/dist/cloud/relay/stats/index.js +9 -0
- package/dist/cloud/relay/stats/index.js.map +1 -0
- package/dist/cloud/relay/sync/index.d.ts +5 -0
- package/dist/cloud/relay/sync/index.d.ts.map +1 -0
- package/dist/cloud/relay/sync/index.js +9 -0
- package/dist/cloud/relay/sync/index.js.map +1 -0
- package/dist/cloud/relay/usage/index.d.ts +5 -0
- package/dist/cloud/relay/usage/index.d.ts.map +1 -0
- package/dist/cloud/relay/usage/index.js +9 -0
- package/dist/cloud/relay/usage/index.js.map +1 -0
- package/dist/cloud/sponsors.d.ts +81 -0
- package/dist/cloud/sponsors.d.ts.map +1 -0
- package/dist/cloud/sponsors.js +130 -0
- package/dist/cloud/sponsors.js.map +1 -0
- package/dist/cloud/types.d.ts +169 -0
- package/dist/cloud/types.d.ts.map +1 -0
- package/dist/cloud/types.js +7 -0
- package/dist/cloud/types.js.map +1 -0
- package/dist/components/index.d.ts +43 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +17 -0
- package/dist/components/index.js.map +1 -0
- package/dist/core/actors.d.ts +95 -0
- package/dist/core/actors.d.ts.map +1 -0
- package/dist/core/actors.js +158 -0
- package/dist/core/actors.js.map +1 -0
- package/dist/core/component/generator.d.ts +122 -0
- package/dist/core/component/generator.d.ts.map +1 -0
- package/dist/core/component/generator.js +307 -0
- package/dist/core/component/generator.js.map +1 -0
- package/dist/core/engine.d.ts +92 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +199 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/introspection.d.ts +141 -0
- package/dist/core/introspection.d.ts.map +1 -0
- package/dist/core/introspection.js +208 -0
- package/dist/core/introspection.js.map +1 -0
- package/dist/core/logic/generator.d.ts +76 -0
- package/dist/core/logic/generator.d.ts.map +1 -0
- package/dist/core/logic/generator.js +339 -0
- package/dist/core/logic/generator.js.map +1 -0
- package/dist/core/pluresdb/generator.d.ts +58 -0
- package/dist/core/pluresdb/generator.d.ts.map +1 -0
- package/dist/core/pluresdb/generator.js +162 -0
- package/dist/core/pluresdb/generator.js.map +1 -0
- package/dist/core/protocol.d.ts +121 -0
- package/dist/core/protocol.d.ts.map +1 -0
- package/dist/core/protocol.js +46 -0
- package/dist/core/protocol.js.map +1 -0
- package/dist/core/rules.d.ts +120 -0
- package/dist/core/rules.d.ts.map +1 -0
- package/dist/core/rules.js +81 -0
- package/dist/core/rules.js.map +1 -0
- package/dist/core/schema/loader.d.ts +47 -0
- package/dist/core/schema/loader.d.ts.map +1 -0
- package/dist/core/schema/loader.js +189 -0
- package/dist/core/schema/loader.js.map +1 -0
- package/dist/core/schema/normalize.d.ts +72 -0
- package/dist/core/schema/normalize.d.ts.map +1 -0
- package/dist/core/schema/normalize.js +190 -0
- package/dist/core/schema/normalize.js.map +1 -0
- package/dist/core/schema/types.d.ts +370 -0
- package/dist/core/schema/types.d.ts.map +1 -0
- package/dist/core/schema/types.js +161 -0
- package/dist/core/schema/types.js.map +1 -0
- package/dist/dsl/index.d.ts +152 -0
- package/dist/dsl/index.d.ts.map +1 -0
- package/dist/dsl/index.js +132 -0
- package/dist/dsl/index.js.map +1 -0
- package/dist/dsl.d.ts +124 -0
- package/dist/dsl.d.ts.map +1 -0
- package/dist/dsl.js +130 -0
- package/dist/dsl.js.map +1 -0
- package/dist/examples/advanced-todo/index.d.ts +55 -0
- package/dist/examples/advanced-todo/index.d.ts.map +1 -0
- package/dist/examples/advanced-todo/index.js +222 -0
- package/dist/examples/advanced-todo/index.js.map +1 -0
- package/dist/examples/auth-basic/index.d.ts +17 -0
- package/dist/examples/auth-basic/index.d.ts.map +1 -0
- package/dist/examples/auth-basic/index.js +122 -0
- package/dist/examples/auth-basic/index.js.map +1 -0
- package/dist/examples/cart/index.d.ts +19 -0
- package/dist/examples/cart/index.d.ts.map +1 -0
- package/dist/examples/cart/index.js +202 -0
- package/dist/examples/cart/index.js.map +1 -0
- package/dist/examples/hero-ecommerce/index.d.ts +39 -0
- package/dist/examples/hero-ecommerce/index.d.ts.map +1 -0
- package/dist/examples/hero-ecommerce/index.js +506 -0
- package/dist/examples/hero-ecommerce/index.js.map +1 -0
- package/dist/examples/svelte-counter/index.d.ts +31 -0
- package/dist/examples/svelte-counter/index.d.ts.map +1 -0
- package/dist/examples/svelte-counter/index.js +123 -0
- package/dist/examples/svelte-counter/index.js.map +1 -0
- package/dist/flows.d.ts +125 -0
- package/dist/flows.d.ts.map +1 -0
- package/dist/flows.js +160 -0
- package/dist/flows.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/pluresdb.d.ts +56 -0
- package/dist/integrations/pluresdb.d.ts.map +1 -0
- package/dist/integrations/pluresdb.js +46 -0
- package/dist/integrations/pluresdb.js.map +1 -0
- package/dist/integrations/svelte.d.ts +306 -0
- package/dist/integrations/svelte.d.ts.map +1 -0
- package/dist/integrations/svelte.js +447 -0
- package/dist/integrations/svelte.js.map +1 -0
- package/dist/registry.d.ts +94 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +181 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime/terminal-adapter.d.ts +105 -0
- package/dist/runtime/terminal-adapter.d.ts.map +1 -0
- package/dist/runtime/terminal-adapter.js +113 -0
- package/dist/runtime/terminal-adapter.js.map +1 -0
- package/dist/step.d.ts +34 -0
- package/dist/step.d.ts.map +1 -0
- package/dist/step.js +111 -0
- package/dist/step.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/docs/MONETIZATION.md +394 -0
- package/docs/TERMINAL_NODE.md +588 -0
- package/docs/guides/canvas.md +389 -0
- package/docs/guides/getting-started.md +347 -0
- package/docs/guides/history-state-pattern.md +618 -0
- package/docs/guides/orchestration.md +617 -0
- package/docs/guides/parallel-state-pattern.md +767 -0
- package/docs/guides/svelte-integration.md +691 -0
- package/package.json +96 -0
- package/src/__tests__/actors.test.ts +270 -0
- package/src/__tests__/billing.test.ts +175 -0
- package/src/__tests__/cloud.test.ts +247 -0
- package/src/__tests__/dsl.test.ts +154 -0
- package/src/__tests__/edge-cases.test.ts +475 -0
- package/src/__tests__/engine.test.ts +137 -0
- package/src/__tests__/generators.test.ts +270 -0
- package/src/__tests__/introspection.test.ts +321 -0
- package/src/__tests__/protocol.test.ts +40 -0
- package/src/__tests__/provisioning.test.ts +162 -0
- package/src/__tests__/schema.test.ts +241 -0
- package/src/__tests__/svelte-integration.test.ts +431 -0
- package/src/__tests__/terminal-node.test.ts +352 -0
- package/src/adapters/cli.ts +175 -0
- package/src/cli/commands/auth.ts +271 -0
- package/src/cli/commands/cloud.ts +281 -0
- package/src/cli/commands/generate.ts +225 -0
- package/src/cli/index.ts +190 -0
- package/src/cloud/README.md +383 -0
- package/src/cloud/auth.ts +245 -0
- package/src/cloud/billing.ts +336 -0
- package/src/cloud/client.ts +221 -0
- package/src/cloud/index.ts +121 -0
- package/src/cloud/marketplace.ts +303 -0
- package/src/cloud/provisioning.ts +254 -0
- package/src/cloud/relay/endpoints.ts +307 -0
- package/src/cloud/relay/health/function.json +17 -0
- package/src/cloud/relay/health/index.ts +10 -0
- package/src/cloud/relay/host.json +15 -0
- package/src/cloud/relay/local.settings.json +8 -0
- package/src/cloud/relay/stats/function.json +17 -0
- package/src/cloud/relay/stats/index.ts +10 -0
- package/src/cloud/relay/sync/function.json +17 -0
- package/src/cloud/relay/sync/index.ts +10 -0
- package/src/cloud/relay/usage/function.json +17 -0
- package/src/cloud/relay/usage/index.ts +10 -0
- package/src/cloud/sponsors.ts +213 -0
- package/src/cloud/types.ts +198 -0
- package/src/components/README.md +125 -0
- package/src/components/TerminalNode.svelte +457 -0
- package/src/components/index.ts +46 -0
- package/src/core/actors.ts +205 -0
- package/src/core/component/generator.ts +432 -0
- package/src/core/engine.ts +243 -0
- package/src/core/introspection.ts +329 -0
- package/src/core/logic/generator.ts +420 -0
- package/src/core/pluresdb/generator.ts +229 -0
- package/src/core/protocol.ts +132 -0
- package/src/core/rules.ts +167 -0
- package/src/core/schema/loader.ts +247 -0
- package/src/core/schema/normalize.ts +322 -0
- package/src/core/schema/types.ts +557 -0
- package/src/dsl/index.ts +218 -0
- package/src/dsl.ts +214 -0
- package/src/examples/advanced-todo/App.svelte +506 -0
- package/src/examples/advanced-todo/README.md +371 -0
- package/src/examples/advanced-todo/index.ts +309 -0
- package/src/examples/auth-basic/index.ts +163 -0
- package/src/examples/cart/index.ts +259 -0
- package/src/examples/hero-ecommerce/index.ts +657 -0
- package/src/examples/svelte-counter/index.ts +168 -0
- package/src/flows.ts +268 -0
- package/src/index.ts +154 -0
- package/src/integrations/pluresdb.ts +93 -0
- package/src/integrations/svelte.ts +617 -0
- package/src/registry.ts +223 -0
- package/src/runtime/terminal-adapter.ts +175 -0
- package/src/step.ts +151 -0
- package/src/types.ts +70 -0
- package/templates/basic-app/README.md +147 -0
- 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
|
+
}
|