@dforge-core/metadata 0.0.44 → 0.0.46

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { a as ActionDef, A as ActionExecutionMode, b as ActionsFile } from './actions-DvYTrCbP.js';
1
+ export { a as ActionDef, A as ActionExecutionMode, b as ActionParamSetDef, c as ActionsFile } from './actions-D-0G_mqf.js';
2
2
 
3
3
  /** A `{ cd, name }` pair for friendly pickers (view kinds, viz types, …). */
4
4
  interface NamedKind {
@@ -16,7 +16,7 @@ type BaseDatatypeCd = "binary" | "bool" | "culture" | "cuid" | "date" | "guid" |
16
16
  * isNullable:false at install). */
17
17
  type FlagCd = "V" | "I" | "E" | "M";
18
18
  /** Known field type codes from the field_type seed. */
19
- type FieldTypeCd = "text" | "email" | "phone" | "url" | "textarea" | "number" | "currency" | "percent" | "checkbox" | "date" | "datetime" | "time" | "dropdown" | "flags" | "json" | "hidden" | "color" | "image" | "file" | "markdown" | "richtext" | "avatar" | "lookup" | "user" | "grid" | "list" | "tags" | "entitylink" | "code";
19
+ type FieldTypeCd = "text" | "email" | "phone" | "url" | "textarea" | "number" | "currency" | "percent" | "filesize" | "checkbox" | "date" | "datetime" | "time" | "dropdown" | "flags" | "json" | "hidden" | "color" | "image" | "file" | "markdown" | "richtext" | "avatar" | "lookup" | "user" | "grid" | "list" | "tags" | "entitylink" | "code";
20
20
  /**
21
21
  * A UI field type from the `field_type` table. The author picks one of these;
22
22
  * the physical `dbDatatype` and `baseDatatypeCd` are derived from it.
@@ -248,6 +248,15 @@ declare const fieldTypes: readonly [{
248
248
  readonly maxLen: null;
249
249
  readonly precision: 2;
250
250
  readonly useStringInFilter: false;
251
+ }, {
252
+ readonly cd: "filesize";
253
+ readonly name: "File Size";
254
+ readonly columnType: "D";
255
+ readonly baseDatatypeCd: "number";
256
+ readonly supportedAgg: "SCAM";
257
+ readonly maxLen: null;
258
+ readonly precision: null;
259
+ readonly useStringInFilter: false;
251
260
  }, {
252
261
  readonly cd: "checkbox";
253
262
  readonly name: "Checkbox";
@@ -1693,6 +1702,8 @@ interface RegisterDef {
1693
1702
  debitField: string;
1694
1703
  creditField: string;
1695
1704
  }>;
1705
+ /** Accounting only: the posting column a loaded posting keeps its document's key in, so `exclude` can leave it out. */
1706
+ importedKeyField?: string;
1696
1707
  resources: Record<string, RegisterResourceDef>;
1697
1708
  totals?: RegisterTotalsDef;
1698
1709
  /** `balance`: may the state go below zero (default false). */
@@ -1776,6 +1787,35 @@ interface RegisterIssue {
1776
1787
  code: string;
1777
1788
  }
1778
1789
 
