@rljson/rljson 0.0.33 → 0.0.36

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.
@@ -23,6 +23,10 @@ export interface Buffet extends Json {
23
23
  */
24
24
  ref: Ref;
25
25
  }>;
26
+ /**
27
+ * An optional ID of the buffet.
28
+ */
29
+ id?: BuffetId;
26
30
  }
27
31
  /**
28
32
  * A table containing buffets
@@ -19,11 +19,16 @@ export interface Cake extends Json {
19
19
  * must match these slice ids of the layers.
20
20
  * The slice ids can be found in the idSet table.
21
21
  */
22
- sliceIds: SliceIdsRef;
23
- /**
24
- * The table containing the slice ids of the layer
25
- */
26
- sliceIdsTable: TableKey;
22
+ sliceIds: {
23
+ /**
24
+ * The table containing the item ids of the layer
25
+ */
26
+ table: TableKey;
27
+ /**
28
+ * A row in table, that contains the slice ids of the layer
29
+ */
30
+ row: SliceIdsRef;
31
+ };
27
32
  /**
28
33
  * The table containing the slice layers defining the layers
29
34
  */
@@ -34,6 +39,10 @@ export interface Cake extends Json {
34
39
  layers: {
35
40
  [layerId: CakeLayerId]: LayerRef;
36
41
  };
42
+ /**
43
+ * An optional ID of the cake.
44
+ */
45
+ id?: string;
37
46
  }
38
47
  /**
39
48
  * A table containing cakes
@@ -1,7 +1,6 @@
1
- import { Json } from '@rljson/json';
2
1
  import { NutritionalValues } from '../example/bakery-example.ts';
3
2
  import { RljsonTable } from '../rljson.ts';
4
- import { Ref } from '../typedefs.ts';
3
+ import { JsonWithId, Ref } from '../typedefs.ts';
5
4
  /**
6
5
  * A reference to a ingredients row in a ingredients table
7
6
  */
@@ -9,7 +8,7 @@ export type IngredientsRef = Ref;
9
8
  /**
10
9
  * A table containing ingredients
11
10
  */
12
- export type IngredientsTable<T extends Json> = RljsonTable<T, 'ingredients'>;
11
+ export type IngredientsTable<T extends JsonWithId> = RljsonTable<T, 'ingredients'>;
13
12
  /**
14
13
  * Provides an example ingredients table for test purposes
15
14
  */
