@stonecrop/graphql-client 0.20.0 → 0.22.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 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
- ## Why transport-only?
5
+ ## Two query paths
6
6
 
7
- This client intentionally never constructs GraphQL queries. All query generation — including PostGraphile-specific field naming (inflection), nested link sub-selections, and fetch strategy dispatch — lives in the server-side middleware (`@stonecrop/graphql-middleware`).
7
+ This client offers two approaches for fetching records:
8
8
 
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.
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
- // Fetch a record
46
- const result = await client.getRecord({ name: 'Recipe' }, 'r1')
47
- result.record // plain object with the record fields
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
- // Fetch with nested links
51
- const withNested = await client.getRecord({ name: 'Recipe' }, 'r1', {
52
- includeNested: true,
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
- - `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.
62
- - `getRecords` returns `Record<string, unknown>[]` — an array of flat objects. Pagination metadata (`count`) is available in the server response but not currently exposed by the client.
63
- - `unknownLinks` will contain link names you requested that don't exist in the doctype schema — useful for catching typos.
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.
@@ -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 — it passes requests to the middleware and returns
27
- * merged results. Does not construct queries itself.
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 { }
@@ -1,8 +1,10 @@
1
- const s = `
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
- `, i = `
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
- `, c = `
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
- class l {
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 n = await (await fetch(this.endpoint, {
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 (n.errors?.length)
122
- throw new Error(n.errors[0].message);
123
- if (n.data === void 0)
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 n.data;
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 o = await this.query(s, {
218
+ const n = await this.query(S, {
144
219
  doctype: e.doctype
145
220
  });
146
- return o.stonecropMeta && this.metaCache.set(e.doctype, o.stonecropMeta), o.stonecropMeta;
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(c);
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, o) {
168
- const n = await this.query(
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: o?.includeNested ? {
179
- includeNested: o.includeNested,
180
- maxDepth: o.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: n.stonecropRecord?.data ?? null,
186
- unknownLinks: n.stonecropRecord?.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 o = await this.query(
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: n, hasMore: a, count: r } = o.stonecropRecords;
228
- return r == null ? { data: n, hasMore: a } : { data: n, hasMore: a, count: r };
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, o) {
237
- return (await this.query(i, {
360
+ async runAction(e, t, n) {
361
+ return (await this.query(k, {
238
362
  doctype: e.name,
239
363
  action: t,
240
- args: o
364
+ args: n
241
365
  })).stonecropAction;
242
366
  }
243
367
  /**
@@ -251,6 +375,13 @@ class l {
251
375
  }
252
376
  }
253
377
  export {
254
- l as StonecropClient
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":"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"}