GraphQL - how to specify maximum String lengths in schema - graphql

I want to tell GraphQL that my 'serialNumber' field is a String with a length of between 1 and 20 characters. I know to use the ! in the schema to make a field Required, but can I tell GraphQL that there is also a maximum field length?
If I pass a null value in for a required String field, GraphQL will not accept it. I want it to behave the same way when it is passed a string that is longer than the maximum allowed length.
I have looked at doing this two ways: 1) Add a 'maxlength' attribute to the field definition in the schema.graphql file. or 2) Create a new type and assign it a maximum length.
I can't find any information on how to do it. Is is possible?

You can use directives:
directive #length(max: Int!) on FIELD_DEFINITION
input Payload {
name: String! #length(max: 50)
}

There is no place for this type of specification in graphql (types) definitions.
You can use directives but they are not supported (and not standarized) by all graphql servers - it's left to specific implementation details - check your server docs.
For single fields you can simply apply this logic inside mutation resolver - just throw an error if conditions (input string length between 1 and 20) not met.

This is possible to implement using GraphQL custom types.
You can define a type for each maximum length you want:
StringMaxLenTypes.js:
const logToConsoleToHelpMeUnderstand = false;
const { GraphQLScalarType, Kind } = require('graphql');
const parseStringMaxLenType = (value,maxLength) => {
logToConsoleToHelpMeUnderstand && console.log('Checking variable passed to a query is a string and max ' + maxLength + 'chars',value)
if (typeof value === 'string') {
if (value.length <= maxLength) {
return value;
} else {
throw new Error('parseCi' + maxLength + 'Type: String must not be more that ' + maxLength + ' characters. It is ' + value.length + ' characters');
}
} else {
throw new Error('parseCi' + maxLength + 'Type: value must be of type String. It is of type \'' + typeof value + '\'');
}
}
const parseStringMaxLen20Type = value => { return parseStringMaxLenType(value, 20) };
const parseStringMaxLen25Type = value => { return parseStringMaxLenType(value, 25) };
const parseStringMaxLen50Type = value => { return parseStringMaxLentype(value, 50) };
const parseStringMaxLen255Type = value => { return parseStringMaxLentype(value, 255) };
const parseStringMaxLen500Type = value => { return parseStringMaxLentype(value, 500) };
const serializeStringMaxLenType = (value, maxLength) => {
/** the serialize methods are called when data is going to be sent to the client. This can return anything
* of any type, as it will end up as JSON, but we check what is coming back from the database is a string
* and not too long.
*/
logToConsoleToHelpMeUnderstand && console.log(`Checking value '${value}' is at most ${maxLength} chars before serialising for output from graphQL to a client (the OCCP gateway is the client)`);
if (typeof value === 'string') {
if (value.length <= maxLength) {
return value;
} else {
throw new Error('serializeCi' + maxLength + 'Type: String must not be more that ' + maxLength + ' characters. It is ' + value.length + ' characters');
}
} else {
throw new Error('serializeCi' + maxLength + 'Type: value must be of type String. It is of type \'' + typeof value + '\'');
}
};
const serializeStringMaxLen20Type = (value) => { return serializeStringMaxLenType(value,20) };
const serializeStringMaxLen25Type = (value) => { return serializeStringMaxLenType(value,25) };
const serializeStringMaxLen50Type = (value) => { return serializeStringMaxLenType(value,50) };
const serializeStringMaxLen255Type = (value) => { return serializeStringMaxLenType(value,255) };
const serializeStringMaxLen500Type = (value) => { return serializeStringMaxLenType(value,500) };
const parseLiteralStringMaxLenType = (ast, maxLength) => {
logToConsoleToHelpMeUnderstand && console.log('checking Abstract Syntax Tree value (these come from parameters in GraphQL queries) is not more than ' + maxLength + ' chars long',ast);
// For input payload i.e. for mutation. ast stands for abstract syntax tree, which is the type of
if (ast.kind === Kind.STRING) {
// Note the parseStringMaxLenType function throws errors, or returns a valid value, so there is no need to throw errors here.
return parseStringMaxLenType(ast.value, maxLength)
} else {
throw new Error()
}
};
const parseLiteralStringMaxLen20Type = (ast) => { return parseLiteralStringMaxLenType(ast, 20) }
const parseLiteralStringMaxLen25Type = (ast) => { return parseLiteralStringMaxLenType(ast, 25) }
const parseLiteralStringMaxLen50Type = (ast) => { return parseLiteralStringMaxLenType(ast, 50) }
const parseLiteralStringMaxLen255Type = (ast) => { return parseLiteralStringMaxLenType(ast, 255) }
const parseLiteralStringMaxLen500Type = (ast) => { return parseLiteralStringMaxLenType(ast, 500) }
const StringMaxLen20Type = new GraphQLScalarType({
name: 'StringMaxLen20Type',
description: 'String up to 20 Chars',
serialize: serializeStringMaxLen20Type,
parseValue: parseStringMaxLen20Type,
parseLiteral: parseLiteralStringMaxLen20Type,
});
const StringMaxLen25Type = new GraphQLScalarType({
name: 'StringMaxLen25Type',
description: 'String up to 25 Chars',
serialize: serializeStringMaxLen25Type,
parseValue: parseStringMaxLen25Type,
parseLiteral: parseLiteralStringMaxLen25Type,
});
const StringMaxLen50Type = new GraphQLScalarType({
name: 'StringMaxLen50Type',
description: 'String up to 50 Chars',
serialize: serializeStringMaxLen50Type,
parseValue: parseStringMaxLen50Type,
parseLiteral: parseLiteralStringMaxLen50Type,
});
const StringMaxLen255Type = new GraphQLScalarType({
name: 'StringMaxLen255Type',
description: 'String up to 255 Chars',
serialize: serializeStringMaxLen255Type,
parseValue: parseStringMaxLen255Type,
parseLiteral: parseLiteralStringMaxLen255Type,
});
const StringMaxLen500Type = new GraphQLScalarType({
name: 'StringMaxLen500Type',
description: 'String up to 500 Chars',
serialize: serializeStringMaxLen500Type,
parseValue: parseStringMaxLen500Type,
parseLiteral: parseLiteralStringMaxLen500Type,
});
module.exports = {StringMaxLen20Type, StringMaxLen25Type, StringMaxLen50Type, StringMaxLen255Type, StringMaxLen500Type };
Then you need to define each of these types in your grapql.shema
schema.graphql:
scalar StringMaxLen20Type
scalar StringMaxLen25Type
scalar StringMaxLen50Type
scalar StringMaxLen255Type
scalar StringMaxLen500Type
type YourOtherTypes {
id: ID!
canUseAboveTypesLikeThis: StringMaxLen50Type
}
And import them into your resolvers.js, and define them in the resolver object passed to your graphQL server :
resolvers.js:
...
...
const {StringMaxLen20Type, StringMaxLen25Type, StringMaxLen50Type, StringMaxLen255Type, StringMaxLen500Type } = require('StringMaxLenTypes.js');```
...
module.exports = {
Query: {...},
Mutation: {...},
StringMaxLen20Type,
StringMaxLen25Type,
StringMaxLen50Type,
StringMaxLen255Type,
StringMaxLen500Type
This will then apply the same strict checking that GraphQL does all its data types to the maximum lengths.

Related

fetch list of data got incomplete result, how to remove the limitation

i try to get all the data which is belong to logged user but got incomplete result, it suppose to be returning 22 data but the code only returning 19 data
export const getHistoryList = async (filterPayload: Array<IFilterPayload>, dispatch: Dispatch<any>) => {
dispatch(setProductStart())
let payload: IHistoryList[] = [];
let filter: ModelImportHistoryFilterInput = {};
let orFilter: ModelImportHistoryFilterInput[] = [];
filterPayload.map((item) => {
if (item.type === "merchantID") {
filter = { ...filter, merchantImportHistoriesId: { eq: item.value } };
}
if (item.type === "action") {
orFilter.push({ action: { eq: ImportHistoryAction[item.value as keyof typeof ImportHistoryAction] } });
}
});
orFilter = orFilter.filter(item => item.action?.eq !== undefined);
if (orFilter.length > 0) {
filter = { ...filter, or: orFilter };
}
const result = await (API.graphql({
query: queriesCustom.listImportHistoriesCustom,
variables: { filter: filter }
}) as Promise<{ data?: any }>)
.then((response) => {
const data = response.data.listImportHistories.items;
return data
})
...
}
if i remove the filter from variables i got only 100 data from total 118 data
i already try this temporary solution to set the limit in variables to 2000, which is worked, but i don't think this is the correct approach
const result = await (API.graphql({
query: queriesCustom.listImportHistoriesCustom,
variables: { limit: 2000, filter: filter }
}) as Promise<{ data?: any }>)
.then((response) => {
const data = response.data.listImportHistories.items;
return data
})
how can i overcome this problem please help?

React Redux Toolkit TS - Can't access class methods from my state objects

I have set up my redux store and I am able to edit my state objects and get their values. But for some reason I can't call methods on the state objects. It's as if they are stored as javascript objects.
When I call getSequence() the first console.log correctly logs the sequence structure. But the second log call gives me an error
sequence.dump is not a function
Here is my store, including getSequence():
import {configureStore} from '#reduxjs/toolkit'
import sequenceReducer, {selectSequence} from '../feature/sequence-slice'
import midiMapsReducer from '../feature/midimaps-slice'
import {Sequence} from "../player/sequence";
export function getSequence() : Sequence {
const sequence: Sequence = selectSequence(store.getState())
console.log(`getCurrentSequence: ${JSON.stringify(sequence)}`)
console.log(`getCurrentSequence: ${sequence.dump()}`)
return sequence
}
const store = configureStore({
reducer: {
sequence: sequenceReducer,
midiMaps: midiMapsReducer,
}
})
export default store
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
I set up my slice state like this:
interface SequenceSliceState {
value: Sequence;
}
const initialState : SequenceSliceState = {
value: new Sequence({})
}
export const sequenceSlice = createSlice({
name: 'sequence',
// type is inferred. now the contained sequence is state.value
initialState,
reducers: {
and here is my Sequence module:
export class SequenceStep {
time: number = 0;
note: number = 64;
velocity: number = 100;
gateLength: number = 0.8;
}
export class MidiSettings {
midiInputDeviceId: string = "";
midiInputDeviceName: string = "";
midiInputChannelNum: number = -1;
midiOutputDeviceId: string = "";
midiOutputDeviceName: string = "";
midiOutputChannelNum: number = 0;
}
export class EnvelopePoint {
time: number = 0;
value: number = 0;
}
export class Envelope {
id: string = "";
controller: string = "";
points: Array<EnvelopePoint> = [];
locked: boolean = true;
mode: string = "loop";
trigger: string = "first";
type: string = "envelope";
// cacheMinValue: number = 0;
// cacheMaxValue: number = 127;
constructor(fake: any) {
this.id = fake.id;
this.controller = fake.controller;
this.points = fake.points;
this.locked = fake.locked;
this.mode = fake.mode;
this.trigger = fake.trigger;
this.type = fake.type;
}
dump() : string {
return "I am an envelope"
}
getValue(time: number) : number {
const numpoints = this.points.length
const length: number = this.points[numpoints - 1].time;
const loop: boolean = true;
const position: number = time % length;
var index = 0
while (index < numpoints && this.points[index].time < position) {
++index
}
if (index == 0) {
return this.points[0].value
} else if (index >= numpoints) {
return this.points[numpoints - 1].value
} else {
const p0: EnvelopePoint = this.points[index - 1];
const p1: EnvelopePoint = this.points[index];
if (p0.time == p1.time) {
return p0.value
} else {
return p0.value + (position - p0.time) / (p1.time - p0.time) * (p1.value - p0.value)
}
}
}
}
export class Sequence {
_id: string = "";
name: string = "";
text: string = "";
user_name: string = "";
user_id: string = "";
steps: Array<SequenceStep> = []
tempo: number = 120.0;
length: number = 8;
numSteps: number = 8;
division: number = 8;
midiSettings: MidiSettings = new MidiSettings();
currentEnvelopeId: string = "";
envelopes: Array<Envelope> = []
constructor(fakeSequence: any) {
this._id = fakeSequence._id;
this.name = fakeSequence.name;
this.text = fakeSequence.text;
this.user_id = fakeSequence.user_id;
this.steps = fakeSequence.steps;
this.tempo = fakeSequence.tempo;
this.length = fakeSequence.length;
this.numSteps = fakeSequence.numSteps;
this.division = fakeSequence.division;
this.midiSettings = fakeSequence.midiSettings;
this.currentEnvelopeId = fakeSequence.currentEnvelopeId;
this.envelopes = new Array<Envelope>();
if (fakeSequence.envelopes) {
for (const fakeEnvelope in fakeSequence.envelopes) {
this.envelopes.push(new Envelope(fakeEnvelope));
}
}
}
dump() : string {
return "I am a Sequence"
}
}
Here is the rest of my sequence slice:
import { createSlice } from '#reduxjs/toolkit'
import { v4 as uuidv4 } from "uuid";
import {Envelope, Sequence} from "../player/sequence"
import {RootState} from "../app/store";
interface SequenceSliceState {
value: Sequence;
}
const initialState : SequenceSliceState = {
value: new Sequence({})
}
export const sequenceSlice = createSlice({
name: 'sequence',
// type is inferred. now the contained sequence is state.value
initialState,
reducers: {
sequenceLoad: (state, payloadAction) => {
console.log(`🍕sequencerSlice.sequenceLoad ${JSON.stringify(payloadAction)}`)
state.value = JSON.parse(JSON.stringify(payloadAction.payload.sequence));
return state;
},
sequenceName: (state, payloadAction) => {
console.log(`🍕sequencerSlice.sequenceName ${JSON.stringify(payloadAction)}`)
state.value.name = payloadAction.payload;
return state
},
numSteps: (state, payloadAction) => {
var sequence: Sequence = state.value
sequence.numSteps = payloadAction.payload;
console.log(`🍕hi from numsteps ${sequence.numSteps} ${sequence.steps.length}`);
if (sequence.numSteps > sequence.steps.length) {
console.log(`🍕extend sequence`);
var newSteps: any = [];
for (var n = sequence.steps.length + 1; n <= sequence.numSteps; n++) {
newSteps = newSteps.concat({ note: 60, velocity: 100, gateLength: 0.9, });
console.log(`added step - now ${newSteps.length} steps`)
}
console.log(`🍕handleNumStepsChange: ${newSteps.length} steps -> ${newSteps}`)
const newStepsArray = sequence.steps.concat(newSteps);
sequence.steps = newStepsArray;
// console.log(`🍕handleNumStepsChange: ${stepsEdits.length} steps -> ${stepsEdits}`)
}
return state
},
midiSettings: (state, payloadAction) => {
console.log(`🍕sequence-slice - payloadAction ${JSON.stringify(payloadAction)}`)
console.log(`🍕sequence-slice - midiSettings ${JSON.stringify(payloadAction.payload)}`)
state.value.midiSettings = payloadAction.payload;
return state
},
division: (state, payloadAction) => {
state.value.division = payloadAction.payload;
return state
},
length: (state, payloadAction) => {
state.value.length = payloadAction.payload;
return state
},
sequenceText: (state, payloadAction) => {
state.value.text = payloadAction.payload;
return state
},
tempo: (state, payloadAction) => {
console.log(`🍕Edit tempo payloadaction ${JSON.stringify(payloadAction)}`)
state.value.tempo = payloadAction.payload
// return { ...state, tempo: parseInt(payloadAction.payload) }
// state.tempo = payloadAction.payload
return state
},
stepControllerValue: (state: any, payloadAction) => {
var sequence: Sequence = state.value
console.log(`🍕Edit stepControllerValue ${JSON.stringify(payloadAction)}`)
const stepNum: number = payloadAction.payload.stepNum
const controllerNum: number = payloadAction.payload.controllerNum
const controllerValue: number = payloadAction.payload.controllerNum
// sequence.steps[stepNum][controllerNum] = controllerValue;
// sequence.steps = steps
return state
},
stepNote: (state, action) => {
const stepnum = action.payload.stepNum
const notenum = action.payload.note
console.log(`🍕sequence-slice.stepNote ${JSON.stringify(action.payload)} ${stepnum} ${notenum}`)
var sequence: Sequence = state.value
// var steps: Array<SequenceStep> = [...sequence.steps];
sequence.steps[stepnum].note = notenum
// sequence.steps = steps;
return state
},
stepGateLength: (state, action) => {
const stepnum = action.payload.stepNum
const gateLength = action.payload.gateLength
// console.log(`🍕sequence-slice.stepNote ${JSON.stringify(action.payload)} ${stepnum} ${notenum}`)
// var steps = [...state.steps];
var sequence: Sequence = state.value
sequence.steps[stepnum].gateLength = gateLength
// state.steps = steps;
return state
},
stepVelocity: (state, action) => {
const stepnum = action.payload.stepNum
const velocity = action.payload.velocity
// console.log(`🍕sequence-slice.stepNote ${JSON.stringify(action.payload)} ${stepnum} ${notenum}`)
var sequence: Sequence = state.value
// var steps = [...state.steps];
sequence.steps[stepnum].velocity = velocity
// state.steps = steps;
return state
},
decrement: state => {
var sequence: Sequence = state.value
sequence.numSteps -= 1;
return state
},
// incrementByAmount: (state, action) => {
// var sequence: Sequence = state.value
// sequence.numSteps += action.payload;
// return state
// },
createEnvelope: (state, action) => {
console.log(`🍕sequenceSlice - createEnvelope: action should be controller ${JSON.stringify(action)}`)
const controller = action.payload.controller
console.log(`🍕sequenceSlice - createEnvelope: controller ${JSON.stringify(controller)}`)
var sequence: Sequence = state.value
var newEnvelopeId = uuidv4()
var newEnvelope = new Envelope({
id: newEnvelopeId,
controller: controller.name,
points: [{ time: 0, value: 0}, ],
locked: true,
mode: "loop",
trigger: "first",
type: "envelope"
})
if (sequence.envelopes == null) {
sequence.envelopes = new Array<Envelope>();
}
sequence.envelopes = [...sequence.envelopes, newEnvelope];
sequence.currentEnvelopeId = newEnvelopeId;
// console.log(`🍕state.envelopes <${state.envelopes}> (added ${newEnvelopeId}`);
return state
},
envelopeValue: (state, action) => {
console.log(`🍕sequenceSlice - envelopeValue: action ${JSON.stringify(action)}`)
const ccValue = action.payload.value
const controller = action.payload.controller
const envelopeId = action.payload.envelopeId
var sequence: Sequence = state.value
var envelope = sequence.envelopes.find((envelope: any) => envelope.id === envelopeId);
if (envelope) {
console.log(`🍕envelope ${envelopeId} ${JSON.stringify(envelope)}`)
const ccid = action.payload.ccid
// const currentValue = envelope.points[0].value
// const currentLsb = currentValue % ((controller.max + 1) / 128)
// const currentMsb = currentValue - currentLsb
// const value = action.payload.value * ((controller.max + 1) / 128)
envelope.points[0] = {time: 0, value: action.payload.value}
}
return state
},
currentEnvelopeId: (state, action) => {
console.log(`🍕sequence-slice: action ${JSON.stringify(action)}`)
var sequence: Sequence = state.value
console.log(`🍕sequence-slice: currentEnvelopeId - was ${sequence.currentEnvelopeId}`);
sequence.currentEnvelopeId = action.payload.envelopeId;
console.log(`🍕sequence-slice: currentEnvelopeId - now ${sequence.currentEnvelopeId}`);
return state
},
addEnvelopePoint(state, action) {
console.log(`addEnvelopePoint: action ${JSON.stringify(action)}`)
const envelopeId = action.payload.envelopeId
var sequence: Sequence = state.value
var envelope = sequence.envelopes.find((envelope: any) => envelope.id === envelopeId);
if (envelope) {
envelope.points.push({time: action.payload.time, value: action.payload.value})
envelope.points = envelope.points.sort((a,b) => { return a.time - b.time })
console.log(`addEnvelopePoint: found envelope. Points are now ${JSON.stringify(envelope.points)}`)
}
return state
},
deleteEnvelopePoint(state, action) {
console.log(`deleteEnvelopePoint: point ${JSON.stringify(action.payload)} ${action.payload.envelopeId}`)
const envelopeId = action.payload.envelopeId
var sequence: Sequence = state.value
var envelope = sequence.envelopes.find((envelope: any) => envelope.id === envelopeId);
if (envelope) {
console.log(`deleteEnvelopePoint: envelope ${JSON.stringify(envelope)} ${envelope.points.length}`)
for (var n = 0; n < envelope.points.length; n++) {
console.log(`envelope.points[n] ${JSON.stringify(envelope.points[n])} == action.payload.point ${JSON.stringify(action.payload.point)}`)
if (envelope.points[n].time == action.payload.point.time && envelope.points[n].value == action.payload.point.value) {
envelope.points.splice(n, 1)
console.log('deleteEnvelopePoint: found it')
console.log(`deleteEnvelopePoint: envelope ${JSON.stringify(envelope)} ${envelope.points.length}`)
break;
}
}
}
return state
},
moveEnvelopePoint(state, action) {
console.log(`moveEnvelopePoint: point ${JSON.stringify(action.payload)} ${action.payload.envelopeId}`)
console.log(`moveEnvelopePoint: point ${JSON.stringify(action)}`)
const envelopeId = action.payload.envelopeId
const pointNum : number = action.payload.pointNum
const time : number = action.payload.time
const value : number = action.payload.value
var sequence: Sequence = state.value
var envelope = sequence.envelopes.find((envelope: any) => envelope.id === envelopeId);
if (envelope) {
console.log(`moveEnvelopePoint: envelope ${JSON.stringify(envelope)} point ${pointNum} -> ${time},${value}`)
envelope.points[pointNum].time = time
envelope.points[pointNum].value = value
}
return state
}
}
})
export const {
sequenceLoad,
sequenceName,
numSteps,
midiSettings,
division,
sequenceText,
length,
tempo,
stepControllerValue,
stepNote,
stepGateLength,
stepVelocity,
decrement,
envelopeValue,
addEnvelopePoint,
deleteEnvelopePoint,
currentEnvelopeId,
moveEnvelopePoint,
} = sequenceSlice.actions
export const selectSequence = (state: RootState) => state.sequence.value
export default sequenceSlice.reducer
The moment you call JSON.parse(JSON.stringify(payloadAction.payload.sequence)), you create a normal JavaScript object that just has the properties of the class instance, but not the functionality.
Generally, you should not be storing things like class instances in a Redux store - classes cannot be serialized (as you just saw here), which causes problems with the devtools and libraries like redux-persist. Also, they tend to modify themselves, which collides with the core tenets of Redux.
Store pure data instead, use reducers to do modifications and selectors to derive further data from it.

Filter collection by multi-reference field

I'm trying to filter a collection by a multireference field before the function does its job.
I used this wix example but I don't want it to filter the whole collection https://www.wix.com/corvid/example/filter-with-multiple-options
I'm new at this and probably doing it wrong this is what i managed to figure out
import wixData from 'wix-data';
const collectionName = 'Blog/Posts'
//const collectionName = wixData.query('Blog/Posts').contains("categories", ["O -Fitness"]);
const fieldToFilterByInCollection = 'hashtags';
$w.onReady(function () {
setRepeatedItemsInRepeater()
loadDataToRepeater()
$w('#tags').onChange((event) => {
const selectedTags = $w('#tags').value
loadDataToRepeater(selectedTags)
})
});
function loadDataToRepeater(selectedCategories = []) {
let dataQuery = wixData.query(collectionName)//.contains("categories", ["O -Fitness"]);
if (selectedCategories.length > 0) {
dataQuery = dataQuery.hasAll(fieldToFilterByInCollection, selectedCategories)
}
dataQuery
.find()
.then(results => {
const itemsReadyForRepeater = results.items
$w('#Stories').data = itemsReadyForRepeater;
const isRepeaterEmpty = itemsReadyForRepeater.length === 0
if (isRepeaterEmpty) {
$w('#noResultsFound').show()
} else {
$w('#noResultsFound').hide()
}
})
}
function setRepeatedItemsInRepeater() {
$w('#Stories').onItemReady(($item, itemData) => {
$item('#image').src = itemData.coverImage;
$item('#title').text = itemData.title;
if ($item("#title").text.length > 40){
$item("#title").text =$item("#title").text.slice(0, 40) + '...' ;}
$item('#excerpt').text = itemData.excerpt;
if ($item('#excerpt').text.length > 100){
$item('#excerpt').text =$item('#excerpt').text.slice(0, 100) + '...' ;}
})
}
its this commented bit I'm trying to add
const collectionName = wixData.query('Blog/Posts').contains("categories", ["O -Fitness"]);
Thanks in advance
You used 'hasAll' to filter multi-refernce field. 'hasSome' working on multi-refernce but 'hasAll' isnt working on this field type.
you can use:
selectedCategories.map(category => {
dataQuery = dataQuery.hasSome(fieldToFilterByInCollection, category)
})
which is the same as hasAll - hasSome(x) & hasSome(Y) = hasAll(x,y) - but beacuse 'hasSome' is working on multirefernce it will work:)

How can I add / replace / remove a type from a GraphQLSchema?

I am working on a GraphQL schema validation tool. I would like to update in memory my GraphQLSchema object.
For instance to replace a type I tried to do:
const replaceType = (schema: GraphQLSchema, oldType: GraphQLNamedType, newType: GraphQLNamedType) => {
const config = schema.toConfig();
config.types = config.types.filter(t => t.name !== oldType.name);
config.types.push(newType);
return new GraphQLSchema(config);
}
This however fails here with
Schema must contain uniquely named types but contains multiple types named "MyType".
at typeMapReducer (../../node_modules/graphql/type/schema.js:262:13)
at Array.reduce (<anonymous>)
at new GraphQLSchema (../../node_modules/graphql/type/schema.js:145:28)
It looks like there are existing references to the old type that I am not updating,
If this is useful to someone else, the following update of type references seem to work
const replaceType = (schema: GraphQLSchema, oldType: GraphQLNamedType, newType: GraphQLNamedType) => {
const config = schema.toConfig();
config.types = config.types.filter(t => t.name !== oldType.name);
config.types.push(newType);
makeConfigConsistent(config);
return new GraphQLSchema(config);
}
/**
* As we add types that originally come from a different schema, we need to update all the references to maintain consistency
* within the set of types we are including.
*
* Types from the original schema need to update their references to point to the new types,
* and types from the new schema need to update their references to point to the original types that were not replaces.
*/
const makeConfigConsistent = (config: SchemaConfig) => {
const typeMap: { [typeName: string]: GraphQLNamedType } = {};
// Update references for root types
config.query = null;
config.mutation = null;
config.subscription = null;
config.types.forEach(type => {
typeMap[type.name] = type;
if (isObjectType(type)) {
if (type.name === 'Query') {
config.query = type;
} else if (type.name === 'Mutation') {
config.mutation = type;
} else if (type.name === 'Subscription') {
config.subscription = type;
}
}
});
// Update references to only point to the final set of types.
const finalTypes = config.types;
if (config.query) {
finalTypes.push(config.query);
}
if (config.mutation) {
finalTypes.push(config.mutation);
}
if (config.subscription) {
finalTypes.push(config.subscription);
}
const updatedType = (type: any): any | undefined => {
if (isNamedType(type)) {
if (type === typeMap[type.name]) {
return type;
}
}
if (isListType(type)) {
const subType = updatedType(type.ofType);
if (!subType) {
return undefined;
}
return new GraphQLList(subType);
}
if (isNonNullType(type)) {
const subType = updatedType(type.ofType);
if (!subType) {
return undefined;
}
return new GraphQLNonNull(subType);
}
if (isScalarType(type)) {
if (type === typeMap[type.name]) {
return type;
}
if (['Int', 'String', 'Float', 'Boolean', 'ID'].includes(type.name)) {
// This is a default scalar type (https://graphql.org/learn/schema/#scalar-types)
return type;
}
}
if (isNamedType(type)) {
const result = typeMap[type.name];
if (!result) {
return undefined;
}
return result;
}
throw new Error(`Unhandled cases for ${type}`);
};
finalTypes.forEach(type => {
if (isObjectType(type) || isInterfaceType(type)) {
const anyType = type as any;
anyType._fields = arraytoDict(
Object.values(type.getFields())
.filter(field => updatedType(field.type) !== undefined)
.map(field => {
field.type = updatedType(field.type);
field.args = field.args
.filter(arg => updatedType(arg.type) !== undefined)
.map(arg => {
arg.type = updatedType(arg.type);
return arg;
});
return field;
}),
field => field.name,
);
if (isObjectType(type)) {
anyType._interfaces = type.getInterfaces().map(int => updatedType(int));
}
} else if (isInputObjectType(type)) {
const anyType = type as any;
anyType._fields = arraytoDict(
Object.values(type.getFields())
.filter(field => updatedType(field.type) !== undefined)
.map(field => {
field.type = updatedType(field.type);
return field;
}),
field => field.name,
);
} else if (isUnionType(type)) {
const anyType = type as any;
anyType._types = type
.getTypes()
.map(t => updatedType(t))
.filter(t => t !== undefined);
}
});
};
function arraytoDict<T>(array: T[], getKey: (element: T) => string): { [key: string]: T } {
const result: { [key: string]: T } = {};
array.forEach(element => {
result[getKey(element)] = element;
});
return result;
};

Angularfire2 & Firestore – retrieve all subcollection content for a collection list

I try to retrieve datas in a subcollection based on the key received on the first call.
Basically, I want a list of all my user with the total of one subcollection for each of them.
I'm able to retrieve the data from the first Payload, but not from pointRef below
What is the correct way to achieve that?
getCurrentLeaderboard() {
return this.afs.collection('users').snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data()
const id = a.payload.doc.id;
const pointRef: Observable<any> = this.afs.collection('users').doc(`${id}`).collection('game').valueChanges()
const points = pointRef.map(arr => {
const sumPoint = arr.map(v => v.value)
return sumPoint.length ? sumPoint.reduce((total, val) => total + val) : ''
})
return { id, first_name: data.first_name, point:points };
})
})
}
I tried to put my code in a comment, but I think it's better formated as a answer.
First you need subscribe your pointRef and you can change your code like this.
getCurrentLeaderboard() {
return this.afs.collection('users').snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data()
const id = a.payload.doc.id;
const pointRef: Observable<any> = this.afs.object(`users/${id}/game`).valueChanges() // <--- Here
const pointsObserver = pointRef.subscribe(points => { //<--- And Here
return { id, first_name: data.first_name, point:points };
})
})
}
....
//Usage:
getCurrentLeaderboard.subscribe(points => this.points = points);
And if you going to use this function alot, you should start to denormalize your data.

Resources