1790
+ /** The entity a column slot is stored on. `carrier` is `entity` on a balance / information register, `journal` otherwise. */
1791
+ type RegisterColumnHost = "entity" | "journal" | "carrier" | "totals" | "composition";
1792
+ interface RegisterColumnSlot {
1793
+ /** Where in a declaration; `*` matches any key or index at that depth. */
1794
+ path: readonly string[];
1795
+ on: RegisterColumnHost;
1796
+ /** What the column is called in a message; `{0}`, `{1}`… stand for the `*` matches in order. */
1797
+ what: string;
1798
+ /**
1799
+ * The kinds of register for which the column is added up and so must store a
1800
+ * number. An information register's resources are what a slice returns — a
1801
+ * currency code as readily as a price — so it is not among them.
1802
+ */
1803
+ numericFor?: readonly RegisterKind[];
1804
+ }
1805
+ declare const REGISTER_COLUMN_SLOTS: readonly RegisterColumnSlot[];
1806
+ interface ResolvedColumnSlot {
1807
+ on: RegisterColumnHost;
1808
+ /** The column's name for a message, with the path's keys filled in. */
1809
+ what: string;
1810
+ numeric: boolean;
1811
+ }
1812
+ /** The slot a path inside one declaration names a column at; nothing for a path that isn't a column. `kind` is the register's, which decides whether the column must be numeric. */
1813
+ declare function registerColumnSlot(path: IssuePath, kind?: RegisterKind): ResolvedColumnSlot | undefined;
1814
+ /** The code of the entity a slot's host names in this declaration. */
1815
+ declare function registerColumnHost(def: Raw, on: RegisterColumnHost): string | undefined;
1816
+ /** Whether a storage type (`numeric(18,2)`, `int8`) is a scalar number; the whole type must match, so `integer[]` is not. */
1817
+ declare const isNumericDatatype: (datatype: string) => boolean;
1818
+
1779
1819
  /**
1780
1820
  * What an entity a declaration names looks like, as far as the caller knows.
1781
1821
  * `columns` is left undefined when they can't be listed (a dependency known
@@ -1783,6 +1823,8 @@ interface RegisterIssue {
1783
1823
  */
1784
1824
  interface RegisterEntityInfo {
1785
1825
  columns?: Iterable<string>;
1826
+ /** Storage type per column (`numeric(18,2)`); a column missing here has no numeric rule run on it. */
1827
+ datatypes?: ReadonlyMap<string, string>;
1786
1828
  }
1787
1829
  /**
1788
1830
  * Resolves an entity code as `registers.json` writes it — bare for the module's
@@ -1791,6 +1833,12 @@ interface RegisterEntityInfo {
1791
1833
  * the module is known and has no such entity.
1792
1834
  */
1793
1835
  type RegisterEntityLookup = (code: string) => RegisterEntityInfo | null | undefined;
1836
+ type ResolvedRegisterEntity = RegisterEntityInfo | null | undefined;
1837
+ /** The entities of a declaration resolved so far, by the slot host that names them. */
1838
+ type RegisterHosts = Partial<Record<RegisterColumnHost, {
1839
+ entity: ResolvedRegisterEntity;
1840
+ code: string | undefined;
1841
+ }>>;
1794
1842
 
