diff --git a/.gitignore b/.gitignore index 20b5254f..493a5102 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules /.nyc_output /dist .DS_Store +.history diff --git a/src/1.ts b/src/1.ts index 1d08fc65..075fa6d7 100644 --- a/src/1.ts +++ b/src/1.ts @@ -1,8 +1,13 @@ export {evaluate} from './evaluator' export type {GroqFunction, GroqFunctionArg, GroqPipeFunction} from './evaluator/functions' export type {Scope} from './evaluator/scope' -export type {EvaluateOptions} from './evaluator/types' -export type {Context, Executor} from './evaluator/types' +export type { + Context, + DereferenceFunction, + Document, + EvaluateOptions, + Executor, +} from './evaluator/types' export * from './nodeTypes' export {parse} from './parser' export type {ParseOptions} from './types' diff --git a/src/evaluator/evaluate.ts b/src/evaluator/evaluate.ts index bf6e68bb..dad4625c 100644 --- a/src/evaluator/evaluate.ts +++ b/src/evaluator/evaluate.ts @@ -253,6 +253,10 @@ const EXECUTORS: ExecutorMap = { return NULL_VALUE } + if (scope.context.dereference) { + return fromJS(await scope.context.dereference({_ref: id})) + } + for await (const doc of scope.source) { if (doc.type === 'object' && id === doc.data._id) { return doc @@ -472,6 +476,7 @@ export function evaluateQuery( sanity: options.sanity, after: options.after ? fromJS(options.after) : null, before: options.before ? fromJS(options.before) : null, + dereference: options.dereference, }, null, ) diff --git a/src/evaluator/types.ts b/src/evaluator/types.ts index dc58d56f..1df56eee 100644 --- a/src/evaluator/types.ts +++ b/src/evaluator/types.ts @@ -3,6 +3,12 @@ import {Value} from '../values' import {Scope} from './scope' export type Executor = (node: N, scope: Scope) => Value | PromiseLike +export type Document = { + _id?: string + _type?: string + [T: string]: unknown +} +export type DereferenceFunction = (obj: {_ref: string}) => PromiseLike export interface EvaluateOptions { // The value that will be available as `@` in GROQ. @@ -31,6 +37,9 @@ export interface EvaluateOptions { projectId: string dataset: string } + + // Custom function to resolve document references + dereference?: DereferenceFunction } export interface Context { @@ -42,4 +51,5 @@ export interface Context { projectId: string dataset: string } + dereference?: DereferenceFunction } diff --git a/test/evaluate.test.ts b/test/evaluate.test.ts index e98ba907..c4847dd2 100644 --- a/test/evaluate.test.ts +++ b/test/evaluate.test.ts @@ -282,4 +282,24 @@ t.test('Basic parsing', async (t) => { const data = await value.get() t.same(data, 'abcdef') }) + + t.test('Custom dereference function', async (t) => { + const dataset = [ + {_id: 'a', name: 'Michael'}, + {_id: 'b', name: 'George Michael', father: {_ref: 'a'}}, + ] + const datasetAsMap = new Map(dataset.map((data) => [data._id, data])) + + const query = `*[]{ name, "father": father->name }` + const tree = parse(query) + const value = await evaluate(tree, { + dataset, + dereference: ({_ref}) => Promise.resolve(datasetAsMap.get(_ref)), + }) + const data = await value.get() + t.same(data, [ + {name: 'Michael', father: null}, + {name: 'George Michael', father: 'Michael'}, + ]) + }) }) diff --git a/test/testUtils.ts b/test/testUtils.ts index fd903385..37c1f573 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -1,5 +1,3 @@ -import t from 'tap' - export async function throwsWithMessage( t: Tap.Test, funcUnderTest: () => {},