bunsane 0.3.2 → 0.5.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 (220) hide show
  1. package/CHANGELOG.md +471 -370
  2. package/core/BatchLoader.ts +56 -32
  3. package/core/Entity.ts +93 -1020
  4. package/core/EntityHookManager.ts +52 -754
  5. package/core/Logger.ts +10 -0
  6. package/core/RequestContext.ts +94 -85
  7. package/core/RequestLoaders.ts +98 -5
  8. package/core/SchedulerManager.ts +28 -600
  9. package/core/app/cors.ts +2 -11
  10. package/core/app/preparedStatementWarmup.ts +9 -49
  11. package/core/app/requestRouter.ts +9 -8
  12. package/core/app/restRegistry.ts +8 -0
  13. package/core/archetype/fieldResolvers.ts +85 -40
  14. package/core/archetype/relationLoader.ts +135 -92
  15. package/core/cache/CacheManager.ts +91 -302
  16. package/core/cache/CompressionUtils.ts +34 -3
  17. package/core/cache/MemoryCache.ts +40 -37
  18. package/core/cache/RedisCache.ts +8 -7
  19. package/core/cache/health.ts +30 -0
  20. package/core/cache/invalidation.ts +96 -0
  21. package/core/cache/strategies/writeInvalidate.ts +111 -0
  22. package/core/cache/strategies/writeThrough.ts +233 -0
  23. package/core/components/BaseComponent.ts +25 -10
  24. package/core/components/ComponentRegistry.ts +28 -0
  25. package/core/decorators/IndexedField.ts +1 -1
  26. package/core/entity/cacheStrategies.ts +97 -0
  27. package/core/entity/componentAccess.ts +383 -0
  28. package/core/entity/finders.ts +202 -0
  29. package/core/entity/getCacheManager.ts +10 -0
  30. package/core/entity/pendingOps.ts +72 -0
  31. package/core/entity/saveEntity.ts +375 -0
  32. package/core/health.ts +93 -4
  33. package/core/hooks/dispatcher.ts +439 -0
  34. package/core/hooks/guards.ts +155 -0
  35. package/core/hooks/registry.ts +247 -0
  36. package/core/metadata/definitions/Component.ts +1 -1
  37. package/core/metadata/index.ts +15 -4
  38. package/core/middleware/RateLimit.ts +102 -105
  39. package/core/middleware/RequestId.ts +2 -9
  40. package/core/middleware/SecurityHeaders.ts +2 -11
  41. package/core/middleware/headers.ts +28 -0
  42. package/core/remote/OutboxWorker.ts +213 -183
  43. package/core/remote/RemoteManager.ts +401 -400
  44. package/core/remote/StreamConsumer.ts +535 -535
  45. package/core/remote/types.ts +153 -151
  46. package/core/requestScope.ts +34 -0
  47. package/core/scheduler/cronEvaluator.ts +174 -0
  48. package/core/scheduler/lifecycleHooks.ts +21 -0
  49. package/core/scheduler/lockCoordinator.ts +27 -0
  50. package/core/scheduler/metrics.ts +14 -0
  51. package/core/scheduler/taskRunner.ts +420 -0
  52. package/core/validateEnv.ts +10 -0
  53. package/database/DatabaseHelper.ts +128 -101
  54. package/database/IndexingStrategy.ts +72 -2
  55. package/database/PreparedStatementCache.ts +8 -2
  56. package/database/cancellable.ts +35 -22
  57. package/database/index.ts +29 -3
  58. package/database/instrumentedDb.ts +141 -141
  59. package/database/sqlHelpers.ts +3 -1
  60. package/endpoints/archetypes.ts +2 -8
  61. package/endpoints/tables.ts +6 -1
  62. package/gql/index.ts +1 -1
  63. package/gql/schema/index.ts +15 -4
  64. package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
  65. package/package.json +22 -1
  66. package/query/CTENode.ts +5 -3
  67. package/query/ComponentInclusionNode.ts +245 -14
  68. package/query/OrNode.ts +8 -19
  69. package/query/Query.ts +208 -79
  70. package/query/QueryContext.ts +6 -0
  71. package/query/QueryDAG.ts +7 -2
  72. package/query/membershipSource.ts +66 -0
  73. package/storage/LocalStorageProvider.ts +8 -3
  74. package/studio/dist/assets/index-BMZ67Npg.js +254 -0
  75. package/studio/dist/assets/index-BpbuYz9g.css +1 -0
  76. package/studio/{index.html → dist/index.html} +3 -2
  77. package/swagger/generator.ts +11 -1
  78. package/upload/UploadManager.ts +8 -6
  79. package/utils/uuid.ts +40 -10
  80. package/.claude/scheduled_tasks.lock +0 -1
  81. package/.claude/settings.local.json +0 -47
  82. package/.prettierrc +0 -4
  83. package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
  84. package/.serena/memories/architecture.md +0 -154
  85. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
  86. package/.serena/memories/code_style_and_conventions.md +0 -76
  87. package/.serena/memories/project_overview.md +0 -43
  88. package/.serena/memories/schema-dsl-plan.md +0 -107
  89. package/.serena/memories/suggested_commands.md +0 -80
  90. package/.serena/memories/typescript-compilation-status.md +0 -54
  91. package/.serena/project.yml +0 -114
  92. package/BunSane.jpg +0 -0
  93. package/CLAUDE.md +0 -198
  94. package/TODO.md +0 -2
  95. package/bun.lock +0 -302
  96. package/bunfig.toml +0 -10
  97. package/docs/RFC_APP_REFACTOR.md +0 -248
  98. package/docs/RFC_REFACTOR_TARGETS.md +0 -251
  99. package/docs/SCALABILITY_PLAN.md +0 -175
  100. package/studio/bun.lock +0 -482
  101. package/studio/package.json +0 -39
  102. package/studio/postcss.config.js +0 -6
  103. package/studio/src/components/DataTable.tsx +0 -211
  104. package/studio/src/components/Layout.tsx +0 -13
  105. package/studio/src/components/PageContainer.tsx +0 -9
  106. package/studio/src/components/PageHeader.tsx +0 -13
  107. package/studio/src/components/SearchBar.tsx +0 -57
  108. package/studio/src/components/Sidebar.tsx +0 -294
  109. package/studio/src/components/ui/button.tsx +0 -56
  110. package/studio/src/components/ui/checkbox.tsx +0 -26
  111. package/studio/src/components/ui/input.tsx +0 -25
  112. package/studio/src/hooks/useDataTable.ts +0 -131
  113. package/studio/src/index.css +0 -36
  114. package/studio/src/lib/api.ts +0 -186
  115. package/studio/src/lib/utils.ts +0 -13
  116. package/studio/src/main.tsx +0 -17
  117. package/studio/src/pages/ArcheType.tsx +0 -239
  118. package/studio/src/pages/Components.tsx +0 -124
  119. package/studio/src/pages/EntityInspector.tsx +0 -302
  120. package/studio/src/pages/QueryRunner.tsx +0 -246
  121. package/studio/src/pages/Table.tsx +0 -94
  122. package/studio/src/pages/Welcome.tsx +0 -241
  123. package/studio/src/routes.tsx +0 -45
  124. package/studio/src/store/archeTypeSettings.ts +0 -30
  125. package/studio/src/store/studio.ts +0 -65
  126. package/studio/src/utils/columnHelpers.tsx +0 -114
  127. package/studio/studio-instructions.md +0 -81
  128. package/studio/tailwind.config.js +0 -77
  129. package/studio/utils.ts +0 -54
  130. package/studio/vite.config.js +0 -19
  131. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
  132. package/tests/benchmark/bunfig.toml +0 -9
  133. package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
  134. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
  135. package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
  136. package/tests/benchmark/fixtures/index.ts +0 -6
  137. package/tests/benchmark/index.ts +0 -22
  138. package/tests/benchmark/noop-preload.ts +0 -3
  139. package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
  140. package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
  141. package/tests/benchmark/runners/index.ts +0 -4
  142. package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
  143. package/tests/benchmark/scripts/generate-db.ts +0 -344
  144. package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
  145. package/tests/e2e/http.test.ts +0 -130
  146. package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
  147. package/tests/fixtures/components/TestOrder.ts +0 -23
  148. package/tests/fixtures/components/TestProduct.ts +0 -23
  149. package/tests/fixtures/components/TestUser.ts +0 -20
  150. package/tests/fixtures/components/index.ts +0 -6
  151. package/tests/graphql/SchemaGeneration.test.ts +0 -90
  152. package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
  153. package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
  154. package/tests/helpers/MockRedisClient.ts +0 -113
  155. package/tests/helpers/MockRedisStreamServer.ts +0 -448
  156. package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
  157. package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
  158. package/tests/integration/entity/Entity.persistence.test.ts +0 -333
  159. package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
  160. package/tests/integration/loaders/RequestLoaders.abort.test.ts +0 -82
  161. package/tests/integration/query/Query.abort.test.ts +0 -66
  162. package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
  163. package/tests/integration/query/Query.edgeCases.test.ts +0 -595
  164. package/tests/integration/query/Query.exec.test.ts +0 -576
  165. package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
  166. package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
  167. package/tests/integration/remote/dlq.test.ts +0 -175
  168. package/tests/integration/remote/event-dispatch.test.ts +0 -114
  169. package/tests/integration/remote/outbox.test.ts +0 -130
  170. package/tests/integration/remote/rpc.test.ts +0 -177
  171. package/tests/pglite-setup.ts +0 -62
  172. package/tests/setup.ts +0 -164
  173. package/tests/stress/BenchmarkRunner.ts +0 -203
  174. package/tests/stress/DataSeeder.ts +0 -190
  175. package/tests/stress/StressTestReporter.ts +0 -229
  176. package/tests/stress/cursor-perf-test.ts +0 -171
  177. package/tests/stress/fixtures/RealisticComponents.ts +0 -235
  178. package/tests/stress/fixtures/StressTestComponents.ts +0 -58
  179. package/tests/stress/index.ts +0 -7
  180. package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
  181. package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
  182. package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
  183. package/tests/unit/BatchLoader.test.ts +0 -196
  184. package/tests/unit/archetype/ArcheType.test.ts +0 -107
  185. package/tests/unit/cache/CacheManager.test.ts +0 -498
  186. package/tests/unit/cache/MemoryCache.test.ts +0 -260
  187. package/tests/unit/cache/RedisCache.test.ts +0 -411
  188. package/tests/unit/database/cancellable.test.ts +0 -81
  189. package/tests/unit/database/instrumentedDb.test.ts +0 -160
  190. package/tests/unit/entity/Entity.components.test.ts +0 -317
  191. package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
  192. package/tests/unit/entity/Entity.reload.test.ts +0 -63
  193. package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
  194. package/tests/unit/entity/Entity.test.ts +0 -345
  195. package/tests/unit/gql/depthLimit.test.ts +0 -203
  196. package/tests/unit/gql/operationMiddleware.test.ts +0 -293
  197. package/tests/unit/health/Health.test.ts +0 -129
  198. package/tests/unit/middleware/AccessLog.test.ts +0 -37
  199. package/tests/unit/middleware/Middleware.test.ts +0 -98
  200. package/tests/unit/middleware/RequestId.test.ts +0 -54
  201. package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
  202. package/tests/unit/query/FilterBuilder.test.ts +0 -111
  203. package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
  204. package/tests/unit/query/Query.emptyString.test.ts +0 -69
  205. package/tests/unit/query/Query.test.ts +0 -310
  206. package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
  207. package/tests/unit/remote/RemoteError.test.ts +0 -55
  208. package/tests/unit/remote/decorators.test.ts +0 -195
  209. package/tests/unit/remote/metrics.test.ts +0 -115
  210. package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
  211. package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
  212. package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
  213. package/tests/unit/schema/schema-integration.test.ts +0 -426
  214. package/tests/unit/schema/schema.test.ts +0 -580
  215. package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
  216. package/tests/unit/upload/RestUpload.test.ts +0 -267
  217. package/tests/unit/validateEnv.test.ts +0 -82
  218. package/tests/utils/entity-tracker.ts +0 -57
  219. package/tests/utils/index.ts +0 -13
  220. 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 +0,0 @@
1
- {"sessionId":"302022ef-825d-48c8-8ef6-656f1cd141e0","pid":60520,"procStart":"639139204827053470","acquiredAt":1778489386099}
@@ -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