@stonecrop/schema 0.31.0 → 0.33.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.
Files changed (82) hide show
  1. package/README.md +10 -10
  2. package/dist/cli.js +194 -121
  3. package/dist/cli.js.map +1 -1
  4. package/dist/converter-CLwduvT_.js +2102 -0
  5. package/dist/converter-CLwduvT_.js.map +1 -0
  6. package/dist/flatten-Bx2cfvw3.js +37 -0
  7. package/dist/flatten-Bx2cfvw3.js.map +1 -0
  8. package/dist/index.js +123 -117
  9. package/dist/index.js.map +1 -1
  10. package/dist/record-BQOOi83C.js +134 -0
  11. package/dist/record-BQOOi83C.js.map +1 -0
  12. package/dist/record.js +2 -6
  13. package/dist/tsdoc-metadata.json +1 -1
  14. package/package.json +34 -18
  15. package/dist/flatten-C1MjkzFh.js +0 -10
  16. package/dist/flatten-C1MjkzFh.js.map +0 -1
  17. package/dist/record-Bc0lI9Rq.js +0 -61
  18. package/dist/record-Bc0lI9Rq.js.map +0 -1
  19. package/dist/record.js.map +0 -1
  20. package/dist/schema.tsbuildinfo +0 -1
  21. package/dist/src/badge.d.ts +0 -74
  22. package/dist/src/badge.d.ts.map +0 -1
  23. package/dist/src/badge.js +0 -158
  24. package/dist/src/cli.d.ts +0 -3
  25. package/dist/src/cli.d.ts.map +0 -1
  26. package/dist/src/cli.js +0 -292
  27. package/dist/src/column-schema.d.ts +0 -163
  28. package/dist/src/column-schema.d.ts.map +0 -1
  29. package/dist/src/column-schema.js +0 -0
  30. package/dist/src/component-meta.d.ts +0 -96
  31. package/dist/src/component-meta.d.ts.map +0 -1
  32. package/dist/src/component-meta.js +0 -88
  33. package/dist/src/converter/aggregate.d.ts +0 -127
  34. package/dist/src/converter/aggregate.d.ts.map +0 -1
  35. package/dist/src/converter/aggregate.js +0 -235
  36. package/dist/src/converter/authored.d.ts +0 -43
  37. package/dist/src/converter/authored.d.ts.map +0 -1
  38. package/dist/src/converter/authored.js +0 -52
  39. package/dist/src/converter/heuristics.d.ts +0 -60
  40. package/dist/src/converter/heuristics.d.ts.map +0 -1
  41. package/dist/src/converter/heuristics.js +0 -304
  42. package/dist/src/converter/index.d.ts +0 -51
  43. package/dist/src/converter/index.d.ts.map +0 -1
  44. package/dist/src/converter/index.js +0 -195
  45. package/dist/src/converter/merge.d.ts +0 -102
  46. package/dist/src/converter/merge.d.ts.map +0 -1
  47. package/dist/src/converter/merge.js +0 -136
  48. package/dist/src/converter/scalars.d.ts +0 -46
  49. package/dist/src/converter/scalars.d.ts.map +0 -1
  50. package/dist/src/converter/scalars.js +0 -83
  51. package/dist/src/converter/types.d.ts +0 -157
  52. package/dist/src/converter/types.d.ts.map +0 -1
  53. package/dist/src/converter/types.js +0 -5
  54. package/dist/src/doctype.d.ts +0 -516
  55. package/dist/src/doctype.d.ts.map +0 -1
  56. package/dist/src/doctype.js +0 -343
  57. package/dist/src/field.d.ts +0 -423
  58. package/dist/src/field.d.ts.map +0 -1
  59. package/dist/src/field.js +0 -378
  60. package/dist/src/flatten.d.ts +0 -29
  61. package/dist/src/flatten.d.ts.map +0 -1
  62. package/dist/src/flatten.js +0 -38
  63. package/dist/src/index.d.ts +0 -16
  64. package/dist/src/index.d.ts.map +0 -1
  65. package/dist/src/index.js +0 -20
  66. package/dist/src/mode.d.ts +0 -15
  67. package/dist/src/mode.d.ts.map +0 -1
  68. package/dist/src/mode.js +0 -0
  69. package/dist/src/naming.d.ts +0 -80
  70. package/dist/src/naming.d.ts.map +0 -1
  71. package/dist/src/naming.js +0 -106
  72. package/dist/src/record.d.ts +0 -29
  73. package/dist/src/record.d.ts.map +0 -1
  74. package/dist/src/record.js +0 -55
  75. package/dist/src/table.d.ts +0 -33
  76. package/dist/src/table.d.ts.map +0 -1
  77. package/dist/src/table.js +0 -25
  78. package/dist/src/validation.d.ts +0 -54
  79. package/dist/src/validation.d.ts.map +0 -1
  80. package/dist/src/validation.js +0 -60
  81. package/dist/validation-C9P__pRF.js +0 -994
  82. package/dist/validation-C9P__pRF.js.map +0 -1
