Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/agent/prompts/REALIZE_OPERATION_CORRECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ When TS2353 says `'X' does not exist in type 'YSelect'`:
- Table name `shopping_categories` vs property name `category`
- FK column `shopping_seller_id` vs relation `seller`
- DTO name `orderItems` vs property name `items`
- Abbreviated FK `organization_id` vs actual column `hrm_platform_organization_id`
4. After finding the correct name, update BOTH the `select` clause AND any `transform`/return code that references the relation

### 4.6. Unwrapping Transformer.select() with `.select`
Expand Down Expand Up @@ -302,6 +303,9 @@ export async function method__path(props: {...}): Promise<IResponse> {
| Table name in query | Use relation property name | Check Prisma schema |
| `.select().select` | Remove trailing `.select` | - |
| `Prisma.DbNull` on non-Json column | Use plain `null` | `Prisma.DbNull` only for `Json?` |
| `{ equals: null }` in where | Use direct `null` | `where: { field: null }` |
| Lowercase `not`/`and`/`or` | Uppercase `NOT`/`AND`/`OR` | Prisma logical operators are UPPERCASE |
| Abbreviated FK name | Use full name from schema | `hrm_platform_organization_id`, not `organization_id` |
| Type validation code | **DELETE IT** | No alternative |

## 7. Final Checklist
Expand All @@ -321,6 +325,8 @@ export async function method__path(props: {...}): Promise<IResponse> {

### Prisma Operations
- [ ] Used relation property names (NOT table names or FK columns)
- [ ] FK column names exact from schema (never abbreviated)
- [ ] `where` filters: direct `null`, uppercase `NOT`/`AND`/`OR`
- [ ] `satisfies Prisma.{table}FindManyArgs` on inline nested selects
- [ ] Transformer.select() assigned directly (NOT `.select().select`)
- [ ] Select includes all accessed fields (relations, scalars, FK columns)
Expand Down
45 changes: 40 additions & 5 deletions packages/agent/prompts/REALIZE_OPERATION_WRITE.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,10 @@ const customerId = props.customer.id; // From ActorPayload

**Before writing ANY query**:
1. READ the database schema thoroughly
2. VERIFY each field name (case-sensitive)
2. VERIFY each field name character-for-character (case-sensitive)
3. VERIFY relation property names from schema
4. NEVER fabricate, imagine, or guess
4. Copy FK column names exactly — never abbreviate (e.g., `hrm_platform_organization_id`, NOT `organization_id`)
5. NEVER fabricate, imagine, or guess

**Key Hints from DTO Schema** — each DTO property has JSDoc annotations:
- `@x-autobe-database-schema`: The DB table this DTO maps to
Expand Down Expand Up @@ -576,7 +577,39 @@ await MyGlobal.prisma.categories.updateMany({

`Prisma.DbNull` is reserved exclusively for JSON-type columns (`Json?`). For all other nullable types, plain `null` is the correct value.

### 8.6. Data Transformation Rules
### 8.6. Prisma Where Filter Syntax

Prisma `where` clauses have strict syntax. These are the most common mistakes:

```typescript
// ❌ WRONG - { equals: null } for nullable filter
where: { deleted_at: { equals: null } }

// ✅ CORRECT - Direct null comparison
where: { deleted_at: null }

// ❌ WRONG - Lowercase `not` (TS2353)
where: { not: { status: "deleted" } }

// ✅ CORRECT - Uppercase `NOT` for logical operators
where: { NOT: { status: "deleted" } }

// ❌ WRONG - Nested relation filter with scalar value
where: { department: props.departmentId }

// ✅ CORRECT - Relation filter uses object with `id`
where: { department: { id: props.departmentId } }

// ❌ WRONG - Nullable relation filter without handling
where: { parent_department: { id: parentId } } // Fails when null

// ✅ CORRECT - Nullable relation: use FK column directly
where: { parent_department_id: parentId ?? null }
```

**Prisma logical operators are UPPERCASE**: `AND`, `OR`, `NOT` — never `and`, `or`, `not`.

### 8.7. Data Transformation Rules

| Transformation | Pattern |
|----------------|---------|
Expand Down Expand Up @@ -636,7 +669,7 @@ return {
};
```

### 8.7. DELETE Operation: Cascade Deletion
### 8.8. DELETE Operation: Cascade Deletion

All tables use `onDelete: Cascade` in their foreign key relations. When deleting a record, simply delete the target row — the database automatically cascades to all dependent rows.

Expand All @@ -658,7 +691,7 @@ await MyGlobal.prisma.shopping_sales.delete({
});
```

### 8.8. Manual CREATE Example
### 8.9. Manual CREATE Example

```typescript
export async function postShoppingSaleReview(props: {
Expand Down Expand Up @@ -856,9 +889,11 @@ throw new HttpException("Forbidden", HttpStatus.FORBIDDEN);

### Manual Code (when no Collector/Transformer)
- [ ] Verified ALL field/relation names against database schema
- [ ] FK column names copied exactly — never abbreviated (e.g., `hrm_platform_organization_id`)
- [ ] Used relation property names (NOT table names or FK columns)
- [ ] Used `connect` syntax for relations (NOT direct FK assignment)
- [ ] `satisfies Prisma.{table}FindManyArgs` on inline nested selects
- [ ] `where` filters: direct `null` (not `{ equals: null }`), uppercase `NOT`/`AND`/`OR`
- [ ] Converted dates with `.toISOString()`
- [ ] Handled null→undefined for optional fields
- [ ] Handled null→null for nullable fields
Expand Down
36 changes: 32 additions & 4 deletions packages/agent/prompts/REALIZE_TRANSFORMER_CORRECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,30 @@ export function select() {

**Key rule**: Every property accessed on `input` in `transform()` MUST have a corresponding entry in `select()`.

### 5.6. FK Column Names Use `snake_case` from the Schema
### 5.6. Computed Fields Selected as Columns (TS2353)

Foreign key columns always use the exact `snake_case` name defined in the Prisma schema. The relation property name (often camelCase) is a separate concept — combining them produces a name that exists nowhere:
When TS2353 says a field doesn't exist and it looks like an aggregation (e.g., `total_hours`, `average_rating`, `total_count`), it's likely a computed field that is NOT a database column.

```typescript
// ❌ ERROR: 'total_billable_hours' does not exist in type Select
select: { total_billable_hours: true }

// ✅ FIX: Select the source relation, compute in transform()
select: {
timelogs: { select: { hours: true, billable: true } }
satisfies Prisma.hrm_platform_timelogsFindManyArgs,
}
// transform():
totalBillableHours: input.timelogs
.filter(t => t.billable)
.reduce((sum, t) => sum + t.hours, 0),
```

**Diagnosis**: If a field name sounds like an aggregation (`*_count`, `total_*`, `average_*`), it's computed from a relation — select the relation instead.

### 5.7. FK Column Names: Exact `snake_case` from Schema (Never Abbreviate)

Foreign key columns always use the FULL exact `snake_case` name defined in the Prisma schema. The relation property name (often camelCase) is a separate concept — combining or abbreviating them produces a name that exists nowhere:

```typescript
// Prisma schema:
Expand All @@ -270,11 +291,17 @@ select: { parentComment_id: true }

// ✅ CORRECT: exact column name from schema
select: { parent_comment_id: true }

// ❌ ERROR: 'organization_id' — abbreviated FK name
select: { organization_id: true }

// ✅ CORRECT: full FK name from schema
select: { hrm_platform_organization_id: true }
```

**Compiler name suggestions are authoritative.** When the compiler says `Did you mean 'Y'?`, it is matching against the schema's actual field list — `Y` is the correct name. Adopt the suggested name for every occurrence in `select()`, `transform()`, and inline objects throughout the entire file. A single wrong name cascades into multiple errors, so one rename can resolve many diagnostics at once.

### 5.7. Typia Tag Type Mismatch
### 5.8. Typia Tag Type Mismatch

```typescript
// ❌ ERROR: Type 'number & Type<"int32">' is not assignable to type 'Minimum<0>'
Expand All @@ -296,7 +323,8 @@ count: input._count.reviews satisfies number as number,

### Naming
- [ ] Relation property names from Prisma model (NOT table names)
- [ ] FK columns use exact `snake_case` from schema
- [ ] FK columns use exact full `snake_case` from schema (never abbreviated)
- [ ] Computed fields derived from relations in `transform()`, not selected as columns
- [ ] Transformer.select() assigned directly (NOT `.select().select`)

### Neighbor Reuse
Expand Down
12 changes: 12 additions & 0 deletions packages/agent/prompts/REALIZE_TRANSFORMER_WRITE.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ select: {
**Rules**:
1. If you need a relation not listed in the table, the DTO field is likely a computed field (see Section 8)
2. FK columns (e.g., `reddit_clone_member_id`) are scalar fields, NOT relations — select them with `true`, not `{ select: {...} }`
3. FK column names use the FULL name from the schema — never abbreviate (e.g., `hrm_platform_organization_id`, NOT `organization_id`)

### 6.3. Mandatory Neighbor Transformer Reuse

Expand Down Expand Up @@ -418,6 +419,17 @@ Cross-check: every `transformMappings` entry must have a corresponding line in t

When a DTO field doesn't exist as a database column, select the underlying relation and compute in `transform()`. Every aggregation is backed by a real relation — select that relation, then derive the value.

**NEVER select a computed field directly** — it does not exist as a column:

```typescript
// ❌ WRONG (TS2353) - total_hours is NOT a column, it's computed from timelogs
select: { total_hours: true }

// ✅ CORRECT - select the source relation, compute in transform()
select: { timelogs: { select: { hours: true } } }
// transform(): total_hours = input.timelogs.reduce((sum, t) => sum + t.hours, 0)
```

```typescript
// DTO: reviewCount, averageRating (NOT in DB)
// Underlying relation: reviews (hasMany)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ export namespace AutoBeInterfaceSchemaPropertyReviseProgrammer {
`,
});

// check that at least one property survives after revisions
if (
props.revises.length > 0 &&
props.revises.every((r) => r.type === "erase")
)
props.errors.push({
path: `${props.path}.revises`,
expected: "At least one non-erase revision to retain a property",
value: props.revises.map((r) => r.type),
description: StringUtil.trim`
All revisions are "erase", which would leave the schema with zero
properties. An object type used as a DTO must have at least one
property — otherwise the downstream Realize stage will fail with
TypeScript compilation errors (TS2339).

Keep at least one property by using "depict", "create", or "update"
instead of "erase" for the essential fields.

Note that, this is not a recommendation, but an instruction you must follow.
`,
});

if (props.model === null) return;

// check all DB schema properties are revised
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ export namespace AutoBeJsonSchemaFactory {
if (value.properties.limit === undefined)
value.properties.limit = pageRequest.properties.limit;
}

// Rewrite every $ref pointing to a bogus .IPagination variant
// (e.g. IEcommerceMall.IPagination) → IPage.IPagination.
for (const value of Object.values(schemas))
AutoBeOpenApiTypeChecker.skim({
schema: value,
accessor: "",
closure: (next) => {
if (
AutoBeOpenApiTypeChecker.isReference(next) &&
next.$ref.endsWith(".IPagination") &&
next.$ref !== "#/components/schemas/IPage.IPagination"
)
next.$ref = "#/components/schemas/IPage.IPagination";
},
});

// Delete the bogus schemas themselves so the LLM never sees them
// in subsequent iterations. Covers both entity variants
// (IEcommerceMall.IPagination) and their page wrappers
// (IPageIEcommerceMall.IPagination).
for (const key of Object.keys(schemas))
if (key.endsWith(".IPagination") && key !== "IPage.IPagination")
delete schemas[key];
};

export const fixAuthorizationSchemas = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export namespace AutoBeJsonSchemaValidator {
validateReferenceId(props);
validatePropertyNames(props);
validateNumericRanges(props);
validatePaginationVariant(props);
// validateEmptyProperties(props);

vo(props.typeName, props.schema);
Expand Down Expand Up @@ -724,6 +725,34 @@ export namespace AutoBeJsonSchemaValidator {
});
};

const validatePaginationVariant = (props: IProps): void => {
// Reject .IPagination variants on entity types (only IPage.IPagination is valid)
if (
props.typeName.endsWith(".IPagination") &&
props.typeName !== "IPage.IPagination"
)
props.errors.push({
path: props.path,
expected: `No .IPagination variant — only "IPage.IPagination" is valid`,
value: props.typeName,
description: StringUtil.trim`
You have defined a type ${JSON.stringify(props.typeName)} with an
".IPagination" suffix, but ".IPagination" is NOT a valid DTO variant.

The only valid pagination metadata type is "IPage.IPagination", which is
a system preset containing { current, limit, records, pages }.

Common DTO variants include: .ISummary, .ICreate, .IUpdate, .IRequest,
.IInvert, .IJoin, .ILogin, .IAuthorized.

Remove this type entirely. If you need pagination support, the system
automatically wraps your entity ISummary types in IPage wrappers.

Note that, this is not a recommendation, but an instruction you must follow.
`,
});
};

// const validateEmptyProperties = (props: IProps): void => {
// if (AutoBeOpenApiTypeChecker.isObject(props.schema) === false) return;
// if (Object.keys(props.schema.properties).length !== 0) return;
Expand Down
Loading