@rljson/rljson 0.0.15 → 0.0.17

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.
@@ -0,0 +1,31 @@
1
+ import { Json } from '@rljson/json';
2
+ import { RljsonTable } from '../rljson.ts';
3
+ import { ItemId, Ref, TableName } from '../typedefs.ts';
4
+ /**
5
+ * A buffet id is a name or id of a buffet
6
+ */
7
+ export type BuffetId = ItemId;
8
+ /**
9
+ * A buffet is a collection of arbitrary but related items, e.g. cakes,
10
+ * collections, or items.
11
+ */
12
+ export interface Buffet extends Json {
13
+ /**
14
+ * The items of the buffet
15
+ */
16
+ items: Array<{
17
+ /**
18
+ * The table the item is taken from
19
+ */
20
+ table: TableName;
21
+ /**
22
+ * The hash of the item in the able
23
+ */
24
+ ref: Ref;
25
+ }>;
26
+ }
27
+ /**
28
+ * A table containing buffets
29
+ */
30
+ export type BuffetsTable = RljsonTable<Buffet, 'buffets'>;
31
+ export declare const exampleBuffetsTable: () => BuffetsTable;
@@ -0,0 +1,40 @@
1
+ import { Json } from '@rljson/json';
2
+ import { RljsonTable } from '../rljson.ts';
3
+ import { ItemId, TableName } from '../typedefs.ts';
4
+ import { CollectionRef } from './collection.ts';
5
+ import { IdSetRef } from './id-set.ts';
6
+ /**
7
+ * A `CakeLayerId` assigns an id or name to a cake layer
8
+ */
9
+ export type CakeLayerId = ItemId;
10
+ /**
11
+ * A cake is a collection of layers.
12
+ *
13
+ * A layer is a collection of items.
14
+ * All layers of a cake refer to the same items.
15
+ */
16
+ export interface Cake extends Json {
17
+ /**
18
+ * The item ids of the collection. If present, the item ids in the layers
19
+ * must match these ids. The item id sets can be found in the _idSets table.
20
+ */
21
+ idSet?: IdSetRef;
22
+ /**
23
+ * The table containing the item collections defining the layers
24
+ */
25
+ collections: TableName;
26
+ /**
27
+ * Assigns a collection to each layer of the cake.
28
+ */
29
+ layers: {
30
+ [layerId: CakeLayerId]: CollectionRef;
31
+ };
32
+ }
33
+ /**
34
+ * A table containing cakes
35
+ */
36
+ export type CakesTable = RljsonTable<Cake, 'cakes'>;
37
+ /**
38
+ * Provides an example collectionsTable for test purposes
39
+ */
40
+ export declare const exampleCakesTable: () => CakesTable;
@@ -0,0 +1,40 @@
1
+ import { Json } from '@rljson/json';
2
+ import { RljsonTable } from '../rljson.ts';
3
+ import { ItemId, Ref, TableName } from '../typedefs.ts';
4
+ import { IdSetRef } from './id-set.ts';
5
+ import { PropertiesRef } from './properties.ts';
6
+ /**
7
+ * A CollectionRef is a hash pointing to a collection
8
+ */
9
+ export type CollectionRef = Ref;
10
+ /**
11
+ * A collection assigns properties to item ids
12
+ */
13
+ export interface Collection extends Json {
14
+ /**
15
+ * `base` an optional base collection that is extended by this collection
16
+ */
17
+ base?: CollectionRef;
18
+ /**
19
+ * The item ids of the collection. If presnet, the item ids in `assign`
20
+ * must match these ids. The item id sets can be found in the _idSets table.
21
+ */
22
+ idSet?: IdSetRef;
23
+ /**
24
+ * The table containing the properties that are assigned to the items
25
+ * with the assign property below
26
+ */
27
+ properties: TableName;
28
+ /**
29
+ * Assign properties to each item of the collection.
30
+ */
31
+ assign: Record<ItemId, PropertiesRef>;
32
+ }
33
+ /**
34
+ * A table containing collections
35
+ */
36
+ export type CollectionsTable = RljsonTable<Collection, 'collections'>;
37
+ /**
38
+ * Provides an example collectionsTable for test purposes
39
+ */
40
+ export declare const exampleCollectionsTable: () => CollectionsTable;
@@ -0,0 +1,33 @@
1
+ import { Json } from '@rljson/json';
2
+ import { RljsonTable } from '../rljson.ts';
3
+ import { ItemId, Ref } from '../typedefs.ts';
4
+ /**
5
+ * An IdSetRef is a hash pointing to an Ids
6
+ */
7
+ export type IdSetRef = Ref;
8
+ /**
9
+ * An Ids manages list of item ids
10
+ */
11
+ export interface IdSet extends Json {
12
+ /**
13
+ * The hash of another item id list which is extended by this one.
14
+ * Must be empty or null, when the list is the root.
15
+ */
16
+ base?: IdSetRef;
17
+ /**
18
+ * The item ids added to base
19
+ */
20
+ add: ItemId[];
21
+ /**
22
+ * The item ids removed from base
23
+ */
24
+ remove?: ItemId[];
25
+ }
26
+ /**
27
+ * A table containing item ids
28
+ */
29
+ export type IdSetsTable = RljsonTable<IdSet, 'idSets'>;
30
+ /**
31
+ * Returns one of the layers of the example cake
32
+ */
33
+ export declare const exampleIdSetsTable: () => IdSetsTable;
@@ -0,0 +1,16 @@
1
+ import { Json } from '@rljson/json';
2
+ import { NutritiveValues } from '../example/bakery-example.ts';
3
+ import { RljsonTable } from '../rljson.ts';
4
+ import { Ref } from '../typedefs.ts';
5
+ /**
6
+ * A reference to a properties row in a properties table
7
+ */
8
+ export type PropertiesRef = Ref;
9
+ /**
10
+ * A table containing item properties
11
+ */
12
+ export type PropertiesTable<T extends Json> = RljsonTable<T, 'properties'>;
13
+ /**
14
+ * Provides an example collectionsTable for test purposes
15
+ */
16
+ export declare const examplePropertiesTable: () => PropertiesTable<NutritiveValues>;
@@ -0,0 +1,48 @@
1
+ import { Json, JsonKey, JsonValueType } from '@rljson/json';
2
+ import { RljsonTable } from '../rljson.ts';
3
+ import { Ref } from '../typedefs.ts';
4
+ /**
5
+ * A ColumnsRef is a hash pointing to columns metadata
6
+ */
7
+ export type TableCfgRef = Ref;
8
+ /**
9
+ * A column configuration
10
+ */
11
+ export interface ColumnCfg extends Json {
12
+ /**
13
+ * The jsonKey of the column used in data
14
+ */
15
+ jsonKey: JsonKey;
16
+ /**
17
+ * A short name of the column to be shown in the table header
18
+ */
19
+ nameShort: string;
20
+ /**
21
+ * A unshorted name to be shown in the tool tip
22
+ */
23
+ name: string;
24
+ /**
25
+ * Average number of characters in the column
26
+ */
27
+ avgChars?: number;
28
+ /**
29
+ * The type of the column
30
+ */
31
+ type: JsonValueType;
32
+ }
33
+ /**
34
+ * Describes the configuration of a table, i.e. table metadata and columns
35
+ */
36
+ export interface TableCfg extends Json {
37
+ name: string;
38
+ jsonKey: JsonKey;
39
+ columns: Record<JsonKey, ColumnCfg>;
40
+ }
41
+ /**
42
+ * A table containing columns
43
+ */
44
+ export type TablesCfgTable = RljsonTable<TableCfg, 'properties'>;
45
+ /**
46
+ * Example matching allTypesRow
47
+ */
48
+ export declare const exampleTableCfgTable: () => TablesCfgTable;
@@ -0,0 +1,35 @@
1
+ import { Json } from '@rljson/json';
2
+ import { BuffetsTable } from '../content/buffet.ts';
3
+ import { CakesTable } from '../content/cake.ts';
4
+ import { CollectionsTable } from '../content/collection.ts';
5
+ import { IdSetsTable } from '../content/id-set.ts';
6
+ import { PropertiesTable } from '../content/properties.ts';
7
+ import { Rljson } from '../rljson.ts';
8
+ import { Ref } from '../typedefs.ts';
9
+ export interface Ingredient extends Json {
10
+ name: string;
11
+ amountUnit: 'g' | 'ml';
12
+ nutritiveValuesRef: Ref;
13
+ }
14
+ export interface RecipeIngredient extends Json {
15
+ ingredientsRef: string;
16
+ quantity: number;
17
+ }
18
+ export type IngredientsTypeTable = PropertiesTable<Ingredient>;
19
+ export interface NutritiveValues extends Json {
20
+ energy: number;
21
+ fat: number;
22
+ protein: number;
23
+ carbohydrates: number;
24
+ }
25
+ export interface Bakery extends Rljson {
26
+ buffets: BuffetsTable;
27
+ cakes: CakesTable;
28
+ slices: IdSetsTable;
29
+ layers: CollectionsTable;
30
+ recipes: CollectionsTable;
31
+ recipeIngredients: PropertiesTable<RecipeIngredient>;
32
+ ingredients: PropertiesTable<Ingredient>;
33
+ nutritiveValues: PropertiesTable<NutritiveValues>;
34
+ }
35
+ export declare const bakeryExample: () => Bakery;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,11 @@ export * from './content/cake.ts';
3
3
  export * from './content/collection.ts';
