bunsane 0.3.1 → 0.4.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 (224) hide show
  1. package/CHANGELOG.md +445 -318
  2. package/config/cache.config.ts +35 -1
  3. package/core/App.ts +24 -1064
  4. package/core/ArcheType.ts +78 -2110
  5. package/core/BatchLoader.ts +56 -32
  6. package/core/Entity.ts +85 -1043
  7. package/core/EntityHookManager.ts +52 -754
  8. package/core/Logger.ts +10 -0
  9. package/core/RequestContext.ts +64 -6
  10. package/core/RequestLoaders.ts +187 -36
  11. package/core/SchedulerManager.ts +28 -600
  12. package/core/app/bootstrap.ts +133 -0
  13. package/core/app/cors.ts +85 -0
  14. package/core/app/graphqlSetup.ts +56 -0
  15. package/core/app/healthEndpoints.ts +31 -0
  16. package/core/app/metricsCollector.ts +27 -0
  17. package/core/app/preparedStatementWarmup.ts +15 -0
  18. package/core/app/processHandlers.ts +43 -0
  19. package/core/app/requestRouter.ts +310 -0
  20. package/core/app/restRegistry.ts +80 -0
  21. package/core/app/shutdown.ts +97 -0
  22. package/core/app/studioRouter.ts +83 -0
  23. package/core/archetype/customTypes.ts +100 -0
  24. package/core/archetype/decorators.ts +171 -0
  25. package/core/archetype/fieldResolvers.ts +666 -0
  26. package/core/archetype/helpers.ts +29 -0
  27. package/core/archetype/relationLoader.ts +161 -0
  28. package/core/archetype/schemaBuilder.ts +141 -0
  29. package/core/archetype/weaver.ts +218 -0
  30. package/core/archetype/zodSchemaBuilder.ts +527 -0
  31. package/core/cache/CacheManager.ts +173 -267
  32. package/core/cache/CompressionUtils.ts +34 -3
  33. package/core/cache/MemoryCache.ts +40 -37
  34. package/core/cache/RedisCache.ts +4 -4
  35. package/core/cache/health.ts +30 -0
  36. package/core/cache/invalidation.ts +96 -0
  37. package/core/cache/strategies/writeInvalidate.ts +111 -0
  38. package/core/cache/strategies/writeThrough.ts +233 -0
  39. package/core/components/BaseComponent.ts +16 -8
  40. package/core/components/ComponentRegistry.ts +28 -0
  41. package/core/decorators/IndexedField.ts +1 -1
  42. package/core/entity/cacheStrategies.ts +97 -0
  43. package/core/entity/componentAccess.ts +364 -0
  44. package/core/entity/finders.ts +202 -0
  45. package/core/entity/pendingOps.ts +72 -0
  46. package/core/entity/saveEntity.ts +377 -0
  47. package/core/hooks/dispatcher.ts +439 -0
  48. package/core/hooks/guards.ts +155 -0
  49. package/core/hooks/registry.ts +247 -0
  50. package/core/metadata/definitions/Component.ts +1 -1
  51. package/core/metadata/index.ts +15 -4
  52. package/core/middleware/AccessLog.ts +8 -1
  53. package/core/middleware/RateLimit.ts +102 -105
  54. package/core/middleware/RequestId.ts +2 -9
  55. package/core/middleware/SecurityHeaders.ts +2 -11
  56. package/core/middleware/headers.ts +28 -0
  57. package/core/remote/OutboxWorker.ts +213 -183
  58. package/core/remote/RemoteManager.ts +401 -400
  59. package/core/remote/types.ts +153 -151
  60. package/core/requestScope.ts +34 -0
  61. package/core/scheduler/cronEvaluator.ts +174 -0
  62. package/core/scheduler/lifecycleHooks.ts +21 -0
  63. package/core/scheduler/lockCoordinator.ts +27 -0
  64. package/core/scheduler/metrics.ts +14 -0
  65. package/core/scheduler/taskRunner.ts +420 -0
  66. package/database/DatabaseHelper.ts +128 -101
  67. package/database/IndexingStrategy.ts +72 -2
  68. package/database/PreparedStatementCache.ts +20 -5
  69. package/database/cancellable.ts +35 -0
  70. package/database/index.ts +15 -3
  71. package/database/instrumentedDb.ts +141 -0
  72. package/endpoints/archetypes.ts +2 -8
  73. package/endpoints/tables.ts +6 -1
  74. package/gql/index.ts +1 -1
  75. package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
  76. package/package.json +22 -1
  77. package/query/CTENode.ts +5 -3
  78. package/query/ComponentInclusionNode.ts +240 -13
  79. package/query/OrNode.ts +6 -5
  80. package/query/Query.ts +203 -59
  81. package/query/QueryContext.ts +6 -0
  82. package/query/QueryDAG.ts +7 -2
  83. package/query/membershipSource.ts +66 -0
  84. package/storage/LocalStorageProvider.ts +8 -3
  85. package/studio/dist/assets/index-BMZ67Npg.js +254 -0
  86. package/studio/dist/assets/index-BpbuYz9g.css +1 -0
  87. package/studio/{index.html → dist/index.html} +3 -2
  88. package/swagger/generator.ts +11 -1
  89. package/upload/UploadManager.ts +8 -6
  90. package/utils/uuid.ts +40 -10
  91. package/.claude/settings.local.json +0 -47
  92. package/.prettierrc +0 -4
  93. package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
  94. package/.serena/memories/architecture.md +0 -154
  95. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
  96. package/.serena/memories/code_style_and_conventions.md +0 -76
  97. package/.serena/memories/project_overview.md +0 -43
  98. package/.serena/memories/schema-dsl-plan.md +0 -107
  99. package/.serena/memories/suggested_commands.md +0 -80
  100. package/.serena/memories/typescript-compilation-status.md +0 -54
  101. package/.serena/project.yml +0 -114
  102. package/BunSane.jpg +0 -0
  103. package/CLAUDE.md +0 -198
  104. package/TODO.md +0 -2
  105. package/bun.lock +0 -302
  106. package/bunfig.toml +0 -10
  107. package/docs/SCALABILITY_PLAN.md +0 -175
  108. package/studio/bun.lock +0 -482
  109. package/studio/package.json +0 -39
  110. package/studio/postcss.config.js +0 -6
  111. package/studio/src/components/DataTable.tsx +0 -211
  112. package/studio/src/components/Layout.tsx +0 -13
  113. package/studio/src/components/PageContainer.tsx +0 -9
  114. package/studio/src/components/PageHeader.tsx +0 -13
  115. package/studio/src/components/SearchBar.tsx +0 -57
  116. package/studio/src/components/Sidebar.tsx +0 -294
  117. package/studio/src/components/ui/button.tsx +0 -56
  118. package/studio/src/components/ui/checkbox.tsx +0 -26
  119. package/studio/src/components/ui/input.tsx +0 -25
  120. package/studio/src/hooks/useDataTable.ts +0 -131
  121. package/studio/src/index.css +0 -36
  122. package/studio/src/lib/api.ts +0 -186
  123. package/studio/src/lib/utils.ts +0 -13
  124. package/studio/src/main.tsx +0 -17
  125. package/studio/src/pages/ArcheType.tsx +0 -239
  126. package/studio/src/pages/Components.tsx +0 -124
  127. package/studio/src/pages/EntityInspector.tsx +0 -302
  128. package/studio/src/pages/QueryRunner.tsx +0 -246
  129. package/studio/src/pages/Table.tsx +0 -94
  130. package/studio/src/pages/Welcome.tsx +0 -241
  131. package/studio/src/routes.tsx +0 -45
  132. package/studio/src/store/archeTypeSettings.ts +0 -30
  133. package/studio/src/store/studio.ts +0 -65
  134. package/studio/src/utils/columnHelpers.tsx +0 -114
  135. package/studio/studio-instructions.md +0 -81
  136. package/studio/tailwind.config.js +0 -77
  137. package/studio/utils.ts +0 -54
  138. package/studio/vite.config.js +0 -19
  139. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
  140. package/tests/benchmark/bunfig.toml +0 -9
  141. package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
  142. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
  143. package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
  144. package/tests/benchmark/fixtures/index.ts +0 -6
  145. package/tests/benchmark/index.ts +0 -22
  146. package/tests/benchmark/noop-preload.ts +0 -3
  147. package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
  148. package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
  149. package/tests/benchmark/runners/index.ts +0 -4
  150. package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
  151. package/tests/benchmark/scripts/generate-db.ts +0 -344
  152. package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
  153. package/tests/e2e/http.test.ts +0 -130
  154. package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
  155. package/tests/fixtures/components/TestOrder.ts +0 -23
  156. package/tests/fixtures/components/TestProduct.ts +0 -23
  157. package/tests/fixtures/components/TestUser.ts +0 -20
  158. package/tests/fixtures/components/index.ts +0 -6
  159. package/tests/graphql/SchemaGeneration.test.ts +0 -90
  160. package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
  161. package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
  162. package/tests/helpers/MockRedisClient.ts +0 -113
  163. package/tests/helpers/MockRedisStreamServer.ts +0 -448
  164. package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
  165. package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
  166. package/tests/integration/entity/Entity.persistence.test.ts +0 -333
  167. package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
  168. package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
  169. package/tests/integration/query/Query.edgeCases.test.ts +0 -595
  170. package/tests/integration/query/Query.exec.test.ts +0 -576
  171. package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
  172. package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
  173. package/tests/integration/remote/dlq.test.ts +0 -175
  174. package/tests/integration/remote/event-dispatch.test.ts +0 -114
  175. package/tests/integration/remote/outbox.test.ts +0 -130
  176. package/tests/integration/remote/rpc.test.ts +0 -177
  177. package/tests/pglite-setup.ts +0 -62
  178. package/tests/setup.ts +0 -164
  179. package/tests/stress/BenchmarkRunner.ts +0 -203
  180. package/tests/stress/DataSeeder.ts +0 -190
  181. package/tests/stress/StressTestReporter.ts +0 -229
  182. package/tests/stress/cursor-perf-test.ts +0 -171
  183. package/tests/stress/fixtures/RealisticComponents.ts +0 -235
  184. package/tests/stress/fixtures/StressTestComponents.ts +0 -58
  185. package/tests/stress/index.ts +0 -7
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
  187. package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
  188. package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
  189. package/tests/unit/BatchLoader.test.ts +0 -196
  190. package/tests/unit/archetype/ArcheType.test.ts +0 -107
  191. package/tests/unit/cache/CacheManager.test.ts +0 -367
  192. package/tests/unit/cache/MemoryCache.test.ts +0 -260
  193. package/tests/unit/cache/RedisCache.test.ts +0 -411
  194. package/tests/unit/entity/Entity.components.test.ts +0 -317
  195. package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
  196. package/tests/unit/entity/Entity.reload.test.ts +0 -63
  197. package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
  198. package/tests/unit/entity/Entity.test.ts +0 -345
  199. package/tests/unit/gql/depthLimit.test.ts +0 -203
  200. package/tests/unit/gql/operationMiddleware.test.ts +0 -293
  201. package/tests/unit/health/Health.test.ts +0 -129
  202. package/tests/unit/middleware/AccessLog.test.ts +0 -37
  203. package/tests/unit/middleware/Middleware.test.ts +0 -98
  204. package/tests/unit/middleware/RequestId.test.ts +0 -54
  205. package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
  206. package/tests/unit/query/FilterBuilder.test.ts +0 -111
  207. package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
  208. package/tests/unit/query/Query.emptyString.test.ts +0 -69
  209. package/tests/unit/query/Query.test.ts +0 -310
  210. package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
  211. package/tests/unit/remote/RemoteError.test.ts +0 -55
  212. package/tests/unit/remote/decorators.test.ts +0 -195
  213. package/tests/unit/remote/metrics.test.ts +0 -115
  214. package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
  215. package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
  216. package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
  217. package/tests/unit/schema/schema-integration.test.ts +0 -426
  218. package/tests/unit/schema/schema.test.ts +0 -580
  219. package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
  220. package/tests/unit/upload/RestUpload.test.ts +0 -267
  221. package/tests/unit/validateEnv.test.ts +0 -82
  222. package/tests/utils/entity-tracker.ts +0 -57
  223. package/tests/utils/index.ts +0 -13
  224. package/tests/utils/test-context.ts +0 -149
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 222.2 84% 4.9%;--card: 0 0% 100%;--card-foreground: 222.2 84% 4.9%;--popover: 0 0% 100%;--popover-foreground: 222.2 84% 4.9%;--primary: 24 95% 53%;--primary-foreground: 0 0% 98%;--secondary: 210 40% 96%;--secondary-foreground: 222.2 84% 4.9%;--muted: 210 40% 96%;--muted-foreground: 215.4 16.3% 46.9%;--accent: 210 40% 96%;--accent-foreground: 222.2 84% 4.9%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 210 40% 98%;--border: 214.3 31.8% 91.4%;--input: 214.3 31.8% 91.4%;--ring: 24 95% 53%;--radius: .5rem}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.left-3{left:.75rem}.top-1\/2{top:50%}.top-full{top:100%}.z-10{z-index:10}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-40{height:10rem}.h-5{height:1.25rem}.h-9{height:2.25rem}.h-screen{height:100vh}.max-h-96{max-height:24rem}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-80{width:20rem}.w-full{width:100%}.min-w-\[220px\]{min-width:220px}.max-w-2xl{max-width:42rem}.max-w-5xl{max-width:64rem}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-border>:not([hidden])~:not([hidden]){border-color:hsl(var(--border))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-border{border-color:hsl(var(--border))}.border-destructive\/20{border-color:hsl(var(--destructive) / .2)}.border-destructive\/30{border-color:hsl(var(--destructive) / .3)}.border-input{border-color:hsl(var(--input))}.border-primary\/30{border-color:hsl(var(--primary) / .3)}.bg-background{background-color:hsl(var(--background))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-destructive\/10{background-color:hsl(var(--destructive) / .1)}.bg-destructive\/5{background-color:hsl(var(--destructive) / .05)}.bg-muted{background-color:hsl(var(--muted))}.bg-muted\/50{background-color:hsl(var(--muted) / .5)}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/10{background-color:hsl(var(--primary) / .1)}.bg-primary\/15{background-color:hsl(var(--primary) / .15)}.bg-secondary{background-color:hsl(var(--secondary))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-0{padding-left:0;padding-right:0}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pl-10{padding-left:2.5rem}.pl-9{padding-left:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-foreground{color:hsl(var(--foreground))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.underline-offset-4{text-underline-offset:4px}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.duration-300{animation-duration:.3s}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.first\:rounded-t-md:first-child{border-top-left-radius:calc(var(--radius) - 2px);border-top-right-radius:calc(var(--radius) - 2px)}.last\:rounded-b-md:last-child{border-bottom-right-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-accent\/50:hover{background-color:hsl(var(--accent) / .5)}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-muted\/30:hover{background-color:hsl(var(--muted) / .3)}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted) / .5)}.hover\:bg-primary\/20:hover{background-color:hsl(var(--primary) / .2)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:underline:hover{text-decoration-line:underline}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-primary:focus{--tw-ring-color: hsl(var(--primary))}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
@@ -5,9 +5,10 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/studio/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>BunSane Studio</title>
8
+ <script type="module" crossorigin src="/studio/assets/index-BMZ67Npg.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/studio/assets/index-BpbuYz9g.css">
8
10
  </head>
9
11
  <body>
10
- <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
12
+ <div id="root"></div>
12
13
  </body>
13
14
  </html>
@@ -25,6 +25,7 @@ export interface OpenAPISpec {
25
25
 
26
26
  export class OpenAPISpecGenerator {
27
27
  private spec: OpenAPISpec;
28
+ private _jsonCache: string | undefined;
28
29
 
29
30
  constructor(title: string = "API Documentation", version: string = "1.0.0") {
30
31
  this.spec = {
@@ -61,6 +62,7 @@ export class OpenAPISpecGenerator {
61
62
  }
62
63
  }
63
64
  };
65
+ this._jsonCache = undefined;
64
66
  }
65
67
 
66
68
  addServer(url: string, description?: string) {
@@ -68,6 +70,7 @@ export class OpenAPISpecGenerator {
68
70
  this.spec.servers = [];
69
71
  }
70
72
  this.spec.servers.push({ url, description });
73
+ this._jsonCache = undefined;
71
74
  }
72
75
 
73
76
  addSecurityScheme(name: string, scheme: any) {
@@ -78,6 +81,7 @@ export class OpenAPISpecGenerator {
78
81
  this.spec.components.securitySchemes = {};
79
82
  }
80
83
  this.spec.components.securitySchemes[name] = scheme;
84
+ this._jsonCache = undefined;
81
85
  }
82
86
 
83
87
  addSchema(name: string, schema: any) {
@@ -88,13 +92,19 @@ export class OpenAPISpecGenerator {
88
92
  this.spec.components.schemas = {};
89
93
  }
90
94
  this.spec.components.schemas[name] = schema;
95
+ this._jsonCache = undefined;
91
96
  }
92
97
 
93
98
  generate(): OpenAPISpec {
94
99
  return this.spec;
95
100
  }
96
101
 
102
+ // Memoized — spec is frozen after startup so re-serialization per request
103
+ // is unnecessary. Mutator methods above clear the cache on any post-boot change.
97
104
  toJSON(): string {
98
- return JSON.stringify(this.spec, null, 2);
105
+ if (!this._jsonCache) {
106
+ this._jsonCache = JSON.stringify(this.spec, null, 2);
107
+ }
108
+ return this._jsonCache;
99
109
  }
100
110
  }
@@ -21,7 +21,7 @@ export class UploadManager {
21
21
  private constructor() {
22
22
  this.fileValidator = new FileValidator();
23
23
  this.globalConfig = this.getDefaultConfiguration();
24
- this.initializeDefaultProviders();
24
+ this.registerDefaultProviders();
25
25
  }
26
26
 
27
27
  public static getInstance(): UploadManager {
@@ -185,11 +185,13 @@ export class UploadManager {
185
185
  return { ...this.globalConfig };
186
186
  }
187
187
 
188
- private async initializeDefaultProviders(): Promise<void> {
189
- // Register default local storage provider
190
- const localProvider = new LocalStorageProvider();
191
- await localProvider.initialize();
192
- this.registerStorageProvider("local", localProvider);
188
+ private registerDefaultProviders(): void {
189
+ // Synchronous registration. Must NOT be async/awaited: any await here
190
+ // would defer registration to a later microtask, after which a caller's
191
+ // post-construction `registerStorageProvider("local", custom)` would be
192
+ // silently clobbered by the default. See BUNSANE-007.
193
+ // LocalStorageProvider creates its base directory in its constructor.
194
+ this.registerStorageProvider("local", new LocalStorageProvider());
193
195
  }
194
196
 
195
197
  private getDefaultConfiguration(): UploadConfiguration {
package/utils/uuid.ts CHANGED
@@ -1,10 +1,40 @@
1
- export const uuidv7 = () => {
2
- return 'tttttttt-tttt-7xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
3
- const r = Math.trunc(Math.random() * 16);
4
- const v = c == 'x' ? r : (r & 0x3 | 0x8);
5
- return v.toString(16);
6
- }).replace(/^[t]{8}-[t]{4}/, function() {
7
- const unixtimestamp = Date.now().toString(16).padStart(12, '0');
8
- return unixtimestamp.slice(0, 8) + '-' + unixtimestamp.slice(8);
9
- });
10
- }
1
+ // RFC 9562 UUIDv7: 48-bit unix-ms timestamp | ver 7 | 12-bit monotonic
2
+ // sub-millisecond sequence (rand_a) | variant | 62 random bits (rand_b).
3
+ //
4
+ // The previous implementation used Math.random() with no sequence two ids
5
+ // generated in the same millisecond could collide and were not monotonic
6
+ // (breaks index locality and the "sortable id" assumption at high insert
7
+ // rates). This one keeps strict per-process monotonicity: same-ms calls bump
8
+ // the 12-bit sequence; on overflow we borrow the next millisecond.
9
+
10
+ let lastTs = -1;
11
+ let seq = 0;
12
+
13
+ const HEX: string[] = new Array(256);
14
+ for (let i = 0; i < 256; i++) HEX[i] = i.toString(16).padStart(2, '0');
15
+
16
+ // Reused buffer — uuidv7 is called once per entity + once per component on
17
+ // every insert; per-call allocation matters. Single-threaded JS makes the
18
+ // shared buffer safe.
19
+ const rnd = new Uint8Array(8);
20
+
21
+ export const uuidv7 = (): string => {
22
+ let ts = Date.now();
23
+ if (ts <= lastTs) {
24
+ seq = (seq + 1) & 0xfff;
25
+ if (seq === 0) lastTs++;
26
+ ts = lastTs;
27
+ } else {
28
+ lastTs = ts;
29
+ seq = 0;
30
+ }
31
+
32
+ const tsHex = ts.toString(16).padStart(12, '0');
33
+ crypto.getRandomValues(rnd);
34
+ rnd[0] = (rnd[0]! & 0x3f) | 0x80; // variant bits 10xx
35
+
36
+ let randHex = '';
37
+ for (let i = 0; i < 8; i++) randHex += HEX[rnd[i]!];
38
+
39
+ return `${tsHex.slice(0, 8)}-${tsHex.slice(8)}-7${seq.toString(16).padStart(3, '0')}-${randHex.slice(0, 4)}-${randHex.slice(4)}`;
40
+ };
@@ -1,47 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(bun test:*)",
5
- "mcp__serena__check_onboarding_performed",
6
- "mcp__serena__activate_project",
7
- "mcp__serena__onboarding",
8
- "mcp__serena__list_dir",
9
- "mcp__serena__find_file",
10
- "mcp__serena__get_symbols_overview",
11
- "mcp__serena__find_symbol",
12
- "mcp__serena__think_about_collected_information",
13
- "mcp__serena__write_memory",
14
- "mcp__serena__list_memories",
15
- "Bash(grep:*)",
16
- "mcp__serena__initial_instructions",
17
- "mcp__serena__read_memory",
18
- "mcp__serena__search_for_pattern",
19
- "mcp__serena__think_about_whether_you_are_done",
20
- "mcp__serena__replace_symbol_body",
21
- "Bash(find:*)",
22
- "mcp__serena__get_current_config",
23
- "mcp__serena__think_about_task_adherence",
24
- "Bash(bun run tsc:*)",
25
- "mcp__serena__insert_after_symbol",
26
- "Bash(npx tsc:*)",
27
- "Bash(STRESS_RECORD_COUNT=10000 bun test:*)",
28
- "Bash(STRESS_RECORD_COUNT=1000 bun test:*)",
29
- "mcp__serena__find_referencing_symbols",
30
- "Bash(bun tsc:*)",
31
- "mcp__serena__edit_memory",
32
- "Bash(bunx tsc:*)",
33
- "Bash(wc:*)",
34
- "Bash(bun run:*)",
35
- "Bash(BUN_CONFIG_FILE=/dev/null bun test:*)",
36
- "Bash(echo:*)",
37
- "WebSearch",
38
- "Bash(bun:*)",
39
- "Bash(tsc:*)",
40
- "Bash(SKIP_TEST_DB_SETUP=true bun test:*)"
41
- ]
42
- },
43
- "statusLine": {
44
- "type": "command",
45
- "command": "input=$(cat); model=$(echo \"$input\" | jq -r '.model.display_name'); used=$(echo \"$input\" | jq -r '.context_window.used_percentage // empty'); remaining=$(echo \"$input\" | jq -r '.context_window.remaining_percentage // empty'); if [ -n \"$remaining\" ]; then printf \"%s | Context: %.1f%% used, %.1f%% remaining\" \"$model\" \"$used\" \"$remaining\"; else echo \"$model\"; fi"
46
- }
47
- }
package/.prettierrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "tabWidth": 4,
3
- "useTabs": false
4
- }
@@ -1,76 +0,0 @@
1
- # Architectural Decision: No Dependency Injection (For Now)
2
-
3
- **Date:** 2026-01-21
4
- **Status:** Decided
5
- **Context:** Framework v0.1.5 (experimental)
6
-
7
- ## Decision
8
-
9
- BunSane will NOT implement a formal Dependency Injection (DI) container at this stage.
10
-
11
- ## Current Approach
12
-
13
- The framework uses:
14
- - **Singleton patterns** for shared services (`CacheManager.getInstance()`, `EntityManager.instance`, `ComponentRegistry`)
15
- - **Global exports** for database (`import db from "database"`) and logger
16
- - **ApplicationLifecycle phases** for managing initialization order
17
- - **Metadata-driven decorators** for component/service registration
18
-
19
- ## Rationale
20
-
21
- ### Why DI Was Considered
22
- - Testing pain points: type-unsafe hacks like `(EntityManager as any).dbReady = true`
23
- - Hard to mock/swap implementations in tests
24
- - Singletons share global state between tests
25
-
26
- ### Why DI Was Rejected (For Now)
27
-
28
- 1. **Framework is experimental (v0.1.5)** - Too early for such fundamental architectural changes
29
-
30
- 2. **Added complexity outweighs benefits** - DI adds another abstraction layer to an already complex ECS + GraphQL framework
31
-
32
- 3. **Current approach works** - ApplicationLifecycle phases already manage dependency initialization order effectively
33
-
34
- 4. **Users don't need to swap implementations** - Extensions happen via Components, Services, and Plugins, none of which benefit significantly from DI
35
-
36
- 5. **Testing pain can be solved simpler** - Lightweight patterns like `reset()` methods or optional instance injection suffice
37
-
38
- 6. **Bun ecosystem norms** - DI isn't a common pattern in modern Bun/TypeScript frameworks
39
-
40
- 7. **Runtime overhead** - Relevant concern for a performance-focused framework
41
-
42
- ## Alternative: Lightweight Testability Pattern
43
-
44
- Instead of full DI, use optional instance injection for testability:
45
-
46
- ```typescript
47
- class CacheManager {
48
- private static _instance: CacheManager | null = null;
49
-
50
- static getInstance(): CacheManager {
51
- return this._instance ??= new CacheManager();
52
- }
53
-
54
- // For testing only
55
- static setInstance(instance: CacheManager | null): void {
56
- this._instance = instance;
57
- }
58
- }
59
- ```
60
-
61
- ## When to Revisit
62
-
63
- Consider DI if/when:
64
- - Framework reaches v1.0 with stable APIs
65
- - Users request ability to swap implementations
66
- - Codebase grows significantly larger (200+ files)
67
- - Multiple database/cache backends become first-class options
68
-
69
- ## Related Files
70
-
71
- - `core/cache/CacheManager.ts` - Singleton cache manager
72
- - `core/EntityManager.ts` - Singleton entity manager
73
- - `core/components/ComponentRegistry.ts` - Singleton component registry
74
- - `service/ServiceRegistry.ts` - Singleton service registry
75
- - `core/ApplicationLifecycle.ts` - Lifecycle phase management
76
- - `database/index.ts` - Global database export
@@ -1,154 +0,0 @@
1
- # Project Architecture
2
-
3
- ## Directory Structure
4
-
5
- ```
6
- bunsane/
7
- ├── core/ # Core framework
8
- │ ├── App.ts # Main application entry point
9
- │ ├── Entity.ts # Entity class (base data unit)
10
- │ ├── EntityManager.ts # Entity lifecycle management
11
- │ ├── ArcheType.ts # Entity templates with component definitions
12
- │ ├── BatchLoader.ts # DataLoader for batching DB queries
13
- │ ├── Config.ts # Configuration management
14
- │ ├── Logger.ts # Pino logging setup
15
- │ ├── ErrorHandler.ts # Error handling utilities
16
- │ ├── RequestContext.ts # Request-scoped context
17
- │ ├── ApplicationLifecycle.ts # App state management
18
- │ ├── cache/ # Caching system
19
- │ │ ├── CacheManager.ts # Main cache orchestration
20
- │ │ ├── CacheProvider.ts # Standard interface (all providers implement this)
21
- │ │ ├── MemoryCache.ts # In-memory cache provider
22
- │ │ ├── RedisCache.ts # Redis cache provider
23
- │ │ ├── MultiLevelCache.ts # L1/L2 cache strategy (wrapper)
24
- │ │ ├── CacheAnalytics.ts # Analytics wrapper provider
25
- │ │ ├── TTLStrategy.ts # Adaptive TTL wrapper provider
26
- │ │ └── ...
27
- │ ├── components/ # Component system
28
- │ │ ├── BaseComponent.ts # Base class for components
29
- │ │ ├── ComponentRegistry.ts # Component registration
30
- │ │ ├── Decorators.ts # @Component, @CompData decorators
31
- │ │ └── ...
32
- │ ├── decorators/ # Additional decorators
33
- │ ├── events/ # Event system
34
- │ └── metadata/ # Metadata utilities
35
- ├── database/ # Database layer
36
- │ ├── index.ts # PostgreSQL connection (postgres.js)
37
- │ ├── DatabaseHelper.ts # DB setup and migrations
38
- │ ├── PreparedStatementCache.ts # Query caching
39
- │ └── IndexingStrategy.ts # Index management
40
- ├── query/ # Query builder
41
- │ ├── Query.ts # Main query class
42
- │ ├── FilterBuilder.ts # Filter construction
43
- │ ├── QueryDAG.ts # Query execution graph
44
- │ └── ...
45
- ├── gql/ # GraphQL generation
46
- │ ├── Generator.ts # Schema generation
47
- │ ├── builders/ # Type/resolver builders
48
- │ ├── decorators/ # GQL decorators
49
- │ ├── strategies/ # Type generation strategies
50
- │ └── visitors/ # Schema visitors
51
- ├── service/ # Service layer
52
- │ ├── Service.ts # Base service class
53
- │ └── ServiceRegistry.ts # Service registration
54
- ├── plugins/ # Plugin system
55
- ├── scheduler/ # Task scheduling
56
- ├── storage/ # File storage
57
- ├── upload/ # File upload handling
58
- ├── rest/ # REST endpoint support
59
- ├── swagger/ # OpenAPI documentation
60
- ├── studio/ # BunSane Studio (web UI)
61
- ├── test/ # Tests
62
- │ ├── setup.ts # Test environment setup
63
- │ ├── integration/ # Integration tests
64
- │ └── gql/ # GraphQL tests
65
- ├── types/ # Type definitions
66
- └── utils/ # Utility functions
67
- ```
68
-
69
- ## Core Concepts Flow
70
-
71
- 1. **App** initializes the application, sets up GraphQL Yoga server
72
- 2. **Components** are registered via `@Component` decorator
73
- 3. **Entities** are created and manipulated, storing component data
74
- 4. **ArcheTypes** define entity templates for type-safe component access
75
- 5. **Query** builder retrieves entities with filters and component population
76
- 6. **Services** define business logic and auto-generate GraphQL resolvers
77
- 7. **CacheManager** handles caching at entity/component/query levels
78
-
79
- ## Cache System Architecture
80
-
81
- All cache providers implement the standardized `CacheProvider` interface:
82
-
83
- ```typescript
84
- interface CacheProvider {
85
- get<T>(key: string): Promise<T | null>;
86
- set<T>(key: string, value: T, ttl?: number): Promise<void>;
87
- delete(key: string | string[]): Promise<void>; // Supports single or multiple
88
- clear(): Promise<void>;
89
- getMany<T>(keys: string[]): Promise<(T | null)[]>; // Returns ordered array
90
- setMany<T>(entries: Array<{key, value, ttl?}>): Promise<void>; // Per-entry TTL
91
- deleteMany(keys: string[]): Promise<void>;
92
- invalidatePattern(pattern: string): Promise<void>;
93
- ping(): Promise<boolean>; // Health check
94
- getStats(): Promise<CacheStats>; // Metrics
95
- }
96
- ```
97
-
98
- **Cache Providers**:
99
- - **MemoryCache**: In-memory LRU cache (base implementation)
100
- - **RedisCache**: Redis-backed cache (base implementation)
101
- - **MultiLevelCacheProvider**: L1 (memory) + L2 (Redis) wrapper
102
- - **AnalyticsCacheProvider**: Metrics and latency tracking wrapper
103
- - **AdaptiveTTLProvider**: Dynamic TTL adjustment wrapper
104
-
105
- **Key Design Decisions** (as of 2026-01-24):
106
- - Batch operations use arrays instead of Maps for ordering and flexibility
107
- - `delete()` accepts both single string and string array for convenience
108
- - `ping()` replaces `healthCheck()` for conventional naming
109
- - All wrappers maintain full interface compliance
110
-
111
- ## Import Resolution (as of 2026-02-05)
112
-
113
- All internal imports use **relative paths** (`./`, `../`). Bare imports like `from "core/Logger"` that rely on `baseUrl` are NOT allowed because they break TypeScript type checking for consumers who install bunsane as a dependency (their tsconfig does not have baseUrl pointing to bunsane root).
114
-
115
- ## Data Model
116
-
117
- - **Entity**: UUID-identified record in `entities` table
118
- - **Component**: JSONB data in `components` table, linked to entity
119
- - Components are identified by `entity_id` + `component_type`
120
- - Supports indexing specific component fields for queries
121
-
122
- ## CORS System (as of 2026-02-04)
123
-
124
- The App class (`core/App.ts`) provides comprehensive CORS support with proper spec compliance.
125
-
126
- **CorsConfig Type**:
127
- ```typescript
128
- type CorsConfig = {
129
- origin?: string | string[] | ((origin: string) => boolean);
130
- credentials?: boolean;
131
- allowedHeaders?: string[];
132
- exposedHeaders?: string[]; // For Access-Control-Expose-Headers
133
- methods?: string[];
134
- maxAge?: number; // Preflight cache duration in seconds
135
- };
136
- ```
137
-
138
- **Key Features**:
139
- - **Origin Validation**: Validates request Origin header against configured origins
140
- - **Array Origins**: Returns the matching origin (not comma-joined) per spec
141
- - **Function Origins**: Supports `(origin: string) => boolean` for dynamic validation
142
- - **Credentials + Wildcard**: When credentials=true with origin="*", reflects actual origin instead of wildcard
143
- - **Vary Header**: Always includes `Vary: Origin` for proper caching
144
- - **All Endpoints**: CORS headers applied to all response paths (health, docs, openapi, studio, errors)
145
-
146
- **Usage**:
147
- ```typescript
148
- app.setCors({
149
- origin: ["http://localhost:3000", "https://myapp.com"],
150
- credentials: true,
151
- maxAge: 86400,
152
- exposedHeaders: ["X-Custom-Header"]
153
- });
154
- ```
@@ -1,165 +0,0 @@
1
- # Cache Interface Refactoring - January 24, 2026
2
-
3
- ## Status: COMPLETED
4
- **Completion Date**: 2026-01-24
5
- **Outcome**: Successfully standardized CacheProvider interface implementation across all cache-related files
6
-
7
- ## Summary
8
- Fixed TypeScript compilation errors caused by mismatches between the `CacheProvider` interface and its implementations. The interface was correct, but several wrapper classes and test files had outdated method signatures.
9
-
10
- ## Impact
11
- - **Errors Reduced**: From 100+ TypeScript errors to 69
12
- - **Cache Errors**: Fully resolved (0 remaining)
13
- - **Remaining Errors**: Unrelated to cache (SchedulerManager, GraphQL builders, test access to private members)
14
-
15
- ## Files Modified
16
-
17
- ### Core Cache Files (3 files)
18
-
19
- #### 1. `core/cache/CacheAnalytics.ts`
20
- **Class**: `AnalyticsCacheProvider`
21
-
22
- Changes:
23
- - Changed import to type-only import: `import type { CacheProvider } from './CacheProvider'`
24
- - Made `recordLatency` method public
25
- - Fixed method signatures:
26
- - `delete(key: string | string[]): Promise<void>` (was single key only)
27
- - `getMany<T>(keys: string[]): Promise<(T | null)[]>` (was returning Map)
28
- - `setMany<T>(entries: Array<{key, value, ttl?}>): Promise<void>` (was using Map)
29
- - `deleteMany(keys: string[]): Promise<void>` (added proper typing)
30
- - `ping(): Promise<boolean>` (replaced `healthCheck()`)
31
- - `getStats(): Promise<CacheStats>` (was async but not typed correctly)
32
- - Removed `has()` method (not in interface)
33
-
34
- #### 2. `core/cache/MultiLevelCache.ts`
35
- **Class**: `MultiLevelCacheProvider`
36
-
37
- Changes:
38
- - Fixed import path and made type-only
39
- - Applied same method signature fixes as CacheAnalytics
40
- - Added proper type guards for array access in `delete()` method
41
- - Ensured L1/L2 cache coordination respects new interface
42
-
43
- #### 3. `core/cache/TTLStrategy.ts`
44
- **Class**: `AdaptiveTTLProvider`
45
-
46
- Changes:
47
- - Applied same method signature fixes as above
48
- - Ensured TTL adaptation logic works with updated interface
49
-
50
- ### Config Files (1 file)
51
-
52
- #### 4. `config/cache.config.ts`
53
-
54
- Changes:
55
- - Added `'multilevel'` to provider type union
56
- - Added `query` property with structure:
57
- ```typescript
58
- query: {
59
- enabled: boolean;
60
- ttl: number;
61
- maxSize: number;
62
- }
63
- ```
64
-
65
- ### Test Files (7 files)
66
-
67
- #### 5. `tests/unit/cache/MemoryCache.test.ts`
68
- - Added explicit type parameters to `cache.get<T>()` calls
69
- - Ensures type safety in test assertions
70
-
71
- #### 6. `tests/unit/cache/RedisCache.test.ts`
72
- - Added explicit type parameters to `cache.get<T>()` calls
73
-
74
- #### 7-11. Test Configuration Files
75
- Added `maxSize` to query config objects in:
76
- - `tests/integration/cache/CacheInvalidation.test.ts`
77
- - `tests/setup.ts`
78
- - `tests/stress/cursor-perf-test.ts`
79
- - `tests/unit/cache/CacheManager.test.ts`
80
- - `tests/utils/test-context.ts`
81
-
82
- ## Key Interface Contract
83
-
84
- ```typescript
85
- interface CacheProvider {
86
- // Single operations
87
- get<T>(key: string): Promise<T | null>;
88
- set<T>(key: string, value: T, ttl?: number): Promise<void>;
89
- delete(key: string | string[]): Promise<void>;
90
- clear(): Promise<void>;
91
-
92
- // Batch operations
93
- getMany<T>(keys: string[]): Promise<(T | null)[]>;
94
- setMany<T>(entries: Array<{key: string, value: T, ttl?: number}>): Promise<void>;
95
- deleteMany(keys: string[]): Promise<void>;
96
-
97
- // Pattern operations
98
- invalidatePattern(pattern: string): Promise<void>;
99
-
100
- // Health and metrics
101
- ping(): Promise<boolean>;
102
- getStats(): Promise<CacheStats>;
103
- }
104
- ```
105
-
106
- ## Critical Design Decisions
107
-
108
- ### 1. Array Return for getMany
109
- **Decision**: `getMany` returns `Promise<(T | null)[]>` instead of `Map<string, T | null>`
110
- **Rationale**:
111
- - Maintains order of requested keys
112
- - Simpler type handling
113
- - Better performance for indexed access
114
-
115
- ### 2. Entry Array for setMany
116
- **Decision**: `setMany` takes `Array<{key, value, ttl?}>` instead of `Map`
117
- **Rationale**:
118
- - Supports per-entry TTL configuration
119
- - More flexible than Map-based approach
120
- - Aligns with common cache API patterns
121
-
122
- ### 3. Flexible Delete
123
- **Decision**: `delete(key: string | string[])` accepts both single and multiple keys
124
- **Rationale**:
125
- - Reduces API surface area (no need for separate deleteOne/deleteMany)
126
- - Convenience for common use cases
127
- - Backward compatible with single-key usage
128
-
129
- ### 4. Ping Instead of HealthCheck
130
- **Decision**: Renamed `healthCheck()` to `ping()`
131
- **Rationale**:
132
- - Shorter, more conventional name
133
- - Matches Redis and memcached naming
134
- - Clearer intent (simple connectivity check)
135
-
136
- ## Verification
137
-
138
- ### TypeScript Compilation
139
- - All cache-related files now compile without errors
140
- - Type safety enforced across all implementations
141
- - No type assertions or `any` types introduced
142
-
143
- ### Interface Compliance
144
- All three wrapper implementations now fully comply:
145
- - AnalyticsCacheProvider (wraps any provider with metrics)
146
- - MultiLevelCacheProvider (L1/L2 cache coordination)
147
- - AdaptiveTTLProvider (dynamic TTL adjustment)
148
-
149
- ## Follow-Up Items
150
-
151
- None for cache. Remaining TypeScript errors are in:
152
- 1. **SchedulerManager**: Unrelated to cache
153
- 2. **GraphQL builders**: Schema generation issues
154
- 3. **Test files**: Access to private members (test-specific issues)
155
-
156
- ## Lessons Learned
157
-
158
- 1. **Interface-First Design**: Having a well-defined interface is only half the battle; keeping implementations in sync is critical
159
- 2. **Type-Only Imports**: Using `import type` helps clarify when you're only referencing types vs. runtime values
160
- 3. **Batch Operations**: The switch from Map to Array for batch ops required careful consideration of ordering and flexibility
161
- 4. **Test Coverage**: Tests that use explicit type parameters catch more type mismatches earlier
162
-
163
- ## Related Memories
164
- - See `architecture` for overall cache system design
165
- - See `code_style_and_conventions` for TypeScript patterns used