@@ -0,0 +1,2102 @@
1
+ import { t as flattenFields } from "./flatten-Bx2cfvw3.js";
2
+ import { z } from "zod";
3
+ import { buildClientSchema, buildSchema, isEnumType, isListType, isNamedType, isNonNullType, isObjectType, isScalarType } from "graphql";
4
+ //#region \0rolldown/runtime.js
5
+ var __create = Object.create;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
27
+ if (typeof require !== "undefined") return require.apply(this, arguments);
28
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
29
+ });
30
+ //#endregion
31
+ //#region src/table.ts
32
+ /**
33
+ * JSON-safe view configuration for table fields in doctype authoring.
34
+ *
35
+ * This is the authoring-time subset of `@stonecrop/atable`'s `TableConfig`. It covers
36
+ * the view discriminator and structural options that can be expressed in static JSON.
37
+ * `rowActions` (which requires function-typed handlers) stays in the runtime `TableConfig`.
38
+ *
39
+ * @public
40
+ */
41
+ var TableViewConfig = z.object({
42
+ /** The table view type */
43
+ view: z.enum([
44
+ "list",
45
+ "uncounted",
46
+ "list-expansion",
47
+ "tree",
48
+ "gantt",
49
+ "tree-gantt"
50
+ ]).optional(),
51
+ /** Allow the table to use the full width of its container */
52
+ fullWidth: z.boolean().optional(),
53
+ /** Default expansion state for tree views */
54
+ defaultTreeExpansion: z.enum([
55
+ "root",
56
+ "branch",
57
+ "leaf"
58
+ ]).optional(),
59
+ /** Enable dependency graph connections for Gantt views */
60
+ dependencyGraph: z.boolean().optional()
61
+ }).meta({
62
+ title: "TableViewConfig",
63
+ description: "JSON-safe view configuration for table fields in doctype authoring"
64
+ });
65
+ //#endregion
66
+ //#region src/field.ts
67
+ /**
68
+ * Field options - flexible bag for type-specific configuration.
69
+ *
70
+ * Usage:
71
+ * - Select: array of choices (["Draft", "Submitted", "Cancelled"])
72
+ * - Select with badges: \{ choices: [...], badges: \{ Open: "warning", ... \} \} or bare map
73
+ * - Decimal: config object (\{ precision: 10, scale: 2 \})
74
+ * - Code: config object (\{ language: "python" \})
75
+ *
76
+ * Deliberately *not* a bare string: a string once meant "link target", which made the value's
77
+ * shape encode its meaning. That job belongs to `ValueField.doctype`, leaving this a plain
78
+ * choices-or-config bag.
79
+ *
80
+ * @public
81
+ */
82
+ var FieldOptions = z.union([z.array(z.string()), z.record(z.string(), z.unknown())]).meta({
83
+ title: "FieldOptions",
84
+ description: "Field options - flexible bag for type-specific configuration"
85
+ });
86
+ /**
87
+ * Validation configuration for form fields
88
+ * @public
89
+ */
90
+ var FieldValidation = z.looseObject({
91
+ /** Error message to display when validation fails */
92
+ errorMessage: z.string() }).meta({
93
+ title: "FieldValidation",
94
+ description: "Validation configuration for form fields"
95
+ });
96
+ /**
97
+ * Which of the three field shapes an entry has, read from the entry's own structure.
98
+ *
99
+ * The single definition of that question. It had three copies before this — the parser's
100
+ * `injectKind`, {@link stripFieldKind}'s agreement check, and the docbuilder's own
101
+ * `isValueField` in another package — each free to drift, and drift here re-types a field rather
102
+ * than throwing: a value field read as a fieldset loses its column, a fieldset read as a value
103
+ * field loses every child.
104
+ *
105
+ * Deliberately **shape-only**: a declared `kind` is ignored. Two callers depend on that. The
106
+ * stripper compares this against the declaration to decide whether removing it is lossless, which
107
+ * it cannot do if this honours it. The docbuilder reads raw JSON off disk and classifies entries to
108
+ * decide which to render as editable rows — and `kind` is Stonecrop's own discriminant, not
109
+ * something a doctype author writes, so a tool reading a file has no business consulting it.
110
+ *
111
+ * `injectKind` is the one place a declaration still wins, and only to leave an already-parsed
112
+ * object untouched on its way back through.
113
+ *
114
+ * @param field - a field entry, authored or parsed
115
+ * @returns the kind its shape implies
116
+ * @public
117
+ */
118
+ function inferFieldKind(field) {
119
+ if (typeof field !== "object" || field === null || Array.isArray(field)) return "field";
120
+ if ("schema" in field) return "fieldset";
121
+ if ("columns" in field) return "table";
122
+ return "field";
123
+ }
124
+ /**
125
+ * Infers the `kind` discriminant from the structural properties of a raw field
126
+ * object, then injects it if absent. This allows authored JSON to omit `kind`
127
+ * entirely — a `schema` key means fieldset, `columns` means table, anything else
128
+ * is a value field.
129
+ *
130
+ * Rules (applied in order):
131
+ * has `schema` → fieldset
132
+ * has `columns` → table
133
+ * otherwise → field (value-holding scalar or link)
134
+ *
135
+ * Objects that already carry `kind` pass through unchanged (backward-compatible).
136
+ *
137
+ * Single-node only. Zod applies this at every level of the discriminated union (via the
138
+ * `z.lazy` in the fieldset schema), so nested fieldset children are normalized during a
139
+ * parse. Callers that bypass Zod — notably `Doctype.fromObject` — must use the exported
140
+ * {@link normalizeFieldKind} instead, which replicates that recursion.
141
+ */
142
+ function injectKind(data) {
143
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return data;
144
+ const obj = data;
145
+ if ("kind" in obj) return data;
146
+ return {
147
+ kind: inferFieldKind(obj),
148
+ ...obj
149
+ };
150
+ }
151
+ /**
152
+ * Recursively injects the `kind` discriminant into a raw field object and, for fieldsets,
153
+ * into each of its nested `schema` children — mirroring exactly what Zod's `preprocess`
154
+ * does at every level of the discriminated union.
155
+ *
156
+ * Table `columns` are {@link ColumnSchema} entries, not `DoctypeField`s, so they are left
157
+ * untouched — the Zod table schema validates them with a plain passthrough and never injects
158
+ * `kind` there either.
159
+ *
160
+ * Needed because `Doctype.fromObject` constructs a Doctype without running Zod, yet the
161
+ * registry's `resolveFields` gates link and fieldset handling on `field.kind`. Without this,
162
+ * a JSON-authored link resolves to a flat scalar and a fieldset's children are dropped.
163
+ *
164
+ * @public
165
+ */
166
+ function normalizeFieldKind(field) {
167
+ const injected = injectKind(field);
168
+ if (typeof injected !== "object" || injected === null) return injected;
169
+ const obj = injected;
170
+ if (obj.kind === "fieldset" && Array.isArray(obj.schema)) return {
171
+ ...obj,
172
+ schema: obj.schema.map(normalizeFieldKind)
173
+ };
174
+ return injected;
175
+ }
176
+ /**
177
+ * Remove the `kind` discriminant from a field, recursing into a fieldset's children.
178
+ *
179
+ * The outbound half of the boundary {@link normalizeFieldKind} owns inbound. `kind` is a
180
+ * discriminated-union tag the parser synthesizes, not something an author writes, so nothing that
181
+ * *writes* a doctype should put it on disk — the generator and the docbuilder's save both call
182
+ * this. Without it the two round-trip asymmetrically: every save adds a key the file never had.
183
+ *
184
+ * Strips only when `injectKind` would restore exactly what was removed. A fieldset carrying no
185
+ * `schema` re-infers as a plain field, so its `kind` is kept rather than silently re-typing the
186
+ * document; `DoctypeMeta` requires `schema` on a fieldset, so that shape is already invalid and
187
+ * belongs to the load gate, not here.
188
+ *
189
+ * Table `columns` are {@link ColumnSchema} entries rather than `DoctypeField`s and never carry an
190
+ * injected `kind`, so they are passed through untouched — the same asymmetry `injectKind` has.
191
+ *
192
+ * @param field - a field object, as held in memory after parsing
193
+ * @returns the field without `kind`, safe to serialize
194
+ * @public
195
+ */
196
+ function stripFieldKind(field) {
197
+ if (typeof field !== "object" || field === null || Array.isArray(field)) return field;
198
+ const obj = field;
199
+ if (obj.kind !== void 0 && obj.kind !== inferFieldKind(obj)) return field;
200
+ const { kind: _kind, ...rest } = obj;
201
+ if (Array.isArray(rest.schema)) return {
202
+ ...rest,
203
+ schema: rest.schema.map(stripFieldKind)
204
+ };
205
+ return rest;
206
+ }
207
+ /**
208
+ * The field properties a `source: 'introspected'` marker freezes — the ones the database owns.
209
+ *
210
+ * This is the single definition of the identity set. The docbuilder greys these inputs on an
211
+ * introspected field, and the converter's merge refuses to rewrite them. Stating it twice is how
212
+ * the two drift, so both read this constant.
213
+ *
214
+ * Everything absent from this list is author-owned, `component` most importantly: it chooses the
215
+ * widget, which is an authoring decision the database has no opinion about.
216
+ *
217
+ * @public
218
+ */
219
+ var INTROSPECTED_IDENTITY_PROPS = [
220
+ "fieldname",
221
+ "primaryKey",
222
+ "required",
223
+ "options",
224
+ "cardinality",
225
+ "doctype"
226
+ ];
227
+ /**
228
+ * Find the field a doctype marks as its primary key, or `undefined` when none is marked.
229
+ *
230
+ * This is the single definition of "which field identifies a record". Both sides depend on it:
231
+ * the middleware builds the SQL identity predicate from it, and the client resolves a record's
232
+ * route/store key from it. Call this; never re-derive the rule at the call site, or the two will
233
+ * drift and the client will key records by a column the server never queried.
234
+ *
235
+ * Two deliberate rules, both matching the shape `primaryKey` actually has:
236
+ * - Fieldset children are **included**, via {@link flattenFields}. A fieldset is layout, not
237
+ * scope: its children are fields of the doctype with columns of their own, which is why the
238
+ * adapter's SELECT already descends and why `getDisplayField` does too. Scanning top level only
239
+ * did not *refuse* a nested declaration — it ignored one, so an author marked identity and
240
+ * nothing honoured it and nothing said so.
241
+ * - The **first** match in document order wins. Identity is single-valued by design — a doctype
242
+ * describes the API surface, and mapping a composite database key onto one identity there is the
243
+ * adapter's job — so a doctype declaring several is malformed rather than composite.
244
+ * `DoctypeMeta` rejects that at the load gate; this stays total for callers holding fields that
245
+ * never went through it.
246
+ *
247
+ * @param fields - the doctype's fields; fieldset children are descended into
248
+ * @returns the primary-key field, or `undefined` for a PK-less doctype
249
+ * @public
250
+ */
251
+ function getPrimaryKeyField(fields) {
252
+ return flattenFields(fields).find((f) => f.kind === "field" && Boolean(f.primaryKey));
253
+ }
254
+ /**
255
+ * Resolve the field a doctype nominates as its display text, or `undefined` when the nomination
256
+ * does not name a readable column.
257
+ *
258
+ * This is the single definition of "is this a usable `displayField`". Both sides depend on it:
259
+ * `DoctypeMeta` refuses a bad nomination at the load gate, and the adapter builds a SELECT from
260
+ * the field it returns. Call this; never re-derive the rule, or the gate and the query will
261
+ * disagree about which nominations are legal — which they did, in both directions at once.
262
+ *
263
+ * Two things disqualify a nomination, and both are the doctype saying so itself:
264
+ * - it names no field at all, fieldset children included
265
+ * - it names a `computed` field, which is declared precisely to state it has no column, so a
266
+ * SELECT built from it would reference a column the database does not have
267
+ *
268
+ * @param fields - the doctype's top-level fields
269
+ * @param displayField - the nominated fieldname
270
+ * @returns the nominated field, or `undefined` when it is not a readable column
271
+ * @public
272
+ */
273
+ function getDisplayField(fields, displayField) {
274
+ if (!displayField) return void 0;
275
+ return flattenFields(fields).find((f) => f.kind === "field" && !f.computed && f.fieldname === displayField);
276
+ }
277
+ /**
278
+ * The name of the field a record is identified by: the declared `primaryKey`, or `id` when the
279
+ * doctype declares none.
280
+ *
281
+ * The `id` fallback is load-bearing, not defensive — a surrogate-key doctype carries an `id`
282
+ * column and marks no primary key, so "nothing declared" means `id`, not "no identity".
283
+ *
284
+ * This exists because that one-line rule had been restated at four sites — the client's
285
+ * `Doctype.recordIdField`, both nuxt hosts' `recordLookupField`, and the Postgres adapter — and
286
+ * the fourth had omitted the fallback, so a doctype the client keyed by `id` was one the adapter
287
+ * could not look up at all. Call this; a fifth restatement is how they diverge again.
288
+ *
289
+ * The returned name is not guaranteed to be a declared field: a doctype that declares no
290
+ * `primaryKey` and no `id` yields `'id'` regardless. An adapter that must build a SQL predicate
291
+ * from it has to confirm the field exists and say so when it does not, because selecting a column
292
+ * the doctype never declared returns nothing rather than failing.
293
+ *
294
+ * @param fields - the doctype's top-level fields
295
+ * @returns the identifying fieldname
296
+ * @public
297
+ */
298
+ function getRecordIdField(fields) {
299
+ return getPrimaryKeyField(fields)?.fieldname ?? "id";
300
+ }
301
+ /**
302
+ * Resolve a record's identity value using the doctype's declared primary key.
303
+ *
304
+ * Falls back to `record.id` when the doctype declares no `primaryKey`. That fallback is
305
+ * load-bearing, not defensive: surrogate-key doctypes carry an `id` column and never mark a
306
+ * primary key, and PostGraphile renames a single-column `id` PK to `rowId` — so the declared
307
+ * field and `id` are both real sources, in that order.
308
+ *
309
+ * @param fields - the doctype's top-level fields
310
+ * @param record - the record to read the identity from
311
+ * @returns the identity as a string, or `undefined` when neither source yields a usable value
312
+ * @public
313
+ */
314
+ function getRecordIdentity(fields, record) {
315
+ const pkField = getPrimaryKeyField(fields);
316
+ const candidates = pkField ? [record[pkField.fieldname], record.id] : [record.id];
317
+ for (const value of candidates) {
318
+ if (typeof value === "number") return String(value);
319
+ if (typeof value === "string" && value !== "") return value;
320
+ }
321
+ }
322
+ function createDoctypeFieldSchemas() {
323
+ const ValueFieldSchema = z.object({
324
+ kind: z.literal("field"),
325
+ fieldname: z.string().min(1),
326
+ component: z.string().min(1),
327
+ primaryKey: z.boolean().optional(),
328
+ computed: z.boolean().optional(),
329
+ language: z.string().optional(),
330
+ doctype: z.string().min(1).optional(),
331
+ label: z.string().optional(),
332
+ width: z.string().optional(),
333
+ height: z.string().optional(),
334
+ align: z.enum([
335
+ "left",
336
+ "center",
337
+ "right",
338
+ "start",
339
+ "end"
340
+ ]).optional(),
341
+ edit: z.boolean().optional(),
342
+ mask: z.string().optional(),
343
+ format: z.string().optional(),
344
+ mode: z.enum([
345
+ "edit",
346
+ "read",
347
+ "display"
348
+ ]).optional(),
349
+ options: FieldOptions.optional(),
350
+ required: z.boolean().optional(),
351
+ readOnly: z.boolean().optional(),
352
+ hidden: z.boolean().optional(),
353
+ default: z.unknown().optional(),
354
+ validation: FieldValidation.optional(),
355
+ cardinality: z.enum([
356
+ "atMostOne",
357
+ "one",
358
+ "noneOrMany",
359
+ "atLeastOne"
360
+ ]).optional(),
361
+ source: z.literal("introspected").optional()
362
+ }).meta({ title: "ValueField" });
363
+ const TableFieldSchema = z.object({
364
+ kind: z.literal("table"),
365
+ fieldname: z.string().min(1),
366
+ component: z.string().optional(),
367
+ label: z.string().optional(),
368
+ columns: z.array(z.object({ fieldname: z.string().min(1) }).passthrough()),
369
+ config: TableViewConfig.optional(),
370
+ mode: z.enum([
371
+ "edit",
372
+ "read",
373
+ "display"
374
+ ]).optional()
375
+ }).meta({ title: "TableField" });
376
+ let DoctypeFieldSchema = z.never();
377
+ const FieldsetFieldSchema = z.object({
378
+ kind: z.literal("fieldset"),
379
+ fieldname: z.string().min(1),
380
+ component: z.string().optional(),
381
+ label: z.string().optional(),
382
+ collapsible: z.boolean().optional(),
383
+ mode: z.enum([
384
+ "edit",
385
+ "read",
386
+ "display"
387
+ ]).optional(),
388
+ schema: z.lazy(() => DoctypeFieldSchema.array())
389
+ }).meta({ title: "FieldsetField" });
390
+ const rawUnion = z.discriminatedUnion("kind", [
391
+ ValueFieldSchema,
392
+ FieldsetFieldSchema,
393
+ TableFieldSchema
394
+ ]);
395
+ DoctypeFieldSchema = z.preprocess(injectKind, rawUnion);
396
+ return {
397
+ ValueFieldSchema,
398
+ TableFieldSchema,
399
+ FieldsetFieldSchema,
400
+ DoctypeFieldSchema
401
+ };
402
+ }
403
+ var schemas = createDoctypeFieldSchemas();
404
+ /**
405
+ * Zod runtime validation schema for ValueField.
406
+ * @public
407
+ */
408
+ var ValueFieldSchema = schemas.ValueFieldSchema;
409
+ /**
410
+ * Zod runtime validation schema for FieldsetField.
411
+ * Recursive — FieldsetField.schema is validated against DoctypeFieldSchema.
412
+ * @public
413
+ */
414
+ var FieldsetFieldSchema = schemas.FieldsetFieldSchema;
415
+ /**
416
+ * Zod runtime validation schema for TableField.
417
+ * @public
418
+ */
419
+ var TableFieldSchema = schemas.TableFieldSchema;
420
+ /**
421
+ * Zod runtime validation schema for the DoctypeField discriminated union.
422
+ * Validates all three field variants: `'field'`, `'fieldset'`, `'table'`.
423
+ * @public
424
+ */
425
+ var DoctypeFieldSchema = schemas.DoctypeFieldSchema;
426
+ //#endregion
427
+ //#region src/naming.ts
428
+ /**
429
+ * Naming Convention Utilities
430
+ * Converts between various naming conventions (snake_case, camelCase, PascalCase, kebab-case)
431
+ * @packageDocumentation
432
+ */
433
+ /**
434
+ * Converts snake_case to camelCase
435
+ * @param snakeCase - Snake case string
436
+ * @returns Camel case string
437
+ * @public
438
+ * @example
439
+ * ```typescript
440
+ * snakeToCamel('user_email') // 'userEmail'
441
+ * snakeToCamel('created_at') // 'createdAt'
442
+ * ```
443
+ */
444
+ function snakeToCamel(snakeCase) {
445
+ return snakeCase.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
446
+ }
447
+ /**
448
+ * Converts camelCase to snake_case
449
+ * @param camelCase - Camel case string
450
+ * @returns Snake case string
451
+ * @public
452
+ * @example
453
+ * ```typescript
454
+ * camelToSnake('userEmail') // 'user_email'
455
+ * camelToSnake('createdAt') // 'created_at'
456
+ * ```
457
+ */
458
+ function camelToSnake(camelCase) {
459
+ return camelCase.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
460
+ }
461
+ /**
462
+ * Converts snake_case to Title Case label
463
+ * @param snakeCase - Snake case string
464
+ * @returns Title case label
465
+ * @public
466
+ * @example
467
+ * ```typescript
468
+ * snakeToLabel('user_email') // 'User Email'
469
+ * snakeToLabel('first_name') // 'First Name'
470
+ * ```
471
+ */
472
+ function snakeToLabel(snakeCase) {
473
+ return snakeCase.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
474
+ }
475
+ /**
476
+ * Converts camelCase to Title Case label
477
+ * @param camelCase - Camel case string
478
+ * @returns Title case label
479
+ * @public
480
+ * @example
481
+ * ```typescript
482
+ * camelToLabel('userEmail') // 'User Email'
483
+ * camelToLabel('firstName') // 'First Name'
484
+ * ```
485
+ */
486
+ function camelToLabel(camelCase) {
487
+ const withSpaces = camelCase.replace(/([A-Z])/g, " $1").trim();
488
+ return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
489
+ }
490
+ /**
491
+ * Convert table name to PascalCase doctype name
492
+ * @param tableName - SQL table name (snake_case)
493
+ * @returns PascalCase name
494
+ * @public
495
+ */
496
+ function toPascalCase(tableName) {
497
+ return tableName.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
498
+ }
499
+ /**
500
+ * Convert to kebab-case slug
501
+ * @param name - Name to convert
502
+ * @returns kebab-case slug
503
+ * @public
504
+ */
505
+ function toSlug(name) {
506
+ return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
507
+ }
508
+ /**
509
+ * Convert PascalCase to snake_case (e.g., for deriving table names from type names)
510
+ * @param pascal - PascalCase string
511
+ * @returns snake_case string
512
+ * @public
513
+ * @example
514
+ * ```typescript
515
+ * pascalToSnake('SalesOrder') // 'sales_order'
516
+ * pascalToSnake('SalesOrderItem') // 'sales_order_item'
517
+ * ```
518
+ */
519
+ function pascalToSnake(pascal) {
520
+ return pascal.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
521
+ }
522
+ //#endregion
523
+ //#region src/doctype.ts
524
+ /**
525
+ * Cardinality for relationship links.
526
+ * @public
527
+ */
528
+ var Cardinality = z.enum([
529
+ "atMostOne",
530
+ "one",
531
+ "noneOrMany",
532
+ "atLeastOne"
533
+ ]).meta({
534
+ title: "Cardinality",
535
+ description: "Cardinality for relationship links between doctypes"
536
+ });
537
+ /**
538
+ * Sync fetch strategy - data is fetched in the initial query.
539
+ * @public
540
+ */
541
+ var SyncFetch = z.object({
542
+ /** Fetch method type */
543
+ method: z.literal("sync"),
544
+ /** Optional limit on number of records to fetch */
545
+ limit: z.number().int().positive().optional()
546
+ }).meta({
547
+ title: "SyncFetch",
548
+ description: "Sync fetch strategy - data is fetched in the initial query"
549
+ });
550
+ /**
551
+ * Lazy fetch strategy - data is fetched on demand in a separate query.
552
+ * @public
553
+ */
554
+ var LazyFetch = z.object({
555
+ /** Fetch method type */
556
+ method: z.literal("lazy") }).meta({
557
+ title: "LazyFetch",
558
+ description: "Lazy fetch strategy - data is fetched on demand in a separate query"
559
+ });
560
+ /**
561
+ * Custom fetch strategy - uses a custom handler function.
562
+ * @public
563
+ */
564
+ var CustomFetch = z.object({
565
+ /** Fetch method type */
566
+ method: z.literal("custom"),
567
+ /** Serialized handler function to invoke */
568
+ handler: z.string()
569
+ }).meta({
570
+ title: "CustomFetch",
571
+ description: "Custom fetch strategy - uses a custom handler function"
572
+ });
573
+ /**
574
+ * Fetch strategy for link data loading.
575
+ * - sync: fetched in the initial query
576
+ * - lazy: fetched on demand in a separate query
577
+ * - custom: uses a custom handler function
578
+ * @public
579
+ */
580
+ var FetchStrategy = z.discriminatedUnion("method", [
581
+ SyncFetch,
582
+ LazyFetch,
583
+ CustomFetch
584
+ ]).meta({
585
+ title: "FetchStrategy",
586
+ description: "Fetch strategy for link data loading"
587
+ });
588
+ /**
589
+ * Link declaration - describes a relationship from one doctype to another.
590
+ * @public
591
+ */
592
+ var LinkDeclaration = z.object({
593
+ /** Target doctype slug */
594
+ target: z.string().min(1),
595
+ /** Cardinality of the relationship */
596
+ cardinality: Cardinality,
597
+ /** Backlink fieldname on the target doctype that points back to this link */
598
+ backlink: z.string().optional(),
599
+ /** Override default rendering component (AForm for 1:1, ATable for 1:many) */
600
+ component: z.string().optional(),
601
+ /** Fieldname of the corresponding Link field in the fields array */
602
+ fieldname: z.string().min(1).optional(),
603
+ /** Fetch strategy for loading nested data */
604
+ fetch: FetchStrategy.optional(),
605
+ /** Whether to block workflow actions until nested data is loaded (default: true) */
606
+ blockWorkflows: z.boolean().optional()
607
+ }).meta({
608
+ title: "LinkDeclaration",
609
+ description: "Declares a relationship from one doctype to another"
610
+ });
611
+ /**
612
+ * Action definition within a workflow
613
+ * @public
614
+ */
615
+ var ActionDefinition = z.object({
616
+ /** Display label for the action */
617
+ label: z.string().min(1),
618
+ /** Fields that must have values before action can execute */
619
+ requiredFields: z.array(z.string()).optional(),
620
+ /** Workflow states where this action is available */
621
+ allowedStates: z.array(z.string()).optional(),
622
+ /** The state the record transitions to after this action executes */
623
+ nextState: z.string().optional(),
624
+ /** True for stateless command actions with no workflow effect at all (print, email, etc.) */
625
+ stateless: z.boolean().optional(),
626
+ /**
627
+ * True for an internal self-transition: the action runs within the current state without
628
+ * advancing the workflow (e.g. `save`, which mutates record data but stays put). Scoped by
629
+ * `allowedStates`, rendered as a self-loop in the graph, and has no `nextState`. Distinct from
630
+ * `stateless` (which has no workflow presence at all): a self-transition is graph-owned and,
631
+ * unlike a stateless command, persists record data on dispatch.
632
+ */
633
+ selfTransition: z.boolean().optional(),
634
+ /** JS function body stored as a string; executed client-side via AsyncFunction with injected API surface */
635
+ clientHandler: z.string().optional()
636
+ }).meta({
637
+ title: "ActionDefinition",
638
+ description: "Action definition within a workflow"
639
+ });
640
+ /**
641
+ * Reactive field-validation trigger — advisory, client-side only.
642
+ *
643
+ * A Trigger is a docbuilder-authored validator: when any field in `on` is edited, its
644
+ * `clientHandler` runs (client-side, no rollback) and may flag a field inline to block save
645
+ * in the UI. It is deliberately a **sibling** to {@link (ActionDefinition:type)}, not a member of it —
646
+ * a reactive validator is not a user-invoked action, so it lives in the `triggers` map on
647
+ * {@link (WorkflowMeta:type)} and never appears to action readers (transition/command dropdowns, the FSM graph).
648
+ *
649
+ * The two bindings are independent: `on` is the fire-set (which fields' edits run it), while the
650
+ * `setError(field, msg)` call inside `clientHandler` chooses which field displays the error.
651
+ * @public
652
+ */
653
+ var TriggerDefinition = z.object({
654
+ /** Optional display label; the map key is the trigger's identity */
655
+ label: z.string().optional(),
656
+ /** Fieldnames whose edits fire this trigger (fires when any listed field changes) */
657
+ on: z.array(z.string()),
658
+ /** JS function body stored as a string; run client-side with `{ record, value, setError }`. Advisory. */
659
+ clientHandler: z.string()
660
+ }).meta({
661
+ title: "TriggerDefinition",
662
+ description: "Reactive field-validation trigger — advisory client-side"
663
+ });
664
+ /**
665
+ * Whether a workflow action may run from `currentState`.
666
+ *
667
+ * Single source of truth for the "is this action available here" rule, shared by
668
+ * the frontend (`getAvailableTransitions`) and the server-side dispatch guard so
669
+ * the two can never disagree. Empty or absent `allowedStates` means the action is
670
+ * available in ALL states — a plain `allowedStates.includes(currentState)` would
671
+ * wrongly block such actions everywhere.
672
+ *
673
+ * @public
674
+ */
675
+ function isActionAllowedInState(action, currentState) {
676
+ const allowedStates = action.allowedStates;
677
+ if (!allowedStates || allowedStates.length === 0) return true;
678
+ return allowedStates.includes(currentState);
679
+ }
680
+ /**
681
+ * DocBuilder graph layout — node positions for the workflow-state graph, keyed by state name.
682
+ * Pure authoring view-state: persisted in the doctype JSON so an author's manual arrangement
683
+ * survives reloads, but — exactly like {@link (WorkflowMeta:type)}'s `triggers` — it is client-only
684
+ * and never mirrored into the runtime GraphQL SDL (see the WorkflowMeta type in the host SDLs, which
685
+ * expose only `states`/`actions`). The shape mirrors VueFlow's node fields; `position` is the node's
686
+ * canvas coordinate and `targetPosition`/`sourcePosition` are the handle sides.
687
+ * @public
688
+ */
689
+ var WorkflowLayout = z.record(z.string(), z.object({
690
+ position: z.object({
691
+ x: z.number(),
692
+ y: z.number()
693
+ }).optional(),
694
+ targetPosition: z.enum([
695
+ "left",
696
+ "top",
697
+ "right",
698
+ "bottom"
699
+ ]).optional(),
700
+ sourcePosition: z.enum([
701
+ "left",
702
+ "top",
703
+ "right",
704
+ "bottom"
705
+ ]).optional()
706
+ }));
707
+ /**
708
+ * Workflow metadata - states and actions for a doctype
709
+ * @public
710
+ */
711
+ var WorkflowMeta = z.object({
712
+ /** List of workflow states */
713
+ states: z.array(z.string()).optional(),
714
+ /** Actions available in this workflow */
715
+ actions: z.record(z.string(), ActionDefinition).optional(),
716
+ /** Reactive field-validation triggers (advisory, client-side), keyed by trigger name */
717
+ triggers: z.record(z.string(), TriggerDefinition).optional(),
718
+ /**
719
+ * DocBuilder node positions keyed by state name — authoring view-state. Persisted here so a
720
+ * doctype author's manual graph arrangement survives reloads; like `triggers`, it is client-only
721
+ * and never enters the runtime GraphQL SDL. See {@link (WorkflowLayout:variable)}.
722
+ */
723
+ layout: WorkflowLayout.optional()
724
+ }).meta({
725
+ title: "WorkflowMeta",
726
+ description: "Workflow metadata - states and actions for a doctype"
727
+ });
728
+ /**
729
+ * Doctype metadata - complete definition of a doctype
730
+ * @public
731
+ */
732
+ var DoctypeMeta = z.object({
733
+ /** Display name of the doctype */
734
+ name: z.string().min(1),
735
+ /** URL-friendly slug (kebab-case) */
736
+ slug: z.string().min(1).optional(),
737
+ /**
738
+ * Field on this doctype used when displaying a reference to one of its records.
739
+ * When a record elsewhere holds an inline foreign key to this doctype, the middleware
740
+ * returns that field as `{ id, displayText }`, reading `displayText` from this field.
741
+ */
742
+ displayField: z.string().min(1).optional(),
743
+ /**
744
+ * URL path this doctype registers at, written literally — `/order` for a collection,
745
+ * `/order/:id` for a record. Absent means the doctype has no page of its own, which is the
746
+ * common case: a child table is reached inside its parent, never at a URL.
747
+ *
748
+ * A path rather than a segment because the record parameter has to be somewhere, and a host
749
+ * that reads a bare segment has to know which kind of doctype it is holding to decide where
750
+ * to put it. Writing it out means nothing downstream re-derives it.
751
+ */
752
+ route: z.string().startsWith("/").optional(),
753
+ /** Field definitions (a link field is one carrying `doctype`) */
754
+ fields: z.array(DoctypeFieldSchema),
755
+ /** Relationship links to other doctypes */
756
+ links: z.record(z.string(), LinkDeclaration).optional(),
757
+ /** Workflow configuration */
758
+ workflow: WorkflowMeta.optional(),
759
+ /** Parent doctype for inheritance */
760
+ inherits: z.string().optional()
761
+ }).meta({
762
+ title: "DoctypeMeta",
763
+ description: "Doctype metadata - complete definition of a doctype"
764
+ }).superRefine((doctype, ctx) => {
765
+ const declared = flattenFields(doctype.fields).filter((f) => f.kind === "field" && f.primaryKey);
766
+ if (declared.length > 1) ctx.addIssue({
767
+ code: "custom",
768
+ path: ["fields"],
769
+ message: `Doctype declares ${declared.length} primaryKey fields (${declared.map((f) => f.kind === "field" ? f.fieldname : "").join(", ")}); a record is identified by exactly one field. A composite database key is mapped to a single identity by the adapter, so a doctype never declares its parts`
770
+ });
771
+ if (doctype.displayField && !getDisplayField(doctype.fields, doctype.displayField)) {
772
+ const named = flattenFields(doctype.fields).find((f) => f.fieldname === doctype.displayField);
773
+ ctx.addIssue({
774
+ code: "custom",
775
+ path: ["displayField"],
776
+ message: named ? `displayField "${doctype.displayField}" names a computed field, which has no column to read a display value from` : `displayField "${doctype.displayField}" is not declared on this doctype`
777
+ });
778
+ }
779
+ });
780
+ /**
781
+ * The one string a doctype is addressed by.
782
+ *
783
+ * A doctype carries two names — `name` (`OrderItem`) and `slug` (`order-item`) — and every registry
784
+ * must agree on which one keys it. Three implementations had drifted apart: the adapter's registry is
785
+ * keyed by `name` and its `getMeta` also scans for a matching `slug`, so it accepts **either**; the
786
+ * client's registry is keyed by a slug it derives itself and accepts **only** that; and
787
+ * `Doctype.fromObject` dropped an authored `slug` on the floor and re-derived one regardless. The
788
+ * adapter's accepted set was therefore a strict superset of the client's, and a link target written
789
+ * as the Name booted the server, passed its reference check, served rows over GraphQL, and was
790
+ * silently dropped by the client — an expanding child table rendering as one empty text input, with
791
+ * nothing logged.
792
+ *
793
+ * Resolving through this in both runtimes is what makes the two answers the same answer. It is the
794
+ * derivation only; a *lookup* still belongs to whichever registry owns the corpus, because the two
795
+ * corpora legitimately differ (a client registers lazily, and a client-only host has no adapter at
796
+ * all).
797
+ *
798
+ * An authored `slug` wins over the derived one because the authored doctype is the source of truth:
799
+ * generation verifies a file and never overwrites it, so a doctype that states its own slug means it.
800
+ * Deriving unconditionally is what `fromObject` did, and it made an authored `slug` a silent no-op on
801
+ * one side of the wire while the other honoured it.
802
+ *
803
+ * @param doctype - anything carrying a doctype's `name` and optional authored `slug`
804
+ * @returns the canonical slug
805
+ * @public
806
+ *
807
+ * @example
808
+ * ```typescript
809
+ * getDoctypeSlug({ name: 'OrderItem' }) // 'order-item'
810
+ * getDoctypeSlug({ name: 'Planner', slug: 'planner-board' }) // 'planner-board'
811
+ * ```
812
+ */
813
+ function getDoctypeSlug(doctype) {
814
+ return doctype.slug || toSlug(doctype.name);
815
+ }
816
+ /**
817
+ * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.
818
+ *
819
+ * @deprecated The `__display` suffix pattern is no longer used. Inline link fields are enriched
820
+ * server-side by `@stonecrop/graphql-middleware` as `{ id, displayText }` objects on the link
821
+ * field itself.
822
+ * @public
823
+ */
824
+ var LINK_DISPLAY_SUFFIX = "__display";
825
+ /**
826
+ * Build the payload key for a link field's display text (e.g. `customerId__display`).
827
+ *
828
+ * @deprecated The `__display` suffix pattern is no longer used. Inline link fields are enriched
829
+ * server-side by `@stonecrop/graphql-middleware` as `{ id, displayText }` objects on the link
830
+ * field itself.
831
+ * @public
832
+ */
833
+ function linkDisplayFieldname(fieldname) {
834
+ return `${fieldname}${LINK_DISPLAY_SUFFIX}`;
835
+ }
836
+ //#endregion
837
+ //#region src/validation.ts
838
+ /**
839
+ * Validate a field definition against the DoctypeField discriminated union
840
+ * @param data - Data to validate
841
+ * @returns Validation result
842
+ * @public
843
+ */
844
+ function validateField(data) {
845
+ const result = DoctypeFieldSchema.safeParse(data);
846
+ if (result.success) return {
847
+ success: true,
848
+ errors: []
849
+ };
850
+ return {
851
+ success: false,
852
+ errors: result.error.issues.map((issue) => ({
853
+ path: issue.path,
854
+ message: issue.message
855
+ }))
856
+ };
857
+ }
858
+ /**
859
+ * Validate a doctype definition
860
+ * @param data - Data to validate
861
+ * @returns Validation result
862
+ * @public
863
+ */
864
+ function validateDoctype(data) {
865
+ const result = DoctypeMeta.safeParse(data);
866
+ if (result.success) return {
867
+ success: true,
868
+ errors: []
869
+ };
870
+ return {
871
+ success: false,
872
+ errors: result.error.issues.map((issue) => ({
873
+ path: issue.path,
874
+ message: issue.message
875
+ }))
876
+ };
877
+ }
878
+ /**
879
+ * Parse and validate a field, throwing on failure
880
+ * @param data - Data to parse
881
+ * @returns Validated DoctypeField
882
+ * @throws ZodError if validation fails
883
+ * @public
884
+ */
885
+ function parseField(data) {
886
+ return DoctypeFieldSchema.parse(data);
887
+ }
888
+ /**
889
+ * Parse and validate a doctype, throwing on failure
890
+ * @param data - Data to parse
891
+ * @returns Validated DoctypeMeta
892
+ * @throws ZodError if validation fails
893
+ * @public
894
+ */
895
+ function parseDoctype(data) {
896
+ return DoctypeMeta.parse(data);
897
+ }
898
+ //#endregion
899
+ //#region src/converter/scalars.ts
900
+ /**
901
+ * Mapping from standard GraphQL scalar types to Stonecrop field types.
902
+ * These are defined by the GraphQL specification and are always available.
903
+ *
904
+ * @public
905
+ */
906
+ var GQL_SCALAR_MAP = {
907
+ String: { component: "ATextInput" },
908
+ Int: { component: "ANumericInput" },
909
+ Float: { component: "ANumericInput" },
910
+ Boolean: { component: "ACheckbox" },
911
+ ID: { component: "ATextInput" }
912
+ };
913
+ /**
914
+ * Mapping from well-known custom GraphQL scalars to Stonecrop field types.
915
+ * These cover scalars commonly used across GraphQL servers (PostGraphile, Hasura, etc.)
916
+ * without baking in knowledge of any specific server.
917
+ *
918
+ * Entries here have lower precedence than `customScalars` from options, but higher
919
+ * precedence than unknown/unmapped scalars.
920
+ *
921
+ * @public
922
+ */
923
+ var WELL_KNOWN_SCALARS = {
924
+ BigFloat: { component: "ANumericInput" },
925
+ BigDecimal: { component: "ANumericInput" },
926
+ Decimal: { component: "ANumericInput" },
927
+ BigInt: { component: "ANumericInput" },
928
+ Long: { component: "ANumericInput" },
929
+ UUID: { component: "ATextInput" },
930
+ DateTime: { component: "ADateTime" },
931
+ Datetime: { component: "ADateTime" },
932
+ Date: { component: "ADate" },
933
+ Time: { component: "ATextInput" },
934
+ Interval: { component: "ADuration" },
935
+ Duration: { component: "ADuration" },
936
+ JSON: { component: "ACodeEditor" },
937
+ JSONObject: { component: "ACodeEditor" },
938
+ JsonNode: { component: "ACodeEditor" }
939
+ };
940
+ /**
941
+ * Set of scalar type names that are internal to GraphQL servers and should be skipped
942
+ * during field conversion (they don't represent meaningful data fields).
943
+ *
944
+ * @public
945
+ */
946
+ var INTERNAL_SCALARS = /* @__PURE__ */ new Set(["Cursor"]);
947
+ /**
948
+ * Build a merged scalar map from the built-in maps and user-provided custom scalars.
949
+ * Precedence (highest to lowest): customScalars → GQL_SCALAR_MAP → WELL_KNOWN_SCALARS
950
+ *
951
+ * @param customScalars - User-provided scalar overrides
952
+ * @returns Merged scalar map
953
+ * @public
954
+ */
955
+ function buildScalarMap(customScalars) {
956
+ const merged = { ...WELL_KNOWN_SCALARS };
957
+ for (const [key, value] of Object.entries(GQL_SCALAR_MAP)) merged[key] = value;
958
+ if (customScalars) for (const [key, value] of Object.entries(customScalars)) merged[key] = { component: value.component ?? "ATextInput" };
959
+ return merged;
960
+ }
961
+ //#endregion
962
+ //#region src/converter/heuristics.ts
963
+ /**
964
+ * Default heuristics for identifying entity types and fields in a GraphQL schema.
965
+ *
966
+ * These heuristics work across common GraphQL servers (PostGraphile, Hasura, Apollo, etc.)
967
+ * by detecting widely-adopted conventions like the Relay connection pattern.
968
+ *
969
+ * All heuristics can be overridden via the `isEntityType`, `isEntityField`, and
970
+ * `classifyField` options in `GraphQLConversionOptions`.
971
+ *
972
+ * @packageDocumentation
973
+ */
974
+ /**
975
+ * Suffixes that identify synthetic/framework types generated by GraphQL servers.
976
+ * Types ending with these suffixes are typically not entities.
977
+ */
978
+ var SYNTHETIC_SUFFIXES = [
979
+ "Connection",
980
+ "Edge",
981
+ "Input",
982
+ "Patch",
983
+ "Payload",
984
+ "Condition",
985
+ "Filter",
986
+ "OrderBy",
987
+ "Aggregate",
988
+ "AggregateResult",
989
+ "AggregateFilter",
990
+ "DeleteResponse",
991
+ "InsertResponse",
992
+ "UpdateResponse",
993
+ "MutationResponse"
994
+ ];
995
+ /**
996
+ * Root operation type names that are never entities.
997
+ */
998
+ var ROOT_TYPE_NAMES = /* @__PURE__ */ new Set([
999
+ "Query",
1000
+ "Mutation",
1001
+ "Subscription"
1002
+ ]);
1003
+ /**
1004
+ * Default heuristic to determine if a GraphQL object type represents an entity.
1005
+ * An entity type becomes a Stonecrop doctype.
1006
+ *
1007
+ * This heuristic excludes:
1008
+ * - Introspection types (`__*`)
1009
+ * - Root operation types (`Query`, `Mutation`, `Subscription`)
1010
+ * - Types with synthetic suffixes (e.g., `*Connection`, `*Edge`, `*Input`)
1011
+ * - Types starting with `Node` interface marker (exact match only)
1012
+ *
1013
+ * @param typeName - The GraphQL type name
1014
+ * @param type - The GraphQL object type definition
1015
+ * @returns `true` if this type should become a Stonecrop doctype
1016
+ * @public
1017
+ */
1018
+ function defaultIsEntityType(typeName, type) {
1019
+ if (typeName.startsWith("__")) return false;
1020
+ if (ROOT_TYPE_NAMES.has(typeName)) return false;
1021
+ if (typeName === "Node") return false;
1022
+ for (const suffix of SYNTHETIC_SUFFIXES) if (typeName.endsWith(suffix)) return false;
1023
+ const fields = type.getFields();
1024
+ if (Object.keys(fields).length === 0) return false;
1025
+ return true;
1026
+ }
1027
+ /**
1028
+ * Fields to skip by default on entity types.
1029
+ * These are internal to GraphQL servers and don't represent semantic data.
1030
+ *
1031
+ * Relay's global object identifier is deliberately absent: which field carries it is a
1032
+ * declaration, not a name. See {@link relayNodeIdField}.
1033
+ */
1034
+ var SKIP_FIELDS = /* @__PURE__ */ new Set(["__typename", "clientMutationId"]);
1035
+ /**
1036
+ * The name Relay's Object Identification spec gives its marker interface.
1037
+ */
1038
+ var RELAY_NODE_INTERFACE = "Node";
1039
+ /**
1040
+ * The field carrying Relay's global object identifier on this type, or `undefined` for a type that
1041
+ * declares none.
1042
+ *
1043
+ * Read off the interface rather than matched against a list of names, because the name is a server
1044
+ * setting: PostGraphile exposes it as `nodeIdFieldName`, which is `id` under the un-overridden
1045
+ * Amber preset, `nodeId` under Stonecrop's, and whatever a foreign host chose under theirs. A
1046
+ * hardcoded name is a snapshot of one of those, and gets it wrong in both directions at once — it
1047
+ * emits an opaque identifier as a column (whose every read then fails on a column that does not
1048
+ * exist), and drops a real column that happens to share the name.
1049
+ *
1050
+ * The interface must be Relay's marker and not a domain interface that shares its name, so it has
1051
+ * to declare exactly one field, a non-null `ID`, and nothing else — anything carrying domain fields
1052
+ * is a different interface, and skipping against it would drop real columns.
1053
+ *
1054
+ * @internal
1055
+ */
1056
+ function relayNodeIdField(type) {
1057
+ for (const iface of type.getInterfaces()) {
1058
+ if (iface.name !== RELAY_NODE_INTERFACE) continue;
1059
+ const declared = Object.values(iface.getFields());
1060
+ if (declared.length !== 1) continue;
1061
+ const { namedType, required, isList } = unwrapType(declared[0].type);
1062
+ if (required && !isList && namedType.name === "ID") return declared[0].name;
1063
+ }
1064
+ }
1065
+ /**
1066
+ * Default heuristic to filter fields on entity types.
1067
+ * Skips internal fields that don't represent meaningful data.
1068
+ *
1069
+ * @param fieldName - The GraphQL field name
1070
+ * @param _field - The GraphQL field definition (unused in default implementation)
1071
+ * @param parentType - The parent entity type, whose interfaces declare its Relay identifier
1072
+ * @returns `true` if this field should be included
1073
+ * @public
1074
+ */
1075
+ function defaultIsEntityField(fieldName, _field, parentType) {
1076
+ if (SKIP_FIELDS.has(fieldName)) return false;
1077
+ return fieldName !== relayNodeIdField(parentType);
1078
+ }
1079
+ /**
1080
+ * Unwrap NonNull and List wrappers from a GraphQL type, tracking nullability.
1081
+ *
1082
+ * @param type - The GraphQL output type
1083
+ * @returns The unwrapped named type, whether it's required, and whether it's a list
1084
+ * @internal
1085
+ */
1086
+ function unwrapType(type) {
1087
+ let required = false;
1088
+ let isList = false;
1089
+ let current = type;
1090
+ if (isNonNullType(current)) {
1091
+ required = true;
1092
+ current = current.ofType;
1093
+ }
1094
+ if (isListType(current)) {
1095
+ isList = true;
1096
+ current = current.ofType;
1097
+ if (isNonNullType(current)) current = current.ofType;
1098
+ }
1099
+ if (!isNamedType(current)) throw new Error(`Expected a named GraphQL type, got: ${String(current)}`);
1100
+ return {
1101
+ namedType: current,
1102
+ required,
1103
+ isList
1104
+ };
1105
+ }
1106
+ /**
1107
+ * Check if a GraphQL object type looks like a Relay Connection type.
1108
+ * A connection type has an `edges` field returning a list of edge types,
1109
+ * where each edge has a `node` field.
1110
+ *
1111
+ * @param type - The GraphQL object type to check
1112
+ * @returns The node type name if this is a connection, or `undefined`
1113
+ * @internal
1114
+ */
1115
+ function getConnectionNodeType(type) {
1116
+ const edgesField = type.getFields()["edges"];
1117
+ if (!edgesField) return void 0;
1118
+ const { namedType: edgesType, isList: edgesIsList } = unwrapType(edgesField.type);
1119
+ if (!edgesIsList || !isObjectType(edgesType)) return void 0;
1120
+ const nodeField = edgesType.getFields()["node"];
1121
+ if (!nodeField) return void 0;
1122
+ const { namedType: nodeType } = unwrapType(nodeField.type);
1123
+ if (!isObjectType(nodeType)) return void 0;
1124
+ return nodeType.name;
1125
+ }
1126
+ /**
1127
+ * Classify a single GraphQL field into a Stonecrop field definition.
1128
+ *
1129
+ * Classification rules (in order):
1130
+ * 1. Scalar types → look up in merged scalar map
1131
+ * 2. Enum types → `Select` with enum values as options
1132
+ * 3. Object types that are entities → `Link` with slug as options
1133
+ * 4. Object types that are Connections → `Doctype` with node type slug as options
1134
+ * 5. List of entity type → `Doctype` with item type slug as options
1135
+ * 6. Anything else → `Data` with `_unmapped: true`
1136
+ *
1137
+ * @param fieldName - The GraphQL field name
1138
+ * @param field - The GraphQL field definition
1139
+ * @param entityTypes - Set of type names classified as entities
1140
+ * @param options - Conversion options (for custom scalars, unmapped meta, etc.)
1141
+ * @returns The Stonecrop field definition
1142
+ * @public
1143
+ */
1144
+ function classifyFieldType(fieldName, field, entityTypes, options = {}) {
1145
+ const { namedType, required, isList } = unwrapType(field.type);
1146
+ const scalarMap = buildScalarMap(options.customScalars);
1147
+ const base = {
1148
+ kind: "field",
1149
+ fieldname: fieldName,
1150
+ label: camelToLabel(fieldName),
1151
+ component: "ATextInput"
1152
+ };
1153
+ if (required) base.required = true;
1154
+ if (isScalarType(namedType)) {
1155
+ if (INTERNAL_SCALARS.has(namedType.name)) {
1156
+ base._unmapped = true;
1157
+ if (options.includeUnmappedMeta) base._graphqlType = namedType.name;
1158
+ return base;
1159
+ }
1160
+ if (namedType.name === "ID") {
1161
+ const candidateTypeName = toPascalCase(fieldName);
1162
+ if (entityTypes.has(candidateTypeName)) {
1163
+ base.component = "AFormLink";
1164
+ base.doctype = toSlug(candidateTypeName);
1165
+ return base;
1166
+ }
1167
+ }
1168
+ const template = scalarMap[namedType.name];
1169
+ if (template) base.component = template.component;
1170
+ else {
1171
+ base._unmapped = true;
1172
+ if (options.includeUnmappedMeta) base._graphqlType = namedType.name;
1173
+ }
1174
+ return base;
1175
+ }
1176
+ if (isEnumType(namedType)) {
1177
+ base.component = "ADropdown";
1178
+ base.options = namedType.getValues().map((v) => v.name);
1179
+ return base;
1180
+ }
1181
+ if (isObjectType(namedType)) {
1182
+ if (!isList && entityTypes.has(namedType.name)) {
1183
+ base.component = "AFormLink";
1184
+ base.doctype = toSlug(namedType.name);
1185
+ return base;
1186
+ }
1187
+ const connectionNodeTypeName = getConnectionNodeType(namedType);
1188
+ if (connectionNodeTypeName && entityTypes.has(connectionNodeTypeName)) {
1189
+ base.component = "ATable";
1190
+ base._isLink = true;
1191
+ base.doctype = toSlug(connectionNodeTypeName);
1192
+ base.cardinality = "noneOrMany";
1193
+ return base;
1194
+ }
1195
+ if (isList && entityTypes.has(namedType.name)) {
1196
+ base.component = "ATable";
1197
+ base._isLink = true;
1198
+ base.doctype = toSlug(namedType.name);
1199
+ base.cardinality = "noneOrMany";
1200
+ return base;
1201
+ }
1202
+ base._unmapped = true;
1203
+ if (options.includeUnmappedMeta) base._graphqlType = namedType.name;
1204
+ return base;
1205
+ }
1206
+ base._unmapped = true;
1207
+ if (options.includeUnmappedMeta) base._graphqlType = namedType.name;
1208
+ return base;
1209
+ }
1210
+ //#endregion
1211
+ //#region src/converter/aggregate.ts
1212
+ var import_pluralize = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
1213
+ (function(root, pluralize) {
1214
+ /* istanbul ignore else */
1215
+ if (typeof __require === "function" && typeof exports === "object" && typeof module === "object") module.exports = pluralize();
1216
+ else if (typeof define === "function" && define.amd) define(function() {
1217
+ return pluralize();
1218
+ });
1219
+ else root.pluralize = pluralize();
1220
+ })(exports, function() {
1221
+ var pluralRules = [];
1222
+ var singularRules = [];
1223
+ var uncountables = {};
1224
+ var irregularPlurals = {};
1225
+ var irregularSingles = {};
1226
+ /**
1227
+ * Sanitize a pluralization rule to a usable regular expression.
1228
+ *
1229
+ * @param {(RegExp|string)} rule
1230
+ * @return {RegExp}
1231
+ */
1232
+ function sanitizeRule(rule) {
1233
+ if (typeof rule === "string") return new RegExp("^" + rule + "$", "i");
1234
+ return rule;
1235
+ }
1236
+ /**
1237
+ * Pass in a word token to produce a function that can replicate the case on
1238
+ * another word.
1239
+ *
1240
+ * @param {string} word
1241
+ * @param {string} token
1242
+ * @return {Function}
1243
+ */
1244
+ function restoreCase(word, token) {
1245
+ if (word === token) return token;
1246
+ if (word === word.toLowerCase()) return token.toLowerCase();
1247
+ if (word === word.toUpperCase()) return token.toUpperCase();
1248
+ if (word[0] === word[0].toUpperCase()) return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();
1249
+ return token.toLowerCase();
1250
+ }
1251
+ /**
1252
+ * Interpolate a regexp string.
1253
+ *
1254
+ * @param {string} str
1255
+ * @param {Array} args
1256
+ * @return {string}
1257
+ */
1258
+ function interpolate(str, args) {
1259
+ return str.replace(/\$(\d{1,2})/g, function(match, index) {
1260
+ return args[index] || "";
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Replace a word using a rule.
1265
+ *
1266
+ * @param {string} word
1267
+ * @param {Array} rule
1268
+ * @return {string}
1269
+ */
1270
+ function replace(word, rule) {
1271
+ return word.replace(rule[0], function(match, index) {
1272
+ var result = interpolate(rule[1], arguments);
1273
+ if (match === "") return restoreCase(word[index - 1], result);
1274
+ return restoreCase(match, result);
1275
+ });
1276
+ }
1277
+ /**
1278
+ * Sanitize a word by passing in the word and sanitization rules.
1279
+ *
1280
+ * @param {string} token
1281
+ * @param {string} word
1282
+ * @param {Array} rules
1283
+ * @return {string}
1284
+ */
1285
+ function sanitizeWord(token, word, rules) {
1286
+ if (!token.length || uncountables.hasOwnProperty(token)) return word;
1287
+ var len = rules.length;
1288
+ while (len--) {
1289
+ var rule = rules[len];
1290
+ if (rule[0].test(word)) return replace(word, rule);
1291
+ }
1292
+ return word;
1293
+ }
1294
+ /**
1295
+ * Replace a word with the updated word.
1296
+ *
1297
+ * @param {Object} replaceMap
1298
+ * @param {Object} keepMap
1299
+ * @param {Array} rules
1300
+ * @return {Function}
1301
+ */
1302
+ function replaceWord(replaceMap, keepMap, rules) {
1303
+ return function(word) {
1304
+ var token = word.toLowerCase();
1305
+ if (keepMap.hasOwnProperty(token)) return restoreCase(word, token);
1306
+ if (replaceMap.hasOwnProperty(token)) return restoreCase(word, replaceMap[token]);
1307
+ return sanitizeWord(token, word, rules);
1308
+ };
1309
+ }
1310
+ /**
1311
+ * Check if a word is part of the map.
1312
+ */
1313
+ function checkWord(replaceMap, keepMap, rules, bool) {
1314
+ return function(word) {
1315
+ var token = word.toLowerCase();
1316
+ if (keepMap.hasOwnProperty(token)) return true;
1317
+ if (replaceMap.hasOwnProperty(token)) return false;
1318
+ return sanitizeWord(token, token, rules) === token;
1319
+ };
1320
+ }
1321
+ /**
1322
+ * Pluralize or singularize a word based on the passed in count.
1323
+ *
1324
+ * @param {string} word The word to pluralize
1325
+ * @param {number} count How many of the word exist
1326
+ * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks)
1327
+ * @return {string}
1328
+ */
1329
+ function pluralize(word, count, inclusive) {
1330
+ var pluralized = count === 1 ? pluralize.singular(word) : pluralize.plural(word);
1331
+ return (inclusive ? count + " " : "") + pluralized;
1332
+ }
1333
+ /**
1334
+ * Pluralize a word.
1335
+ *
1336
+ * @type {Function}
1337
+ */
1338
+ pluralize.plural = replaceWord(irregularSingles, irregularPlurals, pluralRules);
1339
+ /**
1340
+ * Check if a word is plural.
1341
+ *
1342
+ * @type {Function}
1343
+ */
1344
+ pluralize.isPlural = checkWord(irregularSingles, irregularPlurals, pluralRules);
1345
+ /**
1346
+ * Singularize a word.
1347
+ *
1348
+ * @type {Function}
1349
+ */
1350
+ pluralize.singular = replaceWord(irregularPlurals, irregularSingles, singularRules);
1351
+ /**
1352
+ * Check if a word is singular.
1353
+ *
1354
+ * @type {Function}
1355
+ */
1356
+ pluralize.isSingular = checkWord(irregularPlurals, irregularSingles, singularRules);
1357
+ /**
1358
+ * Add a pluralization rule to the collection.
1359
+ *
1360
+ * @param {(string|RegExp)} rule
1361
+ * @param {string} replacement
1362
+ */
1363
+ pluralize.addPluralRule = function(rule, replacement) {
1364
+ pluralRules.push([sanitizeRule(rule), replacement]);
1365
+ };
1366
+ /**
1367
+ * Add a singularization rule to the collection.
1368
+ *
1369
+ * @param {(string|RegExp)} rule
1370
+ * @param {string} replacement
1371
+ */
1372
+ pluralize.addSingularRule = function(rule, replacement) {
1373
+ singularRules.push([sanitizeRule(rule), replacement]);
1374
+ };
1375
+ /**
1376
+ * Add an uncountable word rule.
1377
+ *
1378
+ * @param {(string|RegExp)} word
1379
+ */
1380
+ pluralize.addUncountableRule = function(word) {
1381
+ if (typeof word === "string") {
1382
+ uncountables[word.toLowerCase()] = true;
1383
+ return;
1384
+ }
1385
+ pluralize.addPluralRule(word, "$0");
1386
+ pluralize.addSingularRule(word, "$0");
1387
+ };
1388
+ /**
1389
+ * Add an irregular word definition.
1390
+ *
1391
+ * @param {string} single
1392
+ * @param {string} plural
1393
+ */
1394
+ pluralize.addIrregularRule = function(single, plural) {
1395
+ plural = plural.toLowerCase();
1396
+ single = single.toLowerCase();
1397
+ irregularSingles[single] = plural;
1398
+ irregularPlurals[plural] = single;
1399
+ };
1400
+ /**
1401
+ * Irregular rules.
1402
+ */
1403
+ [
1404
+ ["I", "we"],
1405
+ ["me", "us"],
1406
+ ["he", "they"],
1407
+ ["she", "they"],
1408
+ ["them", "them"],
1409
+ ["myself", "ourselves"],
1410
+ ["yourself", "yourselves"],
1411
+ ["itself", "themselves"],
1412
+ ["herself", "themselves"],
1413
+ ["himself", "themselves"],
1414
+ ["themself", "themselves"],
1415
+ ["is", "are"],
1416
+ ["was", "were"],
1417
+ ["has", "have"],
1418
+ ["this", "these"],
1419
+ ["that", "those"],
1420
+ ["echo", "echoes"],
1421
+ ["dingo", "dingoes"],
1422
+ ["volcano", "volcanoes"],
1423
+ ["tornado", "tornadoes"],
1424
+ ["torpedo", "torpedoes"],
1425
+ ["genus", "genera"],
1426
+ ["viscus", "viscera"],
1427
+ ["stigma", "stigmata"],
1428
+ ["stoma", "stomata"],
1429
+ ["dogma", "dogmata"],
1430
+ ["lemma", "lemmata"],
1431
+ ["schema", "schemata"],
1432
+ ["anathema", "anathemata"],
1433
+ ["ox", "oxen"],
1434
+ ["axe", "axes"],
1435
+ ["die", "dice"],
1436
+ ["yes", "yeses"],
1437
+ ["foot", "feet"],
1438
+ ["eave", "eaves"],
1439
+ ["goose", "geese"],
1440
+ ["tooth", "teeth"],
1441
+ ["quiz", "quizzes"],
1442
+ ["human", "humans"],
1443
+ ["proof", "proofs"],
1444
+ ["carve", "carves"],
1445
+ ["valve", "valves"],
1446
+ ["looey", "looies"],
1447
+ ["thief", "thieves"],
1448
+ ["groove", "grooves"],
1449
+ ["pickaxe", "pickaxes"],
1450
+ ["passerby", "passersby"]
1451
+ ].forEach(function(rule) {
1452
+ return pluralize.addIrregularRule(rule[0], rule[1]);
1453
+ });
1454
+ /**
1455
+ * Pluralization rules.
1456
+ */
1457
+ [
1458
+ [/s?$/i, "s"],
1459
+ [/[^\u0000-\u007F]$/i, "$0"],
1460
+ [/([^aeiou]ese)$/i, "$1"],
1461
+ [/(ax|test)is$/i, "$1es"],
1462
+ [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, "$1es"],
1463
+ [/(e[mn]u)s?$/i, "$1s"],
1464
+ [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, "$1"],
1465
+ [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, "$1i"],
1466
+ [/(alumn|alg|vertebr)(?:a|ae)$/i, "$1ae"],
1467
+ [/(seraph|cherub)(?:im)?$/i, "$1im"],
1468
+ [/(her|at|gr)o$/i, "$1oes"],
1469
+ [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, "$1a"],
1470
+ [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, "$1a"],
1471
+ [/sis$/i, "ses"],
1472
+ [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, "$1$2ves"],
1473
+ [/([^aeiouy]|qu)y$/i, "$1ies"],
1474
+ [/([^ch][ieo][ln])ey$/i, "$1ies"],
1475
+ [/(x|ch|ss|sh|zz)$/i, "$1es"],
1476
+ [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, "$1ices"],
1477
+ [/\b((?:tit)?m|l)(?:ice|ouse)$/i, "$1ice"],
1478
+ [/(pe)(?:rson|ople)$/i, "$1ople"],
1479
+ [/(child)(?:ren)?$/i, "$1ren"],
1480
+ [/eaux$/i, "$0"],
1481
+ [/m[ae]n$/i, "men"],
1482
+ ["thou", "you"]
1483
+ ].forEach(function(rule) {
1484
+ return pluralize.addPluralRule(rule[0], rule[1]);
1485
+ });
1486
+ /**
1487
+ * Singularization rules.
1488
+ */
1489
+ [
1490
+ [/s$/i, ""],
1491
+ [/(ss)$/i, "$1"],
1492
+ [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, "$1fe"],
1493
+ [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, "$1f"],
1494
+ [/ies$/i, "y"],
1495
+ [/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, "$1ie"],
1496
+ [/\b(mon|smil)ies$/i, "$1ey"],
1497
+ [/\b((?:tit)?m|l)ice$/i, "$1ouse"],
1498
+ [/(seraph|cherub)im$/i, "$1"],
1499
+ [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, "$1"],
1500
+ [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, "$1sis"],
1501
+ [/(movie|twelve|abuse|e[mn]u)s$/i, "$1"],
1502
+ [/(test)(?:is|es)$/i, "$1is"],
1503
+ [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, "$1us"],
1504
+ [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, "$1um"],
1505
+ [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, "$1on"],
1506
+ [/(alumn|alg|vertebr)ae$/i, "$1a"],
1507
+ [/(cod|mur|sil|vert|ind)ices$/i, "$1ex"],
1508
+ [/(matr|append)ices$/i, "$1ix"],
1509
+ [/(pe)(rson|ople)$/i, "$1rson"],
1510
+ [/(child)ren$/i, "$1"],
1511
+ [/(eau)x?$/i, "$1"],
1512
+ [/men$/i, "man"]
1513
+ ].forEach(function(rule) {
1514
+ return pluralize.addSingularRule(rule[0], rule[1]);
1515
+ });
1516
+ /**
1517
+ * Uncountable rules.
1518
+ */
1519
+ [
1520
+ "adulthood",
1521
+ "advice",
1522
+ "agenda",
1523
+ "aid",
1524
+ "aircraft",
1525
+ "alcohol",
1526
+ "ammo",
1527
+ "analytics",
1528
+ "anime",
1529
+ "athletics",
1530
+ "audio",
1531
+ "bison",
1532
+ "blood",
1533
+ "bream",
1534
+ "buffalo",
1535
+ "butter",
1536
+ "carp",
1537
+ "cash",
1538
+ "chassis",
1539
+ "chess",
1540
+ "clothing",
1541
+ "cod",
1542
+ "commerce",
1543
+ "cooperation",
1544
+ "corps",
1545
+ "debris",
1546
+ "diabetes",
1547
+ "digestion",
1548
+ "elk",
1549
+ "energy",
1550
+ "equipment",
1551
+ "excretion",
1552
+ "expertise",
1553
+ "firmware",
1554
+ "flounder",
1555
+ "fun",
1556
+ "gallows",
1557
+ "garbage",
1558
+ "graffiti",
1559
+ "hardware",
1560
+ "headquarters",
1561
+ "health",
1562
+ "herpes",
1563
+ "highjinks",
1564
+ "homework",
1565
+ "housework",
1566
+ "information",
1567
+ "jeans",
1568
+ "justice",
1569
+ "kudos",
1570
+ "labour",
1571
+ "literature",
1572
+ "machinery",
1573
+ "mackerel",
1574
+ "mail",
1575
+ "media",
1576
+ "mews",
1577
+ "moose",
1578
+ "music",
1579
+ "mud",
1580
+ "manga",
1581
+ "news",
1582
+ "only",
1583
+ "personnel",
1584
+ "pike",
1585
+ "plankton",
1586
+ "pliers",
1587
+ "police",
1588
+ "pollution",
1589
+ "premises",
1590
+ "rain",
1591
+ "research",
1592
+ "rice",
1593
+ "salmon",
1594
+ "scissors",
1595
+ "series",
1596
+ "sewage",
1597
+ "shambles",
1598
+ "shrimp",
1599
+ "software",
1600
+ "species",
1601
+ "staff",
1602
+ "swine",
1603
+ "tennis",
1604
+ "traffic",
1605
+ "transportation",
1606
+ "trout",
1607
+ "tuna",
1608
+ "wealth",
1609
+ "welfare",
1610
+ "whiting",
1611
+ "wildebeest",
1612
+ "wildlife",
1613
+ "you",
1614
+ /pok[eé]mon$/i,
1615
+ /[^aeiou]ese$/i,
1616
+ /deer$/i,
1617
+ /fish$/i,
1618
+ /measles$/i,
1619
+ /o[iu]s$/i,
1620
+ /pox$/i,
1621
+ /sheep$/i
1622
+ ].forEach(pluralize.addUncountableRule);
1623
+ return pluralize;
1624
+ });
1625
+ })))(), 1);
1626
+ /**
1627
+ * The name an entity's aggregate doctype is generated under: the entity's name, pluralised.
1628
+ *
1629
+ * One definition, because the CLI writes the file under `toSlug` of this and any later caller
1630
+ * (a scaffolder, a docs generator) must land on the same name or it silently addresses a
1631
+ * different file.
1632
+ *
1633
+ * `pluralize` rather than appending `s`, because the irregulars are not rare in practice —
1634
+ * measured against a consumer's 41 hand-authored aggregate doctypes, this rule reproduces every
1635
+ * one of their names, slugs and filenames exactly, while `+ 's'` gets five wrong
1636
+ * (`Currencys`, `JournalEntrys`, …).
1637
+ *
1638
+ * The rule is not total: an already-plural name pluralises to itself. Callers must handle that —
1639
+ * see {@link buildAggregateDoctype}.
1640
+ *
1641
+ * @param doctypeName - the entity doctype's `name`
1642
+ * @returns the aggregate doctype's `name`
1643
+ * @public
1644
+ *
1645
+ * @example
1646
+ * ```typescript
1647
+ * aggregateDoctypeName('SalesOrder') // 'SalesOrders' -> slug 'sales-orders'
1648
+ * ```
1649
+ */
1650
+ function aggregateDoctypeName(doctypeName) {
1651
+ return import_pluralize.default.plural(doctypeName);
1652
+ }
1653
+ /**
1654
+ * Derive the aggregate doctype for a converted entity.
1655
+ *
1656
+ * Returns `undefined` when no identity column can be found — a natural-key table whose key the
1657
+ * converter refuses to guess and whose author has not declared one, or a foreign PostGraphile
1658
+ * endpoint that has left the Relay identifier occupying `id` (Stonecrop's own preset moves it to
1659
+ * `nodeId`). That is deliberate: an aggregate with an empty `fields` array is a valid doctype that
1660
+ * renders a table with no columns, which looks like a data problem rather than a generation one.
1661
+ * Emitting nothing and saying so is the loud failure.
1662
+ *
1663
+ * Identity resolves the same way `getRecordIdField` resolves it — the declared `primaryKey`, then
1664
+ * the conventional `id` — so an aggregate is always keyed on the column the client will later ask
1665
+ * for. `declaredIdentity` overrides both: SDL cannot express which `UNIQUE` column is the key, so
1666
+ * for a natural-key table the answer only exists in the authored file, and the caller that read it
1667
+ * passes the fieldname back.
1668
+ *
1669
+ * @param doctype - a converted entity doctype, as returned by `convertGraphQLSchema`
1670
+ * @param declaredIdentity - fieldname the authored doctype declares as its `primaryKey`, when the
1671
+ * caller has read one. Must name a field the converter emitted; the caller checks that, because
1672
+ * only it can say whether a missing one is a dropped column or a typo.
1673
+ * @returns the aggregate doctype, or `undefined` when no identity column exists
1674
+ * @public
1675
+ *
1676
+ * @example
1677
+ * ```typescript
1678
+ * const [order] = convertGraphQLSchema(sdl, { include: ['Order'] })
1679
+ * const aggregate = buildAggregateDoctype(order)
1680
+ * // { name: 'Orders', slug: 'orders', fields: [ the id field ] }
1681
+ * ```
1682
+ */
1683
+ function buildAggregateDoctype(doctype, declaredIdentity) {
1684
+ const identity = findIdentityField(doctype.fields, declaredIdentity);
1685
+ if (!identity) return void 0;
1686
+ const name = aggregateDoctypeName(doctype.name);
1687
+ if (name === doctype.name) return void 0;
1688
+ const { source, ...rest } = identity;
1689
+ return {
1690
+ name,
1691
+ slug: toSlug(name),
1692
+ fields: [{
1693
+ ...rest,
1694
+ primaryKey: true,
1695
+ ...source === void 0 ? {} : { source }
1696
+ }]
1697
+ };
1698
+ }
1699
+ /**
1700
+ * The field an aggregate is keyed on: an identity the author declared, else the primary key the
1701
+ * converter derived, else the conventional `id`.
1702
+ *
1703
+ * The author wins because the authored doctype is the source of truth — generation verifies it and
1704
+ * never overwrites it (see `mergeIntrospectedDoctype`), and the divergence is already reported as
1705
+ * identity drift.
1706
+ *
1707
+ * Calls `getPrimaryKeyField` for the derived half rather than restating it: a restatement drifted
1708
+ * exactly as one does, staying top-level while the helper learned to descend into fieldsets.
1709
+ *
1710
+ * @internal
1711
+ */
1712
+ function findIdentityField(fields, declared) {
1713
+ if (declared !== void 0) return fields.find((field) => field.fieldname === declared);
1714
+ return getPrimaryKeyField(fields) ?? fields.find((field) => field.fieldname === "id");
1715
+ }
1716
+ /**
1717
+ * The doctypes in a run that get a URL of their own.
1718
+ *
1719
+ * A child table has no page: its rows exist inside a parent and are edited there, so a route for
1720
+ * it is an address nothing can link to. The declaration that says so is the parent's `links` entry
1721
+ * with a to-many cardinality — which the server derives from the foreign keys it treats as owning,
1722
+ * so this reads what the schema states rather than guessing from a name.
1723
+ *
1724
+ * The rule is *listed by something, referenced by nothing*. A single reference wins over any number
1725
+ * of listings, and the asymmetry is deliberate: a doctype that is both a parent's rows and another
1726
+ * doctype's link target — a recipe task, say, embedded in its recipe and pointed at by four other
1727
+ * records — needs somewhere for those links to navigate to. Denying it leaves the arrow on an
1728
+ * `AFormLink` dead, which fails silently; granting it leaves a URL nobody visits, which does not.
1729
+ *
1730
+ * Scoped to one run, so a partial generation sees a partial graph and grants more routes than a
1731
+ * whole one would. That is the safe direction, and the extra routes are deletable — an authored
1732
+ * file's keys survive regeneration untouched.
1733
+ *
1734
+ * @internal
1735
+ */
1736
+ function routableDoctypes(entities) {
1737
+ const listed = /* @__PURE__ */ new Set();
1738
+ const referenced = /* @__PURE__ */ new Set();
1739
+ for (const entity of entities) {
1740
+ for (const link of Object.values(entity.links ?? {})) if (link.cardinality === "noneOrMany" || link.cardinality === "atLeastOne") listed.add(link.target);
1741
+ else referenced.add(link.target);
1742
+ for (const field of flattenFields(entity.fields)) if ("doctype" in field && typeof field.doctype === "string") referenced.add(field.doctype);
1743
+ }
1744
+ return new Set(entities.filter((entity) => {
1745
+ const slug = getDoctypeSlug(entity);
1746
+ return referenced.has(slug) || !listed.has(slug);
1747
+ }).map((entity) => entity.name));
1748
+ }
1749
+ /**
1750
+ * Expand converted entities into the set of doctype files to write.
1751
+ *
1752
+ * Each table yields two: the entity, whose fields carry every column and which backs the record
1753
+ * form, and its aggregate — the collection view. They are written as peers, one file each, with
1754
+ * no key relating them.
1755
+ *
1756
+ * Separate from the CLI because the pairing of a file to its verification basis is the part that
1757
+ * is easy to get wrong and impossible to notice: getting it wrong does not throw, it just reports
1758
+ * drift that is not there, forever.
1759
+ *
1760
+ * @param entities - `convertGraphQLSchema` output
1761
+ * @param options - see {@link GenerationPlanOptions}
1762
+ * @returns one entry per file to write
1763
+ * @public
1764
+ */
1765
+ function planGeneration(entities, options = {}) {
1766
+ const entityNames = new Set(entities.map((entity) => entity.name));
1767
+ const claimed = /* @__PURE__ */ new Set();
1768
+ const routable = routableDoctypes(entities);
1769
+ return entities.flatMap((entity) => {
1770
+ const segment = `/${getDoctypeSlug(entity)}`;
1771
+ const routed = routable.has(entity.name) ? {
1772
+ ...entity,
1773
+ route: `${segment}/:id`
1774
+ } : entity;
1775
+ const self = {
1776
+ generated: routed,
1777
+ basis: routed,
1778
+ subset: false
1779
+ };
1780
+ if (options.noAggregates) return [self];
1781
+ const name = aggregateDoctypeName(entity.name);
1782
+ if (name === entity.name) {
1783
+ options.onWarning?.(`${entity.name} is already plural, so its aggregate would take the same name and the same file. No aggregate was generated. Rename the doctype to its singular form, or author ${entity.slug}.json's collection view by hand.`);
1784
+ return [self];
1785
+ }
1786
+ if (entityNames.has(name) || claimed.has(name)) {
1787
+ options.onWarning?.(`${entity.name}'s aggregate would be named ${name}, which is already taken by another doctype in this run. No aggregate was generated — one of the two needs an explicit name via the doctypeNames option.`);
1788
+ return [self];
1789
+ }
1790
+ const declared = options.identity?.[entity.name];
1791
+ if (declared !== void 0 && !entity.fields.some((field) => field.fieldname === declared)) {
1792
+ options.onWarning?.(`${entity.name} declares its primaryKey on '${declared}', which the schema has no column for. No aggregate was generated — correct the declaration in ${entity.slug}.json, or restore the column to the table.`);
1793
+ return [self];
1794
+ }
1795
+ const aggregate = buildAggregateDoctype(entity, declared);
1796
+ if (!aggregate) {
1797
+ options.onWarning?.(`${entity.name} has no derivable identity column, so no aggregate doctype was generated. Declare a primaryKey on ${entity.slug}.json and re-run.`);
1798
+ return [self];
1799
+ }
1800
+ claimed.add(name);
1801
+ return [self, {
1802
+ generated: routable.has(entity.name) ? {
1803
+ ...aggregate,
1804
+ route: segment
1805
+ } : aggregate,
1806
+ basis: entity,
1807
+ subset: true
1808
+ }];
1809
+ });
1810
+ }
1811
+ //#endregion
1812
+ //#region src/converter/authored.ts
1813
+ /** @internal */
1814
+ function isAuthoredRecord(value) {
1815
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1816
+ }
1817
+ /**
1818
+ * Flatten authored fields, descending into fieldsets.
1819
+ *
1820
+ * A fieldset is a layout grouping, not a scope: a field inside one is still a field of the doctype,
1821
+ * with a column of its own and a key it may declare.
1822
+ *
1823
+ * @internal
1824
+ */
1825
+ function flattenAuthored(fields) {
1826
+ const out = [];
1827
+ for (const field of fields) if (Array.isArray(field.schema)) out.push(...flattenAuthored(field.schema.filter(isAuthoredRecord)));
1828
+ else out.push(field);
1829
+ return out;
1830
+ }
1831
+ /**
1832
+ * The fieldname an authored doctype declares as its identity, or `undefined` when it declares none.
1833
+ *
1834
+ * Descends into fieldsets, because a nested `primaryKey` is a real declaration — ignoring one is
1835
+ * what `getPrimaryKeyField` was fixed for.
1836
+ *
1837
+ * @internal
1838
+ */
1839
+ function authoredPrimaryKey(doctype) {
1840
+ if (!Array.isArray(doctype.fields)) return void 0;
1841
+ const declared = flattenAuthored(doctype.fields.filter(isAuthoredRecord)).find((f) => f.primaryKey === true);
1842
+ return typeof declared?.fieldname === "string" ? declared.fieldname : void 0;
1843
+ }
1844
+ //#endregion
1845
+ //#region src/converter/merge.ts
1846
+ /**
1847
+ * Merge introspected schema facts into an already-authored doctype.
1848
+ *
1849
+ * The authored doctype is the source of truth. Generation **verifies** it and stamps provenance;
1850
+ * it does not overwrite. That polarity is deliberate and load-bearing — a doctype legitimately
1851
+ * declares a `primaryKey` the schema cannot express. A natural business key is very often a
1852
+ * `UNIQUE` constraint rather than the table's `PRIMARY KEY`, and where a table carries several
1853
+ * uniques no rule can pick between them. Overwriting identity from the schema would silently
1854
+ * re-key such a doctype on every regeneration and break the handlers that key on the old value.
1855
+ *
1856
+ * So divergence is **reported, never applied** — a human decides. The only mutation this performs
1857
+ * is adding `source: 'introspected'` to fields confirmed to exist in the GraphQL schema.
1858
+ *
1859
+ * @packageDocumentation
1860
+ */
1861
+ function describe(value) {
1862
+ return value === void 0 ? "—" : JSON.stringify(value);
1863
+ }
1864
+ /**
1865
+ * Verify an authored doctype against freshly generated output and stamp provenance.
1866
+ *
1867
+ * @param authored - the doctype as it exists on disk; every key not named below is preserved verbatim
1868
+ * @param generated - `convertGraphQLSchema` output for the corresponding GraphQL type. For a
1869
+ * `subset` merge this is the **entity**, whose fields are the set the subset is curated from
1870
+ * @param options - see {@link MergeOptions}
1871
+ * @returns the doctype to write, plus a drift report
1872
+ *
1873
+ * @example
1874
+ * ```ts
1875
+ * const [generated] = convertGraphQLSchema(introspection, { include: ['Uom'] })
1876
+ * const { doctype, drift } = mergeIntrospectedDoctype(JSON.parse(onDisk), generated)
1877
+ * if (drift.identityDrift.length) console.warn(drift.identityDrift.join('\n'))
1878
+ * ```
1879
+ *
1880
+ * @public
1881
+ */
1882
+ function mergeIntrospectedDoctype(authored, generated, options = {}) {
1883
+ const authoredFields = Array.isArray(authored.fields) ? authored.fields.filter(isAuthoredRecord) : [];
1884
+ const generatedByName = new Map(generated.fields.map((f) => [f.fieldname, f]));
1885
+ const generatedLinkNames = new Set(Object.keys(generated.links ?? {}));
1886
+ const drift = {
1887
+ doctype: typeof authored.name === "string" ? authored.name : "(unnamed)",
1888
+ mode: "clean",
1889
+ tagged: [],
1890
+ orphan: [],
1891
+ omitted: [],
1892
+ componentDrift: [],
1893
+ requiredDrift: [],
1894
+ identityDrift: []
1895
+ };
1896
+ const tag = (field) => {
1897
+ if (Array.isArray(field.schema)) return {
1898
+ ...field,
1899
+ schema: field.schema.filter(isAuthoredRecord).map(tag)
1900
+ };
1901
+ const name = typeof field.fieldname === "string" ? field.fieldname : "";
1902
+ const match = generatedByName.get(name);
1903
+ if (!match) {
1904
+ if (field.computed !== true && !generatedLinkNames.has(name)) drift.orphan.push(name);
1905
+ return field;
1906
+ }
1907
+ drift.tagged.push(name);
1908
+ if (match.component !== field.component) drift.componentDrift.push(`${name}: authored=${describe(field.component)} schema=${describe(match.component)}`);
1909
+ if (Boolean(match.required) !== Boolean(field.required)) drift.requiredDrift.push(`${name}: authored=${Boolean(field.required)} schema=${Boolean(match.required)}`);
1910
+ for (const prop of INTROSPECTED_IDENTITY_PROPS) {
1911
+ if (prop === "fieldname" || prop === "required") continue;
1912
+ const authoredValue = field[prop];
1913
+ const schemaValue = match[prop];
1914
+ if (authoredValue === void 0 && schemaValue === void 0) continue;
1915
+ if (JSON.stringify(authoredValue) !== JSON.stringify(schemaValue)) drift.identityDrift.push(`${name}.${prop}: authored=${describe(authoredValue)} schema=${describe(schemaValue)}`);
1916
+ }
1917
+ return {
1918
+ ...field,
1919
+ source: "introspected"
1920
+ };
1921
+ };
1922
+ const merged = {
1923
+ ...authored,
1924
+ fields: authoredFields.map(tag)
1925
+ };
1926
+ if (!options.subset) {
1927
+ const authoredNames = new Set(flattenAuthored(authoredFields).map((f) => f.fieldname));
1928
+ drift.omitted = generated.fields.map((f) => f.fieldname).filter((n) => !authoredNames.has(n));
1929
+ }
1930
+ const authoredPk = authoredPrimaryKey(authored);
1931
+ const generatedPk = generated.fields.find((f) => f.primaryKey === true);
1932
+ if (authoredPk && generatedPk && authoredPk !== generatedPk.fieldname) {
1933
+ drift.mode = "partial";
1934
+ drift.reason = `authored primary key '${authoredPk}' is not the derivable '${generatedPk.fieldname}' — left as authored`;
1935
+ } else if (authoredPk && !generatedPk) {
1936
+ drift.mode = "partial";
1937
+ drift.reason = `authored primary key '${authoredPk}' is not derivable from the schema — left as authored`;
1938
+ } else if (!authoredPk && generatedPk) {
1939
+ drift.mode = "partial";
1940
+ drift.reason = `schema suggests '${generatedPk.fieldname}' as primary key but the doctype declares none — not applied`;
1941
+ }
1942
+ return {
1943
+ doctype: merged,
1944
+ drift
1945
+ };
1946
+ }
1947
+ /**
1948
+ * Render a drift report as human-readable lines. Empty when generation agrees with the doctype.
1949
+ *
1950
+ * @param drift - a report from {@link mergeIntrospectedDoctype}
1951
+ * @returns one line per finding, ready to print
1952
+ *
1953
+ * @public
1954
+ */
1955
+ function formatDoctypeDrift(drift) {
1956
+ const lines = [];
1957
+ if (drift.reason) lines.push(` ${drift.doctype}: ${drift.reason}`);
1958
+ const bucket = (label, entries) => {
1959
+ if (entries.length) lines.push(` ${drift.doctype}: ${label} ${entries.join("; ")}`);
1960
+ };
1961
+ bucket("identity drift", drift.identityDrift);
1962
+ bucket("component drift", drift.componentDrift);
1963
+ bucket("required drift", drift.requiredDrift);
1964
+ bucket("authored fields with no schema field:", drift.orphan);
1965
+ bucket("schema fields not modelled:", drift.omitted);
1966
+ return lines;
1967
+ }
1968
+ //#endregion
1969
+ //#region src/converter/index.ts
1970
+ /**
1971
+ * GraphQL Introspection to Stonecrop Schema Converter
1972
+ *
1973
+ * Converts a standard GraphQL introspection result (or SDL string) into
1974
+ * Stonecrop doctype schemas. Source-agnostic — works with any GraphQL server.
1975
+ *
1976
+ * @packageDocumentation
1977
+ */
1978
+ /**
1979
+ * Convert a GraphQL schema to Stonecrop doctype schemas.
1980
+ *
1981
+ * Accepts either an `IntrospectionQuery` result object or an SDL string.
1982
+ * Entity types are identified using heuristics (or a custom `isEntityType` function)
1983
+ * and converted to `DoctypeMeta`-compatible JSON objects.
1984
+ *
1985
+ * @param source - GraphQL introspection result or SDL string
1986
+ * @param options - Conversion options for controlling output format and behavior
1987
+ * @returns Array of converted Stonecrop doctype definitions
1988
+ *
1989
+ * @example
1990
+ * ```typescript
1991
+ * // From introspection result (fetched from any GraphQL server)
1992
+ * const introspection = await fetchIntrospection('http://localhost:5000/graphql')
1993
+ * const doctypes = convertGraphQLSchema(introspection)
1994
+ *
1995
+ * // From SDL string
1996
+ * const sdl = fs.readFileSync('schema.graphql', 'utf-8')
1997
+ * const doctypes = convertGraphQLSchema(sdl)
1998
+ *
1999
+ * // With PostGraphile custom scalars
2000
+ * const doctypes = convertGraphQLSchema(introspection, {
2001
+ * customScalars: {
2002
+ * BigFloat: { component: 'ANumericInput' }
2003
+ * }
2004
+ * })
2005
+ * ```
2006
+ *
2007
+ * @public
2008
+ */
2009
+ function convertGraphQLSchema(source, options = {}) {
2010
+ const schema = buildGraphQLSchema(source);
2011
+ const typeMap = schema.getTypeMap();
2012
+ const rootTypeNames = /* @__PURE__ */ new Set();
2013
+ const queryType = schema.getQueryType();
2014
+ const mutationType = schema.getMutationType();
2015
+ const subscriptionType = schema.getSubscriptionType();
2016
+ if (queryType) rootTypeNames.add(queryType.name);
2017
+ if (mutationType) rootTypeNames.add(mutationType.name);
2018
+ if (subscriptionType) rootTypeNames.add(subscriptionType.name);
2019
+ const isEntityType = options.isEntityType ?? defaultIsEntityType;
2020
+ const entityTypes = /* @__PURE__ */ new Set();
2021
+ for (const [typeName, type] of Object.entries(typeMap)) {
2022
+ if (!isObjectType(type)) continue;
2023
+ if (rootTypeNames.has(typeName)) continue;
2024
+ if (isEntityType(typeName, type)) entityTypes.add(typeName);
2025
+ }
2026
+ let filteredEntityTypes = entityTypes;
2027
+ if (options.include) {
2028
+ const includeSet = new Set(options.include);
2029
+ filteredEntityTypes = new Set([...entityTypes].filter((t) => includeSet.has(t)));
2030
+ }
2031
+ if (options.exclude) {
2032
+ const excludeSet = new Set(options.exclude);
2033
+ filteredEntityTypes = new Set([...filteredEntityTypes].filter((t) => !excludeSet.has(t)));
2034
+ }
2035
+ const isEntityField = options.isEntityField ?? defaultIsEntityField;
2036
+ const doctypes = [];
2037
+ for (const typeName of filteredEntityTypes) {
2038
+ const type = typeMap[typeName];
2039
+ if (!isObjectType(type)) continue;
2040
+ const fields = type.getFields();
2041
+ const isUnnormalizedPostGraphile = "id" in fields && "rowId" in fields;
2042
+ if (isUnnormalizedPostGraphile) options.onWarning?.(`${typeName}: schema exposes both 'id' (Relay identifier) and 'rowId' (the real column). Skipping 'id' and emitting 'rowId' verbatim — no primary key can be derived. Override the '_attributeName' and 'nodeIdFieldName' inflectors so the column keeps its own name.`);
2043
+ const allClassifiedFields = Object.entries(fields).filter(([fieldName, field]) => isEntityField(fieldName, field, type) && !(isUnnormalizedPostGraphile && fieldName === "id")).map(([fieldName, field]) => {
2044
+ if (options.classifyField) {
2045
+ const custom = options.classifyField(fieldName, field, type);
2046
+ if (custom !== null && custom !== void 0) return {
2047
+ kind: "field",
2048
+ fieldname: fieldName,
2049
+ label: custom.label ?? fieldName,
2050
+ component: custom.component ?? "ATextInput",
2051
+ ...custom
2052
+ };
2053
+ }
2054
+ return classifyFieldType(fieldName, field, entityTypes, options);
2055
+ });
2056
+ const primaryKeyFieldname = allClassifiedFields.find((field) => field.fieldname === "id" && field.required && !field.doctype && !field._isLink)?.fieldname;
2057
+ const links = {};
2058
+ const convertedFields = allClassifiedFields.filter((field) => {
2059
+ if (field._isLink && field.doctype && field.cardinality) {
2060
+ links[field.fieldname] = {
2061
+ target: field.doctype,
2062
+ cardinality: field.cardinality
2063
+ };
2064
+ return false;
2065
+ }
2066
+ return true;
2067
+ }).map((field) => {
2068
+ const identity = field.fieldname === primaryKeyFieldname ? { primaryKey: true } : {};
2069
+ if (!options.includeUnmappedMeta) {
2070
+ const { _graphqlType, _unmapped, _isLink, ...clean } = field;
2071
+ return Object.assign(clean, identity, { source: "introspected" });
2072
+ }
2073
+ const { _isLink, ...rest } = field;
2074
+ return Object.assign(rest, identity, { source: "introspected" });
2075
+ });
2076
+ const doctypeName = options.doctypeNames?.[typeName] ?? typeName;
2077
+ const doctype = {
2078
+ name: doctypeName,
2079
+ slug: toSlug(doctypeName),
2080
+ fields: convertedFields
2081
+ };
2082
+ if (Object.keys(links).length > 0) doctype.links = links;
2083
+ if (options.includeUnmappedMeta) doctype._graphqlTypeName = typeName;
2084
+ doctypes.push(doctype);
2085
+ }
2086
+ return doctypes;
2087
+ }
2088
+ /**
2089
+ * Build a GraphQLSchema from either an introspection result or SDL string.
2090
+ *
2091
+ * @param source - IntrospectionQuery object or SDL string
2092
+ * @returns A complete GraphQLSchema
2093
+ * @internal
2094
+ */
2095
+ function buildGraphQLSchema(source) {
2096
+ if (typeof source === "string") return buildSchema(source);
2097
+ return buildClientSchema(source);
2098
+ }
2099
+ //#endregion
2100
+ export { snakeToCamel as A, getPrimaryKeyField as B, WorkflowMeta as C, camelToLabel as D, linkDisplayFieldname as E, FieldsetFieldSchema as F, stripFieldKind as G, getRecordIdentity as H, INTROSPECTED_IDENTITY_PROPS as I, TableViewConfig as K, TableFieldSchema as L, toPascalCase as M, toSlug as N, camelToSnake as O, DoctypeFieldSchema as P, ValueFieldSchema as R, WorkflowLayout as S, isActionAllowedInState as T, inferFieldKind as U, getRecordIdField as V, normalizeFieldKind as W, validateDoctype as _, aggregateDoctypeName as a, LINK_DISPLAY_SUFFIX as b, classifyFieldType as c, GQL_SCALAR_MAP as d, INTERNAL_SCALARS as f, parseField as g, parseDoctype as h, authoredPrimaryKey as i, snakeToLabel as j, pascalToSnake as k, defaultIsEntityField as l, buildScalarMap as m, formatDoctypeDrift as n, buildAggregateDoctype as o, WELL_KNOWN_SCALARS as p, mergeIntrospectedDoctype as r, planGeneration as s, convertGraphQLSchema as t, defaultIsEntityType as u, validateField as v, getDoctypeSlug as w, TriggerDefinition as x, ActionDefinition as y, getDisplayField as z };
2101
+
2102
+ //# sourceMappingURL=converter-CLwduvT_.js.map