plugins/core/rest-api-plugin-methods/query.js:15: * @param {string[]} [queryParams.include=[]] - An optional array of relationship paths to sideload for each resource in the collection. These paths will be converted to a comma-separated string for the URL (e.g., `['author', 'comments.user']` becomes `author,comments.user`). Supports deep relationships (e.g., "publisher.country").
plugins/core/rest-api-plugin-methods/query.js:16: * @param {object} [queryParams.fields] - An object to request only specific fields (sparse fieldsets) for each resource in the collection and its included relationships. Keys are resource types, values are comma-separated field names.
plugins/core/rest-api-plugin-methods/query.js:19: * @param {object} [queryParams.page] - An object for pagination. Typically includes `number` (page number) and `size` (items per page). E.g., `{ number: 1, size: 10 }`.
plugins/core/rest-api-plugin-methods/query.js:54:  context.queryParams.include = cascadeConfig('include', [params.queryParams], [])
plugins/core/rest-api-plugin-methods/query.js:77:  // (preventing SQL injection), pagination uses valid page[size]/page[number] format, and include
plugins/core/rest-api-plugin-methods/query.js:78:  // paths reference real relationships. Example: sort: ['-createdAt', 'title'] is checked against
plugins/core/rest-api-plugin-methods/query.js:80:  validateQueryPayload({ queryParams: context.queryParams }, context.sortableFields, vars.includeDepthLimit)
plugins/core/rest-api-plugin-methods/query.js:172:  for (const entry of (context.record.included || [])) {
plugins/core/rest-api-plugin-methods/query.js:198:    // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
plugins/core/lib/querying/knex-json-api-transformers-querying.js:66: * - Filters out foreign keys that become relationships in JSON:API
plugins/core/lib/querying/knex-json-api-transformers-querying.js:73: * - Output feeds into relationship building and response assembly
plugins/core/lib/querying/knex-json-api-transformers-querying.js:113: * @param {Object} scope - Scope containing schema and relationship definitions
plugins/core/lib/querying/knex-json-api-transformers-querying.js:123: *   author_id: 2,      // belongsTo relationship
plugins/core/lib/querying/knex-json-api-transformers-querying.js:124: *   category_id: 3,    // belongsTo relationship
plugins/core/lib/querying/knex-json-api-transformers-querying.js:143: * // Input: Record with polymorphic relationship
plugins/core/lib/querying/knex-json-api-transformers-querying.js:151: * // Schema has polymorphic relationship defined
plugins/core/lib/querying/knex-json-api-transformers-querying.js:168: * - processIncludes when transforming included resources
plugins/core/lib/querying/knex-json-api-transformers-querying.js:174: * - Handles polymorphic relationship fields (type/id pairs)
plugins/core/lib/querying/knex-json-api-transformers-querying.js:181: * 4. Returns clean JSON:API resource ready for relationship processing
plugins/core/lib/querying/knex-json-api-transformers-querying.js:182: * 5. buildJsonApiResponse adds relationships and links to complete the response
plugins/core/lib/querying/knex-json-api-transformers-querying.js:187:      schemaInfo: { schemaRelationships: relationships, idProperty }
plugins/core/lib/querying/knex-json-api-transformers-querying.js:193:    Object.entries(relationships || {}).forEach(([relName, relDef]) => {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:216: * Builds complete JSON:API response with data, relationships, links, and optional includes
plugins/core/lib/querying/knex-json-api-transformers-querying.js:220: * @param {Array<Object>} records - Primary records to include in response
plugins/core/lib/querying/knex-json-api-transformers-querying.js:221: * @param {Array<Object>} included - Resources to include in 'included' array
plugins/core/lib/querying/knex-json-api-transformers-querying.js:224: * @param {Object} context - Request context with pagination metadata
plugins/core/lib/querying/knex-json-api-transformers-querying.js:228: * // Input: Single article with author include
plugins/core/lib/querying/knex-json-api-transformers-querying.js:237: * const included = [{
plugins/core/lib/querying/knex-json-api-transformers-querying.js:243: * const response = await buildJsonApiResponse(scope, records, included, true, 'articles', context);
plugins/core/lib/querying/knex-json-api-transformers-querying.js:254: * //     relationships: {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:258: * //           self: '/articles/1/relationships/author',
plugins/core/lib/querying/knex-json-api-transformers-querying.js:265: * //           self: '/articles/1/relationships/category',
plugins/core/lib/querying/knex-json-api-transformers-querying.js:274: * //   included: [{
plugins/core/lib/querying/knex-json-api-transformers-querying.js:286: * // Input: Collection with pagination
plugins/core/lib/querying/knex-json-api-transformers-querying.js:294: *   paginationMeta: { page: 2, pageSize: 3, pageCount: 10, total: 30 },
plugins/core/lib/querying/knex-json-api-transformers-querying.js:295: *   paginationLinks: {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:306: * // Output: Collection with pagination metadata
plugins/core/lib/querying/knex-json-api-transformers-querying.js:314: * //     pagination: { page: 2, pageSize: 3, pageCount: 10, total: 30 }
plugins/core/lib/querying/knex-json-api-transformers-querying.js:333: * - Adds relationship objects with proper links for each foreign key
plugins/core/lib/querying/knex-json-api-transformers-querying.js:334: * - Handles both regular belongsTo and polymorphic relationships
plugins/core/lib/querying/knex-json-api-transformers-querying.js:336: * - Includes pagination metadata and links when applicable
plugins/core/lib/querying/knex-json-api-transformers-querying.js:339: * 1. Query operations fetch primary records and optional includes
plugins/core/lib/querying/knex-json-api-transformers-querying.js:341: * 3. buildJsonApiResponse adds relationship objects for all foreign keys
plugins/core/lib/querying/knex-json-api-transformers-querying.js:343: * 5. Assembles included resources with their own links
plugins/core/lib/querying/knex-json-api-transformers-querying.js:344: * 6. Adds pagination metadata and links if provided
plugins/core/lib/querying/knex-json-api-transformers-querying.js:347:export const buildJsonApiResponse = async (scope, records, included = [], isSingle = false, scopeName, context) => {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:350:      schemaInfo: { schemaInstance, schemaRelationships: relationships, idProperty }
plugins/core/lib/querying/knex-json-api-transformers-querying.js:359:    const { [RELATIONSHIPS_KEY]: _relationships, ...cleanRecord } = record
plugins/core/lib/querying/knex-json-api-transformers-querying.js:362:    if (_relationships) {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:363:      jsonApiRecord.relationships = _relationships
plugins/core/lib/querying/knex-json-api-transformers-querying.js:368:        jsonApiRecord.relationships = jsonApiRecord.relationships || {}
plugins/core/lib/querying/knex-json-api-transformers-querying.js:369:        if (!jsonApiRecord.relationships[fieldDef.as]) {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:371:            const relationshipObject = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:378:            relationshipObject.links = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:383:            jsonApiRecord.relationships[fieldDef.as] = relationshipObject
plugins/core/lib/querying/knex-json-api-transformers-querying.js:385:            const relationshipObject = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:389:            relationshipObject.links = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:394:            jsonApiRecord.relationships[fieldDef.as] = relationshipObject
plugins/core/lib/querying/knex-json-api-transformers-querying.js:400:    Object.entries(relationships || {}).forEach(([relName, relDef]) => {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:406:          jsonApiRecord.relationships = jsonApiRecord.relationships || {}
plugins/core/lib/querying/knex-json-api-transformers-querying.js:407:          const relationshipObject = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:414:          relationshipObject.links = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:419:          jsonApiRecord.relationships[relName] = relationshipObject
plugins/core/lib/querying/knex-json-api-transformers-querying.js:421:          jsonApiRecord.relationships = jsonApiRecord.relationships || {}
plugins/core/lib/querying/knex-json-api-transformers-querying.js:422:          const relationshipObject = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:426:          relationshipObject.links = {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:431:          jsonApiRecord.relationships[relName] = relationshipObject
plugins/core/lib/querying/knex-json-api-transformers-querying.js:458:  if (included.length > 0) {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:459:    included.forEach(item => {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:464:    response.included = included
plugins/core/lib/querying/knex-json-api-transformers-querying.js:467:  if (context?.returnMeta?.paginationMeta) {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:469:      pagination: context.returnMeta.paginationMeta
plugins/core/lib/querying/knex-json-api-transformers-querying.js:473:  if (context?.returnMeta?.paginationLinks) {
plugins/core/lib/querying/knex-json-api-transformers-querying.js:474:    response.links = context.returnMeta.paginationLinks
plugins/core/lib/querying/knex-query-helpers.js:130: * Processes filters that target polymorphic relationships where a single relationship can point to different types of resources
plugins/core/lib/querying/knex-query-helpers.js:140: *     polymorphicField: 'commentable',  // Points to the polymorphic relationship
plugins/core/lib/querying/knex-query-helpers.js:189: * - Enables filtering on polymorphic relationships without knowing the concrete type
plugins/core/lib/querying/knex-query-helpers.js:250:    // Get the relationship definition
plugins/core/lib/querying/knex-query-helpers.js:251:    const relationships = scopes[scopeName].vars.schemaInfo.schemaRelationships
plugins/core/lib/querying/knex-query-helpers.js:252:    const polyRel = relationships[polymorphicField]
plugins/core/lib/querying/knex-query-helpers.js:256:        `Polymorphic field '${polymorphicField}' not found in relationships for scope '${scopeName}'`
plugins/core/lib/querying/knex-query-helpers.js:301:        if (targetFieldPath.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:310:            const relationshipName = pathParts[i]
plugins/core/lib/querying/knex-query-helpers.js:312:            // Find the foreign key for this relationship
plugins/core/lib/querying/knex-query-helpers.js:319:              if (fieldDef.as === relationshipName && fieldDef.belongsTo) {
plugins/core/lib/querying/knex-query-helpers.js:327:              // Check relationships for hasOne
plugins/core/lib/querying/knex-query-helpers.js:329:              const rel = currentRelationships?.[relationshipName]
plugins/core/lib/querying/knex-query-helpers.js:333:                  'Cross-table polymorphic search through hasOne relationships not yet supported'
plugins/core/lib/querying/knex-query-helpers.js:338:                `Cannot resolve relationship '${relationshipName}' in path '${targetFieldPath}' for scope '${currentScope}'`
plugins/core/lib/querying/knex-query-helpers.js:343:            const nextAlias = `${currentAlias}_${relationshipName}`
plugins/core/lib/querying/knex-query-helpers.js:369:  // Pre-fetch relationships for WHERE clause processing
plugins/core/lib/querying/knex-query-helpers.js:371:  const relationships = scopes[scopeName].vars.schemaInfo.schemaRelationships
plugins/core/lib/querying/knex-query-helpers.js:374:    const polyRel = relationships[polymorphicField]
plugins/core/lib/querying/knex-query-helpers.js:410:            if (targetFieldPath.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:488: * // Input: Deep nested relationships (3 levels)
plugins/core/lib/querying/knex-query-helpers.js:498: * // Result: Chain of JOINs following relationships
plugins/core/lib/querying/knex-query-helpers.js:514: * - Detects one-to-many relationships and adds DISTINCT to prevent duplicates
plugins/core/lib/querying/knex-query-helpers.js:564:    if (fieldDef.actualField?.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:577:        if (field.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:609:    if (!trimmed.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:620:    if (field.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:628:    if (field.includes('.')) {
plugins/core/lib/querying/knex-query-helpers.js:687:    if (join.isPolymorphic && join.joinCondition.includes(' AND ')) {
plugins/core/lib/querying/knex-query-helpers.js:732:      if (!fieldDef.actualField?.includes('.') &&
plugins/core/lib/querying/knex-query-helpers.js:733:          !fieldDef.oneOf?.some(f => f.includes('.'))) {
plugins/core/lib/querying/knex-query-helpers.js:987:      if (fieldDef.actualField?.includes('.') ||
plugins/core/lib/querying/knex-query-helpers.js:988:          fieldDef.oneOf?.some(f => f.includes('.')) ||
plugins/core/lib/querying/url-helpers.js:76: * Build a relationship URL
plugins/core/lib/querying/url-helpers.js:82: * @param {string} relationshipName - Name of the relationship
plugins/core/lib/querying/url-helpers.js:83: * @param {boolean} isRelationshipEndpoint - If true, builds /relationships/ URL
plugins/core/lib/querying/url-helpers.js:84: * @returns {string} Complete relationship URL
plugins/core/lib/querying/url-helpers.js:87: * // For relationship linkage endpoint
plugins/core/lib/querying/url-helpers.js:89: * // Returns: "https://api.example.com/api/books/1/relationships/author"
plugins/core/lib/querying/url-helpers.js:95:export function buildRelationshipUrl (context, scope, scopeName, id, relationshipName, isRelationshipEndpoint = false) {
plugins/core/lib/querying/url-helpers.js:98:    return `${urlPrefix}/${scopeName}/${id}/relationships/${relationshipName}`
plugins/core/lib/querying/url-helpers.js:100:  return `${urlPrefix}/${scopeName}/${id}/${relationshipName}`
plugins/core/lib/querying/knex-relationship-includes.js:5: * This module implements the JSON:API include functionality, enabling compound documents
plugins/core/lib/querying/knex-relationship-includes.js:6: * that contain both primary data and related resources. It handles all relationship types
plugins/core/lib/querying/knex-relationship-includes.js:7: * and supports arbitrary nesting depths while preventing N+1 query problems.
plugins/core/lib/querying/knex-relationship-includes.js:10: * // Input: Simple include request
plugins/core/lib/querying/knex-relationship-includes.js:11: * // GET /articles?include=author
plugins/core/lib/querying/knex-relationship-includes.js:37: * // Input: Nested includes
plugins/core/lib/querying/knex-relationship-includes.js:38: * // GET /articles?include=comments.author
plugins/core/lib/querying/knex-relationship-includes.js:46: * // Input: Polymorphic includes
plugins/core/lib/querying/knex-relationship-includes.js:47: * // GET /activities?include=trackable
plugins/core/lib/querying/knex-relationship-includes.js:58: * - Results go into the 'included' section of JSON:API response
plugins/core/lib/querying/knex-relationship-includes.js:61: * - Implements JSON:API compound documents with primary data and included resources
plugins/core/lib/querying/knex-relationship-includes.js:62: * - Prevents N+1 queries through batch loading (1 query per resource type)
plugins/core/lib/querying/knex-relationship-includes.js:69: * 1. Parse include parameter into tree structure (author,comments.author)
plugins/core/lib/querying/knex-relationship-includes.js:70: * 2. For each relationship, determine type (belongsTo, hasMany, polymorphic)
plugins/core/lib/querying/knex-relationship-includes.js:72: * 4. Recursively process nested includes
plugins/core/lib/querying/knex-relationship-includes.js:73: * 5. Store all included resources in deduplication Map
plugins/core/lib/querying/knex-relationship-includes.js:74: * 6. Return array of unique included resources
plugins/core/lib/querying/knex-relationship-includes.js:80:import { buildWindowedIncludeQuery, applyStandardIncludeConfig, buildOrderByClause } from './knex-window-queries.js'
plugins/core/lib/querying/knex-relationship-includes.js:115: * // Input: Comments with polymorphic commentable relationship
plugins/core/lib/querying/knex-relationship-includes.js:132: * // Input: Some records have null relationships
plugins/core/lib/querying/knex-relationship-includes.js:154: * - Minimizes database queries when loading polymorphic relationships
plugins/core/lib/querying/knex-relationship-includes.js:180:    if (!grouped[type].includes(id)) {
plugins/core/lib/querying/knex-relationship-includes.js:189: * Parses the include parameter string into a tree structure
plugins/core/lib/querying/knex-relationship-includes.js:191: * @param {string|Array<string>} includeParam - The include parameter value
plugins/core/lib/querying/knex-relationship-includes.js:192: * @returns {Object} Nested object representing the include tree
plugins/core/lib/querying/knex-relationship-includes.js:195: * // Input: Simple comma-separated includes
plugins/core/lib/querying/knex-relationship-includes.js:205: * // Input: Nested includes with dot notation
plugins/core/lib/querying/knex-relationship-includes.js:217: * // Input: Complex mixed-depth includes
plugins/core/lib/querying/knex-relationship-includes.js:234: * - buildIncludedResources parses the include parameter before processing
plugins/core/lib/querying/knex-relationship-includes.js:239: * - Enables recursive traversal of relationship includes
plugins/core/lib/querying/knex-relationship-includes.js:249:export const parseIncludeTree = (includeParam) => {
plugins/core/lib/querying/knex-relationship-includes.js:250:  if (!includeParam) return {}
plugins/core/lib/querying/knex-relationship-includes.js:253:  const includes = Array.isArray(includeParam)
plugins/core/lib/querying/knex-relationship-includes.js:254:    ? includeParam
plugins/core/lib/querying/knex-relationship-includes.js:255:    : includeParam.split(',').map(s => s.trim()).filter(Boolean)
plugins/core/lib/querying/knex-relationship-includes.js:259:  includes.forEach(include => {
plugins/core/lib/querying/knex-relationship-includes.js:260:    const parts = include.split('.')
plugins/core/lib/querying/knex-relationship-includes.js:275: * Loads relationship metadata for included resources
plugins/core/lib/querying/knex-relationship-includes.js:276: * This ensures included resources have complete JSON:API representation
plugins/core/lib/querying/knex-relationship-includes.js:279: * @param {Array<Object>} records - Records to add relationships to
plugins/core/lib/querying/knex-relationship-includes.js:289:    const relationships = schemaInfo.schemaRelationships || {}
plugins/core/lib/querying/knex-relationship-includes.js:295:      // Process belongsTo relationships from schema
plugins/core/lib/querying/knex-relationship-includes.js:312:      // Process hasMany, hasOne and manyToMany relationships
plugins/core/lib/querying/knex-relationship-includes.js:313:      for (const [relName, relDef] of Object.entries(relationships)) {
plugins/core/lib/querying/knex-relationship-includes.js:315:          // Add empty relationship data - will be populated if explicitly included
plugins/core/lib/querying/knex-relationship-includes.js:321:          // Process polymorphic relationships defined in relationships section
plugins/core/lib/querying/knex-relationship-includes.js:343:    console.error('[loadRelationshipMetadata] Error loading relationship metadata:', errorContext)
plugins/core/lib/querying/knex-relationship-includes.js:344:    throw new Error(`Failed to load relationship metadata for scope '${scopeName}': ${error.message}`)
plugins/core/lib/querying/knex-relationship-includes.js:349: * Loads belongsTo relationships (many-to-one)
plugins/core/lib/querying/knex-relationship-includes.js:351: * This function loads the "parent" side of a relationship. For example, if comments
plugins/core/lib/querying/knex-relationship-includes.js:355: *   - records: Array<Object> - Records to load relationships for
plugins/core/lib/querying/knex-relationship-includes.js:358: *   - includeName: string - The relationship name (e.g., 'author')
plugins/core/lib/querying/knex-relationship-includes.js:359: *   - subIncludes: Object - Nested includes to process recursively
plugins/core/lib/querying/knex-relationship-includes.js:360: *   - included: Map - Map of already included resources
plugins/core/lib/querying/knex-relationship-includes.js:362: *   - currentPath: string - Current include path for tracking
plugins/core/lib/querying/knex-relationship-includes.js:372:  const { records, scopeName, fieldName, fieldDef, includeName, subIncludes, included, processedPaths, currentPath, fields, idProperty } = scope
plugins/core/lib/querying/knex-relationship-includes.js:377:      includeName,
plugins/core/lib/querying/knex-relationship-includes.js:401:      // No relationships to load, set all to null
plugins/core/lib/querying/knex-relationship-includes.js:404:        const relationshipObject = { data: null }
plugins/core/lib/querying/knex-relationship-includes.js:409:          relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:410:            self: `${urlPrefix}/${scopeName}/${record[idProperty]}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:411:            related: `${urlPrefix}/${scopeName}/${record[idProperty]}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:415:        record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:450:    // Load relationship metadata for all target records
plugins/core/lib/querying/knex-relationship-includes.js:459:    // Set relationships on original records
plugins/core/lib/querying/knex-relationship-includes.js:476:        // Add relationships from metadata
plugins/core/lib/querying/knex-relationship-includes.js:478:          jsonApiRecord.relationships = targetRecord[RELATIONSHIP_METADATA_KEY]
plugins/core/lib/querying/knex-relationship-includes.js:488:        const relationshipObject = { data: { type: targetScope, id: String(targetId) } }
plugins/core/lib/querying/knex-relationship-includes.js:493:          relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:494:            self: `${urlPrefix}/${scopeName}/${record[idProperty]}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:495:            related: `${urlPrefix}/${scopeName}/${record[idProperty]}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:499:        record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:501:        // Add to included if not already there
plugins/core/lib/querying/knex-relationship-includes.js:503:        if (!included.has(resourceKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:504:          included.set(resourceKey, jsonApiRecord) // Now includes relationships!
plugins/core/lib/querying/knex-relationship-includes.js:512:        const relationshipObject = { data: null }
plugins/core/lib/querying/knex-relationship-includes.js:517:          relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:518:            self: `${urlPrefix}/${scopeName}/${record[idProperty]}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:519:            related: `${urlPrefix}/${scopeName}/${record[idProperty]}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:523:        record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:527:    // Process nested includes if any
plugins/core/lib/querying/knex-relationship-includes.js:529:      const nextPath = `${currentPath}.${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:532:          { records: targetRecordsToProcess, scopeName: targetScope, includeTree: subIncludes, included, processedPaths, currentPath: nextPath, fields, idProperty: targetIdProperty },
plugins/core/lib/querying/knex-relationship-includes.js:539:    log.error('[INCLUDE] Error loading belongsTo relationship:', {
plugins/core/lib/querying/knex-relationship-includes.js:541:      includeName,
plugins/core/lib/querying/knex-relationship-includes.js:550:      `Failed to load belongsTo relationship '${includeName}' for field '${fieldName}': ${error.message}`
plugins/core/lib/querying/knex-relationship-includes.js:553:    enhancedError.context = { fieldName, includeName, targetScope: fieldDef.belongsTo }
plugins/core/lib/querying/knex-relationship-includes.js:559: * Loads hasMany relationships (one-to-many or many-to-many)
plugins/core/lib/querying/knex-relationship-includes.js:561: * This function loads the "child" side of a relationship. For example, if articles
plugins/core/lib/querying/knex-relationship-includes.js:563: * many-to-many relationships through a pivot table.
plugins/core/lib/querying/knex-relationship-includes.js:566: *   - records: Array<Object> - Parent records to load relationships for
plugins/core/lib/querying/knex-relationship-includes.js:568: *   - includeName: string - The relationship name to include
plugins/core/lib/querying/knex-relationship-includes.js:569: *   - relDef: Object - The relationship definition
plugins/core/lib/querying/knex-relationship-includes.js:570: *   - subIncludes: Object - Nested includes to process recursively
plugins/core/lib/querying/knex-relationship-includes.js:571: *   - included: Map - Map of already included resources
plugins/core/lib/querying/knex-relationship-includes.js:573: *   - currentPath: string - Current include path for tracking
plugins/core/lib/querying/knex-relationship-includes.js:582:  const { records, scopeName, includeName, relDef, subIncludes, included, processedPaths, currentPath, fields } = scope
plugins/core/lib/querying/knex-relationship-includes.js:585:    log.trace('[INCLUDE] Loading hasMany/manyToMany relationship:', {
plugins/core/lib/querying/knex-relationship-includes.js:587:      includeName,
plugins/core/lib/querying/knex-relationship-includes.js:602:    // Check if this is a many-to-many relationship
plugins/core/lib/querying/knex-relationship-includes.js:604:    // Handle many-to-many relationship
plugins/core/lib/querying/knex-relationship-includes.js:610:        throw new Error(`Missing foreignKey or otherKey in many-to-many relationship '${relName}' for scope '${scopeName}'`)
plugins/core/lib/querying/knex-relationship-includes.js:613:      // For manyToMany, the target scope is the relationship name itself
plugins/core/lib/querying/knex-relationship-includes.js:614:      const targetScope = includeName
plugins/core/lib/querying/knex-relationship-includes.js:628:      // No relationships found, set empty arrays for all records
plugins/core/lib/querying/knex-relationship-includes.js:631:          const relationshipObject = { data: [] }
plugins/core/lib/querying/knex-relationship-includes.js:636:            relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:637:              self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:638:              related: `${urlPrefix}/${scopeName}/${record.id}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:642:          record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:652:        includeConfig: relDef.include
plugins/core/lib/querying/knex-relationship-includes.js:674:      if (relDef.include?.strategy === 'window') {
plugins/core/lib/querying/knex-relationship-includes.js:675:      // For many-to-many with window functions, we need to limit per parent
plugins/core/lib/querying/knex-relationship-includes.js:682:        const effectiveLimit = relDef.include?.limit ?? targetVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT
plugins/core/lib/querying/knex-relationship-includes.js:688:            detail: `Requested include limit (${effectiveLimit}) exceeds queryMaxLimit (${targetVars.queryMaxLimit})`,
plugins/core/lib/querying/knex-relationship-includes.js:694:        const windowQuery = knex
plugins/core/lib/querying/knex-relationship-includes.js:699:          windowQuery.select(`${targetTable}.${targetIdProperty} as id`)
plugins/core/lib/querying/knex-relationship-includes.js:702:        windowQuery.select(
plugins/core/lib/querying/knex-relationship-includes.js:705:            buildOrderByClause(relDef.include?.orderBy || [targetIdProperty], targetTable) +
plugins/core/lib/querying/knex-relationship-includes.js:717:          .from(windowQuery.as('_windowed'))
plugins/core/lib/querying/knex-relationship-includes.js:737:        // Apply standard include config (global limits)
plugins/core/lib/querying/knex-relationship-includes.js:738:        if (relDef.include) {
plugins/core/lib/querying/knex-relationship-includes.js:740:          query = applyStandardIncludeConfig(query, relDef.include, targetVars, log)
plugins/core/lib/querying/knex-relationship-includes.js:748:      // Step 5: Load relationship metadata for all target records
plugins/core/lib/querying/knex-relationship-includes.js:767:      // Step 7: Set relationships on parent records
plugins/core/lib/querying/knex-relationship-includes.js:778:          // Add to included
plugins/core/lib/querying/knex-relationship-includes.js:780:            if (!included.has(resourceKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:787:              // Add relationships from metadata
plugins/core/lib/querying/knex-relationship-includes.js:789:                jsonApiRecord.relationships = childRecord[RELATIONSHIP_METADATA_KEY]
plugins/core/lib/querying/knex-relationship-includes.js:799:              included.set(resourceKey, jsonApiRecord)
plugins/core/lib/querying/knex-relationship-includes.js:804:        const relationshipObject = { data: relData }
plugins/core/lib/querying/knex-relationship-includes.js:809:          relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:810:            self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:811:            related: `${urlPrefix}/${scopeName}/${record.id}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:815:        record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:817:        // Update the record in the included Map if it exists
plugins/core/lib/querying/knex-relationship-includes.js:819:        if (included.has(recordKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:820:          const existingRecord = included.get(recordKey)
plugins/core/lib/querying/knex-relationship-includes.js:821:          if (!existingRecord.relationships) {
plugins/core/lib/querying/knex-relationship-includes.js:822:            existingRecord.relationships = {}
plugins/core/lib/querying/knex-relationship-includes.js:824:          existingRecord.relationships[includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:828:      // Step 8: Process nested includes if any
plugins/core/lib/querying/knex-relationship-includes.js:830:        const nextPath = `${currentPath}.${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:833:            { records: targetRecords, scopeName: targetScope, includeTree: subIncludes, included, processedPaths, currentPath: nextPath, fields, idProperty: targetIdProperty },
plugins/core/lib/querying/knex-relationship-includes.js:839:    // Handle one-to-many relationship
plugins/core/lib/querying/knex-relationship-includes.js:845:        throw new Error(`Missing foreignKey in hasMany relationship '${relName}' for scope '${scopeName}'`)
plugins/core/lib/querying/knex-relationship-includes.js:850:        includeConfig: relDef.include
plugins/core/lib/querying/knex-relationship-includes.js:872:      // Check if we should use window functions
plugins/core/lib/querying/knex-relationship-includes.js:873:      if (relDef.include?.strategy === 'window') {
plugins/core/lib/querying/knex-relationship-includes.js:875:        // Try to build window function query
plugins/core/lib/querying/knex-relationship-includes.js:886:            relDef.include || {},
plugins/core/lib/querying/knex-relationship-includes.js:891:          log.debug('[INCLUDE] Using window function strategy with limits')
plugins/core/lib/querying/knex-relationship-includes.js:893:        // If window functions not supported, this will throw a clear error
plugins/core/lib/querying/knex-relationship-includes.js:894:          if (error.details?.requiredFeature === 'window_functions') {
plugins/core/lib/querying/knex-relationship-includes.js:906:      // Build standard query if not using window functions
plugins/core/lib/querying/knex-relationship-includes.js:919:          relDef.include || {},
plugins/core/lib/querying/knex-relationship-includes.js:928:      // If using window functions, remove the row number column
plugins/core/lib/querying/knex-relationship-includes.js:938:      // Load relationship metadata for all target records
plugins/core/lib/querying/knex-relationship-includes.js:951:      // Set relationships on parent records
plugins/core/lib/querying/knex-relationship-includes.js:957:        // Add to included
plugins/core/lib/querying/knex-relationship-includes.js:959:          if (!included.has(resourceKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:966:            // Add relationships from metadata
plugins/core/lib/querying/knex-relationship-includes.js:968:              jsonApiRecord.relationships = childRecord[RELATIONSHIP_METADATA_KEY]
plugins/core/lib/querying/knex-relationship-includes.js:978:            included.set(resourceKey, jsonApiRecord)
plugins/core/lib/querying/knex-relationship-includes.js:983:        const relationshipObject = { data: relData }
plugins/core/lib/querying/knex-relationship-includes.js:988:          relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:989:            self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:990:            related: `${urlPrefix}/${scopeName}/${record.id}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:994:        record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:996:        // Update the record in the included Map if it exists
plugins/core/lib/querying/knex-relationship-includes.js:998:        if (included.has(recordKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:999:          const existingRecord = included.get(recordKey)
plugins/core/lib/querying/knex-relationship-includes.js:1000:          if (!existingRecord.relationships) {
plugins/core/lib/querying/knex-relationship-includes.js:1001:            existingRecord.relationships = {}
plugins/core/lib/querying/knex-relationship-includes.js:1003:          existingRecord.relationships[includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:1007:      // Process nested includes
plugins/core/lib/querying/knex-relationship-includes.js:1009:        const nextPath = `${currentPath}.${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:1012:            { records: targetRecords, scopeName: targetScope, includeTree: subIncludes, included, processedPaths, currentPath: nextPath, fields, idProperty: targetIdProperty },
plugins/core/lib/querying/knex-relationship-includes.js:1020:    log.error('[INCLUDE] Error loading hasMany relationship:', {
plugins/core/lib/querying/knex-relationship-includes.js:1022:      includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1031:      `Failed to load hasMany relationship '${includeName}' for scope '${scopeName}': ${error.message}`
plugins/core/lib/querying/knex-relationship-includes.js:1034:    enhancedError.context = { scopeName, includeName, hasThrough: !!relDef.through }
plugins/core/lib/querying/knex-relationship-includes.js:1040: * Loads hasOne relationships (one-to-one)
plugins/core/lib/querying/knex-relationship-includes.js:1049:  const { records, scopeName, includeName, relDef, subIncludes, included, processedPaths, currentPath, fields } = scope
plugins/core/lib/querying/knex-relationship-includes.js:1073:    throw new Error(`Missing foreignKey in hasOne relationship '${includeName}' for scope '${scopeName}'`)
plugins/core/lib/querying/knex-relationship-includes.js:1113:      log.warn(`[INCLUDE] Multiple records found for hasOne relationship '${includeName}' with ${foreignKey}=${parentId}`)
plugins/core/lib/querying/knex-relationship-includes.js:1139:      record[RELATIONSHIPS_KEY][includeName] = {
plugins/core/lib/querying/knex-relationship-includes.js:1143:      // Add to included if not already there
plugins/core/lib/querying/knex-relationship-includes.js:1145:      if (!included.has(key)) {
plugins/core/lib/querying/knex-relationship-includes.js:1146:        included.set(key, transformed)
plugins/core/lib/querying/knex-relationship-includes.js:1149:      // Process nested includes if any
plugins/core/lib/querying/knex-relationship-includes.js:1151:        const newPath = `${currentPath}.${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:1155:            { records: [relatedRecord], scopeName: targetScope, includeTree: subIncludes, included, processedPaths, currentPath: newPath, fields },
plugins/core/lib/querying/knex-relationship-includes.js:1162:      record[RELATIONSHIPS_KEY][includeName] = { data: null }
plugins/core/lib/querying/knex-relationship-includes.js:1168: * Loads polymorphic belongsTo relationships
plugins/core/lib/querying/knex-relationship-includes.js:1170: * Handles relationships where a record can belong to different types of parent records.
plugins/core/lib/querying/knex-relationship-includes.js:1174: *   - records: Array<Object> - Records with polymorphic relationships
plugins/core/lib/querying/knex-relationship-includes.js:1175: *   - relName: string - The relationship name
plugins/core/lib/querying/knex-relationship-includes.js:1176: *   - relDef: Object - The relationship definition with belongsToPolymorphic
plugins/core/lib/querying/knex-relationship-includes.js:1177: *   - subIncludes: Object - Nested includes to process recursively
plugins/core/lib/querying/knex-relationship-includes.js:1178: *   - included: Map - Map of already included resources
plugins/core/lib/querying/knex-relationship-includes.js:1180: *   - currentPath: string - Current include path for tracking
plugins/core/lib/querying/knex-relationship-includes.js:1189:  const { records, scopeName, relName, relDef, subIncludes, included, processedPaths, currentPath, fields } = scope
plugins/core/lib/querying/knex-relationship-includes.js:1214:      if (!types.includes(targetType)) {
plugins/core/lib/querying/knex-relationship-includes.js:1257:      // Load relationship metadata for all target records
plugins/core/lib/querying/knex-relationship-includes.js:1266:      // Add to included and set relationships
plugins/core/lib/querying/knex-relationship-includes.js:1275:          // Add to included
plugins/core/lib/querying/knex-relationship-includes.js:1277:            if (!included.has(resourceKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:1284:              // Add relationships from metadata
plugins/core/lib/querying/knex-relationship-includes.js:1286:                jsonApiRecord.relationships = targetRecord[RELATIONSHIP_METADATA_KEY]
plugins/core/lib/querying/knex-relationship-includes.js:1296:              included.set(resourceKey, jsonApiRecord)
plugins/core/lib/querying/knex-relationship-includes.js:1299:            const relationshipObject = {
plugins/core/lib/querying/knex-relationship-includes.js:1306:              relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:1307:                self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${relName}`,
plugins/core/lib/querying/knex-relationship-includes.js:1312:            record[RELATIONSHIPS_KEY][relName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:1314:            const relationshipObject = { data: null }
plugins/core/lib/querying/knex-relationship-includes.js:1319:              relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:1320:                self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${relName}`,
plugins/core/lib/querying/knex-relationship-includes.js:1325:            record[RELATIONSHIPS_KEY][relName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:1330:      // Process nested includes for this type
plugins/core/lib/querying/knex-relationship-includes.js:1335:            { records: targetRecords, scopeName: targetType, includeTree: subIncludes, included, processedPaths, currentPath: nextPath, fields, idProperty: targetIdProperty },
plugins/core/lib/querying/knex-relationship-includes.js:1342:    // Set null for records without relationships
plugins/core/lib/querying/knex-relationship-includes.js:1346:        const relationshipObject = { data: null }
plugins/core/lib/querying/knex-relationship-includes.js:1351:          relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:1352:            self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${relName}`,
plugins/core/lib/querying/knex-relationship-includes.js:1357:        record[RELATIONSHIPS_KEY][relName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:1362:    log.error('[INCLUDE] Error loading polymorphic belongsTo relationship:', {
plugins/core/lib/querying/knex-relationship-includes.js:1372:      `Failed to load polymorphic belongsTo relationship '${relName}': ${error.message}`
plugins/core/lib/querying/knex-relationship-includes.js:1381: * Loads reverse polymorphic relationships (via)
plugins/core/lib/querying/knex-relationship-includes.js:1383: * Handles loading "child" records that have a polymorphic relationship back to the parent.
plugins/core/lib/querying/knex-relationship-includes.js:1389: *   - includeName: string - The relationship name
plugins/core/lib/querying/knex-relationship-includes.js:1390: *   - relDef: Object - The relationship definition with 'via' property
plugins/core/lib/querying/knex-relationship-includes.js:1391: *   - subIncludes: Object - Nested includes to process recursively
plugins/core/lib/querying/knex-relationship-includes.js:1392: *   - included: Map - Map of already included resources
plugins/core/lib/querying/knex-relationship-includes.js:1394: *   - currentPath: string - Current include path for tracking
plugins/core/lib/querying/knex-relationship-includes.js:1403:  const { records, scopeName, includeName, relDef, subIncludes, included, processedPaths, currentPath, fields } = scope
plugins/core/lib/querying/knex-relationship-includes.js:1408:      includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1421:      log.warn('[INCLUDE] Via relationship not found or not polymorphic:', {
plugins/core/lib/querying/knex-relationship-includes.js:1471:    // Apply include configuration (limits, ordering, etc.)
plugins/core/lib/querying/knex-relationship-includes.js:1475:      relDef.include || {},
plugins/core/lib/querying/knex-relationship-includes.js:1486:    // Load relationship metadata for all target records
plugins/core/lib/querying/knex-relationship-includes.js:1499:    // Set relationships on parent records
plugins/core/lib/querying/knex-relationship-includes.js:1505:      // Add to included
plugins/core/lib/querying/knex-relationship-includes.js:1507:        if (!included.has(resourceKey)) {
plugins/core/lib/querying/knex-relationship-includes.js:1514:          // Add relationships from metadata
plugins/core/lib/querying/knex-relationship-includes.js:1516:            jsonApiRecord.relationships = childRecord[RELATIONSHIP_METADATA_KEY]
plugins/core/lib/querying/knex-relationship-includes.js:1526:          included.set(resourceKey, jsonApiRecord)
plugins/core/lib/querying/knex-relationship-includes.js:1531:      const relationshipObject = { data: relData }
plugins/core/lib/querying/knex-relationship-includes.js:1536:        relationshipObject.links = {
plugins/core/lib/querying/knex-relationship-includes.js:1537:          self: `${urlPrefix}/${scopeName}/${record.id}/relationships/${includeName}`,
plugins/core/lib/querying/knex-relationship-includes.js:1538:          related: `${urlPrefix}/${scopeName}/${record.id}/${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:1542:      record[RELATIONSHIPS_KEY][includeName] = relationshipObject
plugins/core/lib/querying/knex-relationship-includes.js:1545:    // Process nested includes
plugins/core/lib/querying/knex-relationship-includes.js:1547:      const nextPath = `${currentPath}.${includeName}`
plugins/core/lib/querying/knex-relationship-includes.js:1550:          { records: targetRecords, scopeName: targetScope, includeTree: subIncludes, included, processedPaths, currentPath: nextPath, fields, idProperty: targetIdProperty },
plugins/core/lib/querying/knex-relationship-includes.js:1557:    log.error('[INCLUDE] Error loading reverse polymorphic relationship:', {
plugins/core/lib/querying/knex-relationship-includes.js:1559:      includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1569:      `Failed to load reverse polymorphic relationship '${includeName}' via '${relDef?.via}' for scope '${scopeName}': ${error.message}`
plugins/core/lib/querying/knex-relationship-includes.js:1572:    enhancedError.context = { scopeName, includeName, via: relDef?.via, targetScope: relDef?.target }
plugins/core/lib/querying/knex-relationship-includes.js:1578: * Processes includes for a set of records
plugins/core/lib/querying/knex-relationship-includes.js:1580: * This is the main recursive function that processes the include tree. It examines
plugins/core/lib/querying/knex-relationship-includes.js:1581: * the schema to determine relationship types and calls the appropriate loader function.
plugins/core/lib/querying/knex-relationship-includes.js:1584: *   - records: Array<Object> - Records to process includes for
plugins/core/lib/querying/knex-relationship-includes.js:1586: *   - includeTree: Object - Parsed include tree from parseIncludeTree
plugins/core/lib/querying/knex-relationship-includes.js:1587: *   - included: Map - Map storing all included resources
plugins/core/lib/querying/knex-relationship-includes.js:1589: *   - currentPath: string - Current path in the include tree (default '')
plugins/core/lib/querying/knex-relationship-includes.js:1599:  const { records, scopeName, includeTree, included, processedPaths, currentPath = '', fields = {}, idProperty } = scope
plugins/core/lib/querying/knex-relationship-includes.js:1602:    log.trace('[INCLUDE] Processing includes:', {
plugins/core/lib/querying/knex-relationship-includes.js:1604:      includes: Object.keys(includeTree),
plugins/core/lib/querying/knex-relationship-includes.js:1620:    // Process each include
plugins/core/lib/querying/knex-relationship-includes.js:1621:    for (const [includeName, subIncludes] of Object.entries(includeTree)) {
plugins/core/lib/querying/knex-relationship-includes.js:1622:      const fullPath = currentPath ? `${currentPath}.${includeName}` : includeName
plugins/core/lib/querying/knex-relationship-includes.js:1635:        // Look for belongsTo relationships in schema fields
plugins/core/lib/querying/knex-relationship-includes.js:1637:          if (fieldDef.as === includeName && fieldDef.belongsTo) {
plugins/core/lib/querying/knex-relationship-includes.js:1639:              { records, scopeName, fieldName, fieldDef, includeName, subIncludes, included, processedPaths, currentPath, fields, idProperty },
plugins/core/lib/querying/knex-relationship-includes.js:1647:        // Check relationships
plugins/core/lib/querying/knex-relationship-includes.js:1649:          const relDef = schemaRelationships[includeName]
plugins/core/lib/querying/knex-relationship-includes.js:1653:              // Handle hasOne relationship
plugins/core/lib/querying/knex-relationship-includes.js:1656:                includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1660:                { records, scopeName, includeName, relDef, subIncludes, included, processedPaths, currentPath, fields },
plugins/core/lib/querying/knex-relationship-includes.js:1668:                  { records, scopeName, includeName, relDef, subIncludes, included, processedPaths, currentPath, fields },
plugins/core/lib/querying/knex-relationship-includes.js:1674:                  includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1680:                  { records, scopeName, includeName, relDef, subIncludes, included, processedPaths, currentPath, fields },
plugins/core/lib/querying/knex-relationship-includes.js:1687:                { records, scopeName, relName: includeName, relDef, subIncludes, included, processedPaths, currentPath, fields },
plugins/core/lib/querying/knex-relationship-includes.js:1694:      } catch (includeError) {
plugins/core/lib/querying/knex-relationship-includes.js:1695:        // Log specific include error and continue with other includes
plugins/core/lib/querying/knex-relationship-includes.js:1696:        log.error('[INCLUDE] Error processing include:', {
plugins/core/lib/querying/knex-relationship-includes.js:1698:          includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1700:          error: includeError.message
plugins/core/lib/querying/knex-relationship-includes.js:1703:        throw includeError
plugins/core/lib/querying/knex-relationship-includes.js:1707:        log.warn('[INCLUDE] Unknown relationship:', {
plugins/core/lib/querying/knex-relationship-includes.js:1709:          includeName,
plugins/core/lib/querying/knex-relationship-includes.js:1720:      includeTree: Object.keys(includeTree || {}),
plugins/core/lib/querying/knex-relationship-includes.js:1730: * Main entry point for building included resources
plugins/core/lib/querying/knex-relationship-includes.js:1732: * Takes a set of records and an include parameter, loads all requested relationships,
plugins/core/lib/querying/knex-relationship-includes.js:1733: * and returns both the included resources and the records with relationship data attached.
plugins/core/lib/querying/knex-relationship-includes.js:1738: *   - includeParam: string - The include parameter value (e.g., "author,comments.author")
plugins/core/lib/querying/knex-relationship-includes.js:1745: * @returns {Promise<Object>} Object with included array and records with relationships
plugins/core/lib/querying/knex-relationship-includes.js:1752: *     includeParam: 'author,comments.author',
plugins/core/lib/querying/knex-relationship-includes.js:1759: * // result.included = [
plugins/core/lib/querying/knex-relationship-includes.js:1765: * // result.recordsWithRelationships = original records with _relationships added
plugins/core/lib/querying/knex-relationship-includes.js:1768:  const { records, scopeName, includeParam, fields, idProperty } = scope
plugins/core/lib/querying/knex-relationship-includes.js:1771:    log.trace('[INCLUDE] Building included resources:', { scopeName, includeParam, recordCount: records.length })
plugins/core/lib/querying/knex-relationship-includes.js:1773:    // Check if includes are empty or records are empty
plugins/core/lib/querying/knex-relationship-includes.js:1774:    if (!includeParam || records.length === 0) {
plugins/core/lib/querying/knex-relationship-includes.js:1775:      log.trace('[INCLUDE] No includes requested or no records')
plugins/core/lib/querying/knex-relationship-includes.js:1777:        included: [],
plugins/core/lib/querying/knex-relationship-includes.js:1783:    if (Array.isArray(includeParam) && includeParam.length === 0) {
plugins/core/lib/querying/knex-relationship-includes.js:1784:      log.trace('[INCLUDE] Empty include array, no relationships to load')
plugins/core/lib/querying/knex-relationship-includes.js:1786:        included: [],
plugins/core/lib/querying/knex-relationship-includes.js:1791:    // Parse the include parameter
plugins/core/lib/querying/knex-relationship-includes.js:1792:    const includeTree = parseIncludeTree(includeParam)
plugins/core/lib/querying/knex-relationship-includes.js:1794:    log.debug('[INCLUDE] Parsed include tree:', includeTree)
plugins/core/lib/querying/knex-relationship-includes.js:1796:    // Use a Map to track included resources by type:id
plugins/core/lib/querying/knex-relationship-includes.js:1797:    const included = new Map()
plugins/core/lib/querying/knex-relationship-includes.js:1800:    // Process all includes
plugins/core/lib/querying/knex-relationship-includes.js:1802:      { records, scopeName, includeTree, included, processedPaths, currentPath: '', fields, idProperty },
plugins/core/lib/querying/knex-relationship-includes.js:1807:    const includedArray = Array.from(included.values())
plugins/core/lib/querying/knex-relationship-includes.js:1809:    log.debug('[INCLUDE] Completed building includes:', {
plugins/core/lib/querying/knex-relationship-includes.js:1810:      includedCount: includedArray.length,
plugins/core/lib/querying/knex-relationship-includes.js:1811:      uniqueTypes: [...new Set(includedArray.map(r => r.type))]
plugins/core/lib/querying/knex-relationship-includes.js:1815:      included: includedArray,
plugins/core/lib/querying/knex-relationship-includes.js:1820:    log.error('[INCLUDE] Failed to build included resources:', {
plugins/core/lib/querying/knex-relationship-includes.js:1822:      includeParam,
plugins/core/lib/querying/knex-relationship-includes.js:1829:    const enhancedError = new Error(`Failed to build included resources for scope '${scopeName}': ${error.message}`)
plugins/core/lib/querying/knex-relationship-includes.js:1833:      includeParam,
plugins/core/lib/querying/knex-relationship-includes.js:1841: * Loads relationship identifiers for all hasMany relationships without fetching full related records.
plugins/core/lib/querying/knex-relationship-includes.js:1844: * This function ensures that all hasMany relationships (one-to-many, many-to-many, and polymorphic)
plugins/core/lib/querying/knex-relationship-includes.js:1846: * are not included via the ?include parameter.
plugins/core/lib/querying/knex-relationship-includes.js:1849: * 1. **JSON:API Consistency**: The JSON:API spec allows servers to include relationship identifiers
plugins/core/lib/querying/knex-relationship-includes.js:1851: *    clients always know what relationships exist and their IDs.
plugins/core/lib/querying/knex-relationship-includes.js:1853: * 2. **Simplified Mode Support**: In simplified mode, relationship IDs are transformed into
plugins/core/lib/querying/knex-relationship-includes.js:1855: *    these objects only appear when using ?include, creating an inconsistent API where fields appear/disappear.
plugins/core/lib/querying/knex-relationship-includes.js:1858: *    clients the ability to know what relationships exist without the cost of fetching all data.
plugins/core/lib/querying/knex-relationship-includes.js:1861: * - Runs ONE query per relationship type (not per record) to fetch all related IDs
plugins/core/lib/querying/knex-relationship-includes.js:1862: * - Populates the relationship data with resource identifiers: `{ type: 'resource', id: '123' }`
plugins/core/lib/querying/knex-relationship-includes.js:1863: * - Handles all relationship types: one-to-many, many-to-many, and polymorphic
plugins/core/lib/querying/knex-relationship-includes.js:1867: * This runs after the main records are fetched but before includes are processed.
plugins/core/lib/querying/knex-relationship-includes.js:1868: * If includes ARE specified, they will overwrite these IDs with full data.
plugins/core/lib/querying/knex-relationship-includes.js:1870: * @param {Array<Object>} records - The parent records to load relationships for
plugins/core/lib/querying/knex-relationship-includes.js:1874: * @returns {Promise<void>} Modifies records in place by adding relationship data
plugins/core/lib/querying/knex-relationship-includes.js:1882:  const relationships = schemaInfo.schemaRelationships || {}
plugins/core/lib/querying/knex-relationship-includes.js:1885:  // Process each hasMany relationship
plugins/core/lib/querying/knex-relationship-includes.js:1886:  for (const [relName, relDef] of Object.entries(relationships)) {
plugins/core/lib/querying/knex-relationship-includes.js:1895:        throw new Error(`Missing foreignKey in hasMany relationship '${relName}' for scope '${scopeName}'`)
plugins/core/lib/querying/knex-relationship-includes.js:1911:      // Many-to-many relationship
plugins/core/lib/querying/knex-relationship-includes.js:1918:        throw new Error(`Missing foreignKey or otherKey in manyToMany relationship '${relName}' for scope '${scopeName}'`)
plugins/core/lib/querying/knex-relationship-includes.js:1970:        // For manyToMany, the target type is the relationship name itself
plugins/core/lib/querying/knex-process-includes.js:1:import { buildIncludedResources } from './knex-relationship-includes.js'
plugins/core/lib/querying/knex-process-includes.js:4: * Processes the ?include= parameter to load related resources efficiently
plugins/core/lib/querying/knex-process-includes.js:7: * @param {Array<Object>} records - The primary records to load includes for
plugins/core/lib/querying/knex-process-includes.js:9: * @returns {Promise<Array<Object>>} Array of included resources in JSON:API format
plugins/core/lib/querying/knex-process-includes.js:12: * // Input: Articles with author relationship
plugins/core/lib/querying/knex-process-includes.js:22: *     queryParams: { include: ['author'] },
plugins/core/lib/querying/knex-process-includes.js:27: * const included = await processIncludes(scope, articles, deps);
plugins/core/lib/querying/knex-process-includes.js:36: * // Input: Nested includes with dot notation
plugins/core/lib/querying/knex-process-includes.js:42: *       include: ['author', 'comments.user'] // Load author AND comment users
plugins/core/lib/querying/knex-process-includes.js:47: * const included = await processIncludes(scope, articles, deps);
plugins/core/lib/querying/knex-process-includes.js:59: * // Input: With sparse fieldsets limiting included data
plugins/core/lib/querying/knex-process-includes.js:63: *       include: ['author'],
plugins/core/lib/querying/knex-process-includes.js:65: *         users: 'name'  // Only include name field for users
plugins/core/lib/querying/knex-process-includes.js:71: * const included = await processIncludes(scope, articles, deps);
plugins/core/lib/querying/knex-process-includes.js:81: * - rest-api-knex-plugin's dataGet method when includes are requested
plugins/core/lib/querying/knex-process-includes.js:82: * - rest-api-knex-plugin's dataQuery method for collection includes
plugins/core/lib/querying/knex-process-includes.js:86: * - Implements JSON:API compound documents with primary data and includes
plugins/core/lib/querying/knex-process-includes.js:87: * - Prevents N+1 queries by batch loading all related resources
plugins/core/lib/querying/knex-process-includes.js:88: * - Handles complex nested includes like 'comments.author.company'
plugins/core/lib/querying/knex-process-includes.js:89: * - Automatically deduplicates included resources
plugins/core/lib/querying/knex-process-includes.js:90: * - Respects sparse fieldsets on included resources
plugins/core/lib/querying/knex-process-includes.js:93: * 1. Receives primary records and include directives
plugins/core/lib/querying/knex-process-includes.js:94: * 2. Parses include parameter (comma-separated, possibly nested with dots)
plugins/core/lib/querying/knex-process-includes.js:96: * 4. Returns flat array of all included resources
plugins/core/lib/querying/knex-process-includes.js:97: * 5. These resources go into the 'included' section of JSON:API response
plugins/core/lib/querying/knex-process-includes.js:116:    if (!queryParams.include) {
plugins/core/lib/querying/knex-process-includes.js:120:    log.debug('[PROCESS-INCLUDES] Processing includes:', queryParams.include)
plugins/core/lib/querying/knex-process-includes.js:122:    const includeResult = await buildIncludedResources(
plugins/core/lib/querying/knex-process-includes.js:126:        includeParam: queryParams.include,
plugins/core/lib/querying/knex-process-includes.js:141:      includedCount: includeResult.included.length,
plugins/core/lib/querying/knex-process-includes.js:142:      types: [...new Set(includeResult.included.map(r => r.type))]
plugins/core/lib/querying/knex-process-includes.js:145:    return includeResult.included
plugins/core/lib/querying/knex-process-includes.js:150:      log.error('[PROCESS-INCLUDES] Error processing includes:', {
plugins/core/lib/querying/knex-process-includes.js:153:        includeParam: context?.queryParams?.include,
plugins/core/lib/querying/knex-process-includes.js:158:      console.error('[PROCESS-INCLUDES] Error processing includes:', error)
plugins/core/lib/querying/knex-process-includes.js:162:      `Failed to process includes${context?.scopeName ? ` for scope '${context.scopeName}'` : ''}: ${error.message}`
plugins/core/lib/querying/knex-cross-table-search.js:7: * @param {Object} scopes - All registered hooked-api scopes containing schema and relationship definitions
plugins/core/lib/querying/knex-cross-table-search.js:87: * @param {Object} scopes - All registered hooked-api scopes containing schema and relationship definitions
plugins/core/lib/querying/knex-cross-table-search.js:93: * @throws {Error} If path is invalid or relationships not properly configured
plugins/core/lib/querying/knex-cross-table-search.js:96:   * // Input: Simple belongsTo relationship
plugins/core/lib/querying/knex-cross-table-search.js:97:   * // articles table has author_id field, authors schema defines belongsTo relationship
plugins/core/lib/querying/knex-cross-table-search.js:112:   * // Input: Multi-level path through relationships
plugins/core/lib/querying/knex-cross-table-search.js:130:   * //       relationshipField: 'author_id',
plugins/core/lib/querying/knex-cross-table-search.js:131:   * //       relationshipType: 'belongsTo'
plugins/core/lib/querying/knex-cross-table-search.js:138:   * //       relationshipField: 'company_id',
plugins/core/lib/querying/knex-cross-table-search.js:139:   * //       relationshipType: 'belongsTo'
plugins/core/lib/querying/knex-cross-table-search.js:145:   * // Input: One-to-many relationship (hasMany)
plugins/core/lib/querying/knex-cross-table-search.js:167:   * - Supports many-to-one (belongsTo), one-to-many (hasMany), and many-to-many relationships
plugins/core/lib/querying/knex-cross-table-search.js:168:   * - Handles polymorphic relationships and multi-level paths
plugins/core/lib/querying/knex-cross-table-search.js:172:   * 2. buildJoinChain analyzes relationship path segment by segment
plugins/core/lib/querying/knex-cross-table-search.js:173:   * 3. For each segment, finds the appropriate relationship definition
plugins/core/lib/querying/knex-cross-table-search.js:187:  const relationshipPath = pathSegments.slice(0, -1)
plugins/core/lib/querying/knex-cross-table-search.js:189:  log.trace('[BUILD-JOIN] Parsed path:', { relationshipPath, targetFieldName })
plugins/core/lib/querying/knex-cross-table-search.js:194:  for (let i = 0; i < relationshipPath.length; i++) {
plugins/core/lib/querying/knex-cross-table-search.js:195:    const targetScope = relationshipPath[i]
plugins/core/lib/querying/knex-cross-table-search.js:197:    log.trace('[BUILD-JOIN] Looking for direct relationship:', { from: currentScope, to: targetScope })
plugins/core/lib/querying/knex-cross-table-search.js:209:    let relationshipType = null
plugins/core/lib/querying/knex-cross-table-search.js:210:    let relationshipField = null
plugins/core/lib/querying/knex-cross-table-search.js:211:    let relationshipDef = null
plugins/core/lib/querying/knex-cross-table-search.js:223:              relationshipType = 'hasManyPolymorphic'
plugins/core/lib/querying/knex-cross-table-search.js:224:              relationshipField = { typeField, idField, via: relDef.via }
plugins/core/lib/querying/knex-cross-table-search.js:225:              relationshipDef = relDef
plugins/core/lib/querying/knex-cross-table-search.js:226:              log.trace('[BUILD-JOIN] Found polymorphic hasMany relationship:', {
plugins/core/lib/querying/knex-cross-table-search.js:235:            relationshipType = 'manyToMany'
plugins/core/lib/querying/knex-cross-table-search.js:236:            relationshipField = {
plugins/core/lib/querying/knex-cross-table-search.js:241:            relationshipDef = relDef
plugins/core/lib/querying/knex-cross-table-search.js:242:            log.trace('[BUILD-JOIN] Found many-to-many relationship:', {
plugins/core/lib/querying/knex-cross-table-search.js:253:          relationshipType = 'hasMany'
plugins/core/lib/querying/knex-cross-table-search.js:254:          relationshipField = relDef.foreignKey
plugins/core/lib/querying/knex-cross-table-search.js:255:          if (!relationshipField) {
plugins/core/lib/querying/knex-cross-table-search.js:256:            log.error('[BUILD-JOIN] Missing foreignKey in hasMany relationship:', { relName, currentScope })
plugins/core/lib/querying/knex-cross-table-search.js:257:            throw new Error(`Missing foreignKey in hasMany relationship '${relName}' for scope '${currentScope}'`)
plugins/core/lib/querying/knex-cross-table-search.js:259:          relationshipDef = relDef
plugins/core/lib/querying/knex-cross-table-search.js:260:          log.trace('[BUILD-JOIN] Found hasMany relationship:', { relName, targetScope, foreignKey: relationshipField })
plugins/core/lib/querying/knex-cross-table-search.js:270:          relationshipType = 'belongsTo'
plugins/core/lib/querying/knex-cross-table-search.js:271:          relationshipField = fieldName
plugins/core/lib/querying/knex-cross-table-search.js:272:          relationshipDef = fieldDef
plugins/core/lib/querying/knex-cross-table-search.js:273:          log.trace('[BUILD-JOIN] Found belongsTo relationship:', { fieldName, targetScope })
plugins/core/lib/querying/knex-cross-table-search.js:281:          `No searchable relationship from '${currentScope}' to '${targetScope}'. `
plugins/core/lib/querying/knex-cross-table-search.js:290:    if (relationshipType === 'manyToMany') {
plugins/core/lib/querying/knex-cross-table-search.js:291:      const { through, foreignKey, otherKey } = relationshipField
plugins/core/lib/querying/knex-cross-table-search.js:304:        relationshipField: foreignKey,
plugins/core/lib/querying/knex-cross-table-search.js:305:        relationshipType: 'manyToMany_pivot',
plugins/core/lib/querying/knex-cross-table-search.js:318:        relationshipField: otherKey,
plugins/core/lib/querying/knex-cross-table-search.js:319:        relationshipType: 'manyToMany_target',
plugins/core/lib/querying/knex-cross-table-search.js:326:      if (relationshipType === 'hasManyPolymorphic') {
plugins/core/lib/querying/knex-cross-table-search.js:327:        const { typeField, idField } = relationshipField
plugins/core/lib/querying/knex-cross-table-search.js:330:      } else if (relationshipType === 'hasMany') {
plugins/core/lib/querying/knex-cross-table-search.js:332:        joinCondition = `${previousAlias}.id = ${joinAlias}.${relationshipField}`
plugins/core/lib/querying/knex-cross-table-search.js:335:        joinCondition = `${previousAlias}.${relationshipField} = ${joinAlias}.id`
plugins/core/lib/querying/knex-cross-table-search.js:342:        isOneToMany: relationshipType === 'hasMany' || relationshipType === 'hasManyPolymorphic',
plugins/core/lib/querying/knex-cross-table-search.js:343:        isPolymorphic: relationshipType === 'hasManyPolymorphic',
plugins/core/lib/querying/knex-cross-table-search.js:344:        relationshipField,
plugins/core/lib/querying/knex-cross-table-search.js:345:        relationshipType,
plugins/core/lib/querying/knex-cross-table-search.js:353:  const finalScope = relationshipPath[relationshipPath.length - 1]
plugins/core/lib/querying/knex-cross-table-search.js:357:    throw new Error(`Invalid path '${targetPath}': no relationships to traverse`)
plugins/core/lib/querying/knex-cross-table-search.js:377:      sourceField: lastJoin.relationshipField,
plugins/core/lib/querying/knex-cross-table-search.js:390: * @param {Object} scopes - All registered hooked-api scopes containing schema and relationship definitions
plugins/core/lib/querying/knex-cross-table-search.js:458:    if (fieldDef.actualField && fieldDef.actualField.includes('.')) {
plugins/core/lib/querying/knex-cross-table-search.js:469:        if (field.includes('.')) {
plugins/core/lib/querying/knex-cross-table-search.js:488: * @param {Object} scopes - All registered hooked-api scopes containing schema and relationship definitions
plugins/core/lib/querying/knex-window-queries.js:5: * Builds a window function query for limited includes per parent record
plugins/core/lib/querying/knex-window-queries.js:12: * @param {Object} includeConfig - Include configuration (limit, orderBy)
plugins/core/lib/querying/knex-window-queries.js:26: *   { windowFunctions: true },
plugins/core/lib/querying/knex-window-queries.js:31: * // WITH _windowed AS (
plugins/core/lib/querying/knex-window-queries.js:40: * // SELECT * FROM _windowed
plugins/core/lib/querying/knex-window-queries.js:46: * // Input: Database doesn't support window functions
plugins/core/lib/querying/knex-window-queries.js:54: *   { windowFunctions: false, dbInfo: { client: 'mysql', version: '5.7' } }
plugins/core/lib/querying/knex-window-queries.js:58: * // "Include limits require window function support. Your database (mysql 5.7)
plugins/core/lib/querying/knex-window-queries.js:64: * - loadHasMany when strategy: 'window' and database supports window functions
plugins/core/lib/querying/knex-window-queries.js:65: * - Enables per-parent limits for one-to-many relationships
plugins/core/lib/querying/knex-window-queries.js:68: * - Solves the "N+1 limit" problem where you want X records per parent
plugins/core/lib/querying/knex-window-queries.js:69: * - Without window functions, LIMIT 10 gives 10 total across all parents
plugins/core/lib/querying/knex-window-queries.js:70: * - With window functions, each parent gets up to 10 related records
plugins/core/lib/querying/knex-window-queries.js:71: * - Critical for consistent API responses with includes
plugins/core/lib/querying/knex-window-queries.js:86:  includeConfig,
plugins/core/lib/querying/knex-window-queries.js:90:  const { orderBy = [] } = includeConfig
plugins/core/lib/querying/knex-window-queries.js:93:  const effectiveLimit = includeConfig.limit ?? scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT
plugins/core/lib/querying/knex-window-queries.js:99:      detail: `Requested include limit (${effectiveLimit}) exceeds queryMaxLimit (${scopeVars.queryMaxLimit})`,
plugins/core/lib/querying/knex-window-queries.js:104:  // Check if window functions are supported
plugins/core/lib/querying/knex-window-queries.js:105:  if (!capabilities.windowFunctions) {
plugins/core/lib/querying/knex-window-queries.js:108:      `Include limits require window function support. Your database (${dbInfo.client} ${dbInfo.version}) does not support this feature. ` +
plugins/core/lib/querying/knex-window-queries.js:114:        requiredFeature: 'window_functions'
plugins/core/lib/querying/knex-window-queries.js:119:  // Build the window function query
plugins/core/lib/querying/knex-window-queries.js:143:    .from(subquery.as('_windowed'))
plugins/core/lib/querying/knex-window-queries.js:209: * Applies standard (non-windowed) include configuration to a query
plugins/core/lib/querying/knex-window-queries.js:212: * @param {Object} includeConfig - Include configuration object
plugins/core/lib/querying/knex-window-queries.js:218: * // Input: Basic include with limit and ordering
plugins/core/lib/querying/knex-window-queries.js:270: * // Log: "No limit applied to include query (explicitly disabled)"
plugins/core/lib/querying/knex-window-queries.js:274: * - loadHasMany when window functions not available or not requested
plugins/core/lib/querying/knex-window-queries.js:275: * - loadReversePolymorphic for standard relationship queries
plugins/core/lib/querying/knex-window-queries.js:276: * - Any include loader that needs consistent limit/order handling
plugins/core/lib/querying/knex-window-queries.js:279: * - Provides fallback when window functions unavailable
plugins/core/lib/querying/knex-window-queries.js:292:export const applyStandardIncludeConfig = (query, includeConfig, scopeVars, log) => {
plugins/core/lib/querying/knex-window-queries.js:293:  const { orderBy = [] } = includeConfig
plugins/core/lib/querying/knex-window-queries.js:303:  const requestedLimit = includeConfig.limit
plugins/core/lib/querying/knex-window-queries.js:316:    log.debug('Applied include limit:', {
plugins/core/lib/querying/knex-window-queries.js:324:    log.debug('No limit applied to include query (explicitly disabled)')
plugins/core/lib/querying/knex-query-helpers-base.js:26: * // Response will only include these 3 fields, reducing payload size
plugins/core/lib/querying/knex-query-helpers-base.js:56: * 5. JSON:API response includes only requested attributes
plugins/core/lib/querying/knex-pagination-helpers.js:2: * Calculates pagination metadata from query results
plugins/core/lib/querying/knex-pagination-helpers.js:38: * - Applied when offset-based pagination is enabled
plugins/core/lib/querying/knex-pagination-helpers.js:41: * - Provides consistent pagination metadata across all collection responses
plugins/core/lib/querying/knex-pagination-helpers.js:43: * - Validates pagination parameters to prevent invalid states
plugins/core/lib/querying/knex-pagination-helpers.js:46: * 1. Query method counts total records when pagination.counts is enabled
plugins/core/lib/querying/knex-pagination-helpers.js:48: * 3. Returns metadata that goes into response.meta.pagination
plugins/core/lib/querying/knex-pagination-helpers.js:49: * 4. Used by generatePaginationLinks to determine which links to include
plugins/core/lib/querying/knex-pagination-helpers.js:73: * Generates JSON:API compliant pagination links for offset-based pagination
plugins/core/lib/querying/knex-pagination-helpers.js:78: * @param {Object} paginationMeta - Pagination metadata from calculatePaginationMeta
plugins/core/lib/querying/knex-pagination-helpers.js:90: * const paginationMeta = {
plugins/core/lib/querying/knex-pagination-helpers.js:98: * const links = generatePaginationLinks(urlPrefix, scopeName, queryParams, paginationMeta);
plugins/core/lib/querying/knex-pagination-helpers.js:111: * const paginationMeta = { page: 1, pageSize: 10, pageCount: 3 };
plugins/core/lib/querying/knex-pagination-helpers.js:112: * const links = generatePaginationLinks('/api', 'users', {}, paginationMeta);
plugins/core/lib/querying/knex-pagination-helpers.js:125: * - Called when offset-based pagination is used (not cursor-based)
plugins/core/lib/querying/knex-pagination-helpers.js:128: * - JSON:API spec requires pagination links for easy navigation
plugins/core/lib/querying/knex-pagination-helpers.js:129: * - Preserves all other query parameters (filters, sorts, includes)
plugins/core/lib/querying/knex-pagination-helpers.js:131: * - Only includes prev/next when applicable
plugins/core/lib/querying/knex-pagination-helpers.js:134: * 1. After records are fetched and pagination calculated
plugins/core/lib/querying/knex-pagination-helpers.js:136: * 3. Conditionally includes prev (not on page 1) and next (not on last page)
plugins/core/lib/querying/knex-pagination-helpers.js:139:export const generatePaginationLinks = (urlPrefix, scopeName, queryParams, paginationMeta) => {
plugins/core/lib/querying/knex-pagination-helpers.js:143:  const { page, pageCount, pageSize } = paginationMeta
plugins/core/lib/querying/knex-pagination-helpers.js:199: * Creates an opaque cursor string from record data for cursor-based pagination
plugins/core/lib/querying/knex-pagination-helpers.js:201: * @param {Object} record - Database record to create cursor from
plugins/core/lib/querying/knex-pagination-helpers.js:202: * @param {Array<string>} sortFields - Fields to include in cursor (default: ['id'])
plugins/core/lib/querying/knex-pagination-helpers.js:203: * @returns {string} URL-safe cursor string
plugins/core/lib/querying/knex-pagination-helpers.js:206: * // Input: Simple ID-based cursor
plugins/core/lib/querying/knex-pagination-helpers.js:212: * const cursor = createCursor(record, ['id']);
plugins/core/lib/querying/knex-pagination-helpers.js:215: * // This cursor marks position at record with id=123
plugins/core/lib/querying/knex-pagination-helpers.js:218: * // Input: Multi-field cursor for complex sorting
plugins/core/lib/querying/knex-pagination-helpers.js:224: * const cursor = createCursor(record, ['created_at', 'id']);
plugins/core/lib/querying/knex-pagination-helpers.js:237: * const cursor = createCursor(record, ['category', 'title', 'id']);
plugins/core/lib/querying/knex-pagination-helpers.js:244: * - generateCursorPaginationLinks to create next/prev cursors
plugins/core/lib/querying/knex-pagination-helpers.js:245: * - buildCursorMeta to provide cursor in response metadata
plugins/core/lib/querying/knex-pagination-helpers.js:248: * - Cursor-based pagination is more stable than offset for changing data
plugins/core/lib/querying/knex-pagination-helpers.js:256: * 3. Creates cursor encoding those values
plugins/core/lib/querying/knex-pagination-helpers.js:273: * Parses a cursor string back into field/value pairs
plugins/core/lib/querying/knex-pagination-helpers.js:275: * @param {string} cursor - Cursor string to parse
plugins/core/lib/querying/knex-pagination-helpers.js:277: * @throws {Error} If cursor format is invalid
plugins/core/lib/querying/knex-pagination-helpers.js:280: * // Input: Simple cursor
plugins/core/lib/querying/knex-pagination-helpers.js:281: * const cursor = "id:123";
plugins/core/lib/querying/knex-pagination-helpers.js:282: * const data = parseCursor(cursor);
plugins/core/lib/querying/knex-pagination-helpers.js:288: * // Input: Multi-field cursor with URL-encoded values
plugins/core/lib/querying/knex-pagination-helpers.js:289: * const cursor = "created_at:2024-01-15T08%3A30%3A00.000Z,id:5";
plugins/core/lib/querying/knex-pagination-helpers.js:290: * const data = parseCursor(cursor);
plugins/core/lib/querying/knex-pagination-helpers.js:300: * // Input: Invalid cursor (throws error)
plugins/core/lib/querying/knex-pagination-helpers.js:305: *   // "Invalid cursor format: Invalid cursor format: missing colon separator"
plugins/core/lib/querying/knex-pagination-helpers.js:311: * - Used to build WHERE clause for cursor-based queries
plugins/core/lib/querying/knex-pagination-helpers.js:314: * - Decodes cursor back to usable values for SQL queries
plugins/core/lib/querying/knex-pagination-helpers.js:316: * - Validates cursor format to prevent injection attacks
plugins/core/lib/querying/knex-pagination-helpers.js:319: * 1. Client sends page[after]=cursor parameter
plugins/core/lib/querying/knex-pagination-helpers.js:320: * 2. parseCursor extracts field values from cursor
plugins/core/lib/querying/knex-pagination-helpers.js:322: * 4. Ensures pagination continues from exact position
plugins/core/lib/querying/knex-pagination-helpers.js:324:export const parseCursor = (cursor) => {
plugins/core/lib/querying/knex-pagination-helpers.js:327:    if (!cursor || cursor.trim() === '') {
plugins/core/lib/querying/knex-pagination-helpers.js:328:      throw new Error('Empty cursor')
plugins/core/lib/querying/knex-pagination-helpers.js:331:    const pairs = cursor.split(',')
plugins/core/lib/querying/knex-pagination-helpers.js:336:        throw new Error('Invalid cursor format: missing colon separator')
plugins/core/lib/querying/knex-pagination-helpers.js:343:        throw new Error('Invalid cursor format: empty field name')
plugins/core/lib/querying/knex-pagination-helpers.js:351:    throw new Error(`Invalid cursor format: ${e.message}`)
plugins/core/lib/querying/knex-pagination-helpers.js:356: * Generates pagination links for cursor-based pagination
plugins/core/lib/querying/knex-pagination-helpers.js:364: * @param {Array<string>} sortFields - Fields used for cursor
plugins/core/lib/querying/knex-pagination-helpers.js:392: * // The 'next' cursor points after the last record (id:3)
plugins/core/lib/querying/knex-pagination-helpers.js:414: * - rest-api-knex-plugin when cursor pagination is enabled
plugins/core/lib/querying/knex-pagination-helpers.js:415: * - Called after fetching records with cursor-based query
plugins/core/lib/querying/knex-pagination-helpers.js:418: * - Cursor pagination is more efficient for large datasets
plugins/core/lib/querying/knex-pagination-helpers.js:419: * - Stable pagination when data is being added/removed
plugins/core/lib/querying/knex-pagination-helpers.js:420: * - Only includes 'next' link when more data exists
plugins/core/lib/querying/knex-pagination-helpers.js:424: * 1. After cursor-based query fetches records
plugins/core/lib/querying/knex-pagination-helpers.js:425: * 2. If hasMore is true, creates cursor from last record
plugins/core/lib/querying/knex-pagination-helpers.js:490: * Builds cursor pagination metadata for the response
plugins/core/lib/querying/knex-pagination-helpers.js:495: * @param {Array<string>} sortFields - Fields used for cursor
plugins/core/lib/querying/knex-pagination-helpers.js:496: * @returns {Object} Metadata with pageSize, hasMore, and optional cursor
plugins/core/lib/querying/knex-pagination-helpers.js:511: * //   cursor: {
plugins/core/lib/querying/knex-pagination-helpers.js:527: * //   // No cursor property since hasMore is false
plugins/core/lib/querying/knex-pagination-helpers.js:532: * - rest-api-knex-plugin adds this to response.meta for cursor pagination
plugins/core/lib/querying/knex-pagination-helpers.js:533: * - Provides cursor that client can use directly if needed
plugins/core/lib/querying/knex-pagination-helpers.js:536: * - Gives clients direct access to cursor for custom pagination
plugins/core/lib/querying/knex-pagination-helpers.js:538: * - Simpler than offset pagination metadata (no total count)
plugins/core/lib/querying/knex-pagination-helpers.js:541: * 1. Called after fetching records with cursor query
plugins/core/lib/querying/knex-pagination-helpers.js:542: * 2. Creates cursor from last record if more exist
plugins/core/lib/querying/knex-pagination-helpers.js:543: * 3. Added to response.meta.pagination
plugins/core/lib/querying/knex-pagination-helpers.js:544: * 4. Clients can use cursor directly or use the next link
plugins/core/lib/querying/knex-pagination-helpers.js:554:    meta.cursor = {
