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,101 +0,0 @@
1
- import { describe, it, expect, beforeAll, mock } from "bun:test";
2
- import Query from "../core/Query";
3
- import { BaseComponent, CompData, Component } from "../core/Components";
4
- import ComponentRegistry from "../core/ComponentRegistry";
5
-
6
- // Define test components
7
- @Component
8
- class TestUserComponent extends BaseComponent {
9
- @CompData()
10
- name: string = "";
11
-
12
- @CompData()
13
- age: number = 0;
14
-
15
- @CompData()
16
- score: number = 0;
17
- }
18
-
19
- @Component
20
- class TestPostComponent extends BaseComponent {
21
- @CompData()
22
- title: string = "";
23
-
24
- @CompData()
25
- createdAt: string = "";
26
- }
27
-
28
- describe("Query Sorting", () => {
29
- beforeAll(() => {
30
- // Mock ComponentRegistry for testing
31
- mock.restore();
32
-
33
- // Mock the getComponentId method to return predictable IDs
34
- (ComponentRegistry as any).getComponentId = mock((name: string) => {
35
- if (name === "TestUserComponent") return "test-user-type-id";
36
- if (name === "TestPostComponent") return "test-post-type-id";
37
- return undefined;
38
- });
39
- });
40
-
41
- it("should create a query with sortBy method", () => {
42
- const query = new Query();
43
- const result = query.with(TestUserComponent).sortBy(TestUserComponent, "age", "DESC");
44
-
45
- expect(result).toBeInstanceOf(Query);
46
- expect((result as any).sortOrders).toHaveLength(1);
47
- expect((result as any).sortOrders[0]).toEqual({
48
- component: "TestUserComponent",
49
- property: "age",
50
- direction: "DESC",
51
- nullsFirst: false
52
- });
53
- });
54
-
55
- it("should validate component is included in query before sorting", () => {
56
- const query = new Query();
57
-
58
- expect(() => {
59
- query.sortBy(TestUserComponent, "age");
60
- }).toThrow("Cannot sort by component TestUserComponent that is not included in the query");
61
- });
62
-
63
- it("should support orderBy with multiple sort orders", () => {
64
- const query = new Query();
65
- const sortOrders = [
66
- { component: "TestUserComponent", property: "age", direction: "DESC" as const },
67
- { component: "TestUserComponent", property: "name", direction: "ASC" as const }
68
- ];
69
-
70
- const result = query.with(TestUserComponent).orderBy(sortOrders);
71
-
72
- expect((result as any).sortOrders).toEqual(sortOrders);
73
- });
74
-
75
- it("should build correct ORDER BY clause for sorting", () => {
76
- const query = new Query();
77
- query.with(TestUserComponent).sortBy(TestUserComponent, "age", "DESC");
78
-
79
- const orderByClause = (query as any).buildOrderByClause(["test-user-type-id"]);
80
-
81
- expect(orderByClause).toContain("ORDER BY");
82
- expect(orderByClause).toContain("DESC");
83
- expect(orderByClause).toContain("ec.entity_id ASC");
84
- });
85
-
86
- it("should handle nulls first option", () => {
87
- const query = new Query();
88
- query.with(TestUserComponent).sortBy(TestUserComponent, "age", "ASC", true);
89
-
90
- expect((query as any).sortOrders[0].nullsFirst).toBe(true);
91
- });
92
-
93
- it("should build ORDER BY clause with nulls first", () => {
94
- const query = new Query();
95
- query.with(TestUserComponent).sortBy(TestUserComponent, "age", "ASC", true);
96
-
97
- const orderByClause = (query as any).buildOrderByClause(["test-user-type-id"]);
98
-
99
- expect(orderByClause).toContain("NULLS FIRST");
100
- });
101
- });
@@ -1,81 +0,0 @@
1
- import {describe, test, expect, beforeAll} from "bun:test"
2
- import App from "core/App"
3
- import { BaseComponent, CompData, Component } from "core/Components";
4
- import { Entity } from "core/Entity";
5
- import Query from "core/Query";
6
- import ComponentRegistry from "core/ComponentRegistry";
7
-
8
- let app;
9
- beforeAll(async () => {
10
- app = new App();
11
- app.init();
12
- await app.waitForAppReady();
13
- });
14
-
15
- @Component
16
- class QueryTestComponent extends BaseComponent {
17
- @CompData()
18
- value: string = "";
19
- }
20
-
21
- @Component
22
- class CountTestComponent extends BaseComponent {
23
- @CompData()
24
- value: string = "";
25
- }
26
-
27
- describe("Query test", async () => {
28
- test("Create and Update Entity", async () => {
29
- const entity = Entity.Create()
30
- .add(QueryTestComponent, {value: "Test"})
31
- await entity.save();
32
-
33
- const fetchedEntity = await Entity.FindById(entity.id);
34
- expect(fetchedEntity).not.toBeNull();
35
- expect(fetchedEntity?.componentList().length).toBe(1);
36
-
37
- await fetchedEntity?.set(QueryTestComponent, {value: "UpdatedTest"});
38
- console.log("Updating Entity");
39
- await fetchedEntity?.save();
40
-
41
- const updatedEntity = await Entity.FindById(entity.id);
42
- expect(updatedEntity).not.toBeNull();
43
- expect(updatedEntity?.componentList().length).toBe(1);
44
- const comp = await updatedEntity?.get(QueryTestComponent)
45
- expect(comp?.value).toBe("UpdatedTest");
46
-
47
- });
48
-
49
- test("Count method should return total entities matching query", async () => {
50
- // Create a few test entities without relying on component registration
51
- const entity1 = Entity.Create();
52
- const entity2 = Entity.Create();
53
- const entity3 = Entity.Create();
54
-
55
- await entity1.save();
56
- await entity2.save();
57
- await entity3.save();
58
-
59
- // Test count with specific entity ID
60
- const specificCount = await new Query()
61
- .findById(entity1.id)
62
- .count();
63
-
64
- expect(specificCount).toBe(1);
65
-
66
- // Test count with empty query (no components required)
67
- const emptyQueryCount = await new Query().count();
68
- expect(emptyQueryCount).toBe(0); // Empty query should return 0
69
-
70
- // Test count with multiple entity IDs by creating a more complex scenario
71
- // Since we can't easily test component-based counting without registration,
72
- // let's focus on the core functionality that works
73
- const entityIds = [entity1.id, entity2.id, entity3.id];
74
-
75
- // Test that we can count entities that exist
76
- for (const id of entityIds) {
77
- const count = await new Query().findById(id).count();
78
- expect(count).toBe(1);
79
- }
80
- });
81
- })
@@ -1,170 +0,0 @@
1
- import { describe, test, expect, beforeAll, beforeEach } from "bun:test";
2
- import App from "core/App";
3
- import { BaseComponent, CompData, Component } from "core/Components";
4
- import { Entity } from "core/Entity";
5
- import { BatchLoader } from "core/BatchLoader";
6
- import { isFieldRequestedSafe } from "gql/helpers";
7
- import db from "database";
8
-
9
- let app: App;
10
-
11
- beforeAll(async () => {
12
- app = new App();
13
- app.init();
14
- await app.waitForAppReady();
15
- });
16
-
17
- beforeEach(async () => {
18
- await db`TRUNCATE TABLE entities CASCADE;`;
19
- });
20
-
21
- @Component
22
- class AuthorComponent extends BaseComponent {
23
- @CompData()
24
- value: string = ""; // related user id
25
- }
26
-
27
- @Component
28
- class TitleComponent extends BaseComponent {
29
- @CompData()
30
- value: string = "";
31
- }
32
-
33
- @Component
34
- class UserComponent extends BaseComponent {
35
- @CompData()
36
- name: string = "";
37
- }
38
-
39
- describe('Relations Tests', () => {
40
- test('BatchLoader.loadRelatedEntitiesBatched fetches relations in one query', async () => {
41
- // Create users
42
- const user1 = Entity.Create().add(UserComponent, { name: "User1" });
43
- const user2 = Entity.Create().add(UserComponent, { name: "User2" });
44
- await Promise.all([user1.save(), user2.save()]);
45
-
46
- // Create posts with authors
47
- const post1 = Entity.Create()
48
- .add(TitleComponent, { value: "Post1" })
49
- .add(AuthorComponent, { value: user1.id });
50
- const post2 = Entity.Create()
51
- .add(TitleComponent, { value: "Post2" })
52
- .add(AuthorComponent, { value: user2.id });
53
- await Promise.all([post1.save(), post2.save()]);
54
-
55
- // Test batched loading
56
- const loader = async (ids: string[]) => {
57
- const entities = await Entity.LoadMultiple(ids);
58
- return entities;
59
- };
60
-
61
- const result = await BatchLoader.loadRelatedEntitiesBatched([post1, post2], AuthorComponent, loader);
62
-
63
- expect(result.size).toBe(2);
64
- expect(result.get(user1.id)?.id).toBe(user1.id);
65
- expect(result.get(user2.id)?.id).toBe(user2.id);
66
- });
67
-
68
- test('BatchLoader handles large parent list efficiently', async () => {
69
- // Create 100 users
70
- const users = [];
71
- for (let i = 0; i < 100; i++) {
72
- const user = Entity.Create().add(UserComponent, { name: `User${i}` });
73
- users.push(user);
74
- }
75
- await Promise.all(users.map(u => u.save()));
76
-
77
- // Create 100 posts with random authors
78
- const posts = [];
79
- for (let i = 0; i < 100; i++) {
80
- const randomUser = users[Math.floor(Math.random() * users.length)]!;
81
- const post = Entity.Create()
82
- .add(TitleComponent, { value: `Post${i}` })
83
- .add(AuthorComponent, { value: randomUser.id });
84
- posts.push(post);
85
- }
86
- await Promise.all(posts.map(p => p.save()));
87
-
88
- // Test batched loading performance
89
- const startTime = Date.now();
90
- const loader = async (ids: string[]) => {
91
- return await Entity.LoadMultiple(ids);
92
- };
93
-
94
- const result = await BatchLoader.loadRelatedEntitiesBatched(posts, AuthorComponent, loader);
95
- const endTime = Date.now();
96
-
97
- expect(result.size).toBeGreaterThan(0);
98
- expect(endTime - startTime).toBeLessThan(1000); // Should complete under 1 second
99
- });
100
-
101
- test('isFieldRequestedSafe handles nested selections', () => {
102
- const info = {
103
- fieldNodes: [{
104
- selectionSet: {
105
- selections: [
106
- { kind: 'Field', name: { value: 'id' } },
107
- {
108
- kind: 'Field',
109
- name: { value: 'author' },
110
- selectionSet: {
111
- selections: [{ kind: 'Field', name: { value: 'name' } }]
112
- }
113
- }
114
- ]
115
- }
116
- }]
117
- };
118
-
119
- expect(isFieldRequestedSafe(info, 'id')).toBe(true);
120
- expect(isFieldRequestedSafe(info, 'author')).toBe(true);
121
- expect(isFieldRequestedSafe(info, 'author', 'name')).toBe(true);
122
- expect(isFieldRequestedSafe(info, 'title')).toBe(false);
123
- });
124
-
125
- test('isFieldRequestedSafe handles fragments', () => {
126
- const info = {
127
- fieldNodes: [{
128
- selectionSet: {
129
- selections: [
130
- { kind: 'FragmentSpread', name: { value: 'UserFragment' } }
131
- ]
132
- }
133
- }]
134
- };
135
-
136
- // Assuming fragments are expanded, but for test, mock as if selections are there
137
- // In real scenario, fragments would be resolved
138
- expect(isFieldRequestedSafe(info, 'id')).toBe(false); // since no direct field
139
- });
140
-
141
- test('isFieldRequestedSafe handles missing selectionSet', () => {
142
- const info = {
143
- fieldNodes: [{
144
- // no selectionSet
145
- }]
146
- };
147
-
148
- expect(isFieldRequestedSafe(info, 'id')).toBe(false);
149
- });
150
-
151
- test('Entity.doDelete resolves promise correctly', async () => {
152
- const entity = Entity.Create().add(TitleComponent, { value: "Test" });
153
- await entity.save();
154
-
155
- const deletePromise = entity.delete();
156
- await expect(deletePromise).resolves.toBe(true);
157
-
158
- // Verify deleted (soft delete - check deleted_at is set)
159
- const check = await db`SELECT id, deleted_at FROM entities WHERE id = ${entity.id}`;
160
- expect(check.length).toBe(1);
161
- expect(check[0].deleted_at).not.toBeNull();
162
- });
163
-
164
- test('Entity.doDelete resolves false for non-persisted entity', async () => {
165
- const entity = new Entity(); // not saved
166
-
167
- const deletePromise = entity.delete();
168
- await expect(deletePromise).resolves.toBe(false);
169
- });
170
- });