@idealyst/cli 1.2.32 → 1.2.34
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 -3
- 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-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/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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Schema Definition
|
|
3
|
+
*
|
|
4
|
+
* Uses Prisma database for data persistence.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { builder } from './builder.js';
|
|
8
|
+
import { prisma } from '../lib/database.js';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Object Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
const ItemType = builder.objectRef<{
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string | null;
|
|
18
|
+
completed: boolean;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
updatedAt: Date;
|
|
21
|
+
}>('Item');
|
|
22
|
+
|
|
23
|
+
builder.objectType(ItemType, {
|
|
24
|
+
description: 'A simple item for demonstrating CRUD operations',
|
|
25
|
+
fields: (t) => ({
|
|
26
|
+
id: t.exposeString('id'),
|
|
27
|
+
title: t.exposeString('title'),
|
|
28
|
+
description: t.exposeString('description', { nullable: true }),
|
|
29
|
+
completed: t.exposeBoolean('completed'),
|
|
30
|
+
createdAt: t.expose('createdAt', { type: 'DateTime' }),
|
|
31
|
+
updatedAt: t.expose('updatedAt', { type: 'DateTime' }),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const ItemStatsType = builder.objectRef<{
|
|
36
|
+
total: number;
|
|
37
|
+
completed: number;
|
|
38
|
+
pending: number;
|
|
39
|
+
}>('ItemStats');
|
|
40
|
+
|
|
41
|
+
builder.objectType(ItemStatsType, {
|
|
42
|
+
description: 'Aggregate item statistics',
|
|
43
|
+
fields: (t) => ({
|
|
44
|
+
total: t.exposeInt('total'),
|
|
45
|
+
completed: t.exposeInt('completed'),
|
|
46
|
+
pending: t.exposeInt('pending'),
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Queries
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
builder.queryFields((t) => ({
|
|
55
|
+
// Get single item by ID
|
|
56
|
+
item: t.field({
|
|
57
|
+
type: ItemType,
|
|
58
|
+
nullable: true,
|
|
59
|
+
args: {
|
|
60
|
+
id: t.arg.string({ required: true }),
|
|
61
|
+
},
|
|
62
|
+
resolve: async (_, args) => {
|
|
63
|
+
return await prisma.item.findUnique({
|
|
64
|
+
where: { id: args.id },
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
// List all items with optional filtering
|
|
70
|
+
items: t.field({
|
|
71
|
+
type: [ItemType],
|
|
72
|
+
args: {
|
|
73
|
+
completed: t.arg.boolean(),
|
|
74
|
+
search: t.arg.string(),
|
|
75
|
+
},
|
|
76
|
+
resolve: async (_, args) => {
|
|
77
|
+
const where: any = {};
|
|
78
|
+
|
|
79
|
+
if (args.completed !== null && args.completed !== undefined) {
|
|
80
|
+
where.completed = args.completed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (args.search) {
|
|
84
|
+
where.OR = [
|
|
85
|
+
{ title: { contains: args.search } },
|
|
86
|
+
{ description: { contains: args.search } },
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return await prisma.item.findMany({
|
|
91
|
+
where,
|
|
92
|
+
orderBy: { createdAt: 'desc' },
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
// Get item stats
|
|
98
|
+
itemStats: t.field({
|
|
99
|
+
type: ItemStatsType,
|
|
100
|
+
resolve: async () => {
|
|
101
|
+
const items = await prisma.item.findMany();
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
total: items.length,
|
|
105
|
+
completed: items.filter((i) => i.completed).length,
|
|
106
|
+
pending: items.filter((i) => !i.completed).length,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
// =============================================================================
|
|
113
|
+
// Mutations
|
|
114
|
+
// =============================================================================
|
|
115
|
+
|
|
116
|
+
const CreateItemInput = builder.inputType('CreateItemInput', {
|
|
117
|
+
fields: (t) => ({
|
|
118
|
+
title: t.string({ required: true }),
|
|
119
|
+
description: t.string(),
|
|
120
|
+
completed: t.boolean({ defaultValue: false }),
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const UpdateItemInput = builder.inputType('UpdateItemInput', {
|
|
125
|
+
fields: (t) => ({
|
|
126
|
+
title: t.string(),
|
|
127
|
+
description: t.string(),
|
|
128
|
+
completed: t.boolean(),
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
builder.mutationFields((t) => ({
|
|
133
|
+
// Create a new item
|
|
134
|
+
createItem: t.field({
|
|
135
|
+
type: ItemType,
|
|
136
|
+
args: {
|
|
137
|
+
input: t.arg({ type: CreateItemInput, required: true }),
|
|
138
|
+
},
|
|
139
|
+
resolve: async (_, args) => {
|
|
140
|
+
return await prisma.item.create({
|
|
141
|
+
data: {
|
|
142
|
+
title: args.input.title,
|
|
143
|
+
description: args.input.description,
|
|
144
|
+
completed: args.input.completed ?? false,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
|
|
150
|
+
// Update an existing item
|
|
151
|
+
updateItem: t.field({
|
|
152
|
+
type: ItemType,
|
|
153
|
+
nullable: true,
|
|
154
|
+
args: {
|
|
155
|
+
id: t.arg.string({ required: true }),
|
|
156
|
+
input: t.arg({ type: UpdateItemInput, required: true }),
|
|
157
|
+
},
|
|
158
|
+
resolve: async (_, args) => {
|
|
159
|
+
const data: any = {};
|
|
160
|
+
|
|
161
|
+
if (args.input.title !== null && args.input.title !== undefined) {
|
|
162
|
+
data.title = args.input.title;
|
|
163
|
+
}
|
|
164
|
+
if (args.input.description !== undefined) {
|
|
165
|
+
data.description = args.input.description;
|
|
166
|
+
}
|
|
167
|
+
if (args.input.completed !== null && args.input.completed !== undefined) {
|
|
168
|
+
data.completed = args.input.completed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return await prisma.item.update({
|
|
172
|
+
where: { id: args.id },
|
|
173
|
+
data,
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
|
|
178
|
+
// Delete an item
|
|
179
|
+
deleteItem: t.field({
|
|
180
|
+
type: ItemType,
|
|
181
|
+
nullable: true,
|
|
182
|
+
args: {
|
|
183
|
+
id: t.arg.string({ required: true }),
|
|
184
|
+
},
|
|
185
|
+
resolve: async (_, args) => {
|
|
186
|
+
return await prisma.item.delete({
|
|
187
|
+
where: { id: args.id },
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
|
|
192
|
+
// Toggle item completed status
|
|
193
|
+
toggleItem: t.field({
|
|
194
|
+
type: ItemType,
|
|
195
|
+
nullable: true,
|
|
196
|
+
args: {
|
|
197
|
+
id: t.arg.string({ required: true }),
|
|
198
|
+
},
|
|
199
|
+
resolve: async (_, args) => {
|
|
200
|
+
const item = await prisma.item.findUnique({
|
|
201
|
+
where: { id: args.id },
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!item) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return await prisma.item.update({
|
|
209
|
+
where: { id: args.id },
|
|
210
|
+
data: { completed: !item.completed },
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
}),
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// Build and export the schema
|
|
217
|
+
export const schema = builder.toSchema();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express';
|
|
2
|
+
|
|
3
|
+
export interface Context {
|
|
4
|
+
req: CreateExpressContextOptions['req'];
|
|
5
|
+
res: CreateExpressContextOptions['res'];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const createContext = ({ req, res }: CreateExpressContextOptions): Context => {
|
|
9
|
+
return {
|
|
10
|
+
req,
|
|
11
|
+
res,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type { CreateExpressContextOptions };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { router, publicProcedure } from '../trpc.js';
|
|
3
|
+
import { prisma } from '../lib/database.js';
|
|
4
|
+
import { ItemCreateSchema, ItemUpdateSchema } from '@{{workspaceScope}}/database';
|
|
5
|
+
|
|
6
|
+
// ==========================================================================
|
|
7
|
+
// Base Routes (no database required)
|
|
8
|
+
// ==========================================================================
|
|
9
|
+
|
|
10
|
+
const baseRoutes = {
|
|
11
|
+
// Hello world procedure
|
|
12
|
+
hello: publicProcedure
|
|
13
|
+
.input(z.object({ name: z.string().optional() }))
|
|
14
|
+
.query(({ input }) => {
|
|
15
|
+
return {
|
|
16
|
+
greeting: `Hello ${input.name || 'World'}!`,
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
};
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
// Health check procedure
|
|
22
|
+
health: publicProcedure.query(() => {
|
|
23
|
+
return {
|
|
24
|
+
status: 'OK',
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
};
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
// Echo procedure - returns what you send
|
|
31
|
+
echo: publicProcedure
|
|
32
|
+
.input(z.object({ message: z.string() }))
|
|
33
|
+
.query(({ input }) => {
|
|
34
|
+
return {
|
|
35
|
+
original: input.message,
|
|
36
|
+
reversed: input.message.split('').reverse().join(''),
|
|
37
|
+
length: input.message.length,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
// Counter - in-memory state demo
|
|
43
|
+
counter: router({
|
|
44
|
+
// Get current counter value
|
|
45
|
+
get: publicProcedure.query(() => {
|
|
46
|
+
return { value: counterState.value };
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
// Increment counter
|
|
50
|
+
increment: publicProcedure.mutation(() => {
|
|
51
|
+
counterState.value += 1;
|
|
52
|
+
return { value: counterState.value };
|
|
53
|
+
}),
|
|
54
|
+
|
|
55
|
+
// Decrement counter
|
|
56
|
+
decrement: publicProcedure.mutation(() => {
|
|
57
|
+
counterState.value -= 1;
|
|
58
|
+
return { value: counterState.value };
|
|
59
|
+
}),
|
|
60
|
+
|
|
61
|
+
// Reset counter
|
|
62
|
+
reset: publicProcedure.mutation(() => {
|
|
63
|
+
counterState.value = 0;
|
|
64
|
+
return { value: counterState.value };
|
|
65
|
+
}),
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// In-memory counter state
|
|
70
|
+
const counterState = { value: 0 };
|
|
71
|
+
|
|
72
|
+
// ==========================================================================
|
|
73
|
+
// Database Routes (requires Prisma)
|
|
74
|
+
// ==========================================================================
|
|
75
|
+
|
|
76
|
+
const itemsRouter = router({
|
|
77
|
+
// List all items
|
|
78
|
+
list: publicProcedure
|
|
79
|
+
.input(
|
|
80
|
+
z.object({
|
|
81
|
+
completed: z.boolean().optional(),
|
|
82
|
+
search: z.string().optional(),
|
|
83
|
+
}).optional()
|
|
84
|
+
)
|
|
85
|
+
.query(async ({ input }) => {
|
|
86
|
+
const where: any = {};
|
|
87
|
+
|
|
88
|
+
if (input?.completed !== undefined) {
|
|
89
|
+
where.completed = input.completed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (input?.search) {
|
|
93
|
+
where.OR = [
|
|
94
|
+
{ title: { contains: input.search } },
|
|
95
|
+
{ description: { contains: input.search } },
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return await prisma.item.findMany({
|
|
100
|
+
where,
|
|
101
|
+
orderBy: { createdAt: 'desc' },
|
|
102
|
+
});
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
// Get single item by ID
|
|
106
|
+
byId: publicProcedure
|
|
107
|
+
.input(z.object({ id: z.string() }))
|
|
108
|
+
.query(async ({ input }) => {
|
|
109
|
+
return await prisma.item.findUnique({
|
|
110
|
+
where: { id: input.id },
|
|
111
|
+
});
|
|
112
|
+
}),
|
|
113
|
+
|
|
114
|
+
// Create item
|
|
115
|
+
create: publicProcedure
|
|
116
|
+
.input(ItemCreateSchema)
|
|
117
|
+
.mutation(async ({ input }) => {
|
|
118
|
+
return await prisma.item.create({
|
|
119
|
+
data: input,
|
|
120
|
+
});
|
|
121
|
+
}),
|
|
122
|
+
|
|
123
|
+
// Update item
|
|
124
|
+
update: publicProcedure
|
|
125
|
+
.input(
|
|
126
|
+
z.object({
|
|
127
|
+
id: z.string(),
|
|
128
|
+
data: ItemUpdateSchema,
|
|
129
|
+
})
|
|
130
|
+
)
|
|
131
|
+
.mutation(async ({ input }) => {
|
|
132
|
+
return await prisma.item.update({
|
|
133
|
+
where: { id: input.id },
|
|
134
|
+
data: input.data,
|
|
135
|
+
});
|
|
136
|
+
}),
|
|
137
|
+
|
|
138
|
+
// Delete item
|
|
139
|
+
delete: publicProcedure
|
|
140
|
+
.input(z.object({ id: z.string() }))
|
|
141
|
+
.mutation(async ({ input }) => {
|
|
142
|
+
return await prisma.item.delete({
|
|
143
|
+
where: { id: input.id },
|
|
144
|
+
});
|
|
145
|
+
}),
|
|
146
|
+
|
|
147
|
+
// Toggle completed status
|
|
148
|
+
toggle: publicProcedure
|
|
149
|
+
.input(z.object({ id: z.string() }))
|
|
150
|
+
.mutation(async ({ input }) => {
|
|
151
|
+
const item = await prisma.item.findUnique({
|
|
152
|
+
where: { id: input.id },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!item) {
|
|
156
|
+
throw new Error('Item not found');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return await prisma.item.update({
|
|
160
|
+
where: { id: input.id },
|
|
161
|
+
data: { completed: !item.completed },
|
|
162
|
+
});
|
|
163
|
+
}),
|
|
164
|
+
|
|
165
|
+
// Get stats
|
|
166
|
+
stats: publicProcedure.query(async () => {
|
|
167
|
+
const items = await prisma.item.findMany();
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
total: items.length,
|
|
171
|
+
completed: items.filter((i: { completed: boolean }) => i.completed).length,
|
|
172
|
+
pending: items.filter((i: { completed: boolean }) => !i.completed).length,
|
|
173
|
+
};
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ==========================================================================
|
|
178
|
+
// Combined Router
|
|
179
|
+
// ==========================================================================
|
|
180
|
+
|
|
181
|
+
export const appRouter = router({
|
|
182
|
+
...baseRoutes,
|
|
183
|
+
items: itemsRouter,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Export type definition of API
|
|
187
|
+
export type AppRouter = typeof appRouter;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { createExpressMiddleware } from '@trpc/server/adapters/express';
|
|
4
|
+
import { createYoga } from 'graphql-yoga';
|
|
5
|
+
import { appRouter } from './router/index.js';
|
|
6
|
+
import { createContext } from './context.js';
|
|
7
|
+
import { schema } from './graphql/schema.js';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
|
|
10
|
+
// Load environment variables
|
|
11
|
+
dotenv.config();
|
|
12
|
+
|
|
13
|
+
const app = express();
|
|
14
|
+
const PORT = process.env.PORT || 3000;
|
|
15
|
+
|
|
16
|
+
// CORS configuration
|
|
17
|
+
app.use(cors({
|
|
18
|
+
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
|
19
|
+
credentials: true,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
// Health check endpoint
|
|
23
|
+
app.get('/health', (req, res) => {
|
|
24
|
+
res.json({ status: 'OK', timestamp: new Date().toISOString() });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// tRPC middleware
|
|
28
|
+
app.use(
|
|
29
|
+
'/trpc',
|
|
30
|
+
createExpressMiddleware({
|
|
31
|
+
router: appRouter,
|
|
32
|
+
createContext,
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// GraphQL Yoga middleware
|
|
37
|
+
const yoga = createYoga({
|
|
38
|
+
schema,
|
|
39
|
+
graphqlEndpoint: '/graphql',
|
|
40
|
+
// Enable GraphiQL playground in development
|
|
41
|
+
graphiql: process.env.NODE_ENV !== 'production',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.use('/graphql', yoga);
|
|
45
|
+
|
|
46
|
+
// Default route
|
|
47
|
+
app.get('/', (req, res) => {
|
|
48
|
+
res.json({
|
|
49
|
+
message: 'Welcome to {{appDisplayName}} API',
|
|
50
|
+
endpoints: {
|
|
51
|
+
health: '/health',
|
|
52
|
+
trpc: '/trpc',
|
|
53
|
+
graphql: '/graphql',
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
app.listen(PORT, () => {
|
|
59
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
60
|
+
console.log(`tRPC API available at http://localhost:${PORT}/trpc`);
|
|
61
|
+
console.log(`GraphQL API available at http://localhost:${PORT}/graphql`);
|
|
62
|
+
console.log(`Health check at http://localhost:${PORT}/health`);
|
|
63
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { initTRPC } from '@trpc/server';
|
|
2
|
+
import { type Context } from './context.js';
|
|
3
|
+
import { ZodError } from 'zod';
|
|
4
|
+
|
|
5
|
+
const t = initTRPC.context<Context>().create({
|
|
6
|
+
errorFormatter({ shape, error }) {
|
|
7
|
+
return {
|
|
8
|
+
...shape,
|
|
9
|
+
data: {
|
|
10
|
+
...shape.data,
|
|
11
|
+
zodError:
|
|
12
|
+
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Export reusable router and procedure helpers
|
|
19
|
+
export const router = t.router;
|
|
20
|
+
export const publicProcedure = t.procedure;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Prisma schema for {{appDisplayName}}
|
|
2
|
+
// Learn more: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
output = "./generated/client"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "sqlite"
|
|
11
|
+
url = env("DATABASE_URL")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Simple Item model for demonstrating CRUD operations
|
|
15
|
+
model Item {
|
|
16
|
+
id String @id @default(cuid())
|
|
17
|
+
title String
|
|
18
|
+
description String?
|
|
19
|
+
completed Boolean @default(false)
|
|
20
|
+
createdAt DateTime @default(now())
|
|
21
|
+
updatedAt DateTime @updatedAt
|
|
22
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { NavigatorProvider } from '@idealyst/navigation';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
import { AppRouter } from '../navigation/AppRouter';
|
|
5
|
+
import { trpc, createTRPCClient } from '../trpc/client';
|
|
6
|
+
import { ApolloProvider, createApolloClient } from '../graphql/client';
|
|
7
|
+
|
|
8
|
+
// API configuration
|
|
9
|
+
const API_URL = 'http://localhost:3002';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Main App component for the {{appDisplayName}}
|
|
13
|
+
* Sets up navigation with tRPC and Apollo providers
|
|
14
|
+
*/
|
|
15
|
+
export const App: React.FC = () => {
|
|
16
|
+
// Create React Query client
|
|
17
|
+
const [queryClient] = useState(() => new QueryClient({
|
|
18
|
+
defaultOptions: {
|
|
19
|
+
queries: {
|
|
20
|
+
retry: 1,
|
|
21
|
+
staleTime: 5000,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Create tRPC client
|
|
27
|
+
const [trpcClient] = useState(() =>
|
|
28
|
+
createTRPCClient({
|
|
29
|
+
apiUrl: `${API_URL}/trpc`,
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Create Apollo client
|
|
34
|
+
const [apolloClient] = useState(() =>
|
|
35
|
+
createApolloClient({
|
|
36
|
+
graphqlUrl: `${API_URL}/graphql`,
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
|
42
|
+
<QueryClientProvider client={queryClient}>
|
|
43
|
+
<ApolloProvider client={apolloClient}>
|
|
44
|
+
<NavigatorProvider route={AppRouter} />
|
|
45
|
+
</ApolloProvider>
|
|
46
|
+
</QueryClientProvider>
|
|
47
|
+
</trpc.Provider>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default App;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apollo Client Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides a configured Apollo Client for GraphQL queries and mutations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client';
|
|
8
|
+
import { onError } from '@apollo/client/link/error';
|
|
9
|
+
|
|
10
|
+
export interface ApolloClientConfig {
|
|
11
|
+
graphqlUrl?: string;
|
|
12
|
+
headers?: () => Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Error handling link
|
|
16
|
+
const createErrorLink = () =>
|
|
17
|
+
onError(({ graphQLErrors, networkError }) => {
|
|
18
|
+
if (graphQLErrors) {
|
|
19
|
+
graphQLErrors.forEach(({ message, locations, path }) => {
|
|
20
|
+
console.error(
|
|
21
|
+
`[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}`
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (networkError) {
|
|
26
|
+
console.error(`[Network error]: ${networkError}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a configured Apollo Client
|
|
32
|
+
*/
|
|
33
|
+
export function createApolloClient(config: ApolloClientConfig = {}) {
|
|
34
|
+
const { graphqlUrl = 'http://localhost:3000/graphql', headers } = config;
|
|
35
|
+
|
|
36
|
+
// HTTP link to GraphQL endpoint
|
|
37
|
+
const httpLink = new HttpLink({
|
|
38
|
+
uri: graphqlUrl,
|
|
39
|
+
credentials: 'include',
|
|
40
|
+
headers: headers?.(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create the Apollo Client
|
|
44
|
+
return new ApolloClient({
|
|
45
|
+
link: from([createErrorLink(), httpLink]),
|
|
46
|
+
cache: new InMemoryCache({
|
|
47
|
+
typePolicies: {
|
|
48
|
+
Item: {
|
|
49
|
+
keyFields: ['id'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
defaultOptions: {
|
|
54
|
+
watchQuery: {
|
|
55
|
+
fetchPolicy: 'cache-and-network',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Re-export Apollo hooks and types for convenience
|
|
62
|
+
export {
|
|
63
|
+
ApolloProvider,
|
|
64
|
+
useQuery,
|
|
65
|
+
useMutation,
|
|
66
|
+
useLazyQuery,
|
|
67
|
+
gql,
|
|
68
|
+
} from '@apollo/client';
|
|
69
|
+
|
|
70
|
+
export type { ApolloError, QueryResult, MutationResult } from '@apollo/client';
|