@constructive-io/graphql-server 2.19.2 → 2.19.4

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.
Files changed (205) hide show
  1. package/codegen/orm/index.d.ts +7 -603
  2. package/codegen/orm/index.js +4 -127
  3. package/codegen/orm/input-types.d.ts +14682 -7790
  4. package/codegen/orm/input-types.js +0 -5
  5. package/codegen/orm/models/api.d.ts +6 -6
  6. package/codegen/orm/models/api.js +3 -4
  7. package/codegen/orm/models/domain.d.ts +6 -6
  8. package/codegen/orm/models/domain.js +3 -4
  9. package/codegen/orm/models/index.d.ts +1 -58
  10. package/codegen/orm/models/index.js +3 -118
  11. package/codegen/orm/mutation/index.d.ts +1 -525
  12. package/codegen/orm/mutation/index.js +2 -591
  13. package/codegen/orm/query/index.d.ts +3 -257
  14. package/codegen/orm/query/index.js +8 -274
  15. package/codegen/orm/query-builder.d.ts +2 -1
  16. package/codegen/orm/query-builder.js +376 -129
  17. package/codegen/orm/select-types.d.ts +33 -0
  18. package/esm/codegen/orm/index.js +4 -127
  19. package/esm/codegen/orm/input-types.js +0 -5
  20. package/esm/codegen/orm/models/api.js +3 -4
  21. package/esm/codegen/orm/models/domain.js +3 -4
  22. package/esm/codegen/orm/models/index.js +1 -58
  23. package/esm/codegen/orm/mutation/index.js +2 -591
  24. package/esm/codegen/orm/query/index.js +8 -274
  25. package/esm/codegen/orm/query-builder.js +343 -129
  26. package/esm/middleware/api.js +145 -173
  27. package/esm/middleware/gql.js +72 -71
  28. package/middleware/api.d.ts +16 -3
  29. package/middleware/api.js +148 -173
  30. package/middleware/gql.d.ts +174 -24
  31. package/middleware/gql.js +76 -76
  32. package/middleware/types.d.ts +3 -19
  33. package/package.json +17 -14
  34. package/types.d.ts +17 -44
  35. package/codegen/orm/models/apiExtension.d.ts +0 -42
  36. package/codegen/orm/models/apiExtension.js +0 -77
  37. package/codegen/orm/models/apiModule.d.ts +0 -42
  38. package/codegen/orm/models/apiModule.js +0 -77
  39. package/codegen/orm/models/apiSchema.d.ts +0 -42
  40. package/codegen/orm/models/apiSchema.js +0 -77
  41. package/codegen/orm/models/app.d.ts +0 -42
  42. package/codegen/orm/models/app.js +0 -77
  43. package/codegen/orm/models/checkConstraint.d.ts +0 -42
  44. package/codegen/orm/models/checkConstraint.js +0 -77
  45. package/codegen/orm/models/connectedAccountsModule.d.ts +0 -42
  46. package/codegen/orm/models/connectedAccountsModule.js +0 -77
  47. package/codegen/orm/models/cryptoAddressesModule.d.ts +0 -42
  48. package/codegen/orm/models/cryptoAddressesModule.js +0 -77
  49. package/codegen/orm/models/cryptoAuthModule.d.ts +0 -42
  50. package/codegen/orm/models/cryptoAuthModule.js +0 -77
  51. package/codegen/orm/models/database.d.ts +0 -42
  52. package/codegen/orm/models/database.js +0 -77
  53. package/codegen/orm/models/databaseExtension.d.ts +0 -42
  54. package/codegen/orm/models/databaseExtension.js +0 -77
  55. package/codegen/orm/models/databaseProvision.d.ts +0 -42
  56. package/codegen/orm/models/databaseProvision.js +0 -77
  57. package/codegen/orm/models/defaultIdsModule.d.ts +0 -42
  58. package/codegen/orm/models/defaultIdsModule.js +0 -77
  59. package/codegen/orm/models/denormalizedTableField.d.ts +0 -42
  60. package/codegen/orm/models/denormalizedTableField.js +0 -77
  61. package/codegen/orm/models/emailsModule.d.ts +0 -42
  62. package/codegen/orm/models/emailsModule.js +0 -77
  63. package/codegen/orm/models/encryptedSecretsModule.d.ts +0 -42
  64. package/codegen/orm/models/encryptedSecretsModule.js +0 -77
  65. package/codegen/orm/models/extension.d.ts +0 -42
  66. package/codegen/orm/models/extension.js +0 -77
  67. package/codegen/orm/models/field.d.ts +0 -42
  68. package/codegen/orm/models/field.js +0 -77
  69. package/codegen/orm/models/fieldModule.d.ts +0 -42
  70. package/codegen/orm/models/fieldModule.js +0 -77
  71. package/codegen/orm/models/foreignKeyConstraint.d.ts +0 -42
  72. package/codegen/orm/models/foreignKeyConstraint.js +0 -77
  73. package/codegen/orm/models/fullTextSearch.d.ts +0 -42
  74. package/codegen/orm/models/fullTextSearch.js +0 -77
  75. package/codegen/orm/models/hierarchyModule.d.ts +0 -42
  76. package/codegen/orm/models/hierarchyModule.js +0 -77
  77. package/codegen/orm/models/indexModel.d.ts +0 -42
  78. package/codegen/orm/models/indexModel.js +0 -77
  79. package/codegen/orm/models/invitesModule.d.ts +0 -42
  80. package/codegen/orm/models/invitesModule.js +0 -77
  81. package/codegen/orm/models/levelsModule.d.ts +0 -42
  82. package/codegen/orm/models/levelsModule.js +0 -77
  83. package/codegen/orm/models/limitFunction.d.ts +0 -42
  84. package/codegen/orm/models/limitFunction.js +0 -77
  85. package/codegen/orm/models/limitsModule.d.ts +0 -42
  86. package/codegen/orm/models/limitsModule.js +0 -77
  87. package/codegen/orm/models/membershipTypesModule.d.ts +0 -42
  88. package/codegen/orm/models/membershipTypesModule.js +0 -77
  89. package/codegen/orm/models/membershipsModule.d.ts +0 -42
  90. package/codegen/orm/models/membershipsModule.js +0 -77
  91. package/codegen/orm/models/module.d.ts +0 -42
  92. package/codegen/orm/models/module.js +0 -77
  93. package/codegen/orm/models/moduleDefinition.d.ts +0 -42
  94. package/codegen/orm/models/moduleDefinition.js +0 -77
  95. package/codegen/orm/models/moduleField.d.ts +0 -42
  96. package/codegen/orm/models/moduleField.js +0 -77
  97. package/codegen/orm/models/moduleInputRecord.d.ts +0 -42
  98. package/codegen/orm/models/moduleInputRecord.js +0 -77
  99. package/codegen/orm/models/moduleOutput.d.ts +0 -42
  100. package/codegen/orm/models/moduleOutput.js +0 -77
  101. package/codegen/orm/models/permissionsModule.d.ts +0 -42
  102. package/codegen/orm/models/permissionsModule.js +0 -77
  103. package/codegen/orm/models/phoneNumbersModule.d.ts +0 -42
  104. package/codegen/orm/models/phoneNumbersModule.js +0 -77
  105. package/codegen/orm/models/policy.d.ts +0 -42
  106. package/codegen/orm/models/policy.js +0 -77
  107. package/codegen/orm/models/primaryKeyConstraint.d.ts +0 -42
  108. package/codegen/orm/models/primaryKeyConstraint.js +0 -77
  109. package/codegen/orm/models/procedure.d.ts +0 -42
  110. package/codegen/orm/models/procedure.js +0 -77
  111. package/codegen/orm/models/profilesModule.d.ts +0 -42
  112. package/codegen/orm/models/profilesModule.js +0 -77
  113. package/codegen/orm/models/rlsFunction.d.ts +0 -42
  114. package/codegen/orm/models/rlsFunction.js +0 -77
  115. package/codegen/orm/models/rlsModule.d.ts +0 -42
  116. package/codegen/orm/models/rlsModule.js +0 -77
  117. package/codegen/orm/models/schema.d.ts +0 -42
  118. package/codegen/orm/models/schema.js +0 -77
  119. package/codegen/orm/models/schemaGrant.d.ts +0 -42
  120. package/codegen/orm/models/schemaGrant.js +0 -77
  121. package/codegen/orm/models/secretsModule.d.ts +0 -42
  122. package/codegen/orm/models/secretsModule.js +0 -77
  123. package/codegen/orm/models/site.d.ts +0 -42
  124. package/codegen/orm/models/site.js +0 -77
  125. package/codegen/orm/models/siteMetadatum.d.ts +0 -42
  126. package/codegen/orm/models/siteMetadatum.js +0 -77
  127. package/codegen/orm/models/siteModule.d.ts +0 -42
  128. package/codegen/orm/models/siteModule.js +0 -77
  129. package/codegen/orm/models/siteTheme.d.ts +0 -42
  130. package/codegen/orm/models/siteTheme.js +0 -77
  131. package/codegen/orm/models/table.d.ts +0 -42
  132. package/codegen/orm/models/table.js +0 -77
  133. package/codegen/orm/models/tableGrant.d.ts +0 -42
  134. package/codegen/orm/models/tableGrant.js +0 -77
  135. package/codegen/orm/models/tokensModule.d.ts +0 -42
  136. package/codegen/orm/models/tokensModule.js +0 -77
  137. package/codegen/orm/models/trigger.d.ts +0 -42
  138. package/codegen/orm/models/trigger.js +0 -77
  139. package/codegen/orm/models/triggerFunction.d.ts +0 -42
  140. package/codegen/orm/models/triggerFunction.js +0 -77
  141. package/codegen/orm/models/uniqueConstraint.d.ts +0 -42
  142. package/codegen/orm/models/uniqueConstraint.js +0 -77
  143. package/codegen/orm/models/userAuthModule.d.ts +0 -42
  144. package/codegen/orm/models/userAuthModule.js +0 -77
  145. package/codegen/orm/models/usersModule.d.ts +0 -42
  146. package/codegen/orm/models/usersModule.js +0 -77
  147. package/codegen/orm/models/uuidModule.d.ts +0 -42
  148. package/codegen/orm/models/uuidModule.js +0 -77
  149. package/esm/codegen/orm/models/apiExtension.js +0 -73
  150. package/esm/codegen/orm/models/apiModule.js +0 -73
  151. package/esm/codegen/orm/models/apiSchema.js +0 -73
  152. package/esm/codegen/orm/models/app.js +0 -73
  153. package/esm/codegen/orm/models/checkConstraint.js +0 -73
  154. package/esm/codegen/orm/models/connectedAccountsModule.js +0 -73
  155. package/esm/codegen/orm/models/cryptoAddressesModule.js +0 -73
  156. package/esm/codegen/orm/models/cryptoAuthModule.js +0 -73
  157. package/esm/codegen/orm/models/database.js +0 -73
  158. package/esm/codegen/orm/models/databaseExtension.js +0 -73
  159. package/esm/codegen/orm/models/databaseProvision.js +0 -73
  160. package/esm/codegen/orm/models/defaultIdsModule.js +0 -73
  161. package/esm/codegen/orm/models/denormalizedTableField.js +0 -73
  162. package/esm/codegen/orm/models/emailsModule.js +0 -73
  163. package/esm/codegen/orm/models/encryptedSecretsModule.js +0 -73
  164. package/esm/codegen/orm/models/extension.js +0 -73
  165. package/esm/codegen/orm/models/field.js +0 -73
  166. package/esm/codegen/orm/models/fieldModule.js +0 -73
  167. package/esm/codegen/orm/models/foreignKeyConstraint.js +0 -73
  168. package/esm/codegen/orm/models/fullTextSearch.js +0 -73
  169. package/esm/codegen/orm/models/hierarchyModule.js +0 -73
  170. package/esm/codegen/orm/models/indexModel.js +0 -73
  171. package/esm/codegen/orm/models/invitesModule.js +0 -73
  172. package/esm/codegen/orm/models/levelsModule.js +0 -73
  173. package/esm/codegen/orm/models/limitFunction.js +0 -73
  174. package/esm/codegen/orm/models/limitsModule.js +0 -73
  175. package/esm/codegen/orm/models/membershipTypesModule.js +0 -73
  176. package/esm/codegen/orm/models/membershipsModule.js +0 -73
  177. package/esm/codegen/orm/models/module.js +0 -73
  178. package/esm/codegen/orm/models/moduleDefinition.js +0 -73
  179. package/esm/codegen/orm/models/moduleField.js +0 -73
  180. package/esm/codegen/orm/models/moduleInputRecord.js +0 -73
  181. package/esm/codegen/orm/models/moduleOutput.js +0 -73
  182. package/esm/codegen/orm/models/permissionsModule.js +0 -73
  183. package/esm/codegen/orm/models/phoneNumbersModule.js +0 -73
  184. package/esm/codegen/orm/models/policy.js +0 -73
  185. package/esm/codegen/orm/models/primaryKeyConstraint.js +0 -73
  186. package/esm/codegen/orm/models/procedure.js +0 -73
  187. package/esm/codegen/orm/models/profilesModule.js +0 -73
  188. package/esm/codegen/orm/models/rlsFunction.js +0 -73
  189. package/esm/codegen/orm/models/rlsModule.js +0 -73
  190. package/esm/codegen/orm/models/schema.js +0 -73
  191. package/esm/codegen/orm/models/schemaGrant.js +0 -73
  192. package/esm/codegen/orm/models/secretsModule.js +0 -73
  193. package/esm/codegen/orm/models/site.js +0 -73
  194. package/esm/codegen/orm/models/siteMetadatum.js +0 -73
  195. package/esm/codegen/orm/models/siteModule.js +0 -73
  196. package/esm/codegen/orm/models/siteTheme.js +0 -73
  197. package/esm/codegen/orm/models/table.js +0 -73
  198. package/esm/codegen/orm/models/tableGrant.js +0 -73
  199. package/esm/codegen/orm/models/tokensModule.js +0 -73
  200. package/esm/codegen/orm/models/trigger.js +0 -73
  201. package/esm/codegen/orm/models/triggerFunction.js +0 -73
  202. package/esm/codegen/orm/models/uniqueConstraint.js +0 -73
  203. package/esm/codegen/orm/models/userAuthModule.js +0 -73
  204. package/esm/codegen/orm/models/usersModule.js +0 -73
  205. package/esm/codegen/orm/models/uuidModule.js +0 -73
