@appweaver/cli 1.4.0 → 1.5.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/generate/generate-schema.js +69 -8
- package/generate/generate-types.js +7 -0
- package/package.json +1 -1
- package/skill/SKILL.md +764 -753
- package/skill/references/configuration.md +6 -5
- package/skill/references/resources.md +1587 -1424
- package/skill/references/security.md +3 -2
- package/skill/references/storage.md +29 -6
|
@@ -90,8 +90,11 @@ async function generateSchema(models, schemaPath, clientPath, quiet = false) {
|
|
|
90
90
|
scalars: createScalarsSchema(name, schema.config.scalars),
|
|
91
91
|
relations: createRelationsSchema(name, schema.config.relations, idColumnOf),
|
|
92
92
|
files: createFilesSchema(name, schema.config.files, idColumnOf('File')),
|
|
93
|
-
audit: createAuditSchema(name, authModel, schema.config.audit),
|
|
94
|
-
index:
|
|
93
|
+
audit: createAuditSchema(name, authModel, schema.config.audit, (0, common_1.hasSoftDelete)(schema.config)),
|
|
94
|
+
index: [
|
|
95
|
+
...createIndexSchema(schema.config.index),
|
|
96
|
+
...createIndexSchema(schema.config.unique, '@@unique')
|
|
97
|
+
],
|
|
95
98
|
tableName: schema.config.tableName
|
|
96
99
|
};
|
|
97
100
|
for (const [fieldName, scalar] of Object.entries(schema.config.scalars ?? {})) {
|
|
@@ -102,6 +105,7 @@ async function generateSchema(models, schemaPath, clientPath, quiet = false) {
|
|
|
102
105
|
}
|
|
103
106
|
// Resolve model relations to match Prisma schema relationship convention
|
|
104
107
|
const createdByAuthUser = [];
|
|
108
|
+
const deletedByAuthUser = [];
|
|
105
109
|
const fileFields = [];
|
|
106
110
|
for (const [name, model] of Object.entries(prismaModels)) {
|
|
107
111
|
const modelSchema = Object.entries(models).find(([key]) => key === name)?.[1];
|
|
@@ -173,7 +177,8 @@ async function generateSchema(models, schemaPath, clientPath, quiet = false) {
|
|
|
173
177
|
...(relationConfig.type === 'oneToOne' ? ['@unique'] : []),
|
|
174
178
|
...nativeTypeAttributes(idColumn.nativeType)
|
|
175
179
|
],
|
|
176
|
-
foreignKey: true
|
|
180
|
+
foreignKey: true,
|
|
181
|
+
skipIndex: relationConfig.index === false
|
|
177
182
|
});
|
|
178
183
|
}
|
|
179
184
|
}
|
|
@@ -188,6 +193,14 @@ async function generateSchema(models, schemaPath, clientPath, quiet = false) {
|
|
|
188
193
|
attributes: [`@relation("${referenceName}")`]
|
|
189
194
|
});
|
|
190
195
|
}
|
|
196
|
+
if (auditField.name === 'deletedBy') {
|
|
197
|
+
const referenceName = auditField.attributes?.[0].split('"')[1];
|
|
198
|
+
deletedByAuthUser.push({
|
|
199
|
+
name: `deleted${(0, common_1.plural)(name)}`,
|
|
200
|
+
type: `${name}[]`,
|
|
201
|
+
attributes: [`@relation("${referenceName}")`]
|
|
202
|
+
});
|
|
203
|
+
}
|
|
191
204
|
}
|
|
192
205
|
for (const fileField of model.files) {
|
|
193
206
|
const referenceName = fileField.attributes?.[0].split('"')[1];
|
|
@@ -200,11 +213,26 @@ async function generateSchema(models, schemaPath, clientPath, quiet = false) {
|
|
|
200
213
|
}
|
|
201
214
|
}
|
|
202
215
|
}
|
|
216
|
+
// Index the foreign key columns, which PostgreSQL and SQLite leave
|
|
217
|
+
// unindexed, so relation lookups and referential actions avoid table scans
|
|
218
|
+
for (const [name, model] of Object.entries(prismaModels)) {
|
|
219
|
+
const indexed = leadingIndexFields(models[name].config);
|
|
220
|
+
for (const field of [...model.relations, ...model.audit]) {
|
|
221
|
+
if (field.foreignKey &&
|
|
222
|
+
!field.skipIndex &&
|
|
223
|
+
!field.attributes?.includes('@unique') &&
|
|
224
|
+
!indexed.has(field.name)) {
|
|
225
|
+
indexed.add(field.name);
|
|
226
|
+
model.index.push(`@@index([${field.name}])`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
203
230
|
// Add auth model ownership relations
|
|
204
231
|
const authUserModel = prismaModels[authModel?.name ?? ''];
|
|
205
232
|
if (authUserModel) {
|
|
206
233
|
authUserModel.extra = {
|
|
207
|
-
'Ownership models referenced with createdById column': createdByAuthUser
|
|
234
|
+
'Ownership models referenced with createdById column': createdByAuthUser,
|
|
235
|
+
'Soft deleted models referenced with deletedById column': deletedByAuthUser
|
|
208
236
|
};
|
|
209
237
|
}
|
|
210
238
|
// Add file fields relations
|
|
@@ -509,6 +537,7 @@ function validateRelations(models) {
|
|
|
509
537
|
}
|
|
510
538
|
}
|
|
511
539
|
}
|
|
540
|
+
errors.push(...(0, common_1.softDeleteCascadeErrors)(models));
|
|
512
541
|
return errors;
|
|
513
542
|
}
|
|
514
543
|
function createRelationsSchema(modelName, relations = {}, idColumnOf = defaultIdColumn) {
|
|
@@ -565,7 +594,8 @@ function createRelationSchema(name, modelName, relation, relationIdColumn = defa
|
|
|
565
594
|
...(relation.type === 'oneToOne' ? ['@unique'] : []),
|
|
566
595
|
...nativeTypeAttributes(relationIdColumn.nativeType)
|
|
567
596
|
],
|
|
568
|
-
foreignKey: true
|
|
597
|
+
foreignKey: true,
|
|
598
|
+
skipIndex: relation.index === false
|
|
569
599
|
});
|
|
570
600
|
}
|
|
571
601
|
return relationFields;
|
|
@@ -605,7 +635,7 @@ function createFileSchema(name, modelName, file, fileIdColumn = defaultIdColumn(
|
|
|
605
635
|
}
|
|
606
636
|
return fileFields;
|
|
607
637
|
}
|
|
608
|
-
function createAuditSchema(modelName, authModel, audit = {}) {
|
|
638
|
+
function createAuditSchema(modelName, authModel, audit = {}, softDelete = false) {
|
|
609
639
|
const defaultAudit = {
|
|
610
640
|
updatedAt: true,
|
|
611
641
|
createdAt: true,
|
|
@@ -643,9 +673,30 @@ function createAuditSchema(modelName, authModel, audit = {}) {
|
|
|
643
673
|
foreignKey: true
|
|
644
674
|
});
|
|
645
675
|
}
|
|
676
|
+
if (softDelete) {
|
|
677
|
+
fields.push({
|
|
678
|
+
name: 'deletedAt',
|
|
679
|
+
type: 'DateTime?'
|
|
680
|
+
});
|
|
681
|
+
if (authModelName) {
|
|
682
|
+
fields.push({
|
|
683
|
+
name: 'deletedBy',
|
|
684
|
+
type: `${authModelName}?`,
|
|
685
|
+
attributes: [
|
|
686
|
+
`@relation("${modelName}DeletedBy${authModelName}", fields: [deletedById], references: [id])`
|
|
687
|
+
]
|
|
688
|
+
});
|
|
689
|
+
fields.push({
|
|
690
|
+
name: 'deletedById',
|
|
691
|
+
type: `${prismaIdType(authModel?.config.id)}?`,
|
|
692
|
+
attributes: nativeTypeAttributes(idNativeType(authModel?.config.id)),
|
|
693
|
+
foreignKey: true
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
646
697
|
return fields;
|
|
647
698
|
}
|
|
648
|
-
function createIndexSchema(index) {
|
|
699
|
+
function createIndexSchema(index, attribute = '@@index') {
|
|
649
700
|
const indexes = [];
|
|
650
701
|
if (!index || index.length === 0) {
|
|
651
702
|
return indexes;
|
|
@@ -654,13 +705,23 @@ function createIndexSchema(index) {
|
|
|
654
705
|
const indexValue = (0, common_1.isArray)(idx)
|
|
655
706
|
? `[${idx.map(indexFieldSchema).join(', ')}]`
|
|
656
707
|
: indexFieldSchema(idx);
|
|
657
|
-
const indexExpression =
|
|
708
|
+
const indexExpression = `${attribute}(${indexValue})`;
|
|
658
709
|
if (!indexes.includes(indexExpression)) {
|
|
659
710
|
indexes.push(indexExpression);
|
|
660
711
|
}
|
|
661
712
|
}
|
|
662
713
|
return indexes;
|
|
663
714
|
}
|
|
715
|
+
function leadingIndexFields(config) {
|
|
716
|
+
const fields = new Set();
|
|
717
|
+
for (const idx of [...(config.index ?? []), ...(config.unique ?? [])]) {
|
|
718
|
+
const field = (0, common_1.isArray)(idx) ? idx[0] : idx;
|
|
719
|
+
if (field) {
|
|
720
|
+
fields.add(INDEX_SORT_PREFIXES[field.charAt(0)] ? field.slice(1) : field);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return fields;
|
|
724
|
+
}
|
|
664
725
|
function indexFieldSchema(field) {
|
|
665
726
|
const sort = INDEX_SORT_PREFIXES[field.charAt(0)];
|
|
666
727
|
if (!sort || field.length < 2) {
|
|
@@ -106,6 +106,13 @@ function transformUnsafeTypes(schema) {
|
|
|
106
106
|
if (field.nullable === true) {
|
|
107
107
|
properties[name] = (0, common_1.NullType)(properties[name]);
|
|
108
108
|
}
|
|
109
|
+
// A nullable model reference points at the nullable variant of that model,
|
|
110
|
+
// a schema of its own the response serializer can expand once. The type is
|
|
111
|
+
// written as the union it stands for instead.
|
|
112
|
+
const nullableRef = field.$ref?.match(/^(.+Single)Nullable$/)?.[1];
|
|
113
|
+
if (nullableRef) {
|
|
114
|
+
properties[name] = (0, common_1.NullType)(typebox_1.Type.Ref(nullableRef));
|
|
115
|
+
}
|
|
109
116
|
}
|
|
110
117
|
return typebox_1.Type.Object(properties, { ...schema });
|
|
111
118
|
}
|