Odoo GraphQL Subscription using Node, Express JS for Sample
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

601 lines
31 KiB

import { __assign, __rest } from "tslib";
import { invariant, newInvariantError } from "../../utilities/globals/index.js";
import { storeKeyNameFromField, argumentsObjectFromField, isReference, getStoreKeyName, isNonNullObject, stringifyForDisplay, } from "../../utilities/index.js";
import { hasOwn, fieldNameFromStoreName, storeValueIsStoreObject, selectionSetMatchesResult, TypeOrFieldNameRegExp, defaultDataIdFromObject, isArray, } from "./helpers.js";
import { cacheSlot } from "./reactiveVars.js";
import { keyArgsFnFromSpecifier, keyFieldsFnFromSpecifier, } from "./key-extractor.js";
function argsFromFieldSpecifier(spec) {
return (spec.args !== void 0 ? spec.args
: spec.field ? argumentsObjectFromField(spec.field, spec.variables)
: null);
}
var nullKeyFieldsFn = function () { return void 0; };
var simpleKeyArgsFn = function (_args, context) { return context.fieldName; };
// These merge functions can be selected by specifying merge:true or
// merge:false in a field policy.
var mergeTrueFn = function (existing, incoming, _a) {
var mergeObjects = _a.mergeObjects;
return mergeObjects(existing, incoming);
};
var mergeFalseFn = function (_, incoming) { return incoming; };
var Policies = /** @class */ (function () {
function Policies(config) {
this.config = config;
this.typePolicies = Object.create(null);
this.toBeAdded = Object.create(null);
// Map from subtype names to sets of supertype names. Note that this
// representation inverts the structure of possibleTypes (whose keys are
// supertypes and whose values are arrays of subtypes) because it tends
// to be much more efficient to search upwards than downwards.
this.supertypeMap = new Map();
// Any fuzzy subtypes specified by possibleTypes will be converted to
// RegExp objects and recorded here. Every key of this map can also be
// found in supertypeMap. In many cases this Map will be empty, which
// means no fuzzy subtype checking will happen in fragmentMatches.
this.fuzzySubtypes = new Map();
this.rootIdsByTypename = Object.create(null);
this.rootTypenamesById = Object.create(null);
this.usingPossibleTypes = false;
this.config = __assign({ dataIdFromObject: defaultDataIdFromObject }, config);
this.cache = this.config.cache;
this.setRootTypename("Query");
this.setRootTypename("Mutation");
this.setRootTypename("Subscription");
if (config.possibleTypes) {
this.addPossibleTypes(config.possibleTypes);
}
if (config.typePolicies) {
this.addTypePolicies(config.typePolicies);
}
}
Policies.prototype.identify = function (object, partialContext) {
var _a;
var policies = this;
var typename = (partialContext &&
(partialContext.typename || ((_a = partialContext.storeObject) === null || _a === void 0 ? void 0 : _a.__typename))) ||
object.__typename;
// It should be possible to write root Query fields with writeFragment,
// using { __typename: "Query", ... } as the data, but it does not make
// sense to allow the same identification behavior for the Mutation and
// Subscription types, since application code should never be writing
// directly to (or reading directly from) those root objects.
if (typename === this.rootTypenamesById.ROOT_QUERY) {
return ["ROOT_QUERY"];
}
// Default context.storeObject to object if not otherwise provided.
var storeObject = (partialContext && partialContext.storeObject) || object;
var context = __assign(__assign({}, partialContext), { typename: typename, storeObject: storeObject, readField: (partialContext && partialContext.readField) ||
function () {
var options = normalizeReadFieldOptions(arguments, storeObject);
return policies.readField(options, {
store: policies.cache["data"],
variables: options.variables,
});
} });
var id;
var policy = typename && this.getTypePolicy(typename);
var keyFn = (policy && policy.keyFn) || this.config.dataIdFromObject;
while (keyFn) {
var specifierOrId = keyFn(__assign(__assign({}, object), storeObject), context);
if (isArray(specifierOrId)) {
keyFn = keyFieldsFnFromSpecifier(specifierOrId);
}
else {
id = specifierOrId;
break;
}
}
id = id ? String(id) : void 0;
return context.keyObject ? [id, context.keyObject] : [id];
};
Policies.prototype.addTypePolicies = function (typePolicies) {
var _this = this;
Object.keys(typePolicies).forEach(function (typename) {
var _a = typePolicies[typename], queryType = _a.queryType, mutationType = _a.mutationType, subscriptionType = _a.subscriptionType, incoming = __rest(_a, ["queryType", "mutationType", "subscriptionType"]);
// Though {query,mutation,subscription}Type configurations are rare,
// it's important to call setRootTypename as early as possible,
// since these configurations should apply consistently for the
// entire lifetime of the cache. Also, since only one __typename can
// qualify as one of these root types, these three properties cannot
// be inherited, unlike the rest of the incoming properties. That
// restriction is convenient, because the purpose of this.toBeAdded
// is to delay the processing of type/field policies until the first
// time they're used, allowing policies to be added in any order as
// long as all relevant policies (including policies for supertypes)
// have been added by the time a given policy is used for the first
// time. In other words, since inheritance doesn't matter for these
// properties, there's also no need to delay their processing using
// the this.toBeAdded queue.
if (queryType)
_this.setRootTypename("Query", typename);
if (mutationType)
_this.setRootTypename("Mutation", typename);
if (subscriptionType)
_this.setRootTypename("Subscription", typename);
if (hasOwn.call(_this.toBeAdded, typename)) {
_this.toBeAdded[typename].push(incoming);
}
else {
_this.toBeAdded[typename] = [incoming];
}
});
};
Policies.prototype.updateTypePolicy = function (typename, incoming) {
var _this = this;
var existing = this.getTypePolicy(typename);
var keyFields = incoming.keyFields, fields = incoming.fields;
function setMerge(existing, merge) {
existing.merge =
typeof merge === "function" ? merge
// Pass merge:true as a shorthand for a merge implementation
// that returns options.mergeObjects(existing, incoming).
: merge === true ? mergeTrueFn
// Pass merge:false to make incoming always replace existing
// without any warnings about data clobbering.
: merge === false ? mergeFalseFn
: existing.merge;
}
// Type policies can define merge functions, as an alternative to
// using field policies to merge child objects.
setMerge(existing, incoming.merge);
existing.keyFn =
// Pass false to disable normalization for this typename.
keyFields === false ? nullKeyFieldsFn
// Pass an array of strings to use those fields to compute a
// composite ID for objects of this typename.
: isArray(keyFields) ? keyFieldsFnFromSpecifier(keyFields)
// Pass a function to take full control over identification.
: typeof keyFields === "function" ? keyFields
// Leave existing.keyFn unchanged if above cases fail.
: existing.keyFn;
if (fields) {
Object.keys(fields).forEach(function (fieldName) {
var existing = _this.getFieldPolicy(typename, fieldName, true);
var incoming = fields[fieldName];
if (typeof incoming === "function") {
existing.read = incoming;
}
else {
var keyArgs = incoming.keyArgs, read = incoming.read, merge = incoming.merge;
existing.keyFn =
// Pass false to disable argument-based differentiation of
// field identities.
keyArgs === false ? simpleKeyArgsFn
// Pass an array of strings to use named arguments to
// compute a composite identity for the field.
: isArray(keyArgs) ? keyArgsFnFromSpecifier(keyArgs)
// Pass a function to take full control over field identity.
: typeof keyArgs === "function" ? keyArgs
// Leave existing.keyFn unchanged if above cases fail.
: existing.keyFn;
if (typeof read === "function") {
existing.read = read;
}
setMerge(existing, merge);
}
if (existing.read && existing.merge) {
// If we have both a read and a merge function, assume
// keyArgs:false, because read and merge together can take
// responsibility for interpreting arguments in and out. This
// default assumption can always be overridden by specifying
// keyArgs explicitly in the FieldPolicy.
existing.keyFn = existing.keyFn || simpleKeyArgsFn;
}
});
}
};
Policies.prototype.setRootTypename = function (which, typename) {
if (typename === void 0) { typename = which; }
var rootId = "ROOT_" + which.toUpperCase();
var old = this.rootTypenamesById[rootId];
if (typename !== old) {
invariant(!old || old === which, 5, which);
// First, delete any old __typename associated with this rootId from
// rootIdsByTypename.
if (old)
delete this.rootIdsByTypename[old];
// Now make this the only __typename that maps to this rootId.
this.rootIdsByTypename[typename] = rootId;
// Finally, update the __typename associated with this rootId.
this.rootTypenamesById[rootId] = typename;
}
};
Policies.prototype.addPossibleTypes = function (possibleTypes) {
var _this = this;
this.usingPossibleTypes = true;
Object.keys(possibleTypes).forEach(function (supertype) {
// Make sure all types have an entry in this.supertypeMap, even if
// their supertype set is empty, so we can return false immediately
// from policies.fragmentMatches for unknown supertypes.
_this.getSupertypeSet(supertype, true);
possibleTypes[supertype].forEach(function (subtype) {
_this.getSupertypeSet(subtype, true).add(supertype);
var match = subtype.match(TypeOrFieldNameRegExp);
if (!match || match[0] !== subtype) {
// TODO Don't interpret just any invalid typename as a RegExp.
_this.fuzzySubtypes.set(subtype, new RegExp(subtype));
}
});
});
};
Policies.prototype.getTypePolicy = function (typename) {
var _this = this;
if (!hasOwn.call(this.typePolicies, typename)) {
var policy_1 = (this.typePolicies[typename] = Object.create(null));
policy_1.fields = Object.create(null);
// When the TypePolicy for typename is first accessed, instead of
// starting with an empty policy object, inherit any properties or
// fields from the type policies of the supertypes of typename.
//
// Any properties or fields defined explicitly within the TypePolicy
// for typename will take precedence, and if there are multiple
// supertypes, the properties of policies whose types were added
// later via addPossibleTypes will take precedence over those of
// earlier supertypes. TODO Perhaps we should warn about these
// conflicts in development, and recommend defining the property
// explicitly in the subtype policy?
//
// Field policy inheritance is atomic/shallow: you can't inherit a
// field policy and then override just its read function, since read
// and merge functions often need to cooperate, so changing only one
// of them would be a recipe for inconsistency.
//
// Once the TypePolicy for typename has been accessed, its properties can
// still be updated directly using addTypePolicies, but future changes to
// inherited supertype policies will not be reflected in this subtype
// policy, because this code runs at most once per typename.
var supertypes_1 = this.supertypeMap.get(typename);
if (!supertypes_1 && this.fuzzySubtypes.size) {
// To make the inheritance logic work for unknown typename strings that
// may have fuzzy supertypes, we give this typename an empty supertype
// set and then populate it with any fuzzy supertypes that match.
supertypes_1 = this.getSupertypeSet(typename, true);
// This only works for typenames that are directly matched by a fuzzy
// supertype. What if there is an intermediate chain of supertypes?
// While possible, that situation can only be solved effectively by
// specifying the intermediate relationships via possibleTypes, manually
// and in a non-fuzzy way.
this.fuzzySubtypes.forEach(function (regExp, fuzzy) {
if (regExp.test(typename)) {
// The fuzzy parameter is just the original string version of regExp
// (not a valid __typename string), but we can look up the
// associated supertype(s) in this.supertypeMap.
var fuzzySupertypes = _this.supertypeMap.get(fuzzy);
if (fuzzySupertypes) {
fuzzySupertypes.forEach(function (supertype) {
return supertypes_1.add(supertype);
});
}
}
});
}
if (supertypes_1 && supertypes_1.size) {
supertypes_1.forEach(function (supertype) {
var _a = _this.getTypePolicy(supertype), fields = _a.fields, rest = __rest(_a, ["fields"]);
Object.assign(policy_1, rest);
Object.assign(policy_1.fields, fields);
});
}
}
var inbox = this.toBeAdded[typename];
if (inbox && inbox.length) {
// Merge the pending policies into this.typePolicies, in the order they
// were originally passed to addTypePolicy.
inbox.splice(0).forEach(function (policy) {
_this.updateTypePolicy(typename, policy);
});
}
return this.typePolicies[typename];
};
Policies.prototype.getFieldPolicy = function (typename, fieldName, createIfMissing) {
if (typename) {
var fieldPolicies = this.getTypePolicy(typename).fields;
return (fieldPolicies[fieldName] ||
(createIfMissing && (fieldPolicies[fieldName] = Object.create(null))));
}
};
Policies.prototype.getSupertypeSet = function (subtype, createIfMissing) {
var supertypeSet = this.supertypeMap.get(subtype);
if (!supertypeSet && createIfMissing) {
this.supertypeMap.set(subtype, (supertypeSet = new Set()));
}
return supertypeSet;
};
Policies.prototype.fragmentMatches = function (fragment, typename, result, variables) {
var _this = this;
if (!fragment.typeCondition)
return true;
// If the fragment has a type condition but the object we're matching
// against does not have a __typename, the fragment cannot match.
if (!typename)
return false;
var supertype = fragment.typeCondition.name.value;
// Common case: fragment type condition and __typename are the same.
if (typename === supertype)
return true;
if (this.usingPossibleTypes && this.supertypeMap.has(supertype)) {
var typenameSupertypeSet = this.getSupertypeSet(typename, true);
var workQueue_1 = [typenameSupertypeSet];
var maybeEnqueue_1 = function (subtype) {
var supertypeSet = _this.getSupertypeSet(subtype, false);
if (supertypeSet &&
supertypeSet.size &&
workQueue_1.indexOf(supertypeSet) < 0) {
workQueue_1.push(supertypeSet);
}
};
// We need to check fuzzy subtypes only if we encountered fuzzy
// subtype strings in addPossibleTypes, and only while writing to
// the cache, since that's when selectionSetMatchesResult gives a
// strong signal of fragment matching. The StoreReader class calls
// policies.fragmentMatches without passing a result object, so
// needToCheckFuzzySubtypes is always false while reading.
var needToCheckFuzzySubtypes = !!(result && this.fuzzySubtypes.size);
var checkingFuzzySubtypes = false;
// It's important to keep evaluating workQueue.length each time through
// the loop, because the queue can grow while we're iterating over it.
for (var i = 0; i < workQueue_1.length; ++i) {
var supertypeSet = workQueue_1[i];
if (supertypeSet.has(supertype)) {
if (!typenameSupertypeSet.has(supertype)) {
if (checkingFuzzySubtypes) {
globalThis.__DEV__ !== false && invariant.warn(6, typename, supertype);
}
// Record positive results for faster future lookup.
// Unfortunately, we cannot safely cache negative results,
// because new possibleTypes data could always be added to the
// Policies class.
typenameSupertypeSet.add(supertype);
}
return true;
}
supertypeSet.forEach(maybeEnqueue_1);
if (needToCheckFuzzySubtypes &&
// Start checking fuzzy subtypes only after exhausting all
// non-fuzzy subtypes (after the final iteration of the loop).
i === workQueue_1.length - 1 &&
// We could wait to compare fragment.selectionSet to result
// after we verify the supertype, but this check is often less
// expensive than that search, and we will have to do the
// comparison anyway whenever we find a potential match.
selectionSetMatchesResult(fragment.selectionSet, result, variables)) {
// We don't always need to check fuzzy subtypes (if no result
// was provided, or !this.fuzzySubtypes.size), but, when we do,
// we only want to check them once.
needToCheckFuzzySubtypes = false;
checkingFuzzySubtypes = true;
// If we find any fuzzy subtypes that match typename, extend the
// workQueue to search through the supertypes of those fuzzy
// subtypes. Otherwise the for-loop will terminate and we'll
// return false below.
this.fuzzySubtypes.forEach(function (regExp, fuzzyString) {
var match = typename.match(regExp);
if (match && match[0] === typename) {
maybeEnqueue_1(fuzzyString);
}
});
}
}
}
return false;
};
Policies.prototype.hasKeyArgs = function (typename, fieldName) {
var policy = this.getFieldPolicy(typename, fieldName, false);
return !!(policy && policy.keyFn);
};
Policies.prototype.getStoreFieldName = function (fieldSpec) {
var typename = fieldSpec.typename, fieldName = fieldSpec.fieldName;
var policy = this.getFieldPolicy(typename, fieldName, false);
var storeFieldName;
var keyFn = policy && policy.keyFn;
if (keyFn && typename) {
var context = {
typename: typename,
fieldName: fieldName,
field: fieldSpec.field || null,
variables: fieldSpec.variables,
};
var args = argsFromFieldSpecifier(fieldSpec);
while (keyFn) {
var specifierOrString = keyFn(args, context);
if (isArray(specifierOrString)) {
keyFn = keyArgsFnFromSpecifier(specifierOrString);
}
else {
// If the custom keyFn returns a falsy value, fall back to
// fieldName instead.
storeFieldName = specifierOrString || fieldName;
break;
}
}
}
if (storeFieldName === void 0) {
storeFieldName =
fieldSpec.field ?
storeKeyNameFromField(fieldSpec.field, fieldSpec.variables)
: getStoreKeyName(fieldName, argsFromFieldSpecifier(fieldSpec));
}
// Returning false from a keyArgs function is like configuring
// keyArgs: false, but more dynamic.
if (storeFieldName === false) {
return fieldName;
}
// Make sure custom field names start with the actual field.name.value
// of the field, so we can always figure out which properties of a
// StoreObject correspond to which original field names.
return fieldName === fieldNameFromStoreName(storeFieldName) ? storeFieldName
: fieldName + ":" + storeFieldName;
};
Policies.prototype.readField = function (options, context) {
var objectOrReference = options.from;
if (!objectOrReference)
return;
var nameOrField = options.field || options.fieldName;
if (!nameOrField)
return;
if (options.typename === void 0) {
var typename = context.store.getFieldValue(objectOrReference, "__typename");
if (typename)
options.typename = typename;
}
var storeFieldName = this.getStoreFieldName(options);
var fieldName = fieldNameFromStoreName(storeFieldName);
var existing = context.store.getFieldValue(objectOrReference, storeFieldName);
var policy = this.getFieldPolicy(options.typename, fieldName, false);
var read = policy && policy.read;
if (read) {
var readOptions = makeFieldFunctionOptions(this, objectOrReference, options, context, context.store.getStorage(isReference(objectOrReference) ?
objectOrReference.__ref
: objectOrReference, storeFieldName));
// Call read(existing, readOptions) with cacheSlot holding this.cache.
return cacheSlot.withValue(this.cache, read, [
existing,
readOptions,
]);
}
return existing;
};
Policies.prototype.getReadFunction = function (typename, fieldName) {
var policy = this.getFieldPolicy(typename, fieldName, false);
return policy && policy.read;
};
Policies.prototype.getMergeFunction = function (parentTypename, fieldName, childTypename) {
var policy = this.getFieldPolicy(parentTypename, fieldName, false);
var merge = policy && policy.merge;
if (!merge && childTypename) {
policy = this.getTypePolicy(childTypename);
merge = policy && policy.merge;
}
return merge;
};
Policies.prototype.runMergeFunction = function (existing, incoming, _a, context, storage) {
var field = _a.field, typename = _a.typename, merge = _a.merge;
if (merge === mergeTrueFn) {
// Instead of going to the trouble of creating a full
// FieldFunctionOptions object and calling mergeTrueFn, we can
// simply call mergeObjects, as mergeTrueFn would.
return makeMergeObjectsFunction(context.store)(existing, incoming);
}
if (merge === mergeFalseFn) {
// Likewise for mergeFalseFn, whose implementation is even simpler.
return incoming;
}
// If cache.writeQuery or cache.writeFragment was called with
// options.overwrite set to true, we still call merge functions, but
// the existing data is always undefined, so the merge function will
// not attempt to combine the incoming data with the existing data.
if (context.overwrite) {
existing = void 0;
}
return merge(existing, incoming, makeFieldFunctionOptions(this,
// Unlike options.readField for read functions, we do not fall
// back to the current object if no foreignObjOrRef is provided,
// because it's not clear what the current object should be for
// merge functions: the (possibly undefined) existing object, or
// the incoming object? If you think your merge function needs
// to read sibling fields in order to produce a new value for
// the current field, you might want to rethink your strategy,
// because that's a recipe for making merge behavior sensitive
// to the order in which fields are written into the cache.
// However, readField(name, ref) is useful for merge functions
// that need to deduplicate child objects and references.
void 0, {
typename: typename,
fieldName: field.name.value,
field: field,
variables: context.variables,
}, context, storage || Object.create(null)));
};
return Policies;
}());
export { Policies };
function makeFieldFunctionOptions(policies, objectOrReference, fieldSpec, context, storage) {
var storeFieldName = policies.getStoreFieldName(fieldSpec);
var fieldName = fieldNameFromStoreName(storeFieldName);
var variables = fieldSpec.variables || context.variables;
var _a = context.store, toReference = _a.toReference, canRead = _a.canRead;
return {
args: argsFromFieldSpecifier(fieldSpec),
field: fieldSpec.field || null,
fieldName: fieldName,
storeFieldName: storeFieldName,
variables: variables,
isReference: isReference,
toReference: toReference,
storage: storage,
cache: policies.cache,
canRead: canRead,
readField: function () {
return policies.readField(normalizeReadFieldOptions(arguments, objectOrReference, variables), context);
},
mergeObjects: makeMergeObjectsFunction(context.store),
};
}
export function normalizeReadFieldOptions(readFieldArgs, objectOrReference, variables) {
var fieldNameOrOptions = readFieldArgs[0], from = readFieldArgs[1], argc = readFieldArgs.length;
var options;
if (typeof fieldNameOrOptions === "string") {
options = {
fieldName: fieldNameOrOptions,
// Default to objectOrReference only when no second argument was
// passed for the from parameter, not when undefined is explicitly
// passed as the second argument.
from: argc > 1 ? from : objectOrReference,
};
}
else {
options = __assign({}, fieldNameOrOptions);
// Default to objectOrReference only when fieldNameOrOptions.from is
// actually omitted, rather than just undefined.
if (!hasOwn.call(options, "from")) {
options.from = objectOrReference;
}
}
if (globalThis.__DEV__ !== false && options.from === void 0) {
globalThis.__DEV__ !== false && invariant.warn(7, stringifyForDisplay(Array.from(readFieldArgs)));
}
if (void 0 === options.variables) {
options.variables = variables;
}
return options;
}
function makeMergeObjectsFunction(store) {
return function mergeObjects(existing, incoming) {
if (isArray(existing) || isArray(incoming)) {
throw newInvariantError(8);
}
// These dynamic checks are necessary because the parameters of a
// custom merge function can easily have the any type, so the type
// system cannot always enforce the StoreObject | Reference parameter
// types of options.mergeObjects.
if (isNonNullObject(existing) && isNonNullObject(incoming)) {
var eType = store.getFieldValue(existing, "__typename");
var iType = store.getFieldValue(incoming, "__typename");
var typesDiffer = eType && iType && eType !== iType;
if (typesDiffer) {
return incoming;
}
if (isReference(existing) && storeValueIsStoreObject(incoming)) {
// Update the normalized EntityStore for the entity identified by
// existing.__ref, preferring/overwriting any fields contributed by the
// newer incoming StoreObject.
store.merge(existing.__ref, incoming);
return existing;
}
if (storeValueIsStoreObject(existing) && isReference(incoming)) {
// Update the normalized EntityStore for the entity identified by
// incoming.__ref, taking fields from the older existing object only if
// those fields are not already present in the newer StoreObject
// identified by incoming.__ref.
store.merge(existing, incoming.__ref);
return incoming;
}
if (storeValueIsStoreObject(existing) &&
storeValueIsStoreObject(incoming)) {
return __assign(__assign({}, existing), incoming);
}
}
return incoming;
};
}
//# sourceMappingURL=policies.js.map