@@ -1,50 +1,21 @@
1
1
  import { getNodeEnv } from '@constructive-io/graphql-env';
2
2
  import { Logger } from '@pgpmjs/logger';
3
3
  import { svcCache } from '@pgpmjs/server-utils';
4
+ import { parseUrl } from '@constructive-io/url-domains';
4
5
  import { getSchema, GraphileQuery } from 'graphile-query';
5
6
  import { getGraphileSettings } from 'graphile-settings';
6
7
  import { getPgPool } from 'pg-cache';
7
8
  import errorPage50x from '../errors/50x';
8
9
  import errorPage404Message from '../errors/404-message';
9
- import { buildApiByDatabaseIdAndName, buildDomainLookup, buildListApis, } from './gql';
10
+ /**
11
+ * Normalizes API records into ApiStructure
12
+ */
13
+ import { apiListSelect, apiSelect, connectionFirst, createGraphileOrm, domainSelect, normalizeApiRecord, } from './gql';
10
14
  import './types'; // for Request type
15
+ export { normalizeApiRecord } from './gql';
11
16
  const log = new Logger('api');
12
17
  const isDev = () => getNodeEnv() === 'development';
13
- const transformServiceToApi = (svc) => {
14
- const api = svc.data.api;
15
- const schemaNames = api.apiExtensions?.nodes?.map((n) => n.schemaName) || [];
16
- const additionalSchemas = api.schemasByApiSchemaApiIdAndSchemaId?.nodes?.map((n) => n.schemaName) || [];
17
- let domains = [];
18
- if (api.database?.sites?.nodes) {
19
- domains = api.database.sites.nodes.reduce((acc, site) => {
20
- if (site.domains?.nodes && site.domains.nodes.length) {
21
- const siteUrls = site.domains.nodes.map((domain) => {
22
- const hostname = domain.subdomain
23
- ? `${domain.subdomain}.${domain.domain}`
24
- : domain.domain;
25
- const protocol = domain.domain === 'localhost' ? 'http://' : 'https://';
26
- return protocol + hostname;
27
- });
28
- return [...acc, ...siteUrls];
29
- }
30
- return acc;
31
- }, []);
32
- }
33
- return {
34
- dbname: api.dbname,
35
- anonRole: api.anonRole,
36
- roleName: api.roleName,
37
- schema: [...schemaNames, ...additionalSchemas],
38
- apiModules: api.apiModules?.nodes?.map((node) => ({
39
- name: node.name,
40
- data: node.data,
41
- })) || [],
42
- rlsModule: api.rlsModule,
43
- domains,
44
- databaseId: api.databaseId,
45
- isPublic: api.isPublic,
46
- };
47
- };
18
+ const isApiError = (svc) => !!svc && typeof svc.errorHtml === 'string';
48
19
  const getPortFromRequest = (req) => {
49
20
  const host = req.headers.host;
50
21
  if (!host)
@@ -52,6 +23,18 @@ const getPortFromRequest = (req) => {
52
23
  const parts = host.split(':');
53
24
  return parts.length === 2 ? `:${parts[1]}` : null;
54
25
  };
26
+ const parseCommaSeparatedHeader = (value) => value
27
+ .split(',')
28
+ .map((item) => item.trim())
29
+ .filter((item) => item.length > 0);
30
+ const getUrlDomains = (req) => {
31
+ const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
32
+ const parsed = parseUrl(fullUrl);
33
+ return {
34
+ domain: parsed.domain ?? '',
35
+ subdomains: parsed.subdomains ?? [],
36
+ };
37
+ };
55
38
  export const getSubdomain = (reqDomains) => {
56
39
  const names = reqDomains.filter((name) => !['www'].includes(name));
57
40
  return !names.length ? null : names.join('.');
@@ -59,9 +42,9 @@ export const getSubdomain = (reqDomains) => {
59
42
  export const createApiMiddleware = (opts) => {
60
43
  return async (req, res, next) => {
61
44
  if (opts.api?.enableServicesApi === false) {
62
- const schemas = opts.api.exposedSchemas;
63
- const anonRole = opts.api.anonRole;
64
- const roleName = opts.api.roleName;
45
+ const schemas = opts.api.exposedSchemas ?? [];
46
+ const anonRole = opts.api.anonRole ?? '';
47
+ const roleName = opts.api.roleName ?? '';
65
48
  const databaseId = opts.api.defaultDatabaseId;
66
49
  const api = {
67
50
  dbname: opts.pg?.database ?? '',
@@ -79,133 +62,101 @@ export const createApiMiddleware = (opts) => {
79
62
  return next();
80
63
  }
81
64
  try {
82
- const svc = await getApiConfig(opts, req);
83
- if (svc?.errorHtml) {
65
+ const apiConfig = await getApiConfig(opts, req);
66
+ if (isApiError(apiConfig)) {
84
67
  res
85
68
  .status(404)
86
- .send(errorPage404Message('API not found', svc.errorHtml));
69
+ .send(errorPage404Message('API not found', apiConfig.errorHtml));
87
70
  return;
88
71
  }
89
- else if (!svc) {
72
+ else if (!apiConfig) {
90
73
  res
91
74
  .status(404)
92
75
  .send(errorPage404Message('API service not found for the given domain/subdomain.'));
93
76
  return;
94
77
  }
95
- const api = transformServiceToApi(svc);
96
- req.api = api;
97
- req.databaseId = api.databaseId;
78
+ req.api = apiConfig;
79
+ req.databaseId = apiConfig.databaseId;
98
80
  if (isDev())
99
- log.debug(`Resolved API: db=${api.dbname}, schemas=[${api.schema?.join(', ')}]`);
81
+ log.debug(`Resolved API: db=${apiConfig.dbname}, schemas=[${apiConfig.schema?.join(', ')}]`);
100
82
  next();
101
83
  }
102
- catch (e) {
103
- if (e.code === 'NO_VALID_SCHEMAS') {
104
- res.status(404).send(errorPage404Message(e.message));
84
+ catch (error) {
85
+ const err = error;
86
+ if (err.code === 'NO_VALID_SCHEMAS') {
87
+ res.status(404).send(errorPage404Message(err.message));
105
88
  }
106
- else if (e.message.match(/does not exist/)) {
89
+ else if (err.message?.match(/does not exist/)) {
107
90
  res
108
91
  .status(404)
109
92
  .send(errorPage404Message("The resource you're looking for does not exist."));
110
93
  }
111
94
  else {
112
- log.error('API middleware error:', e);
95
+ log.error('API middleware error:', err);
113
96
  res.status(500).send(errorPage50x);
114
97
  }
115
98
  }
116
99
  };
117
100
  };
118
- const getHardCodedSchemata = ({ opts, schemata, databaseId, key, }) => {
119
- const svc = {
120
- data: {
121
- api: {
122
- databaseId,
123
- isPublic: false,
124
- dbname: opts.pg.database,
125
- anonRole: 'administrator',
126
- roleName: 'administrator',
127
- schemaNamesFromExt: {
128
- nodes: schemata
129
- .split(',')
130
- .map((schema) => schema.trim())
131
- .map((schemaName) => ({ schemaName })),
132
- },
133
- schemaNames: { nodes: [] },
134
- apiModules: [],
135
- },
136
- },
101
+ const createAdminApiStructure = ({ opts, schemata, key, databaseId, }) => {
102
+ const api = {
103
+ dbname: opts.pg?.database ?? '',
104
+ anonRole: 'administrator',
105
+ roleName: 'administrator',
106
+ schema: schemata,
107
+ apiModules: [],
108
+ domains: [],
109
+ databaseId,
110
+ isPublic: false,
137
111
  };
138
- svcCache.set(key, svc);
139
- return svc;
112
+ svcCache.set(key, api);
113
+ return api;
140
114
  };
141
- const getMetaSchema = ({ opts, key, databaseId, }) => {
142
- const apiOpts = opts.api || {};
143
- const schemata = apiOpts.metaSchemas || [];
144
- const svc = {
145
- data: {
146
- api: {
147
- databaseId,
148
- isPublic: false,
149
- dbname: opts.pg.database,
150
- anonRole: 'administrator',
151
- roleName: 'administrator',
152
- schemaNamesFromExt: {
153
- nodes: schemata.map((schemaName) => ({ schemaName })),
154
- },
155
- schemaNames: { nodes: [] },
156
- apiModules: [],
157
- },
158
- },
115
+ const queryServiceByDomainAndSubdomain = async ({ opts, key, domainModel, domain, subdomain, }) => {
116
+ const where = {
117
+ domain: { equalTo: domain },
118
+ subdomain: subdomain === null || subdomain === undefined
119
+ ? { isNull: true }
120
+ : { equalTo: subdomain },
159
121
  };
160
- svcCache.set(key, svc);
161
- return svc;
162
- };
163
- const queryServiceByDomainAndSubdomain = async ({ opts, key, client, domain, subdomain, }) => {
164
- const doc = buildDomainLookup({ domain, subdomain });
165
- const result = await client.query({
166
- role: 'administrator',
167
- query: doc.document,
168
- variables: doc.variables,
169
- });
170
- if (result.errors?.length) {
122
+ const result = await domainModel
123
+ .findFirst({ select: domainSelect, where })
124
+ .execute();
125
+ if (!result.ok) {
171
126
  log.error('GraphQL query errors:', result.errors);
172
127
  return null;
173
128
  }
174
- const nodes = result?.data?.domains?.nodes;
175
- if (nodes?.length) {
176
- const data = nodes[0];
177
- const apiPublic = opts.api?.isPublic;
178
- if (!data.api || data.api.isPublic !== apiPublic)
179
- return null;
180
- const svc = { data };
181
- svcCache.set(key, svc);
182
- return svc;
183
- }
184
- return null;
129
+ const domainRecord = result.data.domains.nodes[0];
130
+ const api = domainRecord?.api;
131
+ const apiPublic = opts.api?.isPublic;
132
+ if (!api || api.isPublic !== apiPublic)
133
+ return null;
134
+ const apiStructure = normalizeApiRecord(api);
135
+ svcCache.set(key, apiStructure);
136
+ return apiStructure;
185
137
  };
186
- const queryServiceByApiName = async ({ opts, key, client, databaseId, name, }) => {
187
- const doc = buildApiByDatabaseIdAndName({ databaseId, name });
188
- const result = await client.query({
189
- role: 'administrator',
190
- query: doc.document,
191
- variables: doc.variables,
192
- });
193
- if (result.errors?.length) {
138
+ export const queryServiceByApiName = async ({ opts, key, queryOps, databaseId, name, }) => {
139
+ if (!databaseId)
140
+ return null;
141
+ const result = await queryOps
142
+ .apiByDatabaseIdAndName({ databaseId, name }, { select: apiSelect })
143
+ .execute();
144
+ if (!result.ok) {
194
145
  log.error('GraphQL query errors:', result.errors);
195
146
  return null;
196
147
  }
197
- const data = result?.data;
148
+ const api = result.data.apiByDatabaseIdAndName;
198
149
  const apiPublic = opts.api?.isPublic;
199
- if (data?.api && data.api.isPublic === apiPublic) {
200
- const svc = { data };
201
- svcCache.set(key, svc);
202
- return svc;
150
+ if (api && api.isPublic === apiPublic) {
151
+ const apiStructure = normalizeApiRecord(api);
152
+ svcCache.set(key, apiStructure);
153
+ return apiStructure;
203
154
  }
204
155
  return null;
205
156
  };
206
- const getSvcKey = (opts, req) => {
207
- const domain = req.urlDomains.domain;
208
- const key = req.urlDomains.subdomains
157
+ export const getSvcKey = (opts, req) => {
158
+ const { domain, subdomains } = getUrlDomains(req);
159
+ const key = subdomains
209
160
  .filter((name) => !['www'].includes(name))
210
161
  .concat(domain)
211
162
  .join('.');
@@ -229,95 +180,118 @@ const validateSchemata = async (pool, schemata) => {
229
180
  };
230
181
  export const getApiConfig = async (opts, req) => {
231
182
  const rootPgPool = getPgPool(opts.pg);
232
- // @ts-ignore
233
- const subdomain = getSubdomain(req.urlDomains.subdomains);
234
- const domain = req.urlDomains.domain;
183
+ const { domain, subdomains } = getUrlDomains(req);
184
+ const subdomain = getSubdomain(subdomains);
235
185
  const key = getSvcKey(opts, req);
236
186
  req.svc_key = key;
237
- let svc;
187
+ let apiConfig;
238
188
  if (svcCache.has(key)) {
239
189
  if (isDev())
240
190
  log.debug(`Cache HIT for key=${key}`);
241
- svc = svcCache.get(key);
191
+ apiConfig = svcCache.get(key);
242
192
  }
243
193
  else {
244
194
  if (isDev())
245
195
  log.debug(`Cache MISS for key=${key}, looking up API`);
246
196
  const apiOpts = opts.api || {};
247
- const allSchemata = apiOpts.metaSchemas || [];
248
- const validatedSchemata = await validateSchemata(rootPgPool, allSchemata);
197
+ const apiPublic = apiOpts.isPublic;
198
+ const schemataHeader = req.get('X-Schemata');
199
+ const apiNameHeader = req.get('X-Api-Name');
200
+ const metaSchemaHeader = req.get('X-Meta-Schema');
201
+ const databaseIdHeader = req.get('X-Database-Id');
202
+ const headerSchemata = schemataHeader
203
+ ? parseCommaSeparatedHeader(schemataHeader)
204
+ : [];
205
+ const candidateSchemata = apiPublic === false && headerSchemata.length
206
+ ? Array.from(new Set([...(apiOpts.metaSchemas || []), ...headerSchemata]))
207
+ : apiOpts.metaSchemas || [];
208
+ const validatedSchemata = await validateSchemata(rootPgPool, candidateSchemata);
249
209
  if (validatedSchemata.length === 0) {
250
- const apiOpts2 = opts.api || {};
251
- const message = `No valid schemas found. Configured metaSchemas: [${(apiOpts2.metaSchemas || []).join(', ')}]`;
210
+ const schemaSource = headerSchemata.length
211
+ ? headerSchemata
212
+ : apiOpts.metaSchemas || [];
213
+ const label = headerSchemata.length ? 'X-Schemata' : 'metaSchemas';
214
+ const message = `No valid schemas found. Configured ${label}: [${schemaSource.join(', ')}]`;
252
215
  if (isDev())
253
216
  log.debug(message);
254
217
  const error = new Error(message);
255
218
  error.code = 'NO_VALID_SCHEMAS';
256
219
  throw error;
257
220
  }
221
+ const validSchemaSet = new Set(validatedSchemata);
222
+ const validatedHeaderSchemata = headerSchemata.filter((schemaName) => validSchemaSet.has(schemaName));
258
223
  const settings = getGraphileSettings({
259
224
  graphile: {
260
225
  schema: validatedSchemata,
261
226
  },
262
227
  });
263
- // @ts-ignore
264
- const schema = await getSchema(rootPgPool, settings);
265
- // @ts-ignore
266
- const client = new GraphileQuery({ schema, pool: rootPgPool, settings });
267
- const apiPublic = opts.api?.isPublic;
228
+ const graphileSettings = {
229
+ ...settings,
230
+ schema: validatedSchemata,
231
+ };
232
+ const schema = await getSchema(rootPgPool, graphileSettings);
233
+ const graphileClient = new GraphileQuery({
234
+ schema,
235
+ pool: rootPgPool,
236
+ settings: graphileSettings,
237
+ });
238
+ const orm = createGraphileOrm(graphileClient);
268
239
  if (apiPublic === false) {
269
- if (req.get('X-Schemata')) {
270
- svc = getHardCodedSchemata({
240
+ if (schemataHeader) {
241
+ if (validatedHeaderSchemata.length === 0) {
242
+ return {
243
+ errorHtml: 'No valid schemas found for the supplied X-Schemata header.',
244
+ };
245
+ }
246
+ apiConfig = createAdminApiStructure({
271
247
  opts,
248
+ schemata: validatedHeaderSchemata,
272
249
  key,
273
- schemata: req.get('X-Schemata'),
274
- databaseId: req.get('X-Database-Id'),
250
+ databaseId: databaseIdHeader,
275
251
  });
276
252
  }
277
- else if (req.get('X-Api-Name')) {
278
- svc = await queryServiceByApiName({
253
+ else if (apiNameHeader) {
254
+ apiConfig = await queryServiceByApiName({
279
255
  opts,
280
256
  key,
281
- client,
282
- name: req.get('X-Api-Name'),
283
- databaseId: req.get('X-Database-Id'),
257
+ queryOps: orm.query,
258
+ name: apiNameHeader,
259
+ databaseId: databaseIdHeader,
284
260
  });
285
261
  }
286
- else if (req.get('X-Meta-Schema')) {
287
- svc = getMetaSchema({
262
+ else if (metaSchemaHeader) {
263
+ apiConfig = createAdminApiStructure({
288
264
  opts,
265
+ schemata: validatedSchemata,
289
266
  key,
290
- databaseId: req.get('X-Database-Id'),
267
+ databaseId: databaseIdHeader,
291
268
  });
292
269
  }
293
270
  else {
294
- svc = await queryServiceByDomainAndSubdomain({
271
+ apiConfig = await queryServiceByDomainAndSubdomain({
295
272
  opts,
296
273
  key,
297
- client,
274
+ domainModel: orm.domain,
298
275
  domain,
299
276
  subdomain,
300
277
  });
301
278
  }
302
279
  }
303
280
  else {
304
- svc = await queryServiceByDomainAndSubdomain({
281
+ apiConfig = await queryServiceByDomainAndSubdomain({
305
282
  opts,
306
283
  key,
307
- client,
284
+ domainModel: orm.domain,
308
285
  domain,
309
286
  subdomain,
310
287
  });
311
- if (!svc) {
288
+ if (!apiConfig) {
289
+ // IMPORTANT NOTE: ONLY DO THIS IN DEV MODE
312
290
  if (getNodeEnv() === 'development') {
313
- // TODO ONLY DO THIS IN DEV MODE
314
- const fallbackResult = await client.query({
315
- role: 'administrator',
316
- // @ts-ignore
317
- query: buildListApis().document,
318
- });
319
- if (!fallbackResult.errors?.length &&
320
- fallbackResult.data?.apis?.nodes?.length) {
291
+ const fallbackResult = await orm.api
292
+ .findMany({ select: apiListSelect, first: connectionFirst })
293
+ .execute();
294
+ if (fallbackResult.ok && fallbackResult.data.apis.nodes.length) {
321
295
  const port = getPortFromRequest(req);
322
296
  const allDomains = fallbackResult.data.apis.nodes.flatMap((api) => api.domains.nodes.map((d) => ({
323
297
  domain: d.domain,
@@ -329,7 +303,7 @@ export const getApiConfig = async (opts, req) => {
329
303
  const linksHtml = allDomains.length
330
304
  ? `<ul class="mt-4 pl-5 list-disc space-y-1">` +
331
305
  allDomains
332
- .map((d) => `<li><a href="${d.href}" class="text-brand hover:underline">${d.href}</a></li>`)
306
+ .map((domainLink) => `<li><a href="${domainLink.href}" class="text-brand hover:underline">${domainLink.href}</a></li>`)
333
307
  .join('') +
334
308
  `</ul>`
335
309
  : `<p class="text-gray-600">No APIs are currently registered for this database.</p>`;
@@ -339,13 +313,11 @@ export const getApiConfig = async (opts, req) => {
339
313
  ${linksHtml}
340
314
  </div>
341
315
  `.trim();
342
- return {
343
- errorHtml,
344
- };
316
+ return { errorHtml };
345
317
  }
346
318
  }
347
319
  }
348
320
  }
349
321
  }
350
- return svc;
322
+ return apiConfig;
351
323
  };
@@ -1,5 +1,8 @@
1
- import { buildFindFirstDocument } from '../codegen/orm/query-builder';
2
- const apiSelect = {
1
+ import { OrmClient, } from '../codegen/orm/client';
2
+ import { ApiModel, DomainModel } from '../codegen/orm/models';
3
+ import { createQueryOperations } from '../codegen/orm/query';
4
+ export const connectionFirst = 1000;
5
+ export const apiSelect = {
3
6
  databaseId: true,
4
7
  dbname: true,
5
8
  roleName: true,
@@ -10,15 +13,15 @@ const apiSelect = {
10
13
  subdomain: true,
11
14
  domain: true,
12
15
  },
13
- connection: true,
16
+ first: connectionFirst,
14
17
  },
15
18
  apiExtensions: {
16
19
  select: { schemaName: true },
17
- connection: true,
20
+ first: connectionFirst,
18
21
  },
19
22
  schemasByApiSchemaApiIdAndSchemaId: {
20
23
  select: { schemaName: true },
21
- connection: true,
24
+ first: connectionFirst,
22
25
  },
23
26
  rlsModule: {
24
27
  select: {
@@ -29,30 +32,17 @@ const apiSelect = {
29
32
  currentRoleId: true,
30
33
  },
31
34
  },
32
- database: {
33
- select: {
34
- sites: {
35
- select: {
36
- domains: {
37
- select: { subdomain: true, domain: true },
38
- connection: true,
39
- },
40
- },
41
- connection: true,
42
- },
43
- },
44
- },
45
35
  apiModules: {
46
36
  select: { name: true, data: true },
47
- connection: true,
37
+ first: connectionFirst,
48
38
  },
49
39
  };
50
- const domainSelect = {
40
+ export const domainSelect = {
51
41
  domain: true,
52
42
  subdomain: true,
53
43
  api: { select: apiSelect },
54
44
  };
55
- const apisSelect = {
45
+ export const apiListSelect = {
56
46
  id: true,
57
47
  databaseId: true,
58
48
  name: true,
@@ -62,59 +52,70 @@ const apisSelect = {
62
52
  isPublic: true,
63
53
  domains: {
64
54
  select: { domain: true, subdomain: true },
65
- connection: true,
66
- },
67
- database: {
68
- select: {
69
- sites: {
70
- select: {
71
- domains: {
72
- select: { domain: true, subdomain: true },
73
- connection: true,
74
- },
75
- },
76
- connection: true,
77
- },
78
- },
55
+ first: connectionFirst,
79
56
  },
80
57
  };
81
- /**
82
- * Build query for domain lookup with optional subdomain
83
- * This uses domains connection instead of domainBySubdomainAndDomain
84
- * because we need to handle null subdomain with condition filter
85
- */
86
- export const buildDomainLookup = (vars) => {
87
- const where = {
88
- domain: { equalTo: vars.domain },
89
- };
90
- if (vars.subdomain === null || vars.subdomain === undefined) {
91
- where.subdomain = { isNull: true };
58
+ class GraphileOrmClient extends OrmClient {
59
+ graphile;
60
+ constructor(graphile) {
61
+ super({ endpoint: 'http://localhost/graphql' });
62
+ this.graphile = graphile;
92
63
  }
93
- else {
94
- where.subdomain = { equalTo: vars.subdomain };
64
+ async execute(document, variables) {
65
+ const result = await this.graphile.query({
66
+ role: 'administrator',
67
+ query: document,
68
+ variables,
69
+ });
70
+ if (result.errors?.length) {
71
+ return {
72
+ ok: false,
73
+ data: null,
74
+ errors: result.errors,
75
+ };
76
+ }
77
+ return {
78
+ ok: true,
79
+ data: result.data,
80
+ errors: undefined,
81
+ };
95
82
  }
96
- return buildFindFirstDocument('DomainLookup', 'domains', domainSelect, { where }, 'DomainFilter');
97
- };
98
- /**
99
- * Build query for API lookup by database ID and name
100
- * Uses the generated apiByDatabaseIdAndName custom query
101
- */
102
- export const buildApiByDatabaseIdAndName = (vars) => {
103
- // Import buildCustomDocument locally to avoid circular dependency
104
- const { buildCustomDocument } = require('../codegen/orm/query-builder');
105
- return buildCustomDocument('query', 'ApiByDatabaseIdAndName', 'apiByDatabaseIdAndName', apiSelect, vars, [
106
- { name: 'databaseId', type: 'UUID!' },
107
- { name: 'name', type: 'String!' },
108
- ]);
83
+ }
84
+ export const createGraphileOrm = (graphile) => {
85
+ const client = new GraphileOrmClient(graphile);
86
+ return {
87
+ api: new ApiModel(client),
88
+ domain: new DomainModel(client),
89
+ query: createQueryOperations(client),
90
+ };
109
91
  };
110
- /**
111
- * Build query to list all APIs
112
- */
113
- export const buildListApis = () => {
114
- // Import buildCustomDocument locally to avoid circular dependency
115
- const { buildCustomDocument } = require('../codegen/orm/query-builder');
116
- return buildCustomDocument('query', 'ListApisByDatabaseId', 'apis', {
117
- select: apisSelect,
118
- connection: true,
119
- }, undefined, []);
92
+ export const normalizeApiRecord = (api) => {
93
+ const schemaNames = (api.apiExtensions?.nodes ?? []).flatMap((node) => node.schemaName ? [node.schemaName] : []);
94
+ const additionalSchemas = (api.schemasByApiSchemaApiIdAndSchemaId?.nodes ?? []).flatMap((node) => (node.schemaName ? [node.schemaName] : []));
95
+ let domains = [];
96
+ if (api.domains?.nodes?.length) {
97
+ domains = api.domains.nodes.reduce((acc, domain) => {
98
+ if (!domain.domain)
99
+ return acc;
100
+ const hostname = domain.subdomain
101
+ ? `${domain.subdomain}.${domain.domain}`
102
+ : domain.domain;
103
+ const protocol = domain.domain === 'localhost' ? 'http://' : 'https://';
104
+ return [...acc, protocol + hostname];
105
+ }, []);
106
+ }
107
+ return {
108
+ dbname: api.dbname,
109
+ anonRole: api.anonRole,
110
+ roleName: api.roleName,
111
+ schema: [...schemaNames, ...additionalSchemas],
112
+ apiModules: api.apiModules?.nodes?.map((node) => ({
113
+ name: node.name,
114
+ data: node.data,
115
+ })) || [],
116
+ rlsModule: api.rlsModule,
117
+ domains,
118
+ databaseId: api.databaseId,
119
+ isPublic: api.isPublic,
120
+ };
120
121
  };
@@ -1,6 +1,19 @@
1
- import { PgpmOptions } from '@pgpmjs/types';
2
1
  import { NextFunction, Request, Response } from 'express';
2
+ /**
3
+ * Normalizes API records into ApiStructure
4
+ */
5
+ import { type ApiQueryOps } from './gql';
6
+ import { ApiConfigResult, ApiOptions, ApiStructure } from '../types';
3
7
  import './types';
8
+ export { normalizeApiRecord } from './gql';
4
9
  export declare const getSubdomain: (reqDomains: string[]) => string | null;
5
- export declare const createApiMiddleware: (opts: any) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
6
- export declare const getApiConfig: (opts: PgpmOptions, req: Request) => Promise<any>;
10
+ export declare const createApiMiddleware: (opts: ApiOptions) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
11
+ export declare const queryServiceByApiName: ({ opts, key, queryOps, databaseId, name, }: {
12
+ opts: ApiOptions;
13
+ key: string;
14
+ queryOps: ApiQueryOps;
15
+ databaseId?: string;
16
+ name: string;
17
+ }) => Promise<ApiStructure | null>;
18
+ export declare const getSvcKey: (opts: ApiOptions, req: Request) => string;
19
+ export declare const getApiConfig: (opts: ApiOptions, req: Request) => Promise<ApiConfigResult>;