import * as React from 'react';
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLList,
GraphQLInputObjectType
} from 'graphql';
import Types from '../types';
import { IComponent } from "../types/component";
import { IInfrastructure } from "../types";
import { getChildrenArray, findComponentRecursively } from '../libs';
import { isWebApp } from '../webapp/webapp-component';
import { isAuthentication } from '../authentication/authentication-component';
import { isIdentity } from '../identity/identity-component'
import { isEntry } from './entry-component';
import { setEntry, ddbGetEntry, ddbListEntries, getEntryListQuery } from './datalayer-libs';
export const DATALAYER_INSTANCE_TYPE = "DataLayerComponent";
/**
* Arguments provided by the user
*/
export interface IDataLayerArgs {
/**
* a unique id or name of the datalayer
*/
id: string
}
/**
* properties added programmatically
*/
export interface IDataLayerProps {
/**
* supported queries, i.e. entries that the user can query
*/
queries: any,
/**
* supported mutations
*/
mutations: any,
/**
* get the entry-data-fields of the specified entry
* @param entryId id of the entry to get the fields from
*
getEntryDataFields: (entryId: string) => any,*/
/**
* wrapper function for getEntryListQuery, this allows us to complement some data
* @param entryId
* @param dictKey
*/
getEntryListQuery: (entryId: string, dictKey: any ) => any,
getEntryQuery: (entryId: string, dictKey: any ) => any,
getEntryScanQuery: (entryId: string, dictKey: any ) => any,
setEntryMutation: (entryId: string, values: any ) => any,
deleteEntryMutation: (entryId: string, values: any ) => any,
updateEntryQuery: (entryId, fDictKey: (oldData) => any) => any,
getSchema?: any // optional only because it is implemented in a separate object below. but it is required!
entries: any,
/**
* The Apollo-Client: used at server-side only! Used to provide the Apollo-Client to middlewares
*/
client?: any
setClient: (client: any) => void,
/**
* set to true when running in offline mode
*/
isOffline: boolean,
setOffline: (offline: boolean) => void
};
/**
* identifies a component as a DataLayer
*
* @param component to be tested
*/
export function isDataLayer(component) {
return component !== undefined && component.instanceType === DATALAYER_INSTANCE_TYPE
};
export default (props: IDataLayerArgs | any) => {
const componentProps: IInfrastructure & IComponent = {
infrastructureType: Types.INFRASTRUCTURE_TYPE_COMPONENT,
instanceType: DATALAYER_INSTANCE_TYPE,
instanceId: props.id
};
//const listEntities = getChildrenArray(props).filter(child => isEntity(child));
//const entries = getChildrenArray(props).filter(child => isEntry(child));
const entries = findComponentRecursively(props.children, isEntry);
const complementedProps = {
};
/**
* create the
* @type {{queries: {}, mutations: {}}}
*/
const datalayerProps = {
entries: entries,
mutations: (resolveWithData: boolean) => entries.reduce((result, entry) => {
result[entry.getSetMutationName()] = {
args: entry.createEntryArgs(),
type: entry.createEntryType("set_"),
resolve: (source, args, context, info) => {
if (!resolveWithData) {
return entry.id;
}
//console.log("resolve: ", resolveWithData, source, context, info, args);
// This context gets the data from the context put into the or Mutation...
//console.log("context: ", context);
const result = entry.setEntry(args, context, process.env.TABLE_NAME, complementedProps["isOffline"]);
//console.log("result: ", result);
return result;
}
};
result[entry.getDeleteMutationName()] = {
args: entry.createEntryArgs(),
type: entry.createEntryType("delete_"),
resolve: (source, args, context, info) => {
if (!resolveWithData) {
return entry.id;
}
//console.log("resolve: ", resolveWithData, source, context, info, args);
// This context gets the data from the context put into the or Mutation...
//console.log("context: ", context);
const result = entry.deleteEntry(args, context, process.env.TABLE_NAME, complementedProps["isOffline"]);
//console.log("result: ", result);
return result;
}
};
//console.log("mutation definition: ", result["set_"+entry.id]);
return result;
}, {}),
queries: (resolveWithData: boolean) => entries.reduce((result, entry) => {
const listType = entry.createEntryType("list_");
const getType = entry.createEntryType("get_");
//console.log("listType: ", listType);
//console.log("dl-comp-props: ", complementedProps["isOffline"], datalayerProps["isOffline"])
// list all the items, specifying the primaryKey
const inputArgs = {};
inputArgs[entry.primaryKey] = {name: entry.primaryKey, type: new GraphQLNonNull(GraphQLString)};
result[entry.getPrimaryListQueryName()] = {
args: inputArgs,
type: resolveWithData ? new GraphQLList(listType) : listType,
resolve: (source, args, context, info) => {
//console.log("resolve list: ", resolveWithData, source, args, context, complementedProps["isOffline"]);
if (!resolveWithData) {
return entry.id;
}
return entry.listEntries(args, context, process.env.TABLE_NAME, "pk", complementedProps["isOffline"]);
}
};
// list all the items, specifying the RANGE
const inputRangeArgs = {};
inputRangeArgs[entry.rangeKey] = {name: entry.rangeKey, type: new GraphQLNonNull(GraphQLString)};
result[entry.getRangeListQueryName()] = {
args: inputRangeArgs,
type: resolveWithData ? new GraphQLList(listType): listType,
resolve: (source, args, context, info) => {
//console.log("resolve: ", resolveWithData, source, args, context, complementedProps["isOffline"]);
if (!resolveWithData) {
return entry.id;
}
return entry.listEntries(args, context, process.env.TABLE_NAME, "sk", complementedProps["isOffline"]);
}
};
const inputArgsGet = {};
if (entry.primaryKey) {
inputArgsGet[entry.primaryKey] = {name: entry.primaryKey, type: new GraphQLNonNull(GraphQLString)};
}
if (entry.rangeKey) {
inputArgsGet[entry.rangeKey] = {name: entry.rangeKey, type: new GraphQLNonNull(GraphQLString)};
}
result[entry.getGetQueryName()] = {
args: inputArgsGet,
type: getType,
resolve: (source, args, context, info) => {
//console.log("resolve: ", resolveWithData, source, args, context);
if (!resolveWithData) {
return entry.id;
}
return entry.getEntry(args, context, process.env.TABLE_NAME, complementedProps["isOffline"]);
}
};
const scanRangeArgs = {};
scanRangeArgs[`start_${entry.rangeKey}`] = {name: `start_${entry.rangeKey}`, type: new GraphQLNonNull(GraphQLString)};
scanRangeArgs[`end_${entry.rangeKey}`] = {name: `end_${entry.rangeKey}`, type: new GraphQLNonNull(GraphQLString)};
// scan the table
result[entry.getRangeScanName()] = {
args: scanRangeArgs,
type: resolveWithData ? new GraphQLList(listType) : listType,
resolve: (source, args, context, info) => {
//console.log("resolve scan: ", resolveWithData, source, args, context);
if (!resolveWithData) {
return entry.id;
}
return entry.scan(args, context, process.env.TABLE_NAME, "sk", complementedProps["isOffline"]);
}
};
const scanPrimaryArgs = {};
scanPrimaryArgs[`start_${entry.primaryKey}`] = {name: `start_${entry.primaryKey}`, type: new GraphQLNonNull(GraphQLString)};
scanPrimaryArgs[`end_${entry.primaryKey}`] = {name: `end_${entry.primaryKey}`, type: new GraphQLNonNull(GraphQLString)};
// scan the table
result[entry.getPrimaryScanName()] = {
args: scanPrimaryArgs,
type: resolveWithData ? new GraphQLList(listType) : listType,
resolve: (source, args, context, info) => {
//console.log("resolve scan: ", resolveWithData, source, args, context);
if (!resolveWithData) {
return entry.id;
}
return entry.scan(args, context, process.env.TABLE_NAME, "pk", complementedProps["isOffline"]);
}
};
const scanAllArgs = {scanall: {name: "scanall", type: new GraphQLNonNull(GraphQLString)}};
// scan the table
result[entry.getScanName()] = {
args: scanAllArgs,
type: resolveWithData ? new GraphQLList(listType) : listType,
resolve: (source, args, context, info) => {
//console.log("resolve scan: ", resolveWithData, source, args, context);
if (!resolveWithData) {
return entry.id;
}
return entry.scan(args, context, process.env.TABLE_NAME, "pk", complementedProps["isOffline"]);
}
};
return result;
}, {}),
/*
getEntryDataFields: (entryId) => {
const entry = entries.find(entry => entry.id === entryId);
if (entry !== undefined) {
return entry.createEntryFields()
};
console.warn("could not find entry: ",entryId);
return {};
},*/
// TODO forward this request to the Entry and let the entry handle the whole request!
getEntryListQuery: (entryId, dictKey) => {
const entry = entries.find(entry => entry.id === entryId);
if (entry !== undefined) {
return entry.getEntryListQuery(dictKey)
};
console.warn("could not find entry: ",entryId);
return {};
/*
const fields = datalayerProps.getEntryDataFields(entryId);
//console.log("fields: ", fields);
return getEntryListQuery(
entryId,
dictKey,
fields
);*/
},
getEntryQuery: (entryId, dictKey) => {
const entry = entries.find(entry => entry.id === entryId);
if (entry !== undefined) {
return entry.getEntryQuery(dictKey)
};
console.warn("could not find entry: ",entryId);
return {};
},
getEntryScanQuery: (entryId, dictKey) => {
const entry = entries.find(entry => entry.id === entryId);
if (entry !== undefined) {
return entry.getEntryScanQuery(dictKey)
};
console.warn("could not find entry: ",entryId);
return {};
},
setEntryMutation: (entryId, values) => {
const entry = entries.find(entry => entry.id === entryId);
if (entry !== undefined) {
return entry.setEntryMutation(values)
};
console.warn("could not find entry: ", entryId);
return {};
},
deleteEntryMutation: (entryId, values) => {
const entry = entries.find(entry => entry.id === entryId);
if (entry !== undefined) {
return entry.deleteEntryMutation(values)
};
console.warn("could not find entry: ", entryId);
return {};
},
updateEntryQuery: (entryId, fDictKey: (oldData) => any) => {
return {
entryId: entryId,
getEntryQuery: () => datalayerProps.getEntryQuery(entryId, fDictKey({})),
setEntryMutation: (oldData) => datalayerProps.setEntryMutation(entryId, fDictKey(oldData)),
};
},
setClient: (client) => {
complementedProps["client"] = client;
},
setOffline: (offline: boolean) => {
complementedProps["isOffline"] = offline;
}
};
const schemaProps = {
getSchema: (resolveWithData: boolean) => new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType', // an arbitrary name
fields: datalayerProps.queries(resolveWithData)
}), mutation: new GraphQLObjectType({
name: 'RootMutationType', // an arbitrary name
fields: datalayerProps.mutations(resolveWithData)
})
})
}
// we need to provide the DataLayerId to webApps, these may be anywhere in the tree, not
// only direct children. So rather than mapping the children, we need to change them
findComponentRecursively(props.children, (child) => child.setDataLayerId !== undefined).forEach( child => {
child.setDataLayerId(props.id)
});
findComponentRecursively(props.children, (child) => child.setStoreData !== undefined).forEach( child => {
child.setStoreData(
async function (pkEntity, pkVal, skEntity, skVal, jsonData) {
return await setEntry(
process.env.TABLE_NAME, //"code-architect-dev-data-layer",
pkEntity, // schema.Entry.ENTITY, //pkEntity
pkVal, // pkId
skEntity, //schema.Data.ENTITY, // skEntity
skVal, // skId
jsonData, // jsonData
complementedProps["isOffline"]
)
}
);
child.setGetData(
async function (pkEntity, pkVal, skEntity, skVal) {
if (pkVal !== undefined) {
return await ddbGetEntry(
process.env.TABLE_NAME, //"code-architect-dev-data-layer",
pkEntity, // schema.Entry.ENTITY, //pkEntity
pkVal, // pkId
skEntity, //schema.Data.ENTITY, // skEntity
skVal, // skId
complementedProps["isOffline"]
)
} else {
return ddbListEntries(
process.env.TABLE_NAME, //tableName
"sk", //key
skEntity, // entity
skVal, //value,
pkEntity, // rangeEntity
complementedProps["isOffline"]
)
}
}
);
});
return Object.assign({}, props, componentProps, datalayerProps, schemaProps, complementedProps);
};