edgedb-js icon indicating copy to clipboard operation
edgedb-js copied to clipboard

Export additional utility types from generated query builder code

Open tomnz opened this issue 7 months ago • 5 comments

I'm attempting to write generic functions that accept query builder expressions and wrap them with additional functionality before calling them. (In our particular case, the intent is to combine with Effect to perform logging/tracing/etc and turn the expression call into an "effect-ful" call, but I could imagine loads of other use cases).

However, I am occasionally having difficulty crafting type signatures that satisfy TS. For example, this works:

import { type $expr_Insert } from "@internal/edgedb/edgeql/insert";
import {
  type ObjectType,
} from "@internal/edgedb/edgeql/typesystem";

export const tryInsert = async <T extends ObjectType>(
  statement: $expr_Insert<T>,
): Promise<string> => {
  try {
    const result = (await statement.run(client)) as unknown as { id: string };
    return result.id;
  } catch (error) {
    console.error(error);
    throw error;
  }
};

For whatever reason, statement.run() has an any return type, in spite of being an $expr_Insert - so the clunky cast is required, but works fine. Also, this is obviously a fairly contrived example, but picture additional things happening in the wrapper.

However, the following seems to be impossible:

import { type $expr_Insert } from "@internal/edgedb/edgeql/insert";
import {
  type $expr_WithParams,
  // INVALID: type is NOT exported
  type paramsToParamArgs,
  // INVALID: type is NOT exported
  type ParamsRecord,
} from "@internal/edgedb/edgeql/params";
import {
  type ObjectType,
} from "@internal/edgedb/edgeql/typesystem";

export const tryInsertWithParams = async <
  T extends ObjectType,
  // INVALID: ParamsRecord is not available
  P extends ParamsRecord,
>(
  statement: $expr_WithParams<P, $expr_Insert<T>>,
  // INVALID: paramsToParamArgs is not available
  args: paramsToParamArgs<P>,
): Promise<string> => {
  try {
    const result = (await statement.run(client, args)) as unknown as {
      id: string;
    };
    return result.id;
  } catch (error) {
    console.error(error);
    throw error;
  }
};

The two utility types paramsToParamArgs and ParamsRecord are not exported from the generated code. Therefore, it does not seem possible to correctly type generic parameters to wrap a parameterized expression execution.

I tried copy/pasting the definitions across alongside the above code, but it complains about args being a different type than expected:

Argument of type 'paramsToParamArgs<Params>' is not assignable to parameter of type 'paramsToParamArgs<Params>'.
  Type 'paramsToParamArgs<Params>' is not assignable to type '{ [key in keyof Params as Params[key] extends ParamType ? key : never]: Params[key] extends ParamType ? Readonly<BaseTypeToTsType<Params[key], true>> : never; }'.ts(2345)

(First type is the copied type, second is the type in the generated code).

Open to other ideas about how to achieve this, but it seems the easiest solution would be to export additional types from the generated code.

tomnz avatar Jul 06 '24 21:07 tomnz