@stonecrop/graphql-client 0.19.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/README.md
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
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. Optionally builds native PostGraphile queries for efficient link display text resolution.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Two query paths
|
|
6
6
|
|
|
7
|
-
This client
|
|
7
|
+
This client offers two approaches for fetching records:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Standard path (`getRecord`, `getRecords`)
|
|
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.
|
|
10
22
|
|
|
11
23
|
## Responsibilities
|
|
12
24
|
|
|
@@ -42,15 +54,17 @@ const client = new StonecropClient({
|
|
|
42
54
|
headers: { Authorization: `Bearer ${token}` }, // optional
|
|
43
55
|
})
|
|
44
56
|
|
|
45
|
-
//
|
|
46
|
-
const result = await client.getRecord({ name: '
|
|
47
|
-
result.record //
|
|
48
|
-
result.unknownLinks // links requested but not found in schema
|
|
57
|
+
// Standard path — uses stonecropRecord resolver
|
|
58
|
+
const result = await client.getRecord({ name: 'SalesOrder' }, 'so-1')
|
|
59
|
+
result.record // { id: 'so-1', customerId: 'party-uuid', ... }
|
|
49
60
|
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
// Native path — uses PostGraphile's native queries with relationship expansion
|
|
62
|
+
const native = await client.getNativeRecord({ name: 'SalesOrder' }, 'so-1')
|
|
63
|
+
native.record // { id: 'so-1', customerId: { id: 'party-uuid', displayText: 'Acme Corp' }, ... }
|
|
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
|
|
54
68
|
|
|
55
69
|
// Custom queries
|
|
56
70
|
const custom = await client.query<{ myData: unknown[] }>(`query { myData { id } }`)
|
|
@@ -58,8 +72,14 @@ const custom = await client.query<{ myData: unknown[] }>(`query { myData { id }
|
|
|
58
72
|
|
|
59
73
|
## Data Shapes
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- `
|
|
75
|
+
### Standard methods
|
|
76
|
+
|
|
77
|
+
- `getRecord` returns `{ record: Record<string, unknown> | null, unknownLinks?: string[] }`. The `record` field contains the record's fields with scalar FK values. Nested links are merged when `includeNested` is used.
|
|
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.
|
|
64
84
|
|
|
65
85
|
See [API Reference](./api.md) for full method signatures.
|
package/dist/graphql-client.d.ts
CHANGED
|
@@ -7,10 +7,84 @@ 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
|
+
|
|
10
55
|
export { DoctypeContext }
|
|
11
56
|
|
|
12
57
|
export { DoctypeMeta }
|
|
13
58
|
|
|
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
|
+
|
|
14
88
|
/**
|
|
15
89
|
* Result from getRecord - includes the record data and any unknown links requested
|
|
16
90
|
* @public
|
|
@@ -20,11 +94,27 @@ export declare interface GetRecordResult extends GetRecordResult_2 {
|
|
|
20
94
|
unknownLinks?: string[];
|
|
21
95
|
}
|
|
22
96
|
|
|
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
|
+
|
|
23
113
|
/**
|
|
24
114
|
* Client for interacting with Stonecrop GraphQL API.
|
|
25
115
|
*
|
|
26
|
-
* Acts as a transport layer
|
|
27
|
-
*
|
|
116
|
+
* Acts as a transport layer for stonecropRecord/stonecropRecords/stonecropAction, and
|
|
117
|
+
* builds native PostGraphile queries for getNativeRecord/getNativeRecords.
|
|
28
118
|
*
|
|
29
119
|
* @public
|
|
30
120
|
*/
|
|
@@ -78,6 +168,37 @@ export declare class StonecropClient implements DataClient {
|
|
|
78
168
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
79
169
|
*/
|
|
80
170
|
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>;
|
|
81
202
|
/**
|
|
82
203
|
* Execute a doctype action
|
|
83
204
|
* @param doctype - Doctype reference (name and optional slug)
|
|
@@ -109,4 +230,11 @@ export declare interface StonecropClientOptions {
|
|
|
109
230
|
headers?: Record<string, string>;
|
|
110
231
|
}
|
|
111
232
|
|
|
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
|
+
|
|
112
240
|
export { }
|
package/dist/graphql-client.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { flattenFields as p, componentLinkExpansion as M } from "@stonecrop/schema";
|
|
2
|
+
const S = `
|
|
2
3
|
query GetMeta($doctype: String!) {
|
|
3
4
|
stonecropMeta(doctype: $doctype) {
|
|
4
5
|
name
|
|
5
6
|
slug
|
|
7
|
+
displayField
|
|
6
8
|
fields {
|
|
7
9
|
kind
|
|
8
10
|
fieldname
|
|
@@ -42,7 +44,7 @@ const s = `
|
|
|
42
44
|
inherits
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
|
-
`,
|
|
47
|
+
`, k = `
|
|
46
48
|
mutation RunAction($doctype: String!, $action: String!, $args: JSON) {
|
|
47
49
|
stonecropAction(doctype: $doctype, action: $action, args: $args) {
|
|
48
50
|
success
|
|
@@ -50,11 +52,12 @@ const s = `
|
|
|
50
52
|
error
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
`,
|
|
55
|
+
`, N = `
|
|
54
56
|
query GetAllMeta {
|
|
55
57
|
stonecropAllMeta {
|
|
56
58
|
name
|
|
57
59
|
slug
|
|
60
|
+
displayField
|
|
58
61
|
fields {
|
|
59
62
|
kind
|
|
60
63
|
fieldname
|
|
@@ -95,7 +98,79 @@ const s = `
|
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
`;
|
|
98
|
-
|
|
101
|
+
function m(o) {
|
|
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 {
|
|
99
174
|
endpoint;
|
|
100
175
|
headers;
|
|
101
176
|
metaCache = /* @__PURE__ */ new Map();
|
|
@@ -113,16 +188,16 @@ class l {
|
|
|
113
188
|
* @throws Error if the GraphQL response contains errors
|
|
114
189
|
*/
|
|
115
190
|
async query(e, t) {
|
|
116
|
-
const
|
|
191
|
+
const i = await (await fetch(this.endpoint, {
|
|
117
192
|
method: "POST",
|
|
118
193
|
headers: this.headers,
|
|
119
194
|
body: JSON.stringify({ query: e, variables: t })
|
|
120
195
|
})).json();
|
|
121
|
-
if (
|
|
122
|
-
throw new Error(
|
|
123
|
-
if (
|
|
196
|
+
if (i.errors?.length)
|
|
197
|
+
throw new Error(i.errors[0].message);
|
|
198
|
+
if (i.data === void 0)
|
|
124
199
|
throw new Error("GraphQL response missing data field");
|
|
125
|
-
return
|
|
200
|
+
return i.data;
|
|
126
201
|
}
|
|
127
202
|
/**
|
|
128
203
|
* Execute a GraphQL mutation. Delegates to query() since both use POST.
|
|
@@ -140,16 +215,16 @@ class l {
|
|
|
140
215
|
async getMeta(e) {
|
|
141
216
|
const t = this.metaCache.get(e.doctype);
|
|
142
217
|
if (t) return t;
|
|
143
|
-
const
|
|
218
|
+
const n = await this.query(S, {
|
|
144
219
|
doctype: e.doctype
|
|
145
220
|
});
|
|
146
|
-
return
|
|
221
|
+
return n.stonecropMeta && this.metaCache.set(e.doctype, n.stonecropMeta), n.stonecropMeta;
|
|
147
222
|
}
|
|
148
223
|
/**
|
|
149
224
|
* Get all doctype metadata
|
|
150
225
|
*/
|
|
151
226
|
async getAllMeta() {
|
|
152
|
-
const e = await this.query(
|
|
227
|
+
const e = await this.query(N);
|
|
153
228
|
for (const t of e.stonecropAllMeta)
|
|
154
229
|
this.metaCache.set(t.name, t);
|
|
155
230
|
return e.stonecropAllMeta;
|
|
@@ -164,8 +239,8 @@ class l {
|
|
|
164
239
|
* @param recordId - Record ID to fetch
|
|
165
240
|
* @param options - Query options (includeNested, maxDepth)
|
|
166
241
|
*/
|
|
167
|
-
async getRecord(e, t,
|
|
168
|
-
const
|
|
242
|
+
async getRecord(e, t, n) {
|
|
243
|
+
const i = await this.query(
|
|
169
244
|
`query GetRecord($doctype: String!, $id: String!, $options: JSON) {
|
|
170
245
|
stonecropRecord(doctype: $doctype, id: $id, options: $options) {
|
|
171
246
|
data
|
|
@@ -175,15 +250,15 @@ class l {
|
|
|
175
250
|
{
|
|
176
251
|
doctype: e.name,
|
|
177
252
|
id: t,
|
|
178
|
-
options:
|
|
179
|
-
includeNested:
|
|
180
|
-
maxDepth:
|
|
253
|
+
options: n?.includeNested ? {
|
|
254
|
+
includeNested: n.includeNested,
|
|
255
|
+
maxDepth: n.maxDepth
|
|
181
256
|
} : void 0
|
|
182
257
|
}
|
|
183
258
|
);
|
|
184
259
|
return {
|
|
185
|
-
record:
|
|
186
|
-
unknownLinks:
|
|
260
|
+
record: i.stonecropRecord?.data ?? null,
|
|
261
|
+
unknownLinks: i.stonecropRecord?.unknownLinks
|
|
187
262
|
};
|
|
188
263
|
}
|
|
189
264
|
/**
|
|
@@ -196,7 +271,7 @@ class l {
|
|
|
196
271
|
* @param options - Query options (filters, orderBy, limit, offset)
|
|
197
272
|
*/
|
|
198
273
|
async getRecords(e, t) {
|
|
199
|
-
const
|
|
274
|
+
const n = await this.query(
|
|
200
275
|
`
|
|
201
276
|
query GetRecords(
|
|
202
277
|
$doctype: String!
|
|
@@ -224,8 +299,57 @@ class l {
|
|
|
224
299
|
doctype: e.name,
|
|
225
300
|
...t
|
|
226
301
|
}
|
|
227
|
-
), { data:
|
|
228
|
-
return
|
|
302
|
+
), { data: i, hasMore: a, count: s } = n.stonecropRecords;
|
|
303
|
+
return s == null ? { data: i, hasMore: a } : { data: i, hasMore: a, count: s };
|
|
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
353
|
}
|
|
230
354
|
/**
|
|
231
355
|
* Execute a doctype action
|
|
@@ -233,11 +357,11 @@ class l {
|
|
|
233
357
|
* @param action - Action name to execute
|
|
234
358
|
* @param args - Action arguments
|
|
235
359
|
*/
|
|
236
|
-
async runAction(e, t,
|
|
237
|
-
return (await this.query(
|
|
360
|
+
async runAction(e, t, n) {
|
|
361
|
+
return (await this.query(k, {
|
|
238
362
|
doctype: e.name,
|
|
239
363
|
action: t,
|
|
240
|
-
args:
|
|
364
|
+
args: n
|
|
241
365
|
})).stonecropAction;
|
|
242
366
|
}
|
|
243
367
|
/**
|
|
@@ -251,6 +375,13 @@ class l {
|
|
|
251
375
|
}
|
|
252
376
|
}
|
|
253
377
|
export {
|
|
254
|
-
|
|
378
|
+
A as StonecropClient,
|
|
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
|
|
255
386
|
};
|
|
256
387
|
//# sourceMappingURL=graphql-client.js.map
|
|
@@ -1 +1 @@
|
|
|
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\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\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 — it passes requests to the middleware and returns\n * merged results. Does not construct queries itself.\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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"}
|
|
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":";AAWO,MAAMA,IAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAmDjBC,IAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GActBC,IAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACV3B,SAASC,EAAmBC,GAA6B;AAC/D,SAAOA,EAAY,CAAC,EAAE,gBAAgBA,EAAY,MAAM,CAAC;AAC1D;AASO,SAASC,EAAqBD,GAA6B;AACjE,SAAOD,EAAmBC,CAAW,IAAI;AAC1C;AAUO,SAASE,EAAmBF,GAA6B;AAC/D,QAAMG,IAAOH;AACb,SAAIG,EAAK,SAAS,GAAG,IACb,QAAQA,EAAK,MAAM,GAAG,EAAE,IAAI,QAE7B,QAAQA,IAAO;AACvB;AAYO,SAASC,EAAsBC,GAA2BC,GAA6B;AAC7F,QAAMC,IAASR,EAAmBM,CAAiB,GAC7CG,IAASF,EAAY,CAAC,EAAE,gBAAgBA,EAAY,MAAM,CAAC;AACjE,SAAOC,IAAS,OAAOC;AACxB;AAKA,SAASC,EAAeC,GAAcC,GAAiD;AACtF,SAAOA,EAAQ,KAAK,CAAAC,MAAKA,EAAE,SAASF,KAAQE,EAAE,SAASF,CAAI;AAC5D;AAEA,SAASG,EAAgBC,GAA+BC,GAA2C;AAClG,aAAWC,KAASC,EAAcH,CAAM;AACvC,QAAIE,EAAM,SAAS,WAAWA,EAAM,cAAcD;AACjD,aAAOC;AAIV;AAKA,SAASE,EACRC,GACAR,GACAS,GACAC,GACAC,GACS;AACT,QAAMC,IAAaN,EAAcE,EAAK,MAAM,GACtCK,IAAuB,CAAA;AAE7B,aAAWR,KAASO,GAAY;AAC/B,QAAIP,EAAM,SAAS,QAAS;AAI5B,QAFoBA,EAAM,WAAWS,EAAuBT,EAAM,SAAS,MAAM,YAE9DI,IAAQC,GAAU;AACpC,YAAMK,IAAajB,EAAeO,EAAM,SAAUL,CAAO;AACzD,UAAIe,GAAY;AACf,cAAMC,IAAmBvB,EAAsBsB,EAAW,MAAMV,EAAM,SAAS,GACzEY,IAAeF,EAAW;AAEhC,QAAIE,KACHJ,EAAW,KAAKR,EAAM,SAAS,GAC/BQ,EAAW,KAAK,GAAGG,CAAgB,SAASC,CAAY,IAAI,GAC5DN,EAAc,KAAKN,EAAM,SAAS,KAElCQ,EAAW,KAAKR,EAAM,SAAS;AAAA,MAEjC;AACC,QAAAQ,EAAW,KAAKR,EAAM,SAAS;AAAA,IAEjC;AACC,MAAAQ,EAAW,KAAKR,EAAM,SAAS;AAAA,EAEjC;AAEA,SAAOQ,EAAW,KAAK,GAAG;AAC3B;AAMO,SAASK,EAAuBV,GAAmBW,GAA0C;AACnG,QAAMT,IAAWS,EAAQ,YAAY,GAC/BC,IAAuB,CAAA,GAEvBC,IAAiBd,EAAoBC,GAAMW,EAAQ,SAAS,GAAGT,GAAUU,CAAU;AAKzF,SAAO,EAAE,OAFK,uBAFI9B,EAAqBkB,EAAK,IAAI,CAEF,eAAea,CAAc,QAE3D,YAAAD,EAAA;AACjB;AAMO,SAASE,EACfd,GACAW,GAMa;AACb,QAAMT,IAAWS,EAAQ,YAAY,GAC/BC,IAAuB,CAAA,GAEvBC,IAAiBd,EAAoBC,GAAMW,EAAQ,SAAS,GAAGT,GAAUU,CAAU,GACnFG,IAAYhC,EAAmBiB,EAAK,IAAI,GAExCgB,IAAmB,CAAA,GACnBC,IAAiB,CAAA;AAEvB,EAAIN,EAAQ,UAAU,WACrBK,EAAO,KAAK,aAAa,GACzBC,EAAK,KAAK,eAAe,IAEtBN,EAAQ,WAAW,WACtBK,EAAO,KAAK,cAAc,GAC1BC,EAAK,KAAK,iBAAiB,IAExBN,EAAQ,YACXK,EAAO,KAAK,iCAAiC,GAC7CC,EAAK,KAAK,mBAAmB,IAE1BN,EAAQ,cACXK,EAAO,KAAK,iCAAiC,GAC7CC,EAAK,KAAK,uBAAuB;AAGlC,QAAMC,IAAWF,EAAO,SAAS,IAAI,IAAIA,EAAO,KAAK,IAAI,CAAC,MAAM,IAC1DG,IAASF,EAAK,SAAS,IAAI,IAAIA,EAAK,KAAK,IAAI,CAAC,MAAM;AAI1D,SAAO,EAAE,OAFK,QAAQC,CAAQ,MAAMH,CAAS,GAAGI,CAAM,cAAcN,CAAc,UAElE,YAAAD,EAAA;AACjB;AAOO,SAASQ,EACfC,GACAT,GACAZ,GACAR,GAC0B;AAC1B,QAAM8B,IAAkC,CAAA;AAExC,aAAW,CAACC,GAAKC,CAAK,KAAK,OAAO,QAAQH,CAAM;AAC/C,QAAI,CAAAE,EAAI,SAAS,IAAI,GAIrB;AAAA,UAAIX,EAAW,SAASW,CAAG,GAAG;AAC7B,cAAME,IAAY/B,EAAgBM,EAAK,QAAQuB,CAAG;AAClD,YAAIE,GAAW,SAAS;AACvB,gBAAMlB,IAAajB,EAAemC,EAAU,SAASjC,CAAO;AAC5D,cAAIe,GAAY,cAAc;AAC7B,kBAAMC,IAAmBvB,EAAsBsB,EAAW,MAAMgB,CAAG,GAC7DG,IAAYL,EAAOb,CAAgB,GACnCmB,IACLD,KAAc,QAAmC,OAAOA,KAAc,WACnE,QAAQ,IAAIA,GAAWnB,EAAW,YAAY,IAC9C;AAEJ,gBAAiCoB,KAAgB,QAAQA,MAAgB,IAAI;AAC5E,cAAAL,EAAOC,CAAG,IAAI;AAAA,gBACb,IAAIC;AAAA,gBACJ,aAAAG;AAAA,cAAA;AAED;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAEA,MAAAL,EAAOC,CAAG,IAAIC;AAAA;AAGf,SAAOF;AACR;AC9OO,MAAMM,EAAsC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA,gCAA0C,IAAA;AAAA,EAElD,YAAYjB,GAAiC;AAC5C,SAAK,WAAWA,EAAQ,UACxB,KAAK,UAAU;AAAA,MACd,gBAAgB;AAAA,MAChB,GAAGA,EAAQ;AAAA,IAAA;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAmBkB,GAAeC,GAAiD;AAOxF,UAAMC,IAA0D,OAN/C,MAAM,MAAM,KAAK,UAAU;AAAA,MAC3C,QAAQ;AAAA,MACR,SAAS,KAAK;AAAA,MACd,MAAM,KAAK,UAAU,EAAE,OAAAF,GAAO,WAAAC,GAAW;AAAA,IAAA,CACzC,GAE8E,KAAA;AAE/E,QAAIC,EAAK,QAAQ;AAChB,YAAM,IAAI,MAAMA,EAAK,OAAO,CAAC,EAAE,OAAO;AAGvC,QAAIA,EAAK,SAAS;AACjB,YAAM,IAAI,MAAM,qCAAqC;AAGtD,WAAOA,EAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAoBC,GAAkBF,GAAiD;AAC5F,WAAO,KAAK,MAASE,GAAUF,CAAS;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQG,GAAsD;AACnE,UAAMC,IAAS,KAAK,UAAU,IAAID,EAAQ,OAAO;AACjD,QAAIC,EAAQ,QAAOA;AAEnB,UAAMZ,IAAS,MAAM,KAAK,MAA6C7C,GAAgB;AAAA,MACtF,SAASwD,EAAQ;AAAA,IAAA,CACjB;AAED,WAAIX,EAAO,iBACV,KAAK,UAAU,IAAIW,EAAQ,SAASX,EAAO,aAAa,GAGlDA,EAAO;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAqC;AAC1C,UAAMA,IAAS,MAAM,KAAK,MAA2C3C,CAAkB;AAEvF,eAAWqB,KAAQsB,EAAO;AACzB,WAAK,UAAU,IAAItB,EAAK,MAAMA,CAAI;AAGnC,WAAOsB,EAAO;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,UAAUa,GAAqBC,GAAkBzB,GAAsD;AAC5G,UAAMW,IAAS,MAAM,KAAK;AAAA,MAGzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA;AAAA,QACC,SAASa,EAAQ;AAAA,QACjB,IAAIC;AAAA,QACJ,SAASzB,GAAS,gBACf;AAAA,UACA,eAAeA,EAAQ;AAAA,UACvB,UAAUA,EAAQ;AAAA,QAAA,IAElB;AAAA,MAAA;AAAA,IACJ;AAGD,WAAO;AAAA,MACN,QAAQW,EAAO,iBAAiB,QAAQ;AAAA,MACxC,cAAcA,EAAO,iBAAiB;AAAA,IAAA;AAAA,EAExC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,WAAWa,GAAqBxB,GAAwD;AAC7F,UAAMW,IAAS,MAAM,KAAK;AAAA,MAGzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAuBA;AAAA,QACC,SAASa,EAAQ;AAAA,QACjB,GAAGxB;AAAA,MAAA;AAAA,IACJ,GAGK,EAAE,MAAA0B,GAAM,SAAAC,GAAS,OAAAC,EAAA,IAAUjB,EAAO;AAGxC,WAAOiB,KAAS,OAAO,EAAE,MAAAF,GAAM,SAAAC,MAAY,EAAE,MAAAD,GAAM,SAAAC,GAAS,OAAAC,EAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,gBAAgBJ,GAAqBC,GAA4C;AACtF,UAAM5C,IAAU,MAAM,KAAK,WAAA,GACrBQ,IAAOR,EAAQ,KAAK,CAAAC,MAAKA,EAAE,SAAS0C,EAAQ,QAAQ1C,EAAE,SAAS0C,EAAQ,IAAI;AAEjF,QAAI,CAACnC;AACJ,aAAO,EAAE,QAAQ,KAAA;AAGlB,UAAM,EAAE,OAAA6B,GAAO,YAAAjB,EAAA,IAAeF,EAAuBV,GAAM,EAAE,SAAAR,GAAS,GAChEuB,IAAYjC,EAAqBkB,EAAK,IAAI,GAI1CwC,KAFS,MAAM,KAAK,MAAsDX,GAAO,EAAE,IAAIO,GAAU,GAE9ErB,CAAS;AAClC,WAAKyB,IAKE,EAAE,QADMpB,EAAsBoB,GAAW5B,GAAYZ,GAAMR,CAAO,EAChE,IAJD,EAAE,QAAQ,KAAA;AAAA,EAKnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,iBACL2C,GACAxB,GAC4B;AAC5B,UAAMnB,IAAU,MAAM,KAAK,WAAA,GACrBQ,IAAOR,EAAQ,KAAK,CAAAC,MAAKA,EAAE,SAAS0C,EAAQ,QAAQ1C,EAAE,SAAS0C,EAAQ,IAAI;AAEjF,QAAI,CAACnC;AACJ,aAAO,EAAE,MAAM,IAAI,SAAS,GAAA;AAG7B,UAAM,EAAE,OAAA6B,GAAO,YAAAjB,MAAeE,EAAqBd,GAAM;AAAA,MACxD,SAAAR;AAAA,MACA,OAAOmB,GAAS;AAAA,MAChB,QAAQA,GAAS;AAAA,IAAA,CACjB,GACKI,IAAYhC,EAAmBiB,EAAK,IAAI,GAExC8B,IAAqC,CAAA;AAC3C,IAAInB,GAAS,UAAU,WAAWmB,EAAU,QAAQnB,EAAQ,QACxDA,GAAS,WAAW,WAAWmB,EAAU,SAASnB,EAAQ;AAI9D,UAAM8B,KAFS,MAAM,KAAK,MAA4DZ,GAAOC,CAAS,GAEjFf,CAAS,GAAG,SAAS,CAAA;AAG1C,WAAO;AAAA,MACN,MAHY0B,EAAM,IAAI,CAAAC,MAAQtB,EAAsBsB,GAAM9B,GAAYZ,GAAMR,CAAO,CAAC;AAAA,MAIpF,SAASiD,EAAM,WAAW9B,GAAS;AAAA,IAAA;AAAA,EAErC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UACLwB,GACAQ,GACA1B,GACqE;AASrE,YARe,MAAM,KAAK,MAEvBvC,GAAqB;AAAA,MACvB,SAASyD,EAAQ;AAAA,MACjB,QAAAQ;AAAA,MACA,MAAA1B;AAAA,IAAA,CACA,GAEa;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAuB;AACtB,SAAK,UAAU,MAAA;AAAA,EAChB;AACD;"}
|