@dforge-core/metadata 0.0.25 → 0.0.27
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/CHANGELOG.md +29 -0
- package/dist/index.d.ts +140 -7
- package/dist/index.js +54 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/card_layouts.schema.json +141 -0
- package/schemas/data_views.schema.json +2 -14
- package/schemas/entity.schema.json +45 -1
- package/schemas/manifest.schema.json +4 -0
- package/src/card-layouts.ts +103 -0
- package/src/data-views.ts +2 -4
- package/src/entity.ts +4 -2
- package/src/index.ts +15 -1
- package/src/manifest.ts +2 -0
- package/src/traits.ts +131 -20
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to `@dforge-core/metadata` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.26] — 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`traitFieldConflicts()` and the `TraitFieldConflict` type** report the collisions
|
|
13
|
+
that make the installer refuse an entity: a trait contributing a field the entity
|
|
14
|
+
already has under a different type identity (`columnType ?? dbDatatype ??
|
|
15
|
+
fieldTypeCd`, the identity `TraitExpander.TryAddField` compares). Install throws on
|
|
16
|
+
one, so a module that hits it cannot install — offline readers now name the field,
|
|
17
|
+
both types, and whether the definition being kept is an authored field or another
|
|
18
|
+
trait's. Expansion itself stays total, so an editor can still draw a field list.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **A field code that names an inherited property is a field like any other.** The
|
|
23
|
+
merge read `sink.fields[key]` against a plain object, so a trait defining
|
|
24
|
+
`constructor` found `Object.prototype`'s, contributed no column, and reported a
|
|
25
|
+
conflict against a type identity read off a function. Own-property lookups
|
|
26
|
+
throughout, on a prototype-less merge map — which also fixes the write side, where a
|
|
27
|
+
field named `__proto__` set the prototype instead of a key — and on the
|
|
28
|
+
`localTraits` lookup, which is parsed JSON with the same hazard.
|
|
29
|
+
|
|
30
|
+
- **Two templated keys that collide on one entity are a conflict, not a silent
|
|
31
|
+
overwrite.** A trait defining both `{entity}_id` and `x_id` collapsed them while
|
|
32
|
+
templating, for entity `x`, before the merge ever saw them: the later definition won
|
|
33
|
+
and nothing was reported, while install rejected the entity. Each field is now
|
|
34
|
+
resolved and merged on its own, the way the installer resolves and adds one key at a
|
|
35
|
+
time — the first definition wins, the second is reported.
|
|
36
|
+
|
|
8
37
|
## [0.0.25] — 2026-09-15
|
|
9
38
|
|
|
10
39
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -442,11 +442,33 @@ declare function getColumnType(cd: string): ColumnTypeDef | undefined;
|
|
|
442
442
|
declare const traits: readonly TraitDef[];
|
|
443
443
|
/** Look up a trait by code. */
|
|
444
444
|
declare function getTrait(cd: string): TraitDef | undefined;
|
|
445
|
+
/** Where a field already in the merge came from, for conflict wording. */
|
|
446
|
+
type FieldOrigin = "field" | "trait";
|
|
447
|
+
/**
|
|
448
|
+
* A trait contributing a field the entity already has under a DIFFERENT type.
|
|
449
|
+
* The installer throws on this (`Field 'x' conflicts with trait: ...`), so a
|
|
450
|
+
* module that hits one cannot install — offline readers report it instead.
|
|
451
|
+
*/
|
|
452
|
+
interface TraitFieldConflict {
|
|
453
|
+
/** Column code, after `{entity}` / `{Entity}` resolution. */
|
|
454
|
+
field: string;
|
|
455
|
+
/** Whether the definition being kept is an authored field or a trait's. */
|
|
456
|
+
existingFrom: FieldOrigin;
|
|
457
|
+
/** Type identity of the definition being kept. */
|
|
458
|
+
existingType?: string;
|
|
459
|
+
/** Type identity of the trait definition being dropped. */
|
|
460
|
+
traitType?: string;
|
|
461
|
+
}
|
|
445
462
|
/**
|
|
446
463
|
* Expand a trait (and any traits it includes) into the concrete fields it adds
|
|
447
464
|
* for `entityName`, with `{entity}` / `{Entity}` tokens resolved. Returns an
|
|
448
465
|
* empty object for unknown or marker traits. Field order follows include-depth
|
|
449
|
-
* first, then declaration order
|
|
466
|
+
* first, then declaration order.
|
|
467
|
+
*
|
|
468
|
+
* The FIRST definition of a key wins, mirroring the installer's
|
|
469
|
+
* `TraitExpander.TryAddField`. A later definition of the same key under a
|
|
470
|
+
* different type is one the installer refuses to expand at all — see
|
|
471
|
+
* {@link traitFieldConflicts}, which reports those without throwing.
|
|
450
472
|
*
|
|
451
473
|
* `localTraits` is a module's own `traits.json`. A module may declare traits of
|
|
452
474
|
* its own, and an entity in it names them exactly like a platform trait — so a
|
|
@@ -458,6 +480,24 @@ declare function getTrait(cd: string): TraitDef | undefined;
|
|
|
458
480
|
declare function expandTrait(traitCd: string, entityName: string, localTraits?: TraitsFile): Record<string, FieldDef>;
|
|
459
481
|
/** Expand several traits in order into one merged field map. */
|
|
460
482
|
declare function expandTraits(traitCds: readonly string[], entityName: string, localTraits?: TraitsFile): Record<string, FieldDef>;
|
|
483
|
+
/**
|
|
484
|
+
* The collisions that make the installer refuse an entity: a trait contributing
|
|
485
|
+
* a field the entity already has under a different type identity.
|
|
486
|
+
*
|
|
487
|
+
* `fields` is the entity's own authored fields. The installer seeds its merge
|
|
488
|
+
* with them and expands traits into it (`TraitExpander.ExpandAll`), so an
|
|
489
|
+
* authored field can conflict with a trait's exactly as two traits can — and
|
|
490
|
+
* either way install fails with `Field 'x' conflicts with trait`. Omit it to
|
|
491
|
+
* check the traits against each other alone.
|
|
492
|
+
*
|
|
493
|
+
* Returns one entry per field, in the order the conflicts are reached. An empty
|
|
494
|
+
* array means the expansion the installer performs is the one
|
|
495
|
+
* {@link expandTraits} returns.
|
|
496
|
+
*/
|
|
497
|
+
declare function traitFieldConflicts(traitCds: readonly string[], entityName: string, opts?: {
|
|
498
|
+
localTraits?: TraitsFile;
|
|
499
|
+
fields?: Record<string, FieldDef>;
|
|
500
|
+
}): TraitFieldConflict[];
|
|
461
501
|
|
|
462
502
|
/** Total digits used when building a `numeric(total, scale)` type. */
|
|
463
503
|
declare const NUMERIC_TOTAL = 18;
|
|
@@ -633,8 +673,8 @@ interface AccumulationConfig {
|
|
|
633
673
|
sign?: SignConfig;
|
|
634
674
|
autoCreateBalance?: boolean;
|
|
635
675
|
allowNegative?: boolean;
|
|
636
|
-
/** Document fields locked once posted. */
|
|
637
|
-
lockedFields
|
|
676
|
+
/** Document fields locked once posted. Required; `["*"]` freezes the whole row. */
|
|
677
|
+
lockedFields: string[];
|
|
638
678
|
}
|
|
639
679
|
/** One registry target within an L-column's `registries` array. */
|
|
640
680
|
interface LedgerRegistryEntry {
|
|
@@ -668,6 +708,8 @@ interface EntityDef {
|
|
|
668
708
|
viewSql?: string;
|
|
669
709
|
/** Display pattern using column placeholders, e.g. "{first_name} {last_name}". */
|
|
670
710
|
toString?: string;
|
|
711
|
+
/** Records of this entity accept comments (composer + thread on the card). */
|
|
712
|
+
comments?: boolean;
|
|
671
713
|
/** Traits expanded at install time. */
|
|
672
714
|
traits?: TraitCd[];
|
|
673
715
|
/** Column definitions keyed by column code. Optional when a trait (e.g. `period`) supplies all columns. */
|
|
@@ -716,7 +758,7 @@ declare function getIdentityKeys(entity: EntityDef, entityCode: string): string[
|
|
|
716
758
|
declare function getPrimaryKeys(entity: EntityDef, entityCode: string): string[];
|
|
717
759
|
|
|
718
760
|
/** `viewType` values for `ui/data_views.json`. Source: data_views.schema.json. */
|
|
719
|
-
type ViewType = "grid" | "list" | "kanban" | "calendar" | "gallery" | "tree-grid" | "diagram" | "
|
|
761
|
+
type ViewType = "grid" | "list" | "kanban" | "calendar" | "gallery" | "tree-grid" | "diagram" | "library" | "matrix";
|
|
720
762
|
/** Friendly picker list of the view kinds. */
|
|
721
763
|
declare const dataViewKinds: readonly NamedKind[];
|
|
722
764
|
/** A column in a data source: shorthand string code or a configured object. */
|
|
@@ -736,7 +778,7 @@ interface DataSource {
|
|
|
736
778
|
entityCode: string;
|
|
737
779
|
/** Nesting level (0 = root, 1+ = detail). */
|
|
738
780
|
level?: number;
|
|
739
|
-
/** Human-readable label
|
|
781
|
+
/** Human-readable label for this data source. */
|
|
740
782
|
label?: string;
|
|
741
783
|
/** Column configuration. */
|
|
742
784
|
columns?: ViewColumn[];
|
|
@@ -795,7 +837,7 @@ interface DataViewDef {
|
|
|
795
837
|
/** Bootstrap icon class (e.g. 'bi-bounding-box'). */
|
|
796
838
|
icon?: string;
|
|
797
839
|
description?: string;
|
|
798
|
-
/** Data sources (at least one
|
|
840
|
+
/** Data sources (at least one). Only the first is rendered today. */
|
|
799
841
|
dataSources: DataSource[];
|
|
800
842
|
/** Default filter applied to all sources unless one declares its own. */
|
|
801
843
|
filter?: Filter;
|
|
@@ -1024,6 +1066,8 @@ interface ManifestDef {
|
|
|
1024
1066
|
dependencies?: Record<string, ModuleDependency>;
|
|
1025
1067
|
/** Default audit history mode for this module's entities. */
|
|
1026
1068
|
auditHistory?: "none" | "minimal" | "full";
|
|
1069
|
+
/** Default for `comments` on this module's entities; each entity may override. */
|
|
1070
|
+
comments?: boolean;
|
|
1027
1071
|
/** Entity code → relative path of its JSON file (dotted keys = extensions). */
|
|
1028
1072
|
entities?: Record<string, string>;
|
|
1029
1073
|
/** Module category for display (e.g. 'Integration', 'Finance'). */
|
|
@@ -1382,6 +1426,95 @@ interface PrintTemplateDef {
|
|
|
1382
1426
|
/** A `ui/print_templates.json` file: template_cd → definition. */
|
|
1383
1427
|
type PrintTemplatesFile = Record<string, PrintTemplateDef>;
|
|
1384
1428
|
|
|
1429
|
+
/** A field in a card section — either a bare column code or pixel sizing with it. */
|
|
1430
|
+
type CardFieldEntry = string | CardFieldDef;
|
|
1431
|
+
interface CardFieldDef {
|
|
1432
|
+
column_cd: string;
|
|
1433
|
+
/** Field width in px. */
|
|
1434
|
+
width?: number;
|
|
1435
|
+
/** Field height in px — for multiline/textarea fields. */
|
|
1436
|
+
height?: number;
|
|
1437
|
+
}
|
|
1438
|
+
/** A group of fields laid out in 1, 2 or 3 columns. */
|
|
1439
|
+
interface ColumnGroupSection {
|
|
1440
|
+
type: "columnGroup";
|
|
1441
|
+
code: string;
|
|
1442
|
+
/** Section heading, in the module's authoring language. The installer gives it an
|
|
1443
|
+
* `entity_column_group` row keyed on this section's `code`, so
|
|
1444
|
+
* `translations/<locale>.json` localizes it under
|
|
1445
|
+
* `entities.<entity>.columnGroups.<code>.label` and the card renders that instead. */
|
|
1446
|
+
label?: string;
|
|
1447
|
+
columns: CardFieldEntry[];
|
|
1448
|
+
/** Field columns across the group (1-3). Defaults to the card's own setting. */
|
|
1449
|
+
cols?: number;
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* An embedded detail grid for a 1:N set column — the master/detail building
|
|
1453
|
+
* block. The label comes from the set column's own metadata.
|
|
1454
|
+
*/
|
|
1455
|
+
interface SetSection {
|
|
1456
|
+
type: "set";
|
|
1457
|
+
code: string;
|
|
1458
|
+
/** Set column code on this entity (columnType 'S'). */
|
|
1459
|
+
setField: string;
|
|
1460
|
+
}
|
|
1461
|
+
/** Reference to a tab item: a columnGroup section's code, or a set column's code. */
|
|
1462
|
+
interface CardTabRef {
|
|
1463
|
+
type: "section" | "set";
|
|
1464
|
+
code: string;
|
|
1465
|
+
}
|
|
1466
|
+
/** A tab bar whose tabs are other sections — several detail grids without one long form. */
|
|
1467
|
+
interface TabGroupSection {
|
|
1468
|
+
type: "tabGroup";
|
|
1469
|
+
code: string;
|
|
1470
|
+
tabs: CardTabRef[];
|
|
1471
|
+
}
|
|
1472
|
+
type CardSection = ColumnGroupSection | SetSection | TabGroupSection;
|
|
1473
|
+
/** Per-set-field renderer choice, keyed by the set column code. */
|
|
1474
|
+
interface CardSetConfig {
|
|
1475
|
+
viewType?: "grid" | "list";
|
|
1476
|
+
/** Opaque per-renderer options, passed verbatim to the set registration. */
|
|
1477
|
+
options?: Record<string, unknown>;
|
|
1478
|
+
}
|
|
1479
|
+
/** The layout body, stored verbatim into `dForge.entity_view_layout.layout`. */
|
|
1480
|
+
interface CardLayout {
|
|
1481
|
+
sections: CardSection[];
|
|
1482
|
+
sets?: Record<string, CardSetConfig>;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* A module-shipped card layout (value in the card_layouts map; the key is the
|
|
1486
|
+
* layout name).
|
|
1487
|
+
*
|
|
1488
|
+
* Rows written from here carry `module_id`, so the installer recreates them on
|
|
1489
|
+
* every install and removes them on uninstall. A layout the tenant drew in the
|
|
1490
|
+
* card editor has `module_id` NULL and is never touched.
|
|
1491
|
+
*/
|
|
1492
|
+
interface CardLayoutDef {
|
|
1493
|
+
/**
|
|
1494
|
+
* Entity code the layout belongs to. Unqualified means this module owns the
|
|
1495
|
+
* entity; `module.entity` targets another module's (the bridge case).
|
|
1496
|
+
*/
|
|
1497
|
+
entity: string;
|
|
1498
|
+
/**
|
|
1499
|
+
* Entity view the layout hangs off. Defaults to `"default"` — the same
|
|
1500
|
+
* fallback the runtime uses for an entity with no folder binding of its own.
|
|
1501
|
+
* Matched against the entity's declared views case-insensitively, and filed
|
|
1502
|
+
* under their spelling.
|
|
1503
|
+
*/
|
|
1504
|
+
view?: string;
|
|
1505
|
+
/**
|
|
1506
|
+
* Whether this layout opens by default. Honoured on FIRST install only, and
|
|
1507
|
+
* only when the view has no default yet: once a tenant has chosen their own,
|
|
1508
|
+
* an upgrade does not take it back.
|
|
1509
|
+
*/
|
|
1510
|
+
isDefault?: boolean;
|
|
1511
|
+
/** Human-readable note for the package author. Not stored. */
|
|
1512
|
+
description?: string;
|
|
1513
|
+
layout: CardLayout;
|
|
1514
|
+
}
|
|
1515
|
+
/** `ui/card_layouts.json` — layout name → definition. */
|
|
1516
|
+
type CardLayoutsFile = Record<string, CardLayoutDef>;
|
|
1517
|
+
|
|
1385
1518
|
/** A seed-data file: records to insert into one entity. */
|
|
1386
1519
|
interface SeedDataFile {
|
|
1387
1520
|
/** Target entity code. */
|
|
@@ -1395,4 +1528,4 @@ declare const flagDefs: readonly {
|
|
|
1395
1528
|
label: string;
|
|
1396
1529
|
}[];
|
|
1397
1530
|
|
|
1398
|
-
export { AGG_TYPE_LIST, type AccumulationConfig, AggType, type AlignType, type BaseDatatypeCd, type CalendarViewConfig, type ColumnDef, type ColumnTypeCd, type ColumnTypeDef, DEP_COLUMN_TYPES, type DataSource, type DataViewDef, type DataViewsFile, type Dataset, type DepColumn, type DepColumnMode, type DepColumnType, type DepEntity, type DepProvenance, type DepProvenanceKind, type DepsFile, type DeriveOptions, type DiagramViewConfig, type DomainDef, type DomainsFile, type EntityDef, type EntityEvent, type EntityReference, type EntityViewColumnDef, type EntityViewDef, FILTER_GROUP_OPERATORS, FILTER_OPERATORS, type FieldDef, type FieldLink, type FieldOption, type FieldTypeCd, type FieldTypeDef, type Filter, type FilterCondition, type FilterGroup, type FilterGroupOperator, type FilterOperator, type FlagCd, type FolderDef, type FolderEntityBinding, type FoldersFile, type JobDef, type JobsFile, type KanbanViewConfig, type LedgerRegistryEntry, type ListLevelConfig, type ListViewConfig, type ManifestAuthor, type ManifestDef, type MenuDef, type MenuItemDef, type MenuItemType, type MenusFile, type ModuleDependency, type ModuleFeature, NUMERIC_TOTAL, type NamedKind, type NumberSequenceDef, type ParamDef, type ParamDefBase, type PeriodConfig, type PrintMargins, type PrintPageSettings, type PrintTemplateDef, type PrintTemplatesFile, type ReportDef, type ReportEntityAttachment, type ReportLayout, type ReportPanel, type ReportQuery, type ReportsFile, type RoleDef, type RolesFile, type SeedDataFile, type SettingBaseDatatype, type SettingDef, type SettingsFile, type SignConfig, type SortClause, type TraitCd, type TraitDef, type TraitsFile, type TreeGridViewConfig, type TriggerDef, type TriggersFile, type ViewColumn, type ViewType, type VizType, type WebhookPayload, type WebhookSubscription, type WebhooksFile, baseDatatypes, baseToDbDatatype, chartTypes, columnTypes, dataViewKinds, defaultParams, deriveBaseDatatype, deriveDbDatatype, expandTrait, expandTraits, fieldTypeCds, fieldTypes, fieldTypesByColumnType, flagDefs, getColumnType, getFieldType, getIdentityKeys, getLinks, getPrimaryKeys, getTrait, isFieldTypeCd, traits, vizTypes };
|
|
1531
|
+
export { AGG_TYPE_LIST, type AccumulationConfig, AggType, type AlignType, type BaseDatatypeCd, type CalendarViewConfig, type CardFieldDef, type CardFieldEntry, type CardLayout, type CardLayoutDef, type CardLayoutsFile, type CardSection, type CardSetConfig, type CardTabRef, type ColumnDef, type ColumnGroupSection, type ColumnTypeCd, type ColumnTypeDef, DEP_COLUMN_TYPES, type DataSource, type DataViewDef, type DataViewsFile, type Dataset, type DepColumn, type DepColumnMode, type DepColumnType, type DepEntity, type DepProvenance, type DepProvenanceKind, type DepsFile, type DeriveOptions, type DiagramViewConfig, type DomainDef, type DomainsFile, type EntityDef, type EntityEvent, type EntityReference, type EntityViewColumnDef, type EntityViewDef, FILTER_GROUP_OPERATORS, FILTER_OPERATORS, type FieldDef, type FieldLink, type FieldOption, type FieldTypeCd, type FieldTypeDef, type Filter, type FilterCondition, type FilterGroup, type FilterGroupOperator, type FilterOperator, type FlagCd, type FolderDef, type FolderEntityBinding, type FoldersFile, type JobDef, type JobsFile, type KanbanViewConfig, type LedgerRegistryEntry, type ListLevelConfig, type ListViewConfig, type ManifestAuthor, type ManifestDef, type MenuDef, type MenuItemDef, type MenuItemType, type MenusFile, type ModuleDependency, type ModuleFeature, NUMERIC_TOTAL, type NamedKind, type NumberSequenceDef, type ParamDef, type ParamDefBase, type PeriodConfig, type PrintMargins, type PrintPageSettings, type PrintTemplateDef, type PrintTemplatesFile, type ReportDef, type ReportEntityAttachment, type ReportLayout, type ReportPanel, type ReportQuery, type ReportsFile, type RoleDef, type RolesFile, type SeedDataFile, type SetSection, type SettingBaseDatatype, type SettingDef, type SettingsFile, type SignConfig, type SortClause, type TabGroupSection, type TraitCd, type TraitDef, type TraitFieldConflict, type TraitsFile, type TreeGridViewConfig, type TriggerDef, type TriggersFile, type ViewColumn, type ViewType, type VizType, type WebhookPayload, type WebhookSubscription, type WebhooksFile, baseDatatypes, baseToDbDatatype, chartTypes, columnTypes, dataViewKinds, defaultParams, deriveBaseDatatype, deriveDbDatatype, expandTrait, expandTraits, fieldTypeCds, fieldTypes, fieldTypesByColumnType, flagDefs, getColumnType, getFieldType, getIdentityKeys, getLinks, getPrimaryKeys, getTrait, isFieldTypeCd, traitFieldConflicts, traits, vizTypes };
|
package/dist/index.js
CHANGED
|
@@ -182,39 +182,73 @@ function applyTemplate(value, entity) {
|
|
|
182
182
|
const local = entity.split(".").pop() ?? entity;
|
|
183
183
|
return value.replace(/\{entity\}/g, local).replace(/\{Entity\}/g, titleCaseEntity(entity));
|
|
184
184
|
}
|
|
185
|
-
function
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
185
|
+
function templateField(def, entity) {
|
|
186
|
+
const next = { ...def };
|
|
187
|
+
if (typeof next.description === "string") next.description = applyTemplate(next.description, entity);
|
|
188
|
+
return next;
|
|
189
|
+
}
|
|
190
|
+
function emptyFields() {
|
|
191
|
+
return /* @__PURE__ */ Object.create(null);
|
|
192
|
+
}
|
|
193
|
+
function typeKeyOf(def) {
|
|
194
|
+
return def.columnType ?? def.dbDatatype ?? def.fieldTypeCd;
|
|
195
|
+
}
|
|
196
|
+
function addTraitField(sink, key, def) {
|
|
197
|
+
const existing = sink.fields[key];
|
|
198
|
+
if (!Object.hasOwn(sink.fields, key)) {
|
|
199
|
+
sink.fields[key] = def;
|
|
200
|
+
sink.origin.set(key, "trait");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (sink.conflicts && !sink.conflicts.some((c) => c.field === key)) {
|
|
204
|
+
const existingType = typeKeyOf(existing);
|
|
205
|
+
const traitType = typeKeyOf(def);
|
|
206
|
+
if (existingType !== traitType) {
|
|
207
|
+
sink.conflicts.push({
|
|
208
|
+
field: key,
|
|
209
|
+
existingFrom: sink.origin.get(key) ?? "trait",
|
|
210
|
+
existingType,
|
|
211
|
+
traitType
|
|
212
|
+
});
|
|
213
|
+
}
|
|
192
214
|
}
|
|
193
|
-
return out;
|
|
194
215
|
}
|
|
195
216
|
function expandTrait(traitCd, entityName, localTraits) {
|
|
196
|
-
|
|
217
|
+
const sink = { fields: emptyFields(), origin: /* @__PURE__ */ new Map() };
|
|
218
|
+
expandTraitInner(traitCd, entityName, localTraits, /* @__PURE__ */ new Set(), sink);
|
|
219
|
+
return sink.fields;
|
|
197
220
|
}
|
|
198
|
-
function expandTraitInner(traitCd, entityName, localTraits, path) {
|
|
199
|
-
if (path.has(traitCd)) return
|
|
221
|
+
function expandTraitInner(traitCd, entityName, localTraits, path, sink) {
|
|
222
|
+
if (path.has(traitCd)) return;
|
|
200
223
|
path.add(traitCd);
|
|
201
|
-
const trait = localTraits
|
|
202
|
-
const result = {};
|
|
224
|
+
const trait = localTraits && Object.hasOwn(localTraits, traitCd) ? localTraits[traitCd] : byCd3.get(traitCd);
|
|
203
225
|
if (trait) {
|
|
204
226
|
for (const included of trait.includes ?? []) {
|
|
205
|
-
|
|
227
|
+
expandTraitInner(included, entityName, localTraits, path, sink);
|
|
228
|
+
}
|
|
229
|
+
for (const [name, def] of Object.entries(trait.fields ?? {})) {
|
|
230
|
+
addTraitField(sink, applyTemplate(name, entityName), templateField(def, entityName));
|
|
206
231
|
}
|
|
207
|
-
Object.assign(result, templateFields(trait.fields ?? {}, entityName));
|
|
208
232
|
}
|
|
209
233
|
path.delete(traitCd);
|
|
210
|
-
return result;
|
|
211
234
|
}
|
|
212
235
|
function expandTraits(traitCds, entityName, localTraits) {
|
|
213
|
-
const
|
|
236
|
+
const sink = { fields: emptyFields(), origin: /* @__PURE__ */ new Map() };
|
|
214
237
|
for (const cd of traitCds) {
|
|
215
|
-
|
|
238
|
+
expandTraitInner(cd, entityName, localTraits, /* @__PURE__ */ new Set(), sink);
|
|
216
239
|
}
|
|
217
|
-
return
|
|
240
|
+
return sink.fields;
|
|
241
|
+
}
|
|
242
|
+
function traitFieldConflicts(traitCds, entityName, opts = {}) {
|
|
243
|
+
const sink = { fields: emptyFields(), origin: /* @__PURE__ */ new Map(), conflicts: [] };
|
|
244
|
+
for (const [key, def] of Object.entries(opts.fields ?? {})) {
|
|
245
|
+
sink.fields[key] = def;
|
|
246
|
+
sink.origin.set(key, "field");
|
|
247
|
+
}
|
|
248
|
+
for (const cd of traitCds) {
|
|
249
|
+
expandTraitInner(cd, entityName, opts.localTraits, /* @__PURE__ */ new Set(), sink);
|
|
250
|
+
}
|
|
251
|
+
return sink.conflicts;
|
|
218
252
|
}
|
|
219
253
|
|
|
220
254
|
// src/derive.ts
|
|
@@ -332,7 +366,6 @@ var dataViewKinds = [
|
|
|
332
366
|
{ cd: "gallery", name: "Gallery" },
|
|
333
367
|
{ cd: "tree-grid", name: "Tree Grid" },
|
|
334
368
|
{ cd: "diagram", name: "Diagram" },
|
|
335
|
-
{ cd: "master-detail", name: "Master / Detail" },
|
|
336
369
|
{ cd: "library", name: "Library" },
|
|
337
370
|
{ cd: "matrix", name: "Matrix" }
|
|
338
371
|
];
|
|
@@ -422,6 +455,7 @@ export {
|
|
|
422
455
|
getPrimaryKeys,
|
|
423
456
|
getTrait,
|
|
424
457
|
isFieldTypeCd,
|
|
458
|
+
traitFieldConflicts,
|
|
425
459
|
traits,
|
|
426
460
|
vizTypes
|
|
427
461
|
};
|