1795
1843
  /**
1796
1844
  * Check one declaration against the schema's per-kind rules and the installer's
@@ -2053,4 +2101,4 @@ declare const flagDefs: readonly {
2053
2101
  label: string;
2054
2102
  }[];
2055
2103
 
2056
- export { AGG_TYPE_LIST, type AccumulationConfig, AggType, type AlignType, type AuditHistoryLevel, 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 DiagramDef, type DiagramDetail, type DiagramEntity, type DiagramFile, type DiagramRelation, type DiagramViewConfig, type DomainDef, type DomainsFile, type EntityDef, type EntityEvent, type EntityReaction, 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, REGISTER_CODE, REGISTER_COLUMN_NAME, REGISTER_KINDS, REGISTER_TOTALS_PERIODS, type RegisterBindingParams, type RegisterBindingTarget, type RegisterCompositionDef, type RegisterDef, type RegisterEntityInfo, type RegisterEntityLookup, type RegisterIssue, type RegisterKind, type RegisterMapValue, type RegisterResourceDef, type RegisterSource, type RegisterTotalsDef, type RegisterTotalsPeriod, type RegistersFile, type ReportDef, type ReportEntityAttachment, type ReportLayout, type ReportPanel, type ReportQuery, type ReportsFile, type RoleDef, type RolesFile, type SeedDataFile, type SeedDataRepeat, type SetSection, type SettingBaseDatatype, type SettingConstraintDef, type SettingDef, type SettingsFile, type SignConfig, type SortClause, type SpColumnDef, type SpParamDef, type StoredProcedureDef, type StoredProceduresFile, type TabGroupSection, type TraitCd, type TraitDef, type TraitFieldConflict, type TraitsFile, type TranslatedAction, type TranslatedEntity, type TranslatedMenu, type TranslatedOption, type TranslatedReport, type TranslatedWithOptions, type TranslationsFile, type TreeGridViewConfig, type TriggerDef, type TriggersFile, type ViewColumn, type ViewType, type VizType, type WebhookPayload, type WebhookSubscription, type WebhooksFile, baseDatatypes, baseToDbDatatype, chartTypes, checkRegister, checkRegisterBinding, checkRegisters, columnTypes, dataViewKinds, defaultParams, deriveBaseDatatype, deriveDbDatatype, expandTrait, expandTraits, fieldTypeCds, fieldTypes, fieldTypesByColumnType, flagDefs, getColumnType, getFieldType, getIdentityKeys, getLinks, getPrimaryKeys, getTrait, isFieldTypeCd, isPlainColumnRef, isRegisterKind, registerCarrier, registerDimensions, registerLineMode, registerRefusedProps, registerRequiredProps, registerResourceShape, registerResources, settingDefsOf, traitFieldConflicts, traits, vizTypes };
2104
+ export { AGG_TYPE_LIST, type AccumulationConfig, AggType, type AlignType, type AuditHistoryLevel, 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 DiagramDef, type DiagramDetail, type DiagramEntity, type DiagramFile, type DiagramRelation, type DiagramViewConfig, type DomainDef, type DomainsFile, type EntityDef, type EntityEvent, type EntityReaction, 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, REGISTER_CODE, REGISTER_COLUMN_NAME, REGISTER_COLUMN_SLOTS, REGISTER_KINDS, REGISTER_TOTALS_PERIODS, type RegisterBindingParams, type RegisterBindingTarget, type RegisterColumnHost, type RegisterColumnSlot, type RegisterCompositionDef, type RegisterDef, type RegisterEntityInfo, type RegisterEntityLookup, type RegisterHosts, type RegisterIssue, type RegisterKind, type RegisterMapValue, type RegisterResourceDef, type RegisterSource, type RegisterTotalsDef, type RegisterTotalsPeriod, type RegistersFile, type ReportDef, type ReportEntityAttachment, type ReportLayout, type ReportPanel, type ReportQuery, type ReportsFile, type ResolvedColumnSlot, type RoleDef, type RolesFile, type SeedDataFile, type SeedDataRepeat, type SetSection, type SettingBaseDatatype, type SettingConstraintDef, type SettingDef, type SettingsFile, type SignConfig, type SortClause, type SpColumnDef, type SpParamDef, type StoredProcedureDef, type StoredProceduresFile, type TabGroupSection, type TraitCd, type TraitDef, type TraitFieldConflict, type TraitsFile, type TranslatedAction, type TranslatedEntity, type TranslatedMenu, type TranslatedOption, type TranslatedReport, type TranslatedWithOptions, type TranslationsFile, type TreeGridViewConfig, type TriggerDef, type TriggersFile, type ViewColumn, type ViewType, type VizType, type WebhookPayload, type WebhookSubscription, type WebhooksFile, baseDatatypes, baseToDbDatatype, chartTypes, checkRegister, checkRegisterBinding, checkRegisters, columnTypes, dataViewKinds, defaultParams, deriveBaseDatatype, deriveDbDatatype, expandTrait, expandTraits, fieldTypeCds, fieldTypes, fieldTypesByColumnType, flagDefs, getColumnType, getFieldType, getIdentityKeys, getLinks, getPrimaryKeys, getTrait, isFieldTypeCd, isNumericDatatype, isPlainColumnRef, isRegisterKind, registerCarrier, registerColumnHost, registerColumnSlot, registerDimensions, registerLineMode, registerRefusedProps, registerRequiredProps, registerResourceShape, registerResources, settingDefsOf, traitFieldConflicts, traits, vizTypes };
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var fieldTypes = [
20
20
  { cd: "number", name: "Number", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
21
21
  { cd: "currency", name: "Currency", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: 2, useStringInFilter: false },
22
22
  { cd: "percent", name: "Percent", columnType: "D", baseDatatypeCd: "number", supportedAgg: "CAM", maxLen: null, precision: 2, useStringInFilter: false },
23
+ { cd: "filesize", name: "File Size", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
23
24
  { cd: "checkbox", name: "Checkbox", columnType: "D", baseDatatypeCd: "bool", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
24
25
  { cd: "date", name: "Date", columnType: "D", baseDatatypeCd: "date", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
25
26
  { cd: "datetime", name: "Date & Time", columnType: "D", baseDatatypeCd: "timestamp", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
@@ -256,7 +257,8 @@ var NUMERIC_TOTAL = 18;
256
257
  var TEXT_BACKED = /* @__PURE__ */ new Set(["textarea", "markdown", "richtext", "code"]);
