graphile-many-to-many 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/PgManyToManyRelationEdgeColumnsPlugin.d.ts +3 -0
- package/PgManyToManyRelationEdgeColumnsPlugin.js +60 -0
- package/PgManyToManyRelationEdgeTablePlugin.d.ts +3 -0
- package/PgManyToManyRelationEdgeTablePlugin.js +107 -0
- package/PgManyToManyRelationInflectionPlugin.d.ts +3 -0
- package/PgManyToManyRelationInflectionPlugin.js +41 -0
- package/PgManyToManyRelationPlugin.d.ts +3 -0
- package/PgManyToManyRelationPlugin.js +114 -0
- package/README.md +236 -0
- package/createManyToManyConnectionType.d.ts +16 -0
- package/createManyToManyConnectionType.js +134 -0
- package/esm/PgManyToManyRelationEdgeColumnsPlugin.js +58 -0
- package/esm/PgManyToManyRelationEdgeTablePlugin.js +105 -0
- package/esm/PgManyToManyRelationInflectionPlugin.js +39 -0
- package/esm/PgManyToManyRelationPlugin.js +109 -0
- package/esm/createManyToManyConnectionType.js +132 -0
- package/esm/index.js +28 -0
- package/esm/manyToManyRelationships.js +84 -0
- package/esm/types.js +1 -0
- package/index.d.ts +6 -0
- package/index.js +34 -0
- package/manyToManyRelationships.d.ts +4 -0
- package/manyToManyRelationships.js +86 -0
- package/package.json +51 -0
- package/types.d.ts +23 -0
- package/types.js +2 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const arraysAreEqual = (array1, array2) => array1.length === array2.length && array1.every((el, i) => array2[i] === el);
|
|
2
|
+
const isUniqueConstraint = (constraint, attributes) => ['p', 'u'].includes(constraint.type) &&
|
|
3
|
+
arraysAreEqual(constraint.keyAttributeNums, attributes.map((attr) => attr.num));
|
|
4
|
+
// Given a `leftTable`, trace through the foreign key relations
|
|
5
|
+
// and identify a `junctionTable` and `rightTable`.
|
|
6
|
+
// Returns a list of data objects for these many-to-many relationships.
|
|
7
|
+
const manyToManyRelationships = (leftTable, build) => {
|
|
8
|
+
const { pgIntrospectionResultsByKind: introspectionResultsByKind, pgOmit: omit } = build;
|
|
9
|
+
return leftTable.foreignConstraints
|
|
10
|
+
.filter((con) => con.type === 'f')
|
|
11
|
+
.reduce((memoLeft, junctionLeftConstraint) => {
|
|
12
|
+
if (omit(junctionLeftConstraint, 'read') || omit(junctionLeftConstraint, 'manyToMany')) {
|
|
13
|
+
return memoLeft;
|
|
14
|
+
}
|
|
15
|
+
const junctionTable = introspectionResultsByKind.classById[junctionLeftConstraint.classId];
|
|
16
|
+
if (!junctionTable) {
|
|
17
|
+
throw new Error(`Could not find the table that referenced us (constraint: ${junctionLeftConstraint.name})`);
|
|
18
|
+
}
|
|
19
|
+
if (omit(junctionTable, 'read') || omit(junctionTable, 'manyToMany')) {
|
|
20
|
+
return memoLeft;
|
|
21
|
+
}
|
|
22
|
+
const memoRight = junctionTable.constraints
|
|
23
|
+
.filter((con) => con.id !== junctionLeftConstraint.id &&
|
|
24
|
+
con.type === 'f' &&
|
|
25
|
+
!omit(con, 'read') &&
|
|
26
|
+
!omit(con, 'manyToMany'))
|
|
27
|
+
.reduce((memoRightInner, junctionRightConstraint) => {
|
|
28
|
+
const rightTable = junctionRightConstraint.foreignClass;
|
|
29
|
+
if (omit(rightTable, 'read') || omit(rightTable, 'manyToMany')) {
|
|
30
|
+
return memoRightInner;
|
|
31
|
+
}
|
|
32
|
+
const leftKeyAttributes = junctionLeftConstraint.foreignKeyAttributes;
|
|
33
|
+
const junctionLeftKeyAttributes = junctionLeftConstraint.keyAttributes;
|
|
34
|
+
const junctionRightKeyAttributes = junctionRightConstraint.keyAttributes;
|
|
35
|
+
const rightKeyAttributes = junctionRightConstraint.foreignKeyAttributes;
|
|
36
|
+
// Ensure keys were found
|
|
37
|
+
if (!leftKeyAttributes.every(Boolean) ||
|
|
38
|
+
!junctionLeftKeyAttributes.every(Boolean) ||
|
|
39
|
+
!junctionRightKeyAttributes.every(Boolean) ||
|
|
40
|
+
!rightKeyAttributes.every(Boolean)) {
|
|
41
|
+
throw new Error('Could not find key columns!');
|
|
42
|
+
}
|
|
43
|
+
// Ensure keys can be read
|
|
44
|
+
if (leftKeyAttributes.some((attr) => omit(attr, 'read')) ||
|
|
45
|
+
junctionLeftKeyAttributes.some((attr) => omit(attr, 'read')) ||
|
|
46
|
+
junctionRightKeyAttributes.some((attr) => omit(attr, 'read')) ||
|
|
47
|
+
rightKeyAttributes.some((attr) => omit(attr, 'read'))) {
|
|
48
|
+
return memoRightInner;
|
|
49
|
+
}
|
|
50
|
+
// Ensure both constraints are single-column
|
|
51
|
+
// TODO: handle multi-column
|
|
52
|
+
if (leftKeyAttributes.length > 1 || rightKeyAttributes.length > 1) {
|
|
53
|
+
return memoRightInner;
|
|
54
|
+
}
|
|
55
|
+
// Ensure junction constraint keys are not unique (which would result in a one-to-one relation)
|
|
56
|
+
const junctionLeftConstraintIsUnique = !!junctionTable.constraints.find((c) => isUniqueConstraint(c, junctionLeftKeyAttributes));
|
|
57
|
+
const junctionRightConstraintIsUnique = !!junctionTable.constraints.find((c) => isUniqueConstraint(c, junctionRightKeyAttributes));
|
|
58
|
+
if (junctionLeftConstraintIsUnique || junctionRightConstraintIsUnique) {
|
|
59
|
+
return memoRightInner;
|
|
60
|
+
}
|
|
61
|
+
const allowsMultipleEdgesToNode = !junctionTable.constraints.find((c) => ['p', 'u'].includes(c.type) &&
|
|
62
|
+
arraysAreEqual(c.keyAttributeNums.concat().sort(), [
|
|
63
|
+
...junctionLeftKeyAttributes.map((obj) => obj.num),
|
|
64
|
+
...junctionRightKeyAttributes.map((obj) => obj.num)
|
|
65
|
+
].sort()));
|
|
66
|
+
return [
|
|
67
|
+
...memoRightInner,
|
|
68
|
+
{
|
|
69
|
+
leftKeyAttributes,
|
|
70
|
+
junctionLeftKeyAttributes,
|
|
71
|
+
junctionRightKeyAttributes,
|
|
72
|
+
rightKeyAttributes,
|
|
73
|
+
junctionTable,
|
|
74
|
+
rightTable,
|
|
75
|
+
junctionLeftConstraint,
|
|
76
|
+
junctionRightConstraint,
|
|
77
|
+
allowsMultipleEdgesToNode
|
|
78
|
+
}
|
|
79
|
+
];
|
|
80
|
+
}, []);
|
|
81
|
+
return [...memoLeft, ...memoRight];
|
|
82
|
+
}, []);
|
|
83
|
+
};
|
|
84
|
+
export default manyToManyRelationships;
|
package/esm/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PgManyToManyPlugin = void 0;
|
|
7
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
8
|
+
const PgManyToManyRelationEdgeColumnsPlugin_1 = __importDefault(require("./PgManyToManyRelationEdgeColumnsPlugin"));
|
|
9
|
+
const PgManyToManyRelationEdgeTablePlugin_1 = __importDefault(require("./PgManyToManyRelationEdgeTablePlugin"));
|
|
10
|
+
const PgManyToManyRelationInflectionPlugin_1 = __importDefault(require("./PgManyToManyRelationInflectionPlugin"));
|
|
11
|
+
const PgManyToManyRelationPlugin_1 = __importDefault(require("./PgManyToManyRelationPlugin"));
|
|
12
|
+
const PgManyToManyPlugin = (builder, options = {}) => {
|
|
13
|
+
builder.hook('build', (build) => {
|
|
14
|
+
// Check dependencies
|
|
15
|
+
if (!build.versions) {
|
|
16
|
+
throw new Error(`Plugin ${package_json_1.default.name}@${package_json_1.default.version} requires graphile-build@^4.1.0 in order to check dependencies (current version: ${build.graphileBuildVersion})`);
|
|
17
|
+
}
|
|
18
|
+
const depends = (name, range) => {
|
|
19
|
+
if (!build.hasVersion(name, range)) {
|
|
20
|
+
throw new Error(`Plugin ${package_json_1.default.name}@${package_json_1.default.version} requires ${name}@${range} (${build.versions[name] ? `current version: ${build.versions[name]}` : 'not found'})`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
depends('graphile-build-pg', '^4.5.0');
|
|
24
|
+
// Register this plugin
|
|
25
|
+
build.versions = build.extend(build.versions, { [package_json_1.default.name]: package_json_1.default.version });
|
|
26
|
+
return build;
|
|
27
|
+
});
|
|
28
|
+
(0, PgManyToManyRelationInflectionPlugin_1.default)(builder, options);
|
|
29
|
+
(0, PgManyToManyRelationPlugin_1.default)(builder, options);
|
|
30
|
+
(0, PgManyToManyRelationEdgeColumnsPlugin_1.default)(builder, options);
|
|
31
|
+
(0, PgManyToManyRelationEdgeTablePlugin_1.default)(builder, options);
|
|
32
|
+
};
|
|
33
|
+
exports.PgManyToManyPlugin = PgManyToManyPlugin;
|
|
34
|
+
exports.default = PgManyToManyPlugin;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const arraysAreEqual = (array1, array2) => array1.length === array2.length && array1.every((el, i) => array2[i] === el);
|
|
4
|
+
const isUniqueConstraint = (constraint, attributes) => ['p', 'u'].includes(constraint.type) &&
|
|
5
|
+
arraysAreEqual(constraint.keyAttributeNums, attributes.map((attr) => attr.num));
|
|
6
|
+
// Given a `leftTable`, trace through the foreign key relations
|
|
7
|
+
// and identify a `junctionTable` and `rightTable`.
|
|
8
|
+
// Returns a list of data objects for these many-to-many relationships.
|
|
9
|
+
const manyToManyRelationships = (leftTable, build) => {
|
|
10
|
+
const { pgIntrospectionResultsByKind: introspectionResultsByKind, pgOmit: omit } = build;
|
|
11
|
+
return leftTable.foreignConstraints
|
|
12
|
+
.filter((con) => con.type === 'f')
|
|
13
|
+
.reduce((memoLeft, junctionLeftConstraint) => {
|
|
14
|
+
if (omit(junctionLeftConstraint, 'read') || omit(junctionLeftConstraint, 'manyToMany')) {
|
|
15
|
+
return memoLeft;
|
|
16
|
+
}
|
|
17
|
+
const junctionTable = introspectionResultsByKind.classById[junctionLeftConstraint.classId];
|
|
18
|
+
if (!junctionTable) {
|
|
19
|
+
throw new Error(`Could not find the table that referenced us (constraint: ${junctionLeftConstraint.name})`);
|
|
20
|
+
}
|
|
21
|
+
if (omit(junctionTable, 'read') || omit(junctionTable, 'manyToMany')) {
|
|
22
|
+
return memoLeft;
|
|
23
|
+
}
|
|
24
|
+
const memoRight = junctionTable.constraints
|
|
25
|
+
.filter((con) => con.id !== junctionLeftConstraint.id &&
|
|
26
|
+
con.type === 'f' &&
|
|
27
|
+
!omit(con, 'read') &&
|
|
28
|
+
!omit(con, 'manyToMany'))
|
|
29
|
+
.reduce((memoRightInner, junctionRightConstraint) => {
|
|
30
|
+
const rightTable = junctionRightConstraint.foreignClass;
|
|
31
|
+
if (omit(rightTable, 'read') || omit(rightTable, 'manyToMany')) {
|
|
32
|
+
return memoRightInner;
|
|
33
|
+
}
|
|
34
|
+
const leftKeyAttributes = junctionLeftConstraint.foreignKeyAttributes;
|
|
35
|
+
const junctionLeftKeyAttributes = junctionLeftConstraint.keyAttributes;
|
|
36
|
+
const junctionRightKeyAttributes = junctionRightConstraint.keyAttributes;
|
|
37
|
+
const rightKeyAttributes = junctionRightConstraint.foreignKeyAttributes;
|
|
38
|
+
// Ensure keys were found
|
|
39
|
+
if (!leftKeyAttributes.every(Boolean) ||
|
|
40
|
+
!junctionLeftKeyAttributes.every(Boolean) ||
|
|
41
|
+
!junctionRightKeyAttributes.every(Boolean) ||
|
|
42
|
+
!rightKeyAttributes.every(Boolean)) {
|
|
43
|
+
throw new Error('Could not find key columns!');
|
|
44
|
+
}
|
|
45
|
+
// Ensure keys can be read
|
|
46
|
+
if (leftKeyAttributes.some((attr) => omit(attr, 'read')) ||
|
|
47
|
+
junctionLeftKeyAttributes.some((attr) => omit(attr, 'read')) ||
|
|
48
|
+
junctionRightKeyAttributes.some((attr) => omit(attr, 'read')) ||
|
|
49
|
+
rightKeyAttributes.some((attr) => omit(attr, 'read'))) {
|
|
50
|
+
return memoRightInner;
|
|
51
|
+
}
|
|
52
|
+
// Ensure both constraints are single-column
|
|
53
|
+
// TODO: handle multi-column
|
|
54
|
+
if (leftKeyAttributes.length > 1 || rightKeyAttributes.length > 1) {
|
|
55
|
+
return memoRightInner;
|
|
56
|
+
}
|
|
57
|
+
// Ensure junction constraint keys are not unique (which would result in a one-to-one relation)
|
|
58
|
+
const junctionLeftConstraintIsUnique = !!junctionTable.constraints.find((c) => isUniqueConstraint(c, junctionLeftKeyAttributes));
|
|
59
|
+
const junctionRightConstraintIsUnique = !!junctionTable.constraints.find((c) => isUniqueConstraint(c, junctionRightKeyAttributes));
|
|
60
|
+
if (junctionLeftConstraintIsUnique || junctionRightConstraintIsUnique) {
|
|
61
|
+
return memoRightInner;
|
|
62
|
+
}
|
|
63
|
+
const allowsMultipleEdgesToNode = !junctionTable.constraints.find((c) => ['p', 'u'].includes(c.type) &&
|
|
64
|
+
arraysAreEqual(c.keyAttributeNums.concat().sort(), [
|
|
65
|
+
...junctionLeftKeyAttributes.map((obj) => obj.num),
|
|
66
|
+
...junctionRightKeyAttributes.map((obj) => obj.num)
|
|
67
|
+
].sort()));
|
|
68
|
+
return [
|
|
69
|
+
...memoRightInner,
|
|
70
|
+
{
|
|
71
|
+
leftKeyAttributes,
|
|
72
|
+
junctionLeftKeyAttributes,
|
|
73
|
+
junctionRightKeyAttributes,
|
|
74
|
+
rightKeyAttributes,
|
|
75
|
+
junctionTable,
|
|
76
|
+
rightTable,
|
|
77
|
+
junctionLeftConstraint,
|
|
78
|
+
junctionRightConstraint,
|
|
79
|
+
allowsMultipleEdgesToNode
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
}, []);
|
|
83
|
+
return [...memoLeft, ...memoRight];
|
|
84
|
+
}, []);
|
|
85
|
+
};
|
|
86
|
+
exports.default = manyToManyRelationships;
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "graphile-many-to-many",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Add connection fields for many-to-many relations",
|
|
5
|
+
"author": "Matt Bretl",
|
|
6
|
+
"homepage": "https://github.com/constructive-io/constructive",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"module": "esm/index.js",
|
|
10
|
+
"types": "index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"clean": "makage clean",
|
|
13
|
+
"copy": "makage assets",
|
|
14
|
+
"prepack": "pnpm run build",
|
|
15
|
+
"build": "makage build",
|
|
16
|
+
"build:dev": "makage build --dev",
|
|
17
|
+
"lint": "eslint . --fix",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"directory": "dist"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/constructive-io/constructive"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/constructive-io/constructive/issues"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"postgraphile",
|
|
34
|
+
"graphile",
|
|
35
|
+
"postgres",
|
|
36
|
+
"graphql",
|
|
37
|
+
"plugin"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"graphile-build": "^4.14.1",
|
|
41
|
+
"graphile-build-pg": "^4.14.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"graphile-test": "^2.9.2",
|
|
45
|
+
"graphql": "15.10.1",
|
|
46
|
+
"makage": "^0.1.8",
|
|
47
|
+
"pgsql-test": "^2.15.2",
|
|
48
|
+
"postgraphile": "^4.14.1"
|
|
49
|
+
},
|
|
50
|
+
"gitHead": "8efb023ff0cb814f9097e0b08fcbafeac25eee07"
|
|
51
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Build } from 'graphile-build';
|
|
2
|
+
import type { PgAttribute, PgClass, PgConstraint, PgIntrospectionResultsByKind } from 'graphile-build-pg';
|
|
3
|
+
export type SimpleCollectionSetting = 'only' | 'both' | 'omit' | null | undefined;
|
|
4
|
+
export interface PgManyToManyOptions {
|
|
5
|
+
pgSimpleCollections?: SimpleCollectionSetting;
|
|
6
|
+
pgForbidSetofFunctionsToReturnNull?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ManyToManyRelationship {
|
|
9
|
+
leftKeyAttributes: PgAttribute[];
|
|
10
|
+
junctionLeftKeyAttributes: PgAttribute[];
|
|
11
|
+
junctionRightKeyAttributes: PgAttribute[];
|
|
12
|
+
rightKeyAttributes: PgAttribute[];
|
|
13
|
+
junctionTable: PgClass;
|
|
14
|
+
rightTable: PgClass;
|
|
15
|
+
junctionLeftConstraint: PgConstraint;
|
|
16
|
+
junctionRightConstraint: PgConstraint;
|
|
17
|
+
allowsMultipleEdgesToNode: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface PgManyToManyBuild extends Build {
|
|
20
|
+
pgIntrospectionResultsByKind: PgIntrospectionResultsByKind;
|
|
21
|
+
pgOmit: (entity: any, permission: string) => boolean;
|
|
22
|
+
describePgEntity: (entity: any) => string;
|
|
23
|
+
}
|
package/types.js
ADDED