@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,383 @@
1
+ # Praxis Cloud Relay
2
+
3
+ Azure-hosted relay service for Praxis Cloud monetization (Tier 1).
4
+
5
+ ## Features
6
+
7
+ - **PluresDB CRDT Sync** - Synchronize facts and events across clients
8
+ - **Event Forwarding** - Publish events to Azure Event Grid / Service Bus
9
+ - **Schema Registry** - Store and retrieve application schemas
10
+ - **Encrypted Blob Storage** - Secure storage for generated docs and components
11
+ - **GitHub OAuth** - Identity provider integration
12
+ - **Usage Metering** - Track usage for billing purposes
13
+
14
+ ## Quick Start
15
+
16
+ ### 1. Setup Cloud Connection
17
+
18
+ ```bash
19
+ # Install Praxis
20
+ npm install @plures/praxis
21
+
22
+ # Initialize cloud connection
23
+ npx praxis cloud init
24
+ ```
25
+
26
+ The wizard will:
27
+ 1. Authenticate with GitHub
28
+ 2. Configure the Azure endpoint
29
+ 3. Test the connection
30
+ 4. Save configuration to `.praxis-cloud.json`
31
+
32
+ ### 2. Use in Your Application
33
+
34
+ ```typescript
35
+ import { connectRelay } from "@plures/praxis/cloud";
36
+
37
+ // Connect to cloud relay
38
+ const relay = await connectRelay("https://praxis-relay.azurewebsites.net", {
39
+ appId: "my-app",
40
+ authToken: "your-github-token",
41
+ autoSync: true,
42
+ syncInterval: 5000
43
+ });
44
+
45
+ // Sync data
46
+ await relay.sync({
47
+ type: "delta",
48
+ appId: "my-app",
49
+ clock: {},
50
+ facts: [{ tag: "TaskCreated", payload: { id: "1", title: "Buy milk" } }],
51
+ events: [],
52
+ timestamp: Date.now()
53
+ });
54
+
55
+ // Get usage metrics
56
+ const usage = await relay.getUsage();
57
+ console.log(`Syncs: ${usage.syncCount}, Events: ${usage.eventCount}`);
58
+
59
+ // Check health
60
+ const health = await relay.getHealth();
61
+ console.log(`Status: ${health.status}`);
62
+
63
+ // Disconnect
64
+ await relay.disconnect();
65
+ ```
66
+
67
+ ## CLI Commands
68
+
69
+ ### `praxis cloud init`
70
+
71
+ Setup wizard to connect your app to Praxis Cloud.
72
+
73
+ ```bash
74
+ npx praxis cloud init --endpoint https://praxis-relay.azurewebsites.net --app-id my-app
75
+ ```
76
+
77
+ Options:
78
+ - `-e, --endpoint <url>` - Azure Function App endpoint URL
79
+ - `-a, --app-id <id>` - Application identifier
80
+ - `--auto-sync` - Enable automatic synchronization
81
+ - `--interval <ms>` - Sync interval in milliseconds
82
+
83
+ ### `praxis cloud status`
84
+
85
+ Check connection status and service health.
86
+
87
+ ```bash
88
+ npx praxis cloud status
89
+ ```
90
+
91
+ ### `praxis cloud sync`
92
+
93
+ Manually trigger synchronization.
94
+
95
+ ```bash
96
+ npx praxis cloud sync
97
+ ```
98
+
99
+ ### `praxis cloud usage`
100
+
101
+ View usage metrics for billing.
102
+
103
+ ```bash
104
+ npx praxis cloud usage
105
+ ```
106
+
107
+ ## API Reference
108
+
109
+ ### `connectRelay(endpoint, options)`
110
+
111
+ Connect to Praxis Cloud Relay.
112
+
113
+ **Parameters:**
114
+ - `endpoint: string` - Azure Function App endpoint URL
115
+ - `options: CloudRelayConfig` - Configuration options
116
+ - `appId: string` - Application identifier (required)
117
+ - `authToken?: string` - GitHub OAuth token
118
+ - `autoSync?: boolean` - Enable automatic sync
119
+ - `syncInterval?: number` - Sync interval in ms (default: 5000)
120
+ - `encryption?: boolean` - Enable encryption for blob storage
121
+
122
+ **Returns:** `Promise<CloudRelayClient>`
123
+
124
+ ### `CloudRelayClient`
125
+
126
+ #### `connect(): Promise<void>`
127
+
128
+ Connect to the relay service.
129
+
130
+ #### `disconnect(): Promise<void>`
131
+
132
+ Disconnect from the relay service.
133
+
134
+ #### `sync(message: CRDTSyncMessage): Promise<void>`
135
+
136
+ Sync facts and events using CRDT protocol.
137
+
138
+ **Parameters:**
139
+ - `message.type` - "sync" | "delta" | "snapshot"
140
+ - `message.appId` - Application identifier
141
+ - `message.clock` - Vector clock for causality
142
+ - `message.facts?` - Facts to sync
143
+ - `message.events?` - Events to forward
144
+ - `message.timestamp` - Timestamp
145
+
146
+ #### `getUsage(): Promise<UsageMetrics>`
147
+
148
+ Get usage metrics for the current billing period.
149
+
150
+ **Returns:**
151
+ - `syncCount` - Number of sync operations
152
+ - `eventCount` - Number of events forwarded
153
+ - `factCount` - Number of facts synced
154
+ - `storageBytes` - Storage used in bytes
155
+ - `periodStart` - Period start timestamp
156
+ - `periodEnd` - Period end timestamp
157
+
158
+ #### `getHealth(): Promise<HealthCheckResponse>`
159
+
160
+ Get service health status.
161
+
162
+ **Returns:**
163
+ - `status` - "healthy" | "degraded" | "unhealthy"
164
+ - `services` - Status of individual services
165
+ - `relay` - Relay service status
166
+ - `eventGrid` - Event Grid status
167
+ - `storage` - Blob storage status
168
+ - `auth` - Authentication status
169
+
170
+ #### `getStatus(): RelayStatus`
171
+
172
+ Get current connection status (synchronous).
173
+
174
+ **Returns:**
175
+ - `connected` - Connection state
176
+ - `lastSync` - Last sync timestamp
177
+ - `endpoint` - Endpoint URL
178
+ - `appId` - Application identifier
179
+
180
+ ## Azure Deployment
181
+
182
+ ### Prerequisites
183
+
184
+ - Azure account
185
+ - Azure CLI installed
186
+ - Azure Functions Core Tools
187
+
188
+ ### Deploy to Azure
189
+
190
+ 1. **Create Azure Function App:**
191
+
192
+ ```bash
193
+ az functionapp create \
194
+ --name praxis-cloud-relay \
195
+ --resource-group praxis-rg \
196
+ --consumption-plan-location eastus \
197
+ --runtime node \
198
+ --runtime-version 18 \
199
+ --storage-account praxisstorage
200
+ ```
201
+
202
+ 2. **Deploy Functions:**
203
+
204
+ The GitHub Actions workflow (`.github/workflows/azure-functions.yml`) automatically deploys on push to `main`.
205
+
206
+ Or deploy manually:
207
+
208
+ ```bash
209
+ cd src/cloud/relay
210
+ func azure functionapp publish praxis-cloud-relay
211
+ ```
212
+
213
+ ### Environment Variables
214
+
215
+ Configure in Azure Portal or via CLI:
216
+
217
+ ```bash
218
+ az functionapp config appsettings set \
219
+ --name praxis-cloud-relay \
220
+ --resource-group praxis-rg \
221
+ --settings \
222
+ "GITHUB_CLIENT_ID=your-client-id" \
223
+ "GITHUB_CLIENT_SECRET=your-client-secret" \
224
+ "AZURE_STORAGE_CONNECTION_STRING=your-connection-string"
225
+ ```
226
+
227
+ ## Endpoints
228
+
229
+ The relay exposes the following HTTP endpoints:
230
+
231
+ ### `GET /health`
232
+
233
+ Health check endpoint.
234
+
235
+ **Response:**
236
+ ```json
237
+ {
238
+ "status": "healthy",
239
+ "timestamp": 1234567890,
240
+ "version": "0.1.0",
241
+ "services": {
242
+ "relay": true,
243
+ "eventGrid": true,
244
+ "storage": true,
245
+ "auth": true
246
+ }
247
+ }
248
+ ```
249
+
250
+ ### `POST /sync`
251
+
252
+ CRDT synchronization endpoint.
253
+
254
+ **Request:**
255
+ ```json
256
+ {
257
+ "type": "delta",
258
+ "appId": "my-app",
259
+ "clock": { "my-app": 5 },
260
+ "facts": [{ "tag": "TaskCreated", "payload": { "id": "1" } }],
261
+ "events": [],
262
+ "timestamp": 1234567890
263
+ }
264
+ ```
265
+
266
+ **Response:**
267
+ ```json
268
+ {
269
+ "success": true,
270
+ "clock": { "my-app": 5 },
271
+ "timestamp": 1234567890
272
+ }
273
+ ```
274
+
275
+ ### `GET /usage?appId=<appId>`
276
+
277
+ Usage metrics endpoint.
278
+
279
+ **Response:**
280
+ ```json
281
+ {
282
+ "appId": "my-app",
283
+ "syncCount": 100,
284
+ "eventCount": 500,
285
+ "factCount": 1000,
286
+ "storageBytes": 102400,
287
+ "periodStart": 1234567890,
288
+ "periodEnd": 1234567890
289
+ }
290
+ ```
291
+
292
+ ### `GET /stats?appId=<appId>`
293
+
294
+ Aggregated statistics endpoint.
295
+
296
+ **Response:**
297
+ ```json
298
+ {
299
+ "appId": "my-app",
300
+ "totalSyncs": 100,
301
+ "usage": { ... },
302
+ "lastSync": 1234567890
303
+ }
304
+ ```
305
+
306
+ ## Authentication
307
+
308
+ Praxis Cloud uses GitHub OAuth for authentication.
309
+
310
+ ### Device Flow (CLI)
311
+
312
+ The CLI uses GitHub's device flow for authentication:
313
+
314
+ ```typescript
315
+ import { authenticateWithDeviceFlow } from "@plures/praxis/cloud";
316
+
317
+ const result = await authenticateWithDeviceFlow("your-client-id");
318
+
319
+ if (result.success) {
320
+ console.log(`Authenticated as ${result.user.login}`);
321
+ console.log(`Token: ${result.token}`);
322
+ }
323
+ ```
324
+
325
+ ### Web Flow
326
+
327
+ For web applications, use the standard OAuth flow:
328
+
329
+ ```typescript
330
+ import { createGitHubOAuth } from "@plures/praxis/cloud";
331
+
332
+ const oauth = createGitHubOAuth({
333
+ clientId: "your-client-id",
334
+ clientSecret: "your-client-secret",
335
+ redirectUri: "http://localhost:3000/callback"
336
+ });
337
+
338
+ // Redirect user to GitHub
339
+ window.location.href = oauth.getAuthorizationUrl();
340
+
341
+ // In callback route:
342
+ const result = await oauth.exchangeCode(code);
343
+ ```
344
+
345
+ ## Configuration File
346
+
347
+ The `.praxis-cloud.json` file stores your cloud configuration:
348
+
349
+ ```json
350
+ {
351
+ "endpoint": "https://praxis-relay.azurewebsites.net",
352
+ "appId": "my-app",
353
+ "authToken": "gho_xxxxxxxxxxxx",
354
+ "autoSync": true,
355
+ "syncInterval": 5000
356
+ }
357
+ ```
358
+
359
+ **Security Note:** Add `.praxis-cloud.json` to your `.gitignore` to avoid committing credentials.
360
+
361
+ ## Pricing (Tier 1)
362
+
363
+ Praxis Cloud Base Tier includes:
364
+
365
+ - **PluresDB CRDT Sync** - Up to 10,000 syncs/month
366
+ - **Event Forwarding** - Up to 50,000 events/month
367
+ - **Blob Storage** - 1 GB encrypted storage
368
+ - **GitHub OAuth** - Unlimited authentications
369
+
370
+ Additional usage is billed per:
371
+ - Sync operations: $0.001 per sync
372
+ - Events: $0.0001 per event
373
+ - Storage: $0.10 per GB/month
374
+
375
+ ## Support
376
+
377
+ - [Documentation](https://github.com/plures/praxis/tree/main/docs)
378
+ - [GitHub Issues](https://github.com/plures/praxis/issues)
379
+ - [Discussions](https://github.com/plures/praxis/discussions)
380
+
381
+ ## License
382
+
383
+ MIT
@@ -0,0 +1,245 @@
1
+ /**
2
+ * GitHub OAuth Authentication
3
+ *
4
+ * GitHub OAuth integration for Praxis Cloud Relay identity.
5
+ */
6
+
7
+ import type { AuthResult, GitHubUser } from "./types.js";
8
+
9
+ /**
10
+ * GitHub OAuth configuration
11
+ */
12
+ export interface GitHubOAuthConfig {
13
+ clientId: string;
14
+ clientSecret?: string;
15
+ redirectUri?: string;
16
+ scope?: string;
17
+ }
18
+
19
+ /**
20
+ * GitHub OAuth client
21
+ */
22
+ export class GitHubOAuth {
23
+ private config: GitHubOAuthConfig;
24
+
25
+ constructor(config: GitHubOAuthConfig) {
26
+ this.config = config;
27
+ }
28
+
29
+ /**
30
+ * Get the OAuth authorization URL
31
+ */
32
+ getAuthorizationUrl(state?: string): string {
33
+ const params = new URLSearchParams({
34
+ client_id: this.config.clientId,
35
+ scope: this.config.scope || "read:user user:email",
36
+ state: state || this.generateState(),
37
+ });
38
+
39
+ if (this.config.redirectUri) {
40
+ params.set("redirect_uri", this.config.redirectUri);
41
+ }
42
+
43
+ return `https://github.com/login/oauth/authorize?${params.toString()}`;
44
+ }
45
+
46
+ /**
47
+ * Exchange authorization code for access token
48
+ */
49
+ async exchangeCode(code: string): Promise<AuthResult> {
50
+ if (!this.config.clientSecret) {
51
+ throw new Error("Client secret is required for code exchange");
52
+ }
53
+
54
+ try {
55
+ const response = await fetch(
56
+ "https://github.com/login/oauth/access_token",
57
+ {
58
+ method: "POST",
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ Accept: "application/json",
62
+ },
63
+ body: JSON.stringify({
64
+ client_id: this.config.clientId,
65
+ client_secret: this.config.clientSecret,
66
+ code,
67
+ }),
68
+ }
69
+ );
70
+
71
+ if (!response.ok) {
72
+ throw new Error(`Token exchange failed: ${response.statusText}`);
73
+ }
74
+
75
+ const data = await response.json() as any;
76
+
77
+ if (data.error) {
78
+ throw new Error(`GitHub OAuth error: ${data.error_description || data.error}`);
79
+ }
80
+
81
+ // Get user info
82
+ const user = await this.getUserInfo(data.access_token);
83
+
84
+ return {
85
+ success: true,
86
+ token: data.access_token,
87
+ user,
88
+ expiresAt: data.expires_in
89
+ ? Date.now() + data.expires_in * 1000
90
+ : undefined,
91
+ };
92
+ } catch (error) {
93
+ return {
94
+ success: false,
95
+ };
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get user information from GitHub
101
+ */
102
+ async getUserInfo(token: string): Promise<GitHubUser> {
103
+ const response = await fetch("https://api.github.com/user", {
104
+ headers: {
105
+ Authorization: `Bearer ${token}`,
106
+ Accept: "application/vnd.github.v3+json",
107
+ },
108
+ });
109
+
110
+ if (!response.ok) {
111
+ throw new Error(`Failed to get user info: ${response.statusText}`);
112
+ }
113
+
114
+ const data = await response.json() as any;
115
+
116
+ return {
117
+ id: data.id,
118
+ login: data.login,
119
+ email: data.email,
120
+ name: data.name,
121
+ avatarUrl: data.avatar_url,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Verify a token is valid
127
+ */
128
+ async verifyToken(token: string): Promise<boolean> {
129
+ try {
130
+ await this.getUserInfo(token);
131
+ return true;
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Generate a random state parameter for CSRF protection
139
+ */
140
+ private generateState(): string {
141
+ const array = new Uint8Array(16);
142
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
143
+ crypto.getRandomValues(array);
144
+ } else {
145
+ // Fallback for Node.js
146
+ for (let i = 0; i < array.length; i++) {
147
+ array[i] = Math.floor(Math.random() * 256);
148
+ }
149
+ }
150
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
151
+ ""
152
+ );
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Create a GitHub OAuth client
158
+ */
159
+ export function createGitHubOAuth(config: GitHubOAuthConfig): GitHubOAuth {
160
+ return new GitHubOAuth(config);
161
+ }
162
+
163
+ /**
164
+ * Authenticate with GitHub OAuth device flow (for CLI)
165
+ */
166
+ export async function authenticateWithDeviceFlow(
167
+ clientId: string
168
+ ): Promise<AuthResult> {
169
+ try {
170
+ // Request device code
171
+ const deviceResponse = await fetch(
172
+ "https://github.com/login/device/code",
173
+ {
174
+ method: "POST",
175
+ headers: {
176
+ "Content-Type": "application/json",
177
+ Accept: "application/json",
178
+ },
179
+ body: JSON.stringify({
180
+ client_id: clientId,
181
+ scope: "read:user user:email",
182
+ }),
183
+ }
184
+ );
185
+
186
+ if (!deviceResponse.ok) {
187
+ throw new Error(`Device flow initiation failed: ${deviceResponse.statusText}`);
188
+ }
189
+
190
+ const deviceData = await deviceResponse.json() as any;
191
+
192
+ console.log("\nTo authenticate with GitHub:");
193
+ console.log(`1. Visit: ${deviceData.verification_uri}`);
194
+ console.log(`2. Enter code: ${deviceData.user_code}`);
195
+ console.log("\nWaiting for authentication...\n");
196
+
197
+ // Poll for access token
198
+ const interval = deviceData.interval * 1000 || 5000;
199
+ const expiresAt = Date.now() + deviceData.expires_in * 1000;
200
+
201
+ while (Date.now() < expiresAt) {
202
+ await new Promise((resolve) => setTimeout(resolve, interval));
203
+
204
+ const tokenResponse = await fetch(
205
+ "https://github.com/login/oauth/access_token",
206
+ {
207
+ method: "POST",
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ Accept: "application/json",
211
+ },
212
+ body: JSON.stringify({
213
+ client_id: clientId,
214
+ device_code: deviceData.device_code,
215
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
216
+ }),
217
+ }
218
+ );
219
+
220
+ const tokenData = await tokenResponse.json() as any;
221
+
222
+ if (tokenData.access_token) {
223
+ // Get user info
224
+ const oauth = new GitHubOAuth({ clientId });
225
+ const user = await oauth.getUserInfo(tokenData.access_token);
226
+
227
+ return {
228
+ success: true,
229
+ token: tokenData.access_token,
230
+ user,
231
+ };
232
+ }
233
+
234
+ if (tokenData.error && tokenData.error !== "authorization_pending") {
235
+ throw new Error(`Authentication failed: ${tokenData.error_description || tokenData.error}`);
236
+ }
237
+ }
238
+
239
+ throw new Error("Authentication timeout");
240
+ } catch (error) {
241
+ return {
242
+ success: false,
243
+ };
244
+ }
245
+ }