postgraphile
Version:
Automatic, high performance, and highly customizable GraphQL API for PostgreSQL
256 lines • 9.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PgV4SmartTagsPlugin = void 0;
require("graphile-config");
const graphile_build_1 = require("graphile-build");
const util_1 = require("util");
const EMPTY_OBJECT = Object.freeze({});
exports.PgV4SmartTagsPlugin = {
name: "PgV4SmartTagsPlugin",
description: "For compatibility with PostGraphile v4 schemas, this plugin attempts to convert various V4 smart tags (`@omit`, etc) and convert them to V5 behaviors",
version: "0.0.0",
before: ["PgFakeConstraintsPlugin", "PgEnumTablesPlugin"],
provides: ["smart-tags"],
gather: (0, graphile_build_1.gatherConfig)({
namespace: "pgV4SmartTags",
initialCache() {
return EMPTY_OBJECT;
},
initialState() {
return EMPTY_OBJECT;
},
helpers: {},
hooks: {
// Run in the 'introspection' phase before anything uses the tags
pgIntrospection_introspection(info, event) {
const { introspection } = event;
// Note the code here relies on the fact that `getTagsAndDescription`
// memoizes because it mutates the return result; if this changes then
// the code will no longer achieve its goal.
for (const pgClass of introspection.classes) {
processTags(pgClass.getTags());
}
for (const pgAttr of introspection.attributes) {
processTags(pgAttr.getTags());
}
for (const pgConstraint of introspection.constraints) {
processTags(pgConstraint.getTags());
// In V4, if a attribute has `@omit read` then any constraint that uses that attribute also has `@omit read`
if (pgConstraint.contype === "f") {
for (const attr of [
...pgConstraint.getAttributes(),
...pgConstraint.getForeignAttributes(),
]) {
const attrOmit = attr.getTags().omit;
if (!attrOmit)
continue;
const arr = Array.isArray(attrOmit) ? attrOmit : [attrOmit];
const omitRead = arr.some((omit) => omit === true || expandOmit(omit).includes("read"));
if (omitRead) {
addBehaviorToTags(pgConstraint.getTags(), "-select", true);
}
}
}
}
for (const pgProc of introspection.procs) {
processTags(pgProc.getTags());
}
for (const pgType of introspection.types) {
processTags(pgType.getTags());
}
},
pgFakeConstraints_constraint(info, event) {
const { entity } = event;
processTags(entity.getTags());
},
},
}),
};
exports.default = exports.PgV4SmartTagsPlugin;
function processTags(tags) {
processUniqueKey(tags);
processOmit(tags);
convertBoolean(tags, "sortable", "orderBy order resource:connection:backwards");
convertBoolean(tags, "filterable", "filter filterBy");
// convertBoolean(tags, "enum", "enum");
processSimpleCollections(tags);
}
function processSimpleCollections(tags) {
if (tags?.simpleCollections) {
switch (tags.simpleCollections) {
case "omit": {
addBehaviorToTags(tags, "-list +connection", true);
break;
}
case "both": {
addBehaviorToTags(tags, "+list +connection", true);
break;
}
case "only": {
addBehaviorToTags(tags, "+list -connection", true);
break;
}
default: {
console.warn(`Did not understand @simpleCollections argument '${tags.simpleCollections}'`);
}
}
}
}
function convertBoolean(tags, key, behavior) {
if (tags && tags[key]) {
addBehaviorToTags(tags, behavior, true);
}
}
function processUniqueKey(tags) {
if (tags && typeof tags.uniqueKey === "string") {
const newUnique = `${tags.uniqueKey}|@behavior -single -update -delete`;
if (Array.isArray(tags.unique)) {
tags.unique.push(newUnique);
}
else if (typeof tags.unique === "string") {
tags.unique = [tags.unique, newUnique];
}
else {
tags.unique = newUnique;
}
}
}
function expandOmit(omit) {
if (omit[0] === ":") {
// Convert ':' string into longhand
const letters = omit.slice(1).split("");
return letters.map((l) => {
switch (l) {
case "C":
return "create";
case "R":
return "read";
case "U":
return "update";
case "D":
return "delete";
case "X":
return "execute";
case "F":
return "filter";
case "O":
return "order";
case "A":
return "all";
case "M":
return "many";
default:
console.warn(`Abbreviation '${l}' in '@omit' string '${omit}' not recognized.`);
return l;
}
});
}
const parts = omit.split(",");
return parts;
}
function processOmit(tags) {
const omit = tags?.omit;
if (!omit) {
return;
}
const behavior = [];
const processOmit = (rawOmit) => {
const omit = rawOmit === true || rawOmit === "*"
? "create,read,update,delete,execute,filter,order,all,many,manyToMany"
: rawOmit;
if (typeof omit !== "string") {
throw new Error(`Issue in smart tags; expected omit to be true/string/string[], but found something unexpected: ${(0, util_1.inspect)(omit)}`);
}
const parts = expandOmit(omit);
for (const part of parts) {
switch (part) {
case "create": {
behavior.push("-insert");
break;
}
case "read": {
behavior.push("-select -node -connection -list -array -single");
break;
}
case "update": {
behavior.push("-update");
break;
}
case "delete": {
behavior.push("-delete");
break;
}
case "execute": {
behavior.push("-queryField -mutationField -typeField");
break;
}
case "filter": {
// ENHANCE: we should figure out which of these to use depending on the circumstance
behavior.push("-filter -filterBy");
break;
}
case "order": {
// ENHANCE: we should figure out which of these to use depending on the circumstance
behavior.push("-order -orderBy");
break;
}
case "all": {
behavior.push("-query:resource:list -query:resource:connection");
break;
}
case "many": {
behavior.push("-singularRelation:resource:list -singularRelation:resource:connection -manyRelation:resource:list -manyRelation:resource:connection");
break;
}
case "manyToMany": {
behavior.push("-manyToMany");
break;
}
case "": {
// ignore
break;
}
default: {
// ENHANCE: we should give plugin authors the option of adding other
// omits here, e.g. `@omit manyToMany`
console.warn(`Option '${part}' in '@omit' string '${omit}' not recognized; assuming -${part} behavior`);
behavior.push(`-${part}`);
break;
}
}
}
};
if (Array.isArray(omit)) {
omit.forEach(processOmit);
}
else {
processOmit(omit);
}
if (behavior.length > 0) {
addBehaviorToTags(tags, behavior.join(" "), true);
}
}
function addBehaviorToTags(tags, behavior, prepend = false) {
if (behavior === "")
return;
if (Array.isArray(tags.behavior)) {
if (prepend) {
tags.behavior = [behavior, ...tags.behavior];
}
else {
tags.behavior = [...tags.behavior, behavior];
}
}
else if (typeof tags.behavior === "string") {
tags.behavior = prepend
? [behavior, tags.behavior]
: [tags.behavior, behavior];
}
else if (!tags.behavior) {
tags.behavior = [behavior];
}
else {
throw new Error(`Did not understand tags.behavior - it wasn't an array or a string`);
}
}
//# sourceMappingURL=PgV4SmartTagsPlugin.js.map