@idealyst/cli 1.2.31 → 1.2.33
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/dist/constants.js +5 -1
- package/dist/constants.js.map +1 -1
- package/dist/generators/core/shared.js +382 -4
- package/dist/generators/core/shared.js.map +1 -1
- package/dist/generators/extensions/graphql.js +112 -26
- package/dist/generators/extensions/graphql.js.map +1 -1
- package/dist/generators/extensions/prisma.js +105 -16
- package/dist/generators/extensions/prisma.js.map +1 -1
- package/dist/generators/extensions/trpc.js +137 -41
- package/dist/generators/extensions/trpc.js.map +1 -1
- package/dist/templates/core/api/src-graphql/builder.ts +25 -0
- package/dist/templates/core/api/src-graphql/schema.ts +217 -0
- package/dist/templates/core/api/src-trpc/context.ts +15 -0
- package/dist/templates/core/api/src-trpc/index.ts +6 -0
- package/dist/templates/core/api/src-trpc/lib/database.ts +4 -0
- package/dist/templates/core/api/src-trpc/router/index.ts +187 -0
- package/dist/templates/core/api/src-trpc/server.ts +63 -0
- package/dist/templates/core/api/src-trpc/trpc.ts +20 -0
- package/dist/templates/core/database/schema.prisma +22 -0
- package/dist/templates/core/shared/src/index.ts +1 -1
- package/dist/templates/core/shared/src-components/App.tsx +51 -0
- package/dist/templates/core/shared/src-graphql/client.ts +70 -0
- package/dist/templates/core/shared/{src/navigation → src-navigation}/AppRouter.tsx +34 -0
- package/dist/templates/core/shared/src-screens-graphql/GraphQLDemoScreen.tsx +354 -0
- package/dist/templates/core/shared/src-screens-trpc/TRPCDemoScreen.tsx +432 -0
- package/dist/templates/core/shared/src-trpc/client.ts +44 -0
- package/dist/types/constants.d.ts +9 -5
- package/package.json +1 -1
- package/dist/templates/core/shared/src/components/App.tsx +0 -13
- package/dist/templates/core/shared/src/screens/index.ts +0 -4
- /package/dist/templates/core/shared/src/{styles.ts → theme.ts} +0 -0
- /package/dist/templates/core/shared/{src/components → src-components}/index.ts +0 -0
- /package/dist/templates/core/shared/{src/layouts → src-layouts}/AppLayout.tsx +0 -0
- /package/dist/templates/core/shared/{src/navigation → src-navigation}/index.ts +0 -0
- /package/dist/templates/core/shared/{src/screens → src-screens}/ExploreScreen.tsx +0 -0
- /package/dist/templates/core/shared/{src/screens → src-screens}/HomeScreen.tsx +0 -0
- /package/dist/templates/core/shared/{src/screens → src-screens}/ProfileScreen.tsx +0 -0
- /package/dist/templates/core/shared/{src/screens → src-screens}/SettingsScreen.tsx +0 -0
|
@@ -7,6 +7,8 @@ import { HomeScreen } from '../screens/HomeScreen';
|
|
|
7
7
|
import { ExploreScreen } from '../screens/ExploreScreen';
|
|
8
8
|
import { ProfileScreen } from '../screens/ProfileScreen';
|
|
9
9
|
import { SettingsScreen } from '../screens/SettingsScreen';
|
|
10
|
+
import { TRPCDemoScreen } from '../screens/TRPCDemoScreen';
|
|
11
|
+
import { GraphQLDemoScreen } from '../screens/GraphQLDemoScreen';
|
|
10
12
|
|
|
11
13
|
// Custom Layouts
|
|
12
14
|
import { AppTabLayout, AppStackLayout } from '../layouts/AppLayout';
|
|
@@ -68,6 +70,38 @@ const MainTabNavigator: NavigatorParam = {
|
|
|
68
70
|
),
|
|
69
71
|
} as TabBarScreenOptions,
|
|
70
72
|
},
|
|
73
|
+
{
|
|
74
|
+
path: 'trpc',
|
|
75
|
+
type: 'screen',
|
|
76
|
+
component: TRPCDemoScreen,
|
|
77
|
+
options: {
|
|
78
|
+
title: 'tRPC',
|
|
79
|
+
tabBarLabel: 'tRPC',
|
|
80
|
+
tabBarIcon: ({ focused, color, size }) => (
|
|
81
|
+
<Icon
|
|
82
|
+
name={focused ? 'api' : 'api'}
|
|
83
|
+
color={color}
|
|
84
|
+
size={size}
|
|
85
|
+
/>
|
|
86
|
+
),
|
|
87
|
+
} as TabBarScreenOptions,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
path: 'graphql',
|
|
91
|
+
type: 'screen',
|
|
92
|
+
component: GraphQLDemoScreen,
|
|
93
|
+
options: {
|
|
94
|
+
title: 'GraphQL',
|
|
95
|
+
tabBarLabel: 'GraphQL',
|
|
96
|
+
tabBarIcon: ({ focused, color, size }) => (
|
|
97
|
+
<Icon
|
|
98
|
+
name={focused ? 'graphql' : 'graphql'}
|
|
99
|
+
color={color}
|
|
100
|
+
size={size}
|
|
101
|
+
/>
|
|
102
|
+
),
|
|
103
|
+
} as TabBarScreenOptions,
|
|
104
|
+
},
|
|
71
105
|
],
|
|
72
106
|
};
|
|
73
107
|
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Screen,
|
|
4
|
+
View,
|
|
5
|
+
Text,
|
|
6
|
+
Card,
|
|
7
|
+
Button,
|
|
8
|
+
Icon,
|
|
9
|
+
Badge,
|
|
10
|
+
ActivityIndicator,
|
|
11
|
+
Alert,
|
|
12
|
+
TextInput,
|
|
13
|
+
} from '@idealyst/components';
|
|
14
|
+
import { useQuery, useMutation, gql } from '../graphql/client';
|
|
15
|
+
|
|
16
|
+
// GraphQL Queries
|
|
17
|
+
const GET_ITEMS = gql`
|
|
18
|
+
query GetItems {
|
|
19
|
+
items {
|
|
20
|
+
id
|
|
21
|
+
title
|
|
22
|
+
description
|
|
23
|
+
completed
|
|
24
|
+
createdAt
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const GET_ITEM_STATS = gql`
|
|
30
|
+
query GetItemStats {
|
|
31
|
+
itemStats {
|
|
32
|
+
total
|
|
33
|
+
completed
|
|
34
|
+
pending
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
// GraphQL Mutations
|
|
40
|
+
const CREATE_ITEM = gql`
|
|
41
|
+
mutation CreateItem($input: CreateItemInput!) {
|
|
42
|
+
createItem(input: $input) {
|
|
43
|
+
id
|
|
44
|
+
title
|
|
45
|
+
description
|
|
46
|
+
completed
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const TOGGLE_ITEM = gql`
|
|
52
|
+
mutation ToggleItem($id: String!) {
|
|
53
|
+
toggleItem(id: $id) {
|
|
54
|
+
id
|
|
55
|
+
completed
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const DELETE_ITEM = gql`
|
|
61
|
+
mutation DeleteItem($id: String!) {
|
|
62
|
+
deleteItem(id: $id) {
|
|
63
|
+
id
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
interface Item {
|
|
69
|
+
id: string;
|
|
70
|
+
title: string;
|
|
71
|
+
description: string | null;
|
|
72
|
+
completed: boolean;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ItemStats {
|
|
77
|
+
total: number;
|
|
78
|
+
completed: number;
|
|
79
|
+
pending: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const GraphQLDemoScreen: React.FC = () => {
|
|
83
|
+
const [newItemTitle, setNewItemTitle] = useState('');
|
|
84
|
+
|
|
85
|
+
// Queries
|
|
86
|
+
const {
|
|
87
|
+
data: itemsData,
|
|
88
|
+
loading: itemsLoading,
|
|
89
|
+
error: itemsError,
|
|
90
|
+
refetch: refetchItems,
|
|
91
|
+
} = useQuery<{ items: Item[] }>(GET_ITEMS);
|
|
92
|
+
|
|
93
|
+
const {
|
|
94
|
+
data: statsData,
|
|
95
|
+
loading: statsLoading,
|
|
96
|
+
error: statsError,
|
|
97
|
+
refetch: refetchStats,
|
|
98
|
+
} = useQuery<{ itemStats: ItemStats }>(GET_ITEM_STATS);
|
|
99
|
+
|
|
100
|
+
// Mutations
|
|
101
|
+
const [createItem, { loading: createLoading }] = useMutation(CREATE_ITEM, {
|
|
102
|
+
onCompleted: () => {
|
|
103
|
+
refetchItems();
|
|
104
|
+
refetchStats();
|
|
105
|
+
setNewItemTitle('');
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const [toggleItem] = useMutation(TOGGLE_ITEM, {
|
|
110
|
+
onCompleted: () => {
|
|
111
|
+
refetchItems();
|
|
112
|
+
refetchStats();
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const [deleteItem] = useMutation(DELETE_ITEM, {
|
|
117
|
+
onCompleted: () => {
|
|
118
|
+
refetchItems();
|
|
119
|
+
refetchStats();
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const isLoading = itemsLoading || statsLoading;
|
|
124
|
+
const hasError = itemsError || statsError;
|
|
125
|
+
|
|
126
|
+
const handleCreateItem = () => {
|
|
127
|
+
if (newItemTitle.trim()) {
|
|
128
|
+
createItem({ variables: { input: { title: newItemTitle.trim() } } });
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Screen background="primary" padding="lg" scrollable>
|
|
134
|
+
<View gap="lg">
|
|
135
|
+
{/* Header */}
|
|
136
|
+
<View gap="sm">
|
|
137
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
138
|
+
<Icon name="graphql" size={28} intent="danger" />
|
|
139
|
+
<Text typography="h3">GraphQL Demo</Text>
|
|
140
|
+
</View>
|
|
141
|
+
<Text color="secondary">
|
|
142
|
+
Flexible queries and mutations with Apollo Client
|
|
143
|
+
</Text>
|
|
144
|
+
</View>
|
|
145
|
+
|
|
146
|
+
{/* Loading State */}
|
|
147
|
+
{isLoading && (
|
|
148
|
+
<Card type="outlined" padding="lg">
|
|
149
|
+
<View style={{ alignItems: 'center', gap: 12 }}>
|
|
150
|
+
<ActivityIndicator size="lg" intent="danger" />
|
|
151
|
+
<Text color="secondary">Fetching GraphQL data...</Text>
|
|
152
|
+
</View>
|
|
153
|
+
</Card>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
{/* Error State */}
|
|
157
|
+
{hasError && (
|
|
158
|
+
<Alert intent="danger" title="GraphQL Error">
|
|
159
|
+
Could not fetch data. Make sure the API server is running and the
|
|
160
|
+
database is set up.
|
|
161
|
+
</Alert>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{/* Stats */}
|
|
165
|
+
{statsData?.itemStats && (
|
|
166
|
+
<Card type="elevated" padding="md" gap="md">
|
|
167
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
168
|
+
<Icon name="chart-pie" size={20} intent="danger" />
|
|
169
|
+
<Text weight="semibold">Item Statistics</Text>
|
|
170
|
+
</View>
|
|
171
|
+
|
|
172
|
+
<View style={{ flexDirection: 'row', gap: 12 }}>
|
|
173
|
+
<View
|
|
174
|
+
background="secondary"
|
|
175
|
+
padding="md"
|
|
176
|
+
radius="md"
|
|
177
|
+
style={{ flex: 1, alignItems: 'center' }}
|
|
178
|
+
>
|
|
179
|
+
<Text typography="h4">{statsData.itemStats.total}</Text>
|
|
180
|
+
<Text typography="caption" color="secondary">
|
|
181
|
+
Total
|
|
182
|
+
</Text>
|
|
183
|
+
</View>
|
|
184
|
+
<View
|
|
185
|
+
background="secondary"
|
|
186
|
+
padding="md"
|
|
187
|
+
radius="md"
|
|
188
|
+
style={{ flex: 1, alignItems: 'center' }}
|
|
189
|
+
>
|
|
190
|
+
<Text typography="h4" intent="success">
|
|
191
|
+
{statsData.itemStats.completed}
|
|
192
|
+
</Text>
|
|
193
|
+
<Text typography="caption" color="secondary">
|
|
194
|
+
Completed
|
|
195
|
+
</Text>
|
|
196
|
+
</View>
|
|
197
|
+
<View
|
|
198
|
+
background="secondary"
|
|
199
|
+
padding="md"
|
|
200
|
+
radius="md"
|
|
201
|
+
style={{ flex: 1, alignItems: 'center' }}
|
|
202
|
+
>
|
|
203
|
+
<Text typography="h4" intent="warning">
|
|
204
|
+
{statsData.itemStats.pending}
|
|
205
|
+
</Text>
|
|
206
|
+
<Text typography="caption" color="secondary">
|
|
207
|
+
Pending
|
|
208
|
+
</Text>
|
|
209
|
+
</View>
|
|
210
|
+
</View>
|
|
211
|
+
|
|
212
|
+
<View background="secondary" padding="sm" radius="sm">
|
|
213
|
+
<Text typography="caption" style={{ fontFamily: 'monospace' }}>
|
|
214
|
+
{`query { itemStats { total completed pending } }`}
|
|
215
|
+
</Text>
|
|
216
|
+
</View>
|
|
217
|
+
</Card>
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{/* Create Item */}
|
|
221
|
+
<Card type="elevated" padding="md" gap="md">
|
|
222
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
223
|
+
<Icon name="plus-circle" size={20} intent="success" />
|
|
224
|
+
<Text weight="semibold">Create Item</Text>
|
|
225
|
+
</View>
|
|
226
|
+
|
|
227
|
+
<TextInput
|
|
228
|
+
placeholder="Enter item title..."
|
|
229
|
+
value={newItemTitle}
|
|
230
|
+
onChangeText={setNewItemTitle}
|
|
231
|
+
/>
|
|
232
|
+
|
|
233
|
+
<Button
|
|
234
|
+
intent="danger"
|
|
235
|
+
leftIcon="plus"
|
|
236
|
+
onPress={handleCreateItem}
|
|
237
|
+
disabled={!newItemTitle.trim() || createLoading}
|
|
238
|
+
>
|
|
239
|
+
{createLoading ? 'Creating...' : 'Add Item (GraphQL)'}
|
|
240
|
+
</Button>
|
|
241
|
+
|
|
242
|
+
<View background="secondary" padding="sm" radius="sm">
|
|
243
|
+
<Text typography="caption" style={{ fontFamily: 'monospace' }}>
|
|
244
|
+
{`mutation { createItem(input: { title: "..." }) { id } }`}
|
|
245
|
+
</Text>
|
|
246
|
+
</View>
|
|
247
|
+
</Card>
|
|
248
|
+
|
|
249
|
+
{/* Items List */}
|
|
250
|
+
{itemsData?.items && itemsData.items.length > 0 && (
|
|
251
|
+
<Card type="outlined" padding="md" gap="md">
|
|
252
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
|
253
|
+
<Icon name="format-list-checks" size={20} intent="danger" />
|
|
254
|
+
<Text weight="semibold">Items</Text>
|
|
255
|
+
<Badge intent="danger" size="sm">
|
|
256
|
+
{itemsData.items.length}
|
|
257
|
+
</Badge>
|
|
258
|
+
</View>
|
|
259
|
+
|
|
260
|
+
<View gap="sm">
|
|
261
|
+
{itemsData.items.map((item) => (
|
|
262
|
+
<View
|
|
263
|
+
key={item.id}
|
|
264
|
+
background="secondary"
|
|
265
|
+
padding="sm"
|
|
266
|
+
radius="sm"
|
|
267
|
+
style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}
|
|
268
|
+
>
|
|
269
|
+
<Button
|
|
270
|
+
size="sm"
|
|
271
|
+
intent={item.completed ? 'success' : 'neutral'}
|
|
272
|
+
leftIcon={item.completed ? 'check-circle' : 'circle-outline'}
|
|
273
|
+
onPress={() => toggleItem({ variables: { id: item.id } })}
|
|
274
|
+
/>
|
|
275
|
+
<View style={{ flex: 1 }}>
|
|
276
|
+
<Text
|
|
277
|
+
weight="medium"
|
|
278
|
+
style={{
|
|
279
|
+
textDecorationLine: item.completed ? 'line-through' : 'none',
|
|
280
|
+
opacity: item.completed ? 0.6 : 1,
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
{item.title}
|
|
284
|
+
</Text>
|
|
285
|
+
{item.description && (
|
|
286
|
+
<Text typography="caption" color="secondary">
|
|
287
|
+
{item.description}
|
|
288
|
+
</Text>
|
|
289
|
+
)}
|
|
290
|
+
</View>
|
|
291
|
+
<Button
|
|
292
|
+
size="sm"
|
|
293
|
+
intent="danger"
|
|
294
|
+
leftIcon="delete"
|
|
295
|
+
onPress={() => deleteItem({ variables: { id: item.id } })}
|
|
296
|
+
/>
|
|
297
|
+
</View>
|
|
298
|
+
))}
|
|
299
|
+
</View>
|
|
300
|
+
</Card>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{/* Empty State */}
|
|
304
|
+
{itemsData?.items && itemsData.items.length === 0 && (
|
|
305
|
+
<Card type="outlined" padding="lg">
|
|
306
|
+
<View style={{ alignItems: 'center', gap: 8 }}>
|
|
307
|
+
<Icon name="inbox-outline" size={48} textColor="secondary" />
|
|
308
|
+
<Text color="secondary">No items yet. Create one above!</Text>
|
|
309
|
+
</View>
|
|
310
|
+
</Card>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{/* Refetch Button */}
|
|
314
|
+
<Button
|
|
315
|
+
intent="danger"
|
|
316
|
+
leftIcon="refresh"
|
|
317
|
+
onPress={() => {
|
|
318
|
+
refetchItems();
|
|
319
|
+
refetchStats();
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
Refetch All Queries
|
|
323
|
+
</Button>
|
|
324
|
+
|
|
325
|
+
{/* GraphQL Benefits */}
|
|
326
|
+
<Card type="elevated" padding="md" gap="sm">
|
|
327
|
+
<Text weight="semibold">GraphQL Benefits</Text>
|
|
328
|
+
<View gap="xs">
|
|
329
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
330
|
+
<Icon name="check-circle" size={16} intent="success" />
|
|
331
|
+
<Text typography="caption">Request only the fields you need</Text>
|
|
332
|
+
</View>
|
|
333
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
334
|
+
<Icon name="check-circle" size={16} intent="success" />
|
|
335
|
+
<Text typography="caption">Single endpoint for all operations</Text>
|
|
336
|
+
</View>
|
|
337
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
338
|
+
<Icon name="check-circle" size={16} intent="success" />
|
|
339
|
+
<Text typography="caption">
|
|
340
|
+
Built-in GraphiQL playground at /graphql
|
|
341
|
+
</Text>
|
|
342
|
+
</View>
|
|
343
|
+
<View style={{ flexDirection: 'row', gap: 8 }}>
|
|
344
|
+
<Icon name="check-circle" size={16} intent="success" />
|
|
345
|
+
<Text typography="caption">Strong typing with schema</Text>
|
|
346
|
+
</View>
|
|
347
|
+
</View>
|
|
348
|
+
</Card>
|
|
349
|
+
</View>
|
|
350
|
+
</Screen>
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export default GraphQLDemoScreen;
|