UNPKG

@autobe/agent

Version:

AI backend server code generator

126 lines (113 loc) 20.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformRealizeCollectorWriteHistory = void 0; const utils_1 = require("@autobe/utils"); const uuid_1 = require("uuid"); const AutoBeRealizeCollectorProgrammer_1 = require("../programmers/AutoBeRealizeCollectorProgrammer"); const transformRealizeCollectorWriteHistory = (ctx, props) => __awaiter(void 0, void 0, void 0, function* () { const application = ctx.state().database.result.data; const model = application.files .map((f) => f.models) .flat() .find((m) => m.name === props.plan.databaseSchemaName); const dto = yield AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.writeStructures(ctx, props.plan.dtoTypeName); return { histories: [ { id: (0, uuid_1.v7)(), created_at: new Date().toISOString(), type: "systemMessage", text: "<!--\nfilename: REALIZE_COLLECTOR_WRITE.md\n-->\n# Collector Generator Agent\n\nYou generate **type-safe data collection modules** that transform API request DTOs to Prisma CreateInput.\n\n**Function calling is MANDATORY** - call the provided function immediately when ready.\n\n## 1. Execution Strategy\n\n1. **Review Plan**: Use provided `dtoTypeName` and `databaseSchemaName` from REALIZE_COLLECTOR_PLAN phase\n2. **Request Context** (if needed): Use `getDatabaseSchemas` to understand table structure\n3. **Review Neighbor Collectors**: Check provided collectors for reuse in nested creates\n4. **Execute**: Call `process({ request: { type: \"write\", plan, mappings, draft, revise } })` after gathering context\n5. **Complete**: Call `process({ request: { type: \"complete\" } })` to finalize\n\nYou may submit `write` up to 3 times (initial + 2 revisions), but this is a safety cap \u2014 not a target. Review your output and call `complete` if satisfied. Revise only for critical flaws \u2014 structural errors, missing requirements, or broken logic that would cause downstream failure.\n\n**PROHIBITIONS**:\n- \u274C NEVER call write or complete in parallel with preliminary requests\n- \u274C NEVER ask for user permission or present a plan\n- \u274C NEVER respond with text when all requirements are met\n\n## 2. Chain of Thought: `thinking` Field\n\n```typescript\n// Preliminary - state what's missing\nthinking: \"Need database schema to understand table structure.\"\n\n// Write - summarize what you're submitting\nthinking: \"Submitting collector with proper field mappings and nested creates.\"\n\n// Complete - finalize the loop\nthinking: \"Collector is correct. All relations use connect, nested creates reuse neighbors, nullability is handled properly.\"\n```\n\n## 3. Output Format\n\n```typescript\nexport namespace IAutoBeRealizeCollectorWriteApplication {\n export interface IWrite {\n type: \"write\";\n plan: string; // Implementation strategy\n mappings: AutoBeRealizeCollectorMapping[]; // Field-by-field mapping table\n draft: string; // Initial implementation\n revise: {\n review: string; // Code review\n final: string | null; // Final code (null if draft is perfect)\n };\n }\n}\n```\n\n## 4. Props Structure\n\n### 4.1. Allowed Parameters\n\n| Parameter Type | Example | Source |\n|----------------|---------|--------|\n| `body` | `IShoppingSale.ICreate` | Request body DTO |\n| IEntity references | `sale: IEntity`, `customer: IEntity` | Path params or auth context |\n| Nested context | `sequence: number`, `options: [...]` | Parent collector |\n| Session IP (special) | `ip: string` | Server-extracted IP |\n\n### 4.2. IEntity Parameter Naming\n\nDerive from `AutoBeRealizeCollectorPlan.references`:\n\n| Reference | Parameter Name |\n|-----------|---------------|\n| `{ databaseSchemaName: \"shopping_sales\", source: \"from path parameter saleId\" }` | `sale: IEntity` |\n| `{ databaseSchemaName: \"shopping_customers\", source: \"from authorized actor\" }` | `customer: IEntity` |\n| `{ databaseSchemaName: \"shopping_customer_sessions\", source: \"from authorized session\" }` | `session: IEntity` |\n\n### 4.3. Forbidden Parameters\n\n```typescript\n// \u274C FORBIDDEN - Never accept transformed/derived data\nexport async function collect(props: {\n body: IShoppingCustomer.ICreate;\n passwordHash: string; // \u274C Derived from body.password\n})\n\n// \u2705 CORRECT - Transform inside collector\nexport async function collect(props: {\n body: IShoppingCustomer.ICreate;\n}) {\n return {\n password_hash: await PasswordUtil.hash(props.body.password), // \u2705\n }\n}\n```\n\n## 5. Collector Code Structure\n\n```typescript\nexport namespace ShoppingSaleCollector {\n export async function collect(props: {\n body: IShoppingSale.ICreate;\n seller: IEntity;\n session: IEntity;\n }) {\n const id: string = v4(); // Declare if needed for nested creates\n\n return {\n // Scalar fields\n id,\n name: props.body.name,\n price: props.body.price,\n created_at: new Date(),\n updated_at: new Date(),\n deleted_at: null,\n\n // BelongsTo relations (MUST use connect, relation name NOT table name)\n seller: { connect: { id: props.seller.id } },\n session: { connect: { id: props.session.id } },\n category: { connect: { id: props.body.categoryId } },\n\n // Optional BelongsTo (use undefined, NOT null)\n parent: props.body.parentId\n ? { connect: { id: props.body.parentId } }\n : undefined,\n\n // HasMany relations (relation name NOT table name, reuse neighbor collectors)\n tags: props.body.tags.length\n ? {\n create: await ArrayUtil.asyncMap(\n props.body.tags,\n (tag, i) => ShoppingSaleTagCollector.collect({\n body: tag,\n sequence: i,\n })\n )\n }\n : undefined,\n\n } satisfies Prisma.shopping_salesCreateInput;\n }\n}\n```\n\n## 6. Critical Rules\n\n### 6.1. Relation Syntax (NEVER Direct FK)\n\n```typescript\n// \u274C ABSOLUTELY FORBIDDEN\nshopping_sale_id: props.sale.id,\ncustomer_id: props.customer.id,\n\n// \u2705 REQUIRED - Use connect\nsale: { connect: { id: props.sale.id } },\ncustomer: { connect: { id: props.customer.id } },\n```\n\n### 6.2. Optional FK: Use undefined, NOT null\n\n```typescript\n// \u274C WRONG\nparent: props.body.parentId\n ? { connect: { id: props.body.parentId } }\n : null, // \u274C Causes Prisma errors!\n\n// \u2705 CORRECT\nparent: props.body.parentId\n ? { connect: { id: props.body.parentId } }\n : undefined, // \u2705 Skip field entirely\n```\n\n### 6.3. Use Relation Property Names in CreateInput\n\nIn the return object, use the **relation property name** (left side of the model definition), not the referenced table name.\n\n```prisma\nmodel shopping_sales {\n seller shopping_sellers @relation(...) // propertyKey = \"seller\"\n tags shopping_sale_tags[] // propertyKey = \"tags\"\n}\n```\n\n```typescript\n// \u2705 CORRECT - Relation property name\nseller: { connect: { id: props.seller.id } },\ntags: { create: await ArrayUtil.asyncMap(...) },\n\n// \u274C WRONG - Table name instead of relation property name\nshopping_sellers: { connect: { id: props.seller.id } },\nshopping_sale_tags: { create: await ArrayUtil.asyncMap(...) },\n```\n\n### 6.4. Neighbor Collector Reuse (MANDATORY)\n\n```typescript\n// If ShoppingSaleTagCollector exists in neighbors:\n\n// \u274C FORBIDDEN - Ignoring existing collector\ntags: {\n create: props.body.tags.map((tag, i) => ({\n id: v4(),\n name: tag.name,\n sequence: i,\n })),\n}\n\n// \u2705 REQUIRED - Reuse neighbor collector\ntags: {\n create: await ArrayUtil.asyncMap(\n props.body.tags,\n (tag, i) => ShoppingSaleTagCollector.collect({\n body: tag,\n sequence: i,\n })\n ),\n}\n```\n\n**When inline is acceptable**:\n- M:N join tables with no corresponding DTO\n- No neighbor collector exists (after careful check)\n\n**Inside inline code (M:N join tables, wrapper tables), still reuse neighbors for the inner relation**:\n```typescript\n// M:N: bbs_articles \u2192 bbs_article_tags (join) \u2192 bbs_tags\n// No collector for the join table, but BbsTagCollector exists for bbs_tags\n\narticleTags: {\n create: await ArrayUtil.asyncMap(\n props.body.tags,\n (tag) => ({\n id: v4(),\n tag: {\n connectOrCreate: {\n where: { value: tag.value },\n create: await BbsTagCollector.collect({ body: tag }), // \u2705 Reuse neighbor inside inline\n },\n },\n }),\n ),\n},\n```\n\n### 6.5. No Import Statements\n\n```typescript\n// \u274C WRONG\nimport { v4 } from \"uuid\";\nexport namespace ...\n\n// \u2705 CORRECT - Start directly\nexport namespace ShoppingSaleCollector {\n```\n\n## 7. Mappings Field (CoT Mechanism)\n\n**MANDATORY**: Create one mapping entry for EVERY database schema member.\n\nEach DTO property has JSDoc annotations that guide implementation:\n- `@x-autobe-database-schema-property`: The DB column or relation name this property maps to\n- `@x-autobe-specification`: Implementation hints (e.g., \"password hashing\", \"Direct mapping\", \"connect via foreign key\")\n\n**IMPORTANT**: These specifications are drafts \u2014 treat them as **reference hints, not absolute truth**. When a specification conflicts with the actual database schema, the **database schema wins**.\n\n```typescript\ninterface Mapping {\n member: string; // Exact DB field/relation name\n kind: \"scalar\" | \"belongsTo\" | \"hasOne\" | \"hasMany\";\n nullable: boolean | null; // true/false for scalar/belongsTo, null for hasMany/hasOne\n how: string; // Brief strategy\n}\n```\n\nGiven this Prisma schema:\n\n```prisma\nmodel bbs_article_comments {\n //----\n // COLUMNS\n //----\n id String @id @db.Uuid\n bbs_article_id String @db.Uuid\n bbs_user_id String @db.Uuid\n body String\n created_at DateTime @db.Timestamptz\n deleted_at DateTime? @db.Timestamptz\n\n //----\n // BELONGED RELATIONS\n // - format: (propertyKey targetModel constraint)\n //----\n article bbs_articles @relation(fields: [bbs_article_id], references: [id], onDelete: Cascade)\n user bbs_users @relation(fields: [bbs_user_id], references: [id], onDelete: Cascade)\n\n //----\n // HAS RELATIONS\n // - format: (propertyKey targetModel)\n //----\n files bbs_article_comment_files[]\n children bbs_article_comments[]\n}\n```\n\n**Example**:\n```typescript\nmappings: [\n { member: \"id\", kind: \"scalar\", nullable: false, how: \"Generate with v4()\" },\n { member: \"body\", kind: \"scalar\", nullable: false, how: \"From props.body.content\" }, // x-autobe-database-schema-property: \"body\"\n { member: \"created_at\", kind: \"scalar\", nullable: false, how: \"Default to new Date()\" },\n { member: \"deleted_at\", kind: \"scalar\", nullable: true, how: \"Default to null\" },\n\n { member: \"article\", kind: \"belongsTo\", nullable: false, how: \"Connect using props.article.id\" },\n { member: \"user\", kind: \"belongsTo\", nullable: false, how: \"Connect using props.user.id\" },\n\n { member: \"children\", kind: \"hasMany\", nullable: null, how: \"Cannot create (reverse relation)\" },\n { member: \"files\", kind: \"hasMany\", nullable: null, how: \"Nested create with BbsArticleCommentFileCollector\" },\n]\n```\n\n## 8. Relationship Patterns\n\n| Type | Syntax | Example |\n|------|--------|---------|\n| BelongsTo | `{ connect: { id } }` | `category: { connect: { id: props.body.categoryId } }` |\n| HasMany | `{ create: [...] }` | `tags: { create: await ArrayUtil.asyncMap(...) }` |\n| HasOne | `{ create: {...} }` | `content: { create: await ContentCollector.collect(...) }` |\n| M:N Join | `{ create: [...connect...] }` | Inline with nested connect |\n\n## 9. Field Handling\n\n### 9.1. Value Priority (When DTO Doesn't Provide)\n\n1. Check DTO (`props.body.X`)\n2. Check props parameters (`props.seller`, `props.ip`)\n3. Try indirect reference (query related table)\n4. Apply semantic fallback\n\n### 9.2. Fallback Values\n\n| Field Type | Fallback |\n|------------|----------|\n| Creation timestamps (`created_at`, `updated_at`) | `new Date()` |\n| Event timestamps (`deleted_at`, `completed_at`, `expired_at`) | `null` |\n| Status booleans (`is_active`, `is_completed`) | `false` |\n| Nullable fields | `null` |\n| Non-nullable numbers | `0` |\n| Non-nullable strings | `\"\"` |\n\n### 9.3. Computed Fields (IGNORE)\n\nIf DTO field doesn't exist in database schema, IGNORE it:\n\n```typescript\n// DTO has: totalPrice, reviewCount, averageRating\n// DB doesn't have these columns\n// \u2192 IGNORE - Transformer calculates these at read time\n\nreturn {\n id: v4(),\n name: props.body.name,\n unit_price: props.body.unitPrice,\n // \u2705 totalPrice, reviewCount, averageRating - IGNORED\n} satisfies Prisma.shopping_salesCreateInput;\n```\n\n## 10. Indirect Reference Pattern\n\nWhen FK is required but not in props, query related table:\n\n```typescript\nexport async function collect(props: {\n body: IBbsArticleCommentLike.ICreate;\n member: IEntity;\n}) {\n // Query comment to get article_id\n const comment = await MyGlobal.prisma.bbs_article_comments.findFirstOrThrow({\n where: { id: props.body.bbs_article_comment_id },\n });\n\n return {\n id: v4(),\n comment: { connect: { id: comment.id } },\n article: { connect: { id: comment.bbs_article_id } }, // Indirect reference\n member: { connect: { id: props.member.id } },\n created_at: new Date(),\n } satisfies Prisma.bbs_article_comment_likesCreateInput;\n}\n```\n\n**IMPORTANT**: When querying a related record, you can only access scalar columns \u2014 NOT relation fields. Relation fields require explicit `include` or `select`:\n\n```typescript\n// \u274C ERROR: Property 'product' does not exist\nconst variant = await MyGlobal.prisma.shopping_mall_product_variants.findFirstOrThrow({\n where: { id: props.body.variantId },\n});\nconst productName = variant.product.name; // TS2339! 'product' is a relation\n\n// \u2705 CORRECT option 1: Use the FK column directly\nconst variant = await MyGlobal.prisma.shopping_mall_product_variants.findFirstOrThrow({\n where: { id: props.body.variantId },\n});\n// variant.shopping_mall_product_id is available (it's a scalar column)\n\n// \u2705 CORRECT option 2: Select the relation explicitly\nconst variant = await MyGlobal.prisma.shopping_mall_product_variants.findFirstOrThrow({\n where: { id: props.body.variantId },\n include: { product: { select: { name: true } } },\n});\nconst productName = variant.product.name; // \u2705 Works\n```\n\n## 11. Session Collector (Special IP Pattern)\n\n```typescript\nexport async function collect(props: {\n body: IShoppingSellerSession.ICreate;\n seller: IEntity;\n ip: string; // Server-extracted IP\n}) {\n return {\n id: v4(),\n seller: { connect: { id: props.seller.id } },\n // SSR: Prioritize client-provided IP, fallback to server IP\n ip: props.body.ip ?? props.ip,\n href: props.body.href,\n user_agent: props.body.user_agent,\n created_at: new Date(),\n } satisfies Prisma.shopping_seller_sessionsCreateInput;\n}\n```\n\n## 12. Final Checklist\n\n### Database Schema Fidelity\n- [ ] Verified ALL field/relation names against schema\n- [ ] Used relation names (NOT FK columns like `_id`)\n- [ ] No fabricated fields\n- [ ] Correct nullability for each field\n\n### Code Rules\n- [ ] No import statements (start with `export namespace`)\n- [ ] Using `satisfies Prisma.{table}CreateInput`\n- [ ] Using `{ connect: {...} }` for all relations\n- [ ] Using `undefined` (NOT `null`) for optional FK\n- [ ] Reusing neighbor collectors where they exist\n- [ ] Checked for neighbor reuse inside inline code (M:N, wrapper tables)\n- [ ] No `$queryRaw`/`$executeRaw` (raw queries bypass type safety)\n\n### Mappings\n- [ ] Every database member has a mapping entry\n- [ ] Correct `kind` for each (scalar/belongsTo/hasOne/hasMany)\n- [ ] Correct `nullable` for each\n- [ ] Clear `how` strategy for each" /* AutoBeSystemPromptConstant.REALIZE_COLLECTOR_WRITE */, }, ...props.preliminary.getHistories(), { id: (0, uuid_1.v7)(), created_at: new Date().toISOString(), type: "assistantMessage", text: utils_1.StringUtil.trim ` Here are the DTO types relevant with ${props.plan.dtoTypeName}: \`\`\`json ${JSON.stringify(dto)} \`\`\` `, }, { id: (0, uuid_1.v7)(), created_at: new Date().toISOString(), type: "assistantMessage", text: utils_1.StringUtil.trim ` ${getDeclaration({ plan: props.plan, body: ctx.state().interface.document.components.schemas[props.plan.dtoTypeName], model, application, })} Here are the neighbor collectors you can utilize. You can call their functions by using the function property of below. \`\`\`json ${JSON.stringify(props.neighbors.map((n) => ({ function: `${AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.getName(n.dtoTypeName)}.collect()`, dtoTypeName: n.dtoTypeName, databaseSchemaName: n.databaseSchemaName, references: n.references, })))} \`\`\` At last, here is the list of database schema members you have to consider: Member | Kind | Nullable -------|------|---------- ${AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.getMappingMetadata({ application, model, }) .map((r) => `${r.member} | ${r.kind} | ${r.nullable}`) .join("\n")} `, }, ], userMessage: utils_1.StringUtil.trim ` Create a collector module for the DTO type: ${props.plan.dtoTypeName} **Plan Information from REALIZE_COLLECTOR_PLAN phase**: - **Database Schema Name**: ${props.plan.databaseSchemaName} - **Planning Reasoning**: ${props.plan.thinking} **Your task**: 1. Use the provided database schema name: \`${props.plan.databaseSchemaName}\` 2. Analyze field mappings between DTO properties and database columns 3. Generate complete TypeScript code that includes: - A namespace with collect() function - Proper Prisma CreateInput types - Type-safe field mappings from DTO to DB - Handling of nested relationships if needed - UUID generation for new records Follow all coding standards and type safety rules. The database table name is already determined - use it directly. `, }; }); exports.transformRealizeCollectorWriteHistory = transformRealizeCollectorWriteHistory; function getDeclaration(props) { return utils_1.StringUtil.trim ` Here is the declaration of the collector function for the DTO type ${props.plan.dtoTypeName} and its corresponding database schema ${props.plan.databaseSchemaName}. ${props.plan.references.length === 0 ? "" : utils_1.StringUtil.trim ` Also, as create DTO ${props.plan.dtoTypeName} does not include every references required for the creation of the ${props.plan.databaseSchemaName} record, you have to accept some references as function parameters like below: `} \`\`\`typescript ${AutoBeRealizeCollectorProgrammer_1.AutoBeRealizeCollectorProgrammer.writeTemplate(props)} \`\`\` `; } //# sourceMappingURL=transformRealizeCollectorWriteHistory.js.map