UNPKG

@c15t/backend

Version:

Consent policy engine and API for c15t. Powers the cookie banner, consent manager, and preferences centre. Webhooks, audit logs, storage adapters. Self host or use consent.io

201 lines (190 loc) 6.5 kB
import { logger } from '@c15t/logger'; import { ZodError } from 'zod'; import type { Field, PluginSchema } from '~/pkgs/data-model'; import type { C15TOptions } from '~/types'; import { getAuditLogTable } from './audit-log/table'; import { getConsentTable } from './consent/table'; import { getConsentPolicyTable } from './consent-policy/table'; import { getPurposeTable } from './consent-purpose/table'; import { getConsentRecordTable } from './consent-record/table'; import { getDomainTable } from './domain/table'; import type { InferTableShape } from './schemas'; import { getSubjectTable } from './subject/table'; /** * Retrieves all consent-related database table definitions * * This function combines the core tables with any additional tables * defined by plugins. It handles merging plugin-defined fields with * the standard tables and ensures all table definitions are properly * structured for use by database adapters. * * @param options - The c15t configuration options * @returns A complete schema mapping containing all table definitions * * @remarks * Each table definition includes both field definitions and the physical * entity name used in the database. Plugins can extend core tables by * defining additional fields, which will be merged with the standard fields. * * @example * ```typescript * // Get all tables with default configuration * const tables = getConsentTables(options); * * // Access fields for the consent table * const consentFields = tables.consent.fields; * ``` */ export const getConsentTables = (options: C15TOptions) => { const pluginSchema = options.plugins?.reduce((acc, plugin) => { const schema = plugin.schema; if (!schema) { return acc; } for (const [key, value] of Object.entries(schema)) { acc[key] = { fields: { ...acc[key]?.fields, ...(value as { fields: Record<string, Field> }).fields, }, entityName: key, }; } return acc; }, {} as PluginSchema); const { subject, consentPurpose, consentPolicy, domain, geoLocation, consent, consentPurposeJunction, record, consentGeoLocation, consentWithdrawal, auditLog, ...pluginTables } = pluginSchema || {}; return { subject: getSubjectTable(options, subject?.fields), consentPurpose: getPurposeTable(options, consentPurpose?.fields), consentPolicy: getConsentPolicyTable(options, consentPolicy?.fields), domain: getDomainTable(options, domain?.fields), consent: getConsentTable(options, consent?.fields), consentRecord: getConsentRecordTable(options, record?.fields), auditLog: getAuditLogTable(options, auditLog?.fields), ...pluginTables, }; }; /** * Type representing the complete database schema for c15t * * This type captures the full structure of all tables in the database, * including core tables and any plugin-defined tables. It's derived from * the return type of `getConsentTables()`. * * @remarks * This is a key type for type-safety throughout the codebase, as it * ensures that table names and field references are validated at compile time. * It's used as a basis for many other type definitions in the database layer. * * @example * ```typescript * // Type-safe reference to a specific table * function processTable<TableName extends keyof C15TDBSchema>( * tableName: TableName, * data: Record<string, unknown> * ) { * const tableFields = getConsentTables(options)[tableName].fields; * // Process with type safety... * } * ``` */ export type C15TDBSchema = ReturnType<typeof getConsentTables>; /** * Type to get all output fields of a table by its name * This type extracts only the fields that are included in output operations, * automatically excluding fields marked with { returned: false }. * It also resolves relationships between tables. */ export type EntityOutputFields<TableName extends keyof C15TDBSchema> = InferTableShape<TableName>; /** * Validates output data against table schema using Zod * * This function validates and transforms output data according to the * schema definition for a specific table. It ensures that all values match * their expected types and excludes fields marked as not to be returned. * * @typeParam TableName - The table name from C15TDBSchema * @param tableName - The name of the table to validate against * @param data - The data to validate * @param options - The C15TOptions instance * @returns Validated and typed data * @throws {Error} If the table is not found or validation fails * * @remarks * This function is particularly useful for validating data received from * external sources or database adapters before processing it in application logic. * * @example * ```typescript * // Validate data retrieved from an external API * try { * const validSubjectOutput = validateEntityOutput( * 'subject', * fetchedSubjectData, * options * ); * * // validSubjectOutput is now typed as EntityOutputFields<'subject'> * displaySubjectProfile(validSubjectOutput); * } catch (error) { * console.error('Output validation failed:', error.message); * } * ``` */ export function validateEntityOutput<TableName extends keyof C15TDBSchema>( tableName: TableName, data: Record<string, unknown>, options: C15TOptions ): EntityOutputFields<TableName> { const tables = getConsentTables(options); const table = tables[tableName]; if (!table) { throw new Error(`Table ${tableName} not found`); } // Pre-process data: Convert date strings to Date objects const processedData = { ...data }; for (const [field, def] of Object.entries(table.fields)) { if (def.type === 'date' && typeof processedData[field] === 'string') { processedData[field] = new Date(processedData[field] as string); } } // This is useful for debugging validation issues // console.log('[validateEntityOutput] Debug data:', { // fields: Object.fromEntries( // Object.entries(table.fields).map(([key, field]) => [ // key, // { // type: field.type, // required: field.required, // value: processedData[field.fieldName as keyof typeof processedData], // }, // ]) // ), // }); // Validate and return data using Zod schema try { return table.schema.parse(processedData) as EntityOutputFields<TableName>; } catch (error) { if (error instanceof ZodError) { logger.error( `[validateEntityOutput] Validation failed for table ${String(tableName)}`, { table, issues: error.issues } ); } throw error; } }