bunsane 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +445 -318
  2. package/config/cache.config.ts +35 -1
  3. package/core/App.ts +24 -1064
  4. package/core/ArcheType.ts +78 -2110
  5. package/core/BatchLoader.ts +56 -32
  6. package/core/Entity.ts +85 -1043
  7. package/core/EntityHookManager.ts +52 -754
  8. package/core/Logger.ts +10 -0
  9. package/core/RequestContext.ts +64 -6
  10. package/core/RequestLoaders.ts +187 -36
  11. package/core/SchedulerManager.ts +28 -600
  12. package/core/app/bootstrap.ts +133 -0
  13. package/core/app/cors.ts +85 -0
  14. package/core/app/graphqlSetup.ts +56 -0
  15. package/core/app/healthEndpoints.ts +31 -0
  16. package/core/app/metricsCollector.ts +27 -0
  17. package/core/app/preparedStatementWarmup.ts +15 -0
  18. package/core/app/processHandlers.ts +43 -0
  19. package/core/app/requestRouter.ts +310 -0
  20. package/core/app/restRegistry.ts +80 -0
  21. package/core/app/shutdown.ts +97 -0
  22. package/core/app/studioRouter.ts +83 -0
  23. package/core/archetype/customTypes.ts +100 -0
  24. package/core/archetype/decorators.ts +171 -0
  25. package/core/archetype/fieldResolvers.ts +666 -0
  26. package/core/archetype/helpers.ts +29 -0
  27. package/core/archetype/relationLoader.ts +161 -0
  28. package/core/archetype/schemaBuilder.ts +141 -0
  29. package/core/archetype/weaver.ts +218 -0
  30. package/core/archetype/zodSchemaBuilder.ts +527 -0
  31. package/core/cache/CacheManager.ts +173 -267
  32. package/core/cache/CompressionUtils.ts +34 -3
  33. package/core/cache/MemoryCache.ts +40 -37
  34. package/core/cache/RedisCache.ts +4 -4
  35. package/core/cache/health.ts +30 -0
  36. package/core/cache/invalidation.ts +96 -0
  37. package/core/cache/strategies/writeInvalidate.ts +111 -0
  38. package/core/cache/strategies/writeThrough.ts +233 -0
  39. package/core/components/BaseComponent.ts +16 -8
  40. package/core/components/ComponentRegistry.ts +28 -0
  41. package/core/decorators/IndexedField.ts +1 -1
  42. package/core/entity/cacheStrategies.ts +97 -0
  43. package/core/entity/componentAccess.ts +364 -0
  44. package/core/entity/finders.ts +202 -0
  45. package/core/entity/pendingOps.ts +72 -0
  46. package/core/entity/saveEntity.ts +377 -0
  47. package/core/hooks/dispatcher.ts +439 -0
  48. package/core/hooks/guards.ts +155 -0
  49. package/core/hooks/registry.ts +247 -0
  50. package/core/metadata/definitions/Component.ts +1 -1
  51. package/core/metadata/index.ts +15 -4
  52. package/core/middleware/AccessLog.ts +8 -1
  53. package/core/middleware/RateLimit.ts +102 -105
  54. package/core/middleware/RequestId.ts +2 -9
  55. package/core/middleware/SecurityHeaders.ts +2 -11
  56. package/core/middleware/headers.ts +28 -0
  57. package/core/remote/OutboxWorker.ts +213 -183
  58. package/core/remote/RemoteManager.ts +401 -400
  59. package/core/remote/types.ts +153 -151
  60. package/core/requestScope.ts +34 -0
  61. package/core/scheduler/cronEvaluator.ts +174 -0
  62. package/core/scheduler/lifecycleHooks.ts +21 -0
  63. package/core/scheduler/lockCoordinator.ts +27 -0
  64. package/core/scheduler/metrics.ts +14 -0
  65. package/core/scheduler/taskRunner.ts +420 -0
  66. package/database/DatabaseHelper.ts +128 -101
  67. package/database/IndexingStrategy.ts +72 -2
  68. package/database/PreparedStatementCache.ts +20 -5
  69. package/database/cancellable.ts +35 -0
  70. package/database/index.ts +15 -3
  71. package/database/instrumentedDb.ts +141 -0
  72. package/endpoints/archetypes.ts +2 -8
  73. package/endpoints/tables.ts +6 -1
  74. package/gql/index.ts +1 -1
  75. package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
  76. package/package.json +22 -1
  77. package/query/CTENode.ts +5 -3
  78. package/query/ComponentInclusionNode.ts +240 -13
  79. package/query/OrNode.ts +6 -5
  80. package/query/Query.ts +203 -59
  81. package/query/QueryContext.ts +6 -0
  82. package/query/QueryDAG.ts +7 -2
  83. package/query/membershipSource.ts +66 -0
  84. package/storage/LocalStorageProvider.ts +8 -3
  85. package/studio/dist/assets/index-BMZ67Npg.js +254 -0
  86. package/studio/dist/assets/index-BpbuYz9g.css +1 -0
  87. package/studio/{index.html → dist/index.html} +3 -2
  88. package/swagger/generator.ts +11 -1
  89. package/upload/UploadManager.ts +8 -6
  90. package/utils/uuid.ts +40 -10
  91. package/.claude/settings.local.json +0 -47
  92. package/.prettierrc +0 -4
  93. package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
  94. package/.serena/memories/architecture.md +0 -154
  95. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
  96. package/.serena/memories/code_style_and_conventions.md +0 -76
  97. package/.serena/memories/project_overview.md +0 -43
  98. package/.serena/memories/schema-dsl-plan.md +0 -107
  99. package/.serena/memories/suggested_commands.md +0 -80
  100. package/.serena/memories/typescript-compilation-status.md +0 -54
  101. package/.serena/project.yml +0 -114
  102. package/BunSane.jpg +0 -0
  103. package/CLAUDE.md +0 -198
  104. package/TODO.md +0 -2
  105. package/bun.lock +0 -302
  106. package/bunfig.toml +0 -10
  107. package/docs/SCALABILITY_PLAN.md +0 -175
  108. package/studio/bun.lock +0 -482
  109. package/studio/package.json +0 -39
  110. package/studio/postcss.config.js +0 -6
  111. package/studio/src/components/DataTable.tsx +0 -211
  112. package/studio/src/components/Layout.tsx +0 -13
  113. package/studio/src/components/PageContainer.tsx +0 -9
  114. package/studio/src/components/PageHeader.tsx +0 -13
  115. package/studio/src/components/SearchBar.tsx +0 -57
  116. package/studio/src/components/Sidebar.tsx +0 -294
  117. package/studio/src/components/ui/button.tsx +0 -56
  118. package/studio/src/components/ui/checkbox.tsx +0 -26
  119. package/studio/src/components/ui/input.tsx +0 -25
  120. package/studio/src/hooks/useDataTable.ts +0 -131
  121. package/studio/src/index.css +0 -36
  122. package/studio/src/lib/api.ts +0 -186
  123. package/studio/src/lib/utils.ts +0 -13
  124. package/studio/src/main.tsx +0 -17
  125. package/studio/src/pages/ArcheType.tsx +0 -239
  126. package/studio/src/pages/Components.tsx +0 -124
  127. package/studio/src/pages/EntityInspector.tsx +0 -302
  128. package/studio/src/pages/QueryRunner.tsx +0 -246
  129. package/studio/src/pages/Table.tsx +0 -94
  130. package/studio/src/pages/Welcome.tsx +0 -241
  131. package/studio/src/routes.tsx +0 -45
  132. package/studio/src/store/archeTypeSettings.ts +0 -30
  133. package/studio/src/store/studio.ts +0 -65
  134. package/studio/src/utils/columnHelpers.tsx +0 -114
  135. package/studio/studio-instructions.md +0 -81
  136. package/studio/tailwind.config.js +0 -77
  137. package/studio/utils.ts +0 -54
  138. package/studio/vite.config.js +0 -19
  139. package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
  140. package/tests/benchmark/bunfig.toml +0 -9
  141. package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
  142. package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
  143. package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
  144. package/tests/benchmark/fixtures/index.ts +0 -6
  145. package/tests/benchmark/index.ts +0 -22
  146. package/tests/benchmark/noop-preload.ts +0 -3
  147. package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
  148. package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
  149. package/tests/benchmark/runners/index.ts +0 -4
  150. package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
  151. package/tests/benchmark/scripts/generate-db.ts +0 -344
  152. package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
  153. package/tests/e2e/http.test.ts +0 -130
  154. package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
  155. package/tests/fixtures/components/TestOrder.ts +0 -23
  156. package/tests/fixtures/components/TestProduct.ts +0 -23
  157. package/tests/fixtures/components/TestUser.ts +0 -20
  158. package/tests/fixtures/components/index.ts +0 -6
  159. package/tests/graphql/SchemaGeneration.test.ts +0 -90
  160. package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
  161. package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
  162. package/tests/helpers/MockRedisClient.ts +0 -113
  163. package/tests/helpers/MockRedisStreamServer.ts +0 -448
  164. package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
  165. package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
  166. package/tests/integration/entity/Entity.persistence.test.ts +0 -333
  167. package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
  168. package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
  169. package/tests/integration/query/Query.edgeCases.test.ts +0 -595
  170. package/tests/integration/query/Query.exec.test.ts +0 -576
  171. package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
  172. package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
  173. package/tests/integration/remote/dlq.test.ts +0 -175
  174. package/tests/integration/remote/event-dispatch.test.ts +0 -114
  175. package/tests/integration/remote/outbox.test.ts +0 -130
  176. package/tests/integration/remote/rpc.test.ts +0 -177
  177. package/tests/pglite-setup.ts +0 -62
  178. package/tests/setup.ts +0 -164
  179. package/tests/stress/BenchmarkRunner.ts +0 -203
  180. package/tests/stress/DataSeeder.ts +0 -190
  181. package/tests/stress/StressTestReporter.ts +0 -229
  182. package/tests/stress/cursor-perf-test.ts +0 -171
  183. package/tests/stress/fixtures/RealisticComponents.ts +0 -235
  184. package/tests/stress/fixtures/StressTestComponents.ts +0 -58
  185. package/tests/stress/index.ts +0 -7
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
  187. package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
  188. package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
  189. package/tests/unit/BatchLoader.test.ts +0 -196
  190. package/tests/unit/archetype/ArcheType.test.ts +0 -107
  191. package/tests/unit/cache/CacheManager.test.ts +0 -367
  192. package/tests/unit/cache/MemoryCache.test.ts +0 -260
  193. package/tests/unit/cache/RedisCache.test.ts +0 -411
  194. package/tests/unit/entity/Entity.components.test.ts +0 -317
  195. package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
  196. package/tests/unit/entity/Entity.reload.test.ts +0 -63
  197. package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
  198. package/tests/unit/entity/Entity.test.ts +0 -345
  199. package/tests/unit/gql/depthLimit.test.ts +0 -203
  200. package/tests/unit/gql/operationMiddleware.test.ts +0 -293
  201. package/tests/unit/health/Health.test.ts +0 -129
  202. package/tests/unit/middleware/AccessLog.test.ts +0 -37
  203. package/tests/unit/middleware/Middleware.test.ts +0 -98
  204. package/tests/unit/middleware/RequestId.test.ts +0 -54
  205. package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
  206. package/tests/unit/query/FilterBuilder.test.ts +0 -111
  207. package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
  208. package/tests/unit/query/Query.emptyString.test.ts +0 -69
  209. package/tests/unit/query/Query.test.ts +0 -310
  210. package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
  211. package/tests/unit/remote/RemoteError.test.ts +0 -55
  212. package/tests/unit/remote/decorators.test.ts +0 -195
  213. package/tests/unit/remote/metrics.test.ts +0 -115
  214. package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
  215. package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
  216. package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
  217. package/tests/unit/schema/schema-integration.test.ts +0 -426
  218. package/tests/unit/schema/schema.test.ts +0 -580
  219. package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
  220. package/tests/unit/upload/RestUpload.test.ts +0 -267
  221. package/tests/unit/validateEnv.test.ts +0 -82
  222. package/tests/utils/entity-tracker.ts +0 -57
  223. package/tests/utils/index.ts +0 -13
  224. package/tests/utils/test-context.ts +0 -149
