introspectron 0.2.4 → 2.0.2
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 +1 -1
- package/README.md +12 -0
- package/esm/gql.js +297 -0
- package/esm/index.js +6 -0
- package/esm/introspect.js +85 -0
- package/{module → esm}/introspectGql.js +2 -2
- package/esm/process.js +278 -0
- package/{module → esm}/query.js +16 -12
- package/esm/utils.js +42 -0
- package/gql.d.ts +4 -0
- package/gql.js +301 -0
- package/{module/index.js → index.d.ts} +1 -1
- package/index.js +22 -0
- package/introspect.d.ts +16 -0
- package/introspect.js +89 -0
- package/introspectGql.d.ts +1 -0
- package/introspectGql.js +105 -0
- package/package.json +19 -51
- package/process.d.ts +1 -0
- package/process.js +282 -0
- package/query.d.ts +1 -0
- package/query.js +425 -0
- package/utils.d.ts +2 -0
- package/utils.js +47 -0
- package/CHANGELOG.md +0 -99
- package/main/gql.js +0 -376
- package/main/index.js +0 -70
- package/main/introspect.js +0 -194
- package/main/introspectGql.js +0 -25
- package/main/process.js +0 -410
- package/main/query.js +0 -26
- package/main/utils.js +0 -64
- package/module/gql.js +0 -330
- package/module/introspect.js +0 -75
- package/module/process.js +0 -321
- package/module/utils.js +0 -46
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2024 Dan Lynch <pyramation@gmail.com>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# introspectron
|
|
2
2
|
|
|
3
|
+
<p align="center" width="100%">
|
|
4
|
+
<img height="250" src="https://github.com/user-attachments/assets/d0456af5-b6e9-422e-a45d-2574d5be490f" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center" width="100%">
|
|
8
|
+
<a href="https://github.com/launchql/launchql-2.0/actions/workflows/run-tests.yaml">
|
|
9
|
+
<img height="20" src="https://github.com/launchql/launchql-2.0/actions/workflows/run-tests.yaml/badge.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/launchql/launchql-2.0/blob/main/LICENSE-MIT"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/introspectron"><img height="20" src="https://img.shields.io/github/package-json/v/launchql/launchql-2.0?filename=packages%2Fintrospectron%2Fpackage.json"/></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
3
15
|
```sh
|
|
4
16
|
npm install introspectron
|
|
5
17
|
```
|
package/esm/gql.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
export const parseGraphQuery = (introQuery) => {
|
|
3
|
+
const types = introQuery.__schema.types;
|
|
4
|
+
const HASH = types.reduce((m, v) => {
|
|
5
|
+
m[v.name] = v;
|
|
6
|
+
return m;
|
|
7
|
+
}, {});
|
|
8
|
+
const queriesRoot = types.find((t) => t.name === 'Query');
|
|
9
|
+
const mutationsRoot = types.find((t) => t.name === 'Mutation');
|
|
10
|
+
const getInputForQueries = (input, context = {}) => {
|
|
11
|
+
if (input.kind === 'NON_NULL') {
|
|
12
|
+
context.isNotNull = true;
|
|
13
|
+
return getInputForQueries(input.ofType, context);
|
|
14
|
+
}
|
|
15
|
+
if (input.kind === 'LIST') {
|
|
16
|
+
context.isArray = true;
|
|
17
|
+
if (context.isNotNull) {
|
|
18
|
+
context.isArrayNotNull = true;
|
|
19
|
+
delete context.isNotNull;
|
|
20
|
+
}
|
|
21
|
+
return getInputForQueries(input.ofType, context);
|
|
22
|
+
}
|
|
23
|
+
if (input.kind === 'INPUT_OBJECT') {
|
|
24
|
+
if (input.name && HASH.hasOwnProperty(input.name)) {
|
|
25
|
+
const schema = HASH[input.name];
|
|
26
|
+
context.properties = schema.inputFields
|
|
27
|
+
.map((field) => {
|
|
28
|
+
return {
|
|
29
|
+
name: field.name,
|
|
30
|
+
type: field.type
|
|
31
|
+
};
|
|
32
|
+
})
|
|
33
|
+
.reduce((m3, v) => {
|
|
34
|
+
m3[v.name] = v;
|
|
35
|
+
return m3;
|
|
36
|
+
}, {});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (input.kind === 'OBJECT') {
|
|
40
|
+
if (input.name && HASH.hasOwnProperty(input.name)) {
|
|
41
|
+
const schema = HASH[input.name];
|
|
42
|
+
context.properties = schema.fields
|
|
43
|
+
.map((field) => {
|
|
44
|
+
return {
|
|
45
|
+
name: field.name,
|
|
46
|
+
type: field.type
|
|
47
|
+
};
|
|
48
|
+
})
|
|
49
|
+
.reduce((m3, v) => {
|
|
50
|
+
m3[v.name] = v;
|
|
51
|
+
return m3;
|
|
52
|
+
}, {});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
context.type = input.name;
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
};
|
|
60
|
+
const getInputForMutations = (input, context = {}) => {
|
|
61
|
+
if (input.kind === 'NON_NULL') {
|
|
62
|
+
context.isNotNull = true;
|
|
63
|
+
return getInputForMutations(input.ofType, context);
|
|
64
|
+
}
|
|
65
|
+
if (input.kind === 'LIST') {
|
|
66
|
+
context.isArray = true;
|
|
67
|
+
if (context.isNotNull) {
|
|
68
|
+
context.isArrayNotNull = true;
|
|
69
|
+
delete context.isNotNull;
|
|
70
|
+
}
|
|
71
|
+
return getInputForMutations(input.ofType, context);
|
|
72
|
+
}
|
|
73
|
+
if (input.kind === 'INPUT_OBJECT') {
|
|
74
|
+
if (input.name && HASH.hasOwnProperty(input.name)) {
|
|
75
|
+
const schema = HASH[input.name];
|
|
76
|
+
context.properties = schema.inputFields
|
|
77
|
+
.map((field) => {
|
|
78
|
+
return getInputForMutations(field.type, { name: field.name });
|
|
79
|
+
})
|
|
80
|
+
.reduce((m3, v) => {
|
|
81
|
+
m3[v.name] = v;
|
|
82
|
+
return m3;
|
|
83
|
+
}, {});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (input.kind === 'OBJECT') {
|
|
87
|
+
if (input.name && HASH.hasOwnProperty(input.name)) {
|
|
88
|
+
const schema = HASH[input.name];
|
|
89
|
+
context.properties = schema.fields
|
|
90
|
+
.map((field) => {
|
|
91
|
+
return {
|
|
92
|
+
name: field.name,
|
|
93
|
+
type: field.type
|
|
94
|
+
};
|
|
95
|
+
})
|
|
96
|
+
.reduce((m3, v) => {
|
|
97
|
+
m3[v.name] = v;
|
|
98
|
+
return m3;
|
|
99
|
+
}, {});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
context.type = input.name;
|
|
104
|
+
}
|
|
105
|
+
return context;
|
|
106
|
+
};
|
|
107
|
+
const mutations = mutationsRoot.fields.reduce((m, mutation) => {
|
|
108
|
+
let mutationType = 'other';
|
|
109
|
+
if (/^Create/.test(mutation.type.name)) {
|
|
110
|
+
mutationType = 'create';
|
|
111
|
+
}
|
|
112
|
+
else if (/^Update/.test(mutation.type.name)) {
|
|
113
|
+
mutationType = 'patch';
|
|
114
|
+
}
|
|
115
|
+
else if (/^Delete/.test(mutation.type.name)) {
|
|
116
|
+
mutationType = 'delete';
|
|
117
|
+
}
|
|
118
|
+
const props = mutation.args.reduce((m2, arg) => {
|
|
119
|
+
const type = arg.type?.ofType?.name;
|
|
120
|
+
const isNotNull = arg.type?.kind === 'NON_NULL';
|
|
121
|
+
if (type && HASH.hasOwnProperty(type)) {
|
|
122
|
+
const schema = HASH[type];
|
|
123
|
+
const fields = schema.inputFields.filter((a) => a.name !== 'clientMutationId');
|
|
124
|
+
const properties = fields
|
|
125
|
+
.map((a) => getInputForMutations(a.type, { name: a.name }))
|
|
126
|
+
.reduce((m3, v) => {
|
|
127
|
+
m3[v.name] = v;
|
|
128
|
+
return m3;
|
|
129
|
+
}, {});
|
|
130
|
+
m2[arg.name] = {
|
|
131
|
+
isNotNull,
|
|
132
|
+
type,
|
|
133
|
+
properties
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.warn('whats wrong with ' + arg);
|
|
138
|
+
}
|
|
139
|
+
return m2;
|
|
140
|
+
}, {});
|
|
141
|
+
const getModelTypes = (type) => {
|
|
142
|
+
return type.fields
|
|
143
|
+
.filter((t) => t.type.kind === 'OBJECT')
|
|
144
|
+
.filter((t) => t.type.name !== 'Query')
|
|
145
|
+
.map((f) => ({ name: f.name, type: f.type }));
|
|
146
|
+
};
|
|
147
|
+
const models = getModelTypes(HASH[mutation.type.name]);
|
|
148
|
+
if (models.length > 0) {
|
|
149
|
+
// TODO this is probably brittle
|
|
150
|
+
const model = models[0].type.name;
|
|
151
|
+
m[mutation.name] = {
|
|
152
|
+
qtype: 'mutation',
|
|
153
|
+
mutationType,
|
|
154
|
+
model,
|
|
155
|
+
properties: props,
|
|
156
|
+
output: mutation.type
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// no return args, probably void functions
|
|
161
|
+
let t;
|
|
162
|
+
let outputFields = [];
|
|
163
|
+
if (mutation.type.kind === 'OBJECT') {
|
|
164
|
+
t = HASH[mutation.type.name];
|
|
165
|
+
outputFields = t.fields
|
|
166
|
+
.map((f) => ({ name: f.name, type: f.type }))
|
|
167
|
+
.filter((f) => f.name !== 'clientMutationId')
|
|
168
|
+
.filter((f) => f.type.name !== 'Query');
|
|
169
|
+
}
|
|
170
|
+
m[mutation.name] = {
|
|
171
|
+
qtype: 'mutation',
|
|
172
|
+
mutationType,
|
|
173
|
+
properties: props,
|
|
174
|
+
output: mutation.type,
|
|
175
|
+
outputs: outputFields
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return m;
|
|
179
|
+
}, {});
|
|
180
|
+
// expect(mts).toMatchSnapshot();
|
|
181
|
+
const parseConnectionQuery = (query, nesting) => {
|
|
182
|
+
const objectType = getObjectType(query.type);
|
|
183
|
+
const Connection = HASH[objectType];
|
|
184
|
+
const nodes = Connection.fields.find((f) => f.name === 'nodes');
|
|
185
|
+
const edges = Connection.fields.find((f) => f.name === 'edges');
|
|
186
|
+
const model = getObjectType(nodes.type);
|
|
187
|
+
const context = { HASH, parseConnectionQuery, parseSingleQuery };
|
|
188
|
+
if (nesting === 0) {
|
|
189
|
+
return {
|
|
190
|
+
qtype: 'getMany',
|
|
191
|
+
model,
|
|
192
|
+
selection: parseSelectionScalar(context, model)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
qtype: 'getMany',
|
|
197
|
+
model,
|
|
198
|
+
selection: parseSelectionObject(context, model, 1)
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
const parseSingleQuery = (query, nesting) => {
|
|
202
|
+
const model = getObjectType(query.type);
|
|
203
|
+
const context = { HASH, parseConnectionQuery, parseSingleQuery };
|
|
204
|
+
if (nesting === 0) {
|
|
205
|
+
return {
|
|
206
|
+
qtype: 'getOne',
|
|
207
|
+
model,
|
|
208
|
+
properties: query.args.reduce((m2, v) => {
|
|
209
|
+
m2[v.name] = getInputForQueries(v.type);
|
|
210
|
+
return m2;
|
|
211
|
+
}, {}),
|
|
212
|
+
selection: parseSelectionScalar(context, model)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
model,
|
|
217
|
+
qtype: 'getOne',
|
|
218
|
+
properties: query.args.reduce((m2, v) => {
|
|
219
|
+
m2[v.name] = getInputForQueries(v.type);
|
|
220
|
+
return m2;
|
|
221
|
+
}, {}),
|
|
222
|
+
selection: parseSelectionObject(context, model, 1)
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
const queries = queriesRoot.fields.reduce((m, query) => {
|
|
226
|
+
// m[query.name] = getInputForQueries(query.type);
|
|
227
|
+
if (query.type.kind === 'OBJECT') {
|
|
228
|
+
if (isConnectionQuery(query)) {
|
|
229
|
+
m[query.name] = parseConnectionQuery(query, 1);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
m[query.name] = parseSingleQuery(query, 1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return m;
|
|
236
|
+
}, {});
|
|
237
|
+
return {
|
|
238
|
+
queries,
|
|
239
|
+
mutations
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
// Parse selections for both scalar and object fields
|
|
243
|
+
function parseSelectionObject(context, model, nesting) {
|
|
244
|
+
const { HASH, parseConnectionQuery, parseSingleQuery } = context;
|
|
245
|
+
throwIfInvalidContext(context);
|
|
246
|
+
const selectionFields = HASH[model].fields.filter((f) => !isPureObjectType(f.type));
|
|
247
|
+
const selection = selectionFields.map((f) => {
|
|
248
|
+
if (f.type.ofType?.kind === 'OBJECT') {
|
|
249
|
+
if (isConnectionQuery(f)) {
|
|
250
|
+
return { name: f.name, ...parseConnectionQuery(f, nesting - 1) };
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
return { name: f.name, ...parseSingleQuery(f, nesting - 1) };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return f.name;
|
|
257
|
+
});
|
|
258
|
+
return selection;
|
|
259
|
+
}
|
|
260
|
+
// Parse selections for scalar types only, ignore all field selections
|
|
261
|
+
// that have more nesting selection level
|
|
262
|
+
function parseSelectionScalar(context, model) {
|
|
263
|
+
const { HASH } = context;
|
|
264
|
+
throwIfInvalidContext(context);
|
|
265
|
+
const selectionFields = HASH[model].fields.filter((f) => !isPureObjectType(f.type) && !isConnectionQuery(f));
|
|
266
|
+
const selection = selectionFields.map((f) => f.name);
|
|
267
|
+
return selection;
|
|
268
|
+
}
|
|
269
|
+
function isConnectionQuery(query) {
|
|
270
|
+
const objectType = getObjectType(query.type);
|
|
271
|
+
const fields = query.args.map((a) => a.name);
|
|
272
|
+
return (/Connection$/.test(objectType) &&
|
|
273
|
+
fields.includes('condition') &&
|
|
274
|
+
fields.includes('filter'));
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Check is a type is pure object type
|
|
278
|
+
* pure object type is different from custom types in the sense that
|
|
279
|
+
* it does not inherit from any type, custom types inherit from a parent type
|
|
280
|
+
* @param {Object} typeObj
|
|
281
|
+
* @returns {boolean}
|
|
282
|
+
*/
|
|
283
|
+
function isPureObjectType(typeObj) {
|
|
284
|
+
return typeObj.kind === 'OBJECT' && typeObj.name == null;
|
|
285
|
+
}
|
|
286
|
+
function getObjectType(type) {
|
|
287
|
+
if (type.kind === 'OBJECT')
|
|
288
|
+
return type.name;
|
|
289
|
+
if (type.ofType)
|
|
290
|
+
return getObjectType(type.ofType);
|
|
291
|
+
}
|
|
292
|
+
function throwIfInvalidContext(context) {
|
|
293
|
+
const { HASH, parseConnectionQuery, parseSingleQuery } = context;
|
|
294
|
+
if (!HASH || !parseConnectionQuery || !parseSingleQuery) {
|
|
295
|
+
throw new Error('parseSelection: context missing');
|
|
296
|
+
}
|
|
297
|
+
}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { makeIntrospectionQuery } from './query';
|
|
3
|
+
import { parseTags } from './utils';
|
|
4
|
+
export const introspect = async (pgClient, { schemas, includeExtensions = false, pgEnableTags = true, pgThrowOnMissingSchema = true } = {}) => {
|
|
5
|
+
const versionResult = await pgClient.query('show server_version_num;');
|
|
6
|
+
const serverVersionNum = parseInt(versionResult.rows[0].server_version_num, 10);
|
|
7
|
+
const introspectionQuery = makeIntrospectionQuery(serverVersionNum, {
|
|
8
|
+
pgLegacyFunctionsOnly: false,
|
|
9
|
+
pgIgnoreRBAC: true
|
|
10
|
+
});
|
|
11
|
+
const { rows } = await pgClient.query(introspectionQuery, [
|
|
12
|
+
schemas,
|
|
13
|
+
includeExtensions
|
|
14
|
+
]);
|
|
15
|
+
const result = {
|
|
16
|
+
__pgVersion: serverVersionNum,
|
|
17
|
+
namespace: [],
|
|
18
|
+
class: [],
|
|
19
|
+
attribute: [],
|
|
20
|
+
type: [],
|
|
21
|
+
constraint: [],
|
|
22
|
+
procedure: [],
|
|
23
|
+
extension: [],
|
|
24
|
+
index: []
|
|
25
|
+
};
|
|
26
|
+
for (const { object } of rows) {
|
|
27
|
+
result[object.kind].push(object);
|
|
28
|
+
}
|
|
29
|
+
// Parse tags from comments
|
|
30
|
+
[
|
|
31
|
+
'namespace',
|
|
32
|
+
'class',
|
|
33
|
+
'attribute',
|
|
34
|
+
'type',
|
|
35
|
+
'constraint',
|
|
36
|
+
'procedure',
|
|
37
|
+
'extension',
|
|
38
|
+
'index'
|
|
39
|
+
].forEach((kind) => {
|
|
40
|
+
result[kind].forEach((object) => {
|
|
41
|
+
// Keep a copy of the raw comment
|
|
42
|
+
object.comment = object.description;
|
|
43
|
+
if (pgEnableTags && object.description) {
|
|
44
|
+
const parsed = parseTags(object.description);
|
|
45
|
+
object.tags = parsed.tags;
|
|
46
|
+
object.description = parsed.text;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
object.tags = {};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const extensionConfigurationClassIds = result.extension.flatMap((e) => e.configurationClassIds);
|
|
54
|
+
result.class.forEach((klass) => {
|
|
55
|
+
klass.isExtensionConfigurationTable =
|
|
56
|
+
extensionConfigurationClassIds.indexOf(klass.id) >= 0;
|
|
57
|
+
});
|
|
58
|
+
[
|
|
59
|
+
'namespace',
|
|
60
|
+
'class',
|
|
61
|
+
'attribute',
|
|
62
|
+
'type',
|
|
63
|
+
'constraint',
|
|
64
|
+
'procedure',
|
|
65
|
+
'extension',
|
|
66
|
+
'index'
|
|
67
|
+
].forEach((k) => {
|
|
68
|
+
result[k].forEach(Object.freeze);
|
|
69
|
+
});
|
|
70
|
+
const knownSchemas = result.namespace.map((n) => n.name);
|
|
71
|
+
const missingSchemas = schemas.filter((s) => knownSchemas.indexOf(s) < 0);
|
|
72
|
+
if (missingSchemas.length) {
|
|
73
|
+
const errorMessage = `You requested to use schema '${schemas.join("', '")}'; however we couldn't find some of those! Missing schemas are: '${missingSchemas.join("', '")}'`;
|
|
74
|
+
if (pgThrowOnMissingSchema) {
|
|
75
|
+
throw new Error(errorMessage);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.warn('⚠️ WARNING⚠️ ' + errorMessage); // eslint-disable-line no-console
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// return result;
|
|
82
|
+
return Object.freeze(result);
|
|
83
|
+
};
|
|
84
|
+
// export const processIntrospection = async (pgClient, introspectionResultsByKind) => {
|
|
85
|
+
// }
|