bunsane 0.3.2 → 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.
- package/CHANGELOG.md +445 -370
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +85 -1020
- package/core/EntityHookManager.ts +52 -754
- package/core/Logger.ts +10 -0
- package/core/RequestContext.ts +94 -85
- package/core/RequestLoaders.ts +98 -5
- package/core/SchedulerManager.ts +28 -600
- package/core/app/cors.ts +2 -11
- package/core/app/preparedStatementWarmup.ts +9 -49
- package/core/app/requestRouter.ts +9 -8
- package/core/app/restRegistry.ts +8 -0
- package/core/archetype/fieldResolvers.ts +85 -40
- package/core/archetype/relationLoader.ts +135 -92
- package/core/cache/CacheManager.ts +91 -302
- package/core/cache/CompressionUtils.ts +34 -3
- package/core/cache/MemoryCache.ts +40 -37
- package/core/cache/RedisCache.ts +4 -4
- package/core/cache/health.ts +30 -0
- package/core/cache/invalidation.ts +96 -0
- package/core/cache/strategies/writeInvalidate.ts +111 -0
- package/core/cache/strategies/writeThrough.ts +233 -0
- package/core/components/BaseComponent.ts +16 -8
- package/core/components/ComponentRegistry.ts +28 -0
- package/core/decorators/IndexedField.ts +1 -1
- package/core/entity/cacheStrategies.ts +97 -0
- package/core/entity/componentAccess.ts +364 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +377 -0
- package/core/hooks/dispatcher.ts +439 -0
- package/core/hooks/guards.ts +155 -0
- package/core/hooks/registry.ts +247 -0
- package/core/metadata/definitions/Component.ts +1 -1
- package/core/metadata/index.ts +15 -4
- package/core/middleware/RateLimit.ts +102 -105
- package/core/middleware/RequestId.ts +2 -9
- package/core/middleware/SecurityHeaders.ts +2 -11
- package/core/middleware/headers.ts +28 -0
- package/core/remote/OutboxWorker.ts +213 -183
- package/core/remote/RemoteManager.ts +401 -400
- package/core/remote/types.ts +153 -151
- package/core/requestScope.ts +34 -0
- package/core/scheduler/cronEvaluator.ts +174 -0
- package/core/scheduler/lifecycleHooks.ts +21 -0
- package/core/scheduler/lockCoordinator.ts +27 -0
- package/core/scheduler/metrics.ts +14 -0
- package/core/scheduler/taskRunner.ts +420 -0
- package/database/DatabaseHelper.ts +128 -101
- package/database/IndexingStrategy.ts +72 -2
- package/database/PreparedStatementCache.ts +8 -2
- package/database/cancellable.ts +35 -22
- package/database/index.ts +15 -3
- package/database/instrumentedDb.ts +141 -141
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +240 -13
- package/query/OrNode.ts +6 -5
- package/query/Query.ts +157 -46
- package/query/QueryContext.ts +6 -0
- package/query/QueryDAG.ts +7 -2
- package/query/membershipSource.ts +66 -0
- package/storage/LocalStorageProvider.ts +8 -3
- package/studio/dist/assets/index-BMZ67Npg.js +254 -0
- package/studio/dist/assets/index-BpbuYz9g.css +1 -0
- package/studio/{index.html → dist/index.html} +3 -2
- package/swagger/generator.ts +11 -1
- package/upload/UploadManager.ts +8 -6
- package/utils/uuid.ts +40 -10
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.local.json +0 -47
- package/.prettierrc +0 -4
- package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
- package/.serena/memories/architecture.md +0 -154
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
- package/.serena/memories/code_style_and_conventions.md +0 -76
- package/.serena/memories/project_overview.md +0 -43
- package/.serena/memories/schema-dsl-plan.md +0 -107
- package/.serena/memories/suggested_commands.md +0 -80
- package/.serena/memories/typescript-compilation-status.md +0 -54
- package/.serena/project.yml +0 -114
- package/BunSane.jpg +0 -0
- package/CLAUDE.md +0 -198
- package/TODO.md +0 -2
- package/bun.lock +0 -302
- package/bunfig.toml +0 -10
- package/docs/RFC_APP_REFACTOR.md +0 -248
- package/docs/RFC_REFACTOR_TARGETS.md +0 -251
- package/docs/SCALABILITY_PLAN.md +0 -175
- package/studio/bun.lock +0 -482
- package/studio/package.json +0 -39
- package/studio/postcss.config.js +0 -6
- package/studio/src/components/DataTable.tsx +0 -211
- package/studio/src/components/Layout.tsx +0 -13
- package/studio/src/components/PageContainer.tsx +0 -9
- package/studio/src/components/PageHeader.tsx +0 -13
- package/studio/src/components/SearchBar.tsx +0 -57
- package/studio/src/components/Sidebar.tsx +0 -294
- package/studio/src/components/ui/button.tsx +0 -56
- package/studio/src/components/ui/checkbox.tsx +0 -26
- package/studio/src/components/ui/input.tsx +0 -25
- package/studio/src/hooks/useDataTable.ts +0 -131
- package/studio/src/index.css +0 -36
- package/studio/src/lib/api.ts +0 -186
- package/studio/src/lib/utils.ts +0 -13
- package/studio/src/main.tsx +0 -17
- package/studio/src/pages/ArcheType.tsx +0 -239
- package/studio/src/pages/Components.tsx +0 -124
- package/studio/src/pages/EntityInspector.tsx +0 -302
- package/studio/src/pages/QueryRunner.tsx +0 -246
- package/studio/src/pages/Table.tsx +0 -94
- package/studio/src/pages/Welcome.tsx +0 -241
- package/studio/src/routes.tsx +0 -45
- package/studio/src/store/archeTypeSettings.ts +0 -30
- package/studio/src/store/studio.ts +0 -65
- package/studio/src/utils/columnHelpers.tsx +0 -114
- package/studio/studio-instructions.md +0 -81
- package/studio/tailwind.config.js +0 -77
- package/studio/utils.ts +0 -54
- package/studio/vite.config.js +0 -19
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
- package/tests/benchmark/bunfig.toml +0 -9
- package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
- package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
- package/tests/benchmark/fixtures/index.ts +0 -6
- package/tests/benchmark/index.ts +0 -22
- package/tests/benchmark/noop-preload.ts +0 -3
- package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
- package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
- package/tests/benchmark/runners/index.ts +0 -4
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
- package/tests/benchmark/scripts/generate-db.ts +0 -344
- package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
- package/tests/e2e/http.test.ts +0 -130
- package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
- package/tests/fixtures/components/TestOrder.ts +0 -23
- package/tests/fixtures/components/TestProduct.ts +0 -23
- package/tests/fixtures/components/TestUser.ts +0 -20
- package/tests/fixtures/components/index.ts +0 -6
- package/tests/graphql/SchemaGeneration.test.ts +0 -90
- package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
- package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
- package/tests/helpers/MockRedisClient.ts +0 -113
- package/tests/helpers/MockRedisStreamServer.ts +0 -448
- package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
- package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
- package/tests/integration/entity/Entity.persistence.test.ts +0 -333
- package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
- package/tests/integration/loaders/RequestLoaders.abort.test.ts +0 -82
- package/tests/integration/query/Query.abort.test.ts +0 -66
- package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
- package/tests/integration/query/Query.edgeCases.test.ts +0 -595
- package/tests/integration/query/Query.exec.test.ts +0 -576
- package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
- package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
- package/tests/integration/remote/dlq.test.ts +0 -175
- package/tests/integration/remote/event-dispatch.test.ts +0 -114
- package/tests/integration/remote/outbox.test.ts +0 -130
- package/tests/integration/remote/rpc.test.ts +0 -177
- package/tests/pglite-setup.ts +0 -62
- package/tests/setup.ts +0 -164
- package/tests/stress/BenchmarkRunner.ts +0 -203
- package/tests/stress/DataSeeder.ts +0 -190
- package/tests/stress/StressTestReporter.ts +0 -229
- package/tests/stress/cursor-perf-test.ts +0 -171
- package/tests/stress/fixtures/RealisticComponents.ts +0 -235
- package/tests/stress/fixtures/StressTestComponents.ts +0 -58
- package/tests/stress/index.ts +0 -7
- package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
- package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
- package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
- package/tests/unit/BatchLoader.test.ts +0 -196
- package/tests/unit/archetype/ArcheType.test.ts +0 -107
- package/tests/unit/cache/CacheManager.test.ts +0 -498
- package/tests/unit/cache/MemoryCache.test.ts +0 -260
- package/tests/unit/cache/RedisCache.test.ts +0 -411
- package/tests/unit/database/cancellable.test.ts +0 -81
- package/tests/unit/database/instrumentedDb.test.ts +0 -160
- package/tests/unit/entity/Entity.components.test.ts +0 -317
- package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
- package/tests/unit/entity/Entity.reload.test.ts +0 -63
- package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
- package/tests/unit/entity/Entity.test.ts +0 -345
- package/tests/unit/gql/depthLimit.test.ts +0 -203
- package/tests/unit/gql/operationMiddleware.test.ts +0 -293
- package/tests/unit/health/Health.test.ts +0 -129
- package/tests/unit/middleware/AccessLog.test.ts +0 -37
- package/tests/unit/middleware/Middleware.test.ts +0 -98
- package/tests/unit/middleware/RequestId.test.ts +0 -54
- package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
- package/tests/unit/query/FilterBuilder.test.ts +0 -111
- package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
- package/tests/unit/query/Query.emptyString.test.ts +0 -69
- package/tests/unit/query/Query.test.ts +0 -310
- package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
- package/tests/unit/remote/RemoteError.test.ts +0 -55
- package/tests/unit/remote/decorators.test.ts +0 -195
- package/tests/unit/remote/metrics.test.ts +0 -115
- package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
- package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
- package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
- package/tests/unit/schema/schema-integration.test.ts +0 -426
- package/tests/unit/schema/schema.test.ts +0 -580
- package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
- package/tests/unit/upload/RestUpload.test.ts +0 -267
- package/tests/unit/validateEnv.test.ts +0 -82
- package/tests/utils/entity-tracker.ts +0 -57
- package/tests/utils/index.ts +0 -13
- 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>
|
package/swagger/generator.ts
CHANGED
|
@@ -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
|
-
|
|
105
|
+
if (!this._jsonCache) {
|
|
106
|
+
this._jsonCache = JSON.stringify(this.spec, null, 2);
|
|
107
|
+
}
|
|
108
|
+
return this._jsonCache;
|
|
99
109
|
}
|
|
100
110
|
}
|
package/upload/UploadManager.ts
CHANGED
|
@@ -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.
|
|
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
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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,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
|