4
4
  export * from './content/id-set.ts';
5
5
  export * from './content/properties.ts';
6
+ export * from './content/table-cfg.ts';
6
7
  export * from './example.ts';
8
+ export * from './example/bakery-example.ts';
7
9
  export * from './rljson-indexed.ts';
8
10
  export * from './rljson.ts';
9
11
  export * from './typedefs.ts';
12
+ export * from './validate/base-validator.ts';
13
+ export * from './validate/validate.ts';
package/dist/rljson.js CHANGED
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { hip, hsh } from "@rljson/hash";
5
- import { exampleJsonObject } from "@rljson/json";
5
+ import { exampleJsonObject, jsonValueTypes, jsonValueMatchesType } from "@rljson/json";
6
6
  // @license
7
7
  const bakeryExample = () => {
8
8
  const result = {
@@ -483,6 +483,8 @@ __publicField(_Example, "broken", {
483
483
  });
484
484
  let Example = _Example;
485
485
  // @license
486
+ const exampleTableCfgTable = () => Example.ok.singleRow()._tableCfgs;
487
+ // @license
486
488
  const rljsonIndexed = (rljson) => {
487
489
  const result = {};
488
490
  for (const key in rljson) {
@@ -527,15 +529,676 @@ const exampleTypedefs = () => {
527
529
  contentType: "collections"
528
530
  };
529
531
  };
532
+ // @license
533
+ class BaseValidator {
534
+ constructor() {
535
+ __publicField(this, "name", "syntax");
536
+ }
537
+ async validate(rljson) {
538
+ return this.validateSync(rljson);
539
+ }
540
+ validateSync(rljson) {
541
+ return new _BaseValidator(rljson).validate();
542
+ }
543
+ static isValidFieldName(fieldName) {
544
+ return /^[a-z][a-zA-Z0-9]*$/.test(fieldName);
545
+ }
546
+ }
547
+ class _BaseValidator {
548
+ constructor(rljson) {
549
+ __publicField(this, "errors", { hasErrors: false });
550
+ // ######################
551
+ // Private
552
+ // ######################
553
+ __publicField(this, "tableNames");
554
+ __publicField(this, "rljsonIndexed");
555
+ this.rljson = rljson;
556
+ this.tableNames = Object.keys(this.rljson).filter(
557
+ (table) => !table.startsWith("_")
558
+ );
559
+ this.rljsonIndexed = rljsonIndexed(rljson);
560
+ }
561
+ get hasErrors() {
562
+ return Object.keys(this.errors).length > 1;
563
+ }
564
+ validate() {
565
+ const steps = [
566
+ // Base checks
567
+ () => this._writeAndValidHashes(),
568
+ () => this._tableNamesNotLowerCamelCase(),
569
+ () => this._tableNamesDoNotEndWithRef(),
570
+ () => this._columnNamesNotLowerCamelCase(),
571
+ () => this._dataNotFound(),
572
+ () => this._dataHasWrongType(),
573
+ // Check table cfg
574
+ () => this._tableCfgsReferencedTableNameNotFound(),
575
+ () => this._tableCfgsHaveWrongType(),
576
+ () => this._tableCfgNotFound(),
577
+ () => this._missingColumnConfigs(),
578
+ () => this._dataDoesNotMatchColumnConfig(),
579
+ // Check references
580
+ () => this._refsNotFound(),
581
+ // Check collections
582
+ () => this._collectionBasesNotFound(),
583
+ () => this._collectionIdSetsExist(),
584
+ () => this._collectionPropertyAssignmentsNotFound(),
585
+ // Check cakes
586
+ () => this._cakeIdSetsNotFound(),
587
+ () => this._cakeCollectionTablesNotFound(),
588
+ // Check buffets
589
+ () => this._buffetReferencedTableNotFound()
590
+ ];
591
+ for (const step of steps) {
592
+ step();
593
+ if (this.hasErrors) {
594
+ break;
595
+ }
596
+ }
597
+ this.errors.hasErrors = this.hasErrors;
598
+ return this.errors;
599
+ }
600
+ _tableNamesNotLowerCamelCase() {
601
+ const invalidTableNames = [];
602
+ for (const tableName of this.tableNames) {
603
+ if (!BaseValidator.isValidFieldName(tableName)) {
604
+ invalidTableNames.push(tableName);
605
+ }
606
+ }
607
+ if (invalidTableNames.length > 0) {
608
+ this.errors.tableNamesNotLowerCamelCase = {
609
+ error: "Table names must be lower camel case",
610
+ invalidTableNames
611
+ };
612
+ }
613
+ }
614
+ // ...........................................................................
615
+ _tableNamesDoNotEndWithRef() {
616
+ const invalidTableNames = [];
617
+ for (const tableName of this.tableNames) {
618
+ if (tableName.endsWith("Ref")) {
619
+ invalidTableNames.push(tableName);
620
+ }
621
+ }
622
+ if (invalidTableNames.length > 0) {
623
+ this.errors.tableNamesDoNotEndWithRef = {
624
+ error: 'Table names must not end with "Ref"',
625
+ invalidTableNames
626
+ };
627
+ }
628
+ }
629
+ // ...........................................................................
630
+ _columnNamesNotLowerCamelCase() {
631
+ const invalidColumnNames = {};
632
+ let hadErrors = false;
633
+ for (const tableName of this.tableNames) {
634
+ const table = this.rljson[tableName];
635
+ if (!table._data || !Array.isArray(table._data)) {
636
+ continue;
637
+ }
638
+ for (const row of table._data) {
639
+ for (const columnName in row) {
640
+ if (columnName.startsWith("_")) {
641
+ continue;
642
+ }
643
+ if (!BaseValidator.isValidFieldName(columnName)) {
644
+ invalidColumnNames[tableName] ?? (invalidColumnNames[tableName] = []);
645
+ invalidColumnNames[tableName].push(columnName);
646
+ hadErrors = true;
647
+ }
648
+ }
649
+ }
650
+ }
651
+ if (hadErrors) {
652
+ this.errors.columnNamesNotLowerCamelCase = {
653
+ error: "Column names must be lower camel case",
654
+ invalidColumnNames
655
+ };
656
+ }
657
+ }
658
+ // ...........................................................................
659
+ _writeAndValidHashes() {
660
+ try {
661
+ this.rljson = hsh(this.rljson, {
662
+ inPlace: false,
663
+ updateExistingHashes: true,
664
+ throwOnWrongHashes: true
665
+ });
666
+ } catch (error) {
667
+ if (error instanceof Error) {
668
+ this.errors.hashesNotValid = {
669
+ error: error.message
670
+ };
671
+ } else {
672
+ this.errors.hashesNotValid = {
673
+ error: "Unknown error"
674
+ };
675
+ }
676
+ }
677
+ }
678
+ // ...........................................................................
679
+ /// Checks if data is valid
680
+ _dataNotFound() {
681
+ const rljson = this.rljson;
682
+ const tablesWithMissingData = [];
683
+ for (const table of this.tableNames) {
684
+ const tableData = rljson[table];
685
+ const items = tableData["_data"];
686
+ if (items == null) {
687
+ tablesWithMissingData.push(table);
688
+ }
689
+ }
690
+ if (tablesWithMissingData.length > 0) {
691
+ this.errors.dataNotFound = {
692
+ error: "_data is missing in tables",
693
+ tables: tablesWithMissingData
694
+ };
695
+ }
696
+ }
697
+ // ...........................................................................
698
+ _tableCfgsReferencedTableNameNotFound() {
699
+ const tableCfgs = this.rljson._tableCfgs;
700
+ if (!tableCfgs) {
701
+ return;
702
+ }
703
+ const brokenCfgs = [];
704
+ for (const item of tableCfgs._data) {
705
+ const table = this.rljson[item.jsonKey];
706
+ if (!table) {
707
+ brokenCfgs.push({
708
+ brokenTableCfg: item._hash,
709
+ tableNameNotFound: item.jsonKey
710
+ });
711
+ }
712
+ }
713
+ if (brokenCfgs.length > 0) {
714
+ this.errors.tableCfgsReferencedTableNameNotFound = {
715
+ error: "Tables referenced in _tableCfgs not found",
716
+ brokenCfgs
717
+ };
718
+ }
719
+ }
720
+ // ...........................................................................
721
+ _tableCfgsHaveWrongType() {
722
+ const tableCfgs = this.rljson._tableCfgs;
723
+ if (!tableCfgs) {
724
+ return;
725
+ }
726
+ const brokenCfgs = [];
727
+ for (const item of tableCfgs._data) {
728
+ for (const columnKey in item.columns) {
729
+ if (columnKey.startsWith("_")) {
730
+ continue;
731
+ }
732
+ const column = item.columns[columnKey];
733
+ if (jsonValueTypes.indexOf(column.type) === -1) {
734
+ brokenCfgs.push({
735
+ brokenTableCfg: item._hash,
736
+ brokenColumnKey: columnKey,
737
+ brokenColumnType: column.type
738
+ });
739
+ }
740
+ }
741
+ }
742
+ if (brokenCfgs.length > 0) {
743
+ this.errors.tableCfgsHaveWrongTypes = {
744
+ error: "Some of the columns have invalid types. Valid types are: " + jsonValueTypes.join(", "),
745
+ brokenCfgs
746
+ };
747
+ }
748
+ }
749
+ // ...........................................................................
750
+ _tableCfgNotFound() {
751
+ const tableCfgs = this.rljsonIndexed._tableCfgs;
752
+ const tableCfgNotFound = [];
753
+ iterateTables(this.rljson, (tableName, table) => {
754
+ const tableCfgRef = table._tableCfg;
755
+ if (!tableCfgRef) {
756
+ return;
757
+ }
758
+ const tableCfgData = tableCfgs._data[tableCfgRef];
759
+ if (!tableCfgData) {
760
+ tableCfgNotFound.push({
761
+ tableWithBrokenTableCfgRef: tableName,
762
+ brokenTableCfgRef: tableCfgRef
763
+ });
764
+ return;
765
+ }
766
+ });
767
+ if (tableCfgNotFound.length > 0) {
768
+ this.errors.tableCfgReferencedNotFound = {
769
+ error: "Referenced table config not found",
770
+ tableCfgNotFound
771
+ };
772
+ }
773
+ }
774
+ // ...........................................................................
775
+ _missingColumnConfigs() {
776
+ const tableCfgs = this.rljsonIndexed._tableCfgs;
777
+ const missingColumnConfigs = [];
778
+ iterateTables(this.rljson, (tableName, table) => {
779
+ const tableCfgRef = table._tableCfg;
780
+ if (!tableCfgRef) {
781
+ return;
782
+ }
783
+ const tableCfgData = tableCfgs._data[tableCfgRef];
784
+ const processedColumnKeys = [];
785
+ for (const row of table._data) {
786
+ const columnKeys = Object.keys(row).filter(
787
+ (key) => !key.startsWith("_")
788
+ );
789
+ const newColumnKey = columnKeys.filter(
790
+ (key) => processedColumnKeys.indexOf(key) === -1
791
+ );
792
+ for (const columnKey of newColumnKey) {
793
+ if (!tableCfgData.columns[columnKey]) {
794
+ missingColumnConfigs.push({
795
+ tableCfg: tableCfgRef,
796
+ row: row._hash,
797
+ column: columnKey,
798
+ table: tableName
799
+ });
800
+ }
801
+ processedColumnKeys.push(columnKey);
802
+ }
803
+ }
804
+ });
805
+ if (missingColumnConfigs.length > 0) {
806
+ this.errors.columnConfigNotFound = {
807
+ error: "Column configurations not found",
808
+ missingColumnConfigs
809
+ };
810
+ }
811
+ }
812
+ // ...........................................................................
813
+ _dataDoesNotMatchColumnConfig() {
814
+ const tableCfgs = this.rljsonIndexed._tableCfgs;
815
+ const brokenValues = [];
816
+ iterateTables(this.rljson, (tableName, table) => {
817
+ const tableCfgRef = table._tableCfg;
818
+ if (!tableCfgRef) {
819
+ return;
820
+ }
821
+ const tableCfgData = tableCfgs._data[tableCfgRef];
822
+ for (const row of table._data) {
823
+ const columnKeys = Object.keys(row).filter(
824
+ (key) => !key.startsWith("_")
825
+ );
826
+ for (const columnKey of columnKeys) {
827
+ const columnConfig = tableCfgData.columns[columnKey];
828
+ const value = row[columnKey];
829
+ if (value == null || value == void 0) {
830
+ continue;
831
+ }
832
+ const typeShould = columnConfig.type;
833
+ if (!jsonValueMatchesType(value, typeShould)) {
834
+ brokenValues.push({
835
+ table: tableName,
836
+ row: row._hash,
837
+ column: columnKey,
838
+ tableCfg: tableCfgRef
839
+ });
840
+ }
841
+ }
842
+ }
843
+ });
844
+ if (brokenValues.length > 0) {
845
+ this.errors.dataDoesNotMatchColumnConfig = {
846
+ error: "Table values have wrong types",
847
+ brokenValues
848
+ };
849
+ }
850
+ }
851
+ // ...........................................................................
852
+ _dataHasWrongType() {
853
+ const rljson = this.rljson;
854
+ const tablesWithWrongType = [];
855
+ for (const tableName of this.tableNames) {
856
+ const tableData = rljson[tableName];
857
+ const items = tableData["_data"];
858
+ if (!Array.isArray(items)) {
859
+ tablesWithWrongType.push(tableName);
860
+ }
861
+ }
862
+ if (tablesWithWrongType.length > 0) {
863
+ this.errors.dataHasWrongType = {
864
+ error: "_data must be a list",
865
+ tables: tablesWithWrongType
866
+ };
867
+ }
868
+ }
869
+ _refsNotFound() {
870
+ const missingRefs = [];
871
+ iterateTables(this.rljson, (tableName, table) => {
872
+ const tableData = table._data;
873
+ for (const item of tableData) {
874
+ for (const key of Object.keys(item)) {
875
+ if (key.endsWith("Ref")) {
876
+ const targetItemHash = item[key];
877
+ const targetTableName = key.substring(0, key.length - 3);
878
+ const itemHash = item._hash;
879
+ if (this.tableNames.indexOf(targetTableName) === -1) {
880
+ missingRefs.push({
881
+ error: `Target table "${targetTableName}" not found.`,
882
+ sourceTable: tableName,
883
+ sourceKey: key,
884
+ sourceItemHash: itemHash,
885
+ targetItemHash,
886
+ targetTable: targetTableName
887
+ });
888
+ continue;
889
+ }
890
+ const targetTableIndexed = this.rljsonIndexed[targetTableName];
891
+ const referencedItem = targetTableIndexed._data[targetItemHash];
892
+ if (referencedItem === void 0) {
893
+ missingRefs.push({
894
+ sourceTable: tableName,
895
+ sourceItemHash: itemHash,
896
+ sourceKey: key,
897
+ targetItemHash,
898
+ targetTable: targetTableName,
899
+ error: `Table "${targetTableName}" has no item with hash "${targetItemHash}"`
900
+ });
901
+ }
902
+ }
903
+ }
904
+ }
905
+ });
906
+ if (missingRefs.length > 0) {
907
+ this.errors.refsNotFound = {
908
+ error: "Broken references",
909
+ missingRefs
910
+ };
911
+ }
912
+ }
913
+ _collectionBasesNotFound() {
914
+ const brokenCollections = [];
915
+ iterateTables(this.rljson, (tableName, table) => {
916
+ if (table._type !== "collections") {
917
+ return;
918
+ }
919
+ const collectionsIndexed = this.rljsonIndexed[tableName];
920
+ const collectionsTable = table;
921
+ for (const collection of collectionsTable._data) {
922
+ const baseRef = collection.base;
923
+ if (!baseRef) {
924
+ continue;
925
+ }
926
+ const baseCollection = collectionsIndexed._data[baseRef];
927
+ if (!baseCollection) {
928
+ brokenCollections.push({
929
+ collectionsTable: tableName,
930
+ brokenCollection: collection._hash,
931
+ missingBaseCollection: baseRef
932
+ });
933
+ }
934
+ }
935
+ });
936
+ if (brokenCollections.length > 0) {
937
+ this.errors.collectionBasesNotFound = {
938
+ error: "Base collections are missing",
939
+ brokenCollections
940
+ };
941
+ }
942
+ }
943
+ _collectionIdSetsExist() {
944
+ const brokenCollections = [];
945
+ iterateTables(this.rljson, (tableName, table) => {
946
+ if (table._type !== "collections") {
947
+ return;
948
+ }
949
+ const idSets = this.rljsonIndexed._idSets;
950
+ const collectionsTable = table;
951
+ for (const collection of collectionsTable._data) {
952
+ const idSetRef = collection.idSet;
953
+ if (!idSetRef) {
954
+ continue;
955
+ }
956
+ const idSet = idSets._data[idSetRef];
957
+ if (!idSet) {
958
+ brokenCollections.push({
959
+ collectionsTable: tableName,
960
+ collectionHash: collection._hash,
961
+ missingIdSet: idSetRef
962
+ });
963
+ }
964
+ }
965
+ });
966
+ if (brokenCollections.length > 0) {
967
+ this.errors.collectionIdSetsExist = {
968
+ error: "Id sets of collections are missing",
969
+ brokenCollections
970
+ };
971
+ }
972
+ }
973
+ _collectionPropertyAssignmentsNotFound() {
974
+ const missingPropertyTables = [];
975
+ const brokenAssignments = [];
976
+ iterateTables(this.rljson, (tableName, table) => {
977
+ if (table._type !== "collections") {
978
+ return;
979
+ }
980
+ const collectionsTable = table;
981
+ for (const collection of collectionsTable._data) {
982
+ const propertyTableName = collection.properties;
983
+ const propertiesTable = this.rljsonIndexed[propertyTableName];
984
+ if (!propertiesTable) {
985
+ missingPropertyTables.push({
986
+ brokenCollection: collection._hash,
987
+ collectionsTable: tableName,
988
+ missingPropertyTable: propertyTableName
989
+ });
990
+ continue;
991
+ }
992
+ const assignments = collection.assign;
993
+ for (const itemId in assignments) {
994
+ if (itemId.startsWith("_")) {
995
+ continue;
996
+ }
997
+ const propertyHash = assignments[itemId];
998
+ if (!propertiesTable._data[propertyHash]) {
999
+ brokenAssignments.push({
1000
+ collectionsTable: tableName,
1001
+ brokenCollection: collection._hash,
1002
+ referencedPropertyTable: propertyTableName,
1003
+ brokenAssignment: itemId,
1004
+ missingProperty: propertyHash
1005
+ });
1006
+ }
1007
+ }
1008
+ }
1009
+ });
1010
+ if (missingPropertyTables.length > 0) {
1011
+ this.errors.collectionPropertyTablesNotFound = {
1012
+ error: "Collection property tables do not exist",
1013
+ collections: missingPropertyTables
1014
+ };
1015
+ }
1016
+ if (brokenAssignments.length > 0) {
1017
+ this.errors.collectionPropertyAssignmentsNotFound = {
1018
+ error: "Collection property assignments are broken",
1019
+ brokenAssignments
1020
+ };
1021
+ }
1022
+ }
1023
+ _cakeIdSetsNotFound() {
1024
+ const brokenCakes = [];
1025
+ iterateTables(this.rljson, (tableName, table) => {
1026
+ if (table._type !== "cakes") {
1027
+ return;
1028
+ }
1029
+ const idSets = this.rljsonIndexed._idSets;
1030
+ const cakesTable = table;
1031
+ for (const cake of cakesTable._data) {
1032
+ const idSetRef = cake.idSet;
1033
+ if (!idSetRef) {
1034
+ continue;
1035
+ }
1036
+ const idSet = idSets._data[idSetRef];
1037
+ if (!idSet) {
1038
+ brokenCakes.push({
1039
+ cakeTable: tableName,
1040
+ brokenCake: cake._hash,
1041
+ missingIdSet: idSetRef
1042
+ });
1043
+ }
1044
+ }
1045
+ });
1046
+ if (brokenCakes.length > 0) {
1047
+ this.errors.cakeIdSetsNotFound = {
1048
+ error: "Id sets of cakes are missing",
1049
+ brokenCakes
1050
+ };
1051
+ }
1052
+ }
1053
+ _cakeCollectionTablesNotFound() {
1054
+ const missingCollectionTables = [];
1055
+ const missingLayerCollections = [];
1056
+ iterateTables(this.rljson, (tableName, table) => {
1057
+ if (table._type !== "cakes") {
1058
+ return;
1059
+ }
1060
+ const cakesTable = table;
1061
+ for (const cake of cakesTable._data) {
1062
+ const collectionsTableName = cake.collections;
1063
+ const collectionsTable = this.rljsonIndexed[collectionsTableName];
1064
+ if (!collectionsTable) {
1065
+ missingCollectionTables.push({
1066
+ cakeTable: tableName,
1067
+ brokenCake: cake._hash,
1068
+ missingCollectionsTable: collectionsTableName
1069
+ });
1070
+ continue;
1071
+ }
1072
+ for (const layer in cake.layers) {
1073
+ if (layer.startsWith("_")) {
1074
+ continue;
1075
+ }
1076
+ const collectionRef = cake.layers[layer];
1077
+ const collection = collectionsTable._data[collectionRef];
1078
+ if (!collection) {
1079
+ missingLayerCollections.push({
1080
+ cakeTable: tableName,
1081
+ brokenCake: cake._hash,
1082
+ brokenLayerName: layer,
1083
+ collectionsTable: collectionsTableName,
1084
+ missingCollection: collectionRef
1085
+ });
1086
+ }
1087
+ }
1088
+ }
1089
+ });
1090
+ if (missingCollectionTables.length > 0) {
1091
+ this.errors.cakeCollectionTablesNotFound = {
1092
+ error: "Collection tables of cakes are missing",
1093
+ brokenCakes: missingCollectionTables
1094
+ };
1095
+ }
1096
+ if (missingLayerCollections.length > 0) {
1097
+ this.errors.cakeLayerCollectionsNotFound = {
1098
+ error: "Layer collections of cakes are missing",
1099
+ brokenCakes: missingLayerCollections
1100
+ };
1101
+ }
1102
+ }
1103
+ _buffetReferencedTableNotFound() {
1104
+ const missingTables = [];
1105
+ const missingItems = [];
1106
+ iterateTables(this.rljson, (tableName, table) => {
1107
+ if (table._type !== "buffets") {
1108
+ return;
1109
+ }
1110
+ const buffetsTable = table;
1111
+ for (const buffet of buffetsTable._data) {
1112
+ for (const item of buffet.items) {
1113
+ const itemTableName = item.table;
1114
+ const itemTable = this.rljsonIndexed[itemTableName];
1115
+ if (!itemTable) {
1116
+ missingTables.push({
1117
+ buffetTable: tableName,
1118
+ brokenBuffet: buffet._hash,
1119
+ missingItemTable: itemTableName
1120
+ });
1121
+ continue;
1122
+ }
1123
+ const ref = item.ref;
1124
+ const referencedItem = itemTable._data[ref];
1125
+ if (!referencedItem) {
1126
+ missingItems.push({
1127
+ buffetTable: tableName,
1128
+ brokenBuffet: buffet._hash,
1129
+ itemTable: itemTableName,
1130
+ missingItem: ref
1131
+ });
1132
+ }
1133
+ }
1134
+ }
1135
+ });
1136
+ if (missingTables.length > 0) {
1137
+ this.errors.buffetReferencedTablesNotFound = {
1138
+ error: "Referenced tables of buffets are missing",
1139
+ brokenBuffets: missingTables
1140
+ };
1141
+ }
1142
+ if (missingItems.length > 0) {
1143
+ this.errors.buffetReferencedItemsNotFound = {
1144
+ error: "Referenced items of buffets are missing",
1145
+ brokenItems: missingItems
1146
+ };
1147
+ }
1148
+ }
1149
+ }
1150
+ const isValidFieldName = (fieldName) => BaseValidator.isValidFieldName(fieldName);
1151
+ // @license
1152
+ class Validate {
1153
+ constructor() {
1154
+ // ######################
1155
+ // Private
1156
+ // ######################
1157
+ __publicField(this, "_validators", []);
1158
+ }
1159
+ addValidator(validator) {
1160
+ this._validators.push(validator);
1161
+ }
1162
+ async run(rljson) {
1163
+ const result = await Promise.all(
1164
+ this._validators.map(async (validator) => {
1165
+ const errors = await validator.validate(rljson);
1166
+ const name = validator.name;
1167
+ return {
1168
+ [name]: errors
1169
+ };
1170
+ })
1171
+ );
1172
+ return result.reduce((acc, errors) => {
1173
+ let hasErrors = false;
1174
+ for (const key of Object.keys(errors)) {
1175
+ const e = Object.keys(errors[key]);
1176
+ if (e.length > 0) {
1177
+ hasErrors = true;
1178
+ break;
1179
+ }
1180
+ }
1181
+ if (hasErrors) {
1182
+ return { ...acc, ...errors };
1183
+ }
1184
+ return acc;
1185
+ }, {});
1186
+ }
1187
+ }
530
1188
  export {
1189
+ BaseValidator,
531
1190
  Example,
1191
+ Validate,
1192
+ bakeryExample,
532
1193
  exampleBuffetsTable,
533
1194
  exampleCakesTable,
534
1195
  exampleCollectionsTable,
535
1196
  exampleIdSetsTable,
536
1197
  examplePropertiesTable,
537
1198
  exampleRljson,
1199
+ exampleTableCfgTable,
538
1200
  exampleTypedefs,
1201
+ isValidFieldName,
539
1202
  iterateTables,
540
1203
  reservedFieldNames,
541
1204
  reservedTableNames,
@@ -0,0 +1,39 @@
1
+ import { Json } from '@rljson/json';
2
+ import { Rljson } from '../rljson.ts';
3
+ import { Errors, Validator } from './validate.ts';
4
+ export interface BaseErrors extends Errors {
5
+ tableNamesNotLowerCamelCase?: Json;
6
+ columnNamesNotLowerCamelCase?: Json;
7
+ tableNamesDoNotStartWithANumber?: Json;
8
+ tableNamesDoNotEndWithRef?: Json;
9
+ hashesNotValid?: Json;
10
+ dataNotFound?: Json;
11
+ dataHasWrongType?: Json;
12
+ tableCfgsReferencedTableNameNotFound?: Json;
13
+ tableCfgsHaveWrongTypes?: Json;
14
+ tableCfgReferencedNotFound?: Json;
15
+ columnConfigNotFound?: Json;
16
+ dataDoesNotMatchColumnConfig?: Json;
17
+ refsNotFound?: Json;
18
+ collectionBasesNotFound?: Json;
19
+ collectionIdSetsNotFound?: Json;
20
+ collectionPropertyTablesNotFound?: Json;
21
+ collectionPropertyAssignmentsNotFound?: Json;
22
+ cakeIdSetsNotFound?: Json;
23
+ cakeCollectionTablesNotFound?: Json;
24
+ cakeLayerCollectionsNotFound?: Json;
25
+ buffetReferencedTablesNotFound?: Json;
26
+ buffetReferencedItemsNotFound?: Json;
27
+ }
28
+ export declare class BaseValidator implements Validator {
29
+ name: string;
30
+ validate(rljson: Rljson): Promise<Errors>;
31
+ validateSync(rljson: Rljson): BaseErrors;
32
+ static isValidFieldName(fieldName: string): boolean;
33
+ }
34
+ /**
35
+ * Validates an field name
36
+ * @param fieldName - The field name to validate
37
+ * @returns true if the field name is valid, false otherwise
38
+ */
39
+ export declare const isValidFieldName: (fieldName: string) => boolean;
@@ -0,0 +1,19 @@
1
+ import { Json } from '@rljson/json';
2
+ import { Rljson } from '../rljson.ts';
3
+ export type Errors = Json & {
4
+ hasErrors: boolean;
5
+ };
6
+ export interface ValidationResult {
7
+ [group: string]: Errors;
8
+ }
9
+ export interface Validator {
10
+ name: string;
11
+ validate(rljson: Rljson): Promise<Errors>;
12
+ }
13
+ export declare class Validate {
14
+ addValidator(validator: Validator): void;
15
+ run(rljson: Rljson): Promise<{
16
+ [group: string]: Errors;
17
+ }>;
18
+ private _validators;
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/rljson",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "packageManager": "pnpm@10.6.3",
5
5
  "description": "The RLJSON data format specification",
6
6
  "homepage": "https://github.com/rljson/rljson",