bunsane 0.1.4 → 0.2.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 (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. package/tests/scheduler.test.ts +0 -724
@@ -0,0 +1,94 @@
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
+ }
@@ -0,0 +1,241 @@
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
+ }
@@ -0,0 +1,45 @@
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
+ ]
@@ -0,0 +1,30 @@
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
+ )
@@ -0,0 +1,65 @@
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
+ );
@@ -0,0 +1,114 @@
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
+ }
@@ -0,0 +1,81 @@
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)