@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,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
|
+
}
|