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.
- package/CHANGELOG.md +471 -370
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +93 -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 +8 -7
- 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 +25 -10
- 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 +383 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/getCacheManager.ts +10 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +375 -0
- package/core/health.ts +93 -4
- 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/StreamConsumer.ts +535 -535
- 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/core/validateEnv.ts +10 -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 +29 -3
- package/database/instrumentedDb.ts +141 -141
- package/database/sqlHelpers.ts +3 -1
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/schema/index.ts +15 -4
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +245 -14
- package/query/OrNode.ts +8 -19
- package/query/Query.ts +208 -79
- 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
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
# RFC: Remaining Refactor Targets
|
|
2
|
-
|
|
3
|
-
**Status:** Backlog / planning
|
|
4
|
-
**Author:** uray@qyubit.io (drafted with Claude)
|
|
5
|
-
**Date:** 2026-05-09
|
|
6
|
-
**Prior work:** `core/ArcheType.ts` split — commit `a886b45`, merged to `staging` (`fd75f21`).
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 1. Purpose
|
|
11
|
-
|
|
12
|
-
After `ArcheType.ts` was split (3064 → 1032 LOC across 8 modules under `core/archetype/`), several other files in `core/` remain large and concern-dense. This RFC enumerates them in priority order so future refactor work has a reference.
|
|
13
|
-
|
|
14
|
-
This is a **planning document**, not an approval-bound RFC. Each target listed here would get its own scoped RFC (like `RFC_APP_REFACTOR.md`) before work starts.
|
|
15
|
-
|
|
16
|
-
## 2. Selection Criteria
|
|
17
|
-
|
|
18
|
-
Files are ordered by combined score of:
|
|
19
|
-
|
|
20
|
-
1. **Size** (LOC) — bigger = harder to read, harder to test.
|
|
21
|
-
2. **Concern density** — number of distinct responsibilities mixed in one class/module.
|
|
22
|
-
3. **Blast radius** — how many tests/consumers depend on the file booting cleanly.
|
|
23
|
-
4. **Refactor ROI** — likelihood that splitting yields independently testable modules without behavior change.
|
|
24
|
-
|
|
25
|
-
Pure "library leaf" files (formatter helpers, fixed schemas) are excluded even when large, because splitting them wouldn't reveal new structure.
|
|
26
|
-
|
|
27
|
-
## 3. Targets
|
|
28
|
-
|
|
29
|
-
### 3.1 `core/App.ts` — 1477 LOC — **highest priority**
|
|
30
|
-
|
|
31
|
-
**Why next:** Most God-class. Mixes:
|
|
32
|
-
|
|
33
|
-
- Application lifecycle (phase orchestration, DB prep, component registration).
|
|
34
|
-
- HTTP server (Bun.serve setup, request routing, signal/disconnect plumbing).
|
|
35
|
-
- CORS (origin validation, header injection, preflight handling).
|
|
36
|
-
- OpenAPI spec generation (per-endpoint registration, Swagger UI HTML).
|
|
37
|
-
- GraphQL setup (Yoga instance, depth/complexity limits, plugin pipe, context factory wrap).
|
|
38
|
-
- REST routing (endpoint collection, dispatch).
|
|
39
|
-
- Plugin pipeline (`addPlugin`, `addYogaPlugin`).
|
|
40
|
-
- Scheduler bootstrap (`SchedulerManager` init, scheduled task registration per service).
|
|
41
|
-
- Health endpoints (`/health`, `/health/ready`, `/health/remote`).
|
|
42
|
-
- Metrics endpoint (`/metrics`).
|
|
43
|
-
- Studio routing (`/studio/api/*` — 107 LOC inline).
|
|
44
|
-
- Remote subsystem bootstrap (RemoteManager init, handler registration).
|
|
45
|
-
- Process signal & error handlers (SIGTERM, SIGINT, unhandledRejection, uncaughtException).
|
|
46
|
-
- Graceful shutdown ordering (HTTP → scheduler → remote → cache → DB).
|
|
47
|
-
- Prepared-statement cache warm-up.
|
|
48
|
-
|
|
49
|
-
`init()` is a giant `switch (phase)` (lines 198–476) where each case runs 30–80 LOC of business logic. `handleRequest()` is ~430 LOC across 8+ branches.
|
|
50
|
-
|
|
51
|
-
**Detailed plan:** see `RFC_APP_REFACTOR.md` (drafted, awaiting approval).
|
|
52
|
-
|
|
53
|
-
**Status:** RFC drafted, not started.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
### 3.2 `core/Entity.ts` — 1212 LOC
|
|
58
|
-
|
|
59
|
-
**Why next:** `save()` alone likely 300+ LOC. Cache ops inline. Mixes:
|
|
60
|
-
|
|
61
|
-
- Component add/get/remove (in-memory + persisted).
|
|
62
|
-
- DB persistence (insert/update/delete with abort signal + per-component partitioned writes).
|
|
63
|
-
- Cache write-through / write-invalidate strategies (L1 + L2 + pubsub).
|
|
64
|
-
- Hook dispatch (pre-save, post-save, post-delete) via `EntityHookManager`.
|
|
65
|
-
- Pending side-effects queue (`Entity.pendingCacheOps`, `Entity.pendingSideEffects` static drain methods for shutdown).
|
|
66
|
-
- Profile timing (`DB_SAVE_PROFILE`).
|
|
67
|
-
- Abort signal handling (timeout + client disconnect cancellation).
|
|
68
|
-
- Component-ready preflight (`ComponentRegistry.getReadyPromise`).
|
|
69
|
-
- Static finders (`FindById`, etc.).
|
|
70
|
-
|
|
71
|
-
**Proposed split direction:**
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
core/entity/
|
|
75
|
-
saveEntity.ts # save() body — DB writes, abort, profile
|
|
76
|
-
cacheStrategies.ts # write-through, write-invalidate per component
|
|
77
|
-
pendingOps.ts # pendingCacheOps + pendingSideEffects + drain methods
|
|
78
|
-
componentAccess.ts # add/get/remove + in-memory cache
|
|
79
|
-
finders.ts # static FindById, etc.
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Class skeleton + public API stays in `Entity.ts`.
|
|
83
|
-
|
|
84
|
-
**Risks:**
|
|
85
|
-
|
|
86
|
-
- `Entity.save()` is hot-path. Per-step micro-benchmark (save 1000 entities) before/after each extraction.
|
|
87
|
-
- Hook ordering is load-bearing (per `MEMORY.md` H-HOOK-1..3, C13). Don't reorder pre/post-commit phases.
|
|
88
|
-
- The PGlite Bun-SQL ACK race (documented in `CLAUDE.md`) is in this file's blast radius — keep `await entity.save()` semantics byte-identical.
|
|
89
|
-
|
|
90
|
-
**Estimated effort:** larger than App.ts because of perf sensitivity. ~6–8 hours plus benchmark validation.
|
|
91
|
-
|
|
92
|
-
**Status:** Not started.
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
### 3.3 `core/SchedulerManager.ts` — 932 LOC
|
|
97
|
-
|
|
98
|
-
**Why next:** Scheduling logic + distributed lock + hook orchestration in one class.
|
|
99
|
-
|
|
100
|
-
Concerns to disentangle:
|
|
101
|
-
|
|
102
|
-
- Cron expression parsing + schedule evaluation.
|
|
103
|
-
- Task registration & lookup (`registerScheduledTasks`).
|
|
104
|
-
- Per-task execution loop with skip-on-running guard (H-SCHED-1..5 in memory).
|
|
105
|
-
- Distributed lock (`DistributedLock`) acquisition + release semantics.
|
|
106
|
-
- Lifecycle integration (`disposeLifecycleIntegration`, awaiting in-flight tasks on `stop()` per C14).
|
|
107
|
-
- Metrics (`getMetrics`).
|
|
108
|
-
- Error handling per task.
|
|
109
|
-
|
|
110
|
-
**Proposed split direction:**
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
core/scheduler/
|
|
114
|
-
cronEvaluator.ts # cron expression -> next-fire-time
|
|
115
|
-
taskRunner.ts # per-task execute loop + skip-on-running
|
|
116
|
-
lockCoordinator.ts # DistributedLock wiring
|
|
117
|
-
lifecycleHooks.ts # phase-listener + dispose
|
|
118
|
-
metrics.ts # getMetrics
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
`SchedulerManager` keeps singleton + public API.
|
|
122
|
-
|
|
123
|
-
**Risks:**
|
|
124
|
-
|
|
125
|
-
- Concurrency hardening already done in v0.3.0 (H-SCHED-1..5). Refactor must preserve every guard. Property-based tests on the runner would help.
|
|
126
|
-
- Re-entry semantics on `DistributedLock` (memory: `acquired:false` on overlap). Don't change.
|
|
127
|
-
|
|
128
|
-
**Status:** Not started.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
### 3.4 `core/EntityHookManager.ts` — 921 LOC
|
|
133
|
-
|
|
134
|
-
**Why next:** Hook registry + dispatch + lifecycle in one place.
|
|
135
|
-
|
|
136
|
-
Concerns:
|
|
137
|
-
|
|
138
|
-
- Hook registration (per-component, per-event).
|
|
139
|
-
- Dispatch ordering (pre vs post, sync vs async).
|
|
140
|
-
- Hook chain with timer leak fixes (memory: H-HOOK-2, H-MEM-2).
|
|
141
|
-
- Re-entry / recursion guard.
|
|
142
|
-
- Integration with `Entity.save()` post-commit microtask scheduling.
|
|
143
|
-
|
|
144
|
-
**Proposed split direction:**
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
core/hooks/
|
|
148
|
-
registry.ts # register/lookup
|
|
149
|
-
dispatcher.ts # dispatch loop + ordering
|
|
150
|
-
guards.ts # re-entry guard, timer cleanup
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
`EntityHookManager` keeps public API.
|
|
154
|
-
|
|
155
|
-
**Risks:**
|
|
156
|
-
|
|
157
|
-
- Hook timing fixes (C13, H-HOOK-1..3) are load-bearing. Tests assert specific orderings.
|
|
158
|
-
- Cross-file coupling with `Entity.ts` — coordinate with §3.2 if both run in flight.
|
|
159
|
-
|
|
160
|
-
**Status:** Not started.
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
### 3.5 `core/cache/CacheManager.ts` — 574 LOC
|
|
165
|
-
|
|
166
|
-
**Why next:** L1 (memory) + L2 (Redis) + strategies + pub/sub all-in-one. Already smaller than peers, so lower priority.
|
|
167
|
-
|
|
168
|
-
Concerns:
|
|
169
|
-
|
|
170
|
-
- Provider initialization (memory + Redis).
|
|
171
|
-
- Strategy dispatch (write-through vs write-invalidate).
|
|
172
|
-
- Cross-instance invalidation via Redis pub/sub (`instanceId` loop prevention).
|
|
173
|
-
- Cache stats / health (`ping`, `getStats`).
|
|
174
|
-
- Singleton lifecycle (`initialize` async, `shutdown`).
|
|
175
|
-
|
|
176
|
-
**Proposed split direction:**
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
core/cache/
|
|
180
|
-
CacheManager.ts # singleton + public API (kept)
|
|
181
|
-
strategies/
|
|
182
|
-
writeThrough.ts
|
|
183
|
-
writeInvalidate.ts
|
|
184
|
-
invalidation.ts # pub/sub coordinator
|
|
185
|
-
health.ts # ping + stats
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**Risks:**
|
|
189
|
-
|
|
190
|
-
- `CacheManager.initialize()` is now async (BREAKING CHANGE per memory, 2026-02-17). Don't regress.
|
|
191
|
-
- Cross-instance loop prevention (`instanceId`) is load-bearing. Test with two instances on same Redis.
|
|
192
|
-
|
|
193
|
-
**Status:** Not started. Lowest priority of the five — defer until at least one peer refactor lands.
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## 4. Cross-Cutting Themes
|
|
198
|
-
|
|
199
|
-
Several patterns recur and would benefit from being decided once before any of these refactors start:
|
|
200
|
-
|
|
201
|
-
### 4.1 Extraction pattern
|
|
202
|
-
|
|
203
|
-
`ArcheType.ts` split established the pattern:
|
|
204
|
-
|
|
205
|
-
- Pure functions in submodules accept the class instance as a parameter (`buildFieldResolvers(archetype)`).
|
|
206
|
-
- Class methods become 1-line delegates via lazy `require()` to break circular type deps.
|
|
207
|
-
- Maps/state stay in the submodule that owns them, exported as `const`.
|
|
208
|
-
- Public API preserved by re-export from the parent file.
|
|
209
|
-
|
|
210
|
-
This pattern works well for ECS-style classes where the class is mostly a data bag with methods. **Re-use it for App, Entity, SchedulerManager, EntityHookManager.** `CacheManager` may want a different shape (provider injection) given its strategy variants.
|
|
211
|
-
|
|
212
|
-
### 4.2 Test infrastructure assumed stable
|
|
213
|
-
|
|
214
|
-
All five targets are exercised by the current 770-test suite (under `bun run test:pglite`). No target requires new test scaffolding before extraction starts; existing tests are sufficient guardrails for behavior preservation.
|
|
215
|
-
|
|
216
|
-
### 4.3 No DI introduction
|
|
217
|
-
|
|
218
|
-
Project rule (per `CLAUDE.md` and `MEMORY.md`): singletons + global exports, no dependency injection container. Extracted modules must respect this — pass `app: App`, `entity: Entity`, etc., not an injection token.
|
|
219
|
-
|
|
220
|
-
### 4.4 No bundled bug fixes
|
|
221
|
-
|
|
222
|
-
If a refactor reveals a latent bug (wrong ordering, missing guard, stale comment claim), file it separately. Refactor PRs must show "no behavior change" by passing the existing test suite unchanged.
|
|
223
|
-
|
|
224
|
-
## 5. Recommended Order
|
|
225
|
-
|
|
226
|
-
1. **`App.ts`** — RFC drafted, ready to start. Highest payoff: every test boots through it.
|
|
227
|
-
2. **`Entity.ts`** — Highest perf sensitivity but biggest readability win. Allocate benchmark time.
|
|
228
|
-
3. **`SchedulerManager.ts`** *or* **`EntityHookManager.ts`** — Either next. They're partially coupled (hooks fire from scheduler-triggered work), so coordinate.
|
|
229
|
-
4. **`CacheManager.ts`** — Last. Smallest of the five, already structured around providers.
|
|
230
|
-
|
|
231
|
-
This ordering minimizes risk because the most heavily-tested file goes first (more guardrails) and the perf-sensitive file goes early-second when there's still energy for benchmarking.
|
|
232
|
-
|
|
233
|
-
## 6. Anti-Goals
|
|
234
|
-
|
|
235
|
-
These are not refactors and should not be bundled:
|
|
236
|
-
|
|
237
|
-
- **Adding new abstractions** (router DSL, plugin SPI v2, hook framework). Out of scope for any of these.
|
|
238
|
-
- **Performance "improvements"** that change semantics. If a refactor reveals an O(n²) loop, file it separately.
|
|
239
|
-
- **API renaming** for "consistency". Public symbols stay byte-identical.
|
|
240
|
-
- **Comment cleanup pass** as a side effect. Touch only comments that are actively wrong after a code move.
|
|
241
|
-
|
|
242
|
-
## 7. Decision
|
|
243
|
-
|
|
244
|
-
This RFC requires no decision. It exists so the next person picking up refactor work has:
|
|
245
|
-
|
|
246
|
-
- A prioritized list.
|
|
247
|
-
- Concern inventory per file.
|
|
248
|
-
- Pre-identified risks per file.
|
|
249
|
-
- Cross-cutting guardrails (extraction pattern, no-DI rule, no bundled fixes).
|
|
250
|
-
|
|
251
|
-
When work starts on any one target, that target gets its own RFC and own branch (per the `RFC_APP_REFACTOR.md` template).
|
package/docs/SCALABILITY_PLAN.md
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# BunSane Scalability Plan: 1M+ Entities
|
|
2
|
-
|
|
3
|
-
## Problem Statement
|
|
4
|
-
|
|
5
|
-
At 50k entities, complex multi-component queries with sorting degrade catastrophically:
|
|
6
|
-
- 10k entities: 20ms
|
|
7
|
-
- 50k entities: 7,880ms (394x slower)
|
|
8
|
-
- Projected 1M: minutes to hours
|
|
9
|
-
|
|
10
|
-
Root cause: Cartesian product explosion in nested loop joins when sorting on JSONB fields.
|
|
11
|
-
|
|
12
|
-
## Bottleneck Analysis
|
|
13
|
-
|
|
14
|
-
### 1. Multi-Component Query Pattern (Critical)
|
|
15
|
-
|
|
16
|
-
Current SQL for 2-component query:
|
|
17
|
-
```sql
|
|
18
|
-
SELECT DISTINCT ec.entity_id
|
|
19
|
-
FROM entity_components ec
|
|
20
|
-
WHERE ec.type_id IN ($1, $2) AND ec.deleted_at IS NULL
|
|
21
|
-
GROUP BY ec.entity_id
|
|
22
|
-
HAVING COUNT(DISTINCT ec.type_id) = 2
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Problem**: Scans ALL entity_components for ALL matching types, then aggregates.
|
|
26
|
-
At 1M entities × 3 components = 3M rows scanned before filtering.
|
|
27
|
-
|
|
28
|
-
**Solution**: Use INTERSECT or EXISTS pattern:
|
|
29
|
-
```sql
|
|
30
|
-
-- Option A: INTERSECT (better for 2-3 components)
|
|
31
|
-
SELECT entity_id FROM entity_components WHERE type_id = $1 AND deleted_at IS NULL
|
|
32
|
-
INTERSECT
|
|
33
|
-
SELECT entity_id FROM entity_components WHERE type_id = $2 AND deleted_at IS NULL
|
|
34
|
-
|
|
35
|
-
-- Option B: EXISTS (better for many components)
|
|
36
|
-
SELECT DISTINCT e.entity_id
|
|
37
|
-
FROM entity_components e
|
|
38
|
-
WHERE e.type_id = $1 AND e.deleted_at IS NULL
|
|
39
|
-
AND EXISTS (SELECT 1 FROM entity_components e2
|
|
40
|
-
WHERE e2.entity_id = e.entity_id AND e2.type_id = $2 AND e2.deleted_at IS NULL)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 2. Sorting on JSONB Fields (Critical)
|
|
44
|
-
|
|
45
|
-
Current pattern:
|
|
46
|
-
```sql
|
|
47
|
-
ORDER BY c.data->>'age' DESC
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Problem**: Can't use B-tree indexes, falls to sequential scan + in-memory sort.
|
|
51
|
-
|
|
52
|
-
**Solutions**:
|
|
53
|
-
|
|
54
|
-
A. **Expression Index** (per-field, must exist):
|
|
55
|
-
```sql
|
|
56
|
-
CREATE INDEX idx_testuser_age_btree ON components_testuser ((data->>'age'));
|
|
57
|
-
-- For numeric sorting:
|
|
58
|
-
CREATE INDEX idx_testuser_age_numeric ON components_testuser (((data->>'age')::numeric));
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
B. **Query must cast for numeric sort**:
|
|
62
|
-
```sql
|
|
63
|
-
ORDER BY (c.data->>'age')::numeric DESC -- Uses numeric index
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
C. **Covering Index** (include entity_id for index-only scan):
|
|
67
|
-
```sql
|
|
68
|
-
CREATE INDEX idx_testuser_age_covering
|
|
69
|
-
ON components_testuser ((data->>'age'), entity_id)
|
|
70
|
-
WHERE deleted_at IS NULL;
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 3. Missing Index on entities.deleted_at
|
|
74
|
-
|
|
75
|
-
Every query does `WHERE deleted_at IS NULL` on entities table.
|
|
76
|
-
|
|
77
|
-
**Fix**:
|
|
78
|
-
```sql
|
|
79
|
-
CREATE INDEX idx_entities_deleted_null ON entities (id) WHERE deleted_at IS NULL;
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### 4. OFFSET Pagination Scaling
|
|
83
|
-
|
|
84
|
-
`OFFSET 900000` requires scanning 900k rows to skip them.
|
|
85
|
-
|
|
86
|
-
**Already implemented**: `cursor(entityId)` pagination in Query.ts.
|
|
87
|
-
**Action**: Document as required pattern for large datasets.
|
|
88
|
-
|
|
89
|
-
## Implementation Plan
|
|
90
|
-
|
|
91
|
-
### Phase 1: Quick Wins (Immediate)
|
|
92
|
-
|
|
93
|
-
1. **Add missing index on entities**
|
|
94
|
-
- File: `database/DatabaseHelper.ts`
|
|
95
|
-
- Add: `idx_entities_deleted_null`
|
|
96
|
-
|
|
97
|
-
2. **Numeric cast in ORDER BY**
|
|
98
|
-
- File: `query/ComponentInclusionNode.ts`
|
|
99
|
-
- Detect numeric fields and add `::numeric` cast
|
|
100
|
-
|
|
101
|
-
3. **Use INTERSECT for 2-3 component queries**
|
|
102
|
-
- File: `query/ComponentInclusionNode.ts`
|
|
103
|
-
- Threshold: Use INTERSECT when componentIds.size <= 3
|
|
104
|
-
|
|
105
|
-
### Phase 2: Index Strategy (Short-term)
|
|
106
|
-
|
|
107
|
-
4. **Auto-create expression indexes for sortable fields**
|
|
108
|
-
- File: `database/IndexingStrategy.ts`
|
|
109
|
-
- Add: `createSortIndex(table, field, type: 'text' | 'numeric' | 'date')`
|
|
110
|
-
|
|
111
|
-
5. **Query hints for sort fields**
|
|
112
|
-
- New decorator: `@SortableField(type)`
|
|
113
|
-
- Creates appropriate expression index at registration
|
|
114
|
-
|
|
115
|
-
### Phase 3: Query Restructuring (Medium-term)
|
|
116
|
-
|
|
117
|
-
6. **EXISTS pattern for multi-component with filters**
|
|
118
|
-
- Rewrite CTE to use correlated EXISTS
|
|
119
|
-
- Push filters into EXISTS subqueries
|
|
120
|
-
|
|
121
|
-
7. **Batch entity lookup optimization**
|
|
122
|
-
- Use `= ANY($1::uuid[])` instead of `IN (...)` for large ID lists
|
|
123
|
-
- Better plan caching with array parameter
|
|
124
|
-
|
|
125
|
-
### Phase 4: Denormalization Options (Long-term)
|
|
126
|
-
|
|
127
|
-
8. **entity_component_summary table**
|
|
128
|
-
```sql
|
|
129
|
-
CREATE TABLE entity_component_summary (
|
|
130
|
-
entity_id UUID PRIMARY KEY,
|
|
131
|
-
component_types TEXT[], -- Array of type_ids
|
|
132
|
-
updated_at TIMESTAMP
|
|
133
|
-
);
|
|
134
|
-
CREATE INDEX idx_ecs_types_gin ON entity_component_summary USING GIN (component_types);
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Query pattern:
|
|
138
|
-
```sql
|
|
139
|
-
SELECT entity_id FROM entity_component_summary
|
|
140
|
-
WHERE component_types @> ARRAY[$1, $2]::text[]
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
9. **Materialized views for hot paths**
|
|
144
|
-
- Pre-join common component combinations
|
|
145
|
-
- Refresh on schedule or trigger
|
|
146
|
-
|
|
147
|
-
## Benchmarks Required
|
|
148
|
-
|
|
149
|
-
| Scenario | Target (1M entities) |
|
|
150
|
-
|----------|---------------------|
|
|
151
|
-
| Single component, no filter | < 50ms |
|
|
152
|
-
| Single component, indexed filter | < 20ms |
|
|
153
|
-
| 2-component intersection | < 100ms |
|
|
154
|
-
| 3-component intersection | < 200ms |
|
|
155
|
-
| Sort on indexed field, limit 100 | < 50ms |
|
|
156
|
-
| Complex (2-comp + filter + sort) | < 500ms |
|
|
157
|
-
| Count | < 100ms |
|
|
158
|
-
| Cursor pagination (any page) | < 50ms |
|
|
159
|
-
|
|
160
|
-
## Migration Strategy
|
|
161
|
-
|
|
162
|
-
1. New indexes are additive (no breaking changes)
|
|
163
|
-
2. Query optimizations are **always on** (no feature flag needed)
|
|
164
|
-
3. INTERSECT + scalar subquery patterns enabled by default since v0.2.7
|
|
165
|
-
4. LATERAL joins disabled for INTERSECT queries to fix SQL generation bug (2026-03-14)
|
|
166
|
-
|
|
167
|
-
## Files to Modify
|
|
168
|
-
|
|
169
|
-
- `database/DatabaseHelper.ts` - Add entity index
|
|
170
|
-
- `database/IndexingStrategy.ts` - Sort index creation
|
|
171
|
-
- `query/ComponentInclusionNode.ts` - INTERSECT pattern, numeric cast
|
|
172
|
-
- `query/QueryDAG.ts` - Component count threshold for strategy selection
|
|
173
|
-
- `core/components/Decorators.ts` - @SortableField decorator
|
|
174
|
-
- New: `query/strategies/IntersectStrategy.ts`
|
|
175
|
-
- New: `query/strategies/ExistsStrategy.ts`
|