mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
packages
This commit is contained in:
500
packages/nestjs-query/src/dataProvider/index.ts
Normal file
500
packages/nestjs-query/src/dataProvider/index.ts
Normal file
@@ -0,0 +1,500 @@
|
||||
import type { BaseRecord, DataProvider, LogicalFilter } from "@refinedev/core";
|
||||
|
||||
import camelcase from "camelcase";
|
||||
import * as gql from "gql-query-builder";
|
||||
import type VariableOptions from "gql-query-builder/build/VariableOptions";
|
||||
import { GraphQLClient } from "graphql-request";
|
||||
import gqlTag from "graphql-tag";
|
||||
import { singular } from "pluralize";
|
||||
|
||||
import { generateFilters, generatePaging, generateSorting } from "../utils";
|
||||
|
||||
import { getOperationFields, isMutation } from "../utils/graphql";
|
||||
|
||||
const dataProvider = (client: GraphQLClient): Required<DataProvider> => {
|
||||
return {
|
||||
getList: async ({ resource, pagination, sorters, filters, meta }) => {
|
||||
const operation = camelcase(resource);
|
||||
|
||||
const paging = generatePaging(pagination || {});
|
||||
|
||||
const queryVariables: VariableOptions = {};
|
||||
|
||||
let query;
|
||||
let variables;
|
||||
|
||||
if (meta?.gqlQuery) {
|
||||
query = meta?.gqlQuery;
|
||||
|
||||
variables = {
|
||||
filter: filters ? generateFilters(filters as LogicalFilter[]) : {},
|
||||
sorting: sorters ? generateSorting(sorters) : [],
|
||||
paging,
|
||||
};
|
||||
} else {
|
||||
if (filters) {
|
||||
queryVariables["filter"] = {
|
||||
type: camelcase(`${singular(resource)}Filter`, {
|
||||
pascalCase: true,
|
||||
}),
|
||||
required: true,
|
||||
value: generateFilters(filters as LogicalFilter[]),
|
||||
};
|
||||
}
|
||||
|
||||
if (sorters) {
|
||||
queryVariables["sorting"] = {
|
||||
type: camelcase(`${singular(resource)}Sort`, {
|
||||
pascalCase: true,
|
||||
}),
|
||||
required: true,
|
||||
list: [true],
|
||||
value: generateSorting(sorters),
|
||||
};
|
||||
}
|
||||
|
||||
if (paging) {
|
||||
queryVariables["paging"] = {
|
||||
type: "OffsetPaging",
|
||||
required: true,
|
||||
value: paging,
|
||||
};
|
||||
}
|
||||
|
||||
const gqlQuery = gql.query({
|
||||
operation,
|
||||
fields: [{ nodes: meta?.fields }, "totalCount"],
|
||||
variables: queryVariables,
|
||||
});
|
||||
|
||||
query = gqlQuery.query;
|
||||
variables = gqlQuery.variables;
|
||||
}
|
||||
|
||||
const response = await client.request<BaseRecord>(query, variables);
|
||||
|
||||
return {
|
||||
data: response[operation].nodes,
|
||||
total: response[operation].totalCount,
|
||||
};
|
||||
},
|
||||
|
||||
getMany: async ({ resource, ids, meta }) => {
|
||||
const operation = camelcase(resource);
|
||||
|
||||
if (meta?.gqlQuery) {
|
||||
const response = await client.request<BaseRecord>(meta.gqlQuery, {
|
||||
filter: {
|
||||
id: { in: ids },
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[operation].nodes,
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables } = gql.query({
|
||||
operation,
|
||||
fields: [{ nodes: meta?.fields || ["id"] }],
|
||||
variables: {
|
||||
filter: {
|
||||
type: camelcase(`${singular(resource)}Filter`, {
|
||||
pascalCase: true,
|
||||
}),
|
||||
required: true,
|
||||
value: {
|
||||
id: { in: ids },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.request<BaseRecord>(query, variables);
|
||||
|
||||
return {
|
||||
data: response[operation].nodes,
|
||||
};
|
||||
},
|
||||
|
||||
create: async ({ resource, variables, meta }) => {
|
||||
const operation = `createOne${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}`;
|
||||
|
||||
const gqlOperation = meta?.gqlMutation ?? meta?.gqlQuery;
|
||||
|
||||
if (gqlOperation) {
|
||||
const response = await client.request<BaseRecord>(gqlOperation, {
|
||||
input: { [camelcase(singular(resource))]: variables },
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables: queryVariables } = gql.mutation({
|
||||
operation,
|
||||
fields: meta?.fields || ["id"],
|
||||
variables: {
|
||||
input: {
|
||||
type: `CreateOne${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}Input`,
|
||||
required: true,
|
||||
value: {
|
||||
[camelcase(singular(resource))]: variables,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.request<BaseRecord>(query, queryVariables);
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
},
|
||||
|
||||
createMany: async ({ resource, variables, meta }) => {
|
||||
const pascalResource = camelcase(resource, { pascalCase: true });
|
||||
const operation = `createMany${pascalResource}`;
|
||||
|
||||
const gqlOperation = meta?.gqlMutation ?? meta?.gqlQuery;
|
||||
|
||||
if (gqlOperation) {
|
||||
const response = await client.request<BaseRecord>(gqlOperation, {
|
||||
input: {
|
||||
[camelcase(resource)]: variables,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables: queryVariables } = gql.mutation({
|
||||
operation,
|
||||
fields: meta?.fields || ["id"],
|
||||
variables: {
|
||||
input: {
|
||||
type: `CreateMany${camelcase(resource, {
|
||||
pascalCase: true,
|
||||
})}Input`,
|
||||
required: true,
|
||||
value: {
|
||||
[camelcase(resource)]: variables,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.request<BaseRecord>(query, queryVariables);
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
},
|
||||
update: async ({ resource, id, variables, meta }) => {
|
||||
const operation = `updateOne${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}`;
|
||||
|
||||
const gqlOperation = meta?.gqlMutation ?? meta?.gqlQuery;
|
||||
|
||||
if (gqlOperation) {
|
||||
const response = await client.request<BaseRecord>(gqlOperation, {
|
||||
input: {
|
||||
id,
|
||||
update: variables,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables: queryVariables } = gql.mutation({
|
||||
operation,
|
||||
fields: meta?.fields || ["id"],
|
||||
variables: {
|
||||
input: {
|
||||
type: `UpdateOne${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}Input`,
|
||||
required: true,
|
||||
value: {
|
||||
id,
|
||||
update: variables,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.request<BaseRecord>(query, queryVariables);
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
},
|
||||
updateMany: async ({ resource, ids, variables, meta }) => {
|
||||
const pascalResource = camelcase(resource, {
|
||||
pascalCase: true,
|
||||
});
|
||||
|
||||
const mutationOperation = `updateMany${pascalResource}`;
|
||||
|
||||
const mutation = gqlTag`
|
||||
mutation UpdateMany${pascalResource}($input: UpdateMany${pascalResource}Input!) {
|
||||
${mutationOperation}(input: $input) {
|
||||
updatedCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
await client.request<BaseRecord>(mutation, {
|
||||
input: { filter: { id: { in: ids } }, update: variables },
|
||||
});
|
||||
|
||||
const operation = camelcase(resource);
|
||||
|
||||
let query;
|
||||
let queryVariables;
|
||||
|
||||
if (meta?.fields) {
|
||||
const gqlQuery = gql.query({
|
||||
operation,
|
||||
fields: [{ nodes: meta?.fields || ["id"] }],
|
||||
variables: {
|
||||
filter: {
|
||||
type: camelcase(`${singular(resource)}Filter`, {
|
||||
pascalCase: true,
|
||||
}),
|
||||
required: true,
|
||||
value: {
|
||||
id: { in: ids },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
query = gqlQuery.query;
|
||||
queryVariables = gqlQuery.variables;
|
||||
} else {
|
||||
query = gqlTag`
|
||||
query GetMany${pascalResource}($filter: ${singular(
|
||||
pascalResource,
|
||||
)}Filter!) {
|
||||
${operation}(filter: $filter) {
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
queryVariables = {
|
||||
filter: { id: { in: ids } },
|
||||
};
|
||||
}
|
||||
|
||||
const response = await client.request<BaseRecord>(query, queryVariables);
|
||||
|
||||
return {
|
||||
data: response[operation].nodes,
|
||||
};
|
||||
},
|
||||
getOne: async ({ resource, id, meta }) => {
|
||||
const operation = camelcase(singular(resource));
|
||||
|
||||
const gqlOperation = meta?.gqlQuery ?? meta?.gqlMutation;
|
||||
|
||||
if (gqlOperation) {
|
||||
let query = gqlOperation;
|
||||
const variables = { id };
|
||||
|
||||
if (isMutation(gqlOperation)) {
|
||||
const stringFields = getOperationFields(gqlOperation);
|
||||
|
||||
query = gqlTag`
|
||||
query Get${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}($id: ID!) {
|
||||
${operation}(id: $id) {
|
||||
${stringFields}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const response = await client.request<BaseRecord>(query, variables);
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables } = gql.query({
|
||||
operation,
|
||||
fields: meta?.fields || ["id"],
|
||||
variables: {
|
||||
id: {
|
||||
type: "ID",
|
||||
required: true,
|
||||
value: id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await client.request<BaseRecord>(query, variables);
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
},
|
||||
deleteOne: async ({ resource, id, meta }) => {
|
||||
const pascalResource = camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
});
|
||||
|
||||
const operation = `deleteOne${pascalResource}`;
|
||||
|
||||
if (meta?.gqlMutation) {
|
||||
const response = await client.request<BaseRecord>(meta.gqlMutation, {
|
||||
input: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
}
|
||||
|
||||
const query = gqlTag`
|
||||
mutation DeleteOne${pascalResource}($input: DeleteOne${pascalResource}Input!) {
|
||||
${operation}(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await client.request<BaseRecord>(query, {
|
||||
input: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[operation],
|
||||
};
|
||||
},
|
||||
deleteMany: async ({ resource, ids }) => {
|
||||
const pascalResource = camelcase(resource, {
|
||||
pascalCase: true,
|
||||
});
|
||||
const operation = `deleteMany${pascalResource}`;
|
||||
|
||||
const query = gqlTag`
|
||||
mutation DeleteMany${pascalResource}($input: DeleteMany${pascalResource}Input!) {
|
||||
${operation}(input: $input) {
|
||||
deletedCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = {
|
||||
input: {
|
||||
filter: {
|
||||
id: { in: ids },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await client.request<BaseRecord>(query, variables);
|
||||
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
},
|
||||
getApiUrl: () => {
|
||||
return (client as any).url; // url field in GraphQLClient is private
|
||||
},
|
||||
custom: async ({ url, method, headers, meta }) => {
|
||||
const SUPPORTED_METHODS = ["get", "post"];
|
||||
const requestUrl = url || (client as any).url;
|
||||
|
||||
if (!SUPPORTED_METHODS.some((it) => it === method)) {
|
||||
throw Error(`GraphQL does not support ${method} method.`);
|
||||
}
|
||||
|
||||
const validMethod = method as "get" | "post";
|
||||
|
||||
const _client = new GraphQLClient(requestUrl, {
|
||||
...client.requestConfig,
|
||||
method: validMethod,
|
||||
headers: { ...client.requestConfig.headers, ...headers },
|
||||
});
|
||||
|
||||
const gqlOperation = meta?.gqlMutation ?? meta?.gqlQuery;
|
||||
|
||||
if (gqlOperation) {
|
||||
const response: any = await _client.request<BaseRecord>({
|
||||
document: gqlOperation,
|
||||
variables: meta?.variables,
|
||||
});
|
||||
|
||||
return { data: response };
|
||||
}
|
||||
|
||||
if (meta?.rawQuery) {
|
||||
const response = await _client.request<BaseRecord>({
|
||||
document: meta.rawQuery,
|
||||
variables: meta.variables,
|
||||
});
|
||||
|
||||
return { data: response };
|
||||
}
|
||||
|
||||
if (meta) {
|
||||
if (meta.operation) {
|
||||
let query;
|
||||
let variables;
|
||||
|
||||
if (method === "get") {
|
||||
const gqlQuery = gql.query({
|
||||
operation: meta.operation,
|
||||
fields: meta.fields,
|
||||
variables: meta.variables,
|
||||
});
|
||||
|
||||
query = gqlQuery.query;
|
||||
variables = gqlQuery.variables;
|
||||
} else {
|
||||
const gqlMutation = gql.mutation({
|
||||
operation: meta.operation,
|
||||
fields: meta.fields,
|
||||
variables: meta.variables,
|
||||
});
|
||||
|
||||
query = gqlMutation.query;
|
||||
variables = gqlMutation.variables;
|
||||
}
|
||||
|
||||
const response = await _client.request<BaseRecord>({
|
||||
document: query,
|
||||
variables,
|
||||
});
|
||||
|
||||
return {
|
||||
data: response[meta.operation],
|
||||
};
|
||||
}
|
||||
throw Error("GraphQL operation name required.");
|
||||
}
|
||||
throw Error(
|
||||
"GraphQL needs operation, fields and variables values in meta object.",
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default dataProvider;
|
||||
30
packages/nestjs-query/src/index.ts
Normal file
30
packages/nestjs-query/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import dataProvider from "./dataProvider/index.js";
|
||||
|
||||
export * from "./dataProvider/index.js";
|
||||
export * from "./interfaces.js";
|
||||
export * from "./liveProvider/index.js";
|
||||
export * as qqlQueryBuilder from "gql-query-builder";
|
||||
export type {
|
||||
BatchRequestDocument,
|
||||
BatchRequestsExtendedOptions,
|
||||
BatchRequestsOptions,
|
||||
ClientError,
|
||||
GraphQLWebSocketClient,
|
||||
RawRequestExtendedOptions,
|
||||
RawRequestOptions,
|
||||
RequestDocument,
|
||||
RequestExtendedOptions,
|
||||
RequestOptions,
|
||||
Variables,
|
||||
} from "graphql-request";
|
||||
export {
|
||||
batchRequests,
|
||||
gql,
|
||||
GraphQLClient,
|
||||
rawRequest,
|
||||
request,
|
||||
resolveRequestDocument,
|
||||
} from "graphql-request";
|
||||
export * as graphqlWS from "graphql-ws";
|
||||
|
||||
export default dataProvider;
|
||||
9
packages/nestjs-query/src/interfaces.ts
Normal file
9
packages/nestjs-query/src/interfaces.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type GetFieldsFromList<Q extends Record<string, any>> =
|
||||
Q[keyof Q]["nodes"][0];
|
||||
|
||||
export type GetFields<Q extends Record<string, any>> = Q[keyof Q];
|
||||
|
||||
export type GetVariables<Q extends Record<"input", any>> =
|
||||
Q["input"]["update"] extends object
|
||||
? Q["input"]["update"]
|
||||
: Q["input"][keyof Q["input"]];
|
||||
75
packages/nestjs-query/src/liveProvider/index.ts
Normal file
75
packages/nestjs-query/src/liveProvider/index.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { LiveProvider } from "@refinedev/core";
|
||||
|
||||
import type { Client } from "graphql-ws";
|
||||
|
||||
import { generateSubscription } from "../utils";
|
||||
|
||||
type SubscriptionAction = "created" | "updated" | "deleted";
|
||||
|
||||
export const liveProvider = (client: Client): LiveProvider => {
|
||||
const subscribeToResource = (
|
||||
client: Client,
|
||||
callback: Function,
|
||||
params: any,
|
||||
meta: any,
|
||||
action: SubscriptionAction,
|
||||
resource: string,
|
||||
unsubscribes: Function[],
|
||||
) => {
|
||||
const unsubscribe = generateSubscription(
|
||||
client,
|
||||
{ callback, params, meta },
|
||||
action,
|
||||
);
|
||||
unsubscribes.push(unsubscribe);
|
||||
};
|
||||
|
||||
return {
|
||||
subscribe({ callback, params, meta }) {
|
||||
const { resource, subscriptionType } = params ?? {};
|
||||
|
||||
if (!meta || !subscriptionType || !resource) {
|
||||
throw new Error(
|
||||
"[useSubscription]: `meta`, `subscriptionType` and `resource` are required in `params` for graphql subscriptions",
|
||||
);
|
||||
}
|
||||
|
||||
const unsubscribes: any[] = [];
|
||||
|
||||
if (params?.subscriptionType === "useList") {
|
||||
["created", "updated", "deleted"].forEach((action) =>
|
||||
subscribeToResource(
|
||||
client,
|
||||
callback,
|
||||
params,
|
||||
meta,
|
||||
action as SubscriptionAction,
|
||||
resource,
|
||||
unsubscribes,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (params?.subscriptionType === "useOne") {
|
||||
subscribeToResource(
|
||||
client,
|
||||
callback,
|
||||
params,
|
||||
meta,
|
||||
"updated",
|
||||
resource,
|
||||
unsubscribes,
|
||||
);
|
||||
}
|
||||
|
||||
const unsubscribe = () => {
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
|
||||
return unsubscribe;
|
||||
},
|
||||
unsubscribe(unsubscribe) {
|
||||
unsubscribe();
|
||||
},
|
||||
};
|
||||
};
|
||||
75
packages/nestjs-query/src/utils/graphql.ts
Normal file
75
packages/nestjs-query/src/utils/graphql.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
type FieldNode,
|
||||
type DocumentNode,
|
||||
visit,
|
||||
type SelectionSetNode,
|
||||
} from "graphql";
|
||||
|
||||
const getChildNodesField = (node: FieldNode): FieldNode | undefined => {
|
||||
return node?.selectionSet?.selections?.find(
|
||||
(node) => node.kind === "Field" && node.name.value === "nodes",
|
||||
) as FieldNode;
|
||||
};
|
||||
|
||||
export const getOperationFields = (documentNode: DocumentNode) => {
|
||||
const fieldLines: string[] = [];
|
||||
let isInitialEnter = true;
|
||||
let depth = 0;
|
||||
let isNestedField = false;
|
||||
|
||||
visit(documentNode, {
|
||||
Field: {
|
||||
enter(node): SelectionSetNode | void {
|
||||
if (isInitialEnter) {
|
||||
isInitialEnter = false;
|
||||
|
||||
const childNodesField = getChildNodesField(node);
|
||||
|
||||
const nodeToReturn = childNodesField ?? node;
|
||||
|
||||
if (typeof nodeToReturn.selectionSet === "undefined") {
|
||||
throw new Error("Operation must have a selection set");
|
||||
}
|
||||
|
||||
return nodeToReturn.selectionSet;
|
||||
}
|
||||
|
||||
fieldLines.push(
|
||||
`${depth > 0 ? " ".repeat(isNestedField ? depth : depth - 1) : ""}${
|
||||
node.name.value
|
||||
}${node.selectionSet ? " {" : ""}`,
|
||||
);
|
||||
|
||||
if (node.selectionSet) {
|
||||
depth++;
|
||||
isNestedField = true;
|
||||
}
|
||||
},
|
||||
leave(node) {
|
||||
if (node.selectionSet) {
|
||||
depth--;
|
||||
fieldLines.push(`${" ".repeat(depth)}}`);
|
||||
isNestedField = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return fieldLines.join("\n").trim();
|
||||
};
|
||||
|
||||
export const isMutation = (documentNode: DocumentNode) => {
|
||||
let isMutation = false;
|
||||
|
||||
visit(documentNode, {
|
||||
OperationDefinition: {
|
||||
enter(node) {
|
||||
if (node.operation === "mutation") {
|
||||
isMutation = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return isMutation;
|
||||
};
|
||||
456
packages/nestjs-query/src/utils/index.ts
Normal file
456
packages/nestjs-query/src/utils/index.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
import type {
|
||||
CrudFilter,
|
||||
CrudOperators,
|
||||
CrudSorting,
|
||||
LogicalFilter,
|
||||
Pagination,
|
||||
} from "@refinedev/core";
|
||||
|
||||
import camelcase from "camelcase";
|
||||
import * as gql from "gql-query-builder";
|
||||
import type VariableOptions from "gql-query-builder/build/VariableOptions";
|
||||
import type { Client } from "graphql-ws";
|
||||
import set from "lodash/set";
|
||||
import { singular } from "pluralize";
|
||||
|
||||
import { getOperationFields } from "./graphql";
|
||||
|
||||
export const generateSubscription = (
|
||||
client: Client,
|
||||
{ callback, params, meta }: any,
|
||||
type: string,
|
||||
) => {
|
||||
const generatorMap: any = {
|
||||
created: generateCreatedSubscription,
|
||||
updated: generateUpdatedSubscription,
|
||||
deleted: generateDeletedSubscription,
|
||||
};
|
||||
|
||||
const { resource, filters, subscriptionType, id, ids } = params ?? {};
|
||||
|
||||
const generator = generatorMap[type];
|
||||
|
||||
const { operation, query, variables, operationName } = generator({
|
||||
ids,
|
||||
id,
|
||||
resource,
|
||||
filters,
|
||||
meta,
|
||||
subscriptionType,
|
||||
});
|
||||
|
||||
const onNext = (payload: any) => {
|
||||
callback(payload.data[operation]);
|
||||
};
|
||||
|
||||
const unsubscribe = client.subscribe(
|
||||
{ query, variables, operationName },
|
||||
{
|
||||
next: onNext,
|
||||
error: console.error,
|
||||
complete: () => null,
|
||||
},
|
||||
);
|
||||
|
||||
return unsubscribe;
|
||||
};
|
||||
|
||||
const operatorMap: { [key: string]: string } = {
|
||||
eq: "eq",
|
||||
ne: "neq",
|
||||
lt: "lt",
|
||||
gt: "gt",
|
||||
lte: "lte",
|
||||
gte: "gte",
|
||||
in: "in",
|
||||
nin: "notIn",
|
||||
};
|
||||
|
||||
const operatorMapper = (
|
||||
operator: CrudOperators,
|
||||
value: any,
|
||||
): { [key: string]: any } => {
|
||||
if (operator === "contains") {
|
||||
return { iLike: `%${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "ncontains") {
|
||||
return { notILike: `%${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "containss") {
|
||||
return { like: `%${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "ncontainss") {
|
||||
return { notLike: `%${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "startswith") {
|
||||
return { iLike: `${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "nstartswith") {
|
||||
return { notILike: `${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "startswiths") {
|
||||
return { like: `${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "nstartswiths") {
|
||||
return { notLike: `${value}%` };
|
||||
}
|
||||
|
||||
if (operator === "endswith") {
|
||||
return { iLike: `%${value}` };
|
||||
}
|
||||
|
||||
if (operator === "nendswith") {
|
||||
return { notILike: `%${value}` };
|
||||
}
|
||||
|
||||
if (operator === "endswiths") {
|
||||
return { like: `%${value}` };
|
||||
}
|
||||
|
||||
if (operator === "nendswiths") {
|
||||
return { notLike: `%${value}` };
|
||||
}
|
||||
|
||||
if (operator === "null") {
|
||||
return { is: null };
|
||||
}
|
||||
|
||||
if (operator === "nnull") {
|
||||
return { isNot: null };
|
||||
}
|
||||
|
||||
if (operator === "between") {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error("Between operator requires an array");
|
||||
}
|
||||
|
||||
if (value.length !== 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { between: { lower: value[0], upper: value[1] } };
|
||||
}
|
||||
|
||||
if (operator === "nbetween") {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error("NBetween operator requires an array");
|
||||
}
|
||||
|
||||
if (value.length !== 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { notBetween: { lower: value[0], upper: value[1] } };
|
||||
}
|
||||
|
||||
return { [operatorMap[operator]]: value };
|
||||
};
|
||||
|
||||
export const generateFilters = (filters: LogicalFilter[]) => {
|
||||
const result: { [key: string]: { [key: string]: string | number } } = {};
|
||||
|
||||
filters
|
||||
.filter((f) => {
|
||||
if (Array.isArray(f.value) && f.value.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (typeof f.value === "number") {
|
||||
return Number.isFinite(f.value);
|
||||
}
|
||||
|
||||
// If the value is null or undefined, it returns false.
|
||||
return !(f.value == null);
|
||||
})
|
||||
.map((filter: LogicalFilter | CrudFilter) => {
|
||||
if (filter.operator === "and" || filter.operator === "or") {
|
||||
return set(result, filter.operator, [
|
||||
generateFilters(filter.value as LogicalFilter[]),
|
||||
]);
|
||||
}
|
||||
if ("field" in filter) {
|
||||
return set(
|
||||
result,
|
||||
filter.field,
|
||||
operatorMapper(filter.operator, filter.value),
|
||||
);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const generateSorting = (sorters: CrudSorting) => {
|
||||
return sorters.map((sorter) => {
|
||||
return {
|
||||
field: sorter.field,
|
||||
direction: sorter.order.toUpperCase(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const generatePaging = (pagination: Pagination) => {
|
||||
// maximum value of 32 bit signed integer
|
||||
if (pagination.mode === "off") return { limit: 2147483647 };
|
||||
|
||||
if (pagination.mode !== "server") return undefined;
|
||||
|
||||
if (!pagination.current || !pagination.pageSize) return undefined;
|
||||
|
||||
return {
|
||||
limit: pagination.pageSize,
|
||||
offset: (pagination.current - 1) * pagination.pageSize,
|
||||
};
|
||||
};
|
||||
|
||||
export const generateCreatedSubscription = ({
|
||||
resource,
|
||||
filters,
|
||||
meta,
|
||||
}: any) => {
|
||||
const gqlOperation = meta?.gqlQuery ?? meta?.gqlMutation;
|
||||
|
||||
if (gqlOperation) {
|
||||
const singularResourceName = camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
});
|
||||
|
||||
const operationName = `Created${singularResourceName}`;
|
||||
|
||||
const operation = `created${singularResourceName}`;
|
||||
|
||||
const query = `
|
||||
subscription ${operationName}($input: Create${singularResourceName}SubscriptionFilterInput) {
|
||||
${operation}(input: $input) {
|
||||
${getOperationFields(gqlOperation)}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables: VariableOptions = {};
|
||||
|
||||
if (filters) {
|
||||
variables["input"] = {
|
||||
filter: generateFilters(
|
||||
filters.filter(
|
||||
(filter: LogicalFilter) => !filter.field.includes("."),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return { query, variables, operation, operationName };
|
||||
}
|
||||
|
||||
const operation = `created${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}`;
|
||||
|
||||
const queryVariables: VariableOptions = {};
|
||||
|
||||
if (filters) {
|
||||
queryVariables["input"] = {
|
||||
type: camelcase(
|
||||
`create_${singular(resource)}_subscription_filter_input`,
|
||||
{
|
||||
pascalCase: true,
|
||||
},
|
||||
),
|
||||
required: true,
|
||||
value: {
|
||||
filter: generateFilters(
|
||||
filters.filter(
|
||||
(filter: LogicalFilter) => !filter.field.includes("."),
|
||||
),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables } = gql.subscription({
|
||||
operation,
|
||||
fields: meta.fields,
|
||||
variables: queryVariables,
|
||||
});
|
||||
|
||||
return { query, variables, operation };
|
||||
};
|
||||
|
||||
export const generateUpdatedSubscription = ({
|
||||
id,
|
||||
resource,
|
||||
filters,
|
||||
meta,
|
||||
}: any) => {
|
||||
const gqlOperation = meta?.gqlQuery ?? meta?.gqlMutation;
|
||||
|
||||
if (gqlOperation) {
|
||||
const singularResourceName = camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
});
|
||||
|
||||
const operationName = `Updated${singularResourceName}`;
|
||||
|
||||
const operation = `updatedOne${singularResourceName}`;
|
||||
|
||||
const query = `
|
||||
subscription ${operationName}($input: UpdateOne${singularResourceName}SubscriptionFilterInput) {
|
||||
${operation}(input: $input) {
|
||||
${getOperationFields(gqlOperation)}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables: VariableOptions = {};
|
||||
|
||||
if (filters) {
|
||||
variables["input"] = {
|
||||
filter: generateFilters(
|
||||
filters.filter(
|
||||
(filter: LogicalFilter) => !filter.field.includes("."),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (id) {
|
||||
variables["input"] = {
|
||||
filter: {
|
||||
id: { eq: id },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { query, variables, operation, operationName };
|
||||
}
|
||||
|
||||
const operation = `updatedOne${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}`;
|
||||
|
||||
const queryVariables: VariableOptions = {};
|
||||
|
||||
if (filters) {
|
||||
queryVariables["input"] = {
|
||||
type: camelcase(
|
||||
`update_one_${singular(resource)}_subscription_filter_input`,
|
||||
{
|
||||
pascalCase: true,
|
||||
},
|
||||
),
|
||||
required: true,
|
||||
value: {
|
||||
filter: generateFilters(
|
||||
filters.filter(
|
||||
(filter: LogicalFilter) => !filter.field.includes("."),
|
||||
),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (id) {
|
||||
queryVariables["input"] = {
|
||||
type: camelcase(
|
||||
`update_one_${singular(resource)}_subscription_filter_input`,
|
||||
{
|
||||
pascalCase: true,
|
||||
},
|
||||
),
|
||||
required: true,
|
||||
value: {
|
||||
filter: {
|
||||
id: { eq: id },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables } = gql.subscription({
|
||||
operation,
|
||||
fields: meta.fields,
|
||||
variables: queryVariables,
|
||||
});
|
||||
|
||||
return { query, variables, operation };
|
||||
};
|
||||
|
||||
export const generateDeletedSubscription = ({
|
||||
resource,
|
||||
filters,
|
||||
meta,
|
||||
}: any) => {
|
||||
if (meta?.gqlQuery) {
|
||||
const singularResourceName = camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
});
|
||||
|
||||
const operationName = `Deleted${singularResourceName}`;
|
||||
|
||||
const operation = `deletedOne${singularResourceName}`;
|
||||
|
||||
const query = `
|
||||
subscription ${operationName}($input: DeleteOne${singularResourceName}SubscriptionFilterInput) {
|
||||
${operation}(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables: VariableOptions = {};
|
||||
|
||||
if (filters) {
|
||||
variables["input"] = {
|
||||
filter: generateFilters(
|
||||
filters.filter(
|
||||
(filter: LogicalFilter) => !filter.field.includes("."),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return { query, variables, operation, operationName };
|
||||
}
|
||||
|
||||
const operation = `deletedOne${camelcase(singular(resource), {
|
||||
pascalCase: true,
|
||||
})}`;
|
||||
|
||||
const queryVariables: VariableOptions = {};
|
||||
|
||||
if (filters) {
|
||||
queryVariables["input"] = {
|
||||
type: camelcase(
|
||||
`delete_one_${singular(resource)}_subscription_filter_input`,
|
||||
{
|
||||
pascalCase: true,
|
||||
},
|
||||
),
|
||||
required: true,
|
||||
value: {
|
||||
filter: generateFilters(
|
||||
filters.filter(
|
||||
(filter: LogicalFilter) => !filter.field.includes("."),
|
||||
),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { query, variables } = gql.subscription({
|
||||
operation,
|
||||
fields: meta.fields.filter(
|
||||
(field: string | object) => typeof field !== "object",
|
||||
),
|
||||
variables: queryVariables,
|
||||
});
|
||||
|
||||
return { query, variables, operation };
|
||||
};
|
||||
Reference in New Issue
Block a user