@datatruck/cli 0.30.1 → 0.31.0

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.
@@ -1048,67 +1048,217 @@
1048
1048
  "type": "object",
1049
1049
  "additionalProperties": false,
1050
1050
  "properties": {
1051
- "path": {
1052
- "type": "string"
1053
- },
1054
1051
  "log": {
1055
1052
  "type": "boolean"
1056
1053
  },
1057
- "users": {
1058
- "type": "array",
1059
- "items": {
1060
- "type": "object",
1061
- "additionalProperties": false,
1062
- "properties": {
1063
- "name": {
1064
- "type": "string"
1065
- },
1066
- "password": {
1067
- "type": "string"
1068
- }
1069
- }
1070
- }
1071
- },
1072
- "listen": {
1054
+ "repository": {
1073
1055
  "type": "object",
1074
1056
  "additionalProperties": false,
1075
1057
  "properties": {
1076
- "port": {
1077
- "type": "integer"
1078
- },
1079
- "address": {
1080
- "type": "string"
1081
- }
1082
- }
1083
- },
1084
- "trustProxy": {
1085
- "anyOf": [
1086
- {
1058
+ "enabled": {
1087
1059
  "type": "boolean"
1088
1060
  },
1089
- {
1061
+ "listen": {
1090
1062
  "type": "object",
1091
1063
  "additionalProperties": false,
1092
- "required": [
1093
- "remoteAddressHeader"
1094
- ],
1095
1064
  "properties": {
1096
- "remoteAddressHeader": {
1065
+ "port": {
1066
+ "type": "integer"
1067
+ },
1068
+ "address": {
1097
1069
  "type": "string"
1098
1070
  }
1099
1071
  }
1072
+ },
1073
+ "trustProxy": {
1074
+ "anyOf": [
1075
+ {
1076
+ "type": "boolean"
1077
+ },
1078
+ {
1079
+ "type": "object",
1080
+ "additionalProperties": false,
1081
+ "required": [
1082
+ "remoteAddressHeader"
1083
+ ],
1084
+ "properties": {
1085
+ "remoteAddressHeader": {
1086
+ "type": "string"
1087
+ }
1088
+ }
1089
+ }
1090
+ ]
1091
+ },
1092
+ "allowlist": {
1093
+ "type": "object",
1094
+ "additionalProperties": false,
1095
+ "properties": {
1096
+ "enabled": {
1097
+ "type": "boolean"
1098
+ },
1099
+ "remoteAddresses": {
1100
+ "$ref": "#/definitions/stringlist-util"
1101
+ }
1102
+ }
1103
+ },
1104
+ "backends": {
1105
+ "type": "array",
1106
+ "items": {
1107
+ "type": "object",
1108
+ "additionalProperties": false,
1109
+ "required": [
1110
+ "name",
1111
+ "path"
1112
+ ],
1113
+ "properties": {
1114
+ "name": {
1115
+ "type": "string"
1116
+ },
1117
+ "path": {
1118
+ "type": "string"
1119
+ },
1120
+ "users": {
1121
+ "type": "array",
1122
+ "items": {
1123
+ "type": "object",
1124
+ "additionalProperties": false,
1125
+ "required": [
1126
+ "name",
1127
+ "password"
1128
+ ],
1129
+ "properties": {
1130
+ "enabled": {
1131
+ "type": "boolean"
1132
+ },
1133
+ "name": {
1134
+ "type": "string"
1135
+ },
1136
+ "password": {
1137
+ "type": "string"
1138
+ }
1139
+ }
1140
+ }
1141
+ }
1142
+ }
1143
+ }
1100
1144
  }
1101
- ]
1145
+ }
1102
1146
  },
1103
- "allowlist": {
1147
+ "cron": {
1104
1148
  "type": "object",
1105
1149
  "additionalProperties": false,
1106
1150
  "properties": {
1107
1151
  "enabled": {
1108
1152
  "type": "boolean"
1109
1153
  },
1110
- "remoteAddresses": {
1111
- "$ref": "#/definitions/stringlist-util"
1154
+ "actions": {
1155
+ "type": "array",
1156
+ "items": {
1157
+ "allOf": [
1158
+ {
1159
+ "type": "object",
1160
+ "required": [
1161
+ "schedule"
1162
+ ],
1163
+ "properties": {
1164
+ "schedule": {
1165
+ "type": "string"
1166
+ }
1167
+ }
1168
+ },
1169
+ {
1170
+ "anyOf": [
1171
+ {
1172
+ "if": {
1173
+ "type": "object",
1174
+ "properties": {
1175
+ "type": {
1176
+ "const": "backup"
1177
+ }
1178
+ }
1179
+ },
1180
+ "then": {
1181
+ "type": "object",
1182
+ "properties": {
1183
+ "options": {
1184
+ "type": "object",
1185
+ "additionalProperties": false,
1186
+ "properties": {
1187
+ "package": {
1188
+ "type": "string"
1189
+ },
1190
+ "packageTask": {
1191
+ "type": "string"
1192
+ },
1193
+ "repository": {
1194
+ "type": "string"
1195
+ },
1196
+ "repositoryType": {
1197
+ "enum": [
1198
+ "restic",
1199
+ "datatruck",
1200
+ "git"
1201
+ ]
1202
+ },
1203
+ "tag": {
1204
+ "type": "string"
1205
+ },
1206
+ "date": {
1207
+ "type": "string"
1208
+ },
1209
+ "prune": {
1210
+ "type": "boolean"
1211
+ }
1212
+ }
1213
+ }
1214
+ }
1215
+ },
1216
+ "else": false
1217
+ },
1218
+ {
1219
+ "if": {
1220
+ "type": "object",
1221
+ "properties": {
1222
+ "type": {
1223
+ "const": "copy"
1224
+ }
1225
+ }
1226
+ },
1227
+ "then": {
1228
+ "type": "object",
1229
+ "properties": {
1230
+ "options": {
1231
+ "type": "object",
1232
+ "additionalProperties": false,
1233
+ "properties": {
1234
+ "id": {
1235
+ "type": "string"
1236
+ },
1237
+ "last": {
1238
+ "type": "integer"
1239
+ },
1240
+ "package": {
1241
+ "type": "string"
1242
+ },
1243
+ "packageTask": {
1244
+ "type": "string"
1245
+ },
1246
+ "repository": {
1247
+ "type": "string"
1248
+ },
1249
+ "repository2": {
1250
+ "type": "string"
1251
+ }
1252
+ }
1253
+ }
1254
+ }
1255
+ },
1256
+ "else": false
1257
+ }
1258
+ ]
1259
+ }
1260
+ ]
1261
+ }
1112
1262
  }
1113
1263
  }
1114
1264
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "dependencies": {
5
5
  "@supercharge/promise-pool": "^3.1.0",
6
6
  "ajv": "^8.12.0",
7
7
  "async": "^3.2.4",
8
8
  "chalk": "^4.1.2",
9
9
  "commander": "^11.0.0",
10
+ "croner": "^7.0.4",
10
11
  "dayjs": "^1.11.10",
11
12
  "fast-folder-size": "^2.2.0",
12
13
  "fast-glob": "^3.3.1",
package/utils/cli.d.ts CHANGED
@@ -19,4 +19,7 @@ export type OptionsType<T1, T2 extends {
19
19
  export declare function parseOptions<T1, T2 extends {
20
20
  [K in keyof T1]: unknown;
21
21
  }>(object: T1, options: OptionsType<T1, T2>): T2;
22
+ export declare function stringifyOptions<T1, T2 extends {
23
+ [K in keyof T1]: unknown;
24
+ }>(options: OptionsType<T1, T2>, object: any): string[];
22
25
  export declare function confirm(message: string): Promise<unknown>;
package/utils/cli.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.confirm = exports.parseOptions = exports.renderObject = exports.renderError = exports.renderResult = exports.logExec = exports.renderProgressBar = exports.showCursorCommand = void 0;
6
+ exports.confirm = exports.stringifyOptions = exports.parseOptions = exports.renderObject = exports.renderError = exports.renderResult = exports.logExec = exports.renderProgressBar = exports.showCursorCommand = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const chalk_2 = require("chalk");
9
9
  const readline_1 = require("readline");
@@ -96,6 +96,26 @@ function parseOptions(object, options) {
96
96
  return result;
97
97
  }
98
98
  exports.parseOptions = parseOptions;
99
+ function stringifyOptions(options, object) {
100
+ const result = [];
101
+ for (const key in options) {
102
+ const fullOpt = options[key].option;
103
+ const [opt] = fullOpt.split(",");
104
+ const isNegative = fullOpt.startsWith("--no");
105
+ const isBool = !fullOpt.includes("<") && !fullOpt.includes("[");
106
+ const defaultsValue = isNegative ? true : options[key].defaults;
107
+ const value = object?.[key] ?? defaultsValue;
108
+ if (isBool) {
109
+ if (object[key])
110
+ result.push(opt);
111
+ }
112
+ else if (value !== undefined) {
113
+ result.push(opt, `${value}`);
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ exports.stringifyOptions = stringifyOptions;
99
119
  function confirm(message) {
100
120
  const rl = (0, readline_1.createInterface)({
101
121
  input: process.stdin,
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createFs = exports.isRemoteBackend = exports.RemoteFs = void 0;
4
4
  const http_1 = require("../http");
5
5
  const virtual_fs_1 = require("../virtual-fs");
6
- const server_1 = require("./server");
6
+ const repository_server_1 = require("./repository-server");
7
7
  class RemoteFs extends virtual_fs_1.AbstractFs {
8
8
  options;
9
9
  url;
@@ -13,8 +13,8 @@ class RemoteFs extends virtual_fs_1.AbstractFs {
13
13
  this.options = options;
14
14
  const url = new URL(options.backend);
15
15
  this.headers = {
16
- [server_1.headerKey.user]: url.username,
17
- [server_1.headerKey.password]: url.password,
16
+ [repository_server_1.headerKey.user]: url.username,
17
+ [repository_server_1.headerKey.password]: url.password,
18
18
  };
19
19
  url.username = "";
20
20
  url.password = "";
@@ -0,0 +1,23 @@
1
+ import { BackupCommandOptions } from "../../Command/BackupCommand";
2
+ import { CopyCommandOptionsType } from "../../Command/CopyCommand";
3
+ export type CronAction = {
4
+ schedule: string;
5
+ name: "backup";
6
+ options: BackupCommandOptions;
7
+ } | {
8
+ schedule: string;
9
+ name: "copy";
10
+ options: CopyCommandOptionsType;
11
+ };
12
+ export type DatatruckCronServerOptions = {
13
+ enabled?: boolean;
14
+ actions?: CronAction[];
15
+ };
16
+ export declare function createCronServer(options: DatatruckCronServerOptions, config: {
17
+ log: boolean;
18
+ verbose: boolean;
19
+ configPath: string;
20
+ }): {
21
+ start: () => void;
22
+ stop: () => void;
23
+ };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCronServer = void 0;
4
+ const CommandFactory_1 = require("../../Factory/CommandFactory");
5
+ const cli_1 = require("../cli");
6
+ const process_1 = require("../process");
7
+ const croner_1 = require("croner");
8
+ function createJobs(actions, currentJobs = [], worker) {
9
+ const jobs = [];
10
+ for (const action of actions) {
11
+ const index = actions.indexOf(action);
12
+ const context = JSON.stringify({
13
+ index: actions.indexOf(action),
14
+ data: action,
15
+ });
16
+ const job = currentJobs.at(index);
17
+ if (!job || job.options.context !== context) {
18
+ job?.stop();
19
+ jobs.push((0, croner_1.Cron)(action.schedule, {
20
+ paused: true,
21
+ context: JSON.stringify(action),
22
+ catch: true,
23
+ protect: true,
24
+ }, () => worker(action, index)));
25
+ }
26
+ }
27
+ return jobs;
28
+ }
29
+ function createCronServer(options, config) {
30
+ const worker = async (action, index) => {
31
+ if (config.log)
32
+ console.info(`> [job] ${index} - ${action.name}`);
33
+ try {
34
+ const Command = (0, CommandFactory_1.CommandConstructorFactory)(action.name);
35
+ const command = new Command({ config: { packages: [], repositories: [] } }, {});
36
+ const cliOptions = (0, cli_1.stringifyOptions)(command.onOptions(), action.options);
37
+ const [node, bin] = process.argv;
38
+ await (0, process_1.exec)(node, [bin, "-c", config.configPath, action.name, ...cliOptions], {}, { log: config.verbose });
39
+ if (config.log)
40
+ console.info(`< [job] ${index} - ${action.name}`);
41
+ }
42
+ catch (error) {
43
+ if (config.log)
44
+ console.error(`< [job] ${index} - ${action.name}`, error);
45
+ }
46
+ };
47
+ const jobs = createJobs(options.actions || [], [], worker);
48
+ return {
49
+ start: () => {
50
+ for (const job of jobs)
51
+ job.resume();
52
+ },
53
+ stop: () => {
54
+ for (const job of jobs)
55
+ job.stop();
56
+ },
57
+ };
58
+ }
59
+ exports.createCronServer = createCronServer;
@@ -1,17 +1,16 @@
1
1
  /// <reference types="node" />
2
2
  import { IncomingMessage } from "http";
3
3
  type User = {
4
+ enabled?: boolean;
4
5
  name: string;
5
6
  password: string;
6
7
  };
7
- export type DatatruckServerOptions = {
8
- path?: string;
9
- log?: boolean;
8
+ export type DatatruckRepositoryServerOptions = {
9
+ enabled?: boolean;
10
10
  listen?: {
11
11
  port?: number;
12
12
  address?: string;
13
13
  };
14
- users?: User[];
15
14
  trustProxy?: true | {
16
15
  remoteAddressHeader: string;
17
16
  };
@@ -22,10 +21,17 @@ export type DatatruckServerOptions = {
22
21
  enabled?: boolean;
23
22
  remoteAddresses?: string[];
24
23
  };
24
+ backends?: {
25
+ name: string;
26
+ path: string;
27
+ users?: User[];
28
+ }[];
25
29
  };
26
30
  export declare const headerKey: {
27
31
  user: string;
28
32
  password: string;
29
33
  };
30
- export declare function createDatatruckServer(options: DatatruckServerOptions): import("node:http").Server<typeof IncomingMessage, typeof import("node:http").ServerResponse>;
34
+ export declare function createDatatruckRepositoryServer(options: Omit<DatatruckRepositoryServerOptions, "listen">, config?: {
35
+ log?: boolean;
36
+ }): import("node:http").Server<typeof IncomingMessage, typeof import("node:http").ServerResponse>;
31
37
  export {};
@@ -1,30 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createDatatruckServer = exports.headerKey = void 0;
3
+ exports.createDatatruckRepositoryServer = exports.headerKey = void 0;
4
4
  const http_1 = require("../http");
5
5
  const virtual_fs_1 = require("../virtual-fs");
6
6
  const fs_1 = require("fs");
7
7
  const promises_1 = require("fs/promises");
8
8
  const http_2 = require("http");
9
- function parseUrl(inUrl) {
9
+ exports.headerKey = {
10
+ user: "x-dtt-user",
11
+ password: "x-dtt-password",
12
+ };
13
+ function parseUrl(inUrl, repositoryPrefix = "repo") {
10
14
  const url = new URL(`http://127.0.0.1${inUrl}`);
11
15
  const inParams = url.searchParams.get("params");
12
- const action = url.pathname.slice(1);
13
- if (typeof inParams === "string") {
16
+ const [prefix, repository, action] = url.pathname.slice(1).split("/");
17
+ if (prefix !== repositoryPrefix) {
18
+ return { repository: undefined, action: undefined, params: [] };
19
+ }
20
+ else if (typeof inParams === "string") {
14
21
  const params = JSON.parse(inParams);
15
22
  if (!Array.isArray(params))
16
23
  throw new Error(`Invalid params`);
17
- return { action, params };
24
+ return { repository, action, params };
18
25
  }
19
26
  else {
20
- return { action, params: [] };
27
+ return { repository, action, params: [] };
21
28
  }
22
29
  }
23
- exports.headerKey = {
24
- user: "x-dtt-user",
25
- password: "x-dtt-password",
26
- };
27
- function validateRequest(req, options) {
30
+ function findRepositoryBackend(req, repository, options) {
28
31
  const list = options.allowlist;
29
32
  if (list && (list.enabled ?? true) && list.remoteAddresses) {
30
33
  const remoteAddress = getRemoteAddress(req, options);
@@ -35,7 +38,15 @@ function validateRequest(req, options) {
35
38
  const password = req.headers[exports.headerKey.password]?.toString().trim();
36
39
  if (!name?.length || !password?.length)
37
40
  return;
38
- return (options.users?.some((user) => user.name === name && user.password === password) || false);
41
+ const backend = options.backends?.find((e) => e.name === repository);
42
+ if (!backend)
43
+ return;
44
+ const user = backend.users?.find((user) => user.name === name && user.password === password);
45
+ if (!user)
46
+ return;
47
+ if (!(user.enabled ?? true))
48
+ return;
49
+ return backend;
39
50
  }
40
51
  const getRemoteAddress = (req, options) => {
41
52
  return ((options.trustProxy
@@ -44,24 +55,30 @@ const getRemoteAddress = (req, options) => {
44
55
  : req.headers[options.trustProxy.remoteAddressHeader]?.toString()
45
56
  : undefined) ?? req.socket.remoteAddress);
46
57
  };
47
- function createDatatruckServer(options) {
48
- const log = options.log ?? true;
58
+ function createDatatruckRepositoryServer(options, config = {}) {
49
59
  return (0, http_2.createServer)(async (req, res) => {
50
60
  try {
51
- if (req.url === "/" || req.url === "/favicon.ico") {
61
+ if (req.url === "/" || req.url === "/favicon.ico")
62
+ return res.end();
63
+ const { repository, action, params } = parseUrl(req.url);
64
+ if (!repository || !action) {
65
+ res.statusCode = 404;
52
66
  return res.end();
53
67
  }
54
- else if (!validateRequest(req, options)) {
68
+ const backend = findRepositoryBackend(req, repository, options);
69
+ if (!backend) {
55
70
  res.statusCode = 401;
56
71
  return res.end();
57
72
  }
58
- if (log)
59
- console.info(`> ${req.url}`);
73
+ if (config.log)
74
+ console.info(`> [repository] ${repository} - ${req.url}`);
60
75
  const fs = new virtual_fs_1.LocalFs({
61
- backend: options.path ?? ".",
76
+ backend: backend.path,
62
77
  });
63
- const { action, params } = parseUrl(req.url);
64
- if (action === "upload") {
78
+ if (action === "comcheck") {
79
+ res.write(JSON.stringify({ success: true }));
80
+ }
81
+ else if (action === "upload") {
65
82
  const [target] = params;
66
83
  const path = fs.resolvePath(target);
67
84
  const file = (0, fs_1.createWriteStream)(path);
@@ -99,17 +116,17 @@ function createDatatruckServer(options) {
99
116
  if (json !== undefined)
100
117
  res.write(JSON.stringify(json));
101
118
  }
102
- if (log)
103
- console.info(`<${action}`);
119
+ if (config.log)
120
+ console.info(`< [repository] ${repository} - ${action}`);
104
121
  res.end();
105
122
  }
106
123
  catch (error) {
107
- if (log)
108
- console.error(`<${req.url}`, error);
124
+ if (config.log)
125
+ console.error(`< [repository] ${req.url}`, error);
109
126
  res.statusCode = 500;
110
127
  res.statusMessage = error.message;
111
128
  res.end();
112
129
  }
113
130
  });
114
131
  }
115
- exports.createDatatruckServer = createDatatruckServer;
132
+ exports.createDatatruckRepositoryServer = createDatatruckRepositoryServer;
package/utils/http.d.ts CHANGED
@@ -3,10 +3,16 @@ import { BasicProgress } from "./progress";
3
3
  import { IncomingMessage, Server } from "http";
4
4
  export declare function closeServer(server: Server): Promise<void>;
5
5
  export declare function readRequestData(req: IncomingMessage): Promise<string | undefined>;
6
- export declare function fetchJson<T = any>(url: string, options?: {
6
+ export type FetchOptions = {
7
7
  headers?: Record<string, string>;
8
8
  query?: Record<string, string>;
9
- }): Promise<T>;
9
+ statusError?: boolean;
10
+ };
11
+ export declare function fetch(url: string, options?: FetchOptions): Promise<{
12
+ data: string | undefined;
13
+ status: number | undefined;
14
+ }>;
15
+ export declare function fetchJson<T = any>(url: string, options?: FetchOptions): Promise<T | undefined>;
10
16
  export declare function post(url: string, data: string, options?: {
11
17
  headers?: Record<string, string>;
12
18
  query?: Record<string, string>;
package/utils/http.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadFile = exports.downloadFile = exports.post = exports.fetchJson = exports.readRequestData = exports.closeServer = void 0;
3
+ exports.uploadFile = exports.downloadFile = exports.post = exports.fetchJson = exports.fetch = exports.readRequestData = exports.closeServer = void 0;
4
4
  const math_1 = require("./math");
5
5
  const fs_1 = require("fs");
6
6
  const promises_1 = require("fs/promises");
@@ -37,14 +37,15 @@ function readRequestData(req) {
37
37
  });
38
38
  }
39
39
  exports.readRequestData = readRequestData;
40
- async function fetchJson(url, options = {}) {
40
+ async function fetch(url, options = {}) {
41
+ const throwStatusCodeError = options.statusError ?? true;
41
42
  return new Promise((resolve, reject) => {
42
43
  let data;
43
44
  request(href(url, options.query), {
44
45
  method: "GET",
45
46
  headers: options.headers,
46
47
  }, (res) => {
47
- if (res.statusCode !== 200)
48
+ if (throwStatusCodeError && res.statusCode !== 200)
48
49
  return reject(new Error(`GET failed: ${res.statusCode} ${res.statusMessage}`));
49
50
  res
50
51
  .on("data", (chunk) => {
@@ -54,23 +55,20 @@ async function fetchJson(url, options = {}) {
54
55
  })
55
56
  .on("error", reject)
56
57
  .on("close", () => {
57
- if (data === undefined) {
58
- resolve(undefined);
59
- }
60
- else {
61
- try {
62
- resolve(JSON.parse(data));
63
- }
64
- catch (error) {
65
- reject(error);
66
- }
67
- }
58
+ resolve({ data, status: res.statusCode });
68
59
  });
69
60
  })
70
61
  .on("error", reject)
71
62
  .end();
72
63
  });
73
64
  }
65
+ exports.fetch = fetch;
66
+ async function fetchJson(url, options = {}) {
67
+ const res = await fetch(url, options);
68
+ if (res.data === undefined)
69
+ return;
70
+ return JSON.parse(res.data);
71
+ }
74
72
  exports.fetchJson = fetchJson;
75
73
  async function post(url, data, options = {}) {
76
74
  await new Promise((resolve, reject) => {
package/utils/object.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export declare function merge<T extends Record<string, unknown>>(target: T, ...sources: Record<string, unknown>[]): T;
2
- export declare function push<T>(map: Record<string, T[]>, key: string, object: T): void;
2
+ export declare function omitProp<T extends Record<string, any>, N extends keyof T>(object: T, name: N): Omit<T, N>;
3
3
  export declare function getErrorProperties(error: Error): Record<string, string>;
4
4
  type GroupByKeyParamType<TItem> = ((item: TItem) => string[] | string) | (keyof TItem)[] | keyof TItem;
5
5
  export declare function groupBy<TItem>(items: TItem[], keyOrCb: GroupByKeyParamType<TItem>): Record<string, TItem[]>;