@invect/core 0.1.1 → 0.1.3
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/dist/database/prisma-schema-generator.cjs.map +1 -1
- package/dist/database/prisma-schema-generator.js.map +1 -1
- package/dist/types/plugin.types.d.cts +1 -1
- package/dist/types/plugin.types.d.ts +1 -1
- package/dist/types/schemas-fresh/invect-config.cjs.map +1 -1
- package/dist/types/schemas-fresh/invect-config.js.map +1 -1
- package/package.json +25 -25
- package/LICENSE +0 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma-schema-generator.cjs","names":[],"sources":["../../src/database/prisma-schema-generator.ts"],"sourcesContent":["/**\n * Prisma Schema Generator\n *\n * Converts the merged abstract schema (core + plugins) into Prisma schema\n * model blocks. Unlike the Drizzle generators which produce complete\n * TypeScript files, this generates Prisma DSL.\n *\n * Two modes:\n * 1. `generatePrismaModels(schema, provider)` — returns only the model blocks\n * (for appending to an existing schema.prisma)\n * 2. `generateFullPrismaSchema(schema, provider)` — returns a complete\n * schema.prisma including generator + datasource blocks\n *\n * Uses the abstract schema types directly, converting to Prisma's type system:\n *\n * | Abstract | Prisma (PG/MySQL) | Prisma (SQLite) |\n * |-------------|-------------------|------------------|\n * | \"string\" | String | String |\n * | \"text\" | String | String |\n * | \"number\" | Int | Int |\n * | \"boolean\" | Boolean | Boolean |\n * | \"date\" | DateTime | DateTime |\n * | \"json\" | Json | String |\n * | \"uuid\" | String | String |\n * | \"bigint\" | BigInt | BigInt |\n * | string[] | enum | String (fallback)|\n */\n\nimport type { PluginFieldAttribute, PluginFieldType } from 'src/types/plugin.types';\nimport type { MergedSchema, MergedTable } from './schema-merger';\n\nexport type PrismaProvider = 'postgresql' | 'mysql' | 'sqlite';\n\n// =============================================================================\n// Full Schema Generator\n// =============================================================================\n\n/**\n * Generate a complete Prisma schema file from the merged abstract schema.\n *\n * Includes generator, datasource, enums, and all models.\n */\nexport function generateFullPrismaSchema(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`// Prisma schema for Invect — AUTO-GENERATED by @invect/cli`);\n lines.push(\n `// Do not edit the Invect models manually. Run \\`npx invect-cli generate\\` to regenerate.`,\n );\n lines.push(``);\n\n // Generator block\n lines.push(`generator client {`);\n lines.push(` provider = \"prisma-client-js\"`);\n lines.push(`}`);\n lines.push(``);\n\n // Datasource block\n lines.push(`datasource db {`);\n lines.push(` provider = \"${provider}\"`);\n if (provider === 'sqlite') {\n lines.push(` url = \"file:./dev.db\"`);\n } else {\n lines.push(` url = env(\"DATABASE_URL\")`);\n }\n lines.push(`}`);\n lines.push(``);\n\n // Enums (PostgreSQL and MySQL support enums; SQLite doesn't)\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n if (enums.length > 0) {\n lines.push(\n `// =============================================================================`,\n );\n lines.push(`// Enums`);\n lines.push(\n `// =============================================================================`,\n );\n lines.push(``);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n }\n\n // Models\n lines.push(`// =============================================================================`);\n lines.push(`// Models`);\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Model-Only Generator (for appending to existing schemas)\n// =============================================================================\n\n/**\n * Generate only the Prisma model blocks (no generator/datasource).\n *\n * Useful when adding Invect models to an existing Prisma schema.\n */\nexport function generatePrismaModels(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n lines.push(``);\n lines.push(`// =============================================================================`);\n lines.push(`// Invect Models — AUTO-GENERATED by @invect/cli`);\n lines.push(`// Do not edit these models manually. Run \\`npx invect-cli generate\\` to regenerate.`);\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n // Enums\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n\n // Models\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Single Model Generator\n// =============================================================================\n\nfunction generatePrismaModel(\n table: MergedTable,\n schema: MergedSchema,\n provider: PrismaProvider,\n): string {\n const lines: string[] = [];\n const { name, definition } = table;\n const dbTableName = definition.tableName || toSnakeCase(name);\n const modelName = capitalize(name);\n\n lines.push(`model ${modelName} {`);\n\n // Fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n const dbColName = toSnakeCase(fieldName);\n const prismaType = getPrismaType(field, provider, name, fieldName);\n const attrs = getPrismaAttributes(fieldName, dbColName, field, provider);\n\n lines.push(` ${fieldName} ${prismaType}${attrs}`);\n }\n\n // Relation fields (Prisma requires explicit relation fields)\n const relationLines = generateRelationFields(table, schema, provider);\n for (const line of relationLines) {\n lines.push(` ${line}`);\n }\n\n // Composite primary key\n if (definition.compositePrimaryKey?.length) {\n lines.push(``);\n lines.push(` @@id([${definition.compositePrimaryKey.join(', ')}])`);\n }\n\n // @@index for indexed fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (field.index && !field.unique && !field.primaryKey) {\n lines.push(` @@index([${fieldName}])`);\n }\n }\n\n // @@map for table name mapping\n if (dbTableName !== name) {\n lines.push(` @@map(\"${dbTableName}\")`);\n }\n\n lines.push(`}`);\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Type Mapping\n// =============================================================================\n\nfunction getPrismaType(\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n tableName: string,\n fieldName: string,\n): string {\n const optional = field.required === false ? '?' : '';\n\n if (isEnumType(field.type)) {\n if (provider === 'sqlite') {\n // SQLite doesn't support enums in Prisma, use String\n return `String${optional}`;\n }\n const enumName = prismaEnumName(tableName, fieldName);\n return `${enumName}${optional}`;\n }\n\n switch (field.type) {\n case 'string':\n case 'text':\n case 'uuid':\n return `String${optional}`;\n case 'number':\n return `Int${optional}`;\n case 'bigint':\n return `BigInt${optional}`;\n case 'boolean':\n return `Boolean${optional}`;\n case 'date':\n return `DateTime${optional}`;\n case 'json':\n // SQLite doesn't have native JSON in Prisma — use String\n if (provider === 'sqlite') {\n return `String${optional}`;\n }\n return `Json${optional}`;\n default:\n return `String${optional}`;\n }\n}\n\n// =============================================================================\n// Attribute Generation\n// =============================================================================\n\nfunction getPrismaAttributes(\n fieldName: string,\n dbColName: string,\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n): string {\n const attrs: string[] = [];\n\n // @id (single-column PK)\n if (field.primaryKey) {\n attrs.push('@id');\n }\n\n // @unique\n if (field.unique) {\n attrs.push('@unique');\n }\n\n // @default\n if (field.defaultValue !== undefined) {\n const defaultAttr = getPrismaDefault(field.defaultValue, field.type, provider);\n if (defaultAttr) {\n attrs.push(defaultAttr);\n }\n }\n\n // @updatedAt (for date fields with \"now()\" default and name \"updatedAt\")\n // In Prisma, @updatedAt automatically updates the timestamp\n // We only add it if the field is named updatedAt\n if (fieldName === 'updatedAt' && field.type === 'date') {\n attrs.push('@updatedAt');\n }\n\n // @map (column name differs from field name)\n if (dbColName !== fieldName) {\n attrs.push(`@map(\"${dbColName}\")`);\n }\n\n // @db.Text for MySQL long strings\n if (provider === 'mysql' && field.type === 'text') {\n attrs.push('@db.Text');\n }\n\n // @db.Timestamptz for PostgreSQL dates\n if (provider === 'postgresql' && field.type === 'date') {\n attrs.push('@db.Timestamptz(3)');\n }\n\n if (attrs.length === 0) {\n return '';\n }\n return ' ' + attrs.join(' ');\n}\n\nfunction getPrismaDefault(\n value: string | number | boolean | 'uuid()' | 'now()',\n _type: PluginFieldType,\n _provider: PrismaProvider,\n): string | null {\n if (value === 'uuid()') {\n return '@default(uuid())';\n }\n if (value === 'now()') {\n return '@default(now())';\n }\n if (typeof value === 'boolean') {\n return `@default(${value})`;\n }\n if (typeof value === 'number') {\n return `@default(${value})`;\n }\n if (typeof value === 'string') {\n return `@default(\"${value}\")`;\n }\n return null;\n}\n\n// =============================================================================\n// Relation Fields\n// =============================================================================\n\n/**\n * Generate Prisma relation fields for a model.\n *\n * In Prisma, if model A has a FK `bId → B.id`, we need:\n * - On A: `b B @relation(fields: [bId], references: [id])`\n * - On B: `as A[]` (reverse side)\n *\n * We generate the \"one\" side (FK holder) here; the \"many\" side\n * (reverse) is added to the referenced model.\n */\nfunction generateRelationFields(\n table: MergedTable,\n schema: MergedSchema,\n _provider: PrismaProvider,\n): string[] {\n const lines: string[] = [];\n const { definition } = table;\n\n // One-side: this table has FKs\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n const refTableLogical = findLogicalName(schema, field.references.table);\n const refModelName = capitalize(refTableLogical);\n const relName = fieldName.replace(/Id$/, '').replace(/_id$/, '');\n const onDelete = mapOnDelete(field.references.onDelete);\n\n lines.push(\n `${relName} ${refModelName} @relation(fields: [${fieldName}], references: [${field.references.field}], onDelete: ${onDelete})`,\n );\n }\n\n // Many-side: other tables have FKs pointing to this table\n for (const otherTable of schema.tables) {\n if (otherTable.name === table.name) {\n continue;\n }\n\n for (const [_fieldName, field] of Object.entries(otherTable.definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n // Check if this FK references our table\n const refsOurTable =\n field.references.table === table.name ||\n field.references.table === (table.definition.tableName || toSnakeCase(table.name));\n\n if (refsOurTable) {\n const otherModelName = capitalize(otherTable.name);\n // If the FK is unique, this is a one-to-one relation (singular, optional)\n // Otherwise it's one-to-many (array)\n const isUnique = field.unique === true;\n const relName = otherTable.name;\n lines.push(`${relName} ${otherModelName}${isUnique ? '?' : '[]'}`);\n }\n }\n }\n\n return lines;\n}\n\nfunction mapOnDelete(onDelete?: string): string {\n switch (onDelete) {\n case 'cascade':\n return 'Cascade';\n case 'set null':\n return 'SetNull';\n case 'restrict':\n return 'Restrict';\n case 'no action':\n return 'NoAction';\n default:\n return 'NoAction';\n }\n}\n\n// =============================================================================\n// Enum Helpers\n// =============================================================================\n\ninterface PrismaEnumDef {\n name: string;\n values: string[];\n}\n\nfunction collectPrismaEnums(schema: MergedSchema): PrismaEnumDef[] {\n const enums: PrismaEnumDef[] = [];\n\n for (const table of schema.tables) {\n for (const [fieldName, field] of Object.entries(table.definition.fields)) {\n if (isEnumType(field.type)) {\n enums.push({\n name: prismaEnumName(table.name, fieldName),\n values: field.type,\n });\n }\n }\n }\n\n return enums;\n}\n\nfunction prismaEnumName(tableName: string, fieldName: string): string {\n return `${capitalize(tableName)}${capitalize(fieldName)}`;\n}\n\n// =============================================================================\n// Shared Utilities\n// =============================================================================\n\nfunction isEnumType(type: PluginFieldType): type is string[] {\n return Array.isArray(type);\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Find the logical (camelCase) table name from a DB table name.\n */\nfunction findLogicalName(schema: MergedSchema, tableRef: string): string {\n // First try exact match on logical name\n const exact = schema.tables.find((t) => t.name === tableRef);\n if (exact) {\n return exact.name;\n }\n\n // Try by tableName property\n const byDbName = schema.tables.find(\n (t) => t.definition.tableName === tableRef || toSnakeCase(t.name) === tableRef,\n );\n if (byDbName) {\n return byDbName.name;\n }\n\n // Fallback: convert to camelCase\n if (tableRef.includes('_')) {\n return tableRef.replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n }\n return tableRef;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prisma-schema-generator.cjs","names":[],"sources":["../../src/database/prisma-schema-generator.ts"],"sourcesContent":["/**\n * Prisma Schema Generator\n *\n * Converts the merged abstract schema (core + plugins) into Prisma schema\n * model blocks. Unlike the Drizzle generators which produce complete\n * TypeScript files, this generates Prisma DSL.\n *\n * Two modes:\n * 1. `generatePrismaModels(schema, provider)` — returns only the model blocks\n * (for appending to an existing schema.prisma)\n * 2. `generateFullPrismaSchema(schema, provider)` — returns a complete\n * schema.prisma including generator + datasource blocks\n *\n * Uses the abstract schema types directly, converting to Prisma's type system:\n *\n * | Abstract | Prisma (PG/MySQL) | Prisma (SQLite) |\n * |-------------|-------------------|------------------|\n * | \"string\" | String | String |\n * | \"text\" | String | String |\n * | \"number\" | Int | Int |\n * | \"boolean\" | Boolean | Boolean |\n * | \"date\" | DateTime | DateTime |\n * | \"json\" | Json | String |\n * | \"uuid\" | String | String |\n * | \"bigint\" | BigInt | BigInt |\n * | string[] | enum | String (fallback)|\n */\n\nimport type { PluginFieldAttribute, PluginFieldType } from 'src/types/plugin.types';\nimport type { MergedSchema, MergedTable } from './schema-merger';\n\nexport type PrismaProvider = 'postgresql' | 'mysql' | 'sqlite';\n\n// =============================================================================\n// Full Schema Generator\n// =============================================================================\n\n/**\n * Generate a complete Prisma schema file from the merged abstract schema.\n *\n * Includes generator, datasource, enums, and all models.\n */\nexport function generateFullPrismaSchema(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`// Prisma schema for Invect — AUTO-GENERATED by @invect/cli`);\n lines.push(\n `// Do not edit the Invect models manually. Run \\`npx invect-cli generate\\` to regenerate.`,\n );\n lines.push(``);\n\n // Generator block\n lines.push(`generator client {`);\n lines.push(` provider = \"prisma-client-js\"`);\n lines.push(`}`);\n lines.push(``);\n\n // Datasource block\n lines.push(`datasource db {`);\n lines.push(` provider = \"${provider}\"`);\n if (provider === 'sqlite') {\n lines.push(` url = \"file:./dev.db\"`);\n } else {\n lines.push(` url = env(\"DATABASE_URL\")`);\n }\n lines.push(`}`);\n lines.push(``);\n\n // Enums (PostgreSQL and MySQL support enums; SQLite doesn't)\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n if (enums.length > 0) {\n lines.push(\n `// =============================================================================`,\n );\n lines.push(`// Enums`);\n lines.push(\n `// =============================================================================`,\n );\n lines.push(``);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n }\n\n // Models\n lines.push(`// =============================================================================`);\n lines.push(`// Models`);\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Model-Only Generator (for appending to existing schemas)\n// =============================================================================\n\n/**\n * Generate only the Prisma model blocks (no generator/datasource).\n *\n * Useful when adding Invect models to an existing Prisma schema.\n */\nexport function generatePrismaModels(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n lines.push(``);\n lines.push(`// =============================================================================`);\n lines.push(`// Invect Models — AUTO-GENERATED by @invect/cli`);\n lines.push(\n `// Do not edit these models manually. Run \\`npx invect-cli generate\\` to regenerate.`,\n );\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n // Enums\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n\n // Models\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Single Model Generator\n// =============================================================================\n\nfunction generatePrismaModel(\n table: MergedTable,\n schema: MergedSchema,\n provider: PrismaProvider,\n): string {\n const lines: string[] = [];\n const { name, definition } = table;\n const dbTableName = definition.tableName || toSnakeCase(name);\n const modelName = capitalize(name);\n\n lines.push(`model ${modelName} {`);\n\n // Fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n const dbColName = toSnakeCase(fieldName);\n const prismaType = getPrismaType(field, provider, name, fieldName);\n const attrs = getPrismaAttributes(fieldName, dbColName, field, provider);\n\n lines.push(` ${fieldName} ${prismaType}${attrs}`);\n }\n\n // Relation fields (Prisma requires explicit relation fields)\n const relationLines = generateRelationFields(table, schema, provider);\n for (const line of relationLines) {\n lines.push(` ${line}`);\n }\n\n // Composite primary key\n if (definition.compositePrimaryKey?.length) {\n lines.push(``);\n lines.push(` @@id([${definition.compositePrimaryKey.join(', ')}])`);\n }\n\n // @@index for indexed fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (field.index && !field.unique && !field.primaryKey) {\n lines.push(` @@index([${fieldName}])`);\n }\n }\n\n // @@map for table name mapping\n if (dbTableName !== name) {\n lines.push(` @@map(\"${dbTableName}\")`);\n }\n\n lines.push(`}`);\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Type Mapping\n// =============================================================================\n\nfunction getPrismaType(\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n tableName: string,\n fieldName: string,\n): string {\n const optional = field.required === false ? '?' : '';\n\n if (isEnumType(field.type)) {\n if (provider === 'sqlite') {\n // SQLite doesn't support enums in Prisma, use String\n return `String${optional}`;\n }\n const enumName = prismaEnumName(tableName, fieldName);\n return `${enumName}${optional}`;\n }\n\n switch (field.type) {\n case 'string':\n case 'text':\n case 'uuid':\n return `String${optional}`;\n case 'number':\n return `Int${optional}`;\n case 'bigint':\n return `BigInt${optional}`;\n case 'boolean':\n return `Boolean${optional}`;\n case 'date':\n return `DateTime${optional}`;\n case 'json':\n // SQLite doesn't have native JSON in Prisma — use String\n if (provider === 'sqlite') {\n return `String${optional}`;\n }\n return `Json${optional}`;\n default:\n return `String${optional}`;\n }\n}\n\n// =============================================================================\n// Attribute Generation\n// =============================================================================\n\nfunction getPrismaAttributes(\n fieldName: string,\n dbColName: string,\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n): string {\n const attrs: string[] = [];\n\n // @id (single-column PK)\n if (field.primaryKey) {\n attrs.push('@id');\n }\n\n // @unique\n if (field.unique) {\n attrs.push('@unique');\n }\n\n // @default\n if (field.defaultValue !== undefined) {\n const defaultAttr = getPrismaDefault(field.defaultValue, field.type, provider);\n if (defaultAttr) {\n attrs.push(defaultAttr);\n }\n }\n\n // @updatedAt (for date fields with \"now()\" default and name \"updatedAt\")\n // In Prisma, @updatedAt automatically updates the timestamp\n // We only add it if the field is named updatedAt\n if (fieldName === 'updatedAt' && field.type === 'date') {\n attrs.push('@updatedAt');\n }\n\n // @map (column name differs from field name)\n if (dbColName !== fieldName) {\n attrs.push(`@map(\"${dbColName}\")`);\n }\n\n // @db.Text for MySQL long strings\n if (provider === 'mysql' && field.type === 'text') {\n attrs.push('@db.Text');\n }\n\n // @db.Timestamptz for PostgreSQL dates\n if (provider === 'postgresql' && field.type === 'date') {\n attrs.push('@db.Timestamptz(3)');\n }\n\n if (attrs.length === 0) {\n return '';\n }\n return ' ' + attrs.join(' ');\n}\n\nfunction getPrismaDefault(\n value: string | number | boolean | 'uuid()' | 'now()',\n _type: PluginFieldType,\n _provider: PrismaProvider,\n): string | null {\n if (value === 'uuid()') {\n return '@default(uuid())';\n }\n if (value === 'now()') {\n return '@default(now())';\n }\n if (typeof value === 'boolean') {\n return `@default(${value})`;\n }\n if (typeof value === 'number') {\n return `@default(${value})`;\n }\n if (typeof value === 'string') {\n return `@default(\"${value}\")`;\n }\n return null;\n}\n\n// =============================================================================\n// Relation Fields\n// =============================================================================\n\n/**\n * Generate Prisma relation fields for a model.\n *\n * In Prisma, if model A has a FK `bId → B.id`, we need:\n * - On A: `b B @relation(fields: [bId], references: [id])`\n * - On B: `as A[]` (reverse side)\n *\n * We generate the \"one\" side (FK holder) here; the \"many\" side\n * (reverse) is added to the referenced model.\n */\nfunction generateRelationFields(\n table: MergedTable,\n schema: MergedSchema,\n _provider: PrismaProvider,\n): string[] {\n const lines: string[] = [];\n const { definition } = table;\n\n // One-side: this table has FKs\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n const refTableLogical = findLogicalName(schema, field.references.table);\n const refModelName = capitalize(refTableLogical);\n const relName = fieldName.replace(/Id$/, '').replace(/_id$/, '');\n const onDelete = mapOnDelete(field.references.onDelete);\n\n lines.push(\n `${relName} ${refModelName} @relation(fields: [${fieldName}], references: [${field.references.field}], onDelete: ${onDelete})`,\n );\n }\n\n // Many-side: other tables have FKs pointing to this table\n for (const otherTable of schema.tables) {\n if (otherTable.name === table.name) {\n continue;\n }\n\n for (const [_fieldName, field] of Object.entries(otherTable.definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n // Check if this FK references our table\n const refsOurTable =\n field.references.table === table.name ||\n field.references.table === (table.definition.tableName || toSnakeCase(table.name));\n\n if (refsOurTable) {\n const otherModelName = capitalize(otherTable.name);\n // If the FK is unique, this is a one-to-one relation (singular, optional)\n // Otherwise it's one-to-many (array)\n const isUnique = field.unique === true;\n const relName = otherTable.name;\n lines.push(`${relName} ${otherModelName}${isUnique ? '?' : '[]'}`);\n }\n }\n }\n\n return lines;\n}\n\nfunction mapOnDelete(onDelete?: string): string {\n switch (onDelete) {\n case 'cascade':\n return 'Cascade';\n case 'set null':\n return 'SetNull';\n case 'restrict':\n return 'Restrict';\n case 'no action':\n return 'NoAction';\n default:\n return 'NoAction';\n }\n}\n\n// =============================================================================\n// Enum Helpers\n// =============================================================================\n\ninterface PrismaEnumDef {\n name: string;\n values: string[];\n}\n\nfunction collectPrismaEnums(schema: MergedSchema): PrismaEnumDef[] {\n const enums: PrismaEnumDef[] = [];\n\n for (const table of schema.tables) {\n for (const [fieldName, field] of Object.entries(table.definition.fields)) {\n if (isEnumType(field.type)) {\n enums.push({\n name: prismaEnumName(table.name, fieldName),\n values: field.type,\n });\n }\n }\n }\n\n return enums;\n}\n\nfunction prismaEnumName(tableName: string, fieldName: string): string {\n return `${capitalize(tableName)}${capitalize(fieldName)}`;\n}\n\n// =============================================================================\n// Shared Utilities\n// =============================================================================\n\nfunction isEnumType(type: PluginFieldType): type is string[] {\n return Array.isArray(type);\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Find the logical (camelCase) table name from a DB table name.\n */\nfunction findLogicalName(schema: MergedSchema, tableRef: string): string {\n // First try exact match on logical name\n const exact = schema.tables.find((t) => t.name === tableRef);\n if (exact) {\n return exact.name;\n }\n\n // Try by tableName property\n const byDbName = schema.tables.find(\n (t) => t.definition.tableName === tableRef || toSnakeCase(t.name) === tableRef,\n );\n if (byDbName) {\n return byDbName.name;\n }\n\n // Fallback: convert to camelCase\n if (tableRef.includes('_')) {\n return tableRef.replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n }\n return tableRef;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma-schema-generator.js","names":[],"sources":["../../src/database/prisma-schema-generator.ts"],"sourcesContent":["/**\n * Prisma Schema Generator\n *\n * Converts the merged abstract schema (core + plugins) into Prisma schema\n * model blocks. Unlike the Drizzle generators which produce complete\n * TypeScript files, this generates Prisma DSL.\n *\n * Two modes:\n * 1. `generatePrismaModels(schema, provider)` — returns only the model blocks\n * (for appending to an existing schema.prisma)\n * 2. `generateFullPrismaSchema(schema, provider)` — returns a complete\n * schema.prisma including generator + datasource blocks\n *\n * Uses the abstract schema types directly, converting to Prisma's type system:\n *\n * | Abstract | Prisma (PG/MySQL) | Prisma (SQLite) |\n * |-------------|-------------------|------------------|\n * | \"string\" | String | String |\n * | \"text\" | String | String |\n * | \"number\" | Int | Int |\n * | \"boolean\" | Boolean | Boolean |\n * | \"date\" | DateTime | DateTime |\n * | \"json\" | Json | String |\n * | \"uuid\" | String | String |\n * | \"bigint\" | BigInt | BigInt |\n * | string[] | enum | String (fallback)|\n */\n\nimport type { PluginFieldAttribute, PluginFieldType } from 'src/types/plugin.types';\nimport type { MergedSchema, MergedTable } from './schema-merger';\n\nexport type PrismaProvider = 'postgresql' | 'mysql' | 'sqlite';\n\n// =============================================================================\n// Full Schema Generator\n// =============================================================================\n\n/**\n * Generate a complete Prisma schema file from the merged abstract schema.\n *\n * Includes generator, datasource, enums, and all models.\n */\nexport function generateFullPrismaSchema(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`// Prisma schema for Invect — AUTO-GENERATED by @invect/cli`);\n lines.push(\n `// Do not edit the Invect models manually. Run \\`npx invect-cli generate\\` to regenerate.`,\n );\n lines.push(``);\n\n // Generator block\n lines.push(`generator client {`);\n lines.push(` provider = \"prisma-client-js\"`);\n lines.push(`}`);\n lines.push(``);\n\n // Datasource block\n lines.push(`datasource db {`);\n lines.push(` provider = \"${provider}\"`);\n if (provider === 'sqlite') {\n lines.push(` url = \"file:./dev.db\"`);\n } else {\n lines.push(` url = env(\"DATABASE_URL\")`);\n }\n lines.push(`}`);\n lines.push(``);\n\n // Enums (PostgreSQL and MySQL support enums; SQLite doesn't)\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n if (enums.length > 0) {\n lines.push(\n `// =============================================================================`,\n );\n lines.push(`// Enums`);\n lines.push(\n `// =============================================================================`,\n );\n lines.push(``);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n }\n\n // Models\n lines.push(`// =============================================================================`);\n lines.push(`// Models`);\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Model-Only Generator (for appending to existing schemas)\n// =============================================================================\n\n/**\n * Generate only the Prisma model blocks (no generator/datasource).\n *\n * Useful when adding Invect models to an existing Prisma schema.\n */\nexport function generatePrismaModels(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n lines.push(``);\n lines.push(`// =============================================================================`);\n lines.push(`// Invect Models — AUTO-GENERATED by @invect/cli`);\n lines.push(`// Do not edit these models manually. Run \\`npx invect-cli generate\\` to regenerate.`);\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n // Enums\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n\n // Models\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Single Model Generator\n// =============================================================================\n\nfunction generatePrismaModel(\n table: MergedTable,\n schema: MergedSchema,\n provider: PrismaProvider,\n): string {\n const lines: string[] = [];\n const { name, definition } = table;\n const dbTableName = definition.tableName || toSnakeCase(name);\n const modelName = capitalize(name);\n\n lines.push(`model ${modelName} {`);\n\n // Fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n const dbColName = toSnakeCase(fieldName);\n const prismaType = getPrismaType(field, provider, name, fieldName);\n const attrs = getPrismaAttributes(fieldName, dbColName, field, provider);\n\n lines.push(` ${fieldName} ${prismaType}${attrs}`);\n }\n\n // Relation fields (Prisma requires explicit relation fields)\n const relationLines = generateRelationFields(table, schema, provider);\n for (const line of relationLines) {\n lines.push(` ${line}`);\n }\n\n // Composite primary key\n if (definition.compositePrimaryKey?.length) {\n lines.push(``);\n lines.push(` @@id([${definition.compositePrimaryKey.join(', ')}])`);\n }\n\n // @@index for indexed fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (field.index && !field.unique && !field.primaryKey) {\n lines.push(` @@index([${fieldName}])`);\n }\n }\n\n // @@map for table name mapping\n if (dbTableName !== name) {\n lines.push(` @@map(\"${dbTableName}\")`);\n }\n\n lines.push(`}`);\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Type Mapping\n// =============================================================================\n\nfunction getPrismaType(\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n tableName: string,\n fieldName: string,\n): string {\n const optional = field.required === false ? '?' : '';\n\n if (isEnumType(field.type)) {\n if (provider === 'sqlite') {\n // SQLite doesn't support enums in Prisma, use String\n return `String${optional}`;\n }\n const enumName = prismaEnumName(tableName, fieldName);\n return `${enumName}${optional}`;\n }\n\n switch (field.type) {\n case 'string':\n case 'text':\n case 'uuid':\n return `String${optional}`;\n case 'number':\n return `Int${optional}`;\n case 'bigint':\n return `BigInt${optional}`;\n case 'boolean':\n return `Boolean${optional}`;\n case 'date':\n return `DateTime${optional}`;\n case 'json':\n // SQLite doesn't have native JSON in Prisma — use String\n if (provider === 'sqlite') {\n return `String${optional}`;\n }\n return `Json${optional}`;\n default:\n return `String${optional}`;\n }\n}\n\n// =============================================================================\n// Attribute Generation\n// =============================================================================\n\nfunction getPrismaAttributes(\n fieldName: string,\n dbColName: string,\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n): string {\n const attrs: string[] = [];\n\n // @id (single-column PK)\n if (field.primaryKey) {\n attrs.push('@id');\n }\n\n // @unique\n if (field.unique) {\n attrs.push('@unique');\n }\n\n // @default\n if (field.defaultValue !== undefined) {\n const defaultAttr = getPrismaDefault(field.defaultValue, field.type, provider);\n if (defaultAttr) {\n attrs.push(defaultAttr);\n }\n }\n\n // @updatedAt (for date fields with \"now()\" default and name \"updatedAt\")\n // In Prisma, @updatedAt automatically updates the timestamp\n // We only add it if the field is named updatedAt\n if (fieldName === 'updatedAt' && field.type === 'date') {\n attrs.push('@updatedAt');\n }\n\n // @map (column name differs from field name)\n if (dbColName !== fieldName) {\n attrs.push(`@map(\"${dbColName}\")`);\n }\n\n // @db.Text for MySQL long strings\n if (provider === 'mysql' && field.type === 'text') {\n attrs.push('@db.Text');\n }\n\n // @db.Timestamptz for PostgreSQL dates\n if (provider === 'postgresql' && field.type === 'date') {\n attrs.push('@db.Timestamptz(3)');\n }\n\n if (attrs.length === 0) {\n return '';\n }\n return ' ' + attrs.join(' ');\n}\n\nfunction getPrismaDefault(\n value: string | number | boolean | 'uuid()' | 'now()',\n _type: PluginFieldType,\n _provider: PrismaProvider,\n): string | null {\n if (value === 'uuid()') {\n return '@default(uuid())';\n }\n if (value === 'now()') {\n return '@default(now())';\n }\n if (typeof value === 'boolean') {\n return `@default(${value})`;\n }\n if (typeof value === 'number') {\n return `@default(${value})`;\n }\n if (typeof value === 'string') {\n return `@default(\"${value}\")`;\n }\n return null;\n}\n\n// =============================================================================\n// Relation Fields\n// =============================================================================\n\n/**\n * Generate Prisma relation fields for a model.\n *\n * In Prisma, if model A has a FK `bId → B.id`, we need:\n * - On A: `b B @relation(fields: [bId], references: [id])`\n * - On B: `as A[]` (reverse side)\n *\n * We generate the \"one\" side (FK holder) here; the \"many\" side\n * (reverse) is added to the referenced model.\n */\nfunction generateRelationFields(\n table: MergedTable,\n schema: MergedSchema,\n _provider: PrismaProvider,\n): string[] {\n const lines: string[] = [];\n const { definition } = table;\n\n // One-side: this table has FKs\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n const refTableLogical = findLogicalName(schema, field.references.table);\n const refModelName = capitalize(refTableLogical);\n const relName = fieldName.replace(/Id$/, '').replace(/_id$/, '');\n const onDelete = mapOnDelete(field.references.onDelete);\n\n lines.push(\n `${relName} ${refModelName} @relation(fields: [${fieldName}], references: [${field.references.field}], onDelete: ${onDelete})`,\n );\n }\n\n // Many-side: other tables have FKs pointing to this table\n for (const otherTable of schema.tables) {\n if (otherTable.name === table.name) {\n continue;\n }\n\n for (const [_fieldName, field] of Object.entries(otherTable.definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n // Check if this FK references our table\n const refsOurTable =\n field.references.table === table.name ||\n field.references.table === (table.definition.tableName || toSnakeCase(table.name));\n\n if (refsOurTable) {\n const otherModelName = capitalize(otherTable.name);\n // If the FK is unique, this is a one-to-one relation (singular, optional)\n // Otherwise it's one-to-many (array)\n const isUnique = field.unique === true;\n const relName = otherTable.name;\n lines.push(`${relName} ${otherModelName}${isUnique ? '?' : '[]'}`);\n }\n }\n }\n\n return lines;\n}\n\nfunction mapOnDelete(onDelete?: string): string {\n switch (onDelete) {\n case 'cascade':\n return 'Cascade';\n case 'set null':\n return 'SetNull';\n case 'restrict':\n return 'Restrict';\n case 'no action':\n return 'NoAction';\n default:\n return 'NoAction';\n }\n}\n\n// =============================================================================\n// Enum Helpers\n// =============================================================================\n\ninterface PrismaEnumDef {\n name: string;\n values: string[];\n}\n\nfunction collectPrismaEnums(schema: MergedSchema): PrismaEnumDef[] {\n const enums: PrismaEnumDef[] = [];\n\n for (const table of schema.tables) {\n for (const [fieldName, field] of Object.entries(table.definition.fields)) {\n if (isEnumType(field.type)) {\n enums.push({\n name: prismaEnumName(table.name, fieldName),\n values: field.type,\n });\n }\n }\n }\n\n return enums;\n}\n\nfunction prismaEnumName(tableName: string, fieldName: string): string {\n return `${capitalize(tableName)}${capitalize(fieldName)}`;\n}\n\n// =============================================================================\n// Shared Utilities\n// =============================================================================\n\nfunction isEnumType(type: PluginFieldType): type is string[] {\n return Array.isArray(type);\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Find the logical (camelCase) table name from a DB table name.\n */\nfunction findLogicalName(schema: MergedSchema, tableRef: string): string {\n // First try exact match on logical name\n const exact = schema.tables.find((t) => t.name === tableRef);\n if (exact) {\n return exact.name;\n }\n\n // Try by tableName property\n const byDbName = schema.tables.find(\n (t) => t.definition.tableName === tableRef || toSnakeCase(t.name) === tableRef,\n );\n if (byDbName) {\n return byDbName.name;\n }\n\n // Fallback: convert to camelCase\n if (tableRef.includes('_')) {\n return tableRef.replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n }\n return tableRef;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prisma-schema-generator.js","names":[],"sources":["../../src/database/prisma-schema-generator.ts"],"sourcesContent":["/**\n * Prisma Schema Generator\n *\n * Converts the merged abstract schema (core + plugins) into Prisma schema\n * model blocks. Unlike the Drizzle generators which produce complete\n * TypeScript files, this generates Prisma DSL.\n *\n * Two modes:\n * 1. `generatePrismaModels(schema, provider)` — returns only the model blocks\n * (for appending to an existing schema.prisma)\n * 2. `generateFullPrismaSchema(schema, provider)` — returns a complete\n * schema.prisma including generator + datasource blocks\n *\n * Uses the abstract schema types directly, converting to Prisma's type system:\n *\n * | Abstract | Prisma (PG/MySQL) | Prisma (SQLite) |\n * |-------------|-------------------|------------------|\n * | \"string\" | String | String |\n * | \"text\" | String | String |\n * | \"number\" | Int | Int |\n * | \"boolean\" | Boolean | Boolean |\n * | \"date\" | DateTime | DateTime |\n * | \"json\" | Json | String |\n * | \"uuid\" | String | String |\n * | \"bigint\" | BigInt | BigInt |\n * | string[] | enum | String (fallback)|\n */\n\nimport type { PluginFieldAttribute, PluginFieldType } from 'src/types/plugin.types';\nimport type { MergedSchema, MergedTable } from './schema-merger';\n\nexport type PrismaProvider = 'postgresql' | 'mysql' | 'sqlite';\n\n// =============================================================================\n// Full Schema Generator\n// =============================================================================\n\n/**\n * Generate a complete Prisma schema file from the merged abstract schema.\n *\n * Includes generator, datasource, enums, and all models.\n */\nexport function generateFullPrismaSchema(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n // Header\n lines.push(`// Prisma schema for Invect — AUTO-GENERATED by @invect/cli`);\n lines.push(\n `// Do not edit the Invect models manually. Run \\`npx invect-cli generate\\` to regenerate.`,\n );\n lines.push(``);\n\n // Generator block\n lines.push(`generator client {`);\n lines.push(` provider = \"prisma-client-js\"`);\n lines.push(`}`);\n lines.push(``);\n\n // Datasource block\n lines.push(`datasource db {`);\n lines.push(` provider = \"${provider}\"`);\n if (provider === 'sqlite') {\n lines.push(` url = \"file:./dev.db\"`);\n } else {\n lines.push(` url = env(\"DATABASE_URL\")`);\n }\n lines.push(`}`);\n lines.push(``);\n\n // Enums (PostgreSQL and MySQL support enums; SQLite doesn't)\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n if (enums.length > 0) {\n lines.push(\n `// =============================================================================`,\n );\n lines.push(`// Enums`);\n lines.push(\n `// =============================================================================`,\n );\n lines.push(``);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n }\n\n // Models\n lines.push(`// =============================================================================`);\n lines.push(`// Models`);\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Model-Only Generator (for appending to existing schemas)\n// =============================================================================\n\n/**\n * Generate only the Prisma model blocks (no generator/datasource).\n *\n * Useful when adding Invect models to an existing Prisma schema.\n */\nexport function generatePrismaModels(schema: MergedSchema, provider: PrismaProvider): string {\n const lines: string[] = [];\n\n lines.push(``);\n lines.push(`// =============================================================================`);\n lines.push(`// Invect Models — AUTO-GENERATED by @invect/cli`);\n lines.push(\n `// Do not edit these models manually. Run \\`npx invect-cli generate\\` to regenerate.`,\n );\n lines.push(`// =============================================================================`);\n lines.push(``);\n\n // Enums\n if (provider !== 'sqlite') {\n const enums = collectPrismaEnums(schema);\n for (const e of enums) {\n lines.push(`enum ${e.name} {`);\n for (const v of e.values) {\n lines.push(` ${v}`);\n }\n lines.push(`}`);\n lines.push(``);\n }\n }\n\n // Models\n for (const table of schema.tables) {\n lines.push(generatePrismaModel(table, schema, provider));\n lines.push(``);\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Single Model Generator\n// =============================================================================\n\nfunction generatePrismaModel(\n table: MergedTable,\n schema: MergedSchema,\n provider: PrismaProvider,\n): string {\n const lines: string[] = [];\n const { name, definition } = table;\n const dbTableName = definition.tableName || toSnakeCase(name);\n const modelName = capitalize(name);\n\n lines.push(`model ${modelName} {`);\n\n // Fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n const dbColName = toSnakeCase(fieldName);\n const prismaType = getPrismaType(field, provider, name, fieldName);\n const attrs = getPrismaAttributes(fieldName, dbColName, field, provider);\n\n lines.push(` ${fieldName} ${prismaType}${attrs}`);\n }\n\n // Relation fields (Prisma requires explicit relation fields)\n const relationLines = generateRelationFields(table, schema, provider);\n for (const line of relationLines) {\n lines.push(` ${line}`);\n }\n\n // Composite primary key\n if (definition.compositePrimaryKey?.length) {\n lines.push(``);\n lines.push(` @@id([${definition.compositePrimaryKey.join(', ')}])`);\n }\n\n // @@index for indexed fields\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (field.index && !field.unique && !field.primaryKey) {\n lines.push(` @@index([${fieldName}])`);\n }\n }\n\n // @@map for table name mapping\n if (dbTableName !== name) {\n lines.push(` @@map(\"${dbTableName}\")`);\n }\n\n lines.push(`}`);\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Type Mapping\n// =============================================================================\n\nfunction getPrismaType(\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n tableName: string,\n fieldName: string,\n): string {\n const optional = field.required === false ? '?' : '';\n\n if (isEnumType(field.type)) {\n if (provider === 'sqlite') {\n // SQLite doesn't support enums in Prisma, use String\n return `String${optional}`;\n }\n const enumName = prismaEnumName(tableName, fieldName);\n return `${enumName}${optional}`;\n }\n\n switch (field.type) {\n case 'string':\n case 'text':\n case 'uuid':\n return `String${optional}`;\n case 'number':\n return `Int${optional}`;\n case 'bigint':\n return `BigInt${optional}`;\n case 'boolean':\n return `Boolean${optional}`;\n case 'date':\n return `DateTime${optional}`;\n case 'json':\n // SQLite doesn't have native JSON in Prisma — use String\n if (provider === 'sqlite') {\n return `String${optional}`;\n }\n return `Json${optional}`;\n default:\n return `String${optional}`;\n }\n}\n\n// =============================================================================\n// Attribute Generation\n// =============================================================================\n\nfunction getPrismaAttributes(\n fieldName: string,\n dbColName: string,\n field: PluginFieldAttribute,\n provider: PrismaProvider,\n): string {\n const attrs: string[] = [];\n\n // @id (single-column PK)\n if (field.primaryKey) {\n attrs.push('@id');\n }\n\n // @unique\n if (field.unique) {\n attrs.push('@unique');\n }\n\n // @default\n if (field.defaultValue !== undefined) {\n const defaultAttr = getPrismaDefault(field.defaultValue, field.type, provider);\n if (defaultAttr) {\n attrs.push(defaultAttr);\n }\n }\n\n // @updatedAt (for date fields with \"now()\" default and name \"updatedAt\")\n // In Prisma, @updatedAt automatically updates the timestamp\n // We only add it if the field is named updatedAt\n if (fieldName === 'updatedAt' && field.type === 'date') {\n attrs.push('@updatedAt');\n }\n\n // @map (column name differs from field name)\n if (dbColName !== fieldName) {\n attrs.push(`@map(\"${dbColName}\")`);\n }\n\n // @db.Text for MySQL long strings\n if (provider === 'mysql' && field.type === 'text') {\n attrs.push('@db.Text');\n }\n\n // @db.Timestamptz for PostgreSQL dates\n if (provider === 'postgresql' && field.type === 'date') {\n attrs.push('@db.Timestamptz(3)');\n }\n\n if (attrs.length === 0) {\n return '';\n }\n return ' ' + attrs.join(' ');\n}\n\nfunction getPrismaDefault(\n value: string | number | boolean | 'uuid()' | 'now()',\n _type: PluginFieldType,\n _provider: PrismaProvider,\n): string | null {\n if (value === 'uuid()') {\n return '@default(uuid())';\n }\n if (value === 'now()') {\n return '@default(now())';\n }\n if (typeof value === 'boolean') {\n return `@default(${value})`;\n }\n if (typeof value === 'number') {\n return `@default(${value})`;\n }\n if (typeof value === 'string') {\n return `@default(\"${value}\")`;\n }\n return null;\n}\n\n// =============================================================================\n// Relation Fields\n// =============================================================================\n\n/**\n * Generate Prisma relation fields for a model.\n *\n * In Prisma, if model A has a FK `bId → B.id`, we need:\n * - On A: `b B @relation(fields: [bId], references: [id])`\n * - On B: `as A[]` (reverse side)\n *\n * We generate the \"one\" side (FK holder) here; the \"many\" side\n * (reverse) is added to the referenced model.\n */\nfunction generateRelationFields(\n table: MergedTable,\n schema: MergedSchema,\n _provider: PrismaProvider,\n): string[] {\n const lines: string[] = [];\n const { definition } = table;\n\n // One-side: this table has FKs\n for (const [fieldName, field] of Object.entries(definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n const refTableLogical = findLogicalName(schema, field.references.table);\n const refModelName = capitalize(refTableLogical);\n const relName = fieldName.replace(/Id$/, '').replace(/_id$/, '');\n const onDelete = mapOnDelete(field.references.onDelete);\n\n lines.push(\n `${relName} ${refModelName} @relation(fields: [${fieldName}], references: [${field.references.field}], onDelete: ${onDelete})`,\n );\n }\n\n // Many-side: other tables have FKs pointing to this table\n for (const otherTable of schema.tables) {\n if (otherTable.name === table.name) {\n continue;\n }\n\n for (const [_fieldName, field] of Object.entries(otherTable.definition.fields)) {\n if (!field.references) {\n continue;\n }\n\n // Check if this FK references our table\n const refsOurTable =\n field.references.table === table.name ||\n field.references.table === (table.definition.tableName || toSnakeCase(table.name));\n\n if (refsOurTable) {\n const otherModelName = capitalize(otherTable.name);\n // If the FK is unique, this is a one-to-one relation (singular, optional)\n // Otherwise it's one-to-many (array)\n const isUnique = field.unique === true;\n const relName = otherTable.name;\n lines.push(`${relName} ${otherModelName}${isUnique ? '?' : '[]'}`);\n }\n }\n }\n\n return lines;\n}\n\nfunction mapOnDelete(onDelete?: string): string {\n switch (onDelete) {\n case 'cascade':\n return 'Cascade';\n case 'set null':\n return 'SetNull';\n case 'restrict':\n return 'Restrict';\n case 'no action':\n return 'NoAction';\n default:\n return 'NoAction';\n }\n}\n\n// =============================================================================\n// Enum Helpers\n// =============================================================================\n\ninterface PrismaEnumDef {\n name: string;\n values: string[];\n}\n\nfunction collectPrismaEnums(schema: MergedSchema): PrismaEnumDef[] {\n const enums: PrismaEnumDef[] = [];\n\n for (const table of schema.tables) {\n for (const [fieldName, field] of Object.entries(table.definition.fields)) {\n if (isEnumType(field.type)) {\n enums.push({\n name: prismaEnumName(table.name, fieldName),\n values: field.type,\n });\n }\n }\n }\n\n return enums;\n}\n\nfunction prismaEnumName(tableName: string, fieldName: string): string {\n return `${capitalize(tableName)}${capitalize(fieldName)}`;\n}\n\n// =============================================================================\n// Shared Utilities\n// =============================================================================\n\nfunction isEnumType(type: PluginFieldType): type is string[] {\n return Array.isArray(type);\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Find the logical (camelCase) table name from a DB table name.\n */\nfunction findLogicalName(schema: MergedSchema, tableRef: string): string {\n // First try exact match on logical name\n const exact = schema.tables.find((t) => t.name === tableRef);\n if (exact) {\n return exact.name;\n }\n\n // Try by tableName property\n const byDbName = schema.tables.find(\n (t) => t.definition.tableName === tableRef || toSnakeCase(t.name) === tableRef,\n );\n if (byDbName) {\n return byDbName.name;\n }\n\n // Fallback: convert to camelCase\n if (tableRef.includes('_')) {\n return tableRef.replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n }\n return tableRef;\n}\n"],"mappings":"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"}
|
|
@@ -519,7 +519,7 @@ interface InvectPlugin {
|
|
|
519
519
|
* The Invect CLI generates the concrete Drizzle schema files
|
|
520
520
|
* from core + plugin schemas combined.
|
|
521
521
|
*
|
|
522
|
-
|
|
522
|
+
* Run `npx invect-cli generate` after adding/changing plugin schemas.
|
|
523
523
|
*/
|
|
524
524
|
schema?: InvectPluginSchema;
|
|
525
525
|
/**
|
|
@@ -519,7 +519,7 @@ interface InvectPlugin {
|
|
|
519
519
|
* The Invect CLI generates the concrete Drizzle schema files
|
|
520
520
|
* from core + plugin schemas combined.
|
|
521
521
|
*
|
|
522
|
-
|
|
522
|
+
* Run `npx invect-cli generate` after adding/changing plugin schemas.
|
|
523
523
|
*/
|
|
524
524
|
schema?: InvectPluginSchema;
|
|
525
525
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invect-config.cjs","names":["z","ChatConfigSchema"],"sources":["../../../src/types/schemas-fresh/invect-config.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { ChatConfigSchema } from 'src/services/chat/chat-types';\n\nconst databaseConfigSchema = z.object({\n connectionString: z.string().min(1, 'Database URL is required'),\n type: z.enum(['postgresql', 'sqlite', 'mysql']),\n id: z.string(), // Optional ID for the database, useful for multi-database setups\n name: z.string().optional(), // Human readable name for the database\n});\n\nexport type InvectDatabaseConfig = z.infer<typeof databaseConfigSchema>;\n\n/**\n * Database configuration schema\n */\nexport const queryDatabasesConfigSchema = z\n .array(databaseConfigSchema)\n .optional()\n .default(() => []);\n\n/**\n * Execution configuration schema\n */\nexport const ExecutionConfigSchema = z.object({\n defaultTimeout: z.number().positive().default(60000),\n maxConcurrentExecutions: z.number().positive().default(10),\n enableTracing: z.boolean().default(true),\n /**\n * Maximum time (in ms) a flow run is allowed to stay in RUNNING state\n * before being considered stale and marked as FAILED.\n * @default 600_000 (10 minutes)\n */\n flowTimeoutMs: z.number().positive().default(600_000),\n /**\n * Interval (in ms) at which the heartbeat is updated during flow execution.\n * @default 30_000 (30 seconds)\n */\n heartbeatIntervalMs: z.number().positive().default(30_000),\n /**\n * Interval (in ms) at which the stale run detector polls for stuck runs.\n * @default 60_000 (60 seconds)\n */\n staleRunCheckIntervalMs: z.number().positive().default(60_000),\n});\n\n/**\n * Log level enum values\n */\nexport const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error', 'silent']);\n\n/**\n * Logging configuration schema with support for scope-level overrides.\n *\n * Scopes allow independent log level control for different feature areas:\n * - execution: Flow execution orchestration\n * - validation: Flow validation\n * - batch: Batch processing (AI providers)\n * - database: Database operations\n * - node: Node execution\n * - graph: Graph operations (topological sort, etc.)\n * - credentials: Credential management\n * - ai: AI/LLM operations\n * - template: Template rendering\n * - renderer: React Flow rendering\n * - flows: Flow management (CRUD)\n * - versions: Flow version management\n * - http: HTTP/API layer\n *\n * @example\n * ```typescript\n * const config = {\n * logging: {\n * level: 'info', // Default level for all scopes\n * scopes: {\n * execution: 'debug', // Verbose execution logging\n * validation: 'warn', // Only validation warnings\n * batch: 'silent', // Disable batch logging\n * }\n * }\n * };\n * ```\n */\nexport const LoggingConfigSchema = z.object({\n /** Default log level for all scopes */\n level: LogLevelSchema.default('silent'),\n /** Per-scope log level overrides */\n scopes: z.record(z.string(), LogLevelSchema).optional(),\n});\n\n/**\n * Built-in Invect roles\n */\nexport const InvectRoleSchema = z.enum(['admin', 'editor', 'operator', 'viewer']);\n\n/**\n * Invect permissions\n */\nexport const InvectPermissionSchema = z.enum([\n // Flow permissions\n 'flow:create',\n 'flow:read',\n 'flow:update',\n 'flow:delete',\n 'flow:publish',\n // Flow version permissions\n 'flow-version:create',\n 'flow-version:read',\n // Execution permissions\n 'flow-run:create',\n 'flow-run:read',\n 'flow-run:cancel',\n // Credential permissions\n 'credential:create',\n 'credential:read',\n 'credential:update',\n 'credential:delete',\n // Agent/tool permissions\n 'agent-tool:read',\n 'agent-tool:configure',\n // Node testing\n 'node:test',\n // Admin wildcard\n 'admin:*',\n]);\n\n/**\n * Authentication/Authorization configuration schema.\n *\n * Invect uses a \"BYO Auth\" (Bring Your Own Authentication) pattern.\n * The host app handles authentication and provides user identity to Invect.\n * Invect handles authorization based on roles and permissions.\n *\n * @example\n * ```typescript\n * const config = {\n * auth: {\n * enabled: true,\n * resolveUser: async (req) => ({\n * id: req.user.id,\n * role: req.user.invectRole,\n * }),\n * roleMapper: {\n * 'super_admin': 'admin',\n * 'content_manager': 'editor',\n * },\n * }\n * };\n * ```\n */\nexport const InvectAuthConfigSchema = z.object({\n /**\n * Enable RBAC (Role-Based Access Control).\n * When false, all requests are allowed without authentication checks.\n * @default false\n */\n enabled: z.boolean().default(false),\n\n /**\n * Function to resolve user identity from incoming request.\n * This is provided at runtime, not validated by Zod.\n */\n resolveUser: z.any().optional(),\n\n /**\n * Map host app roles to Invect roles.\n */\n roleMapper: z.record(z.string(), z.string()).optional(),\n\n /**\n * Define custom roles with specific permissions.\n */\n customRoles: z.record(z.string(), z.array(InvectPermissionSchema)).optional(),\n\n /**\n * Custom authorization callback (provided at runtime).\n */\n customAuthorize: z.any().optional(),\n\n /**\n * Routes that don't require authentication.\n */\n publicRoutes: z.array(z.string()).optional(),\n\n /**\n * Default role for authenticated users without explicit role.\n */\n defaultRole: z.string().default('viewer'),\n\n /**\n * Behavior when auth fails.\n */\n onAuthFailure: z.enum(['throw', 'log', 'deny']).default('throw'),\n\n /**\n * Use the flow_access database table to manage flow-level permissions.\n * When enabled, Invect stores flow access records in its own database.\n * @default false\n */\n useFlowAccessTable: z.boolean().default(false),\n});\n\n/**\n * Core Invect configuration schema\n */\nexport const InvectConfigSchema = z.object({\n queryDatabases: queryDatabasesConfigSchema.optional(),\n baseDatabaseConfig: databaseConfigSchema,\n logging: LoggingConfigSchema.default(() => ({\n level: 'info' as const,\n })).optional(),\n logger: z.any().optional(),\n basePath: z.string().optional(),\n /**\n * Execution settings: flow timeout, heartbeat interval, stale run detection.\n */\n execution: ExecutionConfigSchema.optional(),\n /**\n * Authentication and authorization configuration.\n * When not provided, auth is disabled (all requests allowed).\n */\n auth: InvectAuthConfigSchema.optional(),\n /**\n * Trigger system configuration.\n * Controls webhook and cron scheduler behavior.\n */\n triggers: z\n .object({\n /**\n * The public-facing base URL where Invect routes are mounted.\n * Used to display the full webhook URL in the flow editor.\n * Example: \"https://api.myapp.com/invect\"\n */\n webhookBaseUrl: z.string().optional(),\n /**\n * Enable/disable the cron scheduler. When disabled, cron trigger nodes\n * can still be placed on flows but won't fire automatically.\n * @default true\n */\n cronEnabled: z.boolean().default(true),\n })\n .optional(),\n /**\n * Chat assistant configuration.\n * Controls the AI chat sidebar for flow building assistance.\n */\n chat: ChatConfigSchema.optional(),\n /**\n * Plugins extend Invect with additional capabilities:\n * actions, hooks, endpoints, database schema, and middleware.\n *\n * Plugins are plain objects satisfying the `InvectPlugin` interface.\n * They are initialized in array order during `Invect.initialize()`.\n *\n * Database schema changes from plugins require running `npx invect-cli generate`\n * to regenerate the Drizzle schema files, then `npx invect-cli migrate` to apply.\n *\n * @example\n * ```typescript\n * import { rbac } from '@invect/plugin-rbac';\n * import { auditLog } from '@invect/plugin-audit-log';\n *\n * const config = {\n * plugins: [\n * rbac({ resolveUser: (req) => req.user }),\n * auditLog({ destination: 'database' }),\n * ],\n * };\n * ```\n */\n plugins: z.array(z.any()).optional(),\n /**\n * Schema verification configuration.\n *\n * When enabled, Invect checks on startup that the database has all\n * required tables and columns. This does NOT run migrations — the\n * developer is responsible for applying schema changes via the CLI:\n *\n * npx invect-cli generate # regenerate schema files\n * npx drizzle-kit push # apply via Drizzle\n * npx prisma db push # apply via Prisma\n *\n * @example\n * ```typescript\n * const config = {\n * schemaVerification: true, // warn on missing tables/columns\n * // or\n * schemaVerification: { strict: true }, // throw on missing tables/columns\n * };\n * ```\n */\n schemaVerification: z\n .union([\n z.boolean(),\n z.object({\n /** If true, throw an error when schema is invalid. If false, only log warnings. */\n strict: z.boolean().default(false),\n }),\n ])\n .optional(),\n /**\n * Default credentials to ensure on startup.\n *\n * Each entry is created if no credential with the same `name` already exists.\n * Useful for development environments where you want API keys and OAuth\n * tokens available immediately without running a separate seed script.\n *\n * @example\n * ```typescript\n * const config = {\n * defaultCredentials: [\n * {\n * name: 'Anthropic API Key',\n * type: 'http-api',\n * authType: 'bearer',\n * config: { token: process.env.SEED_ANTHROPIC_API_KEY! },\n * description: 'Seeded Anthropic credential',\n * },\n * ],\n * };\n * ```\n */\n defaultCredentials: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n authType: z.string(),\n config: z.record(z.string(), z.unknown()),\n description: z.string().optional(),\n isShared: z.boolean().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n }),\n )\n .optional(),\n});\n\n/**\n * Type inference from schemas\n */\nexport type QueryDatabasesConfig = z.infer<typeof queryDatabasesConfigSchema>;\nexport type ExecutionConfig = z.infer<typeof ExecutionConfigSchema>;\nexport type LoggingConfig = z.infer<typeof LoggingConfigSchema>;\nexport type InvectAuthConfigZod = z.infer<typeof InvectAuthConfigSchema>;\nexport type InvectConfig = z.infer<typeof InvectConfigSchema>;\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"invect-config.cjs","names":["z","ChatConfigSchema"],"sources":["../../../src/types/schemas-fresh/invect-config.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { ChatConfigSchema } from 'src/services/chat/chat-types';\n\nconst databaseConfigSchema = z.object({\n connectionString: z.string().min(1, 'Database URL is required'),\n type: z.enum(['postgresql', 'sqlite', 'mysql']),\n id: z.string(), // Optional ID for the database, useful for multi-database setups\n name: z.string().optional(), // Human readable name for the database\n});\n\nexport type InvectDatabaseConfig = z.infer<typeof databaseConfigSchema>;\n\n/**\n * Database configuration schema\n */\nexport const queryDatabasesConfigSchema = z\n .array(databaseConfigSchema)\n .optional()\n .default(() => []);\n\n/**\n * Execution configuration schema\n */\nexport const ExecutionConfigSchema = z.object({\n defaultTimeout: z.number().positive().default(60000),\n maxConcurrentExecutions: z.number().positive().default(10),\n enableTracing: z.boolean().default(true),\n /**\n * Maximum time (in ms) a flow run is allowed to stay in RUNNING state\n * before being considered stale and marked as FAILED.\n * @default 600_000 (10 minutes)\n */\n flowTimeoutMs: z.number().positive().default(600_000),\n /**\n * Interval (in ms) at which the heartbeat is updated during flow execution.\n * @default 30_000 (30 seconds)\n */\n heartbeatIntervalMs: z.number().positive().default(30_000),\n /**\n * Interval (in ms) at which the stale run detector polls for stuck runs.\n * @default 60_000 (60 seconds)\n */\n staleRunCheckIntervalMs: z.number().positive().default(60_000),\n});\n\n/**\n * Log level enum values\n */\nexport const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error', 'silent']);\n\n/**\n * Logging configuration schema with support for scope-level overrides.\n *\n * Scopes allow independent log level control for different feature areas:\n * - execution: Flow execution orchestration\n * - validation: Flow validation\n * - batch: Batch processing (AI providers)\n * - database: Database operations\n * - node: Node execution\n * - graph: Graph operations (topological sort, etc.)\n * - credentials: Credential management\n * - ai: AI/LLM operations\n * - template: Template rendering\n * - renderer: React Flow rendering\n * - flows: Flow management (CRUD)\n * - versions: Flow version management\n * - http: HTTP/API layer\n *\n * @example\n * ```typescript\n * const config = {\n * logging: {\n * level: 'info', // Default level for all scopes\n * scopes: {\n * execution: 'debug', // Verbose execution logging\n * validation: 'warn', // Only validation warnings\n * batch: 'silent', // Disable batch logging\n * }\n * }\n * };\n * ```\n */\nexport const LoggingConfigSchema = z.object({\n /** Default log level for all scopes */\n level: LogLevelSchema.default('silent'),\n /** Per-scope log level overrides */\n scopes: z.record(z.string(), LogLevelSchema).optional(),\n});\n\n/**\n * Built-in Invect roles\n */\nexport const InvectRoleSchema = z.enum(['admin', 'editor', 'operator', 'viewer']);\n\n/**\n * Invect permissions\n */\nexport const InvectPermissionSchema = z.enum([\n // Flow permissions\n 'flow:create',\n 'flow:read',\n 'flow:update',\n 'flow:delete',\n 'flow:publish',\n // Flow version permissions\n 'flow-version:create',\n 'flow-version:read',\n // Execution permissions\n 'flow-run:create',\n 'flow-run:read',\n 'flow-run:cancel',\n // Credential permissions\n 'credential:create',\n 'credential:read',\n 'credential:update',\n 'credential:delete',\n // Agent/tool permissions\n 'agent-tool:read',\n 'agent-tool:configure',\n // Node testing\n 'node:test',\n // Admin wildcard\n 'admin:*',\n]);\n\n/**\n * Authentication/Authorization configuration schema.\n *\n * Invect uses a \"BYO Auth\" (Bring Your Own Authentication) pattern.\n * The host app handles authentication and provides user identity to Invect.\n * Invect handles authorization based on roles and permissions.\n *\n * @example\n * ```typescript\n * const config = {\n * auth: {\n * enabled: true,\n * resolveUser: async (req) => ({\n * id: req.user.id,\n * role: req.user.invectRole,\n * }),\n * roleMapper: {\n * 'super_admin': 'admin',\n * 'content_manager': 'editor',\n * },\n * }\n * };\n * ```\n */\nexport const InvectAuthConfigSchema = z.object({\n /**\n * Enable RBAC (Role-Based Access Control).\n * When false, all requests are allowed without authentication checks.\n * @default false\n */\n enabled: z.boolean().default(false),\n\n /**\n * Function to resolve user identity from incoming request.\n * This is provided at runtime, not validated by Zod.\n */\n resolveUser: z.any().optional(),\n\n /**\n * Map host app roles to Invect roles.\n */\n roleMapper: z.record(z.string(), z.string()).optional(),\n\n /**\n * Define custom roles with specific permissions.\n */\n customRoles: z.record(z.string(), z.array(InvectPermissionSchema)).optional(),\n\n /**\n * Custom authorization callback (provided at runtime).\n */\n customAuthorize: z.any().optional(),\n\n /**\n * Routes that don't require authentication.\n */\n publicRoutes: z.array(z.string()).optional(),\n\n /**\n * Default role for authenticated users without explicit role.\n */\n defaultRole: z.string().default('viewer'),\n\n /**\n * Behavior when auth fails.\n */\n onAuthFailure: z.enum(['throw', 'log', 'deny']).default('throw'),\n\n /**\n * Use the flow_access database table to manage flow-level permissions.\n * When enabled, Invect stores flow access records in its own database.\n * @default false\n */\n useFlowAccessTable: z.boolean().default(false),\n});\n\n/**\n * Core Invect configuration schema\n */\nexport const InvectConfigSchema = z.object({\n queryDatabases: queryDatabasesConfigSchema.optional(),\n baseDatabaseConfig: databaseConfigSchema,\n logging: LoggingConfigSchema.default(() => ({\n level: 'info' as const,\n })).optional(),\n logger: z.any().optional(),\n basePath: z.string().optional(),\n /**\n * Execution settings: flow timeout, heartbeat interval, stale run detection.\n */\n execution: ExecutionConfigSchema.optional(),\n /**\n * Authentication and authorization configuration.\n * When not provided, auth is disabled (all requests allowed).\n */\n auth: InvectAuthConfigSchema.optional(),\n /**\n * Trigger system configuration.\n * Controls webhook and cron scheduler behavior.\n */\n triggers: z\n .object({\n /**\n * The public-facing base URL where Invect routes are mounted.\n * Used to display the full webhook URL in the flow editor.\n * Example: \"https://api.myapp.com/invect\"\n */\n webhookBaseUrl: z.string().optional(),\n /**\n * Enable/disable the cron scheduler. When disabled, cron trigger nodes\n * can still be placed on flows but won't fire automatically.\n * @default true\n */\n cronEnabled: z.boolean().default(true),\n })\n .optional(),\n /**\n * Chat assistant configuration.\n * Controls the AI chat sidebar for flow building assistance.\n */\n chat: ChatConfigSchema.optional(),\n /**\n * Plugins extend Invect with additional capabilities:\n * actions, hooks, endpoints, database schema, and middleware.\n *\n * Plugins are plain objects satisfying the `InvectPlugin` interface.\n * They are initialized in array order during `Invect.initialize()`.\n *\n * Database schema changes from plugins require running `npx invect-cli generate`\n * to regenerate the Drizzle schema files, then `npx invect-cli migrate` to apply.\n *\n * @example\n * ```typescript\n * import { rbac } from '@invect/plugin-rbac';\n * import { auditLog } from '@invect/plugin-audit-log';\n *\n * const config = {\n * plugins: [\n * rbac({ resolveUser: (req) => req.user }),\n * auditLog({ destination: 'database' }),\n * ],\n * };\n * ```\n */\n plugins: z.array(z.any()).optional(),\n /**\n * Schema verification configuration.\n *\n * When enabled, Invect checks on startup that the database has all\n * required tables and columns. This does NOT run migrations — the\n * developer is responsible for applying schema changes via the CLI:\n *\n * npx invect-cli generate # regenerate schema files\n * npx drizzle-kit push # apply via Drizzle\n * npx prisma db push # apply via Prisma\n *\n * @example\n * ```typescript\n * const config = {\n * schemaVerification: true, // warn on missing tables/columns\n * // or\n * schemaVerification: { strict: true }, // throw on missing tables/columns\n * };\n * ```\n */\n schemaVerification: z\n .union([\n z.boolean(),\n z.object({\n /** If true, throw an error when schema is invalid. If false, only log warnings. */\n strict: z.boolean().default(false),\n }),\n ])\n .optional(),\n /**\n * Default credentials to ensure on startup.\n *\n * Each entry is created if no credential with the same `name` already exists.\n * Useful for development environments where you want API keys and OAuth\n * tokens available immediately without running a separate seed script.\n *\n * @example\n * ```typescript\n * const config = {\n * defaultCredentials: [\n * {\n * name: 'Anthropic API Key',\n * type: 'http-api',\n * authType: 'bearer',\n * config: { token: process.env.SEED_ANTHROPIC_API_KEY! },\n * description: 'Seeded Anthropic credential',\n * },\n * ],\n * };\n * ```\n */\n defaultCredentials: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n authType: z.string(),\n config: z.record(z.string(), z.unknown()),\n description: z.string().optional(),\n isShared: z.boolean().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n }),\n )\n .optional(),\n});\n\n/**\n * Type inference from schemas\n */\nexport type QueryDatabasesConfig = z.infer<typeof queryDatabasesConfigSchema>;\nexport type ExecutionConfig = z.infer<typeof ExecutionConfigSchema>;\nexport type LoggingConfig = z.infer<typeof LoggingConfigSchema>;\nexport type InvectAuthConfigZod = z.infer<typeof InvectAuthConfigSchema>;\nexport type InvectConfig = z.infer<typeof InvectConfigSchema>;\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invect-config.js","names":[],"sources":["../../../src/types/schemas-fresh/invect-config.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { ChatConfigSchema } from 'src/services/chat/chat-types';\n\nconst databaseConfigSchema = z.object({\n connectionString: z.string().min(1, 'Database URL is required'),\n type: z.enum(['postgresql', 'sqlite', 'mysql']),\n id: z.string(), // Optional ID for the database, useful for multi-database setups\n name: z.string().optional(), // Human readable name for the database\n});\n\nexport type InvectDatabaseConfig = z.infer<typeof databaseConfigSchema>;\n\n/**\n * Database configuration schema\n */\nexport const queryDatabasesConfigSchema = z\n .array(databaseConfigSchema)\n .optional()\n .default(() => []);\n\n/**\n * Execution configuration schema\n */\nexport const ExecutionConfigSchema = z.object({\n defaultTimeout: z.number().positive().default(60000),\n maxConcurrentExecutions: z.number().positive().default(10),\n enableTracing: z.boolean().default(true),\n /**\n * Maximum time (in ms) a flow run is allowed to stay in RUNNING state\n * before being considered stale and marked as FAILED.\n * @default 600_000 (10 minutes)\n */\n flowTimeoutMs: z.number().positive().default(600_000),\n /**\n * Interval (in ms) at which the heartbeat is updated during flow execution.\n * @default 30_000 (30 seconds)\n */\n heartbeatIntervalMs: z.number().positive().default(30_000),\n /**\n * Interval (in ms) at which the stale run detector polls for stuck runs.\n * @default 60_000 (60 seconds)\n */\n staleRunCheckIntervalMs: z.number().positive().default(60_000),\n});\n\n/**\n * Log level enum values\n */\nexport const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error', 'silent']);\n\n/**\n * Logging configuration schema with support for scope-level overrides.\n *\n * Scopes allow independent log level control for different feature areas:\n * - execution: Flow execution orchestration\n * - validation: Flow validation\n * - batch: Batch processing (AI providers)\n * - database: Database operations\n * - node: Node execution\n * - graph: Graph operations (topological sort, etc.)\n * - credentials: Credential management\n * - ai: AI/LLM operations\n * - template: Template rendering\n * - renderer: React Flow rendering\n * - flows: Flow management (CRUD)\n * - versions: Flow version management\n * - http: HTTP/API layer\n *\n * @example\n * ```typescript\n * const config = {\n * logging: {\n * level: 'info', // Default level for all scopes\n * scopes: {\n * execution: 'debug', // Verbose execution logging\n * validation: 'warn', // Only validation warnings\n * batch: 'silent', // Disable batch logging\n * }\n * }\n * };\n * ```\n */\nexport const LoggingConfigSchema = z.object({\n /** Default log level for all scopes */\n level: LogLevelSchema.default('silent'),\n /** Per-scope log level overrides */\n scopes: z.record(z.string(), LogLevelSchema).optional(),\n});\n\n/**\n * Built-in Invect roles\n */\nexport const InvectRoleSchema = z.enum(['admin', 'editor', 'operator', 'viewer']);\n\n/**\n * Invect permissions\n */\nexport const InvectPermissionSchema = z.enum([\n // Flow permissions\n 'flow:create',\n 'flow:read',\n 'flow:update',\n 'flow:delete',\n 'flow:publish',\n // Flow version permissions\n 'flow-version:create',\n 'flow-version:read',\n // Execution permissions\n 'flow-run:create',\n 'flow-run:read',\n 'flow-run:cancel',\n // Credential permissions\n 'credential:create',\n 'credential:read',\n 'credential:update',\n 'credential:delete',\n // Agent/tool permissions\n 'agent-tool:read',\n 'agent-tool:configure',\n // Node testing\n 'node:test',\n // Admin wildcard\n 'admin:*',\n]);\n\n/**\n * Authentication/Authorization configuration schema.\n *\n * Invect uses a \"BYO Auth\" (Bring Your Own Authentication) pattern.\n * The host app handles authentication and provides user identity to Invect.\n * Invect handles authorization based on roles and permissions.\n *\n * @example\n * ```typescript\n * const config = {\n * auth: {\n * enabled: true,\n * resolveUser: async (req) => ({\n * id: req.user.id,\n * role: req.user.invectRole,\n * }),\n * roleMapper: {\n * 'super_admin': 'admin',\n * 'content_manager': 'editor',\n * },\n * }\n * };\n * ```\n */\nexport const InvectAuthConfigSchema = z.object({\n /**\n * Enable RBAC (Role-Based Access Control).\n * When false, all requests are allowed without authentication checks.\n * @default false\n */\n enabled: z.boolean().default(false),\n\n /**\n * Function to resolve user identity from incoming request.\n * This is provided at runtime, not validated by Zod.\n */\n resolveUser: z.any().optional(),\n\n /**\n * Map host app roles to Invect roles.\n */\n roleMapper: z.record(z.string(), z.string()).optional(),\n\n /**\n * Define custom roles with specific permissions.\n */\n customRoles: z.record(z.string(), z.array(InvectPermissionSchema)).optional(),\n\n /**\n * Custom authorization callback (provided at runtime).\n */\n customAuthorize: z.any().optional(),\n\n /**\n * Routes that don't require authentication.\n */\n publicRoutes: z.array(z.string()).optional(),\n\n /**\n * Default role for authenticated users without explicit role.\n */\n defaultRole: z.string().default('viewer'),\n\n /**\n * Behavior when auth fails.\n */\n onAuthFailure: z.enum(['throw', 'log', 'deny']).default('throw'),\n\n /**\n * Use the flow_access database table to manage flow-level permissions.\n * When enabled, Invect stores flow access records in its own database.\n * @default false\n */\n useFlowAccessTable: z.boolean().default(false),\n});\n\n/**\n * Core Invect configuration schema\n */\nexport const InvectConfigSchema = z.object({\n queryDatabases: queryDatabasesConfigSchema.optional(),\n baseDatabaseConfig: databaseConfigSchema,\n logging: LoggingConfigSchema.default(() => ({\n level: 'info' as const,\n })).optional(),\n logger: z.any().optional(),\n basePath: z.string().optional(),\n /**\n * Execution settings: flow timeout, heartbeat interval, stale run detection.\n */\n execution: ExecutionConfigSchema.optional(),\n /**\n * Authentication and authorization configuration.\n * When not provided, auth is disabled (all requests allowed).\n */\n auth: InvectAuthConfigSchema.optional(),\n /**\n * Trigger system configuration.\n * Controls webhook and cron scheduler behavior.\n */\n triggers: z\n .object({\n /**\n * The public-facing base URL where Invect routes are mounted.\n * Used to display the full webhook URL in the flow editor.\n * Example: \"https://api.myapp.com/invect\"\n */\n webhookBaseUrl: z.string().optional(),\n /**\n * Enable/disable the cron scheduler. When disabled, cron trigger nodes\n * can still be placed on flows but won't fire automatically.\n * @default true\n */\n cronEnabled: z.boolean().default(true),\n })\n .optional(),\n /**\n * Chat assistant configuration.\n * Controls the AI chat sidebar for flow building assistance.\n */\n chat: ChatConfigSchema.optional(),\n /**\n * Plugins extend Invect with additional capabilities:\n * actions, hooks, endpoints, database schema, and middleware.\n *\n * Plugins are plain objects satisfying the `InvectPlugin` interface.\n * They are initialized in array order during `Invect.initialize()`.\n *\n * Database schema changes from plugins require running `npx invect-cli generate`\n * to regenerate the Drizzle schema files, then `npx invect-cli migrate` to apply.\n *\n * @example\n * ```typescript\n * import { rbac } from '@invect/plugin-rbac';\n * import { auditLog } from '@invect/plugin-audit-log';\n *\n * const config = {\n * plugins: [\n * rbac({ resolveUser: (req) => req.user }),\n * auditLog({ destination: 'database' }),\n * ],\n * };\n * ```\n */\n plugins: z.array(z.any()).optional(),\n /**\n * Schema verification configuration.\n *\n * When enabled, Invect checks on startup that the database has all\n * required tables and columns. This does NOT run migrations — the\n * developer is responsible for applying schema changes via the CLI:\n *\n * npx invect-cli generate # regenerate schema files\n * npx drizzle-kit push # apply via Drizzle\n * npx prisma db push # apply via Prisma\n *\n * @example\n * ```typescript\n * const config = {\n * schemaVerification: true, // warn on missing tables/columns\n * // or\n * schemaVerification: { strict: true }, // throw on missing tables/columns\n * };\n * ```\n */\n schemaVerification: z\n .union([\n z.boolean(),\n z.object({\n /** If true, throw an error when schema is invalid. If false, only log warnings. */\n strict: z.boolean().default(false),\n }),\n ])\n .optional(),\n /**\n * Default credentials to ensure on startup.\n *\n * Each entry is created if no credential with the same `name` already exists.\n * Useful for development environments where you want API keys and OAuth\n * tokens available immediately without running a separate seed script.\n *\n * @example\n * ```typescript\n * const config = {\n * defaultCredentials: [\n * {\n * name: 'Anthropic API Key',\n * type: 'http-api',\n * authType: 'bearer',\n * config: { token: process.env.SEED_ANTHROPIC_API_KEY! },\n * description: 'Seeded Anthropic credential',\n * },\n * ],\n * };\n * ```\n */\n defaultCredentials: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n authType: z.string(),\n config: z.record(z.string(), z.unknown()),\n description: z.string().optional(),\n isShared: z.boolean().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n }),\n )\n .optional(),\n});\n\n/**\n * Type inference from schemas\n */\nexport type QueryDatabasesConfig = z.infer<typeof queryDatabasesConfigSchema>;\nexport type ExecutionConfig = z.infer<typeof ExecutionConfigSchema>;\nexport type LoggingConfig = z.infer<typeof LoggingConfigSchema>;\nexport type InvectAuthConfigZod = z.infer<typeof InvectAuthConfigSchema>;\nexport type InvectConfig = z.infer<typeof InvectConfigSchema>;\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"invect-config.js","names":[],"sources":["../../../src/types/schemas-fresh/invect-config.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { ChatConfigSchema } from 'src/services/chat/chat-types';\n\nconst databaseConfigSchema = z.object({\n connectionString: z.string().min(1, 'Database URL is required'),\n type: z.enum(['postgresql', 'sqlite', 'mysql']),\n id: z.string(), // Optional ID for the database, useful for multi-database setups\n name: z.string().optional(), // Human readable name for the database\n});\n\nexport type InvectDatabaseConfig = z.infer<typeof databaseConfigSchema>;\n\n/**\n * Database configuration schema\n */\nexport const queryDatabasesConfigSchema = z\n .array(databaseConfigSchema)\n .optional()\n .default(() => []);\n\n/**\n * Execution configuration schema\n */\nexport const ExecutionConfigSchema = z.object({\n defaultTimeout: z.number().positive().default(60000),\n maxConcurrentExecutions: z.number().positive().default(10),\n enableTracing: z.boolean().default(true),\n /**\n * Maximum time (in ms) a flow run is allowed to stay in RUNNING state\n * before being considered stale and marked as FAILED.\n * @default 600_000 (10 minutes)\n */\n flowTimeoutMs: z.number().positive().default(600_000),\n /**\n * Interval (in ms) at which the heartbeat is updated during flow execution.\n * @default 30_000 (30 seconds)\n */\n heartbeatIntervalMs: z.number().positive().default(30_000),\n /**\n * Interval (in ms) at which the stale run detector polls for stuck runs.\n * @default 60_000 (60 seconds)\n */\n staleRunCheckIntervalMs: z.number().positive().default(60_000),\n});\n\n/**\n * Log level enum values\n */\nexport const LogLevelSchema = z.enum(['debug', 'info', 'warn', 'error', 'silent']);\n\n/**\n * Logging configuration schema with support for scope-level overrides.\n *\n * Scopes allow independent log level control for different feature areas:\n * - execution: Flow execution orchestration\n * - validation: Flow validation\n * - batch: Batch processing (AI providers)\n * - database: Database operations\n * - node: Node execution\n * - graph: Graph operations (topological sort, etc.)\n * - credentials: Credential management\n * - ai: AI/LLM operations\n * - template: Template rendering\n * - renderer: React Flow rendering\n * - flows: Flow management (CRUD)\n * - versions: Flow version management\n * - http: HTTP/API layer\n *\n * @example\n * ```typescript\n * const config = {\n * logging: {\n * level: 'info', // Default level for all scopes\n * scopes: {\n * execution: 'debug', // Verbose execution logging\n * validation: 'warn', // Only validation warnings\n * batch: 'silent', // Disable batch logging\n * }\n * }\n * };\n * ```\n */\nexport const LoggingConfigSchema = z.object({\n /** Default log level for all scopes */\n level: LogLevelSchema.default('silent'),\n /** Per-scope log level overrides */\n scopes: z.record(z.string(), LogLevelSchema).optional(),\n});\n\n/**\n * Built-in Invect roles\n */\nexport const InvectRoleSchema = z.enum(['admin', 'editor', 'operator', 'viewer']);\n\n/**\n * Invect permissions\n */\nexport const InvectPermissionSchema = z.enum([\n // Flow permissions\n 'flow:create',\n 'flow:read',\n 'flow:update',\n 'flow:delete',\n 'flow:publish',\n // Flow version permissions\n 'flow-version:create',\n 'flow-version:read',\n // Execution permissions\n 'flow-run:create',\n 'flow-run:read',\n 'flow-run:cancel',\n // Credential permissions\n 'credential:create',\n 'credential:read',\n 'credential:update',\n 'credential:delete',\n // Agent/tool permissions\n 'agent-tool:read',\n 'agent-tool:configure',\n // Node testing\n 'node:test',\n // Admin wildcard\n 'admin:*',\n]);\n\n/**\n * Authentication/Authorization configuration schema.\n *\n * Invect uses a \"BYO Auth\" (Bring Your Own Authentication) pattern.\n * The host app handles authentication and provides user identity to Invect.\n * Invect handles authorization based on roles and permissions.\n *\n * @example\n * ```typescript\n * const config = {\n * auth: {\n * enabled: true,\n * resolveUser: async (req) => ({\n * id: req.user.id,\n * role: req.user.invectRole,\n * }),\n * roleMapper: {\n * 'super_admin': 'admin',\n * 'content_manager': 'editor',\n * },\n * }\n * };\n * ```\n */\nexport const InvectAuthConfigSchema = z.object({\n /**\n * Enable RBAC (Role-Based Access Control).\n * When false, all requests are allowed without authentication checks.\n * @default false\n */\n enabled: z.boolean().default(false),\n\n /**\n * Function to resolve user identity from incoming request.\n * This is provided at runtime, not validated by Zod.\n */\n resolveUser: z.any().optional(),\n\n /**\n * Map host app roles to Invect roles.\n */\n roleMapper: z.record(z.string(), z.string()).optional(),\n\n /**\n * Define custom roles with specific permissions.\n */\n customRoles: z.record(z.string(), z.array(InvectPermissionSchema)).optional(),\n\n /**\n * Custom authorization callback (provided at runtime).\n */\n customAuthorize: z.any().optional(),\n\n /**\n * Routes that don't require authentication.\n */\n publicRoutes: z.array(z.string()).optional(),\n\n /**\n * Default role for authenticated users without explicit role.\n */\n defaultRole: z.string().default('viewer'),\n\n /**\n * Behavior when auth fails.\n */\n onAuthFailure: z.enum(['throw', 'log', 'deny']).default('throw'),\n\n /**\n * Use the flow_access database table to manage flow-level permissions.\n * When enabled, Invect stores flow access records in its own database.\n * @default false\n */\n useFlowAccessTable: z.boolean().default(false),\n});\n\n/**\n * Core Invect configuration schema\n */\nexport const InvectConfigSchema = z.object({\n queryDatabases: queryDatabasesConfigSchema.optional(),\n baseDatabaseConfig: databaseConfigSchema,\n logging: LoggingConfigSchema.default(() => ({\n level: 'info' as const,\n })).optional(),\n logger: z.any().optional(),\n basePath: z.string().optional(),\n /**\n * Execution settings: flow timeout, heartbeat interval, stale run detection.\n */\n execution: ExecutionConfigSchema.optional(),\n /**\n * Authentication and authorization configuration.\n * When not provided, auth is disabled (all requests allowed).\n */\n auth: InvectAuthConfigSchema.optional(),\n /**\n * Trigger system configuration.\n * Controls webhook and cron scheduler behavior.\n */\n triggers: z\n .object({\n /**\n * The public-facing base URL where Invect routes are mounted.\n * Used to display the full webhook URL in the flow editor.\n * Example: \"https://api.myapp.com/invect\"\n */\n webhookBaseUrl: z.string().optional(),\n /**\n * Enable/disable the cron scheduler. When disabled, cron trigger nodes\n * can still be placed on flows but won't fire automatically.\n * @default true\n */\n cronEnabled: z.boolean().default(true),\n })\n .optional(),\n /**\n * Chat assistant configuration.\n * Controls the AI chat sidebar for flow building assistance.\n */\n chat: ChatConfigSchema.optional(),\n /**\n * Plugins extend Invect with additional capabilities:\n * actions, hooks, endpoints, database schema, and middleware.\n *\n * Plugins are plain objects satisfying the `InvectPlugin` interface.\n * They are initialized in array order during `Invect.initialize()`.\n *\n * Database schema changes from plugins require running `npx invect-cli generate`\n * to regenerate the Drizzle schema files, then `npx invect-cli migrate` to apply.\n *\n * @example\n * ```typescript\n * import { rbac } from '@invect/plugin-rbac';\n * import { auditLog } from '@invect/plugin-audit-log';\n *\n * const config = {\n * plugins: [\n * rbac({ resolveUser: (req) => req.user }),\n * auditLog({ destination: 'database' }),\n * ],\n * };\n * ```\n */\n plugins: z.array(z.any()).optional(),\n /**\n * Schema verification configuration.\n *\n * When enabled, Invect checks on startup that the database has all\n * required tables and columns. This does NOT run migrations — the\n * developer is responsible for applying schema changes via the CLI:\n *\n * npx invect-cli generate # regenerate schema files\n * npx drizzle-kit push # apply via Drizzle\n * npx prisma db push # apply via Prisma\n *\n * @example\n * ```typescript\n * const config = {\n * schemaVerification: true, // warn on missing tables/columns\n * // or\n * schemaVerification: { strict: true }, // throw on missing tables/columns\n * };\n * ```\n */\n schemaVerification: z\n .union([\n z.boolean(),\n z.object({\n /** If true, throw an error when schema is invalid. If false, only log warnings. */\n strict: z.boolean().default(false),\n }),\n ])\n .optional(),\n /**\n * Default credentials to ensure on startup.\n *\n * Each entry is created if no credential with the same `name` already exists.\n * Useful for development environments where you want API keys and OAuth\n * tokens available immediately without running a separate seed script.\n *\n * @example\n * ```typescript\n * const config = {\n * defaultCredentials: [\n * {\n * name: 'Anthropic API Key',\n * type: 'http-api',\n * authType: 'bearer',\n * config: { token: process.env.SEED_ANTHROPIC_API_KEY! },\n * description: 'Seeded Anthropic credential',\n * },\n * ],\n * };\n * ```\n */\n defaultCredentials: z\n .array(\n z.object({\n name: z.string(),\n type: z.string(),\n authType: z.string(),\n config: z.record(z.string(), z.unknown()),\n description: z.string().optional(),\n isShared: z.boolean().optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n }),\n )\n .optional(),\n});\n\n/**\n * Type inference from schemas\n */\nexport type QueryDatabasesConfig = z.infer<typeof queryDatabasesConfigSchema>;\nexport type ExecutionConfig = z.infer<typeof ExecutionConfigSchema>;\nexport type LoggingConfig = z.infer<typeof LoggingConfigSchema>;\nexport type InvectAuthConfigZod = z.infer<typeof InvectAuthConfigSchema>;\nexport type InvectConfig = z.infer<typeof InvectConfigSchema>;\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@invect/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Framework-agnostic core package for Invect workflow execution engine",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -33,8 +33,29 @@
|
|
|
33
33
|
"require": "./dist/database/schema-mysql.cjs"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"build:watch": "tsdown --watch",
|
|
39
|
+
"dev": "tsdown --watch",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "eslint src tests e2e",
|
|
44
|
+
"lint:fix": "eslint src tests --fix",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"test:coverage": "vitest run --coverage",
|
|
48
|
+
"test:unit": "vitest run tests/unit",
|
|
49
|
+
"test:integration": "vitest run tests/integration",
|
|
50
|
+
"test:e2e": "tsx e2e/run.ts",
|
|
51
|
+
"test:run-all": "tsx tests/run-tests.ts",
|
|
52
|
+
"db:generate": "drizzle-kit generate",
|
|
53
|
+
"db:migrate": "drizzle-kit migrate",
|
|
54
|
+
"db:studio": "drizzle-kit studio"
|
|
55
|
+
},
|
|
36
56
|
"dependencies": {
|
|
37
57
|
"@anthropic-ai/sdk": "^0.57.0",
|
|
58
|
+
"@invect/layouts": "workspace:*",
|
|
38
59
|
"better-sqlite3": "^12.0.0",
|
|
39
60
|
"croner": "^10.0.1",
|
|
40
61
|
"drizzle-orm": "^0.44.7",
|
|
@@ -45,8 +66,7 @@
|
|
|
45
66
|
"openai": "^5.11.0",
|
|
46
67
|
"postgres": "^3.4.7",
|
|
47
68
|
"quickjs-emscripten": "^0.32.0",
|
|
48
|
-
"zod": "^3.25.1"
|
|
49
|
-
"@invect/layouts": "0.1.0"
|
|
69
|
+
"zod": "^3.25.1"
|
|
50
70
|
},
|
|
51
71
|
"devDependencies": {
|
|
52
72
|
"@eslint/js": "^9.31.0",
|
|
@@ -97,25 +117,5 @@
|
|
|
97
117
|
"core",
|
|
98
118
|
"framework-agnostic"
|
|
99
119
|
],
|
|
100
|
-
"license": "MIT"
|
|
101
|
-
|
|
102
|
-
"build": "tsdown",
|
|
103
|
-
"build:watch": "tsdown --watch",
|
|
104
|
-
"dev": "tsdown --watch",
|
|
105
|
-
"clean": "rm -rf dist",
|
|
106
|
-
"format": "prettier --write \"src/**/*.ts\"",
|
|
107
|
-
"typecheck": "tsc --noEmit",
|
|
108
|
-
"lint": "eslint src tests e2e",
|
|
109
|
-
"lint:fix": "eslint src tests --fix",
|
|
110
|
-
"test": "vitest run",
|
|
111
|
-
"test:watch": "vitest",
|
|
112
|
-
"test:coverage": "vitest run --coverage",
|
|
113
|
-
"test:unit": "vitest run tests/unit",
|
|
114
|
-
"test:integration": "vitest run tests/integration",
|
|
115
|
-
"test:e2e": "tsx e2e/run.ts",
|
|
116
|
-
"test:run-all": "tsx tests/run-tests.ts",
|
|
117
|
-
"db:generate": "drizzle-kit generate",
|
|
118
|
-
"db:migrate": "drizzle-kit migrate",
|
|
119
|
-
"db:studio": "drizzle-kit studio"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
120
|
+
"license": "MIT"
|
|
121
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 @robase
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|