Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api-client-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gadgetinc/api-client-core",
"version": "0.15.46",
"version": "0.15.47",
"files": [
"Readme.md",
"dist/**/*"
Expand Down
81 changes: 81 additions & 0 deletions packages/api-client-core/spec/InternalModelManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,33 @@ describe("InternalModelManager", () => {
expect(plan.variables).toEqual({ search: "term" });
});

test("should build a find many query with searchFields", () => {
const plan = internalFindManyQuery("widget", [], { search: "term", searchFields: { name: true, state: false, id: { weight: 10 } } });
expect(plan.query).toMatchInlineSnapshot(`
"query InternalFindManyWidget($search: String, $searchFields: WidgetSearchFields) {
internal {
listWidget(search: $search, searchFields: $searchFields) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node
}
}
}
gadgetMeta {
hydrations(modelName: "widget")
}
}"
`);
expectValidGraphQLQuery(plan.query);
expect(plan.variables).toEqual({ search: "term", searchFields: { name: {}, id: { weight: 10 } } });
});

test("should build a find many query with filter", () => {
const plan = internalFindManyQuery("widget", [], { filter: [{ id: { equals: "1" } }] });
expect(plan.query).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -460,6 +487,26 @@ describe("InternalModelManager", () => {
expect(plan.variables).toEqual({ first: 1, search: "term" });
});

test("should build a find first query with searchFields", () => {
const plan = internalFindFirstQuery("widget", [], { search: "term", searchFields: { name: true, state: false, id: { weight: 10 } } });
expect(plan.query).toMatchInlineSnapshot(`
"query InternalFindFirstWidget($search: String, $searchFields: WidgetSearchFields, $first: Int) {
internal {
listWidget(search: $search, searchFields: $searchFields, first: $first) {
edges {
node
}
}
}
gadgetMeta {
hydrations(modelName: "widget")
}
}"
`);
expectValidGraphQLQuery(plan.query);
expect(plan.variables).toEqual({ first: 1, search: "term", searchFields: { name: {}, id: { weight: 10 } } });
});

test("should build a find first query with filter", () => {
const plan = internalFindFirstQuery("widget", [], { filter: [{ id: { equals: "1" } }] });

Expand Down Expand Up @@ -578,6 +625,40 @@ describe("InternalModelManager", () => {
expectValidGraphQLQuery(plan.query);
});

test("should build a find first query with searchFilter", () => {
const plan = internalFindFirstQuery("widget_model", [], {
search: "term",
searchFields: { name: true, state: false, id: { weight: 10 } },
});
expect(plan).toMatchInlineSnapshot(`
{
"query": "query InternalFindFirstWidgetModel($search: String, $searchFields: WidgetModelSearchFields, $first: Int) {
internal {
listWidgetModel(search: $search, searchFields: $searchFields, first: $first) {
edges {
node
}
}
}
gadgetMeta {
hydrations(modelName: "widget_model")
}
}",
"variables": {
"first": 1,
"search": "term",
"searchFields": {
"id": {
"weight": 10,
},
"name": {},
},
},
}
`);
expectValidGraphQLQuery(plan.query);
});

test("should build a find first query with filter", () => {
const plan = internalFindFirstQuery("widget_model", [], { filter: [{ id: { equals: "1" } }] });
expect(plan).toMatchInlineSnapshot(`
Expand Down
61 changes: 58 additions & 3 deletions packages/api-client-core/spec/operationBuilders.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,48 @@ describe("operation builders", () => {
`);
});

test("findManyOperation should build a findMany query with searchFields if option provided", () => {
expect(
findManyOperation("widgets", { __typename: true, id: true, state: true }, "widget", {
search: "Search Term",
searchFields: { name: true, state: false, id: { weight: 10 } },
})
).toMatchInlineSnapshot(`
{
"query": "query widgets($after: String, $first: Int, $before: String, $last: Int, $search: String, $searchFields: WidgetSearchFields) {
widgets(after: $after, first: $first, before: $before, last: $last, search: $search, searchFields: $searchFields) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
__typename
id
state
}
}
}
gadgetMeta {
hydrations(modelName: "widget")
}
}",
"variables": {
"search": "Search Term",
"searchFields": {
"id": {
"weight": 10,
},
"name": {},
},
},
}
`);
});

test("findManyOperation should build a findMany query with sort if option provided", () => {
expect(findManyOperation("widgets", { __typename: true, id: true, state: true }, "widget", { sort: [{ id: "Ascending" }] }))
.toMatchInlineSnapshot(`
Expand Down Expand Up @@ -304,15 +346,21 @@ describe("operation builders", () => {
"widgets",
{ __typename: true, id: true, state: true },
"widget",
{ sort: [{ id: "Ascending" }], filter: [{ foo: { equals: "bar" } }] },
{
sort: [{ id: "Ascending" }],
filter: [{ foo: { equals: "bar" } }],
searchFields: { name: true, state: false, id: { weight: 10 } },
search: "Search Term",
},

["outer", "inner"]
)
).toMatchInlineSnapshot(`
{
"query": "query widgets($after: String, $first: Int, $before: String, $last: Int, $sort: [OuterInnerWidgetSort!], $filter: [OuterInnerWidgetFilter!]) {
"query": "query widgets($after: String, $first: Int, $before: String, $last: Int, $sort: [OuterInnerWidgetSort!], $filter: [OuterInnerWidgetFilter!], $search: String, $searchFields: OuterInnerWidgetSearchFields) {
outer {
inner {
widgets(after: $after, first: $first, before: $before, last: $last, sort: $sort, filter: $filter) {
widgets(after: $after, first: $first, before: $before, last: $last, sort: $sort, filter: $filter, search: $search, searchFields: $searchFields) {
pageInfo {
hasNextPage
hasPreviousPage
Expand Down Expand Up @@ -342,6 +390,13 @@ describe("operation builders", () => {
},
},
],
"search": "Search Term",
"searchFields": {
"id": {
"weight": 10,
},
"name": {},
},
"sort": [
{
"id": "Ascending",
Expand Down
8 changes: 8 additions & 0 deletions packages/api-client-core/src/InternalModelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import {
hydrateRecord,
hydrateRecordArray,
hydrationSelection,
jsSearchFieldsToGqlSearchFields,
namespaceDataPath,
namespacedGraphQLTypeName,
namespacify,
searchableFieldTypeName,
sortTypeName,
} from "./support.js";
import type {
Expand Down Expand Up @@ -51,6 +53,12 @@ export const internalFindOneQuery = (apiIdentifier: string, id: string, namespac
const internalFindListVariables = (apiIdentifier: string, namespace: string[], options?: InternalFindListOptions) => {
return {
search: options?.search ? Var({ value: options?.search, type: "String" }) : undefined,
searchFields: options?.searchFields
? Var({
value: jsSearchFieldsToGqlSearchFields(options.searchFields),
type: `${searchableFieldTypeName(apiIdentifier, namespace)}`,
})
: undefined,
sort: options?.sort ? Var({ value: options?.sort, type: `[${sortTypeName(apiIdentifier, namespace)}!]` }) : undefined,
filter: options?.filter ? Var({ value: options?.filter, type: `[${filterTypeName(apiIdentifier, namespace)}!]` }) : undefined,
select: options?.select ? Var({ value: formatInternalSelectVariable(options?.select), type: `[String!]` }) : undefined,
Expand Down
8 changes: 8 additions & 0 deletions packages/api-client-core/src/operationBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
capitalizeIdentifier,
filterTypeName,
hydrationSelection,
jsSearchFieldsToGqlSearchFields,
namespacify,
searchableFieldTypeName,
sortTypeName,
} from "./support.js";
import type { ActionFunctionOptions, BaseFindOptions, EnqueueBackgroundActionOptions, FindManyOptions, VariablesOptions } from "./types.js";
Expand Down Expand Up @@ -100,6 +102,12 @@ export const findManyOperation = (
sort: options?.sort ? Var({ value: options.sort, type: `[${sortTypeName(modelApiIdentifier, namespace)}!]` }) : undefined,
filter: options?.filter ? Var({ value: options.filter, type: `[${filterTypeName(modelApiIdentifier, namespace)}!]` }) : undefined,
search: options?.search ? Var({ value: options.search, type: "String" }) : undefined,
searchFields: options?.searchFields
? Var({
value: jsSearchFieldsToGqlSearchFields(options.searchFields),
type: `${searchableFieldTypeName(modelApiIdentifier, namespace)}`,
})
: undefined,
},
{
pageInfo: { hasNextPage: true, hasPreviousPage: true, startCursor: true, endCursor: true },
Expand Down
20 changes: 19 additions & 1 deletion packages/api-client-core/src/support.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { OperationResult } from "@urql/core";
import { CombinedError } from "@urql/core";
import { isObject } from "lodash-es";
import { Call, type FieldSelection as BuilderFieldSelection } from "tiny-graphql-query-compiler";
import { DataHydrator } from "./DataHydrator.js";
import type { ActionFunctionMetadata, AnyActionFunction } from "./GadgetFunctions.js";
import type { RecordShape } from "./GadgetRecord.js";
import { GadgetRecord } from "./GadgetRecord.js";
import type { VariablesOptions } from "./types.js";
import type { AnySearchableFieldConfig, SearchableFieldConfig, VariablesOptions } from "./types.js";

/**
* Generic type of the state of any record of a Gadget model
Expand Down Expand Up @@ -295,6 +296,9 @@ export const sortTypeName = (modelApiIdentifier: string, namespace: string | str
export const filterTypeName = (modelApiIdentifier: string, namespace: string | string[] | null | undefined) =>
`${namespacedGraphQLTypeName(modelApiIdentifier, namespace)}Filter`;

export const searchableFieldTypeName = (modelApiIdentifier: string, namespace: string | string[] | null | undefined) =>
`${namespacedGraphQLTypeName(modelApiIdentifier, namespace)}SearchFields`;

export const getNonUniqueDataError = (modelApiIdentifier: string, fieldName: string, fieldValue: string) =>
new GadgetNonUniqueDataError(
`More than one record found for ${modelApiIdentifier}.${fieldName} = ${fieldValue}. Please confirm your unique validation is not reporting an error.`
Expand Down Expand Up @@ -758,3 +762,17 @@ export const formatErrorMessages = (error: Error) => {

return result;
};

export const jsSearchFieldsToGqlSearchFields = (searchFields: AnySearchableFieldConfig) => {
const result: Record<string, SearchableFieldConfig> = {};

for (const [field, config] of Object.entries(searchFields)) {
if (isObject(config)) {
result[field] = config;
} else if (config) {
result[field] = {};
}
}

return result;
};
14 changes: 14 additions & 0 deletions packages/api-client-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,14 @@ export type FilterElement =
**/
export type AnyFilter = FilterElement | FilterElement[];

export type SearchableFieldConfig = {
weight?: number;
};

export type AnySearchableFieldConfig = {
[key: string]: SearchableFieldConfig | boolean | null | undefined;
};

/**
* A list of fields to return from the backend
* Is not specific to any backend model. Look for the backend specific types in the generated API client if you need strong type safety.
Expand All @@ -621,6 +629,8 @@ export interface FindManyOptions extends BaseFindOptions {
filter?: AnyFilter | AnyFilter[] | null;
/** Only return records which match this given search string */
search?: string | null;
/** Which fields on a record to search. If not specified, all searchable fields will be searched. */
searchFields?: AnySearchableFieldConfig | null;

/**
* Return records after the given cursor for pagination. Useful in tandem with the `first` count option for pagination.
Expand Down Expand Up @@ -684,6 +694,10 @@ export interface InternalFindListOptions {
* Matches the behavior of the Public API `search` option
**/
search?: string | null;
/**
* Which fields on a record to search. If not specified, all searchable fields will be searched.
*/
searchFields?: AnySearchableFieldConfig | null;
/**
* How to sort the returned records
* Matches the format and behavior of the Public API `sort` option
Expand Down
4 changes: 2 additions & 2 deletions packages/react-bigcommerce/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gadgetinc/react-bigcommerce",
"version": "0.4.1",
"version": "0.4.2",
"files": [
"README.md",
"dist/**/*"
Expand All @@ -27,7 +27,7 @@
"prerelease": "gitpkg publish"
},
"dependencies": {
"@gadgetinc/api-client-core": "^0.15.46"
"@gadgetinc/api-client-core": "^0.15.47"
},
"devDependencies": {
"@gadgetinc/api-client-core": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-shopify-app-bridge/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gadgetinc/react-shopify-app-bridge",
"version": "0.19.1",
"version": "0.19.2",
"files": [
"README.md",
"dist/**/*"
Expand All @@ -27,7 +27,7 @@
"prerelease": "gitpkg publish"
},
"dependencies": {
"@gadgetinc/api-client-core": "^0.15.46",
"@gadgetinc/api-client-core": "^0.15.47",
"crypto-js": "^4.2.0",
"tslib": "^2.6.2"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gadgetinc/react",
"version": "0.22.1",
"version": "0.22.2",
"files": [
"README.md",
"dist/**/*",
Expand Down Expand Up @@ -50,7 +50,7 @@
},
"dependencies": {
"@0no-co/graphql.web": "^1.0.4",
"@gadgetinc/api-client-core": "^0.15.46",
"@gadgetinc/api-client-core": "^0.15.47 ",
"@hookform/resolvers": "^5.2.1",
"filesize": "^10.1.2",
"pluralize": "^8.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/shopify-extensions/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gadgetinc/shopify-extensions",
"version": "0.6.1",
"version": "0.6.2",
"files": [
"README.md",
"dist/**/*",
Expand Down Expand Up @@ -33,7 +33,7 @@
"prerelease": "gitpkg publish"
},
"dependencies": {
"@gadgetinc/api-client-core": "^0.15.46"
"@gadgetinc/api-client-core": "^0.15.47"
},
"devDependencies": {
"@gadgetinc/api-client-core": "workspace:*",
Expand Down