stardog.js icon indicating copy to clipboard operation
stardog.js copied to clipboard

Improve Typings for query.execute

Open jameslaneconkling opened this issue 7 years ago • 2 comments

It would be nice if functions like execute returned typed results. Some potential steps towards that could be:

  • make the HTTP.Body generic:
type SuccessBody<T> = {
    status: number;
    statusText: string;
    headers: Headers;
    url: string
    ok: true;
    body: T;
}
    
type ErrorBody = {
    status: number;
    statusText: string;
    headers: Headers;
    url: string
    ok: false;
    body: {
        message: string,
        code: string
    };
}
    
export type Body<T = any> = SuccessBody<T> | ErrorBody

By making Body a tagged union of success and error types, a type guard can properly narrow Body based on the ok key:

const handleResponse = (res: HTTP.Body<SomeResponseType>) => {
  if (!res.ok) {
    return console.error(res.body.message); // res.body is of type { message: string, code: string }
  }
  console.log(res.body); // res.body is SomeResponseType
};

With that in place, execute could look something like:

type RDFIRITerm = { type: 'uri', value: string }
type RDFLiteralTerm = { type: 'literal', value: string, 'xml:lang'?: string, datatype?: string }
type RDFBlankTerm = { type: 'bnode', value: string }
type RDFTerm = RDFIRITerm | RDFLiteralTerm | RDFBlankTerm

type SPARQLSelectResult<Variable extends string> = {
    head: {
        vars: Variable[]
        link: string[]
    }
    results: {
        bindings: Array<{ [V in Variable]: RDFTerm }>
    }
}

type SPARQLAskResult = {
    head: {},
    boolean: boolean
}

type SPARQLResult<Variable extends string> = SPARQLSelectResult<Variable> | SPARQLAskResult

function execute<Variable extends string = ''>(
    conn: Connection, database: string, query: string, accept?: HTTP.AcceptMimeType, params?: object
): Promise<HTTP.Body<SPARQLResult<Variable>>>;

and be used like

query.execute<"name" | "age">(
  conn,
  'myDB',
  'SELECT * WHERE { :personA :name ?name ; :age ?age }',
  'application/sparql-results+json'
)
  .then((res) => {
    if (!res.ok) {
      return console.error(res.body.message);
    }

    console.log(res.body.head) // res.body.head is of type {} | { vars: ("name" | "age")[], link: string[], }
  });

If this is of interest, I'd be happy to open a PR.

jameslaneconkling avatar Sep 20 '18 21:09 jameslaneconkling

Actually, we could do better by overloading execute to better preserve the type of the response, either a SPARQL select query result or ask query result. E.g.

function execute(
    conn: Connection, database: string, query: string, accept?: HTTP.AcceptMimeType, params?: object
): Promise<HTTP.Body<SPARQLResult<''>>>;
function execute<QueryType extends 'Ask'>(
    conn: Connection, database: string, query: string, accept?: HTTP.AcceptMimeType, params?: object
): Promise<HTTP.Body<SPARQLAskResult>>;
function execute<QueryType extends 'Select', Variable extends string = ''>(
    conn: Connection, database: string, query: string, accept?: HTTP.AcceptMimeType, params?: object
): Promise<HTTP.Body<SPARQLSelectResult<Variable>>>;

and to use:

query.execute<'Select', 'name' | 'age'>(
  conn,
  'myDB',
  'SELECT * WHERE { :personA :name ?name ; :age ?age }',
  'application/sparql-results+json'
)
  .then((res) => {
    if (!res.ok) {
      return console.error(res.body.message);
    }

    res.body.results.bindings.map((result) => ( // result is of type { name: RDFTerm, age: RDFTerm }
      console.log(result.name, result.age)
    ));
  });

jameslaneconkling avatar Sep 20 '18 22:09 jameslaneconkling

We're currently working on a 2.0 release of stardog.js, which may improve things here. If it doesn't, it's something that we can consider doing then. Thanks!

jmrog avatar Aug 05 '19 19:08 jmrog