     1	/**
     2	 * Extracts all foreign key fields from a schema definition
     3	 *
     4	 * @param {Object} schema - The schema definition (Schema object or plain object)
     5	 * @returns {Set<string>} Set of foreign key field names
     6	 *
     7	 * @example
     8	 * // Input: Schema with belongsTo relationships
     9	 * const schema = {
    10	 *   title: { type: 'string' },
    11	 *   content: { type: 'string' },
    12	 *   author_id: { type: 'number', belongsTo: 'users', as: 'author' },
    13	 *   category_id: { type: 'number', belongsTo: 'categories', as: 'category' },
    14	 *   status: { type: 'string' }
    15	 * };
    16	 *
    17	 * const foreignKeys = getForeignKeyFields(schema);
    18	 *
    19	 * // Output: Set containing only foreign key fields
    20	 * // Set(['author_id', 'category_id'])
    21	 *
    22	 * @example
    23	 * // Input: Works with compiled Schema objects too
    24	 * const schemaObject = {
    25	 *   structure: {
    26	 *     post_id: { type: 'id', belongsTo: 'posts', as: 'post' },
    27	 *     user_id: { type: 'id', belongsTo: 'users', as: 'user' }
    28	 *   }
    29	 * };
    30	 *
    31	 * const foreignKeys = getForeignKeyFields(schemaObject);
    32	 *
    33	 * // Output: Handles both formats
    34	 * // Set(['post_id', 'user_id'])
    35	 *
    36	 * @description
    37	 * Used by:
    38	 * - toJsonApi to filter foreign keys from attributes
    39	 * - buildFieldSelection to ensure foreign keys are always selected
    40	 * - knex-json-api-helpers for field filtering
    41	 *
    42	 * Purpose:
    43	 * - Identifies fields that should be relationships, not attributes
    44	 * - Foreign keys are stored in DB but not exposed as JSON:API attributes
    45	 * - Ensures these fields are included in SELECT for relationship building
    46	 * - Helps transform database structure to JSON:API format
    47	 *
    48	 * Data flow:
    49	 * 1. Accepts schema in either format (plain or with structure property)
    50	 * 2. Iterates through all field definitions
    51	 * 3. Collects fields with belongsTo property
    52	 * 4. Returns Set for efficient lookups
    53	 */
    54	export const getForeignKeyFields = (schema) => {
    55	  const foreignKeys = new Set()
    56	  if (!schema) return foreignKeys
    57	
    58	  // Handle both Schema objects and plain objects
    59	  const schemaStructure = schema.structure || schema
    60	
    61	  Object.entries(schemaStructure).forEach(([field, def]) => {
    62	    if (def.belongsTo) {
    63	      foreignKeys.add(field)
    64	    }
    65	  })
    66	  return foreignKeys
    67	}
    68	
    69	/**
    70	 * Filters hidden fields from attributes based on schema rules.
    71	 *
    72	 * This pure function removes fields marked as hidden or normallyHidden
    73	 * based on the schema definition and requested fields. It ensures that
    74	 * sensitive data is never exposed in API responses.
    75	 *
    76	 * @param {Object} attributes - The attributes object to filter
    77	 * @param {Object} schema - The schema object with structure property
    78	 * @param {Array<string>|string} requestedFields - Fields explicitly requested (for normallyHidden)
    79	 * @returns {Object} Filtered attributes object
    80	 *
    81	 * @example <caption>Filtering hidden fields</caption>
    82	 * const attributes = {
    83	 *   name: 'John',
    84	 *   email: 'john@example.com',
    85	 *   password_hash: 'xxx',
    86	 *   internal_id: '123'
    87	 * };
    88	 * const schema = {
    89	 *   structure: {
    90	 *     name: { type: 'string' },
    91	 *     email: { type: 'string' },
    92	 *     password_hash: { type: 'string', hidden: true },
    93	 *     internal_id: { type: 'string', normallyHidden: true }
    94	 *   }
    95	 * };
    96	 * const filtered = filterHiddenFields(attributes, schema, null);
    97	 * // Returns: { name: 'John', email: 'john@example.com' }
    98	 * // password_hash (hidden) and internal_id (normallyHidden) are removed
    99	 *
   100	 * @example <caption>Including normallyHidden fields when requested</caption>
   101	 * const filtered = filterHiddenFields(attributes, schema, 'name,internal_id');
   102	 * // Returns: { name: 'John', internal_id: '123' }
   103	 * // internal_id is included because explicitly requested
   104	 * // password_hash is still filtered (always hidden)
   105	 */
   106	export const filterHiddenFields = (attributes, schema, requestedFields) => {
   107	  const filtered = {}
   108	
   109	  // Parse requested fields if it's a string (from query params)
   110	  // Example: "name,price,cost" -> ['name', 'price', 'cost']
   111	  const requested = requestedFields
   112	    ? (
   113	        typeof requestedFields === 'string'
   114	          ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
   115	          : requestedFields
   116	      )
   117	    : null
   118	
   119	  Object.entries(attributes).forEach(([field, value]) => {
   120	    const fieldDef = schema.structure?.[field]
   121	
   122	    // Never include hidden fields - these are completely invisible
   123	    // Example: password_hash with hidden:true is always filtered out
   124	    if (fieldDef?.hidden === true) return
   125	
   126	    // Include normallyHidden fields only if explicitly requested
   127	    // Example: 'cost' with normallyHidden:true is only included if user requests it
   128	    // via sparse fieldsets like ?fields[products]=name,cost
   129	    if (fieldDef?.normallyHidden === true) {
   130	      if (!requested || !requested.includes(field)) {
   131	        return // Filter out normallyHidden field
   132	      }
   133	    }
   134	
   135	    filtered[field] = value
   136	  })
   137	
   138	  return filtered
   139	}
