@stonecrop/schema 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { readFileSync as c, existsSync as v, mkdirSync as O, writeFileSync as k
3
3
  import { resolve as d, join as C } from "node:path";
4
4
  import { parseArgs as P } from "node:util";
5
5
  import { getIntrospectionQuery as j } from "graphql";
6
- import { c as E, m as J, f as L, v as R } from "./validation-C25HJXWs.js";
6
+ import { c as E, m as J, f as L, v as R } from "./validation-1DGAGmuU.js";
7
7
  async function q(e, f) {
8
8
  const o = await fetch(e, {
9
9
  method: "POST",
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as d, D as A, F as p, G as u, I as y, a as T, T as m, b as S, d as I, V as C, W as D, e as L, g as N, h as F, i as f, j as O, k as b, c as k, l as E, n as x, f as g, o as P, p as R, q as _, r as h, m as M, s as v, t as w, u as W, w as G, x as K, y as j, z as Q, B as V, v as q, C as z } from "./validation-C25HJXWs.js";
1
+ import { A as c, D as A, F as p, G as y, I as u, a as S, L as T, T as m, b as F, d as I, V as D, W as L, e as N, g as C, h as f, i as O, j as k, k as E, c as b, l as x, n as _, o as g, f as P, p as R, q as h, r as M, s as v, t as w, u as K, m as W, w as G, x as j, y as Q, z as V, B as Y, C as q, E as z, H as X, v as B, J as H } from "./validation-1DGAGmuU.js";
2
2
  const a = {
3
3
  ATextInput: "text",
4
4
  ATextboxInput: "text",
@@ -17,7 +17,7 @@ const a = {
17
17
  AQuantityInput: "quantity",
18
18
  ACurrencyInput: "currency"
19
19
  };
20
- function o(e) {
20
+ function s(e) {
21
21
  return e ? a[e] : void 0;
22
22
  }
23
23
  const t = {
@@ -28,54 +28,58 @@ const t = {
28
28
  function i(e) {
29
29
  return e ? t[e] : void 0;
30
30
  }
31
- function s(e, n) {
31
+ function o(e, n) {
32
32
  return i(e.component ?? n) === "inline" ? "inline" : e.cardinality === "noneOrMany" || e.cardinality === "atLeastOne" ? "table" : "record";
33
33
  }
34
- const r = [
34
+ const l = [
35
35
  .../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(t)])
36
36
  ].toSorted();
37
37
  export {
38
- d as ActionDefinition,
39
- r as CANONICAL_COMPONENTS,
38
+ c as ActionDefinition,
39
+ l as CANONICAL_COMPONENTS,
40
40
  a as COMPONENT_CATEGORY,
41
41
  t as COMPONENT_LINK_EXPANSION,
42
42
  A as DoctypeFieldSchema,
43
43
  p as FieldsetFieldSchema,
44
- u as GQL_SCALAR_MAP,
45
- y as INTERNAL_SCALARS,
46
- T as INTROSPECTED_IDENTITY_PROPS,
44
+ y as GQL_SCALAR_MAP,
45
+ u as INTERNAL_SCALARS,
46
+ S as INTROSPECTED_IDENTITY_PROPS,
47
+ T as LINK_DISPLAY_SUFFIX,
47
48
  m as TableFieldSchema,
48
- S as TableViewConfig,
49
+ F as TableViewConfig,
49
50
  I as TriggerDefinition,
50
- C as ValueFieldSchema,
51
- D as WELL_KNOWN_SCALARS,
52
- L as WorkflowLayout,
53
- N as WorkflowMeta,
54
- F as buildScalarMap,
55
- f as camelToLabel,
56
- O as camelToSnake,
57
- b as classifyFieldType,
58
- o as componentCategory,
51
+ D as ValueFieldSchema,
52
+ L as WELL_KNOWN_SCALARS,
53
+ N as WorkflowLayout,
54
+ C as WorkflowMeta,
55
+ f as buildScalarMap,
56
+ O as camelToLabel,
57
+ k as camelToSnake,
58
+ E as classifyFieldType,
59
+ s as componentCategory,
59
60
  i as componentLinkExpansion,
60
- k as convertGraphQLSchema,
61
- E as defaultIsEntityField,
62
- x as defaultIsEntityType,
63
- g as formatDoctypeDrift,
64
- P as getPrimaryKeyField,
65
- R as getRecordIdField,
66
- _ as getRecordIdentity,
67
- h as isActionAllowedInState,
68
- M as mergeIntrospectedDoctype,
69
- v as normalizeFieldKind,
70
- w as parseDoctype,
71
- W as parseField,
72
- G as pascalToSnake,
73
- s as resolveLinkRenderMode,
74
- K as snakeToCamel,
75
- j as snakeToLabel,
76
- Q as toPascalCase,
77
- V as toSlug,
78
- q as validateDoctype,
79
- z as validateField
61
+ b as convertGraphQLSchema,
62
+ x as defaultIsEntityField,
63
+ _ as defaultIsEntityType,
64
+ g as flattenFields,
65
+ P as formatDoctypeDrift,
66
+ R as getDisplayField,
67
+ h as getPrimaryKeyField,
68
+ M as getRecordIdField,
69
+ v as getRecordIdentity,
70
+ w as isActionAllowedInState,
71
+ K as linkDisplayFieldname,
72
+ W as mergeIntrospectedDoctype,
73
+ G as normalizeFieldKind,
74
+ j as parseDoctype,
75
+ Q as parseField,
76
+ V as pascalToSnake,
77
+ o as resolveLinkRenderMode,
78
+ Y as snakeToCamel,
79
+ q as snakeToLabel,
80
+ z as toPascalCase,
81
+ X as toSlug,
82
+ B as validateDoctype,
83
+ H as validateField
80
84
  };
81
85
  //# sourceMappingURL=index.js.map
package/dist/schema.d.ts CHANGED
@@ -524,6 +524,7 @@ export declare const DoctypeFieldSchema: z.ZodType<DoctypeField, unknown, z.core
524
524
  export declare const DoctypeMeta: z.ZodObject<{
525
525
  name: z.ZodString;
526
526
  slug: z.ZodOptional<z.ZodString>;
527
+ displayField: z.ZodOptional<z.ZodString>;
527
528
  fields: z.ZodArray<z.ZodType<DoctypeField, unknown, z.core.$ZodTypeInternals<DoctypeField, unknown>>>;
528
529
  links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
529
530
  target: z.ZodString;
@@ -715,6 +716,28 @@ export declare const FieldValidation: z.ZodObject<{
715
716
  */
716
717
  export declare type FieldValidation = z.infer<typeof FieldValidation>;
717
718
 
719
+ /**
720
+ * Recursively flatten Fieldset containers into a flat array of non-container fields.
721
+ * Fieldset entries are replaced by their children; all other fields pass through.
722
+ *
723
+ * A fieldset is a layout grouping, not a scope: every field inside one is a field of the doctype,
724
+ * with a column of its own and a name a link can bind to. Anything asking "what does this doctype
725
+ * declare" must therefore descend, and the two ways to get that wrong point opposite ways — the
726
+ * SELECT builder would omit real columns, while a validator would report a working declaration as
727
+ * broken.
728
+ *
729
+ * Lives here rather than in the adapter because both sides need it: the middleware builds SQL from
730
+ * it, and `DoctypeMeta`'s own validation asks the same question at the load gate. It sat in the
731
+ * adapter while the validator hand-rolled a top-level-only scan, and that is exactly the second
732
+ * failure this comment names — a `displayField` inside a fieldset was rejected at authoring time
733
+ * and would have worked at runtime.
734
+ *
735
+ * @param fields - the doctype's top-level fields
736
+ * @returns every non-container field, fieldset children included
737
+ * @public
738
+ */
739
+ export declare function flattenFields(fields: readonly DoctypeField[]): (ValueField | TableField)[];
740
+
718
741
  /**
719
742
  * Render a drift report as human-readable lines. Empty when generation agrees with the doctype.
720
743
  *
@@ -725,6 +748,27 @@ export declare type FieldValidation = z.infer<typeof FieldValidation>;
725
748
  */
726
749
  export declare function formatDoctypeDrift(drift: DoctypeDrift): string[];
727
750
 
751
+ /**
752
+ * Resolve the field a doctype nominates as its display text, or `undefined` when the nomination
753
+ * does not name a readable column.
754
+ *
755
+ * This is the single definition of "is this a usable `displayField`". Both sides depend on it:
756
+ * `DoctypeMeta` refuses a bad nomination at the load gate, and the adapter builds a SELECT from
757
+ * the field it returns. Call this; never re-derive the rule, or the gate and the query will
758
+ * disagree about which nominations are legal — which they did, in both directions at once.
759
+ *
760
+ * Two things disqualify a nomination, and both are the doctype saying so itself:
761
+ * - it names no field at all, fieldset children included
762
+ * - it names a `computed` field, which is declared precisely to state it has no column, so a
763
+ * SELECT built from it would reference a column the database does not have
764
+ *
765
+ * @param fields - the doctype's top-level fields
766
+ * @param displayField - the nominated fieldname
767
+ * @returns the nominated field, or `undefined` when it is not a readable column
768
+ * @public
769
+ */
770
+ export declare function getDisplayField(fields: readonly DoctypeField[], displayField: string | undefined): ValueField | undefined;
771
+
728
772
  /**
729
773
  * Find the field a doctype marks as its primary key, or `undefined` when none is marked.
730
774
  *
@@ -1059,6 +1103,12 @@ export declare const LazyFetch: z.ZodObject<{
1059
1103
  */
1060
1104
  export declare type LazyFetch = z.infer<typeof LazyFetch>;
1061
1105
 
1106
+ /**
1107
+ * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.
1108
+ * @public
1109
+ */
1110
+ export declare const LINK_DISPLAY_SUFFIX = "__display";
1111
+
1062
1112
  /**
1063
1113
  * Link declaration - describes a relationship from one doctype to another.
1064
1114
  * @public
@@ -1092,6 +1142,12 @@ export declare const LinkDeclaration: z.ZodObject<{
1092
1142
  */
1093
1143
  export declare type LinkDeclaration = z.infer<typeof LinkDeclaration>;
1094
1144
 
1145
+ /**
1146
+ * Build the payload key for a link field's display text (e.g. `customerId__display`).
1147
+ * @public
1148
+ */
1149
+ export declare function linkDisplayFieldname(fieldname: string): string;
1150
+
1095
1151
  /**
1096
1152
  * Whether a link component expands its target doctype, or renders the link inline.
1097
1153
  *
@@ -248,6 +248,7 @@ export type WorkflowMeta = z.infer<typeof WorkflowMeta>;
248
248
  export declare const DoctypeMeta: z.ZodObject<{
249
249
  name: z.ZodString;
250
250
  slug: z.ZodOptional<z.ZodString>;
251
+ displayField: z.ZodOptional<z.ZodString>;
251
252
  fields: z.ZodArray<z.ZodType<import("./field").DoctypeField, unknown, z.core.$ZodTypeInternals<import("./field").DoctypeField, unknown>>>;
252
253
  links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
253
254
  target: z.ZodString;
@@ -313,6 +314,16 @@ export declare const DoctypeMeta: z.ZodObject<{
313
314
  * @public
314
315
  */
315
316
  export type DoctypeMeta = z.infer<typeof DoctypeMeta>;
317
+ /**
318
+ * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.
319
+ * @public
320
+ */
321
+ export declare const LINK_DISPLAY_SUFFIX = "__display";
322
+ /**
323
+ * Build the payload key for a link field's display text (e.g. `customerId__display`).
324
+ * @public
325
+ */
326
+ export declare function linkDisplayFieldname(fieldname: string): string;
316
327
  /**
317
328
  * Context for identifying what doctype/record we're working with.
318
329
  * Used by graphql-middleware and graphql-client to resolve schema metadata.
@@ -1 +1 @@
1
- {"version":3,"file":"doctype.d.ts","sourceRoot":"","sources":["../../src/doctype.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctype.d.ts","sourceRoot":"","sources":["../../src/doctype.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { DoctypeFieldSchema } from './field';
2
+ import { DoctypeFieldSchema, flattenFields, getDisplayField } from './field';
3
3
  /**
4
4
  * Cardinality for relationship links.
5
5
  * @public
@@ -208,6 +208,12 @@ export const DoctypeMeta = z
208
208
  name: z.string().min(1),
209
209
  /** URL-friendly slug (kebab-case) */
210
210
  slug: z.string().min(1).optional(),
211
+ /**
212
+ * Field on this doctype used when displaying a reference to one of its records.
213
+ * When a record elsewhere holds a foreign key to this doctype, the middleware may
214
+ * include `fieldname__display` alongside the raw id, resolved from this field.
215
+ */
216
+ displayField: z.string().min(1).optional(),
211
217
  /** Field definitions (a link field is one carrying `doctype`) */
212
218
  fields: z.array(DoctypeFieldSchema),
213
219
  /** Relationship links to other doctypes */
@@ -245,4 +251,31 @@ export const DoctypeMeta = z
245
251
  .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`,
246
252
  });
247
253
  }
254
+ // Through `getDisplayField` rather than a scan written here, because the adapter builds its
255
+ // SELECT from that same call. The two hand-rolled versions disagreed in both directions at
256
+ // once: this gate scanned top-level only, so it rejected a fieldset-nested field that would
257
+ // have worked, while neither side excluded `computed` fields, so a nomination naming one
258
+ // passed the gate and then failed as a missing column at query time.
259
+ if (doctype.displayField && !getDisplayField(doctype.fields, doctype.displayField)) {
260
+ const named = flattenFields(doctype.fields).find(f => f.fieldname === doctype.displayField);
261
+ ctx.addIssue({
262
+ code: 'custom',
263
+ path: ['displayField'],
264
+ message: named
265
+ ? `displayField "${doctype.displayField}" names a computed field, which has no column to read a display value from`
266
+ : `displayField "${doctype.displayField}" is not declared on this doctype`,
267
+ });
268
+ }
248
269
  });
270
+ /**
271
+ * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.
272
+ * @public
273
+ */
274
+ export const LINK_DISPLAY_SUFFIX = '__display';
275
+ /**
276
+ * Build the payload key for a link field's display text (e.g. `customerId__display`).
277
+ * @public
278
+ */
279
+ export function linkDisplayFieldname(fieldname) {
280
+ return `${fieldname}${LINK_DISPLAY_SUFFIX}`;
281
+ }
@@ -205,6 +205,47 @@ export declare const INTROSPECTED_IDENTITY_PROPS: readonly ["fieldname", "primar
205
205
  * @public
206
206
  */
207
207
  export declare function getPrimaryKeyField(fields: readonly DoctypeField[]): ValueField | undefined;
208
+ /**
209
+ * Recursively flatten Fieldset containers into a flat array of non-container fields.
210
+ * Fieldset entries are replaced by their children; all other fields pass through.
211
+ *
212
+ * A fieldset is a layout grouping, not a scope: every field inside one is a field of the doctype,
213
+ * with a column of its own and a name a link can bind to. Anything asking "what does this doctype
214
+ * declare" must therefore descend, and the two ways to get that wrong point opposite ways — the
215
+ * SELECT builder would omit real columns, while a validator would report a working declaration as
216
+ * broken.
217
+ *
218
+ * Lives here rather than in the adapter because both sides need it: the middleware builds SQL from
219
+ * it, and `DoctypeMeta`'s own validation asks the same question at the load gate. It sat in the
220
+ * adapter while the validator hand-rolled a top-level-only scan, and that is exactly the second
221
+ * failure this comment names — a `displayField` inside a fieldset was rejected at authoring time
222
+ * and would have worked at runtime.
223
+ *
224
+ * @param fields - the doctype's top-level fields
225
+ * @returns every non-container field, fieldset children included
226
+ * @public
227
+ */
228
+ export declare function flattenFields(fields: readonly DoctypeField[]): (ValueField | TableField)[];
229
+ /**
230
+ * Resolve the field a doctype nominates as its display text, or `undefined` when the nomination
231
+ * does not name a readable column.
232
+ *
233
+ * This is the single definition of "is this a usable `displayField`". Both sides depend on it:
234
+ * `DoctypeMeta` refuses a bad nomination at the load gate, and the adapter builds a SELECT from
235
+ * the field it returns. Call this; never re-derive the rule, or the gate and the query will
236
+ * disagree about which nominations are legal — which they did, in both directions at once.
237
+ *
238
+ * Two things disqualify a nomination, and both are the doctype saying so itself:
239
+ * - it names no field at all, fieldset children included
240
+ * - it names a `computed` field, which is declared precisely to state it has no column, so a
241
+ * SELECT built from it would reference a column the database does not have
242
+ *
243
+ * @param fields - the doctype's top-level fields
244
+ * @param displayField - the nominated fieldname
245
+ * @returns the nominated field, or `undefined` when it is not a readable column
246
+ * @public
247
+ */
248
+ export declare function getDisplayField(fields: readonly DoctypeField[], displayField: string | undefined): ValueField | undefined;
208
249
  /**
209
250
  * The name of the field a record is identified by: the declared `primaryKey`, or `id` when the
210
251
  * doctype declares none.
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/field.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/field.ts"],"names":[],"mappings":"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"}
package/dist/src/field.js CHANGED
@@ -139,6 +139,62 @@ export const INTROSPECTED_IDENTITY_PROPS = [
139
139
  export function getPrimaryKeyField(fields) {
140
140
  return fields.find((f) => f.kind === 'field' && Boolean(f.primaryKey));
141
141
  }
142
+ /**
143
+ * Recursively flatten Fieldset containers into a flat array of non-container fields.
144
+ * Fieldset entries are replaced by their children; all other fields pass through.
145
+ *
146
+ * A fieldset is a layout grouping, not a scope: every field inside one is a field of the doctype,
147
+ * with a column of its own and a name a link can bind to. Anything asking "what does this doctype
148
+ * declare" must therefore descend, and the two ways to get that wrong point opposite ways — the
149
+ * SELECT builder would omit real columns, while a validator would report a working declaration as
150
+ * broken.
151
+ *
152
+ * Lives here rather than in the adapter because both sides need it: the middleware builds SQL from
153
+ * it, and `DoctypeMeta`'s own validation asks the same question at the load gate. It sat in the
154
+ * adapter while the validator hand-rolled a top-level-only scan, and that is exactly the second
155
+ * failure this comment names — a `displayField` inside a fieldset was rejected at authoring time
156
+ * and would have worked at runtime.
157
+ *
158
+ * @param fields - the doctype's top-level fields
159
+ * @returns every non-container field, fieldset children included
160
+ * @public
161
+ */
162
+ export function flattenFields(fields) {
163
+ const result = [];
164
+ for (const f of fields) {
165
+ if (f.kind === 'fieldset') {
166
+ result.push(...flattenFields(f.schema));
167
+ }
168
+ else {
169
+ result.push(f);
170
+ }
171
+ }
172
+ return result;
173
+ }
174
+ /**
175
+ * Resolve the field a doctype nominates as its display text, or `undefined` when the nomination
176
+ * does not name a readable column.
177
+ *
178
+ * This is the single definition of "is this a usable `displayField`". Both sides depend on it:
179
+ * `DoctypeMeta` refuses a bad nomination at the load gate, and the adapter builds a SELECT from
180
+ * the field it returns. Call this; never re-derive the rule, or the gate and the query will
181
+ * disagree about which nominations are legal — which they did, in both directions at once.
182
+ *
183
+ * Two things disqualify a nomination, and both are the doctype saying so itself:
184
+ * - it names no field at all, fieldset children included
185
+ * - it names a `computed` field, which is declared precisely to state it has no column, so a
186
+ * SELECT built from it would reference a column the database does not have
187
+ *
188
+ * @param fields - the doctype's top-level fields
189
+ * @param displayField - the nominated fieldname
190
+ * @returns the nominated field, or `undefined` when it is not a readable column
191
+ * @public
192
+ */
193
+ export function getDisplayField(fields, displayField) {
194
+ if (!displayField)
195
+ return undefined;
196
+ return flattenFields(fields).find((f) => f.kind === 'field' && !f.computed && f.fieldname === displayField);
197
+ }
142
198
  /**
143
199
  * The name of the field a record is identified by: the declared `primaryKey`, or `id` when the
144
200
  * doctype declares none.
@@ -2,8 +2,8 @@ export type { InteractionMode } from './mode';
2
2
  export { TableViewConfig } from './table';
3
3
  export { CANONICAL_COMPONENTS, COMPONENT_CATEGORY, COMPONENT_LINK_EXPANSION, componentCategory, componentLinkExpansion, resolveLinkRenderMode, type ComponentCategory, type LinkExpansion, type LinkRenderMode, } from './component-meta';
4
4
  export type { DoctypeField, FieldOptions, FieldValidation, FieldsetField, TableField, ValueField } from './field';
5
- export { DoctypeFieldSchema, FieldsetFieldSchema, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
6
- export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, isActionAllowedInState } from './doctype';
5
+ export { DoctypeFieldSchema, FieldsetFieldSchema, flattenFields, getDisplayField, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
6
+ export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, isActionAllowedInState, LINK_DISPLAY_SUFFIX, linkDisplayFieldname, } from './doctype';
7
7
  export type { Cardinality, CustomFetch, DataClient, DoctypeContext, DoctypeMeta, DoctypeRef, FetchStrategy, GetRecordOptions, GetRecordResult, GetRecordsOptions, GetRecordsResult, LazyFetch, LinkDeclaration, SerializedFunction, SyncFetch, } from './doctype';
8
8
  export { parseDoctype, parseField, validateDoctype, validateField, type ValidationError, type ValidationResult, } from './validation';
9
9
  export { buildScalarMap, classifyFieldType, convertGraphQLSchema, defaultIsEntityField, defaultIsEntityType, formatDoctypeDrift, GQL_SCALAR_MAP, INTERNAL_SCALARS, mergeIntrospectedDoctype, WELL_KNOWN_SCALARS, type AuthoredDoctype, type ConvertedGraphQLDoctype, type DoctypeDrift, type GraphQLConversionFieldMeta, type GraphQLConversionOptions, type IntrospectionSource, type MergeResult, } from './converter';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAG7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGzC,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,GACnB,MAAM,kBAAkB,CAAA;AAGzB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACjH,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,SAAS,CAAA;AAKhB,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAA;AACrH,YAAY,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,SAAS,GACT,MAAM,WAAW,CAAA;AAGlB,OAAO,EACN,YAAY,EACZ,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,cAAc,CAAA;AAGrB,OAAO,EACN,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,WAAW,GAChB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGtH,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAG7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGzC,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,GACnB,MAAM,kBAAkB,CAAA;AAGzB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACjH,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,SAAS,CAAA;AAKhB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,WAAW,CAAA;AAClB,YAAY,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,SAAS,GACT,MAAM,WAAW,CAAA;AAGlB,OAAO,EACN,YAAY,EACZ,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,cAAc,CAAA;AAGrB,OAAO,EACN,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,WAAW,GAChB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGtH,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA"}
package/dist/src/index.js CHANGED
@@ -2,11 +2,11 @@
2
2
  export { TableViewConfig } from './table';
3
3
  // Component → semantic category — the single source of "what kind of value does this component render"
4
4
  export { CANONICAL_COMPONENTS, COMPONENT_CATEGORY, COMPONENT_LINK_EXPANSION, componentCategory, componentLinkExpansion, resolveLinkRenderMode, } from './component-meta';
5
- export { DoctypeFieldSchema, FieldsetFieldSchema, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
5
+ export { DoctypeFieldSchema, FieldsetFieldSchema, flattenFields, getDisplayField, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
6
6
  // Doctype schema
7
7
  // ActionDefinition and WorkflowMeta are exported as values (Zod schemas) so consumers can use
8
8
  // .safeParse(), .shape, etc. at runtime. TypeScript types are inferred from the same exports.
9
- export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, isActionAllowedInState } from './doctype';
9
+ export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, isActionAllowedInState, LINK_DISPLAY_SUFFIX, linkDisplayFieldname, } from './doctype';
10
10
  // Validation helpers
11
11
  export { parseDoctype, parseField, validateDoctype, validateField, } from './validation';
12
12
  // GraphQL to Doctype conversion