@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,432 @@
1
+ /**
2
+ * Praxis Component Generator
3
+ *
4
+ * Generates Svelte components from schema definitions.
5
+ */
6
+
7
+ import type { ComponentDefinition, ModelDefinition } from '../schema/types.js';
8
+
9
+ /**
10
+ * Generator configuration
11
+ */
12
+ export interface GeneratorConfig {
13
+ /** Output directory */
14
+ outputDir: string;
15
+ /** Component framework */
16
+ framework: 'svelte' | 'react' | 'vue';
17
+ /** TypeScript support */
18
+ typescript: boolean;
19
+ /** Include tests */
20
+ includeTests: boolean;
21
+ /** Include documentation */
22
+ includeDocs: boolean;
23
+ }
24
+
25
+ /**
26
+ * Generation result
27
+ */
28
+ export interface GenerationResult {
29
+ /** Success status */
30
+ success: boolean;
31
+ /** Generated files */
32
+ files: GeneratedFile[];
33
+ /** Generation errors */
34
+ errors: GenerationError[];
35
+ }
36
+
37
+ /**
38
+ * Generated file
39
+ */
40
+ export interface GeneratedFile {
41
+ /** File path */
42
+ path: string;
43
+ /** File content */
44
+ content: string;
45
+ /** File type */
46
+ type: 'component' | 'test' | 'docs' | 'types';
47
+ }
48
+
49
+ /**
50
+ * Generation error
51
+ */
52
+ export interface GenerationError {
53
+ /** Error message */
54
+ message: string;
55
+ /** Component name */
56
+ component?: string;
57
+ /** Error code */
58
+ code?: string;
59
+ }
60
+
61
+ /**
62
+ * Component generator class
63
+ */
64
+ export class ComponentGenerator {
65
+ private config: GeneratorConfig;
66
+
67
+ constructor(config: GeneratorConfig) {
68
+ this.config = config;
69
+ }
70
+
71
+ /**
72
+ * Generate component from definition
73
+ */
74
+ generateComponent(
75
+ component: ComponentDefinition,
76
+ model?: ModelDefinition
77
+ ): GenerationResult {
78
+ const files: GeneratedFile[] = [];
79
+ const errors: GenerationError[] = [];
80
+
81
+ try {
82
+ // Generate main component file
83
+ const componentFile = this.generateComponentFile(component, model);
84
+ files.push(componentFile);
85
+
86
+ // Generate TypeScript types if enabled
87
+ if (this.config.typescript) {
88
+ const typesFile = this.generateTypesFile(component, model);
89
+ files.push(typesFile);
90
+ }
91
+
92
+ // Generate tests if enabled
93
+ if (this.config.includeTests) {
94
+ const testFile = this.generateTestFile(component);
95
+ files.push(testFile);
96
+ }
97
+
98
+ // Generate docs if enabled
99
+ if (this.config.includeDocs) {
100
+ const docsFile = this.generateDocsFile(component);
101
+ files.push(docsFile);
102
+ }
103
+ } catch (error) {
104
+ errors.push({
105
+ message: error instanceof Error ? error.message : 'Unknown error',
106
+ component: component.name,
107
+ });
108
+ }
109
+
110
+ return {
111
+ success: errors.length === 0,
112
+ files,
113
+ errors,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Generate component file
119
+ */
120
+ private generateComponentFile(
121
+ component: ComponentDefinition,
122
+ model?: ModelDefinition
123
+ ): GeneratedFile {
124
+ // Note: ext could be used for file naming in future versions
125
+ // const ext = this.config.typescript ? 'ts' : 'js';
126
+ const content = this.generateSvelteComponent(component, model);
127
+
128
+ return {
129
+ path: `${this.config.outputDir}/${component.name}.svelte`,
130
+ content,
131
+ type: 'component',
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Generate Svelte component code
137
+ */
138
+ private generateSvelteComponent(
139
+ component: ComponentDefinition,
140
+ model?: ModelDefinition
141
+ ): string {
142
+ const script = this.generateScript(component, model);
143
+ const template = this.generateTemplate(component, model);
144
+ const styles = this.generateStyles(component);
145
+
146
+ return `<script${this.config.typescript ? ' lang="ts"' : ''}>
147
+ ${script}
148
+ </script>
149
+
150
+ ${template}
151
+
152
+ ${styles ? `<style>\n${styles}\n</style>` : ''}`;
153
+ }
154
+
155
+ /**
156
+ * Generate component script
157
+ */
158
+ private generateScript(
159
+ component: ComponentDefinition,
160
+ _model?: ModelDefinition // Prefix with _ to indicate intentionally unused
161
+ ): string {
162
+ const lines: string[] = [];
163
+
164
+ // Import statements
165
+ lines.push(` import { createPraxisStore } from '@plures/praxis/svelte';`);
166
+ lines.push('');
167
+
168
+ // Props
169
+ if (component.props && component.props.length > 0) {
170
+ component.props.forEach((prop) => {
171
+ const typeAnnotation = this.config.typescript ? `: ${prop.type}` : '';
172
+ const defaultValue =
173
+ prop.default !== undefined ? ` = ${JSON.stringify(prop.default)}` : '';
174
+ lines.push(
175
+ ` export let ${prop.name}${typeAnnotation}${defaultValue};`
176
+ );
177
+ });
178
+ lines.push('');
179
+ }
180
+
181
+ // Component logic placeholder
182
+ lines.push(' // Component logic will be generated based on schema');
183
+ lines.push(' // TODO: Implement component behavior');
184
+
185
+ return lines.join('\n');
186
+ }
187
+
188
+ /**
189
+ * Generate component template
190
+ */
191
+ private generateTemplate(
192
+ component: ComponentDefinition,
193
+ model?: ModelDefinition
194
+ ): string {
195
+ switch (component.type) {
196
+ case 'form':
197
+ return this.generateFormTemplate(component, model);
198
+ case 'display':
199
+ return this.generateDisplayTemplate(component, model);
200
+ case 'list':
201
+ return this.generateListTemplate(component, model);
202
+ case 'navigation':
203
+ return this.generateNavigationTemplate(component);
204
+ default:
205
+ return `<div class="${component.name.toLowerCase()}">\n <!-- ${component.description || component.name} -->\n <p>Component: ${component.name}</p>\n</div>`;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Generate form template
211
+ */
212
+ private generateFormTemplate(
213
+ component: ComponentDefinition,
214
+ model?: ModelDefinition
215
+ ): string {
216
+ const fields = model?.fields || [];
217
+ const formFields = fields
218
+ .map((field) => {
219
+ return ` <label>\n ${field.name}\n <input type="text" bind:value={data.${field.name}} />\n </label>`;
220
+ })
221
+ .join('\n');
222
+
223
+ return `<form class="${component.name.toLowerCase()}">\n${formFields}\n <button type="submit">Submit</button>\n</form>`;
224
+ }
225
+
226
+ /**
227
+ * Generate display template
228
+ */
229
+ private generateDisplayTemplate(
230
+ component: ComponentDefinition,
231
+ model?: ModelDefinition
232
+ ): string {
233
+ const fields = model?.fields || [];
234
+ const displayFields = fields
235
+ .map((field) => {
236
+ return ` <div class="field">\n <strong>${field.name}:</strong> {data.${field.name}}\n </div>`;
237
+ })
238
+ .join('\n');
239
+
240
+ return `<div class="${component.name.toLowerCase()}">\n${displayFields}\n</div>`;
241
+ }
242
+
243
+ /**
244
+ * Generate list template
245
+ */
246
+ private generateListTemplate(
247
+ component: ComponentDefinition,
248
+ _model?: ModelDefinition // Prefix with _ to indicate intentionally unused
249
+ ): string {
250
+ return `<div class="${component.name.toLowerCase()}">\n {#each items as item}\n <div class="item">{item.name}</div>\n {/each}\n</div>`;
251
+ }
252
+
253
+ /**
254
+ * Generate navigation template
255
+ */
256
+ private generateNavigationTemplate(component: ComponentDefinition): string {
257
+ return `<nav class="${component.name.toLowerCase()}">\n <ul>\n <li><a href="/">Home</a></li>\n <li><a href="/about">About</a></li>\n </ul>\n</nav>`;
258
+ }
259
+
260
+ /**
261
+ * Generate component styles
262
+ */
263
+ private generateStyles(component: ComponentDefinition): string {
264
+ if (!component.styling) {
265
+ return '';
266
+ }
267
+
268
+ const lines: string[] = [];
269
+ const styles = component.styling.styles || {};
270
+
271
+ Object.entries(styles).forEach(([key, value]) => {
272
+ lines.push(` ${key}: ${value};`);
273
+ });
274
+
275
+ return lines.length > 0 ? lines.join('\n') : '';
276
+ }
277
+
278
+ /**
279
+ * Generate types file
280
+ */
281
+ private generateTypesFile(
282
+ component: ComponentDefinition,
283
+ model?: ModelDefinition
284
+ ): GeneratedFile {
285
+ const lines: string[] = [];
286
+
287
+ // Component props type
288
+ if (component.props && component.props.length > 0) {
289
+ lines.push(`export interface ${component.name}Props {`);
290
+ component.props.forEach((prop) => {
291
+ const optional = prop.required ? '' : '?';
292
+ lines.push(` ${prop.name}${optional}: ${prop.type};`);
293
+ });
294
+ lines.push('}');
295
+ lines.push('');
296
+ }
297
+
298
+ // Model type if available
299
+ if (model) {
300
+ lines.push(`export interface ${model.name} {`);
301
+ model.fields.forEach((field) => {
302
+ const optional = field.optional ? '?' : '';
303
+ lines.push(` ${field.name}${optional}: ${this.mapFieldType(field.type)};`);
304
+ });
305
+ lines.push('}');
306
+ }
307
+
308
+ return {
309
+ path: `${this.config.outputDir}/${component.name}.types.ts`,
310
+ content: lines.join('\n'),
311
+ type: 'types',
312
+ };
313
+ }
314
+
315
+ /**
316
+ * Map field type to TypeScript type
317
+ */
318
+ private mapFieldType(type: any): string {
319
+ if (typeof type === 'string') {
320
+ switch (type) {
321
+ case 'string':
322
+ return 'string';
323
+ case 'number':
324
+ return 'number';
325
+ case 'boolean':
326
+ return 'boolean';
327
+ case 'date':
328
+ return 'Date';
329
+ case 'array':
330
+ return 'unknown[]';
331
+ case 'object':
332
+ return 'Record<string, unknown>';
333
+ default:
334
+ return 'unknown';
335
+ }
336
+ }
337
+ return 'unknown';
338
+ }
339
+
340
+ /**
341
+ * Generate test file
342
+ */
343
+ private generateTestFile(component: ComponentDefinition): GeneratedFile {
344
+ const content = `import { describe, it, expect } from 'vitest';
345
+ import { render } from '@testing-library/svelte';
346
+ import ${component.name} from './${component.name}.svelte';
347
+
348
+ describe('${component.name}', () => {
349
+ it('renders correctly', () => {
350
+ const { container } = render(${component.name});
351
+ expect(container).toBeTruthy();
352
+ });
353
+
354
+ // TODO: Add more tests based on component behavior
355
+ });
356
+ `;
357
+
358
+ return {
359
+ path: `${this.config.outputDir}/${component.name}.test.ts`,
360
+ content,
361
+ type: 'test',
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Generate documentation file
367
+ */
368
+ private generateDocsFile(component: ComponentDefinition): GeneratedFile {
369
+ const lines: string[] = [];
370
+
371
+ lines.push(`# ${component.name}`);
372
+ lines.push('');
373
+ if (component.description) {
374
+ lines.push(component.description);
375
+ lines.push('');
376
+ }
377
+
378
+ lines.push('## Props');
379
+ lines.push('');
380
+ if (component.props && component.props.length > 0) {
381
+ component.props.forEach((prop) => {
382
+ lines.push(
383
+ `- \`${prop.name}\`: ${prop.type}${prop.required ? ' (required)' : ' (optional)'}`
384
+ );
385
+ if (prop.description) {
386
+ lines.push(` - ${prop.description}`);
387
+ }
388
+ });
389
+ } else {
390
+ lines.push('No props defined.');
391
+ }
392
+
393
+ lines.push('');
394
+ lines.push('## Events');
395
+ lines.push('');
396
+ if (component.events && component.events.length > 0) {
397
+ component.events.forEach((event) => {
398
+ lines.push(`- \`${event.name}\`: ${event.payload || 'void'}`);
399
+ if (event.description) {
400
+ lines.push(` - ${event.description}`);
401
+ }
402
+ });
403
+ } else {
404
+ lines.push('No events defined.');
405
+ }
406
+
407
+ return {
408
+ path: `${this.config.outputDir}/${component.name}.md`,
409
+ content: lines.join('\n'),
410
+ type: 'docs',
411
+ };
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Create a component generator with default configuration
417
+ */
418
+ export function createComponentGenerator(
419
+ outputDir: string,
420
+ options?: Partial<GeneratorConfig>
421
+ ): ComponentGenerator {
422
+ const config: GeneratorConfig = {
423
+ outputDir,
424
+ framework: 'svelte',
425
+ typescript: true,
426
+ includeTests: false,
427
+ includeDocs: false,
428
+ ...options,
429
+ };
430
+
431
+ return new ComponentGenerator(config);
432
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Praxis Logic Engine
3
+ *
4
+ * The logic engine manages state, processes events through rules,
5
+ * checks constraints, and provides a strongly-typed API for application logic.
6
+ */
7
+
8
+ import type {
9
+ PraxisEvent,
10
+ PraxisFact,
11
+ PraxisState,
12
+ PraxisStepConfig,
13
+ PraxisStepResult,
14
+ PraxisDiagnostics,
15
+ } from "./protocol.js";
16
+ import { PRAXIS_PROTOCOL_VERSION } from "./protocol.js";
17
+ import { PraxisRegistry } from "./rules.js";
18
+
19
+ /**
20
+ * Options for creating a Praxis engine
21
+ */
22
+ export interface PraxisEngineOptions<TContext = unknown> {
23
+ /** Initial context */
24
+ initialContext: TContext;
25
+ /** Registry of rules and constraints */
26
+ registry: PraxisRegistry<TContext>;
27
+ /** Initial facts (optional) */
28
+ initialFacts?: PraxisFact[];
29
+ /** Initial metadata (optional) */
30
+ initialMeta?: Record<string, unknown>;
31
+ }
32
+
33
+ /**
34
+ * The Praxis Logic Engine
35
+ *
36
+ * Manages application logic through facts, events, rules, and constraints.
37
+ * The engine is strongly typed and functional - all state updates are immutable.
38
+ */
39
+ export class LogicEngine<TContext = unknown> {
40
+ private state: PraxisState & { context: TContext };
41
+ private readonly registry: PraxisRegistry<TContext>;
42
+
43
+ constructor(options: PraxisEngineOptions<TContext>) {
44
+ this.registry = options.registry;
45
+ this.state = {
46
+ context: options.initialContext,
47
+ facts: options.initialFacts ?? [],
48
+ meta: options.initialMeta ?? {},
49
+ protocolVersion: PRAXIS_PROTOCOL_VERSION,
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Get the current state (immutable copy)
55
+ */
56
+ getState(): Readonly<PraxisState & { context: TContext }> {
57
+ return {
58
+ context: structuredClone(this.state.context),
59
+ facts: [...this.state.facts],
60
+ meta: this.state.meta ? { ...this.state.meta } : undefined,
61
+ protocolVersion: this.state.protocolVersion,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Get the current context
67
+ */
68
+ getContext(): TContext {
69
+ return structuredClone(this.state.context);
70
+ }
71
+
72
+ /**
73
+ * Get current facts
74
+ */
75
+ getFacts(): PraxisFact[] {
76
+ return [...this.state.facts];
77
+ }
78
+
79
+ /**
80
+ * Process events through the engine.
81
+ * Applies all registered rules and checks all registered constraints.
82
+ *
83
+ * @param events Events to process
84
+ * @returns Result with new state and diagnostics
85
+ */
86
+ step(events: PraxisEvent[]): PraxisStepResult {
87
+ const config: PraxisStepConfig = {
88
+ ruleIds: this.registry.getRuleIds(),
89
+ constraintIds: this.registry.getConstraintIds(),
90
+ };
91
+ return this.stepWithConfig(events, config);
92
+ }
93
+
94
+ /**
95
+ * Process events with specific rule and constraint configuration.
96
+ *
97
+ * @param events Events to process
98
+ * @param config Step configuration
99
+ * @returns Result with new state and diagnostics
100
+ */
101
+ stepWithConfig(
102
+ events: PraxisEvent[],
103
+ config: PraxisStepConfig
104
+ ): PraxisStepResult {
105
+ const diagnostics: PraxisDiagnostics[] = [];
106
+ let newState = { ...this.state };
107
+
108
+ // Apply rules
109
+ const newFacts: PraxisFact[] = [];
110
+ for (const ruleId of config.ruleIds) {
111
+ const rule = this.registry.getRule(ruleId);
112
+ if (!rule) {
113
+ diagnostics.push({
114
+ kind: "rule-error",
115
+ message: `Rule "${ruleId}" not found in registry`,
116
+ data: { ruleId },
117
+ });
118
+ continue;
119
+ }
120
+
121
+ try {
122
+ const ruleFacts = rule.impl(newState, events);
123
+ newFacts.push(...ruleFacts);
124
+ } catch (error) {
125
+ diagnostics.push({
126
+ kind: "rule-error",
127
+ message: `Error executing rule "${ruleId}": ${error instanceof Error ? error.message : String(error)}`,
128
+ data: { ruleId, error },
129
+ });
130
+ }
131
+ }
132
+
133
+ // Add new facts to state
134
+ newState = {
135
+ ...newState,
136
+ facts: [...newState.facts, ...newFacts],
137
+ };
138
+
139
+ // Check constraints
140
+ for (const constraintId of config.constraintIds) {
141
+ const constraint = this.registry.getConstraint(constraintId);
142
+ if (!constraint) {
143
+ diagnostics.push({
144
+ kind: "constraint-violation",
145
+ message: `Constraint "${constraintId}" not found in registry`,
146
+ data: { constraintId },
147
+ });
148
+ continue;
149
+ }
150
+
151
+ try {
152
+ const result = constraint.impl(newState);
153
+ if (result === false) {
154
+ diagnostics.push({
155
+ kind: "constraint-violation",
156
+ message: `Constraint "${constraintId}" violated`,
157
+ data: { constraintId, description: constraint.description },
158
+ });
159
+ } else if (typeof result === "string") {
160
+ diagnostics.push({
161
+ kind: "constraint-violation",
162
+ message: result,
163
+ data: { constraintId, description: constraint.description },
164
+ });
165
+ }
166
+ } catch (error) {
167
+ diagnostics.push({
168
+ kind: "constraint-violation",
169
+ message: `Error checking constraint "${constraintId}": ${error instanceof Error ? error.message : String(error)}`,
170
+ data: { constraintId, error },
171
+ });
172
+ }
173
+ }
174
+
175
+ // Update internal state
176
+ this.state = newState;
177
+
178
+ return {
179
+ state: newState,
180
+ diagnostics,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Update the context directly (for exceptional cases).
186
+ * Generally, context should be updated through rules.
187
+ *
188
+ * @param updater Function that produces new context from old context
189
+ */
190
+ updateContext(updater: (context: TContext) => TContext): void {
191
+ this.state = {
192
+ ...this.state,
193
+ context: updater(this.state.context),
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Add facts directly (for exceptional cases).
199
+ * Generally, facts should be added through rules.
200
+ *
201
+ * @param facts Facts to add
202
+ */
203
+ addFacts(facts: PraxisFact[]): void {
204
+ this.state = {
205
+ ...this.state,
206
+ facts: [...this.state.facts, ...facts],
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Clear all facts
212
+ */
213
+ clearFacts(): void {
214
+ this.state = {
215
+ ...this.state,
216
+ facts: [],
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Reset the engine to initial state
222
+ */
223
+ reset(options: PraxisEngineOptions<TContext>): void {
224
+ this.state = {
225
+ context: options.initialContext,
226
+ facts: options.initialFacts ?? [],
227
+ meta: options.initialMeta ?? {},
228
+ protocolVersion: PRAXIS_PROTOCOL_VERSION,
229
+ };
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Create a new Praxis logic engine.
235
+ *
236
+ * @param options Engine options
237
+ * @returns New LogicEngine instance
238
+ */
239
+ export function createPraxisEngine<TContext = unknown>(
240
+ options: PraxisEngineOptions<TContext>
241
+ ): LogicEngine<TContext> {
242
+ return new LogicEngine(options);
243
+ }