UNPKG

altair-graphql-core

Version:

Several of the core logic for altair graphql client

141 lines 5.63 kB
import { parseJson } from '../utils/json'; import { MultiResponseStrategy } from './types'; import { setByDotNotation } from '../utils/dot-notation'; // https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md // https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md // https://github.com/graphql/graphql-spec/blob/c630301560d9819d33255d3ba00f548e8abbcdc6/spec/Section%207%20--%20Response.md#incremental // https://github.com/graphql/defer-stream-wg/discussions/69 export const buildResponse = (responses, strategy = MultiResponseStrategy.AUTO) => { switch (strategy) { case MultiResponseStrategy.CONCATENATE: return buildResponse__concatenate(responses); case MultiResponseStrategy.APPEND: return buildResponse__append(responses); case MultiResponseStrategy.PATCH: return buildResponse__patch(responses); case MultiResponseStrategy.AUTO: { const firstResponse = responses[0]; const parsedFirstResponse = parseJson(firstResponse?.content ?? '', { defaultValue: null, }); // if response[0] is not a JSON object, then concatenate if (!parsedFirstResponse || typeof parsedFirstResponse !== 'object') { return buildResponse__concatenate(responses); } // if response[0] is patchable, then patch if ('data' in parsedFirstResponse && 'hasNext' in parsedFirstResponse) { return buildResponse__patch(responses); } // otherwise append return buildResponse__append(responses); } default: throw new Error('Invalid strategy'); } }; const buildResponse__concatenate = (responses) => { const content = responses.map((r) => r.content).join(''); const parsedContent = parseJson(content, { defaultValue: null }); return [ { content: parsedContent ? JSON.stringify(parsedContent, null, 2) : content, timestamp: responses.at(-1)?.timestamp ?? 0, json: !!parsedContent, // if parsedContent is not null, then it's JSON }, ]; }; const buildResponse__append = (responses) => { return responses; }; const buildResponse__patch = (responses) => { if (responses.length === 0) { return []; } // first response is the same shape as a standard GraphQL response const obj = parseJson(responses[0]?.content ?? '', { defaultValue: null, }); if (!obj) { throw new Error('JSON response required for patching!'); } // remove hasNext field from obj (since it's just used for patching) Reflect.deleteProperty(obj, 'hasNext'); for (let i = 1; i < responses.length; i++) { const nextObj = parseJson(responses[i]?.content ?? '', { defaultValue: null }); if (!nextObj) { throw new Error('JSON response required for patching!'); } patchResponse(obj, nextObj); } // TODO: Handle cases with labels // return the patched response return [ { content: JSON.stringify(obj, null, 2), timestamp: responses.at(0)?.timestamp ?? 0, json: true, // always JSON for patched responses }, ]; }; const patchResponse = (obj, nextData) => { const result = { ...obj }; const errors = result.errors ? result.errors : []; const extensions = { ...result.extensions, ...('extensions' in nextData ? nextData.extensions : {}), }; if (nextData.errors) { errors.push(...nextData.errors); } if ('path' in nextData) { // old version of the incremental delivery payloads // https://github.com/graphql/graphql-over-http/blob/d80afd45782b40a9a8447fcff3d772689d83df56/rfcs/IncrementalDelivery.md nextData = { ...nextData, incremental: [ { data: nextData.data ?? null, path: nextData.path, }, ], hasNext: true, }; } if (nextData.hasNext && nextData.incremental) { for (const patch of nextData.incremental) { if (Array.isArray(patch.errors)) { errors.push(...patch.errors); } if (patch.extensions) { Object.assign(extensions, patch.extensions); } const path = ['data', ...patch.path]; if ('data' in patch) { // @defer patch // set the data at the path in result if (patch.data) { result.data = result.data ?? {}; setByDotNotation(result, path, patch.data, true); } } if ('items' in patch) { // @stream patch patch.items?.forEach((item, i) => { const lastPathIdx = path[path.length - 1]; if (typeof lastPathIdx !== 'number') { throw new Error('Path for stream incremental payload should end with a number!'); } // for each item, we want to place it at the end of the array const newPath = [...path.slice(0, -1), lastPathIdx + i]; setByDotNotation(result, newPath, item); }); } } } return { ...result, ...(errors.length && { errors }), ...(Object.keys(extensions).length && { extensions }), }; }; //# sourceMappingURL=response-builder.js.map