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