introspectron 0.2.12 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2020 Dan Lynch <pyramation@gmail.com>
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,6 @@
1
+ // @ts-nocheck
2
+ export * from './introspect';
3
+ export * from './process';
4
+ export * from './query';
5
+ export * from './gql';
6
+ export * from './introspectGql';
@@ -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
+ // }
@@ -1,5 +1,5 @@
1
1
  import gql from 'graphql-tag';
2
- export const IntrospectionQuery = gql`
2
+ export const IntrospectionQuery = gql `
3
3
  query IntrospectionQuery {
4
4
  __schema {
5
5
  queryType {
@@ -96,4 +96,4 @@ export const IntrospectionQuery = gql`
96
96
  }
97
97
  }
98
98
  }
99
- `;
99
+ `;