257
258
  var DB_DATATYPE_OVERRIDE = {
258
259
  file: "jsonb",
259
- image: "jsonb"
260
+ image: "jsonb",
261
+ filesize: "int8"
260
262
  };
261
263
  function deriveDbDatatype(fieldTypeCd, options = {}) {
262
264
  const ft = getFieldType(fieldTypeCd);
@@ -467,7 +469,8 @@ var ACCOUNTING_PROPERTIES = [
467
469
  "creditAccount",
468
470
  "debitAnalyticFields",
469
471
  "creditAnalyticFields",
470
- "sideDimensions"
472
+ "sideDimensions",
473
+ "importedKeyField"
471
474
  ];
472
475
  var BALANCE_PROPERTIES = ["allowNegative", "autoCreate"];
473
476
  var REFUSED = {
@@ -491,8 +494,79 @@ var KIND_LABEL = {
491
494
  accounting: { sentence: "An accounting register", inline: "an accounting register" }
492
495
  };
493
496
 
497
+ // src/registers/columns.ts
498
+ var SUMMED = ["balance", "turnover", "accounting"];
499
+ var slot = (path, on, what, summed = false) => ({
500
+ path: path.split("."),
501
+ on,
502
+ what,
503
+ ...summed ? { numericFor: SUMMED } : {}
504
+ });
505
+ var REGISTER_COLUMN_SLOTS = [
506
+ slot("journalDirection", "journal", "Journal direction column"),
507
+ slot("journalDate", "journal", "Journal date column"),
508
+ slot("period", "entity", "Period column"),
509
+ slot("recorder", "entity", "Recorder column"),
510
+ slot("dimensions.*", "carrier", "Dimension"),
511
+ slot("resources.*.field", "carrier", "Resource '{0}' column", true),
512
+ slot("resources.*.debitField", "carrier", "Resource '{0}' column", true),
513
+ slot("resources.*.creditField", "carrier", "Resource '{0}' column", true),
514
+ slot("account", "journal", "Account column"),
515
+ slot("debitAccount", "journal", "Debit account column"),
516
+ slot("creditAccount", "journal", "Credit account column"),
517
+ slot("analyticFields.*", "journal", "Analytic slot column"),
518
+ slot("debitAnalyticFields.*", "journal", "Analytic slot column"),
519
+ slot("creditAnalyticFields.*", "journal", "Analytic slot column"),
520
+ slot("sideDimensions.*.debitField", "journal", "Dimension '{0}' debit column"),
521
+ slot("sideDimensions.*.creditField", "journal", "Dimension '{0}' credit column"),
522
+ slot("composition.account", "composition", "Composition 'account' column"),
523
+ slot("composition.slot", "composition", "Composition 'slot' column"),
524
+ slot("composition.analytic", "composition", "Composition 'analytic' column"),
525
+ slot("totals.periodField", "totals", "Totals period column"),
526
+ slot("totals.accountField", "totals", "Totals account column"),
527
+ slot("totals.resources.*.inField", "totals", "Totals '{0}' inField", true),
528
+ slot("totals.resources.*.outField", "totals", "Totals '{0}' outField", true),
529
+ slot("totals.resources.*.debitField", "totals", "Totals '{0}' debitField", true),
530
+ slot("totals.resources.*.creditField", "totals", "Totals '{0}' creditField", true)
531
+ ];
532
+ function registerColumnSlot(path, kind) {
533
+ for (const candidate of REGISTER_COLUMN_SLOTS) {
534
+ if (candidate.path.length !== path.length) {
535
+ continue;
536
+ }
537
+ const captured = [];
538
+ const matches = candidate.path.every((segment, i) => {
539
+ if (segment !== "*") {
540
+ return segment === path[i];
541
+ }
542
+ captured.push(String(path[i]));
543
+ return true;
544
+ });
545
+ if (matches) {
546
+ return { on: candidate.on, what: candidate.what.replace(/\{(\d+)\}/g, (_, n) => captured[Number(n)] ?? ""), numeric: !!kind && !!candidate.numericFor?.includes(kind) };
547
+ }
548
+ }
549
+ return void 0;
550
+ }
551
+ function registerColumnHost(def, on) {
552
+ switch (on) {
553
+ case "entity":
554
+ return safeTrim(def.entity);
555
+ case "journal":
556
+ return safeTrim(def.journal);
557
+ case "carrier":
558
+ return registerCarrier(def);
559
+ case "totals":
560
+ return isRecord(def.totals) ? safeTrim(def.totals.entity) : void 0;
561
+ case "composition":
562
+ return isRecord(def.composition) ? safeTrim(def.composition.entity) : void 0;
563
+ }
564
+ }
565
+ var NUMERIC_DATATYPE = /^(?:(?:numeric|decimal)(?:\s*\(\s*\d+(?:\s*,\s*\d+)?\s*\))?|int[248]?|integer|bigint|smallint|(?:big|small)?serial[248]?|float[48]?|double precision|real|money)$/i;
566
+ var isNumericDatatype = (datatype) => NUMERIC_DATATYPE.test(datatype.trim());
567
+
494
568
  // src/registers/entities.ts
495
- function createEntityChecks(lookup, error, warning) {
569
+ function createEntityChecks(lookup, error, warning, hosts, kind) {
496
570
  const resolved = /* @__PURE__ */ new Map();
497
571
  const columnSets = /* @__PURE__ */ new WeakMap();
498
572
  const entityAt = (path, code, what) => {
@@ -517,17 +591,27 @@ function createEntityChecks(lookup, error, warning) {
517
591
  }
518
592
  return columnSets.get(entity) ?? null;
519
593
  };
520
- const columnAt = (path, entity, entityCode, column, what) => {
594
+ const columnAt = (path, column) => {
521
595
  if (typeof column !== "string" || !column.trim()) {
522
596
  return;
523
597
  }
598
+ const slot2 = registerColumnSlot(path, kind);
599
+ if (!slot2) {
600
+ return;
601
+ }
524
602
  if (!REGISTER_COLUMN_NAME.test(column)) {
525
- error(path, "column-name", `${what} '${column}' isn't a column name (lowercase letters, digits and '_').`);
603
+ error(path, "column-name", `${slot2.what} '${column}' isn't a column name (lowercase letters, digits and '_').`);
526
604
  return;
527
605
  }
528
- const columns = columnsOf(entity);
606
+ const host = hosts[slot2.on];
607
+ const columns = columnsOf(host?.entity);
529
608
  if (columns && !columns.has(column)) {
530
- warning(path, "unknown-column", `${what} '${column}' isn't a stored column of ${entityCode}.`);
609
+ warning(path, "unknown-column", `${slot2.what} '${column}' isn't a stored column of ${host?.code ? `'${host.code}'` : `the ${slot2.on} entity`}.`);
610
+ return;
611
+ }
612
+ const datatype = host?.entity?.datatypes?.get(column);
613
+ if (slot2.numeric && datatype && !isNumericDatatype(datatype)) {
614
+ warning(path, "column-not-numeric", `${slot2.what} '${column}' is ${datatype}, not a number.`);
531
615
  }
532
616
  };
533
617
  return { entityAt, columnAt };
@@ -574,7 +658,7 @@ function checkProperties({ def, kind, error }) {
574
658
  error(["recordChangesOnly"], "record-changes-only", "'recordChangesOnly' needs a 'recorder' \u2014 only a document's record can be compared with the slice in effect.");
575
659
  }
576
660
  }
577
- function resolveEntities(def, kind, checks) {
661
+ function resolveEntities(def, kind, checks, hosts) {
578
662
  const { entityAt, columnAt } = checks;
579
663
  const entityCode = safeTrim(def.entity);
580
664
  const journalCode = safeTrim(def.journal);
@@ -584,13 +668,17 @@ function resolveEntities(def, kind, checks) {
584
668
  if (kind === "accounting") {
585
669
  entityAt(["chart"], safeTrim(def.chart), "Chart");
586
670
  }
587
- columnAt(["journalDirection"], journal, journalCode, def.journalDirection, "Journal direction column");
588
- columnAt(["journalDate"], journal, journalCode, def.journalDate, "Journal date column");
589
- columnAt(["period"], entity, entityCode, def.period, "Period column");
590
- columnAt(["recorder"], entity, entityCode, def.recorder, "Recorder column");
591
- return { journalCode, journal, carrierCode: registerCarrier(def), carrier: carriedByEntity ? entity : journal };
671
+ const carrier = carriedByEntity ? entity : journal;
672
+ hosts.entity = { entity, code: entityCode };
673
+ hosts.journal = { entity: journal, code: journalCode };
674
+ hosts.carrier = { entity: carrier, code: registerCarrier(def) };
675
+ columnAt(["journalDirection"], def.journalDirection);
676
+ columnAt(["journalDate"], def.journalDate);
677
+ columnAt(["period"], def.period);
678
+ columnAt(["recorder"], def.recorder);
679
+ return { journalCode };
592
680
  }
593
- function checkDimensions({ def, dimensions, sides, carrier, carrierCode, columnAt, error }) {
681
+ function checkDimensions({ def, dimensions, sides, columnAt, error }) {
594
682
  if (!Array.isArray(def.dimensions) || dimensions.length === 0) {
595
683
  error(["dimensions"], "dimensions", "A register needs at least one dimension.");
596
684
  }
@@ -603,7 +691,7 @@ function checkDimensions({ def, dimensions, sides, carrier, carrierCode, columnA
603
691
  }
604
692
  dimensions.forEach((name, i) => {
605
693
  if (!(name in sides)) {
606
- columnAt(["dimensions", i], carrier, carrierCode, name, "Dimension");
694
+ columnAt(["dimensions", i], name);
607
695
  }
608
696
  });
609
697
  }
@@ -633,7 +721,7 @@ function checkResourceShape(ctx, name, resource, path) {
633
721
  }
634
722
  }
635
723
  function checkResources(ctx) {
636
- const { def, kind, resources, dimensions, carrier, carrierCode, columnAt, error } = ctx;
724
+ const { def, kind, resources, dimensions, columnAt, error } = ctx;
637
725
  if (!isRecord(def.resources) || resources.length === 0) {
638
726
  error(["resources"], "resources", "A register needs at least one resource.");
639
727
  }
@@ -653,7 +741,7 @@ function checkResources(ctx) {
653
741
  if (!column) {
654
742
  continue;
655
743
  }
656
- columnAt(path(key), carrier, carrierCode, column, `Resource '${name}' column`);
744
+ columnAt(path(key), column);
657
745
  const other = storage.get(column);
658
746
  if (other && other !== name) {
659
747
  error(path(key), "resource-column-shared", `Resources '${other}' and '${name}' both store into '${column}' \u2014 posting would add both into one column.`);
@@ -670,10 +758,10 @@ function checkResources(ctx) {
670
758
  }
671
759
  }
672
760
  function checkAnalyticSlotColumns(ctx, key, columns) {
673
- columns.forEach((column, i) => ctx.columnAt([key, i], ctx.journal, ctx.journalCode, column, "Analytic slot column"));
761
+ columns.forEach((column, i) => ctx.columnAt([key, i], column));
674
762
  }
675
763
  function checkSideDimensions(ctx) {
676
- const { dimensions, sides, journal, journalCode, columnAt, error } = ctx;
764
+ const { dimensions, sides, columnAt, error } = ctx;
677
765
  for (const [name, side] of Object.entries(sides)) {
678
766
  const base = ["sideDimensions", name];
679
767
  if (!dimensions.includes(name)) {
@@ -688,12 +776,12 @@ function checkSideDimensions(ctx) {
688
776
  same: [...base, "creditField"],
689
777
  sameMessage: `Two-sided dimension '${name}': the two columns must differ.`
690
778
  });
691
- columnAt([...base, "debitField"], journal, journalCode, debitField, `Dimension '${name}' debit column`);
692
- columnAt([...base, "creditField"], journal, journalCode, creditField, `Dimension '${name}' credit column`);
779
+ columnAt([...base, "debitField"], debitField);
780
+ columnAt([...base, "creditField"], creditField);
693
781
  }
694
782
  }
695
783
  function checkCompoundAccounts(ctx) {
696
- const { def, journal, journalCode, columnAt, error } = ctx;
784
+ const { def, columnAt, error } = ctx;
697
785
  refuseKeys(def, ["account", "analyticFields"], error, "line-mode", (key) => `'${key}' belongs to a classic register; a compound one names 'debitAccount' and 'creditAccount'.`);
698
786
  const debit = safeTrim(def.debitAccount);
699
787
  const credit = safeTrim(def.creditAccount);
@@ -703,8 +791,8 @@ function checkCompoundAccounts(ctx) {
703
791
  same: ["creditAccount"],
704
792
  sameMessage: "'debitAccount' and 'creditAccount' must differ \u2014 one column would record no correspondence."
705
793
  });
706
- columnAt(["debitAccount"], journal, journalCode, debit, "Debit account column");
707
- columnAt(["creditAccount"], journal, journalCode, credit, "Credit account column");
794
+ columnAt(["debitAccount"], debit);
795
+ columnAt(["creditAccount"], credit);
708
796
  const debitSlots = stringList(def.debitAnalyticFields);
709
797
  const creditSlots = stringList(def.creditAnalyticFields);
710
798
  if (debitSlots.length > MAX_ANALYTIC_SLOTS || creditSlots.length > MAX_ANALYTIC_SLOTS) {
@@ -721,12 +809,12 @@ function checkCompoundAccounts(ctx) {
721
809
  return needsComposition;
722
810
  }
723
811
  function checkClassicAccounts(ctx) {
724
- const { def, journal, journalCode, columnAt, error } = ctx;
812
+ const { def, columnAt, error } = ctx;
725
813
  refuseKeys(def, ["debitAccount", "creditAccount", "debitAnalyticFields", "creditAnalyticFields", "sideDimensions"], error, "line-mode", (key) => `'${key}' belongs to a compound register.`);
726
814
  if (!safeTrim(def.account)) {
727
815
  error(["account"], "accounts", "A classic register names the posting's account column ('account').");
728
816
  }
729
- columnAt(["account"], journal, journalCode, safeTrim(def.account), "Account column");
817
+ columnAt(["account"], safeTrim(def.account));
730
818
  const slots = stringList(def.analyticFields);
731
819
  if (slots.length > MAX_ANALYTIC_SLOTS) {
732
820
  error(["analyticFields"], "analytic-slots", `At most ${MAX_ANALYTIC_SLOTS} analytic slots.`);
@@ -735,7 +823,7 @@ function checkClassicAccounts(ctx) {
735
823
  return slots.length > 0;
736
824
  }
737
825
  function checkComposition(ctx, needsComposition) {
738
- const { def, entityAt, columnAt, error } = ctx;
826
+ const { def, entityAt, columnAt, hosts, error } = ctx;
739
827
  const composition = isRecord(def.composition) ? def.composition : void 0;
740
828
  if (needsComposition && !composition) {
741
829
  error(["composition"], "composition", "Analytic slots need a 'composition' \u2014 where the chart says which analytic each account takes in which slot.");
@@ -752,9 +840,9 @@ function checkComposition(ctx, needsComposition) {
752
840
  }
753
841
  }
754
842
  const code = safeTrim(composition.entity);
755
- const entity = entityAt(["composition", "entity"], code, "Composition entity");
843
+ hosts.composition = { entity: entityAt(["composition", "entity"], code, "Composition entity"), code };
756
844
  for (const key of ["account", "slot", "analytic"]) {
757
- columnAt(["composition", key], entity, code, composition[key], `Composition '${key}' column`);
845
+ columnAt(["composition", key], composition[key]);
758
846
  }
759
847
  }
760
848
  function checkAccounting(ctx) {
@@ -769,7 +857,7 @@ function checkAccounting(ctx) {
769
857
  checkComposition(ctx, mode === "compound" ? checkCompoundAccounts(ctx) : checkClassicAccounts(ctx));
770
858
  }
771
859
  function checkTotals(ctx) {
772
- const { def, kind, dimensions, resources, entityAt, columnAt, error } = ctx;
860
+ const { def, kind, dimensions, resources, entityAt, columnAt, hosts, error } = ctx;
773
861
  const totals = isRecord(def.totals) ? def.totals : void 0;
774
862
  if (!totals || kind === "info") {
775
863
  return;
@@ -778,7 +866,7 @@ function checkTotals(ctx) {
778
866
  if (!totalsCode) {
779
867
  error(["totals", "entity"], "totals", "The totals cache needs its entity.");
780
868
  }
781
- const totalsEntity = entityAt(["totals", "entity"], totalsCode, "Totals entity");
869
+ hosts.totals = { entity: entityAt(["totals", "entity"], totalsCode, "Totals entity"), code: totalsCode };
782
870
  const period = safeTrim(totals.period);
783
871
  if (!period || !REGISTER_TOTALS_PERIODS.includes(period)) {
784
872
  error(["totals", "period"], "totals", "The totals cache needs a period: day, month, quarter or year.");
@@ -786,12 +874,12 @@ function checkTotals(ctx) {
786
874
  if (!safeTrim(totals.periodField)) {
787
875
  error(["totals", "periodField"], "totals", "The totals cache needs 'periodField' \u2014 the column holding each period's first date.");
788
876
  }
789
- columnAt(["totals", "periodField"], totalsEntity, totalsCode, safeTrim(totals.periodField), "Totals period column");
877
+ columnAt(["totals", "periodField"], safeTrim(totals.periodField));
790
878
  if (kind === "accounting") {
791
879
  if (!safeTrim(totals.accountField)) {
792
880
  error(["totals", "accountField"], "totals", "An accounting register's cache needs 'accountField'.");
793
881
  }
794
- columnAt(["totals", "accountField"], totalsEntity, totalsCode, safeTrim(totals.accountField), "Totals account column");
882
+ columnAt(["totals", "accountField"], safeTrim(totals.accountField));
795
883
  stringList(totals.dimensions).forEach((name, i) => {
796
884
  if (!dimensions.includes(name)) {
797
885
  error(["totals", "dimensions", i], "totals", `Totals dimension '${name}' isn't a dimension of the register.`);
@@ -817,7 +905,7 @@ function checkTotals(ctx) {
817
905
  if (!safeTrim(entry[key])) {
818
906
  error(["totals", "resources", name], "totals", `Totals resource '${name}' needs '${key}'.`);
819
907
  }
820
- columnAt(["totals", "resources", name, key], totalsEntity, totalsCode, safeTrim(entry[key]), `Totals '${name}' ${key}`);
908
+ columnAt(["totals", "resources", name, key], safeTrim(entry[key]));
821
909
  }
822
910
  }
823
911
  for (const name of Object.keys(cached)) {
@@ -853,14 +941,16 @@ function checkRegister(raw, lookup) {
853
941
  }
854
942
  const kind = def.kind;
855
943
  checkProperties({ def, kind, error });
856
- const checks = createEntityChecks(lookup, error, warning);
944
+ const hosts = {};
945
+ const checks = createEntityChecks(lookup, error, warning, hosts, kind);
857
946
  const ctx = {
858
947
  def,
859
948
  kind,
860
949
  error,
861
950
  warning,
951
+ hosts,
862
952
  ...checks,
863
- ...resolveEntities(def, kind, checks),
953
+ ...resolveEntities(def, kind, checks, hosts),
864
954
  dimensions: registerDimensions(def),
865
955
  sides: isRecord(def.sideDimensions) ? def.sideDimensions : {},
866
956
  resources: registerResources(def)
@@ -1114,6 +1204,7 @@ export {
1114
1204
  NUMERIC_TOTAL,
1115
1205
  REGISTER_CODE,
1116
1206
  REGISTER_COLUMN_NAME,
1207
+ REGISTER_COLUMN_SLOTS,
1117
1208
  REGISTER_KINDS,
1118
1209
  REGISTER_TOTALS_PERIODS,
1119
1210
  baseDatatypes,
@@ -1140,9 +1231,12 @@ export {
1140
1231
  getPrimaryKeys,
1141
1232
  getTrait,
1142
1233
  isFieldTypeCd,
1234
+ isNumericDatatype,
1143
1235
  isPlainColumnRef,
1144
1236
  isRegisterKind,
1145
1237
  registerCarrier,
1238
+ registerColumnHost,
1239
+ registerColumnSlot,
1146
1240
  registerDimensions,
1147
1241
  registerLineMode,
1148
1242
  registerRefusedProps,