@claudetools/tools 0.8.11 → 0.9.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.
- package/dist/codedna/generators/astro.d.ts +18 -0
- package/dist/codedna/generators/astro.js +91 -0
- package/dist/codedna/generators/authjs.d.ts +18 -0
- package/dist/codedna/generators/authjs.js +68 -0
- package/dist/codedna/generators/better-auth.d.ts +18 -0
- package/dist/codedna/generators/better-auth.js +62 -0
- package/dist/codedna/generators/drizzle-orm.d.ts +18 -0
- package/dist/codedna/generators/drizzle-orm.js +65 -0
- package/dist/codedna/generators/elysia-api.d.ts +12 -0
- package/dist/codedna/generators/elysia-api.js +64 -0
- package/dist/codedna/generators/hono-api.d.ts +12 -0
- package/dist/codedna/generators/hono-api.js +64 -0
- package/dist/codedna/generators/lucia-auth.d.ts +18 -0
- package/dist/codedna/generators/lucia-auth.js +69 -0
- package/dist/codedna/generators/prisma.d.ts +18 -0
- package/dist/codedna/generators/prisma.js +64 -0
- package/dist/codedna/generators/react-router-v7.d.ts +18 -0
- package/dist/codedna/generators/react-router-v7.js +77 -0
- package/dist/codedna/generators/react19-shadcn.d.ts +21 -0
- package/dist/codedna/generators/react19-shadcn.js +367 -0
- package/dist/codedna/generators/sveltekit.d.ts +18 -0
- package/dist/codedna/generators/sveltekit.js +73 -0
- package/dist/codedna/generators/tanstack-start-drizzle.d.ts +92 -0
- package/dist/codedna/generators/tanstack-start-drizzle.js +824 -0
- package/dist/codedna/generators/trpc-api.d.ts +12 -0
- package/dist/codedna/generators/trpc-api.js +64 -0
- package/dist/codedna/index.d.ts +31 -0
- package/dist/codedna/index.js +39 -0
- package/dist/codedna/kappa-api-generator.d.ts +89 -0
- package/dist/codedna/kappa-api-generator.js +493 -0
- package/dist/codedna/kappa-ast.d.ts +552 -0
- package/dist/codedna/kappa-ast.js +141 -0
- package/dist/codedna/kappa-cli.d.ts +2 -0
- package/dist/codedna/kappa-cli.js +302 -0
- package/dist/codedna/kappa-component-generator.d.ts +47 -0
- package/dist/codedna/kappa-component-generator.js +295 -0
- package/dist/codedna/kappa-design-generator.d.ts +52 -0
- package/dist/codedna/kappa-design-generator.js +365 -0
- package/dist/codedna/kappa-drizzle-generator.d.ts +45 -0
- package/dist/codedna/kappa-drizzle-generator.js +355 -0
- package/dist/codedna/kappa-form-generator.d.ts +51 -0
- package/dist/codedna/kappa-form-generator.js +319 -0
- package/dist/codedna/kappa-lexer.d.ts +268 -0
- package/dist/codedna/kappa-lexer.js +757 -0
- package/dist/codedna/kappa-page-generator.d.ts +57 -0
- package/dist/codedna/kappa-page-generator.js +338 -0
- package/dist/codedna/kappa-parser.d.ts +261 -0
- package/dist/codedna/kappa-parser.js +2547 -0
- package/dist/codedna/kappa-provenance.d.ts +101 -0
- package/dist/codedna/kappa-provenance.js +199 -0
- package/dist/codedna/kappa-types-generator.d.ts +37 -0
- package/dist/codedna/kappa-types-generator.js +159 -0
- package/dist/codedna/kappa-validator.d.ts +86 -0
- package/dist/codedna/kappa-validator.js +638 -0
- package/dist/codedna/kappa-zod-generator.d.ts +32 -0
- package/dist/codedna/kappa-zod-generator.js +216 -0
- package/dist/handlers/kappa-handlers.d.ts +116 -0
- package/dist/handlers/kappa-handlers.js +465 -0
- package/dist/handlers/tool-handlers.js +121 -0
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +166 -9
- package/dist/tools.js +199 -0
- package/docs/research/2026-01-02-codedna-il-specification.md +639 -0
- package/docs/research/2026-01-02-codedna-v2-research.md +943 -0
- package/docs/research/2026-01-02-computation-foundations.md +564 -0
- package/docs/research/2026-01-02-hardware-description.md +814 -0
- package/docs/research/2026-01-02-kappa-specification.md +697 -0
- package/docs/research/2026-01-02-kappa-tanstack-example.md +527 -0
- package/docs/research/2026-01-02-kappa-v2-synthesis.md +406 -0
- package/docs/research/2026-01-02-kappa-v2.5-specification.md +1218 -0
- package/docs/research/2026-01-02-kappa-v3-specification.md +1864 -0
- package/docs/research/2026-01-02-kappa-whitepaper.md +662 -0
- package/docs/research/2026-01-02-logic-constraint.md +731 -0
- package/docs/research/2026-01-02-quantum-computation.md +635 -0
- package/package.json +4 -2
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Tanstack Start + Drizzle ORM Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generates complete full-stack application with:
|
|
6
|
+
// - Tanstack Start (React framework with SSR/CSR)
|
|
7
|
+
// - Drizzle ORM (type-safe SQL)
|
|
8
|
+
// - File-based routing
|
|
9
|
+
// - Type-safe data loading
|
|
10
|
+
// - Server actions for mutations
|
|
11
|
+
// - Multiple database support (PostgreSQL, MySQL, SQLite)
|
|
12
|
+
// - Cloudflare and Node deployment
|
|
13
|
+
//
|
|
14
|
+
import { BaseGenerator } from './base.js';
|
|
15
|
+
import { TemplateEngine, buildContext } from '../template-engine.js';
|
|
16
|
+
export class TanstackStartDrizzleGenerator extends BaseGenerator {
|
|
17
|
+
engine;
|
|
18
|
+
dbOptions;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
super();
|
|
21
|
+
this.engine = new TemplateEngine();
|
|
22
|
+
this.dbOptions = {
|
|
23
|
+
database: options.database || 'postgresql',
|
|
24
|
+
deployment: options.deployment || 'cloudflare',
|
|
25
|
+
ssr: options.ssr ?? true,
|
|
26
|
+
auth: options.auth ?? false,
|
|
27
|
+
...options,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate complete Tanstack Start + Drizzle application
|
|
32
|
+
*/
|
|
33
|
+
async generate(entity) {
|
|
34
|
+
const files = [];
|
|
35
|
+
const context = buildContext(entity, this.dbOptions);
|
|
36
|
+
// 1. Drizzle schema
|
|
37
|
+
files.push(await this.generateSchema(entity, context));
|
|
38
|
+
// 2. Drizzle migrations
|
|
39
|
+
files.push(await this.generateMigration(entity, context));
|
|
40
|
+
// 3. Database client configuration
|
|
41
|
+
files.push(await this.generateDbClient(context));
|
|
42
|
+
// 4. CRUD operations (queries + mutations)
|
|
43
|
+
files.push(await this.generateQueries(entity, context));
|
|
44
|
+
files.push(await this.generateMutations(entity, context));
|
|
45
|
+
// 5. Tanstack Start routes
|
|
46
|
+
files.push(await this.generateIndexRoute(entity, context)); // List view
|
|
47
|
+
files.push(await this.generateDetailRoute(entity, context)); // Detail view
|
|
48
|
+
files.push(await this.generateCreateRoute(entity, context)); // Create form
|
|
49
|
+
files.push(await this.generateEditRoute(entity, context)); // Edit form
|
|
50
|
+
// 6. React components
|
|
51
|
+
files.push(await this.generateListComponent(entity, context));
|
|
52
|
+
files.push(await this.generateFormComponent(entity, context));
|
|
53
|
+
// 7. Type definitions
|
|
54
|
+
files.push(await this.generateTypes(entity, context));
|
|
55
|
+
// 8. Configuration files
|
|
56
|
+
if (this.dbOptions.deployment === 'cloudflare') {
|
|
57
|
+
files.push(await this.generateWranglerConfig(context));
|
|
58
|
+
}
|
|
59
|
+
files.push(await this.generateDrizzleConfig(context));
|
|
60
|
+
files.push(await this.generatePackageJson(context));
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generate Drizzle schema definition
|
|
65
|
+
*/
|
|
66
|
+
async generateSchema(entity, context) {
|
|
67
|
+
const template = this.getSchemaTemplate();
|
|
68
|
+
const content = this.engine.render(template, context);
|
|
69
|
+
return {
|
|
70
|
+
path: `src/db/schema/${entity.name.toLowerCase()}.ts`,
|
|
71
|
+
content,
|
|
72
|
+
language: 'typescript',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate database migration
|
|
77
|
+
*/
|
|
78
|
+
async generateMigration(entity, context) {
|
|
79
|
+
const template = this.getMigrationTemplate();
|
|
80
|
+
const content = this.engine.render(template, context);
|
|
81
|
+
const timestamp = new Date().getTime();
|
|
82
|
+
return {
|
|
83
|
+
path: `drizzle/migrations/${timestamp}_create_${entity.name.toLowerCase()}.sql`,
|
|
84
|
+
content,
|
|
85
|
+
language: 'sql',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Generate database client configuration
|
|
90
|
+
*/
|
|
91
|
+
async generateDbClient(context) {
|
|
92
|
+
const template = this.getDbClientTemplate();
|
|
93
|
+
const content = this.engine.render(template, context);
|
|
94
|
+
return {
|
|
95
|
+
path: 'src/db/client.ts',
|
|
96
|
+
content,
|
|
97
|
+
language: 'typescript',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Generate type-safe query functions
|
|
102
|
+
*/
|
|
103
|
+
async generateQueries(entity, context) {
|
|
104
|
+
const template = this.getQueriesTemplate();
|
|
105
|
+
const content = this.engine.render(template, context);
|
|
106
|
+
return {
|
|
107
|
+
path: `src/queries/${entity.name.toLowerCase()}.ts`,
|
|
108
|
+
content,
|
|
109
|
+
language: 'typescript',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generate server action mutations
|
|
114
|
+
*/
|
|
115
|
+
async generateMutations(entity, context) {
|
|
116
|
+
const template = this.getMutationsTemplate();
|
|
117
|
+
const content = this.engine.render(template, context);
|
|
118
|
+
return {
|
|
119
|
+
path: `src/mutations/${entity.name.toLowerCase()}.ts`,
|
|
120
|
+
content,
|
|
121
|
+
language: 'typescript',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Generate index route (list view)
|
|
126
|
+
*/
|
|
127
|
+
async generateIndexRoute(entity, context) {
|
|
128
|
+
const template = this.getIndexRouteTemplate();
|
|
129
|
+
const content = this.engine.render(template, context);
|
|
130
|
+
return {
|
|
131
|
+
path: `src/routes/${entity.name.toLowerCase()}/index.tsx`,
|
|
132
|
+
content,
|
|
133
|
+
language: 'typescript',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generate detail route
|
|
138
|
+
*/
|
|
139
|
+
async generateDetailRoute(entity, context) {
|
|
140
|
+
const template = this.getDetailRouteTemplate();
|
|
141
|
+
const content = this.engine.render(template, context);
|
|
142
|
+
return {
|
|
143
|
+
path: `src/routes/${entity.name.toLowerCase()}/$id.tsx`,
|
|
144
|
+
content,
|
|
145
|
+
language: 'typescript',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate create route
|
|
150
|
+
*/
|
|
151
|
+
async generateCreateRoute(entity, context) {
|
|
152
|
+
const template = this.getCreateRouteTemplate();
|
|
153
|
+
const content = this.engine.render(template, context);
|
|
154
|
+
return {
|
|
155
|
+
path: `src/routes/${entity.name.toLowerCase()}/create.tsx`,
|
|
156
|
+
content,
|
|
157
|
+
language: 'typescript',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Generate edit route
|
|
162
|
+
*/
|
|
163
|
+
async generateEditRoute(entity, context) {
|
|
164
|
+
const template = this.getEditRouteTemplate();
|
|
165
|
+
const content = this.engine.render(template, context);
|
|
166
|
+
return {
|
|
167
|
+
path: `src/routes/${entity.name.toLowerCase()}/$id/edit.tsx`,
|
|
168
|
+
content,
|
|
169
|
+
language: 'typescript',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Generate list component
|
|
174
|
+
*/
|
|
175
|
+
async generateListComponent(entity, context) {
|
|
176
|
+
const template = this.getListComponentTemplate();
|
|
177
|
+
const content = this.engine.render(template, context);
|
|
178
|
+
return {
|
|
179
|
+
path: `src/components/${entity.name}List.tsx`,
|
|
180
|
+
content,
|
|
181
|
+
language: 'typescript',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Generate form component
|
|
186
|
+
*/
|
|
187
|
+
async generateFormComponent(entity, context) {
|
|
188
|
+
const template = this.getFormComponentTemplate();
|
|
189
|
+
const content = this.engine.render(template, context);
|
|
190
|
+
return {
|
|
191
|
+
path: `src/components/${entity.name}Form.tsx`,
|
|
192
|
+
content,
|
|
193
|
+
language: 'typescript',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Generate TypeScript type definitions
|
|
198
|
+
*/
|
|
199
|
+
async generateTypes(entity, context) {
|
|
200
|
+
const template = this.getTypesTemplate();
|
|
201
|
+
const content = this.engine.render(template, context);
|
|
202
|
+
return {
|
|
203
|
+
path: `src/types/${entity.name.toLowerCase()}.ts`,
|
|
204
|
+
content,
|
|
205
|
+
language: 'typescript',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate Wrangler configuration for Cloudflare
|
|
210
|
+
*/
|
|
211
|
+
async generateWranglerConfig(context) {
|
|
212
|
+
const template = this.getWranglerConfigTemplate();
|
|
213
|
+
const content = this.engine.render(template, context);
|
|
214
|
+
return {
|
|
215
|
+
path: 'wrangler.toml',
|
|
216
|
+
content,
|
|
217
|
+
language: 'toml',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Generate Drizzle configuration
|
|
222
|
+
*/
|
|
223
|
+
async generateDrizzleConfig(context) {
|
|
224
|
+
const template = this.getDrizzleConfigTemplate();
|
|
225
|
+
const content = this.engine.render(template, context);
|
|
226
|
+
return {
|
|
227
|
+
path: 'drizzle.config.ts',
|
|
228
|
+
content,
|
|
229
|
+
language: 'typescript',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Generate package.json with dependencies
|
|
234
|
+
*/
|
|
235
|
+
async generatePackageJson(context) {
|
|
236
|
+
const template = this.getPackageJsonTemplate();
|
|
237
|
+
const content = this.engine.render(template, context);
|
|
238
|
+
return {
|
|
239
|
+
path: 'package.json',
|
|
240
|
+
content,
|
|
241
|
+
language: 'json',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// =============================================================================
|
|
245
|
+
// Templates
|
|
246
|
+
// =============================================================================
|
|
247
|
+
getSchemaTemplate() {
|
|
248
|
+
return `// {{ entity.name }} Drizzle Schema
|
|
249
|
+
// Generated by CodeDNA
|
|
250
|
+
|
|
251
|
+
{% if options.database === 'postgresql' %}
|
|
252
|
+
import { pgTable, serial, varchar, integer, boolean, timestamp, text, jsonb } from 'drizzle-orm/pg-core';
|
|
253
|
+
{% elif options.database === 'mysql' %}
|
|
254
|
+
import { mysqlTable, serial, varchar, int, boolean, timestamp, text, json } from 'drizzle-orm/mysql-core';
|
|
255
|
+
{% elif options.database === 'sqlite' %}
|
|
256
|
+
import { sqliteTable, integer as sqliteInt, text as sqliteText } from 'drizzle-orm/sqlite-core';
|
|
257
|
+
{% endif %}
|
|
258
|
+
import { relations } from 'drizzle-orm';
|
|
259
|
+
|
|
260
|
+
{% if options.database === 'postgresql' %}
|
|
261
|
+
export const {{ entity.name | lower }} = pgTable('{{ entity.name | lower | plural }}', {
|
|
262
|
+
{% elif options.database === 'mysql' %}
|
|
263
|
+
export const {{ entity.name | lower }} = mysqlTable('{{ entity.name | lower | plural }}', {
|
|
264
|
+
{% elif options.database === 'sqlite' %}
|
|
265
|
+
export const {{ entity.name | lower }} = sqliteTable('{{ entity.name | lower | plural }}', {
|
|
266
|
+
{% endif %}
|
|
267
|
+
id: serial('id').primaryKey(),
|
|
268
|
+
{% for field in entity.fields %}
|
|
269
|
+
{% if field.type.kind === 'primitive' %}
|
|
270
|
+
{{ field.name }}: {{ field.type | sqlType | lower }}('{{ field.name }}'){% if field | hasConstraint('required') %}.notNull(){% endif %}{% if field | hasConstraint('unique') %}.unique(){% endif %}{% if field | getConstraint('default') %}.default({{ field | getConstraint('default') }}){% endif %},
|
|
271
|
+
{% elif field.type.kind === 'reference' %}
|
|
272
|
+
{{ field.name }}Id: integer('{{ field.name }}_id'){% if field | hasConstraint('required') %}.notNull(){% endif %}.references(() => {{ field.type.entity | lower }}.id),
|
|
273
|
+
{% elif field.type.kind === 'enum' %}
|
|
274
|
+
{{ field.name }}: varchar('{{ field.name }}', { length: 50 }){% if field | hasConstraint('required') %}.notNull(){% endif %},
|
|
275
|
+
{% elif field.type.kind === 'array' %}
|
|
276
|
+
{{ field.name }}: {% if options.database === 'postgresql' %}jsonb{% else %}json{% endif %}('{{ field.name }}'){% if field | hasConstraint('required') %}.notNull(){% endif %},
|
|
277
|
+
{% elif field.type.kind === 'json' %}
|
|
278
|
+
{{ field.name }}: {% if options.database === 'postgresql' %}jsonb{% else %}json{% endif %}('{{ field.name }}'){% if field | hasConstraint('required') %}.notNull(){% endif %},
|
|
279
|
+
{% endif %}
|
|
280
|
+
{% endfor %}
|
|
281
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
282
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Relations
|
|
286
|
+
export const {{ entity.name | lower }}Relations = relations({{ entity.name | lower }}, ({ one, many }) => ({
|
|
287
|
+
{% for field in entity.fields %}
|
|
288
|
+
{% if field.type.kind === 'reference' %}
|
|
289
|
+
{% if field.type.relation === 'oneToMany' %}
|
|
290
|
+
{{ field.name }}: one({{ field.type.entity | lower }}, {
|
|
291
|
+
fields: [{{ entity.name | lower }}.{{ field.name }}Id],
|
|
292
|
+
references: [{{ field.type.entity | lower }}.id],
|
|
293
|
+
}),
|
|
294
|
+
{% elif field.type.relation === 'manyToMany' %}
|
|
295
|
+
{{ field.name }}: many({{ field.type.entity | lower }}),
|
|
296
|
+
{% else %}
|
|
297
|
+
{{ field.name }}: one({{ field.type.entity | lower }}, {
|
|
298
|
+
fields: [{{ entity.name | lower }}.{{ field.name }}Id],
|
|
299
|
+
references: [{{ field.type.entity | lower }}.id],
|
|
300
|
+
}),
|
|
301
|
+
{% endif %}
|
|
302
|
+
{% endif %}
|
|
303
|
+
{% endfor %}
|
|
304
|
+
}));
|
|
305
|
+
|
|
306
|
+
// TypeScript type
|
|
307
|
+
export type {{ entity.name }} = typeof {{ entity.name | lower }}.$inferSelect;
|
|
308
|
+
export type New{{ entity.name }} = typeof {{ entity.name | lower }}.$inferInsert;
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
getMigrationTemplate() {
|
|
312
|
+
return `-- {{ entity.name }} Migration
|
|
313
|
+
-- Generated by CodeDNA
|
|
314
|
+
|
|
315
|
+
CREATE TABLE {{ entity.name | lower | plural }} (
|
|
316
|
+
id SERIAL PRIMARY KEY,
|
|
317
|
+
{% for field in entity.fields %}
|
|
318
|
+
{% if field.type.kind === 'primitive' %}
|
|
319
|
+
{{ field.name }} {{ field.type | sqlType }}{% if field | hasConstraint('required') %} NOT NULL{% endif %}{% if field | hasConstraint('unique') %} UNIQUE{% endif %},
|
|
320
|
+
{% elif field.type.kind === 'reference' %}
|
|
321
|
+
{{ field.name }}_id INTEGER{% if field | hasConstraint('required') %} NOT NULL{% endif %} REFERENCES {{ field.type.entity | lower | plural }}(id),
|
|
322
|
+
{% elif field.type.kind === 'enum' %}
|
|
323
|
+
{{ field.name }} VARCHAR(50){% if field | hasConstraint('required') %} NOT NULL{% endif %},
|
|
324
|
+
{% elif field.type.kind === 'array' or field.type.kind === 'json' %}
|
|
325
|
+
{{ field.name }} {% if options.database === 'postgresql' %}JSONB{% else %}JSON{% endif %}{% if field | hasConstraint('required') %} NOT NULL{% endif %},
|
|
326
|
+
{% endif %}
|
|
327
|
+
{% endfor %}
|
|
328
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
329
|
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
-- Indexes
|
|
333
|
+
{% for field in entity.fields %}
|
|
334
|
+
{% if field | hasConstraint('index') %}
|
|
335
|
+
CREATE INDEX idx_{{ entity.name | lower }}_{{ field.name }} ON {{ entity.name | lower | plural }}({{ field.name }});
|
|
336
|
+
{% endif %}
|
|
337
|
+
{% if field.type.kind === 'reference' %}
|
|
338
|
+
CREATE INDEX idx_{{ entity.name | lower }}_{{ field.name }}_id ON {{ entity.name | lower | plural }}({{ field.name }}_id);
|
|
339
|
+
{% endif %}
|
|
340
|
+
{% endfor %}
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
getDbClientTemplate() {
|
|
344
|
+
return `// Database Client Configuration
|
|
345
|
+
// Generated by CodeDNA
|
|
346
|
+
|
|
347
|
+
{% if options.database === 'postgresql' %}
|
|
348
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
349
|
+
import postgres from 'postgres';
|
|
350
|
+
{% elif options.database === 'mysql' %}
|
|
351
|
+
import { drizzle } from 'drizzle-orm/mysql2';
|
|
352
|
+
import mysql from 'mysql2/promise';
|
|
353
|
+
{% elif options.database === 'sqlite' %}
|
|
354
|
+
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
355
|
+
import Database from 'better-sqlite3';
|
|
356
|
+
{% endif %}
|
|
357
|
+
|
|
358
|
+
import * as schema from './schema';
|
|
359
|
+
|
|
360
|
+
{% if options.deployment === 'cloudflare' %}
|
|
361
|
+
// Cloudflare D1 binding (set in wrangler.toml)
|
|
362
|
+
declare const DB: D1Database;
|
|
363
|
+
|
|
364
|
+
export const db = drizzle(DB, { schema });
|
|
365
|
+
{% else %}
|
|
366
|
+
// Database connection
|
|
367
|
+
const connectionString = process.env.DATABASE_URL!;
|
|
368
|
+
|
|
369
|
+
{% if options.database === 'postgresql' %}
|
|
370
|
+
const queryClient = postgres(connectionString);
|
|
371
|
+
export const db = drizzle(queryClient, { schema });
|
|
372
|
+
{% elif options.database === 'mysql' %}
|
|
373
|
+
const pool = mysql.createPool(connectionString);
|
|
374
|
+
export const db = drizzle(pool, { schema });
|
|
375
|
+
{% elif options.database === 'sqlite' %}
|
|
376
|
+
const sqlite = new Database(connectionString);
|
|
377
|
+
export const db = drizzle(sqlite, { schema });
|
|
378
|
+
{% endif %}
|
|
379
|
+
{% endif %}
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
getQueriesTemplate() {
|
|
383
|
+
return `// {{ entity.name }} Queries
|
|
384
|
+
// Generated by CodeDNA
|
|
385
|
+
|
|
386
|
+
import { db } from '../db/client';
|
|
387
|
+
import { {{ entity.name | lower }} } from '../db/schema/{{ entity.name | lower }}';
|
|
388
|
+
import { eq, desc } from 'drizzle-orm';
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get all {{ entity.name | plural }}
|
|
392
|
+
*/
|
|
393
|
+
export async function getAll{{ entity.name | plural }}() {
|
|
394
|
+
return db.select().from({{ entity.name | lower }}).orderBy(desc({{ entity.name | lower }}.createdAt));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get {{ entity.name }} by ID
|
|
399
|
+
*/
|
|
400
|
+
export async function get{{ entity.name }}ById(id: number) {
|
|
401
|
+
const result = await db.select().from({{ entity.name | lower }}).where(eq({{ entity.name | lower }}.id, id));
|
|
402
|
+
return result[0] || null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Search {{ entity.name | plural }}
|
|
407
|
+
*/
|
|
408
|
+
export async function search{{ entity.name | plural }}(query: string) {
|
|
409
|
+
// Implement search logic based on your needs
|
|
410
|
+
return db.select().from({{ entity.name | lower }});
|
|
411
|
+
}
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
414
|
+
getMutationsTemplate() {
|
|
415
|
+
return `// {{ entity.name }} Mutations (Server Actions)
|
|
416
|
+
// Generated by CodeDNA
|
|
417
|
+
|
|
418
|
+
'use server';
|
|
419
|
+
|
|
420
|
+
import { db } from '../db/client';
|
|
421
|
+
import { {{ entity.name | lower }}, New{{ entity.name }} } from '../db/schema/{{ entity.name | lower }}';
|
|
422
|
+
import { eq } from 'drizzle-orm';
|
|
423
|
+
import { revalidatePath } from '@tanstack/react-router';
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Create new {{ entity.name }}
|
|
427
|
+
*/
|
|
428
|
+
export async function create{{ entity.name }}(data: New{{ entity.name }}) {
|
|
429
|
+
const result = await db.insert({{ entity.name | lower }}).values(data).returning();
|
|
430
|
+
revalidatePath('/{{ entity.name | lower }}');
|
|
431
|
+
return result[0];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Update {{ entity.name }}
|
|
436
|
+
*/
|
|
437
|
+
export async function update{{ entity.name }}(id: number, data: Partial<New{{ entity.name }}>) {
|
|
438
|
+
const result = await db
|
|
439
|
+
.update({{ entity.name | lower }})
|
|
440
|
+
.set({ ...data, updatedAt: new Date() })
|
|
441
|
+
.where(eq({{ entity.name | lower }}.id, id))
|
|
442
|
+
.returning();
|
|
443
|
+
|
|
444
|
+
revalidatePath('/{{ entity.name | lower }}');
|
|
445
|
+
revalidatePath(\`/{{ entity.name | lower }}/\${id}\`);
|
|
446
|
+
return result[0];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Delete {{ entity.name }}
|
|
451
|
+
*/
|
|
452
|
+
export async function delete{{ entity.name }}(id: number) {
|
|
453
|
+
await db.delete({{ entity.name | lower }}).where(eq({{ entity.name | lower }}.id, id));
|
|
454
|
+
revalidatePath('/{{ entity.name | lower }}');
|
|
455
|
+
}
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
getIndexRouteTemplate() {
|
|
459
|
+
return `// {{ entity.name }} List Route
|
|
460
|
+
// Generated by CodeDNA
|
|
461
|
+
|
|
462
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
463
|
+
import { getAll{{ entity.name | plural }} } from '../../queries/{{ entity.name | lower }}';
|
|
464
|
+
import { {{ entity.name }}List } from '../../components/{{ entity.name }}List';
|
|
465
|
+
|
|
466
|
+
export const Route = createFileRoute('/{{ entity.name | lower }}/')({
|
|
467
|
+
loader: async () => {
|
|
468
|
+
const items = await getAll{{ entity.name | plural }}();
|
|
469
|
+
return { items };
|
|
470
|
+
},
|
|
471
|
+
component: {{ entity.name }}IndexPage,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
function {{ entity.name }}IndexPage() {
|
|
475
|
+
const { items } = Route.useLoaderData();
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
<div className="container mx-auto p-6">
|
|
479
|
+
<div className="flex justify-between items-center mb-6">
|
|
480
|
+
<h1 className="text-3xl font-bold">{{ entity.name | plural }}</h1>
|
|
481
|
+
<a
|
|
482
|
+
href="/{{ entity.name | lower }}/create"
|
|
483
|
+
className="btn btn-primary"
|
|
484
|
+
>
|
|
485
|
+
Create {{ entity.name }}
|
|
486
|
+
</a>
|
|
487
|
+
</div>
|
|
488
|
+
<{{ entity.name }}List items={items} />
|
|
489
|
+
</div>
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
`;
|
|
493
|
+
}
|
|
494
|
+
getDetailRouteTemplate() {
|
|
495
|
+
return `// {{ entity.name }} Detail Route
|
|
496
|
+
// Generated by CodeDNA
|
|
497
|
+
|
|
498
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
499
|
+
import { get{{ entity.name }}ById } from '../../queries/{{ entity.name | lower }}';
|
|
500
|
+
import { notFound } from '@tanstack/react-router';
|
|
501
|
+
|
|
502
|
+
export const Route = createFileRoute('/{{ entity.name | lower }}/$id')({
|
|
503
|
+
loader: async ({ params }) => {
|
|
504
|
+
const item = await get{{ entity.name }}ById(Number(params.id));
|
|
505
|
+
if (!item) throw notFound();
|
|
506
|
+
return { item };
|
|
507
|
+
},
|
|
508
|
+
component: {{ entity.name }}DetailPage,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
function {{ entity.name }}DetailPage() {
|
|
512
|
+
const { item } = Route.useLoaderData();
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<div className="container mx-auto p-6">
|
|
516
|
+
<h1 className="text-3xl font-bold mb-6">{{ entity.name }} Details</h1>
|
|
517
|
+
<div className="card">
|
|
518
|
+
{% for field in entity.fields %}
|
|
519
|
+
<div className="mb-4">
|
|
520
|
+
<label className="font-semibold">{{ field.name | capitalize }}:</label>
|
|
521
|
+
<p>{item.{{ field.name }}}</p>
|
|
522
|
+
</div>
|
|
523
|
+
{% endfor %}
|
|
524
|
+
</div>
|
|
525
|
+
<div className="mt-6 flex gap-4">
|
|
526
|
+
<a href="/{{ entity.name | lower }}/{item.id}/edit" className="btn btn-secondary">
|
|
527
|
+
Edit
|
|
528
|
+
</a>
|
|
529
|
+
<a href="/{{ entity.name | lower }}" className="btn">
|
|
530
|
+
Back to List
|
|
531
|
+
</a>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
`;
|
|
537
|
+
}
|
|
538
|
+
getCreateRouteTemplate() {
|
|
539
|
+
return `// {{ entity.name }} Create Route
|
|
540
|
+
// Generated by CodeDNA
|
|
541
|
+
|
|
542
|
+
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
543
|
+
import { {{ entity.name }}Form } from '../../components/{{ entity.name }}Form';
|
|
544
|
+
import { create{{ entity.name }} } from '../../mutations/{{ entity.name | lower }}';
|
|
545
|
+
|
|
546
|
+
export const Route = createFileRoute('/{{ entity.name | lower }}/create')({
|
|
547
|
+
component: {{ entity.name }}CreatePage,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
function {{ entity.name }}CreatePage() {
|
|
551
|
+
const navigate = useNavigate();
|
|
552
|
+
|
|
553
|
+
const handleSubmit = async (data: any) => {
|
|
554
|
+
const created = await create{{ entity.name }}(data);
|
|
555
|
+
navigate({ to: '/{{ entity.name | lower }}/$id', params: { id: created.id.toString() } });
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<div className="container mx-auto p-6">
|
|
560
|
+
<h1 className="text-3xl font-bold mb-6">Create {{ entity.name }}</h1>
|
|
561
|
+
<{{ entity.name }}Form onSubmit={handleSubmit} />
|
|
562
|
+
</div>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
`;
|
|
566
|
+
}
|
|
567
|
+
getEditRouteTemplate() {
|
|
568
|
+
return `// {{ entity.name }} Edit Route
|
|
569
|
+
// Generated by CodeDNA
|
|
570
|
+
|
|
571
|
+
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
572
|
+
import { get{{ entity.name }}ById } from '../../../queries/{{ entity.name | lower }}';
|
|
573
|
+
import { update{{ entity.name }} } from '../../../mutations/{{ entity.name | lower }}';
|
|
574
|
+
import { {{ entity.name }}Form } from '../../../components/{{ entity.name }}Form';
|
|
575
|
+
import { notFound } from '@tanstack/react-router';
|
|
576
|
+
|
|
577
|
+
export const Route = createFileRoute('/{{ entity.name | lower }}/$id/edit')({
|
|
578
|
+
loader: async ({ params }) => {
|
|
579
|
+
const item = await get{{ entity.name }}ById(Number(params.id));
|
|
580
|
+
if (!item) throw notFound();
|
|
581
|
+
return { item };
|
|
582
|
+
},
|
|
583
|
+
component: {{ entity.name }}EditPage,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
function {{ entity.name }}EditPage() {
|
|
587
|
+
const { item } = Route.useLoaderData();
|
|
588
|
+
const navigate = useNavigate();
|
|
589
|
+
|
|
590
|
+
const handleSubmit = async (data: any) => {
|
|
591
|
+
await update{{ entity.name }}(item.id, data);
|
|
592
|
+
navigate({ to: '/{{ entity.name | lower }}/$id', params: { id: item.id.toString() } });
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
return (
|
|
596
|
+
<div className="container mx-auto p-6">
|
|
597
|
+
<h1 className="text-3xl font-bold mb-6">Edit {{ entity.name }}</h1>
|
|
598
|
+
<{{ entity.name }}Form initialData={item} onSubmit={handleSubmit} />
|
|
599
|
+
</div>
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
`;
|
|
603
|
+
}
|
|
604
|
+
getListComponentTemplate() {
|
|
605
|
+
return `// {{ entity.name }} List Component
|
|
606
|
+
// Generated by CodeDNA
|
|
607
|
+
|
|
608
|
+
import { {{ entity.name }} } from '../types/{{ entity.name | lower }}';
|
|
609
|
+
|
|
610
|
+
interface {{ entity.name }}ListProps {
|
|
611
|
+
items: {{ entity.name }}[];
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function {{ entity.name }}List({ items }: {{ entity.name }}ListProps) {
|
|
615
|
+
return (
|
|
616
|
+
<div className="overflow-x-auto">
|
|
617
|
+
<table className="table w-full">
|
|
618
|
+
<thead>
|
|
619
|
+
<tr>
|
|
620
|
+
<th>ID</th>
|
|
621
|
+
{% for field in entity.fields %}
|
|
622
|
+
<th>{{ field.name | capitalize }}</th>
|
|
623
|
+
{% endfor %}
|
|
624
|
+
<th>Actions</th>
|
|
625
|
+
</tr>
|
|
626
|
+
</thead>
|
|
627
|
+
<tbody>
|
|
628
|
+
{items.map((item) => (
|
|
629
|
+
<tr key={item.id}>
|
|
630
|
+
<td>{item.id}</td>
|
|
631
|
+
{% for field in entity.fields %}
|
|
632
|
+
<td>{item.{{ field.name }}}</td>
|
|
633
|
+
{% endfor %}
|
|
634
|
+
<td>
|
|
635
|
+
<a href={\`/{{ entity.name | lower }}/\${item.id}\`} className="btn btn-sm">
|
|
636
|
+
View
|
|
637
|
+
</a>
|
|
638
|
+
</td>
|
|
639
|
+
</tr>
|
|
640
|
+
))}
|
|
641
|
+
</tbody>
|
|
642
|
+
</table>
|
|
643
|
+
</div>
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
`;
|
|
647
|
+
}
|
|
648
|
+
getFormComponentTemplate() {
|
|
649
|
+
return `// {{ entity.name }} Form Component
|
|
650
|
+
// Generated by CodeDNA
|
|
651
|
+
|
|
652
|
+
import { useState } from 'react';
|
|
653
|
+
import { {{ entity.name }} } from '../types/{{ entity.name | lower }}';
|
|
654
|
+
|
|
655
|
+
interface {{ entity.name }}FormProps {
|
|
656
|
+
initialData?: Partial<{{ entity.name }}>;
|
|
657
|
+
onSubmit: (data: any) => Promise<void>;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function {{ entity.name }}Form({ initialData, onSubmit }: {{ entity.name }}FormProps) {
|
|
661
|
+
const [formData, setFormData] = useState(initialData || {});
|
|
662
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
663
|
+
|
|
664
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
665
|
+
e.preventDefault();
|
|
666
|
+
setIsSubmitting(true);
|
|
667
|
+
try {
|
|
668
|
+
await onSubmit(formData);
|
|
669
|
+
} finally {
|
|
670
|
+
setIsSubmitting(false);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
return (
|
|
675
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
676
|
+
{% for field in entity.fields %}
|
|
677
|
+
{% if field.type.kind === 'primitive' %}
|
|
678
|
+
<div>
|
|
679
|
+
<label className="label">{{ field.name | capitalize }}</label>
|
|
680
|
+
<input
|
|
681
|
+
type="{% if field.type.value === 'email' %}email{% elif field.type.value === 'url' %}url{% elif field.type.value === 'integer' or field.type.value === 'decimal' %}number{% else %}text{% endif %}"
|
|
682
|
+
className="input w-full"
|
|
683
|
+
value={formData.{{ field.name }} || ''}
|
|
684
|
+
onChange={(e) => setFormData({ ...formData, {{ field.name }}: e.target.value })}
|
|
685
|
+
{% if field | hasConstraint('required') %}required{% endif %}
|
|
686
|
+
/>
|
|
687
|
+
</div>
|
|
688
|
+
{% elif field.type.kind === 'enum' %}
|
|
689
|
+
<div>
|
|
690
|
+
<label className="label">{{ field.name | capitalize }}</label>
|
|
691
|
+
<select
|
|
692
|
+
className="select w-full"
|
|
693
|
+
value={formData.{{ field.name }} || ''}
|
|
694
|
+
onChange={(e) => setFormData({ ...formData, {{ field.name }}: e.target.value })}
|
|
695
|
+
{% if field | hasConstraint('required') %}required{% endif %}
|
|
696
|
+
>
|
|
697
|
+
<option value="">Select {{ field.name }}</option>
|
|
698
|
+
{% for value in field.type.values %}
|
|
699
|
+
<option value="{{ value }}">{{ value | capitalize }}</option>
|
|
700
|
+
{% endfor %}
|
|
701
|
+
</select>
|
|
702
|
+
</div>
|
|
703
|
+
{% endif %}
|
|
704
|
+
{% endfor %}
|
|
705
|
+
|
|
706
|
+
<div className="flex gap-4">
|
|
707
|
+
<button type="submit" className="btn btn-primary" disabled={isSubmitting}>
|
|
708
|
+
{isSubmitting ? 'Saving...' : 'Save'}
|
|
709
|
+
</button>
|
|
710
|
+
<a href="/{{ entity.name | lower }}" className="btn">
|
|
711
|
+
Cancel
|
|
712
|
+
</a>
|
|
713
|
+
</div>
|
|
714
|
+
</form>
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
`;
|
|
718
|
+
}
|
|
719
|
+
getTypesTemplate() {
|
|
720
|
+
return `// {{ entity.name }} Types
|
|
721
|
+
// Generated by CodeDNA
|
|
722
|
+
|
|
723
|
+
export interface {{ entity.name }} {
|
|
724
|
+
id: number;
|
|
725
|
+
{% for field in entity.fields %}
|
|
726
|
+
{{ field.name }}: {{ field.type | tsType }}{% if not (field | hasConstraint('required')) %} | null{% endif %};
|
|
727
|
+
{% endfor %}
|
|
728
|
+
createdAt: Date;
|
|
729
|
+
updatedAt: Date;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export type New{{ entity.name }} = Omit<{{ entity.name }}, 'id' | 'createdAt' | 'updatedAt'>;
|
|
733
|
+
`;
|
|
734
|
+
}
|
|
735
|
+
getWranglerConfigTemplate() {
|
|
736
|
+
return `# Wrangler Configuration for Cloudflare
|
|
737
|
+
# Generated by CodeDNA
|
|
738
|
+
|
|
739
|
+
name = "{{ options.projectName || 'tanstack-app' }}"
|
|
740
|
+
main = "src/index.ts"
|
|
741
|
+
compatibility_date = "2024-12-01"
|
|
742
|
+
|
|
743
|
+
[[ d1_databases ]]
|
|
744
|
+
binding = "DB"
|
|
745
|
+
database_name = "{{ options.database || 'app' }}"
|
|
746
|
+
database_id = "{{ options.databaseId || 'YOUR_DATABASE_ID' }}"
|
|
747
|
+
|
|
748
|
+
[[ kv_namespaces ]]
|
|
749
|
+
binding = "KV"
|
|
750
|
+
id = "{{ options.kvId || 'YOUR_KV_ID' }}"
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
getDrizzleConfigTemplate() {
|
|
754
|
+
return `// Drizzle Configuration
|
|
755
|
+
// Generated by CodeDNA
|
|
756
|
+
|
|
757
|
+
import type { Config } from 'drizzle-kit';
|
|
758
|
+
|
|
759
|
+
export default {
|
|
760
|
+
schema: './src/db/schema',
|
|
761
|
+
out: './drizzle/migrations',
|
|
762
|
+
{% if options.database === 'postgresql' %}
|
|
763
|
+
driver: 'pg',
|
|
764
|
+
{% elif options.database === 'mysql' %}
|
|
765
|
+
driver: 'mysql2',
|
|
766
|
+
{% elif options.database === 'sqlite' %}
|
|
767
|
+
driver: 'better-sqlite',
|
|
768
|
+
{% endif %}
|
|
769
|
+
dbCredentials: {
|
|
770
|
+
{% if options.database === 'postgresql' %}
|
|
771
|
+
connectionString: process.env.DATABASE_URL!,
|
|
772
|
+
{% elif options.database === 'mysql' %}
|
|
773
|
+
connectionString: process.env.DATABASE_URL!,
|
|
774
|
+
{% elif options.database === 'sqlite' %}
|
|
775
|
+
url: process.env.DATABASE_URL!,
|
|
776
|
+
{% endif %}
|
|
777
|
+
},
|
|
778
|
+
} satisfies Config;
|
|
779
|
+
`;
|
|
780
|
+
}
|
|
781
|
+
getPackageJsonTemplate() {
|
|
782
|
+
return `{
|
|
783
|
+
"name": "{{ options.projectName || 'tanstack-drizzle-app' }}",
|
|
784
|
+
"version": "0.1.0",
|
|
785
|
+
"private": true,
|
|
786
|
+
"type": "module",
|
|
787
|
+
"scripts": {
|
|
788
|
+
"dev": "vinxi dev",
|
|
789
|
+
"build": "vinxi build",
|
|
790
|
+
"start": "vinxi start",
|
|
791
|
+
"db:generate": "drizzle-kit generate",
|
|
792
|
+
"db:migrate": "drizzle-kit migrate",
|
|
793
|
+
"db:push": "drizzle-kit push",
|
|
794
|
+
"db:studio": "drizzle-kit studio"
|
|
795
|
+
},
|
|
796
|
+
"dependencies": {
|
|
797
|
+
"@tanstack/react-router": "^1.80.0",
|
|
798
|
+
"@tanstack/start": "^1.80.0",
|
|
799
|
+
"react": "^19.0.0",
|
|
800
|
+
"react-dom": "^19.0.0",
|
|
801
|
+
{% if options.database === 'postgresql' %}
|
|
802
|
+
"drizzle-orm": "^0.38.0",
|
|
803
|
+
"postgres": "^3.4.5",
|
|
804
|
+
{% elif options.database === 'mysql' %}
|
|
805
|
+
"drizzle-orm": "^0.38.0",
|
|
806
|
+
"mysql2": "^3.11.5",
|
|
807
|
+
{% elif options.database === 'sqlite' %}
|
|
808
|
+
"drizzle-orm": "^0.38.0",
|
|
809
|
+
"better-sqlite3": "^11.8.1",
|
|
810
|
+
{% endif %}
|
|
811
|
+
"vinxi": "^0.4.0"
|
|
812
|
+
},
|
|
813
|
+
"devDependencies": {
|
|
814
|
+
"@types/node": "^22.10.2",
|
|
815
|
+
"@types/react": "^19.0.0",
|
|
816
|
+
"@types/react-dom": "^19.0.0",
|
|
817
|
+
"drizzle-kit": "^0.29.0",
|
|
818
|
+
"typescript": "^5.7.2",
|
|
819
|
+
"vite": "^6.0.0"
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
`;
|
|
823
|
+
}
|
|
824
|
+
}
|