@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,132 @@
1
+ /**
2
+ * Core Praxis Protocol
3
+ *
4
+ * Language-neutral, JSON-friendly protocol that forms the foundation of Praxis.
5
+ * This protocol is designed to be stable and portable across languages (TypeScript, C#, PowerShell, etc.)
6
+ *
7
+ * The protocol defines the conceptual core of the engine:
8
+ * - Pure, deterministic, data in → data out
9
+ * - No side effects, no global state
10
+ * - All higher-level TypeScript APIs are built on top of this protocol
11
+ *
12
+ * ## Protocol Versioning
13
+ *
14
+ * The Praxis protocol follows semantic versioning (MAJOR.MINOR.PATCH):
15
+ * - MAJOR: Breaking changes to core protocol types or semantics
16
+ * - MINOR: Backward-compatible additions to protocol (new optional fields)
17
+ * - PATCH: Clarifications, documentation updates, no functional changes
18
+ *
19
+ * Current version: 1.0.0
20
+ *
21
+ * ### Stability Guarantees
22
+ *
23
+ * 1. **Core Types Stability**: The following types are considered stable and will not
24
+ * change in backward-incompatible ways within the same major version:
25
+ * - PraxisFact (tag, payload structure)
26
+ * - PraxisEvent (tag, payload structure)
27
+ * - PraxisState (context, facts, meta structure)
28
+ * - PraxisStepFn signature
29
+ *
30
+ * 2. **JSON Compatibility**: All protocol types will remain JSON-serializable.
31
+ * No non-JSON-safe types (functions, symbols, etc.) will be added to the protocol.
32
+ *
33
+ * 3. **Cross-Language Compatibility**: Protocol changes will be coordinated across
34
+ * all official language implementations (TypeScript, C#, PowerShell) to ensure
35
+ * interoperability.
36
+ *
37
+ * 4. **Migration Path**: Major version changes will be accompanied by:
38
+ * - Migration guide
39
+ * - Deprecation warnings in previous version
40
+ * - Compatibility shims where possible
41
+ */
42
+
43
+ /**
44
+ * Protocol version following semantic versioning
45
+ */
46
+ export const PRAXIS_PROTOCOL_VERSION = "1.0.0" as const;
47
+
48
+ /**
49
+ * A fact is a typed proposition about the domain.
50
+ * Examples: UserLoggedIn, CartItem, NetworkOnline
51
+ */
52
+ export interface PraxisFact {
53
+ /** Tag identifying the fact type */
54
+ tag: string;
55
+ /** Payload containing the fact data */
56
+ payload: unknown;
57
+ }
58
+
59
+ /**
60
+ * An event is a temporally ordered fact meant to drive change.
61
+ * Examples: LOGIN, LOGOUT, ADD_TO_CART
62
+ */
63
+ export interface PraxisEvent {
64
+ /** Tag identifying the event type */
65
+ tag: string;
66
+ /** Payload containing the event data */
67
+ payload: unknown;
68
+ }
69
+
70
+ /**
71
+ * The state of the Praxis engine at a point in time.
72
+ */
73
+ export interface PraxisState {
74
+ /** Application context (domain-specific data) */
75
+ context: unknown;
76
+ /** Current facts about the domain */
77
+ facts: PraxisFact[];
78
+ /** Optional metadata (timestamps, version, etc.) */
79
+ meta?: Record<string, unknown>;
80
+ /** Protocol version (for cross-language compatibility) */
81
+ protocolVersion?: string;
82
+ }
83
+
84
+ /**
85
+ * Diagnostic information about constraint violations or rule errors.
86
+ */
87
+ export interface PraxisDiagnostics {
88
+ /** Kind of diagnostic */
89
+ kind: "constraint-violation" | "rule-error";
90
+ /** Human-readable message */
91
+ message: string;
92
+ /** Additional diagnostic data */
93
+ data?: unknown;
94
+ }
95
+
96
+ /**
97
+ * Configuration for a step execution.
98
+ * Specifies which rules and constraints to apply.
99
+ */
100
+ export interface PraxisStepConfig {
101
+ /** IDs of rules to apply during this step */
102
+ ruleIds: string[];
103
+ /** IDs of constraints to check during this step */
104
+ constraintIds: string[];
105
+ }
106
+
107
+ /**
108
+ * Result of a step execution.
109
+ */
110
+ export interface PraxisStepResult {
111
+ /** New state after applying rules and checking constraints */
112
+ state: PraxisState;
113
+ /** Diagnostics from rule execution and constraint checking */
114
+ diagnostics: PraxisDiagnostics[];
115
+ }
116
+
117
+ /**
118
+ * The core step function of the Praxis engine.
119
+ *
120
+ * This is the conceptual heart of the engine:
121
+ * - Takes current state, events, and configuration
122
+ * - Applies rules and checks constraints
123
+ * - Returns new state and diagnostics
124
+ *
125
+ * Pure, deterministic, data in → data out.
126
+ * No side effects, no global state.
127
+ */
128
+ export type PraxisStepFn = (
129
+ state: PraxisState,
130
+ events: PraxisEvent[],
131
+ config: PraxisStepConfig
132
+ ) => PraxisStepResult;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Rules and Constraints System
3
+ *
4
+ * This module defines the types and registry for rules and constraints.
5
+ * Rules and constraints are identified by stable IDs and can be described as data,
6
+ * making them portable across languages and suitable for DSL-based definitions.
7
+ */
8
+
9
+ import type { PraxisEvent, PraxisFact, PraxisState } from "./protocol.js";
10
+
11
+ /**
12
+ * Unique identifier for a rule
13
+ */
14
+ export type RuleId = string;
15
+
16
+ /**
17
+ * Unique identifier for a constraint
18
+ */
19
+ export type ConstraintId = string;
20
+
21
+ /**
22
+ * A rule function derives new facts or transitions from context + input facts/events.
23
+ * Rules must be pure - no side effects.
24
+ *
25
+ * @param state Current Praxis state
26
+ * @param events Events to process
27
+ * @returns Array of new facts to add to the state
28
+ */
29
+ export type RuleFn<TContext = unknown> = (
30
+ state: PraxisState & { context: TContext },
31
+ events: PraxisEvent[]
32
+ ) => PraxisFact[];
33
+
34
+ /**
35
+ * A constraint function checks that an invariant holds.
36
+ * Constraints must be pure - no side effects.
37
+ *
38
+ * @param state Current Praxis state
39
+ * @returns true if constraint is satisfied, false or error message if violated
40
+ */
41
+ export type ConstraintFn<TContext = unknown> = (
42
+ state: PraxisState & { context: TContext }
43
+ ) => boolean | string;
44
+
45
+ /**
46
+ * Descriptor for a rule, including its ID, description, and implementation.
47
+ */
48
+ export interface RuleDescriptor<TContext = unknown> {
49
+ /** Unique identifier for the rule */
50
+ id: RuleId;
51
+ /** Human-readable description */
52
+ description: string;
53
+ /** Implementation function */
54
+ impl: RuleFn<TContext>;
55
+ /** Optional metadata */
56
+ meta?: Record<string, unknown>;
57
+ }
58
+
59
+ /**
60
+ * Descriptor for a constraint, including its ID, description, and implementation.
61
+ */
62
+ export interface ConstraintDescriptor<TContext = unknown> {
63
+ /** Unique identifier for the constraint */
64
+ id: ConstraintId;
65
+ /** Human-readable description */
66
+ description: string;
67
+ /** Implementation function */
68
+ impl: ConstraintFn<TContext>;
69
+ /** Optional metadata */
70
+ meta?: Record<string, unknown>;
71
+ }
72
+
73
+ /**
74
+ * A Praxis module bundles rules and constraints.
75
+ * Modules can be composed and registered with the engine.
76
+ */
77
+ export interface PraxisModule<TContext = unknown> {
78
+ /** Rules in this module */
79
+ rules: RuleDescriptor<TContext>[];
80
+ /** Constraints in this module */
81
+ constraints: ConstraintDescriptor<TContext>[];
82
+ /** Optional module metadata */
83
+ meta?: Record<string, unknown>;
84
+ }
85
+
86
+ /**
87
+ * Registry for rules and constraints.
88
+ * Maps IDs to their descriptors.
89
+ */
90
+ export class PraxisRegistry<TContext = unknown> {
91
+ private rules = new Map<RuleId, RuleDescriptor<TContext>>();
92
+ private constraints = new Map<ConstraintId, ConstraintDescriptor<TContext>>();
93
+
94
+ /**
95
+ * Register a rule
96
+ */
97
+ registerRule(descriptor: RuleDescriptor<TContext>): void {
98
+ if (this.rules.has(descriptor.id)) {
99
+ throw new Error(`Rule with id "${descriptor.id}" already registered`);
100
+ }
101
+ this.rules.set(descriptor.id, descriptor);
102
+ }
103
+
104
+ /**
105
+ * Register a constraint
106
+ */
107
+ registerConstraint(descriptor: ConstraintDescriptor<TContext>): void {
108
+ if (this.constraints.has(descriptor.id)) {
109
+ throw new Error(`Constraint with id "${descriptor.id}" already registered`);
110
+ }
111
+ this.constraints.set(descriptor.id, descriptor);
112
+ }
113
+
114
+ /**
115
+ * Register a module (all its rules and constraints)
116
+ */
117
+ registerModule(module: PraxisModule<TContext>): void {
118
+ for (const rule of module.rules) {
119
+ this.registerRule(rule);
120
+ }
121
+ for (const constraint of module.constraints) {
122
+ this.registerConstraint(constraint);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Get a rule by ID
128
+ */
129
+ getRule(id: RuleId): RuleDescriptor<TContext> | undefined {
130
+ return this.rules.get(id);
131
+ }
132
+
133
+ /**
134
+ * Get a constraint by ID
135
+ */
136
+ getConstraint(id: ConstraintId): ConstraintDescriptor<TContext> | undefined {
137
+ return this.constraints.get(id);
138
+ }
139
+
140
+ /**
141
+ * Get all registered rule IDs
142
+ */
143
+ getRuleIds(): RuleId[] {
144
+ return Array.from(this.rules.keys());
145
+ }
146
+
147
+ /**
148
+ * Get all registered constraint IDs
149
+ */
150
+ getConstraintIds(): ConstraintId[] {
151
+ return Array.from(this.constraints.keys());
152
+ }
153
+
154
+ /**
155
+ * Get all rules
156
+ */
157
+ getAllRules(): RuleDescriptor<TContext>[] {
158
+ return Array.from(this.rules.values());
159
+ }
160
+
161
+ /**
162
+ * Get all constraints
163
+ */
164
+ getAllConstraints(): ConstraintDescriptor<TContext>[] {
165
+ return Array.from(this.constraints.values());
166
+ }
167
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Praxis Schema Loader
3
+ *
4
+ * Loads and validates Praxis schema files.
5
+ */
6
+
7
+ import { readFile } from 'fs/promises';
8
+ import { pathToFileURL } from 'url';
9
+ import { load as yamlLoad } from 'js-yaml';
10
+ import type { PraxisSchema, ValidationResult } from './types.js';
11
+ import { validateSchema } from './types.js';
12
+
13
+ /**
14
+ * Loader options
15
+ */
16
+ export interface LoaderOptions {
17
+ /** Validate schema after loading */
18
+ validate?: boolean;
19
+ /** Base directory for resolving relative paths */
20
+ baseDir?: string;
21
+ }
22
+
23
+ /**
24
+ * Loader result
25
+ */
26
+ export interface LoaderResult {
27
+ /** Loaded schema */
28
+ schema?: PraxisSchema;
29
+ /** Validation result */
30
+ validation?: ValidationResult;
31
+ /** Load errors */
32
+ errors: string[];
33
+ }
34
+
35
+ /**
36
+ * Load a Praxis schema from a file
37
+ */
38
+ export async function loadSchema(
39
+ filePath: string,
40
+ options: LoaderOptions = {}
41
+ ): Promise<LoaderResult> {
42
+ const errors: string[] = [];
43
+
44
+ try {
45
+ // Only .js files are supported. TypeScript schema files must be compiled to .js first.
46
+ // Use a compiled JavaScript file for your schema.
47
+ // Dynamic import is used, which works with .js files only.
48
+
49
+ // Convert to absolute URL for ES module import
50
+ let fileUrl = pathToFileURL(filePath).href;
51
+
52
+ // Attempt to import .ts or .js files directly. If import fails, error will be caught below.
53
+
54
+ // Dynamic import of the schema file
55
+ const module = await import(fileUrl);
56
+
57
+ // Look for common schema export names
58
+ let schema: PraxisSchema | undefined;
59
+ if (module.default) {
60
+ schema = module.default;
61
+ } else if (module.schema) {
62
+ schema = module.schema;
63
+ } else if (module.appSchema) {
64
+ schema = module.appSchema;
65
+ } else {
66
+ // Try to find any object that looks like a PraxisSchema
67
+ const exports = Object.values(module);
68
+ const possibleSchema = exports.find(
69
+ (exp): exp is PraxisSchema =>
70
+ typeof exp === 'object' &&
71
+ exp !== null &&
72
+ 'version' in exp &&
73
+ 'name' in exp
74
+ );
75
+ if (possibleSchema) {
76
+ schema = possibleSchema;
77
+ }
78
+ }
79
+
80
+ if (!schema) {
81
+ errors.push(
82
+ 'Schema file must export a PraxisSchema object (as default, schema, or appSchema)'
83
+ );
84
+ return { errors };
85
+ }
86
+
87
+ // Validate if requested
88
+ let validation: ValidationResult | undefined;
89
+ if (options.validate !== false) {
90
+ validation = validateSchema(schema);
91
+ if (!validation.valid) {
92
+ errors.push('Schema validation failed:');
93
+ validation.errors.forEach((error) => {
94
+ errors.push(` ${error.path}: ${error.message}`);
95
+ });
96
+ }
97
+ }
98
+
99
+ return {
100
+ schema,
101
+ validation,
102
+ errors,
103
+ };
104
+ } catch (error) {
105
+ if (error instanceof Error) {
106
+ errors.push(`Failed to load schema: ${error.message}`);
107
+ } else {
108
+ errors.push('Failed to load schema: Unknown error');
109
+ }
110
+ return { errors };
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Load schema from JSON string
116
+ */
117
+ export function loadSchemaFromJson(
118
+ json: string,
119
+ options: LoaderOptions = {}
120
+ ): LoaderResult {
121
+ const errors: string[] = [];
122
+
123
+ try {
124
+ const schema = JSON.parse(json) as PraxisSchema;
125
+
126
+ // Validate if requested
127
+ let validation: ValidationResult | undefined;
128
+ if (options.validate !== false) {
129
+ validation = validateSchema(schema);
130
+ if (!validation.valid) {
131
+ errors.push('Schema validation failed:');
132
+ validation.errors.forEach((error) => {
133
+ errors.push(` ${error.path}: ${error.message}`);
134
+ });
135
+ }
136
+ }
137
+
138
+ return {
139
+ schema,
140
+ validation,
141
+ errors,
142
+ };
143
+ } catch (error) {
144
+ if (error instanceof Error) {
145
+ errors.push(`Failed to parse JSON: ${error.message}`);
146
+ } else {
147
+ errors.push('Failed to parse JSON: Unknown error');
148
+ }
149
+ return { errors };
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Load schema from YAML string
155
+ */
156
+ export function loadSchemaFromYaml(
157
+ yaml: string,
158
+ options: LoaderOptions = {}
159
+ ): LoaderResult {
160
+ const errors: string[] = [];
161
+
162
+ try {
163
+ const schema = yamlLoad(yaml) as PraxisSchema;
164
+
165
+ // Validate if requested
166
+ let validation: ValidationResult | undefined;
167
+ if (options.validate !== false) {
168
+ validation = validateSchema(schema);
169
+ if (!validation.valid) {
170
+ errors.push('Schema validation failed:');
171
+ validation.errors.forEach((error) => {
172
+ errors.push(` ${error.path}: ${error.message}`);
173
+ });
174
+ }
175
+ }
176
+
177
+ return {
178
+ schema,
179
+ validation,
180
+ errors,
181
+ };
182
+ } catch (error) {
183
+ if (error instanceof Error) {
184
+ errors.push(`Failed to parse YAML: ${error.message}`);
185
+ } else {
186
+ errors.push('Failed to parse YAML: Unknown error');
187
+ }
188
+ return { errors };
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Load schema from file (supports .ts, .json, and .yaml/.yml)
194
+ */
195
+ export async function loadSchemaFromFile(
196
+ filePath: string,
197
+ options: LoaderOptions = {}
198
+ ): Promise<LoaderResult> {
199
+ if (filePath.endsWith('.json')) {
200
+ const content = await readFile(filePath, 'utf-8');
201
+ return loadSchemaFromJson(content, options);
202
+ } else if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
203
+ const content = await readFile(filePath, 'utf-8');
204
+ return loadSchemaFromYaml(content, options);
205
+ } else {
206
+ return loadSchema(filePath, options);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Validate that a loaded schema has required fields for generation
212
+ */
213
+ export function validateForGeneration(schema: PraxisSchema): ValidationResult {
214
+ const errors: string[] = [];
215
+
216
+ // Check for entities/models
217
+ if (!schema.models || schema.models.length === 0) {
218
+ errors.push('Schema must define at least one model for generation');
219
+ }
220
+
221
+ // Check that models have valid fields
222
+ schema.models?.forEach((model, index) => {
223
+ if (!model.fields || model.fields.length === 0) {
224
+ errors.push(
225
+ `Model "${model.name}" at index ${index} must have at least one field`
226
+ );
227
+ }
228
+
229
+ model.fields?.forEach((field, fieldIndex) => {
230
+ if (!field.name) {
231
+ errors.push(
232
+ `Field at index ${fieldIndex} in model "${model.name}" must have a name`
233
+ );
234
+ }
235
+ if (!field.type) {
236
+ errors.push(
237
+ `Field "${field.name}" in model "${model.name}" must have a type`
238
+ );
239
+ }
240
+ });
241
+ });
242
+
243
+ return {
244
+ valid: errors.length === 0,
245
+ errors: errors.map((message) => ({ path: 'schema', message })),
246
+ };
247
+ }