     1	/**
     2	 * Parses URL query strings into JSON:API compliant parameter objects
     3	 *
     4	 * @param {string} queryString - The query string part of URL (without ?)
     5	 * @returns {object} Parsed query parameters in JSON:API format
     6	 *
     7	 * @example
     8	 * // Input: Basic include and sort
     9	 * const query = parseJsonApiQuery('include=author&sort=-created_at');
    10	 *
    11	 * // Output: Parsed into arrays
    12	 * // {
    13	 * //   include: ['author'],      // Split by comma
    14	 * //   fields: {},
    15	 * //   filters: {},
    16	 * //   sort: ['-created_at'],    // Split by comma
    17	 * //   page: {}
    18	 * // }
    19	 *
    20	 * @example
    21	 * // Input: Filters with bracket notation
    22	 * const query = parseJsonApiQuery('filter[status]=published&filter[author_id]=123');
    23	 *
    24	 * // Output: Extracted filter keys
    25	 * // {
    26	 * //   include: [],
    27	 * //   fields: {},
    28	 * //   filters: {
    29	 * //     status: 'published',   // filter[status] → filters.status
    30	 * //     author_id: '123'       // Values kept as strings
    31	 * //   },
    32	 * //   sort: [],
    33	 * //   page: {}
    34	 * // }
    35	 *
    36	 * @example
    37	 * // Input: Sparse fieldsets
    38	 * const query = parseJsonApiQuery('fields[articles]=title,body&fields[users]=name');
    39	 *
    40	 * // Output: Fields kept as comma-separated strings
    41	 * // {
    42	 * //   include: [],
    43	 * //   fields: {
    44	 * //     articles: 'title,body',  // NOT split into array
    45	 * //     users: 'name'            // Validation expects strings
    46	 * //   },
    47	 * //   filters: {},
    48	 * //   sort: [],
    49	 * //   page: {}
    50	 * // }
    51	 *
    52	 * @example
    53	 * // Input: Pagination with numeric conversion
    54	 * const query = parseJsonApiQuery('page[size]=20&page[number]=3&page[cursor]=abc123');
    55	 *
    56	 * // Output: Numbers converted, strings preserved
    57	 * // {
    58	 * //   include: [],
    59	 * //   fields: {},
    60	 * //   filters: {},
    61	 * //   sort: [],
    62	 * //   page: {
    63	 * //     size: 20,        // "20" → 20 (numeric)
    64	 * //     number: 3,       // "3" → 3 (numeric)
    65	 * //     cursor: 'abc123' // Non-numeric stays string
    66	 * //   }
    67	 * // }
    68	 *
    69	 * @example
    70	 * // Input: Complex real-world query
    71	 * const query = parseJsonApiQuery(
    72	 *   'include=author,comments.user&' +
    73	 *   'filter[status]=published&' +
    74	 *   'filter[created_after]=2024-01-01&' +
    75	 *   'fields[articles]=title,summary&' +
    76	 *   'sort=-created_at,title&' +
    77	 *   'page[size]=10'
    78	 * );
    79	 *
    80	 * // Output: All parameters properly categorized
    81	 * // {
    82	 * //   include: ['author', 'comments.user'],
    83	 * //   fields: { articles: 'title,summary' },
    84	 * //   filters: {
    85	 * //     status: 'published',
    86	 * //     created_after: '2024-01-01'
    87	 * //   },
    88	 * //   sort: ['-created_at', 'title'],
    89	 * //   page: { size: 10 }
    90	 * // }
    91	 *
    92	 * @description
    93	 * Used by:
    94	 * - express-plugin parses req.query with this
    95	 * - http-plugin parses URL query strings with this
    96	 * - websocket-plugin parses message query parameters with this
    97	 *
    98	 * Purpose:
    99	 * - Provides uniform JSON:API query parsing across all transports
   100	 * - Handles bracket notation without regex complexity
   101	 * - Converts data types appropriately (numbers for pagination)
   102	 * - Ignores non-JSON:API parameters gracefully
   103	 * - Returns consistent structure even for empty input
   104	 *
   105	 * Data flow:
   106	 * 1. Uses URLSearchParams for reliable parsing
   107	 * 2. Categorizes parameters by their prefix pattern
   108	 * 3. Extracts bracketed keys (filter[x] → x)
   109	 * 4. Splits comma-separated values for include/sort
   110	 * 5. Converts numeric strings for pagination
   111	 * 6. Returns normalized structure for REST API
   112	 */
   113	export function parseJsonApiQuery (queryString) {
   114	  if (!queryString) {
   115	    return {
   116	      include: [],
   117	      fields: {},
   118	      filters: {},
   119	      sort: [],
   120	      page: {}
   121	    }
   122	  }
   123	
   124	  const params = new URLSearchParams(queryString)
   125	  const result = {
   126	    include: [],
   127	    fields: {},
   128	    filters: {},
   129	    sort: [],
   130	    page: {}
   131	  }
   132	
   133	  for (const [key, value] of params) {
   134	    if (key === 'include') {
   135	      // Parse include (comma-separated string to array)
   136	      result.include = value.split(',').map(s => s.trim()).filter(s => s.length > 0)
   137	    } else if (key === 'sort') {
   138	      // Parse sort (comma-separated string to array)
   139	      result.sort = value.split(',').map(s => s.trim()).filter(s => s.length > 0)
   140	    } else if (key.startsWith('filter[') && key.endsWith(']')) {
   141	      // Parse filter[key] = value into filters: { key: value }
   142	      const filterKey = key.slice(7, -1) // Remove 'filter[' and ']'
   143	      if (filterKey) {
   144	        result.filters[filterKey] = value
   145	      }
   146	    } else if (key.startsWith('fields[') && key.endsWith(']')) {
   147	      // Parse fields[type] = fields into fields: { type: "field1,field2" }
   148	      // Keep as comma-separated string to match REST API validation expectations
   149	      const fieldType = key.slice(7, -1) // Remove 'fields[' and ']'
   150	      if (fieldType) {
   151	        result.fields[fieldType] = value
   152	      }
   153	    } else if (key.startsWith('page[') && key.endsWith(']')) {
   154	      // Parse page[size] = 10 into page: { size: 10 }
   155	      const pageKey = key.slice(5, -1) // Remove 'page[' and ']'
   156	      if (pageKey) {
   157	        // Convert to number if it's a valid number, otherwise keep as string
   158	        result.page[pageKey] = isNaN(value) ? value : parseInt(value, 10)
   159	      }
   160	    }
   161	    // Ignore other query parameters that don't match JSON:API patterns
   162	  }
   163	
   164	  return result
   165	}
