Skip to content

Commit

Permalink
selections
Browse files Browse the repository at this point in the history
  • Loading branch information
mkobetic committed Nov 5, 2024
1 parent 62ac24f commit 8a64ed0
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 14 deletions.
26 changes: 25 additions & 1 deletion packages/api-client-core/spec/default-selection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AssertTrue, IsExact } from "conditional-type-checks";
import type { DefaultSelection } from "../src/types.js";
import type { AllFieldsSelected } from "src/FieldSelection.js";
import type { AvailableSelection, DefaultSelection } from "../src/types.js";
import type { AvailableTestSchemaSelection } from "./TestSchema.js";

type _NullDefault = DefaultSelection<AvailableTestSchemaSelection, { select: null }, { num: true }>;
Expand All @@ -8,4 +9,27 @@ type _TestDefaultsNullToTheDefault = AssertTrue<IsExact<_NullDefault, { num: tru
type _NonNullDefault = DefaultSelection<AvailableTestSchemaSelection, { select: { num: false; str: true } }, { num: true }>;
type _TestRespectsTruthySelections = AssertTrue<IsExact<_NonNullDefault, { num: false; str: true }>>;

type _availableSelection = AvailableSelection<{ a: number; b: { c: string; d: { e: boolean } } }>;
type _TestAvailableSelection = AssertTrue<
IsExact<
_availableSelection,
{
a?: boolean | null | undefined;
b?:
| {
c?: boolean | null | undefined;
d?:
| {
e?: boolean | undefined;
}
| undefined;
}
| undefined;
}
>
>;

type _allFieldsSelected = AllFieldsSelected<_availableSelection>;
type _TestAllFieldsSelected = AssertTrue<IsExact<_allFieldsSelected, { a: true; b: { c: true; d: { e: true } } }>>;

test("true", () => undefined);
49 changes: 49 additions & 0 deletions packages/api-client-core/spec/operationBuilders.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
actionOperation,
backgroundActionResultOperation,
computedViewOperation,
enqueueActionOperation,
findManyOperation,
findOneByFieldOperation,
Expand Down Expand Up @@ -1491,4 +1492,52 @@ describe("operation builders", () => {
`);
});
});
describe("computedViewOperation", () => {
test("global view without selection", () => {
expect(
computedViewOperation(
"boom",
{ a: { required: false, type: "Int", value: 42 }, b: { required: false, type: "String", value: "fortytwo" } },
{ a: true, b: true }
)
).toMatchInlineSnapshot(`
{
"query": "query boom($a: Int, $b: String) {
boom(a: $a, b: $b) {
a
b
__typename
}
}",
"variables": {
"a": 42,
"b": "fortytwo",
},
}
`);
});
test("global view with selection", () => {
expect(
computedViewOperation(
"boom",
{ a: { required: false, type: "Int", value: 42 }, b: { required: false, type: "String", value: "fortytwo" } },
{ a: true, b: true },
{ a: true }
)
).toMatchInlineSnapshot(`
{
"query": "query boom($a: Int, $b: String) {
boom(a: $a, b: $b) {
a
__typename
}
}",
"variables": {
"a": 42,
"b": "fortytwo",
},
}
`);
});
});
});
21 changes: 21 additions & 0 deletions packages/api-client-core/spec/operationRunners.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
GadgetConnection,
actionRunner,
backgroundActionResultRunner,
computedViewRunner,
enqueueActionRunner,
findManyRunner,
findOneByFieldRunner,
Expand Down Expand Up @@ -2040,4 +2041,24 @@ describe("operationRunners", () => {
});
});
});
describe("computedViewRunner", () => {
test("global view", () => {
const _promise = computedViewRunner(
connection,
"boom",
{ a: { required: false, type: "Int", value: 42 }, b: { required: false, type: "String", value: "fortytwo" } },
{ a: true, b: true },
{ a: true }
);

expect(query).toMatchInlineSnapshot(`
"query boom($a: Int, $b: String) {
boom(a: $a, b: $b) {
a
__typename
}
}"
`);
});
});
});
15 changes: 15 additions & 0 deletions packages/api-client-core/src/FieldSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,18 @@
export interface FieldSelection {
[key: string]: boolean | null | undefined | FieldSelection;
}

/**
* Take a FieldSelection type and construct a type with all its fields required and selected.
*/
export type AllFieldsSelected<Selection extends FieldSelection> = {
[K in keyof Selection]-?: NonNullable<Selection[K]> extends FieldSelection ? AllFieldsSelected<NonNullable<Selection[K]>> : true;
};

/**
* Take a FieldSelection type and construct a type with its fields set to true
* rather than (boolean | null | undefined)
*/
export type SomeFieldsSelected<Selection extends FieldSelection> = {
[K in keyof Selection]?: NonNullable<Selection[K]> extends FieldSelection ? SomeFieldsSelected<NonNullable<Selection[K]>> : true;
};
17 changes: 11 additions & 6 deletions packages/api-client-core/src/GadgetFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FieldSelection } from "tiny-graphql-query-compiler";
import type { AllFieldsSelected, FieldSelection } from "./FieldSelection.js";
import type { GadgetRecord, RecordShape } from "./GadgetRecord.js";
import type { GadgetRecordList } from "./GadgetRecordList.js";
import type { LimitToKnownKeys, VariablesOptions } from "./types.js";
import type { DefaultSelection, LimitToKnownKeys, Select, Selectable, VariablesOptions } from "./types.js";

export type PromiseOrLiveIterator<T> = Promise<T> | AsyncIterable<T>;
export type AsyncRecord<T extends RecordShape> = PromiseOrLiveIterator<GadgetRecord<T>>;
Expand Down Expand Up @@ -208,12 +208,17 @@ export interface GlobalActionFunction<VariablesT> {
export type AnyActionFunction = ActionFunctionMetadata<any, any, any, any, any, any> | GlobalActionFunction<any>;
export type AnyBulkActionFunction = ActionFunctionMetadata<any, any, any, any, any, true>;

export interface ComputedViewFunction<VariablesT, ResultT> {
(variables: VariablesT): Promise<ResultT>;
export interface ComputedViewFunction<Variables, AvailableSelection extends FieldSelection, Result> {
(variables: Variables, options?: { select?: AvailableSelection }): Promise<
Select<Result, DefaultSelection<AvailableSelection, Selectable<AvailableSelection>, AllFieldsSelected<AvailableSelection>>>
>;
type: "computedView";
operationName: string;
namespace: string | string[] | null;
variables: VariablesOptions;
variablesType: VariablesT;
selections: FieldSelection;
variablesType: Variables;
defaultSelection: FieldSelection;
selection?: FieldSelection;
selectionType: AvailableSelection;
resultType: Result;
}
5 changes: 3 additions & 2 deletions packages/api-client-core/src/operationBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,12 @@ export const globalActionOperation = (
export const computedViewOperation = (
operation: string,
variables: VariablesOptions,
selections: FieldSelection,
defaultSelection: FieldSelection,
selection?: FieldSelection,
namespace?: string | string[] | null
) => {
let fields = {
[operation]: Call(variableOptionsToVariables(variables), fieldSelectionToQueryCompilerFields(selections, true)),
[operation]: Call(variableOptionsToVariables(variables), fieldSelectionToQueryCompilerFields(selection ?? defaultSelection, true)),
};

if (namespace) {
Expand Down
5 changes: 3 additions & 2 deletions packages/api-client-core/src/operationRunners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,11 @@ export const computedViewRunner = async (
connection: GadgetConnection,
operation: string,
variables: VariablesOptions,
selections: FieldSelection,
defaultSelection: FieldSelection,
selection?: FieldSelection,
namespace?: string | string[] | null
) => {
const plan = computedViewOperation(operation, variables, selections, namespace);
const plan = computedViewOperation(operation, variables, defaultSelection, selection, namespace);
const response = await connection.currentClient.query(plan.query, plan.variables);
const dataPath = namespaceDataPath([operation], namespace);
return assertOperationSuccess(response, dataPath);
Expand Down
8 changes: 5 additions & 3 deletions packages/api-client-core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { OperationContext } from "@urql/core";
import type { VariableOptions } from "tiny-graphql-query-compiler";
import type { FieldSelection } from "./FieldSelection.js";
import type { FieldSelection, SomeFieldsSelected } from "./FieldSelection.js";
import type { ActionFunction, AnyActionFunction, BulkActionFunction, GlobalActionFunction } from "./GadgetFunctions.js";

/**
Expand All @@ -25,9 +25,9 @@ export type VariablesOptions = Record<string, VariableOptions>;
* Given an options object from a find method, default the type of the selection to a default if no selection is passed
*/
export type DefaultSelection<
SelectionType,
SelectionType extends FieldSelection,
Options extends { select?: SelectionType | null },
Defaults extends SelectionType
Defaults extends SomeFieldsSelected<SelectionType>
> = Options["select"] extends SelectionType ? Options["select"] : Defaults;

/**
Expand Down Expand Up @@ -774,6 +774,8 @@ export type PaginateOptions = {
*/
export type AvailableSelection<Schema> = Schema extends string | number | bigint | null | undefined
? boolean | null | undefined
: Schema extends Array<infer T>
? AvailableSelection<T>
: { [key in keyof Schema]?: AvailableSelection<Schema[key]> };

/** Options for configuring the queue for a background action */
Expand Down

0 comments on commit 8a64ed0

Please sign in to comment.