@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,162 @@
1
+ /**
2
+ * Provisioning Tests
3
+ *
4
+ * Tests for auto-provisioning utilities.
5
+ */
6
+
7
+ import { describe, it, expect } from "vitest";
8
+ import {
9
+ generateStorageNamespace,
10
+ generateTenantId,
11
+ createTenant,
12
+ validateStorageNamespace,
13
+ getAppStorageContainer,
14
+ provisionTenant,
15
+ } from "../cloud/provisioning.js";
16
+ import { createFreeSubscription } from "../cloud/billing.js";
17
+ import type { GitHubUser } from "../cloud/types.js";
18
+
19
+ describe("Provisioning", () => {
20
+ const mockUser: GitHubUser = {
21
+ id: 12345,
22
+ login: "testuser",
23
+ email: "test@example.com",
24
+ name: "Test User",
25
+ };
26
+
27
+ describe("generateStorageNamespace", () => {
28
+ it("should generate a valid namespace", () => {
29
+ const namespace = generateStorageNamespace("testuser", 12345);
30
+ expect(namespace).toMatch(/^gh-testuser-[a-z0-9]+$/);
31
+ expect(namespace.length).toBeGreaterThanOrEqual(3);
32
+ expect(namespace.length).toBeLessThanOrEqual(63);
33
+ });
34
+
35
+ it("should sanitize special characters", () => {
36
+ const namespace = generateStorageNamespace("test_user-123", 12345);
37
+ expect(namespace).toMatch(/^gh-test-user-123-[a-z0-9]+$/);
38
+ });
39
+
40
+ it("should convert to lowercase", () => {
41
+ const namespace = generateStorageNamespace("TestUser", 12345);
42
+ expect(namespace).toMatch(/^gh-testuser-[a-z0-9]+$/);
43
+ });
44
+
45
+ it("should generate unique namespaces for different users", () => {
46
+ const ns1 = generateStorageNamespace("user1", 123);
47
+ const ns2 = generateStorageNamespace("user1", 456);
48
+ expect(ns1).not.toBe(ns2);
49
+ });
50
+ });
51
+
52
+ describe("generateTenantId", () => {
53
+ it("should generate a tenant ID from GitHub user", () => {
54
+ const tenantId = generateTenantId(mockUser);
55
+ expect(tenantId).toBe("github-12345");
56
+ });
57
+
58
+ it("should generate unique IDs for different users", () => {
59
+ const id1 = generateTenantId({ ...mockUser, id: 123 });
60
+ const id2 = generateTenantId({ ...mockUser, id: 456 });
61
+ expect(id1).not.toBe(id2);
62
+ });
63
+ });
64
+
65
+ describe("createTenant", () => {
66
+ it("should create a tenant from GitHub user", () => {
67
+ const subscription = createFreeSubscription();
68
+ const tenant = createTenant(mockUser, subscription);
69
+
70
+ expect(tenant.id).toBe("github-12345");
71
+ expect(tenant.githubUserId).toBe(12345);
72
+ expect(tenant.githubLogin).toBe("testuser");
73
+ expect(tenant.type).toBe("user");
74
+ expect(tenant.subscription).toBe(subscription);
75
+ expect(tenant.storageNamespace).toMatch(/^gh-testuser-[a-z0-9]+$/);
76
+ expect(tenant.createdAt).toBeDefined();
77
+ expect(tenant.lastAccessedAt).toBeDefined();
78
+ });
79
+ });
80
+
81
+ describe("validateStorageNamespace", () => {
82
+ it("should validate valid namespaces", () => {
83
+ const result = validateStorageNamespace("gh-testuser-abc123");
84
+ expect(result.valid).toBe(true);
85
+ });
86
+
87
+ it("should reject namespaces that are too short", () => {
88
+ const result = validateStorageNamespace("ab");
89
+ expect(result.valid).toBe(false);
90
+ expect(result.error).toContain("3-63 characters");
91
+ });
92
+
93
+ it("should reject namespaces that are too long", () => {
94
+ const result = validateStorageNamespace("a".repeat(64));
95
+ expect(result.valid).toBe(false);
96
+ expect(result.error).toContain("3-63 characters");
97
+ });
98
+
99
+ it("should reject namespaces starting with hyphen", () => {
100
+ const result = validateStorageNamespace("-testuser");
101
+ expect(result.valid).toBe(false);
102
+ expect(result.error).toContain("start with a letter or number");
103
+ });
104
+
105
+ it("should reject namespaces with uppercase letters", () => {
106
+ const result = validateStorageNamespace("testUser");
107
+ expect(result.valid).toBe(false);
108
+ expect(result.error).toContain("lowercase letters");
109
+ });
110
+
111
+ it("should reject namespaces with consecutive hyphens", () => {
112
+ const result = validateStorageNamespace("test--user");
113
+ expect(result.valid).toBe(false);
114
+ expect(result.error).toContain("consecutive hyphens");
115
+ });
116
+
117
+ it("should reject namespaces with special characters", () => {
118
+ const result = validateStorageNamespace("test_user");
119
+ expect(result.valid).toBe(false);
120
+ expect(result.error).toContain("lowercase letters, numbers, and hyphens");
121
+ });
122
+ });
123
+
124
+ describe("getAppStorageContainer", () => {
125
+ it("should generate app storage container name", () => {
126
+ const container = getAppStorageContainer("gh-testuser-abc", "my-app");
127
+ expect(container).toBe("gh-testuser-abc-my-app");
128
+ });
129
+
130
+ it("should sanitize app ID", () => {
131
+ const container = getAppStorageContainer("gh-testuser-abc", "My_App!");
132
+ expect(container).toMatch(/^gh-testuser-abc-my-app-?$/);
133
+ });
134
+ });
135
+
136
+ describe("provisionTenant", () => {
137
+ it("should provision a tenant", async () => {
138
+ const subscription = createFreeSubscription();
139
+ const result = await provisionTenant(mockUser, subscription);
140
+
141
+ expect(result.success).toBe(true);
142
+ expect(result.tenant).toBeDefined();
143
+ expect(result.tenant?.id).toBe("github-12345");
144
+ });
145
+
146
+ it("should validate storage namespace during provisioning", async () => {
147
+ // Create a user with invalid characters that would create an invalid namespace
148
+ const invalidUser: GitHubUser = {
149
+ id: 1,
150
+ login: "a", // Too short after sanitization
151
+ email: "test@example.com",
152
+ };
153
+
154
+ const subscription = createFreeSubscription();
155
+ const result = await provisionTenant(invalidUser, subscription);
156
+
157
+ // The namespace will be like "gh-a-000001" which is valid
158
+ // So we need to test with a user that would create an invalid namespace
159
+ expect(result).toBeDefined();
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,241 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateSchema, createSchemaTemplate } from '../core/schema/types.js';
3
+ import { normalizeSchema } from '../core/schema/normalize.js';
4
+ import type { PraxisSchema } from '../core/schema/types.js';
5
+
6
+ describe('Schema System', () => {
7
+ describe('validateSchema', () => {
8
+ it('validates a valid schema', () => {
9
+ const schema: PraxisSchema = {
10
+ version: '1.0.0',
11
+ name: 'TestSchema',
12
+ models: [
13
+ {
14
+ name: 'User',
15
+ fields: [
16
+ { name: 'id', type: 'string' },
17
+ { name: 'name', type: 'string' },
18
+ ],
19
+ },
20
+ ],
21
+ };
22
+
23
+ const result = validateSchema(schema);
24
+ expect(result.valid).toBe(true);
25
+ expect(result.errors).toHaveLength(0);
26
+ });
27
+
28
+ it('fails validation when version is missing', () => {
29
+ const schema = {
30
+ name: 'TestSchema',
31
+ } as PraxisSchema;
32
+
33
+ const result = validateSchema(schema);
34
+ expect(result.valid).toBe(false);
35
+ expect(result.errors.length).toBeGreaterThan(0);
36
+ expect(result.errors[0].message).toContain('version');
37
+ });
38
+
39
+ it('fails validation when name is missing', () => {
40
+ const schema = {
41
+ version: '1.0.0',
42
+ } as PraxisSchema;
43
+
44
+ const result = validateSchema(schema);
45
+ expect(result.valid).toBe(false);
46
+ expect(result.errors.length).toBeGreaterThan(0);
47
+ expect(result.errors[0].message).toContain('name');
48
+ });
49
+
50
+ it('fails validation when model has no fields', () => {
51
+ const schema: PraxisSchema = {
52
+ version: '1.0.0',
53
+ name: 'TestSchema',
54
+ models: [
55
+ {
56
+ name: 'User',
57
+ fields: [],
58
+ },
59
+ ],
60
+ };
61
+
62
+ const result = validateSchema(schema);
63
+ expect(result.valid).toBe(false);
64
+ expect(result.errors.length).toBeGreaterThan(0);
65
+ });
66
+
67
+ it('fails validation when fact tag is not a valid identifier', () => {
68
+ const schema: PraxisSchema = {
69
+ version: '1.0.0',
70
+ name: 'TestSchema',
71
+ logic: [
72
+ {
73
+ id: 'test-logic',
74
+ description: 'Test logic',
75
+ facts: [
76
+ {
77
+ tag: 'Invalid-Tag',
78
+ payload: { value: 'string' },
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ };
84
+
85
+ const result = validateSchema(schema);
86
+ expect(result.valid).toBe(false);
87
+ expect(result.errors.length).toBeGreaterThan(0);
88
+ expect(result.errors[0].message).toContain('not a valid JavaScript identifier');
89
+ });
90
+
91
+ it('fails validation when event tag is not a valid identifier', () => {
92
+ const schema: PraxisSchema = {
93
+ version: '1.0.0',
94
+ name: 'TestSchema',
95
+ logic: [
96
+ {
97
+ id: 'test-logic',
98
+ description: 'Test logic',
99
+ events: [
100
+ {
101
+ tag: 'My Event',
102
+ payload: { value: 'string' },
103
+ },
104
+ ],
105
+ },
106
+ ],
107
+ };
108
+
109
+ const result = validateSchema(schema);
110
+ expect(result.valid).toBe(false);
111
+ expect(result.errors.length).toBeGreaterThan(0);
112
+ expect(result.errors[0].message).toContain('not a valid JavaScript identifier');
113
+ });
114
+
115
+ it('allows valid fact and event tags', () => {
116
+ const schema: PraxisSchema = {
117
+ version: '1.0.0',
118
+ name: 'TestSchema',
119
+ logic: [
120
+ {
121
+ id: 'test-logic',
122
+ description: 'Test logic',
123
+ facts: [
124
+ {
125
+ tag: 'ValidFactTag',
126
+ payload: { value: 'string' },
127
+ },
128
+ {
129
+ tag: 'ANOTHER_VALID_TAG',
130
+ payload: { value: 'string' },
131
+ },
132
+ ],
133
+ events: [
134
+ {
135
+ tag: 'ValidEvent',
136
+ payload: { value: 'string' },
137
+ },
138
+ ],
139
+ },
140
+ ],
141
+ };
142
+
143
+ const result = validateSchema(schema);
144
+ expect(result.valid).toBe(true);
145
+ expect(result.errors).toHaveLength(0);
146
+ });
147
+ });
148
+
149
+ describe('createSchemaTemplate', () => {
150
+ it('creates a valid schema template', () => {
151
+ const schema = createSchemaTemplate('MyApp');
152
+
153
+ expect(schema.name).toBe('MyApp');
154
+ expect(schema.version).toBe('1.0.0');
155
+ expect(schema.description).toContain('MyApp');
156
+ expect(schema.models).toBeDefined();
157
+ expect(schema.components).toBeDefined();
158
+ expect(schema.logic).toBeDefined();
159
+ });
160
+ });
161
+
162
+ describe('normalizeSchema', () => {
163
+ it('normalizes a schema with models', () => {
164
+ const schema: PraxisSchema = {
165
+ version: '1.0.0',
166
+ name: 'TestApp',
167
+ models: [
168
+ {
169
+ name: 'User',
170
+ fields: [
171
+ { name: 'id', type: 'string' },
172
+ { name: 'name', type: 'string' },
173
+ ],
174
+ },
175
+ ],
176
+ };
177
+
178
+ const normalized = normalizeSchema(schema);
179
+
180
+ expect(normalized.models).toHaveLength(1);
181
+ expect(normalized.models[0].fullName).toBe('TestApp.User');
182
+ expect(normalized.models[0].allFields).toHaveLength(2);
183
+ });
184
+
185
+ it('resolves model references in components', () => {
186
+ const schema: PraxisSchema = {
187
+ version: '1.0.0',
188
+ name: 'TestApp',
189
+ models: [
190
+ {
191
+ name: 'User',
192
+ fields: [
193
+ { name: 'id', type: 'string' },
194
+ ],
195
+ },
196
+ ],
197
+ components: [
198
+ {
199
+ name: 'UserForm',
200
+ type: 'form',
201
+ model: 'User',
202
+ },
203
+ ],
204
+ };
205
+
206
+ const normalized = normalizeSchema(schema);
207
+
208
+ expect(normalized.components).toHaveLength(1);
209
+ expect(normalized.components[0].resolvedModel).toBeDefined();
210
+ expect(normalized.components[0].resolvedModel?.name).toBe('User');
211
+ });
212
+
213
+ it('extracts model dependencies', () => {
214
+ const schema: PraxisSchema = {
215
+ version: '1.0.0',
216
+ name: 'TestApp',
217
+ models: [
218
+ {
219
+ name: 'User',
220
+ fields: [
221
+ { name: 'id', type: 'string' },
222
+ ],
223
+ },
224
+ {
225
+ name: 'Task',
226
+ fields: [
227
+ { name: 'id', type: 'string' },
228
+ { name: 'userId', type: { reference: 'User' } },
229
+ ],
230
+ },
231
+ ],
232
+ };
233
+
234
+ const normalized = normalizeSchema(schema);
235
+ const taskModel = normalized.models.find(m => m.name === 'Task');
236
+
237
+ expect(taskModel).toBeDefined();
238
+ expect(taskModel?.dependencies).toContain('User');
239
+ });
240
+ });
241
+ });