bunsane 0.1.4 → 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 (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. package/tests/scheduler.test.ts +0 -724
@@ -1,339 +0,0 @@
1
- import { describe, test, expect, beforeAll } from "bun:test";
2
- import { BaseComponent, CompData, Component } from "../core/Components";
3
- import type { ComponentDataType } from "../core/Components";
4
- import { Entity } from "../core/Entity";
5
- import App from "../core/App";
6
- import ComponentRegistry from "../core/ComponentRegistry";
7
- import db from "../database";
8
-
9
- // Test component with 'value' attribute (standard assumption)
10
- @Component
11
- class ValueComponent extends BaseComponent {
12
- @CompData()
13
- value: string = "";
14
- }
15
-
16
- // Test component with different attribute name
17
- @Component
18
- class CustomAttributeComponent extends BaseComponent {
19
- @CompData()
20
- customData: string = "";
21
- }
22
-
23
- // Test component with multiple attributes
24
- @Component
25
- class MultiAttributeComponent extends BaseComponent {
26
- @CompData()
27
- title: string = "";
28
- @CompData()
29
- content: string = "";
30
- @CompData({ indexed: true })
31
- category: string = "";
32
- }
33
-
34
- // Test component with no data attributes
35
- @Component
36
- class NoDataComponent extends BaseComponent {
37
- // No @CompData properties
38
- }
39
-
40
- // Test component with numeric value
41
- @Component
42
- class NumericComponent extends BaseComponent {
43
- @CompData()
44
- count: number = 0;
45
- }
46
-
47
- // Test component with boolean value
48
- @Component
49
- class BooleanComponent extends BaseComponent {
50
- @CompData()
51
- enabled: boolean = false;
52
- }
53
-
54
- let app: App;
55
-
56
- beforeAll(async () => {
57
- app = new App();
58
- app.init();
59
- await app.waitForAppReady();
60
- });
61
-
62
- describe("Component Edge Cases and Attribute Handling", () => {
63
- describe("Component with standard 'value' attribute", () => {
64
- test("should correctly identify and return value property", () => {
65
- const comp = new ValueComponent();
66
- comp.value = "test value";
67
-
68
- const props = comp.properties();
69
- expect(props).toContain("value");
70
- expect(props).toHaveLength(1);
71
-
72
- const data = comp.data();
73
- expect(data.value).toBe("test value");
74
- });
75
- });
76
-
77
- describe("Component with custom attribute name", () => {
78
- test("should correctly identify and return custom property", () => {
79
- const comp = new CustomAttributeComponent();
80
- comp.customData = "custom data";
81
-
82
- const props = comp.properties();
83
- expect(props).toContain("customData");
84
- expect(props).toHaveLength(1);
85
-
86
- const data = comp.data();
87
- expect(data.customData).toBe("custom data");
88
- });
89
- });
90
-
91
- describe("Component with multiple attributes", () => {
92
- test("should correctly identify all data properties", () => {
93
- const comp = new MultiAttributeComponent();
94
- comp.title = "Test Title";
95
- comp.content = "Test Content";
96
- comp.category = "Test Category";
97
-
98
- const props = comp.properties();
99
- expect(props).toContain("title");
100
- expect(props).toContain("content");
101
- expect(props).toContain("category");
102
- expect(props).toHaveLength(3);
103
-
104
- const indexedProps = comp.indexedProperties();
105
- expect(indexedProps).toContain("category");
106
- expect(indexedProps).toHaveLength(1);
107
- });
108
-
109
- test("should return all data in data() method", () => {
110
- const comp = new MultiAttributeComponent();
111
- comp.title = "Title";
112
- comp.content = "Content";
113
- comp.category = "Category";
114
-
115
- const data = comp.data();
116
- expect(data.title).toBe("Title");
117
- expect(data.content).toBe("Content");
118
- expect(data.category).toBe("Category");
119
- });
120
- });
121
-
122
- describe("Component with no data attributes", () => {
123
- test("should have empty properties array", () => {
124
- const comp = new NoDataComponent();
125
-
126
- const props = comp.properties();
127
- expect(props).toHaveLength(0);
128
-
129
- const data = comp.data();
130
- expect(Object.keys(data)).toHaveLength(0);
131
- });
132
- });
133
-
134
- describe("Component with numeric attribute", () => {
135
- test("should handle numeric values correctly", () => {
136
- const comp = new NumericComponent();
137
- comp.count = 42;
138
-
139
- const data = comp.data();
140
- expect(data.count).toBe(42);
141
- expect(typeof data.count).toBe("number");
142
- });
143
- });
144
-
145
- describe("Component with boolean attribute", () => {
146
- test("should handle boolean values correctly", () => {
147
- const comp = new BooleanComponent();
148
- comp.enabled = true;
149
-
150
- const data = comp.data();
151
- expect(data.enabled).toBe(true);
152
- expect(typeof data.enabled).toBe("boolean");
153
- });
154
- });
155
-
156
- describe("Component update operations", () => {
157
- test("should handle partial updates for multi-attribute components", () => {
158
- const comp = new MultiAttributeComponent();
159
- comp.title = "Initial Title";
160
- comp.content = "Initial Content";
161
- comp.category = "Initial Category";
162
-
163
- // Simulate partial update
164
- comp.title = "Updated Title";
165
-
166
- const data = comp.data();
167
- expect(data.title).toBe("Updated Title");
168
- expect(data.content).toBe("Initial Content");
169
- expect(data.category).toBe("Initial Category");
170
- });
171
- });
172
-
173
- describe("Component type safety and data integrity", () => {
174
- test("should maintain type safety for ComponentDataType", () => {
175
- const comp = new ValueComponent();
176
- comp.value = "test";
177
-
178
- const data: ComponentDataType<ValueComponent> = comp.data();
179
- expect(data.value).toBe("test");
180
- // TypeScript should prevent accessing non-existent properties
181
- // This test ensures the type system works correctly
182
- });
183
-
184
- test("should exclude non-data properties from data()", () => {
185
- const comp = new ValueComponent();
186
- comp.value = "test";
187
- // id is not a data property
188
- comp.id = "some-id";
189
-
190
- const data = comp.data();
191
- expect(data.value).toBe("test");
192
- expect((data as any).id).toBeUndefined();
193
- });
194
- });
195
-
196
- describe("Component registry and type identification", () => {
197
- test("should generate unique type IDs for different components", () => {
198
- const comp1 = new ValueComponent();
199
- const comp2 = new CustomAttributeComponent();
200
-
201
- expect(comp1.getTypeID()).not.toBe(comp2.getTypeID());
202
- });
203
- });
204
-
205
- describe("Error handling for malformed components", () => {
206
- test("should handle components with undefined values gracefully", () => {
207
- const comp = new ValueComponent();
208
- // value is undefined initially
209
-
210
- const data = comp.data();
211
- expect(data.value).toBe(""); // Default value
212
- });
213
- });
214
- });
215
-
216
- describe("Component Removal from Entities", () => {
217
- test("should successfully remove an existing component from an entity", () => {
218
- const entity = Entity.Create();
219
- entity.add(ValueComponent, { value: "test" });
220
-
221
- // Verify component is added
222
- expect(entity.componentList()).toHaveLength(1);
223
- expect(entity.componentList()[0]).toBeInstanceOf(ValueComponent);
224
-
225
- // Remove the component
226
- const removed = entity.remove(ValueComponent);
227
- expect(removed).toBe(true);
228
-
229
- // Verify component is removed
230
- expect(entity.componentList()).toHaveLength(0);
231
- });
232
-
233
- test("should return false when trying to remove a non-existent component", () => {
234
- const entity = Entity.Create();
235
-
236
- // Try to remove a component that was never added
237
- const removed = entity.remove(ValueComponent);
238
- expect(removed).toBe(false);
239
-
240
- // Entity should still have no components
241
- expect(entity.componentList()).toHaveLength(0);
242
- });
243
-
244
- test("should remove only the specified component type, leaving others intact", () => {
245
- const entity = Entity.Create();
246
- entity.add(ValueComponent, { value: "value comp" });
247
- entity.add(CustomAttributeComponent, { customData: "custom comp" });
248
-
249
- // Verify both components are added
250
- expect(entity.componentList()).toHaveLength(2);
251
-
252
- // Remove only the ValueComponent
253
- const removed = entity.remove(ValueComponent);
254
- expect(removed).toBe(true);
255
-
256
- // Verify only CustomAttributeComponent remains
257
- expect(entity.componentList()).toHaveLength(1);
258
- expect(entity.componentList()[0]).toBeInstanceOf(CustomAttributeComponent);
259
- });
260
-
261
- test("should mark entity as dirty after component removal", () => {
262
- const entity = Entity.Create();
263
- entity.add(ValueComponent, { value: "test" });
264
-
265
- // Entity should be dirty after adding
266
- expect((entity as any)._dirty).toBe(true);
267
-
268
- // Save to make it clean
269
- // Note: We can't actually save without database, but we can set it manually for test
270
- entity.setDirty(false);
271
- expect((entity as any)._dirty).toBe(false);
272
-
273
- // Remove component
274
- entity.remove(ValueComponent);
275
-
276
- // Entity should be dirty again
277
- expect((entity as any)._dirty).toBe(true);
278
- });
279
-
280
- test("should persist component addition and removal to database", async () => {
281
- const entity = Entity.Create();
282
- entity.add(ValueComponent, { value: "test value" });
283
-
284
- // Save to database
285
- const saveResult = await entity.save();
286
- expect(saveResult).toBe(true);
287
-
288
- // Verify component exists in database
289
- const componentsAfterAdd = await db`SELECT id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
290
- expect(componentsAfterAdd.length).toBe(1);
291
- expect(componentsAfterAdd[0].data.value).toBe("test value");
292
-
293
- // Remove the component
294
- const removed = entity.remove(ValueComponent);
295
- expect(removed).toBe(true);
296
-
297
- // Save again to persist removal
298
- const saveResult2 = await entity.save();
299
- expect(saveResult2).toBe(true);
300
-
301
- // Verify component is removed from database
302
- const componentsAfterRemove = await db`SELECT id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
303
- expect(componentsAfterRemove.length).toBe(0);
304
- });
305
-
306
- test("should handle multiple component additions and removals in database", async () => {
307
- const entity = Entity.Create();
308
- entity.add(ValueComponent, { value: "value comp" });
309
- entity.add(CustomAttributeComponent, { customData: "custom comp" });
310
-
311
- // Save to database
312
- await entity.save();
313
-
314
- // Verify both components exist
315
- const componentsAfterAdd = await db`SELECT type_id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL ORDER BY type_id`;
316
- expect(componentsAfterAdd.length).toBe(2);
317
- const valueComp = componentsAfterAdd.find((c: any) => c.data.value === "value comp");
318
- const customComp = componentsAfterAdd.find((c: any) => c.data.customData === "custom comp");
319
- expect(valueComp).toBeDefined();
320
- expect(customComp).toBeDefined();
321
-
322
- // Remove one component
323
- entity.remove(ValueComponent);
324
- await entity.save();
325
-
326
- // Verify only custom component remains
327
- const componentsAfterRemove = await db`SELECT type_id, data FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
328
- expect(componentsAfterRemove.length).toBe(1);
329
- expect(componentsAfterRemove[0].data.customData).toBe("custom comp");
330
-
331
- // Remove the last component
332
- entity.remove(CustomAttributeComponent);
333
- await entity.save();
334
-
335
- // Verify no components remain
336
- const componentsAfterRemoveAll = await db`SELECT id FROM components WHERE entity_id = ${entity.id} AND deleted_at IS NULL`;
337
- expect(componentsAfterRemoveAll.length).toBe(0);
338
- });
339
- });
@@ -1,155 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { createUserFriendlyError, handleGraphQLError } from "../core/ErrorHandler";
3
- import { getErrorMessage, mapZodPathToErrorCode } from "../utils/errorMessages";
4
- import { GraphQLError } from "graphql";
5
- import * as z from "zod";
6
-
7
- describe('Error Handling Phase 1 Tests', () => {
8
- describe('User-Friendly Error Messages', () => {
9
- test('should return correct error message for known error code', () => {
10
- const errorInfo = getErrorMessage('INVALID_EMAIL');
11
- expect(errorInfo.userMessage).toBe('Please enter a valid email address');
12
- expect(errorInfo.suggestion).toBe('Check that your email follows the format: name@example.com');
13
- expect(errorInfo.category).toBe('validation');
14
- });
15
-
16
- test('should return fallback message for unknown error code', () => {
17
- const errorInfo = getErrorMessage('UNKNOWN_CODE');
18
- expect(errorInfo.userMessage).toBe('An unexpected error occurred');
19
- expect(errorInfo.category).toBe('system');
20
- });
21
-
22
- test('should map Zod paths to error codes correctly', () => {
23
- expect(mapZodPathToErrorCode(['email'])).toBe('INVALID_EMAIL');
24
- expect(mapZodPathToErrorCode(['password'])).toBe('TOO_SHORT');
25
- expect(mapZodPathToErrorCode(['unknownField'])).toBe('INVALID_FORMAT');
26
- });
27
- });
28
-
29
- describe('createUserFriendlyError function', () => {
30
- test('should create GraphQL error with user-friendly message', () => {
31
- const error = createUserFriendlyError('INVALID_EMAIL');
32
-
33
- expect(error).toBeInstanceOf(GraphQLError);
34
- expect(error.message).toBe('Please enter a valid email address');
35
- expect(error.extensions).toEqual({
36
- code: 'INVALID_EMAIL',
37
- category: 'validation',
38
- suggestion: 'Check that your email follows the format: name@example.com',
39
- userFriendly: true
40
- });
41
- });
42
-
43
- test('should allow custom message override', () => {
44
- const customMessage = 'Custom email error';
45
- const error = createUserFriendlyError('INVALID_EMAIL', customMessage);
46
-
47
- expect(error.message).toBe(customMessage);
48
- expect(error.extensions?.code).toBe('INVALID_EMAIL');
49
- });
50
-
51
- test('should merge additional extensions', () => {
52
- const error = createUserFriendlyError('INVALID_EMAIL', undefined, {
53
- extensions: { additionalField: 'test' }
54
- });
55
-
56
- expect(error.extensions?.additionalField).toBe('test');
57
- expect(error.extensions?.userFriendly).toBe(true);
58
- });
59
- });
60
-
61
- describe('handleGraphQLError function', () => {
62
- test('should handle Zod validation errors with user-friendly messages', () => {
63
- // Create a real Zod error by validating invalid data with a field name
64
- const userSchema = z.object({
65
- email: z.string().email()
66
- });
67
- let zodError: z.ZodError;
68
-
69
- try {
70
- userSchema.parse({ email: 'invalid-email' });
71
- } catch (error) {
72
- zodError = error as z.ZodError;
73
- }
74
-
75
- expect(() => handleGraphQLError(zodError!)).toThrow(GraphQLError);
76
-
77
- try {
78
- handleGraphQLError(zodError!);
79
- } catch (error: any) {
80
- expect(error.message).toBe('Please enter a valid email address');
81
- expect(error.extensions?.code).toBe('VALIDATION_ERROR');
82
- expect(error.extensions?.category).toBe('validation');
83
- expect(error.extensions?.userFriendly).toBe(true);
84
- expect(error.extensions?.validationErrors).toBeDefined();
85
- expect(error.extensions?.suggestion).toBe('Check that your email follows the format: name@example.com');
86
- }
87
- });
88
-
89
- test('should handle multiple Zod validation errors', () => {
90
- // Create a schema that will produce multiple validation errors
91
- const userSchema = z.object({
92
- email: z.string().email(),
93
- password: z.string().min(8)
94
- });
95
-
96
- let zodError: z.ZodError;
97
-
98
- try {
99
- userSchema.parse({
100
- email: 'invalid-email',
101
- password: 'short'
102
- });
103
- } catch (error) {
104
- zodError = error as z.ZodError;
105
- }
106
-
107
- expect(() => handleGraphQLError(zodError!)).toThrow(GraphQLError);
108
-
109
- try {
110
- handleGraphQLError(zodError!);
111
- } catch (error: any) {
112
- expect(error.message).toContain('Please enter a valid email address');
113
- expect(error.extensions?.validationErrors).toHaveLength(2);
114
- }
115
- });
116
-
117
- test('should handle empty Zod errors gracefully', () => {
118
- const zodError = new z.ZodError([]);
119
-
120
- expect(() => handleGraphQLError(zodError)).toThrow(GraphQLError);
121
-
122
- try {
123
- handleGraphQLError(zodError);
124
- } catch (error: any) {
125
- expect(error.message).toBe('Validation failed');
126
- expect(error.extensions?.code).toBe('VALIDATION_ERROR');
127
- expect(error.extensions?.userFriendly).toBe(true);
128
- }
129
- });
130
-
131
- test('should re-throw existing GraphQL errors', () => {
132
- const originalError = new GraphQLError('Original error', {
133
- extensions: { code: 'ORIGINAL' }
134
- });
135
-
136
- expect(() => handleGraphQLError(originalError)).toThrow(originalError);
137
- });
138
-
139
- test('should handle unknown errors with user-friendly message', () => {
140
- const unknownError = new Error('Some unknown error');
141
-
142
- expect(() => handleGraphQLError(unknownError)).toThrow(GraphQLError);
143
-
144
- try {
145
- handleGraphQLError(unknownError);
146
- } catch (error: any) {
147
- expect(error.message).toBe('Something went wrong on our end');
148
- expect(error.extensions?.code).toBe('INTERNAL_ERROR');
149
- expect(error.extensions?.category).toBe('system');
150
- expect(error.extensions?.suggestion).toBe('Please try again in a few moments. If the problem persists, contact support');
151
- expect(error.extensions?.userFriendly).toBe(true);
152
- }
153
- });
154
- });
155
- });