@rljson/io 0.0.30 → 0.0.31
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/conformance-tests/goldens/dump/empty.json +105 -0
- package/dist/conformance-tests/goldens/dump/two-tables.json +165 -0
- package/dist/conformance-tests/goldens/dumpTable/table1.json +12 -0
- package/dist/conformance-tests/goldens/tableCfgs.json +99 -0
- package/dist/conformance-tests/io-conformance.spec.ts +885 -0
- package/dist/conformance-tests/scripts/install-conformance-tests.js +61 -0
- package/package.json +2 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_hash": "h9UNtoBx3lvHU4yzAyz10M",
|
|
3
|
+
"tableCfgs": {
|
|
4
|
+
"_data": [
|
|
5
|
+
{
|
|
6
|
+
"_hash": "oS4O57K1hbZY9hQzRID9nO",
|
|
7
|
+
"key": "tableCfgs",
|
|
8
|
+
"type": "ingredients",
|
|
9
|
+
"isHead": false,
|
|
10
|
+
"isRoot": false,
|
|
11
|
+
"isShared": true,
|
|
12
|
+
"previous": "",
|
|
13
|
+
"columns": [
|
|
14
|
+
{
|
|
15
|
+
"key": "_hash",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "key",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"_hash": "tapEY-QdaJwWhz1PPZoG35"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"key": "type",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"_hash": "zE7ALsNzu5JPgDD4AxeRCh"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"key": "isHead",
|
|
31
|
+
"type": "boolean",
|
|
32
|
+
"_hash": "ar8Hajt2UZDn-F21QLMyC_"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"key": "isRoot",
|
|
36
|
+
"type": "boolean",
|
|
37
|
+
"_hash": "hedsCm_HtiiTuzJyTeMqhm"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"key": "isShared",
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"_hash": "pxXOf3-3qCo9Z3FzC9JXSH"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "previous",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"_hash": "e0TEeDmSvJcLEMtrVxc0oW"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"key": "columns",
|
|
51
|
+
"type": "jsonArray",
|
|
52
|
+
"_hash": "aPmGTcNng9E1RvlEj1LtxB"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"key": "revisions",
|
|
58
|
+
"type": "ingredients",
|
|
59
|
+
"isHead": true,
|
|
60
|
+
"isRoot": true,
|
|
61
|
+
"isShared": false,
|
|
62
|
+
"columns": [
|
|
63
|
+
{
|
|
64
|
+
"key": "_hash",
|
|
65
|
+
"type": "string",
|
|
66
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"key": "table",
|
|
70
|
+
"type": "string",
|
|
71
|
+
"_hash": "FmsYjsit04XJi02Sihdwl-"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"key": "predecessor",
|
|
75
|
+
"type": "string",
|
|
76
|
+
"_hash": "RlAk7Lj-lZf2KDlUk5fekZ"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"key": "successor",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"_hash": "MJlFtQVty5oUJKVzzj5hyB"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"key": "timestamp",
|
|
85
|
+
"type": "number",
|
|
86
|
+
"_hash": "XpKYGh23thW6vONgQuoMHf"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"key": "id",
|
|
90
|
+
"type": "string",
|
|
91
|
+
"_hash": "eq2P3RIaSgy81i7PomNUvn"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"_hash": "CubBZQUGTMLa5wMqqzHLXz"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"_tableCfg": "oS4O57K1hbZY9hQzRID9nO",
|
|
98
|
+
"_hash": "elFe1LT7JfBOyJnhHO1nm2"
|
|
99
|
+
},
|
|
100
|
+
"revisions": {
|
|
101
|
+
"_data": [],
|
|
102
|
+
"_tableCfg": "CubBZQUGTMLa5wMqqzHLXz",
|
|
103
|
+
"_hash": "dppr2oAzB9-npmA3E7ZLNI"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_hash": "h9UNtoBx3lvHU4yzAyz10M",
|
|
3
|
+
"tableCfgs": {
|
|
4
|
+
"_data": [
|
|
5
|
+
{
|
|
6
|
+
"_hash": "oS4O57K1hbZY9hQzRID9nO",
|
|
7
|
+
"key": "tableCfgs",
|
|
8
|
+
"type": "ingredients",
|
|
9
|
+
"isHead": false,
|
|
10
|
+
"isRoot": false,
|
|
11
|
+
"isShared": true,
|
|
12
|
+
"previous": "",
|
|
13
|
+
"columns": [
|
|
14
|
+
{
|
|
15
|
+
"key": "_hash",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"key": "key",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"_hash": "tapEY-QdaJwWhz1PPZoG35"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"key": "type",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"_hash": "zE7ALsNzu5JPgDD4AxeRCh"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"key": "isHead",
|
|
31
|
+
"type": "boolean",
|
|
32
|
+
"_hash": "ar8Hajt2UZDn-F21QLMyC_"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"key": "isRoot",
|
|
36
|
+
"type": "boolean",
|
|
37
|
+
"_hash": "hedsCm_HtiiTuzJyTeMqhm"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"key": "isShared",
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"_hash": "pxXOf3-3qCo9Z3FzC9JXSH"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "previous",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"_hash": "e0TEeDmSvJcLEMtrVxc0oW"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"key": "columns",
|
|
51
|
+
"type": "jsonArray",
|
|
52
|
+
"_hash": "aPmGTcNng9E1RvlEj1LtxB"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"key": "revisions",
|
|
58
|
+
"type": "ingredients",
|
|
59
|
+
"isHead": true,
|
|
60
|
+
"isRoot": true,
|
|
61
|
+
"isShared": false,
|
|
62
|
+
"columns": [
|
|
63
|
+
{
|
|
64
|
+
"key": "_hash",
|
|
65
|
+
"type": "string",
|
|
66
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"key": "table",
|
|
70
|
+
"type": "string",
|
|
71
|
+
"_hash": "FmsYjsit04XJi02Sihdwl-"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"key": "predecessor",
|
|
75
|
+
"type": "string",
|
|
76
|
+
"_hash": "RlAk7Lj-lZf2KDlUk5fekZ"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"key": "successor",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"_hash": "MJlFtQVty5oUJKVzzj5hyB"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"key": "timestamp",
|
|
85
|
+
"type": "number",
|
|
86
|
+
"_hash": "XpKYGh23thW6vONgQuoMHf"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"key": "id",
|
|
90
|
+
"type": "string",
|
|
91
|
+
"_hash": "eq2P3RIaSgy81i7PomNUvn"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"_hash": "CubBZQUGTMLa5wMqqzHLXz"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"key": "table1",
|
|
98
|
+
"columns": [
|
|
99
|
+
{
|
|
100
|
+
"key": "_hash",
|
|
101
|
+
"type": "string",
|
|
102
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"key": "a",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"_hash": "4sOy83iAANZEj6_Lmlg0d3"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"key": "b",
|
|
111
|
+
"type": "number",
|
|
112
|
+
"_hash": "dHYaVsRiXeP_WY7Qrd5Y8J"
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"type": "ingredients",
|
|
116
|
+
"isHead": true,
|
|
117
|
+
"isRoot": true,
|
|
118
|
+
"isShared": false,
|
|
119
|
+
"_hash": "LM5fm8eNChH3kE3D38X0Fa"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"key": "table2",
|
|
123
|
+
"columns": [
|
|
124
|
+
{
|
|
125
|
+
"key": "_hash",
|
|
126
|
+
"type": "string",
|
|
127
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"key": "a",
|
|
131
|
+
"type": "string",
|
|
132
|
+
"_hash": "4sOy83iAANZEj6_Lmlg0d3"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"key": "b",
|
|
136
|
+
"type": "number",
|
|
137
|
+
"_hash": "dHYaVsRiXeP_WY7Qrd5Y8J"
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"type": "ingredients",
|
|
141
|
+
"isHead": true,
|
|
142
|
+
"isRoot": true,
|
|
143
|
+
"isShared": false,
|
|
144
|
+
"_hash": "GGPEyV5H-4C3fZr_emK8iC"
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"_tableCfg": "oS4O57K1hbZY9hQzRID9nO",
|
|
148
|
+
"_hash": "elFe1LT7JfBOyJnhHO1nm2"
|
|
149
|
+
},
|
|
150
|
+
"revisions": {
|
|
151
|
+
"_data": [],
|
|
152
|
+
"_tableCfg": "CubBZQUGTMLa5wMqqzHLXz",
|
|
153
|
+
"_hash": "dppr2oAzB9-npmA3E7ZLNI"
|
|
154
|
+
},
|
|
155
|
+
"table1": {
|
|
156
|
+
"_data": [],
|
|
157
|
+
"_tableCfg": "LM5fm8eNChH3kE3D38X0Fa",
|
|
158
|
+
"_hash": "jpM5ZNlmmoojoYcdPN7htq"
|
|
159
|
+
},
|
|
160
|
+
"table2": {
|
|
161
|
+
"_data": [],
|
|
162
|
+
"_tableCfg": "GGPEyV5H-4C3fZr_emK8iC",
|
|
163
|
+
"_hash": "yCGKKwr92WWo7rdRSUzon9"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tableCfgs": {
|
|
3
|
+
"_data": [
|
|
4
|
+
{
|
|
5
|
+
"_hash": "oS4O57K1hbZY9hQzRID9nO",
|
|
6
|
+
"key": "tableCfgs",
|
|
7
|
+
"type": "ingredients",
|
|
8
|
+
"isHead": false,
|
|
9
|
+
"isRoot": false,
|
|
10
|
+
"isShared": true,
|
|
11
|
+
"previous": "",
|
|
12
|
+
"columns": [
|
|
13
|
+
{
|
|
14
|
+
"key": "_hash",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"key": "key",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"_hash": "tapEY-QdaJwWhz1PPZoG35"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "type",
|
|
25
|
+
"type": "string",
|
|
26
|
+
"_hash": "zE7ALsNzu5JPgDD4AxeRCh"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"key": "isHead",
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"_hash": "ar8Hajt2UZDn-F21QLMyC_"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"key": "isRoot",
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"_hash": "hedsCm_HtiiTuzJyTeMqhm"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"key": "isShared",
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"_hash": "pxXOf3-3qCo9Z3FzC9JXSH"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"key": "previous",
|
|
45
|
+
"type": "string",
|
|
46
|
+
"_hash": "e0TEeDmSvJcLEMtrVxc0oW"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"key": "columns",
|
|
50
|
+
"type": "jsonArray",
|
|
51
|
+
"_hash": "aPmGTcNng9E1RvlEj1LtxB"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"key": "revisions",
|
|
57
|
+
"type": "ingredients",
|
|
58
|
+
"isHead": true,
|
|
59
|
+
"isRoot": true,
|
|
60
|
+
"isShared": false,
|
|
61
|
+
"columns": [
|
|
62
|
+
{
|
|
63
|
+
"key": "_hash",
|
|
64
|
+
"type": "string",
|
|
65
|
+
"_hash": "df4oYftB-71Njv9FprRCeg"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"key": "table",
|
|
69
|
+
"type": "string",
|
|
70
|
+
"_hash": "FmsYjsit04XJi02Sihdwl-"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"key": "predecessor",
|
|
74
|
+
"type": "string",
|
|
75
|
+
"_hash": "RlAk7Lj-lZf2KDlUk5fekZ"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"key": "successor",
|
|
79
|
+
"type": "string",
|
|
80
|
+
"_hash": "MJlFtQVty5oUJKVzzj5hyB"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"key": "timestamp",
|
|
84
|
+
"type": "number",
|
|
85
|
+
"_hash": "XpKYGh23thW6vONgQuoMHf"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"key": "id",
|
|
89
|
+
"type": "string",
|
|
90
|
+
"_hash": "eq2P3RIaSgy81i7PomNUvn"
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"_hash": "CubBZQUGTMLa5wMqqzHLXz"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"_tableCfg": "oS4O57K1hbZY9hQzRID9nO",
|
|
97
|
+
"_hash": "elFe1LT7JfBOyJnhHO1nm2"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
// @license
|
|
2
|
+
// Copyright (c) 2025 Rljson
|
|
3
|
+
//
|
|
4
|
+
// Use of this source code is governed by terms that can be
|
|
5
|
+
// found in the LICENSE file in the root of this package.
|
|
6
|
+
|
|
7
|
+
// ⚠️ DO NOT MODIFY THIS FILE DIRECTLY ⚠️
|
|
8
|
+
//
|
|
9
|
+
// This file is a copy of @rljson/io/test/io-conformance.spec.ts.
|
|
10
|
+
//
|
|
11
|
+
// To make changes, please execute the following steps:
|
|
12
|
+
// 1. Clone <https://github.com/rljson/io>
|
|
13
|
+
// 2. Make changes to the original file in the test folder
|
|
14
|
+
// 3. Submit a pull request
|
|
15
|
+
// 4. Publish a the new changes to npm
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import { hip, rmhsh } from '@rljson/hash';
|
|
19
|
+
import {
|
|
20
|
+
addColumnsToTableCfg, exampleTableCfg, IngredientsTable, Rljson, TableCfg, TableType
|
|
21
|
+
} from '@rljson/rljson';
|
|
22
|
+
|
|
23
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
24
|
+
|
|
25
|
+
import { Io, IoTestSetup, IoTools, testSetup } from './io-conformance.setup.ts';
|
|
26
|
+
import { expectGolden, ExpectGoldenOptions } from './setup/goldens.ts';
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const ego: ExpectGoldenOptions = {
|
|
30
|
+
npmUpdateGoldensEnabled: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const runIoConformanceTests = () => {
|
|
34
|
+
return describe('Io Conformance', async () => {
|
|
35
|
+
let io: Io;
|
|
36
|
+
let ioTools: IoTools;
|
|
37
|
+
let setup: IoTestSetup;
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
setup = testSetup();
|
|
41
|
+
await setup.init();
|
|
42
|
+
io = setup.io;
|
|
43
|
+
await io.init();
|
|
44
|
+
await io.isReady();
|
|
45
|
+
ioTools = new IoTools(io);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
await io.close();
|
|
50
|
+
await setup.tearDown();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('isReady()', () => {
|
|
54
|
+
it('should return a resolved promise', async () => {
|
|
55
|
+
await io.isReady();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const createExampleTable = async (key: string) => {
|
|
60
|
+
// Register a new table config and generate the table
|
|
61
|
+
const tableCfg: TableCfg = exampleTableCfg({ key });
|
|
62
|
+
try {
|
|
63
|
+
await io.createOrExtendTable({ tableCfg: tableCfg });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw error; // Re-throw the error after logging it
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
describe('tableCfgs table', () => {
|
|
70
|
+
it('should be available after isReady() resolves', async () => {
|
|
71
|
+
const dump = await io.dumpTable({ table: 'tableCfgs' });
|
|
72
|
+
await expectGolden('io-conformance/tableCfgs.json', ego).toBe(dump);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('tableCfgs()', () => {
|
|
77
|
+
it('returns an rljson object containing the newest config for each table', async () => {
|
|
78
|
+
//create four tables with two versions each
|
|
79
|
+
const tableV0: TableCfg = {
|
|
80
|
+
key: 'table0',
|
|
81
|
+
type: 'ingredients',
|
|
82
|
+
isHead: false,
|
|
83
|
+
isRoot: false,
|
|
84
|
+
isShared: true,
|
|
85
|
+
columns: [
|
|
86
|
+
{ key: '_hash', type: 'string' },
|
|
87
|
+
{ key: 'col0', type: 'string' },
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const tableV1 = addColumnsToTableCfg(tableV0, [
|
|
92
|
+
{ key: 'col1', type: 'string' },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const tableV2 = addColumnsToTableCfg(tableV1, [
|
|
96
|
+
{ key: 'col2', type: 'string' },
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
await io.createOrExtendTable({ tableCfg: tableV0 });
|
|
100
|
+
await io.createOrExtendTable({ tableCfg: tableV1 });
|
|
101
|
+
await io.createOrExtendTable({ tableCfg: tableV2 });
|
|
102
|
+
|
|
103
|
+
// Check the tableCfgs
|
|
104
|
+
const actualTableCfgs = (await io.tableCfgs()).tableCfgs
|
|
105
|
+
._data as unknown as TableCfg[];
|
|
106
|
+
|
|
107
|
+
expect(actualTableCfgs.length).toBe(3);
|
|
108
|
+
expect((actualTableCfgs[0] as TableCfg).key).toBe('tableCfgs');
|
|
109
|
+
expect((actualTableCfgs[1] as TableCfg).key).toBe('revisions');
|
|
110
|
+
expect((actualTableCfgs[2] as TableCfg).key).toBe('table0');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('throws an error', async () => {
|
|
115
|
+
it('if the hashes in the tableCfg are wrong', async () => {
|
|
116
|
+
const tableCfg: TableCfg = hip(exampleTableCfg({ key: 'table1' }));
|
|
117
|
+
tableCfg._hash = 'wrongHash';
|
|
118
|
+
let message: string = '';
|
|
119
|
+
try {
|
|
120
|
+
await io.createOrExtendTable({ tableCfg: tableCfg });
|
|
121
|
+
} catch (err: any) {
|
|
122
|
+
message = err.message;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
expect(message).toBe(
|
|
126
|
+
'Hash "wrongHash" does not match the newly calculated one "LM5fm8eNChH3kE3D38X0Fa". ' +
|
|
127
|
+
'Please make sure that all systems are producing the same hashes.',
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('tableExists(tableKey)', () => {
|
|
133
|
+
it('returns true if the table exists', async () => {
|
|
134
|
+
await createExampleTable('table1');
|
|
135
|
+
const exists = await io.tableExists('table1');
|
|
136
|
+
expect(exists).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('returns false if the table does not exist', async () => {
|
|
140
|
+
const exists = await io.tableExists('nonexistentTable');
|
|
141
|
+
expect(exists).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('createOrExtendTable(request)', () => {
|
|
146
|
+
let existing: TableCfg;
|
|
147
|
+
beforeEach(async () => {
|
|
148
|
+
existing = exampleTableCfg();
|
|
149
|
+
await io.createOrExtendTable({ tableCfg: existing });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('throws an error', () => {
|
|
153
|
+
it('if the hashes in the tableCfg are wrong', async () => {
|
|
154
|
+
const tableCfg: TableCfg = hip(exampleTableCfg({ key: 'table' }));
|
|
155
|
+
const rightHash = tableCfg._hash;
|
|
156
|
+
tableCfg._hash = 'wrongHash';
|
|
157
|
+
let message: string = '';
|
|
158
|
+
try {
|
|
159
|
+
await io.createOrExtendTable({ tableCfg: tableCfg });
|
|
160
|
+
} catch (err: any) {
|
|
161
|
+
message = err.message;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
expect(message).toBe(
|
|
165
|
+
`Hash "wrongHash" does not match the newly calculated one "${rightHash}". ` +
|
|
166
|
+
'Please make sure that all systems are producing the same hashes.',
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('when the update has invalid column types', async () => {
|
|
171
|
+
const update = exampleTableCfg({
|
|
172
|
+
...existing,
|
|
173
|
+
columns: [
|
|
174
|
+
...existing.columns,
|
|
175
|
+
{
|
|
176
|
+
key: 'x',
|
|
177
|
+
type: 'unknown' as any,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await expect(
|
|
183
|
+
io.createOrExtendTable({ tableCfg: update }),
|
|
184
|
+
).rejects.toThrow(
|
|
185
|
+
'Invalid table configuration: Column "x" of table "table" has an unsupported type "unknown"',
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('when the update has deleted columns', async () => {
|
|
190
|
+
const update = {
|
|
191
|
+
...existing,
|
|
192
|
+
columns: [existing.columns[0], existing.columns[1]],
|
|
193
|
+
};
|
|
194
|
+
await expect(
|
|
195
|
+
io.createOrExtendTable({ tableCfg: update }),
|
|
196
|
+
).rejects.toThrow(
|
|
197
|
+
'Invalid update of table able "table": ' +
|
|
198
|
+
'Columns must not be deleted. Deleted columns: b',
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('when column keys have changed', async () => {
|
|
203
|
+
const update = {
|
|
204
|
+
...existing,
|
|
205
|
+
columns: [
|
|
206
|
+
{ ...existing.columns[0], key: '_hash' },
|
|
207
|
+
{ ...existing.columns[1], key: 'b' },
|
|
208
|
+
{ ...existing.columns[2], key: 'a' },
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
await expect(
|
|
212
|
+
io.createOrExtendTable({ tableCfg: update }),
|
|
213
|
+
).rejects.toThrow(
|
|
214
|
+
'Invalid update of table able "table": Column keys must not change! ' +
|
|
215
|
+
'Column "a" was renamed into "b".',
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('when column types have changed', async () => {
|
|
220
|
+
const update = {
|
|
221
|
+
...existing,
|
|
222
|
+
columns: [
|
|
223
|
+
{ ...existing.columns[0], type: 'string' },
|
|
224
|
+
{ ...existing.columns[1], type: 'boolean' },
|
|
225
|
+
{ ...existing.columns[2], type: 'number' },
|
|
226
|
+
],
|
|
227
|
+
} as TableCfg;
|
|
228
|
+
await expect(
|
|
229
|
+
io.createOrExtendTable({ tableCfg: update }),
|
|
230
|
+
).rejects.toThrow(
|
|
231
|
+
'Invalid update of table able "table": ' +
|
|
232
|
+
'Column types must not change! ' +
|
|
233
|
+
'Type of column "a" was changed from "string" to boolean.',
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should add a table and a table config', async () => {
|
|
239
|
+
const tablesFromConfig = async () => {
|
|
240
|
+
const tables = (await io.tableCfgs())
|
|
241
|
+
.tableCfgs as IngredientsTable<TableCfg>;
|
|
242
|
+
|
|
243
|
+
return tables._data.map((e) => e.key);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const physicalTables = async () => await ioTools.allTableKeys();
|
|
247
|
+
|
|
248
|
+
// Create a first table
|
|
249
|
+
await createExampleTable('table1');
|
|
250
|
+
|
|
251
|
+
expect(await tablesFromConfig()).toEqual([
|
|
252
|
+
'tableCfgs',
|
|
253
|
+
'revisions',
|
|
254
|
+
'table',
|
|
255
|
+
'table1',
|
|
256
|
+
]);
|
|
257
|
+
expect(await physicalTables()).toEqual([
|
|
258
|
+
'tableCfgs',
|
|
259
|
+
'revisions',
|
|
260
|
+
'table',
|
|
261
|
+
'table1',
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
// Create a second table
|
|
265
|
+
await createExampleTable('table2');
|
|
266
|
+
expect(await tablesFromConfig()).toEqual([
|
|
267
|
+
'tableCfgs',
|
|
268
|
+
'revisions',
|
|
269
|
+
'table',
|
|
270
|
+
'table1',
|
|
271
|
+
'table2',
|
|
272
|
+
]);
|
|
273
|
+
expect(await physicalTables()).toEqual([
|
|
274
|
+
'tableCfgs',
|
|
275
|
+
'revisions',
|
|
276
|
+
'table',
|
|
277
|
+
'table1',
|
|
278
|
+
'table2',
|
|
279
|
+
]);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should do nothing when the columns do not have changed', async () => {
|
|
283
|
+
const exampleCfg: TableCfg = exampleTableCfg({ key: 'tableA' });
|
|
284
|
+
await io.createOrExtendTable({ tableCfg: exampleCfg });
|
|
285
|
+
|
|
286
|
+
// Check state before
|
|
287
|
+
const dumpBefore = await io.dumpTable({ table: 'tableA' });
|
|
288
|
+
|
|
289
|
+
// Add same table config again
|
|
290
|
+
await io.createOrExtendTable({ tableCfg: exampleCfg });
|
|
291
|
+
|
|
292
|
+
// Dump again, should be the same
|
|
293
|
+
const dumpAfter = await io.dumpTable({ table: 'tableA' });
|
|
294
|
+
expect(dumpBefore).toEqual(dumpAfter);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should extend an existing table', async () => {
|
|
298
|
+
// Create a first table
|
|
299
|
+
const tableCfg: TableCfg = exampleTableCfg({ key: 'tableA' });
|
|
300
|
+
await io.createOrExtendTable({ tableCfg });
|
|
301
|
+
await io.write({
|
|
302
|
+
data: {
|
|
303
|
+
tableA: {
|
|
304
|
+
_data: [{ a: 'hello', b: 5 }],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Check the table content before
|
|
310
|
+
const dump = rmhsh(await io.dumpTable({ table: 'tableA' }));
|
|
311
|
+
const dumpExpected = {
|
|
312
|
+
tableA: {
|
|
313
|
+
_data: [
|
|
314
|
+
{
|
|
315
|
+
a: 'hello',
|
|
316
|
+
b: 5,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
_tableCfg: 'MfpwQygnDmu3ISp6dBjsEf',
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
expect(dump).toEqual(dumpExpected);
|
|
323
|
+
|
|
324
|
+
// Update the table by adding a new column
|
|
325
|
+
const tableCfg2 = addColumnsToTableCfg(tableCfg, [
|
|
326
|
+
{ key: 'keyA1', type: 'string' },
|
|
327
|
+
{ key: 'keyA2', type: 'string' },
|
|
328
|
+
{ key: 'keyB2', type: 'string' },
|
|
329
|
+
]);
|
|
330
|
+
|
|
331
|
+
await io.createOrExtendTable({ tableCfg: tableCfg2 });
|
|
332
|
+
|
|
333
|
+
// Check the table contents after.
|
|
334
|
+
const dump2 = rmhsh(await io.dumpTable({ table: 'tableA' }));
|
|
335
|
+
|
|
336
|
+
// Only the hash of the table config has changed
|
|
337
|
+
expect(dump.tableA._tableCfg).not.toEqual(dump2.tableA._tableCfg);
|
|
338
|
+
|
|
339
|
+
const dumpExpected2 = {
|
|
340
|
+
...dumpExpected,
|
|
341
|
+
tableA: {
|
|
342
|
+
...dumpExpected.tableA,
|
|
343
|
+
_tableCfg: dump2.tableA._tableCfg,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
expect(dump2).toEqual(dumpExpected2);
|
|
348
|
+
|
|
349
|
+
// Now add a new row adding
|
|
350
|
+
await io.write({
|
|
351
|
+
data: {
|
|
352
|
+
tableA: {
|
|
353
|
+
_data: [{ keyA1: 'a1', keyA2: 'a2', keyB2: 'b2' }],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Check the table contents after. It has an additional row.
|
|
359
|
+
const dump3 = rmhsh(await io.dumpTable({ table: 'tableA' }));
|
|
360
|
+
expect(dump3).toEqual({
|
|
361
|
+
tableA: {
|
|
362
|
+
_data: [
|
|
363
|
+
{
|
|
364
|
+
a: 'hello',
|
|
365
|
+
b: 5,
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
keyA1: 'a1',
|
|
369
|
+
keyA2: 'a2',
|
|
370
|
+
keyB2: 'b2',
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
_tableCfg: 'swD0rJhzryBIY7sfxIV8Gl',
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('write(request)', async () => {
|
|
380
|
+
it('adds data to existing data', async () => {
|
|
381
|
+
const exampleCfg: TableCfg = exampleTableCfg({ key: 'tableA' });
|
|
382
|
+
const tableCfg: TableCfg = {
|
|
383
|
+
...exampleCfg,
|
|
384
|
+
columns: [
|
|
385
|
+
{ key: '_hash', type: 'string' },
|
|
386
|
+
{ key: 'keyA1', type: 'string' },
|
|
387
|
+
{ key: 'keyA2', type: 'string' },
|
|
388
|
+
{ key: 'keyB2', type: 'string' },
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
await io.createOrExtendTable({ tableCfg });
|
|
393
|
+
const allTableKeys = await ioTools.allTableKeys();
|
|
394
|
+
expect(allTableKeys).toContain('tableA');
|
|
395
|
+
|
|
396
|
+
expect('tableA').toBe(tableCfg.key);
|
|
397
|
+
|
|
398
|
+
// Write a first item
|
|
399
|
+
await io.write({
|
|
400
|
+
data: {
|
|
401
|
+
tableA: {
|
|
402
|
+
_data: [{ keyA2: 'a2' }],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
expect(await io.rowCount('tableA')).toEqual(1);
|
|
408
|
+
|
|
409
|
+
const dump = await io.dump();
|
|
410
|
+
const items = (dump.tableA as TableType)._data;
|
|
411
|
+
expect(items).toEqual([
|
|
412
|
+
{
|
|
413
|
+
_hash: 'apLP3I2XLnVm13umIZdVhV',
|
|
414
|
+
keyA2: 'a2',
|
|
415
|
+
},
|
|
416
|
+
]);
|
|
417
|
+
|
|
418
|
+
// Write a second item
|
|
419
|
+
await io.write({
|
|
420
|
+
data: {
|
|
421
|
+
tableA: {
|
|
422
|
+
_data: [{ keyB2: 'b2' }],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const dump2 = await io.dump();
|
|
428
|
+
const items2 = (dump2.tableA as TableType)._data;
|
|
429
|
+
expect(items2).toEqual([
|
|
430
|
+
{
|
|
431
|
+
_hash: 'apLP3I2XLnVm13umIZdVhV',
|
|
432
|
+
keyA2: 'a2',
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
_hash: 'oNNJMCE_2iycGPDyM_5_lp',
|
|
436
|
+
keyB2: 'b2',
|
|
437
|
+
},
|
|
438
|
+
]);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('does not add the same data twice', async () => {
|
|
442
|
+
const tableName = 'testTable';
|
|
443
|
+
const exampleCfg: TableCfg = exampleTableCfg({ key: tableName });
|
|
444
|
+
const tableCfg: TableCfg = {
|
|
445
|
+
...exampleCfg,
|
|
446
|
+
columns: [
|
|
447
|
+
{ key: '_hash', type: 'string' },
|
|
448
|
+
{ key: 'string', type: 'string' },
|
|
449
|
+
{ key: 'number', type: 'number' },
|
|
450
|
+
{ key: 'null', type: 'string' },
|
|
451
|
+
{ key: 'boolean', type: 'boolean' },
|
|
452
|
+
{ key: 'array', type: 'jsonArray' },
|
|
453
|
+
{ key: 'object', type: 'json' },
|
|
454
|
+
],
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
await io.createOrExtendTable({ tableCfg });
|
|
458
|
+
const allTableKeys = await ioTools.allTableKeys();
|
|
459
|
+
expect(allTableKeys).toContain(tableName);
|
|
460
|
+
expect(await ioTools.allColumnKeys(tableName)).toEqual([
|
|
461
|
+
'_hash',
|
|
462
|
+
'string',
|
|
463
|
+
'number',
|
|
464
|
+
'null',
|
|
465
|
+
'boolean',
|
|
466
|
+
'array',
|
|
467
|
+
'object',
|
|
468
|
+
]);
|
|
469
|
+
|
|
470
|
+
const rows = [
|
|
471
|
+
{
|
|
472
|
+
string: 'hello',
|
|
473
|
+
number: 5,
|
|
474
|
+
null: null,
|
|
475
|
+
boolean: true,
|
|
476
|
+
array: [1, 2, { a: 10 }],
|
|
477
|
+
object: { a: 1, b: { c: 3 } },
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
string: 'world',
|
|
481
|
+
number: 6,
|
|
482
|
+
null: null,
|
|
483
|
+
boolean: true,
|
|
484
|
+
array: [1, 2, { a: 10 }],
|
|
485
|
+
object: { a: 1, b: 2 },
|
|
486
|
+
},
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
const testData: Rljson = {
|
|
490
|
+
testTable: {
|
|
491
|
+
_data: rows,
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// Get row count before
|
|
496
|
+
const rowCountBefore = await io.rowCount(tableName);
|
|
497
|
+
expect(rowCountBefore).toEqual(0);
|
|
498
|
+
|
|
499
|
+
// Write first two rows
|
|
500
|
+
await io.write({ data: testData });
|
|
501
|
+
const rowCountAfterFirstWrite = await io.rowCount(tableName);
|
|
502
|
+
expect(rowCountAfterFirstWrite).toEqual(2);
|
|
503
|
+
|
|
504
|
+
// Write the same item again
|
|
505
|
+
expect(io.write({ data: testData }));
|
|
506
|
+
|
|
507
|
+
// Nothing changes because the data is the same
|
|
508
|
+
const rowCountAfterSecondWrite = await io.rowCount(tableName);
|
|
509
|
+
expect(rowCountAfterSecondWrite).toEqual(rowCountAfterFirstWrite);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe('throws', () => {
|
|
513
|
+
it('when table does not exist', async () => {
|
|
514
|
+
await expect(
|
|
515
|
+
io.write({
|
|
516
|
+
data: {
|
|
517
|
+
tableA: {
|
|
518
|
+
_data: [{ keyA2: 'a2' }],
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
}),
|
|
522
|
+
).rejects.toThrow('The following tables do not exist: tableA');
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
describe('readRows({table, where})', async () => {
|
|
528
|
+
describe('should return rows matching the where clause', async () => {
|
|
529
|
+
beforeEach(async () => {
|
|
530
|
+
const tableName = 'testTable';
|
|
531
|
+
const exampleCfg: TableCfg = exampleTableCfg({ key: tableName });
|
|
532
|
+
const tableCfg: TableCfg = {
|
|
533
|
+
...exampleCfg,
|
|
534
|
+
columns: [
|
|
535
|
+
{ key: '_hash', type: 'string' },
|
|
536
|
+
{ key: 'string', type: 'string' },
|
|
537
|
+
{ key: 'number', type: 'number' },
|
|
538
|
+
{ key: 'null', type: 'string' },
|
|
539
|
+
{ key: 'boolean', type: 'boolean' },
|
|
540
|
+
{ key: 'array', type: 'jsonArray' },
|
|
541
|
+
{ key: 'object', type: 'json' },
|
|
542
|
+
],
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
await io.createOrExtendTable({ tableCfg });
|
|
546
|
+
|
|
547
|
+
const testData: Rljson = {
|
|
548
|
+
testTable: {
|
|
549
|
+
_data: [
|
|
550
|
+
{
|
|
551
|
+
string: 'hello',
|
|
552
|
+
number: 5,
|
|
553
|
+
null: null,
|
|
554
|
+
boolean: true,
|
|
555
|
+
array: [1, 2, { a: 10 }],
|
|
556
|
+
object: { a: 1, b: { c: 3 } },
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
string: 'world',
|
|
560
|
+
number: 6,
|
|
561
|
+
null: null,
|
|
562
|
+
boolean: true,
|
|
563
|
+
array: [1, 2, { a: 10 }],
|
|
564
|
+
object: { a: 1, b: 2 },
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
string: 'third',
|
|
568
|
+
number: null,
|
|
569
|
+
null: 'test',
|
|
570
|
+
boolean: false,
|
|
571
|
+
array: [3, 4, { a: 10 }],
|
|
572
|
+
object: { a: 1, b: 2 },
|
|
573
|
+
},
|
|
574
|
+
],
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
await io.write({ data: testData });
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('with where searching string values', async () => {
|
|
581
|
+
const result = rmhsh(
|
|
582
|
+
await io.readRows({
|
|
583
|
+
table: 'testTable',
|
|
584
|
+
where: { string: 'hello' },
|
|
585
|
+
}),
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
expect(result).toEqual({
|
|
589
|
+
testTable: {
|
|
590
|
+
_data: [
|
|
591
|
+
{
|
|
592
|
+
array: [1, 2, { a: 10 }],
|
|
593
|
+
boolean: true,
|
|
594
|
+
null: null,
|
|
595
|
+
number: 5,
|
|
596
|
+
object: {
|
|
597
|
+
a: 1,
|
|
598
|
+
b: {
|
|
599
|
+
c: 3,
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
string: 'hello',
|
|
603
|
+
},
|
|
604
|
+
],
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('with where searching number values', async () => {
|
|
610
|
+
const result = rmhsh(
|
|
611
|
+
await io.readRows({
|
|
612
|
+
table: 'testTable',
|
|
613
|
+
where: { number: 6 },
|
|
614
|
+
}),
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
expect(result).toEqual({
|
|
618
|
+
testTable: {
|
|
619
|
+
_data: [
|
|
620
|
+
{
|
|
621
|
+
array: [1, 2, { a: 10 }],
|
|
622
|
+
boolean: true,
|
|
623
|
+
null: null,
|
|
624
|
+
number: 6,
|
|
625
|
+
object: { a: 1, b: 2 },
|
|
626
|
+
string: 'world',
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('with where searching null values', async () => {
|
|
634
|
+
const result = rmhsh(
|
|
635
|
+
await io.readRows({
|
|
636
|
+
table: 'testTable',
|
|
637
|
+
where: { null: null },
|
|
638
|
+
}),
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
expect(result).toEqual({
|
|
642
|
+
testTable: {
|
|
643
|
+
_data: [
|
|
644
|
+
{
|
|
645
|
+
array: [1, 2, { a: 10 }],
|
|
646
|
+
boolean: true,
|
|
647
|
+
null: null,
|
|
648
|
+
number: 5,
|
|
649
|
+
object: { a: 1, b: { c: 3 } },
|
|
650
|
+
string: 'hello',
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
array: [1, 2, { a: 10 }],
|
|
654
|
+
boolean: true,
|
|
655
|
+
null: null,
|
|
656
|
+
number: 6,
|
|
657
|
+
object: { a: 1, b: 2 },
|
|
658
|
+
string: 'world',
|
|
659
|
+
},
|
|
660
|
+
],
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('with where searching boolean values', async () => {
|
|
666
|
+
const result = rmhsh(
|
|
667
|
+
await io.readRows({
|
|
668
|
+
table: 'testTable',
|
|
669
|
+
where: { boolean: true },
|
|
670
|
+
}),
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
expect(result).toEqual({
|
|
674
|
+
testTable: {
|
|
675
|
+
_data: [
|
|
676
|
+
{
|
|
677
|
+
array: [1, 2, { a: 10 }],
|
|
678
|
+
boolean: true,
|
|
679
|
+
null: null,
|
|
680
|
+
number: 5,
|
|
681
|
+
object: { a: 1, b: { c: 3 } },
|
|
682
|
+
string: 'hello',
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
array: [1, 2, { a: 10 }],
|
|
686
|
+
boolean: true,
|
|
687
|
+
null: null,
|
|
688
|
+
number: 6,
|
|
689
|
+
object: { a: 1, b: 2 },
|
|
690
|
+
string: 'world',
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('with where searching array values', async () => {
|
|
698
|
+
const result = rmhsh(
|
|
699
|
+
await io.readRows({
|
|
700
|
+
table: 'testTable',
|
|
701
|
+
//where: { array: [1, 2, { a: 10 }] },
|
|
702
|
+
where: {
|
|
703
|
+
array: [1, 2, { a: 10, _hash: 'LeFJOCQVgToOfbUuKJQ-GO' }],
|
|
704
|
+
},
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
expect(result).toEqual({
|
|
709
|
+
testTable: {
|
|
710
|
+
_data: [
|
|
711
|
+
{
|
|
712
|
+
array: [1, 2, { a: 10 }],
|
|
713
|
+
boolean: true,
|
|
714
|
+
null: null,
|
|
715
|
+
number: 5,
|
|
716
|
+
object: { a: 1, b: { c: 3 } },
|
|
717
|
+
string: 'hello',
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
array: [1, 2, { a: 10 }],
|
|
721
|
+
boolean: true,
|
|
722
|
+
null: null,
|
|
723
|
+
number: 6,
|
|
724
|
+
object: { a: 1, b: 2 },
|
|
725
|
+
string: 'world',
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it('with where searching object values', async () => {
|
|
733
|
+
const result = rmhsh(
|
|
734
|
+
await io.readRows({
|
|
735
|
+
table: 'testTable',
|
|
736
|
+
//where: { object: { a: 1, b: { c: 3 } } },
|
|
737
|
+
where: {
|
|
738
|
+
object: {
|
|
739
|
+
a: 1,
|
|
740
|
+
b: { c: 3, _hash: 'yrqcsGrHfad4G4u9fgcAxY' },
|
|
741
|
+
_hash: 'd-0fwNtdekpWJzLu4goUDI',
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
}),
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
expect(result).toEqual({
|
|
748
|
+
testTable: {
|
|
749
|
+
_data: [
|
|
750
|
+
{
|
|
751
|
+
array: [1, 2, { a: 10 }],
|
|
752
|
+
boolean: true,
|
|
753
|
+
null: null,
|
|
754
|
+
number: 5,
|
|
755
|
+
object: { a: 1, b: { c: 3 } },
|
|
756
|
+
string: 'hello',
|
|
757
|
+
},
|
|
758
|
+
],
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it('should return an empty array if no rows match the where clause', async () => {
|
|
765
|
+
await createExampleTable('testTable');
|
|
766
|
+
|
|
767
|
+
await io.write({
|
|
768
|
+
data: {
|
|
769
|
+
testTable: {
|
|
770
|
+
_data: [
|
|
771
|
+
{ a: 'value1', b: 2 },
|
|
772
|
+
{ a: 'value3', b: 4 },
|
|
773
|
+
],
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
const result = await io.readRows({
|
|
779
|
+
table: 'testTable',
|
|
780
|
+
where: { a: 'nonexistent' },
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
expect(result).toEqual({
|
|
784
|
+
testTable: {
|
|
785
|
+
_data: [],
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
it('should throw an error if the table does not exist', async () => {
|
|
791
|
+
await expect(
|
|
792
|
+
io.readRows({
|
|
793
|
+
table: 'nonexistentTable',
|
|
794
|
+
where: { column1: 'value1' },
|
|
795
|
+
}),
|
|
796
|
+
).rejects.toThrow('Table "nonexistentTable" not found');
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('should throw an error if the where clause is invalid', async () => {
|
|
800
|
+
await createExampleTable('testTable');
|
|
801
|
+
|
|
802
|
+
await expect(
|
|
803
|
+
io.readRows({
|
|
804
|
+
table: 'testTable',
|
|
805
|
+
where: { invalidColumn: 'value' },
|
|
806
|
+
}),
|
|
807
|
+
).rejects.toThrow(
|
|
808
|
+
'The following columns do not exist in table "testTable": invalidColumn.',
|
|
809
|
+
);
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
describe('rowCount(table)', () => {
|
|
814
|
+
it('returns the number of rows in the table', async () => {
|
|
815
|
+
await createExampleTable('table1');
|
|
816
|
+
await createExampleTable('table2');
|
|
817
|
+
await io.write({
|
|
818
|
+
data: {
|
|
819
|
+
table1: {
|
|
820
|
+
_data: [
|
|
821
|
+
{ a: 'a1' },
|
|
822
|
+
{ a: 'a2' },
|
|
823
|
+
{ a: 'a3' },
|
|
824
|
+
{ a: 'a4' },
|
|
825
|
+
{ a: 'a5' },
|
|
826
|
+
],
|
|
827
|
+
},
|
|
828
|
+
table2: {
|
|
829
|
+
_data: [{ a: 'a1' }, { a: 'a2' }],
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
const count1 = await io.rowCount('table1');
|
|
834
|
+
const count2 = await io.rowCount('table2');
|
|
835
|
+
expect(count1).toBe(5);
|
|
836
|
+
expect(count2).toBe(2);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it('throws an error if the table does not exist', async () => {
|
|
840
|
+
await expect(io.rowCount('nonexistentTable')).rejects.toThrow(
|
|
841
|
+
'Table "nonexistentTable" not found',
|
|
842
|
+
);
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
describe('dump()', () => {
|
|
847
|
+
it('returns a copy of the complete database', async () => {
|
|
848
|
+
await expectGolden('io-conformance/dump/empty.json', ego).toBe(
|
|
849
|
+
await io.dump(),
|
|
850
|
+
);
|
|
851
|
+
await createExampleTable('table1');
|
|
852
|
+
await createExampleTable('table2');
|
|
853
|
+
await expectGolden('io-conformance/dump/two-tables.json', ego).toBe(
|
|
854
|
+
await io.dump(),
|
|
855
|
+
);
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
describe('dumpTable(request)', () => {
|
|
860
|
+
it('returns a copy of the table', async () => {
|
|
861
|
+
await createExampleTable('table1');
|
|
862
|
+
|
|
863
|
+
await io.write({
|
|
864
|
+
data: {
|
|
865
|
+
table1: {
|
|
866
|
+
_data: [{ a: 'a2' }],
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
await expectGolden('io-conformance/dumpTable/table1.json', ego).toBe(
|
|
872
|
+
await io.dumpTable({ table: 'table1' }),
|
|
873
|
+
);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it('throws an error if the table does not exist', async () => {
|
|
877
|
+
await expect(
|
|
878
|
+
io.dumpTable({ table: 'nonexistentTable' }),
|
|
879
|
+
).rejects.toThrow('Table "nonexistentTable" not found');
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
runIoConformanceTests();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2025 Rljson
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Create a folder dist/conformance-tests if it does not exist
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import fs from 'fs/promises';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { red } from './functions/colors.js';
|
|
14
|
+
import { nodeModulesDir, testDir } from './functions/directories.js';
|
|
15
|
+
import { syncFolders } from './functions/sync-folders.js';
|
|
16
|
+
|
|
17
|
+
// .............................................................................
|
|
18
|
+
async function _copyGoldens(srcDir) {
|
|
19
|
+
const from = path.join(srcDir, 'goldens');
|
|
20
|
+
const to = path.join(testDir, 'goldens', 'io-conformance');
|
|
21
|
+
if (!existsSync(to)) {
|
|
22
|
+
await fs.mkdir(to, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await syncFolders(from, to, { excludeHidden: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// .............................................................................
|
|
29
|
+
async function _copyConformanceTests(srcDir) {
|
|
30
|
+
const from = path.join(srcDir, 'io-conformance.spec.ts');
|
|
31
|
+
const to = path.join(testDir, 'io-conformance.spec.ts');
|
|
32
|
+
await fs.copyFile(from, to);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// .............................................................................
|
|
36
|
+
async function _srcDir() {
|
|
37
|
+
const targetDir = path.join(
|
|
38
|
+
nodeModulesDir,
|
|
39
|
+
'@rljson',
|
|
40
|
+
'io',
|
|
41
|
+
'dist',
|
|
42
|
+
'conformance-tests',
|
|
43
|
+
);
|
|
44
|
+
if (!existsSync(targetDir)) {
|
|
45
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
return targetDir;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// .............................................................................
|
|
51
|
+
try {
|
|
52
|
+
// Create target directory if it doesn't exist
|
|
53
|
+
const srcDir = await _srcDir();
|
|
54
|
+
await _copyConformanceTests(srcDir);
|
|
55
|
+
await _copyGoldens(srcDir);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(
|
|
58
|
+
red('❌ Error while deploying conformance tests:', err.message),
|
|
59
|
+
);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rljson/io",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"packageManager": "pnpm@10.9.0",
|
|
5
5
|
"description": "Low level interface for reading and writing RLJSON data",
|
|
6
6
|
"homepage": "https://github.com/rljson/io",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "pnpx vite build && tsc && node scripts/copy-readme-to-dist.js && node scripts/copy-file.js src/example.ts dist/src",
|
|
24
|
+
"build": "pnpx vite build && tsc && node scripts/copy-readme-to-dist.js && node scripts/copy-file.js src/example.ts dist/src && node scripts/deploy-conformance-tests.js",
|
|
25
25
|
"test": "pnpx vitest run --coverage && pnpm run lint",
|
|
26
26
|
"prebuild": "pnpm run test",
|
|
27
27
|
"prepublishOnly": "pnpm run build && pnpm run test",
|