bunsane 0.3.2 → 0.4.0

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