@stonecrop/graphql-client 0.23.0 → 0.24.0

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