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,100 +1,210 @@
1
- import { Entity } from "core/Entity";
2
- import { BaseComponent } from "core/Components";
1
+ import { Entity } from "./Entity";
2
+ import { BaseComponent } from "./components";
3
3
  import { timed } from "./Decorators";
4
4
  import db from "../database";
5
5
  import { sql } from "bun";
6
6
 
7
- // Phase 2A: Memory Pooling for Entity Objects
8
- class EntityPool {
9
- private static instance: EntityPool;
10
- private pool: Map<string, Entity[]> = new Map();
11
- private maxPoolSize = 1000;
12
-
13
- static getInstance(): EntityPool {
14
- if (!EntityPool.instance) {
15
- EntityPool.instance = new EntityPool();
16
- }
17
- return EntityPool.instance;
18
- }
19
-
20
- get(entityId: string): Entity | null {
21
- const entities = this.pool.get(entityId);
22
- if (entities && entities.length > 0) {
23
- return entities.pop()!;
24
- }
25
- return null;
26
- }
27
-
28
- put(entity: Entity): void {
29
- const entityId = entity.id;
30
- let entities = this.pool.get(entityId);
31
- if (!entities) {
32
- entities = [];
33
- this.pool.set(entityId, entities);
34
- }
35
- if (entities.length < this.maxPoolSize) {
36
- entities.push(entity);
37
- }
38
- }
7
+ interface CachedRelation {
8
+ ids: string[];
9
+ expiresAt: number;
10
+ }
39
11
 
40
- clear(): void {
41
- this.pool.clear();
42
- }
12
+ interface BatchLoaderOptions {
13
+ fieldName?: string;
14
+ batchSize?: number;
15
+ maxConcurrency?: number;
16
+ cacheTTL?: number;
43
17
  }
44
18
 
45
19
  export class BatchLoader {
46
- private static entityPool = EntityPool.getInstance();
47
-
48
- @timed("BatchLoader.loadRelatedEntities")
49
- static async loadRelatedEntities<C extends BaseComponent & { value: string }>(
50
- entities: Entity[],
51
- component: new () => C,
52
- loader: (ids: string[]) => Promise<Entity[]>
53
- ): Promise<Map<string, Entity>> {
54
- const ids: string[] = [];
55
- for (const entity of entities) {
56
- const data = await entity.get(component) as any;
57
- if (data?.value) {
58
- ids.push(data.value);
59
- }
60
- }
61
- const uniqueIds = [...new Set(ids)];
62
- const relatedEntities = await loader(uniqueIds);
63
- const map = new Map<string, Entity>();
64
- for (const related of relatedEntities) {
65
- map.set(related.id, related);
66
- }
67
- return map;
68
- }
20
+ private static cache = new Map<string, Map<string, CachedRelation>>();
21
+ private static readonly DEFAULT_BATCH_SIZE = 1000;
22
+ private static readonly DEFAULT_MAX_CONCURRENCY = 5;
23
+ private static readonly DEFAULT_CACHE_TTL = 300_000; // 5 minutes
69
24
 
25
+ /**
26
+ * Load related entities efficiently with caching and batching
27
+ */
70
28
  @timed("BatchLoader.loadRelatedEntitiesBatched")
71
29
  static async loadRelatedEntitiesBatched<C extends BaseComponent>(
72
30
  entities: Entity[],
73
31
  component: new () => C,
74
- loader: (ids: string[]) => Promise<Entity[]>
32
+ loader: (ids: string[]) => Promise<Entity[]>,
33
+ options: BatchLoaderOptions = {}
75
34
  ): Promise<Map<string, Entity>> {
76
35
  if (entities.length === 0) return new Map();
77
36
 
37
+ const {
38
+ fieldName = 'value',
39
+ batchSize = this.DEFAULT_BATCH_SIZE,
40
+ maxConcurrency = this.DEFAULT_MAX_CONCURRENCY,
41
+ cacheTTL = this.DEFAULT_CACHE_TTL
42
+ } = options;
43
+
78
44
  const comp = new component();
79
45
  const typeId = comp.getTypeID();
80
46
  const parentIds = entities.map(e => e.id);
81
47
 
82
- const rows = await db`
83
- SELECT c.entity_id, (c.data->>'value') AS related_id
84
- FROM components c
85
- WHERE c.entity_id IN ${sql(parentIds)}
86
- AND c.type_id = ${typeId}
87
- AND c.deleted_at IS NULL
88
- `;
48
+ // Check cache first
49
+ const cacheKey = `${typeId}:${fieldName}`;
50
+ let cachedResults = this.cache.get(cacheKey);
51
+ if (!cachedResults) {
52
+ cachedResults = new Map();
53
+ this.cache.set(cacheKey, cachedResults);
54
+ }
55
+
56
+ const now = Date.now();
57
+
58
+ // Get uncached or expired parent IDs
59
+ const uncachedParentIds = parentIds.filter(id => {
60
+ const entry = cachedResults.get(id);
61
+ if (!entry) return true;
62
+ if (now > entry.expiresAt) {
63
+ cachedResults.delete(id);
64
+ return true;
65
+ }
66
+ return false;
67
+ });
68
+
69
+ if (uncachedParentIds.length > 0) {
70
+ // Batch the parent IDs to avoid huge IN clauses
71
+ const batches = this.chunkArray(uncachedParentIds, batchSize);
72
+
73
+ for (const batch of batches) {
74
+ const rows = await db`
75
+ SELECT c.entity_id, (c.data->>${sql(fieldName)}) AS related_id
76
+ FROM components c
77
+ WHERE c.entity_id IN ${sql(batch)}
78
+ AND c.type_id = ${typeId}
79
+ AND c.deleted_at IS NULL
80
+ AND c.data->>${sql(fieldName)} IS NOT NULL
81
+ `;
89
82
 
90
- const uniqueIds = [...new Set(rows.map((r: any) => r.related_id).filter(Boolean))] as string[];
91
- if (uniqueIds.length === 0) return new Map();
83
+ // Group by parent entity for caching
84
+ const parentGroups = new Map<string, string[]>();
85
+ for (const row of rows) {
86
+ const parentId = row.entity_id;
87
+ const relatedId = row.related_id;
88
+ if (!parentGroups.has(parentId)) {
89
+ parentGroups.set(parentId, []);
90
+ }
91
+ parentGroups.get(parentId)!.push(relatedId);
92
+ }
93
+
94
+ const expiresAt = Date.now() + cacheTTL;
95
+
96
+ // Cache the related IDs for each parent
97
+ for (const [parentId, relatedIds] of parentGroups) {
98
+ cachedResults.set(parentId, { ids: relatedIds, expiresAt });
99
+ }
100
+
101
+ // Cache empty arrays for parents with no relations
102
+ for (const parentId of batch) {
103
+ if (!parentGroups.has(parentId)) {
104
+ cachedResults.set(parentId, { ids: [], expiresAt });
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ // Collect all unique related IDs from cache
111
+ const allRelatedIds = new Set<string>();
112
+ for (const parentId of parentIds) {
113
+ const entry = cachedResults.get(parentId);
114
+ const relatedIds = entry?.ids || [];
115
+ relatedIds.forEach((id: string) => allRelatedIds.add(id));
116
+ }
117
+
118
+ if (allRelatedIds.size === 0) return new Map();
119
+
120
+ // Load related entities with concurrency control
121
+ const relatedIdArray = Array.from(allRelatedIds);
122
+ const entityBatches = this.chunkArray(relatedIdArray, batchSize);
123
+
124
+ const relatedEntities: Entity[] = [];
125
+ const semaphore = new Semaphore(maxConcurrency);
126
+
127
+ await Promise.all(
128
+ entityBatches.map(async (batch) => {
129
+ await semaphore.acquire();
130
+ try {
131
+ const entities = await loader(batch);
132
+ relatedEntities.push(...entities);
133
+ } finally {
134
+ semaphore.release();
135
+ }
136
+ })
137
+ );
92
138
 
93
- const relatedEntities = await loader(uniqueIds);
94
139
  const map = new Map<string, Entity>();
95
140
  for (const related of relatedEntities) {
96
141
  map.set(related.id, related);
97
142
  }
98
143
  return map;
99
144
  }
145
+
146
+ /**
147
+ * Clear the cache (useful for testing or memory management)
148
+ */
149
+ static clearCache(): void {
150
+ this.cache.clear();
151
+ }
152
+
153
+ /**
154
+ * Get cache statistics including expired entry count
155
+ */
156
+ static getCacheStats(): { size: number; entries: number; expired: number } {
157
+ let totalEntries = 0;
158
+ let expiredEntries = 0;
159
+ const now = Date.now();
160
+ for (const [, parentMap] of this.cache) {
161
+ for (const [, entry] of parentMap) {
162
+ totalEntries++;
163
+ if (now > entry.expiresAt) {
164
+ expiredEntries++;
165
+ }
166
+ }
167
+ }
168
+ return { size: this.cache.size, entries: totalEntries, expired: expiredEntries };
169
+ }
170
+
171
+ private static chunkArray<T>(array: T[], size: number): T[][] {
172
+ const chunks: T[][] = [];
173
+ for (let i = 0; i < array.length; i += size) {
174
+ chunks.push(array.slice(i, i + size));
175
+ }
176
+ return chunks;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Simple semaphore for concurrency control
182
+ */
183
+ class Semaphore {
184
+ private permits: number;
185
+ private waitQueue: (() => void)[] = [];
186
+
187
+ constructor(permits: number) {
188
+ this.permits = permits;
189
+ }
190
+
191
+ async acquire(): Promise<void> {
192
+ if (this.permits > 0) {
193
+ this.permits--;
194
+ return;
195
+ }
196
+
197
+ return new Promise((resolve) => {
198
+ this.waitQueue.push(resolve);
199
+ });
200
+ }
201
+
202
+ release(): void {
203
+ this.permits++;
204
+ if (this.waitQueue.length > 0) {
205
+ const resolve = this.waitQueue.shift()!;
206
+ this.permits--;
207
+ resolve();
208
+ }
209
+ }
100
210
  }
package/core/Config.ts ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Configuration module for Bunsane Framework
3
+ * Handles environment variables and application settings
4
+ */
5
+
6
+ export interface BunsaneConfig {
7
+ // Query optimization settings
8
+ useLateralJoins: boolean;
9
+ partitionStrategy: 'list' | 'hash';
10
+ useDirectPartition: boolean;
11
+
12
+ // Database settings
13
+ databaseUrl?: string;
14
+ databasePoolSize?: number;
15
+
16
+ // Application settings
17
+ appPort: number;
18
+ nodeEnv: string;
19
+
20
+ // Debug settings
21
+ debugMode: boolean;
22
+ logLevel: string;
23
+ }
24
+
25
+ /**
26
+ * Default configuration values
27
+ */
28
+ const DEFAULT_CONFIG: BunsaneConfig = {
29
+ useLateralJoins: true, // Default to true for PG12+
30
+ partitionStrategy: 'list', // LIST partitioning - one partition per component type
31
+ useDirectPartition: true, // Direct partition access - queries go directly to partition tables
32
+ appPort: 3000,
33
+ nodeEnv: process.env.NODE_ENV || 'development',
34
+ debugMode: false,
35
+ logLevel: 'info'
36
+ };
37
+
38
+ /**
39
+ * Configuration singleton class
40
+ */
41
+ class ConfigManager {
42
+ private static instance: ConfigManager;
43
+ private config: BunsaneConfig;
44
+
45
+ private constructor() {
46
+ this.config = this.loadConfig();
47
+ }
48
+
49
+ public static getInstance(): ConfigManager {
50
+ if (!ConfigManager.instance) {
51
+ ConfigManager.instance = new ConfigManager();
52
+ }
53
+ return ConfigManager.instance;
54
+ }
55
+
56
+ /**
57
+ * Load configuration from environment variables
58
+ */
59
+ private loadConfig(): BunsaneConfig {
60
+ return {
61
+ useLateralJoins: this.parseBoolean(process.env.BUNSANE_USE_LATERAL_JOINS, DEFAULT_CONFIG.useLateralJoins),
62
+ partitionStrategy: this.parsePartitionStrategy(process.env.BUNSANE_PARTITION_STRATEGY, DEFAULT_CONFIG.partitionStrategy),
63
+ useDirectPartition: this.parseBoolean(process.env.BUNSANE_USE_DIRECT_PARTITION, DEFAULT_CONFIG.useDirectPartition),
64
+ databaseUrl: process.env.DATABASE_URL,
65
+ databasePoolSize: this.parseNumber(process.env.DATABASE_POOL_SIZE, 10),
66
+ appPort: this.parseNumber(process.env.APP_PORT, DEFAULT_CONFIG.appPort),
67
+ nodeEnv: process.env.NODE_ENV || DEFAULT_CONFIG.nodeEnv,
68
+ debugMode: this.parseBoolean(process.env.DEBUG, DEFAULT_CONFIG.debugMode),
69
+ logLevel: process.env.LOG_LEVEL || DEFAULT_CONFIG.logLevel
70
+ };
71
+ }
72
+
73
+ private parseBoolean(value: string | undefined, defaultValue: boolean): boolean {
74
+ if (!value) return defaultValue;
75
+ return value.toLowerCase() === 'true' || value === '1';
76
+ }
77
+
78
+ private parseNumber(value: string | undefined, defaultValue: number): number {
79
+ if (!value) return defaultValue;
80
+ const parsed = parseInt(value, 10);
81
+ return isNaN(parsed) ? defaultValue : parsed;
82
+ }
83
+
84
+ private parsePartitionStrategy(value: string | undefined, defaultValue: 'list' | 'hash'): 'list' | 'hash' {
85
+ if (!value) return defaultValue;
86
+ const lowerValue = value.toLowerCase();
87
+ return lowerValue === 'hash' ? 'hash' : 'list';
88
+ }
89
+
90
+ /**
91
+ * Get the current configuration
92
+ */
93
+ public getConfig(): BunsaneConfig {
94
+ return { ...this.config };
95
+ }
96
+
97
+ /**
98
+ * Get a specific configuration value
99
+ */
100
+ public get<K extends keyof BunsaneConfig>(key: K): BunsaneConfig[K] {
101
+ return this.config[key];
102
+ }
103
+
104
+ /**
105
+ * Check if LATERAL joins should be used
106
+ */
107
+ public shouldUseLateralJoins(): boolean {
108
+ return this.config.useLateralJoins;
109
+ }
110
+
111
+ /**
112
+ * Check if debug mode is enabled
113
+ */
114
+ public isDebugMode(): boolean {
115
+ return this.config.debugMode;
116
+ }
117
+
118
+ /**
119
+ * Get partition strategy
120
+ */
121
+ public getPartitionStrategy(): 'list' | 'hash' {
122
+ return this.config.partitionStrategy;
123
+ }
124
+
125
+ /**
126
+ * Check if direct partition table access should be used
127
+ */
128
+ public shouldUseDirectPartition(): boolean {
129
+ return this.config.useDirectPartition;
130
+ }
131
+
132
+ /**
133
+ * Reload configuration from environment variables
134
+ */
135
+ public reloadConfig(): void {
136
+ this.config = this.loadConfig();
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Global configuration instance
142
+ */
143
+ export const config = ConfigManager.getInstance();
144
+
145
+ /**
146
+ * Convenience functions for common config checks
147
+ */
148
+ export const shouldUseLateralJoins = () => config.shouldUseLateralJoins();
149
+ export const isDebugMode = () => config.isDebugMode();
150
+ export const getPartitionStrategy = () => config.getPartitionStrategy();
151
+ export const shouldUseDirectPartition = () => config.shouldUseDirectPartition();
152
+
153
+ export default config;
@@ -16,7 +16,10 @@ export function timed(hint?: string) {
16
16
  const start = performance.now();
17
17
  const result = await originalMethod.apply(this, args);
18
18
  const end = performance.now();
19
- logger.trace(`Execution time for ${propertyKey}${hint ? ` (${hint})` : ''}: ${end - start} ms`);
19
+ if(end - start > 100) {
20
+ logger.warn(`Execution time for ${propertyKey}${hint ? ` (${hint})` : ''}: ${end - start} ms`);
21
+ }
22
+ // logger.trace(`Execution time for ${propertyKey}${hint ? ` (${hint})` : ''}: ${end - start} ms`);
20
23
  return result;
21
24
  } else {
22
25
  return await originalMethod.apply(this, args);