@rljson/rljson 0.0.14 → 0.0.16

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/example.d.ts CHANGED
@@ -10,16 +10,21 @@ export declare class Example {
10
10
  complete: () => Rljson;
11
11
  };
12
12
  static readonly broken: {
13
- brokenTableName: () => {
14
- brok$en: {
15
- _type: string;
16
- _data: never[];
13
+ base: {
14
+ brokenTableName: () => {
15
+ brok$en: {
16
+ _type: string;
17
+ _data: never[];
18
+ };
17
19
  };
20
+ missingData: () => Rljson;
21
+ dataNotBeingAnArray: () => Rljson;
22
+ missingRef: () => Rljson;
23
+ missingReferencedTable: () => Rljson;
24
+ };
25
+ tableCfg: {
26
+ wrongType: () => import('@rljson/hash').Hashed<Rljson>;
18
27
  };
19
- missingData: () => Rljson;
20
- dataNotBeingAnArray: () => Rljson;
21
- missingRef: () => Rljson;
22
- missingReferencedTable: () => Rljson;
23
28
  collections: {
24
29
  missingBase: () => Rljson;
25
30
  missingIdSet: () => Rljson;
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.d.ts CHANGED
@@ -4,6 +4,7 @@ import { CakesTable } from './content/cake.ts';
4
4
  import { CollectionsTable } from './content/collection.ts';
5
5
  import { IdSetsTable } from './content/id-set.ts';
6
6
  import { PropertiesTable } from './content/properties.ts';
7
+ import { TableCfgRef, TablesCfgTable } from './content/table-cfg.ts';
7
8
  import { ContentType, Ref, TableName } from './typedefs.ts';
8
9
  export declare const reservedFieldNames: string[];
9
10
  export declare const reservedTableNames: string[];
@@ -19,15 +20,26 @@ export interface Rljson extends Json {
19
20
  * Rljson set with private fields
20
21
  */
21
22
  export type RljsonPrivate = {
23
+ /**
24
+ * The hash of the Rljson object
25
+ */
26
+ _hash: string;
22
27
  /**
23
28
  * Contains id sets used accross the Rljson object
24
29
  */
25
- _idSets: IdSetsTable;
30
+ _idSet?: IdSetsTable;
31
+ /**
32
+ * References that are not part of the Rljson object
33
+ */
34
+ _externalRefs?: Ref[];
35
+ /**
36
+ * Referenced tables that are not part of the Rljson object
37
+ */
38
+ _externalTables?: Ref[];
26
39
  /**
27
- * Used by validation. If external references are not present,
28
- * validation does not throw an error.
40
+ * Column configurations used accross the Rljson object
29
41
  */
30
- _externalRefs: Ref[];
42
+ _tableCfgs?: TablesCfgTable;
31
43
  };
32
44
  /** An example rljson object */
33
45
  export declare const exampleRljson: () => Rljson;
@@ -37,6 +49,8 @@ export interface RljsonTable<Data extends Json, Type extends ContentType> extend
37
49
  _data: Data[];
38
50
  /** The type of the table. If not set, the type is "properties" */
39
51
  _type: Type;
52
+ /** The columns configuration of the table */
53
+ _tableCfg?: TableCfgRef;
40
54
  }
41
55
  /**
42
56
  * Iterates over all tables of an Rljson object.
package/dist/rljson.js CHANGED
@@ -2,99 +2,92 @@ 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
- return {
9
- // Every rljson object has a list of id-sets other objects can refer to
8
+ const result = {
10
9
  _idSets: {
11
10
  _type: "idSets",
12
11
  _data: [
13
12
  {
14
13
  add: ["slice0", "slice1"],
15
- _hash: "KAwCRFD4mdx8b1bzxBb2_O"
14
+ _hash: "Ko990SJfgPJvNGxC63CRf7"
16
15
  }
17
- ]
16
+ ],
17
+ _hash: "NiojsJvZ7iEU7WeMWttzyJ"
18
18
  },
19
- // A bakery offers a variety of buffets containing different pastries
20
19
  buffets: {
21
20
  _type: "buffets",
22
21
  _data: [
23
22
  {
24
- items: [{ table: "cakes", ref: "fw8IrV05Z1ZekPXTdUdmt8" }]
23
+ items: [
24
+ {
25
+ table: "cakes",
26
+ ref: "KdLv3zTftqKKUeqYrTtO2r"
27
+ }
28
+ ]
25
29
  }
26
30
  ]
27
31
  },
28
- // A cake is a collection of layers, i.e. a base, a filling and a topping.
29
- // Each layer of the cake has the same slice structure.
30
32
  cakes: {
31
33
  _type: "cakes",
32
34
  _data: [
33
35
  {
34
- idSet: "KAwCRFD4mdx8b1bzxBb2_O",
36
+ idSet: "Ko990SJfgPJvNGxC63CRf7",
35
37
  collections: "layers",
36
38
  layers: {
37
- _hash: "50ZHxZ8KeUYJ3NC7ax5CbR"
39
+ _hash: "RBNvo1WzZ4oRRq0W9-hknp"
38
40
  },
39
- _hash: "gMqFPTkYU66L32jDYZiZ79"
41
+ _hash: "KdLv3zTftqKKUeqYrTtO2r"
40
42
  }
41
- ],
42
- _hash: "fw8IrV05Z1ZekPXTdUdmt8"
43
+ ]
43
44
  },
44
- // Cakes are cut into slices. The cake layers are shared among the slices.
45
45
  slices: {
46
46
  _type: "idSets",
47
47
  _data: [
48
48
  {
49
49
  add: ["slice0", "slice1"],
50
- remove: []
50
+ remove: [],
51
+ _hash: "wyYfK5E4ArrMKQ_zvi2-EE"
51
52
  }
52
- ]
53
+ ],
54
+ _hash: "Qt6FzyzwHdEdYC3fKUXaAm"
53
55
  },
54
- // A layer is a collection of ingredients described by a recipe
55
56
  layers: {
56
57
  _type: "collections",
57
58
  _data: [
58
59
  {
59
60
  properties: "recipes",
60
61
  assign: {
61
- slice0: "VXUW79JrkOAG_pQ-75ahH5",
62
- slice1: "VXUW79JrkOAG_pQ-75ahH5",
63
- _hash: "Di4uG3-DHSq3_ArFu0V68m"
64
- },
65
- _hash: "IIg0e-6Qr73s7sNZe0RhKs"
62
+ slice0: "uRTo_Jmt9lOA09e2QnwB9W",
63
+ slice1: "uRTo_Jmt9lOA09e2QnwB9W"
64
+ }
66
65
  }
67
- ],
68
- _hash: "e3utfEf_VS1-ljAMgqesua"
66
+ ]
69
67
  },
70
- // Recipes are sets of ingredients
71
68
  recipes: {
72
69
  _type: "collections",
73
70
  _data: [
74
71
  {
75
72
  properties: "recipeIngredients",
76
73
  assign: {
77
- flour: "mqLl7Q44V1t2MotJvaVRM_",
78
- _hash: "UImWlyWZTSGhNAMC1JUzF1"
74
+ flour: "RCA4yzQe6mYOqquoLijKop"
79
75
  },
80
- _hash: "G1YV_P8-_vTxtkXXHJeNBI"
76
+ _hash: "uRTo_Jmt9lOA09e2QnwB9W"
81
77
  }
82
- ],
83
- _hash: "r3lw7VsnywyasRmtynZqIt"
78
+ ]
84
79
  },
85
- // A recipe ingredient combines an ingredient type with a quantity
86
80
  recipeIngredients: {
87
81
  _type: "properties",
88
82
  _data: [
89
83
  {
90
- ingredientTypesRef: "WOfnFT1svec3iJ6x",
84
+ ingredientsRef: "CdSJV-WOfnFT1svec3iJ6x",
91
85
  quantity: 500,
92
- _hash: "mqLl7Q44V1t2MotJvaVRM_"
86
+ _hash: "RCA4yzQe6mYOqquoLijKop"
93
87
  }
94
88
  ],
95
- _hash: "J7GJEjCRXLNfdAHARg_fzx"
89
+ _hash: "R6dJq4ZJ3QDa9Bz8QJDhNq"
96
90
  },
97
- // A table describing basic properties of ingredients
98
91
  ingredients: {
99
92
  _type: "properties",
100
93
  _data: [
@@ -107,7 +100,6 @@ const bakeryExample = () => {
107
100
  ],
108
101
  _hash: "FgJeTM0NcZvXwFcU-PD8Jf"
109
102
  },
110
- // A table with nutritive values of ingredients
111
103
  nutritiveValues: {
112
104
  _type: "properties",
113
105
  _data: [
@@ -122,6 +114,7 @@ const bakeryExample = () => {
122
114
  _hash: "7k4OqQtk71w3yVGMoA9F6p"
123
115
  }
124
116
  };
117
+ return result;
125
118
  };
126
119
  // @license
127
120
  const exampleBuffetsTable = () => bakeryExample().buffets;
@@ -155,12 +148,77 @@ __publicField(_Example, "ok", {
155
148
  };
156
149
  },
157
150
  singleRow: () => {
158
- return {
151
+ const tableCfgs = {
152
+ _hash: "",
153
+ _type: "properties",
154
+ _data: [
155
+ {
156
+ _hash: "R-rCQ4YwYYJAp6uAo6S_6n",
157
+ name: "Single Row Table",
158
+ jsonKey: "table",
159
+ columns: {
160
+ int: {
161
+ jsonKey: "int",
162
+ type: "number",
163
+ name: "Integer",
164
+ nameShort: "Int"
165
+ },
166
+ double: {
167
+ jsonKey: "double",
168
+ type: "number",
169
+ name: "Double",
170
+ nameShort: "Dbl"
171
+ },
172
+ string: {
173
+ jsonKey: "string",
174
+ type: "string",
175
+ name: "String",
176
+ nameShort: "Str"
177
+ },
178
+ boolean: {
179
+ jsonKey: "boolean",
180
+ type: "boolean",
181
+ name: "Boolean",
182
+ nameShort: "Bool"
183
+ },
184
+ null: {
185
+ jsonKey: "null",
186
+ type: "null",
187
+ name: "null",
188
+ nameShort: "null"
189
+ },
190
+ jsonArray: {
191
+ jsonKey: "jsonArray",
192
+ type: "jsonArray",
193
+ name: "Json Array",
194
+ nameShort: "Jarray"
195
+ },
196
+ json: {
197
+ jsonKey: "json",
198
+ type: "json",
199
+ name: "Json",
200
+ nameShort: "Json"
201
+ },
202
+ jsonValue: {
203
+ jsonKey: "jsonValue",
204
+ type: "jsonValue",
205
+ name: "Json Value",
206
+ nameShort: "Jval"
207
+ }
208
+ }
209
+ }
210
+ ]
211
+ };
212
+ const result = {
213
+ _tableCfgs: tableCfgs,
159
214
  table: {
160
215
  _type: "properties",
161
- _data: [exampleJsonObject()]
216
+ _tableCfg: "R-rCQ4YwYYJAp6uAo6S_6n",
217
+ _data: [exampleJsonObject()],
218
+ _hash: ""
162
219
  }
163
220
  };
221
+ return result;
164
222
  },
165
223
  multipleRows: () => {
166
224
  return {
@@ -289,65 +347,75 @@ __publicField(_Example, "ok", {
289
347
  }
290
348
  });
291
349
  __publicField(_Example, "broken", {
292
- brokenTableName: () => {
293
- return {
294
- brok$en: {
295
- _type: "properties",
296
- _data: []
297
- }
298
- };
299
- },
300
- missingData: () => {
301
- return {
302
- table: {
303
- _type: "properties"
304
- }
305
- };
306
- },
307
- dataNotBeingAnArray: () => {
308
- return {
309
- table: {
310
- _type: "properties",
311
- _data: {}
312
- }
313
- };
314
- },
315
- missingRef: () => {
316
- return {
317
- tableA: {
318
- _type: "properties",
319
- _data: [
320
- {
321
- keyA0: "a0"
322
- },
323
- {
324
- keyA1: "a1"
325
- }
326
- ]
327
- },
328
- tableB: {
329
- _type: "properties",
330
- _data: [
331
- {
332
- tableARef: "MISSINGREF"
333
- // MISSINGREF does not exist in tableA
334
- }
335
- ]
336
- }
337
- };
350
+ base: {
351
+ brokenTableName: () => {
352
+ return {
353
+ brok$en: {
354
+ _type: "properties",
355
+ _data: []
356
+ }
357
+ };
358
+ },
359
+ missingData: () => {
360
+ return {
361
+ table: {
362
+ _type: "properties"
363
+ }
364
+ };
365
+ },
366
+ dataNotBeingAnArray: () => {
367
+ return {
368
+ table: {
369
+ _type: "properties",
370
+ _data: {}
371
+ }
372
+ };
373
+ },
374
+ missingRef: () => {
375
+ return {
376
+ tableA: {
377
+ _type: "properties",
378
+ _data: [
379
+ {
380
+ keyA0: "a0"
381
+ },
382
+ {
383
+ keyA1: "a1"
384
+ }
385
+ ]
386
+ },
387
+ tableB: {
388
+ _type: "properties",
389
+ _data: [
390
+ {
391
+ tableARef: "MISSINGREF"
392
+ // MISSINGREF does not exist in tableA
393
+ }
394
+ ]
395
+ }
396
+ };
397
+ },
398
+ missingReferencedTable: () => {
399
+ return {
400
+ tableB: {
401
+ _type: "properties",
402
+ _data: [
403
+ {
404
+ tableARef: "MISSINGREF"
405
+ // tableA is missing
406
+ }
407
+ ]
408
+ }
409
+ };
410
+ }
338
411
  },
339
- missingReferencedTable: () => {
340
- return {
341
- tableB: {
342
- _type: "properties",
343
- _data: [
344
- {
345
- tableARef: "MISSINGREF"
346
- // tableA is missing
347
- }
348
- ]
349
- }
350
- };
412
+ tableCfg: {
413
+ wrongType: () => {
414
+ const result = _Example.ok.singleRow();
415
+ const tableCfg = result._tableCfgs._data[0];
416
+ tableCfg.columns["int"].type = "numberBroken";
417
+ return hip(result, true, false);
418
+ }
351
419
  },
352
420
  collections: {
353
421
  missingBase: () => {
@@ -415,6 +483,8 @@ __publicField(_Example, "broken", {
415
483
  });
416
484
  let Example = _Example;
417
485
  // @license
486
+ const exampleTableCfgTable = () => Example.ok.singleRow()._tableCfgs;
487
+ // @license
418
488
  const rljsonIndexed = (rljson) => {
419
489
  const result = {};
420
490
  for (const key in rljson) {
@@ -459,15 +529,676 @@ const exampleTypedefs = () => {
459
529
  contentType: "collections"
460
530
  };
461
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
+ }
462
1188
  export {
1189
+ BaseValidator,
463
1190
  Example,
1191
+ Validate,
1192
+ bakeryExample,
464
1193
  exampleBuffetsTable,
465
1194
  exampleCakesTable,
466
1195
  exampleCollectionsTable,
467
1196
  exampleIdSetsTable,
468
1197
  examplePropertiesTable,
469
1198
  exampleRljson,
1199
+ exampleTableCfgTable,
470
1200
  exampleTypedefs,
1201
+ isValidFieldName,
471
1202
  iterateTables,
472
1203
  reservedFieldNames,
473
1204
  reservedTableNames,
@@ -7,6 +7,7 @@
7
7
  import { hip } from '@rljson/hash';
8
8
  import { exampleJsonObject } from '@rljson/json';
9
9
 
10
+ import { TablesCfgTable } from './content/table-cfg.ts';
10
11
  import { bakeryExample } from './example/bakery-example.ts';
11
12
  import { Rljson } from './rljson.ts';
12
13
 
@@ -33,12 +34,78 @@ export class Example {
33
34
  },
34
35
 
35
36
  singleRow: (): Rljson => {
36
- return {
37
+ const tableCfgs: TablesCfgTable = {
38
+ _hash: '',
39
+ _type: 'properties',
40
+ _data: [
41
+ {
42
+ _hash: 'R-rCQ4YwYYJAp6uAo6S_6n',
43
+ name: 'Single Row Table',
44
+ jsonKey: 'table',
45
+ columns: {
46
+ int: {
47
+ jsonKey: 'int',
48
+ type: 'number',
49
+ name: 'Integer',
50
+ nameShort: 'Int',
51
+ },
52
+ double: {
53
+ jsonKey: 'double',
54
+ type: 'number',
55
+ name: 'Double',
56
+ nameShort: 'Dbl',
57
+ },
58
+ string: {
59
+ jsonKey: 'string',
60
+ type: 'string',
61
+ name: 'String',
62
+ nameShort: 'Str',
63
+ },
64
+ boolean: {
65
+ jsonKey: 'boolean',
66
+ type: 'boolean',
67
+ name: 'Boolean',
68
+ nameShort: 'Bool',
69
+ },
70
+ null: {
71
+ jsonKey: 'null',
72
+ type: 'null',
73
+ name: 'null',
74
+ nameShort: 'null',
75
+ },
76
+ jsonArray: {
77
+ jsonKey: 'jsonArray',
78
+ type: 'jsonArray',
79
+ name: 'Json Array',
80
+ nameShort: 'Jarray',
81
+ },
82
+ json: {
83
+ jsonKey: 'json',
84
+ type: 'json',
85
+ name: 'Json',
86
+ nameShort: 'Json',
87
+ },
88
+ jsonValue: {
89
+ jsonKey: 'jsonValue',
90
+ type: 'jsonValue',
91
+ name: 'Json Value',
92
+ nameShort: 'Jval',
93
+ },
94
+ },
95
+ },
96
+ ],
97
+ };
98
+
99
+ const result: Rljson = {
100
+ _tableCfgs: tableCfgs,
37
101
  table: {
38
102
  _type: 'properties',
103
+ _tableCfg: 'R-rCQ4YwYYJAp6uAo6S_6n',
39
104
  _data: [exampleJsonObject()],
105
+ _hash: '',
40
106
  },
41
107
  };
108
+ return result as Rljson;
42
109
  },
43
110
 
44
111
  multipleRows: (): Rljson => {
@@ -177,67 +244,78 @@ export class Example {
177
244
  };
178
245
 
179
246
  static readonly broken = {
180
- brokenTableName: () => {
181
- return {
182
- brok$en: {
183
- _type: 'properties',
184
- _data: [],
185
- },
186
- };
187
- },
247
+ base: {
248
+ brokenTableName: () => {
249
+ return {
250
+ brok$en: {
251
+ _type: 'properties',
252
+ _data: [],
253
+ },
254
+ };
255
+ },
188
256
 
189
- missingData: () => {
190
- return {
191
- table: {
192
- _type: 'properties',
193
- },
194
- } as unknown as Rljson;
195
- },
257
+ missingData: () => {
258
+ return {
259
+ table: {
260
+ _type: 'properties',
261
+ },
262
+ } as unknown as Rljson;
263
+ },
196
264
 
197
- dataNotBeingAnArray: () => {
198
- return {
199
- table: {
200
- _type: 'properties',
201
- _data: {},
202
- },
203
- } as unknown as Rljson;
204
- },
265
+ dataNotBeingAnArray: () => {
266
+ return {
267
+ table: {
268
+ _type: 'properties',
269
+ _data: {},
270
+ },
271
+ } as unknown as Rljson;
272
+ },
205
273
 
206
- missingRef: (): Rljson => {
207
- return {
208
- tableA: {
209
- _type: 'properties',
210
- _data: [
211
- {
212
- keyA0: 'a0',
213
- },
214
- {
215
- keyA1: 'a1',
216
- },
217
- ],
218
- },
219
- tableB: {
220
- _type: 'properties',
221
- _data: [
222
- {
223
- tableARef: 'MISSINGREF', // MISSINGREF does not exist in tableA
224
- },
225
- ],
226
- },
227
- };
274
+ missingRef: (): Rljson => {
275
+ return {
276
+ tableA: {
277
+ _type: 'properties',
278
+ _data: [
279
+ {
280
+ keyA0: 'a0',
281
+ },
282
+ {
283
+ keyA1: 'a1',
284
+ },
285
+ ],
286
+ },
287
+ tableB: {
288
+ _type: 'properties',
289
+ _data: [
290
+ {
291
+ tableARef: 'MISSINGREF', // MISSINGREF does not exist in tableA
292
+ },
293
+ ],
294
+ },
295
+ };
296
+ },
297
+
298
+ missingReferencedTable: (): Rljson => {
299
+ return {
300
+ tableB: {
301
+ _type: 'properties',
302
+ _data: [
303
+ {
304
+ tableARef: 'MISSINGREF', // tableA is missing
305
+ },
306
+ ],
307
+ },
308
+ };
309
+ },
228
310
  },
229
311
 
230
- missingReferencedTable: (): Rljson => {
231
- return {
232
- tableB: {
233
- _type: 'properties',
234
- _data: [
235
- {
236
- tableARef: 'MISSINGREF', // tableA is missing
237
- },
238
- ],
239
- },
240
- };
312
+ tableCfg: {
313
+ wrongType: () => {
314
+ const result = Example.ok.singleRow();
315
+ const tableCfg = result._tableCfgs._data[0];
316
+ tableCfg.columns['int'].type = 'numberBroken'; // Break one of the types
317
+ return hip(result, true, false);
318
+ },
241
319
  },
242
320
 
243
321
  collections: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/rljson",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "packageManager": "pnpm@10.6.3",
5
5
  "description": "The RLJSON data format specification",
6
6
  "homepage": "https://github.com/rljson/rljson",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@rljson/hash": "^0.0.12",
54
- "@rljson/json": "^0.0.15"
54
+ "@rljson/json": "^0.0.18"
55
55
  },
56
56
  "pnpm": {
57
57
  "onlyBuiltDependencies": [