@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,270 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createLogicGenerator } from '../core/logic/generator.js';
3
+ import { createComponentGenerator } from '../core/component/generator.js';
4
+ import { createPluresDBGenerator } from '../core/pluresdb/generator.js';
5
+ import { normalizeSchema } from '../core/schema/normalize.js';
6
+ import type { PraxisSchema } from '../core/schema/types.js';
7
+
8
+ describe('Code Generators', () => {
9
+ const testSchema: PraxisSchema = {
10
+ version: '1.0.0',
11
+ name: 'TestApp',
12
+ models: [
13
+ {
14
+ name: 'User',
15
+ fields: [
16
+ { name: 'id', type: 'string' },
17
+ { name: 'name', type: 'string' },
18
+ { name: 'email', type: 'string' },
19
+ ],
20
+ },
21
+ ],
22
+ components: [
23
+ {
24
+ name: 'UserForm',
25
+ type: 'form',
26
+ model: 'User',
27
+ },
28
+ ],
29
+ logic: [
30
+ {
31
+ id: 'user-logic',
32
+ description: 'User logic',
33
+ events: [
34
+ {
35
+ tag: 'CREATE_USER',
36
+ payload: { name: 'string', email: 'string' },
37
+ },
38
+ ],
39
+ facts: [
40
+ {
41
+ tag: 'UserCreated',
42
+ payload: { userId: 'string' },
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ };
48
+
49
+ describe('LogicGenerator', () => {
50
+ it('generates logic files', () => {
51
+ const normalized = normalizeSchema(testSchema);
52
+ const generator = createLogicGenerator('/tmp/test');
53
+
54
+ const files = generator.generateLogic(normalized);
55
+
56
+ expect(files).toHaveLength(5);
57
+ expect(files.map(f => f.type)).toContain('facts');
58
+ expect(files.map(f => f.type)).toContain('events');
59
+ expect(files.map(f => f.type)).toContain('rules');
60
+ expect(files.map(f => f.type)).toContain('engine');
61
+ expect(files.map(f => f.type)).toContain('index');
62
+ });
63
+
64
+ it('generates facts with correct types', () => {
65
+ const normalized = normalizeSchema(testSchema);
66
+ const generator = createLogicGenerator('/tmp/test');
67
+
68
+ const files = generator.generateLogic(normalized);
69
+ const factsFile = files.find(f => f.type === 'facts');
70
+
71
+ expect(factsFile).toBeDefined();
72
+ expect(factsFile?.content).toContain('UserCreated');
73
+ expect(factsFile?.content).toContain('defineFact');
74
+ expect(factsFile?.content).toContain('userId: string');
75
+ });
76
+
77
+ it('generates events with correct types', () => {
78
+ const normalized = normalizeSchema(testSchema);
79
+ const generator = createLogicGenerator('/tmp/test');
80
+
81
+ const files = generator.generateLogic(normalized);
82
+ const eventsFile = files.find(f => f.type === 'events');
83
+
84
+ expect(eventsFile).toBeDefined();
85
+ expect(eventsFile?.content).toContain('CREATE_USER');
86
+ expect(eventsFile?.content).toContain('defineEvent');
87
+ expect(eventsFile?.content).toContain('name: string');
88
+ expect(eventsFile?.content).toContain('email: string');
89
+ });
90
+
91
+ it('generates engine with model types', () => {
92
+ const normalized = normalizeSchema(testSchema);
93
+ const generator = createLogicGenerator('/tmp/test');
94
+
95
+ const files = generator.generateLogic(normalized);
96
+ const engineFile = files.find(f => f.type === 'engine');
97
+
98
+ expect(engineFile).toBeDefined();
99
+ expect(engineFile?.content).toContain('interface User');
100
+ expect(engineFile?.content).toContain('id: string');
101
+ expect(engineFile?.content).toContain('name: string');
102
+ expect(engineFile?.content).toContain('email: string');
103
+ });
104
+ });
105
+
106
+ describe('ComponentGenerator', () => {
107
+ it('generates component files', () => {
108
+ const component = testSchema.components![0];
109
+ const model = testSchema.models![0];
110
+ const generator = createComponentGenerator('/tmp/test', {
111
+ typescript: true,
112
+ includeDocs: true,
113
+ });
114
+
115
+ const result = generator.generateComponent(component, model);
116
+
117
+ expect(result.success).toBe(true);
118
+ expect(result.files.length).toBeGreaterThan(0);
119
+ expect(result.errors).toHaveLength(0);
120
+ });
121
+
122
+ it('generates Svelte component with form fields', () => {
123
+ const component = testSchema.components![0];
124
+ const model = testSchema.models![0];
125
+ const generator = createComponentGenerator('/tmp/test');
126
+
127
+ const result = generator.generateComponent(component, model);
128
+ const componentFile = result.files.find(f => f.type === 'component');
129
+
130
+ expect(componentFile).toBeDefined();
131
+ expect(componentFile?.content).toContain('<form');
132
+ expect(componentFile?.content).toContain('input');
133
+ expect(componentFile?.content).toContain('id');
134
+ expect(componentFile?.content).toContain('name');
135
+ expect(componentFile?.content).toContain('email');
136
+ });
137
+
138
+ it('generates TypeScript types when enabled', () => {
139
+ const component = testSchema.components![0];
140
+ const model = testSchema.models![0];
141
+ const generator = createComponentGenerator('/tmp/test', {
142
+ typescript: true,
143
+ });
144
+
145
+ const result = generator.generateComponent(component, model);
146
+ const typesFile = result.files.find(f => f.type === 'types');
147
+
148
+ expect(typesFile).toBeDefined();
149
+ expect(typesFile?.content).toContain('interface User');
150
+ });
151
+
152
+ it('generates documentation when enabled', () => {
153
+ const component = testSchema.components![0];
154
+ const model = testSchema.models![0];
155
+ const generator = createComponentGenerator('/tmp/test', {
156
+ includeDocs: true,
157
+ });
158
+
159
+ const result = generator.generateComponent(component, model);
160
+ const docsFile = result.files.find(f => f.type === 'docs');
161
+
162
+ expect(docsFile).toBeDefined();
163
+ expect(docsFile?.content).toContain('# UserForm');
164
+ });
165
+ });
166
+
167
+ describe('PluresDBGenerator', () => {
168
+ it('generates PluresDB config', () => {
169
+ const normalized = normalizeSchema(testSchema);
170
+ const generator = createPluresDBGenerator('/tmp/test');
171
+
172
+ const files = generator.generateConfig(normalized);
173
+
174
+ expect(files).toHaveLength(1);
175
+ expect(files[0].type).toBe('config');
176
+ });
177
+
178
+ it('generates stores for models', () => {
179
+ const normalized = normalizeSchema(testSchema);
180
+ const generator = createPluresDBGenerator('/tmp/test');
181
+
182
+ const files = generator.generateConfig(normalized);
183
+ const configFile = files[0];
184
+
185
+ expect(configFile.content).toContain('users:');
186
+ expect(configFile.content).toContain("keyPath: 'id'");
187
+ expect(configFile.content).toContain('indexes:');
188
+ });
189
+
190
+ it('includes database name in config', () => {
191
+ const normalized = normalizeSchema(testSchema);
192
+ const generator = createPluresDBGenerator('/tmp/test', {
193
+ dbName: 'custom-db',
194
+ });
195
+
196
+ const files = generator.generateConfig(normalized);
197
+ const configFile = files[0];
198
+
199
+ expect(configFile.content).toContain("name: 'custom-db'");
200
+ });
201
+
202
+ it('includes sync config when enabled', () => {
203
+ const normalized = normalizeSchema(testSchema);
204
+ const generator = createPluresDBGenerator('/tmp/test', {
205
+ enableSync: true,
206
+ syncEndpoint: 'ws://example.com/sync',
207
+ });
208
+
209
+ const files = generator.generateConfig(normalized);
210
+ const configFile = files[0];
211
+
212
+ expect(configFile.content).toContain('sync:');
213
+ expect(configFile.content).toContain('enabled: true');
214
+ expect(configFile.content).toContain('ws://example.com/sync');
215
+ });
216
+
217
+ it('respects autoIndex "all" strategy', () => {
218
+ const normalized = normalizeSchema(testSchema);
219
+ const generator = createPluresDBGenerator('/tmp/test', {
220
+ autoIndex: 'all',
221
+ });
222
+
223
+ const files = generator.generateConfig(normalized);
224
+ const configFile = files[0];
225
+
226
+ // Should include all string/number/date fields
227
+ expect(configFile.content).toContain("indexes: ['name', 'email']");
228
+ expect(configFile.content).toContain('auto-indexed by default');
229
+ });
230
+
231
+ it('respects autoIndex "explicit" strategy', () => {
232
+ const schemaWithIndexes = {
233
+ ...testSchema,
234
+ models: [
235
+ {
236
+ ...testSchema.models![0],
237
+ indexes: [
238
+ { name: 'email_idx', fields: ['email'] },
239
+ ],
240
+ },
241
+ ],
242
+ };
243
+ const normalized = normalizeSchema(schemaWithIndexes);
244
+ const generator = createPluresDBGenerator('/tmp/test', {
245
+ autoIndex: 'explicit',
246
+ });
247
+
248
+ const files = generator.generateConfig(normalized);
249
+ const configFile = files[0];
250
+
251
+ // Should only include explicitly defined indexes
252
+ expect(configFile.content).toContain("indexes: ['email']");
253
+ expect(configFile.content).toContain('explicitly defined in schema');
254
+ });
255
+
256
+ it('respects autoIndex "none" strategy', () => {
257
+ const normalized = normalizeSchema(testSchema);
258
+ const generator = createPluresDBGenerator('/tmp/test', {
259
+ autoIndex: 'none',
260
+ });
261
+
262
+ const files = generator.generateConfig(normalized);
263
+ const configFile = files[0];
264
+
265
+ // Should have no auto-generated indexes
266
+ expect(configFile.content).not.toContain('indexes:');
267
+ expect(configFile.content).toContain('Auto-indexing disabled');
268
+ });
269
+ });
270
+ });
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Introspection tests
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+ import { PraxisRegistry } from "../core/rules.js";
7
+ import { defineRule, defineConstraint, defineModule } from "../dsl/index.js";
8
+ import { RegistryIntrospector, createIntrospector } from "../core/introspection.js";
9
+ import { PRAXIS_PROTOCOL_VERSION } from "../core/protocol.js";
10
+
11
+ describe("Registry Introspection", () => {
12
+ describe("RegistryIntrospector", () => {
13
+ it("should get basic statistics", () => {
14
+ const registry = new PraxisRegistry<{ value: number }>();
15
+
16
+ registry.registerRule(
17
+ defineRule({ id: "rule1", description: "Rule 1", impl: () => [] })
18
+ );
19
+ registry.registerRule(
20
+ defineRule({ id: "rule2", description: "Rule 2", impl: () => [] })
21
+ );
22
+ registry.registerConstraint(
23
+ defineConstraint({ id: "c1", description: "Constraint 1", impl: () => true })
24
+ );
25
+
26
+ const introspector = new RegistryIntrospector(registry);
27
+ const stats = introspector.getStats();
28
+
29
+ expect(stats.ruleCount).toBe(2);
30
+ expect(stats.constraintCount).toBe(1);
31
+ expect(stats.rulesById).toEqual(["rule1", "rule2"]);
32
+ expect(stats.constraintsById).toEqual(["c1"]);
33
+ });
34
+
35
+ it("should generate schema", () => {
36
+ const registry = new PraxisRegistry<{ value: number }>();
37
+
38
+ registry.registerRule(
39
+ defineRule({
40
+ id: "rule1",
41
+ description: "Test rule",
42
+ impl: () => [],
43
+ meta: { category: "test" },
44
+ })
45
+ );
46
+ registry.registerConstraint(
47
+ defineConstraint({
48
+ id: "c1",
49
+ description: "Test constraint",
50
+ impl: () => true,
51
+ meta: { level: "error" },
52
+ })
53
+ );
54
+
55
+ const introspector = new RegistryIntrospector(registry);
56
+ const schema = introspector.generateSchema(PRAXIS_PROTOCOL_VERSION);
57
+
58
+ expect(schema.protocolVersion).toBe(PRAXIS_PROTOCOL_VERSION);
59
+ expect(schema.rules).toHaveLength(1);
60
+ expect(schema.constraints).toHaveLength(1);
61
+ expect(schema.rules[0]?.id).toBe("rule1");
62
+ expect(schema.rules[0]?.description).toBe("Test rule");
63
+ expect(schema.rules[0]?.meta).toEqual({ category: "test" });
64
+ expect(schema.constraints[0]?.id).toBe("c1");
65
+ expect(schema.constraints[0]?.meta).toEqual({ level: "error" });
66
+ });
67
+
68
+ it("should generate graph", () => {
69
+ const registry = new PraxisRegistry<{ value: number }>();
70
+
71
+ registry.registerRule(
72
+ defineRule({ id: "rule1", description: "Rule 1", impl: () => [] })
73
+ );
74
+ registry.registerRule(
75
+ defineRule({ id: "rule2", description: "Rule 2", impl: () => [] })
76
+ );
77
+ registry.registerConstraint(
78
+ defineConstraint({ id: "c1", description: "Constraint 1", impl: () => true })
79
+ );
80
+
81
+ const introspector = new RegistryIntrospector(registry);
82
+ const graph = introspector.generateGraph();
83
+
84
+ expect(graph.nodes).toHaveLength(3);
85
+ expect(graph.nodes.filter((n) => n.type === "rule")).toHaveLength(2);
86
+ expect(graph.nodes.filter((n) => n.type === "constraint")).toHaveLength(1);
87
+ expect(graph.meta.nodeCount).toBe(3);
88
+ expect(graph.meta.ruleCount).toBe(2);
89
+ expect(graph.meta.constraintCount).toBe(1);
90
+ });
91
+
92
+ it("should generate graph with dependencies", () => {
93
+ const registry = new PraxisRegistry<{ value: number }>();
94
+
95
+ registry.registerRule(
96
+ defineRule({
97
+ id: "rule1",
98
+ description: "Rule 1",
99
+ impl: () => [],
100
+ meta: {},
101
+ })
102
+ );
103
+ registry.registerRule(
104
+ defineRule({
105
+ id: "rule2",
106
+ description: "Rule 2 depends on Rule 1",
107
+ impl: () => [],
108
+ meta: { dependsOn: "rule1" },
109
+ })
110
+ );
111
+
112
+ const introspector = new RegistryIntrospector(registry);
113
+ const graph = introspector.generateGraph();
114
+
115
+ expect(graph.edges).toHaveLength(1);
116
+ expect(graph.edges[0]).toEqual({
117
+ from: "rule1",
118
+ to: "rule2",
119
+ type: "depends-on",
120
+ });
121
+ });
122
+
123
+ it("should generate graph with constraint relationships", () => {
124
+ const registry = new PraxisRegistry<{ value: number }>();
125
+
126
+ registry.registerRule(
127
+ defineRule({ id: "rule1", description: "Rule 1", impl: () => [] })
128
+ );
129
+ registry.registerConstraint(
130
+ defineConstraint({
131
+ id: "c1",
132
+ description: "Constrains rule1",
133
+ impl: () => true,
134
+ meta: { constrains: "rule1" },
135
+ })
136
+ );
137
+
138
+ const introspector = new RegistryIntrospector(registry);
139
+ const graph = introspector.generateGraph();
140
+
141
+ expect(graph.edges).toHaveLength(1);
142
+ expect(graph.edges[0]).toEqual({
143
+ from: "c1",
144
+ to: "rule1",
145
+ type: "constrains",
146
+ });
147
+ });
148
+
149
+ it("should export DOT format", () => {
150
+ const registry = new PraxisRegistry<{ value: number }>();
151
+
152
+ registry.registerRule(
153
+ defineRule({ id: "rule1", description: "Rule 1", impl: () => [] })
154
+ );
155
+ registry.registerConstraint(
156
+ defineConstraint({ id: "c1", description: "Constraint 1", impl: () => true })
157
+ );
158
+
159
+ const introspector = new RegistryIntrospector(registry);
160
+ const dot = introspector.exportDOT();
161
+
162
+ expect(dot).toContain("digraph PraxisRegistry");
163
+ expect(dot).toContain('"rule1"');
164
+ expect(dot).toContain('"c1"');
165
+ expect(dot).toContain("shape=box");
166
+ expect(dot).toContain("shape=diamond");
167
+ });
168
+
169
+ it("should export Mermaid format", () => {
170
+ const registry = new PraxisRegistry<{ value: number }>();
171
+
172
+ registry.registerRule(
173
+ defineRule({ id: "rule1", description: "Rule 1", impl: () => [] })
174
+ );
175
+ registry.registerConstraint(
176
+ defineConstraint({ id: "c1", description: "Constraint 1", impl: () => true })
177
+ );
178
+
179
+ const introspector = new RegistryIntrospector(registry);
180
+ const mermaid = introspector.exportMermaid();
181
+
182
+ expect(mermaid).toContain("graph TB");
183
+ expect(mermaid).toContain("rule1");
184
+ expect(mermaid).toContain("c1");
185
+ });
186
+
187
+ it("should get rule info", () => {
188
+ const rule = defineRule({
189
+ id: "test.rule",
190
+ description: "Test rule",
191
+ impl: () => [],
192
+ meta: { version: "1.0.0" },
193
+ });
194
+
195
+ const registry = new PraxisRegistry<{ value: number }>();
196
+ registry.registerRule(rule);
197
+
198
+ const introspector = new RegistryIntrospector(registry);
199
+ const info = introspector.getRuleInfo("test.rule");
200
+
201
+ expect(info).toBeDefined();
202
+ expect(info?.id).toBe("test.rule");
203
+ expect(info?.description).toBe("Test rule");
204
+ expect(info?.meta).toEqual({ version: "1.0.0" });
205
+ });
206
+
207
+ it("should get constraint info", () => {
208
+ const constraint = defineConstraint({
209
+ id: "test.constraint",
210
+ description: "Test constraint",
211
+ impl: () => true,
212
+ meta: { severity: "error" },
213
+ });
214
+
215
+ const registry = new PraxisRegistry<{ value: number }>();
216
+ registry.registerConstraint(constraint);
217
+
218
+ const introspector = new RegistryIntrospector(registry);
219
+ const info = introspector.getConstraintInfo("test.constraint");
220
+
221
+ expect(info).toBeDefined();
222
+ expect(info?.id).toBe("test.constraint");
223
+ expect(info?.description).toBe("Test constraint");
224
+ expect(info?.meta).toEqual({ severity: "error" });
225
+ });
226
+
227
+ it("should search rules by query", () => {
228
+ const registry = new PraxisRegistry<{ value: number }>();
229
+
230
+ registry.registerRule(
231
+ defineRule({ id: "auth.login", description: "Login rule", impl: () => [] })
232
+ );
233
+ registry.registerRule(
234
+ defineRule({ id: "auth.logout", description: "Logout rule", impl: () => [] })
235
+ );
236
+ registry.registerRule(
237
+ defineRule({ id: "cart.add", description: "Add to cart", impl: () => [] })
238
+ );
239
+
240
+ const introspector = new RegistryIntrospector(registry);
241
+
242
+ const authRules = introspector.searchRules("auth");
243
+ expect(authRules).toHaveLength(2);
244
+
245
+ const loginRules = introspector.searchRules("login");
246
+ expect(loginRules).toHaveLength(1);
247
+ expect(loginRules[0]?.id).toBe("auth.login");
248
+
249
+ const cartRules = introspector.searchRules("cart");
250
+ expect(cartRules).toHaveLength(1);
251
+ });
252
+
253
+ it("should search constraints by query", () => {
254
+ const registry = new PraxisRegistry<{ value: number }>();
255
+
256
+ registry.registerConstraint(
257
+ defineConstraint({ id: "auth.maxSessions", description: "Max sessions", impl: () => true })
258
+ );
259
+ registry.registerConstraint(
260
+ defineConstraint({ id: "cart.maxItems", description: "Max items in cart", impl: () => true })
261
+ );
262
+
263
+ const introspector = new RegistryIntrospector(registry);
264
+
265
+ const authConstraints = introspector.searchConstraints("auth");
266
+ expect(authConstraints).toHaveLength(1);
267
+ expect(authConstraints[0]?.id).toBe("auth.maxSessions");
268
+
269
+ const maxConstraints = introspector.searchConstraints("max");
270
+ expect(maxConstraints).toHaveLength(2);
271
+ });
272
+
273
+ it("should work with empty registry", () => {
274
+ const registry = new PraxisRegistry<{ value: number }>();
275
+ const introspector = new RegistryIntrospector(registry);
276
+
277
+ const stats = introspector.getStats();
278
+ expect(stats.ruleCount).toBe(0);
279
+ expect(stats.constraintCount).toBe(0);
280
+
281
+ const graph = introspector.generateGraph();
282
+ expect(graph.nodes).toHaveLength(0);
283
+ expect(graph.edges).toHaveLength(0);
284
+
285
+ const schema = introspector.generateSchema("1.0.0");
286
+ expect(schema.rules).toHaveLength(0);
287
+ expect(schema.constraints).toHaveLength(0);
288
+ });
289
+
290
+ it("should work with modules", () => {
291
+ const module = defineModule({
292
+ rules: [
293
+ defineRule({ id: "m1.rule1", description: "Module rule 1", impl: () => [] }),
294
+ defineRule({ id: "m1.rule2", description: "Module rule 2", impl: () => [] }),
295
+ ],
296
+ constraints: [
297
+ defineConstraint({ id: "m1.c1", description: "Module constraint 1", impl: () => true }),
298
+ ],
299
+ meta: { moduleName: "Module 1" },
300
+ });
301
+
302
+ const registry = new PraxisRegistry<{ value: number }>();
303
+ registry.registerModule(module);
304
+
305
+ const introspector = new RegistryIntrospector(registry);
306
+ const stats = introspector.getStats();
307
+
308
+ expect(stats.ruleCount).toBe(2);
309
+ expect(stats.constraintCount).toBe(1);
310
+ });
311
+ });
312
+
313
+ describe("createIntrospector", () => {
314
+ it("should create introspector from registry", () => {
315
+ const registry = new PraxisRegistry<{ value: number }>();
316
+ const introspector = createIntrospector(registry);
317
+
318
+ expect(introspector).toBeInstanceOf(RegistryIntrospector);
319
+ });
320
+ });
321
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Core protocol tests
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+ import type { PraxisFact, PraxisEvent, PraxisState } from "../core/protocol.js";
7
+
8
+ describe("Protocol Types", () => {
9
+ it("should create a valid fact", () => {
10
+ const fact: PraxisFact = {
11
+ tag: "TestFact",
12
+ payload: { value: 42 },
13
+ };
14
+
15
+ expect(fact.tag).toBe("TestFact");
16
+ expect(fact.payload).toEqual({ value: 42 });
17
+ });
18
+
19
+ it("should create a valid event", () => {
20
+ const event: PraxisEvent = {
21
+ tag: "TEST_EVENT",
22
+ payload: { action: "test" },
23
+ };
24
+
25
+ expect(event.tag).toBe("TEST_EVENT");
26
+ expect(event.payload).toEqual({ action: "test" });
27
+ });
28
+
29
+ it("should create a valid state", () => {
30
+ const state: PraxisState = {
31
+ context: { count: 0 },
32
+ facts: [{ tag: "CountInitialized", payload: {} }],
33
+ meta: { version: "1.0.0" },
34
+ };
35
+
36
+ expect(state.context).toEqual({ count: 0 });
37
+ expect(state.facts).toHaveLength(1);
38
+ expect(state.meta?.version).toBe("1.0.0");
39
+ });
40
+ });