@@ -1,94 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { useParams } from 'react-router-dom'
3
- import { type ColumnDef } from '@tanstack/react-table'
4
- import { fetchTableData, deleteTableRecords } from '../lib/api'
5
- import { PageContainer } from "../components/PageContainer";
6
- import { PageHeader } from "../components/PageHeader";
7
- import { SearchBar } from "../components/SearchBar";
8
- import { DataTable } from '../components/DataTable'
9
- import { useDataTable } from '../hooks/useDataTable'
10
- import { createSelectColumn, createTextColumn } from '../utils/columnHelpers'
11
-
12
- interface TableRecord {
13
- [key: string]: any
14
- }
15
-
16
- export function Table() {
17
- const { name } = useParams<{ name: string }>()
18
- const [columns, setColumns] = useState<ColumnDef<TableRecord>[]>([])
19
-
20
- const {
21
- data,
22
- loading,
23
- hasMore,
24
- search,
25
- sorting,
26
- selectedRecords,
27
- setSearch,
28
- setSorting,
29
- setSelectedRecords,
30
- handleDelete,
31
- loadMoreRef,
32
- } = useDataTable<TableRecord>({
33
- key: name || '',
34
- fetchData: (params) => fetchTableData(name!, params),
35
- deleteRecords: (ids) => deleteTableRecords(name!, ids),
36
- fetchErrorMessage: 'Failed to load table data',
37
- deleteErrorMessage: 'Failed to delete table records',
38
- })
39
-
40
- // Generate columns from the first data record
41
- useEffect(() => {
42
- const sampleRecord = data[0]
43
- if (sampleRecord && columns.length === 0) {
44
- const newColumns: ColumnDef<TableRecord>[] = [
45
- createSelectColumn<TableRecord>(),
46
- ...Object.keys(sampleRecord).map(key =>
47
- createTextColumn<TableRecord>(key, key)
48
- ),
49
- ]
50
- setColumns(newColumns)
51
- }
52
- }, [data, columns.length])
53
-
54
- // Reset columns when table name changes
55
- useEffect(() => {
56
- setColumns([])
57
- }, [name])
58
-
59
- if (!name) {
60
- return <div className="p-8">Table name not found</div>
61
- }
62
-
63
- return (
64
- <PageContainer>
65
- <PageHeader
66
- title={`${name} Table`}
67
- description={`Browse and manage records in the ${name} table`}
68
- />
69
- <SearchBar
70
- search={search}
71
- onSearchChange={setSearch}
72
- placeholder="Search records..."
73
- selectedCount={selectedRecords.size}
74
- onDelete={handleDelete}
75
- itemSingular="record"
76
- itemPlural="records"
77
- />
78
- <DataTable
79
- data={data}
80
- columns={columns}
81
- loading={loading}
82
- hasMore={hasMore}
83
- sorting={sorting}
84
- onSortingChange={setSorting}
85
- selectedRecords={selectedRecords}
86
- onSelectionChange={setSelectedRecords}
87
- getRecordId={(record) => String(record.id)}
88
- loadMoreRef={loadMoreRef}
89
- emptyMessage="No records found"
90
- loadingMessage="Loading more records..."
91
- />
92
- </PageContainer>
93
- );
94
- }
@@ -1,241 +0,0 @@
1
- import { useState, useEffect } from 'react'
2
- import { useNavigate, Link } from 'react-router-dom'
3
- import {
4
- Search,
5
- Loader2,
6
- AlertCircle,
7
- Box,
8
- Layers,
9
- FlameIcon,
10
- RefreshCw,
11
- Trash2,
12
- } from 'lucide-react'
13
- import { PageContainer } from '../components/PageContainer'
14
- import { Input } from '../components/ui/input'
15
- import { Button } from '../components/ui/button'
16
- import { fetchStats, type StudioStats } from '../lib/api'
17
- import { toast } from 'sonner'
18
-
19
- export function Welcome() {
20
- const navigate = useNavigate()
21
- const [stats, setStats] = useState<StudioStats | null>(null)
22
- const [loading, setLoading] = useState(true)
23
- const [error, setError] = useState<string | null>(null)
24
- const [entitySearch, setEntitySearch] = useState('')
25
-
26
- const loadStats = async () => {
27
- setLoading(true)
28
- setError(null)
29
- try {
30
- const data = await fetchStats()
31
- setStats(data)
32
- } catch (err) {
33
- const message = err instanceof Error ? err.message : 'Failed to fetch stats'
34
- setError(message)
35
- toast.error(message)
36
- } finally {
37
- setLoading(false)
38
- }
39
- }
40
-
41
- useEffect(() => {
42
- loadStats()
43
- }, [])
44
-
45
- const handleEntityLookup = (e: React.FormEvent) => {
46
- e.preventDefault()
47
- const trimmed = entitySearch.trim()
48
- if (!trimmed) return
49
- navigate(`/entity/${trimmed}`)
50
- }
51
-
52
- return (
53
- <PageContainer>
54
- <div className="max-w-5xl">
55
- {/* Header */}
56
- <div className="flex items-center justify-between mb-8">
57
- <div>
58
- <h1 className="text-3xl font-bold text-primary">Dashboard</h1>
59
- <p className="text-muted-foreground mt-1">
60
- Overview of your ECS database
61
- </p>
62
- </div>
63
- <Button
64
- variant="outline"
65
- size="sm"
66
- onClick={loadStats}
67
- disabled={loading}
68
- >
69
- {loading ? (
70
- <Loader2 className="h-4 w-4 animate-spin mr-2" />
71
- ) : (
72
- <RefreshCw className="h-4 w-4 mr-2" />
73
- )}
74
- Refresh
75
- </Button>
76
- </div>
77
-
78
- {/* Quick entity lookup */}
79
- <form onSubmit={handleEntityLookup} className="flex gap-2 mb-8 max-w-lg">
80
- <div className="relative flex-1">
81
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
82
- <Input
83
- value={entitySearch}
84
- onChange={(e) => setEntitySearch(e.target.value)}
85
- placeholder="Quick entity lookup — paste UUID..."
86
- className="pl-9 font-mono text-sm"
87
- />
88
- </div>
89
- <Button type="submit" disabled={!entitySearch.trim()}>
90
- Inspect
91
- </Button>
92
- </form>
93
-
94
- {/* Error */}
95
- {error && !loading && (
96
- <div className="flex items-center gap-2 text-destructive bg-destructive/10 border border-destructive/20 rounded-md p-4 mb-6">
97
- <AlertCircle className="h-5 w-5 shrink-0" />
98
- <span>{error}</span>
99
- </div>
100
- )}
101
-
102
- {/* Loading skeleton */}
103
- {loading && !stats && (
104
- <div className="flex items-center gap-2 text-muted-foreground py-12 justify-center">
105
- <Loader2 className="h-5 w-5 animate-spin" />
106
- <span>Loading stats...</span>
107
- </div>
108
- )}
109
-
110
- {/* Stats content */}
111
- {stats && (
112
- <>
113
- {/* Summary cards */}
114
- <div className="grid gap-4 md:grid-cols-3 mb-8">
115
- <SummaryCard
116
- icon={Box}
117
- label="Entities"
118
- value={stats.entities.active}
119
- sub={
120
- stats.entities.deleted > 0
121
- ? `${stats.entities.deleted} deleted`
122
- : undefined
123
- }
124
- />
125
- <SummaryCard
126
- icon={Layers}
127
- label="Component Types"
128
- value={stats.componentTypes.length}
129
- />
130
- <SummaryCard
131
- icon={FlameIcon}
132
- label="Archetypes"
133
- value={stats.archetypes.length}
134
- />
135
- </div>
136
-
137
- {/* Two-column layout */}
138
- <div className="grid gap-6 lg:grid-cols-2">
139
- {/* Archetypes */}
140
- <div className="border border-border rounded-lg bg-card">
141
- <div className="px-5 py-4 border-b border-border">
142
- <h2 className="font-semibold">Archetypes</h2>
143
- </div>
144
- {stats.archetypes.length === 0 ? (
145
- <p className="p-5 text-sm text-muted-foreground">
146
- No archetypes registered.
147
- </p>
148
- ) : (
149
- <div className="divide-y divide-border">
150
- {stats.archetypes.map((a) => (
151
- <Link
152
- key={a.name}
153
- to={`/archetype/${a.name}`}
154
- className="flex items-center justify-between px-5 py-3 hover:bg-accent/50 transition-colors"
155
- >
156
- <div>
157
- <span className="text-sm font-medium">{a.name}</span>
158
- <span className="text-xs text-muted-foreground ml-2">
159
- {a.componentCount} component{a.componentCount !== 1 ? 's' : ''}
160
- </span>
161
- </div>
162
- <span className="text-sm font-mono tabular-nums">
163
- {a.entityCount.toLocaleString()}
164
- </span>
165
- </Link>
166
- ))}
167
- </div>
168
- )}
169
- </div>
170
-
171
- {/* Component types */}
172
- <div className="border border-border rounded-lg bg-card">
173
- <div className="px-5 py-4 border-b border-border">
174
- <h2 className="font-semibold">Component Types</h2>
175
- </div>
176
- {stats.componentTypes.length === 0 ? (
177
- <p className="p-5 text-sm text-muted-foreground">
178
- No components found.
179
- </p>
180
- ) : (
181
- <div className="divide-y divide-border max-h-96 overflow-auto">
182
- {stats.componentTypes.map((ct) => (
183
- <div
184
- key={ct.name}
185
- className="flex items-center justify-between px-5 py-3"
186
- >
187
- <span className="text-sm">{ct.name}</span>
188
- <span className="text-sm font-mono tabular-nums text-muted-foreground">
189
- {ct.count.toLocaleString()}
190
- </span>
191
- </div>
192
- ))}
193
- </div>
194
- )}
195
- </div>
196
- </div>
197
-
198
- {/* Deleted entities note */}
199
- {stats.entities.deleted > 0 && (
200
- <div className="mt-6 flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 rounded-md px-4 py-3">
201
- <Trash2 className="h-4 w-4 shrink-0" />
202
- <span>
203
- {stats.entities.deleted.toLocaleString()} soft-deleted{' '}
204
- {stats.entities.deleted === 1 ? 'entity' : 'entities'} in the
205
- database ({stats.entities.total.toLocaleString()} total)
206
- </span>
207
- </div>
208
- )}
209
- </>
210
- )}
211
- </div>
212
- </PageContainer>
213
- )
214
- }
215
-
216
- function SummaryCard({
217
- icon: Icon,
218
- label,
219
- value,
220
- sub,
221
- }: {
222
- icon: typeof Box
223
- label: string
224
- value: number
225
- sub?: string
226
- }) {
227
- return (
228
- <div className="border border-border rounded-lg p-5 bg-card">
229
- <div className="flex items-center gap-3 mb-3">
230
- <div className="p-2 rounded-md bg-primary/10">
231
- <Icon className="h-5 w-5 text-primary" />
232
- </div>
233
- <span className="text-sm text-muted-foreground">{label}</span>
234
- </div>
235
- <p className="text-3xl font-bold tabular-nums">{value.toLocaleString()}</p>
236
- {sub && (
237
- <p className="text-xs text-muted-foreground mt-1">{sub}</p>
238
- )}
239
- </div>
240
- )
241
- }
@@ -1,45 +0,0 @@
1
- import { type RouteObject } from 'react-router-dom'
2
- import { Layout } from './components/Layout'
3
- import { Welcome } from './pages/Welcome'
4
- import { ArcheType } from './pages/ArcheType'
5
- import { Table } from './pages/Table'
6
- import { EntityInspector } from './pages/EntityInspector'
7
- import { Components } from './pages/Components'
8
- import { QueryRunner } from './pages/QueryRunner'
9
-
10
- export const routes: RouteObject[] = [
11
- {
12
- path: '/',
13
- element: <Layout />,
14
- children: [
15
- {
16
- index: true,
17
- element: <Welcome />,
18
- },
19
- {
20
- path: 'archetype/:name',
21
- element: <ArcheType />,
22
- },
23
- {
24
- path: 'table/:name',
25
- element: <Table />,
26
- },
27
- {
28
- path: 'entity/:id',
29
- element: <EntityInspector />,
30
- },
31
- {
32
- path: 'entity',
33
- element: <EntityInspector />,
34
- },
35
- {
36
- path: 'components',
37
- element: <Components />,
38
- },
39
- {
40
- path: 'query',
41
- element: <QueryRunner />,
42
- },
43
- ],
44
- },
45
- ]
@@ -1,30 +0,0 @@
1
- import { create } from 'zustand'
2
- import { persist } from 'zustand/middleware'
3
-
4
- interface ArcheTypeSettingsState {
5
- useRealDbFieldName: boolean
6
- autoExpandRow: boolean
7
- showDeleted: boolean
8
-
9
- // Actions
10
- setUseRealDbFieldName: (value: boolean) => void
11
- setAutoExpandRow: (value: boolean) => void
12
- setShowDeleted: (value: boolean) => void
13
- }
14
-
15
- export const useArcheTypeSettings = create<ArcheTypeSettingsState>()(
16
- persist(
17
- (set) => ({
18
- useRealDbFieldName: false,
19
- autoExpandRow: true,
20
- showDeleted: false,
21
-
22
- setUseRealDbFieldName: (value) => set({ useRealDbFieldName: value }),
23
- setAutoExpandRow: (value) => set({ autoExpandRow: value }),
24
- setShowDeleted: (value) => set({ showDeleted: value }),
25
- }),
26
- {
27
- name: 'bunsane-archetype-settings',
28
- }
29
- )
30
- )
@@ -1,65 +0,0 @@
1
- import { create } from 'zustand'
2
- import { persist } from "zustand/middleware";
3
-
4
- export interface Metadata {
5
- archeTypes: Record<string, {
6
- fieldName: string
7
- componentName: string
8
- fieldLabel: string
9
- nullable?: boolean
10
- }[]>
11
- }
12
-
13
- interface StudioState {
14
- metadata: Metadata | null;
15
- tables: string[];
16
- isLoading: boolean;
17
- error: string | null;
18
- isSidebarCollapsed: boolean;
19
- expandedSections: Record<string, boolean>;
20
-
21
- // Actions
22
- setMetadata: (metadata: Metadata) => void;
23
- setTables: (tables: string[]) => void;
24
- setLoading: (loading: boolean) => void;
25
- setError: (error: string | null) => void;
26
- setSidebarCollapsed: (collapsed: boolean) => void;
27
- toggleSection: (section: string) => void;
28
- }
29
-
30
- export const useStudioStore = create<StudioState>()(
31
- persist(
32
- (set) => ({
33
- metadata: null,
34
- tables: [],
35
- isLoading: false,
36
- error: null,
37
- isSidebarCollapsed: false,
38
- expandedSections: {
39
- archeTypes: true,
40
- tables: true,
41
- },
42
-
43
- setMetadata: (metadata) => set({ metadata }),
44
- setTables: (tables) => set({ tables }),
45
- setLoading: (loading) => set({ isLoading: loading }),
46
- setError: (error) => set({ error }),
47
- setSidebarCollapsed: (collapsed) =>
48
- set({ isSidebarCollapsed: collapsed }),
49
- toggleSection: (section) =>
50
- set((state) => ({
51
- expandedSections: {
52
- ...state.expandedSections,
53
- [section]: !state.expandedSections[section],
54
- },
55
- })),
56
- }),
57
- {
58
- name: "bunsane-studio-storage",
59
- partialize: (state) => ({
60
- isSidebarCollapsed: state.isSidebarCollapsed,
61
- expandedSections: state.expandedSections,
62
- }),
63
- }
64
- )
65
- );
@@ -1,114 +0,0 @@
1
- import { type ColumnDef } from '@tanstack/react-table'
2
- import { Link } from 'react-router-dom'
3
- import ReactJson from 'react-json-view'
4
-
5
- /**
6
- * Creates the select checkbox column for data tables
7
- */
8
- export function createSelectColumn<T>(): ColumnDef<T> {
9
- return {
10
- id: 'select',
11
- header: ({ table }) => (
12
- <input
13
- type="checkbox"
14
- checked={table.getIsAllRowsSelected()}
15
- onChange={table.getToggleAllRowsSelectedHandler()}
16
- className="rounded border-border"
17
- />
18
- ),
19
- cell: ({ row }) => (
20
- <input
21
- type="checkbox"
22
- checked={row.getIsSelected()}
23
- onChange={row.getToggleSelectedHandler()}
24
- className="rounded border-border"
25
- />
26
- ),
27
- }
28
- }
29
-
30
- /**
31
- * Renders a cell value, displaying objects as ReactJson and primitives as text
32
- */
33
- export function renderCellValue(
34
- value: any,
35
- extractValue = false,
36
- autoExpandRow = false
37
- ): JSX.Element {
38
- // If extractValue is true and value has a .value property, extract it
39
- let actualValue =
40
- extractValue && value?.value !== undefined ? value.value : value;
41
-
42
- actualValue = actualValue ?? "-";
43
-
44
- if (typeof actualValue === "object" && actualValue !== null) {
45
- return (
46
- <div className="max-w-xs">
47
- <ReactJson
48
- src={actualValue}
49
- collapsed={autoExpandRow ? 2 : 1}
50
- enableClipboard
51
- displayDataTypes={false}
52
- displayObjectSize={false}
53
- name={null}
54
- />
55
- </div>
56
- );
57
- }
58
-
59
- return (
60
- <span className="truncate max-w-xs block">{String(actualValue)}</span>
61
- );
62
- }
63
-
64
- /**
65
- * Creates a standard text column with proper rendering
66
- */
67
- export function createTextColumn<T>(
68
- key: string,
69
- header: string,
70
- options: {
71
- extractValue?: boolean;
72
- className?: string;
73
- autoExpandRow?: boolean;
74
- } = {}
75
- ): ColumnDef<T> {
76
- return {
77
- accessorKey: key,
78
- header,
79
- cell: ({ getValue }) => {
80
- const value = getValue();
81
- return renderCellValue(
82
- value,
83
- options.extractValue,
84
- options.autoExpandRow
85
- );
86
- },
87
- };
88
- }
89
-
90
- /**
91
- * Creates an ID column with monospace font styling and link to Entity Inspector
92
- */
93
- export function createIdColumn<T>(options?: { linkToEntity?: boolean }): ColumnDef<T> {
94
- const linkToEntity = options?.linkToEntity ?? false
95
- return {
96
- accessorKey: 'id',
97
- header: 'ID',
98
- cell: ({ getValue }) => {
99
- const value = getValue() as string
100
- if (linkToEntity) {
101
- return (
102
- <Link
103
- to={`/entity/${value}`}
104
- className="font-mono text-xs text-primary hover:underline"
105
- title="Inspect entity"
106
- >
107
- {value}
108
- </Link>
109
- )
110
- }
111
- return <span className="font-mono text-xs">{value}</span>
112
- },
113
- }
114
- }
@@ -1,81 +0,0 @@
1
- ---
2
- applyTo: '**'
3
- ---
4
-
5
- # Bunsane studio
6
-
7
- ## About the project
8
-
9
- the project is to create PostgreSQL database management
10
- with support for ECS (Entity Component System) model
11
-
12
- there are some tables that we need to pay attention, i.e:
13
- - components (ECS table)
14
- - entities (ECS table)
15
- - entity_components (ECS table)
16
- - spatial_ref_sys (postgis table)
17
-
18
- the tables displays will be devided by 3 part, i.e general table, ECS table, invisible table
19
-
20
- ### General table
21
-
22
- user can do normal CRUD management to the table, just like normal database table.
23
-
24
- ### ECS table
25
-
26
- user can do CRUD, but not directly to the table. because the ECS table means to be used to work with ECS thing, it will contain multiple entities.
27
- Those entities is what user going to CRUD to.
28
- therefore lets use word table for general table, and entity for ECS table.
29
-
30
- ECS system design explanation:
31
- - Entity can be anything e.g user, payment, item, .etc (it can be called as table in traditional design system) it also can be called ArcheType
32
- - fields that usually used in normal system e.g email, phone (for user) or price (for item) .etc in ECS is a Component
33
- - there is 3 table dedicated for ECS i.e entities, components, and entity_components (intermediate table for entities and components)
34
- - entities table contain all entities that exist in the system. the table contain id, created_at, updated_at, and deleted_at
35
- - components table contain the all useful data that user consume, the table contain id, entity_id, type_id, name, data, created_at, updated_at, and deleted_at (data is jsonb that contain the actual useful data)
36
- - entity_components is intermediate table. the table contain, entity_id, type_id, component_id, created_at, updated_at, and deleted_at
37
-
38
- ## Feature
39
- - for now I only need Read feature, but in the future all CRUD should be supported so we can show the edit/delete ui but can disable it for now
40
- - General table and ECS table should be easily distinguished
41
- - user can create connection to initialize the database connection but there is default value that point to local postgres with default db
42
- - the connection detail is stored in localstorage after created
43
- - user can see the list of tables and entities in the sidebar
44
- - user can click the table or entity to see the data in the main content area
45
- - user can see the table column info (name, type, is_nullable, default value) for General table
46
- - the entity displayed as table, with the columns are id and component types (name from components table), the value for id is from entities.id, and the value for component types is from components.data (jsonb) field
47
- - the default 50 records can be displayed, then there will be load more button to load 50 more records
48
- - user can see the total row count of the table
49
-
50
- ## Tech stack
51
- - Bun for runtime and package manager
52
- - Vite + React router + @react-router/fs-routes + Tailwind + Shadcn
53
- - tanstack table
54
- - zustand for state management
55
- - sonner for snack/notification
56
- - lucide-react for icon
57
- - zod for schema validation
58
- - react-json-view for json data display
59
-
60
- ## File structure
61
- - use @react-router/fs-routes see https://reactrouter.com/how-to/file-route-conventions#basic-routes
62
- - components for ui components
63
- - store for zustand store
64
- - ensure its easy to manage and understand
65
-
66
- ## Api endpoint
67
- - can use /studio/api/tables to get all tables
68
-
69
- ## Code style
70
- - ensure typesafety with typescript and drizzle orm
71
- - ensure code is clean and easy to understand
72
- - ensure proper error handling
73
- - ensure proper separation of concerns
74
-
75
- ## App UI style
76
- - for now, only support light theme
77
- - use shadcn available components/template if available
78
- - the theme is orange-ish colour like fire in the sky
79
- - ensure the ui is clean and easy to use
80
- - ensure hover, and focus styles are properly implemented
81
- - only support desktop view for now (min width 1024px)