     1	import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
     2	import { validateGetPayload } from '../lib/querying-writing/payload-validators.js'
     3	import { normalizeRecordAttributes } from '../lib/querying-writing/database-value-normalizers.js'
     4	import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
     5	import { transformJsonApiToSimplified } from '../lib/querying-writing/simplified-helpers.js'
     6	import { cascadeConfig } from './common.js'
     7	
     8	/**
     9	 * GET
    10	 * Retrieves a single resource by its type and ID.
    11	 * @param {(string|number)} id - The unique ID of the resource to fetch.
    12	 * @param {object} [queryParams={}] - Optional. An object to customize the query.
    13	 * @param {string} [queryParams.include] - A comma-separated string of relationship paths to sideload (e.g., "authors,publisher").
    14	 * @param {object} [queryParams.fields] - An object to request only specific fields (sparse fieldsets). Keys are resource types, values are comma-separated field names.
    15	 * @returns {Promise<object>} A Promise that resolves to the JSON:API response document.
    16	 */
    17	export default async function getMethod ({
    18	  params,
    19	  context,
    20	  vars,
    21	  helpers,
    22	  scope,
    23	  scopes,
    24	  runHooks,
    25	  apiOptions,
    26	  pluginOptions,
    27	  scopeOptions,
    28	  scopeName,
    29	  api
    30	}) {
    31	  context.method = 'get'
    32	
    33	  // Determine which simplified setting to use based on transport
    34	  const isTransport = params.isTransport === true
    35	
    36	  // Use vars which automatically cascade from scope to global
    37	  const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
    38	
    39	  // Get simplified setting - from params only (per-call override) or use default
    40	  context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
    41	
    42	  // Assign common context properties
    43	  context.schemaInfo = scopes[scopeName].vars.schemaInfo
    44	  context.queryParams = params.queryParams || {}
    45	
    46	  // These only make sense as parameter per query
    47	  context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
    48	  context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
    49	
    50	  context.transaction = params.transaction
    51	  context.db = context.transaction || api.knex.instance
    52	
    53	  context.scopeName = scopeName
    54	
    55	  // These are just shortcuts used in this function and will be returned
    56	  const schemaStructure = context.schemaInfo.schemaInstance.structure
    57	  const schemaRelationships = context.schemaInfo.schemaRelationships
    58	
    59	  context.id = params.id
    60	
    61	  // Validate GET request to ensure required parameters are present and properly formatted.
    62	  // This checks that 'id' parameter exists and is not empty (you can't GET without an ID),
    63	  // validates 'include' contains valid relationship names (not arbitrary fields), and ensures
    64	  // 'fields' for sparse fieldsets follow the format fields[type]=comma,separated,list.
    65	  // Example: validates id: '123' exists, include: ['author', 'tags'] are real relationships.
    66	  validateGetPayload({ id: context.id, queryParams: context.queryParams }, vars.includeDepthLimit)
    67	
    68	  // Fetch minimal record for authorization checks
    69	  const minimalRecord = await helpers.dataGetMinimal({
    70	    scopeName,
    71	    context,
    72	    runHooks
    73	  })
    74	
    75	  if (!minimalRecord) {
    76	    throw new RestApiResourceError(
    77	      'Resource not found',
    78	      {
    79	        subtype: 'not_found',
    80	        resourceType: scopeName,
    81	        resourceId: context.id
    82	      }
    83	    )
    84	  }
    85	
    86	  context.minimalRecord = minimalRecord
    87	
    88	  // Centralised checkPermissions function
    89	  await scope.checkPermissions({
    90	    method: 'get',
    91	    originalContext: context,
    92	  })
    93	
    94	  await runHooks('beforeData')
    95	  await runHooks('beforeDataGet')
    96	
    97	  context.record = await helpers.dataGet({
    98	    scopeName,
    99	    context,
   100	    runHooks
   101	  })
   102	
   103	  // Check if record was found - storage layer returns null/undefined for non-existent records.
   104	  // This generates a proper 404 error with JSON:API error format instead of returning empty data.
   105	  if (!context.record || !context.record.data) {
   106	    throw new RestApiResourceError(
   107	      'Resource not found',
   108	      {
   109	        subtype: 'not_found',
   110	        resourceType: scopeName,
   111	        resourceId: context.id
   112	      }
   113	    )
   114	  }
   115	
   116	  // Normalize database values (e.g., convert 1/0 to true/false for booleans)
   117	  context.record = normalizeRecordAttributes(context.record, scopes)
   118	
   119	  await runHooks('checkDataPermissions')
   120	  await runHooks('checkDataPermissionsGet')
   121	
   122	  // Make a backup
   123	  context.originalRecord = structuredClone(context.record)
   124	
   125	  // This will enhance record, which is the WHOLE JSON:API record
   126	  await runHooks('enrichRecord')
   127	
   128	  // Get computed field information for main resource
   129	  const computedFields = scope.vars.schemaInfo?.computed || {}
   130	  const requestedFields = context.queryParams.fields?.[scopeName]
   131	  const requestedComputedFields = getRequestedComputedFields(scopeName, requestedFields, computedFields)
   132	
   133	  // Enrich attributes for the main resource
   134	  // Pass computedDependencies from context (set by dataGet in Knex plugin)
   135	  // This tells enrichAttributes which fields to remove after computation
   136	  context.record.data.attributes = await scope.enrichAttributes({
   137	    attributes: context.record.data.attributes,
   138	    parentContext: context,
   139	    requestedComputedFields,
   140	    isMainResource: true,
   141	    computedDependencies: context.computedDependencies  // Fields to remove if not requested
   142	  })
   143	
   144	  for (const entry of (context.record.included || [])) {
   145	    const entryScope = scopes[entry.type]
   146	    const entryComputed = entryScope.vars.schemaInfo?.computed || {}
   147	    const entryRequestedFields = context.queryParams.fields?.[entry.type]
   148	    const entryRequestedComputed = getRequestedComputedFields(
   149	      entry.type,
   150	      entryRequestedFields,
   151	      entryComputed
   152	    )
   153	
   154	    entry.attributes = await entryScope.enrichAttributes({
   155	      attributes: entry.attributes,
   156	      parentContext: context,
   157	      requestedComputedFields: entryRequestedComputed,
   158	      isMainResource: false,
   159	      computedDependencies: entry.__$jsonrestapi_computed_deps$__
   160	    })
   161	  }
   162	
   163	  await runHooks('enrichRecordWithRelationships')
   164	
   165	  // The called hooks should NOT change context.record
   166	  await runHooks('finish')
   167	  await runHooks('finishGet')
   168	
   169	  // Get schema info for transformation
   170	  context.schemaInfo = scopes[scopeName].vars.schemaInfo
   171	
   172	  // Transform output if in simplified mode
   173	  if (context.simplified) {
   174	    // Convert JSON:API response back to simplified format
   175	    // Example: {data: {type: 'posts', id: '1', attributes: {title: 'My Post'}, relationships: {author: {data: {type: 'users', id: '123'}}}}}
   176	    // becomes: {id: '1', title: 'My Post', author_id: '123'} - flattens structure and restores foreign keys
   177	    return transformJsonApiToSimplified(
   178	      { record: context.record },
   179	      { context: { schemaStructure, schemaRelationships, scopes } }
   180	    )
   181	  }
   182	
   183	  return context.record
   184	}
