🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,648 @@
|
||||
/**
|
||||
* Shared Model Contract Layer
|
||||
*
|
||||
* 单一字段定义源(Single Source of Truth)。
|
||||
* 所有 agent 通过 import { createContract } from "./model-contract.mjs" 引用,
|
||||
* 禁止自行定义任何字段名。
|
||||
*
|
||||
* 提供:
|
||||
* - entities[] — 所有实体的字段定义(PascalCase 名 + snake_case 表名)
|
||||
* - auth{} — 认证配置
|
||||
* - permissions[] — RBAC 权限矩阵
|
||||
* - fieldMapping{} — snake_case ↔ camelCase 双向映射
|
||||
* - validation{} — 字段级校验规则
|
||||
*/
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 1. Domain-Specific Entity Templates
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Base entity templates for common domains.
|
||||
* These are the canonical field definitions for every entity.
|
||||
* Key: domain name → Array of entity templates.
|
||||
*/
|
||||
const DOMAIN_ENTITIES = {
|
||||
enterprise: [
|
||||
{
|
||||
name: "User",
|
||||
table: "users",
|
||||
description: "系统用户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "username", type: "VARCHAR(128)", required: true, unique: true, validation: { minLength: 2, maxLength: 50 } },
|
||||
{ name: "password_hash", type: "VARCHAR(256)", required: true, isSecret: true },
|
||||
{ name: "nickname", type: "VARCHAR(128)" },
|
||||
{ name: "role", type: "VARCHAR(32)", defaultValue: "user", enum: ["user", "admin"] },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "hasMany", entity: "contracts", via: "user_id" },
|
||||
{ type: "hasMany", entity: "customers", via: "user_id" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Contract",
|
||||
table: "contracts",
|
||||
description: "合同",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "title", type: "VARCHAR(256)", required: true },
|
||||
{ name: "party_a", type: "VARCHAR(128)" },
|
||||
{ name: "party_b", type: "VARCHAR(128)" },
|
||||
{ name: "amount", type: "DECIMAL(14,2)" },
|
||||
{ name: "signed_at", type: "DATE" },
|
||||
{ name: "expires_at", type: "DATE" },
|
||||
{ name: "status", type: "VARCHAR(32)", defaultValue: "draft", enum: ["draft", "pending", "active", "expired", "terminated"] },
|
||||
{ name: "file_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Approval",
|
||||
table: "approvals",
|
||||
description: "审批流程",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "entity_type", type: "VARCHAR(32)", required: true },
|
||||
{ name: "entity_id", type: "UUID" },
|
||||
{ name: "applicant_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "status", type: "VARCHAR(32)", defaultValue: "pending", enum: ["pending", "approved", "rejected"] },
|
||||
{ name: "form_data", type: "JSONB" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
{ type: "belongsTo", entity: "users", via: "applicant_id", as: "applicant" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Customer",
|
||||
table: "customers",
|
||||
description: "客户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "name", type: "VARCHAR(128)", required: true },
|
||||
{ name: "email", type: "VARCHAR(256)" },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "company", type: "VARCHAR(128)" },
|
||||
{ name: "source", type: "VARCHAR(64)" },
|
||||
{ name: "tags", type: "JSONB", defaultValue: "[]" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Reminder",
|
||||
table: "reminders",
|
||||
description: "到期提醒",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "entity_type", type: "VARCHAR(32)", required: true },
|
||||
{ name: "entity_id", type: "UUID" },
|
||||
{ name: "remind_at", type: "TIMESTAMP", required: true },
|
||||
{ name: "message", type: "TEXT" },
|
||||
{ name: "sent", type: "BOOLEAN", defaultValue: "false" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
pet: [
|
||||
{
|
||||
name: "User",
|
||||
table: "users",
|
||||
description: "系统用户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "username", type: "VARCHAR(128)", required: true, unique: true, validation: { minLength: 2, maxLength: 50 } },
|
||||
{ name: "password_hash", type: "VARCHAR(256)", required: true, isSecret: true },
|
||||
{ name: "nickname", type: "VARCHAR(128)" },
|
||||
{ name: "role", type: "VARCHAR(32)", defaultValue: "user", enum: ["user", "admin"] },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [],
|
||||
},
|
||||
{
|
||||
name: "Pet",
|
||||
table: "pets",
|
||||
description: "宠物",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "name", type: "VARCHAR(64)", required: true },
|
||||
{ name: "species", type: "VARCHAR(32)" },
|
||||
{ name: "breed", type: "VARCHAR(64)" },
|
||||
{ name: "birth_date", type: "DATE" },
|
||||
{ name: "weight_kg", type: "DECIMAL(5,2)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
ecommerce: [
|
||||
{
|
||||
name: "User",
|
||||
table: "users",
|
||||
description: "系统用户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "username", type: "VARCHAR(128)", required: true, unique: true, validation: { minLength: 2, maxLength: 50 } },
|
||||
{ name: "password_hash", type: "VARCHAR(256)", required: true, isSecret: true },
|
||||
{ name: "nickname", type: "VARCHAR(128)" },
|
||||
{ name: "role", type: "VARCHAR(32)", defaultValue: "user", enum: ["user", "admin"] },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [],
|
||||
},
|
||||
{
|
||||
name: "Product",
|
||||
table: "products",
|
||||
description: "商品",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "title", type: "VARCHAR(256)", required: true },
|
||||
{ name: "description", type: "TEXT" },
|
||||
{ name: "price", type: "DECIMAL(10,2)" },
|
||||
{ name: "stock", type: "INTEGER", defaultValue: "0" },
|
||||
{ name: "images", type: "JSONB", defaultValue: "[]" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Order",
|
||||
table: "orders",
|
||||
description: "订单",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "user_id", type: "UUID", required: true, fkEntity: "users", fkColumn: "id" },
|
||||
{ name: "status", type: "VARCHAR(32)", defaultValue: "pending", enum: ["pending", "paid", "shipped", "delivered", "cancelled"] },
|
||||
{ name: "total_amount", type: "DECIMAL(12,2)" },
|
||||
{ name: "address_id", type: "UUID", fkEntity: "addresses", fkColumn: "id" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [
|
||||
{ type: "belongsTo", entity: "users", via: "user_id" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
education: [
|
||||
{
|
||||
name: "User",
|
||||
table: "users",
|
||||
description: "系统用户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "username", type: "VARCHAR(128)", required: true, unique: true, validation: { minLength: 2, maxLength: 50 } },
|
||||
{ name: "password_hash", type: "VARCHAR(256)", required: true, isSecret: true },
|
||||
{ name: "nickname", type: "VARCHAR(128)" },
|
||||
{ name: "role", type: "VARCHAR(32)", defaultValue: "user", enum: ["user", "admin", "instructor"] },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [],
|
||||
},
|
||||
],
|
||||
|
||||
note: [
|
||||
{
|
||||
name: "User",
|
||||
table: "users",
|
||||
description: "系统用户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "username", type: "VARCHAR(128)", required: true, unique: true, validation: { minLength: 2, maxLength: 50 } },
|
||||
{ name: "password_hash", type: "VARCHAR(256)", required: true, isSecret: true },
|
||||
{ name: "nickname", type: "VARCHAR(128)" },
|
||||
{ name: "role", type: "VARCHAR(32)", defaultValue: "user", enum: ["user", "admin"] },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [],
|
||||
},
|
||||
],
|
||||
|
||||
fitness: [
|
||||
{
|
||||
name: "User",
|
||||
table: "users",
|
||||
description: "系统用户",
|
||||
fields: [
|
||||
{ name: "id", type: "UUID", isPrimary: true, isAuto: true },
|
||||
{ name: "username", type: "VARCHAR(128)", required: true, unique: true, validation: { minLength: 2, maxLength: 50 } },
|
||||
{ name: "password_hash", type: "VARCHAR(256)", required: true, isSecret: true },
|
||||
{ name: "nickname", type: "VARCHAR(128)" },
|
||||
{ name: "role", type: "VARCHAR(32)", defaultValue: "user", enum: ["user", "admin"] },
|
||||
{ name: "phone", type: "VARCHAR(20)" },
|
||||
{ name: "avatar_url", type: "TEXT" },
|
||||
{ name: "created_at", type: "TIMESTAMP", isAuto: true },
|
||||
{ name: "updated_at", type: "TIMESTAMP", isAuto: true },
|
||||
],
|
||||
relationships: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 2. Field Mapping Engine
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Build bidirectional snake_case ↔ camelCase mappings from all contract entities.
|
||||
* Scanned once; cached in the returned contract.
|
||||
*/
|
||||
function buildFieldMappings(entities) {
|
||||
const snakeToCamel = {};
|
||||
const camelToSnake = {};
|
||||
|
||||
// Base conversions from common SQL columns
|
||||
const baseMappings = {
|
||||
snakeToCamel: {
|
||||
"created_at": "createdAt",
|
||||
"updated_at": "updatedAt",
|
||||
"user_id": "userId",
|
||||
},
|
||||
camelToSnake: {
|
||||
"createdAt": "created_at",
|
||||
"updatedAt": "updated_at",
|
||||
"userId": "user_id",
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(snakeToCamel, baseMappings.snakeToCamel);
|
||||
Object.assign(camelToSnake, baseMappings.camelToSnake);
|
||||
|
||||
for (const entity of entities) {
|
||||
for (const field of entity.fields) {
|
||||
const snake = field.name; // fields are stored in snake_case
|
||||
// Convert to camelCase
|
||||
const camel = snake.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
||||
|
||||
if (snake !== camel) {
|
||||
snakeToCamel[snake] = camel;
|
||||
camelToSnake[camel] = snake;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add common patterns
|
||||
const known = [
|
||||
["id", "id"],
|
||||
["username", "username"],
|
||||
["password_hash", "passwordHash"],
|
||||
["nickname", "nickname"],
|
||||
["role", "role"],
|
||||
["phone", "phone"],
|
||||
["avatar_url", "avatarUrl"],
|
||||
["title", "title"],
|
||||
["description", "description"],
|
||||
["status", "status"],
|
||||
["data", "data"],
|
||||
["amount", "amount"],
|
||||
["party_a", "partyA"],
|
||||
["party_b", "partyB"],
|
||||
["signed_at", "signedAt"],
|
||||
["expires_at", "expiresAt"],
|
||||
["file_url", "fileUrl"],
|
||||
["entity_type", "entityType"],
|
||||
["entity_id", "entityId"],
|
||||
["applicant_id", "applicantId"],
|
||||
["form_data", "formData"],
|
||||
["name", "name"],
|
||||
["email", "email"],
|
||||
["company", "company"],
|
||||
["source", "source"],
|
||||
["tags", "tags"],
|
||||
["breed", "breed"],
|
||||
["species", "species"],
|
||||
["birth_date", "birthDate"],
|
||||
["weight_kg", "weightKg"],
|
||||
["price", "price"],
|
||||
["stock", "stock"],
|
||||
["images", "images"],
|
||||
["total_amount", "totalAmount"],
|
||||
["address_id", "addressId"],
|
||||
["remind_at", "remindAt"],
|
||||
["message", "message"],
|
||||
["sent", "sent"],
|
||||
];
|
||||
|
||||
for (const [snake, camel] of known) {
|
||||
if (!snakeToCamel[snake]) snakeToCamel[snake] = camel;
|
||||
if (!camelToSnake[camel]) camelToSnake[camel] = snake;
|
||||
}
|
||||
|
||||
return { snakeToCamel, camelToSnake };
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 3. Auth Configuration
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function buildAuthConfig(domain) {
|
||||
// Enterprise domain has stricter auth
|
||||
const isEnterprise = domain === "enterprise";
|
||||
|
||||
return {
|
||||
registrationFields: ["username", "password", "nickname"],
|
||||
loginFields: ["username", "password"],
|
||||
jwtPayload: ["userId", "username", "role"],
|
||||
passwordPolicy: {
|
||||
minLength: isEnterprise ? 8 : 6,
|
||||
requireSpecial: isEnterprise,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 4. Permissions Matrix
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function buildPermissions(domain) {
|
||||
return [
|
||||
{ role: "admin", allow: ["*"] },
|
||||
{ role: "user", allow: ["read:own", "create:*", "update:own", "delete:own"] },
|
||||
];
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 5. Validation Rules
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
function buildValidation(entities) {
|
||||
const rules = {};
|
||||
|
||||
for (const entity of entities) {
|
||||
const fieldRules = {};
|
||||
for (const field of entity.fields) {
|
||||
if (field.validation) {
|
||||
fieldRules[field.name] = { ...field.validation };
|
||||
}
|
||||
// Auto-generate from field properties
|
||||
if (field.required || field.unique || field.enum || field.defaultValue !== undefined) {
|
||||
if (!fieldRules[field.name]) fieldRules[field.name] = {};
|
||||
}
|
||||
if (field.required) {
|
||||
fieldRules[field.name].required = true;
|
||||
}
|
||||
if (field.unique) {
|
||||
fieldRules[field.name].unique = true;
|
||||
}
|
||||
if (field.enum) {
|
||||
fieldRules[field.name].enum = field.enum;
|
||||
}
|
||||
if (field.defaultValue !== undefined) {
|
||||
fieldRules[field.name].defaultValue = field.defaultValue;
|
||||
}
|
||||
}
|
||||
if (Object.keys(fieldRules).length > 0) {
|
||||
rules[entity.name] = fieldRules;
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 6. Main Contract Builder
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Create a Model Contract from PRD and Architecture.
|
||||
*
|
||||
* The contract is the SINGLE SOURCE OF TRUTH for all entity field definitions.
|
||||
* All generators MUST read from the contract; they MUST NOT invent field names.
|
||||
*
|
||||
* @param {object} prd — PRD from SF-01 (project-intake-agent)
|
||||
* @param {object} arch — Architecture from SF-02 (architecture-agent), optional
|
||||
* @returns {object} Contract object
|
||||
*/
|
||||
export function createContract(prd, arch) {
|
||||
const domain = (prd && prd.domain) || (arch && arch.domain) || "generic";
|
||||
const projectName = (prd && prd.projectName) || (arch && arch.projectName) || "App";
|
||||
|
||||
// ── Get domain-specific entities ──
|
||||
let entities = DOMAIN_ENTITIES[domain] || DOMAIN_ENTITIES.enterprise;
|
||||
|
||||
// Deep clone to avoid mutation
|
||||
entities = JSON.parse(JSON.stringify(entities));
|
||||
|
||||
// ── Augment with domain-specific entities from features/APIs ──
|
||||
// If architecture has derived tables, merge them into the contract
|
||||
if (arch && arch.databaseSchema) {
|
||||
const existingTables = new Set(entities.map(e => e.table));
|
||||
const existingNames = new Set(entities.map(e => e.name));
|
||||
|
||||
for (const table of arch.databaseSchema) {
|
||||
if (!existingTables.has(table.table) && table.table !== "users") {
|
||||
// Auto-generate PascalCase entity name from snake_case table
|
||||
const pascalName = table.table
|
||||
.split("_")
|
||||
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join("");
|
||||
|
||||
if (!existingNames.has(pascalName)) {
|
||||
const entity = {
|
||||
name: pascalName,
|
||||
table: table.table,
|
||||
description: table.description || `${pascalName}表`,
|
||||
fields: (table.fields || []).map(f => ({
|
||||
name: f.name,
|
||||
type: f.type || "TEXT",
|
||||
required: (f.constraints || "").includes("NOT NULL") || (f.constraints || "").includes("PK"),
|
||||
isPrimary: (f.constraints || "").includes("PK"),
|
||||
isAuto: f.name === "created_at" || f.name === "updated_at" || (f.constraints || "").includes("DEFAULT"),
|
||||
...((f.constraints || "").includes("UNIQUE") ? { unique: true } : {}),
|
||||
...((f.constraints || "").includes("FK") ? {
|
||||
fkEntity: (f.name.endsWith("_id") ? f.name.replace(/_id$/, "s") : "items"),
|
||||
fkColumn: "id",
|
||||
} : {}),
|
||||
})),
|
||||
relationships: [],
|
||||
};
|
||||
|
||||
// Run FK detection on fields
|
||||
for (const field of entity.fields) {
|
||||
if (field.fkEntity) {
|
||||
entity.relationships.push({
|
||||
type: "belongsTo",
|
||||
entity: field.fkEntity,
|
||||
via: field.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
entities.push(entity);
|
||||
existingTables.add(table.table);
|
||||
existingNames.add(pascalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Build mappings ──
|
||||
const fieldMapping = buildFieldMappings(entities);
|
||||
const auth = buildAuthConfig(domain);
|
||||
const permissions = buildPermissions(domain);
|
||||
const validation = buildValidation(entities);
|
||||
|
||||
return {
|
||||
projectName,
|
||||
domain,
|
||||
entities,
|
||||
auth,
|
||||
permissions,
|
||||
fieldMapping,
|
||||
validation,
|
||||
};
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 7. Contract Utility Functions
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Look up a field in the contract by entity.table and field name (snake_case).
|
||||
*
|
||||
* @param {object} contract
|
||||
* @param {string} tableName — snake_case table name
|
||||
* @param {string} fieldName — snake_case field name
|
||||
* @returns {object|null} Field definition
|
||||
*/
|
||||
export function lookupField(contract, tableName, fieldName) {
|
||||
for (const entity of contract.entities) {
|
||||
if (entity.table === tableName) {
|
||||
return entity.fields.find(f => f.name === fieldName) || null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the camelCase name for a snake_case column from the contract's fieldMapping.
|
||||
*
|
||||
* @param {object} contract
|
||||
* @param {string} snakeName
|
||||
* @returns {string}
|
||||
*/
|
||||
export function toCamel(contract, snakeName) {
|
||||
return contract.fieldMapping.snakeToCamel[snakeName] || snakeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the snake_case name for a camelCase property from the contract's fieldMapping.
|
||||
*
|
||||
* @param {object} contract
|
||||
* @param {string} camelName
|
||||
* @returns {string}
|
||||
*/
|
||||
export function toSnake(contract, camelName) {
|
||||
return contract.fieldMapping.camelToSnake[camelName] || camelName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TypeScript type for a contract field.
|
||||
*
|
||||
* @param {object} field — Contract field definition
|
||||
* @returns {string} TypeScript type string
|
||||
*/
|
||||
export function tsType(field) {
|
||||
const t = (field.type || "").toUpperCase();
|
||||
if (t.includes("INT") || t.includes("SERIAL") || t.includes("BIGINT") || t.includes("DECIMAL") || t.includes("NUMERIC") || t.includes("FLOAT") || t.includes("DOUBLE") || t.includes("REAL")) return "number";
|
||||
if (t.includes("BOOL")) return "boolean";
|
||||
if (t.includes("JSONB") || t.includes("JSON")) return "Record<string, unknown>";
|
||||
return "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQLite column type for a contract field.
|
||||
*
|
||||
* @param {object} field — Contract field definition
|
||||
* @returns {string} SQLite type
|
||||
*/
|
||||
export function sqliteType(field) {
|
||||
const t = (field.type || "").toUpperCase();
|
||||
if (t.includes("UUID") || t.includes("TEXT") || t.includes("VARCHAR") || t.includes("CHAR")) return "TEXT";
|
||||
if (t.includes("INT") || t.includes("SERIAL") || t.includes("BIGINT")) return "INTEGER";
|
||||
if (t.includes("DECIMAL") || t.includes("NUMERIC") || t.includes("FLOAT") || t.includes("DOUBLE") || t.includes("REAL")) return "REAL";
|
||||
if (t.includes("BOOL")) return "INTEGER";
|
||||
if (t.includes("DATE") || t.includes("TIME") || t.includes("TIMESTAMP")) return "TEXT";
|
||||
if (t.includes("JSONB") || t.includes("JSON")) return "TEXT";
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the non-auto fields for CreateInput (exclude id, created_at, updated_at).
|
||||
*
|
||||
* @param {object} entity — Contract entity
|
||||
* @returns {object[]} Fields for CreateInput
|
||||
*/
|
||||
export function createInputFields(entity) {
|
||||
return entity.fields.filter(f =>
|
||||
!f.isPrimary && !f.isAuto && f.name !== "created_at" && f.name !== "updated_at"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the non-identity fields for UpdateInput (exclude id, created_at, updated_at).
|
||||
*
|
||||
* @param {object} entity — Contract entity
|
||||
* @returns {object[]} Fields for UpdateInput
|
||||
*/
|
||||
export function updateInputFields(entity) {
|
||||
return createInputFields(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity has the given field (by snake_case name).
|
||||
*/
|
||||
export function hasField(entity, snakeName) {
|
||||
return entity.fields.some(f => f.name === snakeName);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// 8. Export DOMAIN_ENTITIES for introspection
|
||||
// ═══════════════════════════════════════════════════════
|
||||
|
||||
export { DOMAIN_ENTITIES };
|
||||
Reference in New Issue
Block a user