metal-orm 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/decorators/index.cjs +2564 -0
- package/dist/decorators/index.cjs.map +1 -0
- package/dist/decorators/index.d.cts +53 -0
- package/dist/decorators/index.d.ts +53 -0
- package/dist/decorators/index.js +2530 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +701 -0
- package/dist/index.d.ts +701 -0
- package/dist/index.js +4131 -0
- package/dist/index.js.map +1 -0
- package/dist/select-654m4qy8.d.cts +1522 -0
- package/dist/select-654m4qy8.d.ts +1522 -0
- package/package.json +27 -20
- package/src/codegen/typescript.ts +405 -393
- package/src/core/ast/aggregate-functions.ts +30 -0
- package/src/core/ast/builders.ts +43 -0
- package/src/core/ast/expression-builders.ts +310 -0
- package/src/core/ast/expression-nodes.ts +211 -0
- package/src/core/ast/expression-visitor.ts +99 -0
- package/src/core/ast/expression.ts +5 -0
- package/src/{utils → core/ast}/join-node.ts +20 -20
- package/src/{ast → core/ast}/join.ts +18 -18
- package/src/{ast → core/ast}/query.ts +113 -113
- package/src/core/ast/window-functions.ts +140 -0
- package/src/{dialect → core/dialect}/abstract.ts +94 -94
- package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
- package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
- package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
- package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
- package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
- package/src/decorators/bootstrap.ts +126 -0
- package/src/decorators/column.ts +78 -0
- package/src/decorators/entity.ts +36 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/relations.ts +107 -0
- package/src/global.d.ts +1 -0
- package/src/index.ts +22 -22
- package/src/orm/db-executor.ts +11 -0
- package/src/orm/domain-event-bus.ts +52 -0
- package/src/{runtime → orm}/entity-meta.ts +52 -52
- package/src/orm/entity-metadata.ts +140 -0
- package/src/{runtime → orm}/entity.ts +252 -252
- package/src/{runtime → orm}/execute.ts +36 -36
- package/src/{runtime → orm}/hydration.ts +103 -103
- package/src/orm/identity-map.ts +37 -0
- package/src/{runtime → orm}/lazy-batch.ts +205 -205
- package/src/orm/orm-context.ts +154 -0
- package/src/orm/relation-change-processor.ts +140 -0
- package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
- package/src/{runtime → orm}/relations/has-many.ts +111 -111
- package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
- package/src/orm/runtime-types.ts +39 -0
- package/src/orm/transaction-runner.ts +17 -0
- package/src/orm/unit-of-work.ts +232 -0
- package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
- package/src/{builder → query-builder}/delete-query-state.ts +38 -42
- package/src/{builder → query-builder}/delete.ts +46 -57
- package/src/{builder → query-builder}/hydration-manager.ts +87 -87
- package/src/{builder → query-builder}/hydration-planner.ts +182 -182
- package/src/{builder → query-builder}/insert-query-state.ts +51 -62
- package/src/{builder → query-builder}/insert.ts +48 -59
- package/src/{builder → query-builder}/query-ast-service.ts +208 -226
- package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
- package/src/{builder → query-builder}/relation-conditions.ts +112 -112
- package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
- package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
- package/src/{builder → query-builder}/relation-service.ts +284 -284
- package/src/{builder → query-builder}/relation-types.ts +21 -21
- package/src/{builder → query-builder}/relation-utils.ts +12 -12
- package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
- package/src/{builder → query-builder}/select-query-state.ts +179 -179
- package/src/{builder → query-builder}/select.ts +78 -69
- package/src/{builder → query-builder}/update-query-state.ts +55 -59
- package/src/{builder → query-builder}/update.ts +50 -61
- package/src/schema/column.ts +25 -25
- package/src/schema/relation.ts +116 -116
- package/src/schema/table.ts +34 -34
- package/src/schema/types.ts +76 -76
- package/.github/workflows/publish-metal-orm.yml +0 -38
- package/ROADMAP.md +0 -125
- package/docs/CHANGES.md +0 -104
- package/docs/advanced-features.md +0 -176
- package/docs/api-reference.md +0 -31
- package/docs/dml-operations.md +0 -156
- package/docs/getting-started.md +0 -171
- package/docs/hydration.md +0 -115
- package/docs/index.md +0 -36
- package/docs/multi-dialect-support.md +0 -59
- package/docs/query-builder.md +0 -135
- package/docs/runtime.md +0 -105
- package/docs/schema-definition.md +0 -112
- package/metadata.json +0 -5
- package/playground/api/playground-api.ts +0 -94
- package/playground/index.html +0 -15
- package/playground/src/App.css +0 -1
- package/playground/src/App.tsx +0 -114
- package/playground/src/components/CodeDisplay.tsx +0 -43
- package/playground/src/components/QueryExecutor.tsx +0 -189
- package/playground/src/components/ResultsTable.tsx +0 -67
- package/playground/src/components/ResultsTabs.tsx +0 -105
- package/playground/src/components/ScenarioList.tsx +0 -56
- package/playground/src/components/logo.svg +0 -45
- package/playground/src/data/scenarios.ts +0 -2
- package/playground/src/main.tsx +0 -9
- package/playground/src/services/PlaygroundApiService.ts +0 -60
- package/postcss.config.cjs +0 -5
- package/sql_sql-ansi-cheatsheet-2025.md +0 -264
- package/src/ast/expression.ts +0 -658
- package/src/builder/operations/cte-manager.ts +0 -34
- package/src/builder/operations/filter-manager.ts +0 -68
- package/src/builder/operations/join-manager.ts +0 -36
- package/src/builder/operations/pagination-manager.ts +0 -36
- package/src/playground/features/playground/api/types.ts +0 -16
- package/src/playground/features/playground/clients/MockClient.ts +0 -17
- package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
- package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
- package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
- package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
- package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
- package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
- package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
- package/src/playground/features/playground/data/scenarios/index.ts +0 -29
- package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
- package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
- package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
- package/src/playground/features/playground/data/scenarios/types.ts +0 -70
- package/src/playground/features/playground/data/schema.ts +0 -91
- package/src/playground/features/playground/data/seed.ts +0 -104
- package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
- package/src/runtime/orm-context.ts +0 -539
- package/tests/belongs-to-many.test.ts +0 -57
- package/tests/between.test.ts +0 -43
- package/tests/case-expression.test.ts +0 -58
- package/tests/complex-exists.test.ts +0 -230
- package/tests/cte.test.ts +0 -118
- package/tests/dml.test.ts +0 -206
- package/tests/exists.test.ts +0 -127
- package/tests/like.test.ts +0 -33
- package/tests/orm-runtime.test.ts +0 -254
- package/tests/postgres.test.ts +0 -30
- package/tests/right-join.test.ts +0 -89
- package/tests/subquery-having.test.ts +0 -193
- package/tests/window-function.test.ts +0 -151
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -10
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -14
- /package/src/{constants → core/sql}/sql.ts +0 -0
- /package/src/{runtime → orm}/als.ts +0 -0
- /package/src/{utils → query-builder}/relation-alias.ts +0 -0
package/playground/src/App.tsx
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import { MantineProvider, AppShell, Burger, Group, Title, Text, ActionIcon, useMantineTheme } from '@mantine/core';
|
|
3
|
-
import { useDisclosure } from '@mantine/hooks';
|
|
4
|
-
import { SCENARIOS, type Scenario } from './data/scenarios';
|
|
5
|
-
import { PlaygroundApiService } from './services/PlaygroundApiService';
|
|
6
|
-
import { ScenarioList } from './components/ScenarioList';
|
|
7
|
-
import { QueryExecutor } from './components/QueryExecutor';
|
|
8
|
-
import '@mantine/core/styles.css';
|
|
9
|
-
import './App.css'; // Keeping for custom overrides if needed, but will likely remove
|
|
10
|
-
|
|
11
|
-
function App() {
|
|
12
|
-
const [selectedScenario, setSelectedScenario] = useState<Scenario | null>(null);
|
|
13
|
-
const [apiReady, setApiReady] = useState(false);
|
|
14
|
-
const [statusMessage, setStatusMessage] = useState<string | null>(null);
|
|
15
|
-
const [opened, { toggle }] = useDisclosure();
|
|
16
|
-
|
|
17
|
-
const queryService = useMemo(() => new PlaygroundApiService(), []);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
let isMounted = true;
|
|
21
|
-
let timerId: ReturnType<typeof setTimeout> | null = null;
|
|
22
|
-
|
|
23
|
-
const pollStatus = async () => {
|
|
24
|
-
const status = await queryService.getStatus();
|
|
25
|
-
if (!isMounted) return;
|
|
26
|
-
|
|
27
|
-
if (status.error) {
|
|
28
|
-
setStatusMessage(status.error);
|
|
29
|
-
} else {
|
|
30
|
-
setStatusMessage(null);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (status.ready) {
|
|
34
|
-
setApiReady(true);
|
|
35
|
-
timerId && clearTimeout(timerId);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
timerId = setTimeout(pollStatus, 250);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
pollStatus();
|
|
43
|
-
|
|
44
|
-
return () => {
|
|
45
|
-
isMounted = false;
|
|
46
|
-
if (timerId) {
|
|
47
|
-
clearTimeout(timerId);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}, [queryService]);
|
|
51
|
-
|
|
52
|
-
// Auto-select first scenario when API is ready
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (apiReady && !selectedScenario && SCENARIOS.length > 0) {
|
|
55
|
-
setSelectedScenario(SCENARIOS[0]);
|
|
56
|
-
}
|
|
57
|
-
}, [apiReady, selectedScenario]);
|
|
58
|
-
|
|
59
|
-
const handleScenarioSelect = (scenario: Scenario) => {
|
|
60
|
-
setSelectedScenario(scenario);
|
|
61
|
-
if (window.innerWidth < 768) {
|
|
62
|
-
toggle(); // Close sidebar on mobile selection
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
if (!apiReady) {
|
|
67
|
-
return (
|
|
68
|
-
<MantineProvider defaultColorScheme="dark">
|
|
69
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh', flexDirection: 'column' }}>
|
|
70
|
-
<Title order={2}>Initializing Metal ORM Playground</Title>
|
|
71
|
-
<Text c="dimmed">Waiting for the playground API to become ready...</Text>
|
|
72
|
-
{statusMessage && <Text c="red">Error: {statusMessage}</Text>}
|
|
73
|
-
</div>
|
|
74
|
-
</MantineProvider>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<MantineProvider defaultColorScheme="dark">
|
|
80
|
-
<AppShell
|
|
81
|
-
header={{ height: 60 }}
|
|
82
|
-
navbar={{ width: 300, breakpoint: 'sm', collapsed: { mobile: !opened } }}
|
|
83
|
-
padding="md"
|
|
84
|
-
>
|
|
85
|
-
<AppShell.Header>
|
|
86
|
-
<Group h="100%" px="md">
|
|
87
|
-
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
|
|
88
|
-
<Group justify="space-between" style={{ flex: 1 }}>
|
|
89
|
-
<Title order={3}>⚡ Metal ORM Playground</Title>
|
|
90
|
-
<Text size="sm" c="dimmed" visibleFrom="sm">Explore and test ORM query scenarios</Text>
|
|
91
|
-
</Group>
|
|
92
|
-
</Group>
|
|
93
|
-
</AppShell.Header>
|
|
94
|
-
|
|
95
|
-
<AppShell.Navbar p="md">
|
|
96
|
-
<ScenarioList
|
|
97
|
-
scenarios={SCENARIOS}
|
|
98
|
-
selectedId={selectedScenario?.id || null}
|
|
99
|
-
onSelect={handleScenarioSelect}
|
|
100
|
-
/>
|
|
101
|
-
</AppShell.Navbar>
|
|
102
|
-
|
|
103
|
-
<AppShell.Main>
|
|
104
|
-
<QueryExecutor
|
|
105
|
-
scenario={selectedScenario}
|
|
106
|
-
queryService={queryService}
|
|
107
|
-
/>
|
|
108
|
-
</AppShell.Main>
|
|
109
|
-
</AppShell>
|
|
110
|
-
</MantineProvider>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export default App;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
3
|
-
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
4
|
-
import { ScrollArea } from '@mantine/core';
|
|
5
|
-
|
|
6
|
-
interface CodeDisplayProps {
|
|
7
|
-
code: string;
|
|
8
|
-
language?: 'sql' | 'typescript';
|
|
9
|
-
title?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Component responsible for displaying syntax-highlighted code
|
|
14
|
-
* Follows SRP by handling only code display formatting
|
|
15
|
-
*/
|
|
16
|
-
export const CodeDisplay: React.FC<CodeDisplayProps> = ({
|
|
17
|
-
code,
|
|
18
|
-
language = 'sql',
|
|
19
|
-
title
|
|
20
|
-
}) => {
|
|
21
|
-
return (
|
|
22
|
-
<ScrollArea bg="#282c34">
|
|
23
|
-
<SyntaxHighlighter
|
|
24
|
-
language={language}
|
|
25
|
-
style={oneDark}
|
|
26
|
-
customStyle={{
|
|
27
|
-
margin: 0,
|
|
28
|
-
padding: '1.25rem',
|
|
29
|
-
background: 'transparent',
|
|
30
|
-
fontSize: '0.9rem',
|
|
31
|
-
lineHeight: '1.6'
|
|
32
|
-
}}
|
|
33
|
-
codeTagProps={{
|
|
34
|
-
style: {
|
|
35
|
-
fontFamily: 'JetBrains Mono, Fira Code, monospace'
|
|
36
|
-
}
|
|
37
|
-
}}
|
|
38
|
-
>
|
|
39
|
-
{code}
|
|
40
|
-
</SyntaxHighlighter>
|
|
41
|
-
</ScrollArea>
|
|
42
|
-
);
|
|
43
|
-
};
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Card, Button, Title, Text, Tabs, Loader, Group, Stack, Badge, ActionIcon, CopyButton, Tooltip } from '@mantine/core';
|
|
3
|
-
import { IconPlayerPlay, IconCode, IconDatabase, IconCheck, IconCopy } from '@tabler/icons-react'; // Assuming tabler icons are available or we use text
|
|
4
|
-
import type { Scenario } from '../data/scenarios';
|
|
5
|
-
import type { QueryExecutionResult } from '@orm/playground/features/playground/api/types';
|
|
6
|
-
import { CodeDisplay } from './CodeDisplay';
|
|
7
|
-
import { ResultsTabs } from './ResultsTabs';
|
|
8
|
-
import { PlaygroundApiService } from '../services/PlaygroundApiService';
|
|
9
|
-
|
|
10
|
-
const describeBinding = (value: unknown): { display: string; type: string } => {
|
|
11
|
-
if (value === null) {
|
|
12
|
-
return { display: 'null', type: 'null' };
|
|
13
|
-
}
|
|
14
|
-
if (typeof value === 'undefined') {
|
|
15
|
-
return { display: 'undefined', type: 'undefined' };
|
|
16
|
-
}
|
|
17
|
-
if (typeof value === 'string') {
|
|
18
|
-
return { display: `"${value}"`, type: 'string' };
|
|
19
|
-
}
|
|
20
|
-
if (typeof value === 'number') {
|
|
21
|
-
return { display: value.toString(), type: Number.isInteger(value) ? 'integer' : 'number' };
|
|
22
|
-
}
|
|
23
|
-
if (typeof value === 'boolean') {
|
|
24
|
-
return { display: value ? 'true' : 'false', type: 'boolean' };
|
|
25
|
-
}
|
|
26
|
-
if (Array.isArray(value)) {
|
|
27
|
-
return { display: JSON.stringify(value), type: 'array' };
|
|
28
|
-
}
|
|
29
|
-
return { display: JSON.stringify(value), type: typeof value };
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const BindingsDisplay: React.FC<{ params: unknown[] }> = ({ params }) => {
|
|
33
|
-
const hasParams = Array.isArray(params) && params.length > 0;
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
style={{
|
|
38
|
-
border: '1px solid var(--mantine-color-dark-5)',
|
|
39
|
-
borderRadius: 'var(--mantine-radius-md)',
|
|
40
|
-
padding: 'var(--mantine-spacing-md)',
|
|
41
|
-
background: 'var(--mantine-color-dark-8)'
|
|
42
|
-
}}
|
|
43
|
-
>
|
|
44
|
-
<Group justify="space-between" mb="sm">
|
|
45
|
-
<Text fw={500}>Bindings</Text>
|
|
46
|
-
{hasParams && (
|
|
47
|
-
<CopyButton value={JSON.stringify(params, null, 2)}>
|
|
48
|
-
{({ copied, copy }) => (
|
|
49
|
-
<Tooltip label={copied ? 'Copied' : 'Copy JSON'} withArrow>
|
|
50
|
-
<ActionIcon variant="subtle" color={copied ? 'teal' : 'blue'} onClick={copy}>
|
|
51
|
-
{copied ? <IconCheck size={14} /> : <IconCopy size={14} />}
|
|
52
|
-
</ActionIcon>
|
|
53
|
-
</Tooltip>
|
|
54
|
-
)}
|
|
55
|
-
</CopyButton>
|
|
56
|
-
)}
|
|
57
|
-
</Group>
|
|
58
|
-
{!hasParams && <Text c="dimmed" fz="sm">Query has no parameter bindings.</Text>}
|
|
59
|
-
{hasParams && (
|
|
60
|
-
<Stack gap="xs">
|
|
61
|
-
{params.map((value, index) => {
|
|
62
|
-
const { display, type } = describeBinding(value);
|
|
63
|
-
return (
|
|
64
|
-
<Group
|
|
65
|
-
key={`${index}-${String(value)}`}
|
|
66
|
-
justify="space-between"
|
|
67
|
-
style={{
|
|
68
|
-
border: '1px solid var(--mantine-color-dark-5)',
|
|
69
|
-
borderRadius: 'var(--mantine-radius-sm)',
|
|
70
|
-
padding: 'var(--mantine-spacing-xs)'
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
73
|
-
<Badge variant="light" color="grape">#{index + 1}</Badge>
|
|
74
|
-
<div style={{ textAlign: 'right' }}>
|
|
75
|
-
<Text style={{ fontFamily: 'var(--mantine-font-family-monospace)' }}>
|
|
76
|
-
{display}
|
|
77
|
-
</Text>
|
|
78
|
-
<Text fz="xs" c="dimmed">
|
|
79
|
-
{type.toUpperCase()}
|
|
80
|
-
</Text>
|
|
81
|
-
</div>
|
|
82
|
-
</Group>
|
|
83
|
-
);
|
|
84
|
-
})}
|
|
85
|
-
</Stack>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
interface QueryExecutorProps {
|
|
92
|
-
scenario: Scenario | null;
|
|
93
|
-
queryService: PlaygroundApiService;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Component responsible for executing queries and displaying results
|
|
98
|
-
* Follows SRP by coordinating query execution and result display
|
|
99
|
-
*/
|
|
100
|
-
export const QueryExecutor: React.FC<QueryExecutorProps> = ({
|
|
101
|
-
scenario,
|
|
102
|
-
queryService
|
|
103
|
-
}) => {
|
|
104
|
-
const [result, setResult] = useState<QueryExecutionResult | null>(null);
|
|
105
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
106
|
-
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
if (scenario) {
|
|
109
|
-
executeQuery();
|
|
110
|
-
}
|
|
111
|
-
}, [scenario]);
|
|
112
|
-
|
|
113
|
-
const executeQuery = async () => {
|
|
114
|
-
if (!scenario) return;
|
|
115
|
-
|
|
116
|
-
setIsLoading(true);
|
|
117
|
-
const executionResult = await queryService.executeScenario(scenario.id);
|
|
118
|
-
setResult(executionResult);
|
|
119
|
-
setIsLoading(false);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
if (!scenario) {
|
|
123
|
-
return (
|
|
124
|
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: 'var(--mantine-color-dimmed)' }}>
|
|
125
|
-
<Text>Select a scenario from the list to execute</Text>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<Stack gap="lg">
|
|
132
|
-
<div>
|
|
133
|
-
<Group justify="space-between" align="start" mb="md">
|
|
134
|
-
<div>
|
|
135
|
-
<Title order={2}>{scenario.title}</Title>
|
|
136
|
-
<Text c="dimmed" mt="xs">{scenario.description}</Text>
|
|
137
|
-
</div>
|
|
138
|
-
<Button
|
|
139
|
-
onClick={executeQuery}
|
|
140
|
-
loading={isLoading}
|
|
141
|
-
leftSection={<span>▶</span>}
|
|
142
|
-
variant="gradient"
|
|
143
|
-
gradient={{ from: 'indigo', to: 'cyan' }}
|
|
144
|
-
>
|
|
145
|
-
Re-execute Query
|
|
146
|
-
</Button>
|
|
147
|
-
</Group>
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
{result && (
|
|
151
|
-
<>
|
|
152
|
-
<Card withBorder shadow="sm" radius="md" p={0}>
|
|
153
|
-
<Tabs defaultValue="sql">
|
|
154
|
-
<Tabs.List>
|
|
155
|
-
<Tabs.Tab value="sql" leftSection={<span>SQL</span>}>Generated SQL</Tabs.Tab>
|
|
156
|
-
<Tabs.Tab value="typescript" leftSection={<span>TS</span>}>TypeScript</Tabs.Tab>
|
|
157
|
-
</Tabs.List>
|
|
158
|
-
|
|
159
|
-
<Tabs.Panel value="sql">
|
|
160
|
-
<Stack gap="sm" p="md">
|
|
161
|
-
<CodeDisplay code={result.sql} language="sql" />
|
|
162
|
-
<BindingsDisplay params={result.params} />
|
|
163
|
-
</Stack>
|
|
164
|
-
</Tabs.Panel>
|
|
165
|
-
|
|
166
|
-
<Tabs.Panel value="typescript">
|
|
167
|
-
<CodeDisplay code={result.typescriptCode} language="typescript" />
|
|
168
|
-
</Tabs.Panel>
|
|
169
|
-
</Tabs>
|
|
170
|
-
</Card>
|
|
171
|
-
|
|
172
|
-
<ResultsTabs
|
|
173
|
-
results={result.results}
|
|
174
|
-
hydratedResults={result.hydratedResults}
|
|
175
|
-
executionTime={result.executionTime}
|
|
176
|
-
error={result.error}
|
|
177
|
-
/>
|
|
178
|
-
</>
|
|
179
|
-
)}
|
|
180
|
-
|
|
181
|
-
{isLoading && (
|
|
182
|
-
<Group justify="center" p="xl">
|
|
183
|
-
<Loader type="dots" />
|
|
184
|
-
<Text>Executing query...</Text>
|
|
185
|
-
</Group>
|
|
186
|
-
)}
|
|
187
|
-
</Stack>
|
|
188
|
-
);
|
|
189
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Table, Text, Alert, ScrollArea } from '@mantine/core';
|
|
3
|
-
import type { QueryResult } from '@orm/playground/features/playground/common/IDatabaseClient';
|
|
4
|
-
|
|
5
|
-
interface ResultsTableProps {
|
|
6
|
-
results: QueryResult[];
|
|
7
|
-
executionTime?: number;
|
|
8
|
-
error?: string | null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Component responsible for displaying query results in a table
|
|
13
|
-
* Follows SRP by handling only results visualization
|
|
14
|
-
*/
|
|
15
|
-
export const ResultsTable: React.FC<ResultsTableProps> = ({
|
|
16
|
-
results,
|
|
17
|
-
executionTime,
|
|
18
|
-
error
|
|
19
|
-
}) => {
|
|
20
|
-
if (error) {
|
|
21
|
-
return (
|
|
22
|
-
<Alert variant="light" color="red" title="Error">
|
|
23
|
-
{error}
|
|
24
|
-
</Alert>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (results.length === 0 || !results[0] || results[0].values.length === 0) {
|
|
29
|
-
return (
|
|
30
|
-
<div style={{ padding: '2rem', textAlign: 'center', color: 'var(--mantine-color-dimmed)' }}>
|
|
31
|
-
<Text>No results returned</Text>
|
|
32
|
-
</div>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const { columns, values } = results[0];
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div>
|
|
40
|
-
<ScrollArea>
|
|
41
|
-
<Table striped highlightOnHover horizontalSpacing="md" verticalSpacing="sm">
|
|
42
|
-
<Table.Thead>
|
|
43
|
-
<Table.Tr>
|
|
44
|
-
{columns.map((col, idx) => (
|
|
45
|
-
<Table.Th key={idx} style={{ whiteSpace: 'nowrap' }}>{col}</Table.Th>
|
|
46
|
-
))}
|
|
47
|
-
</Table.Tr>
|
|
48
|
-
</Table.Thead>
|
|
49
|
-
<Table.Tbody>
|
|
50
|
-
{values.map((row, rowIdx) => (
|
|
51
|
-
<Table.Tr key={rowIdx}>
|
|
52
|
-
{row.map((cell, cellIdx) => (
|
|
53
|
-
<Table.Td key={cellIdx}>
|
|
54
|
-
{cell === null ? <Text span c="dimmed" fs="italic">NULL</Text> : String(cell)}
|
|
55
|
-
</Table.Td>
|
|
56
|
-
))}
|
|
57
|
-
</Table.Tr>
|
|
58
|
-
))}
|
|
59
|
-
</Table.Tbody>
|
|
60
|
-
</Table>
|
|
61
|
-
</ScrollArea>
|
|
62
|
-
<div style={{ padding: '0.5rem 1rem', borderTop: '1px solid var(--mantine-color-default-border)', backgroundColor: 'var(--mantine-color-body)' }}>
|
|
63
|
-
<Text size="xs" c="dimmed">{values.length} row{values.length !== 1 ? 's' : ''}</Text>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Tabs, Card, Text, Badge, Group, Alert, Code, ScrollArea } from '@mantine/core';
|
|
3
|
-
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
4
|
-
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
5
|
-
import type { QueryResult } from '@orm/playground/features/playground/common/IDatabaseClient';
|
|
6
|
-
import { ResultsTable } from './ResultsTable';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Converts tabular results to JSON array of objects
|
|
10
|
-
*/
|
|
11
|
-
const convertTabularToJson = (results: QueryResult[]): Record<string, any>[] => {
|
|
12
|
-
if (!results.length || !results[0].values.length) return [];
|
|
13
|
-
|
|
14
|
-
const { columns, values } = results[0];
|
|
15
|
-
return values.map(row => {
|
|
16
|
-
const obj: Record<string, any> = {};
|
|
17
|
-
columns.forEach((col, idx) => {
|
|
18
|
-
obj[col] = row[idx];
|
|
19
|
-
});
|
|
20
|
-
return obj;
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
interface ResultsTabsProps {
|
|
25
|
-
results: QueryResult[];
|
|
26
|
-
hydratedResults?: Record<string, any>[];
|
|
27
|
-
executionTime?: number;
|
|
28
|
-
error?: string | null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Component that displays query results in tabbed interface with Table and JSON views
|
|
33
|
-
* Follows SRP by coordinating result display modes
|
|
34
|
-
*/
|
|
35
|
-
export const ResultsTabs: React.FC<ResultsTabsProps> = ({
|
|
36
|
-
results,
|
|
37
|
-
hydratedResults,
|
|
38
|
-
executionTime,
|
|
39
|
-
error
|
|
40
|
-
}) => {
|
|
41
|
-
const hasResults = results.length > 0 && results[0].values.length > 0;
|
|
42
|
-
const hasHydratedResults = hydratedResults && hydratedResults.length > 0;
|
|
43
|
-
|
|
44
|
-
// Use hydrated results if available, otherwise convert tabular data to JSON
|
|
45
|
-
const jsonData = hasHydratedResults ? hydratedResults : convertTabularToJson(results);
|
|
46
|
-
const jsonTitle = hasHydratedResults ? 'Hydrated Results' : 'Results as JSON';
|
|
47
|
-
|
|
48
|
-
if (error) {
|
|
49
|
-
return (
|
|
50
|
-
<Alert variant="light" color="red" title="Error" mt="md">
|
|
51
|
-
{error}
|
|
52
|
-
</Alert>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<Card withBorder shadow="sm" radius="md" p={0}>
|
|
58
|
-
<Tabs defaultValue="table">
|
|
59
|
-
<Group justify="space-between" px="md" py="xs" style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}>
|
|
60
|
-
<Tabs.List style={{ borderBottom: 'none' }}>
|
|
61
|
-
<Tabs.Tab value="table">Table</Tabs.Tab>
|
|
62
|
-
{hasResults && <Tabs.Tab value="json">JSON</Tabs.Tab>}
|
|
63
|
-
</Tabs.List>
|
|
64
|
-
{executionTime !== undefined && (
|
|
65
|
-
<Badge variant="light" color="green" size="lg">
|
|
66
|
-
{executionTime.toFixed(2)}ms
|
|
67
|
-
</Badge>
|
|
68
|
-
)}
|
|
69
|
-
</Group>
|
|
70
|
-
|
|
71
|
-
<Tabs.Panel value="table">
|
|
72
|
-
<ResultsTable
|
|
73
|
-
results={results}
|
|
74
|
-
executionTime={undefined}
|
|
75
|
-
error={null}
|
|
76
|
-
/>
|
|
77
|
-
</Tabs.Panel>
|
|
78
|
-
|
|
79
|
-
{hasResults && (
|
|
80
|
-
<Tabs.Panel value="json">
|
|
81
|
-
<Group justify="space-between" px="md" py="xs" bg="var(--mantine-color-dark-6)">
|
|
82
|
-
<Text size="sm" fw={500}>{jsonTitle}</Text>
|
|
83
|
-
<Text size="xs" c="dimmed">{jsonData.length} object{jsonData.length !== 1 ? 's' : ''}</Text>
|
|
84
|
-
</Group>
|
|
85
|
-
<ScrollArea h={400} bg="#282c34">
|
|
86
|
-
<SyntaxHighlighter
|
|
87
|
-
language="json"
|
|
88
|
-
style={oneDark}
|
|
89
|
-
customStyle={{
|
|
90
|
-
margin: 0,
|
|
91
|
-
padding: '1.25rem',
|
|
92
|
-
background: 'transparent',
|
|
93
|
-
fontSize: '0.85rem',
|
|
94
|
-
lineHeight: '1.5'
|
|
95
|
-
}}
|
|
96
|
-
>
|
|
97
|
-
{JSON.stringify(jsonData, null, 2)}
|
|
98
|
-
</SyntaxHighlighter>
|
|
99
|
-
</ScrollArea>
|
|
100
|
-
</Tabs.Panel>
|
|
101
|
-
)}
|
|
102
|
-
</Tabs>
|
|
103
|
-
</Card>
|
|
104
|
-
);
|
|
105
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { NavLink, Stack, Text, ScrollArea } from '@mantine/core';
|
|
3
|
-
import type { Scenario } from '../data/scenarios';
|
|
4
|
-
|
|
5
|
-
interface ScenarioListProps {
|
|
6
|
-
scenarios: Scenario[];
|
|
7
|
-
selectedId: string | null;
|
|
8
|
-
onSelect: (scenario: Scenario) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Component responsible for displaying the list of scenarios
|
|
13
|
-
* Follows SRP by handling only scenario list rendering and selection
|
|
14
|
-
*/
|
|
15
|
-
export const ScenarioList: React.FC<ScenarioListProps> = ({
|
|
16
|
-
scenarios,
|
|
17
|
-
selectedId,
|
|
18
|
-
onSelect
|
|
19
|
-
}) => {
|
|
20
|
-
// Group scenarios by category
|
|
21
|
-
const categorizedScenarios = scenarios.reduce((acc, scenario) => {
|
|
22
|
-
if (!acc[scenario.category]) {
|
|
23
|
-
acc[scenario.category] = [];
|
|
24
|
-
}
|
|
25
|
-
acc[scenario.category].push(scenario);
|
|
26
|
-
return acc;
|
|
27
|
-
}, {} as Record<string, Scenario[]>);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<ScrollArea h="calc(100vh - 80px)">
|
|
31
|
-
<Stack gap="md">
|
|
32
|
-
{Object.entries(categorizedScenarios).map(([category, items]) => (
|
|
33
|
-
<div key={category}>
|
|
34
|
-
<Text size="xs" fw={700} c="dimmed" tt="uppercase" mb="xs" px="xs">
|
|
35
|
-
{category}
|
|
36
|
-
</Text>
|
|
37
|
-
<Stack gap={2}>
|
|
38
|
-
{items.map((scenario) => (
|
|
39
|
-
<NavLink
|
|
40
|
-
key={scenario.id}
|
|
41
|
-
label={scenario.title}
|
|
42
|
-
description={scenario.description}
|
|
43
|
-
active={selectedId === scenario.id}
|
|
44
|
-
onClick={() => onSelect(scenario)}
|
|
45
|
-
variant="light"
|
|
46
|
-
color="indigo"
|
|
47
|
-
style={{ borderRadius: 'var(--mantine-radius-md)' }}
|
|
48
|
-
/>
|
|
49
|
-
))}
|
|
50
|
-
</Stack>
|
|
51
|
-
</div>
|
|
52
|
-
))}
|
|
53
|
-
</Stack>
|
|
54
|
-
</ScrollArea>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="metalGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" style="stop-color:#e0e0e0;stop-opacity:1" />
|
|
5
|
-
<stop offset="50%" style="stop-color:#909090;stop-opacity:1" />
|
|
6
|
-
<stop offset="100%" style="stop-color:#404040;stop-opacity:1" />
|
|
7
|
-
</linearGradient>
|
|
8
|
-
|
|
9
|
-
<linearGradient id="speedGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
10
|
-
<stop offset="0%" style="stop-color:#ff8c00;stop-opacity:1" />
|
|
11
|
-
<stop offset="100%" style="stop-color:#ffeb3b;stop-opacity:1" />
|
|
12
|
-
</linearGradient>
|
|
13
|
-
|
|
14
|
-
<filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
15
|
-
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
|
|
16
|
-
<feOffset dx="2" dy="2" result="offsetblur"/>
|
|
17
|
-
<feComponentTransfer>
|
|
18
|
-
<feFuncA type="linear" slope="0.5"/>
|
|
19
|
-
</feComponentTransfer>
|
|
20
|
-
<feMerge>
|
|
21
|
-
<feMergeNode in="offsetblur"/>
|
|
22
|
-
<feMergeNode in="SourceGraphic"/>
|
|
23
|
-
</feMerge>
|
|
24
|
-
</filter>
|
|
25
|
-
</defs>
|
|
26
|
-
|
|
27
|
-
<path d="M100,20 L130,10 L145,35 L175,35 L180,65 L200,80 L185,105 L200,130 L180,145 L175,175 L145,175 L130,200 L100,190 L70,200 L55,175 L25,175 L20,145 L0,130 L15,105 L0,80 L20,65 L25,35 L55,35 L70,10 Z"
|
|
28
|
-
fill="url(#metalGrad)"
|
|
29
|
-
stroke="#333"
|
|
30
|
-
stroke-width="2"
|
|
31
|
-
filter="url(#dropShadow)" />
|
|
32
|
-
|
|
33
|
-
<circle cx="100" cy="105" r="55" fill="#222" stroke="#555" stroke-width="2" />
|
|
34
|
-
|
|
35
|
-
<ellipse cx="100" cy="85" rx="35" ry="10" fill="none" stroke="#ccc" stroke-width="3" />
|
|
36
|
-
<path d="M65,85 v30 a35,10 0 0,0 70,0 v-30" fill="none" stroke="#ccc" stroke-width="3" />
|
|
37
|
-
<path d="M65,100 v15 a35,10 0 0,0 70,0 v-15" fill="none" stroke="#ccc" stroke-width="3" opacity="0.5" />
|
|
38
|
-
|
|
39
|
-
<path d="M120,55 L85,105 L105,105 L80,155 L135,95 L110,95 Z"
|
|
40
|
-
fill="url(#speedGrad)"
|
|
41
|
-
stroke="#fff"
|
|
42
|
-
stroke-width="1"
|
|
43
|
-
transform="translate(-5, 0)" />
|
|
44
|
-
|
|
45
|
-
</svg>
|