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
@@ -0,0 +1,79 @@
1
+ import { GraphVisitor } from "./GraphVisitor";
2
+ import { TypeNode, OperationNode, FieldNode, InputNode, ScalarNode } from "../graph/GraphNode";
3
+
4
+ /**
5
+ * Visitor that collects all type definitions in dependency order.
6
+ * Used to gather type definitions before schema generation.
7
+ */
8
+ export class TypeCollectorVisitor extends GraphVisitor {
9
+ private collectedTypes: Map<string, string> = new Map();
10
+ private collectedInputs: Map<string, string> = new Map();
11
+ private collectedScalars: Set<string> = new Set();
12
+
13
+ visitTypeNode(node: TypeNode): void {
14
+ this.collectedTypes.set(node.name, node.typeDef);
15
+ }
16
+
17
+ visitOperationNode(node: OperationNode): void {
18
+ // Operations don't define types themselves, but may reference them
19
+ // This visitor focuses on collecting type definitions
20
+ }
21
+
22
+ visitFieldNode(node: FieldNode): void {
23
+ // Fields don't define types themselves
24
+ }
25
+
26
+ visitInputNode(node: InputNode): void {
27
+ this.collectedInputs.set(node.name, node.typeDef);
28
+ }
29
+
30
+ visitScalarNode(node: ScalarNode): void {
31
+ this.collectedScalars.add(node.name);
32
+ }
33
+
34
+ getResults(): {
35
+ types: Map<string, string>;
36
+ inputs: Map<string, string>;
37
+ scalars: Set<string>;
38
+ } {
39
+ return {
40
+ types: new Map(this.collectedTypes),
41
+ inputs: new Map(this.collectedInputs),
42
+ scalars: new Set(this.collectedScalars)
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Get all type definitions as a single string in dependency order
48
+ */
49
+ getTypeDefsString(): string {
50
+ const results = this.getResults();
51
+ let typeDefs = '';
52
+
53
+ // Add scalar definitions first
54
+ for (const scalarName of results.scalars) {
55
+ typeDefs += `scalar ${scalarName}\n`;
56
+ }
57
+
58
+ // Add input types
59
+ for (const [name, typeDef] of results.inputs) {
60
+ typeDefs += `${typeDef}\n`;
61
+ }
62
+
63
+ // Add object types
64
+ for (const [name, typeDef] of results.types) {
65
+ typeDefs += `${typeDef}\n`;
66
+ }
67
+
68
+ return typeDefs.trim();
69
+ }
70
+
71
+ /**
72
+ * Clear all collected types
73
+ */
74
+ clear(): void {
75
+ this.collectedTypes.clear();
76
+ this.collectedInputs.clear();
77
+ this.collectedScalars.clear();
78
+ }
79
+ }
@@ -0,0 +1,96 @@
1
+ import { GraphVisitor } from "./GraphVisitor";
2
+ import { SchemaGraph } from "../graph/SchemaGraph";
3
+
4
+ /**
5
+ * Composes multiple visitors to run them in sequence on a graph.
6
+ * Allows chaining visitor operations for complex processing pipelines.
7
+ */
8
+ export class VisitorComposer {
9
+ private visitors: GraphVisitor[] = [];
10
+
11
+ /**
12
+ * Add a visitor to the composition
13
+ */
14
+ addVisitor(visitor: GraphVisitor): VisitorComposer {
15
+ this.visitors.push(visitor);
16
+ return this;
17
+ }
18
+
19
+ /**
20
+ * Run all visitors on the given graph in sequence
21
+ */
22
+ visitGraph(graph: SchemaGraph): Record<string, any> {
23
+ for (const visitor of this.visitors) {
24
+ // Call beforeVisit for setup
25
+ visitor.beforeVisit();
26
+
27
+ // Visit all nodes in topological order
28
+ const sortedNodes = graph.topologicalSort();
29
+ for (const node of sortedNodes) {
30
+ visitor.visit(node);
31
+ }
32
+
33
+ // Call afterVisit for cleanup
34
+ visitor.afterVisit();
35
+ }
36
+
37
+ return this.getResults();
38
+ }
39
+
40
+ /**
41
+ * Run all visitors on a specific set of nodes
42
+ */
43
+ visitNodes(nodes: any[]): Record<string, any> {
44
+ for (const visitor of this.visitors) {
45
+ for (const node of nodes) {
46
+ visitor.visit(node);
47
+ }
48
+ }
49
+
50
+ return this.getResults();
51
+ }
52
+
53
+ /**
54
+ * Remove a specific visitor from the composition
55
+ */
56
+ removeVisitor(visitor: GraphVisitor): boolean {
57
+ const index = this.visitors.indexOf(visitor);
58
+ if (index > -1) {
59
+ this.visitors.splice(index, 1);
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ /**
66
+ * Clear all visitors from the composer
67
+ */
68
+ clearVisitors(): void {
69
+ this.visitors = [];
70
+ }
71
+
72
+ /**
73
+ * Get results from all visitors as an object
74
+ */
75
+ getResults(): Record<string, any> {
76
+ const results: Record<string, any> = {};
77
+ this.visitors.forEach((visitor, index) => {
78
+ results[`visitor-${index}`] = visitor.getResults();
79
+ });
80
+ return results;
81
+ }
82
+
83
+ /**
84
+ * Get the number of visitors in the composition
85
+ */
86
+ size(): number {
87
+ return this.visitors.length;
88
+ }
89
+
90
+ /**
91
+ * Get all visitors in the composition
92
+ */
93
+ getVisitors(): GraphVisitor[] {
94
+ return [...this.visitors];
95
+ }
96
+ }
@@ -0,0 +1,7 @@
1
+ export { GraphVisitor } from "./GraphVisitor";
2
+ export { TypeCollectorVisitor } from "./TypeCollectorVisitor";
3
+ export { DeduplicationVisitor } from "./DeduplicationVisitor";
4
+ export { SchemaGeneratorVisitor } from "./SchemaGeneratorVisitor";
5
+ export { ResolverGeneratorVisitor } from "./ResolverGeneratorVisitor";
6
+ export { ArchetypePreprocessorVisitor } from "./ArchetypePreprocessorVisitor";
7
+ export { VisitorComposer } from "./VisitorComposer";
package/package.json CHANGED
@@ -1,39 +1,61 @@
1
1
  {
2
- "name": "bunsane",
3
- "version": "0.1.4",
4
- "author": {
5
- "name": "yaaruu"
6
- },
7
- "keywords": [
8
- "bun",
9
- "framework",
10
- "entity-component-system",
11
- "ecs",
12
- "graphql",
13
- "typescript"
14
- ],
15
- "module": "index.ts",
16
- "type": "module",
17
- "devDependencies": {
18
- "@types/bun": "latest"
19
- },
20
- "peerDependencies": {
21
- "typescript": "^5"
22
- },
23
- "dependencies": {
24
- "@gqloom/core": "^0.12.0",
25
- "@gqloom/zod": "^0.12.2",
26
- "dataloader": "2.2.2",
27
- "graphql": "16.11.0",
28
- "graphql-yoga": "5.15.1",
29
- "pino": "9.9.0",
30
- "pino-pretty": "13.1.1",
31
- "reflect-metadata": "0.2.2",
32
- "zod": "4.1.5"
33
- },
34
- "repository": {
35
- "type": "git",
36
- "url": "https://github.com/yaaruu/bunsane.git"
37
- },
38
- "license": "MIT"
2
+ "name": "bunsane",
3
+ "version": "0.2.0",
4
+ "author": {
5
+ "name": "yaaruu"
6
+ },
7
+ "keywords": [
8
+ "bun",
9
+ "framework",
10
+ "entity-component-system",
11
+ "ecs",
12
+ "graphql",
13
+ "typescript"
14
+ ],
15
+ "module": "index.ts",
16
+ "type": "module",
17
+ "scripts": {
18
+ "build": "bun run build:studio && tsc",
19
+ "build:studio": "cd studio && bun install && bun run build",
20
+ "dev": "tsc --watch",
21
+ "test": "bun test tests/unit tests/integration tests/graphql",
22
+ "test:all": "bun test tests/",
23
+ "test:unit": "bun test tests/unit/",
24
+ "test:integration": "bun test tests/integration/",
25
+ "test:graphql": "bun test tests/graphql/",
26
+ "test:pglite": "bun tests/pglite-setup.ts tests/unit tests/integration tests/graphql",
27
+ "test:pglite:unit": "bun tests/pglite-setup.ts tests/unit/",
28
+ "test:e2e": "SKIP_TEST_DB_SETUP=true bun test tests/e2e/",
29
+ "test:stress": "bun test tests/stress --timeout 600000",
30
+ "test:stress:smoke": "STRESS_RECORD_COUNT=10000 bun test tests/stress --timeout 300000",
31
+ "test:stress:standard": "STRESS_RECORD_COUNT=100000 bun test tests/stress --timeout 600000",
32
+ "test:stress:full": "STRESS_RECORD_COUNT=1000000 bun test tests/stress --timeout 1800000"
33
+ },
34
+ "devDependencies": {
35
+ "@electric-sql/pglite": "^0.3.15",
36
+ "@electric-sql/pglite-socket": "^0.0.20",
37
+ "@types/bun": "latest",
38
+ "@types/node": "^25.0.3",
39
+ "knip": "^5.78.0"
40
+ },
41
+ "peerDependencies": {
42
+ "typescript": "^5.9.3"
43
+ },
44
+ "dependencies": {
45
+ "@gqloom/core": "^0.12.0",
46
+ "@gqloom/zod": "^0.12.2",
47
+ "dataloader": "2.2.2",
48
+ "graphql": "16.11.0",
49
+ "graphql-yoga": "5.15.1",
50
+ "ioredis": "5.8.2",
51
+ "pino": "9.9.0",
52
+ "pino-pretty": "13.1.1",
53
+ "reflect-metadata": "0.2.2",
54
+ "zod": "4.1.5"
55
+ },
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "https://github.com/yaaruu/bunsane.git"
59
+ },
60
+ "license": "MIT"
39
61
  }
package/plugins/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type App from "core/App";
2
- import type { ApplicationPhase } from "core/ApplicationLifecycle";
1
+ import type App from "../core/App";
2
+ import type { ApplicationPhase } from "../core/ApplicationLifecycle";
3
3
 
4
4
  abstract class BasePlugin {
5
5
  name!: string;
@@ -0,0 +1,97 @@
1
+ import { QueryNode } from "./QueryNode";
2
+ import type { QueryResult } from "./QueryNode";
3
+ import { QueryContext } from "./QueryContext";
4
+
5
+ export class CTENode extends QueryNode {
6
+ public execute(context: QueryContext): QueryResult {
7
+ // Generate CTE for base entity filtering
8
+ const componentIds = Array.from(context.componentIds);
9
+ const excludedIds = Array.from(context.excludedComponentIds);
10
+
11
+ if (componentIds.length === 0) {
12
+ throw new Error("CTENode requires at least one component type to filter on");
13
+ }
14
+
15
+ let cteSql = "WITH base_entities AS (\n";
16
+ cteSql += " SELECT DISTINCT ec.entity_id\n";
17
+ cteSql += " FROM entity_components ec\n";
18
+ cteSql += " WHERE ec.type_id IN (";
19
+
20
+ // Add component type placeholders
21
+ const typePlaceholders = componentIds.map((_, index) => `$${context.addParam(componentIds[index])}`).join(', ');
22
+ cteSql += typePlaceholders + ")\n";
23
+ cteSql += " AND ec.deleted_at IS NULL\n";
24
+
25
+ // Add cursor-based pagination filter in CTE (more efficient than OFFSET)
26
+ if (context.cursorId !== null) {
27
+ const operator = context.cursorDirection === 'after' ? '>' : '<';
28
+ cteSql += ` AND ec.entity_id ${operator} $${context.addParam(context.cursorId)}\n`;
29
+ }
30
+
31
+ // Add exclusions if any
32
+ if (excludedIds.length > 0) {
33
+ const excludedPlaceholders = excludedIds.map((id) => `$${context.addParam(id)}`).join(', ');
34
+ cteSql += ` AND NOT EXISTS (\n`;
35
+ cteSql += ` SELECT 1 FROM entity_components ec_ex\n`;
36
+ cteSql += ` WHERE ec_ex.entity_id = ec.entity_id\n`;
37
+ cteSql += ` AND ec_ex.type_id IN (${excludedPlaceholders})\n`;
38
+ cteSql += ` AND ec_ex.deleted_at IS NULL\n`;
39
+ cteSql += ` )\n`;
40
+ }
41
+
42
+ // Add entity exclusions if any
43
+ if (context.excludedEntityIds.size > 0) {
44
+ const entityExcludedIds = Array.from(context.excludedEntityIds);
45
+ const entityPlaceholders = entityExcludedIds.map((id) => `$${context.addParam(id)}`).join(', ');
46
+ cteSql += ` AND ec.entity_id NOT IN (${entityPlaceholders})\n`;
47
+ }
48
+
49
+ // Group by entity_id to count distinct component types
50
+ // This ensures entities have ALL required components
51
+ cteSql += ` GROUP BY ec.entity_id\n`;
52
+ cteSql += ` HAVING COUNT(DISTINCT ec.type_id) >= $${context.addParam(componentIds.length)}\n`;
53
+
54
+ // Add ORDER BY for deterministic pagination results
55
+ // Must be before LIMIT/OFFSET for consistent page results
56
+ // Reverse order for 'before' cursor direction
57
+ const orderDirection = context.cursorDirection === 'before' ? 'DESC' : 'ASC';
58
+ cteSql += ` ORDER BY ec.entity_id ${orderDirection}\n`;
59
+
60
+ // Check if there are component filters - if so, pagination must happen AFTER filtering
61
+ // Otherwise we'd limit results before applying filters, causing incorrect results
62
+ const hasComponentFilters = context.componentFilters.size > 0;
63
+
64
+ // Add LIMIT/OFFSET at CTE level ONLY when there are no component filters
65
+ // When filters exist, pagination must be applied after LATERAL joins filter the results
66
+ if (!hasComponentFilters) {
67
+ if (context.limit !== null) {
68
+ cteSql += ` LIMIT $${context.addParam(context.limit)}\n`;
69
+ }
70
+ // Only include OFFSET when not using cursor-based pagination
71
+ if (context.cursorId === null && (context.offsetValue > 0 || context.limit !== null)) {
72
+ cteSql += ` OFFSET $${context.addParam(context.offsetValue)}\n`;
73
+ }
74
+ // Mark pagination as handled at CTE level to prevent double application
75
+ context.paginationAppliedInCTE = true;
76
+ } else {
77
+ // Pagination will be applied later, after filters
78
+ context.paginationAppliedInCTE = false;
79
+ }
80
+
81
+ cteSql += ")";
82
+
83
+ // Mark CTE as available in context
84
+ context.hasCTE = true;
85
+ context.cteName = "base_entities";
86
+
87
+ return {
88
+ sql: cteSql,
89
+ params: context.params,
90
+ context
91
+ };
92
+ }
93
+
94
+ public getNodeType(): string {
95
+ return "CTENode";
96
+ }
97
+ }