@@ -15,15 +15,16 @@ export interface Layer extends Json {
15
15
  * `base` an optional base layer that is extended by this layer
16
16
  */
17
17
  base?: LayerRef;
18
- /**
19
- * The item ids of the layer. If present, the item ids in `assign`
20
- * must match these ids. The item id sets can be found in the sliceIds table.
21
- */
22
- sliceIds?: SliceIdsRef;
23
- /**
24
- * The table containing the item ids of the layer
25
- */
26
- sliceIdsTable?: TableKey;
18
+ sliceIds: {
19
+ /**
20
+ * The table containing the item ids of the layer
21
+ */
22
+ table: TableKey;
23
+ /**
24
+ * A row in table, that contains the slice ids of the layer
25
+ */
26
+ row: SliceIdsRef;
27
+ };
27
28
  /**
28
29
  * The table containing the ingredients that are assigned to the items
29
30
  * with the assign property below
@@ -0,0 +1,37 @@
1
+ import { Json } from '@rljson/json';
2
+ import { RljsonTable } from '../rljson.ts';
3
+ import { Ref, TableKey } from '../typedefs.ts';
4
+ /**
5
+ * Describes a revision of a row in a table
6
+ */
7
+ export interface Revision extends Json {
8
+ /**
9
+ * The name of the table the revisione row belongs to
10
+ */
11
+ table: TableKey;
12
+ /**
13
+ * The predecessor of the revision
14
+ */
15
+ predecessor: Ref;
16
+ /**
17
+ * The successor of the revision
18
+ */
19
+ successor: Ref;
20
+ /**
21
+ * The UTC timestamp of the revision
22
+ */
23
+ timestamp: number;
24
+ /**
25
+ * The optional ID of the revisioned element.
26
+ * Can be used get all revisions of a specific ingredient.
27
+ */
28
+ id?: string;
29
+ }
30
+ /**
31
+ * A table containing revisions
32
+ */
33
+ export type RevisionsTable = RljsonTable<Revision, 'ingredients'>;
34
+ /**
35
+ * Example revision object for test purposes
36
+ */
37
+ export declare const exampleRevision: () => Revision;
@@ -9,10 +9,6 @@ export type TableCfgRef = Ref;
9
9
  * A column configuration
10
10
  */
11
11
  export interface ColumnCfg extends Json {
12
- /**
13
- * The key of the column used in data
14
- */
15
- key: ColumnKey;
16
12
  /**
17
13
  * The type of the column
18
14
  */
@@ -43,6 +39,30 @@ export interface TableCfg extends Json {
43
39
  * Needs to be increased when new columns are added.
44
40
  */
45
41
  version: number;
42
+ /**
43
+ * Head tables serve as versioning entry points.
44
+ *
45
+ * - Head tables must contain an id column
46
+ * - Rows in an head table must contain a non-null id
47
+ * - Same row ids must refer to the same physical object
48
+ * - Head tables have no or only one parent table
49
+ */
50
+ isHead: boolean;
51
+ /**
52
+ * Root tables are tables that have no parent table.
53
+ *
54
+ * - Root tables are also head tables
55
+ */
56
+ isRoot: boolean;
57
+ /**
58
+ * Shared tables are tables that are used by multiple parents.
59
+ *
60
+ * - The don't need an id column
61
+ * - Same id can refer to different physical objects
62
+ * - Shared tables can have multiple parents
63
+ * - Shared tables can not be head or root tables
64
+ */
65
+ isShared: boolean;
46
66
  }
47
67
  /**
48
68
  * A table containing columns
package/dist/example.d.ts CHANGED
@@ -23,7 +23,7 @@ export declare class Example {
23
23
  missingReferencedTable: () => Rljson;
24
24
  };
25
25
  tableCfg: {
26
- wrongType: () => import('@rljson/hash').Hashed<Rljson>;
26
+ wrongType: () => Rljson;
27
27
  };
28
28
  layers: {
29
29
  missingBase: () => Rljson;
package/dist/rljson.d.ts CHANGED
@@ -3,6 +3,7 @@ import { BuffetsTable } from './content/buffet.ts';
3
3
  import { CakesTable } from './content/cake.ts';
4
4
  import { IngredientsTable } from './content/ingredients.ts';
5
5
  import { LayersTable } from './content/layer.ts';
6
+ import { RevisionsTable } from './content/revision.ts';
6
7
  import { SliceIdsTable } from './content/slice-ids.ts';
7
8
  import { TableCfgRef, TablesCfgTable } from './content/table-cfg.ts';
8
9
  import { ContentType, Ref, TableKey } from './typedefs.ts';
@@ -11,7 +12,7 @@ export declare const reservedTableKeys: string[];
11
12
  /**
12
13
  * One of the supported Rljson table types
13
14
  */
14
- export type TableType = BuffetsTable | IngredientsTable<any> | LayersTable | SliceIdsTable | CakesTable;
15
+ export type TableType = BuffetsTable | IngredientsTable<any> | LayersTable | SliceIdsTable | CakesTable | RevisionsTable;
15
16
  /** The rljson data format */
16
17
  export interface Rljson extends Json {
17
18
  [tableId: TableKey]: TableType;
package/dist/rljson.js CHANGED
@@ -9,11 +9,20 @@ const bakeryExample = () => {
9
9
  _type: "ingredients",
10
10
  _data: [
11
11
  {
12
+ id: "flour",
12
13
  energy: 364,
13
14
  fat: 0.98,
14
15
  protein: 10.33,
15
16
  carbohydrates: 76.31,
16
17
  _hash: ""
18
+ },
19
+ {
20
+ id: "flour",
21
+ energy: 364.1,
22
+ fat: 0.981,
23
+ protein: 10.331,
24
+ carbohydrates: 76.311,
25
+ _hash: ""
17
26
  }
18
27
  ],
19
28
  _hash: ""
@@ -22,7 +31,7 @@ const bakeryExample = () => {
22
31
  _type: "ingredients",
23
32
  _data: [
24
33
  {
25
- name: "flour",
34
+ id: "flour",
26
35
  amountUnit: "g",
27
36
  nutritionalValuesRef: nutritionalValues._data[0]._hash,
28
37
  _hash: ""
@@ -34,6 +43,7 @@ const bakeryExample = () => {
34
43
  _type: "ingredients",
35
44
  _data: [
36
45
  {
46
+ id: "flour",
37
47
  ingredientsRef: ingredients._data[0]._hash,
38
48
  quantity: 500,
39
49
  _hash: ""
@@ -41,11 +51,34 @@ const bakeryExample = () => {
41
51
  ],
42
52
  _hash: ""
43
53
  });
54
+ const slices = hip({
55
+ _type: "sliceIds",
56
+ _data: [
57
+ {
58
+ add: ["slice0", "slice1"],
59
+ remove: []
60
+ }
61
+ ]
62
+ });
63
+ const ingredientTypes = hip({
64
+ _type: "sliceIds",
65
+ _data: [
66
+ {
67
+ add: ["flour"],
68
+ remove: []
69
+ }
70
+ ]
71
+ });
44
72
  const recipes = hip({
45
73
  _type: "layers",
46
74
  _data: [
47
75
  {
76
+ id: "tastyCake",
48
77
  ingredientsTable: "recipeIngredients",
78
+ sliceIds: {
79
+ table: "ingredientTypes",
80
+ row: ingredientTypes._data[0]._hash
81
+ },
49
82
  assign: {
50
83
  flour: recipeIngredients._data[0]._hash
51
84
  },
@@ -58,6 +91,10 @@ const bakeryExample = () => {
58
91
  _data: [
59
92
  {
60
93
  ingredientsTable: "recipes",
94
+ sliceIds: {
95
+ table: "slices",
96
+ row: slices._data[0]._hash
97
+ },
61
98
  assign: {
62
99
  slice0: recipes._data[0]._hash,
63
100
  slice1: recipes._data[0]._hash
@@ -65,28 +102,19 @@ const bakeryExample = () => {
65
102
  }
66
103
  ]
67
104
  });
68
- const slices = hip({
69
- _type: "sliceIds",
70
- _data: [
71
- {
72
- add: ["slice0", "slice1"],
73
- remove: [],
74
- _hash: ""
75
- }
76
- ],
77
- _hash: ""
78
- });
79
105
  const cakes = hip({
80
106
  _type: "cakes",
81
107
  _data: [
82
108
  {
83
- sliceIdsTable: "slices",
84
- sliceIds: slices._data[0]._hash,
109
+ id: "cake1",
110
+ sliceIds: {
111
+ table: "slices",
112
+ row: slices._data[0]._hash
113
+ },
85
114
  layersTable: "layers",
86
115
  layers: {
87
116
  flour: layers._data[0]._hash
88
- },
89
- _hash: ""
117
+ }
90
118
  }
91
119
  ]
92
120
  });
@@ -94,6 +122,7 @@ const bakeryExample = () => {
94
122
  _type: "buffets",
95
123
  _data: [
96
124
  {
125
+ id: "salesCounter",
97
126
  items: [
98
127
  {
99
128
  table: "cakes",
@@ -107,6 +136,7 @@ const bakeryExample = () => {
107
136
  buffets,
108
137
  cakes,
109
138
  slices,
139
+ ingredientTypes,
110
140
  layers,
111
141
  recipes,
112
142
  recipeIngredients,
@@ -156,37 +186,32 @@ __publicField(_Example, "ok", {
156
186
  _hash: "",
157
187
  key: "table",
158
188
  type: "ingredients",
189
+ isHead: false,
190
+ isRoot: false,
191
+ isShared: true,
159
192
  columns: {
160
193
  int: {
161
- key: "int",
162
194
  type: "number"
163
195
  },
164
196
  double: {
165
- key: "double",
166
197
  type: "number"
167
198
  },
168
199
  string: {
169
- key: "string",
170
200
  type: "string"
171
201
  },
172
202
  boolean: {
173
- key: "boolean",
174
203
  type: "boolean"
175
204
  },
176
205
  null: {
177
- key: "null",
178
206
  type: "null"
179
207
  },
180
208
  jsonArray: {
181
- key: "jsonArray",
182
209
  type: "jsonArray"
183
210
  },
184
211
  json: {
185
- key: "json",
186
212
  type: "json"
187
213
  },
188
214
  jsonValue: {
189
- key: "jsonValue",
190
215
  type: "jsonValue"
191
216
  }
192
217
  }
@@ -273,15 +298,22 @@ __publicField(_Example, "ok", {
273
298
  const ingredient0 = ingredients._data[0];
274
299
  const ingredient1 = ingredients._data[1];
275
300
  const layer0 = hip({
276
- sliceIdsTable: "sliceIds",
277
- sliceIds: "MgHRBYSrhpyl4rvsOmAWcQ",
301
+ sliceIds: {
302
+ table: "sliceIds",
303
+ row: "MgHRBYSrhpyl4rvsOmAWcQ"
304
+ },
278
305
  ingredientsTable: "ingredients",
279
- assign: {}
306
+ assign: {
307
+ id0: ingredient0._hash,
308
+ id1: ingredient1._hash
309
+ }
280
310
  });
281
311
  const layer1 = hip({
282
312
  base: layer0._hash,
283
- sliceIdsTable: "sliceIds",
284
- sliceIds: "MgHRBYSrhpyl4rvsOmAWcQ",
313
+ sliceIds: {
314
+ table: "sliceIds",
315
+ row: "MgHRBYSrhpyl4rvsOmAWcQ"
316
+ },
285
317
  ingredientsTable: "ingredients",
286
318
  assign: {
287
319
  id0: ingredient0._hash,
@@ -293,8 +325,10 @@ __publicField(_Example, "ok", {
293
325
  _data: [layer0, layer1]
294
326
  });
295
327
  const cake = hip({
296
- sliceIdsTable: "sliceIds",
297
- sliceIds: sliceIds._data[0]._hash,
328
+ sliceIds: {
329
+ table: "sliceIds",
330
+ row: sliceIds._data[0]._hash
331
+ },
298
332
  layersTable: "layers",
299
333
  layers: {
300
334
  layer0: layer0._hash,
@@ -399,7 +433,10 @@ __publicField(_Example, "broken", {
399
433
  const result = _Example.ok.singleRow();
400
434
  const tableCfg = result.tableCfgs._data[0];
401
435
  tableCfg.columns["int"].type = "numberBroken";
402
- return hip(result, true, false);
436
+ return hip(result, {
437
+ updateExistingHashes: true,
438
+ throwOnWrongHashes: false
439
+ });
403
440
  }
404
441
  },
405
442
  layers: {
@@ -407,13 +444,19 @@ __publicField(_Example, "broken", {
407
444
  const result = _Example.ok.complete();
408
445
  const layer1 = result.layers._data[1];
409
446
  layer1.base = "MISSING";
410
- return hip(result, true, false);
447
+ return hip(result, {
448
+ updateExistingHashes: true,
449
+ throwOnWrongHashes: false
450
+ });
411
451
  },
412
452
  missingSliceIdSet: () => {
413
453
  const result = _Example.ok.complete();
414
454
  const layer1 = result.layers._data[1];
415
- layer1.sliceIds = "MISSING1";
416
- return hip(result, true, false);
455
+ layer1.sliceIds.row = "MISSING1";
456
+ return hip(result, {
457
+ updateExistingHashes: true,
458
+ throwOnWrongHashes: false
459
+ });
417
460
  },
418
461
  missingAssignedIngredientTable: () => {
419
462
  const result = _Example.ok.complete();
@@ -423,27 +466,44 @@ __publicField(_Example, "broken", {
423
466
  missingAssignedIngredient: () => {
424
467
  const result = _Example.ok.complete();
425
468
  result.ingredients._data.splice(1, 2);
426
- return hip(result, true, false);
469
+ return hip(result, {
470
+ updateExistingHashes: true,
471
+ throwOnWrongHashes: false
472
+ });
427
473
  }
428
474
  },
429
475
  cakes: {
430
476
  missingSliceIdSet: () => {
431
477
  const result = _Example.ok.complete();
432
- result.cakes._data[0].sliceIds = "MISSING";
433
- hip(result.cakes, true, false);
478
+ result.cakes._data[0].sliceIds.row = "MISSING";
479
+ hip(result.cakes, {
480
+ updateExistingHashes: true,
481
+ throwOnWrongHashes: false
482
+ });
483
+ result.buffets._data[0].items[0].ref = result.cakes._data[0]._hash;
484
+ hip(result.buffets, {
485
+ updateExistingHashes: true,
486
+ throwOnWrongHashes: false
487
+ });
434
488
  return result;
435
489
  },
436
490
  missingLayersTable: () => {
437
491
  const result = _Example.ok.complete();
438
492
  result.cakes._data[0].layersTable = "MISSING";
439
- hip(result.cakes, true, false);
493
+ hip(result.cakes, {
494
+ updateExistingHashes: true,
495
+ throwOnWrongHashes: false
496
+ });
440
497
  return result;
441
498
  },
442
499
  missingCakeLayer: () => {
443
500
  const result = _Example.ok.complete();
444
501
  result.cakes._data[0].layers["layer0"] = "MISSING0";
445
502
  result.cakes._data[0].layers["layer1"] = "MISSING1";
446
- hip(result.cakes, true, false);
503
+ hip(result.cakes, {
504
+ updateExistingHashes: true,
505
+ throwOnWrongHashes: false
506
+ });
447
507
  return result;
448
508
  }
449
509
  },
@@ -453,7 +513,7 @@ __publicField(_Example, "broken", {
453
513
  const buffet = result.buffets._data[0];
454
514
  buffet.items[0].table = "MISSING0";
455
515
  buffet.items[1].table = "MISSING1";
456
- hip(result, true, false);
516
+ hip(result, { updateExistingHashes: true, throwOnWrongHashes: false });
457
517
  return result;
458
518
  },
459
519
  missingItems: () => {
@@ -461,7 +521,7 @@ __publicField(_Example, "broken", {
461
521
  const buffet = result.buffets._data[0];
462
522
  buffet.items[0].ref = "MISSING0";
463
523
  buffet.items[1].ref = "MISSING1";
464
- hip(result, true, false);
524
+ hip(result, { updateExistingHashes: true, throwOnWrongHashes: false });
465
525
  return result;
466
526
  }
467
527
  }
@@ -475,15 +535,16 @@ const exampleTableCfg = (tableCfg = void 0) => {
475
535
  version: 1,
476
536
  columns: (tableCfg == null ? void 0 : tableCfg.columns) ?? {
477
537
  a: {
478
- key: "a",
479
538
  type: "string"
480
539
  },
481
540
  b: {
482
- key: "b",
483
541
  type: "number"
484
542
  }
485
543
  },
486
- type: (tableCfg == null ? void 0 : tableCfg.type) ?? "ingredients"
544
+ type: (tableCfg == null ? void 0 : tableCfg.type) ?? "ingredients",
545
+ isHead: true,
546
+ isRoot: true,
547
+ isShared: false
487
548
  };
488
549
  };
489
550
  // @license
@@ -511,7 +572,13 @@ const rljsonIndexed = (rljson) => {
511
572
  };
512
573
  // @license
513
574
  const reservedFieldNames = ["_type", "_data"];
514
- const reservedTableKeys = ["_hash", "sliceIds", "tableCfgs"];
575
+ const reservedTableKeys = [
576
+ "_hash",
577
+ "sliceIds",
578
+ "tableCfgs",
579
+ "reverseRefs",
580
+ "revisions"
581
+ ];
515
582
  const exampleRljson = () => Example.ok.singleRow();
516
583
  const iterateTables = (rljson, callback) => {
517
584
  for (const tableKey in rljson) {
@@ -587,13 +654,16 @@ class _BaseValidator {
587
654
  () => this._missingColumnConfigs(),
588
655
  () => this._dataDoesNotMatchColumnConfig(),
589
656
  () => this._tableTypesDoNotMatch(),
657
+ () => this._tableCfgHasRootHeadSharedError(),
658
+ () => this._rootOrHeadTableHasNoIdColumn(),
590
659
  // Check references
591
660
  () => this._refsNotFound(),
592
661
  // Check layers
593
662
  () => this._layerBasesNotFound(),
594
663
  () => this._layerSliceIdsTableNotFound(),
595
- () => this._layerSliceIdsNotFound(),
664
+ () => this._layerSliceIdsRowNotFound(),
596
665
  () => this._layerIngredientAssignmentsNotFound(),
666
+ () => this._layerAssignmentsDoNotMatchSliceIds(),
597
667
  // Check cakes
598
668
  () => this._cakeSliceIdsTableNotFound(),
599
669
  () => this._cakeSliceIdsNotFound(),
@@ -893,6 +963,76 @@ class _BaseValidator {
893
963
  }
894
964
  }
895
965
  // ...........................................................................
966
+ _tableCfgHasRootHeadSharedError() {
967
+ const rljson = this.rljson;
968
+ const inconsistentTableCfgs = [];
969
+ for (const tableKey of this.tableKeys) {
970
+ const table = rljson[tableKey];
971
+ const cfgRef = table._tableCfg;
972
+ if (!cfgRef) {
973
+ continue;
974
+ }
975
+ const cfg = this.rljsonIndexed.tableCfgs._data[cfgRef];
976
+ const { isRoot, isHead, isShared } = cfg;
977
+ if (isShared && (isRoot || isHead)) {
978
+ inconsistentTableCfgs.push({
979
+ error: "Tables with isShared = true must have isRoot = false and isHead = false",
980
+ table: tableKey,
981
+ tableCfg: cfgRef
982
+ });
983
+ } else if (isRoot && !isHead) {
984
+ inconsistentTableCfgs.push({
985
+ error: "Tables with isRoot = true must also have isHead = true",
986
+ table: tableKey,
987
+ tableCfg: cfgRef
988
+ });
989
+ } else if (!isRoot && !isHead && !isShared) {
990
+ inconsistentTableCfgs.push({
991
+ error: "Tables must be either root, root+head or shared",
992
+ table: tableKey,
993
+ tableCfg: cfgRef
994
+ });
995
+ }
996
+ }
997
+ if (inconsistentTableCfgs.length > 0) {
998
+ this.errors.tableCfgHasRootHeadSharedError = {
999
+ error: "Table configs have inconsistent root/head/shared settings",
1000
+ tables: inconsistentTableCfgs
1001
+ };
1002
+ }
1003
+ }
1004
+ // ...........................................................................
1005
+ _rootOrHeadTableHasNoIdColumn() {
1006
+ const rljson = this.rljson;
1007
+ const rootOrHeadTablesWithoutIdColumns = [];
1008
+ for (const tableKey of this.tableKeys) {
1009
+ const table = rljson[tableKey];
1010
+ const cfgRef = table._tableCfg;
1011
+ if (!cfgRef) {
1012
+ continue;
1013
+ }
1014
+ const cfg = this.rljsonIndexed.tableCfgs._data[cfgRef];
1015
+ const isRootOrHeadTable = cfg.isRoot || cfg.isHead;
1016
+ if (!isRootOrHeadTable) {
1017
+ continue;
1018
+ }
1019
+ const columns = cfg.columns;
1020
+ const idField = columns["id"];
1021
+ if (!idField) {
1022
+ rootOrHeadTablesWithoutIdColumns.push({
1023
+ table: tableKey,
1024
+ tableCfg: cfgRef
1025
+ });
1026
+ }
1027
+ }
1028
+ if (rootOrHeadTablesWithoutIdColumns.length > 0) {
1029
+ this.errors.rootOrHeadTableHasNoIdColumn = {
1030
+ error: "Root or head tables must have an id column",
1031
+ tables: rootOrHeadTablesWithoutIdColumns
1032
+ };
1033
+ }
1034
+ }
1035
+ // ...........................................................................
896
1036
  _dataHasWrongType() {
897
1037
  const rljson = this.rljson;
898
1038
  const tablesWithWrongType = [];
@@ -1014,16 +1154,16 @@ class _BaseValidator {
1014
1154
  }
1015
1155
  const layersTable = table;
1016
1156
  for (const layer of layersTable._data) {
1017
- const sliceIds = layer.sliceIdsTable;
1157
+ const sliceIds = layer.sliceIds;
1018
1158
  if (!sliceIds) {
1019
1159
  continue;
1020
1160
  }
1021
- const sliceIdsTable = this.rljsonIndexed[sliceIds];
1161
+ const sliceIdsTable = this.rljsonIndexed[sliceIds.table];
1022
1162
  if (!sliceIdsTable) {
1023
1163
  brokenLayers.push({
1024
1164
  layersTable: tableKey,
1025
1165
  layerHash: layer._hash,
1026
- missingSliceIdsTable: sliceIds
1166
+ missingSliceIdsTable: sliceIds.table
1027
1167
  });
1028
1168
  }
1029
1169
  }
@@ -1035,7 +1175,7 @@ class _BaseValidator {
1035
1175
  };
1036
1176
  }
1037
1177
  }
1038
- _layerSliceIdsNotFound() {
1178
+ _layerSliceIdsRowNotFound() {
1039
1179
  const brokenLayers = [];
1040
1180
  iterateTables(this.rljson, (tableKey, table) => {
1041
1181
  if (table._type !== "layers") {
@@ -1043,24 +1183,24 @@ class _BaseValidator {
1043
1183
  }
1044
1184
  const layersTable = table;
1045
1185
  for (const layer of layersTable._data) {
1046
- const idSetRef = layer.sliceIds;
1047
- if (!idSetRef) {
1186
+ const sliceIds = layer.sliceIds;
1187
+ if (!sliceIds) {
1048
1188
  continue;
1049
1189
  }
1050
- const sliceIds = layer.sliceIdsTable;
1051
- const sliceIdsTable = this.rljsonIndexed[sliceIds];
1052
- const idSet = sliceIdsTable._data[idSetRef];
1190
+ const sliceIdsTableName = sliceIds.table;
1191
+ const sliceIdsTable = this.rljsonIndexed[sliceIdsTableName];
1192
+ const idSet = sliceIdsTable._data[sliceIds.row];
1053
1193
  if (!idSet) {
1054
1194
  brokenLayers.push({
1055
1195
  layersTable: tableKey,
1056
1196
  layerHash: layer._hash,
1057
- missingSliceIds: idSetRef
1197
+ missingSliceIdsRow: sliceIds.row
1058
1198
  });
1059
1199
  }
1060
1200
  }
1061
1201
  });
1062
1202
  if (brokenLayers.length > 0) {
1063
- this.errors.layerSliceIdsNotFound = {
1203
+ this.errors.layerSliceIdsRowNotFound = {
1064
1204
  error: "Id sets of layers are missing",
1065
1205
  brokenLayers
1066
1206
  };
@@ -1116,6 +1256,44 @@ class _BaseValidator {
1116
1256
  };
1117
1257
  }
1118
1258
  }
1259
+ _layerAssignmentsDoNotMatchSliceIds() {
1260
+ const layersWithMissingAssignments = [];
1261
+ iterateTables(this.rljson, (tableKey, table) => {
1262
+ if (table._type !== "layers") {
1263
+ return;
1264
+ }
1265
+ const layersTable = table;
1266
+ for (const layer of layersTable._data) {
1267
+ if (!layer.sliceIds) {
1268
+ continue;
1269
+ }
1270
+ const sliceIdsTable = this.rljsonIndexed[layer.sliceIds.table];
1271
+ const sliceIdsRow = sliceIdsTable._data[layer.sliceIds.row];
1272
+ const sliceIds = sliceIdsRow.add;
1273
+ const sliceIdsInLayer = Object.keys(layer.assign);
1274
+ const unassignedSliceIds = [];
1275
+ for (const expectedSliceId of sliceIds) {
1276
+ if (sliceIdsInLayer.indexOf(expectedSliceId) === -1) {
1277
+ unassignedSliceIds.push(expectedSliceId);
1278
+ }
1279
+ }
1280
+ if (unassignedSliceIds.length) {
1281
+ layersWithMissingAssignments.push({
1282
+ brokenLayer: layer._hash,
1283
+ layersTable: tableKey,
1284
+ unassignedSliceIds
1285
+ });
1286
+ continue;
1287
+ }
1288
+ }
1289
+ });
1290
+ if (layersWithMissingAssignments.length > 0) {
1291
+ this.errors.layerAssignmentsDoNotMatchSliceIds = {
1292
+ error: "Layers have missing assignments",
1293
+ layers: layersWithMissingAssignments
1294
+ };
1295
+ }
1296
+ }
1119
1297
  _cakeSliceIdsTableNotFound() {
1120
1298
  const brokenCakes = [];
1121
1299
  iterateTables(this.rljson, (tableKey, table) => {
@@ -1124,16 +1302,16 @@ class _BaseValidator {
1124
1302
  }
1125
1303
  const cakesTable = table;
1126
1304
  for (const cake of cakesTable._data) {
1127
- const sliceIdsTableName = cake.sliceIdsTable;
1128
- if (!sliceIdsTableName) {
1305
+ const sliceIds = cake.sliceIds;
1306
+ if (!sliceIds) {
1129
1307
  continue;
1130
1308
  }
1131
- const sliceIdsTable = this.rljsonIndexed[sliceIdsTableName];
1309
+ const sliceIdsTable = this.rljsonIndexed[sliceIds.table];
1132
1310
  if (!sliceIdsTable) {
1133
1311
  brokenCakes.push({
1134
1312
  cakeTable: tableKey,
1135
1313
  brokenCake: cake._hash,
1136
- missingSliceIdsTable: sliceIdsTableName
1314
+ missingSliceIdsTable: sliceIds.table
1137
1315
  });
1138
1316
  }
1139
1317
  }
@@ -1153,18 +1331,18 @@ class _BaseValidator {
1153
1331
  }
1154
1332
  const cakesTable = table;
1155
1333
  for (const cake of cakesTable._data) {
1156
- const sliceIdsRef = cake.sliceIds;
1157
- if (!sliceIdsRef) {
1334
+ const sliceIds = cake.sliceIds;
1335
+ if (!sliceIds) {
1158
1336
  continue;
1159
1337
  }
1160
- const sliceIdsTableName = cake.sliceIdsTable;
1338
+ const sliceIdsTableName = cake.sliceIds.table;
1161
1339
  const sliceIdsTable = this.rljsonIndexed[sliceIdsTableName];
1162
- const sliceIds = sliceIdsTable._data[sliceIdsRef];
1163
- if (!sliceIds) {
1340
+ const sliceIdValues = sliceIdsTable._data[sliceIds.row];
1341
+ if (!sliceIdValues) {
1164
1342
  brokenCakes.push({
1165
1343
  cakeTable: tableKey,
1166
1344
  brokenCake: cake._hash,
1167
- missingSliceIds: sliceIdsRef
1345
+ missingSliceIdsRow: sliceIds.row
1168
1346
  });
1169
1347
  }
1170
1348
  }
@@ -49,37 +49,32 @@ export class Example {
49
49
  _hash: '',
50
50
  key: 'table',
51
51
  type: 'ingredients',
52
+ isHead: false,
53
+ isRoot: false,
54
+ isShared: true,
52
55
  columns: {
53
56
  int: {
54
- key: 'int',
55
57
  type: 'number',
56
58
  },
57
59
  double: {
58
- key: 'double',
59
60
  type: 'number',
60
61
  },
61
62
  string: {
62
- key: 'string',
63
63
  type: 'string',
64
64
  },
65
65
  boolean: {
66
- key: 'boolean',
67
66
  type: 'boolean',
68
67
  },
69
68
  null: {
70
- key: 'null',
71
69
  type: 'null',
72
70
  },
73
71
  jsonArray: {
74
- key: 'jsonArray',
75
72
  type: 'jsonArray',
76
73
  },
77
74
  json: {
78
- key: 'json',
79
75
  type: 'json',
80
76
  },
81
77
  jsonValue: {
82
- key: 'jsonValue',
83
78
  type: 'jsonValue',
84
79
  },
85
80
  },
@@ -173,16 +168,23 @@ export class Example {
173
168
  const ingredient1 = ingredients._data[1];
174
169
 
175
170
  const layer0: Layer = hip({
176
- sliceIdsTable: 'sliceIds',
177
- sliceIds: 'MgHRBYSrhpyl4rvsOmAWcQ',
171
+ sliceIds: {
172
+ table: 'sliceIds',
173
+ row: 'MgHRBYSrhpyl4rvsOmAWcQ',
174
+ },
178
175
  ingredientsTable: 'ingredients',
179
- assign: {},
176
+ assign: {
177
+ id0: ingredient0._hash,
178
+ id1: ingredient1._hash,
179
+ },
180
180
  });
181
181
 
182
182
  const layer1: Layer = hip({
183
183
  base: layer0._hash as string,
184
- sliceIdsTable: 'sliceIds',
185
- sliceIds: 'MgHRBYSrhpyl4rvsOmAWcQ',
184
+ sliceIds: {
185
+ table: 'sliceIds',
186
+ row: 'MgHRBYSrhpyl4rvsOmAWcQ',
187
+ },
186
188
  ingredientsTable: 'ingredients',
187
189
  assign: {
188
190
  id0: ingredient0._hash,
@@ -196,8 +198,10 @@ export class Example {
196
198
  } as LayersTable);
197
199
 
198
200
  const cake: Cake = hip({
199
- sliceIdsTable: 'sliceIds',
200
- sliceIds: sliceIds._data[0]._hash as string,
201
+ sliceIds: {
202
+ table: 'sliceIds',
203
+ row: sliceIds._data[0]._hash as string,
204
+ },
201
205
  layersTable: 'layers',
202
206
  layers: {
203
207
  layer0: layer0._hash as string,
@@ -309,7 +313,10 @@ export class Example {
309
313
  const result = Example.ok.singleRow();
310
314
  const tableCfg = result.tableCfgs._data[0];
311
315
  tableCfg.columns['int'].type = 'numberBroken'; // Break one of the types
312
- return hip(result, true, false);
316
+ return hip(result, {
317
+ updateExistingHashes: true,
318
+ throwOnWrongHashes: false,
319
+ });
313
320
  },
314
321
  },
315
322
 
@@ -320,17 +327,23 @@ export class Example {
320
327
  layer1.base = 'MISSING'; // Missing base
321
328
 
322
329
  // Recalculate hashes
323
- return hip(result, true, false);
330
+ return hip(result, {
331
+ updateExistingHashes: true,
332
+ throwOnWrongHashes: false,
333
+ });
324
334
  },
325
335
 
326
336
  missingSliceIdSet: (): Rljson => {
327
337
  const result = Example.ok.complete();
328
338
  const layer1 = result.layers._data[1];
329
339
 
330
- layer1.sliceIds = 'MISSING1';
340
+ layer1.sliceIds.row = 'MISSING1';
331
341
 
332
342
  // Recalculate hashes
333
- return hip(result, true, false);
343
+ return hip(result, {
344
+ updateExistingHashes: true,
345
+ throwOnWrongHashes: false,
346
+ });
334
347
  },
335
348
 
336
349
  missingAssignedIngredientTable: (): Rljson => {
@@ -342,22 +355,40 @@ export class Example {
342
355
  missingAssignedIngredient: (): Rljson => {
343
356
  const result = Example.ok.complete();
344
357
  result.ingredients._data.splice(1, 2); // Remove an ingredient that is assigned
345
- return hip(result, true, false);
358
+ return hip(result, {
359
+ updateExistingHashes: true,
360
+ throwOnWrongHashes: false,
361
+ });
346
362
  },
347
363
  },
348
364
 
349
365
  cakes: {
350
366
  missingSliceIdSet: (): Rljson => {
351
367
  const result = Example.ok.complete();
352
- result.cakes._data[0].sliceIds = 'MISSING'; // Missing ID set
353
- hip(result.cakes, true, false);
368
+ result.cakes._data[0].sliceIds.row = 'MISSING'; // Missing ID set
369
+ hip(result.cakes, {
370
+ updateExistingHashes: true,
371
+ throwOnWrongHashes: false,
372
+ });
373
+
374
+ result.buffets._data[0].items[0].ref = result.cakes._data[0]
375
+ ._hash as string; // Update buffet reference
376
+
377
+ hip(result.buffets, {
378
+ updateExistingHashes: true,
379
+ throwOnWrongHashes: false,
380
+ });
381
+
354
382
  return result;
355
383
  },
356
384
 
357
385
  missingLayersTable: (): Rljson => {
358
386
  const result = Example.ok.complete();
359
387
  result.cakes._data[0].layersTable = 'MISSING'; // Missing layers table
360
- hip(result.cakes, true, false);
388
+ hip(result.cakes, {
389
+ updateExistingHashes: true,
390
+ throwOnWrongHashes: false,
391
+ });
361
392
  return result;
362
393
  },
363
394
 
@@ -365,7 +396,10 @@ export class Example {
365
396
  const result = Example.ok.complete();
366
397
  result.cakes._data[0].layers['layer0'] = 'MISSING0';
367
398
  result.cakes._data[0].layers['layer1'] = 'MISSING1';
368
- hip(result.cakes, true, false);
399
+ hip(result.cakes, {
400
+ updateExistingHashes: true,
401
+ throwOnWrongHashes: false,
402
+ });
369
403
  return result;
370
404
  },
371
405
  },
@@ -376,7 +410,7 @@ export class Example {
376
410
  const buffet = result.buffets._data[0];
377
411
  buffet.items[0].table = 'MISSING0';
378
412
  buffet.items[1].table = 'MISSING1';
379
- hip(result, true, false);
413
+ hip(result, { updateExistingHashes: true, throwOnWrongHashes: false });
380
414
  return result;
381
415
  },
382
416
 
@@ -385,7 +419,7 @@ export class Example {
385
419
  const buffet = result.buffets._data[0];
386
420
  buffet.items[0].ref = 'MISSING0';
387
421
  buffet.items[1].ref = 'MISSING1';
388
- hip(result, true, false);
422
+ hip(result, { updateExistingHashes: true, throwOnWrongHashes: false });
389
423
  return result;
390
424
  },
391
425
  },
@@ -1,4 +1,4 @@
1
- import { JsonKey } from '@rljson/json';
1
+ import { Json, JsonKey } from '@rljson/json';
2
2
  /**
3
3
  * A ref is a hash that references to another element
4
4
  */
@@ -37,3 +37,9 @@ export declare const exampleTypedefs: () => {
37
37
  tableKey: TableKey;
38
38
  contentType: ContentType;
39
39
  };
40
+ /**
41
+ * A json object with an optional id
42
+ */
43
+ export type JsonWithId = Json & {
44
+ id?: string;
45
+ };
@@ -16,12 +16,16 @@ export interface BaseErrors extends Errors {
16
16
  columnConfigNotFound?: Json;
17
17
  dataDoesNotMatchColumnConfig?: Json;
18
18
  tableTypesDoNotMatch?: Json;
19
+ rootOrHeadTableHasNoIdColumn?: Json;
20
+ tableCfgHasRootHeadSharedError?: Json;
19
21
  refsNotFound?: Json;
20
22
  layerBasesNotFound?: Json;
23
+ layerSliceIdsGivenButNoTable?: Json;
21
24
  layerSliceIdsTableNotFound?: Json;
22
- layerSliceIdsNotFound?: Json;
25
+ layerSliceIdsRowNotFound?: Json;
23
26
  layerIngredientTablesNotFound?: Json;
24
27
  layerIngredientAssignmentsNotFound?: Json;
28
+ layerAssignmentsDoNotMatchSliceIds?: Json;
25
29
  cakeSliceIdsTableNotFound?: Json;
26
30
  cakeSliceIdsNotFound?: Json;
27
31
  cakeLayerTablesNotFound?: Json;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rljson/rljson",
3
- "version": "0.0.33",
3
+ "version": "0.0.36",
4
4
  "packageManager": "pnpm@10.6.3",
5
5
  "description": "The RLJSON data format specification",
6
6
  "homepage": "https://github.com/rljson/rljson",
@@ -29,10 +29,10 @@
29
29
  "updateGoldens": "cross-env UPDATE_GOLDENS=true npm test"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^22.13.14",
33
- "@typescript-eslint/eslint-plugin": "^8.28.0",
34
- "@typescript-eslint/parser": "^8.28.0",
35
- "@vitest/coverage-v8": "^3.0.9",
32
+ "@types/node": "^22.13.17",
33
+ "@typescript-eslint/eslint-plugin": "^8.29.0",
34
+ "@typescript-eslint/parser": "^8.29.0",
35
+ "@vitest/coverage-v8": "^3.1.1",
36
36
  "cross-env": "^7.0.3",
37
37
  "eslint": "^9.23.0",
38
38
  "eslint-plugin-jsdoc": "^50.6.9",
@@ -41,16 +41,16 @@
41
41
  "jsdoc": "^4.0.4",
42
42
  "read-pkg": "^9.0.1",
43
43
  "typescript": "~5.8.2",
44
- "typescript-eslint": "^8.28.0",
45
- "vite": "^6.2.3",
46
- "vite-node": "^3.0.9",
44
+ "typescript-eslint": "^8.29.0",
45
+ "vite": "^6.2.4",
46
+ "vite-node": "^3.1.1",
47
47
  "vite-plugin-dts": "^4.5.3",
48
48
  "vite-tsconfig-paths": "^5.1.4",
49
- "vitest": "^3.0.9",
49
+ "vitest": "^3.1.1",
50
50
  "vitest-dom": "^0.1.1"
51
51
  },
52
52
  "dependencies": {
53
- "@rljson/hash": "^0.0.12",
53
+ "@rljson/hash": "^0.0.13",
54
54
  "@rljson/json": "^0.0.18"
55
55
  },
56
56
  "pnpm": {