@stonecrop/graphql-client 0.23.0 → 0.24.0
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/README.md +15 -35
- package/dist/graphql-client.d.ts +1 -130
- package/dist/graphql-client.js +26 -155
- package/dist/graphql-client.js.map +1 -1
- package/dist/src/client.d.ts +1 -33
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +1 -70
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +0 -1
- package/package.json +4 -4
- package/src/client.ts +1 -90
- package/src/index.ts +0 -11
- package/dist/src/query-builder.d.ts +0 -110
- package/dist/src/query-builder.d.ts.map +0 -1
- package/dist/src/query-builder.js +0 -199
- package/src/query-builder.ts +0 -280
package/src/query-builder.ts
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Query builder for constructing PostGraphile native queries with nested link selections.
|
|
3
|
-
*
|
|
4
|
-
* Instead of using the `stonecropRecord`/`stonecropRecords` resolvers which return JSON blobs,
|
|
5
|
-
* this module builds native PostGraphile queries that leverage the ORM's relationship resolution
|
|
6
|
-
* for efficient single-query fetches with JOINs.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* // Instead of:
|
|
11
|
-
* // stonecropRecord(doctype: "SalesOrder", id: "...") { data }
|
|
12
|
-
* // Which returns { customerId: "uuid" }
|
|
13
|
-
*
|
|
14
|
-
* // We generate:
|
|
15
|
-
* // salesOrderById(id: "...") { id, customerId, partyByCustomerId { id, partyName } }
|
|
16
|
-
* // Which returns { customerId: "uuid", partyByCustomerId: { id: "uuid", partyName: "Acme" } }
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* @public
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import type { DoctypeMeta, ValueField } from '@stonecrop/schema'
|
|
23
|
-
import { flattenFields, componentLinkExpansion } from '@stonecrop/schema'
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Options for building queries
|
|
27
|
-
* @public
|
|
28
|
-
*/
|
|
29
|
-
export interface QueryBuilderOptions {
|
|
30
|
-
/**
|
|
31
|
-
* All available doctype metadata. Used to resolve target doctypes for link fields.
|
|
32
|
-
*/
|
|
33
|
-
allMeta: DoctypeMeta[]
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Maximum depth for nested link resolution. Defaults to 1 (immediate links only).
|
|
37
|
-
* Set to 0 to disable link expansion.
|
|
38
|
-
*/
|
|
39
|
-
maxDepth?: number
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Result of building a native query
|
|
44
|
-
* @public
|
|
45
|
-
*/
|
|
46
|
-
export interface BuiltQuery {
|
|
47
|
-
/**
|
|
48
|
-
* The GraphQL query string
|
|
49
|
-
*/
|
|
50
|
-
query: string
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Field names that are link fields with nested selections.
|
|
54
|
-
* The consumer can use this to know which fields will have relationship data.
|
|
55
|
-
*/
|
|
56
|
-
linkFields: string[]
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Convert a PascalCase doctype name to the camelCase query name PostGraphile uses.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* doctypeToQueryName('SalesOrder') // 'salesOrder'
|
|
64
|
-
* doctypeToQueryName('Party') // 'party'
|
|
65
|
-
* @public
|
|
66
|
-
*/
|
|
67
|
-
export function doctypeToQueryName(doctypeName: string): string {
|
|
68
|
-
return doctypeName[0].toLowerCase() + doctypeName.slice(1)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Convert a PascalCase doctype name to the PostGraphile single-record query name.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* doctypeToSingleQuery('SalesOrder') // 'salesOrderById'
|
|
76
|
-
* @public
|
|
77
|
-
*/
|
|
78
|
-
export function doctypeToSingleQuery(doctypeName: string): string {
|
|
79
|
-
return doctypeToQueryName(doctypeName) + 'ById'
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Convert a PascalCase doctype name to the PostGraphile list query name.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* doctypeToListQuery('SalesOrder') // 'allSalesOrders'
|
|
87
|
-
* doctypeToListQuery('Party') // 'allParties'
|
|
88
|
-
* @public
|
|
89
|
-
*/
|
|
90
|
-
export function doctypeToListQuery(doctypeName: string): string {
|
|
91
|
-
const name = doctypeName
|
|
92
|
-
if (name.endsWith('y')) {
|
|
93
|
-
return 'all' + name.slice(0, -1) + 'ies'
|
|
94
|
-
}
|
|
95
|
-
return 'all' + name + 's'
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Build the PostGraphile relationship field name for a foreign key.
|
|
100
|
-
*
|
|
101
|
-
* PostGraphile names relationships as `targetTypeByFkField` in camelCase.
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* buildRelationshipName('Party', 'customerId') // 'partyByCustomerId'
|
|
105
|
-
* buildRelationshipName('Company', 'companyId') // 'companyByCompanyId'
|
|
106
|
-
* @public
|
|
107
|
-
*/
|
|
108
|
-
export function buildRelationshipName(targetDoctypeName: string, fkFieldname: string): string {
|
|
109
|
-
const prefix = doctypeToQueryName(targetDoctypeName)
|
|
110
|
-
const suffix = fkFieldname[0].toUpperCase() + fkFieldname.slice(1)
|
|
111
|
-
return prefix + 'By' + suffix
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Resolve a doctype slug to its metadata.
|
|
116
|
-
*/
|
|
117
|
-
function resolveDoctype(slug: string, allMeta: DoctypeMeta[]): DoctypeMeta | undefined {
|
|
118
|
-
return allMeta.find(m => m.slug === slug || m.name === slug)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function valueFieldNamed(fields: DoctypeMeta['fields'], fieldname: string): ValueField | undefined {
|
|
122
|
-
for (const field of flattenFields(fields)) {
|
|
123
|
-
if (field.kind === 'field' && field.fieldname === fieldname) {
|
|
124
|
-
return field
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return undefined
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Build the field selection for a doctype, including nested selections for link fields.
|
|
132
|
-
*/
|
|
133
|
-
function buildFieldSelection(
|
|
134
|
-
meta: DoctypeMeta,
|
|
135
|
-
allMeta: DoctypeMeta[],
|
|
136
|
-
depth: number,
|
|
137
|
-
maxDepth: number,
|
|
138
|
-
linkFieldsOut: string[]
|
|
139
|
-
): string {
|
|
140
|
-
const flatFields = flattenFields(meta.fields)
|
|
141
|
-
const selections: string[] = []
|
|
142
|
-
|
|
143
|
-
for (const field of flatFields) {
|
|
144
|
-
if (field.kind !== 'field') continue
|
|
145
|
-
|
|
146
|
-
const isLinkField = field.doctype && componentLinkExpansion(field.component) === 'inline'
|
|
147
|
-
|
|
148
|
-
if (isLinkField && depth < maxDepth) {
|
|
149
|
-
const targetMeta = resolveDoctype(field.doctype!, allMeta)
|
|
150
|
-
if (targetMeta) {
|
|
151
|
-
const relationshipName = buildRelationshipName(targetMeta.name, field.fieldname)
|
|
152
|
-
const displayField = targetMeta.displayField
|
|
153
|
-
|
|
154
|
-
if (displayField) {
|
|
155
|
-
selections.push(field.fieldname)
|
|
156
|
-
selections.push(`${relationshipName} { id ${displayField} }`)
|
|
157
|
-
linkFieldsOut.push(field.fieldname)
|
|
158
|
-
} else {
|
|
159
|
-
selections.push(field.fieldname)
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
selections.push(field.fieldname)
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
selections.push(field.fieldname)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return selections.join(' ')
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Build a native PostGraphile query for fetching a single record by ID.
|
|
174
|
-
* @public
|
|
175
|
-
*/
|
|
176
|
-
export function buildSingleRecordQuery(meta: DoctypeMeta, options: QueryBuilderOptions): BuiltQuery {
|
|
177
|
-
const maxDepth = options.maxDepth ?? 1
|
|
178
|
-
const linkFields: string[] = []
|
|
179
|
-
|
|
180
|
-
const fieldSelection = buildFieldSelection(meta, options.allMeta, 0, maxDepth, linkFields)
|
|
181
|
-
const queryName = doctypeToSingleQuery(meta.name)
|
|
182
|
-
|
|
183
|
-
const query = `query($id: UUID!) { ${queryName}(id: $id) { ${fieldSelection} } }`
|
|
184
|
-
|
|
185
|
-
return { query, linkFields }
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Build a native PostGraphile query for fetching multiple records.
|
|
190
|
-
* @public
|
|
191
|
-
*/
|
|
192
|
-
export function buildListRecordQuery(
|
|
193
|
-
meta: DoctypeMeta,
|
|
194
|
-
options: QueryBuilderOptions & {
|
|
195
|
-
first?: number
|
|
196
|
-
offset?: number
|
|
197
|
-
orderBy?: string
|
|
198
|
-
condition?: Record<string, unknown>
|
|
199
|
-
}
|
|
200
|
-
): BuiltQuery {
|
|
201
|
-
const maxDepth = options.maxDepth ?? 1
|
|
202
|
-
const linkFields: string[] = []
|
|
203
|
-
|
|
204
|
-
const fieldSelection = buildFieldSelection(meta, options.allMeta, 0, maxDepth, linkFields)
|
|
205
|
-
const queryName = doctypeToListQuery(meta.name)
|
|
206
|
-
|
|
207
|
-
const params: string[] = []
|
|
208
|
-
const args: string[] = []
|
|
209
|
-
|
|
210
|
-
if (options.first !== undefined) {
|
|
211
|
-
params.push('$first: Int')
|
|
212
|
-
args.push('first: $first')
|
|
213
|
-
}
|
|
214
|
-
if (options.offset !== undefined) {
|
|
215
|
-
params.push('$offset: Int')
|
|
216
|
-
args.push('offset: $offset')
|
|
217
|
-
}
|
|
218
|
-
if (options.orderBy) {
|
|
219
|
-
params.push('$orderBy: [SalesOrdersOrderBy!]')
|
|
220
|
-
args.push('orderBy: $orderBy')
|
|
221
|
-
}
|
|
222
|
-
if (options.condition) {
|
|
223
|
-
params.push('$condition: SalesOrderCondition')
|
|
224
|
-
args.push('condition: $condition')
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const paramStr = params.length > 0 ? `(${params.join(', ')})` : ''
|
|
228
|
-
const argStr = args.length > 0 ? `(${args.join(', ')})` : ''
|
|
229
|
-
|
|
230
|
-
const query = `query${paramStr} { ${queryName}${argStr} { nodes { ${fieldSelection} } } }`
|
|
231
|
-
|
|
232
|
-
return { query, linkFields }
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Transform a record fetched via native PostGraphile query to the flat format
|
|
237
|
-
* expected by the Stonecrop client. Link fields become objects with `id` and `displayText`.
|
|
238
|
-
* @public
|
|
239
|
-
*/
|
|
240
|
-
export function transformNativeRecord(
|
|
241
|
-
record: Record<string, unknown>,
|
|
242
|
-
linkFields: string[],
|
|
243
|
-
meta: DoctypeMeta,
|
|
244
|
-
allMeta: DoctypeMeta[]
|
|
245
|
-
): Record<string, unknown> {
|
|
246
|
-
const result: Record<string, unknown> = {}
|
|
247
|
-
|
|
248
|
-
for (const [key, value] of Object.entries(record)) {
|
|
249
|
-
if (key.includes('By')) {
|
|
250
|
-
continue
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (linkFields.includes(key)) {
|
|
254
|
-
const linkField = valueFieldNamed(meta.fields, key)
|
|
255
|
-
if (linkField?.doctype) {
|
|
256
|
-
const targetMeta = resolveDoctype(linkField.doctype, allMeta)
|
|
257
|
-
if (targetMeta?.displayField) {
|
|
258
|
-
const relationshipName = buildRelationshipName(targetMeta.name, key)
|
|
259
|
-
const nestedRaw = record[relationshipName]
|
|
260
|
-
const displayText =
|
|
261
|
-
nestedRaw !== null && nestedRaw !== undefined && typeof nestedRaw === 'object'
|
|
262
|
-
? Reflect.get(nestedRaw, targetMeta.displayField)
|
|
263
|
-
: undefined
|
|
264
|
-
|
|
265
|
-
if (displayText !== undefined && displayText !== null && displayText !== '') {
|
|
266
|
-
result[key] = {
|
|
267
|
-
id: value,
|
|
268
|
-
displayText,
|
|
269
|
-
}
|
|
270
|
-
continue
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
result[key] = value
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return result
|
|
280
|
-
}
|