@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,307 @@
1
+ /**
2
+ * Azure Functions Relay Endpoints
3
+ *
4
+ * HTTP-triggered Azure Functions for Praxis Cloud Relay.
5
+ */
6
+
7
+ import type {
8
+ CRDTSyncMessage,
9
+ UsageMetrics,
10
+ HealthCheckResponse,
11
+ } from "../types.js";
12
+
13
+ /**
14
+ * Azure Function context (simplified interface)
15
+ */
16
+ export interface AzureContext {
17
+ log: (message: string) => void;
18
+ done: (err?: Error, result?: unknown) => void;
19
+ }
20
+
21
+ /**
22
+ * Azure HTTP request
23
+ */
24
+ export interface AzureHttpRequest {
25
+ method: string;
26
+ url: string;
27
+ headers: Record<string, string>;
28
+ query: Record<string, string>;
29
+ body?: unknown;
30
+ }
31
+
32
+ /**
33
+ * Azure HTTP response
34
+ */
35
+ export interface AzureHttpResponse {
36
+ status: number;
37
+ headers?: Record<string, string>;
38
+ body?: unknown;
39
+ }
40
+
41
+ /**
42
+ * In-memory storage for demo (replace with Azure Storage in production)
43
+ */
44
+ const storage = {
45
+ syncs: new Map<string, CRDTSyncMessage[]>(),
46
+ usage: new Map<string, UsageMetrics>(),
47
+ };
48
+
49
+ /**
50
+ * Health check endpoint
51
+ * GET /health
52
+ */
53
+ export async function healthEndpoint(
54
+ context: AzureContext,
55
+ _req: AzureHttpRequest
56
+ ): Promise<AzureHttpResponse> {
57
+ context.log("Health check requested");
58
+
59
+ const response: HealthCheckResponse = {
60
+ status: "healthy",
61
+ timestamp: Date.now(),
62
+ version: "0.1.0",
63
+ services: {
64
+ relay: true,
65
+ eventGrid: true,
66
+ storage: true,
67
+ auth: true,
68
+ },
69
+ };
70
+
71
+ return {
72
+ status: 200,
73
+ headers: { "Content-Type": "application/json" },
74
+ body: response,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * CRDT sync endpoint
80
+ * POST /sync
81
+ */
82
+ export async function syncEndpoint(
83
+ context: AzureContext,
84
+ req: AzureHttpRequest
85
+ ): Promise<AzureHttpResponse> {
86
+ context.log("Sync request received");
87
+
88
+ // Validate request
89
+ if (req.method !== "POST") {
90
+ return {
91
+ status: 405,
92
+ body: { error: "Method not allowed" },
93
+ };
94
+ }
95
+
96
+ const message = req.body as CRDTSyncMessage;
97
+
98
+ if (!message || !message.appId) {
99
+ return {
100
+ status: 400,
101
+ body: { error: "Invalid sync message" },
102
+ };
103
+ }
104
+
105
+ // Store sync message
106
+ const appSyncs = storage.syncs.get(message.appId) || [];
107
+ appSyncs.push(message);
108
+ storage.syncs.set(message.appId, appSyncs);
109
+
110
+ // Update usage metrics
111
+ const usage = storage.usage.get(message.appId) || {
112
+ appId: message.appId,
113
+ syncCount: 0,
114
+ eventCount: 0,
115
+ factCount: 0,
116
+ storageBytes: 0,
117
+ periodStart: Date.now(),
118
+ periodEnd: Date.now(),
119
+ };
120
+
121
+ usage.syncCount++;
122
+ usage.eventCount += message.events?.length || 0;
123
+ usage.factCount += message.facts?.length || 0;
124
+ usage.storageBytes += JSON.stringify(message).length;
125
+ usage.periodEnd = Date.now();
126
+
127
+ storage.usage.set(message.appId, usage);
128
+
129
+ context.log(`Synced for app ${message.appId}: ${usage.syncCount} total syncs`);
130
+
131
+ // Return updated vector clock
132
+ return {
133
+ status: 200,
134
+ headers: { "Content-Type": "application/json" },
135
+ body: {
136
+ success: true,
137
+ clock: message.clock,
138
+ timestamp: Date.now(),
139
+ },
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Usage metrics endpoint
145
+ * GET /usage?appId=<appId>
146
+ */
147
+ export async function usageEndpoint(
148
+ context: AzureContext,
149
+ req: AzureHttpRequest
150
+ ): Promise<AzureHttpResponse> {
151
+ context.log("Usage metrics requested");
152
+
153
+ const appId = req.query.appId;
154
+
155
+ if (!appId) {
156
+ return {
157
+ status: 400,
158
+ body: { error: "appId query parameter is required" },
159
+ };
160
+ }
161
+
162
+ const usage = storage.usage.get(appId);
163
+
164
+ if (!usage) {
165
+ return {
166
+ status: 404,
167
+ body: { error: "No usage data found for this app" },
168
+ };
169
+ }
170
+
171
+ return {
172
+ status: 200,
173
+ headers: { "Content-Type": "application/json" },
174
+ body: usage,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Stats endpoint for aggregated metrics
180
+ * GET /stats?appId=<appId>
181
+ */
182
+ export async function statsEndpoint(
183
+ context: AzureContext,
184
+ req: AzureHttpRequest
185
+ ): Promise<AzureHttpResponse> {
186
+ context.log("Stats requested");
187
+
188
+ const appId = req.query.appId;
189
+
190
+ if (!appId) {
191
+ return {
192
+ status: 400,
193
+ body: { error: "appId query parameter is required" },
194
+ };
195
+ }
196
+
197
+ const usage = storage.usage.get(appId);
198
+ const syncs = storage.syncs.get(appId) || [];
199
+
200
+ return {
201
+ status: 200,
202
+ headers: { "Content-Type": "application/json" },
203
+ body: {
204
+ appId,
205
+ totalSyncs: syncs.length,
206
+ usage: usage || null,
207
+ lastSync: syncs.length > 0 ? syncs[syncs.length - 1].timestamp : null,
208
+ },
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Event forwarding endpoint
214
+ * POST /events
215
+ */
216
+ export async function eventsEndpoint(
217
+ context: AzureContext,
218
+ req: AzureHttpRequest
219
+ ): Promise<AzureHttpResponse> {
220
+ context.log("Event forwarding requested");
221
+
222
+ if (req.method !== "POST") {
223
+ return {
224
+ status: 405,
225
+ body: { error: "Method not allowed" },
226
+ };
227
+ }
228
+
229
+ const { appId, events } = req.body as { appId: string; events: unknown[] };
230
+
231
+ if (!appId || !events) {
232
+ return {
233
+ status: 400,
234
+ body: { error: "Invalid event forwarding request" },
235
+ };
236
+ }
237
+
238
+ context.log(`Forwarding ${events.length} events for app ${appId}`);
239
+
240
+ // In production, publish to Azure Event Grid / Service Bus
241
+ // For now, just acknowledge receipt
242
+ return {
243
+ status: 200,
244
+ headers: { "Content-Type": "application/json" },
245
+ body: {
246
+ success: true,
247
+ forwarded: events.length,
248
+ timestamp: Date.now(),
249
+ },
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Schema registry endpoint
255
+ * GET /schema?appId=<appId>
256
+ * POST /schema (to register a schema)
257
+ */
258
+ export async function schemaEndpoint(
259
+ context: AzureContext,
260
+ req: AzureHttpRequest
261
+ ): Promise<AzureHttpResponse> {
262
+ context.log("Schema registry requested");
263
+
264
+ if (req.method === "POST") {
265
+ const { appId, schema } = req.body as { appId: string; schema: unknown };
266
+
267
+ if (!appId || !schema) {
268
+ return {
269
+ status: 400,
270
+ body: { error: "Invalid schema registration request" },
271
+ };
272
+ }
273
+
274
+ context.log(`Schema registered for app ${appId}`);
275
+
276
+ return {
277
+ status: 200,
278
+ headers: { "Content-Type": "application/json" },
279
+ body: {
280
+ success: true,
281
+ schemaId: `${appId}-${Date.now()}`,
282
+ timestamp: Date.now(),
283
+ },
284
+ };
285
+ }
286
+
287
+ // GET request
288
+ const appId = req.query.appId;
289
+
290
+ if (!appId) {
291
+ return {
292
+ status: 400,
293
+ body: { error: "appId query parameter is required" },
294
+ };
295
+ }
296
+
297
+ // Return placeholder schema
298
+ return {
299
+ status: 200,
300
+ headers: { "Content-Type": "application/json" },
301
+ body: {
302
+ appId,
303
+ schema: null,
304
+ message: "Schema not found",
305
+ },
306
+ };
307
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "bindings": [
3
+ {
4
+ "authLevel": "anonymous",
5
+ "type": "httpTrigger",
6
+ "direction": "in",
7
+ "name": "req",
8
+ "methods": ["get"],
9
+ "route": "health"
10
+ },
11
+ {
12
+ "type": "http",
13
+ "direction": "out",
14
+ "name": "res"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Azure Function: Health Check
3
+ */
4
+
5
+ import { healthEndpoint } from "../endpoints.js";
6
+
7
+ export default async function (context: any, req: any) {
8
+ const response = await healthEndpoint(context, req);
9
+ context.res = response;
10
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "2.0",
3
+ "logging": {
4
+ "applicationInsights": {
5
+ "samplingSettings": {
6
+ "isEnabled": true,
7
+ "maxTelemetryItemsPerSecond": 20
8
+ }
9
+ }
10
+ },
11
+ "extensionBundle": {
12
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
13
+ "version": "[4.*, 5.0.0)"
14
+ }
15
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "IsEncrypted": false,
3
+ "Values": {
4
+ "AzureWebJobsStorage": "",
5
+ "FUNCTIONS_WORKER_RUNTIME": "node",
6
+ "NODE_ENV": "production"
7
+ }
8
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "bindings": [
3
+ {
4
+ "authLevel": "anonymous",
5
+ "type": "httpTrigger",
6
+ "direction": "in",
7
+ "name": "req",
8
+ "methods": ["get"],
9
+ "route": "stats"
10
+ },
11
+ {
12
+ "type": "http",
13
+ "direction": "out",
14
+ "name": "res"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Azure Function: Stats
3
+ */
4
+
5
+ import { statsEndpoint } from "../endpoints.js";
6
+
7
+ export default async function (context: any, req: any) {
8
+ const response = await statsEndpoint(context, req);
9
+ context.res = response;
10
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "bindings": [
3
+ {
4
+ "authLevel": "anonymous",
5
+ "type": "httpTrigger",
6
+ "direction": "in",
7
+ "name": "req",
8
+ "methods": ["post"],
9
+ "route": "sync"
10
+ },
11
+ {
12
+ "type": "http",
13
+ "direction": "out",
14
+ "name": "res"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Azure Function: CRDT Sync
3
+ */
4
+
5
+ import { syncEndpoint } from "../endpoints.js";
6
+
7
+ export default async function (context: any, req: any) {
8
+ const response = await syncEndpoint(context, req);
9
+ context.res = response;
10
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "bindings": [
3
+ {
4
+ "authLevel": "anonymous",
5
+ "type": "httpTrigger",
6
+ "direction": "in",
7
+ "name": "req",
8
+ "methods": ["get"],
9
+ "route": "usage"
10
+ },
11
+ {
12
+ "type": "http",
13
+ "direction": "out",
14
+ "name": "res"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Azure Function: Usage Metrics
3
+ */
4
+
5
+ import { usageEndpoint } from "../endpoints.js";
6
+
7
+ export default async function (context: any, req: any) {
8
+ const response = await usageEndpoint(context, req);
9
+ context.res = response;
10
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * GitHub Sponsors API Client
3
+ *
4
+ * Client for checking GitHub Sponsors subscription status.
5
+ */
6
+
7
+ import type { Subscription } from "./billing.js";
8
+ import { createSponsorSubscription, createFreeSubscription } from "./billing.js";
9
+
10
+ /**
11
+ * GitHub Sponsors tier information
12
+ */
13
+ export interface SponsorTier {
14
+ /**
15
+ * Tier ID
16
+ */
17
+ id: string;
18
+
19
+ /**
20
+ * Tier name
21
+ */
22
+ name: string;
23
+
24
+ /**
25
+ * Monthly price in cents
26
+ */
27
+ monthlyPriceInCents: number;
28
+
29
+ /**
30
+ * Tier description
31
+ */
32
+ description?: string;
33
+
34
+ /**
35
+ * Whether this is a one-time sponsorship
36
+ */
37
+ isOneTime: boolean;
38
+ }
39
+
40
+ /**
41
+ * Sponsorship information
42
+ */
43
+ export interface Sponsorship {
44
+ /**
45
+ * Sponsor login
46
+ */
47
+ sponsorLogin: string;
48
+
49
+ /**
50
+ * Sponsor ID
51
+ */
52
+ sponsorId: number;
53
+
54
+ /**
55
+ * Tier information
56
+ */
57
+ tier: SponsorTier;
58
+
59
+ /**
60
+ * Creation date
61
+ */
62
+ createdAt: string;
63
+
64
+ /**
65
+ * Whether sponsorship is active
66
+ */
67
+ isActive: boolean;
68
+ }
69
+
70
+ /**
71
+ * GitHub Sponsors API client
72
+ */
73
+ export class GitHubSponsorsClient {
74
+ private token: string;
75
+ private accountLogin: string;
76
+
77
+ constructor(token: string, accountLogin: string) {
78
+ this.token = token;
79
+ this.accountLogin = accountLogin;
80
+ }
81
+
82
+ /**
83
+ * Get current user's sponsorship of the Praxis account
84
+ */
85
+ async getSponsorship(userLogin: string): Promise<Sponsorship | null> {
86
+ try {
87
+ const query = `
88
+ query($accountLogin: String!, $userLogin: String!) {
89
+ user(login: $userLogin) {
90
+ sponsorshipForViewerAsSponsor(activeOnly: true) {
91
+ tier {
92
+ id
93
+ name
94
+ monthlyPriceInCents
95
+ description
96
+ isOneTime
97
+ }
98
+ createdAt
99
+ isActive
100
+ }
101
+ sponsorshipsAsSponsor(first: 100, activeOnly: true) {
102
+ nodes {
103
+ sponsorable {
104
+ ... on User {
105
+ login
106
+ }
107
+ ... on Organization {
108
+ login
109
+ }
110
+ }
111
+ tier {
112
+ id
113
+ name
114
+ monthlyPriceInCents
115
+ description
116
+ isOneTime
117
+ }
118
+ createdAt
119
+ isActive
120
+ }
121
+ }
122
+ }
123
+ }
124
+ `;
125
+
126
+ const response = await fetch("https://api.github.com/graphql", {
127
+ method: "POST",
128
+ headers: {
129
+ Authorization: `Bearer ${this.token}`,
130
+ "Content-Type": "application/json",
131
+ },
132
+ body: JSON.stringify({
133
+ query,
134
+ variables: {
135
+ accountLogin: this.accountLogin,
136
+ userLogin,
137
+ },
138
+ }),
139
+ });
140
+
141
+ if (!response.ok) {
142
+ throw new Error(`GitHub API error: ${response.statusText}`);
143
+ }
144
+
145
+ const data = await response.json() as any;
146
+
147
+ if (data.errors) {
148
+ throw new Error(`GraphQL error: ${JSON.stringify(data.errors)}`);
149
+ }
150
+
151
+ // Check if user sponsors the Praxis account
152
+ const sponsorships = data.data?.user?.sponsorshipsAsSponsor?.nodes || [];
153
+ const praxisSponsorship = sponsorships.find(
154
+ (s: any) => s.sponsorable?.login === this.accountLogin
155
+ );
156
+
157
+ if (!praxisSponsorship) {
158
+ return null;
159
+ }
160
+
161
+ return {
162
+ sponsorLogin: userLogin,
163
+ sponsorId: data.data.user.id,
164
+ tier: {
165
+ id: praxisSponsorship.tier.id,
166
+ name: praxisSponsorship.tier.name,
167
+ monthlyPriceInCents: praxisSponsorship.tier.monthlyPriceInCents,
168
+ description: praxisSponsorship.tier.description,
169
+ isOneTime: praxisSponsorship.tier.isOneTime,
170
+ },
171
+ createdAt: praxisSponsorship.createdAt,
172
+ isActive: praxisSponsorship.isActive,
173
+ };
174
+ } catch (error) {
175
+ console.error("Failed to get sponsorship:", error);
176
+ return null;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Get subscription from sponsorship
182
+ */
183
+ async getSubscription(userLogin: string): Promise<Subscription> {
184
+ const sponsorship = await this.getSponsorship(userLogin);
185
+
186
+ if (!sponsorship || !sponsorship.isActive) {
187
+ return createFreeSubscription();
188
+ }
189
+
190
+ return createSponsorSubscription(
191
+ sponsorship.tier.name,
192
+ sponsorship.tier.monthlyPriceInCents
193
+ );
194
+ }
195
+
196
+ /**
197
+ * Check if user is a sponsor
198
+ */
199
+ async isSponsor(userLogin: string): Promise<boolean> {
200
+ const sponsorship = await this.getSponsorship(userLogin);
201
+ return sponsorship !== null && sponsorship.isActive;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Create a GitHub Sponsors client
207
+ */
208
+ export function createSponsorsClient(
209
+ token: string,
210
+ accountLogin: string = "plures"
211
+ ): GitHubSponsorsClient {
212
+ return new GitHubSponsorsClient(token, accountLogin);
213
+ }