@appweaver/cli 1.4.1 → 1.5.1

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.
@@ -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: createIndexSchema(schema.config.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 = `@@index(${indexValue})`;
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appweaver/cli",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Appweaver - the backend framework for AI-first development (@cli)",
5
5
  "author": "Luka Matosevic",
6
6
  "license": "MIT",