brizzle 0.2.8 → 0.2.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 +18 -0
- package/dist/index.js +36 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,6 +181,24 @@ Next steps:
|
|
|
181
181
|
- Next.js project with App Router
|
|
182
182
|
- Drizzle ORM configured
|
|
183
183
|
|
|
184
|
+
## Roadmap
|
|
185
|
+
|
|
186
|
+
- [ ] **Drizzle init** - `brizzle init` to set up Drizzle ORM, database config, and db connection
|
|
187
|
+
- [ ] **Authentication** - `brizzle auth` to generate [better-auth](https://www.better-auth.com/) setup with user model and sign-in/sign-up pages
|
|
188
|
+
- [ ] **Zod schemas** - Generate validation schemas for forms and API routes
|
|
189
|
+
- [ ] **Indexes** - Support for `name:string:index` field modifier
|
|
190
|
+
- [ ] **Default values** - Support for `status:string:default:active`
|
|
191
|
+
- [ ] **Soft deletes** - Add `--soft-delete` flag for `deletedAt` timestamp
|
|
192
|
+
- [ ] **Pagination** - Add pagination to list pages and API routes
|
|
193
|
+
- [ ] **Search & filtering** - Generate search/filter UI for list pages
|
|
194
|
+
- [ ] **Seed generator** - `brizzle seed <model>` to generate seed data files
|
|
195
|
+
- [ ] **Relations helper** - Better syntax for has-many/belongs-to relationships
|
|
196
|
+
- [ ] **Custom templates** - Allow overriding templates via `.brizzle/` directory
|
|
197
|
+
- [ ] **Interactive mode** - `brizzle new` wizard for step-by-step model creation
|
|
198
|
+
- [ ] **Import cleanup** - Remove unused imports when destroying models
|
|
199
|
+
|
|
200
|
+
Have a feature request? [Open an issue](https://github.com/mantaskaveckas/brizzle/issues)
|
|
201
|
+
|
|
184
202
|
## License
|
|
185
203
|
|
|
186
204
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -945,31 +945,31 @@ export default async function ${pascalPlural}Page() {
|
|
|
945
945
|
return (
|
|
946
946
|
<div className="mx-auto max-w-3xl px-6 py-12">
|
|
947
947
|
<div className="mb-10 flex items-center justify-between">
|
|
948
|
-
<h1 className="text-2xl font-semibold text-
|
|
948
|
+
<h1 className="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">${pascalPlural}</h1>
|
|
949
949
|
<Link
|
|
950
950
|
href="/${kebabPlural}/new"
|
|
951
|
-
className="rounded-
|
|
951
|
+
className="flex h-10 items-center rounded-full bg-zinc-900 px-4 text-sm font-medium text-white transition-colors hover:bg-zinc-700 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
|
952
952
|
>
|
|
953
953
|
New ${pascalName}
|
|
954
954
|
</Link>
|
|
955
955
|
</div>
|
|
956
956
|
|
|
957
957
|
{${camelName}s.length === 0 ? (
|
|
958
|
-
<p className="text-
|
|
958
|
+
<p className="text-zinc-500 dark:text-zinc-400">No ${camelName}s yet.</p>
|
|
959
959
|
) : (
|
|
960
|
-
<div className="divide-y divide-
|
|
960
|
+
<div className="divide-y divide-zinc-100 dark:divide-zinc-800">
|
|
961
961
|
{${camelName}s.map((${camelName}) => (
|
|
962
962
|
<div
|
|
963
963
|
key={${camelName}.id}
|
|
964
964
|
className="flex items-center justify-between py-4"
|
|
965
965
|
>
|
|
966
|
-
<Link href={\`/${kebabPlural}/\${${camelName}.id}\`} className="font-medium text-
|
|
966
|
+
<Link href={\`/${kebabPlural}/\${${camelName}.id}\`} className="font-medium text-zinc-900 hover:text-zinc-600 dark:text-zinc-50 dark:hover:text-zinc-300">
|
|
967
967
|
{${camelName}.${displayField}}
|
|
968
968
|
</Link>
|
|
969
969
|
<div className="flex gap-4 text-sm">
|
|
970
970
|
<Link
|
|
971
971
|
href={\`/${kebabPlural}/\${${camelName}.id}/edit\`}
|
|
972
|
-
className="text-
|
|
972
|
+
className="text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-50"
|
|
973
973
|
>
|
|
974
974
|
Edit
|
|
975
975
|
</Link>
|
|
@@ -979,7 +979,7 @@ export default async function ${pascalPlural}Page() {
|
|
|
979
979
|
await delete${pascalName}(${camelName}.id);
|
|
980
980
|
}}
|
|
981
981
|
>
|
|
982
|
-
<button type="submit" className="text-
|
|
982
|
+
<button type="submit" className="text-zinc-500 hover:text-red-600 dark:text-zinc-400 dark:hover:text-red-400">
|
|
983
983
|
Delete
|
|
984
984
|
</button>
|
|
985
985
|
</form>
|
|
@@ -1011,7 +1011,7 @@ ${fields.map((f) => ` ${f.name}: ${formDataValue(f)},`).join("\n")}
|
|
|
1011
1011
|
|
|
1012
1012
|
return (
|
|
1013
1013
|
<div className="mx-auto max-w-xl px-6 py-12">
|
|
1014
|
-
<h1 className="mb-8 text-2xl font-semibold text-
|
|
1014
|
+
<h1 className="mb-8 text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">New ${pascalName}</h1>
|
|
1015
1015
|
|
|
1016
1016
|
<form action={handleCreate} className="space-y-5">
|
|
1017
1017
|
${fields.map((f) => generateFormField(f, camelName)).join("\n\n")}
|
|
@@ -1019,13 +1019,13 @@ ${fields.map((f) => generateFormField(f, camelName)).join("\n\n")}
|
|
|
1019
1019
|
<div className="flex gap-3 pt-4">
|
|
1020
1020
|
<button
|
|
1021
1021
|
type="submit"
|
|
1022
|
-
className="rounded-
|
|
1022
|
+
className="flex h-10 items-center rounded-full bg-zinc-900 px-4 text-sm font-medium text-white transition-colors hover:bg-zinc-700 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
|
1023
1023
|
>
|
|
1024
1024
|
Create ${pascalName}
|
|
1025
1025
|
</button>
|
|
1026
1026
|
<Link
|
|
1027
1027
|
href="/${kebabPlural}"
|
|
1028
|
-
className="rounded-
|
|
1028
|
+
className="flex h-10 items-center rounded-full border border-zinc-200 px-4 text-sm font-medium text-zinc-600 transition-colors hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
1029
1029
|
>
|
|
1030
1030
|
Cancel
|
|
1031
1031
|
</Link>
|
|
@@ -1063,33 +1063,33 @@ export default async function ${pascalName}Page({
|
|
|
1063
1063
|
return (
|
|
1064
1064
|
<div className="mx-auto max-w-xl px-6 py-12">
|
|
1065
1065
|
<div className="mb-8 flex items-center justify-between">
|
|
1066
|
-
<h1 className="text-2xl font-semibold text-
|
|
1066
|
+
<h1 className="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">${pascalName}</h1>
|
|
1067
1067
|
<div className="flex gap-3">
|
|
1068
1068
|
<Link
|
|
1069
1069
|
href={\`/${kebabPlural}/\${${camelName}.id}/edit\`}
|
|
1070
|
-
className="rounded-
|
|
1070
|
+
className="flex h-10 items-center rounded-full bg-zinc-900 px-4 text-sm font-medium text-white transition-colors hover:bg-zinc-700 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
|
1071
1071
|
>
|
|
1072
1072
|
Edit
|
|
1073
1073
|
</Link>
|
|
1074
1074
|
<Link
|
|
1075
1075
|
href="/${kebabPlural}"
|
|
1076
|
-
className="rounded-
|
|
1076
|
+
className="flex h-10 items-center rounded-full border border-zinc-200 px-4 text-sm font-medium text-zinc-600 transition-colors hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
1077
1077
|
>
|
|
1078
1078
|
Back
|
|
1079
1079
|
</Link>
|
|
1080
1080
|
</div>
|
|
1081
1081
|
</div>
|
|
1082
1082
|
|
|
1083
|
-
<dl className="divide-y divide-
|
|
1083
|
+
<dl className="divide-y divide-zinc-100 dark:divide-zinc-800">
|
|
1084
1084
|
${fields.map(
|
|
1085
1085
|
(f) => ` <div className="py-3">
|
|
1086
|
-
<dt className="text-sm text-
|
|
1087
|
-
<dd className="mt-1 text-
|
|
1086
|
+
<dt className="text-sm text-zinc-500 dark:text-zinc-400">${toPascalCase(f.name)}</dt>
|
|
1087
|
+
<dd className="mt-1 text-zinc-900 dark:text-zinc-50">{${camelName}.${f.name}}</dd>
|
|
1088
1088
|
</div>`
|
|
1089
1089
|
).join("\n")}${options.noTimestamps ? "" : `
|
|
1090
1090
|
<div className="py-3">
|
|
1091
|
-
<dt className="text-sm text-
|
|
1092
|
-
<dd className="mt-1 text-
|
|
1091
|
+
<dt className="text-sm text-zinc-500 dark:text-zinc-400">Created At</dt>
|
|
1092
|
+
<dd className="mt-1 text-zinc-900 dark:text-zinc-50">{${camelName}.createdAt.toLocaleString()}</dd>
|
|
1093
1093
|
</div>`}
|
|
1094
1094
|
</dl>
|
|
1095
1095
|
</div>
|
|
@@ -1134,7 +1134,7 @@ ${fields.map((f) => ` ${f.name}: ${formDataValue(f)},`).join("\n")}
|
|
|
1134
1134
|
|
|
1135
1135
|
return (
|
|
1136
1136
|
<div className="mx-auto max-w-xl px-6 py-12">
|
|
1137
|
-
<h1 className="mb-8 text-2xl font-semibold text-
|
|
1137
|
+
<h1 className="mb-8 text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">Edit ${pascalName}</h1>
|
|
1138
1138
|
|
|
1139
1139
|
<form action={handleUpdate} className="space-y-5">
|
|
1140
1140
|
${fields.map((f) => generateFormField(f, camelName, true)).join("\n\n")}
|
|
@@ -1142,13 +1142,13 @@ ${fields.map((f) => generateFormField(f, camelName, true)).join("\n\n")}
|
|
|
1142
1142
|
<div className="flex gap-3 pt-4">
|
|
1143
1143
|
<button
|
|
1144
1144
|
type="submit"
|
|
1145
|
-
className="rounded-
|
|
1145
|
+
className="flex h-10 items-center rounded-full bg-zinc-900 px-4 text-sm font-medium text-white transition-colors hover:bg-zinc-700 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
|
1146
1146
|
>
|
|
1147
1147
|
Update ${pascalName}
|
|
1148
1148
|
</button>
|
|
1149
1149
|
<Link
|
|
1150
1150
|
href="/${kebabPlural}"
|
|
1151
|
-
className="rounded-
|
|
1151
|
+
className="flex h-10 items-center rounded-full border border-zinc-200 px-4 text-sm font-medium text-zinc-600 transition-colors hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
|
1152
1152
|
>
|
|
1153
1153
|
Cancel
|
|
1154
1154
|
</Link>
|
|
@@ -1163,7 +1163,7 @@ function createFieldContext(field, camelName, withDefault) {
|
|
|
1163
1163
|
return {
|
|
1164
1164
|
field,
|
|
1165
1165
|
label: toPascalCase(field.name),
|
|
1166
|
-
optionalLabel: field.nullable ? ` <span className="text-
|
|
1166
|
+
optionalLabel: field.nullable ? ` <span className="text-zinc-400 dark:text-zinc-500">(optional)</span>` : "",
|
|
1167
1167
|
required: field.nullable ? "" : " required",
|
|
1168
1168
|
defaultValue: withDefault ? ` defaultValue={${camelName}.${field.name}}` : ""
|
|
1169
1169
|
};
|
|
@@ -1173,14 +1173,14 @@ function generateTextareaField(ctx) {
|
|
|
1173
1173
|
const rows = field.type === "json" ? 6 : 4;
|
|
1174
1174
|
const placeholder = field.type === "json" ? ` placeholder="{}"` : "";
|
|
1175
1175
|
return ` <div>
|
|
1176
|
-
<label htmlFor="${field.name}" className="block text-sm font-medium text-
|
|
1176
|
+
<label htmlFor="${field.name}" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1177
1177
|
${label}${optionalLabel}
|
|
1178
1178
|
</label>
|
|
1179
1179
|
<textarea
|
|
1180
1180
|
id="${field.name}"
|
|
1181
1181
|
name="${field.name}"
|
|
1182
1182
|
rows={${rows}}
|
|
1183
|
-
className="mt-1.5 block w-full rounded-lg border border-
|
|
1183
|
+
className="mt-1.5 block w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500 resize-none"${defaultValue}${placeholder}${required}
|
|
1184
1184
|
/>
|
|
1185
1185
|
</div>`;
|
|
1186
1186
|
}
|
|
@@ -1192,9 +1192,9 @@ function generateCheckboxField(ctx, camelName, withDefault) {
|
|
|
1192
1192
|
type="checkbox"
|
|
1193
1193
|
id="${field.name}"
|
|
1194
1194
|
name="${field.name}"
|
|
1195
|
-
className="h-4 w-4 rounded border-
|
|
1195
|
+
className="h-4 w-4 rounded border-zinc-300 text-zinc-900 focus:ring-0 focus:ring-offset-0 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-50"${defaultChecked}
|
|
1196
1196
|
/>
|
|
1197
|
-
<label htmlFor="${field.name}" className="text-sm font-medium text-
|
|
1197
|
+
<label htmlFor="${field.name}" className="text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1198
1198
|
${label}
|
|
1199
1199
|
</label>
|
|
1200
1200
|
</div>`;
|
|
@@ -1204,14 +1204,14 @@ function generateNumberField(ctx, step) {
|
|
|
1204
1204
|
const stepAttr = step ? `
|
|
1205
1205
|
step="${step}"` : "";
|
|
1206
1206
|
return ` <div>
|
|
1207
|
-
<label htmlFor="${field.name}" className="block text-sm font-medium text-
|
|
1207
|
+
<label htmlFor="${field.name}" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1208
1208
|
${label}${optionalLabel}
|
|
1209
1209
|
</label>
|
|
1210
1210
|
<input
|
|
1211
1211
|
type="number"${stepAttr}
|
|
1212
1212
|
id="${field.name}"
|
|
1213
1213
|
name="${field.name}"
|
|
1214
|
-
className="mt-1.5 block w-full rounded-lg border border-
|
|
1214
|
+
className="mt-1.5 block w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500"${defaultValue}${required}
|
|
1215
1215
|
/>
|
|
1216
1216
|
</div>`;
|
|
1217
1217
|
}
|
|
@@ -1219,14 +1219,14 @@ function generateDateField(ctx, camelName, withDefault) {
|
|
|
1219
1219
|
const { field, label, optionalLabel, required } = ctx;
|
|
1220
1220
|
const dateDefault = withDefault ? ` defaultValue={${camelName}.${field.name}?.toISOString().split("T")[0]}` : "";
|
|
1221
1221
|
return ` <div>
|
|
1222
|
-
<label htmlFor="${field.name}" className="block text-sm font-medium text-
|
|
1222
|
+
<label htmlFor="${field.name}" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1223
1223
|
${label}${optionalLabel}
|
|
1224
1224
|
</label>
|
|
1225
1225
|
<input
|
|
1226
1226
|
type="date"
|
|
1227
1227
|
id="${field.name}"
|
|
1228
1228
|
name="${field.name}"
|
|
1229
|
-
className="mt-1.5 block w-full rounded-lg border border-
|
|
1229
|
+
className="mt-1.5 block w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500"${dateDefault}${required}
|
|
1230
1230
|
/>
|
|
1231
1231
|
</div>`;
|
|
1232
1232
|
}
|
|
@@ -1234,14 +1234,14 @@ function generateDatetimeField(ctx, camelName, withDefault) {
|
|
|
1234
1234
|
const { field, label, optionalLabel, required } = ctx;
|
|
1235
1235
|
const dateDefault = withDefault ? ` defaultValue={${camelName}.${field.name}?.toISOString().slice(0, 16)}` : "";
|
|
1236
1236
|
return ` <div>
|
|
1237
|
-
<label htmlFor="${field.name}" className="block text-sm font-medium text-
|
|
1237
|
+
<label htmlFor="${field.name}" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1238
1238
|
${label}${optionalLabel}
|
|
1239
1239
|
</label>
|
|
1240
1240
|
<input
|
|
1241
1241
|
type="datetime-local"
|
|
1242
1242
|
id="${field.name}"
|
|
1243
1243
|
name="${field.name}"
|
|
1244
|
-
className="mt-1.5 block w-full rounded-lg border border-
|
|
1244
|
+
className="mt-1.5 block w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500"${dateDefault}${required}
|
|
1245
1245
|
/>
|
|
1246
1246
|
</div>`;
|
|
1247
1247
|
}
|
|
@@ -1249,13 +1249,13 @@ function generateSelectField(ctx) {
|
|
|
1249
1249
|
const { field, label, optionalLabel, required, defaultValue } = ctx;
|
|
1250
1250
|
const options = field.enumValues.map((v) => ` <option value="${escapeString(v)}">${toPascalCase(v)}</option>`).join("\n");
|
|
1251
1251
|
return ` <div>
|
|
1252
|
-
<label htmlFor="${field.name}" className="block text-sm font-medium text-
|
|
1252
|
+
<label htmlFor="${field.name}" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1253
1253
|
${label}${optionalLabel}
|
|
1254
1254
|
</label>
|
|
1255
1255
|
<select
|
|
1256
1256
|
id="${field.name}"
|
|
1257
1257
|
name="${field.name}"
|
|
1258
|
-
className="mt-1.5 block w-full rounded-lg border border-
|
|
1258
|
+
className="mt-1.5 block w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-zinc-900 focus:border-zinc-400 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:focus:border-zinc-500"${defaultValue}${required}
|
|
1259
1259
|
>
|
|
1260
1260
|
${options}
|
|
1261
1261
|
</select>
|
|
@@ -1264,14 +1264,14 @@ ${options}
|
|
|
1264
1264
|
function generateTextField(ctx) {
|
|
1265
1265
|
const { field, label, optionalLabel, required, defaultValue } = ctx;
|
|
1266
1266
|
return ` <div>
|
|
1267
|
-
<label htmlFor="${field.name}" className="block text-sm font-medium text-
|
|
1267
|
+
<label htmlFor="${field.name}" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
|
1268
1268
|
${label}${optionalLabel}
|
|
1269
1269
|
</label>
|
|
1270
1270
|
<input
|
|
1271
1271
|
type="text"
|
|
1272
1272
|
id="${field.name}"
|
|
1273
1273
|
name="${field.name}"
|
|
1274
|
-
className="mt-1.5 block w-full rounded-lg border border-
|
|
1274
|
+
className="mt-1.5 block w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-zinc-900 placeholder:text-zinc-400 focus:border-zinc-400 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-50 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500"${defaultValue}${required}
|
|
1275
1275
|
/>
|
|
1276
1276
|
</div>`;
|
|
1277
1277
|
}
|