@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/README.md
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
# @stonecrop/graphql-client
|
|
2
2
|
|
|
3
|
-
Transport layer for Stonecrop GraphQL APIs. Handles HTTP communication, response parsing, and metadata caching.
|
|
3
|
+
Transport layer for Stonecrop GraphQL APIs. Handles HTTP communication, response parsing, and metadata caching.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Why transport-only?
|
|
6
6
|
|
|
7
|
-
This client
|
|
7
|
+
This client intentionally never constructs GraphQL queries. All query generation — including PostGraphile-specific field naming (inflection), nested link sub-selections, link display enrichment, and fetch strategy dispatch — lives in the server-side middleware (`@stonecrop/graphql-middleware`).
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
These methods use `stonecropRecord` and `stonecropRecords` resolvers which return JSON blobs. Query construction stays server-side. Use this when you don't need display text for linked records, or when you're using the client-side `aformLinkResolver` fallback.
|
|
12
|
-
|
|
13
|
-
### Native path (`getNativeRecord`, `getNativeRecords`)
|
|
14
|
-
|
|
15
|
-
These methods build native PostGraphile queries with nested selections for link fields:
|
|
16
|
-
|
|
17
|
-
```graphql
|
|
18
|
-
query { salesOrderById(id: $id) { id customerId partyByCustomerId { id partyName } } }
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
PostGraphile resolves relationships via JOINs in a single database query. Link fields are returned as `{ id, displayText }` objects where `displayText` comes from the target doctype's `displayField`. Use this when you need link display text without N+1 queries.
|
|
9
|
+
This boundary exists because PostGraphile's schema naming is configurable. An application might use `ById` for UUID primary keys, `ByRowId` for `row_id` columns, or entirely custom conventions. If the client hardcoded any of these conventions, it would silently produce wrong queries for non-default setups. The middleware owns the single `StonecropInflectionConfig` — the client only knows how to pass `options.includeNested` through to the `stonecropRecord` resolver and receive pre-merged flat data.
|
|
22
10
|
|
|
23
11
|
## Responsibilities
|
|
24
12
|
|
|
@@ -54,17 +42,15 @@ const client = new StonecropClient({
|
|
|
54
42
|
headers: { Authorization: `Bearer ${token}` }, // optional
|
|
55
43
|
})
|
|
56
44
|
|
|
57
|
-
//
|
|
45
|
+
// Fetch a record
|
|
58
46
|
const result = await client.getRecord({ name: 'SalesOrder' }, 'so-1')
|
|
59
|
-
result.record //
|
|
47
|
+
result.record // plain object with the record fields
|
|
48
|
+
result.unknownLinks // links requested but not found in schema
|
|
60
49
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Native list query
|
|
66
|
-
const list = await client.getNativeRecords({ name: 'SalesOrder' }, { limit: 50 })
|
|
67
|
-
list.data // Array of records with link fields as { id, displayText } objects
|
|
50
|
+
// Fetch with nested links
|
|
51
|
+
const withNested = await client.getRecord({ name: 'SalesOrder' }, 'so-1', {
|
|
52
|
+
includeNested: true,
|
|
53
|
+
})
|
|
68
54
|
|
|
69
55
|
// Custom queries
|
|
70
56
|
const custom = await client.query<{ myData: unknown[] }>(`query { myData { id } }`)
|
|
@@ -72,14 +58,8 @@ const custom = await client.query<{ myData: unknown[] }>(`query { myData { id }
|
|
|
72
58
|
|
|
73
59
|
## Data Shapes
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- `
|
|
78
|
-
- `getRecords` returns `{ data: Record<string, unknown>[], hasMore: boolean, count?: number }` — flat objects with scalar FK values.
|
|
79
|
-
|
|
80
|
-
### Native methods
|
|
81
|
-
|
|
82
|
-
- `getNativeRecord` returns `{ record: Record<string, unknown> | null }`. Link fields are `{ id, displayText }` objects where `displayText` is resolved from the target doctype's `displayField`.
|
|
83
|
-
- `getNativeRecords` returns `{ data: Record<string, unknown>[], hasMore: boolean }`. Each record has link fields as `{ id, displayText }` objects.
|
|
61
|
+
- `getRecord` returns `{ record: Record<string, unknown> | null, unknownLinks?: string[] }`. The `record` field contains the record's fields. Nested links are merged into the same object when `includeNested` is used. Inline link fields are enriched by the middleware as `{ id, displayText }` objects where `displayText` comes from the target doctype's `displayField`.
|
|
62
|
+
- `getRecords` returns `{ data: Record<string, unknown>[], hasMore: boolean, count?: number }` — flat objects with the same inline link enrichment.
|
|
63
|
+
- `unknownLinks` will contain link names you requested that don't exist in the doctype schema — useful for catching typos.
|
|
84
64
|
|
|
85
|
-
See [API Reference](./api.md) for full method signatures.
|
|
65
|
+
See [API Reference](./api.md) for full method signatures.
|
package/dist/graphql-client.d.ts
CHANGED
|
@@ -7,84 +7,10 @@ import type { GetRecordResult as GetRecordResult_2 } from '@stonecrop/schema';
|
|
|
7
7
|
import type { GetRecordsOptions } from '@stonecrop/schema';
|
|
8
8
|
import type { GetRecordsResult } from '@stonecrop/schema';
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Build a native PostGraphile query for fetching multiple records.
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
export declare function buildListRecordQuery(meta: DoctypeMeta, options: QueryBuilderOptions & {
|
|
15
|
-
first?: number;
|
|
16
|
-
offset?: number;
|
|
17
|
-
orderBy?: string;
|
|
18
|
-
condition?: Record<string, unknown>;
|
|
19
|
-
}): BuiltQuery;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Build the PostGraphile relationship field name for a foreign key.
|
|
23
|
-
*
|
|
24
|
-
* PostGraphile names relationships as `targetTypeByFkField` in camelCase.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* buildRelationshipName('Party', 'customerId') // 'partyByCustomerId'
|
|
28
|
-
* buildRelationshipName('Company', 'companyId') // 'companyByCompanyId'
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
export declare function buildRelationshipName(targetDoctypeName: string, fkFieldname: string): string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Build a native PostGraphile query for fetching a single record by ID.
|
|
35
|
-
* @public
|
|
36
|
-
*/
|
|
37
|
-
export declare function buildSingleRecordQuery(meta: DoctypeMeta, options: QueryBuilderOptions): BuiltQuery;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Result of building a native query
|
|
41
|
-
* @public
|
|
42
|
-
*/
|
|
43
|
-
export declare interface BuiltQuery {
|
|
44
|
-
/**
|
|
45
|
-
* The GraphQL query string
|
|
46
|
-
*/
|
|
47
|
-
query: string;
|
|
48
|
-
/**
|
|
49
|
-
* Field names that are link fields with nested selections.
|
|
50
|
-
* The consumer can use this to know which fields will have relationship data.
|
|
51
|
-
*/
|
|
52
|
-
linkFields: string[];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
10
|
export { DoctypeContext }
|
|
56
11
|
|
|
57
12
|
export { DoctypeMeta }
|
|
58
13
|
|
|
59
|
-
/**
|
|
60
|
-
* Convert a PascalCase doctype name to the PostGraphile list query name.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* doctypeToListQuery('SalesOrder') // 'allSalesOrders'
|
|
64
|
-
* doctypeToListQuery('Party') // 'allParties'
|
|
65
|
-
* @public
|
|
66
|
-
*/
|
|
67
|
-
export declare function doctypeToListQuery(doctypeName: string): string;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Convert a PascalCase doctype name to the camelCase query name PostGraphile uses.
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* doctypeToQueryName('SalesOrder') // 'salesOrder'
|
|
74
|
-
* doctypeToQueryName('Party') // 'party'
|
|
75
|
-
* @public
|
|
76
|
-
*/
|
|
77
|
-
export declare function doctypeToQueryName(doctypeName: string): string;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Convert a PascalCase doctype name to the PostGraphile single-record query name.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* doctypeToSingleQuery('SalesOrder') // 'salesOrderById'
|
|
84
|
-
* @public
|
|
85
|
-
*/
|
|
86
|
-
export declare function doctypeToSingleQuery(doctypeName: string): string;
|
|
87
|
-
|
|
88
14
|
/**
|
|
89
15
|
* Result from getRecord - includes the record data and any unknown links requested
|
|
90
16
|
* @public
|
|
@@ -94,27 +20,10 @@ export declare interface GetRecordResult extends GetRecordResult_2 {
|
|
|
94
20
|
unknownLinks?: string[];
|
|
95
21
|
}
|
|
96
22
|
|
|
97
|
-
/**
|
|
98
|
-
* Options for building queries
|
|
99
|
-
* @public
|
|
100
|
-
*/
|
|
101
|
-
export declare interface QueryBuilderOptions {
|
|
102
|
-
/**
|
|
103
|
-
* All available doctype metadata. Used to resolve target doctypes for link fields.
|
|
104
|
-
*/
|
|
105
|
-
allMeta: DoctypeMeta[];
|
|
106
|
-
/**
|
|
107
|
-
* Maximum depth for nested link resolution. Defaults to 1 (immediate links only).
|
|
108
|
-
* Set to 0 to disable link expansion.
|
|
109
|
-
*/
|
|
110
|
-
maxDepth?: number;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
23
|
/**
|
|
114
24
|
* Client for interacting with Stonecrop GraphQL API.
|
|
115
25
|
*
|
|
116
|
-
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction
|
|
117
|
-
* builds native PostGraphile queries for getNativeRecord/getNativeRecords.
|
|
26
|
+
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction.
|
|
118
27
|
*
|
|
119
28
|
* @public
|
|
120
29
|
*/
|
|
@@ -168,37 +77,6 @@ export declare class StonecropClient implements DataClient {
|
|
|
168
77
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
169
78
|
*/
|
|
170
79
|
getRecords(doctype: DoctypeRef, options?: GetRecordsOptions): Promise<GetRecordsResult>;
|
|
171
|
-
/**
|
|
172
|
-
* Get a single record by ID using PostGraphile's native query with relationship expansion.
|
|
173
|
-
*
|
|
174
|
-
* Unlike `getRecord()` which uses the `stonecropRecord` resolver returning a JSON blob,
|
|
175
|
-
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
176
|
-
* resolution for efficient single-query fetches with JOINs.
|
|
177
|
-
*
|
|
178
|
-
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
179
|
-
* resolved from the target doctype's `displayField`.
|
|
180
|
-
*
|
|
181
|
-
* @param doctype - Doctype reference (name and optional slug)
|
|
182
|
-
* @param recordId - Record ID to fetch
|
|
183
|
-
*/
|
|
184
|
-
getNativeRecord(doctype: DoctypeRef, recordId: string): Promise<GetRecordResult>;
|
|
185
|
-
/**
|
|
186
|
-
* Get multiple records using PostGraphile's native query with relationship expansion.
|
|
187
|
-
*
|
|
188
|
-
* Unlike `getRecords()` which uses the `stonecropRecords` resolver returning JSON blobs,
|
|
189
|
-
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
190
|
-
* resolution for efficient single-query fetches with JOINs.
|
|
191
|
-
*
|
|
192
|
-
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
193
|
-
* resolved from the target doctype's `displayField`.
|
|
194
|
-
*
|
|
195
|
-
* @param doctype - Doctype reference (name and optional slug)
|
|
196
|
-
* @param options - Query options (limit, offset)
|
|
197
|
-
*/
|
|
198
|
-
getNativeRecords(doctype: DoctypeRef, options?: {
|
|
199
|
-
limit?: number;
|
|
200
|
-
offset?: number;
|
|
201
|
-
}): Promise<GetRecordsResult>;
|
|
202
80
|
/**
|
|
203
81
|
* Execute a doctype action
|
|
204
82
|
* @param doctype - Doctype reference (name and optional slug)
|
|
@@ -230,11 +108,4 @@ export declare interface StonecropClientOptions {
|
|
|
230
108
|
headers?: Record<string, string>;
|
|
231
109
|
}
|
|
232
110
|
|
|
233
|
-
/**
|
|
234
|
-
* Transform a record fetched via native PostGraphile query to the flat format
|
|
235
|
-
* expected by the Stonecrop client. Link fields become objects with `id` and `displayText`.
|
|
236
|
-
* @public
|
|
237
|
-
*/
|
|
238
|
-
export declare function transformNativeRecord(record: Record<string, unknown>, linkFields: string[], meta: DoctypeMeta, allMeta: DoctypeMeta[]): Record<string, unknown>;
|
|
239
|
-
|
|
240
111
|
export { }
|
package/dist/graphql-client.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const S = `
|
|
1
|
+
const s = `
|
|
3
2
|
query GetMeta($doctype: String!) {
|
|
4
3
|
stonecropMeta(doctype: $doctype) {
|
|
5
4
|
name
|
|
@@ -44,7 +43,7 @@ const S = `
|
|
|
44
43
|
inherits
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
|
-
`,
|
|
46
|
+
`, i = `
|
|
48
47
|
mutation RunAction($doctype: String!, $action: String!, $args: JSON) {
|
|
49
48
|
stonecropAction(doctype: $doctype, action: $action, args: $args) {
|
|
50
49
|
success
|
|
@@ -52,7 +51,7 @@ const S = `
|
|
|
52
51
|
error
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
|
-
`,
|
|
54
|
+
`, d = `
|
|
56
55
|
query GetAllMeta {
|
|
57
56
|
stonecropAllMeta {
|
|
58
57
|
name
|
|
@@ -98,79 +97,7 @@ const S = `
|
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
`;
|
|
101
|
-
|
|
102
|
-
return o[0].toLowerCase() + o.slice(1);
|
|
103
|
-
}
|
|
104
|
-
function h(o) {
|
|
105
|
-
return m(o) + "ById";
|
|
106
|
-
}
|
|
107
|
-
function $(o) {
|
|
108
|
-
const e = o;
|
|
109
|
-
return e.endsWith("y") ? "all" + e.slice(0, -1) + "ies" : "all" + e + "s";
|
|
110
|
-
}
|
|
111
|
-
function g(o, e) {
|
|
112
|
-
const t = m(o), n = e[0].toUpperCase() + e.slice(1);
|
|
113
|
-
return t + "By" + n;
|
|
114
|
-
}
|
|
115
|
-
function q(o, e) {
|
|
116
|
-
return e.find((t) => t.slug === o || t.name === o);
|
|
117
|
-
}
|
|
118
|
-
function R(o, e) {
|
|
119
|
-
for (const t of p(o))
|
|
120
|
-
if (t.kind === "field" && t.fieldname === e)
|
|
121
|
-
return t;
|
|
122
|
-
}
|
|
123
|
-
function w(o, e, t, n, i) {
|
|
124
|
-
const a = p(o.fields), s = [];
|
|
125
|
-
for (const r of a) {
|
|
126
|
-
if (r.kind !== "field") continue;
|
|
127
|
-
if (r.doctype && M(r.component) === "inline" && t < n) {
|
|
128
|
-
const c = q(r.doctype, e);
|
|
129
|
-
if (c) {
|
|
130
|
-
const l = g(c.name, r.fieldname), u = c.displayField;
|
|
131
|
-
u ? (s.push(r.fieldname), s.push(`${l} { id ${u} }`), i.push(r.fieldname)) : s.push(r.fieldname);
|
|
132
|
-
} else
|
|
133
|
-
s.push(r.fieldname);
|
|
134
|
-
} else
|
|
135
|
-
s.push(r.fieldname);
|
|
136
|
-
}
|
|
137
|
-
return s.join(" ");
|
|
138
|
-
}
|
|
139
|
-
function v(o, e) {
|
|
140
|
-
const t = e.maxDepth ?? 1, n = [], i = w(o, e.allMeta, 0, t, n);
|
|
141
|
-
return { query: `query($id: UUID!) { ${h(o.name)}(id: $id) { ${i} } }`, linkFields: n };
|
|
142
|
-
}
|
|
143
|
-
function F(o, e) {
|
|
144
|
-
const t = e.maxDepth ?? 1, n = [], i = w(o, e.allMeta, 0, t, n), a = $(o.name), s = [], r = [];
|
|
145
|
-
e.first !== void 0 && (s.push("$first: Int"), r.push("first: $first")), e.offset !== void 0 && (s.push("$offset: Int"), r.push("offset: $offset")), e.orderBy && (s.push("$orderBy: [SalesOrdersOrderBy!]"), r.push("orderBy: $orderBy")), e.condition && (s.push("$condition: SalesOrderCondition"), r.push("condition: $condition"));
|
|
146
|
-
const d = s.length > 0 ? `(${s.join(", ")})` : "", c = r.length > 0 ? `(${r.join(", ")})` : "";
|
|
147
|
-
return { query: `query${d} { ${a}${c} { nodes { ${i} } } }`, linkFields: n };
|
|
148
|
-
}
|
|
149
|
-
function y(o, e, t, n) {
|
|
150
|
-
const i = {};
|
|
151
|
-
for (const [a, s] of Object.entries(o))
|
|
152
|
-
if (!a.includes("By")) {
|
|
153
|
-
if (e.includes(a)) {
|
|
154
|
-
const r = R(t.fields, a);
|
|
155
|
-
if (r?.doctype) {
|
|
156
|
-
const d = q(r.doctype, n);
|
|
157
|
-
if (d?.displayField) {
|
|
158
|
-
const c = g(d.name, a), l = o[c], u = l != null && typeof l == "object" ? Reflect.get(l, d.displayField) : void 0;
|
|
159
|
-
if (u != null && u !== "") {
|
|
160
|
-
i[a] = {
|
|
161
|
-
id: s,
|
|
162
|
-
displayText: u
|
|
163
|
-
};
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
i[a] = s;
|
|
170
|
-
}
|
|
171
|
-
return i;
|
|
172
|
-
}
|
|
173
|
-
class A {
|
|
100
|
+
class l {
|
|
174
101
|
endpoint;
|
|
175
102
|
headers;
|
|
176
103
|
metaCache = /* @__PURE__ */ new Map();
|
|
@@ -188,16 +115,16 @@ class A {
|
|
|
188
115
|
* @throws Error if the GraphQL response contains errors
|
|
189
116
|
*/
|
|
190
117
|
async query(e, t) {
|
|
191
|
-
const
|
|
118
|
+
const n = await (await fetch(this.endpoint, {
|
|
192
119
|
method: "POST",
|
|
193
120
|
headers: this.headers,
|
|
194
121
|
body: JSON.stringify({ query: e, variables: t })
|
|
195
122
|
})).json();
|
|
196
|
-
if (
|
|
197
|
-
throw new Error(
|
|
198
|
-
if (
|
|
123
|
+
if (n.errors?.length)
|
|
124
|
+
throw new Error(n.errors[0].message);
|
|
125
|
+
if (n.data === void 0)
|
|
199
126
|
throw new Error("GraphQL response missing data field");
|
|
200
|
-
return
|
|
127
|
+
return n.data;
|
|
201
128
|
}
|
|
202
129
|
/**
|
|
203
130
|
* Execute a GraphQL mutation. Delegates to query() since both use POST.
|
|
@@ -215,16 +142,16 @@ class A {
|
|
|
215
142
|
async getMeta(e) {
|
|
216
143
|
const t = this.metaCache.get(e.doctype);
|
|
217
144
|
if (t) return t;
|
|
218
|
-
const
|
|
145
|
+
const o = await this.query(s, {
|
|
219
146
|
doctype: e.doctype
|
|
220
147
|
});
|
|
221
|
-
return
|
|
148
|
+
return o.stonecropMeta && this.metaCache.set(e.doctype, o.stonecropMeta), o.stonecropMeta;
|
|
222
149
|
}
|
|
223
150
|
/**
|
|
224
151
|
* Get all doctype metadata
|
|
225
152
|
*/
|
|
226
153
|
async getAllMeta() {
|
|
227
|
-
const e = await this.query(
|
|
154
|
+
const e = await this.query(d);
|
|
228
155
|
for (const t of e.stonecropAllMeta)
|
|
229
156
|
this.metaCache.set(t.name, t);
|
|
230
157
|
return e.stonecropAllMeta;
|
|
@@ -239,8 +166,8 @@ class A {
|
|
|
239
166
|
* @param recordId - Record ID to fetch
|
|
240
167
|
* @param options - Query options (includeNested, maxDepth)
|
|
241
168
|
*/
|
|
242
|
-
async getRecord(e, t,
|
|
243
|
-
const
|
|
169
|
+
async getRecord(e, t, o) {
|
|
170
|
+
const n = await this.query(
|
|
244
171
|
`query GetRecord($doctype: String!, $id: String!, $options: JSON) {
|
|
245
172
|
stonecropRecord(doctype: $doctype, id: $id, options: $options) {
|
|
246
173
|
data
|
|
@@ -250,15 +177,15 @@ class A {
|
|
|
250
177
|
{
|
|
251
178
|
doctype: e.name,
|
|
252
179
|
id: t,
|
|
253
|
-
options:
|
|
254
|
-
includeNested:
|
|
255
|
-
maxDepth:
|
|
180
|
+
options: o?.includeNested ? {
|
|
181
|
+
includeNested: o.includeNested,
|
|
182
|
+
maxDepth: o.maxDepth
|
|
256
183
|
} : void 0
|
|
257
184
|
}
|
|
258
185
|
);
|
|
259
186
|
return {
|
|
260
|
-
record:
|
|
261
|
-
unknownLinks:
|
|
187
|
+
record: n.stonecropRecord?.data ?? null,
|
|
188
|
+
unknownLinks: n.stonecropRecord?.unknownLinks
|
|
262
189
|
};
|
|
263
190
|
}
|
|
264
191
|
/**
|
|
@@ -271,7 +198,7 @@ class A {
|
|
|
271
198
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
272
199
|
*/
|
|
273
200
|
async getRecords(e, t) {
|
|
274
|
-
const
|
|
201
|
+
const o = await this.query(
|
|
275
202
|
`
|
|
276
203
|
query GetRecords(
|
|
277
204
|
$doctype: String!
|
|
@@ -299,57 +226,8 @@ class A {
|
|
|
299
226
|
doctype: e.name,
|
|
300
227
|
...t
|
|
301
228
|
}
|
|
302
|
-
), { data:
|
|
303
|
-
return
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Get a single record by ID using PostGraphile's native query with relationship expansion.
|
|
307
|
-
*
|
|
308
|
-
* Unlike `getRecord()` which uses the `stonecropRecord` resolver returning a JSON blob,
|
|
309
|
-
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
310
|
-
* resolution for efficient single-query fetches with JOINs.
|
|
311
|
-
*
|
|
312
|
-
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
313
|
-
* resolved from the target doctype's `displayField`.
|
|
314
|
-
*
|
|
315
|
-
* @param doctype - Doctype reference (name and optional slug)
|
|
316
|
-
* @param recordId - Record ID to fetch
|
|
317
|
-
*/
|
|
318
|
-
async getNativeRecord(e, t) {
|
|
319
|
-
const n = await this.getAllMeta(), i = n.find((u) => u.name === e.name || u.slug === e.name);
|
|
320
|
-
if (!i)
|
|
321
|
-
return { record: null };
|
|
322
|
-
const { query: a, linkFields: s } = v(i, { allMeta: n }), r = h(i.name), c = (await this.query(a, { id: t }))[r];
|
|
323
|
-
return c ? { record: y(c, s, i, n) } : { record: null };
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Get multiple records using PostGraphile's native query with relationship expansion.
|
|
327
|
-
*
|
|
328
|
-
* Unlike `getRecords()` which uses the `stonecropRecords` resolver returning JSON blobs,
|
|
329
|
-
* this method builds a native PostGraphile query that leverages the ORM's relationship
|
|
330
|
-
* resolution for efficient single-query fetches with JOINs.
|
|
331
|
-
*
|
|
332
|
-
* Link fields are returned as `{ id, displayText }` objects where `displayText` is
|
|
333
|
-
* resolved from the target doctype's `displayField`.
|
|
334
|
-
*
|
|
335
|
-
* @param doctype - Doctype reference (name and optional slug)
|
|
336
|
-
* @param options - Query options (limit, offset)
|
|
337
|
-
*/
|
|
338
|
-
async getNativeRecords(e, t) {
|
|
339
|
-
const n = await this.getAllMeta(), i = n.find((f) => f.name === e.name || f.slug === e.name);
|
|
340
|
-
if (!i)
|
|
341
|
-
return { data: [], hasMore: !1 };
|
|
342
|
-
const { query: a, linkFields: s } = F(i, {
|
|
343
|
-
allMeta: n,
|
|
344
|
-
first: t?.limit,
|
|
345
|
-
offset: t?.offset
|
|
346
|
-
}), r = $(i.name), d = {};
|
|
347
|
-
t?.limit !== void 0 && (d.first = t.limit), t?.offset !== void 0 && (d.offset = t.offset);
|
|
348
|
-
const l = (await this.query(a, d))[r]?.nodes ?? [];
|
|
349
|
-
return {
|
|
350
|
-
data: l.map((f) => y(f, s, i, n)),
|
|
351
|
-
hasMore: l.length === t?.limit
|
|
352
|
-
};
|
|
229
|
+
), { data: n, hasMore: a, count: r } = o.stonecropRecords;
|
|
230
|
+
return r == null ? { data: n, hasMore: a } : { data: n, hasMore: a, count: r };
|
|
353
231
|
}
|
|
354
232
|
/**
|
|
355
233
|
* Execute a doctype action
|
|
@@ -357,11 +235,11 @@ class A {
|
|
|
357
235
|
* @param action - Action name to execute
|
|
358
236
|
* @param args - Action arguments
|
|
359
237
|
*/
|
|
360
|
-
async runAction(e, t,
|
|
361
|
-
return (await this.query(
|
|
238
|
+
async runAction(e, t, o) {
|
|
239
|
+
return (await this.query(i, {
|
|
362
240
|
doctype: e.name,
|
|
363
241
|
action: t,
|
|
364
|
-
args:
|
|
242
|
+
args: o
|
|
365
243
|
})).stonecropAction;
|
|
366
244
|
}
|
|
367
245
|
/**
|
|
@@ -375,13 +253,6 @@ class A {
|
|
|
375
253
|
}
|
|
376
254
|
}
|
|
377
255
|
export {
|
|
378
|
-
|
|
379
|
-
F as buildListRecordQuery,
|
|
380
|
-
g as buildRelationshipName,
|
|
381
|
-
v as buildSingleRecordQuery,
|
|
382
|
-
$ as doctypeToListQuery,
|
|
383
|
-
m as doctypeToQueryName,
|
|
384
|
-
h as doctypeToSingleQuery,
|
|
385
|
-
y as transformNativeRecord
|
|
256
|
+
l as StonecropClient
|
|
386
257
|
};
|
|
387
258
|
//# sourceMappingURL=graphql-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphql-client.js","sources":["../src/queries.ts","../src/query-builder.ts","../src/client.ts"],"sourcesContent":["/**\n * GraphQL query documents sent by {@link StonecropClient} to the middleware.\n *\n * These are the client's half of the wire contract with `@stonecrop/graphql-middleware`.\n * They live here as exported constants (rather than inline in the client methods) so the\n * cross-package contract test can validate the exact strings the client sends against the\n * middleware's published SDL — a field the server drops while a query still selects it must\n * fail CI, not production.\n *\n * @public\n */\nexport 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`\n\n/**\n * Mutation document for dispatching a workflow action (the server-owned transition).\n * @public\n */\nexport const RUN_ACTION_MUTATION = `\n\tmutation RunAction($doctype: String!, $action: String!, $args: JSON) {\n\t\tstonecropAction(doctype: $doctype, action: $action, args: $args) {\n\t\t\tsuccess\n\t\t\tdata\n\t\t\terror\n\t\t}\n\t}\n`\n\n/**\n * Query document for fetching all doctype metadata.\n * @public\n */\nexport 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`\n","/**\n * Query builder for constructing PostGraphile native queries with nested link selections.\n *\n * Instead of using the `stonecropRecord`/`stonecropRecords` resolvers which return JSON blobs,\n * this module builds native PostGraphile queries that leverage the ORM's relationship resolution\n * for efficient single-query fetches with JOINs.\n *\n * @example\n * ```ts\n * // Instead of:\n * // stonecropRecord(doctype: \"SalesOrder\", id: \"...\") { data }\n * // Which returns { customerId: \"uuid\" }\n *\n * // We generate:\n * // salesOrderById(id: \"...\") { id, customerId, partyByCustomerId { id, partyName } }\n * // Which returns { customerId: \"uuid\", partyByCustomerId: { id: \"uuid\", partyName: \"Acme\" } }\n * ```\n *\n * @public\n */\n\nimport type { DoctypeMeta, ValueField } from '@stonecrop/schema'\nimport { flattenFields, componentLinkExpansion } from '@stonecrop/schema'\n\n/**\n * Options for building queries\n * @public\n */\nexport interface QueryBuilderOptions {\n\t/**\n\t * All available doctype metadata. Used to resolve target doctypes for link fields.\n\t */\n\tallMeta: DoctypeMeta[]\n\n\t/**\n\t * Maximum depth for nested link resolution. Defaults to 1 (immediate links only).\n\t * Set to 0 to disable link expansion.\n\t */\n\tmaxDepth?: number\n}\n\n/**\n * Result of building a native query\n * @public\n */\nexport interface BuiltQuery {\n\t/**\n\t * The GraphQL query string\n\t */\n\tquery: string\n\n\t/**\n\t * Field names that are link fields with nested selections.\n\t * The consumer can use this to know which fields will have relationship data.\n\t */\n\tlinkFields: string[]\n}\n\n/**\n * Convert a PascalCase doctype name to the camelCase query name PostGraphile uses.\n *\n * @example\n * doctypeToQueryName('SalesOrder') // 'salesOrder'\n * doctypeToQueryName('Party') // 'party'\n * @public\n */\nexport function doctypeToQueryName(doctypeName: string): string {\n\treturn doctypeName[0].toLowerCase() + doctypeName.slice(1)\n}\n\n/**\n * Convert a PascalCase doctype name to the PostGraphile single-record query name.\n *\n * @example\n * doctypeToSingleQuery('SalesOrder') // 'salesOrderById'\n * @public\n */\nexport function doctypeToSingleQuery(doctypeName: string): string {\n\treturn doctypeToQueryName(doctypeName) + 'ById'\n}\n\n/**\n * Convert a PascalCase doctype name to the PostGraphile list query name.\n *\n * @example\n * doctypeToListQuery('SalesOrder') // 'allSalesOrders'\n * doctypeToListQuery('Party') // 'allParties'\n * @public\n */\nexport function doctypeToListQuery(doctypeName: string): string {\n\tconst name = doctypeName\n\tif (name.endsWith('y')) {\n\t\treturn 'all' + name.slice(0, -1) + 'ies'\n\t}\n\treturn 'all' + name + 's'\n}\n\n/**\n * Build the PostGraphile relationship field name for a foreign key.\n *\n * PostGraphile names relationships as `targetTypeByFkField` in camelCase.\n *\n * @example\n * buildRelationshipName('Party', 'customerId') // 'partyByCustomerId'\n * buildRelationshipName('Company', 'companyId') // 'companyByCompanyId'\n * @public\n */\nexport function buildRelationshipName(targetDoctypeName: string, fkFieldname: string): string {\n\tconst prefix = doctypeToQueryName(targetDoctypeName)\n\tconst suffix = fkFieldname[0].toUpperCase() + fkFieldname.slice(1)\n\treturn prefix + 'By' + suffix\n}\n\n/**\n * Resolve a doctype slug to its metadata.\n */\nfunction resolveDoctype(slug: string, allMeta: DoctypeMeta[]): DoctypeMeta | undefined {\n\treturn allMeta.find(m => m.slug === slug || m.name === slug)\n}\n\nfunction valueFieldNamed(fields: DoctypeMeta['fields'], fieldname: string): ValueField | undefined {\n\tfor (const field of flattenFields(fields)) {\n\t\tif (field.kind === 'field' && field.fieldname === fieldname) {\n\t\t\treturn field\n\t\t}\n\t}\n\treturn undefined\n}\n\n/**\n * Build the field selection for a doctype, including nested selections for link fields.\n */\nfunction buildFieldSelection(\n\tmeta: DoctypeMeta,\n\tallMeta: DoctypeMeta[],\n\tdepth: number,\n\tmaxDepth: number,\n\tlinkFieldsOut: string[]\n): string {\n\tconst flatFields = flattenFields(meta.fields)\n\tconst selections: string[] = []\n\n\tfor (const field of flatFields) {\n\t\tif (field.kind !== 'field') continue\n\n\t\tconst isLinkField = field.doctype && componentLinkExpansion(field.component) === 'inline'\n\n\t\tif (isLinkField && depth < maxDepth) {\n\t\t\tconst targetMeta = resolveDoctype(field.doctype!, allMeta)\n\t\t\tif (targetMeta) {\n\t\t\t\tconst relationshipName = buildRelationshipName(targetMeta.name, field.fieldname)\n\t\t\t\tconst displayField = targetMeta.displayField\n\n\t\t\t\tif (displayField) {\n\t\t\t\t\tselections.push(field.fieldname)\n\t\t\t\t\tselections.push(`${relationshipName} { id ${displayField} }`)\n\t\t\t\t\tlinkFieldsOut.push(field.fieldname)\n\t\t\t\t} else {\n\t\t\t\t\tselections.push(field.fieldname)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tselections.push(field.fieldname)\n\t\t\t}\n\t\t} else {\n\t\t\tselections.push(field.fieldname)\n\t\t}\n\t}\n\n\treturn selections.join(' ')\n}\n\n/**\n * Build a native PostGraphile query for fetching a single record by ID.\n * @public\n */\nexport function buildSingleRecordQuery(meta: DoctypeMeta, options: QueryBuilderOptions): BuiltQuery {\n\tconst maxDepth = options.maxDepth ?? 1\n\tconst linkFields: string[] = []\n\n\tconst fieldSelection = buildFieldSelection(meta, options.allMeta, 0, maxDepth, linkFields)\n\tconst queryName = doctypeToSingleQuery(meta.name)\n\n\tconst query = `query($id: UUID!) { ${queryName}(id: $id) { ${fieldSelection} } }`\n\n\treturn { query, linkFields }\n}\n\n/**\n * Build a native PostGraphile query for fetching multiple records.\n * @public\n */\nexport function buildListRecordQuery(\n\tmeta: DoctypeMeta,\n\toptions: QueryBuilderOptions & {\n\t\tfirst?: number\n\t\toffset?: number\n\t\torderBy?: string\n\t\tcondition?: Record<string, unknown>\n\t}\n): BuiltQuery {\n\tconst maxDepth = options.maxDepth ?? 1\n\tconst linkFields: string[] = []\n\n\tconst fieldSelection = buildFieldSelection(meta, options.allMeta, 0, maxDepth, linkFields)\n\tconst queryName = doctypeToListQuery(meta.name)\n\n\tconst params: string[] = []\n\tconst args: string[] = []\n\n\tif (options.first !== undefined) {\n\t\tparams.push('$first: Int')\n\t\targs.push('first: $first')\n\t}\n\tif (options.offset !== undefined) {\n\t\tparams.push('$offset: Int')\n\t\targs.push('offset: $offset')\n\t}\n\tif (options.orderBy) {\n\t\tparams.push('$orderBy: [SalesOrdersOrderBy!]')\n\t\targs.push('orderBy: $orderBy')\n\t}\n\tif (options.condition) {\n\t\tparams.push('$condition: SalesOrderCondition')\n\t\targs.push('condition: $condition')\n\t}\n\n\tconst paramStr = params.length > 0 ? `(${params.join(', ')})` : ''\n\tconst argStr = args.length > 0 ? `(${args.join(', ')})` : ''\n\n\tconst query = `query${paramStr} { ${queryName}${argStr} { nodes { ${fieldSelection} } } }`\n\n\treturn { query, linkFields }\n}\n\n/**\n * Transform a record fetched via native PostGraphile query to the flat format\n * expected by the Stonecrop client. Link fields become objects with `id` and `displayText`.\n * @public\n */\nexport function transformNativeRecord(\n\trecord: Record<string, unknown>,\n\tlinkFields: string[],\n\tmeta: DoctypeMeta,\n\tallMeta: DoctypeMeta[]\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {}\n\n\tfor (const [key, value] of Object.entries(record)) {\n\t\tif (key.includes('By')) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif (linkFields.includes(key)) {\n\t\t\tconst linkField = valueFieldNamed(meta.fields, key)\n\t\t\tif (linkField?.doctype) {\n\t\t\t\tconst targetMeta = resolveDoctype(linkField.doctype, allMeta)\n\t\t\t\tif (targetMeta?.displayField) {\n\t\t\t\t\tconst relationshipName = buildRelationshipName(targetMeta.name, key)\n\t\t\t\t\tconst nestedRaw = record[relationshipName]\n\t\t\t\t\tconst displayText =\n\t\t\t\t\t\tnestedRaw !== null && nestedRaw !== undefined && typeof nestedRaw === 'object'\n\t\t\t\t\t\t\t? Reflect.get(nestedRaw, targetMeta.displayField)\n\t\t\t\t\t\t\t: undefined\n\n\t\t\t\t\tif (displayText !== undefined && displayText !== null && displayText !== '') {\n\t\t\t\t\t\tresult[key] = {\n\t\t\t\t\t\t\tid: value,\n\t\t\t\t\t\t\tdisplayText,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult[key] = value\n\t}\n\n\treturn result\n}\n","import type {\n\tDataClient,\n\tDoctypeMeta,\n\tDoctypeContext,\n\tDoctypeRef,\n\tGetRecordOptions,\n\tGetRecordsOptions,\n\tGetRecordsResult,\n} from '@stonecrop/schema'\nimport type { GetRecordResult } from './types'\nimport { GET_META_QUERY, GET_ALL_META_QUERY, RUN_ACTION_MUTATION } from './queries'\nimport {\n\tbuildSingleRecordQuery,\n\tbuildListRecordQuery,\n\ttransformNativeRecord,\n\tdoctypeToSingleQuery,\n\tdoctypeToListQuery,\n} from './query-builder'\n\nexport type { DoctypeContext, DoctypeRef }\nexport type { GetRecordResult, GetRecordsResult }\n\n/**\n * Options for creating a Stonecrop client\n * @public\n */\nexport interface StonecropClientOptions {\n\t/** GraphQL endpoint URL */\n\tendpoint: string\n\t/** Additional HTTP headers to include in requests */\n\theaders?: Record<string, string>\n}\n\n/**\n * Client for interacting with Stonecrop GraphQL API.\n *\n * Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction, and\n * builds native PostGraphile queries for getNativeRecord/getNativeRecords.\n *\n * @public\n */\nexport class StonecropClient implements DataClient {\n\tprivate endpoint: string\n\tprivate headers: Record<string, string>\n\tprivate metaCache: Map<string, DoctypeMeta> = new Map()\n\n\tconstructor(options: StonecropClientOptions) {\n\t\tthis.endpoint = options.endpoint\n\t\tthis.headers = {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t...options.headers,\n\t\t}\n\t}\n\n\t/**\n\t * Execute a GraphQL query against the configured endpoint.\n\t *\n\t * @param query - GraphQL query string\n\t * @param variables - Query variables\n\t * @throws Error if the GraphQL response contains errors\n\t */\n\tasync query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T> {\n\t\tconst response = await fetch(this.endpoint, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: this.headers,\n\t\t\tbody: JSON.stringify({ query, variables }),\n\t\t})\n\n\t\tconst json: { data?: T; errors?: Array<{ message: string }> } = await response.json()\n\n\t\tif (json.errors?.length) {\n\t\t\tthrow new Error(json.errors[0].message)\n\t\t}\n\n\t\tif (json.data === undefined) {\n\t\t\tthrow new Error('GraphQL response missing data field')\n\t\t}\n\n\t\treturn json.data\n\t}\n\n\t/**\n\t * Execute a GraphQL mutation. Delegates to query() since both use POST.\n\t *\n\t * @param mutation - GraphQL mutation string\n\t * @param variables - Mutation variables\n\t */\n\tasync mutate<T = unknown>(mutation: string, variables?: Record<string, unknown>): Promise<T> {\n\t\treturn this.query<T>(mutation, variables)\n\t}\n\n\t/**\n\t * Get doctype metadata\n\t * @param context - Doctype context containing doctype name\n\t */\n\tasync getMeta(context: DoctypeContext): Promise<DoctypeMeta | null> {\n\t\tconst cached = this.metaCache.get(context.doctype)\n\t\tif (cached) return cached\n\n\t\tconst result = await this.query<{ stonecropMeta: DoctypeMeta | null }>(GET_META_QUERY, {\n\t\t\tdoctype: context.doctype,\n\t\t})\n\n\t\tif (result.stonecropMeta) {\n\t\t\tthis.metaCache.set(context.doctype, result.stonecropMeta)\n\t\t}\n\n\t\treturn result.stonecropMeta\n\t}\n\n\t/**\n\t * Get all doctype metadata\n\t */\n\tasync getAllMeta(): Promise<DoctypeMeta[]> {\n\t\tconst result = await this.query<{ stonecropAllMeta: DoctypeMeta[] }>(GET_ALL_META_QUERY)\n\n\t\tfor (const meta of result.stonecropAllMeta) {\n\t\t\tthis.metaCache.set(meta.name, meta)\n\t\t}\n\n\t\treturn result.stonecropAllMeta\n\t}\n\n\t/**\n\t * Get a single record by ID.\n\t *\n\t * Routes through the stonecropRecord resolver which handles nested data\n\t * fetching based on the includeNested option.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param recordId - Record ID to fetch\n\t * @param options - Query options (includeNested, maxDepth)\n\t */\n\tasync getRecord(doctype: DoctypeRef, recordId: string, options?: GetRecordOptions): Promise<GetRecordResult> {\n\t\tconst result = await this.query<{\n\t\t\tstonecropRecord: { data: Record<string, unknown> | null; unknownLinks?: string[] }\n\t\t}>(\n\t\t\t`query GetRecord($doctype: String!, $id: String!, $options: JSON) {\n\t\t\t\tstonecropRecord(doctype: $doctype, id: $id, options: $options) {\n\t\t\t\t\tdata\n\t\t\t\t\tunknownLinks\n\t\t\t\t}\n\t\t\t}`,\n\t\t\t{\n\t\t\t\tdoctype: doctype.name,\n\t\t\t\tid: recordId,\n\t\t\t\toptions: options?.includeNested\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tincludeNested: options.includeNested,\n\t\t\t\t\t\t\tmaxDepth: options.maxDepth,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t}\n\t\t)\n\n\t\treturn {\n\t\t\trecord: result.stonecropRecord?.data ?? null,\n\t\t\tunknownLinks: result.stonecropRecord?.unknownLinks,\n\t\t}\n\t}\n\n\t/**\n\t * Get multiple records with optional filtering and pagination.\n\t *\n\t * Returns flat arrays — the middleware merges connection format (\\{ nodes: [...] \\})\n\t * into plain arrays before returning.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param options - Query options (filters, orderBy, limit, offset)\n\t */\n\tasync getRecords(doctype: DoctypeRef, options?: GetRecordsOptions): Promise<GetRecordsResult> {\n\t\tconst result = await this.query<{\n\t\t\tstonecropRecords: { data: Record<string, unknown>[]; hasMore: boolean; count: number | null }\n\t\t}>(\n\t\t\t`\n\t\t\tquery GetRecords(\n\t\t\t\t$doctype: String!\n\t\t\t\t$filters: JSON\n\t\t\t\t$orderBy: String\n\t\t\t\t$limit: Int\n\t\t\t\t$offset: Int\n\t\t\t\t$includeTotal: Boolean\n\t\t\t) {\n\t\t\t\tstonecropRecords(\n\t\t\t\t\tdoctype: $doctype\n\t\t\t\t\tfilters: $filters\n\t\t\t\t\torderBy: $orderBy\n\t\t\t\t\tlimit: $limit\n\t\t\t\t\toffset: $offset\n\t\t\t\t\tincludeTotal: $includeTotal\n\t\t\t\t) {\n\t\t\t\t\tdata\n\t\t\t\t\thasMore\n\t\t\t\t\tcount\n\t\t\t\t}\n\t\t\t}\n\t\t\t`,\n\t\t\t{\n\t\t\t\tdoctype: doctype.name,\n\t\t\t\t...options,\n\t\t\t}\n\t\t)\n\n\t\tconst { data, hasMore, count } = result.stonecropRecords\n\t\t// `count` is null unless includeTotal was set. Omitting the key rather than passing null\n\t\t// through keeps \"not asked for\" and \"asked for, and it is zero\" distinguishable.\n\t\treturn count == null ? { data, hasMore } : { data, hasMore, count }\n\t}\n\n\t/**\n\t * Get a single record by ID using PostGraphile's native query with relationship expansion.\n\t *\n\t * Unlike `getRecord()` which uses the `stonecropRecord` resolver returning a JSON blob,\n\t * this method builds a native PostGraphile query that leverages the ORM's relationship\n\t * resolution for efficient single-query fetches with JOINs.\n\t *\n\t * Link fields are returned as `{ id, displayText }` objects where `displayText` is\n\t * resolved from the target doctype's `displayField`.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param recordId - Record ID to fetch\n\t */\n\tasync getNativeRecord(doctype: DoctypeRef, recordId: string): Promise<GetRecordResult> {\n\t\tconst allMeta = await this.getAllMeta()\n\t\tconst meta = allMeta.find(m => m.name === doctype.name || m.slug === doctype.name)\n\n\t\tif (!meta) {\n\t\t\treturn { record: null }\n\t\t}\n\n\t\tconst { query, linkFields } = buildSingleRecordQuery(meta, { allMeta })\n\t\tconst queryName = doctypeToSingleQuery(meta.name)\n\n\t\tconst result = await this.query<Record<string, Record<string, unknown> | null>>(query, { id: recordId })\n\n\t\tconst rawRecord = result[queryName]\n\t\tif (!rawRecord) {\n\t\t\treturn { record: null }\n\t\t}\n\n\t\tconst record = transformNativeRecord(rawRecord, linkFields, meta, allMeta)\n\t\treturn { record }\n\t}\n\n\t/**\n\t * Get multiple records using PostGraphile's native query with relationship expansion.\n\t *\n\t * Unlike `getRecords()` which uses the `stonecropRecords` resolver returning JSON blobs,\n\t * this method builds a native PostGraphile query that leverages the ORM's relationship\n\t * resolution for efficient single-query fetches with JOINs.\n\t *\n\t * Link fields are returned as `{ id, displayText }` objects where `displayText` is\n\t * resolved from the target doctype's `displayField`.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param options - Query options (limit, offset)\n\t */\n\tasync getNativeRecords(\n\t\tdoctype: DoctypeRef,\n\t\toptions?: { limit?: number; offset?: number }\n\t): Promise<GetRecordsResult> {\n\t\tconst allMeta = await this.getAllMeta()\n\t\tconst meta = allMeta.find(m => m.name === doctype.name || m.slug === doctype.name)\n\n\t\tif (!meta) {\n\t\t\treturn { data: [], hasMore: false }\n\t\t}\n\n\t\tconst { query, linkFields } = buildListRecordQuery(meta, {\n\t\t\tallMeta,\n\t\t\tfirst: options?.limit,\n\t\t\toffset: options?.offset,\n\t\t})\n\t\tconst queryName = doctypeToListQuery(meta.name)\n\n\t\tconst variables: Record<string, unknown> = {}\n\t\tif (options?.limit !== undefined) variables.first = options.limit\n\t\tif (options?.offset !== undefined) variables.offset = options.offset\n\n\t\tconst result = await this.query<Record<string, { nodes: Record<string, unknown>[] }>>(query, variables)\n\n\t\tconst nodes = result[queryName]?.nodes ?? []\n\t\tconst data = nodes.map(node => transformNativeRecord(node, linkFields, meta, allMeta))\n\n\t\treturn {\n\t\t\tdata,\n\t\t\thasMore: nodes.length === options?.limit,\n\t\t}\n\t}\n\n\t/**\n\t * Execute a doctype action\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param action - Action name to execute\n\t * @param args - Action arguments\n\t */\n\tasync runAction(\n\t\tdoctype: DoctypeRef,\n\t\taction: string,\n\t\targs?: unknown[]\n\t): Promise<{ success: boolean; data: unknown; error: string | null }> {\n\t\tconst result = await this.query<{\n\t\t\tstonecropAction: { success: boolean; data: unknown; error: string | null }\n\t\t}>(RUN_ACTION_MUTATION, {\n\t\t\tdoctype: doctype.name,\n\t\t\taction,\n\t\t\targs,\n\t\t})\n\n\t\treturn result.stonecropAction\n\t}\n\n\t/**\n\t * Clear the cached doctype metadata.\n\t *\n\t * Call this if the server-side doctype schema has changed and you need\n\t * to fetch fresh metadata (e.g., after adding a new field).\n\t */\n\tclearMetaCache(): void {\n\t\tthis.metaCache.clear()\n\t}\n}\n"],"names":["GET_META_QUERY","RUN_ACTION_MUTATION","GET_ALL_META_QUERY","doctypeToQueryName","doctypeName","doctypeToSingleQuery","doctypeToListQuery","name","buildRelationshipName","targetDoctypeName","fkFieldname","prefix","suffix","resolveDoctype","slug","allMeta","m","valueFieldNamed","fields","fieldname","field","flattenFields","buildFieldSelection","meta","depth","maxDepth","linkFieldsOut","flatFields","selections","componentLinkExpansion","targetMeta","relationshipName","displayField","buildSingleRecordQuery","options","linkFields","fieldSelection","buildListRecordQuery","queryName","params","args","paramStr","argStr","transformNativeRecord","record","result","key","value","linkField","nestedRaw","displayText","StonecropClient","query","variables","json","mutation","context","cached","doctype","recordId","data","hasMore","count","rawRecord","nodes","node","action"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"graphql-client.js","sources":["../src/queries.ts","../src/client.ts"],"sourcesContent":["/**\n * GraphQL query documents sent by {@link StonecropClient} to the middleware.\n *\n * These are the client's half of the wire contract with `@stonecrop/graphql-middleware`.\n * They live here as exported constants (rather than inline in the client methods) so the\n * cross-package contract test can validate the exact strings the client sends against the\n * middleware's published SDL — a field the server drops while a query still selects it must\n * fail CI, not production.\n *\n * @public\n */\nexport 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`\n\n/**\n * Mutation document for dispatching a workflow action (the server-owned transition).\n * @public\n */\nexport const RUN_ACTION_MUTATION = `\n\tmutation RunAction($doctype: String!, $action: String!, $args: JSON) {\n\t\tstonecropAction(doctype: $doctype, action: $action, args: $args) {\n\t\t\tsuccess\n\t\t\tdata\n\t\t\terror\n\t\t}\n\t}\n`\n\n/**\n * Query document for fetching all doctype metadata.\n * @public\n */\nexport 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`\n","import type {\n\tDataClient,\n\tDoctypeMeta,\n\tDoctypeContext,\n\tDoctypeRef,\n\tGetRecordOptions,\n\tGetRecordsOptions,\n\tGetRecordsResult,\n} from '@stonecrop/schema'\nimport type { GetRecordResult } from './types'\nimport { GET_META_QUERY, GET_ALL_META_QUERY, RUN_ACTION_MUTATION } from './queries'\n\nexport type { DoctypeContext, DoctypeRef }\nexport type { GetRecordResult, GetRecordsResult }\n\n/**\n * Options for creating a Stonecrop client\n * @public\n */\nexport interface StonecropClientOptions {\n\t/** GraphQL endpoint URL */\n\tendpoint: string\n\t/** Additional HTTP headers to include in requests */\n\theaders?: Record<string, string>\n}\n\n/**\n * Client for interacting with Stonecrop GraphQL API.\n *\n * Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction.\n *\n * @public\n */\nexport class StonecropClient implements DataClient {\n\tprivate endpoint: string\n\tprivate headers: Record<string, string>\n\tprivate metaCache: Map<string, DoctypeMeta> = new Map()\n\n\tconstructor(options: StonecropClientOptions) {\n\t\tthis.endpoint = options.endpoint\n\t\tthis.headers = {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t...options.headers,\n\t\t}\n\t}\n\n\t/**\n\t * Execute a GraphQL query against the configured endpoint.\n\t *\n\t * @param query - GraphQL query string\n\t * @param variables - Query variables\n\t * @throws Error if the GraphQL response contains errors\n\t */\n\tasync query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T> {\n\t\tconst response = await fetch(this.endpoint, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: this.headers,\n\t\t\tbody: JSON.stringify({ query, variables }),\n\t\t})\n\n\t\tconst json: { data?: T; errors?: Array<{ message: string }> } = await response.json()\n\n\t\tif (json.errors?.length) {\n\t\t\tthrow new Error(json.errors[0].message)\n\t\t}\n\n\t\tif (json.data === undefined) {\n\t\t\tthrow new Error('GraphQL response missing data field')\n\t\t}\n\n\t\treturn json.data\n\t}\n\n\t/**\n\t * Execute a GraphQL mutation. Delegates to query() since both use POST.\n\t *\n\t * @param mutation - GraphQL mutation string\n\t * @param variables - Mutation variables\n\t */\n\tasync mutate<T = unknown>(mutation: string, variables?: Record<string, unknown>): Promise<T> {\n\t\treturn this.query<T>(mutation, variables)\n\t}\n\n\t/**\n\t * Get doctype metadata\n\t * @param context - Doctype context containing doctype name\n\t */\n\tasync getMeta(context: DoctypeContext): Promise<DoctypeMeta | null> {\n\t\tconst cached = this.metaCache.get(context.doctype)\n\t\tif (cached) return cached\n\n\t\tconst result = await this.query<{ stonecropMeta: DoctypeMeta | null }>(GET_META_QUERY, {\n\t\t\tdoctype: context.doctype,\n\t\t})\n\n\t\tif (result.stonecropMeta) {\n\t\t\tthis.metaCache.set(context.doctype, result.stonecropMeta)\n\t\t}\n\n\t\treturn result.stonecropMeta\n\t}\n\n\t/**\n\t * Get all doctype metadata\n\t */\n\tasync getAllMeta(): Promise<DoctypeMeta[]> {\n\t\tconst result = await this.query<{ stonecropAllMeta: DoctypeMeta[] }>(GET_ALL_META_QUERY)\n\n\t\tfor (const meta of result.stonecropAllMeta) {\n\t\t\tthis.metaCache.set(meta.name, meta)\n\t\t}\n\n\t\treturn result.stonecropAllMeta\n\t}\n\n\t/**\n\t * Get a single record by ID.\n\t *\n\t * Routes through the stonecropRecord resolver which handles nested data\n\t * fetching based on the includeNested option.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param recordId - Record ID to fetch\n\t * @param options - Query options (includeNested, maxDepth)\n\t */\n\tasync getRecord(doctype: DoctypeRef, recordId: string, options?: GetRecordOptions): Promise<GetRecordResult> {\n\t\tconst result = await this.query<{\n\t\t\tstonecropRecord: { data: Record<string, unknown> | null; unknownLinks?: string[] }\n\t\t}>(\n\t\t\t`query GetRecord($doctype: String!, $id: String!, $options: JSON) {\n\t\t\t\tstonecropRecord(doctype: $doctype, id: $id, options: $options) {\n\t\t\t\t\tdata\n\t\t\t\t\tunknownLinks\n\t\t\t\t}\n\t\t\t}`,\n\t\t\t{\n\t\t\t\tdoctype: doctype.name,\n\t\t\t\tid: recordId,\n\t\t\t\toptions: options?.includeNested\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tincludeNested: options.includeNested,\n\t\t\t\t\t\t\tmaxDepth: options.maxDepth,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t}\n\t\t)\n\n\t\treturn {\n\t\t\trecord: result.stonecropRecord?.data ?? null,\n\t\t\tunknownLinks: result.stonecropRecord?.unknownLinks,\n\t\t}\n\t}\n\n\t/**\n\t * Get multiple records with optional filtering and pagination.\n\t *\n\t * Returns flat arrays — the middleware merges connection format (\\{ nodes: [...] \\})\n\t * into plain arrays before returning.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param options - Query options (filters, orderBy, limit, offset)\n\t */\n\tasync getRecords(doctype: DoctypeRef, options?: GetRecordsOptions): Promise<GetRecordsResult> {\n\t\tconst result = await this.query<{\n\t\t\tstonecropRecords: { data: Record<string, unknown>[]; hasMore: boolean; count: number | null }\n\t\t}>(\n\t\t\t`\n\t\t\tquery GetRecords(\n\t\t\t\t$doctype: String!\n\t\t\t\t$filters: JSON\n\t\t\t\t$orderBy: String\n\t\t\t\t$limit: Int\n\t\t\t\t$offset: Int\n\t\t\t\t$includeTotal: Boolean\n\t\t\t) {\n\t\t\t\tstonecropRecords(\n\t\t\t\t\tdoctype: $doctype\n\t\t\t\t\tfilters: $filters\n\t\t\t\t\torderBy: $orderBy\n\t\t\t\t\tlimit: $limit\n\t\t\t\t\toffset: $offset\n\t\t\t\t\tincludeTotal: $includeTotal\n\t\t\t\t) {\n\t\t\t\t\tdata\n\t\t\t\t\thasMore\n\t\t\t\t\tcount\n\t\t\t\t}\n\t\t\t}\n\t\t\t`,\n\t\t\t{\n\t\t\t\tdoctype: doctype.name,\n\t\t\t\t...options,\n\t\t\t}\n\t\t)\n\n\t\tconst { data, hasMore, count } = result.stonecropRecords\n\t\t// `count` is null unless includeTotal was set. Omitting the key rather than passing null\n\t\t// through keeps \"not asked for\" and \"asked for, and it is zero\" distinguishable.\n\t\treturn count == null ? { data, hasMore } : { data, hasMore, count }\n\t}\n\n\t/**\n\t * Execute a doctype action\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param action - Action name to execute\n\t * @param args - Action arguments\n\t */\n\tasync runAction(\n\t\tdoctype: DoctypeRef,\n\t\taction: string,\n\t\targs?: unknown[]\n\t): Promise<{ success: boolean; data: unknown; error: string | null }> {\n\t\tconst result = await this.query<{\n\t\t\tstonecropAction: { success: boolean; data: unknown; error: string | null }\n\t\t}>(RUN_ACTION_MUTATION, {\n\t\t\tdoctype: doctype.name,\n\t\t\taction,\n\t\t\targs,\n\t\t})\n\n\t\treturn result.stonecropAction\n\t}\n\n\t/**\n\t * Clear the cached doctype metadata.\n\t *\n\t * Call this if the server-side doctype schema has changed and you need\n\t * to fetch fresh metadata (e.g., after adding a new field).\n\t */\n\tclearMetaCache(): void {\n\t\tthis.metaCache.clear()\n\t}\n}\n"],"names":["GET_META_QUERY","RUN_ACTION_MUTATION","GET_ALL_META_QUERY","StonecropClient","options","query","variables","json","mutation","context","cached","result","meta","doctype","recordId","data","hasMore","count","action","args"],"mappings":"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"}
|
package/dist/src/client.d.ts
CHANGED
|
@@ -15,8 +15,7 @@ export interface StonecropClientOptions {
|
|
|
15
15
|
/**
|
|
16
16
|
* Client for interacting with Stonecrop GraphQL API.
|
|
17
17
|
*
|
|
18
|
-
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction
|
|
19
|
-
* builds native PostGraphile queries for getNativeRecord/getNativeRecords.
|
|
18
|
+
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction.
|
|
20
19
|
*
|
|
21
20
|
* @public
|
|
22
21
|
*/
|
|
@@ -70,37 +69,6 @@ export declare class StonecropClient implements DataClient {
|
|
|
70
69
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
71
70
|
*/
|
|
72
71
|
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>;
|
|
104
72
|
/**
|
|
105
73
|
* Execute a doctype action
|
|
106
74
|
* @param doctype - Doctype reference (name and optional slug)
|