@stonecrop/graphql-client 0.20.0 → 0.21.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 +35 -15
- package/dist/graphql-client.d.ts +130 -2
- package/dist/graphql-client.js +157 -26
- package/dist/graphql-client.js.map +1 -1
- package/dist/src/client.d.ts +33 -2
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +70 -2
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/queries.d.ts +2 -2
- package/dist/src/queries.d.ts.map +1 -1
- package/dist/src/queries.js +2 -0
- package/dist/src/query-builder.d.ts +110 -0
- package/dist/src/query-builder.d.ts.map +1 -0
- package/dist/src/query-builder.js +199 -0
- package/package.json +4 -4
- package/src/client.ts +90 -2
- package/src/index.ts +11 -0
- package/src/queries.ts +2 -0
- package/src/query-builder.ts +280 -0
package/dist/src/client.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export interface StonecropClientOptions {
|
|
|
15
15
|
/**
|
|
16
16
|
* Client for interacting with Stonecrop GraphQL API.
|
|
17
17
|
*
|
|
18
|
-
* Acts as a transport layer
|
|
19
|
-
*
|
|
18
|
+
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction, and
|
|
19
|
+
* builds native PostGraphile queries for getNativeRecord/getNativeRecords.
|
|
20
20
|
*
|
|
21
21
|
* @public
|
|
22
22
|
*/
|
|
@@ -70,6 +70,37 @@ export declare class StonecropClient implements DataClient {
|
|
|
70
70
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
71
71
|
*/
|
|
72
72
|
getRecords(doctype: DoctypeRef, options?: GetRecordsOptions): Promise<GetRecordsResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Get a single record by ID using PostGraphile's native query with relationship expansion.
|
|
75
|
+
*
|
|
76
|
+
* Unlike `getRecord()` which uses the `stonecropRecord` resolver returning a JSON blob,
|
|
77
|
+
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
78
|
+
* resolution for efficient single-query fetches with JOINs.
|
|
79
|
+
*
|
|
80
|
+
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
81
|
+
* resolved from the target doctype's `displayField`.
|
|
82
|
+
*
|
|
83
|
+
* @param doctype - Doctype reference (name and optional slug)
|
|
84
|
+
* @param recordId - Record ID to fetch
|
|
85
|
+
*/
|
|
86
|
+
getNativeRecord(doctype: DoctypeRef, recordId: string): Promise<GetRecordResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Get multiple records using PostGraphile's native query with relationship expansion.
|
|
89
|
+
*
|
|
90
|
+
* Unlike `getRecords()` which uses the `stonecropRecords` resolver returning JSON blobs,
|
|
91
|
+
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
92
|
+
* resolution for efficient single-query fetches with JOINs.
|
|
93
|
+
*
|
|
94
|
+
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
95
|
+
* resolved from the target doctype's `displayField`.
|
|
96
|
+
*
|
|
97
|
+
* @param doctype - Doctype reference (name and optional slug)
|
|
98
|
+
* @param options - Query options (limit, offset)
|
|
99
|
+
*/
|
|
100
|
+
getNativeRecords(doctype: DoctypeRef, options?: {
|
|
101
|
+
limit?: number;
|
|
102
|
+
offset?: number;
|
|
103
|
+
}): Promise<GetRecordsResult>;
|
|
73
104
|
/**
|
|
74
105
|
* Execute a doctype action
|
|
75
106
|
* @param doctype - Doctype reference (name and optional slug)
|
package/dist/src/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAU9C,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,CAAA;AAC1C,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAA;AAEjD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED;;;;;;;GAOG;AACH,qBAAa,eAAgB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAAsC;gBAE3C,OAAO,EAAE,sBAAsB;IAQ3C;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoBxF;;;;;OAKG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI5F;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAenE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAU1C;;;;;;;;;OASG;IACG,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IA4B5G;;;;;;;;OAQG;IACG,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAuC7F;;;;;;;;;;;;OAYG;IACG,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAsBtF;;;;;;;;;;;;OAYG;IACG,gBAAgB,CACrB,OAAO,EAAE,UAAU,EACnB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC,gBAAgB,CAAC;IA8B5B;;;;;OAKG;IACG,SAAS,CACd,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO,EAAE,GACd,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAYrE;;;;;OAKG;IACH,cAAc,IAAI,IAAI;CAGtB"}
|
package/dist/src/client.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { GET_META_QUERY, GET_ALL_META_QUERY, RUN_ACTION_MUTATION } from './queries';
|
|
2
|
+
import { buildSingleRecordQuery, buildListRecordQuery, transformNativeRecord, doctypeToSingleQuery, doctypeToListQuery, } from './query-builder';
|
|
2
3
|
/**
|
|
3
4
|
* Client for interacting with Stonecrop GraphQL API.
|
|
4
5
|
*
|
|
5
|
-
* Acts as a transport layer
|
|
6
|
-
*
|
|
6
|
+
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction, and
|
|
7
|
+
* builds native PostGraphile queries for getNativeRecord/getNativeRecords.
|
|
7
8
|
*
|
|
8
9
|
* @public
|
|
9
10
|
*/
|
|
@@ -147,6 +148,73 @@ export class StonecropClient {
|
|
|
147
148
|
// through keeps "not asked for" and "asked for, and it is zero" distinguishable.
|
|
148
149
|
return count == null ? { data, hasMore } : { data, hasMore, count };
|
|
149
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Get a single record by ID using PostGraphile's native query with relationship expansion.
|
|
153
|
+
*
|
|
154
|
+
* Unlike `getRecord()` which uses the `stonecropRecord` resolver returning a JSON blob,
|
|
155
|
+
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
156
|
+
* resolution for efficient single-query fetches with JOINs.
|
|
157
|
+
*
|
|
158
|
+
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
159
|
+
* resolved from the target doctype's `displayField`.
|
|
160
|
+
*
|
|
161
|
+
* @param doctype - Doctype reference (name and optional slug)
|
|
162
|
+
* @param recordId - Record ID to fetch
|
|
163
|
+
*/
|
|
164
|
+
async getNativeRecord(doctype, recordId) {
|
|
165
|
+
const allMeta = await this.getAllMeta();
|
|
166
|
+
const meta = allMeta.find(m => m.name === doctype.name || m.slug === doctype.name);
|
|
167
|
+
if (!meta) {
|
|
168
|
+
return { record: null };
|
|
169
|
+
}
|
|
170
|
+
const { query, linkFields } = buildSingleRecordQuery(meta, { allMeta });
|
|
171
|
+
const queryName = doctypeToSingleQuery(meta.name);
|
|
172
|
+
const result = await this.query(query, { id: recordId });
|
|
173
|
+
const rawRecord = result[queryName];
|
|
174
|
+
if (!rawRecord) {
|
|
175
|
+
return { record: null };
|
|
176
|
+
}
|
|
177
|
+
const record = transformNativeRecord(rawRecord, linkFields, meta, allMeta);
|
|
178
|
+
return { record };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get multiple records using PostGraphile's native query with relationship expansion.
|
|
182
|
+
*
|
|
183
|
+
* Unlike `getRecords()` which uses the `stonecropRecords` resolver returning JSON blobs,
|
|
184
|
+
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
185
|
+
* resolution for efficient single-query fetches with JOINs.
|
|
186
|
+
*
|
|
187
|
+
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
188
|
+
* resolved from the target doctype's `displayField`.
|
|
189
|
+
*
|
|
190
|
+
* @param doctype - Doctype reference (name and optional slug)
|
|
191
|
+
* @param options - Query options (limit, offset)
|
|
192
|
+
*/
|
|
193
|
+
async getNativeRecords(doctype, options) {
|
|
194
|
+
const allMeta = await this.getAllMeta();
|
|
195
|
+
const meta = allMeta.find(m => m.name === doctype.name || m.slug === doctype.name);
|
|
196
|
+
if (!meta) {
|
|
197
|
+
return { data: [], hasMore: false };
|
|
198
|
+
}
|
|
199
|
+
const { query, linkFields } = buildListRecordQuery(meta, {
|
|
200
|
+
allMeta,
|
|
201
|
+
first: options?.limit,
|
|
202
|
+
offset: options?.offset,
|
|
203
|
+
});
|
|
204
|
+
const queryName = doctypeToListQuery(meta.name);
|
|
205
|
+
const variables = {};
|
|
206
|
+
if (options?.limit !== undefined)
|
|
207
|
+
variables.first = options.limit;
|
|
208
|
+
if (options?.offset !== undefined)
|
|
209
|
+
variables.offset = options.offset;
|
|
210
|
+
const result = await this.query(query, variables);
|
|
211
|
+
const nodes = result[queryName]?.nodes ?? [];
|
|
212
|
+
const data = nodes.map(node => transformNativeRecord(node, linkFields, meta, allMeta));
|
|
213
|
+
return {
|
|
214
|
+
data,
|
|
215
|
+
hasMore: nodes.length === options?.limit,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
150
218
|
/**
|
|
151
219
|
* Execute a doctype action
|
|
152
220
|
* @param doctype - Doctype reference (name and optional slug)
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { DoctypeMeta } from '@stonecrop/schema';
|
|
2
2
|
export { StonecropClient, type StonecropClientOptions, type DoctypeContext } from './client';
|
|
3
3
|
export type { GetRecordResult } from './types';
|
|
4
|
+
export { buildSingleRecordQuery, buildListRecordQuery, transformNativeRecord, doctypeToSingleQuery, doctypeToListQuery, buildRelationshipName, doctypeToQueryName, type QueryBuilderOptions, type BuiltQuery, } from './query-builder';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAC5F,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAC5F,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,UAAU,GACf,MAAM,iBAAiB,CAAA"}
|
package/dist/src/index.js
CHANGED
package/dist/src/queries.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @public
|
|
11
11
|
*/
|
|
12
|
-
export declare const GET_META_QUERY = "\n\tquery GetMeta($doctype: String!) {\n\t\tstonecropMeta(doctype: $doctype) {\n\t\t\tname\n\t\t\tslug\n\t\t\tfields {\n\t\t\t\tkind\n\t\t\t\tfieldname\n\t\t\t\tcomponent\n\t\t\t\tprimaryKey\n\t\t\t\tcomputed\n\t\t\t\tlanguage\n\t\t\t\tdoctype\n\t\t\t\tlabel\n\t\t\t\twidth\n\t\t\t\talign\n\t\t\t\tedit\n\t\t\t\tmask\n\t\t\t\tformat\n\t\t\t\tmode\n\t\t\t\toptions\n\t\t\t\trequired\n\t\t\t\treadOnly\n\t\t\t\thidden\n\t\t\t\tdefault\n\t\t\t\tvalidation\n\t\t\t\tcardinality\n\t\t\t\tsource\n\t\t\t}\n\t\t\tworkflow {\n\t\t\t\tstates\n\t\t\t\tactions {\n\t\t\t\t\tlabel\n\t\t\t\t\trequiredFields\n\t\t\t\t\tallowedStates\n\t\t\t\t\tnextState\n\t\t\t\t\tstateless\n\t\t\t\t\tselfTransition\n\t\t\t\t\tclientHandler\n\t\t\t\t}\n\t\t\t}\n\t\t\tinherits\n\t\t}\n\t}\n";
|
|
12
|
+
export declare const GET_META_QUERY = "\n\tquery GetMeta($doctype: String!) {\n\t\tstonecropMeta(doctype: $doctype) {\n\t\t\tname\n\t\t\tslug\n\t\t\tdisplayField\n\t\t\tfields {\n\t\t\t\tkind\n\t\t\t\tfieldname\n\t\t\t\tcomponent\n\t\t\t\tprimaryKey\n\t\t\t\tcomputed\n\t\t\t\tlanguage\n\t\t\t\tdoctype\n\t\t\t\tlabel\n\t\t\t\twidth\n\t\t\t\talign\n\t\t\t\tedit\n\t\t\t\tmask\n\t\t\t\tformat\n\t\t\t\tmode\n\t\t\t\toptions\n\t\t\t\trequired\n\t\t\t\treadOnly\n\t\t\t\thidden\n\t\t\t\tdefault\n\t\t\t\tvalidation\n\t\t\t\tcardinality\n\t\t\t\tsource\n\t\t\t}\n\t\t\tworkflow {\n\t\t\t\tstates\n\t\t\t\tactions {\n\t\t\t\t\tlabel\n\t\t\t\t\trequiredFields\n\t\t\t\t\tallowedStates\n\t\t\t\t\tnextState\n\t\t\t\t\tstateless\n\t\t\t\t\tselfTransition\n\t\t\t\t\tclientHandler\n\t\t\t\t}\n\t\t\t}\n\t\t\tinherits\n\t\t}\n\t}\n";
|
|
13
13
|
/**
|
|
14
14
|
* Mutation document for dispatching a workflow action (the server-owned transition).
|
|
15
15
|
* @public
|
|
@@ -19,5 +19,5 @@ export declare const RUN_ACTION_MUTATION = "\n\tmutation RunAction($doctype: Str
|
|
|
19
19
|
* Query document for fetching all doctype metadata.
|
|
20
20
|
* @public
|
|
21
21
|
*/
|
|
22
|
-
export declare const GET_ALL_META_QUERY = "\n\tquery GetAllMeta {\n\t\tstonecropAllMeta {\n\t\t\tname\n\t\t\tslug\n\t\t\tfields {\n\t\t\t\tkind\n\t\t\t\tfieldname\n\t\t\t\tcomponent\n\t\t\t\tprimaryKey\n\t\t\t\tcomputed\n\t\t\t\tlanguage\n\t\t\t\tdoctype\n\t\t\t\tlabel\n\t\t\t\twidth\n\t\t\t\talign\n\t\t\t\tedit\n\t\t\t\tmask\n\t\t\t\tformat\n\t\t\t\tmode\n\t\t\t\toptions\n\t\t\t\trequired\n\t\t\t\treadOnly\n\t\t\t\thidden\n\t\t\t\tdefault\n\t\t\t\tvalidation\n\t\t\t\tcardinality\n\t\t\t\tsource\n\t\t\t}\n\t\t\tworkflow {\n\t\t\t\tstates\n\t\t\t\tactions {\n\t\t\t\t\tlabel\n\t\t\t\t\trequiredFields\n\t\t\t\t\tallowedStates\n\t\t\t\t\tnextState\n\t\t\t\t\tstateless\n\t\t\t\t\tselfTransition\n\t\t\t\t\tclientHandler\n\t\t\t\t}\n\t\t\t}\n\t\t\tinherits\n\t\t}\n\t}\n";
|
|
22
|
+
export declare const GET_ALL_META_QUERY = "\n\tquery GetAllMeta {\n\t\tstonecropAllMeta {\n\t\t\tname\n\t\t\tslug\n\t\t\tdisplayField\n\t\t\tfields {\n\t\t\t\tkind\n\t\t\t\tfieldname\n\t\t\t\tcomponent\n\t\t\t\tprimaryKey\n\t\t\t\tcomputed\n\t\t\t\tlanguage\n\t\t\t\tdoctype\n\t\t\t\tlabel\n\t\t\t\twidth\n\t\t\t\talign\n\t\t\t\tedit\n\t\t\t\tmask\n\t\t\t\tformat\n\t\t\t\tmode\n\t\t\t\toptions\n\t\t\t\trequired\n\t\t\t\treadOnly\n\t\t\t\thidden\n\t\t\t\tdefault\n\t\t\t\tvalidation\n\t\t\t\tcardinality\n\t\t\t\tsource\n\t\t\t}\n\t\t\tworkflow {\n\t\t\t\tstates\n\t\t\t\tactions {\n\t\t\t\t\tlabel\n\t\t\t\t\trequiredFields\n\t\t\t\t\tallowedStates\n\t\t\t\t\tnextState\n\t\t\t\t\tstateless\n\t\t\t\t\tselfTransition\n\t\t\t\t\tclientHandler\n\t\t\t\t}\n\t\t\t}\n\t\t\tinherits\n\t\t}\n\t}\n";
|
|
23
23
|
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,mxBA6C1B,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,6MAQ/B,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,mvBA6C9B,CAAA"}
|
package/dist/src/queries.js
CHANGED
|
@@ -14,6 +14,7 @@ export const GET_META_QUERY = `
|
|
|
14
14
|
stonecropMeta(doctype: $doctype) {
|
|
15
15
|
name
|
|
16
16
|
slug
|
|
17
|
+
displayField
|
|
17
18
|
fields {
|
|
18
19
|
kind
|
|
19
20
|
fieldname
|
|
@@ -76,6 +77,7 @@ export const GET_ALL_META_QUERY = `
|
|
|
76
77
|
stonecropAllMeta {
|
|
77
78
|
name
|
|
78
79
|
slug
|
|
80
|
+
displayField
|
|
79
81
|
fields {
|
|
80
82
|
kind
|
|
81
83
|
fieldname
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
import type { DoctypeMeta } from '@stonecrop/schema';
|
|
22
|
+
/**
|
|
23
|
+
* Options for building queries
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export interface QueryBuilderOptions {
|
|
27
|
+
/**
|
|
28
|
+
* All available doctype metadata. Used to resolve target doctypes for link fields.
|
|
29
|
+
*/
|
|
30
|
+
allMeta: DoctypeMeta[];
|
|
31
|
+
/**
|
|
32
|
+
* Maximum depth for nested link resolution. Defaults to 1 (immediate links only).
|
|
33
|
+
* Set to 0 to disable link expansion.
|
|
34
|
+
*/
|
|
35
|
+
maxDepth?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Result of building a native query
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export interface BuiltQuery {
|
|
42
|
+
/**
|
|
43
|
+
* The GraphQL query string
|
|
44
|
+
*/
|
|
45
|
+
query: string;
|
|
46
|
+
/**
|
|
47
|
+
* Field names that are link fields with nested selections.
|
|
48
|
+
* The consumer can use this to know which fields will have relationship data.
|
|
49
|
+
*/
|
|
50
|
+
linkFields: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert a PascalCase doctype name to the camelCase query name PostGraphile uses.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* doctypeToQueryName('SalesOrder') // 'salesOrder'
|
|
57
|
+
* doctypeToQueryName('Party') // 'party'
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
export declare function doctypeToQueryName(doctypeName: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Convert a PascalCase doctype name to the PostGraphile single-record query name.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* doctypeToSingleQuery('SalesOrder') // 'salesOrderById'
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export declare function doctypeToSingleQuery(doctypeName: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Convert a PascalCase doctype name to the PostGraphile list query name.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* doctypeToListQuery('SalesOrder') // 'allSalesOrders'
|
|
74
|
+
* doctypeToListQuery('Party') // 'allParties'
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export declare function doctypeToListQuery(doctypeName: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Build the PostGraphile relationship field name for a foreign key.
|
|
80
|
+
*
|
|
81
|
+
* PostGraphile names relationships as `targetTypeByFkField` in camelCase.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* buildRelationshipName('Party', 'customerId') // 'partyByCustomerId'
|
|
85
|
+
* buildRelationshipName('Company', 'companyId') // 'companyByCompanyId'
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildRelationshipName(targetDoctypeName: string, fkFieldname: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Build a native PostGraphile query for fetching a single record by ID.
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildSingleRecordQuery(meta: DoctypeMeta, options: QueryBuilderOptions): BuiltQuery;
|
|
94
|
+
/**
|
|
95
|
+
* Build a native PostGraphile query for fetching multiple records.
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildListRecordQuery(meta: DoctypeMeta, options: QueryBuilderOptions & {
|
|
99
|
+
first?: number;
|
|
100
|
+
offset?: number;
|
|
101
|
+
orderBy?: string;
|
|
102
|
+
condition?: Record<string, unknown>;
|
|
103
|
+
}): BuiltQuery;
|
|
104
|
+
/**
|
|
105
|
+
* Transform a record fetched via native PostGraphile query to the flat format
|
|
106
|
+
* expected by the Stonecrop client. Link fields become objects with `id` and `displayText`.
|
|
107
|
+
* @public
|
|
108
|
+
*/
|
|
109
|
+
export declare function transformNativeRecord(record: Record<string, unknown>, linkFields: string[], meta: DoctypeMeta, allMeta: DoctypeMeta[]): Record<string, unknown>;
|
|
110
|
+
//# sourceMappingURL=query-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-builder.d.ts","sourceRoot":"","sources":["../../src/query-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,mBAAmB,CAAA;AAGhE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IACnC;;OAEG;IACH,OAAO,EAAE,WAAW,EAAE,CAAA;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,UAAU,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAI5F;AA4DD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,mBAAmB,GAAG,UAAU,CAUlG;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CACnC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,mBAAmB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,GACC,UAAU,CAiCZ;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,WAAW,EAAE,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmCzB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
import { flattenFields, componentLinkExpansion } from '@stonecrop/schema';
|
|
22
|
+
/**
|
|
23
|
+
* Convert a PascalCase doctype name to the camelCase query name PostGraphile uses.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* doctypeToQueryName('SalesOrder') // 'salesOrder'
|
|
27
|
+
* doctypeToQueryName('Party') // 'party'
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export function doctypeToQueryName(doctypeName) {
|
|
31
|
+
return doctypeName[0].toLowerCase() + doctypeName.slice(1);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Convert a PascalCase doctype name to the PostGraphile single-record query name.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* doctypeToSingleQuery('SalesOrder') // 'salesOrderById'
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export function doctypeToSingleQuery(doctypeName) {
|
|
41
|
+
return doctypeToQueryName(doctypeName) + 'ById';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert a PascalCase doctype name to the PostGraphile list query name.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* doctypeToListQuery('SalesOrder') // 'allSalesOrders'
|
|
48
|
+
* doctypeToListQuery('Party') // 'allParties'
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export function doctypeToListQuery(doctypeName) {
|
|
52
|
+
const name = doctypeName;
|
|
53
|
+
if (name.endsWith('y')) {
|
|
54
|
+
return 'all' + name.slice(0, -1) + 'ies';
|
|
55
|
+
}
|
|
56
|
+
return 'all' + name + 's';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the PostGraphile relationship field name for a foreign key.
|
|
60
|
+
*
|
|
61
|
+
* PostGraphile names relationships as `targetTypeByFkField` in camelCase.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* buildRelationshipName('Party', 'customerId') // 'partyByCustomerId'
|
|
65
|
+
* buildRelationshipName('Company', 'companyId') // 'companyByCompanyId'
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export function buildRelationshipName(targetDoctypeName, fkFieldname) {
|
|
69
|
+
const prefix = doctypeToQueryName(targetDoctypeName);
|
|
70
|
+
const suffix = fkFieldname[0].toUpperCase() + fkFieldname.slice(1);
|
|
71
|
+
return prefix + 'By' + suffix;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a doctype slug to its metadata.
|
|
75
|
+
*/
|
|
76
|
+
function resolveDoctype(slug, allMeta) {
|
|
77
|
+
return allMeta.find(m => m.slug === slug || m.name === slug);
|
|
78
|
+
}
|
|
79
|
+
function valueFieldNamed(fields, fieldname) {
|
|
80
|
+
for (const field of flattenFields(fields)) {
|
|
81
|
+
if (field.kind === 'field' && field.fieldname === fieldname) {
|
|
82
|
+
return field;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build the field selection for a doctype, including nested selections for link fields.
|
|
89
|
+
*/
|
|
90
|
+
function buildFieldSelection(meta, allMeta, depth, maxDepth, linkFieldsOut) {
|
|
91
|
+
const flatFields = flattenFields(meta.fields);
|
|
92
|
+
const selections = [];
|
|
93
|
+
for (const field of flatFields) {
|
|
94
|
+
if (field.kind !== 'field')
|
|
95
|
+
continue;
|
|
96
|
+
const isLinkField = field.doctype && componentLinkExpansion(field.component) === 'inline';
|
|
97
|
+
if (isLinkField && depth < maxDepth) {
|
|
98
|
+
const targetMeta = resolveDoctype(field.doctype, allMeta);
|
|
99
|
+
if (targetMeta) {
|
|
100
|
+
const relationshipName = buildRelationshipName(targetMeta.name, field.fieldname);
|
|
101
|
+
const displayField = targetMeta.displayField;
|
|
102
|
+
if (displayField) {
|
|
103
|
+
selections.push(field.fieldname);
|
|
104
|
+
selections.push(`${relationshipName} { id ${displayField} }`);
|
|
105
|
+
linkFieldsOut.push(field.fieldname);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
selections.push(field.fieldname);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
selections.push(field.fieldname);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
selections.push(field.fieldname);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return selections.join(' ');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build a native PostGraphile query for fetching a single record by ID.
|
|
123
|
+
* @public
|
|
124
|
+
*/
|
|
125
|
+
export function buildSingleRecordQuery(meta, options) {
|
|
126
|
+
const maxDepth = options.maxDepth ?? 1;
|
|
127
|
+
const linkFields = [];
|
|
128
|
+
const fieldSelection = buildFieldSelection(meta, options.allMeta, 0, maxDepth, linkFields);
|
|
129
|
+
const queryName = doctypeToSingleQuery(meta.name);
|
|
130
|
+
const query = `query($id: UUID!) { ${queryName}(id: $id) { ${fieldSelection} } }`;
|
|
131
|
+
return { query, linkFields };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build a native PostGraphile query for fetching multiple records.
|
|
135
|
+
* @public
|
|
136
|
+
*/
|
|
137
|
+
export function buildListRecordQuery(meta, options) {
|
|
138
|
+
const maxDepth = options.maxDepth ?? 1;
|
|
139
|
+
const linkFields = [];
|
|
140
|
+
const fieldSelection = buildFieldSelection(meta, options.allMeta, 0, maxDepth, linkFields);
|
|
141
|
+
const queryName = doctypeToListQuery(meta.name);
|
|
142
|
+
const params = [];
|
|
143
|
+
const args = [];
|
|
144
|
+
if (options.first !== undefined) {
|
|
145
|
+
params.push('$first: Int');
|
|
146
|
+
args.push('first: $first');
|
|
147
|
+
}
|
|
148
|
+
if (options.offset !== undefined) {
|
|
149
|
+
params.push('$offset: Int');
|
|
150
|
+
args.push('offset: $offset');
|
|
151
|
+
}
|
|
152
|
+
if (options.orderBy) {
|
|
153
|
+
params.push('$orderBy: [SalesOrdersOrderBy!]');
|
|
154
|
+
args.push('orderBy: $orderBy');
|
|
155
|
+
}
|
|
156
|
+
if (options.condition) {
|
|
157
|
+
params.push('$condition: SalesOrderCondition');
|
|
158
|
+
args.push('condition: $condition');
|
|
159
|
+
}
|
|
160
|
+
const paramStr = params.length > 0 ? `(${params.join(', ')})` : '';
|
|
161
|
+
const argStr = args.length > 0 ? `(${args.join(', ')})` : '';
|
|
162
|
+
const query = `query${paramStr} { ${queryName}${argStr} { nodes { ${fieldSelection} } } }`;
|
|
163
|
+
return { query, linkFields };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Transform a record fetched via native PostGraphile query to the flat format
|
|
167
|
+
* expected by the Stonecrop client. Link fields become objects with `id` and `displayText`.
|
|
168
|
+
* @public
|
|
169
|
+
*/
|
|
170
|
+
export function transformNativeRecord(record, linkFields, meta, allMeta) {
|
|
171
|
+
const result = {};
|
|
172
|
+
for (const [key, value] of Object.entries(record)) {
|
|
173
|
+
if (key.includes('By')) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (linkFields.includes(key)) {
|
|
177
|
+
const linkField = valueFieldNamed(meta.fields, key);
|
|
178
|
+
if (linkField?.doctype) {
|
|
179
|
+
const targetMeta = resolveDoctype(linkField.doctype, allMeta);
|
|
180
|
+
if (targetMeta?.displayField) {
|
|
181
|
+
const relationshipName = buildRelationshipName(targetMeta.name, key);
|
|
182
|
+
const nestedRaw = record[relationshipName];
|
|
183
|
+
const displayText = nestedRaw !== null && nestedRaw !== undefined && typeof nestedRaw === 'object'
|
|
184
|
+
? Reflect.get(nestedRaw, targetMeta.displayField)
|
|
185
|
+
: undefined;
|
|
186
|
+
if (displayText !== undefined && displayText !== null && displayText !== '') {
|
|
187
|
+
result[key] = {
|
|
188
|
+
id: value,
|
|
189
|
+
displayText,
|
|
190
|
+
};
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
result[key] = value;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/graphql-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "GraphQL client integration for Stonecrop",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/agritheory/stonecrop/issues"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"src/*"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@stonecrop/schema": "0.
|
|
33
|
+
"@stonecrop/schema": "0.21.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@microsoft/api-documenter": "^7.30.5",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"vitest": "^4.1.5",
|
|
49
49
|
"vue": "^3.5.33",
|
|
50
50
|
"vue-router": "^5.0.6",
|
|
51
|
-
"stonecrop-
|
|
52
|
-
"
|
|
51
|
+
"@stonecrop/graphql-middleware": "0.21.0",
|
|
52
|
+
"stonecrop-rig": "0.7.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=24.0.0"
|
package/src/client.ts
CHANGED
|
@@ -9,6 +9,13 @@ import type {
|
|
|
9
9
|
} from '@stonecrop/schema'
|
|
10
10
|
import type { GetRecordResult } from './types'
|
|
11
11
|
import { GET_META_QUERY, GET_ALL_META_QUERY, RUN_ACTION_MUTATION } from './queries'
|
|
12
|
+
import {
|
|
13
|
+
buildSingleRecordQuery,
|
|
14
|
+
buildListRecordQuery,
|
|
15
|
+
transformNativeRecord,
|
|
16
|
+
doctypeToSingleQuery,
|
|
17
|
+
doctypeToListQuery,
|
|
18
|
+
} from './query-builder'
|
|
12
19
|
|
|
13
20
|
export type { DoctypeContext, DoctypeRef }
|
|
14
21
|
export type { GetRecordResult, GetRecordsResult }
|
|
@@ -27,8 +34,8 @@ export interface StonecropClientOptions {
|
|
|
27
34
|
/**
|
|
28
35
|
* Client for interacting with Stonecrop GraphQL API.
|
|
29
36
|
*
|
|
30
|
-
* Acts as a transport layer
|
|
31
|
-
*
|
|
37
|
+
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction, and
|
|
38
|
+
* builds native PostGraphile queries for getNativeRecord/getNativeRecords.
|
|
32
39
|
*
|
|
33
40
|
* @public
|
|
34
41
|
*/
|
|
@@ -200,6 +207,87 @@ export class StonecropClient implements DataClient {
|
|
|
200
207
|
return count == null ? { data, hasMore } : { data, hasMore, count }
|
|
201
208
|
}
|
|
202
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Get a single record by ID using PostGraphile's native query with relationship expansion.
|
|
212
|
+
*
|
|
213
|
+
* Unlike `getRecord()` which uses the `stonecropRecord` resolver returning a JSON blob,
|
|
214
|
+
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
215
|
+
* resolution for efficient single-query fetches with JOINs.
|
|
216
|
+
*
|
|
217
|
+
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
218
|
+
* resolved from the target doctype's `displayField`.
|
|
219
|
+
*
|
|
220
|
+
* @param doctype - Doctype reference (name and optional slug)
|
|
221
|
+
* @param recordId - Record ID to fetch
|
|
222
|
+
*/
|
|
223
|
+
async getNativeRecord(doctype: DoctypeRef, recordId: string): Promise<GetRecordResult> {
|
|
224
|
+
const allMeta = await this.getAllMeta()
|
|
225
|
+
const meta = allMeta.find(m => m.name === doctype.name || m.slug === doctype.name)
|
|
226
|
+
|
|
227
|
+
if (!meta) {
|
|
228
|
+
return { record: null }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const { query, linkFields } = buildSingleRecordQuery(meta, { allMeta })
|
|
232
|
+
const queryName = doctypeToSingleQuery(meta.name)
|
|
233
|
+
|
|
234
|
+
const result = await this.query<Record<string, Record<string, unknown> | null>>(query, { id: recordId })
|
|
235
|
+
|
|
236
|
+
const rawRecord = result[queryName]
|
|
237
|
+
if (!rawRecord) {
|
|
238
|
+
return { record: null }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const record = transformNativeRecord(rawRecord, linkFields, meta, allMeta)
|
|
242
|
+
return { record }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get multiple records using PostGraphile's native query with relationship expansion.
|
|
247
|
+
*
|
|
248
|
+
* Unlike `getRecords()` which uses the `stonecropRecords` resolver returning JSON blobs,
|
|
249
|
+
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
250
|
+
* resolution for efficient single-query fetches with JOINs.
|
|
251
|
+
*
|
|
252
|
+
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
253
|
+
* resolved from the target doctype's `displayField`.
|
|
254
|
+
*
|
|
255
|
+
* @param doctype - Doctype reference (name and optional slug)
|
|
256
|
+
* @param options - Query options (limit, offset)
|
|
257
|
+
*/
|
|
258
|
+
async getNativeRecords(
|
|
259
|
+
doctype: DoctypeRef,
|
|
260
|
+
options?: { limit?: number; offset?: number }
|
|
261
|
+
): Promise<GetRecordsResult> {
|
|
262
|
+
const allMeta = await this.getAllMeta()
|
|
263
|
+
const meta = allMeta.find(m => m.name === doctype.name || m.slug === doctype.name)
|
|
264
|
+
|
|
265
|
+
if (!meta) {
|
|
266
|
+
return { data: [], hasMore: false }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { query, linkFields } = buildListRecordQuery(meta, {
|
|
270
|
+
allMeta,
|
|
271
|
+
first: options?.limit,
|
|
272
|
+
offset: options?.offset,
|
|
273
|
+
})
|
|
274
|
+
const queryName = doctypeToListQuery(meta.name)
|
|
275
|
+
|
|
276
|
+
const variables: Record<string, unknown> = {}
|
|
277
|
+
if (options?.limit !== undefined) variables.first = options.limit
|
|
278
|
+
if (options?.offset !== undefined) variables.offset = options.offset
|
|
279
|
+
|
|
280
|
+
const result = await this.query<Record<string, { nodes: Record<string, unknown>[] }>>(query, variables)
|
|
281
|
+
|
|
282
|
+
const nodes = result[queryName]?.nodes ?? []
|
|
283
|
+
const data = nodes.map(node => transformNativeRecord(node, linkFields, meta, allMeta))
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
data,
|
|
287
|
+
hasMore: nodes.length === options?.limit,
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
203
291
|
/**
|
|
204
292
|
* Execute a doctype action
|
|
205
293
|
* @param doctype - Doctype reference (name and optional slug)
|