diff --git a/.github/actions/node/action.yml b/.github/actions/node/action.yml index 910703ca..07b3a30b 100644 --- a/.github/actions/node/action.yml +++ b/.github/actions/node/action.yml @@ -1,11 +1,10 @@ runs: using: "composite" steps: - - name: Setup node uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 22.11.0 registry-url: https://registry.npmjs.org/ - name: Cache node modules diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index cfce8cc1..2ff55ae2 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -2,18 +2,35 @@ name: C++ CI on: push: - branches: [ "master" ] + branches: ["master", "v1"] pull_request: - branches: [ "master" ] + branches: ["master", "v1"] jobs: build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install gtest manually - run: sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake CMakeLists.txt && sudo make && sudo cp lib/*.a /usr/lib && sudo ln -s /usr/lib/libgtest.a /usr/local/lib/libgtest.a && sudo ln -s /usr/lib/libgtest_main.a /usr/local/lib/libgtest_main.a - - name: Run tests - run: cd tests && make all \ No newline at end of file + - uses: actions/checkout@v3 + + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libgtest-dev cmake ninja-build gcc g++ + version: 1.0 + + - name: Build Debug + run: | + mkdir -p build_debug + cmake -Bbuild_debug -S. -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Debug + cmake --build build_debug + + - name: Test Debug + run: ctest --test-dir build_debug/tests/ --output-on-failure + + - name: Build Release + run: | + mkdir -p build_release + cmake -Bbuild_release -S. -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=Release + cmake --build build_release + + - name: Test Release + run: ctest --test-dir build_release/tests/ --output-on-failure diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index c8a265a5..a4c94e89 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -2,12 +2,13 @@ name: JS CI on: push: - branches: [ master ] + branches: [master, "v1"] pull_request: - branches: [ master ] + branches: [master, "v1"] jobs: build: + if: false # disable the entire workflow runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -18,10 +19,18 @@ jobs: working-directory: ./port/js run: npm ci --prefer-offline --no-audit + - name: Run lint + working-directory: ./port/js + run: npm run check:lint + + - name: Run type check + working-directory: ./port/js + run: npm run check:types + - name: Run tests working-directory: ./port/js - run: npm test + run: npm run test - name: Build working-directory: ./port/js - run: npm run build \ No newline at end of file + run: npm run build diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 00000000..5f428d27 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,33 @@ +name: Python package + +on: + push: + branches: ["master", "v1"] + pull_request: + branches: ["master", "v1"] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests + run: | + python -m pytest + + - name: Check mypy + run: | + python -m mypy . diff --git a/.gitignore b/.gitignore index 8ff021be..0a61eca9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ # IntelliJ project files .idea +.vscode .run # Build files +.cache *.pyc *.pyd +cmake-build-*/ +build +Testing # Log files dump.txt @@ -14,7 +19,3 @@ dump.txt tests/cpp/messgen .done.* -tests/js/tmp -tests/js/coverage -tests/js/messgen -tests/js/MessgenHelper.ts diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..f1c84971 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.5) + +project(messgen) + +set(CMAKE_CXX_STANDARD 20) + +include("integrations/cmake/messgen.cmake") + +add_subdirectory(tests) diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..5e176640 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +ROOT_DIR ?= $(realpath $(PWD)) +BUILD_DIR ?= $(ROOT_DIR)/build +BUILD_TYPE ?= Debug + +all: check + +configure: + cmake -B${BUILD_DIR} -S. -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_BUILD_TYPE=${BUILD_TYPE} + +build: configure + cmake --build ${BUILD_DIR} + +test: build + ctest --test-dir ${BUILD_DIR}/tests/ --output-on-failure + python3 -m pytest . + +check: test + python3 -m mypy . + +clean: + rm -rf ${BUILD_DIR} + diff --git a/README.md b/README.md index 990f0a4f..cbc0d7b6 100644 --- a/README.md +++ b/README.md @@ -4,95 +4,202 @@ # Messgen Lightweight and fast message serialization library. -Generates message classes/structs from yml scheme. +Generates message classes/structs from YAML descriptions. Features: - Embedded-friendly - Fixed size arrays -- Dynamic size arrays +- Vectors (dynamic size arrays) +- Maps - Nested messages - Messages metadata -- Supported languages: C++, Go, JavaScript +- Supported output formats: C++, JSON, TypeScript +- Supported output formats TODO: Go, Markdown (documentation) -## Dependencies +## Runtime Dependencies -- python 3.X +- Python 3.X On Linux: -``` +```bash sudo apt install python3 ``` -On Windows 10: +On Windows: 1. Download https://bootstrap.pypa.io/get-pip.py 2. Execute `python3 get_pip.py` 3. Execute `pip3 install pyyaml` -## Generate messages +## Build & Test Dependencies -Each protocol should be placed in directory `base_dir/vendor/protocol`. -`base_dir` is base directory for message definitions (is allowed to specify multiple base directories). -`vendor` is protocol vendor, it is used as namespace in generated messages allowing to avoid conflict between protocols from different vendors if used in one application. -`protocol` is protocol name, each protocol has protocol ID, that allows to use multiple protocols on single connection, e.g. bootloader and application protocols. +- libgtest-dev (for testing) +- pytest (for testing) +- cmake +- ninja +- mypy -Message generator usage: -``` -python3 generate.py -b -m / -l -o [-D variable=value] +## Testing & Verification + +```bash +make check ``` -For some languages it's necessary to specify some variables using `-D` option. +## Generating Types and Protocols -Generated messages placed in `out_dir` directory. +All data types should be placed in one directory. Each protocol can be placed in any arbitrary directory. -#### Go +`types` is the base directory for type definitions (specifying multiple type directories is allowed). The subdirectories are treated as namespaces "my_company/core" or "my_company/the_product/some_items". -Example for Go messages generation: +`protocol` is a single protocol definition file (specifying multiple protocols is allowed). The protocol consists of a base directory and protocol name separated by a colon e.g. "protocols_dir:my_namespace/protocol". -``` -python3 generate.py -b ./base_dir -m my_vendor/my_protocol -l go -o out/go -D messgen_go_module=example.com/path/to/messgen +Message generator usage: + +```bash +python3 messgen-generate.py --types --protocol --lang --outdir [--options key1=value1,key2=value2,...] ``` -Variable `messgen_go_module` must point to messgen Go module (`port/go/messgen`), to add necessary imports in generated messages. +Generated messages are placed in the `out_dir` directory. #### C++ Example for C++ messages generation: -``` -python3 generate.py -b ./base_dir -m my_vendor/my_protocol -l cpp -o out/cpp +```bash +python3 messgen-generate.py --types ./types_dir --protocol "protocols_dir:my_namespace/my_protocol" --lang cpp --outdir out/cpp --options cpp_standard=20 ``` -Variable `metadata_json=true` can be passed to generate metadata in JSON format, rather than legacy. - -#### JS/TS +#### JSON Example for JS messages generation: +```bash +python3 messgen-generate.py --types ./types_dir --protocol "protocols_dir:my_namespace/my_protocol" --lang json --outdir out/json ``` -python3 generate.py -b ./base_dir -m my_vendor/my_protocol -l json -o out/json + +#### TypeScript + +Example for TypeScript messages generation: + +```bash +python3 messgen-generate.py --types ./types_dir --protocol "protocols_dir:my_namespace/my_protocol" --lang ts --outdir out/ts ``` -This command will generate json messages. -The types of these messages for TS can be generated as follows: +### Basic Concepts + +#### Overview + +There is no "one for all" solution, and messgen is not an exception. +Before selecting messgen keep in mind: + +- Statically typed: there is no "variant" type, but it's possible to work around this limitation with some tricks +- Optimized for embedded systems: systems where non-aligned access to float/int is forbidden, systems without heap +- Optimized for cross-platform compatibility (gives the same result on CPUs with different paddings, from 8bit microcontrollers to AMD64) +- Optimized for serialization/deserialization speed on C++ port, allows zero-copy in some cases +- Serialization level only, i.e. information about the type and size of the message must be added in separate header (examples provided) +- No optional fields in structs and messages +- No messages versioning + +Type and protocol descriptions are stored as `.yaml` files following the structure demonstrated below. ``` -python3 generate.py -b ./base_dir -m my_vendor/my_protocol -l ts -o out/ts +procol_dir/ +├── some_proto_namespace +│ ├── protocol_one.yaml +│ └── protocol_two.yaml +└── another_proto_namespace + └── ... ``` -if it is necessary to generate typed arrays for TS, it is necessary to pass the flag `-D typed_arrays=true`: ``` -python3 generate.py -b ./base_dir -m my_vendor/my_protocol -l ts -o out/ts -D typed_arrays=true +types_dir/ +├── some_type_namespace +│ ├── type1.yaml +│ └── type1.yaml +└── another_type_namespace + └── ... ``` +Naming style for all identifiers in yaml is strictly: `snake_case`. +In generated files identifiers will be converted to style that is specific for each port. + +#### Type + +The lowest level of hierarchy is **type**. It can be: + +- Scalar: e.g. `int32`, `float32`, `uint8`, `bool` +- Enum: wrapper around int, described in yaml file +- Array: fixed size `element_type[]`, e.g. `int32[4]`, `my_struct[3]` +- Vector: dynamic size array `element_type[]`, e.g. `int32[]`, `my_struct[]` +- Map: ordered map `value_type{key_type}`, e.g. `string{int32}`, `my_struct{int32}{string}` +- String: vector of `uint8`, representing string, `string` +- Bytes: vector of `uint8`, representing bytes buffer, `bytes` +- Struct: list of fields, described in yaml file +- External: user-defined types, user must provide serialization/deserialization methods for each port that is used (TODO) +#### Enum -#### MD +Enum may contain constants enumeration or bitfield. +Each enum defined in separate file. -Example for protocol documentation generation: +For the bitfield the format should be: `(1 << n)`, where `n` is the position of the bit. +Example enum definition file (`command.yaml`): +```yaml +type_class: enum +comment: "Example of command enum" +base_type: uint8 +values: + - { name: "start", value: 0, comment: "Start node operation" } + - { name: "stop", value: 1, comment: "Stop node operation" } + - { name: "reset", value: 2, comment: "Reset node state" } ``` -python3 generate.py -b ./base_dir -m my_vendor/my_protocol -l md -o out/md + +Example `flags.yaml` file with bitfield: +```yaml +type_class: enum +comment: "Example of flags bitfield" +base_type: uint8 +values: + - { name: "online", value: "(1 << 0)", comment: "Node is online" } + - { name: "sensor_error", value: "(1 << 1)", comment: "Internal node error" } +``` + +#### Struct + +**Structs** are the most important part of the serialization. +Each struct defined in separate file. + +Example struct definition file (`baro_report.yaml`): +```yaml +type_class: struct +comment: "Barometer report" +fields: + - { name: "timestamp", type: "uint64", comment: "[ns] Timestamp of the measurement" } + - { name: "temp", type: "float32", comment: "[deg C] Temperature" } + - { name: "pres", type: "float32", comment: "[Pa] Pressure" } +``` + +Struct itself don't have any type identifier that is serialized in the message. +Type ids can be assigned to structs in `weather_station.yaml` file (see below). + +#### Protocol + +**Protocol** defines the protocol ID and message IDs for structs that will be used +as messages. Message ID used during serialization/deserialization to identify the +message type. Multiple protocols may be used in one system, e.g. +`my_namespace/bootloader` and `my_namespace/application`. Parser can check the +protocol by protocol ID, that can be serialized in message header. + +Example protocol definition (`weather_station.yaml`): + +```yaml +comment: "Weather station application protocol" +messages: + 0: { name: "heartbeat", type: "application/heartbeat", comment: "Heartbeat message" } + 1: { name: "system_status", type: "system/status", comment: "System status message" } + 2: { name: "system_command", type: "system/command", comment: "System command message" } + 3: { name: "baro_report", type: "measurement/baro_report", comment: "Barometer report message" } ``` diff --git a/generate.py b/generate.py deleted file mode 100644 index a0c61422..00000000 --- a/generate.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 - -import argparse - -import os -from messgen.go_generator import GoGenerator -from messgen.json_generator import JsonGenerator -from messgen.ts_generator import TsGenerator -from messgen.md_generator import MdGenerator -from messgen.parser import load_modules -from messgen.cpp_generator import CppGenerator -from messgen.data_types_preprocessor import DataTypesPreprocessor -from messgen import MessgenException - -MODULE_SEP = "/" - -generators = { - "cpp": CppGenerator, - "go": GoGenerator, - "json": JsonGenerator, - "ts": TsGenerator, - "md": MdGenerator -} - -PLAIN_TYPES = { - "char": {"size": 1, "align": 1}, - "int8": {"size": 1, "align": 1}, - "uint8": {"size": 1, "align": 1}, - "int16": {"size": 2, "align": 2}, - "uint16": {"size": 2, "align": 2}, - "int32": {"size": 4, "align": 4}, - "uint32": {"size": 4, "align": 4}, - "int64": {"size": 8, "align": 8}, - "uint64": {"size": 8, "align": 8}, - "float32": {"size": 4, "align": 4}, - "float64": {"size": 8, "align": 8}, -} - -SPECIAL_TYPES = { - "string": {"size": 1, "align": 1} -} - - -def __get_free_id_list(module, free_ids_cnt): - module_msg_ids = [] - for msg in module["messages"]: - module_msg_ids.append(msg["id"]) - - module_msg_ids.sort() - - free_ids = [] - last_id = 0 - for id in module_msg_ids: - if id - last_id > 0: - free_ids.extend(range(last_id+1, id)) - - last_id = id - - if len(free_ids) < free_ids_cnt: - d = free_ids_cnt - len(free_ids) - free_ids.extend(range(module_msg_ids[-1] + 1, module_msg_ids[-1] + d + 1)) - else: - free_ids = free_ids[:free_ids_cnt] - - return free_ids - - -def __dump_datatypes(modules_map, datatypes_map, free_ids_cnt=10): - dump = "" - - for module_name, module_obj in modules_map.items(): - free_ids = __get_free_id_list(module_obj, free_ids_cnt) - dump += ("%s free ids list: %s" % (module_name, str(free_ids))) + os.linesep - - dump += os.linesep - - for typename, datatype in datatypes_map.items(): - dump += "****************" + os.linesep - dump += typename + os.linesep - - if datatype["plain"]: - continue - - for field in datatype["fields"]: - dump += "\t\t" + field["type"] + " " + field["name"] + ": " - if field["is_array"]: - if field["is_dynamic"]: - dump += "[]" - else: - dump += ("[%d]" % field["num"]) - - dump += os.linesep - - # type_info = message["type_info"] - dump += "\t\tAlignment: " + str(datatype["align"]) + os.linesep - dump += "\t\tStatic size: " + str(datatype["static_size"]) + os.linesep - - dump += "\t\tDepends:" + os.linesep - for dep in datatype["deps"]: - dump += ("\t\t\t" + dep) + os.linesep - - dump += os.linesep + os.linesep - - return dump - - -def main(): - parser = argparse.ArgumentParser(description='Message generator.') - parser.add_argument("-b", "--basedirs", required=True, type=str, nargs="+", - help='Message definition base directories') - parser.add_argument("-m", "--modules", required=True, type=str, nargs="+", help='Modules') - parser.add_argument("-o", "--outdir", type=str, help='Output directory', default=".") - parser.add_argument("-l", "--lang", required=True, type=str, - help='Output language (cpp=C++, go=Golang, js=JavaScript, md=Markdown)') - parser.add_argument("-D", "--define", action='append', help="Define variables in 'key=value' format") - - args = parser.parse_args() - - try: - # Parse variables - variables = {} - if args.define: - for v in args.define: - p = v.split("=") - if len(p) != 2: - raise Exception("Invalid argument in -D option, must be 'key=value'") - variables[p[0]] = p[1] - - modules_map = load_modules(args.basedirs, args.modules) - - data_types_preprocessor = DataTypesPreprocessor(PLAIN_TYPES, SPECIAL_TYPES) - data_types_map = data_types_preprocessor.create_types_map(modules_map) - with open("dump.txt", "w+") as f: - f.write(__dump_datatypes(modules_map, data_types_map)) - - g_type = generators.get(args.lang) - if g_type is None: - raise MessgenException("Unsupported language \"%s\"" % args.lang) - - g = g_type(modules_map, data_types_map, MODULE_SEP, variables) - g.generate(args.outdir) - print("Successfully generated to %s" % args.outdir) - except MessgenException as e: - print("ERROR: %s" % e) - exit(-1) - - -if __name__ == "__main__": - main() - - diff --git a/integrations/cmake/messgen.cmake b/integrations/cmake/messgen.cmake index 3b9f3a21..cd6baad6 100644 --- a/integrations/cmake/messgen.cmake +++ b/integrations/cmake/messgen.cmake @@ -1,97 +1,111 @@ # -# Function adds target for message generation for given modules -# Searches for MODULES in BASEDIRS and generate files in OUTDIR +# Function adds custom command for type generation for given modules +# Searches for TYPES in BASE_DIRS and generate files in OUT_DIR. +# List of generated files provided in OUT_FILES_VAR variable. # -# Provide MESSGEN_OUT_FILES variable which contains all C++ files for target to depend on -# You have to define global variable MESSGEN_DIR pointing to messgen root directory -# -FUNCTION(MESSGEN_GENERATE_MESSAGES BASEDIRS MODULES OUTDIR) - SET(MESSGEN_INPUT_FILES "") - SET(MESSGEN_OUT_FILES "") - - LIST(REMOVE_DUPLICATES MODULES) +function(messgen_generate_types BASE_DIRS OUT_DIR OUT_FILES_VAR OPTIONS) + get_filename_component(MESSGEN_DIR ${CMAKE_CURRENT_FUNCTION_LIST_DIR} DIRECTORY) + get_filename_component(MESSGEN_DIR ${MESSGEN_DIR} DIRECTORY) - FOREACH (MODULE ${MODULES}) - SET(FOUND FALSE) - FOREACH (BASEDIR ${BASEDIRS}) - SET(MESSAGES_PATH ${BASEDIR}/${MODULE}) - IF (EXISTS ${MESSAGES_PATH}) - SET(FOUND TRUE) + set(MESSGEN_INPUT_FILES "") + set(OUT_FILES "") - STRING(FIND ${MODULE} "/" VENDOR_SEP_POS) - IF (VENDOR_SEP_POS EQUAL -1) - MESSAGE(FATAL_ERROR "Messgen module must have the following format: vendor/module_name") - ENDIF () + set(MESSGEN_ARGS "") + foreach (BASE_DIR ${BASE_DIRS}) + list(APPEND MESSGEN_ARGS "--types" ${BASE_DIR}) + endforeach () - STRING(SUBSTRING ${MODULE} 0 ${VENDOR_SEP_POS} MODULE_VENDOR) - STRING(SUBSTRING ${MODULE} ${VENDOR_SEP_POS} -1 MODULE_NAME) + foreach (TYPES_PATH ${BASE_DIRS}) + if (EXISTS ${TYPES_PATH}) + # This will trigger reconfiguration when messages definition changes + # However this requires CMake >= 3.0. Need to change all configs with minimum required version + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${TYPES_PATH}) - SET(MESSAGES_OUTDIR ${OUTDIR}/${MODULE_VENDOR}/msgs${MODULE_NAME}) + file(GLOB_RECURSE TYPES RELATIVE ${TYPES_PATH} ${TYPES_PATH}/*.yaml) - # This will trigger reconfiguration when messages definition changes - # However this requires CMake >= 3.0. Need to change all configs with minimum required version - set_property( - DIRECTORY - APPEND - PROPERTY CMAKE_CONFIGURE_DEPENDS - ${MESSAGES_PATH} - ) + if (TYPES) + foreach (TYPE_YAML ${TYPES}) + string(REGEX REPLACE "(yaml)" "h" MSG_HEADER ${TYPE_YAML}) + string(REGEX REPLACE "(yaml)" "cpp" MSG_CPP ${TYPE_YAML}) - FILE(GLOB MODULE_MESSAGES RELATIVE ${MESSAGES_PATH} ${MESSAGES_PATH}/*.yaml) - LIST(REMOVE_ITEM MODULE_MESSAGES "_protocol.yaml") - LIST(REMOVE_ITEM MODULE_MESSAGES "_constants.yaml") - LIST(REMOVE_ITEM MODULE_MESSAGES "_types.yaml") + list(APPEND MESSGEN_INPUT_FILES ${TYPES_PATH}/${TYPE_YAML}) + list(APPEND OUT_FILES ${OUT_DIR}/${MSG_HEADER}) + endforeach () + endif () + endif () + endforeach () - IF (MODULE_MESSAGES) - FOREACH (MSG_YAML ${MODULE_MESSAGES}) - STRING(REGEX REPLACE "(yaml)" "h" MSG_HEADER ${MSG_YAML}) - STRING(REGEX REPLACE "(yaml)" "cpp" MSG_CPP ${MSG_YAML}) + if (OPTIONS) + list(APPEND MESSGEN_ARGS "--options") + list(APPEND MESSGEN_ARGS ${OPTIONS}) + endif () - LIST(APPEND MESSGEN_INPUT_FILES ${MESSAGES_PATH}/${MSG_YAML}) + file(GLOB_RECURSE GENERATOR_DEPS ${MESSGEN_DIR}/*.py) + add_custom_command( + OUTPUT ${OUT_FILES} + COMMAND "python3" + ARGS + ${MESSGEN_DIR}/messgen-generate.py + ${MESSGEN_ARGS} + "--outdir" ${OUT_DIR} + "--lang" "cpp" + DEPENDS ${GENERATOR_DEPS} ${MESSGEN_INPUT_FILES} + ) + set(${OUT_FILES_VAR} ${OUT_FILES} PARENT_SCOPE) +endfunction() - LIST(APPEND MESSGEN_OUT_FILES ${MESSAGES_OUTDIR}/${MSG_HEADER}) - ENDFOREACH () - ENDIF () +# +# Function adds custom command for protocol generation for give. +# Searches for PROTOCOL file and generates files in OUT_DIR. +# List of generated files provided in OUT_FILES_VAR variable. +# +function(messgen_generate_protocol BASE_DIR PROTOCOL OUT_DIR OUT_FILES_VAR) + get_filename_component(MESSGEN_DIR ${CMAKE_CURRENT_FUNCTION_LIST_DIR} DIRECTORY) + get_filename_component(MESSGEN_DIR ${MESSGEN_DIR} DIRECTORY) - LIST(APPEND MESSGEN_INPUT_FILES - ${MESSAGES_PATH}/_protocol.yaml - ${MESSAGES_PATH}/_constants.yaml - ) + set(PROTO_FILE "${BASE_DIR}/${PROTOCOL}.yaml") + set(OUT_FILE "${OUT_DIR}/${PROTOCOL}.h") - LIST(APPEND MESSGEN_OUT_FILES - ${MESSAGES_OUTDIR}/proto.h - ${MESSAGES_OUTDIR}/messages.h - ${MESSAGES_OUTDIR}/constants.h - ${MESSAGES_OUTDIR}/metadata.cpp - ) - ENDIF () - ENDFOREACH () + file(GLOB_RECURSE GENERATOR_DEPS ${MESSGEN_DIR}/*.py) + add_custom_command( + OUTPUT ${OUT_FILE} + COMMAND "python3" + ARGS + ${MESSGEN_DIR}/messgen-generate.py + "--protocol" "${BASE_DIR}:${PROTOCOL}" + "--outdir" ${OUT_DIR} + "--lang" "cpp" + DEPENDS ${GENERATOR_DEPS} "${BASE_DIR}/${PROTOCOL}.yaml" + ) + set(${OUT_FILES_VAR} ${OUT_FILE} PARENT_SCOPE) - IF (NOT FOUND) - MESSAGE(FATAL_ERROR "Messages for module ${MODULE} not found! Searched in: ${BASEDIRS}") - ENDIF () - ENDFOREACH () +endfunction() - SET(MESSGEN_OUT_FILES ${MESSGEN_OUT_FILES} PARENT_SCOPE) +# +# Function creates a target for specified types. +# +function(messgen_add_types_library LIBRARY_NAME BASE_DIRS MODE) + string(JOIN "," OPTIONS "mode=${MODE}" ${ARGN}) + set(MESSAGES_OUT_DIR "${CMAKE_BINARY_DIR}/${LIBRARY_NAME}/generated_src") + get_filename_component(MESSGEN_DIR ${CMAKE_CURRENT_FUNCTION_LIST_DIR} DIRECTORY) + get_filename_component(MESSGEN_DIR ${MESSGEN_DIR} DIRECTORY) + add_library(${LIBRARY_NAME} INTERFACE) + messgen_generate_types("${BASE_DIRS}" "${MESSAGES_OUT_DIR}" MESSGEN_OUT_FILES "${OPTIONS}") + target_sources(${LIBRARY_NAME} INTERFACE ${MESSGEN_OUT_FILES}) + target_include_directories(${LIBRARY_NAME} INTERFACE + ${MESSAGES_OUT_DIR} + ${MESSGEN_DIR}/port/cpp_${MODE}) +endfunction() - IF (MESSGEN_OUT_FILES) - FILE(GLOB_RECURSE GENERATOR_DEPS ${MESSGEN_DIR}/*.py) - ADD_CUSTOM_COMMAND( - OUTPUT ${MESSGEN_OUT_FILES} - COMMAND "python3" - ARGS - ${MESSGEN_DIR}/generate.py - "-b" - ${BASEDIRS} - "-m" - ${MODULES} - "-o" - ${OUTDIR} - "-l" - "cpp" - DEPENDS - ${GENERATOR_DEPS} - ${MESSGEN_INPUT_FILES} - ) - ENDIF () -ENDFUNCTION() +# +# Function creates a target for specified protocol. +# +function(messgen_add_proto_library LIBRARY_NAME BASE_DIR PROTOCOL TYPES_TARGET) + string(JOIN "," OPTIONS "mode=${MODE}" ${ARGN}) + set(MESSAGES_OUT_DIR "${CMAKE_BINARY_DIR}/${LIBRARY_NAME}/generated_src") + add_library(${LIBRARY_NAME} INTERFACE) + messgen_generate_protocol(${BASE_DIR} ${PROTOCOL} "${MESSAGES_OUT_DIR}" MESSGEN_OUT_FILES) + target_sources(${LIBRARY_NAME} INTERFACE ${MESSGEN_OUT_FILES}) + target_include_directories(${LIBRARY_NAME} INTERFACE ${MESSAGES_OUT_DIR}) + target_link_libraries(${LIBRARY_NAME} INTERFACE ${TYPES_TARGET}) +endfunction() diff --git a/messgen-generate.py b/messgen-generate.py new file mode 100644 index 00000000..e1915fd4 --- /dev/null +++ b/messgen-generate.py @@ -0,0 +1,57 @@ +import argparse +import os + +from messgen.validation import validate_protocol, validate_types +from messgen import generator, yaml_parser +from pathlib import Path + + +print(os.getcwd()) +print(os.path.dirname(os.path.realpath(__file__))) + + +def generate(args: argparse.Namespace): + if not args.protocol and not args.types: + raise RuntimeError("No types or protocols to generate (--types or --protocols)") + + print("Options:") + opts = {} + for a in args.options.split(","): + p = a.split("=") + if len(p) == 2: + print(f" {p[0]} = {p[1]}") + opts[p[0]] = p[1] + + parsed_protocols = yaml_parser.parse_protocols(args.protocol) + parsed_types = yaml_parser.parse_types(args.types) + + if (gen := generator.get_generator(args.lang, opts)) is not None: + if parsed_protocols and parsed_types: + for proto_def in parsed_protocols.values(): + validate_protocol(proto_def, parsed_types) + + if parsed_types: + validate_types(parsed_types) + gen.generate_types(Path(args.outdir), parsed_types) + + if parsed_protocols: + gen.generate_protocols(Path(args.outdir), parsed_protocols) + + else: + raise RuntimeError("Unsupported language \"%s\"" % args.lang) + + print("Successfully generated to %s" % args.outdir) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--types", action='append', help="Type directory to load, may repeat") + parser.add_argument("--protocol", action='append', help="Protocol to load in format /path/of/basedir:namespace/of/proto, may repeat") + parser.add_argument("--lang", required=True, help="Output language") + parser.add_argument("--outdir", required=True, help="Output directory") + parser.add_argument("--options", default="", help="Generator options") + generate(parser.parse_args()) + + +if __name__ == "__main__": + main() diff --git a/messgen/__init__.py b/messgen/__init__.py deleted file mode 100644 index ad64c296..00000000 --- a/messgen/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .messgen_ex import MessgenException \ No newline at end of file diff --git a/messgen/common.py b/messgen/common.py new file mode 100644 index 00000000..7215ce1b --- /dev/null +++ b/messgen/common.py @@ -0,0 +1,15 @@ +import os + +SEPARATOR = "/" +SIZE_TYPE = "uint32" + + +def write_file_if_diff(fn, code_lines): + old_code = None + try: + old_code = open(fn, "r").read() + except: + pass + new_code = "\n".join(code_lines) + if old_code != new_code: + open(fn, "w+").write(new_code) diff --git a/messgen/cpp_generator.py b/messgen/cpp_generator.py index b45725cc..3d31bd04 100644 --- a/messgen/cpp_generator.py +++ b/messgen/cpp_generator.py @@ -1,856 +1,874 @@ +import json import os -from .messgen_ex import MessgenException -from .json_generator import JsonGenerator -from .version_protocol import VersionProtocol - -PROTO_ID_VAR_TYPE = "uint8_t" -PROTO_MAX_MESSAGE_SIZE_TYPE = "uint32_t" -MESSGEN_NAMESPACE = "messgen" - -MESSAGE_ID_C_TYPE = "uint8_t" -MESSAGE_SIZE_C_TYPE = "size_t" -MESSAGE_PROTO_C_TYPE = "uint8_t" - -TYPE_FIELD_SIZE = 1 -DYN_FIELD_LEN_SIZE = 4 -INPUT_BUF_NAME = "buf" -INPUT_ALLOC_NAME = "allocator" -INDENT = " " - -PLAIN2CPP_TYPES_MAP = { - "char": "char", - "uint8": "uint8_t", - "uint16": "uint16_t", - "uint32": "uint32_t", - "uint64": "uint64_t", - "int8": "int8_t", - "int16": "int16_t", - "int32": "int32_t", - "int64": "int64_t", - "float32": "float", - "float64": "double", -} - - -def strlen(s): - return "%s.length()" % s - - -def to_cpp_type(_type): - if _type in PLAIN2CPP_TYPES_MAP: - return PLAIN2CPP_TYPES_MAP[_type] - else: - return _type.replace("/", "::") - - -def is_type_constant(_type): - return _type in PLAIN2CPP_TYPES_MAP - - -def to_cpp_type_short(_type): - if _type in PLAIN2CPP_TYPES_MAP: - return PLAIN2CPP_TYPES_MAP[_type] - else: - return _type.split("/")[-1] - - -def make_module_include(module, file): - return "#include <%s/%s.h>" % (module["namespace"], file) - - -def make_include(file): - return "#include \"%s\"" % file - - -def make_variable(name, var_type, array_size): - var = var_type + " " + name - if array_size > 0: - var += "[" + str(array_size) + "]" - var += ";" - - return var - - -def open_namespace(module_namespace): - code = [] - namespaces = module_namespace.split("::") - for ns in namespaces: - code.append("namespace %s {" % ns) - - return code - - -def close_namespace(namespace): - code = [] - namespaces = namespace.split("::") - for ns in reversed(namespaces): - code.append("} // %s" % ns) - - code.append("") - return code - - -def make_enum(name, basetype, fields): - enum = ["enum %s : %s {" % (name, basetype)] - - for field in fields: - enum.append("\t%s = %s," % (str(field["name"]).upper(), field["value"])) - enum.append("};") - - return enum - - -def memcpy(dst, src, size): - return "memcpy(%s, %s, %s);" % (dst, src, size) - - -def set_inc_var(name, value): - return "%s += %s;" % (name, value) - - -def set_dec_var(name, value): - return "%s -= %s;" % (name, value) - - -def set_var(name, value): - return "%s = %s;" % (name, value) - - -def ptr(var): - return "&" + str(var) - - -def ignore_variable(var): - return "(void)%s;" % var - - -def simplify_type_namespace(typename, current_namespace): - typename_entries = typename.split("::") - ns_entries = current_namespace.split("::") - - i = 0 - while typename_entries[i] == ns_entries[i]: - i += 1 - - new_typename_entries = typename_entries[i:] - return "".join([entry for entry in new_typename_entries]) - - -def write_code_file(fpath, code): - with open(fpath, "w+") as f: - for line in code: - f.write(line + os.linesep) - - -def generate_messages_file(includes): - file = [ - "#pragma once", - "", - *includes, - "" - ] - - return file - - -def generate_constants_file(namespace, constants): - code = [ - "#pragma once", - "", - "#include ", - "#include ", - "", - *open_namespace(namespace), - ] - - # Generate enums - for const in constants: +import textwrap + +from contextlib import contextmanager +from dataclasses import asdict +from pathlib import ( + PosixPath, + Path, +) + +from .common import ( + SEPARATOR, + SIZE_TYPE, + write_file_if_diff, +) +from .model import ( + ArrayType, + BasicType, + EnumType, + EnumValue, + FieldType, + MapType, + MessgenType, + Protocol, + StructType, + TypeClass, + VectorType, +) + + +def _unqual_name(type_name: str) -> str: + return PosixPath(type_name).stem + + +def _qual_name(type_name: str) -> str: + return type_name.replace(SEPARATOR, "::") + + +def _split_last_name(type_name) -> tuple[str, str]: + split_name = type_name.split(SEPARATOR) + return SEPARATOR.join(split_name[:-1]), split_name[-1] + + +@contextmanager +def _namespace(name: str, code:list[str]): + ns_name = None + try: + ns_name = _qual_name(name) + if ns_name: + code.append(f"namespace {ns_name} {{") + code.append("") + yield + + finally: + if ns_name: + code.append("") + code.append(f"}} // namespace {ns_name}") + code.append("") + + +@contextmanager +def _struct(name: str, code: list[str]): + try: + code.append(f"struct {name} {{") + yield + finally: + code.append("};") code.append("") - code.extend(make_enum(const["name"], to_cpp_type(const["basetype"]), const["fields"])) - - code.extend(close_namespace(namespace)) - - # Enable bitmask operations for generated enums - code.extend(open_namespace("messgen")) - for const in constants: - if const.get("bitmask") is True: - full_type = namespace + "::" + const["name"] - code.append("ENABLE_BITMASK_OPERATORS(%s);" % full_type) - code.extend(close_namespace("messgen")) - - return code - - -def generate_proto_file(namespace, module, modules_map): - proto_id = module["proto_id"] - max_msg_size = module["max_datatype_size"] - - struct = ["struct ProtoInfo {", - " static constexpr %s ID = %d;" % (PROTO_ID_VAR_TYPE, proto_id), - " static constexpr %s MAX_MESSAGE_SIZE = %d;" % (PROTO_MAX_MESSAGE_SIZE_TYPE, max_msg_size), - " static constexpr const char* VERSION = \"%s\";" % (VersionProtocol(module).generate()), - "};"] - - code = [ - "#pragma once", - "", - *open_namespace(namespace), - "", - *struct, - "", - "static constexpr %s PROTO_ID = %d;" % (PROTO_ID_VAR_TYPE, proto_id), - "static constexpr %s PROTO_MAX_MESSAGE_SIZE = %d;" % (PROTO_MAX_MESSAGE_SIZE_TYPE, max_msg_size), - "static constexpr const char* PROTO_VERSION = \"%s\";" % (VersionProtocol(module).generate()), - "", - *close_namespace(namespace) - ] - - return code - - -def get_dyn_field_ptr(field): - return field["name"] + ".ptr" - - -def get_dyn_field_size(field): - return field["name"] + ".size" - -def get_dyn_field_vars(field): - return get_dyn_field_ptr(field), get_dyn_field_size(field) +def _inline_comment(type_def: FieldType | EnumValue): + try: + return " ///< %s" % type_def.comment + finally: + return "" -def allocate_memory(dst, mem_type, size): - return ["%s = %s.alloc<%s>(%s);" % (dst, INPUT_ALLOC_NAME, mem_type, str(size)), - "if (%s == nullptr) {return -1;}" % dst] - -def get_dynamic_field_items_num(): - size = "" - for i in range(DYN_FIELD_LEN_SIZE): - shift_str = "(ptr[%d] << (%dU*8U))" % (i, i) - size += " " + shift_str + " |" - - return size[:-2] - - -def get_mem_size(dynamic_field_len, dyn_type): - return "%s * sizeof(%s)" % (str(dynamic_field_len), dyn_type) - - -def is_null(s): - return "%s == nullptr" % s - - -def is_not_null(s): - return "%s != nullptr" % s - - -def if_not_null(s): - return "if (%s)" % is_not_null(s) +def _indent(c): + spaces = " " + if type(c) is str: + return spaces + c + elif type(c) is list: + r = [] + for i in c: + r.append(spaces + i if i else "") + return r + else: + raise RuntimeError("Unsupported type for indent: %s" % type(c)) -def if_null(s): - return "if (%s)" % is_null(s) +class FieldsGroup: + def __init__(self) -> None: + self.fields: list = [] + self.field_names: list = [] + self.size: int = 0 + def __repr__(self) -> str: + return str(self) -def arr_item(arr, idx): - return "%s[%s]" % (arr, idx) + def __str__(self) -> str: + return "" % (self.size, self.field_names) class CppGenerator: - def __init__(self, modules_map, data_types_map, module_sep, variables): - self.MODULE_SEP = module_sep - self._modules_map = modules_map - self._data_types_map = data_types_map - self._variables = variables - - self._indent_cnt = 0 - self._indent = "" - self._code = [] - self._metadata_json = (self._variables.get("metadata_json", "false").lower() == "true") - if self._metadata_json: - self._json_generator = JsonGenerator(modules_map, data_types_map, module_sep, variables) - - def generate(self, out_dir): - for module_name, module in self._modules_map.items(): - module_out_dir = out_dir + os.path.sep + module["namespace"].replace(self.MODULE_SEP, os.path.sep) - - try: - os.makedirs(module_out_dir) - except: - pass - - namespace = module["namespace"].replace(self.MODULE_SEP, "::") - - proto_file = generate_proto_file(namespace, module, self._modules_map) - proto_fpath = module_out_dir + os.path.sep + "proto.h" - write_code_file(proto_fpath, proto_file) - - all_includes = [] - - source_fpath = module_out_dir + os.path.sep + "metadata.cpp" - source_includes = ["#include "] - source_file_data = [] - - for message in module["messages"]: - header_file = self.__generate_message_header(namespace, message) - header_fpath = module_out_dir + os.path.sep + message["name"] + ".h" - write_code_file(header_fpath, header_file) - - source_includes += [make_include(message["name"] + ".h")] - source_file_data += self.generate_metadata(module, message) - source_file_data += [""] - - cpp_include_path = make_module_include(module, message["name"]) - all_includes.append(cpp_include_path) - - source = self.__generate_message_source(namespace, - source_includes, - source_file_data) - write_code_file(source_fpath, source) - - all_includes.append(make_include("proto.h")) - all_includes.append(make_include("constants.h")) + _PREAMBLE_HEADER = ["#pragma once", ""] + _EXT_HEADER = ".h" + _CPP_TYPES_MAP = { + "bool": "bool", + "uint8": "uint8_t", + "int8": "int8_t", + "uint16": "uint16_t", + "int16": "int16_t", + "uint32": "uint32_t", + "int32": "int32_t", + "uint64": "uint64_t", + "int64": "int64_t", + "float32": "float", + "float64": "double", + } + + def __init__(self, options: dict): + self._options: dict = options + self._includes: set = set() + self._ctx: dict = {} + self._types: dict[str, MessgenType] = {} + + def generate_types(self, out_dir: Path, types: dict[str, MessgenType]) -> None: + self._types = types + for type_name, type_def in types.items(): + if type_def.type_class not in [TypeClass.struct, TypeClass.enum]: + continue + file_name = out_dir / (type_name + self._EXT_HEADER) + file_name.parent.mkdir(parents=True, exist_ok=True) + write_file_if_diff(file_name, self._generate_type_file(type_name, type_def)) + + def generate_protocols(self, out_dir: Path, protocols: dict[str, Protocol]) -> None: + for proto_name, proto_def in protocols.items(): + file_name = out_dir / (proto_name + self._EXT_HEADER) + file_name.parent.mkdir(parents=True, exist_ok=True) + write_file_if_diff(file_name, self._generate_proto_file(proto_name, proto_def)) + + def _get_mode(self): + return self._options.get("mode", "stl") + + def _get_cpp_standard(self): + return int(self._options.get("cpp_standard", "11")) + + def _reset_file(self): + self._includes.clear() + + def _generate_type_file(self, type_name: str, type_def: MessgenType) -> list: + print(f"Generate type: {type_name}") + self._reset_file() + code: list[str] = [] + + with _namespace(_split_last_name(type_name)[0], code): + if isinstance(type_def, EnumType): + code.extend(self._generate_type_enum(type_name, type_def)) + + elif isinstance(type_def, StructType): + code.extend(self._generate_type_struct(type_name, type_def)) + code.extend(self._generate_type_members_of(type_name, type_def)) + + code = self._PREAMBLE_HEADER + self._generate_includes() + code + + return code + + def _generate_proto_file(self, proto_name: str, proto_def: Protocol) -> list[str]: + print("Generate protocol file: %s" % proto_name) + + self._reset_file() + code: list[str] = [] + + self._add_include("cstdint") + self._add_include("messgen/messgen.h") + + namespace_name, class_name = _split_last_name(proto_name) + with _namespace(namespace_name, code): + with _struct(class_name, code): + for message in proto_def.messages.values(): + self._add_include(message.type + self._EXT_HEADER) + + proto_id = proto_def.proto_id + if proto_id is not None: + code.append(f" constexpr static inline int PROTO_ID = {proto_id};") + code.append(f" constexpr static inline uint32_t HASH = {hash(proto_def)};") + + code.extend(self._generate_messages(class_name, proto_def)) + code.extend(self._generate_reflect_message_decl()) + code.extend(self._generate_dispatcher_decl()) + + code.extend(self._generate_protocol_members_of(class_name, proto_def)) + code.extend(self._generate_reflect_message(class_name, proto_def)) + code.extend(self._generate_dispatcher(class_name)) + code.append("") + + return self._PREAMBLE_HEADER + self._generate_includes() + code + + + def _generate_messages(self, class_name: str, proto_def: Protocol): + self._add_include("tuple") + code: list[str] = [] + for message in proto_def.messages.values(): + code.extend(textwrap.indent(textwrap.dedent(f""" + struct {message.name} : {_qual_name(message.type)} {{ + using data_type = {_qual_name(message.type)}; + using protocol_type = {class_name}; + constexpr inline static int PROTO_ID = protocol_type::PROTO_ID; + constexpr inline static int MESSAGE_ID = {message.message_id}; + }};"""), " ").splitlines()) + return code + + def _generate_protocol_members_of(self, class_name: str, proto_def: Protocol): + self._add_include("tuple") + code: list[str] = [] + code.append(f"[[nodiscard]] consteval auto members_of(::messgen::reflect_t<{class_name}>) noexcept {{") + code.append(" return std::tuple{") + for message in proto_def.messages.values(): + code.append(f" ::messgen::member<{class_name}, {class_name}::{message.name}>{{\"{message.name}\"}},") + code.append(" };") + code.append("}") + code.append("") + return code + + @staticmethod + def _generate_reflect_message_decl() -> list[str]: + return textwrap.indent(textwrap.dedent(""" + template + constexpr static auto reflect_message(int msg_id, Fn &&fn); + """), " ").splitlines() + + @staticmethod + def _generate_reflect_message(class_name: str, proto: Protocol) -> list[str]: + code: list[str] = [] + code.append("template ") + code.append(f"constexpr auto {class_name}::reflect_message(int msg_id, Fn &&fn) {{") + code.append(" switch (msg_id) {") + for message in proto.messages.values(): + msg_type = f"{class_name}::{_unqual_name(message.name)}" + code.append(f" case {msg_type}::MESSAGE_ID:") + code.append(f" std::forward(fn)(::messgen::reflect_type<{msg_type}>);") + code.append(f" return;") + code.append(" }") + code.append("}") + return code + + @staticmethod + def _generate_dispatcher_decl() -> list[str]: + return textwrap.indent(textwrap.dedent(""" + template + constexpr static bool dispatch_message(int msg_id, const uint8_t *payload, T handler); + """), " ").splitlines() + + @staticmethod + def _generate_dispatcher(class_name: str) -> list[str]: + return textwrap.dedent(f""" + template + constexpr bool {class_name}::dispatch_message(int msg_id, const uint8_t *payload, T handler) {{ + auto result = false; + reflect_message(msg_id, [&](R) {{ + using message_type = messgen::splice_t; + if constexpr (requires(message_type msg) {{ handler(msg); }}) {{ + auto msg = message_type{{}}; + msg.deserialize(payload); + handler(std::move(msg)); + result = true; + }} + }}); + return result; + }}""").splitlines() + + @staticmethod + def _generate_comment_type(type_def): + if not type_def.comment: + return [] + + code = [] + code.append("/**") + code.append(" * %s" % type_def.comment) + code.append(" */") + return code + + def _generate_type_enum(self, type_name, type_def): + self._add_include("messgen/messgen.h") + + unqual_name = _unqual_name(type_name) + qual_name = _qual_name(type_name) + + code = [] + code.extend(self._generate_comment_type(type_def)) + code.append(f"enum class {unqual_name}: {self._cpp_type(type_def.base_type)} {{") + for enum_value in type_def.values: + code.append(" %s = %s,%s" % (enum_value.name, enum_value.value, _inline_comment(enum_value))) + code.append("};") - # Messages.h - messages_file = generate_messages_file(all_includes) - messages_fpath = module_out_dir + os.path.sep + "messages.h" - write_code_file(messages_fpath, messages_file) + code.append("") + code.append(f"[[nodiscard]] inline constexpr std::string_view name_of(::messgen::reflect_t<{unqual_name}>) noexcept {{") + code.append(f" return \"{qual_name}\";") + code.append("}") + + return code + + def _get_alignment(self, type_def: MessgenType): + type_class = type_def.type_class + + if isinstance(type_def, BasicType): + if type_class in [TypeClass.scalar]: + return type_def.size + elif type_class in [TypeClass.string, TypeClass.bytes]: + return self._get_alignment(self._types[SIZE_TYPE]) + + if isinstance(type_def, EnumType): + return type_def.size + + elif isinstance(type_def, StructType): + # Alignment of struct is equal to max of the field alignments + a_max = 0 + for field in type_def.fields: + a = self._get_alignment(self._types[field.type]) + if a > a_max: + a_max = a + return a_max + + elif isinstance(type_def, ArrayType): + # Alignment of array is equal to alignment of element + return self._get_alignment(self._types[type_def.element_type]) + + elif isinstance(type_def, VectorType): + # Alignment of array is equal to max of size field alignment and alignment of element + a_sz = self._get_alignment(self._types[SIZE_TYPE]) + a_el = self._get_alignment(self._types[type_def.element_type]) + return max(a_sz, a_el) + + elif isinstance(type_def, MapType): + # Alignment of array is equal to max of size field alignment and alignment of element + a_sz = self._get_alignment(self._types[SIZE_TYPE]) + a_key = self._get_alignment(self._types[type_def.key_type]) + a_value = self._get_alignment(self._types[type_def.value_type]) + return max(a_sz, a_key, a_value) - constants = module.get("constants") - if constants is None: - constants = [] + else: + raise RuntimeError("Unsupported type_class in _get_alignment: %s" % type_class) + + def _check_alignment(self, type_def, offs): + align = self._get_alignment(type_def) + return offs % align == 0 + + def _generate_type_struct(self, type_name: str, type_def: StructType): + fields = type_def.fields + + self._add_include("cstddef") + self._add_include("cstring") + self._add_include("messgen/messgen.h") + + unqual_name = _unqual_name(type_name) + + code = [] + code.extend(self._generate_comment_type(type_def)) + code.append(f"struct {unqual_name} {{") + + groups = self._field_groups(fields) + if len(groups) > 1 and self._all_fields_scalar(fields): + print("Warn: padding in '%s' after '%s' causes extra memcpy call during serialization." % ( + type_name, groups[0].fields[0].name)) + + # IS_FLAT flag + is_flat_str = "false" + is_empty = len(groups) == 0 + is_flat = is_empty or (len(groups) == 1 and groups[0].size is not None) + if is_flat: + code.append(_indent("constexpr static inline size_t FLAT_SIZE = %d;" % (0 if is_empty else groups[0].size))) + is_flat_str = "true" + code.append(_indent(f"constexpr static inline bool IS_FLAT = {is_flat_str};")) + code.append(_indent(f"constexpr static inline uint32_t HASH = {hash(type_def)};")) + code.append(_indent(f"constexpr static inline const char* NAME = \"{_qual_name(type_name)}\";")) + code.append(_indent(f"constexpr static inline const char* SCHEMA = R\"_({self._generate_schema(type_def)})_\";")) + code.append("") - constants_file = generate_constants_file(namespace, constants) - constants_fpath = module_out_dir + os.path.sep + "constants.h" - write_code_file(constants_fpath, constants_file) + for field in type_def.fields: + field_c_type = self._cpp_type(field.type) + code.append(_indent(f"{field_c_type} {field.name}; {_inline_comment(field)}")) - def __generate_message_header(self, namespace, message): - self.reset() - msg_struct, msg_includes = self.generate_message(message) - msg_simple_detector = self.generate_detector(namespace, message) + # Serialize function + code_ser = [] - header = [ - "#pragma once", - "", - *msg_includes, - "", - "", - *open_namespace(namespace), - "", - *msg_struct, + code_ser.extend([ + "size_t _size = 0;", + "[[maybe_unused]] size_t _field_size;", "", - *close_namespace(namespace), - "", - *msg_simple_detector - ] - - return header - - def __generate_message_source(self, namespace, includes, data): - self.reset() + ]) - source = includes + [ + for group in groups: + if len(group.fields) > 1: + # There is padding before current field + # Write together previous aligned fields, if any + code_ser.append("// %s" % ", ".join(group.field_names)) + code_ser.extend(self._memcpy_to_buf("&" + group.fields[0].name, group.size)) + elif len(group.fields) == 1: + field = group.fields[0] + field_name = field.name + field_type_def = self._types.get(field.type) + code_ser.extend(self._serialize_field(field_name, field_type_def)) + code_ser.append("") + code_ser.append("return _size;") + + code_ser = ["", + "size_t serialize(uint8_t *" + ("_buf" if not is_empty else "") + ") const {", + ] + _indent(code_ser) + [ + "}"] + code.extend(_indent(code_ser)) + + # Deserialize function + code_deser = [] + + code_deser.extend([ + "size_t _size = 0;", + "[[maybe_unused]] size_t _field_size;", "", - *open_namespace(namespace), - "" - ] + data + [ - "", - *close_namespace(namespace) - ] - - return source - - def generate_message(self, message_obj): - data_type = self._data_types_map[message_obj["typename"]] - - message_id_const = "static constexpr %s TYPE = %d;" % \ - (MESSAGE_ID_C_TYPE, message_obj["id"]) - - message_size_const = self.generate_static_size(data_type) - - message_proto_id_const = "static constexpr %s PROTO = PROTO_ID;" % MESSAGE_PROTO_C_TYPE - - message_has_dynamics = "static constexpr bool HAS_DYNAMICS = %s;" % str(data_type["has_dynamics"]).lower() - - if data_type["has_dynamics"]: - message_obj["deps"].append("messgen/Dynamic") - - self.start_block("struct " + message_obj["name"]) - self.extend([ - message_id_const, - message_size_const, - message_proto_id_const, - message_has_dynamics, - "" ]) - has_const, has_str = self.generate_data_fields(data_type["fields"]) - self.append("") - - cpp_typename = message_obj["typename"].replace("/", "::") - self.generate_compare_operator(cpp_typename, data_type) - self.append("") - - self.generate_serialize_method(message_obj) - self.append("") - - self.generate_parse_method(message_obj) - self.append("") - - self.generate_get_size_method() - self.append("") - - self.generate_get_dynamic_size_method(message_obj) - self.append("") - - self.append("static const messgen::Metadata METADATA;") - self.append("") - - self.stop_block(";") - - includes = ["#include ", - "#include ", - ] - - if has_str: - includes.append("#if (__cplusplus >= 201703L)") - includes.append("# include ") - includes.append("#endif") - - includes += ["#include ", - "#include ", - "#include ", - "#include ", - "#include ", - "#include \"proto.h\"", - ] - - if has_const: - includes.append("#include \"constants.h\"") - - for dep in message_obj["deps"]: - inc = "#include <" + dep + ".h>" - includes.append(inc) + groups = self._field_groups(fields) + for group in groups: + if len(group.fields) > 1: + # There is padding before current field + # Write together previous aligned fields, if any + code_deser.append("// %s" % ", ".join(group.field_names)) + code_deser.extend(self._memcpy_from_buf("&" + group.fields[0].name, group.size)) + elif len(group.fields) == 1: + field = group.fields[0] + field_type_def = self._types.get(field.type) + code_deser.extend(self._deserialize_field(field.name, field_type_def)) + code_deser.append("") + code_deser.append("return _size;") + + alloc = "" + if self._get_mode() == "nostl": + alloc = ", messgen::Allocator &_alloc" + code_deser = ["", + "size_t deserialize(const uint8_t *" + ("_buf" if not is_empty else "") + alloc + ") {", + ] + _indent(code_deser) + [ + "}"] + code.extend(_indent(code_deser)) + + # Size function + code_ss = [] + + fixed_size = 0 + fixed_fields = [] + for field in fields: + field_type_def = self._types[field.type] + field_size = field_type_def.size - return list(self._code), includes + if field_size is None: + code_ss.extend(self._serialized_size_field(field.name, field_type_def)) + code_ss.append("") + else: + fixed_fields.append(field.name) + fixed_size += field_size + + code_ss.append("return _size;") + + code_ss = ["", + "[[nodiscard]] size_t serialized_size() const {", + _indent("// %s" % ", ".join(fixed_fields)), + _indent("size_t _size = %d;" % fixed_size), + "", + ] + _indent(code_ss) + [ + "}"] + code.extend(_indent(code_ss)) + + + + if self._get_cpp_standard() >= 20: + # Operator <=> + code.append("") + code.append(_indent("auto operator<=>(const %s &) const = default;" % unqual_name)) + + code.append("};") + + if self._get_cpp_standard() < 20: + # Operator == + code_eq = [] + if len(fields) > 0: + field_name = fields[0].name + code_eq.append("return l.%s == r.%s" % (field_name, field_name)) + for field in fields[1:]: + field_name = field.name + code_eq.append(" and l.%s == r.%s" % (field_name, field_name)) + else: + code_eq.append("return true") + code_eq[-1] += ";" + + code.extend([ + "", + f"bool operator==(const {unqual_name}& l, const {unqual_name}& r) {{", + ] + _indent(code_eq) + [ + "}" + ]) + + return code + + @staticmethod + def _generate_schema(type_def: MessgenType): + return json.dumps(asdict(type_def)).replace(" ", "") + + def _add_include(self, inc, scope="global"): + self._includes.add((inc, scope)) + + def _generate_includes(self): + code = [] + for inc in sorted(list(self._includes)): + if inc[1] == "local": + code.append("#include \"%s\"" % inc[0]) + else: + code.append("#include <%s>" % inc[0]) + if len(code) > 0: + code.append("") + return code - def generate_detector(self, namespace, message_obj): - declaration = ["struct SimpleDetector<%s::%s> {" % (namespace, message_obj["name"])] - using = [" using suspect = %s::%s;" % (namespace, message_obj["name"])] - fields = [" && SimpleDetector::is_simple_enough" % field["name"] for field in - message_obj["fields"]] - return [ - *open_namespace("messgen"), - "", - "template<>", - *declaration, - *using, - " static const bool is_simple_enough = sizeof(suspect) == suspect::STATIC_SIZE", - *fields, - " ;", - "};", - "", - *close_namespace("messgen") - ] + def _generate_type_members_of(self, type_name: str, type_def: StructType): + self._add_include("tuple") - def generate_get_size_method(self): - self.start_block("size_t get_size() const") - self.extend([ - "return STATIC_SIZE + get_dynamic_size();" - ]) - self.stop_block() - - def generate_get_dynamic_size_method(self, message_obj): - self.start_block("size_t get_dynamic_size() const") - self.append("size_t size = 0;") - - for field in message_obj["fields"]: - typeinfo = self._data_types_map[field["type"]] - - if field["is_array"]: - if typeinfo["plain"] and field["is_dynamic"]: - dyn_len_var = get_dyn_field_size(field) - mem_size = dyn_len_var + "*" + str(typeinfo["static_size"]) - self.append(set_inc_var("size", mem_size)) - - elif not typeinfo["plain"]: - if field["is_dynamic"]: - size_limit = get_dyn_field_size(field) - ptr = "%s.ptr" % field["name"] - size_func = "get_size" - else: - size_limit = str(field["num"]) - ptr = field["name"] - size_func = "get_dynamic_size" - - self.start_for_cycle(size_limit) - self.append(set_inc_var("size", "%s[i].%s()" % (ptr, size_func))) - self.stop_cycle() - - elif not typeinfo["plain"]: - self.append(set_inc_var("size", "messgen::Serializer::get_dynamic_size(%s)" % ( - field["name"], field["name"]))) - - elif field["type"] == "string": - self.append(set_inc_var("size", strlen(field["name"]))) - - self.append("return size;") - self.stop_block() - - def generate_compare_operator(self, typename, datatype): - self.start_block("bool operator== (const " + typename + "& " + "other) const") - - if len(datatype["fields"]) == 0: - self.append("(void)other;") - self.append("return true;") - self.stop_block() - else: - for field in datatype["fields"]: - typeinfo = self._data_types_map[field["type"]] + unqual_name = _unqual_name(type_name) - if not field["is_array"]: - self.append("if (!(%s == other.%s)) {return false;}" % (field["name"], field["name"])) + code: list[str] = [] + code.append("") + code.append(f"[[nodiscard]] consteval auto members_of(::messgen::reflect_t<{unqual_name}>) noexcept {{") + code.append(" return std::tuple{") + for field in type_def.fields: + code.append(f" ::messgen::member_variable{{{{\"{field.name}\"}}, &{unqual_name}::{field.name}}},") + code.append(" };") + code.append("}") + + return code + + def _cpp_type(self, type_name: str) -> str: + type_def = self._types[type_name] + mode = self._get_mode() + + if isinstance(type_def, BasicType): + + if type_def.type_class == TypeClass.scalar: + self._add_include("cstdint") + return self._CPP_TYPES_MAP[type_name] + + elif type_def.type_class == TypeClass.string: + if mode == "stl": + self._add_include("string") + return "std::string" + elif mode == "nostl": + self._add_include("string_view") + return "std::string_view" else: - if field["is_dynamic"]: - self.append("if (%s.size != other.%s.size) {return false;}" % (field["name"], field["name"])) - ptr, num = get_dyn_field_vars(field) - else: - ptr = field["name"] - num = field["num"] - - if typeinfo["plain"]: - memcmp = "memcmp(%s, other.%s, %s * %s)" % (ptr, ptr, typeinfo["static_size"], num) - self.append("if (%s != 0) {return false;}" % memcmp) - else: - self.start_for_cycle(num) - self.append("if (!(%s[i] == other.%s[i])) {return false;}" % (ptr, ptr)) - self.stop_cycle() - - self.append("") - - self.append("return true;") - self.stop_block() - - def __get_plain_fields_size_and_last_field_position(self, fields): - fpos = 0 - copy_size = 0 - - for field in fields: - typeinfo = self._data_types_map[field["type"]] + raise RuntimeError("Unsupported mode for string: %s" % mode) + + elif type_def.type_class == TypeClass.bytes: + if mode == "stl": + self._add_include("vector") + return "std::vector" + elif mode == "nostl": + return "messgen::vector" + else: + raise RuntimeError("Unsupported mode for bytes: %s" % mode) + + elif isinstance(type_def, ArrayType): + self._add_include("array") + el_c_type = self._cpp_type(type_def.element_type) + return "std::array<%s, %d>" % (el_c_type, type_def.array_size) + + elif isinstance(type_def, VectorType): + el_c_type = self._cpp_type(type_def.element_type) + if mode == "stl": + self._add_include("vector") + return "std::vector<%s>" % el_c_type + elif mode == "nostl": + return "messgen::vector<%s>" % el_c_type + else: + raise RuntimeError("Unsupported mode for vector: %s" % mode) + + elif isinstance(type_def, MapType): + key_c_type = self._cpp_type(type_def.key_type) + value_c_type = self._cpp_type(type_def.value_type) + if mode == "stl": + self._add_include("map") + return "std::map<%s, %s>" % (key_c_type, value_c_type) + elif mode == "nostl": + self._add_include("span") + return "std::span>" % (key_c_type, value_c_type) + else: + raise RuntimeError("Unsupported mode for map: %s" % mode) - if (not typeinfo["plain"]) or (field["is_dynamic"]): - break + elif isinstance(type_def, (EnumType, StructType)): + scope = ("global" + if SEPARATOR in type_name + else "local") + self._add_include("%s.h" % type_name, scope) + return _qual_name(type_name) - fpos += 1 + raise RuntimeError("Can't get c++ type for %s" % type_name) - if field["is_array"]: - num = field["num"] - else: - num = 1 + def _all_fields_scalar(self, fields: list[FieldType]): + return all(self._types[field.type].type_class != TypeClass.scalar + for field in fields) - copy_size += typeinfo["static_size"] * num + def _field_groups(self, fields): + groups = [FieldsGroup()] if len(fields) > 0 else [] + for field in fields: + field_def = self._types.get(field.type) + align = self._get_alignment(field_def) + size = field_def.size + + # Check if there is padding before this field + if len(groups[-1].fields) > 0 and ( + (size is None) or + (groups[-1].size is None) or + (groups[-1].size % align != 0) or + (size % align != 0)): + # Start next group + groups.append(FieldsGroup()) + + groups[-1].fields.append(field) + groups[-1].field_names.append(field.name) + if groups[-1].size is not None: + if size is not None: + groups[-1].size += size + else: + groups[-1].size = None - return copy_size, fpos + return groups - def generate_serialize_method(self, message): - self.start_block("size_t serialize_msg(uint8_t *%s) const" % INPUT_BUF_NAME) - self.extend([ - "uint8_t * ptr = %s;" % INPUT_BUF_NAME, - "(void)ptr;", - "uint32_t dyn_field_len;", - "(void)dyn_field_len;", - "" - ]) + def _serialize_field(self, field_name, field_type_def, level_n=0): + c = [] - # Process plain fields - copy_size, current_field_pos = self.__get_plain_fields_size_and_last_field_position(message["fields"]) + type_class = field_type_def.type_class - if copy_size != 0: - self.__copy_struct_block("&" + message["fields"][0]["name"], copy_size) + c.append("// %s" % field_name) + if type_class in [TypeClass.scalar, TypeClass.enum]: + c_type = self._cpp_type(field_type_def.type) + size = field_type_def.size + c.append("*reinterpret_cast<%s *>(&_buf[_size]) = %s;" % (c_type, field_name)) + c.append("_size += %s;" % size) - # Process composite fields - for field in message["fields"][current_field_pos:]: - if field["is_dynamic"]: - break + elif type_class == TypeClass.struct: + c.append("_size += %s.serialize(&_buf[_size]);" % field_name) - current_field_pos += 1 + elif type_class in [TypeClass.array, TypeClass.vector]: + if type_class == TypeClass.vector: + c.append("*reinterpret_cast(&_buf[_size]) = %s.size();" % field_name) + c.append("_size += sizeof(messgen::size_type);") + el_type_def = self._types.get(field_type_def.element_type) + el_size = el_type_def.size + el_align = self._get_alignment(el_type_def) - if field["is_array"]: - self.__serialize_struct_array(field["name"], field["num"]) - else: - serialize_call = "messgen::Serializer::serialize(%s, %s)" % ( - field["name"], "ptr", field["name"]) - self.append(set_inc_var("ptr", serialize_call)) - - self.append("") - - # Process dynamic fields - for field in message["fields"][current_field_pos:]: - if field["type"] == "string": - if field["is_array"]: - raise MessgenException("Array of strings is not supported in C++ generator") - - self.append(set_var("dyn_field_len", strlen(field["name"]))) - self.__serialize_dynamic_field_length("dyn_field_len") - self.extend([ - memcpy("ptr", field["name"] + ".data()", "dyn_field_len"), - set_inc_var("ptr", "dyn_field_len"), - ]) - self.append("") + if el_size == 0: + pass + elif el_size is not None and el_size % el_align == 0: + # Vector of fixed size elements, optimize with single memcpy + c.append("_field_size = %d * %s.size();" % (el_size, field_name)) + c.extend(self._memcpy_to_buf("%s.data()" % field_name, "_field_size")) else: - serialize_call = "%s.serialize_msg(%s)" % (field["name"], "ptr") - self.append(set_inc_var("ptr", serialize_call)) - - self.append("return ptr - %s;" % INPUT_BUF_NAME) - self.stop_block() - - return list(self._code) - - def generate_parse_method(self, message): - self.start_block("int parse_msg(const uint8_t *%s, uint32_t len, messgen::MemoryAllocator & %s)" % - (INPUT_BUF_NAME, INPUT_ALLOC_NAME)) - - if not message["has_dynamics"]: - self.append(ignore_variable(INPUT_ALLOC_NAME)) - - if len(message["fields"]) == 0: - self.append("(void)len;") - - self.extend([ - "const uint8_t * ptr = %s;" % INPUT_BUF_NAME, - "(void)ptr;", - "char * string_tmp_buf;", - "(void) string_tmp_buf;", - "size_t dyn_parsed_len;", - "(void)dyn_parsed_len;", - "int parse_result;", - "(void)parse_result;", - "" - ]) + # Vector of variable size elements + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend(_indent(self._serialize_field("_i%d" % level_n, el_type_def, level_n + 1))) + c.append("}") + + elif type_class == TypeClass.map: + c.append("*reinterpret_cast(&_buf[_size]) = %s.size();" % field_name) + c.append("_size += sizeof(messgen::size_type);") + key_type_def = self._types.get(field_type_def.key_type) + value_type_def = self._types.get(field_type_def.value_type) + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend( + _indent(self._serialize_field("_i%d.first" % level_n, key_type_def, level_n + 1))) + c.extend(_indent( + self._serialize_field("_i%d.second" % level_n, value_type_def, level_n + 1))) + c.append("}") + + elif type_class == TypeClass.string: + c.append("*reinterpret_cast(&_buf[_size]) = %s.size();" % field_name) + c.append("_size += sizeof(messgen::size_type);") + c.append("%s.copy(reinterpret_cast(&_buf[_size]), %s.size());" % (field_name, field_name)) + c.append("_size += %s.size();" % field_name) + + elif type_class == TypeClass.bytes: + c.append("*reinterpret_cast(&_buf[_size]) = %s.size();" % field_name) + c.append("_size += sizeof(messgen::size_type);") + c.append("std::copy(%s.begin(), %s.end(), &_buf[_size]);" % (field_name, field_name)) + c.append("_size += %s.size();" % field_name) - # Process plain fields - copy_size, current_field_pos = self.__get_plain_fields_size_and_last_field_position(message["fields"]) - if copy_size != 0: - self.extend([ - "if (len < %d) {return -1;}" % copy_size, - memcpy(ptr(message["fields"][0]["name"]), "ptr", copy_size), - set_inc_var("ptr", copy_size), - set_dec_var("len", copy_size), - "" - ]) - - # Process composite fields - for field in message["fields"][current_field_pos:]: - if field["is_dynamic"]: - break - - current_field_pos += 1 - - if field["is_array"]: - self.__parse_struct_array(field["name"], field["num"]) - else: - parse_call = "parse_result = messgen::Parser::parse(%s, len, %s, %s);" % ( - field["name"], "ptr", INPUT_ALLOC_NAME, field["name"]) - self.extend([ - parse_call, - "if (parse_result < 0) { return -1; }", - set_inc_var("ptr", "parse_result"), - set_dec_var("len", "parse_result") - ]) - - self.append("") - - # Process dynamic fields - for field in message["fields"][current_field_pos:]: - if field["type"] == "string": - self.extend([ - "if (len < %d) {return -1;}" % DYN_FIELD_LEN_SIZE, - ]) - - dyn_field_items_num = get_dynamic_field_items_num() - - self.extend([ - set_var("dyn_parsed_len", dyn_field_items_num), - set_inc_var("ptr", DYN_FIELD_LEN_SIZE), - set_dec_var("len", DYN_FIELD_LEN_SIZE) - ]) - - self.start_block("if (dyn_parsed_len > 0)") - self.extend([ - "if (len < dyn_parsed_len) {return -1;}", - # Increase allocation size by 1 byte for null terminator - *allocate_memory("string_tmp_buf", "char", "dyn_parsed_len + 1"), - memcpy("string_tmp_buf", "ptr", "dyn_parsed_len"), - "string_tmp_buf[dyn_parsed_len] = '\\0';", - set_var(field["name"], "std::string_view{string_tmp_buf, dyn_parsed_len}"), - set_inc_var("ptr", "dyn_parsed_len"), - set_dec_var("len", "dyn_parsed_len"), - "" - ]) - self.continue_block("else") - self.append(set_var(field["name"], "\"\"")) - self.stop_block() - else: - parse_call = "parse_result = %s.parse_msg(%s, len, %s);" % (field["name"], "ptr", INPUT_ALLOC_NAME) - self.extend([ - parse_call, - "if (parse_result < 0) { return -1; }", - set_inc_var("ptr", "parse_result"), - set_dec_var("len", "parse_result"), - "" - ]) - - self.append("return static_cast(ptr - %s);" % INPUT_BUF_NAME) - self.stop_block() - - return list(self._code) - - def generate_static_size(self, data_type): - var = "static constexpr {} STATIC_SIZE = {}".format(MESSAGE_SIZE_C_TYPE, data_type["static_size"]) - fields = data_type["fields"] - for field in fields: - typeinfo = self._data_types_map[field["type"]] - if not typeinfo["generated"] and not field["is_dynamic"]: - var += " + {}::STATIC_SIZE".format(to_cpp_type(field["type"])) - var += ";" - return var - - def generate_data_fields(self, fields): - has_constant = False - has_string = False - for field in fields: - if field["type"] == "string": - var = make_variable(field["name"], "std::string_view", 0) - has_string = True + else: + raise RuntimeError("Unsupported type_class in _serialize_field: %s" % type_class) + + return c + + def _deserialize_field(self, field_name, field_type_def, level_n=0): + c = [] + + type_class = field_type_def.type_class + mode = self._get_mode() + + c.append("// %s" % field_name) + if type_class in [TypeClass.scalar, TypeClass.enum]: + c_type = self._cpp_type(field_type_def.type) + size = field_type_def.size + c.append("%s = *reinterpret_cast(&_buf[_size]);" % (field_name, c_type)) + c.append("_size += %s;" % size) + + elif type_class == TypeClass.struct: + alloc = "" + if mode == "nostl": + alloc = ", _alloc" + c.append("_size += %s.deserialize(&_buf[_size]%s);" % (field_name, alloc)) + + elif type_class == TypeClass.array: + el_type_def = self._types.get(field_type_def.element_type) + el_size = el_type_def.size + el_align = self._get_alignment(el_type_def) + if el_size == 0: + pass + elif el_size is not None and el_size % el_align == 0: + # Vector or array of fixed size elements, optimize with single memcpy + c.append("_field_size = %d * %s.size();" % (el_size, field_name)) + c.extend(self._memcpy_from_buf("%s.data()" % field_name, "_field_size")) else: - has_constant |= is_type_constant(field["type"]) - c_type = to_cpp_type(field["type"]) - if field["is_dynamic"]: - var = make_variable(field["name"], "messgen::Dynamic<" + c_type + ">", field["num"]) + # Vector or array of variable size elements + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend(_indent(self._deserialize_field("_i%d" % level_n, el_type_def, level_n + 1))) + c.append("}") + + elif type_class == TypeClass.vector: + if mode == "stl": + el_type_def = self._types.get(field_type_def.element_type) + el_size = el_type_def.size + el_align = self._get_alignment(el_type_def) + c.append("%s.resize(*reinterpret_cast(&_buf[_size]));" % field_name) + c.append("_size += sizeof(messgen::size_type);") + if el_size == 0: + pass + elif el_size is not None and el_size % el_align == 0: + # Vector or array of fixed size elements, optimize with single memcpy + c.append("_field_size = %d * %s.size();" % (el_size, field_name)) + c.extend(self._memcpy_from_buf("%s.data()" % field_name, "_field_size")) else: - var = make_variable(field["name"], c_type, field["num"]) - - if field.get("descr") is not None: - var += " // " + str(field["descr"]) - - self.append(var) - return has_constant, has_string - - def convert_constant_to_basetype(self, name, constants): - el = [x for x in constants if x['name'] == name] - if len(el) != 0: - return to_cpp_type_short(el[0]['basetype']) - return name - - def generate_metadata_fields_legacy(self, constants, message_obj): - descr = ['"'] - for field in message_obj["fields"]: - t = self.convert_constant_to_basetype(to_cpp_type_short(field["type"]), constants) - descr.append(t) - if field["is_array"]: - if field["is_dynamic"]: - descr.append("[]") + # Vector or array of variable size elements + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend(_indent(self._deserialize_field("_i%d" % level_n, el_type_def, level_n + 1))) + c.append("}") + elif mode == "nostl": + el_type_def = self._types.get(field_type_def.element_type) + el_c_type = self._cpp_type(field_type_def.element_type) + el_size = el_type_def.size + el_align = self._get_alignment(el_type_def) + c.append("_field_size = *reinterpret_cast(&_buf[_size]);") + c.append(f"{field_name} = {{_alloc.alloc<{el_c_type}>(_field_size), _field_size}};") + c.append("_size += sizeof(messgen::size_type);") + if el_size == 0: + pass + elif el_size is not None and el_size % el_align == 0: + # Vector or array of fixed size elements, optimize with single memcpy + if el_size != 1: + c.append("_field_size *= %d;" % el_size) + c.extend(self._memcpy_from_buf("%s.data()" % field_name, "_field_size")) else: - descr.append("[%d]" % field["num"]) - - descr.append(" " + field["name"] + ";") - descr.append('"') - return "".join(descr) - - def generate_metadata_fields_json(self, message_obj): - return '"[' + ",".join(self._json_generator.generate_fields(message_obj)).replace('"', '\\"') + ']"' + # Vector or array of variable size elements + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend(_indent(self._deserialize_field("_i%d" % level_n, el_type_def, level_n + 1))) + c.append("}") + + elif type_class == TypeClass.map: + c.append("{") + c.append(_indent("size_t _map_size%d = *reinterpret_cast(&_buf[_size]);" % level_n)) + c.append(_indent("_size += sizeof(messgen::size_type);")) + key_c_type = self._cpp_type(field_type_def.key_type) + key_type_def = self._types.get(field_type_def.key_type) + value_c_type = self._cpp_type(field_type_def.value_type) + value_type_def = self._types.get(field_type_def.value_type) + c.append( + _indent( + "for (size_t _i%d = 0; _i%d < _map_size%d; ++_i%d) {" % (level_n, level_n, level_n, level_n))) + c.append(_indent(_indent("%s _key%d;" % (key_c_type, level_n)))) + c.append(_indent(_indent("%s _value%d;" % (value_c_type, level_n)))) + c.append("") + c.extend(_indent( + _indent( + self._deserialize_field("_key%d" % level_n, key_type_def, level_n + 1)))) + c.extend(_indent(_indent( + self._deserialize_field("_value%d" % level_n, value_type_def, level_n + 1)))) + c.append(_indent(_indent("%s[_key%d] = _value%d;" % (field_name, level_n, level_n)))) + c.append(_indent("}")) + c.append("}") + + elif type_class == TypeClass.string: + c.append("_field_size = *reinterpret_cast(&_buf[_size]);") + c.append("_size += sizeof(messgen::size_type);") + c.append("%s = {reinterpret_cast(&_buf[_size]), _field_size};" % field_name) + c.append("_size += _field_size;") + + elif type_class == TypeClass.bytes: + c.append("_field_size = *reinterpret_cast(&_buf[_size]);") + c.append("_size += sizeof(messgen::size_type);") + c.append("%s.assign(&_buf[_size], &_buf[_size + _field_size]);" % field_name) + c.append("_size += _field_size;") - def generate_metadata(self, module, message_obj): - self.reset() - if self._metadata_json: - fields_description = self.generate_metadata_fields_json(message_obj) else: - constants = module.get("constants") - fields_description = self.generate_metadata_fields_legacy(constants, message_obj) - - nested_structs_metadata = "{" - for field in message_obj["fields"]: - typeinfo = self._data_types_map[field["type"]] - if not typeinfo["plain"]: - nested_structs_metadata += "&" + to_cpp_type(field["type"]) + "::METADATA, " - nested_structs_metadata += "nullptr}" - - self.append("static const messgen::Metadata *nested_msgs_%s[] = %s;" % (message_obj["name"], nested_structs_metadata)) - self.start_block("const messgen::Metadata %s::METADATA = " % message_obj["name"]) - self.extend([ - "\"%s\"," % message_obj["name"], - fields_description + ",", - "nested_msgs_%s" % message_obj["name"] - ]) - self.stop_block(term=";") - - return list(self._code) + raise RuntimeError("Unsupported type_class in _deserialize_field: %s" % type_class) - def append(self, v): - self._code.append(self._indent + v if v else "") + c.append("") - def extend(self, v): - for line in v: - self._code.append(self._indent + line if line else "") + return c - def reset(self): - self._code = [] - self._indent_cnt = 0 - self._indent = "" + def _serialized_size_field(self, field_name, field_type_def, level_n=0): + c = [] - def start_block(self, decl): - self._code.append(self._indent + decl + " {") - self._indent_cnt += 1 - self._indent = INDENT * self._indent_cnt + type_class = field_type_def.type_class - def stop_block(self, term=""): - self._indent_cnt -= 1 - self._indent = INDENT * self._indent_cnt - self._code.append(self._indent + "}" + term) + c.append("// %s" % field_name) + if type_class == TypeClass.scalar: + size = field_type_def.size + c.append("_size += %d;" % size) - def continue_block(self, decl, term=""): - indent = INDENT * (self._indent_cnt - 1) - self._code.append(indent + "}" + term) - self._code.append(indent + decl + " {") + elif type_class == TypeClass.struct: + c.append("_size += %s.serialized_size();" % field_name) - def start_for_cycle(self, cycle_limit): - self.start_block("for (size_t i = 0; i < %s; ++i)" % cycle_limit) + elif type_class in [TypeClass.array, TypeClass.vector]: + if field_type_def.type_class == TypeClass.vector: + c.append("_size += sizeof(messgen::size_type);") + el_type = self._types.get(field_type_def.element_type) + el_size = el_type.size + if el_size is not None: + # Vector or array of fixed size elements + c.append("_size += %d * %s.size();" % (el_size, field_name)) + else: + # Vector or array of variable size elements + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend(_indent(self._serialized_size_field("_i%d" % level_n, el_type, level_n + 1))) + c.append("}") + + elif type_class == TypeClass.map: + c.append("_size += sizeof(messgen::size_type);") + key_type = self._types.get(field_type_def.key_type) + value_type = self._types.get(field_type_def.value_type) + key_size = key_type.size + value_size = value_type.size + if key_size is not None and value_size is not None: + # Vector or array of fixed size elements + c.append("_size += %d * %s.size();" % (key_size + value_size, field_name)) + else: + # Vector or array of variable size elements + c.append("for (auto &_i%d: %s) {" % (level_n, field_name)) + c.extend(_indent(self._serialized_size_field("_i%d.first" % level_n, key_type, level_n + 1))) + c.extend(_indent(self._serialized_size_field("_i%d.second" % level_n, value_type, level_n + 1))) + c.append("}") - def stop_cycle(self): - self.stop_block() + elif type_class in [TypeClass.string, TypeClass.bytes]: + c.append("_size += sizeof(messgen::size_type);") + c.append("_size += %s.size();" % field_name) - def __copy_struct_block(self, field_ptr, size): - self.extend([ - memcpy("ptr", field_ptr, size), - set_inc_var("ptr", size), - "" - ]) + else: + raise RuntimeError("Unsupported type_class in _serialized_size_field: %s" % field_type_def.type_class) - def __serialize_struct_array(self, field_ptr, size): - serialize_call = "%s[i].serialize_msg(%s)" % (field_ptr, "ptr") - - self.start_for_cycle(str(size)) - self.append(set_inc_var("ptr", serialize_call)) - self.stop_cycle() - self.append("") - - def __parse_struct_array(self, field_ptr, size): - parse_call = "%s[i].parse_msg(%s, len, %s)" % (field_ptr, "ptr", INPUT_ALLOC_NAME) - self.start_for_cycle(str(size)) - self.extend([ - set_var("parse_result", parse_call), - "if (parse_result < 0) {return -1;}", - set_inc_var("ptr", "parse_result"), - set_dec_var("len", "parse_result"), - ]) - self.stop_cycle() - self.append("") + return c - def __serialize_dynamic_field_length(self, length): - for i in range(DYN_FIELD_LEN_SIZE): - shift_str = "((%s >> (%dU*8U)) & 0xFFU)" % (str(length), i) - self.append("ptr[%d] = %s;" % (i, shift_str)) + def _memcpy_to_buf(self, src: str, size) -> list: + return [ + "::memcpy(&_buf[_size], reinterpret_cast(%s), %s);" % (src, size), + "_size += %s;" % size + ] - self.append(set_inc_var("ptr", str(DYN_FIELD_LEN_SIZE))) + def _memcpy_from_buf(self, dst: str, size) -> list: + return [ + "::memcpy(reinterpret_cast(%s), &_buf[_size], %s);" % (dst, size), + "_size += %s;" % size + ] diff --git a/messgen/data_types_preprocessor.py b/messgen/data_types_preprocessor.py deleted file mode 100644 index 46450ce1..00000000 --- a/messgen/data_types_preprocessor.py +++ /dev/null @@ -1,269 +0,0 @@ -from .utils import remove_array, get_module_name, get_array_size, is_dynamic, is_array -from .messgen_ex import MessgenException - - -class DataTypesPreprocessor: - MAX_PROTO_ID = 0x80 - MSGS_NAMESPACE = "msgs" - - def __init__(self, plain_types_map, special_types_map): - self._plain_types_map = plain_types_map - self._special_types_map = special_types_map - self._types_map = {} - self._lookup_table = {} - - for k, v in plain_types_map.items(): - self._types_map[k] = { - "align": v["align"], - "static_size": v["size"], - "plain": True, - "generated": True, - "has_dynamics": False - } - - # TODO make aliases in correct way - for k, v in special_types_map.items(): - self._types_map[k] = { - "align": v["align"], - "static_size": v["size"], - "plain": True, - "generated": True, - "has_dynamics": False - } - - def create_types_map(self, modules_map): - """ - Data type post-processing. - Creates additional fields for each module: - "max_datatype_size" - maximum size among all datatypes in module - "namespace" - datatypes namespace inside a module - - Returns data types map of the following structure: - { - "typename": { - "align": alignment, - "static_size": size of static fields, - "plain": indicates whether data type is plain one, - "has_dynammics": number of dynamic arrays in data type - "fields": [{ - "name": field name, - "type": normalized typename, - "dynamic": indicates whether this is a dynamic array, - "num": number of items. If > 1 -> fixed size array - }, ...] - list of fields sorted by alignment - } - } - - Since currently each message is data type "typename" field is also - added to each message. - """ - self.__create_lookup_messages_set(modules_map) - self.__load_constants(modules_map) - - max_datatype_size = 0 - - for module_name, module in modules_map.items(): - module_ns = DataTypesPreprocessor.__create_namespace(module_name) - module["namespace"] = module_ns - - for data_type in module["messages"]: - typename = self.__normalize_typename(module_name, data_type["name"]) - - if typename not in self._types_map: - self.__load_data_type(module_name, typename, data_type) - - data_type_size = self._types_map[typename]["static_size"] - if data_type_size > max_datatype_size: - max_datatype_size = data_type_size - - module["max_datatype_size"] = max_datatype_size - - return self._types_map - - def __create_lookup_messages_set(self, modules_map): - for module_name, module in modules_map.items(): - if module["proto_id"] >= self.MAX_PROTO_ID: - raise MessgenException("Forbidden proto_id=%d for module %s" % (module["proto_id"], module_name)) - - for message in module["messages"]: - message_type = self.__normalize_typename(module_name, message["name"]) - self._lookup_table[message_type] = message - self._lookup_table[message_type]["generated"] = True - - for message in module["existing_types"]: - message_type = self.__normalize_typename(message["namespace"], message["name"]) - self._lookup_table[message_type] = message - self._lookup_table[message_type]["generated"] = False - - def __load_constants(self, modules_map): - for module_name, module in modules_map.items(): - for constant_type in module["constants"]: - typename = self.__normalize_typename(module_name, constant_type["name"]) - - if typename not in self._types_map: - self.__load_constant_type(typename, constant_type) - else: - raise MessgenException("Constant typename '%s' duplication" % typename) - - def __load_constant_type(self, typename, constant): - basetype = constant["basetype"] - - if basetype not in self._plain_types_map: - raise MessgenException("Unknown basetype %s for constant %s" % (basetype, typename)) - - basetype_info = self._types_map[constant["basetype"]] - data_type_entry = { - "deps": [], - "typename": typename, - "fields": [], - "align": basetype_info["align"], - "static_size": basetype_info["static_size"], - "plain": True, - "has_dynamics": False, - "generated": True - } - - self._types_map[typename] = data_type_entry - - def __load_data_type(self, module_name, typename, data_type): - data_type["deps"] = [] - data_type["typename"] = typename - data_type["has_dynamics"] = False - - if data_type.get("fields") is None: - data_type["fields"] = [] - - fields = data_type["fields"] - - alignment = 0 - static_size = 0 - - for field in fields: - child_norm_typename = self.__normalize_typename(module_name, field["type"]) - child_module_name = get_module_name(child_norm_typename) - - field["is_array"] = is_array(field["type"]) - field["num"] = int(get_array_size(field["type"])) - - if field["type"] == "string": - field["is_dynamic"] = True - else: - field["is_dynamic"] = is_dynamic(field["type"]) - - field["type"] = child_norm_typename - - if field["is_dynamic"]: - data_type["has_dynamics"] = True - - if child_norm_typename not in self._types_map: - if child_norm_typename not in self._lookup_table: - raise MessgenException("Data type '%s' not found for field '%s' in message '%s/%s'" % - (child_norm_typename, field["name"], module_name, typename)) - - child_datatype = self._lookup_table.get(child_norm_typename) - self.__load_data_type(child_module_name, child_norm_typename, child_datatype) - - child_datatype_entry = self._types_map[child_norm_typename] - - if child_datatype_entry["has_dynamics"]: - data_type["has_dynamics"] = True - - children_number = 0 - if not child_datatype_entry["plain"]: - if child_norm_typename not in data_type["deps"]: - data_type["deps"].append(child_norm_typename) - - if field["is_array"] and not field["is_dynamic"]: - children_number = field["num"] - elif field["is_dynamic"]: - children_number = 0 - static_size += 4 - elif not field["is_array"]: - children_number = 1 - - static_size += child_datatype_entry["static_size"]*children_number - child_alignment = child_datatype_entry["align"] - - assert((child_alignment == 1) or (child_alignment % 2 == 0)) - assert(child_alignment <= 8) - - if child_alignment > alignment: - alignment = child_alignment - - if len(data_type["fields"]) != 0: - data_type["fields"] = self.__sort_fields(data_type["fields"]) - else: - alignment = 1 - - data_type_entry = { - "fields": data_type["fields"], - "deps": data_type["deps"], - "align": alignment, - "static_size": static_size, - "plain": False, - "generated": data_type["generated"], - "has_dynamics": data_type["has_dynamics"] - } - - self._types_map[typename] = data_type_entry - - @staticmethod - def __insert_msgs_namespace(typename): - # type_entries[0] - vendor name. type_entries[1:] - module path - type_entries = typename.split("/") - msgs_type = type_entries[0] + "/" + DataTypesPreprocessor.MSGS_NAMESPACE - - for entry in type_entries[1:]: - msgs_type += "/" + entry - - return msgs_type - - @staticmethod - def __create_namespace(module_name): - return DataTypesPreprocessor.__insert_msgs_namespace(module_name) - - def __normalize_typename(self, module_name, typename): - type_without_array = remove_array(typename) - - if (type_without_array in self._plain_types_map) or (type_without_array in self._special_types_map) or (self.MSGS_NAMESPACE in typename): - return str(type_without_array) - - if "/" in type_without_array: - return self.__insert_msgs_namespace(type_without_array) - - namespace = self.__create_namespace(module_name) - return namespace + "/" + type_without_array - - def __sort_fields(self, fields): - def sort_fields_by_alignment(fields): - def _alignment_getter(field): - # "type" field is already normalized - return self._types_map[field["type"]]["align"] - - return list( - sorted( - fields, - key=_alignment_getter, - reverse=True - ) - ) - - dynamic_fields = [] - composite_fields = [] - plain_fields = [] - - for field in fields: - if field["is_dynamic"]: - dynamic_fields.append(field) - elif self._types_map[field["type"]]["plain"]: - plain_fields.append(field) - else: - composite_fields.append(field) - - return [ - *sort_fields_by_alignment(plain_fields), - *sort_fields_by_alignment(composite_fields), - *sort_fields_by_alignment(dynamic_fields) - ] - - diff --git a/messgen/dynamic.py b/messgen/dynamic.py new file mode 100644 index 00000000..2b977906 --- /dev/null +++ b/messgen/dynamic.py @@ -0,0 +1,387 @@ +import struct + +from functools import singledispatchmethod +from abc import ABC, abstractmethod +from pathlib import Path + +from .model import ( + ArrayType, + EnumType, + MapType, + MessgenType, + StructType, + TypeClass, + VectorType, +) +from .yaml_parser import ( + parse_protocols, + parse_types +) + +STRUCT_TYPES_MAP = { + "uint8": "B", + "int8": "b", + "uint16": "H", + "int16": "h", + "uint32": "I", + "int32": "i", + "uint64": "Q", + "int64": "q", + "float32": "f", + "float64": "d", + "bool": "?", +} + + +class MessgenError(Exception): + pass + + +class TypeConverter(ABC): + + def __init__(self, types: dict[str, MessgenType], type_name: str): + self._type_name = type_name + self._type_def = types[type_name] + self._type_class = self._type_def.type_class + + def type_name(self) -> str: + return self._type_name + + def type_hash(self) -> int: + return hash(self._type_def) + + def serialize(self, data: dict) -> bytes: + return self._serialize(data) + + def deserialize(self, data: bytes) -> dict: + msg, sz = self._deserialize(data) + if sz != len(data): + raise MessgenError( + f"Invalid message size: expected={sz} actual={len(data)} type_name={self._type_name}") + return msg + + @abstractmethod + def _serialize(self, data) -> bytes: + pass + + @abstractmethod + def _deserialize(self, data) -> tuple[dict, int]: + pass + + +class ScalarConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name: str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.scalar + self.struct_fmt = STRUCT_TYPES_MAP.get(type_name) + if self.struct_fmt is None: + raise RuntimeError("Unsupported scalar type \"%s\"" % type_name) + self.struct_fmt = "<" + self.struct_fmt + self.size = struct.calcsize(self.struct_fmt) + self.def_value: bool | float | int = 0 + if type_name == "bool": + self.def_value = False + elif type_name == "float32" or type_name == "float64": + self.def_value = 0.0 + + def _serialize(self, data): + return struct.pack(self.struct_fmt, data) + + def _deserialize(self, data): + return struct.unpack(self.struct_fmt, data[:self.size])[0], self.size + + def default_value(self): + return self.def_value + + +class EnumConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name:str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.enum + assert isinstance(self._type_def, EnumType) + self.base_type = self._type_def.base_type + self.struct_fmt = STRUCT_TYPES_MAP.get(self.base_type, None) + if self.struct_fmt is None: + raise RuntimeError("Unsupported base type \"%s\" in %s" % (self.base_type, type_name)) + self.struct_fmt = "<" + self.struct_fmt + self.size = struct.calcsize(self.struct_fmt) + self.mapping = {} + for item in self._type_def.values: + self.mapping[item.value] = item.name + self.rev_mapping = {v: k for k, v in self.mapping.items()} + + def _serialize(self, data): + v = self.rev_mapping[data] + return struct.pack(self.struct_fmt, v) + + def _deserialize(self, data): + v, = struct.unpack(self.struct_fmt, data[:self.size]) + return self.mapping[v], self.size + + def default_value(self): + return self._type_def.values[0].name + + +class StructConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name:str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.struct + assert isinstance(self._type_def, StructType) + self.fields = [(field.name, create_type_converter(types, field.type)) + for field in self._type_def.fields] + + def _serialize(self, data): + out = [] + for field_name, field_type in self.fields: + v = data.get(field_name, None) + if v is None: + v = field_type.default_value() + out.append(field_type._serialize(v)) + return b"".join(out) + + def _deserialize(self, data): + out = {} + offset = 0 + for field_name, field_type in self.fields: + value, size = field_type._deserialize(data[offset:]) + out[field_name] = value + offset += size + return out, offset + + def default_value(self): + return {field_name : field_type.default_value() + for field_name, field_type in self.fields} + + +class ArrayConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name:str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.array + assert isinstance(self._type_def, ArrayType) + self.element_type = create_type_converter(types, self._type_def.element_type) + self.array_size = self._type_def.array_size + + def _serialize(self, data): + out = [] + assert len(data) == self.array_size + for item in data: + out.append(self.element_type._serialize(item)) + return b"".join(out) + + def _deserialize(self, data): + out = [] + offset = 0 + for i in range(self.array_size): + value, size = self.element_type._deserialize(data[offset:]) + out.append(value) + offset += size + return out, offset + + def default_value(self): + out = [] + for i in range(self.array_size): + out.append(self.element_type.default_value()) + return out + + +class VectorConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name: str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.vector + assert isinstance(self._type_def, VectorType) + self.size_type = create_type_converter(types, "uint32") + self.element_type = create_type_converter(types, self._type_def.element_type) + + def _serialize(self, data): + out = [] + out.append(self.size_type._serialize(len(data))) + + for item in data: + out.append(self.element_type._serialize(item)) + return b"".join(out) + + def _deserialize(self, data): + out = [] + offset = 0 + n, n_size = self.size_type._deserialize(data[offset:]) + offset += n_size + for i in range(n): + value, n = self.element_type._deserialize(data[offset:]) + out.append(value) + offset += n + return out, offset + + def default_value(self): + return [] + + +class MapConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name:str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.map + assert isinstance(self._type_def, MapType) + self.size_type = create_type_converter(types, "uint32") + self.key_type = create_type_converter(types, self._type_def.key_type) + self.value_type = create_type_converter(types, self._type_def.value_type) + + def _serialize(self, data): + out = [] + out.append(self.size_type._serialize(len(data))) + for k, v in data.items(): + out.append(self.key_type._serialize(k)) + out.append(self.value_type._serialize(v)) + return b"".join(out) + + def _deserialize(self, data): + out = {} + offset = 0 + n, n_size = self.size_type._deserialize(data[offset:]) + offset += n_size + for i in range(n): + key, n = self.key_type._deserialize(data[offset:]) + offset += n + value, n = self.value_type._deserialize(data[offset:]) + offset += n + out[key] = value + return out, offset + + def default_value(self): + return {} + + +class StringConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name:str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.string + self.size_type = create_type_converter(types, "uint32") + self.struct_fmt = "<%is" + + def _serialize(self, data): + return self.size_type._serialize(len(data)) + struct.pack(self.struct_fmt % len(data), data.encode("utf-8")) + + def _deserialize(self, data): + n, n_size = self.size_type._deserialize(data) + offset = n_size + value = struct.unpack(self.struct_fmt % n, data[offset:offset + n])[0] + offset += n + return value.decode("utf-8"), offset + + def default_value(self): + return "" + + +class BytesConverter(TypeConverter): + def __init__(self, types: dict[str, MessgenType], type_name:str): + super().__init__(types, type_name) + assert self._type_class == TypeClass.bytes + self.size_type = create_type_converter(types, "uint32") + self.struct_fmt = "<%is" + + def _serialize(self, data): + return self.size_type._serialize(len(data)) + struct.pack(self.struct_fmt % len(data), data) + + def _deserialize(self, data): + n, n_size = self.size_type._deserialize(data) + offset = n_size + value = struct.unpack(self.struct_fmt % n, data[offset:offset + n])[0] + offset += n + return value, offset + + def default_value(self): + return b"" + + +def create_type_converter(types: dict[str, MessgenType], type_name: str) -> TypeConverter: + type_def = types[type_name] + type_class = type_def.type_class + if type_class == TypeClass.scalar: + return ScalarConverter(types, type_name) + elif type_class == TypeClass.enum: + return EnumConverter(types, type_name) + elif type_class == TypeClass.struct: + return StructConverter(types, type_name) + elif type_class == TypeClass.array: + return ArrayConverter(types, type_name) + elif type_class == TypeClass.vector: + return VectorConverter(types, type_name) + elif type_class == TypeClass.map: + return MapConverter(types, type_name) + elif type_class == TypeClass.string: + return StringConverter(types, type_name) + elif type_class == TypeClass.bytes: + return BytesConverter(types, type_name) + raise RuntimeError("Unsupported field type class \"%s\" in %s" % (type_class, type_def.type)) + + +class MessageInfo: + + def __init__(self, proto_id: int, message_id: int, proto_name: str, message_name: str, type_converter: TypeConverter): + self._proto_id = proto_id + self._message_id = message_id + self._proto_name = proto_name + self._message_name = message_name + self._type_converter = type_converter + + def proto_name(self) -> str: + return self._proto_name + + def message_name(self) -> str: + return self._message_name + + def proto_id(self) -> int: + return self._proto_id + + def message_id(self) -> int: + return self._message_id + + def type_name(self) -> str: + return self._type_converter.type_name() + + def type_hash(self) -> int: + return self._type_converter.type_hash() + + def type_converter(self) -> TypeConverter: + return self._type_converter + + +class Codec: + + def __init__(self) -> None: + self._converters_by_name: dict[str, TypeConverter] = {} + self._id_by_name: dict[tuple[str, str], tuple[int, int, str]] = {} + self._name_by_id: dict[tuple[int, int], tuple[str, str, str]] = {} + + def load(self, type_dirs: list[str | Path], protocols: list[str] | None = None): + parsed_types = parse_types(type_dirs) + if not protocols: + return + + for type_name in parsed_types: + self._converters_by_name[type_name] = create_type_converter(parsed_types, type_name) + + parsed_protocols = parse_protocols(protocols) + for proto_name, proto_def in parsed_protocols.items(): + for msg_id, message in proto_def.messages.items(): + self._id_by_name[(proto_name, message.name)] = (proto_def.proto_id, msg_id, message.type) + self._name_by_id[(proto_def.proto_id, msg_id)] = (proto_name, message.name, message.type) + + def type_converter(self, type_name: str) -> TypeConverter: + if converter := self._converters_by_name.get(type_name): + return converter + raise MessgenError(f"Unsupported type_name={type_name}") + + def message_info_by_id(self, proto_id: int, message_id: int) -> MessageInfo: + key = (proto_id, message_id) + if not key in self._name_by_id: + raise MessgenError(f"Unsupported proto_id={proto_id} message_id={message_id}") + + proto_name, message_name, type_name = self._name_by_id[key] + return MessageInfo(proto_id, message_id, proto_name, message_name, self._converters_by_name[type_name]) + + def message_info_by_name(self, proto_name: str, message_name: str) -> MessageInfo: + key = (proto_name, message_name) + if not key in self._id_by_name: + raise MessgenError(f"Unsupported proto_name={proto_name} message_name={message_name}") + + proto_id, message_id, type_name = self._id_by_name[key] + return MessageInfo(proto_id, message_id, proto_name, message_name, self._converters_by_name[type_name]) diff --git a/messgen/generator.py b/messgen/generator.py new file mode 100644 index 00000000..1f5478c4 --- /dev/null +++ b/messgen/generator.py @@ -0,0 +1,12 @@ +from .json_generator import JsonGenerator +from .cpp_generator import CppGenerator +from .ts_generator import TypeScriptGenerator + + +def get_generator(lang: str, options): + if lang == "json": + return JsonGenerator(options) + elif lang == "cpp": + return CppGenerator(options) + elif lang == 'ts': + return TypeScriptGenerator(options) diff --git a/messgen/go_generator.py b/messgen/go_generator.py index 90ca7f00..9fa5915e 100644 --- a/messgen/go_generator.py +++ b/messgen/go_generator.py @@ -1,7 +1,5 @@ import os -from messgen import MessgenException - import re from pathlib import Path from string import Template @@ -15,6 +13,11 @@ defaultStringer = Template('fmt.Sprintf("%v", $varName)') + +class MessgenException(Exception): + pass + + def to_camelcase(str): if not any(c in "_" for c in str): return str[0].upper() + str[1:] @@ -172,7 +175,7 @@ def make_const_type(enumName, basetype, fields): for field in fields: value = "%s" % field["value"] - matches = re.fullmatch("\s*\(?(\d+)U\s*<<\s*(\d+)U\)?", value) + matches = re.fullmatch("\\s*\\(?(\\d+)U\\s*<<\\s*(\\d+)U\\)?", value) if matches is not None: value = matches.expand("\\1 << \\2") diff --git a/messgen/json_generator.py b/messgen/json_generator.py index f018a0ad..3f349195 100644 --- a/messgen/json_generator.py +++ b/messgen/json_generator.py @@ -1,135 +1,55 @@ -import os -from messgen.version_protocol import VersionProtocol +import json -json_types_map = { - "bytes": "uint8[]", -} +from dataclasses import asdict +from pathlib import Path +from .protocol_version import version_hash -def format_type_name(module_path, typename): - t = json_types_map.get(typename, typename) - le = t.rfind('/') +from .validation import validate_protocol - if le > 0 and module_path == t[:le]: - f_type = t[le+1:] - else: - f_type = t[0].upper() + t[1:] +from .model import ( + MessgenType, + Protocol, + TypeClass, +) - return f_type +class JsonGenerator: + _FILE_EXT = ".json" + def __init__(self, options): + self._options = options -def format_type(module_path, f): - f_type = format_type_name(module_path, f["type"]) - if f["num"] > 1: - f_type += "[%s]" % f["num"] - elif f["is_dynamic"] and (f["type"] != "string"): - f_type += "[]" - return f_type + def generate(self, out_dir: Path, types: dict[str, MessgenType], protocols: dict[str, Protocol]) -> None: + self.validate(types, protocols) + self.generate_types(out_dir, types) + self.generate_protocols(out_dir, protocols) + def validate(self, types: dict[str, MessgenType], protocols: dict[str, Protocol]): + for proto_def in protocols.values(): + validate_protocol(proto_def, types) -class JsonGenerator: - PROTO_TYPE_VAR_TYPE = "uint8" + def generate_types(self, out_dir: Path, types: dict[str, MessgenType]) -> None: + combined: list = [] + + for type_def in types.values(): + if type_def.type_class in [TypeClass.struct, TypeClass.enum]: + combined.append(asdict(type_def)) + + self._write_file(out_dir, "types", combined) + + def generate_protocols(self, out_dir: Path, protocols: dict[str, Protocol]) -> None: + combined: list = [] + + for proto_def in protocols.values(): + proto_dict = asdict(proto_def) + proto_dict["version"] = version_hash(proto_dict) + combined.append(proto_dict) + + self._write_file(out_dir, "protocols", combined) + + def _write_file(self, out_dir: Path, name: str, data: list) -> None: + file_name = out_dir / (name + self._FILE_EXT) + file_name.parent.mkdir(parents=True, exist_ok=True) + + with open(file_name, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) - def __init__(self, modules_map, data_types_map, module_sep, variables): - self.MODULE_SEP = module_sep - self._modules_map = modules_map - self._data_types_map = data_types_map - - def generate(self, out_dir): - for module_name, module in self._modules_map.items(): - module_out_dir = out_dir + os.path.sep + module_name.replace(self.MODULE_SEP, os.path.sep) - - try: - os.makedirs(module_out_dir) - except: - pass - - code = [] - code_msgs = [] - for const in module["constants"]: - code_msgs.append("\n".join(self.generate_constant(const))) - - if len(code_msgs) > 0: - code.append("{") - code.append(",\n".join(code_msgs)) - code.append("}") - - self.__write_file(module_out_dir + os.path.sep + "cosntants.json", code) - - code = [] - code_msgs = [] - - code.append("{") - for msg in module["messages"]: - code_msgs.append("\n".join(self.generate_msg(msg))) - code.append(",\n".join(code_msgs)) - code.append("}") - - self.__write_file(module_out_dir + os.path.sep + "messages.json", code) - - - code = [] - - code.append("{") - code.append(" \"version\": \"%s\"" % VersionProtocol(module).generate()) - code.append("}") - - self.__write_file(module_out_dir + os.path.sep + "version.json", code) - - def generate_constant(self, msg): - msg_name = msg["name"] - - out = [] - out.append(' "%s": {' % msg_name) - out.append(' "type": "%s",' % format_type_name("", msg["basetype"])) - out.append(' "is_const": true,') - - if "id" in msg: - out.append(' "id": %s,' % msg["id"]) - - out.append(' "fields": [') - fields_p = self.generate_const_fields(msg) - out.append(",\n".join([" " + x for x in fields_p])) - out.append(" ]") - out.append(" }") - return out - - def generate_const_fields(self, msg): - fields_p = [] - - for f in msg["fields"]: - f_name = f["name"] - fields_p.append('{"name": "%s", "value": "%s"}' % (f_name, str(f["value"]))) - - return fields_p - - def generate_msg(self, msg): - msg_name = msg["name"] - - out = [] - out.append(' "%s": {' % msg_name) - if "id" in msg: - out.append(' "id": %s,' % msg["id"]) - - out.append(' "fields": [') - fields_p = self.generate_fields(msg) - out.append(",\n".join([" " + x for x in fields_p])) - out.append(" ]") - out.append(" }") - return out - - def generate_fields(self, msg): - fields_p = [] - module_path = msg["typename"] - module_path = module_path[:module_path.rfind('/')] - for f in msg["fields"]: - f_name = f["name"] - f_type = format_type(module_path, f) - fields_p.append('{"name": "%s", "type": "%s"}' % (f_name, f_type)) - return fields_p - - @staticmethod - def __write_file(fpath, code): - with open(fpath, "w") as f: - for line in code: - f.write("%s\n" % line) diff --git a/messgen/messgen_ex.py b/messgen/messgen_ex.py deleted file mode 100644 index 6b1eee96..00000000 --- a/messgen/messgen_ex.py +++ /dev/null @@ -1,5 +0,0 @@ - - -class MessgenException(Exception): - def __init__(self, message): - super().__init__(message) diff --git a/messgen/model.py b/messgen/model.py new file mode 100644 index 00000000..d2776c17 --- /dev/null +++ b/messgen/model.py @@ -0,0 +1,146 @@ +import hashlib +import json + +from dataclasses import dataclass, asdict +from enum import Enum, auto +from typing import Union + + +def _hash_model_type(dt) -> int: + input_string = json.dumps(asdict(dt)).replace(" ", "") + hash_object = hashlib.md5(input_string.encode()) + hex_digest = hash_object.hexdigest() + hash_32_bits = int(hex_digest[:8], 16) + return hash_32_bits + + +class TypeClass(str, Enum): + scalar = auto() + string = auto() + bytes = auto() + vector = auto() + array = auto() + map = auto() + enum = auto() + struct = auto() + + +@dataclass +class BasicType: + type: str + type_class: TypeClass + size: int | None + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class ArrayType: + type: str + type_class: TypeClass + element_type: str + array_size: int + size: int | None + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class VectorType: + type: str + type_class: TypeClass + element_type: str + size: None + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class MapType: + type: str + type_class: TypeClass + key_type: str + value_type: str + size: None + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class EnumValue: + name: str + value: int + comment: str + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class EnumType: + type: str + type_class: TypeClass + base_type: str + comment: str | None + values: list[EnumValue] + size: int + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class FieldType: + name: str + type: str + comment: str | None + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class StructType: + type: str + type_class: TypeClass + comment: str | None + fields: list[FieldType] + size: int | None + + def __hash__(self): + return _hash_model_type(self) + + +MessgenType = Union[ + ArrayType, + BasicType, + EnumType, + MapType, + StructType, + VectorType, +] + + +@dataclass +class Message: + message_id: int + name: str + type: str + comment: str | None + + def __hash__(self): + return _hash_model_type(self) + + +@dataclass +class Protocol: + name: str + proto_id: int + messages: dict[int, Message] + + def __hash__(self): + return _hash_model_type(self) diff --git a/messgen/parser.py b/messgen/parser.py deleted file mode 100644 index 4c0b1b88..00000000 --- a/messgen/parser.py +++ /dev/null @@ -1,89 +0,0 @@ -import os - -import yaml - -from .messgen_ex import MessgenException - -CONFIG_EXT = ".yaml" -PROTOCOL_FILE = "_protocol" + CONFIG_EXT -CONSTANTS_FILE = "_constants" + CONFIG_EXT -EXISTING_TYPES_FILE = "_types" + CONFIG_EXT - - -def load_modules(basedirs, modules): - modules_map = {} - proto_id = None - - for module_name in modules: - module_messages = [] - module_constants = [] - module_existing_types = [] - paths_checked = [] - for basedir in basedirs: - module_path = basedir + os.path.sep + module_name - paths_checked.append(module_path) - - if os.path.exists(module_path): - for item in os.listdir(module_path): - msg_file_path = module_path + os.path.sep + item - - if not (os.path.isfile(msg_file_path) and item.endswith(CONFIG_EXT)): - continue - - msg_name = item.replace(CONFIG_EXT, "") - - with open(msg_file_path, "r") as f: - msg = yaml.safe_load(f) - - if item == PROTOCOL_FILE: - if msg.get("proto_id") is None: - raise MessgenException("Missing proto id field") - - proto_id = msg["proto_id"] - - for existing_mod_name, existing_mod in modules_map.items(): - if existing_mod["proto_id"] == proto_id: - raise MessgenException( - "Duplicate proto_id=%s in modules '%s' and '%s'" % - (proto_id, module_name, existing_mod_name)) - - continue - - if item == CONSTANTS_FILE: - if msg is not None: - module_constants = msg - - continue - - if item == EXISTING_TYPES_FILE: - if msg is not None: - module_existing_types = msg - - continue - - if (msg is None) or (msg.get("id") is None): - raise MessgenException("Wrong message file format in %s" % msg_file_path) - - msg["name"] = msg_name - - for m in module_messages: - if m["id"] == msg["id"]: - raise MessgenException( - "Duplicate ID=%s for messages '%s' and '%s' in module %s" - % (m["id"], m["name"], msg["name"], module_name)) - module_messages.append(msg) - - if proto_id == None: - raise MessgenException("No messages found for module %s. Paths checked: %s" % (module_name, paths_checked)) - - modules_map[module_name] = { - "proto_id": proto_id, - "constants": module_constants, - "existing_types": module_existing_types, - "messages": list( - sorted(module_messages, - key=lambda msg: msg["id"]) - ) - } - - return modules_map diff --git a/messgen/protocol_version.py b/messgen/protocol_version.py new file mode 100644 index 00000000..05802cdf --- /dev/null +++ b/messgen/protocol_version.py @@ -0,0 +1,7 @@ +import hashlib +import json + + +def version_hash(proto): + result = hashlib.md5(json.dumps(proto).encode()) + return result.hexdigest()[0:6] diff --git a/messgen/ts_generator.py b/messgen/ts_generator.py index 92a2c21e..c9cd2b4a 100644 --- a/messgen/ts_generator.py +++ b/messgen/ts_generator.py @@ -1,161 +1,190 @@ import os - -ts_types_map = { - "bytes": "uint8[]", - "char": "string", - "int8": "number", - "uint8": "number", - "int16": "number", - "uint16": "number", - "int32": "number", - "uint32": "number", - "int64": "bigint", - "uint64": "bigint", - "float32": "number", - "float64": "bigint", - "string": "string", -} - -ts_types_array = { - "int8": "Int8Array", - "uint8": "Uint8Array", - "int16": "Int16Array", - "uint16": "Uint16Array", - "int32": "Int32Array", - "uint32": "Uint32Array", - "int64": "BigInt64Array", - "uint64": "BigUint64Array", - "float32": "Float32Array", - "float64": "Float64Array", -} - - -def to_camelcase(str): - return ''.join(x for x in str.title().replace('_', '') if not x.isspace()) - - -def format_type(f, use_typed_arrays=False): - t = ts_types_map.get(f["type"], f["type"]) - f_type = t[0].lower() + t[1:] - - if ('/' in f["type"]): - din_type = to_camelcase(f_type.split('/').pop()) - f_type = din_type + "Message" - - if (f["is_array"]): - tArray = ts_types_array.get(f["type"]) - if (tArray is not None and use_typed_arrays): - f_type = tArray - else: - f_type = f_type + "[]" - - return f_type - - -class TsGenerator: - PROTO_TYPE_VAR_TYPE = "uint8" - - def __init__(self, modules_map, data_types_map, module_sep, variables): - self.MODULE_SEP = module_sep - self._modules_map = modules_map - self._data_types_map = data_types_map - self._variables = variables - - def generate(self, out_dir): - imports = [] - for module_name, module in self._modules_map.items(): - module_out_dir = out_dir + os.path.sep + module_name.replace(self.MODULE_SEP, os.path.sep) - spl = module_name.split(self.MODULE_SEP) - class_path = '%s/%s' % (spl[0], to_camelcase(spl[1])) - prefix = '// === AUTO GENERATED CODE ===\n' - imports.append(prefix) - imports.append('import { MessageName, MessageData, ClearSystemInfo } from "./%s"' % spl[1]) - imports.append('import { Messages, Struct } from "messgen"; // TODO: add alias in project or crate npm module\n') - - dts = [] - methods = [] - mes_names = [] - - for msg in module["messages"]: - dts += self.generate_interface(msg) - mes_names.append(msg["name"]) - methods.append(self.generate_send(msg["name"])) - methods.append(self.generate_on(msg["name"], msg["id"])) - dts = dts + self.generate_names(mes_names) - dts = dts + self.generate_datas(mes_names) - dts += ["export type ClearSystemInfo = Omit \n"] - self.__write_file( out_dir + os.path.sep + module_name + ".ts", [prefix] + dts) - self.__write_file(out_dir + os.path.sep + "%sHelper.ts" % class_path, imports + self.generate_class(methods, to_camelcase(spl[1]))) - - def generate_interface(self, msg): - msg_name = msg["name"] - - out = [] - if msg.get("descr") is not None: - out.append("/**\n * %s" % msg["descr"]) - out.append(" */") - out.append('export interface %sMessage {' % to_camelcase(msg_name)) - out.append(' __type__?: "%s"' % msg_name) - fields_p = [] - for f in msg["fields"]: - f_name = f["name"] - f_type = format_type(f, self._variables.get('typed_arrays','false') == 'true') - if f.get("descr") is not None: - fields_p.append(' /** %s */' % str(f.get("descr"))) - fields_p.append(' %s: %s' % (f_name, f_type)) - out.append("\n".join(fields_p)) - out.append("}") - return out - - - def generate_import(self, module_name, msg_name): - return 'import { %sMessage } from "./%s/%s"\n' % (msg_name, module_name, msg_name) - - def generate_send(self, name): - msg_name = to_camelcase(name) - return ''' - send_%s(data: ClearSystemInfo>, callback?: (data: ClearSystemInfo>) => void): any { - return this.send(this.messages.MSG_%s, data, callback); - }''' % (msg_name,name, name, name.upper()) - - def generate_on(self, name, id): - msg_name = to_camelcase(name) - return ''' - on_%s(callback: (data: MessageData<"%s">) => any) { - this.onmessage[%s] = callback; - }''' % (msg_name, name, id) - - def generate_class(self, methods, name_file): - out = [] - out.append('export default class %sHelper {' % (name_file)) - out.append(" send(struct: Struct, data: ClearSystemInfo, callback?: (...args: any) => any) {}") - out.append(" protected onmessage: {(args?: any): void}[] = []") - out.append(" protected messages!: Messages") - out.append("\n".join(methods)) - out.append("}") - return out - - def generate_names(self, names): - out = [] - out.append("export type MessageName =") - for name in iter(names): - out.append('| "%s"' % name) - out.append('\n') - return out - - def generate_datas(self, names): - out = [] - out.append("export type MessageData =") - for name in iter(names): - out.append(' TMessageName extends "%s" ? ' % name) - out.append(' %sMessage :' % to_camelcase(name)) - for name in iter(names): - out.append(' | %sMessage ' % to_camelcase(name)) - out.append('\n') - return out +from .common import SEPARATOR +from pathlib import Path + +from .validation import validate_protocol + +from .model import ( + MessgenType, + Protocol, + TypeClass, +) + +class TypeScriptTypes: + TYPE_MAP = { + "bool": "boolean", + "char": "string", + "int8": "number", + "uint8": "number", + "int16": "number", + "uint16": "number", + "int32": "number", + "uint32": "number", + "int64": "bigint", + "uint64": "bigint", + "float32": "number", + "float64": "number", + "string": "string", + "bytes": "Uint8Array", + } + + TYPED_ARRAY_MAP = { + "int8": "Int8Array", + "uint8": "Uint8Array", + "int16": "Int16Array", + "uint16": "Uint16Array", + "int32": "Int32Array", + "uint32": "Uint32Array", + "int64": "BigInt64Array", + "uint64": "BigUint64Array", + "float32": "Float32Array", + "float64": "Float64Array", + } + + @classmethod + def get_type(cls, type_name): + return cls.TYPE_MAP.get(type_name, type_name) + + @classmethod + def get_typed_array(cls, type_name): + return cls.TYPED_ARRAY_MAP.get(type_name, None) + +class TypeScriptGenerator: + _TYPES_FILE = "types.ts" + _PROTOCOLS_FILE = "protocols.ts" + + def __init__(self, options): + self._options = options + self._types = [] + + + def generate(self, out_dir: Path, types: dict[str, MessgenType], protocols: dict[str, Protocol]) -> None: + self.validate(types, protocols) + self.generate_types(out_dir, types) + self.generate_protocols(out_dir, protocols) + + def validate(self, types: dict[str, MessgenType], protocols: dict[str, Protocol]): + for proto_def in protocols.values(): + validate_protocol(proto_def, types) + + def generate_protocols(self, out_dir: Path, protocols: dict[str, Protocol]) -> None: + types = set() + code = [] + + for proto_name, proto_def in protocols.items(): + code.append(f"export interface {self._to_camel_case(proto_name)} {{") + code.append(f" '{proto_name}': {{") + for message in proto_def.messages.values(): + ts_struct_name = self._to_camel_case(message.type) + code.append(f" '{message.name}': {message.type};") + types.add(ts_struct_name) + code.append(' }') + code.append('}') + code.append('') + + import_statements = self._generate_protocol_imports(types) + protocol_types = ' | '.join(self._to_camel_case(proto_name) for proto_name in protocols.keys()) + final_code = '\n'.join(import_statements + code) + f'export type Protocol = {protocol_types};' + + + self._write_output_file(out_dir, self._PROTOCOLS_FILE, final_code) + + def _generate_protocol_imports(self, types: set[str]) -> list[str]: + import_statements = ["import type {"] + for struct_name in types: + import_statements.append(f" {struct_name},") + import_statements.append("} from './types';") + import_statements.append('') + return import_statements + + def generate_types(self, out_dir: Path, types: dict[str, MessgenType]) -> None: + self._types.clear() + + for type_name, type_def in types.items(): + if type_def.type_class == TypeClass.struct: + self._generate_struct(type_name, type_def) + elif type_def.type_class == TypeClass.enum: + self._generate_enum(type_name, type_def) + + code = '\n'.join(self._types) + + self._write_output_file(out_dir, self._TYPES_FILE, code) + + def _generate_enum(self, enum_name, type_def): + self._types.append(f"export enum {self._to_camel_case(enum_name)} {{") + + for value in type_def.values or []: + if value.comment != None: + self._types.append(f" /** {value.comment} */") + value_name = self._to_camel_case(value.name) + self._types.append(f" {value_name} = {value.value},") + + self._types.append("}") + self._types.append("") + + def _generate_struct(self, name: str, type_def: MessgenType): + self._types.append(f"export interface {self._to_camel_case(name)} {{") + fields = getattr(type_def, 'fields', []) or [] + + for field in fields: + if field.comment != None: + self._types.append(f" /** {field.comment} */") + + field_name = field.name + field_type = self._get_ts_type(field.type) + self._types.append(f" {field_name}: {field_type};") + + self._types.append("}") + self._types.append("") + + def _get_ts_type(self, field_type: str): + typed_array_type = self._is_typed_array(field_type) + if typed_array_type: + return typed_array_type + + if field_type.endswith('[]'): + base_type = field_type[:-2] + ts_base_type = self._get_ts_type(base_type) + return f"{ts_base_type}[]" + if '[' in field_type and field_type.endswith(']'): + base_type = field_type[:field_type.find('[')] + ts_base_type = self._get_ts_type(base_type) + return f"{ts_base_type}[]" + + if '{' in field_type and field_type.endswith('}'): + base_type = field_type[:field_type.find('{')] + key_type = field_type[field_type.find('{')+1:-1] + ts_value_type = self._get_ts_type(base_type) + ts_key_type = self._get_ts_type(key_type) + return f"Map<{ts_key_type}, {ts_value_type}>" + + if field_type in TypeScriptTypes.TYPE_MAP: + return TypeScriptTypes.get_type(field_type) + + return self._to_camel_case(field_type) + + def _is_typed_array(self, field_type): + if field_type.endswith('[]'): + base_type = field_type[:-2] + typed_array = TypeScriptTypes.get_typed_array(base_type) + if typed_array: + return typed_array + if '[' in field_type and field_type.endswith(']'): + base_type = field_type[:field_type.find('[')] + typed_array = TypeScriptTypes.get_typed_array(base_type) + if typed_array: + return typed_array + return None + + def _write_output_file(self, output_path, file, content): + output_file = os.path.join(output_path, f"{file}") + + with open(output_file, 'w', encoding='utf-8') as f: + f.write(content) @staticmethod - def __write_file(fpath, code): - with open(fpath, "w") as f: - for line in code: - f.write("%s\n" % line) + def _to_camel_case(s: str): + name = '_'.join(s.split(SEPARATOR)) + return ''.join(word.capitalize() for word in name.split('_')) + + diff --git a/messgen/utils.py b/messgen/utils.py deleted file mode 100644 index 84b595da..00000000 --- a/messgen/utils.py +++ /dev/null @@ -1,108 +0,0 @@ -import re - - -def __get_types_map_pattern(types_map): - pattern = "(" - for _type in types_map: - pattern += _type + "|" - - return pattern[:-1] + ")" - - -def remove_array(typename): - return re.sub("(\[\d*\])$", "", typename) - - -def is_dynamic(typename): - se = re.search("(\[\])$", typename) - return se is not None - - -def in_types_map(types_map, typename): - pattern = __get_types_map_pattern(types_map) - typename = remove_array(typename) - - return re.search(pattern, typename) is not None - - -def get_module_name(norm_typename): - items = norm_typename.split('/') - if len(items) != 4: - return "" - - return items[0] + "/" + items[2] - - -def is_array(typename): - se = re.search("(\[\d*\])$", typename) - return se is not None - - -def get_array_size(array): - size_pattern = "\d+\]$" - - matches = re.search(size_pattern, array) - if matches is not None: - return matches.group(0).replace("]", "") - - return 0 - - -def get_type_size_from_array(types_map, array): - type_pattern = "" - - for k in types_map: - type_pattern += "(" + k + ")" + "|" - - type_pattern = type_pattern[:-1] - - matches = re.search(type_pattern, array) - _type = matches.group(0) - - size_pattern = "\d+\]$" - - matches = re.search(size_pattern, array) - _size = matches.group(0).replace("]", "") - - return _type, _size - - -def get_field_type(field_type): - return remove_array(field_type) - - -if __name__ == "__main__": - assert (remove_array("ololo[10]") == "ololo") - assert (remove_array("int8[11]") == "int8") - assert (remove_array("ahah") == "ahah") - assert (remove_array("uint8") == "uint8") - - TYPE_MAP = { - "char": {"size": 1, "align": 1}, - "int8": {"size": 1, "align": 1}, - "uint8": {"size": 1, "align": 1}, - "int16": {"size": 2, "align": 2}, - "uint16": {"size": 2, "align": 2}, - "int32": {"size": 4, "align": 4}, - "uint32": {"size": 4, "align": 4}, - "int64": {"size": 8, "align": 8}, - "uint64": {"size": 8, "align": 8}, - "float32": {"size": 4, "align": 8}, - "float64": {"size": 8, "align": 8}, - "microavia/mess1": {}, - "microavia/batmon/mess1": {}, - } - - assert (in_types_map(TYPE_MAP, "char")) - assert (in_types_map(TYPE_MAP, "int8")) - assert (in_types_map(TYPE_MAP, "float64[10]")) - assert (in_types_map(TYPE_MAP, "uint64[20]")) - assert (in_types_map(TYPE_MAP, "microavia/mess1")) - assert (in_types_map(TYPE_MAP, "microavia/batmon/mess1")) - assert (in_types_map(TYPE_MAP, "microavia/mess1[10]")) - assert (in_types_map(TYPE_MAP, "microavia/batmon/mess1[2]")) - assert (not in_types_map(TYPE_MAP, "microavia/batmon/mess2")) - assert (not in_types_map(TYPE_MAP, "microavia/")) - assert (not in_types_map(TYPE_MAP, "microavia/random_msg[10]")) - assert (get_array_size("uint32[10]") == "10") - assert (get_array_size("uint8[12]") == "12") diff --git a/messgen/validation.py b/messgen/validation.py new file mode 100644 index 00000000..67017078 --- /dev/null +++ b/messgen/validation.py @@ -0,0 +1,173 @@ +from typing import Any + +from .model import ( + EnumType, + MessgenType, + Protocol, + StructType, +) + +_CPP_KEYWORDS = { + "alignas", + "alignof", + "and_eq", + "and", + "asm", + "atomic_cancel", + "atomic_commit", + "atomic_noexcept", + "auto", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "char16_t", + "char32_t", + "char8_t", + "class", + "co_await", + "co_return", + "co_yield", + "compl", + "concept", + "const_cast", + "const", + "consteval", + "constexpr", + "constinit", + "continue", + "decltype", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "import", + "inline", + "int", + "long", + "module", + "mutable", + "namespace", + "new", + "noexcept", + "not_eq", + "not", + "nullptr", + "operator", + "or_eq", + "or", + "private", + "protected", + "public", + "reflexpr", + "register", + "reinterpret_cast", + "requires", + "return", + "short", + "signed", + "sizeof", + "static_assert", + "static_cast", + "static", + "struct", + "switch", + "synchronized", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor_eq", + "xor", +} + +_CPP_INT_TYPES = { + "int8_t", + "int16_t", + "int32_t", + "int64_t", + "uint8_t", + "uint16_t", + "uint32_t", + "uint64_t", +} + + +def validate_protocol(protocol: Protocol, types: dict[str, MessgenType]): + seen_names = set() + for msg_id, msg in protocol.messages.items(): + if msg.name in seen_names: + raise RuntimeError(f"Message with name={msg.name} appears multiple times in protocol={protocol.name}") + if msg.type not in types: + raise RuntimeError(f"Type {msg.type} required by message={msg.name} protocol={protocol.name} not found") + if msg.message_id != msg_id: + raise RuntimeError(f"Message {msg.name} has different message_id={msg.message_id} than key={msg_id} in protocol={protocol.name}") + seen_names.add(msg.name) + + +def validate_types(types: dict[str, MessgenType]): + seen_hashes: dict[int, Any] = {} + for type_name, type_def in types.items(): + type_hash = hash(type_def) + if hash_conflict := seen_hashes.get(type_hash): + raise RuntimeError(f"Type {type_name} has the same hash as {hash_conflict.type}") + seen_hashes[type_hash] = type_def + + +# Checks if `s` is a valid name for a field or a message type +def is_valid_name(name: str): + if not isinstance(name, str) or not name: + return False + + if not (name[0].isalpha() or name[0] == '_'): + return False + + if not all(c.isalnum() or c == '_' for c in name[1:]): + return False + + if name in _CPP_KEYWORDS: + return False + + if name in _CPP_INT_TYPES: + return False + + return True + + +def validate_type_dict(item_name: str, item: dict[str, StructType | EnumType]) -> None: + if not is_valid_name(item_name): + raise RuntimeError("Invalid message name %s" % item_name) + + if "type_class" not in item: + raise RuntimeError("type_class missing in '%s': %s" % (item_name, item)) + + if (type_class := item.get("type_class")) not in ["struct", "enum"]: + raise RuntimeError("type_class '%s' in '%s' is not supported %s" % (type_class, item_name, item)) diff --git a/messgen/version_protocol.py b/messgen/version_protocol.py deleted file mode 100644 index 72dfa203..00000000 --- a/messgen/version_protocol.py +++ /dev/null @@ -1,12 +0,0 @@ -import hashlib - - -class VersionProtocol: - def __init__(self, module): - self._module = module - - - def generate(self): - result = hashlib.md5(str(self._module).encode()) - - return result.hexdigest()[0:6] diff --git a/messgen/yaml_parser.py b/messgen/yaml_parser.py new file mode 100644 index 00000000..f6eb2ed1 --- /dev/null +++ b/messgen/yaml_parser.py @@ -0,0 +1,305 @@ +import os +import yaml + +from pathlib import Path +from typing import Any + +from .common import SEPARATOR +from .model import ( + ArrayType, + BasicType, + EnumType, + EnumValue, + FieldType, + MapType, + Message, + MessgenType, + Protocol, + StructType, + TypeClass, + VectorType, +) +from .validation import ( + is_valid_name, + validate_type_dict, +) + + +_CONFIG_EXT = ".yaml" +_SCALAR_TYPES_INFO = { + "bool": {"size": 1}, + "int8": {"size": 1}, + "uint8": {"size": 1}, + "int16": {"size": 2}, + "uint16": {"size": 2}, + "int32": {"size": 4}, + "uint32": {"size": 4}, + "int64": {"size": 8}, + "uint64": {"size": 8}, + "float32": {"size": 4}, + "float64": {"size": 8}, + "int": {"size": 4}, +} + + +def parse_protocols(protocols: list[str]) -> dict[str, Protocol]: + if not protocols: + return {} + + if not all(proto.count(":") == 1 for proto in (protocols or [])): + raise RuntimeError("Protocol must be in format /path/of/basedir:namespace/of/proto") + + protocol_files = {} + for proto in protocols: + proto_path, proto_name = proto.split(":") + expected_file = Path(proto_path) / f"{proto_name}{_CONFIG_EXT}" + if not expected_file.exists(): + raise RuntimeError(f"Protocol file not found: {expected_file}") + protocol_files[proto_name] = expected_file + + protocol_descriptors: dict[str, Protocol] = {} + for proto_name, protocol_file in protocol_files.items(): + protocol_descriptors[proto_name] = _parse_protocol(protocol_file) + + return protocol_descriptors + + +def _parse_protocol(protocol_file: Path) -> Protocol: + with open(protocol_file, "r") as f: + item = yaml.safe_load(f) + item_name = protocol_file.stem + if not is_valid_name(item_name): + raise RuntimeError(f"Invalid message name {item_name}") + return _get_protocol(item_name, item) + raise RuntimeError(f"Failed to open file: {protocol_file}") + + +def _get_protocol(proto_name, protocol_desc: dict[str, Any]) -> Protocol: + return Protocol(name=proto_name, + proto_id=int(protocol_desc["proto_id"]), + messages={msg_id: _get_message_type(msg_id, msg) for msg_id, msg in protocol_desc.get("messages", {}).items()}) + + +def _get_message_type(msg_id: int, message_desc: dict[str, Any]) -> Message: + return Message(message_id=msg_id, + name=message_desc["name"], + type=message_desc["type"], + comment=message_desc.get("comment")) + + +def parse_types(base_dirs: list[str | Path]) -> dict[str, MessgenType]: + if not base_dirs: + return {} + + type_descriptors = {} + for directory in base_dirs: + base_dir = Path.cwd() / directory if not isinstance(directory, Path) else directory + type_files = base_dir.rglob(f"*{_CONFIG_EXT}") + for type_file in type_files: + with open(type_file, "r") as f: + item = yaml.safe_load(f) + validate_type_dict(type_file.stem, item) + type_descriptors[_type_name(type_file, base_dir)] = item + + type_dependencies: set[str] = set() + parsed_types = { + type_name: _get_type(type_name, type_descriptors, type_dependencies) + for type_name in type_descriptors + } + + ignore_dependencies: set[str] = set() + type_dependencies -= set(parsed_types.keys()) + + parsed_types.update({ + type_name: _get_type(type_name, type_descriptors, ignore_dependencies) + for type_name in type_dependencies + }) + + return parsed_types + + +def _type_name(type_file: Path, base_dir: Path) -> str: + return type_file.relative_to(base_dir).with_suffix("").as_posix().replace(os.sep, SEPARATOR) + + +def _get_type(type_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> MessgenType: + # Scalar + if scalar_type := _SCALAR_TYPES_INFO.get(type_name): + return _get_scalar_type(type_name, scalar_type) + + if type_name in ["string", "bytes"]: + return _get_basic_type(type_name) + + if len(type_name) > 2: + if type_name.endswith("[]"): + return _get_vector_type(type_name, type_descriptors, type_dependencies) + + if type_name.endswith("]"): + return _get_array_type(type_name, type_descriptors, type_dependencies) + + if type_name.endswith("}"): + return _get_map_type(type_name, type_descriptors, type_dependencies) + + type_desc = type_descriptors.get(type_name) + if not type_desc: + raise RuntimeError(f"Invalid type: {type_name}") + type_class = TypeClass[type_desc.get("type_class", None)] + + if type_class == TypeClass.enum: + return _get_enum_type(type_name, type_descriptors, type_dependencies) + + if type_class == TypeClass.struct: + return _get_struct_type(type_name, type_descriptors, type_dependencies) + + raise RuntimeError("Invalid type class: %s" % type_class) + + +def _get_scalar_type(type_name: str, scalar_type: dict[str, Any]) -> BasicType: + return BasicType(type=type_name, + type_class=TypeClass.scalar, + size=scalar_type["size"]) + + +def _get_basic_type(type_name: str) -> BasicType: + return BasicType(type=type_name, + type_class=TypeClass[type_name], + size=None) + + +def _get_vector_type(type_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> VectorType: + assert _get_type(type_name[:-2], type_descriptors, type_dependencies) + + element_type = type_name[:-2] + type_dependencies.add(_get_dependency_type(type_name, element_type, type_descriptors, type_dependencies)[0]) + + return VectorType(type=type_name, + type_class=TypeClass.vector, + element_type=element_type, + size=None) + + +def _get_array_type(type_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> ArrayType: + p = type_name[:-1].split("[") + element_type = "[".join(p[:-1]) + type_dependencies.add(_get_dependency_type(type_name, element_type, type_descriptors, type_dependencies)[0]) + + array_size = int(p[-1]) + if array_size > 0x10000: + print("Warn: %s array size is too large and may cause SIGSEGV on init" % type_name) + + res = ArrayType(type=type_name, + type_class=TypeClass.array, + element_type=element_type, + array_size=array_size, + size=None) + + element_type_def = _get_type(element_type, type_descriptors, type_dependencies) + assert element_type_def + + sz = element_type_def.size + if sz is not None: + res.size = sz * array_size + + return res + + +def _get_map_type(type_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> MapType: + p = type_name[:-1].split("{") + value_type = "{".join(p[:-1]) + key_type = p[-1] + + type_dependencies.add(_get_dependency_type(type_name, key_type, type_descriptors, type_dependencies)[0]) + type_dependencies.add(_get_dependency_type(type_name, value_type, type_descriptors, type_dependencies)[0]) + + return MapType(type=type_name, + type_class=TypeClass.map, + key_type=key_type, + value_type=value_type, + size=None) + + +def _get_enum_type(type_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> EnumType: + type_desc = type_descriptors.get(type_name) + assert type_desc + + base_type = type_desc.get("base_type") + + if base_type: + type_dependencies.add(_get_dependency_type(type_name, base_type, type_descriptors, type_dependencies)[0]) + dependency = _get_type(base_type, type_descriptors, type_dependencies) + assert dependency + + values = [ EnumValue(name=item.get("name"), + value=item.get("value"), + comment=item.get("comment")) + for item in type_desc.get("values", {}) ] + + return EnumType(type=type_name, + type_class=TypeClass.enum, + base_type=base_type, + comment=type_desc.get("comment"), + values=values, + size=dependency.size or _SCALAR_TYPES_INFO["int"]["size"]) + + +def _get_struct_type(type_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> StructType: + type_desc = type_descriptors[type_name] + type_class = type_desc.get("type_class") + + struct_type = StructType(type=type_name, + type_class=TypeClass.struct, + comment=type_desc.get("comment"), + fields=[], + size=None) + + fields = (type_desc.get("fields", []) + if isinstance(type_desc.get("fields"), list) + else []) + + sz = 0 + fixed_size = True + seen_names = set() + for field in fields: + field_name, field_type = field.get("name"), field.get("type") + + if not is_valid_name(field_name): + raise RuntimeError(f"Invalid field '{field_name}' in {type_class}") + + if field_name in seen_names: + raise RuntimeError(f"Duplicate field name '{field_name}' in {type_class}") + + seen_names.add(field_name) + + dependency_name, dependency = _get_dependency_type(type_name, field_type, type_descriptors, type_dependencies) + struct_type.fields.append(FieldType(name=field_name, type=dependency_name, comment=field.get("comment"))) + type_dependencies.add(dependency_name) + + if (dsz := dependency.size) is not None: + sz += dsz + else: + fixed_size = False + + if fixed_size: + struct_type.size = sz + + return struct_type + + +def _get_dependency_type(type_name: str, dependency_name: str, type_descriptors: dict[str, dict[str, Any]], type_dependencies: set[str]) -> tuple[str, MessgenType]: + if dependency := _value_or_none(_get_type, dependency_name, type_descriptors, type_dependencies): + return dependency_name, dependency + + if SEPARATOR in type_name: + ns_name = SEPARATOR.join(type_name.split(SEPARATOR)[:-1]) + qual_dependency_name = f"{ns_name}{SEPARATOR}{dependency_name}" + if dependency := _value_or_none(_get_type, qual_dependency_name, type_descriptors, type_dependencies): + return qual_dependency_name, dependency + + raise RuntimeError(f"Could not resolve {dependency_name} dependency of {type_name}") + + +def _value_or_none(func, *args, **kwargs): + try: + return func(*args, **kwargs) + except Exception: + return None diff --git a/port/cpp/messgen/Dynamic.h b/port/cpp/messgen/Dynamic.h deleted file mode 100644 index 19da4fe1..00000000 --- a/port/cpp/messgen/Dynamic.h +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "MemoryAllocator.h" -#include "SimpleDetector.h" - -namespace messgen { - -template -struct Dynamic; - -namespace dynamic { -namespace detail { - -template -struct Serializer; - -template -struct Serializer> { - static size_t serialize(uint8_t* buf, const Dynamic& dynamic) { - auto bytes = dynamic.size * sizeof(T); - std::memcpy(buf, dynamic.ptr, bytes); - return bytes; - } -}; - -template -struct Serializer> { - static size_t serialize(uint8_t* buf, const Dynamic& dynamic) { - uint8_t* dst = buf; - for (size_t i = 0; i < dynamic.size; ++i) { - dst += dynamic.ptr[i].serialize_msg(dst); - } - return dst - buf; - } -}; - -template -struct Parser; - -template -struct Parser> { - static size_t parse(const uint8_t* buf, uint32_t, MemoryAllocator&, Dynamic& dynamic) { - auto bytes = dynamic.size * sizeof(T); - memcpy(dynamic.ptr, buf, bytes); - return bytes; - } -}; - -template -struct Parser> { - static int parse(const uint8_t* buf, uint32_t len, MemoryAllocator& allocator, Dynamic& dynamic) { - const uint8_t *src = buf; - for (size_t i = 0; i < dynamic.size; ++i) { - auto dyn_parsed_len = dynamic.ptr[i].parse_msg(src, len, allocator); - if (dyn_parsed_len < 0) { - return -1; - } - src += dyn_parsed_len; - len -= dyn_parsed_len; - } - - return src - buf; - } -}; - -} -} - -template::is_simple_enough> -struct Dynamic { - using this_type = Dynamic; - - T *ptr; - uint32_t size; - - bool operator==(const Dynamic &other) const { - if (size != other.size) { - return false; - } - - for (size_t i = 0; i < size; ++i) { - if (ptr[i] != other.ptr[i]) { - return false; - } - } - - return true; - } - - size_t serialize_msg(uint8_t *buf) const { - uint8_t* dst = buf; - - std::memcpy(dst, std::addressof(this->size), sizeof(this->size)); - dst += sizeof(this->size); - - dst += dynamic::detail::Serializer::serialize(dst, *this); - - return dst - buf; - } - - int parse_msg(const uint8_t *buf, uint32_t len, MemoryAllocator & allocator) { - const uint8_t* src = buf; - - if (len < sizeof(this->size)) { return -1; } - - std::memcpy(std::addressof(this->size), src, sizeof(this->size)); - src += sizeof(this->size); - len -= sizeof(this->size); - - this->ptr = allocator.alloc(this->size); - if (nullptr == this->ptr) { return -1; } - - int res = dynamic::detail::Parser::parse(src, len, allocator, *this); - if (res < 0) { - return -1; - } - - src += res; - return src - buf; - } - - T &operator[](uint32_t idx) { - return ptr[idx]; - } - - const T &operator[](uint32_t idx) const { - return ptr[idx]; - } -}; - -template -inline Dynamic make_dynamic(const T (&arr)[N]) { - return Dynamic{.ptr = const_cast(arr), .size = N}; -} - -} diff --git a/port/cpp/messgen/MemoryAllocator.h b/port/cpp/messgen/MemoryAllocator.h deleted file mode 100644 index 3519dd95..00000000 --- a/port/cpp/messgen/MemoryAllocator.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include -#include - - -namespace messgen { - -/** - * @brief Class for holding dynamic fields while parsing - * @note Class is supposed to be re-created after each parse call - */ -class MemoryAllocator { -public: - MemoryAllocator(uint8_t *mem, size_t memory_size) noexcept: - _mem_start(mem), _size(memory_size) {} - - template - T * alloc(size_t num) noexcept { - if (num == 0) { - return reinterpret_cast(_mem_start); - } - - const size_t alloc_size = sizeof(T) * num; - if (align(alignof(T), alloc_size, _mem_start, _size)) { - T *ptr = reinterpret_cast(_mem_start); - _mem_start = (uint8_t *) _mem_start + alloc_size; - _size -= alloc_size; - - return ptr; - } - - return nullptr; - } - -private: - static inline void* - align(size_t __align, size_t __size, void*& __ptr, size_t& __space) noexcept - { - const auto __intptr = reinterpret_cast(__ptr); - const auto __aligned = (__intptr - 1u + __align) & -__align; - const auto __diff = __aligned - __intptr; - if ((__size + __diff) > __space) - return nullptr; - else - { - __space -= __diff; - return __ptr = reinterpret_cast(__aligned); - } - } - - - void *_mem_start; - size_t _size; -}; - -/** - * @brief Class which allows to statically allocate memory for messgen parsing - * @tparam MEM_SIZE - memory size - * @warning Each parse call on this class will clear memory, so if you want to do multiple parse calls - * store it into temporary MemoryAllocator& variable. - */ -template -class StaticMemoryAllocator { -public: - explicit StaticMemoryAllocator() noexcept: - _alloc(_memory, MEM_SIZE) {} - - operator MemoryAllocator &() noexcept { - return get(); - } - - MemoryAllocator &get() noexcept { - _alloc = MemoryAllocator(_memory, MEM_SIZE); - return _alloc; - } - -private: - uint8_t _memory[MEM_SIZE]{}; - MemoryAllocator _alloc; -}; - - -} diff --git a/port/cpp/messgen/MessageInfo.h b/port/cpp/messgen/MessageInfo.h deleted file mode 100644 index 30daebe5..00000000 --- a/port/cpp/messgen/MessageInfo.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - - -namespace messgen { - -using MessageID = uint8_t; - -struct MessageInfo { - static constexpr size_t HEADER_SIZE = 5; - - uint32_t size; //!< Message payload size - MessageID msg_id; //!< Message type ID - - const uint8_t *payload; //!< Pointer to message payload - - /** - * Get total serialized message size, including header - * @return total serialized size - */ - size_t get_total_size() const { - return HEADER_SIZE + size; - } -}; - - -} \ No newline at end of file diff --git a/port/cpp/messgen/Metadata.h b/port/cpp/messgen/Metadata.h deleted file mode 100644 index 481edcb0..00000000 --- a/port/cpp/messgen/Metadata.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -namespace messgen { - -struct Metadata { - const char *name; ///< Struct name. - const char *fields; ///< Fields definition. - const Metadata **nested_msgs; ///< Nested messages list, null-terminated. -}; - -} diff --git a/port/cpp/messgen/Parser.h b/port/cpp/messgen/Parser.h deleted file mode 100644 index ef4dafc6..00000000 --- a/port/cpp/messgen/Parser.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include "MemoryAllocator.h" -#include "SimpleDetector.h" - -namespace messgen { -namespace detail { - -template::is_simple_enough> -struct Parser; - -template -struct Parser { - static int parse(const uint8_t* buf, uint32_t len, MemoryAllocator&, T& value) { - size_t bytes = sizeof(T); - if (len < bytes) { return -1; } - std::memcpy(&value, buf, bytes); - return bytes; - } -}; - -template -struct Parser { - static int parse(const uint8_t* buf, uint32_t len, MemoryAllocator& allocator, T& value) { - const uint8_t* src = buf; - auto dyn_parsed_len = value.parse_msg(src, len, allocator); - if (dyn_parsed_len < 0) { return -1; } - src += dyn_parsed_len; - - return src - buf; - } -}; - -} - -template -struct Parser { - static int parse(const uint8_t *buf, uint32_t len, MemoryAllocator & allocator, T& value) { - return detail::Parser::parse(buf, len, allocator, value); - } -}; - -} \ No newline at end of file diff --git a/port/cpp/messgen/Serializer.h b/port/cpp/messgen/Serializer.h deleted file mode 100644 index 7c48badd..00000000 --- a/port/cpp/messgen/Serializer.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include "MemoryAllocator.h" -#include "SimpleDetector.h" - -namespace messgen { -namespace detail { - -template::is_simple_enough> -struct Serializer; - -template -struct Serializer { - static size_t serialize(uint8_t *buf, const T &value) { - auto bytes = sizeof(T); - std::memcpy(buf, &value, bytes); - return bytes; - } - - static size_t get_dynamic_size(const T&) { - return 0; - } -}; - -template -struct Serializer { - static size_t serialize(uint8_t *buf, const T &value) { - return value.serialize_msg(buf); - } - - static size_t get_dynamic_size(const T& value) { - return value.get_dynamic_size(); - } -}; - -} - -template -struct Serializer { - static size_t serialize(uint8_t *buf, const T &value) { - return detail::Serializer::serialize(buf, value); - } - - static size_t get_dynamic_size(const T& value) { - return detail::Serializer::get_dynamic_size(value); - } -}; - -} \ No newline at end of file diff --git a/port/cpp/messgen/SimpleDetector.h b/port/cpp/messgen/SimpleDetector.h deleted file mode 100644 index 6b24cf91..00000000 --- a/port/cpp/messgen/SimpleDetector.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace messgen { - -template -struct SimpleDetector { - static const bool is_simple_enough = std::is_integral::value || std::is_floating_point::value; -}; - -template -struct SimpleDetector { - static const bool is_simple_enough = std::is_integral::value || std::is_floating_point::value; -}; - -template -struct SimpleDetector { - static const bool is_simple_enough = std::is_integral::value || std::is_floating_point::value; -}; - -} \ No newline at end of file diff --git a/port/cpp/messgen/Storage.h b/port/cpp/messgen/Storage.h deleted file mode 100644 index ab5362c3..00000000 --- a/port/cpp/messgen/Storage.h +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -#include "MemoryAllocator.h" - -#include - -namespace messgen { - -/** - * @brief Class for storing objects with dynamic fields. - * Owns memory for dynamic allocations. - */ -template -class StorageBase { -public: - static constexpr uint8_t TYPE = T::TYPE; - static constexpr size_t STATIC_SIZE = T::STATIC_SIZE; - static constexpr uint8_t PROTO = T::PROTO; - static constexpr bool HAS_DYNAMICS = T::HAS_DYNAMICS; - - StorageBase() noexcept = default; - - virtual ~StorageBase() = default; - - explicit StorageBase(const T& value) noexcept: - _value(value) {} - - explicit StorageBase(const T&& value) noexcept: - _value(value) {} - - bool operator== (const T& rhs) const noexcept { - return _value == rhs; - } - - bool operator== (const T&& rhs) const noexcept { - return _value == rhs; - } - - T& operator->() noexcept { - return _value; - } - - const T& operator->() const noexcept { - return _value; - } - - StorageBase& operator= (const T& rhs) noexcept { - _value = rhs; - return *this; - } - - StorageBase& operator= (const T&& rhs) noexcept { - _value = rhs; - return *this; - } - - operator T& () noexcept { - return _value; - } - - operator const T& () const noexcept { - return _value; - } - - size_t get_size() const noexcept { - return _value.get_size(); - } - - size_t get_dynamic_size() const noexcept { - return _value.get_dynamic_size(); - } - - int serialize_msg(uint8_t *buf) const noexcept { - return _value.serialize_msg(buf); - } - -protected: - T _value; -}; - -template -class Storage {}; - -/** - * @brief Class for storing objects with dynamic fields. - * Owns memory for dynamic allocations. - */ -template -class Storage : public StorageBase { -public: - static_assert(SIZE != 0, "Storage size for message with dynamic fields has zero length!"); - - Storage() noexcept = default; - - explicit Storage(const T& value) noexcept: - StorageBase(value) {} - - explicit Storage(const T&& value) noexcept: - StorageBase(value) {} - - int parse_msg(const uint8_t *buf, uint32_t len) noexcept { - return this->_value.parse_msg(buf, len, _memory_allocator); - } - -private: - StaticMemoryAllocator _memory_allocator; -}; - -/** - * @brief Class for storing objects with dynamic fields. - * Owns memory for dynamic allocations. - */ -template -class Storage : public StorageBase { -public: - static_assert(SIZE == 0, "Storage for message without dynamic fields has non zero length!"); - - Storage() noexcept = default; - - explicit Storage(const T& value) noexcept: - StorageBase(value) {} - - explicit Storage(const T&& value) noexcept: - StorageBase(value) {} - - int parse_msg(const uint8_t *buf, uint32_t len) noexcept { - return this->_value.parse_msg(buf, len, _memory_allocator); - } - -private: - MemoryAllocator _memory_allocator{nullptr, 0}; -}; - - -/** - * @brief Function for parsing buffer into Storage wrapper. - * @tparam T - message type - * @tparam S - storage size - * @tparam D - indicates whether storage contains dynamic fields - * @param info - message info to parse - * @param msg - storage wrapper to parse message to - * @return number of bytes parsed in case of success, -1 in case of error - */ -template -int parse(const MessageInfo &info, Storage &msg) { - if (info.msg_id != T::TYPE) { - return -1; - } - - return msg.parse_msg(info.payload, info.size); -} - -} \ No newline at end of file diff --git a/port/cpp/messgen/bitmasks.h b/port/cpp/messgen/bitmasks.h deleted file mode 100644 index aaaa545e..00000000 --- a/port/cpp/messgen/bitmasks.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once - -#include - - -namespace messgen { - -/** - * @brief Structure for enabling bitmasks operations on enums - * @tparam Enum - enum type - */ -template -struct EnableBitMaskOperators -{ - static const bool enable = false; -}; - -} - -/** - * @brief Macros for enabling bitmask operations on Enum. - * Must be used in messgen namespace - */ -#define ENABLE_BITMASK_OPERATORS(ENUM) \ - template<> \ - struct EnableBitMaskOperators { \ - static const bool enable = true; \ - } - - -/** - * Below are bit operations overloaded for enums - */ - -template -typename std::enable_if::enable, Enum>::type -operator |(Enum lhs, Enum rhs) -{ - using underlying = typename std::underlying_type::type; - return static_cast ( - static_cast(lhs) | - static_cast(rhs) - ); -} - -template -typename std::enable_if::enable, Enum>::type -operator &(Enum lhs, Enum rhs) -{ - using underlying = typename std::underlying_type::type; - return static_cast ( - static_cast(lhs) & - static_cast(rhs) - ); -} - -template -typename std::enable_if::enable, Enum>::type -operator ^(Enum lhs, Enum rhs) -{ - using underlying = typename std::underlying_type::type; - return static_cast ( - static_cast(lhs) ^ - static_cast(rhs) - ); -} - -template -typename std::enable_if::enable, Enum>::type & -operator |=(Enum &lhs, Enum rhs) -{ - using underlying = typename std::underlying_type::type; - lhs = static_cast ( - static_cast(lhs) | - static_cast(rhs) - ); - - return lhs; -} - -template -typename std::enable_if::enable, Enum>::type & -operator &=(Enum &lhs, Enum rhs) -{ - using underlying = typename std::underlying_type::type; - lhs = static_cast ( - static_cast(lhs) & - static_cast(rhs) - ); - - return lhs; -} - -template -typename std::enable_if::enable, Enum>::type & -operator ^=(Enum &lhs, Enum rhs) -{ - using underlying = typename std::underlying_type::type; - lhs = static_cast ( - static_cast(lhs) ^ - static_cast(rhs) - ); - - return lhs; -} - -template -typename std::enable_if::enable, Enum>::type -operator ~(Enum rhs) -{ - using underlying = typename std::underlying_type::type; - return static_cast ( - ~static_cast(rhs) - ); -} \ No newline at end of file diff --git a/port/cpp/messgen/messgen.h b/port/cpp/messgen/messgen.h deleted file mode 100644 index d0ce165b..00000000 --- a/port/cpp/messgen/messgen.h +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -#include "MemoryAllocator.h" -#include "Dynamic.h" -#include "Metadata.h" -#include "Serializer.h" -#include "Parser.h" -#include "MessageInfo.h" - -namespace messgen { - -/** - * @brief Get serialized size (message size + header size) - * @tparam T - message type - * @param msg - message instance - * @return number of bytes in serialized message - */ -template -size_t get_serialized_size(const T & msg) { - return msg.get_size() + MessageInfo::HEADER_SIZE; -} - -/** - * @brief Get message info from buffer - * @param buf - buffer with serialized message inside - * @param buf_len - buffer length - * @param info - where to store message info - * @return 0 in case of success, -1 in case of error - */ -inline int get_message_info(const uint8_t *buf, size_t buf_len, MessageInfo &info) { - if (buf_len < MessageInfo::HEADER_SIZE) { - return -1; - } - - info.msg_id = buf[0]; - info.size = (buf[4] << 24U) | - (buf[3] << 16U) | - (buf[2] << 8U) | - (buf[1]); - - if (buf_len < info.size + MessageInfo::HEADER_SIZE) { - return -1; - } - - info.payload = buf + MessageInfo::HEADER_SIZE; - return 0; -} - - -/** - * @brief Serialize message into a given buffer - * @tparam T - message type - * @param msg - message instance - * @param buf - buffer to serialize into - * @param buf_len - buffer size - * @return number of bytes written in case of success, -1 in case of error - */ -template -int serialize(const T &msg, uint8_t *buf, size_t buf_len) { - size_t payload_size = msg.get_size(); - size_t ser_total_size = payload_size + MessageInfo::HEADER_SIZE; - - if (buf_len < ser_total_size) { - return -1; - } - - // info.seq and info.cls must be filled by caller - buf[0] = T::TYPE; - buf[1] = payload_size & 0xFF; - buf[2] = (payload_size >> 8U) & 0xFF; - buf[3] = (payload_size >> 16U) & 0xFF; - buf[4] = (payload_size >> 24U) & 0xFF; - - msg.serialize_msg(buf + MessageInfo::HEADER_SIZE); - return ser_total_size; -} - -/** - * @brief Parse message - * @tparam T - message type - * @param info - message info. See get_message_info. - * @param msg - message instance to parse into - * @param allocator - memory allocator instance - * @return number of bytes parsed in case of success, -1 in case of error - */ -template -int parse(const MessageInfo &info, T &msg, MemoryAllocator &allocator) { - if (info.msg_id != T::TYPE) { - return -1; - } - - return msg.parse_msg(info.payload, info.size, allocator); -} - -/** - * @brief Iterate over all messages inside a buffer - * @tparam F - Message handler type - * @param data - buffer with messages - * @param data_size - buffer size - * @param f - message handler. Must override operator()(const MessageInfo &) - * @return Number of bytes parsed - */ -template -size_t for_each_message(const uint8_t *data, size_t data_size, F& f) { - const uint8_t *buf = data; - size_t remaining = data_size; - - messgen::MessageInfo msg_info{}; - while (0 == get_message_info(buf, remaining, msg_info)) { - f(msg_info); - - const auto total_size = msg_info.get_total_size(); - buf += total_size; - remaining -= total_size; - } - - return buf - data; -} - -template -size_t for_each_message(const messgen::Dynamic &message, F && f) { - return for_each_message(message.ptr, message.size, f); -} - -} diff --git a/port/cpp/messgen/stl.h b/port/cpp/messgen/stl.h deleted file mode 100644 index ace84cf8..00000000 --- a/port/cpp/messgen/stl.h +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - -#include "messgen.h" -#include -#include - - -namespace messgen { -namespace stl { - -/** - * @brief Class which allows to dynamically allocate memory for messgen parsing - * @warning Each parse call on this class will clear memory, so if you want to do multiple parse calls - * store it into temporary MemoryAllocator& variable. - */ -class DynamicMemoryAllocator { -public: - explicit DynamicMemoryAllocator(size_t size) : - _memory(size), - _alloc(&_memory[0], _memory.size()) {} - - operator MemoryAllocator &() noexcept { - _alloc = MemoryAllocator(&_memory[0], _memory.size()); - return _alloc; - } - -private: - std::vector _memory; - MemoryAllocator _alloc; -}; - - -/** - * @brief Serialize message into std::vector - * @details This function will not write into vector more than its' capacity allows in order to avoid - * unexpected memory allocations - * @tparam T - message type - * @param msg - message instance - * @param buf - vector to serialize to - * @return 0 in case of success, -1 in case of error - */ -template -int serialize(const T &msg, std::vector &buf) { - const size_t initial_size = buf.size(); - const size_t serialized_size = get_serialized_size(msg); - const size_t total_size = initial_size + serialized_size; - - if (buf.capacity() < total_size) { - return -1; - } - - buf.resize(total_size); - int res = messgen::serialize(msg, &buf[initial_size], serialized_size); - if (res == -1) { - buf.resize(initial_size); - } - - return res; -} - -/** - * @brief Helper wrapper around std::vector. See messgen.h get_message_info(). - */ -inline int get_message_info(const std::vector &buf, MessageInfo &info) { - return messgen::get_message_info(&buf[0], buf.size(), info); -} - -/** - * @brief Helper wrapper around std::vector. See messgen.h for_each_message(). - */ -template -size_t for_each_message(const std::vector &payload, F &f) { - return messgen::for_each_message(&payload[0], payload.size(), f); -} - -/** - * @brief Create Dynamic from std::vector. - */ -template -Dynamic make_dynamic(std::vector &vec) { - if (vec.empty()) { - return Dynamic{nullptr, 0}; - } else { - return Dynamic{vec.data(), static_cast(vec.size())}; - } -} - -/** - * @brief Create memory allocator from std::vector - * @param vec - vector which memory is to be used for objects allocations. Only capacity() matters - * @return Memory allocator object - */ -inline MemoryAllocator make_memory_allocator(std::vector &vec) { - return MemoryAllocator(&vec[0], vec.capacity()); -} - -} -} diff --git a/port/cpp_nostl/messgen/Allocator.h b/port/cpp_nostl/messgen/Allocator.h new file mode 100644 index 00000000..3225ea88 --- /dev/null +++ b/port/cpp_nostl/messgen/Allocator.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include + +namespace messgen { + +/** + * @brief Class for holding dynamic fields while parsing + * @note Class is supposed to be re-created after each parse call + */ +class Allocator { +public: + Allocator() noexcept: _ptr(nullptr), _size(0) {} + + Allocator(uint8_t *ptr, size_t size) noexcept: _ptr(ptr), _size(size) {} + + /** + * @brief Allocates memory for num objects of type T + * @tparam T - type of object + * @param num - number of objects + * @return pointer to allocated memory or nullptr if not enough memory + */ + template + T *alloc(size_t n) noexcept { + if (n == 0) { + return reinterpret_cast(_ptr); + } + + const size_t alloc_size = sizeof(T) * n; + if (align(alignof(T), alloc_size, _ptr, _size)) { + T *ptr = reinterpret_cast(_ptr); + _ptr = (uint8_t *) _ptr + alloc_size; + _size -= alloc_size; + + return ptr; + } + + return nullptr; + } + +private: + /** + * @brief Aligns pointer to align bytes + * @param align - alignment + * @param size - size of object + * @param ptr - pointer to align + * @param space - space left + * @return aligned pointer + */ + static inline void *align(size_t align, size_t size, void *&ptr, size_t &space) noexcept { + const auto intptr = reinterpret_cast(ptr); + const auto aligned = (intptr - 1u + align) & -align; + const auto diff = aligned - intptr; + if ((size + diff) > space) + return nullptr; + else { + space -= diff; + return ptr = reinterpret_cast(aligned); + } + } + + void *_ptr; + size_t _size; +}; + +/** + * @brief Class which allows to statically allocate memory for messgen parsing + * @tparam MEM_SIZE - memory size + * @warning Each parse call on this class will clear memory, so if you want to do multiple parse calls + * store it into temporary MemoryAllocator& variable. + */ +template +class StaticAllocator { +public: + explicit StaticAllocator() noexcept: + _alloc(_memory, MEM_SIZE) {} + + operator Allocator &() noexcept { + return get(); + } + + Allocator &get() noexcept { + _alloc = Allocator(_memory, MEM_SIZE); + return _alloc; + } + +private: + uint8_t _memory[MEM_SIZE]{}; + Allocator _alloc; +}; + +} diff --git a/port/cpp_nostl/messgen/concepts.h b/port/cpp_nostl/messgen/concepts.h new file mode 100644 index 00000000..ce536c24 --- /dev/null +++ b/port/cpp_nostl/messgen/concepts.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Allocator.h" + +#include +#include +#include +#include + +namespace messgen { + +template +concept serializable = requires(std::remove_cvref_t msg, uint8_t *buf, Allocator &allocator) { + { msg.serialized_size() } -> std::same_as; + { msg.serialize(buf) } -> std::same_as; + { msg.deserialize(buf, allocator) } -> std::same_as; +}; + +template +concept type = serializable && requires(std::remove_cvref_t msg) { + { msg.NAME } -> std::convertible_to; + { msg.SCHEMA } -> std::convertible_to; + { msg.IS_FLAT } -> std::convertible_to; +}; + +template +concept flat_type = type && std::remove_cvref_t::IS_FLAT; + +template +concept message = type::data_type> && requires(std::remove_cvref_t msg) { + { msg.PROTO_ID } -> std::convertible_to; + { msg.MESSAGE_ID } -> std::convertible_to; +}; + +} // namespace messgen \ No newline at end of file diff --git a/port/cpp_nostl/messgen/messgen.h b/port/cpp_nostl/messgen/messgen.h new file mode 100644 index 00000000..4aa08e39 --- /dev/null +++ b/port/cpp_nostl/messgen/messgen.h @@ -0,0 +1,223 @@ +#pragma once + +#include "Allocator.h" +#include "concepts.h" + +#include +#include + +namespace messgen { + +template +using reflect_t = T *; // we could use a hard type instead, but that would incur + // a penalty on compile time + +template +using splice_t = std::remove_pointer_t; + +template +constexpr reflect_t reflect_type = {}; + +template +constexpr reflect_t> reflect_object(T &&t) { + return &t; +} + +template +struct member { + using class_type = C; + using member_type = std::remove_cvref_t; + + const char *name; +}; + +template +struct member_variable : member { + using member::name; + M C::*ptr; +}; + +template +member_variable(const char *, M C::*) -> member_variable; + +template + requires std::same_as, std::remove_cvref_t> +[[nodiscard]] constexpr decltype(auto) value_of(S &&obj, const member_variable &m) noexcept { + return std::forward(obj).*m.ptr; +} + +template +[[nodiscard]] constexpr auto parent_of(const member &) noexcept { + return reflect_type::class_type>; +} + +template +[[nodiscard]] constexpr auto type_of(const member &) noexcept { + return reflect_type::member_type>; +} + +template +[[nodiscard]] constexpr std::string_view name_of(const member &m) noexcept { + return m.name; +} + +template + requires requires(T &&t) { + { t.NAME }; + } +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return T::NAME; +} + +template + requires std::is_enum_v +[[nodiscard]] constexpr std::string_view name_of(reflect_t r) noexcept { + return name_of(r); +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "bool"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint8_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int8_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint16_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int16_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint32_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int32_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint64_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int64_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "float"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "double"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "string"; +} + +template +[[nodiscard]] std::string_view name_of(reflect_t>) { + static auto name = "vector<" + std::string(name_of(reflect_type)) + ">"; + return name.c_str(); +} + +using size_type = uint32_t; + +template +struct vector { + T *_ptr = nullptr; + size_t _size = 0; + + vector() = default; + + vector(const vector &other) { + _ptr = other._ptr; + _size = other._size; + } + + vector(T *ptr, size_t size) + : _ptr(ptr), + _size(size) { + } + + vector(const T *ptr, size_t size) + : _ptr(const_cast(ptr)), + _size(size) { + } + + vector(std::vector &v) + : _ptr(v.begin().base()), + _size(v.size()) { + } + + vector(const std::vector &v) + : _ptr(const_cast(v.begin().base())), + _size(v.size()) { + } + + vector &operator=(const vector &other) { + _ptr = other._ptr; + _size = other._size; + return *this; + } + + size_t size() const { + return _size; + } + + T *begin() { + return _ptr; + } + + const T *begin() const { + return _ptr; + } + + T *end() { + return _ptr + _size; + } + + const T *end() const { + return _ptr + _size; + } + + bool operator==(const vector &other) const { + if (_size != other._size) { + return false; + } + + for (size_t i = 0; i < _size; ++i) { + if (_ptr[i] != other._ptr[i]) { + return false; + } + } + + return true; + } + + T &operator[](size_t idx) { + return _ptr[idx]; + } + + const T &operator[](size_t idx) const { + return _ptr[idx]; + } + + T *data() { + return _ptr; + } + + const T *data() const { + return _ptr; + } +}; + +} // namespace messgen diff --git a/port/cpp_stl/messgen/concepts.h b/port/cpp_stl/messgen/concepts.h new file mode 100644 index 00000000..aa0c1347 --- /dev/null +++ b/port/cpp_stl/messgen/concepts.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +namespace messgen { + +template +concept serializable = requires(std::remove_cvref_t msg, uint8_t *buf) { + { msg.serialized_size() } -> std::same_as; + { msg.serialize(buf) } -> std::same_as; + { msg.deserialize(buf) } -> std::same_as; +}; + +template +concept type = serializable && requires(std::remove_cvref_t msg) { + { msg.NAME } -> std::convertible_to; + { msg.SCHEMA } -> std::convertible_to; + { msg.IS_FLAT } -> std::convertible_to; +}; + +template +concept flat_type = type && std::remove_cvref_t::IS_FLAT; + +template +concept message = type::data_type> && requires(std::remove_cvref_t msg) { + { msg.PROTO_ID } -> std::convertible_to; + { msg.MESSAGE_ID } -> std::convertible_to; +}; + +} // namespace messgen \ No newline at end of file diff --git a/port/cpp_stl/messgen/messgen.h b/port/cpp_stl/messgen/messgen.h new file mode 100644 index 00000000..7064225c --- /dev/null +++ b/port/cpp_stl/messgen/messgen.h @@ -0,0 +1,155 @@ +#pragma once + +#include "concepts.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace messgen { + +template +using reflect_t = T *; // we could use a hard type instead, but that would incur + // a penalty on compile time + +template +using splice_t = std::remove_pointer_t; + +template +constexpr reflect_t reflect_type = {}; + +template +constexpr reflect_t> reflect_object(T &&t) { + return &t; +} + +template +struct member { + using class_type = C; + using member_type = std::remove_cvref_t; + + const char *name; +}; + +template +struct member_variable : member { + M C::*ptr; +}; + +template +member_variable(const char *, M C::*) -> member_variable; + +template + requires std::same_as, std::remove_cvref_t> +[[nodiscard]] constexpr decltype(auto) value_of(S &&obj, const member_variable &m) noexcept { + return std::forward(obj).*m.ptr; +} + +template +[[nodiscard]] constexpr auto parent_of(const member &) noexcept { + return reflect_type::class_type>; +} + +template +[[nodiscard]] constexpr auto type_of(const member &) noexcept { + return reflect_type::member_type>; +} + +template +[[nodiscard]] constexpr std::string_view name_of(const member &m) noexcept { + return m.name; +} + +template + requires requires(T &&t) { + { t.NAME }; + } +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return T::NAME; +} + +template + requires std::is_enum_v +[[nodiscard]] constexpr std::string_view name_of(reflect_t r) noexcept { + return name_of(r); +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "bool"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint8_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int8_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint16_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int16_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint32_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int32_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "uint64_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "int64_t"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "float"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "double"; +} + +[[nodiscard]] constexpr std::string_view name_of(reflect_t) noexcept { + return "string"; +} + +template +[[nodiscard]] std::string_view name_of(reflect_t>); + +template +[[nodiscard]] std::string_view name_of(reflect_t>); + +template +[[nodiscard]] std::string_view name_of(reflect_t>) { + static auto name = "array<" + std::string(name_of(reflect_type)) + ", " + std::to_string(N) + ">"; + return name.c_str(); +} + +template +[[nodiscard]] std::string_view name_of(reflect_t>) { + static auto name = "vector<" + std::string(name_of(reflect_type)) + ">"; + return name.c_str(); +} + +template +[[nodiscard]] std::string_view name_of(reflect_t>) { + static auto name = "map<" + std::string(name_of(reflect_type)) + ", " + std::string(name_of(reflect_type)) + ">"; + return name.c_str(); +} + +using size_type = uint32_t; + +} // namespace messgen diff --git a/port/go/messgen/messgen.go b/port/go/messgen/messgen.go index 499a2339..a73d66b2 100644 --- a/port/go/messgen/messgen.go +++ b/port/go/messgen/messgen.go @@ -39,7 +39,7 @@ func Serialize(msg Message) ([]byte, error) { func SerializeToBuffer(msg Message, buf []byte) (int, error) { totalSize := HeaderSize + msg.MsgSize() if len(buf) < totalSize { - return 0, errors.New("wrong buffer size") + return 0, errors.New("wrong buffer _size") } buf[0] = byte(msg.MsgId()) @@ -51,8 +51,8 @@ func SerializeToBuffer(msg Message, buf []byte) (int, error) { return totalSize, nil } -// Parse header of the first message in buffer, return message info, total size (including header). -// Returns nil, 0 if buffer size or header is invalid. +// Parse header of the first message in buffer, return message info, total _size (including header). +// Returns nil, 0 if buffer _size or header is invalid. func Parse(buf []byte) (*MessageInfo, int) { if len(buf) < HeaderSize { return nil, 0 diff --git a/port/js/.editorconfig b/port/js/.editorconfig new file mode 100644 index 00000000..5be6a3dd --- /dev/null +++ b/port/js/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +max_line_length = 120 + +indent_style = space +indent_size = 2 + +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/port/js/.eslintrc.js b/port/js/.eslintrc.js new file mode 100644 index 00000000..7583c341 --- /dev/null +++ b/port/js/.eslintrc.js @@ -0,0 +1,99 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + 'import', + ], + extends: [ + 'airbnb-base', + 'airbnb-typescript/base', + 'plugin:eslint-comments/recommended', + 'plugin:import/recommended', + ], + parserOptions: { + project: './tsconfig.eslint.json', + }, + settings: { + 'import/parsers': { + '@typescript-eslint/parser': ['.ts'] + }, + 'import/resolver': { + node: {}, + typescript: { + alwaysTryTypes: true, + roject: './tsconfig.json', + } + }, + 'import/extensions': ['.ts'], + 'import/external-module-folders': ['node_modules'], + }, + rules: { + 'import/no-default-export': 'error', + 'import/no-cycle': 'off', + 'import/no-duplicates': 'off', + 'import/no-unused-modules': 'off', + 'import/no-relative-packages': 'off', + 'import/no-extraneous-dependencies': 'off', + 'import/no-useless-path-segments': 'off', + 'import/no-self-import': 'off', + 'import/no-import-module-exports': 'off', + 'import/prefer-default-export': 'off', + 'import/extensions': 'off', + 'import/order': 'off', + 'import/no-named-default': 'off', + 'import/namespace': 'off', + 'import/named': 'off', + 'import/no-unresolved': 'off', + 'no-duplicate-imports': 'off', + + 'no-underscore-dangle': 'off', + 'multiline-ternary': ['error', 'never'], + 'no-nested-ternary': ['error'], + 'no-else-return': ['error', { allowElseIf: true }], + 'no-plusplus': 'off', + '@typescript-eslint/no-unused-expressions': ['error'], + '@typescript-eslint/no-unused-vars': ['error', { + varsIgnorePattern: '^(_[0-9a-zA-Z]*)$', + argsIgnorePattern: '^(_[0-9a-zA-Z]*)$', + }], + '@typescript-eslint/no-use-before-define': ['error', { functions: false }], + + '@typescript-eslint/consistent-type-imports': ['error'], + '@typescript-eslint/consistent-type-exports': ['error', { + fixMixedExportsWithInlineTypeSpecifier: true, + }], + + '@typescript-eslint/no-useless-constructor': 'error', + + 'no-restricted-syntax': [ + 'error', + 'ForInStatement', + 'LabeledStatement', + 'WithStatement', + ], + 'no-bitwise': 'off', + 'no-buffer-constructor': 'off', + 'no-param-reassign': 'off', + + 'prefer-arrow-callback': ['error', { allowNamedFunctions: true, allowUnboundThis: true }], + 'no-confusing-arrow': 'off', + + '@typescript-eslint/space-infix-ops': 'error', + '@typescript-eslint/lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], + '@typescript-eslint/no-explicit-any': 'error', + 'class-methods-use-this': 'off', + 'no-await-in-loop': 'off', + 'no-continue': 'off', + 'curly': ['error', 'all'], + 'max-len': ['error', { code: 120 }], + 'object-curly-newline': ['error', { + ImportDeclaration: { + multiline: true, + consistent: true, + minProperties: 100, + }, + }], + 'yoda': 'off', + } +}; diff --git a/port/js/.gitignore b/port/js/.gitignore index f26a9f29..891ef45a 100644 --- a/port/js/.gitignore +++ b/port/js/.gitignore @@ -1,3 +1,6 @@ /dist /node_modules -/coverage \ No newline at end of file +/coverage + +/tests/another_proto/ +/tests/messgen/ diff --git a/port/js/.nvmrc b/port/js/.nvmrc new file mode 100644 index 00000000..bb8c76c6 --- /dev/null +++ b/port/js/.nvmrc @@ -0,0 +1 @@ +v22.11.0 diff --git a/port/js/babel.config.js b/port/js/babel.config.js deleted file mode 100644 index 6ab438ed..00000000 --- a/port/js/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: ['@babel/preset-env'] -} diff --git a/port/js/benchmarks/deserialize-variant.bench.js b/port/js/benchmarks/deserialize-variant.bench.js deleted file mode 100644 index 8a1bc3ce..00000000 --- a/port/js/benchmarks/deserialize-variant.bench.js +++ /dev/null @@ -1,115 +0,0 @@ -import { bench } from 'vitest' - -import {Struct} from '../src/messgen.js'; -import {Buffer} from './deserialize-variant/messgen-old.js'; -import { Buffer as BufferFromEntries} from './deserialize-variant/messgen-fromEntries.js'; -import { Buffer as BufferPreBuild, Struct as StructPreBuild} from './deserialize-variant/messgen-pre-build.js'; - -import { bench, describe } from 'vitest' - -let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: 'Int8' }, - { name: 'type_Uint8', type: 'Uint8' }, - { name: 'type_Int16', type: 'Int16' }, - { name: 'type_Uint16', type: 'Uint16' }, - { name: 'type_Int32', type: 'Int32' }, - { name: 'type_Uint32', type: 'Uint32' }, - { name: 'type_Int64', type: 'Int64' }, - { name: 'type_Uint64', type: 'Uint64' }, - { name: 'type_String', type: 'String' }, - { name: 'type_Double', type: 'Double' }, - { name: 'type_Char', type: 'Char' }, - { name: '_type_Int8', type: 'Int8' }, - { name: '_type_Uint8', type: 'Uint8' }, - { name: '_type_Int16', type: 'Int16' }, - { name: '_type_Uint16', type: 'Uint16' }, - { name: '_type_Int32', type: 'Int32' }, - { name: '_type_Uint32', type: 'Uint32' }, - { name: '_type_Int64', type: 'Int64' }, - { name: '_type_Uint64', type: 'Uint64' }, - { name: '_type_String', type: 'String' }, - { name: '_type_Double', type: 'Double' }, - { name: '_type_Char', type: 'Char' } - ] -}); - -let srcStructPrebild = new StructPreBuild({ - id: 2, - fields: [ - { name: 'type_Int8', type: 'Int8' }, - { name: 'type_Uint8', type: 'Uint8' }, - { name: 'type_Int16', type: 'Int16' }, - { name: 'type_Uint16', type: 'Uint16' }, - { name: 'type_Int32', type: 'Int32' }, - { name: 'type_Uint32', type: 'Uint32' }, - { name: 'type_Int64', type: 'Int64' }, - { name: 'type_Uint64', type: 'Uint64' }, - { name: 'type_String', type: 'String' }, - { name: 'type_Double', type: 'Double' }, - { name: 'type_Char', type: 'Char' }, - { name: '_type_Int8', type: 'Int8' }, - { name: '_type_Uint8', type: 'Uint8' }, - { name: '_type_Int16', type: 'Int16' }, - { name: '_type_Uint16', type: 'Uint16' }, - { name: '_type_Int32', type: 'Int32' }, - { name: '_type_Uint32', type: 'Uint32' }, - { name: '_type_Int64', type: 'Int64' }, - { name: '_type_Uint64', type: 'Uint64' }, - { name: '_type_String', type: 'String' }, - { name: '_type_Double', type: 'Double' }, - { name: '_type_Char', type: 'Char' } - ] -}); - -let srcData = { - type_Int8: 8, - type_Uint8: 8, - type_Int16: 8, - type_Uint16: 8, - type_Int32: 8, - type_Uint32: 8, - type_Int64: BigInt(8), - type_Uint64: BigInt(8), - type_String: 'This is test string', - type_Double: -Math.PI, - type_Char: 'A', - _type_Int8: 8, - _type_Uint8: 8, - _type_Int16: 8, - _type_Uint16: 8, - _type_Int32: 8, - _type_Uint32: 8, - _type_Int64: BigInt(8), - _type_Uint64: BigInt(8), - _type_String: 'This is test string', - _type_Double: -Math.PI, - _type_Char: 'A' -}; -srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); -let b = Buffer.serializeObj(srcStruct.schema.fields, srcData); - - - -describe('Buffer(b).deserialize ', () => { - - beforeEach(() => { - srcData = { - ...srcData, - type_Int32: srcData.type_Int32 + 1, - }; - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); - b = Buffer.serializeObj(srcStruct.schema.fields, srcData); - }) - bench('Object.fromEntries', () => { - let res = new BufferFromEntries(b).deserialize(srcStruct); - }) - bench('pre build object Object', () => { - let res = new BufferPreBuild(b).deserialize(srcStructPrebild); - }) - bench('mutation Object', () => { - let res = new Buffer(b).deserialize(srcStruct); - }) - -}) diff --git a/port/js/benchmarks/deserialize-variant/messgen-fromEntries.js b/port/js/benchmarks/deserialize-variant/messgen-fromEntries.js deleted file mode 100644 index a55e1816..00000000 --- a/port/js/benchmarks/deserialize-variant/messgen-fromEntries.js +++ /dev/null @@ -1,657 +0,0 @@ -'use strict' - -import { encodeUTF8, decodeUTF8 } from './utf8.js' - -const IS_LITTLE_ENDIAN = true - -const DYNAMIC_SIZE_TYPE = 'Uint32' - -/** - * - * Read function returns value from byte array. - * Write function returns type byte size. - */ -const basicTypes = [ - { - name: 'Char', - size: 1, - read: (v, s) => String.fromCharCode(v.getInt8(s, IS_LITTLE_ENDIAN)), - write: (v, s, a) => { - v.setInt8(s, a ? a.toString().charCodeAt(0) : 0, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Array - }, - { - name: 'Int8', - size: 1, - read: (v, s) => v.getInt8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Int8Array - }, - { - name: 'Uint8', - size: 1, - read: (v, s) => v.getUint8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Uint8Array - }, - { - name: 'Int16', - size: 2, - read: (v, s) => v.getInt16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Int16Array - }, - { - name: 'Uint16', - size: 2, - read: (v, s) => v.getUint16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Uint16Array - }, - { - name: 'Int32', - size: 4, - read: (v, s) => v.getInt32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Int32Array - }, - { - name: 'Uint32', - size: 4, - read: (v, s) => v.getUint32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Uint32Array - }, - { - name: 'Int64', - size: 8, - read: (v, s) => v.getBigInt64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigInt64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigInt64Array - }, - { - name: 'Uint64', - size: 8, - read: (v, s) => v.getBigUint64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigUint64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigUint64Array - }, - { - name: 'Float', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - } - }, - { - name: 'Float32', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Float32Array - }, - { - name: 'Double', - size: 8, - read: (v, s) => v.getFloat64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: Float64Array - }, - { - name: 'String', - size: 4, - read: (v, s) => decodeUTF8(new Uint8Array(v.buffer, s + 4, v.getUint32(s, IS_LITTLE_ENDIAN))), - write: (v, s, a) => { - let size = a.length - v.setUint32(s, size, true) - for (let i = 0, s2 = s + 4; i < size; i++) { - v.setUint8(s2 + i, a[i], true) - } - return size + 4 - }, - typedArray: Array - } -] - -let typeIndex = [], - typeSize = [] - -let readFunc = [], - writeFunc = [], - typedArray = [] - -for (let i = 0; i < basicTypes.length; i++) { - let ti = basicTypes[i] - typeIndex[ti.name] = i - typeSize[i] = ti.size - readFunc[i] = ti.read - writeFunc[i] = ti.write - typedArray[i] = ti.typedArray -} - -const DYN_TYPE = typeIndex[DYNAMIC_SIZE_TYPE] -const DYN_TYPE_SIZE = typeSize[DYN_TYPE] -const DYN_READ = readFunc[DYN_TYPE] -const DYN_WRITE = writeFunc[DYN_TYPE] - -function parseType(typeStr, includeMessages) { - let a = typeStr.split('['), - name = a[0].trim() - - let size, - isComplex = false - - let type = typeIndex[name] - - if (type !== undefined) { - size = typeSize[type] - } else if (includeMessages && includeMessages[name]) { - type = includeMessages[name] - size = type.size - isComplex = true - } else { - throw new Error(`Unknown type: ${name}, if is complex type you must define before the struct. `) - } - - let length = parseInt(a[1]) - - return { - typeIndex: type, - typeSize: size, - length: isNaN(length) ? 0 : length, - isArray: a.length === 2, - isComplex: isComplex - } -} - -/** - * class Struct - */ -export class Struct { - constructor(schema, includeMessages) { - this._id = 0 - this._size = 0 - this._fields = null - this._schema = null - - this._includeMessages = includeMessages - - this.set(schema) - } - - get schema() { - return this._schema - } - - get id() { - return this._id - } - - get size() { - return this._size - } - - get fields() { - return this._fields - } - - set(schema) { - if (schema) { - this._id = schema.id || 0 - this._size = 0 - this._fields = new Array(schema.fields.length) - this._schema = schema - this._init() - } - } - - _init() { - let schemaFields = this._schema.fields - - let offset = 0 - - for (let i = 0, len = schemaFields.length; i < len; i++) { - let si = schemaFields[i], - tp = parseType(si.type, this._includeMessages) - - this._fields[i] = { - name: si.name, - type: si.type, - _offset: offset, - _prop: tp - } - - if (tp.isArray) { - if (tp.length === 0) { - offset += DYN_TYPE_SIZE - } else { - offset += tp.typeSize * tp.length - } - } else { - offset += tp.typeSize - } - } - - this._size = offset - } -} - -// uint32 seq; //!< Sequence number -// uint16 size; //!< Message payload size -// uint8 cls; //!< Message class -// uint8 msg_id; //!< Message type ID - -export const HEADER_STRUCT = new Struct({ - fields: [ - { name: 'seq', type: 'Uint32' }, - { name: 'cls', type: 'Uint8' }, - { name: 'msg_id', type: 'Uint8' }, - { name: 'size', type: 'Uint32' } - ] -}) - -/** - * class Buffer - */ -export class Buffer { - _useTypedArray = false - - constructor(arrayBuffer, useTypedArray = false) { - this._dataView = new DataView(arrayBuffer) - this._dynamicOffset = 0 - this._useTypedArray = useTypedArray - } - - // TODO: перенести в модуль messages - // а модуль messages генерализировать - static deserialize(messages, data, headerStruct = HEADER_STRUCT, includeMessages) { - let res = [] - let buf = new Buffer(data) - let cur = 0 - while (cur < buf.size) { - let h = buf.deserialize(headerStruct, cur), - m = buf.deserialize(messages.__id__[h.msg_id], cur + h.__SIZE__) - m.__MSG_ID__ = h.msg_id - cur += h.__SIZE__ + m.__SIZE__ - res.push(m) - } - return res - } - - static mergeArrayBuffers(tArrs, type = Uint8Array) { - const ret = new type(tArrs.reduce((acc, tArr) => acc + tArr.byteLength, 0)) - let off = 0 - tArrs.forEach((tArr) => { - ret.set(new type(tArr), off) - off += tArr.byteLength - }) - return ret - } - - static appendBuffer(buffer1, buffer2) { - var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) - tmp.set(new Uint8Array(buffer1), 0) - tmp.set(new Uint8Array(buffer2), buffer1.byteLength) - return tmp.buffer - } - - static calcSize(fields, includeMessages) { - let size = 0 - - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - tp = (fi._prop = parseType(fi.type, includeMessages)) - - if (tp.isArray) { - if (tp.isComplex) { - if (tp.length === 0) { - size += DYN_TYPE_SIZE - } - for (let i = 0; i < fi.value.length; i++) { - let arr = Buffer.createValueArray(fi._prop.typeIndex.fields, fi.value[i], includeMessages) - size += Buffer.calcSize(arr, includeMessages) - } - } else { - let arrayLength = 0 - - // Dynamic size array - if (tp.length === 0) { - arrayLength = fi.value.length - size += tp.typeSize * arrayLength + DYN_TYPE_SIZE // for dynamic array length descriptor - } else { - // static size array - arrayLength = tp.length - size += tp.typeSize * arrayLength - } - - if (tp.typeIndex === typeIndex.String) { - fi._bytes = [] - for (let i = 0; i < arrayLength; i++) { - let b = encodeUTF8(fi.value[i] || '') - fi._bytes.push(b) - size += b.length - } - } - } - } else if (tp.typeIndex === typeIndex.String) { - fi._bytes = encodeUTF8(fi.value || '') - size += tp.typeSize + fi._bytes.length - } else { - if (tp.isComplex) { - size += Buffer.calcSize(fi.value, includeMessages) - } else { - size += tp.typeSize - } - } - } - - return size - } - - static createValueArray(schemaFields, obj, includeMessages) { - const len = schemaFields.length - - let arr = new Array(len) - - for (let k = 0; k < len; k++) { - let sk = schemaFields[k], - type = sk.type - - if (includeMessages && includeMessages[type]) { - arr[k] = { - value: Buffer.createValueArray(includeMessages[type].fields, obj[sk.name], includeMessages), - type: type - } - } else { - arr[k] = { value: obj[sk.name], type: type } - } - } - - return arr - } - - static serializeMessage(struct, obj, headerStruct = HEADER_STRUCT, includeMessages) { - let arr = Buffer.createValueArray(struct.fields, obj, includeMessages) - - let messageSize = Buffer.calcSize(arr, includeMessages) - - let headerBuf = Buffer.serializeObj( - headerStruct.fields, - { - seq: obj.seq, - size: messageSize, - cls: obj.cls, - msg_id: struct.id - }, - includeMessages - ) - - return Buffer.appendBuffer(headerBuf, Buffer.serialize(arr, includeMessages)) - } - - static serializeObj(schemaFields, obj, includeMessages) { - let arr = Buffer.createValueArray(schemaFields, obj, includeMessages) - - return Buffer.serialize(arr, includeMessages) - } - - static writeDataView(fields, dataView, includeMessages, offset = 0) { - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - p = fi._prop - - if (p.isArray) { - let arrayLength = p.length - - // Setting array size value for dynamic array size - if (arrayLength === 0) { - arrayLength = fi.value.length - offset += DYN_WRITE(dataView, offset, fi.value.length) - } - - // Write array - for (let j = 0; j < arrayLength; j++) { - let val = (fi._bytes && fi._bytes[j]) || fi.value[j] - if (p.isComplex) { - let valArr = Buffer.createValueArray(p.typeIndex.fields, fi.value[j], includeMessages) - let size = Buffer.calcSize(valArr, includeMessages) - Buffer.writeDataView(valArr, dataView, includeMessages, offset) - offset += size - } else { - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } else { - if (p.isComplex) { - let size = Buffer.calcSize(fi.value, includeMessages) - Buffer.writeDataView(fi.value, dataView, includeMessages, offset) - offset += size - } else { - let val = fi._bytes || fi.value - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } - - return offset - } - - static serialize(fields, includeMessages) { - let allSize = Buffer.calcSize(fields, includeMessages) - - let arrayBuffer = new ArrayBuffer(allSize), - dv = new DataView(arrayBuffer) - - Buffer.writeDataView(fields, dv, includeMessages) - - return arrayBuffer - } - - get size() { - return this._dataView.buffer.byteLength - } - - get dataView() { - return this._dataView - } - - set(arrayBuffer) { - this._dataView = null - this._dataView = new DataView(arrayBuffer) - } - - deserialize(struct, offset = 0, sizeOffset = 0) { - this._dynamicOffset = 0 - let res = this.__deserialize__(struct, offset, sizeOffset) - res.__SIZE__ = struct.size + this._dynamicOffset + sizeOffset - return res - } - - __deserialize__(struct, offset, sizeOffset) { - this._includeMessages = struct._includeMessages - - let fields = struct.fields, - dv = this._dataView, - resEntries = new Array(struct.fields.length ) - - - let currOffset = 0 - - for (let f = 0, len = fields.length; f < len; f++) { - let fi = fields[f], - p = fi._prop - - currOffset = offset + fi._offset - const ArrayType = this._useTypedArray ? typedArray[p.typeIndex] ?? Array : Array - - if (p.isArray) { - if (p.length === 0) { - // - // Dynamic size array - // - - let length = DYN_READ(dv, currOffset + this._dynamicOffset) - - - let value = new ArrayType(length) - resEntries[f]=([fi.name, value]) - - let currOffset_dyn = DYN_TYPE_SIZE + currOffset - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < length; j++) { - value[j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32( - currOffset_dyn + this._dynamicOffset + j * p.typeSize, - true - ) - } - } else { - if (p.isComplex) { - for (let j = 0; j < length; j++) { - value[j] = this.__deserialize__( - p.typeIndex, - currOffset_dyn + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < length; j++) { - value[j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - } - } - } - - this._dynamicOffset += length * p.typeSize - } else { - // - //Static size array - // - let value = new ArrayType(p.length) - resEntries[f]=([fi.name, value]) - - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < p.length; j++) { - value[j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset + j * p.typeSize, true) - } - } else { - if (p.isComplex) { - for (let j = 0; j < p.length; j++) { - value[j] = this.__deserialize__( - p.typeIndex, - currOffset + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < p.length; j++) { - value[j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - } - } - } - } - } else { - if (p.isComplex) { - resEntries[f]=([fi.name, this.__deserialize__(p.typeIndex, currOffset, sizeOffset)]) - } else { - resEntries[f]=([fi.name, readFunc[p.typeIndex](dv, currOffset + this._dynamicOffset)]) - - if (p.typeIndex === typeIndex.String) { - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset, true) - } - } - } - } - - return Object.fromEntries(resEntries); - } -} - -/** - * Creates message struct namespace - * @param {*} messagesJson - Array of messages schemas - * @param {*} headerSchema - message header schema - */ -export function initializeMessages(messagesJson, headerSchema) { - let res = { - __id__: [], - __name__: [], - HEADER_STRUCT: headerSchema ? new Struct(headerSchema) : HEADER_STRUCT - } - - for (let m of getKeysWithSortById(messagesJson)) { - let name = m.trim(), - messageObj = messagesJson[m], - msg = 'MSG_' + name.toUpperCase(), - id = messageObj.id - - if (!res.__id__[id]) { - let msg_struct = new Struct(messageObj, res) - - res.__id__[id] = msg_struct - res.__name__[id] = m.trim() - res[msg] = msg_struct - res[name] = msg_struct - } else { - console.warn(`Warning: message ${id} ${msg} already exists.`) - } - } - - return res -} - -const getKeysWithSortById = (obj) => { - let keys = Object.keys(obj) - keys.sort((a, b) => { - return obj[a].id - obj[b].id - }) - return keys -} diff --git a/port/js/benchmarks/deserialize-variant/messgen-old.js b/port/js/benchmarks/deserialize-variant/messgen-old.js index b9440585..0944547c 100644 --- a/port/js/benchmarks/deserialize-variant/messgen-old.js +++ b/port/js/benchmarks/deserialize-variant/messgen-old.js @@ -12,155 +12,155 @@ const DYNAMIC_SIZE_TYPE = 'Uint32' * Write function returns type byte size. */ const basicTypes = [ - { - name: 'Char', - size: 1, - read: (v, s) => String.fromCharCode(v.getInt8(s, IS_LITTLE_ENDIAN)), - write: (v, s, a) => { - v.setInt8(s, a ? a.toString().charCodeAt(0) : 0, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Array + { + name: 'Char', + size: 1, + read: (v, s) => String.fromCharCode(v.getInt8(s, IS_LITTLE_ENDIAN)), + write: (v, s, a) => { + v.setInt8(s, a ? a.toString().charCodeAt(0) : 0, IS_LITTLE_ENDIAN) + return 1 }, - { - name: 'Int8', - size: 1, - read: (v, s) => v.getInt8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Int8Array + typedArray: Array + }, + { + name: 'Int8', + size: 1, + read: (v, s) => v.getInt8(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setInt8(s, a, IS_LITTLE_ENDIAN) + return 1 }, - { - name: 'Uint8', - size: 1, - read: (v, s) => v.getUint8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Uint8Array + typedArray: Int8Array + }, + { + name: 'Uint8', + size: 1, + read: (v, s) => v.getUint8(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setUint8(s, a, IS_LITTLE_ENDIAN) + return 1 }, - { - name: 'Int16', - size: 2, - read: (v, s) => v.getInt16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Int16Array + typedArray: Uint8Array + }, + { + name: 'Int16', + size: 2, + read: (v, s) => v.getInt16(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setInt16(s, a, IS_LITTLE_ENDIAN) + return 2 }, - { - name: 'Uint16', - size: 2, - read: (v, s) => v.getUint16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Uint16Array + typedArray: Int16Array + }, + { + name: 'Uint16', + size: 2, + read: (v, s) => v.getUint16(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setUint16(s, a, IS_LITTLE_ENDIAN) + return 2 }, - { - name: 'Int32', - size: 4, - read: (v, s) => v.getInt32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Int32Array + typedArray: Uint16Array + }, + { + name: 'Int32', + size: 4, + read: (v, s) => v.getInt32(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setInt32(s, a, IS_LITTLE_ENDIAN) + return 4 }, - { - name: 'Uint32', - size: 4, - read: (v, s) => v.getUint32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Uint32Array + typedArray: Int32Array + }, + { + name: 'Uint32', + size: 4, + read: (v, s) => v.getUint32(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setUint32(s, a, IS_LITTLE_ENDIAN) + return 4 }, - { - name: 'Int64', - size: 8, - read: (v, s) => v.getBigInt64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigInt64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigInt64Array + typedArray: Uint32Array + }, + { + name: 'Int64', + size: 8, + read: (v, s) => v.getBigInt64(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setBigInt64(s, a, IS_LITTLE_ENDIAN) + return 8 }, - { - name: 'Uint64', - size: 8, - read: (v, s) => v.getBigUint64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigUint64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigUint64Array + typedArray: BigInt64Array + }, + { + name: 'Uint64', + size: 8, + read: (v, s) => v.getBigUint64(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setBigUint64(s, a, IS_LITTLE_ENDIAN) + return 8 }, - { - name: 'Float', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - } + typedArray: BigUint64Array + }, + { + name: 'Float', + size: 4, + read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setFloat32(s, a, IS_LITTLE_ENDIAN) + return 4 + } + }, + { + name: 'Float32', + size: 4, + read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setFloat32(s, a, IS_LITTLE_ENDIAN) + return 4 }, - { - name: 'Float32', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Float32Array + typedArray: Float32Array + }, + { + name: 'Double', + size: 8, + read: (v, s) => v.getFloat64(s, IS_LITTLE_ENDIAN), + write: (v, s, a) => { + v.setFloat64(s, a, IS_LITTLE_ENDIAN) + return 8 }, - { - name: 'Double', - size: 8, - read: (v, s) => v.getFloat64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: Float64Array + typedArray: Float64Array + }, + { + name: 'String', + size: 4, + read: (v, s) => decodeUTF8(new Uint8Array(v.buffer, s + 4, v.getUint32(s, IS_LITTLE_ENDIAN))), + write: (v, s, a) => { + let size = a.length + v.setUint32(s, size, true) + for (let i = 0, s2 = s + 4; i < size; i++) { + v.setUint8(s2 + i, a[i], true) + } + return size + 4 }, - { - name: 'String', - size: 4, - read: (v, s) => decodeUTF8(new Uint8Array(v.buffer, s + 4, v.getUint32(s, IS_LITTLE_ENDIAN))), - write: (v, s, a) => { - let size = a.length - v.setUint32(s, size, true) - for (let i = 0, s2 = s + 4; i < size; i++) { - v.setUint8(s2 + i, a[i], true) - } - return size + 4 - }, - typedArray: Array - } + typedArray: Array + } ] let typeIndex = [], - typeSize = [] + typeSize = [] let readFunc = [], - writeFunc = [], - typedArray = [] + writeFunc = [], + typedArray = [] for (let i = 0; i < basicTypes.length; i++) { - let ti = basicTypes[i] - typeIndex[ti.name] = i - typeSize[i] = ti.size - readFunc[i] = ti.read - writeFunc[i] = ti.write - typedArray[i] = ti.typedArray + let ti = basicTypes[i] + typeIndex[ti.name] = i + typeSize[i] = ti.size + readFunc[i] = ti.read + writeFunc[i] = ti.write + typedArray[i] = ti.typedArray } const DYN_TYPE = typeIndex[DYNAMIC_SIZE_TYPE] @@ -169,105 +169,105 @@ const DYN_READ = readFunc[DYN_TYPE] const DYN_WRITE = writeFunc[DYN_TYPE] function parseType(typeStr, includeMessages) { - let a = typeStr.split('['), - name = a[0].trim() - - let size, - isComplex = false - - let type = typeIndex[name] - - if (type !== undefined) { - size = typeSize[type] - } else if (includeMessages && includeMessages[name]) { - type = includeMessages[name] - size = type.size - isComplex = true - } else { - throw new Error(`Unknown type: ${name}, if is complex type you must define before the struct. `) - } - - let length = parseInt(a[1]) - - return { - typeIndex: type, - typeSize: size, - length: isNaN(length) ? 0 : length, - isArray: a.length === 2, - isComplex: isComplex - } + let a = typeStr.split('['), + name = a[0].trim() + + let size, + isComplex = false + + let type = typeIndex[name] + + if (type !== undefined) { + size = typeSize[type] + } else if (includeMessages && includeMessages[name]) { + type = includeMessages[name] + size = type.size + isComplex = true + } else { + throw new Error(`Unknown type: ${name}, if is complex type you must define before the struct. `) + } + + let length = parseInt(a[1]) + + return { + typeIndex: type, + typeSize: size, + length: isNaN(length) ? 0 : length, + isArray: a.length === 2, + isComplex: isComplex + } } /** * class Struct */ export class Struct { - constructor(schema, includeMessages) { - this._id = 0 - this._size = 0 - this._fields = null - this._schema = null - - this._includeMessages = includeMessages - - this.set(schema) + constructor(schema, includeMessages) { + this._id = 0 + this._size = 0 + this._fields = null + this._schema = null + + this._includeMessages = includeMessages + + this.set(schema) + } + + get schema() { + return this._schema + } + + get id() { + return this._id + } + + get size() { + return this._size + } + + get fields() { + return this._fields + } + + set(schema) { + if (schema) { + this._id = schema.id || 0 + this._size = 0 + this._fields = new Array(schema.fields.length) + this._schema = schema + this._init() } + } - get schema() { - return this._schema - } + _init() { + let schemaFields = this._schema.fields - get id() { - return this._id - } + let offset = 0 - get size() { - return this._size - } + for (let i = 0, len = schemaFields.length; i < len; i++) { + let si = schemaFields[i], + tp = parseType(si.type, this._includeMessages) - get fields() { - return this._fields - } + this._fields[i] = { + name: si.name, + type: si.type, + _offset: offset, + _prop: tp + } - set(schema) { - if (schema) { - this._id = schema.id || 0 - this._size = 0 - this._fields = new Array(schema.fields.length) - this._schema = schema - this._init() + if (tp.isArray) { + if (tp.length === 0) { + offset += DYN_TYPE_SIZE + } else { + offset += tp.typeSize * tp.length } + } else { + offset += tp.typeSize + } } - _init() { - let schemaFields = this._schema.fields - - let offset = 0 - - for (let i = 0, len = schemaFields.length; i < len; i++) { - let si = schemaFields[i], - tp = parseType(si.type, this._includeMessages) - - this._fields[i] = { - name: si.name, - type: si.type, - _offset: offset, - _prop: tp - } - - if (tp.isArray) { - if (tp.length === 0) { - offset += DYN_TYPE_SIZE - } else { - offset += tp.typeSize * tp.length - } - } else { - offset += tp.typeSize - } - } - - this._size = offset - } + this._size = offset + } } // uint32 seq; //!< Sequence number @@ -276,339 +276,339 @@ export class Struct { // uint8 msg_id; //!< Message type ID export const HEADER_STRUCT = new Struct({ - fields: [ - { name: 'seq', type: 'Uint32' }, - { name: 'cls', type: 'Uint8' }, - { name: 'msg_id', type: 'Uint8' }, - { name: 'size', type: 'Uint32' } - ] + fields: [ + { name: 'seq', type: 'Uint32' }, + { name: 'cls', type: 'Uint8' }, + { name: 'msg_id', type: 'Uint8' }, + { name: 'size', type: 'Uint32' } + ] }) /** * class Buffer */ export class Buffer { - _useTypedArray = false - - constructor(arrayBuffer, useTypedArray = false) { - this._dataView = new DataView(arrayBuffer) - this._dynamicOffset = 0 - this._useTypedArray = useTypedArray + _useTypedArray = false + + constructor(arrayBuffer, useTypedArray = false) { + this._dataView = new DataView(arrayBuffer) + this._dynamicOffset = 0 + this._useTypedArray = useTypedArray + } + + // TODO: перенести в модуль messages + // а модуль messages генерализировать + static deserialize(messages, data, headerStruct = HEADER_STRUCT, includeMessages) { + let res = [] + let buf = new Buffer(data) + let cur = 0 + while (cur < buf.size) { + let h = buf.deserialize(headerStruct, cur), + m = buf.deserialize(messages.__id__[h.msg_id], cur + h.__SIZE__) + m.__MSG_ID__ = h.msg_id + cur += h.__SIZE__ + m.__SIZE__ + res.push(m) } - - // TODO: перенести в модуль messages - // а модуль messages генерализировать - static deserialize(messages, data, headerStruct = HEADER_STRUCT, includeMessages) { - let res = [] - let buf = new Buffer(data) - let cur = 0 - while (cur < buf.size) { - let h = buf.deserialize(headerStruct, cur), - m = buf.deserialize(messages.__id__[h.msg_id], cur + h.__SIZE__) - m.__MSG_ID__ = h.msg_id - cur += h.__SIZE__ + m.__SIZE__ - res.push(m) + return res + } + + static mergeArrayBuffers(tArrs, type = Uint8Array) { + const ret = new type(tArrs.reduce((acc, tArr) => acc + tArr.byteLength, 0)) + let off = 0 + tArrs.forEach((tArr) => { + ret.set(new type(tArr), off) + off += tArr.byteLength + }) + return ret + } + + static appendBuffer(buffer1, buffer2) { + var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) + tmp.set(new Uint8Array(buffer1), 0) + tmp.set(new Uint8Array(buffer2), buffer1.byteLength) + return tmp.buffer + } + + static calcSize(fields, includeMessages) { + let size = 0 + + for (let i = 0, len = fields.length; i < len; i++) { + let fi = fields[i], + tp = (fi._prop = parseType(fi.type, includeMessages)) + + if (tp.isArray) { + if (tp.isComplex) { + if (tp.length === 0) { + size += DYN_TYPE_SIZE + } + for (let i = 0; i < fi.value.length; i++) { + let arr = Buffer.createValueArray(fi._prop.typeIndex.fields, fi.value[i], includeMessages) + size += Buffer.calcSize(arr, includeMessages) + } + } else { + let arrayLength = 0 + + // Dynamic size array + if (tp.length === 0) { + arrayLength = fi.value.length + size += tp.typeSize * arrayLength + DYN_TYPE_SIZE // for dynamic array length descriptor + } else { + // static size array + arrayLength = tp.length + size += tp.typeSize * arrayLength + } + + if (tp.typeIndex === typeIndex.String) { + fi._bytes = [] + for (let i = 0; i < arrayLength; i++) { + let b = encodeUTF8(fi.value[i] || '') + fi._bytes.push(b) + size += b.length + } + } + } + } else if (tp.typeIndex === typeIndex.String) { + fi._bytes = encodeUTF8(fi.value || '') + size += tp.typeSize + fi._bytes.length + } else { + if (tp.isComplex) { + size += Buffer.calcSize(fi.value, includeMessages) + } else { + size += tp.typeSize } - return res + } } - static mergeArrayBuffers(tArrs, type = Uint8Array) { - const ret = new type(tArrs.reduce((acc, tArr) => acc + tArr.byteLength, 0)) - let off = 0 - tArrs.forEach((tArr) => { - ret.set(new type(tArr), off) - off += tArr.byteLength - }) - return ret - } + return size + } - static appendBuffer(buffer1, buffer2) { - var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) - tmp.set(new Uint8Array(buffer1), 0) - tmp.set(new Uint8Array(buffer2), buffer1.byteLength) - return tmp.buffer - } + static createValueArray(schemaFields, obj, includeMessages) { + const len = schemaFields.length - static calcSize(fields, includeMessages) { - let size = 0 - - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - tp = (fi._prop = parseType(fi.type, includeMessages)) - - if (tp.isArray) { - if (tp.isComplex) { - if (tp.length === 0) { - size += DYN_TYPE_SIZE - } - for (let i = 0; i < fi.value.length; i++) { - let arr = Buffer.createValueArray(fi._prop.typeIndex.fields, fi.value[i], includeMessages) - size += Buffer.calcSize(arr, includeMessages) - } - } else { - let arrayLength = 0 - - // Dynamic size array - if (tp.length === 0) { - arrayLength = fi.value.length - size += tp.typeSize * arrayLength + DYN_TYPE_SIZE // for dynamic array length descriptor - } else { - // static size array - arrayLength = tp.length - size += tp.typeSize * arrayLength - } - - if (tp.typeIndex === typeIndex.String) { - fi._bytes = [] - for (let i = 0; i < arrayLength; i++) { - let b = encodeUTF8(fi.value[i] || '') - fi._bytes.push(b) - size += b.length - } - } - } - } else if (tp.typeIndex === typeIndex.String) { - fi._bytes = encodeUTF8(fi.value || '') - size += tp.typeSize + fi._bytes.length - } else { - if (tp.isComplex) { - size += Buffer.calcSize(fi.value, includeMessages) - } else { - size += tp.typeSize - } - } - } + let arr = new Array(len) - return size + for (let k = 0; k < len; k++) { + let sk = schemaFields[k], + type = sk.type + + if (includeMessages && includeMessages[type]) { + arr[k] = { + value: Buffer.createValueArray(includeMessages[type].fields, obj[sk.name], includeMessages), + type: type + } + } else { + arr[k] = { value: obj[sk.name], type: type } + } } - static createValueArray(schemaFields, obj, includeMessages) { - const len = schemaFields.length + return arr + } - let arr = new Array(len) + static serializeMessage(struct, obj, headerStruct = HEADER_STRUCT, includeMessages) { + let arr = Buffer.createValueArray(struct.fields, obj, includeMessages) - for (let k = 0; k < len; k++) { - let sk = schemaFields[k], - type = sk.type + let messageSize = Buffer.calcSize(arr, includeMessages) - if (includeMessages && includeMessages[type]) { - arr[k] = { - value: Buffer.createValueArray(includeMessages[type].fields, obj[sk.name], includeMessages), - type: type - } - } else { - arr[k] = { value: obj[sk.name], type: type } - } + let headerBuf = Buffer.serializeObj( + headerStruct.fields, + { + seq: obj.seq, + size: messageSize, + cls: obj.cls, + msg_id: struct.id + }, + includeMessages + ) + + return Buffer.appendBuffer(headerBuf, Buffer.serialize(arr, includeMessages)) + } + + static serializeObj(schemaFields, obj, includeMessages) { + let arr = Buffer.createValueArray(schemaFields, obj, includeMessages) + + return Buffer.serialize(arr, includeMessages) + } + + static writeDataView(fields, dataView, includeMessages, offset = 0) { + for (let i = 0, len = fields.length; i < len; i++) { + let fi = fields[i], + p = fi._prop + + if (p.isArray) { + let arrayLength = p.length + + // Setting array size value for dynamic array size + if (arrayLength === 0) { + arrayLength = fi.value.length + offset += DYN_WRITE(dataView, offset, fi.value.length) } - return arr + // Write array + for (let j = 0; j < arrayLength; j++) { + let val = (fi._bytes && fi._bytes[j]) || fi.value[j] + if (p.isComplex) { + let valArr = Buffer.createValueArray(p.typeIndex.fields, fi.value[j], includeMessages) + let size = Buffer.calcSize(valArr, includeMessages) + Buffer.writeDataView(valArr, dataView, includeMessages, offset) + offset += size + } else { + offset += writeFunc[p.typeIndex](dataView, offset, val) + } + } + } else { + if (p.isComplex) { + let size = Buffer.calcSize(fi.value, includeMessages) + Buffer.writeDataView(fi.value, dataView, includeMessages, offset) + offset += size + } else { + let val = fi._bytes || fi.value + offset += writeFunc[p.typeIndex](dataView, offset, val) + } + } } - static serializeMessage(struct, obj, headerStruct = HEADER_STRUCT, includeMessages) { - let arr = Buffer.createValueArray(struct.fields, obj, includeMessages) + return offset + } - let messageSize = Buffer.calcSize(arr, includeMessages) + static serialize(fields, includeMessages) { + let allSize = Buffer.calcSize(fields, includeMessages) - let headerBuf = Buffer.serializeObj( - headerStruct.fields, - { - seq: obj.seq, - size: messageSize, - cls: obj.cls, - msg_id: struct.id - }, - includeMessages - ) + let arrayBuffer = new ArrayBuffer(allSize), + dv = new DataView(arrayBuffer) - return Buffer.appendBuffer(headerBuf, Buffer.serialize(arr, includeMessages)) - } + Buffer.writeDataView(fields, dv, includeMessages) - static serializeObj(schemaFields, obj, includeMessages) { - let arr = Buffer.createValueArray(schemaFields, obj, includeMessages) + return arrayBuffer + } - return Buffer.serialize(arr, includeMessages) - } + get size() { + return this._dataView.buffer.byteLength + } - static writeDataView(fields, dataView, includeMessages, offset = 0) { - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - p = fi._prop - - if (p.isArray) { - let arrayLength = p.length - - // Setting array size value for dynamic array size - if (arrayLength === 0) { - arrayLength = fi.value.length - offset += DYN_WRITE(dataView, offset, fi.value.length) - } - - // Write array - for (let j = 0; j < arrayLength; j++) { - let val = (fi._bytes && fi._bytes[j]) || fi.value[j] - if (p.isComplex) { - let valArr = Buffer.createValueArray(p.typeIndex.fields, fi.value[j], includeMessages) - let size = Buffer.calcSize(valArr, includeMessages) - Buffer.writeDataView(valArr, dataView, includeMessages, offset) - offset += size - } else { - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } else { - if (p.isComplex) { - let size = Buffer.calcSize(fi.value, includeMessages) - Buffer.writeDataView(fi.value, dataView, includeMessages, offset) - offset += size - } else { - let val = fi._bytes || fi.value - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } + get dataView() { + return this._dataView + } - return offset - } + set(arrayBuffer) { + this._dataView = null + this._dataView = new DataView(arrayBuffer) + } - static serialize(fields, includeMessages) { - let allSize = Buffer.calcSize(fields, includeMessages) + deserialize(struct, offset = 0, sizeOffset = 0) { + this._dynamicOffset = 0 + let res = this.__deserialize__(struct, offset, sizeOffset) + res.__SIZE__ = struct.size + this._dynamicOffset + sizeOffset + return res + } - let arrayBuffer = new ArrayBuffer(allSize), - dv = new DataView(arrayBuffer) + __deserialize__(struct, offset, sizeOffset) { + this._includeMessages = struct._includeMessages - Buffer.writeDataView(fields, dv, includeMessages) + let fields = struct.fields, + dv = this._dataView, + res = {} - return arrayBuffer - } + let currOffset = 0 - get size() { - return this._dataView.buffer.byteLength - } + for (let f = 0, len = fields.length; f < len; f++) { + let fi = fields[f], + p = fi._prop - get dataView() { - return this._dataView - } + currOffset = offset + fi._offset + const ArrayType = this._useTypedArray ? typedArray[p.typeIndex] ?? Array : Array - set(arrayBuffer) { - this._dataView = null - this._dataView = new DataView(arrayBuffer) - } + if (p.isArray) { + if (p.length === 0) { + // + // Dynamic size array + // - deserialize(struct, offset = 0, sizeOffset = 0) { - this._dynamicOffset = 0 - let res = this.__deserialize__(struct, offset, sizeOffset) - res.__SIZE__ = struct.size + this._dynamicOffset + sizeOffset - return res - } + let length = DYN_READ(dv, currOffset + this._dynamicOffset) + + res[fi.name] = new ArrayType(length) + + let currOffset_dyn = DYN_TYPE_SIZE + currOffset - __deserialize__(struct, offset, sizeOffset) { - this._includeMessages = struct._includeMessages - - let fields = struct.fields, - dv = this._dataView, - res = {} - - let currOffset = 0 - - for (let f = 0, len = fields.length; f < len; f++) { - let fi = fields[f], - p = fi._prop - - currOffset = offset + fi._offset - const ArrayType = this._useTypedArray ? typedArray[p.typeIndex] ?? Array : Array - - if (p.isArray) { - if (p.length === 0) { - // - // Dynamic size array - // - - let length = DYN_READ(dv, currOffset + this._dynamicOffset) - - res[fi.name] = new ArrayType(length) - - let currOffset_dyn = DYN_TYPE_SIZE + currOffset - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32( - currOffset_dyn + this._dynamicOffset + j * p.typeSize, - true - ) - } - } else { - if (p.isComplex) { - for (let j = 0; j < length; j++) { - res[fi.name][j] = this.__deserialize__( - p.typeIndex, - currOffset_dyn + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - } - } - } - - this._dynamicOffset += length * p.typeSize - } else { - // - //Static size array - // - - res[fi.name] = new ArrayType(p.length) - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset + j * p.typeSize, true) - } - } else { - if (p.isComplex) { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = this.__deserialize__( - p.typeIndex, - currOffset + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - } - } - } - } + if (p.typeIndex === typeIndex.String) { + for (let j = 0; j < length; j++) { + res[fi.name][j] = readFunc[p.typeIndex]( + dv, + currOffset_dyn + this._dynamicOffset + j * p.typeSize + ) + this._dynamicOffset += dv.getUint32( + currOffset_dyn + this._dynamicOffset + j * p.typeSize, + true + ) + } + } else { + if (p.isComplex) { + for (let j = 0; j < length; j++) { + res[fi.name][j] = this.__deserialize__( + p.typeIndex, + currOffset_dyn + j * p.typeSize, + sizeOffset + ) + } } else { - if (p.isComplex) { - res[fi.name] = this.__deserialize__(p.typeIndex, currOffset, sizeOffset) - } else { - res[fi.name] = readFunc[p.typeIndex](dv, currOffset + this._dynamicOffset) - - if (p.typeIndex === typeIndex.String) { - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset, true) - } - } + for (let j = 0; j < length; j++) { + res[fi.name][j] = readFunc[p.typeIndex]( + dv, + currOffset_dyn + this._dynamicOffset + j * p.typeSize + ) + } } + } + + this._dynamicOffset += length * p.typeSize + } else { + // + //Static size array + // + + res[fi.name] = new ArrayType(p.length) + + if (p.typeIndex === typeIndex.String) { + for (let j = 0; j < p.length; j++) { + res[fi.name][j] = readFunc[p.typeIndex]( + dv, + currOffset + this._dynamicOffset + j * p.typeSize + ) + this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset + j * p.typeSize, true) + } + } else { + if (p.isComplex) { + for (let j = 0; j < p.length; j++) { + res[fi.name][j] = this.__deserialize__( + p.typeIndex, + currOffset + j * p.typeSize, + sizeOffset + ) + } + } else { + for (let j = 0; j < p.length; j++) { + res[fi.name][j] = readFunc[p.typeIndex]( + dv, + currOffset + this._dynamicOffset + j * p.typeSize + ) + } + } + } } + } else { + if (p.isComplex) { + res[fi.name] = this.__deserialize__(p.typeIndex, currOffset, sizeOffset) + } else { + res[fi.name] = readFunc[p.typeIndex](dv, currOffset + this._dynamicOffset) - return res + if (p.typeIndex === typeIndex.String) { + this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset, true) + } + } + } } + + return res + } } /** @@ -617,37 +617,37 @@ export class Buffer { * @param {*} headerSchema - message header schema */ export function initializeMessages(messagesJson, headerSchema) { - let res = { - __id__: [], - __name__: [], - HEADER_STRUCT: headerSchema ? new Struct(headerSchema) : HEADER_STRUCT - } - - for (let m of getKeysWithSortById(messagesJson)) { - let name = m.trim(), - messageObj = messagesJson[m], - msg = 'MSG_' + name.toUpperCase(), - id = messageObj.id - - if (!res.__id__[id]) { - let msg_struct = new Struct(messageObj, res) - - res.__id__[id] = msg_struct - res.__name__[id] = m.trim() - res[msg] = msg_struct - res[name] = msg_struct - } else { - console.warn(`Warning: message ${id} ${msg} already exists.`) - } + let res = { + __id__: [], + __name__: [], + HEADER_STRUCT: headerSchema ? new Struct(headerSchema) : HEADER_STRUCT + } + + for (let m of getKeysWithSortById(messagesJson)) { + let name = m.trim(), + messageObj = messagesJson[m], + msg = 'MSG_' + name.toUpperCase(), + id = messageObj.id + + if (!res.__id__[id]) { + let msg_struct = new Struct(messageObj, res) + + res.__id__[id] = msg_struct + res.__name__[id] = m.trim() + res[msg] = msg_struct + res[name] = msg_struct + } else { + console.warn(`Warning: message ${id} ${msg} already exists.`) } + } - return res + return res } const getKeysWithSortById = (obj) => { - let keys = Object.keys(obj) - keys.sort((a, b) => { - return obj[a].id - obj[b].id - }) - return keys + let keys = Object.keys(obj) + keys.sort((a, b) => { + return obj[a].id - obj[b].id + }) + return keys } diff --git a/port/js/benchmarks/deserialize-variant/messgen-pre-build.js b/port/js/benchmarks/deserialize-variant/messgen-pre-build.js deleted file mode 100644 index e28a3c4e..00000000 --- a/port/js/benchmarks/deserialize-variant/messgen-pre-build.js +++ /dev/null @@ -1,676 +0,0 @@ -'use strict' - -import { encodeUTF8, decodeUTF8 } from './utf8.js' - -const IS_LITTLE_ENDIAN = true - -const DYNAMIC_SIZE_TYPE = 'Uint32' - -/** - * - * Read function returns value from byte array. - * Write function returns type byte size. - */ -const basicTypes = [ - { - name: 'Char', - size: 1, - read: (v, s) => String.fromCharCode(v.getInt8(s, IS_LITTLE_ENDIAN)), - write: (v, s, a) => { - v.setInt8(s, a ? a.toString().charCodeAt(0) : 0, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Array, - default: '' - }, - { - name: 'Int8', - size: 1, - read: (v, s) => v.getInt8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Int8Array, - default: new Int8Array([0])[0] - }, - { - name: 'Uint8', - size: 1, - read: (v, s) => v.getUint8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Uint8Array, - default: new Uint8Array([0])[0] - }, - { - name: 'Int16', - size: 2, - read: (v, s) => v.getInt16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Int16Array, - default: new Int16Array([0])[0] - }, - { - name: 'Uint16', - size: 2, - read: (v, s) => v.getUint16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Uint16Array, - default: new Uint16Array([0])[0] - }, - { - name: 'Int32', - size: 4, - read: (v, s) => v.getInt32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Int32Array, - default: new Int32Array([0])[0] - }, - { - name: 'Uint32', - size: 4, - read: (v, s) => v.getUint32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Uint32Array, - default: new Uint32Array([0])[0] - }, - { - name: 'Int64', - size: 8, - read: (v, s) => v.getBigInt64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigInt64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigInt64Array, - default: 1n - }, - { - name: 'Uint64', - size: 8, - read: (v, s) => v.getBigUint64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigUint64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigUint64Array, - default: 1n - }, - { - name: 'Float', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - } - }, - { - name: 'Float32', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Float32Array, - default: new Float32Array([0])[0] - }, - { - name: 'Double', - size: 8, - read: (v, s) => v.getFloat64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: Float64Array, - default: new Float64Array([0])[0] - }, - { - name: 'String', - size: 4, - read: (v, s) => decodeUTF8(new Uint8Array(v.buffer, s + 4, v.getUint32(s, IS_LITTLE_ENDIAN))), - write: (v, s, a) => { - let size = a.length - v.setUint32(s, size, true) - for (let i = 0, s2 = s + 4; i < size; i++) { - v.setUint8(s2 + i, a[i], true) - } - return size + 4 - }, - typedArray: Array, - default: '' - } -] - -let typeIndex = [], - typeSize = [] - -let readFunc = [], - writeFunc = [], - typedArray = [] - -for (let i = 0; i < basicTypes.length; i++) { - let ti = basicTypes[i] - typeIndex[ti.name] = i - typeSize[i] = ti.size - readFunc[i] = ti.read - writeFunc[i] = ti.write - typedArray[i] = ti.typedArray -} - -const DYN_TYPE = typeIndex[DYNAMIC_SIZE_TYPE] -const DYN_TYPE_SIZE = typeSize[DYN_TYPE] -const DYN_READ = readFunc[DYN_TYPE] -const DYN_WRITE = writeFunc[DYN_TYPE] - -function parseType(typeStr, includeMessages) { - let a = typeStr.split('['), - name = a[0].trim() - - let size, - isComplex = false - - let type = typeIndex[name] - - if (type !== undefined) { - size = typeSize[type] - } else if (includeMessages && includeMessages[name]) { - type = includeMessages[name] - size = type.size - isComplex = true - } else { - throw new Error(`Unknown type: ${ name }, if is complex type you must define before the struct. `) - } - - let length = parseInt(a[1]) - - return { - typeIndex: type, - typeSize: size, - length: isNaN(length) ? 0 : length, - isArray: a.length === 2, - isComplex: isComplex - } -} - -/** - * class Struct - */ -export class Struct { - constructor(schema, includeMessages) { - this._id = 0 - this._size = 0 - this._fields = null - this._schema = null - this._default = null - - this._includeMessages = includeMessages - - this.set(schema) - } - - get schema() { - return this._schema - } - - get id() { - return this._id - } - - get size() { - return this._size - } - - get fields() { - return this._fields - } - - set(schema) { - if (schema) { - this._id = schema.id || 0 - this._size = 0 - this._fields = new Array(schema.fields.length) - this._schema = schema - this._init() - } - } - - _init() { - let schemaFields = this._schema.fields - - let offset = 0 - - for (let i = 0, len = schemaFields.length; i < len; i++) { - let si = schemaFields[i], - tp = parseType(si.type, this._includeMessages) - - this._fields[i] = { - name: si.name, - type: si.type, - _offset: offset, - _prop: tp - } - - if (tp.isArray) { - if (tp.length === 0) { - offset += DYN_TYPE_SIZE - } else { - offset += tp.typeSize * tp.length - } - } else { - offset += tp.typeSize - } - } - this._default = Object.fromEntries(this._fields.map((f) => { - let value - if (f._prop.isArray) { - let TypedArray = basicTypes[f._prop.typeIndex].typedArray - return [f.name, TypedArray(f._prop.length)] - } else if (f._prop.isComplex) { - return [f.name, null] - } else { - return [f.name, basicTypes[f._prop.typeIndex].default] - } - })) - this._size = offset - } -} - -// uint32 seq; //!< Sequence number -// uint16 size; //!< Message payload size -// uint8 cls; //!< Message class -// uint8 msg_id; //!< Message type ID - -export const HEADER_STRUCT = new Struct({ - fields: [ - { name: 'seq', type: 'Uint32' }, - { name: 'cls', type: 'Uint8' }, - { name: 'msg_id', type: 'Uint8' }, - { name: 'size', type: 'Uint32' } - ] -}) - -/** - * class Buffer - */ -export class Buffer { - _useTypedArray = false - - constructor(arrayBuffer, useTypedArray = false) { - this._dataView = new DataView(arrayBuffer) - this._dynamicOffset = 0 - this._useTypedArray = useTypedArray - } - - // TODO: перенести в модуль messages - // а модуль messages генерализировать - static deserialize(messages, data, headerStruct = HEADER_STRUCT, includeMessages) { - let res = [] - let buf = new Buffer(data) - let cur = 0 - while (cur < buf.size) { - let h = buf.deserialize(headerStruct, cur), - m = buf.deserialize(messages.__id__[h.msg_id], cur + h.__SIZE__) - m.__MSG_ID__ = h.msg_id - cur += h.__SIZE__ + m.__SIZE__ - res.push(m) - } - return res - } - - static mergeArrayBuffers(tArrs, type = Uint8Array) { - const ret = new type(tArrs.reduce((acc, tArr) => acc + tArr.byteLength, 0)) - let off = 0 - tArrs.forEach((tArr) => { - ret.set(new type(tArr), off) - off += tArr.byteLength - }) - return ret - } - - static appendBuffer(buffer1, buffer2) { - var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) - tmp.set(new Uint8Array(buffer1), 0) - tmp.set(new Uint8Array(buffer2), buffer1.byteLength) - return tmp.buffer - } - - static calcSize(fields, includeMessages) { - let size = 0 - - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - tp = (fi._prop = parseType(fi.type, includeMessages)) - - if (tp.isArray) { - if (tp.isComplex) { - if (tp.length === 0) { - size += DYN_TYPE_SIZE - } - for (let i = 0; i < fi.value.length; i++) { - let arr = Buffer.createValueArray(fi._prop.typeIndex.fields, fi.value[i], includeMessages) - size += Buffer.calcSize(arr, includeMessages) - } - } else { - let arrayLength = 0 - - // Dynamic size array - if (tp.length === 0) { - arrayLength = fi.value.length - size += tp.typeSize * arrayLength + DYN_TYPE_SIZE // for dynamic array length descriptor - } else { - // static size array - arrayLength = tp.length - size += tp.typeSize * arrayLength - } - - if (tp.typeIndex === typeIndex.String) { - fi._bytes = [] - for (let i = 0; i < arrayLength; i++) { - let b = encodeUTF8(fi.value[i] || '') - fi._bytes.push(b) - size += b.length - } - } - } - } else if (tp.typeIndex === typeIndex.String) { - fi._bytes = encodeUTF8(fi.value || '') - size += tp.typeSize + fi._bytes.length - } else { - if (tp.isComplex) { - size += Buffer.calcSize(fi.value, includeMessages) - } else { - size += tp.typeSize - } - } - } - - return size - } - - static createValueArray(schemaFields, obj, includeMessages) { - const len = schemaFields.length - - let arr = new Array(len) - - for (let k = 0; k < len; k++) { - let sk = schemaFields[k], - type = sk.type - - if (includeMessages && includeMessages[type]) { - arr[k] = { - value: Buffer.createValueArray(includeMessages[type].fields, obj[sk.name], includeMessages), - type: type - } - } else { - arr[k] = { value: obj[sk.name], type: type } - } - } - - return arr - } - - static serializeMessage(struct, obj, headerStruct = HEADER_STRUCT, includeMessages) { - let arr = Buffer.createValueArray(struct.fields, obj, includeMessages) - - let messageSize = Buffer.calcSize(arr, includeMessages) - - let headerBuf = Buffer.serializeObj( - headerStruct.fields, - { - seq: obj.seq, - size: messageSize, - cls: obj.cls, - msg_id: struct.id - }, - includeMessages - ) - - return Buffer.appendBuffer(headerBuf, Buffer.serialize(arr, includeMessages)) - } - - static serializeObj(schemaFields, obj, includeMessages) { - let arr = Buffer.createValueArray(schemaFields, obj, includeMessages) - - return Buffer.serialize(arr, includeMessages) - } - - static writeDataView(fields, dataView, includeMessages, offset = 0) { - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - p = fi._prop - - if (p.isArray) { - let arrayLength = p.length - - // Setting array size value for dynamic array size - if (arrayLength === 0) { - arrayLength = fi.value.length - offset += DYN_WRITE(dataView, offset, fi.value.length) - } - - // Write array - for (let j = 0; j < arrayLength; j++) { - let val = (fi._bytes && fi._bytes[j]) || fi.value[j] - if (p.isComplex) { - let valArr = Buffer.createValueArray(p.typeIndex.fields, fi.value[j], includeMessages) - let size = Buffer.calcSize(valArr, includeMessages) - Buffer.writeDataView(valArr, dataView, includeMessages, offset) - offset += size - } else { - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } else { - if (p.isComplex) { - let size = Buffer.calcSize(fi.value, includeMessages) - Buffer.writeDataView(fi.value, dataView, includeMessages, offset) - offset += size - } else { - let val = fi._bytes || fi.value - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } - - return offset - } - - static serialize(fields, includeMessages) { - let allSize = Buffer.calcSize(fields, includeMessages) - - let arrayBuffer = new ArrayBuffer(allSize), - dv = new DataView(arrayBuffer) - - Buffer.writeDataView(fields, dv, includeMessages) - - return arrayBuffer - } - - get size() { - return this._dataView.buffer.byteLength - } - - get dataView() { - return this._dataView - } - - set(arrayBuffer) { - this._dataView = null - this._dataView = new DataView(arrayBuffer) - } - - deserialize(struct, offset = 0, sizeOffset = 0) { - this._dynamicOffset = 0 - let res = this.__deserialize__(struct, offset, sizeOffset) - res.__SIZE__ = struct.size + this._dynamicOffset + sizeOffset - return res - } - - __deserialize__(struct, offset, sizeOffset) { - this._includeMessages = struct._includeMessages - - let fields = struct.fields, - dv = this._dataView, - res = Object.create(struct._default) - - let currOffset = 0 - - for (let f = 0, len = fields.length; f < len; f++) { - let fi = fields[f], - p = fi._prop - - currOffset = offset + fi._offset - const ArrayType = this._useTypedArray ? typedArray[p.typeIndex] ?? Array : Array - - if (p.isArray) { - if (p.length === 0) { - // - // Dynamic size array - // - - let length = DYN_READ(dv, currOffset + this._dynamicOffset) - - res[fi.name] = new ArrayType(length) - - let currOffset_dyn = DYN_TYPE_SIZE + currOffset - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32( - currOffset_dyn + this._dynamicOffset + j * p.typeSize, - true - ) - } - } else { - if (p.isComplex) { - for (let j = 0; j < length; j++) { - res[fi.name][j] = this.__deserialize__( - p.typeIndex, - currOffset_dyn + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - } - } - } - - this._dynamicOffset += length * p.typeSize - } else { - // - //Static size array - // - - res[fi.name] = new ArrayType(p.length) - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset + j * p.typeSize, true) - } - } else { - if (p.isComplex) { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = this.__deserialize__( - p.typeIndex, - currOffset + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - } - } - } - } - } else { - if (p.isComplex) { - res[fi.name] = this.__deserialize__(p.typeIndex, currOffset, sizeOffset) - } else { - res[fi.name] = readFunc[p.typeIndex](dv, currOffset + this._dynamicOffset) - - if (p.typeIndex === typeIndex.String) { - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset, true) - } - } - } - } - - return res - } -} - -/** - * Creates message struct namespace - * @param {*} messagesJson - Array of messages schemas - * @param {*} headerSchema - message header schema - */ -export function initializeMessages(messagesJson, headerSchema) { - let res = { - __id__: [], - __name__: [], - HEADER_STRUCT: headerSchema ? new Struct(headerSchema) : HEADER_STRUCT - } - - for (let m of getKeysWithSortById(messagesJson)) { - let name = m.trim(), - messageObj = messagesJson[m], - msg = 'MSG_' + name.toUpperCase(), - id = messageObj.id - - if (!res.__id__[id]) { - let msg_struct = new Struct(messageObj, res) - - res.__id__[id] = msg_struct - res.__name__[id] = m.trim() - res[msg] = msg_struct - res[name] = msg_struct - } else { - console.warn(`Warning: message ${ id } ${ msg } already exists.`) - } - } - - return res -} - -const getKeysWithSortById = (obj) => { - let keys = Object.keys(obj) - keys.sort((a, b) => { - return obj[a].id - obj[b].id - }) - return keys -} diff --git a/port/js/benchmarks/perf-process/index.ts b/port/js/benchmarks/perf-process/index.ts new file mode 100644 index 00000000..7c3f1d32 --- /dev/null +++ b/port/js/benchmarks/perf-process/index.ts @@ -0,0 +1,108 @@ +import { StructConverter } from '../../src/converters/base/StructConverter'; +import type { StructTypeDefinition } from '../../src/types'; +import { initGetType } from '../../tests/utils'; +import { Buffer } from '../../src/Buffer'; + +const schema: StructTypeDefinition = { + typeClass: 'struct', + typeName: 'testStruct', + fields: [ + { name: 'type_Int8', type: 'int8' }, + { name: 'type_Uint8', type: 'uint8' }, + { name: 'type_Int16', type: 'int16' }, + { name: 'type_Uint16', type: 'uint16' }, + { name: 'type_Int32', type: 'int32' }, + { name: 'type_Uint32', type: 'uint32' }, + { name: 'type_Int64', type: 'int64' }, + { name: 'type_Uint64', type: 'uint64' }, + { name: 'type_String', type: 'string' }, + { name: 'type_Double', type: 'float64' }, + + { name: 'type_Int8_a', type: 'int8[]' }, + { name: 'type_Uint8_a', type: 'uint8[]' }, + { name: 'type_Int16_a', type: 'int16[]' }, + { name: 'type_Uint16_a', type: 'uint16[]' }, + { name: 'type_Int32_a', type: 'int32[]' }, + { name: 'type_Uint32_a', type: 'uint32[]' }, + { name: 'type_Int64_a', type: 'int64[]' }, + { name: 'type_Uint64_a', type: 'uint64[]' }, + { name: 'type_String_a', type: 'string[]' }, + { name: 'type_Double_a', type: 'float64[]' }, + + { name: 'type_Int8_as', type: 'int8[1000]' }, + { name: 'type_Uint8_as', type: 'uint8[1000]' }, + { name: 'type_Int16_as', type: 'int16[1000]' }, + { name: 'type_Uint16_as', type: 'uint16[1000]' }, + { name: 'type_Int32_as', type: 'int32[1000]' }, + { name: 'type_Uint32_as', type: 'uint32[1000]' }, + { name: 'type_Int64_as', type: 'int64[1000]' }, + { name: 'type_Uint64_as', type: 'uint64[1000]' }, + { name: 'type_String_as', type: 'string[1000]' }, + { name: 'type_Double_as', type: 'float64[1000]' }, + ], +}; + +const array = new Array(1000).fill(0).map((_, i) => i); +const srcDataFn = () => ({ + type_Int8: 8, + type_Uint8: 8, + type_Int16: 8, + type_Uint16: 8, + type_Int32: 8, + type_Uint32: 8, + type_Int64: BigInt(8), + type_Uint64: BigInt(8), + type_String: 'This is test string', + type_Double: -Math.PI, + + type_Int8_a: new Int8Array(array), + type_Uint8_a: new Uint8Array(array), + type_Int16_a: new Int16Array(array), + type_Uint16_a: new Uint16Array(array), + type_Int32_a: new Int32Array(array), + type_Uint32_a: new Uint32Array(array), + type_Int64_a: new BigInt64Array(array.map(BigInt)), + type_Uint64_a: new BigUint64Array(array.map(BigInt)), + type_String_a: array.map((i) => `String ${i}`), + type_Double_a: array.map((i) => Math.PI * i), + + type_Int8_as: new Int8Array(array), + type_Uint8_as: new Uint8Array(array), + type_Int16_as: new Int16Array(array), + type_Uint16_as: new Uint16Array(array), + type_Int32_as: new Int32Array(array), + type_Uint32_as: new Uint32Array(array), + type_Int64_as: new BigInt64Array(array.map(BigInt)), + type_Uint64_as: new BigUint64Array(array.map(BigInt)), + type_String_as: array.map((i) => `String ${i}`), + type_Double_as: array.map((i) => Math.PI * i), +}); +const srcData = srcDataFn(); + +const structConverter = new StructConverter(schema, initGetType()); +const size = structConverter.size(srcData); +const buffer = new Buffer(new ArrayBuffer(size)); + +// run with 'node --inspect=9229 --require ts-node/register ./src/index.ts" +// to debug performance issues with structure serialization/deserialization +const run = async (): Promise => { + structConverter.serialize(srcData, buffer); + let counter = 0; + while (true) { + counter += 1; + + buffer.offset = 0; + structConverter.deserialize(buffer); + console.log('Counter:', counter); + } +}; + +process.on('uncaughtException', (error) => { + console.error('Uncaught Exception:', error); + process.exit(1); +}); + +run().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/port/js/benchmarks/serializeObj.bench.js b/port/js/benchmarks/serializeObj.bench.js deleted file mode 100644 index 7bc40b8c..00000000 --- a/port/js/benchmarks/serializeObj.bench.js +++ /dev/null @@ -1,50 +0,0 @@ -import {Buffer, Struct} from '../src/messgen.js'; -import { bench, describe } from 'vitest' - -let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: 'Int8' }, - { name: 'type_Uint8', type: 'Uint8' }, - { name: 'type_Int16', type: 'Int16' }, - { name: 'type_Uint16', type: 'Uint16' }, - { name: 'type_Int32', type: 'Int32' }, - { name: 'type_Uint32', type: 'Uint32' }, - { name: 'type_Int64', type: 'Int64' }, - { name: 'type_Uint64', type: 'Uint64' }, - { name: 'type_String', type: 'String' }, - { name: 'type_Double', type: 'Double' }, - { name: 'type_Char', type: 'Char' } - ] -}); - -let srcData = { - type_Int8: 8, - type_Uint8: 8, - type_Int16: 8, - type_Uint16: 8, - type_Int32: 8, - type_Uint32: 8, - type_Int64: BigInt(8), - type_Uint64: BigInt(8), - type_String: 'This is test string', - type_Double: -Math.PI, - type_Char: 'A' -}; -srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); -let b = Buffer.serializeObj(srcStruct.schema.fields, srcData); - -describe('Buffer Operations', () => { - bench('calculate size', () => { - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); - }, { time: 1000 }) - - bench('serialize object', () => { - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData); - }, { time: 1000 }) - - bench('deserialize object', () => { - let res = new Buffer(b).deserialize(srcStruct); - }, { time: 1000 }) -}) - diff --git a/port/js/benchmarks/struct.bench.ts b/port/js/benchmarks/struct.bench.ts new file mode 100644 index 00000000..412390b2 --- /dev/null +++ b/port/js/benchmarks/struct.bench.ts @@ -0,0 +1,29 @@ +import { bench, describe } from 'vitest'; +import { Codec } from '../src/Codec'; +import { uploadShema, uploadBinary } from '../tests/utils'; +import type { MessageId } from '../src/types'; + +const serializedMsgs = [ + [0, uploadBinary('../../../tests/serialized_data/bin/simple_struct.bin')], + [2, uploadBinary('../../../tests/serialized_data/bin/var_size_struct.bin')], + [3, uploadBinary('../../../tests/serialized_data/bin/struct_with_enum.bin')], + [4, uploadBinary('../../../tests/serialized_data/bin/empty_struct.bin')], + [5, uploadBinary('../../../tests/serialized_data/bin/complex_struct_with_empty.bin')], + [6, uploadBinary('../../../tests/serialized_data/bin/complex_struct_nostl.bin')], + [7, uploadBinary('../../../tests/serialized_data/bin/flat_struct.bin')], +] as unknown as [MessageId, Buffer][]; + +const protoId = 1; + +describe('Codec deserialization benchmark', () => { + const protocolData = uploadShema('./messgen/test_proto/protocol.json'); + const codec = new Codec([protocolData]); + + bench('deserialize 10,000 iterations', () => { + for (const [msgId, msgData] of serializedMsgs) { + codec.deserialize(protoId, msgId, new Uint8Array(msgData).buffer); + } + }, { + iterations: 1000, + }); +}); diff --git a/port/js/benchmarks/utils/logBench.js b/port/js/benchmarks/utils/logBench.js deleted file mode 100644 index adeb69b0..00000000 --- a/port/js/benchmarks/utils/logBench.js +++ /dev/null @@ -1,25 +0,0 @@ -const round = (number, precision) => { - const pow = Math.pow(10, precision) - return Math.round(number * pow) / pow -}; - -export function logBench(bench, isMs = false) { - const min = bench.results.reduce((acc, cur) => Math.min(acc, cur?.mean ?? Infinity), Infinity) * 1000 * 1000 - - console.table( - bench.table().map((row) => { - if (row) { - // @ts-ignore - row['diff from min %'] = -round((Number(row['Average Time (ns)']) / min - 1) * 100, 1) - if (isMs) { - // @ts-ignore - row['Average Time (ms)'] = round(row['Average Time (ns)'] / 1000 / 1000) - // @ts-ignore - delete row['Average Time (ns)'] - } - } - return row - }) - ) - console.log(bench.results) -} diff --git a/port/js/benchmarks/v1-vs-old-typedArry.bench.ts b/port/js/benchmarks/v1-vs-old-typedArry.bench.ts new file mode 100644 index 00000000..1db45739 --- /dev/null +++ b/port/js/benchmarks/v1-vs-old-typedArry.bench.ts @@ -0,0 +1,161 @@ +import { bench, describe } from 'vitest'; +// @ts-ignore +import { Buffer, Struct } from './deserialize-variant/messgen-old.js'; +import type { StructTypeDefinition } from '../src/types.js'; +import { initGetType } from '../tests/utils.js'; +import { StructConverter } from '../src/converters/base/StructConverter.js'; + +const srcStruct = new Struct({ + id: 2, + fields: [ + { name: 'type_Int8', type: 'Int8' }, + { name: 'type_Uint8', type: 'Uint8' }, + { name: 'type_Int16', type: 'Int16' }, + { name: 'type_Uint16', type: 'Uint16' }, + { name: 'type_Int32', type: 'Int32' }, + { name: 'type_Uint32', type: 'Uint32' }, + { name: 'type_Int64', type: 'Int64' }, + { name: 'type_Uint64', type: 'Uint64' }, + { name: 'type_String', type: 'String' }, + { name: 'type_Double', type: 'Double' }, + + { name: 'type_Int8_a', type: 'Int8[]' }, + { name: 'type_Uint8_a', type: 'Uint8[]' }, + { name: 'type_Int16_a', type: 'Int16[]' }, + { name: 'type_Uint16_a', type: 'Uint16[]' }, + { name: 'type_Int32_a', type: 'Int32[]' }, + { name: 'type_Uint32_a', type: 'Uint32[]' }, + { name: 'type_Int64_a', type: 'Int64[]' }, + { name: 'type_Uint64_a', type: 'Uint64[]' }, + { name: 'type_String_a', type: 'String[]' }, + { name: 'type_Double_a', type: 'Double[]' }, + + { name: 'type_Int8_as', type: 'Int8[1000]' }, + { name: 'type_Uint8_as', type: 'Uint8[1000]' }, + { name: 'type_Int16_as', type: 'Int16[1000]' }, + { name: 'type_Uint16_as', type: 'Uint16[1000]' }, + { name: 'type_Int32_as', type: 'Int32[1000]' }, + { name: 'type_Uint32_as', type: 'Uint32[1000]' }, + { name: 'type_Int64_as', type: 'Int64[1000]' }, + { name: 'type_Uint64_as', type: 'Uint64[1000]' }, + { name: 'type_String_as', type: 'String[1000]' }, + { name: 'type_Double_as', type: 'Double[1000]' }, + + ], +}); + +const array = new Array(1000).fill(0).map((_, i) => i); +const srcDataFn = () => ({ + type_Int8: 8, + type_Uint8: 8, + type_Int16: 8, + type_Uint16: 8, + type_Int32: 8, + type_Uint32: 8, + type_Int64: BigInt(8), + type_Uint64: BigInt(8), + type_String: 'This is test string', + type_Double: -Math.PI, + + type_Int8_a: new Int8Array(array), + type_Uint8_a: new Uint8Array(array), + type_Int16_a: new Int16Array(array), + type_Uint16_a: new Uint16Array(array), + type_Int32_a: new Int32Array(array), + type_Uint32_a: new Uint32Array(array), + type_Int64_a: new BigInt64Array(array.map(BigInt)), + type_Uint64_a: new BigUint64Array(array.map(BigInt)), + type_String_a: array.map((i) => `String ${i}`), + type_Double_a: array.map((i) => Math.PI * i), + + type_Int8_as: new Int8Array(array), + type_Uint8_as: new Uint8Array(array), + type_Int16_as: new Int16Array(array), + type_Uint16_as: new Uint16Array(array), + type_Int32_as: new Int32Array(array), + type_Uint32_as: new Uint32Array(array), + type_Int64_as: new BigInt64Array(array.map(BigInt)), + type_Uint64_as: new BigUint64Array(array.map(BigInt)), + type_String_as: array.map((i) => `String ${i}`), + type_Double_as: array.map((i) => Math.PI * i), +}); +const srcData = srcDataFn(); +// @ts-ignore +srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); +const b = Buffer.serializeObj(srcStruct.schema.fields, srcData); + +const schema: StructTypeDefinition = { + typeClass: 'struct', + typeName: 'testStruct', + fields: [ + { name: 'type_Int8', type: 'int8' }, + { name: 'type_Uint8', type: 'uint8' }, + { name: 'type_Int16', type: 'int16' }, + { name: 'type_Uint16', type: 'uint16' }, + { name: 'type_Int32', type: 'int32' }, + { name: 'type_Uint32', type: 'uint32' }, + { name: 'type_Int64', type: 'int64' }, + { name: 'type_Uint64', type: 'uint64' }, + { name: 'type_String', type: 'string' }, + { name: 'type_Double', type: 'float64' }, + + { name: 'type_Int8_a', type: 'int8[]' }, + { name: 'type_Uint8_a', type: 'uint8[]' }, + { name: 'type_Int16_a', type: 'int16[]' }, + { name: 'type_Uint16_a', type: 'uint16[]' }, + { name: 'type_Int32_a', type: 'int32[]' }, + { name: 'type_Uint32_a', type: 'uint32[]' }, + { name: 'type_Int64_a', type: 'int64[]' }, + { name: 'type_Uint64_a', type: 'uint64[]' }, + { name: 'type_String_a', type: 'string[]' }, + { name: 'type_Double_a', type: 'float64[]' }, + + { name: 'type_Int8_as', type: 'int8[1000]' }, + { name: 'type_Uint8_as', type: 'uint8[1000]' }, + { name: 'type_Int16_as', type: 'int16[1000]' }, + { name: 'type_Uint16_as', type: 'uint16[1000]' }, + { name: 'type_Int32_as', type: 'int32[1000]' }, + { name: 'type_Uint32_as', type: 'uint32[1000]' }, + { name: 'type_Int64_as', type: 'int64[1000]' }, + { name: 'type_Uint64_as', type: 'uint64[1000]' }, + { name: 'type_String_as', type: 'string[1000]' }, + { name: 'type_Double_as', type: 'float64[1000]' }, + ], +}; +const name = 'testStruct'; + +const getType = initGetType(); +const structConverter = new StructConverter('testStruct', schema, getType); +const size = structConverter.size(srcData); +const buffer = new Buffer(new ArrayBuffer(size)); + +describe('calculate size with typedArray', () => { + bench('old', () => { + // @ts-ignore + Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcDataFn())); + }, { time: 1000 }); + + bench('v1', () => { + structConverter.size(srcDataFn()); + }); +}); +describe('serialize Obj with typedArray', () => { + bench('Old', () => { + Buffer.serializeObj(srcStruct.schema.fields, srcDataFn()); + }, { time: 1000 }); + bench('v1', () => { + buffer.offset = 0; + structConverter.serialize(srcDataFn(), buffer); + }); +}); + +describe('deserialize object with typedArray', () => { + bench('Old', () => { + new Buffer(b).deserialize(srcStruct); + }, { time: 1000 }); + + bench('v1', () => { + buffer.offset = 0; + structConverter.deserialize(buffer); + }); +}); diff --git a/port/js/benchmarks/v1-vs-old.bench.ts b/port/js/benchmarks/v1-vs-old.bench.ts new file mode 100644 index 00000000..778e8a32 --- /dev/null +++ b/port/js/benchmarks/v1-vs-old.bench.ts @@ -0,0 +1,90 @@ +import { bench, describe } from 'vitest'; +// @ts-ignore +import { Buffer, Struct } from './deserialize-variant/messgen-old.js'; +import { StructConverter } from '../src/converters/base/StructConverter.js'; +import type { StructTypeDefinition } from '../src/types.js'; +import { initGetType } from '../tests/utils.js'; + +const srcStruct = new Struct({ + id: 2, + fields: [ + { name: 'type_Int8', type: 'Int8' }, + { name: 'type_Uint8', type: 'Uint8' }, + { name: 'type_Int16', type: 'Int16' }, + { name: 'type_Uint16', type: 'Uint16' }, + { name: 'type_Int32', type: 'Int32' }, + { name: 'type_Uint32', type: 'Uint32' }, + { name: 'type_Int64', type: 'Int64' }, + { name: 'type_Uint64', type: 'Uint64' }, + { name: 'type_String', type: 'String' }, + { name: 'type_Double', type: 'Double' }, + ], +}); + +const srcData = { + type_Int8: 8, + type_Uint8: 8, + type_Int16: 8, + type_Uint16: 8, + type_Int32: 8, + type_Uint32: 8, + type_Int64: BigInt(8), + type_Uint64: BigInt(8), + type_String: 'This is test string', + type_Double: -Math.PI, +}; +// @ts-ignore +srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); +const b = Buffer.serializeObj(srcStruct.schema.fields, srcData); + +const schema: StructTypeDefinition = { + typeClass: 'struct', + typeName: 'testStruct', + fields: [ + { name: 'type_Int8', type: 'int8' }, + { name: 'type_Uint8', type: 'uint8' }, + { name: 'type_Int16', type: 'int16' }, + { name: 'type_Uint16', type: 'uint16' }, + { name: 'type_Int32', type: 'int32' }, + { name: 'type_Uint32', type: 'uint32' }, + { name: 'type_Int64', type: 'int64' }, + { name: 'type_Uint64', type: 'uint64' }, + { name: 'type_String', type: 'string' }, + { name: 'type_Double', type: 'float64' }, + ], +}; + +const getType = initGetType(); +const structConverter = new StructConverter('testStruct', schema, getType); +const size = structConverter.size(srcData); +const buffer = new Buffer(new ArrayBuffer(size)); + +describe('calculate size', () => { + bench('old', () => { + // @ts-ignore + Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)); + }, { time: 1000 }); + + bench('v1', () => { + structConverter.size(srcData); + }); +}); +describe('serialize Obj', () => { + bench('Old', () => { + Buffer.serializeObj(srcStruct.schema.fields, srcData); + }, { time: 1000 }); + bench('v1', () => { + buffer.offset = 0; + structConverter.serialize(srcData, buffer); + }); +}); + +describe('deserialize object', () => { + bench('Old', () => { + new Buffer(b).deserialize(srcStruct); + }, { time: 1000 }); + bench('v1', () => { + buffer.offset = 0; + structConverter.deserialize(buffer); + }); +}); diff --git a/port/js/docs/shema.drawio.svg b/port/js/docs/shema.drawio.svg new file mode 100644 index 00000000..a8e7b640 --- /dev/null +++ b/port/js/docs/shema.drawio.svg @@ -0,0 +1,1156 @@ + + + + + + + + + + + + + + + +
+
+
+ Struct +
+
+
+
+ Struct + +
+
+
+ + + + + + + +
+
+
+ constructor(schema: SchemaObj, includeMessages?: Messages<string>) +
+
+
+
+ + constructor(schema: SchemaObj, includeMessages?: Messages<string>) + +
+
+
+ + + + + + + +
+
+
+ id: IId +
+
+
+
+ id: IId +
+
+
+ + + + + + + + + + + +
+
+
+ schema: SchemaObj +
+
+
+
+ schema: + SchemaObj + +
+
+
+ + + + + + + +
+
+
+ fields: FieldStruct +
+
+
+
+ + fields: FieldStruct + +
+
+
+ + + + + + + +
+
+
+ _includeMessages?: Messages<string> +
+
+
+
+ + _includeMessages?: Messages<string> + +
+
+
+ + + + + + + + + +
+
+
+ Messgen +
+
+
+
+ Messgen + +
+
+
+ + + + + + + +
+
+
+
+ constructor + ( + + %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%26lt%3Bspan%20style%3D%26quot%3Btext-align%3A%20center%3B%26quot%3B%26gt%3B%26lt%3Bspan%20style%3D%26quot%3Bfont-weight%3A%20400%3B%26quot%3B%26gt%3Bdeserialize%3A%20(data%3A%20ArrayBufferLike)%3D%26amp%3Bgt%3B%26lt%3B%2Fspan%26gt%3B%26lt%3B%2Fspan%26gt%3B%22%20style%3D%22text%3BstrokeColor%3Dnone%3BfillColor%3Dnone%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BspacingLeft%3D4%3BspacingRight%3D4%3Boverflow%3Dhidden%3Bpoints%3D%5B%5B0%2C0.5%5D%2C%5B1%2C0.5%5D%5D%3BportConstraint%3Deastwest%3Brotatable%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BfontStyle%3D1%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2250%22%20y%3D%22350%22%20width%3D%22260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E + + +   messagesJson: Record<KEYS, + SchemaObj>, + + +
+
+ +
+ +
+
+
+
+ +
+ +   headerSchema?: + SchemaObj + + +   messagesJson: Record<KEYS, + SchemaObj>, + +
+
+   headerSchema?: SchemaObj +
+
+ + ) + +
+
+
+
+
+
+ + constructor(%3CmxGraphModel%3E%3Croot%3E%3CmxCell... + +
+
+
+ + + + + + + +
+
+
+ _includeMessages?: Messages<string> +
+
+
+
+ + _includeMessages?: Messages<string> + +
+
+
+ + + + + + + +
+
+
+ + deserialize: (data: ArrayBufferLike) => + Obj[] + + +
+
+
+
+ deserialize: (data: ArrayBufferLike) => Obj[] + +
+
+
+ + + + + + + +
+
+
+ serialize + + : (data:  + + Obj + + ) =>   + + ArrayBufferLike +
+
+
+
+ serialize: (data: Obj) =>  ArrayBufferLike + +
+
+
+ + + + + + + + + +
+
+
+ Converter +
+
+
+
+ Converter + +
+
+
+ + + + + + + +
+
+
+ name:string +
+
+
+
+
+
+
+ + name:string + +
+
+
+ + + + + + + +
+
+
+ serialize(value: +  Obj, buffer:Buffer + ):  + Buffer +
+
+
+
+ + serialize(value: Obj, buffer:Buffer): Buffer + +
+
+
+ + + + + + + +
+
+
+ serializeSIze(value:Obj): number +
+
+
+
+ + serializeSIze(value:Obj): number + +
+
+
+ + + + + + + +
+
+
+ deserialize(arrayBuffer:  + Buffer + ): + Obj +
+
+
+
+ + deserialize(arrayBuffer: Buffer): Obj + +
+
+
+ + + + + + + + + + + + + +
+
+
+ Primetiv +
+
+
+
+ Primetiv + +
+
+
+ + + + + + + +
+
+
+ constructor(type:string) +
+
+
+
+ + constructor(type:string) + +
+
+
+ + + + + + + +
+
+
+ _includeMessages?: Messages<string> +
+
+
+
+ + _includeMessages?: Messages<string> + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ Dinamical +
+
+
+
+ Dinamical + +
+
+
+ + + + + + + +
+
+
+ constructor(type:string,  includeMessages?: Messages<string>) +
+
+
+
+ + constructor(type:string,  includeMessages?: Messages<string>) + +
+
+
+ + + + + + + +
+
+
+ _includeMessages?: Messages<string> +
+
+
+
+ + _includeMessages?: Messages<string> + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ { +
v1: 1
+
}
+
+
+
+
+ {... +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ config +
id: 1
name: varibals  +
filds:[
+
{ +
+
name: 'v1'
+
 type: 'int8'
+
} +
+
+
]
+
+
+
+
+
+ config... +
+
+
+ + + + + + + +
+
+
+ FE +
+
+
+
+ FE + +
+
+
+ + + + + + + +
+
+
+ BE +
+
+
+
+ BE + +
+
+
+ + + + + + + +
+
+
+ { +
v1: 1
+
}
+
+
+
+
+ {... +
+
+
+ + + + + + + + + + + +
+
+
+ 1 +
+
+
+
+ 1 + +
+
+
+ + + + + + + + + + + +
+
+
+ head[1] +
+
+
+
+ head[1] + +
+
+
+ + + + + + + +
+
+
+ dataView:  +
+
+
+
+ dataView:  + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ d int +
+
+
+
+ d int + +
+
+
+ + + + + + + +
+
+
+ d int +
+
+
+
+ d int + +
+
+
+ + + + + + + + + + +
+
+
+ d int +
+
+
+
+ d int + +
+
+
+ + + + + + + + + +
+
+
+ Buffer +
+
+
+
+ Buffer + +
+
+
+ + + + + + + +
+
+
+ dataView: DataView +
+
+
+
+ dataView: DataView + +
+
+
+ + + + + + + +
+
+
+ offset: number // mutation +
+
+
+
+
+
+
+
+
+ offset: number + // mutation... + +
+
+
+
+ + + + Text is not SVG - cannot display + + +
diff --git a/port/js/jest.config.js b/port/js/jest.config.js deleted file mode 100644 index a1c7a58b..00000000 --- a/port/js/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('jest').Config} */ -const config = { - transform: { - '\\.js?$': 'babel-jest' - } -} - -module.exports = config diff --git a/port/js/package-lock.json b/port/js/package-lock.json index 2d81def9..461766dc 100644 --- a/port/js/package-lock.json +++ b/port/js/package-lock.json @@ -1,36 +1,139 @@ { - "name": "messgenjs", - "version": "1.0.0", + "name": "messgen", + "version": "0.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "messgenjs", - "version": "1.0.0", - "license": "ISC", + "name": "messgen", + "version": "0.0.6", "devDependencies": { - "@rollup/plugin-terser": "^0.4.3", - "prettier": "3.0.3", - "rollup": "^3.28.1", - "tinybench": "^2.6.0", - "unplugin-auto-import": "^0.17.5", - "vite": "^5.2.6", - "vitest": "^1.4.0" + "@rollup/plugin-terser": "^0.4.4", + "@types/node": "^20.12.7", + "@vitest/coverage-v8": "2.1.5", + "bunchee": "5.6.1", + "eslint": "8.57.0", + "eslint-config-airbnb": "19.0.4", + "eslint-config-airbnb-typescript": "17.1.0", + "eslint-import-resolver-typescript": "3.6.1", + "eslint-plugin-eslint-comments": "3.2.0", + "eslint-plugin-import": "2.26.0", + "prettier": "3.2.5", + "tinybench": "^2.7.0", + "ts-node": "10.9.2", + "tslib": "2.8.1", + "tsup": "8.3.5", + "typescript": "5.7.2", + "vite": "5.4.11", + "vitest": "2.1.5" } }, - "node_modules/@antfu/utils": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -44,9 +147,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -60,9 +163,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -76,9 +179,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -92,9 +195,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -108,9 +211,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -124,9 +227,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -140,9 +243,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -156,9 +259,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -172,9 +275,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -188,9 +291,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -204,9 +307,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -220,9 +323,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -236,9 +339,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -252,9 +355,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -268,9 +371,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -284,9 +387,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -300,9 +403,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -315,10 +418,26 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -332,9 +451,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -348,9 +467,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -364,9 +483,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -380,9 +499,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -395,27 +514,185 @@ "node": ">=12" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -431,9 +708,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -450,15 +727,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -500,10 +777,117 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", + "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", + "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.1.tgz", + "integrity": "sha512-2sPh9b73dj5IxuMmDAsQWVFT7mR+yoHweBaXG2W/R8vQ+IWZlnaI7BR7J6EguVQUp1hd8Z7XuozpDjEKQAAC2Q==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-terser": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz", - "integrity": "sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "dev": true, "dependencies": { "serialize-javascript": "^6.0.1", @@ -514,7 +898,27 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.x || ^3.x" + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-wasm": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-wasm/-/plugin-wasm-6.2.2.tgz", + "integrity": "sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -523,14 +927,14 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", + "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "picomatch": "^4.0.2" }, "engines": { "node": ">=14.0.0" @@ -551,9 +955,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.1.tgz", - "integrity": "sha512-4C4UERETjXpC4WpBXDbkgNVgHyWfG3B/NKY46e7w5H134UDOFqUJKpsLm0UYmuupW+aJmRgeScrDNfvZ5WV80A==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", + "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", "cpu": [ "arm" ], @@ -564,9 +968,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.1.tgz", - "integrity": "sha512-TrTaFJ9pXgfXEiJKQ3yQRelpQFqgRzVR9it8DbeRzG0RX7mKUy0bqhCFsgevwXLJepQKTnLl95TnPGf9T9AMOA==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", + "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", "cpu": [ "arm64" ], @@ -577,9 +981,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.1.tgz", - "integrity": "sha512-fz7jN6ahTI3cKzDO2otQuybts5cyu0feymg0bjvYCBrZQ8tSgE8pc0sSNEuGvifrQJWiwx9F05BowihmLxeQKw==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", + "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", "cpu": [ "arm64" ], @@ -590,9 +994,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.1.tgz", - "integrity": "sha512-WTvdz7SLMlJpektdrnWRUN9C0N2qNHwNbWpNo0a3Tod3gb9leX+yrYdCeB7VV36OtoyiPAivl7/xZ3G1z5h20g==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", + "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", "cpu": [ "x64" ], @@ -602,25 +1006,64 @@ "darwin" ] }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.1.tgz", - "integrity": "sha512-dBHQl+7wZzBYcIF6o4k2XkAfwP2ks1mYW2q/Gzv9n39uDcDiAGDqEyml08OdY0BIct0yLSPkDTqn4i6czpBLLw==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", + "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", "cpu": [ - "arm" + "arm64" ], "dev": true, "optional": true, "os": [ - "linux" + "freebsd" ] }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.1.tgz", - "integrity": "sha512-bur4JOxvYxfrAmocRJIW0SADs3QdEYK6TQ7dTNz6Z4/lySeu3Z1H/+tl0a4qDYv0bCdBpUYM0sYa/X+9ZqgfSQ==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", + "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", "cpu": [ - "arm64" + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", + "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", + "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", + "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", + "cpu": [ + "arm64" ], "dev": true, "optional": true, @@ -629,9 +1072,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.1.tgz", - "integrity": "sha512-ssp77SjcDIUSoUyj7DU7/5iwM4ZEluY+N8umtCT9nBRs3u045t0KkW02LTyHouHDomnMXaXSZcCSr2bdMK63kA==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", + "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", "cpu": [ "arm64" ], @@ -641,10 +1084,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", + "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.1.tgz", - "integrity": "sha512-Jv1DkIvwEPAb+v25/Unrnnq9BO3F5cbFPT821n3S5litkz+O5NuXuNhqtPx5KtcwOTtaqkTsO+IVzJOsxd11aQ==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", + "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", "cpu": [ "riscv64" ], @@ -655,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.1.tgz", - "integrity": "sha512-U564BrhEfaNChdATQaEODtquCC7Ez+8Hxz1h5MAdMYj0AqD0GA9rHCpElajb/sQcaFL6NXmHc5O+7FXpWMa73Q==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", + "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", "cpu": [ "s390x" ], @@ -668,9 +1124,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.1.tgz", - "integrity": "sha512-zGRDulLTeDemR8DFYyFIQ8kMP02xpUsX4IBikc7lwL9PrwR3gWmX2NopqiGlI2ZVWMl15qZeUjumTwpv18N7sQ==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", + "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", "cpu": [ "x64" ], @@ -681,9 +1137,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.1.tgz", - "integrity": "sha512-VTk/MveyPdMFkYJJPCkYBw07KcTkGU2hLEyqYMsU4NjiOfzoaDTW9PWGRsNwiOA3qI0k/JQPjkl/4FCK1smskQ==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", + "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", "cpu": [ "x64" ], @@ -694,9 +1150,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.1.tgz", - "integrity": "sha512-L+hX8Dtibb02r/OYCsp4sQQIi3ldZkFI0EUkMTDwRfFykXBPptoz/tuuGqEd3bThBSLRWPR6wsixDSgOx/U3Zw==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", + "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", "cpu": [ "arm64" ], @@ -707,9 +1163,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.1.tgz", - "integrity": "sha512-+dI2jVPfM5A8zme8riEoNC7UKk0Lzc7jCj/U89cQIrOjrZTCWZl/+IXUeRT2rEZ5j25lnSA9G9H1Ob9azaF/KQ==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", + "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", "cpu": [ "ia32" ], @@ -720,9 +1176,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.1.tgz", - "integrity": "sha512-YY1Exxo2viZ/O2dMHuwQvimJ0SqvL+OAWQLLY6rvXavgQKjhQUzn7nc1Dd29gjB5Fqi00nrBWctJBOyfVMIVxw==", + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", + "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", "cpu": [ "x64" ], @@ -732,1145 +1188,5973 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.5.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", - "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "node_modules/@swc/core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.3.tgz", + "integrity": "sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==", "dev": true, + "hasInstallScript": true, "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", - "chai": "^4.3.10" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.17" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.9.3", + "@swc/core-darwin-x64": "1.9.3", + "@swc/core-linux-arm-gnueabihf": "1.9.3", + "@swc/core-linux-arm64-gnu": "1.9.3", + "@swc/core-linux-arm64-musl": "1.9.3", + "@swc/core-linux-x64-gnu": "1.9.3", + "@swc/core-linux-x64-musl": "1.9.3", + "@swc/core-win32-arm64-msvc": "1.9.3", + "@swc/core-win32-ia32-msvc": "1.9.3", + "@swc/core-win32-x64-msvc": "1.9.3" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } } }, - "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "node_modules/@swc/core-darwin-arm64": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz", + "integrity": "sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@vitest/utils": "1.4.0", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@swc/core-darwin-x64": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.3.tgz", + "integrity": "sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.3.tgz", + "integrity": "sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==", + "cpu": [ + "arm" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.3.tgz", + "integrity": "sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.3.tgz", + "integrity": "sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "tinyspy": "^2.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz", + "integrity": "sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz", + "integrity": "sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.3.tgz", + "integrity": "sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.3.tgz", + "integrity": "sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 8" + "node": ">=10" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.3.tgz", + "integrity": "sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "*" + "node": ">=10" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "tslib": "^2.8.0" } }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@swc/types": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "@swc/counter": "^0.1.3" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "peer": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" + "undici-types": "~5.26.4" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true, - "engines": { - "node": ">=8" - } + "peer": true }, - "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, + "peer": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": ">=4" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, + "peer": true, "dependencies": { - "get-func-name": "^2.0.2" + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" }, "engines": { - "node": "*" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, + "peer": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { - "node": ">= 8.10.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, + "peer": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": ">= 8" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "peer": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, + "peer": true, "dependencies": { - "ms": "2.1.2" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": ">=6.0" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { - "supports-color": { + "typescript": { "optional": true } } }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "peer": true, "dependencies": { - "type-detect": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=12" + "node": "^16.0.0 || >=18.0.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", + "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", "dev": true, - "engines": { - "node": ">=12" + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.5", + "vitest": "2.1.5" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/@vitest/expect": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", + "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", "dev": true, "dependencies": { - "@types/estree": "^1.0.0" + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/@vitest/mocker": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", + "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "@vitest/spy": "2.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" }, - "engines": { - "node": ">=8.6.0" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "node_modules/@vitest/pretty-format": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", "dev": true, "dependencies": { - "reusify": "^1.0.4" + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/@vitest/runner": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", + "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" + "@vitest/utils": "2.1.5", + "pathe": "^1.1.2" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@vitest/snapshot": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", + "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "@vitest/pretty-format": "2.1.5", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "node_modules/@vitest/spy": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", + "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", "dev": true, - "engines": { - "node": "*" + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/@vitest/utils": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", + "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "@vitest/pretty-format": "2.1.5", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.4.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "engines": { - "node": ">=0.12.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "peer": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, + "peer": true, "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" } }, - "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mlly": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", - "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" + "engines": { + "node": ">=12" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "peer": true }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", "dev": true, + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/bunchee": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/bunchee/-/bunchee-5.6.1.tgz", + "integrity": "sha512-vWd2eZFJmZYHVY+Snvsy9X7E4qIO6v7ur9qYjjYWW67P9uVQ1hhoARn7uGIM1gkYtfohZR+LnY7e3Xzo/PMxvQ==", "dev": true, + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^6.0.1", + "@rollup/plugin-wasm": "^6.2.2", + "@rollup/pluginutils": "^5.1.0", + "@swc/core": "^1.7.14", + "@swc/helpers": "^0.5.11", + "clean-css": "^5.3.3", + "magic-string": "^0.30.11", + "ora": "^8.0.1", + "pretty-bytes": "^5.6.0", + "rollup": "^4.24.0", + "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-swc3": "^0.11.1", + "rollup-preserve-directives": "^1.1.2", + "tslib": "^2.7.0", + "yargs": "^17.7.2" + }, + "bin": { + "bunchee": "dist/bin/cli.js" + }, "engines": { - "node": ">=8.6" + "node": ">= 18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "typescript": "^4.1 || ^5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "node_modules/bundle-require": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.0.0.tgz", + "integrity": "sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==", "dev": true, "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" } }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" } }, - "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "engines": { + "node": ">= 16" + } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "dev": true, "dependencies": { - "safe-buffer": "^5.1.0" + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "dependencies": { - "picomatch": "^2.2.1" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rollup": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", - "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "bin": { - "rollup": "dist/bin/rollup" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=12" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" + "engines": { + "node": ">=8" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "randombytes": "^2.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "shebang-regex": "^3.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/smob": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz", - "integrity": "sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==", + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "peer": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.23.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", + "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", "dev": true, "dependencies": { - "js-tokens": "^8.0.2" + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/strip-literal/node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true }, - "node_modules/terser": { - "version": "5.19.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.3.tgz", - "integrity": "sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg==", + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", "dev": true, "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "engines": { + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "peer": true, + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.1.0", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "peer": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "peer": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "peer": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.30.13", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.13.tgz", + "integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz", + "integrity": "sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==", + "dev": true, + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", + "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "which-builtin-type": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", + "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.27.4", + "@rollup/rollup-android-arm64": "4.27.4", + "@rollup/rollup-darwin-arm64": "4.27.4", + "@rollup/rollup-darwin-x64": "4.27.4", + "@rollup/rollup-freebsd-arm64": "4.27.4", + "@rollup/rollup-freebsd-x64": "4.27.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", + "@rollup/rollup-linux-arm-musleabihf": "4.27.4", + "@rollup/rollup-linux-arm64-gnu": "4.27.4", + "@rollup/rollup-linux-arm64-musl": "4.27.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", + "@rollup/rollup-linux-riscv64-gnu": "4.27.4", + "@rollup/rollup-linux-s390x-gnu": "4.27.4", + "@rollup/rollup-linux-x64-gnu": "4.27.4", + "@rollup/rollup-linux-x64-musl": "4.27.4", + "@rollup/rollup-win32-arm64-msvc": "4.27.4", + "@rollup/rollup-win32-ia32-msvc": "4.27.4", + "@rollup/rollup-win32-x64-msvc": "4.27.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-dts": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.1.tgz", + "integrity": "sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.10" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.24.2" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0" + } + }, + "node_modules/rollup-plugin-swc3": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-swc3/-/rollup-plugin-swc3-0.11.2.tgz", + "integrity": "sha512-o1ih9B806fV2wBSNk46T0cYfTF2eiiKmYXRpWw3K4j/Cp3tCAt10UCVsTqvUhGP58pcB3/GZcAVl5e7TCSKN6Q==", + "dev": true, + "dependencies": { + "@fastify/deepmerge": "^1.3.0", + "@rollup/pluginutils": "^5.1.0", + "get-tsconfig": "^4.7.3", + "rollup-preserve-directives": "^1.1.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@swc/core": ">=1.2.165", + "rollup": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rollup-preserve-directives": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rollup-preserve-directives/-/rollup-preserve-directives-1.1.3.tgz", + "integrity": "sha512-oXqxd6ZzkoQej8Qt0k+S/yvO2+S4CEVEVv2g85oL15o0cjAKTKEuo2MzyA8FcsBBXbtytBzBMFAbhvQg4YyPUQ==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5" + }, + "peerDependencies": { + "rollup": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smob": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz", + "integrity": "sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.19.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.3.tgz", + "integrity": "sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.2.tgz", + "integrity": "sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/tsup": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.3.5.tgz", + "integrity": "sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==", + "dev": true, + "dependencies": { + "bundle-require": "^5.0.0", + "cac": "^6.7.14", + "chokidar": "^4.0.1", + "consola": "^3.2.3", + "debug": "^4.3.7", + "esbuild": "^0.24.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.24.0", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.1", + "tinyglobby": "^0.2.9", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/esbuild": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "dev": true, + "hasInstallScript": true, "bin": { - "terser": "bin/terser" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" } }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/tsup/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", "dev": true, "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/tinybench": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", - "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", - "dev": true - }, - "node_modules/tinypool": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", - "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.8.0" } }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=14.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "is-number": "^7.0.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">=8.0" + "node": ">= 0.4" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ufo": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", - "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", - "dev": true - }, - "node_modules/unimport": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.7.1.tgz", - "integrity": "sha512-V9HpXYfsZye5bPPYUgs0Otn3ODS1mDUciaBlXljI4C2fTwfFpvFZRywmlOu943puN9sncxROMZhsZCjNXEpzEQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.1.0", - "acorn": "^8.11.2", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "fast-glob": "^3.3.2", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "mlly": "^1.4.2", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "scule": "^1.1.1", - "strip-literal": "^1.3.0", - "unplugin": "^1.5.1" - } - }, - "node_modules/unimport/node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", "dev": true, "dependencies": { - "acorn": "^8.10.0" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unplugin": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.10.0.tgz", - "integrity": "sha512-CuZtvvO8ua2Wl+9q2jEaqH6m3DoQ38N7pvBYQbbaeNlWGvK2l6GHiKi29aIHDPoSxdUzQ7Unevf1/ugil5X6Pg==", + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "dependencies": { - "acorn": "^8.11.3", - "chokidar": "^3.6.0", - "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.6.1" + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unplugin-auto-import": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.17.5.tgz", - "integrity": "sha512-fHNDkDSxv3PGagX1wmKBYBkgaM4AKAgZmdJw/bxjhNljx9KSXSgHpGfX0MwUrq9qw6q1bhHIZVWyOwoY2koo4w==", + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, - "dependencies": { - "@antfu/utils": "^0.7.7", - "@rollup/pluginutils": "^5.1.0", - "fast-glob": "^3.3.2", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "minimatch": "^9.0.3", - "unimport": "^3.7.1", - "unplugin": "^1.6.0" + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14" + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@nuxt/kit": "^3.2.2", - "@vueuse/core": "*" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "@vueuse/core": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/vite": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", - "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.36", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -1889,6 +7173,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -1906,6 +7191,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -1918,15 +7206,15 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", + "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { @@ -1939,65 +7227,32 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/rollup": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.1.tgz", - "integrity": "sha512-hFi+fU132IvJ2ZuihN56dwgpltpmLZHZWsx27rMCTZ2sYwrqlgL5sECGy1eeV2lAihD8EzChBVVhsXci0wD4Tg==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.1", - "@rollup/rollup-android-arm64": "4.13.1", - "@rollup/rollup-darwin-arm64": "4.13.1", - "@rollup/rollup-darwin-x64": "4.13.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.1", - "@rollup/rollup-linux-arm64-gnu": "4.13.1", - "@rollup/rollup-linux-arm64-musl": "4.13.1", - "@rollup/rollup-linux-riscv64-gnu": "4.13.1", - "@rollup/rollup-linux-s390x-gnu": "4.13.1", - "@rollup/rollup-linux-x64-gnu": "4.13.1", - "@rollup/rollup-linux-x64-musl": "4.13.1", - "@rollup/rollup-win32-arm64-msvc": "4.13.1", - "@rollup/rollup-win32-ia32-msvc": "4.13.1", - "@rollup/rollup-win32-x64-msvc": "4.13.1", - "fsevents": "~2.3.2" - } - }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", + "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", "dev": true, "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "@vitest/expect": "2.1.5", + "@vitest/mocker": "2.1.5", + "@vitest/pretty-format": "^2.1.5", + "@vitest/runner": "2.1.5", + "@vitest/snapshot": "2.1.5", + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.4.0", - "why-is-node-running": "^2.2.2" + "vite-node": "2.1.5", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -2011,8 +7266,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "2.1.5", + "@vitest/ui": "2.1.5", "happy-dom": "*", "jsdom": "*" }, @@ -2037,185 +7292,337 @@ } } }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" }, "engines": { - "node": ">=16.17" + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/which-builtin-type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", + "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, "engines": { - "node": ">=16" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, "engines": { - "node": ">=16.17.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "path-key": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "mimic-fn": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "engines": { - "node": ">=14" + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=12" } }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { - "node": ">=10.13.0" + "node": ">=8" } }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", - "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/port/js/package.json b/port/js/package.json index 19d1c9c0..4faddc1f 100644 --- a/port/js/package.json +++ b/port/js/package.json @@ -1,27 +1,62 @@ { - "name": "messgenjs", - "version": "1.0.0", - "description": "", - "main": "src/messgen.js", - "type": "module", + "name": "messgen", + "version": "0.0.6", "scripts": { - "build": "vite build", + "build": "bunchee", "test": "vitest", - "prettier": "prettier --write \"src/**/*.js\" \"tests/**/*.js\" \"*.js\"", - "gen-json": "python3 ../../generate.py -b ../../tests/messages -m messgen/messgen_test -l json -o ../../tests/js", - "gen-ts": "npm run gen-json && python3 ../../generate.py -b ../../tests/messages -m messgen/messgen_test -l ts -o ../../tests/js", - "gen-cpp": "python3 ../../generate.py -b ../../tests/messages -m messgen/messgen_test -l cpp -o ../../tests/js", - "gen-md": "python3 ../../generate.py -b ../../tests/messages -m messgen/messgen_test -l md -o ../../tests/js", - "benchmark": "vitest bench" + "benchmark": "vitest bench", + "coverage": "vitest run --coverage", + "gen": "python3 ../../messgen-generate.py --types ../../tests/data/types --protocol ../../tests/data/protocols:test_proto --protocol ../../tests/data/protocols:nested/another_proto --outdir ./tests", + "gen:json": "npm run gen -- --lang json", + "gen:ts": "npm run gen:json && npm run gen -- --lang ts", + "gen:md": "npm run gen -- --lang md", + "generate-bit": "cd ../.. && python3 tests/python/generate_serialized_data.py", + "check:lint": "eslint --ext .ts src", + "check:types": "tsc --noEmit" }, - "author": "", - "license": "ISC", "devDependencies": { - "@rollup/plugin-terser": "^0.4.3", - "prettier": "3.0.3", - "rollup": "^3.28.1", - "tinybench": "^2.6.0", - "vite": "^5.2.6", - "vitest": "^1.4.0" - } + "@rollup/plugin-terser": "^0.4.4", + "@types/node": "^20.12.7", + "@vitest/coverage-v8": "2.1.5", + "bunchee": "5.6.1", + "prettier": "3.2.5", + "tinybench": "^2.7.0", + "ts-node": "10.9.2", + "tslib": "2.8.1", + "tsup": "8.3.5", + "typescript": "5.7.2", + "vite": "5.4.11", + "vitest": "2.1.5", + "eslint": "8.57.0", + "eslint-config-airbnb": "19.0.4", + "eslint-config-airbnb-typescript": "17.1.0", + "eslint-import-resolver-typescript": "3.6.1", + "eslint-plugin-eslint-comments": "3.2.0", + "eslint-plugin-import": "2.26.0" + }, + "exports": { + ".": { + "import": "./dist/es/index.js", + "types": "./dist/types/index.d.ts" + } + }, + "typesVersions": { + "*": { + "*": [ + "dist/types/index.d.ts" + ], + "specs": [ + "dist/types/specs.d.ts" + ] + } + }, + "keywords": [ + "messgen", + "messaging", + "protocol" + ], + "files": [ + "dist", + "src" + ] } diff --git a/port/js/src/Buffer.ts b/port/js/src/Buffer.ts new file mode 100644 index 00000000..63dc92d5 --- /dev/null +++ b/port/js/src/Buffer.ts @@ -0,0 +1,25 @@ +export class Buffer { + size: number; + buffer: ArrayBuffer; + dataView: DataView; + + private _offset: number; + + constructor(arrayBuffer: ArrayBuffer) { + this.buffer = arrayBuffer; + this.dataView = new DataView(arrayBuffer); + this._offset = 0; + this.size = arrayBuffer.byteLength; + } + + get offset(): number { + return this._offset; + } + + set offset(value: number) { + if (value < 0 || value > this.size) { + throw new Error('Buffer offset is out of bounds'); + } + this._offset = value; + } +} diff --git a/port/js/src/Codec.ts b/port/js/src/Codec.ts new file mode 100644 index 00000000..9c223c91 --- /dev/null +++ b/port/js/src/Codec.ts @@ -0,0 +1,69 @@ +import { type RawType, type Protocol, Protocols } from './protocol'; +import { type Converter, ConverterFactory } from './converters'; +import type { ExtractPayload, GenericConfig, TypeToIdMap, TypeToNameMap } from './Codec.types'; +import { Buffer } from './Buffer'; +import type { ProtocolId, MessageId } from './types'; + +export class Codec { + private typesByName: TypeToNameMap = new Map(); + private typesById: TypeToIdMap = new Map(); + private protocols = new Protocols(); + + constructor(rawTypes: RawType[] = [], protocols: Protocol[] = []) { + this.protocols.load(rawTypes); + const converterFactory = new ConverterFactory(this.protocols); + + for (const { name, proto_id: protoId, types } of protocols) { + const typeMap = new Map(); + const idMap = new Map(); + + for (const [messageId, typeName] of Object.entries(types)) { + const converter = converterFactory.toConverter(typeName); + + typeMap.set(typeName, converter); + idMap.set(parseInt(messageId, 10), converter); + } + this.typesByName.set(name, typeMap); + this.typesById.set(protoId, idMap); + } + } + + public serialize( + name: N, + type: T, + data: ExtractPayload, + ): Buffer { + const types = this.typesByName.get(name as string); + if (!types) { + throw new Error(`Protocol not found: ${name as string}`); + } + + const converter = types.get(type as string); + if (!converter) { + throw new Error(`Converter not found for type: ${type as string}`); + } + + const buffer = new Buffer(new ArrayBuffer(converter.size(data))); + converter.serialize(data, buffer); + + return buffer; + } + + public deserialize( + protocolId: ProtocolId, + messageId: MessageId, + arrayBuffer: ArrayBufferLike, + ): ExtractPayload { + const types = this.typesById.get(protocolId); + if (!types) { + throw new Error(`Protocol not found with ID: ${protocolId}`); + } + + const converter = types.get(messageId); + if (!converter) { + throw new Error(`Converter not found for message ID: ${messageId}`); + } + + return converter.deserialize(new Buffer(arrayBuffer)) as ExtractPayload; + } +} diff --git a/port/js/src/Codec.types.ts b/port/js/src/Codec.types.ts new file mode 100644 index 00000000..90dc1ee8 --- /dev/null +++ b/port/js/src/Codec.types.ts @@ -0,0 +1,14 @@ +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Converter } from './converters/Converter'; +import type { MessageId, ProtocolId, ProtocolName } from './types'; + +export type GenericConfig = Record>; +export type TypeToNameMap = Map>; +export type TypeToIdMap = Map>; + +export type ExtractPayload< + Schema extends Record>, + Name extends keyof Schema, + MessageType extends keyof Schema[Name], +> = Schema[Name][MessageType]; diff --git a/port/js/src/config.ts b/port/js/src/config.ts new file mode 100644 index 00000000..b2f9a578 --- /dev/null +++ b/port/js/src/config.ts @@ -0,0 +1,2 @@ +export const IS_LITTLE_ENDIAN = true; +export const SIZE_TYPE = 'uint32'; diff --git a/port/js/src/converters/Converter.ts b/port/js/src/converters/Converter.ts new file mode 100644 index 00000000..1ddefc99 --- /dev/null +++ b/port/js/src/converters/Converter.ts @@ -0,0 +1,26 @@ +import type { Buffer } from '../Buffer'; +import type { IType, IValue } from '../types'; + +export abstract class Converter { + name: IType; + + constructor(name: IType) { + this.name = name; + } + + serialize(_value: IValue, _buffer: Buffer) { + throw new Error(`Not implemented in abstract class ${this.name} `); + } + + size(_value: IValue): number { + throw new Error(`Not implemented in abstract class ${this.name} `); + } + + deserialize(_buffer: Buffer): IValue { + throw new Error(`Not implemented in abstract class ${this.name} `); + } + + default(): IValue { + return null; + } +} diff --git a/port/js/src/converters/ConverterFactory.ts b/port/js/src/converters/ConverterFactory.ts new file mode 100644 index 00000000..c1f67d9a --- /dev/null +++ b/port/js/src/converters/ConverterFactory.ts @@ -0,0 +1,39 @@ +import type { Converter } from './Converter'; +import { Protocols } from '../protocol/Protocols'; +import { + ScalarConverter, + StructConverter, + ArrayConverter, + TypedArrayConverter, + MapConverter, + EnumConverter, +} from './base'; + +export class ConverterFactory { + constructor(private protocols: Protocols = new Protocols()) { + } + + toConverter(typeName: string): Converter { + const typeDef = this.protocols.getType(typeName); + const getType = this.toConverter.bind(this); + + switch (typeDef.typeClass) { + case 'scalar': + return new ScalarConverter(typeDef.type); + case 'enum': + return new EnumConverter(typeDef, getType); + case 'struct': + return new StructConverter(typeDef, getType); + case 'array': + return new ArrayConverter(typeDef, getType); + case 'typed-array': + return new TypedArrayConverter(typeDef, getType); + case 'map': + return new MapConverter(typeDef, getType); + default: + throw new Error(`Unsupported type class ${typeName}`); + } + } +} + +export type GetType = (typeName: string) => Converter; diff --git a/port/js/src/converters/base/ArrayConverter.ts b/port/js/src/converters/base/ArrayConverter.ts new file mode 100644 index 00000000..05c8d137 --- /dev/null +++ b/port/js/src/converters/base/ArrayConverter.ts @@ -0,0 +1,59 @@ +import type { Buffer } from '../../Buffer'; +import { SIZE_TYPE } from '../../config'; +import type { ArrayTypeDefinition, IValue } from '../../types'; +import { Converter } from '../Converter'; +import type { GetType } from '../ConverterFactory'; + +export class ArrayConverter extends Converter { + private converter: Converter; + private sizeConverter: Converter; + private arraySize?: number; + + constructor(typeDef: ArrayTypeDefinition, getType: GetType) { + super(typeDef.type + typeDef.elementType); + this.converter = getType(typeDef.elementType); + this.sizeConverter = getType(SIZE_TYPE); + this.arraySize = typeDef.arraySize; + } + + serialize(value: Array, buffer: Buffer): void { + const { length } = value; + if (this.arraySize !== undefined && length !== this.arraySize) { + throw new Error(`Array length mismatch: ${length} !== ${this.arraySize}`); + } + + if (this.arraySize === undefined) { + this.sizeConverter.serialize(length, buffer); + } + + for (let i = 0; i < length; i++) { + this.converter.serialize(value[i], buffer); + } + } + + deserialize(buffer: Buffer): Array { + const length = this.arraySize ?? this.sizeConverter.deserialize(buffer); + const result = []; + + for (let i = 0; i < length; i++) { + result[i] = this.converter.deserialize(buffer); + } + + return result; + } + + size(value: Array): number { + const arraySize = value.length; + if (this.arraySize !== undefined && arraySize !== this.arraySize) { + throw new Error(`Array length mismatch: ${arraySize} !== ${this.arraySize}`); + } + + const size = this.arraySize === undefined ? this.sizeConverter.size(arraySize) : 0; + + return size + value.reduce((acc, item) => acc + this.converter.size(item), 0); + } + + default(): Array { + return []; + } +} diff --git a/port/js/src/converters/base/EnumConverter.ts b/port/js/src/converters/base/EnumConverter.ts new file mode 100644 index 00000000..0b012043 --- /dev/null +++ b/port/js/src/converters/base/EnumConverter.ts @@ -0,0 +1,42 @@ +import { Converter } from '../Converter'; +import type { IValue, EnumTypeDefinition } from '../../types'; +import type { Buffer } from '../../Buffer'; +import type { GetType } from './../ConverterFactory'; + +export class EnumConverter extends Converter { + private converter: Converter; + private enumsByName: Record; + private enumsByValue: string[]; + + constructor(typeDef: EnumTypeDefinition, getType: GetType) { + super(typeDef.typeName); + + this.converter = getType(typeDef.type); + + this.enumsByName = typeDef.values.reduce((acc, value) => { + acc[value.name] = value.value; + return acc; + }, {} as Record); + + this.enumsByValue = typeDef.values.reduce((acc, value) => { + acc[value.value] = value.name; + return acc; + }, [] as string[]); + } + + serialize(value: IValue, buffer: Buffer) { + this.converter.serialize(this.enumsByName[value] ?? value, buffer); + } + + deserialize(buffer: Buffer) { + return this.converter.deserialize(buffer); + } + + size(value: IValue) { + return this.converter.size(value); + } + + default() { + return this.enumsByValue[0]; + } +} diff --git a/port/js/src/converters/base/MapConverter.ts b/port/js/src/converters/base/MapConverter.ts new file mode 100644 index 00000000..b0cb04fd --- /dev/null +++ b/port/js/src/converters/base/MapConverter.ts @@ -0,0 +1,57 @@ +import type { IValue, MapTypeDefinition } from '../../types'; +import { Converter } from '../Converter'; +import type { Buffer } from '../../Buffer'; +import type { GetType } from '../ConverterFactory'; +import { SIZE_TYPE } from '../../config'; + +export class MapConverter extends Converter { + protected keyConverter: Converter; + protected valueConverter: Converter; + protected dynamicSizeConverter: Converter; + + constructor(typeDef: MapTypeDefinition, getType: GetType) { + super(typeDef.typeClass); + this.keyConverter = getType(typeDef.keyType); + this.valueConverter = getType(typeDef.valueType); + this.dynamicSizeConverter = getType(SIZE_TYPE); + } + + serialize(value: Map | Record, buffer: Buffer): void { + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value); + this.dynamicSizeConverter.serialize(entries.length, buffer); + + for (const [key, val] of entries) { + this.keyConverter.serialize(key, buffer); + this.valueConverter.serialize(val, buffer); + } + } + + deserialize(buffer: Buffer): Map { + const size = this.dynamicSizeConverter.deserialize(buffer); + const result = new Map(); + + for (let i = 0; i < size; i++) { + const key = this.keyConverter.deserialize(buffer); + const value = this.valueConverter.deserialize(buffer); + result.set(key, value); + } + + return result; + } + + size(value: Map | Record): number { + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value); + let totalSize = this.dynamicSizeConverter.size(entries.length); + + for (const [key, val] of entries) { + totalSize += this.keyConverter.size(key); + totalSize += this.valueConverter.size(val); + } + + return totalSize; + } + + default(): Map { + return new Map(); + } +} diff --git a/port/js/src/converters/base/ScalarConverter.ts b/port/js/src/converters/base/ScalarConverter.ts new file mode 100644 index 00000000..e72d6cb5 --- /dev/null +++ b/port/js/src/converters/base/ScalarConverter.ts @@ -0,0 +1,169 @@ +import type { Buffer } from '../../Buffer'; +import type { IValue, IBasicType } from '../../types'; +import { Converter } from './../Converter'; +import { IS_LITTLE_ENDIAN } from '../../config'; +import { decodeUTF8, encodeUTF8 } from '../../utils/utf8'; + +interface ScalarTypeConfig { + size: number | ((value: IValue) => number); + read: (v: DataView, offset: number) => IValue; + write: (v: DataView, offset: number, value: IValue) => void; + default: IValue; + typedArray?: boolean; +} + +export const SCALAR_TYPES = new Map([ + ['int8', { + size: 1, + read: (v, o) => v.getInt8(o), + write: (v, o, a) => v.setInt8(o, a as number), + default: 0, + }], + ['uint8', { + size: 1, + read: (v, o) => v.getUint8(o), + write: (v, o, a) => v.setUint8(o, a as number), + default: 0, + }], + ['int16', { + size: 2, + read: (v, o) => v.getInt16(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setInt16(o, a as number, IS_LITTLE_ENDIAN), + default: 0, + }], + ['uint16', { + size: 2, + read: (v, o) => v.getUint16(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setUint16(o, a as number, IS_LITTLE_ENDIAN), + default: 0, + }], + ['int32', { + size: 4, + read: (v, o) => v.getInt32(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setInt32(o, a as number, IS_LITTLE_ENDIAN), + default: 0, + }], + ['uint32', { + size: 4, + read: (v, o) => v.getUint32(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setUint32(o, a as number, IS_LITTLE_ENDIAN), + default: 0, + }], + ['int64', { + size: 8, + read: (v, o) => v.getBigInt64(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setBigInt64(o, BigInt(a), IS_LITTLE_ENDIAN), + default: BigInt(0), + }], + ['uint64', { + size: 8, + read: (v, o) => v.getBigUint64(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setBigUint64(o, BigInt(a), IS_LITTLE_ENDIAN), + default: BigInt(0), + }], + ['float32', { + size: 4, + read: (v, o) => v.getFloat32(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setFloat32(o, a as number, IS_LITTLE_ENDIAN), + default: 0.0, + }], + ['float64', { + size: 8, + read: (v, o) => v.getFloat64(o, IS_LITTLE_ENDIAN), + write: (v, o, a) => v.setFloat64(o, a as number, IS_LITTLE_ENDIAN), + default: 0.0, + }], + ['bool', { + size: 1, + read: (v, o) => Boolean(v.getUint8(o)), + write: (v, o, a) => v.setUint8(o, (a as boolean) ? 1 : 0), + default: false, + }], + ['char', { + size: 1, + read: (v, o) => String.fromCharCode(v.getUint8(o)), + write: (v, o, a) => v.setUint8(o, (a as string).charCodeAt(0)), + default: ' ', + }], + ['string', { + size: (value: string) => value.length + 4, + read: (v, s) => decodeUTF8(new Uint8Array(v.buffer, s + 4, v.getUint32(s, IS_LITTLE_ENDIAN))), + write: (v, s, a: string) => { + const size = a.length; + v.setUint32(s, size, IS_LITTLE_ENDIAN); + const uint8View = new Uint8Array(v.buffer, v.byteOffset, v.byteLength); + uint8View.set(encodeUTF8(a), s + 4); + + return size + 4; + }, + default: () => '', + }], + ['bytes', { + size: (value: IValue) => { + const { length } = (value as Uint8Array); + return 4 + length; + }, + read: (v, o) => { + const length = v.getUint32(o, IS_LITTLE_ENDIAN); + o += 4; + return new Uint8Array(v.buffer, v.byteOffset + o, length); + }, + write: (v, o, a) => { + const bytes = a as Uint8Array; + v.setUint32(o, bytes.length, IS_LITTLE_ENDIAN); + o += 4; + new Uint8Array(v.buffer, v.byteOffset + o, bytes.length).set(bytes); + }, + default: new Uint8Array(0), + }], +]); + +export class ScalarConverter extends Converter { + private config: ScalarTypeConfig; + + constructor(name: IBasicType) { + super(name); + + const config = SCALAR_TYPES.get(name); + if (!config) { + throw new Error(`Unsupported scalar type "${name}"`); + } + + this.config = config; + } + + serialize(value: IValue, buffer: Buffer): void { + const { config } = this; + + this.config.write(buffer.dataView, buffer.offset, value); + + if (typeof config.size === 'number') { + buffer.offset += config.size; + } else { + buffer.offset += config.size(value); + } + } + + deserialize(buffer: Buffer): IValue { + const value = this.config.read(buffer.dataView, buffer.offset); + + if (typeof this.config.size === 'number') { + buffer.offset += this.config.size; + } else { + buffer.offset += this.config.size(value); + } + + return value; + } + + size(value: IValue): number { + if (typeof this.config.size === 'number') { + return this.config.size; + } + return this.config.size(value); + } + + default(): IValue { + return this.config.default; + } +} diff --git a/port/js/src/converters/base/StructConverter.ts b/port/js/src/converters/base/StructConverter.ts new file mode 100644 index 00000000..847e125a --- /dev/null +++ b/port/js/src/converters/base/StructConverter.ts @@ -0,0 +1,69 @@ +import type { Buffer } from '../../Buffer'; +import type { IName, IValue, StructTypeDefinition } from '../../types'; +import { Converter } from '../Converter'; +import type { GetType } from '../ConverterFactory'; + +export class StructConverter extends Converter { + convertorsList: { converter: Converter, name: string }[] = []; + private static RESERVED_WORDS: Set = new Set(Object.getOwnPropertyNames(Object.prototype)); + parentObject: Record; + + constructor(typeDef: StructTypeDefinition, getType: GetType) { + super(typeDef.typeName); + const fieldsSet = new Set(); + + typeDef.fields?.forEach((field) => { + if (fieldsSet.has(field.name)) { + throw new Error(`Field ${field.name} is duplicated in ${this.name}`); + } + fieldsSet.add(field.name); + + if (StructConverter.RESERVED_WORDS.has(field.name)) { + throw new Error(`Field ${field.name} is a reserved word in JavaScript`); + } + + const converter = getType(field.type); + if (!converter) { + throw new Error(`Converter for type ${field.type} is not found in ${this.name}`); + } + + this.convertorsList.push({ converter, name: field.name }); + }); + + this.parentObject = Object.fromEntries( + this.convertorsList.map(({ name, converter }) => [name, converter.default()]), + ); + } + + serialize(value: IValue, buffer: Buffer) { + this.convertorsList.forEach(({ converter, name }) => { + const data = value[name]; + if (data === null || data === undefined) { + throw new Error(`Field ${name} is not found in ${this.name}`); + } + converter.serialize(data, buffer); + }); + } + + deserialize(buffer: Buffer): IValue { + return this.convertorsList.reduce((acc, { converter, name }) => { + acc[name] = converter.deserialize(buffer); + return acc; + }, {} as Record); + } + + size(value: IValue): number { + return this.convertorsList.reduce((acc, { converter, name }) => { + const data = value[name]; + if (data === null || data === undefined) { + throw new Error(`Field ${name} is not found in ${this.name}`); + } + + return acc + converter.size(data); + }, 0); + } + + default(): IValue { + return this.parentObject; + } +} diff --git a/port/js/src/converters/base/TypedArrayConverter.ts b/port/js/src/converters/base/TypedArrayConverter.ts new file mode 100644 index 00000000..376c122e --- /dev/null +++ b/port/js/src/converters/base/TypedArrayConverter.ts @@ -0,0 +1,95 @@ +import type { Buffer } from '../../Buffer'; +import { SIZE_TYPE } from '../../config'; +import type { IType, TypedArrayTypeDefinition } from '../../types'; +import { Converter } from '../Converter'; +import type { GetType } from '../ConverterFactory'; + +const TYPED_ARRAY_MAP = new Map([ + ['int8', Int8Array], + ['uint8', Uint8Array], + ['int16', Int16Array], + ['uint16', Uint16Array], + ['int32', Int32Array], + ['uint32', Uint32Array], + ['int64', BigInt64Array], + ['uint64', BigUint64Array], + ['float32', Float32Array], + ['float64', Float64Array], +]); + +export class TypedArrayConverter extends Converter { + private converter: Converter; + private sizeConverter: Converter; + private arraySize?: number; + private TypedArrayConstructor: TypedArrayConstructor; + + constructor(typeDef: TypedArrayTypeDefinition, getType: GetType) { + super(typeDef.type); + this.converter = getType(typeDef.elementType); + this.sizeConverter = getType(SIZE_TYPE); + this.arraySize = typeDef.arraySize; + + const arrayConstructor = TYPED_ARRAY_MAP.get(typeDef.elementType); + + if (!arrayConstructor) { + throw new Error(`Unknown typed array type: ${typeDef.elementType}`); + } + this.TypedArrayConstructor = arrayConstructor; + } + + serialize(value: TypedArray, buffer: Buffer): void { + const { length } = value; + + if (this.arraySize !== undefined && length !== this.arraySize) { + throw new Error(`Array length mismatch: ${length} !== ${this.arraySize}`); + } + + if (this.arraySize === undefined) { + this.sizeConverter.serialize(length, buffer); + } + + for (let i = 0; i < length; i++) { + this.converter.serialize(value[i], buffer); + } + } + + deserialize(buffer: Buffer): TypedArray { + const length = this.arraySize ?? this.sizeConverter.deserialize(buffer); + const TypedArray = this.TypedArrayConstructor; + + const typedArray = new TypedArray(buffer.dataView.buffer.slice( + buffer.offset, + buffer.offset + length * TypedArray.BYTES_PER_ELEMENT, + )); + + buffer.offset += typedArray.byteLength; + return typedArray; + } + + size(value: TypedArray): number { + const { length } = value; + if (this.arraySize !== undefined && length !== this.arraySize) { + throw new Error(`Array length mismatch: ${length} !== ${this.arraySize}`); + } + + const size = this.arraySize === undefined ? this.sizeConverter.size(length) : 0; + return size + this.converter.size(value) * length; + } + + default(): TypedArray { + return new this.TypedArrayConstructor(this.arraySize ?? 0); + } +} + +export type TypedArrayConstructor = + Int8ArrayConstructor | Uint8ArrayConstructor | + Int16ArrayConstructor | Uint16ArrayConstructor | + Int32ArrayConstructor | Uint32ArrayConstructor | + Float32ArrayConstructor | Float64ArrayConstructor | + BigUint64ArrayConstructor | BigInt64ArrayConstructor | + Float64ArrayConstructor; + +export type TypedArray = + Int8Array | Uint8Array | Int16Array | Uint16Array | + Int32Array | Uint32Array | Float32Array | Float64Array | + BigUint64Array | BigInt64Array | Float64Array; diff --git a/port/js/src/converters/base/index.ts b/port/js/src/converters/base/index.ts new file mode 100644 index 00000000..d7b097ec --- /dev/null +++ b/port/js/src/converters/base/index.ts @@ -0,0 +1,6 @@ +export { ScalarConverter } from './ScalarConverter'; +export { ArrayConverter } from './ArrayConverter'; +export { TypedArrayConverter } from './TypedArrayConverter'; +export { MapConverter } from './MapConverter'; +export { EnumConverter } from './EnumConverter'; +export { StructConverter } from './StructConverter'; diff --git a/port/js/src/converters/index.ts b/port/js/src/converters/index.ts new file mode 100644 index 00000000..b7ed7bb5 --- /dev/null +++ b/port/js/src/converters/index.ts @@ -0,0 +1,3 @@ +export * from './base'; +export { type GetType, ConverterFactory } from './ConverterFactory'; +export { Converter } from './Converter'; diff --git a/port/js/src/index.ts b/port/js/src/index.ts new file mode 100644 index 00000000..b3a29340 --- /dev/null +++ b/port/js/src/index.ts @@ -0,0 +1,7 @@ +export * from './converters'; +export * from './types'; +export * from './protocol'; + +export { Buffer } from './Buffer'; +export { Codec } from './Codec'; +export * from './Codec.types'; diff --git a/port/js/src/messgen.d.ts b/port/js/src/messgen.d.ts deleted file mode 100644 index ab2e846c..00000000 --- a/port/js/src/messgen.d.ts +++ /dev/null @@ -1,92 +0,0 @@ -export type Field = { - name: string - type: string -} - -export type Schema = { - id: number - fields: Field[] -} - -export class Struct { - constructor(schema: Schema) - - get schema(): Schema - - get id(): number - - get size(): number - - get fields(): Field[] - - set(schema: Schema): void -} - -declare type Messages = { - __id__: Struct[] - __name__: KEYS[] -} & Record & - addPrefixToObject>, 'MSG_'> & - Record - -export const HEADER_STRUCT: Struct - -type Obj = Record - -export class Buffer { - constructor(data: ArrayBufferLike, useTypedArray?: boolean) - static deserialize(messages, data, headerStruct?: Struct, includeMessages?: Messages) - - static mergeArrayBuffers(tArrs: Array, type: Uint8ArrayConstructor): Uint8Array - - static appendBuffer(buffer1: ArrayBuffer, buffer2: ArrayBuffer): Uint8Array - - static calcSize(fields: Field[], includeMessages?: Messages) - - static createValueArray(schemaFields: Schema['fields'], obj: Obj, includeMessages?: Messages) - - static serializeMessage( - struct: Struct, - obj: Obj, - headerStruct?: Struct, - includeMessages?: Messages - ): Uint8Array - - static serializeObj(schemaFields: Schema['fields'], obj: Obj, includeMessages?: Messages) - - static writeDataView(fields: Field[], dataView, includeMessages?: Messages, offset?: number) - - static serialize(fields: Field[], includeMessages?: Messages) - - get size(): number - - get dataView(): DataView - - set(arrayBuffer: ArrayBufferLike): void - - deserialize(struct: Struct, offset?: number, sizeOffset?: number): Obj -} - -export function initializeMessages( - messagesJson: Record, - headerSchema?: Schema -): Messages - -/* - ____,-------------------------------,____ - \ | HELPERS | / - /___|-------------------------------|___\ - -*/ - -// https://stackoverflow.com/questions/71824852/convert-typescript-object-keys-to-uppercase -// All string-type keys in the object, and then uppercased -type UppercaseStringKeys = Uppercase> -// An object consisting of the above keys with the same values from the original object -type UppercaseObjectKeys = { - [x in UppercaseStringKeys]: x extends string ? T[Lowercase] : T[x] -} -// https://stackoverflow.com/questions/57510388/define-prefix-for-object-keys-using-types-in-typescript -type addPrefixToObject = { - [K in keyof T as K extends string ? `${P}${K}` : never]: T[K] -} diff --git a/port/js/src/messgen.js b/port/js/src/messgen.js deleted file mode 100644 index b9440585..00000000 --- a/port/js/src/messgen.js +++ /dev/null @@ -1,653 +0,0 @@ -'use strict' - -import { encodeUTF8, decodeUTF8 } from './utf8.js' - -const IS_LITTLE_ENDIAN = true - -const DYNAMIC_SIZE_TYPE = 'Uint32' - -/** - * - * Read function returns value from byte array. - * Write function returns type byte size. - */ -const basicTypes = [ - { - name: 'Char', - size: 1, - read: (v, s) => String.fromCharCode(v.getInt8(s, IS_LITTLE_ENDIAN)), - write: (v, s, a) => { - v.setInt8(s, a ? a.toString().charCodeAt(0) : 0, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Array - }, - { - name: 'Int8', - size: 1, - read: (v, s) => v.getInt8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Int8Array - }, - { - name: 'Uint8', - size: 1, - read: (v, s) => v.getUint8(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint8(s, a, IS_LITTLE_ENDIAN) - return 1 - }, - typedArray: Uint8Array - }, - { - name: 'Int16', - size: 2, - read: (v, s) => v.getInt16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Int16Array - }, - { - name: 'Uint16', - size: 2, - read: (v, s) => v.getUint16(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint16(s, a, IS_LITTLE_ENDIAN) - return 2 - }, - typedArray: Uint16Array - }, - { - name: 'Int32', - size: 4, - read: (v, s) => v.getInt32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setInt32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Int32Array - }, - { - name: 'Uint32', - size: 4, - read: (v, s) => v.getUint32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setUint32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Uint32Array - }, - { - name: 'Int64', - size: 8, - read: (v, s) => v.getBigInt64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigInt64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigInt64Array - }, - { - name: 'Uint64', - size: 8, - read: (v, s) => v.getBigUint64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setBigUint64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: BigUint64Array - }, - { - name: 'Float', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - } - }, - { - name: 'Float32', - size: 4, - read: (v, s) => v.getFloat32(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat32(s, a, IS_LITTLE_ENDIAN) - return 4 - }, - typedArray: Float32Array - }, - { - name: 'Double', - size: 8, - read: (v, s) => v.getFloat64(s, IS_LITTLE_ENDIAN), - write: (v, s, a) => { - v.setFloat64(s, a, IS_LITTLE_ENDIAN) - return 8 - }, - typedArray: Float64Array - }, - { - name: 'String', - size: 4, - read: (v, s) => decodeUTF8(new Uint8Array(v.buffer, s + 4, v.getUint32(s, IS_LITTLE_ENDIAN))), - write: (v, s, a) => { - let size = a.length - v.setUint32(s, size, true) - for (let i = 0, s2 = s + 4; i < size; i++) { - v.setUint8(s2 + i, a[i], true) - } - return size + 4 - }, - typedArray: Array - } -] - -let typeIndex = [], - typeSize = [] - -let readFunc = [], - writeFunc = [], - typedArray = [] - -for (let i = 0; i < basicTypes.length; i++) { - let ti = basicTypes[i] - typeIndex[ti.name] = i - typeSize[i] = ti.size - readFunc[i] = ti.read - writeFunc[i] = ti.write - typedArray[i] = ti.typedArray -} - -const DYN_TYPE = typeIndex[DYNAMIC_SIZE_TYPE] -const DYN_TYPE_SIZE = typeSize[DYN_TYPE] -const DYN_READ = readFunc[DYN_TYPE] -const DYN_WRITE = writeFunc[DYN_TYPE] - -function parseType(typeStr, includeMessages) { - let a = typeStr.split('['), - name = a[0].trim() - - let size, - isComplex = false - - let type = typeIndex[name] - - if (type !== undefined) { - size = typeSize[type] - } else if (includeMessages && includeMessages[name]) { - type = includeMessages[name] - size = type.size - isComplex = true - } else { - throw new Error(`Unknown type: ${name}, if is complex type you must define before the struct. `) - } - - let length = parseInt(a[1]) - - return { - typeIndex: type, - typeSize: size, - length: isNaN(length) ? 0 : length, - isArray: a.length === 2, - isComplex: isComplex - } -} - -/** - * class Struct - */ -export class Struct { - constructor(schema, includeMessages) { - this._id = 0 - this._size = 0 - this._fields = null - this._schema = null - - this._includeMessages = includeMessages - - this.set(schema) - } - - get schema() { - return this._schema - } - - get id() { - return this._id - } - - get size() { - return this._size - } - - get fields() { - return this._fields - } - - set(schema) { - if (schema) { - this._id = schema.id || 0 - this._size = 0 - this._fields = new Array(schema.fields.length) - this._schema = schema - this._init() - } - } - - _init() { - let schemaFields = this._schema.fields - - let offset = 0 - - for (let i = 0, len = schemaFields.length; i < len; i++) { - let si = schemaFields[i], - tp = parseType(si.type, this._includeMessages) - - this._fields[i] = { - name: si.name, - type: si.type, - _offset: offset, - _prop: tp - } - - if (tp.isArray) { - if (tp.length === 0) { - offset += DYN_TYPE_SIZE - } else { - offset += tp.typeSize * tp.length - } - } else { - offset += tp.typeSize - } - } - - this._size = offset - } -} - -// uint32 seq; //!< Sequence number -// uint16 size; //!< Message payload size -// uint8 cls; //!< Message class -// uint8 msg_id; //!< Message type ID - -export const HEADER_STRUCT = new Struct({ - fields: [ - { name: 'seq', type: 'Uint32' }, - { name: 'cls', type: 'Uint8' }, - { name: 'msg_id', type: 'Uint8' }, - { name: 'size', type: 'Uint32' } - ] -}) - -/** - * class Buffer - */ -export class Buffer { - _useTypedArray = false - - constructor(arrayBuffer, useTypedArray = false) { - this._dataView = new DataView(arrayBuffer) - this._dynamicOffset = 0 - this._useTypedArray = useTypedArray - } - - // TODO: перенести в модуль messages - // а модуль messages генерализировать - static deserialize(messages, data, headerStruct = HEADER_STRUCT, includeMessages) { - let res = [] - let buf = new Buffer(data) - let cur = 0 - while (cur < buf.size) { - let h = buf.deserialize(headerStruct, cur), - m = buf.deserialize(messages.__id__[h.msg_id], cur + h.__SIZE__) - m.__MSG_ID__ = h.msg_id - cur += h.__SIZE__ + m.__SIZE__ - res.push(m) - } - return res - } - - static mergeArrayBuffers(tArrs, type = Uint8Array) { - const ret = new type(tArrs.reduce((acc, tArr) => acc + tArr.byteLength, 0)) - let off = 0 - tArrs.forEach((tArr) => { - ret.set(new type(tArr), off) - off += tArr.byteLength - }) - return ret - } - - static appendBuffer(buffer1, buffer2) { - var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) - tmp.set(new Uint8Array(buffer1), 0) - tmp.set(new Uint8Array(buffer2), buffer1.byteLength) - return tmp.buffer - } - - static calcSize(fields, includeMessages) { - let size = 0 - - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - tp = (fi._prop = parseType(fi.type, includeMessages)) - - if (tp.isArray) { - if (tp.isComplex) { - if (tp.length === 0) { - size += DYN_TYPE_SIZE - } - for (let i = 0; i < fi.value.length; i++) { - let arr = Buffer.createValueArray(fi._prop.typeIndex.fields, fi.value[i], includeMessages) - size += Buffer.calcSize(arr, includeMessages) - } - } else { - let arrayLength = 0 - - // Dynamic size array - if (tp.length === 0) { - arrayLength = fi.value.length - size += tp.typeSize * arrayLength + DYN_TYPE_SIZE // for dynamic array length descriptor - } else { - // static size array - arrayLength = tp.length - size += tp.typeSize * arrayLength - } - - if (tp.typeIndex === typeIndex.String) { - fi._bytes = [] - for (let i = 0; i < arrayLength; i++) { - let b = encodeUTF8(fi.value[i] || '') - fi._bytes.push(b) - size += b.length - } - } - } - } else if (tp.typeIndex === typeIndex.String) { - fi._bytes = encodeUTF8(fi.value || '') - size += tp.typeSize + fi._bytes.length - } else { - if (tp.isComplex) { - size += Buffer.calcSize(fi.value, includeMessages) - } else { - size += tp.typeSize - } - } - } - - return size - } - - static createValueArray(schemaFields, obj, includeMessages) { - const len = schemaFields.length - - let arr = new Array(len) - - for (let k = 0; k < len; k++) { - let sk = schemaFields[k], - type = sk.type - - if (includeMessages && includeMessages[type]) { - arr[k] = { - value: Buffer.createValueArray(includeMessages[type].fields, obj[sk.name], includeMessages), - type: type - } - } else { - arr[k] = { value: obj[sk.name], type: type } - } - } - - return arr - } - - static serializeMessage(struct, obj, headerStruct = HEADER_STRUCT, includeMessages) { - let arr = Buffer.createValueArray(struct.fields, obj, includeMessages) - - let messageSize = Buffer.calcSize(arr, includeMessages) - - let headerBuf = Buffer.serializeObj( - headerStruct.fields, - { - seq: obj.seq, - size: messageSize, - cls: obj.cls, - msg_id: struct.id - }, - includeMessages - ) - - return Buffer.appendBuffer(headerBuf, Buffer.serialize(arr, includeMessages)) - } - - static serializeObj(schemaFields, obj, includeMessages) { - let arr = Buffer.createValueArray(schemaFields, obj, includeMessages) - - return Buffer.serialize(arr, includeMessages) - } - - static writeDataView(fields, dataView, includeMessages, offset = 0) { - for (let i = 0, len = fields.length; i < len; i++) { - let fi = fields[i], - p = fi._prop - - if (p.isArray) { - let arrayLength = p.length - - // Setting array size value for dynamic array size - if (arrayLength === 0) { - arrayLength = fi.value.length - offset += DYN_WRITE(dataView, offset, fi.value.length) - } - - // Write array - for (let j = 0; j < arrayLength; j++) { - let val = (fi._bytes && fi._bytes[j]) || fi.value[j] - if (p.isComplex) { - let valArr = Buffer.createValueArray(p.typeIndex.fields, fi.value[j], includeMessages) - let size = Buffer.calcSize(valArr, includeMessages) - Buffer.writeDataView(valArr, dataView, includeMessages, offset) - offset += size - } else { - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } else { - if (p.isComplex) { - let size = Buffer.calcSize(fi.value, includeMessages) - Buffer.writeDataView(fi.value, dataView, includeMessages, offset) - offset += size - } else { - let val = fi._bytes || fi.value - offset += writeFunc[p.typeIndex](dataView, offset, val) - } - } - } - - return offset - } - - static serialize(fields, includeMessages) { - let allSize = Buffer.calcSize(fields, includeMessages) - - let arrayBuffer = new ArrayBuffer(allSize), - dv = new DataView(arrayBuffer) - - Buffer.writeDataView(fields, dv, includeMessages) - - return arrayBuffer - } - - get size() { - return this._dataView.buffer.byteLength - } - - get dataView() { - return this._dataView - } - - set(arrayBuffer) { - this._dataView = null - this._dataView = new DataView(arrayBuffer) - } - - deserialize(struct, offset = 0, sizeOffset = 0) { - this._dynamicOffset = 0 - let res = this.__deserialize__(struct, offset, sizeOffset) - res.__SIZE__ = struct.size + this._dynamicOffset + sizeOffset - return res - } - - __deserialize__(struct, offset, sizeOffset) { - this._includeMessages = struct._includeMessages - - let fields = struct.fields, - dv = this._dataView, - res = {} - - let currOffset = 0 - - for (let f = 0, len = fields.length; f < len; f++) { - let fi = fields[f], - p = fi._prop - - currOffset = offset + fi._offset - const ArrayType = this._useTypedArray ? typedArray[p.typeIndex] ?? Array : Array - - if (p.isArray) { - if (p.length === 0) { - // - // Dynamic size array - // - - let length = DYN_READ(dv, currOffset + this._dynamicOffset) - - res[fi.name] = new ArrayType(length) - - let currOffset_dyn = DYN_TYPE_SIZE + currOffset - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32( - currOffset_dyn + this._dynamicOffset + j * p.typeSize, - true - ) - } - } else { - if (p.isComplex) { - for (let j = 0; j < length; j++) { - res[fi.name][j] = this.__deserialize__( - p.typeIndex, - currOffset_dyn + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset_dyn + this._dynamicOffset + j * p.typeSize - ) - } - } - } - - this._dynamicOffset += length * p.typeSize - } else { - // - //Static size array - // - - res[fi.name] = new ArrayType(p.length) - - if (p.typeIndex === typeIndex.String) { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset + j * p.typeSize, true) - } - } else { - if (p.isComplex) { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = this.__deserialize__( - p.typeIndex, - currOffset + j * p.typeSize, - sizeOffset - ) - } - } else { - for (let j = 0; j < p.length; j++) { - res[fi.name][j] = readFunc[p.typeIndex]( - dv, - currOffset + this._dynamicOffset + j * p.typeSize - ) - } - } - } - } - } else { - if (p.isComplex) { - res[fi.name] = this.__deserialize__(p.typeIndex, currOffset, sizeOffset) - } else { - res[fi.name] = readFunc[p.typeIndex](dv, currOffset + this._dynamicOffset) - - if (p.typeIndex === typeIndex.String) { - this._dynamicOffset += dv.getUint32(currOffset + this._dynamicOffset, true) - } - } - } - } - - return res - } -} - -/** - * Creates message struct namespace - * @param {*} messagesJson - Array of messages schemas - * @param {*} headerSchema - message header schema - */ -export function initializeMessages(messagesJson, headerSchema) { - let res = { - __id__: [], - __name__: [], - HEADER_STRUCT: headerSchema ? new Struct(headerSchema) : HEADER_STRUCT - } - - for (let m of getKeysWithSortById(messagesJson)) { - let name = m.trim(), - messageObj = messagesJson[m], - msg = 'MSG_' + name.toUpperCase(), - id = messageObj.id - - if (!res.__id__[id]) { - let msg_struct = new Struct(messageObj, res) - - res.__id__[id] = msg_struct - res.__name__[id] = m.trim() - res[msg] = msg_struct - res[name] = msg_struct - } else { - console.warn(`Warning: message ${id} ${msg} already exists.`) - } - } - - return res -} - -const getKeysWithSortById = (obj) => { - let keys = Object.keys(obj) - keys.sort((a, b) => { - return obj[a].id - obj[b].id - }) - return keys -} diff --git a/port/js/src/protocol/Protocols.ts b/port/js/src/protocol/Protocols.ts new file mode 100644 index 00000000..5ef492ea --- /dev/null +++ b/port/js/src/protocol/Protocols.ts @@ -0,0 +1,98 @@ +import type { IBasicType, IName, IType, TypeDefinition } from '../types'; +import { type RawType, TypeClass } from './Protocols.types'; + +const SCALAR_TYPES_INFO = new Map([ + ['int8', true], + ['uint8', true], + ['int16', true], + ['uint16', true], + ['int32', true], + ['uint32', true], + ['int64', true], + ['uint64', true], + ['float32', true], + ['float64', true], + ['char', false], + ['string', false], + ['bytes', false], + ['bool', false], +]); + +export class Protocols { + private types = new Map(); + + load(types: RawType[]): void { + types.forEach((type) => { + if (type.type_class === TypeClass.STRUCT) { + this.types.set(type.type, { + typeClass: 'struct', + fields: type.fields, + typeName: type.type, + }); + } else if (type.type_class === TypeClass.ENUM) { + this.types.set(type.type, { + typeClass: 'enum', + type: type.base_type, + typeName: type.type, + values: type.values, + }); + } + }); + } + + getType(typeName: IType): TypeDefinition { + if (SCALAR_TYPES_INFO.has(typeName)) { + return { type: typeName as IBasicType, typeClass: 'scalar' }; + } + if (typeName.endsWith(']')) { + return this.parseArrayType(typeName); + } + if (typeName.endsWith('}')) { + return this.parseMapType(typeName); + } + return this.resolveType(typeName); + } + + private parseArrayType(typeName: string): TypeDefinition { + const [elementType, size] = this.parseArray(typeName); + + const isTyped = SCALAR_TYPES_INFO.get(elementType); + return { + type: typeName, + typeClass: isTyped ? 'typed-array' : 'array', + elementType, + arraySize: size, + }; + } + + private parseMapType(typeName: string): TypeDefinition { + const [keyType, valueType] = this.parseMap(typeName); + return { + type: typeName, + typeClass: 'map', + keyType, + valueType, + }; + } + + private parseArray(typeName: string): [string, number | undefined] { + const parts = typeName.slice(0, -1).split('['); + return [ + parts.slice(0, -1).join('['), + parts[parts.length - 1] ? parseInt(parts[parts.length - 1], 10) : undefined, + ]; + } + + private parseMap(typeName: string): [string, string] { + const parts = typeName.slice(0, -1).split('{'); + return [parts[parts.length - 1], parts.slice(0, -1).join('{')]; + } + + private resolveType(typeName: string): TypeDefinition { + const typeDefinition = this.types.get(typeName); + if (!typeDefinition) { + throw new Error(`Unknown type: ${typeName} not found`); + } + return typeDefinition; + } +} diff --git a/port/js/src/protocol/Protocols.types.ts b/port/js/src/protocol/Protocols.types.ts new file mode 100644 index 00000000..e3df3713 --- /dev/null +++ b/port/js/src/protocol/Protocols.types.ts @@ -0,0 +1,47 @@ +import type { IName, INumberType, Field } from '../types'; + +export interface RawStructType { + type: string; + type_class: '8' + fields: Field[]; +} + +export interface RawEnumType { + type: string; + type_class: '7'; + base_type: INumberType; + values: EnumValue[]; +} + +export type RawType = RawStructType | RawEnumType; + +interface EnumValue { + name: IName; + value: number; +} + +export enum TypeClass { + STRUCT = '8', + ENUM = '7', +} + +export interface StructTypeClass { + type_class: 'struct'; + comment?: string; + fields: Field[] | null; +} + +export interface EnumTypeClass { + type_class: 'enum'; + comment?: string; + base_type: INumberType; + values: EnumValue[]; +} + +export type StructureType = StructTypeClass | EnumTypeClass; + +export interface Protocol { + name: string; + proto_id: number; + types: Record; +} diff --git a/port/js/src/protocol/index.ts b/port/js/src/protocol/index.ts new file mode 100644 index 00000000..2b394b36 --- /dev/null +++ b/port/js/src/protocol/index.ts @@ -0,0 +1,2 @@ +export { Protocols } from './Protocols'; +export * from './Protocols.types'; diff --git a/port/js/src/types.ts b/port/js/src/types.ts new file mode 100644 index 00000000..1b1e3ef7 --- /dev/null +++ b/port/js/src/types.ts @@ -0,0 +1,107 @@ +/* + ____,-------------------------------------,____ + \ | Nominal types | / + /___|-------------------------------------|___\ + +*/ +declare const NominalType: unique symbol; +// String-typed unique nominal types generator: +// +// let a: NominalStrict<'DateTime'> = '2021-10-26T13:53:05.997Z'; +// let b: NominalStrict<'DayDate'> = '2021-10-26'; +// a = b; - compile-time error; +export type NominalStrict = Type & { [NominalType]: NAME }; +export type Nominal = Type & { [NominalType]?: NAME }; + +export type IName = string; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type IValue = Nominal<'Value', any>; +export type ProtocolId = Nominal<'ProtocolId', number>; +export type MessageId = Nominal<'MessageId', number>; +export type ProtocolName = Nominal<'ProtocolName', string>; + +export type INumberType = + 'uint8' | + 'int8' | + 'uint16' | + 'int16' | + 'uint32' | + 'int32' | + 'uint64' | + 'int64' | + 'float32' | + 'float64'; + +export type IBasicType = + INumberType | + 'string' | + 'bool' | + 'char' | + 'bytes'; + +type ArrayDynamicSize = '[]'; +type ArrayFixSize = `[${number}]`; +type MapType = `{${IBasicType}}`; + +type SubType = ArrayDynamicSize | ArrayFixSize | MapType | ''; + +export type IType = `${IName | IBasicType}${SubType}${SubType}${SubType}`; + +export interface Field { + name: IName + type: IType +} + +export interface EnumValue { + name: IName; + value: number; + comment?: string; +} + +export type ScalarTypeDefinition = { + type: IBasicType; + typeClass: 'scalar'; +}; + +export type TypedArrayTypeDefinition = { + type: IType; + typeClass: 'typed-array'; + elementType: IType; + arraySize?: number; +}; + +export type ArrayTypeDefinition = { + type: IType; + typeClass: 'array'; + elementType: IType; + arraySize?: number; + size?: number; +}; + +export type MapTypeDefinition = { + type: IType; + typeClass: 'map'; + keyType: IType; + valueType: IType; +}; + +export type StructTypeDefinition = { + typeClass: 'struct'; + fields: Field[] | null; + typeName: IName; +}; + +export type EnumTypeDefinition = { + type: IType; + typeClass: 'enum'; + values: EnumValue[]; + typeName: IName; +}; + +export type TypeDefinition = + ScalarTypeDefinition | + TypedArrayTypeDefinition | + ArrayTypeDefinition | + MapTypeDefinition | + StructTypeDefinition | + EnumTypeDefinition; diff --git a/port/js/src/utf8.js b/port/js/src/utf8.js deleted file mode 100644 index e37c8bc4..00000000 --- a/port/js/src/utf8.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict' - -// This is free and unencumbered software released into the public domain. -// https://gist.github.com/pascaldekloe - -// Marshals a string to an Uint8Array. -export function encodeUTF8(s) { - var i = 0, - bytes = new Uint8Array(s.length * 4) - for (var ci = 0; ci !== s.length; ci++) { - var c = s.charCodeAt(ci) - if (c < 128) { - bytes[i++] = c - continue - } - if (c < 2048) { - bytes[i++] = (c >> 6) | 192 - } else { - if (c > 0xd7ff && c < 0xdc00) { - if (++ci >= s.length) throw new Error('UTF-8 encode: incomplete surrogate pair') - var c2 = s.charCodeAt(ci) - if (c2 < 0xdc00 || c2 > 0xdfff) - throw new Error( - 'UTF-8 encode: second surrogate character 0x' + - c2.toString(16) + - ' at index ' + - ci + - ' out of range' - ) - c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff) - bytes[i++] = (c >> 18) | 240 - bytes[i++] = ((c >> 12) & 63) | 128 - } else bytes[i++] = (c >> 12) | 224 - bytes[i++] = ((c >> 6) & 63) | 128 - } - bytes[i++] = (c & 63) | 128 - } - return bytes.subarray(0, i) -} - -// Unmarshals a string from an Uint8Array. -export function decodeUTF8(bytes) { - var i = 0, - s = '' - while (i < bytes.length) { - var c = bytes[i++] - if (c > 127) { - if (c > 191 && c < 224) { - if (i >= bytes.length) throw new Error('UTF-8 decode: incomplete 2-byte sequence') - c = ((c & 31) << 6) | (bytes[i++] & 63) - } else if (c > 223 && c < 240) { - if (i + 1 >= bytes.length) throw new Error('UTF-8 decode: incomplete 3-byte sequence') - c = ((c & 15) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63) - } else if (c > 239 && c < 248) { - if (i + 2 >= bytes.length) throw new Error('UTF-8 decode: incomplete 4-byte sequence') - c = ((c & 7) << 18) | ((bytes[i++] & 63) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63) - } else throw new Error('UTF-8 decode: unknown multibyte start 0x' + c.toString(16) + ' at index ' + (i - 1)) - } - if (c <= 0xffff) s += String.fromCharCode(c) - else if (c <= 0x10ffff) { - c -= 0x10000 - s += String.fromCharCode((c >> 10) | 0xd800) - s += String.fromCharCode((c & 0x3ff) | 0xdc00) - } else throw new Error('UTF-8 decode: code point 0x' + c.toString(16) + ' exceeds UTF-16 reach') - } - return s -} diff --git a/port/js/src/utils/utf8.ts b/port/js/src/utils/utf8.ts new file mode 100644 index 00000000..a2c0edde --- /dev/null +++ b/port/js/src/utils/utf8.ts @@ -0,0 +1,63 @@ +// This is free and unencumbered software released into the public domain. +// https://gist.github.com/pascaldekloe + +// Marshals a string to an Uint8Array. +export function encodeUTF8(s: string): Uint8Array { + let i = 0; + const bytes = new Uint8Array(s.length * 4); + for (let ci = 0; ci !== s.length; ci++) { + let c = s.charCodeAt(ci); + if (c < 128) { + bytes[i++] = c; + continue; + } + if (c < 2048) { + bytes[i++] = (c >> 6) | 192; + } else { + if (c > 0xd7ff && c < 0xdc00) { + if (++ci >= s.length) { throw new Error('UTF-8 encode: incomplete surrogate pair'); } + const c2 = s.charCodeAt(ci); + if (c2 < 0xdc00 || c2 > 0xdfff) { + throw new Error( + `UTF-8 encode: second surrogate character 0x${c2.toString(16) + } at index ${ci + } out of range`, + ); + } + c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff); + bytes[i++] = (c >> 18) | 240; + bytes[i++] = ((c >> 12) & 63) | 128; + } else { bytes[i++] = (c >> 12) | 224; } + bytes[i++] = ((c >> 6) & 63) | 128; + } + bytes[i++] = (c & 63) | 128; + } + return bytes.subarray(0, i); +} + +// Unmarshals a string from an Uint8Array. +export function decodeUTF8(bytes: Uint8Array): string { + let i = 0; + let s = ''; + while (i < bytes.length) { + let c = bytes[i++]; + if (c > 127) { + if (c > 191 && c < 224) { + if (i >= bytes.length) { throw new Error('UTF-8 decode: incomplete 2-byte sequence'); } + c = ((c & 31) << 6) | (bytes[i++] & 63); + } else if (c > 223 && c < 240) { + if (i + 1 >= bytes.length) { throw new Error('UTF-8 decode: incomplete 3-byte sequence'); } + c = ((c & 15) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63); + } else if (c > 239 && c < 248) { + if (i + 2 >= bytes.length) { throw new Error('UTF-8 decode: incomplete 4-byte sequence'); } + c = ((c & 7) << 18) | ((bytes[i++] & 63) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63); + } else { throw new Error(`UTF-8 decode: unknown multibyte start 0x${c.toString(16)} at index ${i - 1}`); } + } + if (c <= 0xffff) { s += String.fromCharCode(c); } else if (c <= 0x10ffff) { + c -= 0x10000; + s += String.fromCharCode((c >> 10) | 0xd800); + s += String.fromCharCode((c & 0x3ff) | 0xdc00); + } else { throw new Error(`UTF-8 decode: code point 0x${c.toString(16)} exceeds UTF-16 reach`); } + } + return s; +} diff --git a/port/js/tests/Codec.test.ts b/port/js/tests/Codec.test.ts new file mode 100644 index 00000000..7e089545 --- /dev/null +++ b/port/js/tests/Codec.test.ts @@ -0,0 +1,64 @@ +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable @typescript-eslint/no-loss-of-precision */ +import { describe, it, expect, beforeAll } from 'vitest'; +import { execSync } from 'child_process'; +import { Codec } from '../src/Codec'; +import { uploadTypes, uploadProtocols } from './utils'; +import type { Protocol, RawType } from '../src/protocol'; + +describe('Codec', () => { + let types: RawType[]; + let protocols: Protocol[]; + let codec: Codec; + + beforeAll(() => { + execSync('npm run gen:json'); + types = uploadTypes('./types.json'); + protocols = uploadProtocols('./protocols.json'); + codec = new Codec(types, protocols); + }); + describe('init example', () => { + it('should initialize the messages', () => { + expect(new Codec(types, protocols)).toBeDefined(); + }); + + it('should serialize and deserialize a message', () => { + const bigint = BigInt('0x1234567890abcdef'); + const rawData = { + f0: bigint, + f1: bigint, + f1_pad: 0x12, + f2: 1.2345678901234567890, + f3: 0x12345678, + f4: 0x12345678, + f5: 1.2345678901234567890, + f6: 0x1234, + f7: 0x12, + f8: -0x12, + f9: true, + }; + + const message = codec.serialize('test_proto', 'messgen/test/simple_struct', rawData); + + expect(codec.deserialize(1, 0, message.buffer)).toEqual({ + ...rawData, + f5: expect.closeTo(rawData.f5, 5), + }); + }); + + it('should surialize and deserialize a message with cors', () => { + const rawData = { + f0: BigInt('0x1234567890abcdef'), + cross0: 1, + }; + + const message = codec.serialize( + 'another_proto', + 'cross_proto', + rawData, + ); + + expect(codec.deserialize(2, 0, message.buffer)).toEqual(rawData); + }); + }); +}); diff --git a/port/js/tests/ConverterFactory.test.ts b/port/js/tests/ConverterFactory.test.ts new file mode 100644 index 00000000..d03e781e --- /dev/null +++ b/port/js/tests/ConverterFactory.test.ts @@ -0,0 +1,180 @@ +import { describe, it, expect } from 'vitest'; +import { ConverterFactory } from '../src/converters/ConverterFactory'; +import type { IType } from '../src/types'; +import type { Converter } from '../src/converters/Converter'; +import { Buffer } from '../src/Buffer'; + +describe('ConverterFactory', () => { + it('should serialize scalar correctly', () => { + const name = 'int32'; + const converter = getConverter(name); + const value = 42; + const buffer = new Buffer(new ArrayBuffer(4)); + + converter.serialize(value, buffer); + + expect(buffer.offset).toBe(4); + }); + + it('should deserialize scalar type type', () => { + const name = 'int32'; + const converter = getConverter(name); + const value = 42; + const buffer = new Buffer(new ArrayBuffer(4)); + + converter.serialize(value, buffer); + buffer.offset = 0; + + expect(converter.deserialize(buffer)).toBe(value); + }); + + it('should serialize sized array of scalar types', () => { + const name = 'int32[3]'; + const converter = getConverter(name); + const value = new Int32Array([1, 2, 3]); + const buffer = new Buffer(new ArrayBuffer(converter.size(value))); + + converter.serialize(value, buffer); + + expect(buffer.offset).toEqual(12); + }); + + it('should deserializie sized array of scalar types', () => { + const name = 'int32[3]'; + const converter = getConverter(name); + const value = new Int32Array([1, 2, 3]); + const buffer = new Buffer(new ArrayBuffer(converter.size(value))); + + converter.serialize(value, buffer); + buffer.offset = 0; + + expect(converter.deserialize(buffer)).toEqual(value); + }); + + it('should throw an error when the basis type is not found in the converters map', () => { + expect(() => getConverter('customType')).toThrowError('Unknown type: customType not found'); + }); + + it('should throw an error when the map key type is not found in the converters map', () => { + expect(() => { getConverter('int32{customType}'); }).toThrowError('Unknown type: customType not found'); + }); + + it('should serialize multidimensional array', () => { + const converter = getConverter('int32[3][2]'); + const value = [ + new Int32Array([1, 2, 3]), + new Int32Array([4, 5, 4]), + ]; + const size = converter.size(value); + const buffer = new Buffer(new ArrayBuffer(size)); + + converter.serialize(value, buffer); + + expect(buffer.offset).toEqual(24); + }); + + it('should deserialize multidimensional array', () => { + const converter = getConverter('int32[3][2]'); + const value = [ + new Int32Array([1, 2, 3]), + new Int32Array([4, 5, 4]), + ]; + const size = converter.size(value); + const buffer = new Buffer(new ArrayBuffer(size)); + + converter.serialize(value, buffer); + buffer.offset = 0; + + expect(converter.deserialize(buffer)).toEqual(value); + }); + + it('should serialize map of scalar typess', () => { + const converter = getConverter('string{int32}'); + const value = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], + ]); + const buffer = new Buffer(new ArrayBuffer(converter.size(value))); + + converter.serialize(value, buffer); + + expect(buffer.offset).toEqual(39); + }); + + it('should serialize and deserialize a map of basic types correctly', () => { + const converter = getConverter('string{int32}'); + const value = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], + ]); + const buffer = new Buffer(new ArrayBuffer(converter.size(value))); + + converter.serialize(value, buffer); + buffer.offset = 0; + + expect(converter.deserialize(buffer)).is.deep.eq(value); + }); + + it('should calculate the correct size for flat array', () => { + const converter = getConverter('int32[5]'); + const value = new Int32Array([1, 2, 3, 4, 5]); + + expect(converter.size(value)).toBe(4 * 5); + }); + + it('should calculate size for nested array', () => { + const converter = getConverter('int32[3][2]'); + const value = [Int32Array.from([1, 2, 3]), Int32Array.from([4, 5, 6])]; + + expect(converter.size(value)).toBe(24); + }); + + it('should throw an error for unknown map key type', () => { + const serializeFn = () => getConverter('int32{undefined}'); + + expect(serializeFn).toThrowError('Unknown type: undefined not found'); + }); + + it('should throw an error when the array length is out of bounds', () => { + const converter = getConverter('int32[3]'); + const value = [1, 2, 3, 4]; // Array length is out of bounds + const serialize = () => converter.serialize(value, new Buffer(new ArrayBuffer(converter.size(value)))); + + expect(serialize).toThrowError('Array length mismatch: 4 !== 3'); + }); + + it('it should serialize nested maps with nested structs', () => { + const converter = getConverter('int32[3][]{string}{string}'); + const value = new Map>([ + ['key1', new Map([ + ['key2', [new Int32Array([1, 2, 3]), new Int32Array([4, 5, 6])]], + ])], + ]); + const buffer = new Buffer(new ArrayBuffer(converter.size(value))); + + converter.serialize(value, buffer); + expect(buffer.offset).toEqual(buffer.size); + }); + + it('it should deserialize nested maps with nested structs', () => { + const converter = getConverter('int32[3][]{string}{string}'); + const value = new Map>([ + ['key1', new Map([ + ['key2', [new Int32Array([1, 2, 3]), new Int32Array([4, 5, 6])]], + ])], + ]); + + const buffer = new Buffer(new ArrayBuffer(converter.size(value))); + converter.serialize(value, buffer); + buffer.offset = 0; + + expect(converter.deserialize(buffer)).toEqual(value); + }); + + function getConverter(type: IType): Converter { + const factory = new ConverterFactory(); + return factory.toConverter(type); + } +}); diff --git a/port/js/tests/EnumConverter.test.ts b/port/js/tests/EnumConverter.test.ts new file mode 100644 index 00000000..ffea098e --- /dev/null +++ b/port/js/tests/EnumConverter.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from 'vitest'; +import { EnumConverter } from '../src/converters/base/EnumConverter'; +import type { EnumTypeDefinition, EnumValue, IBasicType } from '../src/types'; +import { Buffer } from '../src/Buffer'; +import { initGetType } from './utils'; + +describe('EnumConverter', () => { + it('should serialize single valued enum', () => { + const value = 1; + const converter = intiEnumConverter([{ name: 'Value1', value }]); + const buffer = new Buffer(new ArrayBuffer(2)); + + converter.serialize(value, buffer); + + expect(buffer.offset).toBe(1); + }); + + it('should deserialize single value enum', () => { + const value = 1; + const converter = intiEnumConverter([{ name: 'Value1', value }]); + const buffer = new Buffer(new ArrayBuffer(2)); + + converter.serialize(value, buffer); + buffer.offset = 0; + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize multiple values enum', () => { + const converter = intiEnumConverter([ + { name: 'VALUE1', value: 1 }, + { name: 'VALUE2', value: 2 }, + { name: 'VALUE3', value: 3 }, + ]); + const buffer = new Buffer(new ArrayBuffer(2)); + + converter.serialize('value2', buffer); + + expect(buffer.offset).toEqual(1); + }); + + it('should return size for enum value', () => { + const value = 1; + const converter = intiEnumConverter([{ name: 'Value1', value }], 'int32'); + + const result = converter.size(value); + + expect(result).toEqual(4); + }); + + function intiEnumConverter(values: EnumValue[], type?: IBasicType): EnumConverter { + const schema = createSchema(values, type); + const getType = initGetType(); + return new EnumConverter(schema, getType); + } + + function createSchema(values: EnumValue[] = [], type: IBasicType = 'int8'): EnumTypeDefinition { + return { typeClass: 'enum', values, typeName: 'testStruct', type }; + } +}); diff --git a/port/js/tests/Integration.test.ts b/port/js/tests/Integration.test.ts new file mode 100644 index 00000000..18763172 --- /dev/null +++ b/port/js/tests/Integration.test.ts @@ -0,0 +1,256 @@ +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable @typescript-eslint/no-loss-of-precision */ +import { describe, it, expect, beforeAll } from 'vitest'; +import { execSync } from 'child_process'; +import { Codec } from '../src/Codec'; +import { uploadBinary, uploadTypes, uploadProtocols } from './utils'; + +describe('integration', () => { + let codec: Codec; + const bigint = BigInt('0x1234567890abcdef'); + + beforeAll(() => { + execSync(' npm run generate-bit'); + execSync(' npm run gen:json'); + const types = uploadTypes('./types.json'); + const protocols = uploadProtocols('./protocols.json'); + codec = new Codec(types, protocols); + }); + + it('shpuld parse simple_struct (flat structure)', () => { + const rawData = { + f0: bigint, + f1: bigint, + f1_pad: 0x12, + f2: 1.2345678901234567890, + f3: 0x12345678, + f4: 0x12345678, + f5: 1.2345678901234567890, + f6: 0x1234, + f7: 0x12, + f8: -0x12, + f9: true, + }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/simple_struct.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/simple_struct', rawData); + const result = codec.deserialize(1, 0, new Uint8Array(rawDataBit).buffer); + + expect(result).toEqual({ ...rawData, f5: expect.closeTo(rawData.f5, 5) }); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array(rawDataBit))); + }); + + it('should parse var_size_struct.bin', () => { + const rawData = { + f0: bigint, + f1_vec: new BigInt64Array([-bigint, BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/var_size_struct.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/var_size_struct', rawData); + const result = codec.deserialize(1, 2, new Uint8Array(rawDataBit).buffer); + + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array(rawDataBit))); + }); + + it('should parse enum struct_with_enum', () => { + const rawData = { f0: bigint, f1: bigint, e0: 1 }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/struct_with_enum.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/struct_with_enum', rawData); + const result = codec.deserialize(1, 3, new Uint8Array(rawDataBit).buffer); + + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array(rawDataBit))); + }); + + it('should parse empty structure', () => { + const rawData = {}; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/empty_struct.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/empty_struct', rawData); + const result = codec.deserialize(1, 4, new Uint8Array(rawDataBit).buffer); + + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array(rawDataBit))); + }); + + it('should parse complex struct with nested empty struct', () => { + const rawData = { + e: {}, + dynamic_array: [{}, {}, {}], + static_array: [{}, {}, {}, {}, {}], + multi_array: [ + [[{}], [{}], [{}], [{}], [{}]], + [[{}], [{}], [{}], [{}], [{}]], + [[{}], [{}], [{}], [{}], [{}]], + ], + map_empty_by_int: new Map([ + [0, {}], + [1, {}], + [2, {}], + ]), + map_vec_by_str: new Map([ + ['key0', [{}]], + ['key1', [{}]], + ['key2', [{}]], + ]), + array_of_size_zero: new Int32Array(0), + }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/complex_struct_with_empty.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/complex_struct_with_empty', rawData); + const result = codec.deserialize(1, 5, new Uint8Array(rawDataBit).buffer); + + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array( + rawDataBit, + ))); + }); + + it('shut be parse complex_struct_nostl', () => { + const simpleStruct = { + f0: bigint, + f1: bigint, + f1_pad: 0x12, + f2: 1.2345678901234567890, + f3: 0x12345678, + f4: 0x12345678, + f5: 1.2345678901234567890, + f6: 0x1234, + f7: 0x12, + f8: -0x12, + f9: true, + }; + + const rawData = { + f0: BigInt('0x1234567890abcdef'), + f1: 0x12345678, + f2: BigInt('0x1234567890abcdef'), + s_arr: Array(2).fill(simpleStruct), + f1_arr: new BigInt64Array(4).fill(BigInt('0x1234567890abcdef')), + v_arr: Array(2).fill({ + f0: BigInt('0x1234567890abcdef'), + f1_vec: new BigInt64Array([BigInt('0x1234567890abcdef'), BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + }), + f2_vec: new Float64Array(3).fill(1.2345678901234567890), + e_vec: [ + 0, + 1], + s_vec: Array(3).fill(simpleStruct), + v_vec0: Array(3).fill(Array(2).fill({ + f0: BigInt('0x1234567890abcdef'), + f1_vec: new BigInt64Array([BigInt('0x1234567890abcdef'), BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + })), // replace 3 with desired outer list length + v_vec1: Array(4).fill(Array(3).fill({ + f0: BigInt('0x1234567890abcdef'), + f1_vec: new BigInt64Array([BigInt('0x1234567890abcdef'), BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + })), // replace 3 with desired outer list length + v_vec2: Array(2).fill(Array(4).fill(new Int16Array(3).fill(0x1234))), // replace 2 with desired outer list length + str: 'Example String', + str_vec: ['string1', 'string2', 'string3'], + }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/complex_struct_nostl.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/complex_struct_nostl', rawData); + const result = codec.deserialize(1, 6, new Uint8Array(rawDataBit).buffer); + + simpleStruct.f5 = expect.closeTo(simpleStruct.f5, 4); + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array( + rawDataBit, + ))); + }); + it('should parse complex_struct', () => { + const simpleStruct = { + f0: BigInt('0x1234567890abcdef'), + f1: BigInt('0x1234567890abcdef'), + f1_pad: 0x12, + f2: 1.2345678901234567890, + f3: 0x12345678, + f4: 0x12345678, + f5: 1.2345678901234567890, + f6: 0x1234, + f7: 0x12, + f8: -0x12, + f9: true, + }; + + const rawData = { + f0: BigInt('0x1234567890abcdef'), + f1: 0x12345678, + f2: BigInt('0x1234567890abcdef'), + s_arr: Array(2).fill(simpleStruct), + f1_arr: new BigInt64Array(4).fill(BigInt('0x1234567890abcdef')), + v_arr: Array(2).fill({ + f0: BigInt('0x1234567890abcdef'), + f1_vec: new BigInt64Array([BigInt('0x1234567890abcdef'), BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + }), + f2_vec: new Float64Array(3).fill(1.2345678901234567890), + e_vec: [0, 1], + s_vec: Array(3).fill(simpleStruct), + v_vec0: Array(3).fill(Array(2).fill({ + f0: BigInt('0x1234567890abcdef'), + f1_vec: new BigInt64Array([BigInt('0x1234567890abcdef'), BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + })), + v_vec1: Array(4).fill(Array(3).fill({ + f0: BigInt('0x1234567890abcdef'), + f1_vec: new BigInt64Array([BigInt('0x1234567890abcdef'), BigInt(5), BigInt(1)]), + str: 'Hello messgen!', + })), + v_vec2: Array(2).fill(Array(4).fill(new Int16Array(3).fill(0x1234))), + str: 'Example String', + bs: new Uint8Array([0x62, 0x79, 0x74, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67]), // "byte string" + str_vec: ['string1', 'string2', 'string3'], + map_str_by_int: new Map(Array.from({ length: 3 }, (_, i) => [i, `string${i}`])), + map_vec_by_str: new Map(Array.from({ length: 3 }, (_, i) => [`key${i}`, new Int32Array(3).fill(0x1234)])), + }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/complex_struct.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/complex_struct', rawData); + const result = codec.deserialize(1, 1, new Uint8Array(rawDataBit).buffer); + + simpleStruct.f5 = expect.closeTo(simpleStruct.f5, 4); + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array( + rawDataBit, + ))); + }); + + it('should parse flat structure flat_struct', () => { + const rawData = { + f0: bigint, + f1: bigint, + f2: 1.2345678901234567890, + f3: 0x12345678, + f4: 0x12345678, + f5: 1.2345678901234567890, + f6: 0x1234, + f7: 0x12, + f8: -0x12, + }; + const rawDataBit = uploadBinary('../../../tests/data/serialized/bin/flat_struct.bin'); + + const buffer = codec.serialize('test_proto', 'messgen/test/flat_struct', rawData); + const result = codec.deserialize(1, 7, new Uint8Array(rawDataBit).buffer); + + rawData.f5 = expect.closeTo(rawData.f5, 5); + expect(result).toEqual(rawData); + expect(buffer.size).toEqual(rawDataBit.length); + expect((new Uint8Array(buffer.dataView.buffer))).toEqual((new Uint8Array(rawDataBit))); + }); +}); diff --git a/port/js/tests/Protocols.test.ts b/port/js/tests/Protocols.test.ts new file mode 100644 index 00000000..83e17d5c --- /dev/null +++ b/port/js/tests/Protocols.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { Protocols } from '../src/protocol/Protocols'; + +describe('Protocols', () => { + let protocols: Protocols; + + beforeAll(() => { + protocols = new Protocols(); + protocols.load([ + { + type: 'simple_struct', + type_class: '8', + fields: [ + { name: 'f0', type: 'uint64' }, + { name: 'f1', type: 'int64' }, + ], + }, + { + type: 'simple_enum', + type_class: '7', + base_type: 'uint8', + values: [ + { name: 'one_value', value: 0 }, + { name: 'another_value', value: 1 }, + ], + }, + ]); + }); + + describe('#getType', () => { + it('should resolve scalar types', () => { + const type = protocols.getType('uint64'); + expect(type).toEqual({ + type: 'uint64', + typeClass: 'scalar', + }); + }); + + it('should resolve array types', () => { + const type = protocols.getType('uint64[4]'); + expect(type).toEqual({ + type: 'uint64[4]', + typeClass: 'typed-array', + elementType: 'uint64', + arraySize: 4, + }); + }); + + it('should resolve dynamic array types', () => { + const type = protocols.getType('uint64[]'); + expect(type).toEqual({ + type: 'uint64[]', + typeClass: 'typed-array', + elementType: 'uint64', + size: undefined, + }); + }); + + it('should resolve map types', () => { + const type = protocols.getType('string{int32}'); + expect(type).toEqual({ + type: 'string{int32}', + typeClass: 'map', + keyType: 'int32', + valueType: 'string', + }); + }); + + it('should resolve struct types', () => { + const type = protocols.getType('simple_struct'); + expect(type).toEqual({ + typeClass: 'struct', + typeName: 'simple_struct', + fields: [ + { name: 'f0', type: 'uint64' }, + { name: 'f1', type: 'int64' }, + ], + }); + }); + + it('should resolve enum types', () => { + const type = protocols.getType('simple_enum'); + expect(type).toEqual({ + typeClass: 'enum', + typeName: 'simple_enum', + type: 'uint8', + values: [ + { name: 'one_value', value: 0 }, + { name: 'another_value', value: 1 }, + ], + }); + }); + + it('should throw error for unknown types', () => { + expect(() => { + protocols.getType('unknown_type'); + }).toThrow('Unknown type: unknown_type not found'); + }); + + it('should resolve cross-protocol type references', () => { + const type = protocols.getType('simple_struct'); + expect(type).toBeDefined(); + expect(type.typeClass).toBe('struct'); + }); + }); +}); diff --git a/port/js/tests/ScalarConverter.test.ts b/port/js/tests/ScalarConverter.test.ts new file mode 100644 index 00000000..f2eec006 --- /dev/null +++ b/port/js/tests/ScalarConverter.test.ts @@ -0,0 +1,570 @@ +import { describe, it, expect } from 'vitest'; +import { Buffer } from '../src/Buffer'; +import { ScalarConverter } from '../src/converters/base/ScalarConverter'; +import type { IBasicType } from '../src/types'; +import { IS_LITTLE_ENDIAN } from '../src/config'; + +describe('ScalarConverter', () => { + describe('::primitive', () => { + it('should serialize int8', () => { + const converter = getConverter('int8'); + const buffer = getBuffer(1); + const value = 3; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt8(0)).toBe(value); + }); + + it('should deserialize int8', () => { + const value = 3; + const converter = getConverter('int8'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt8(0, value); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize negative in8', () => { + const converter = getConverter('int8'); + const buffer = getBuffer(1); + const value = -3; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt8(0)).toBe(value); + }); + + it('should deserialize negative int8', () => { + const value = -3; + const converter = getConverter('int8'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt8(0, value); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize int16', () => { + const converter = getConverter('int16'); + const buffer = getBuffer(2); + const value = 6457; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt16(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize int16', () => { + const value = 6457; + const converter = getConverter('int16'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt16(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize negative int16', () => { + const converter = getConverter('int16'); + const buffer = getBuffer(2); + const value = -6457; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt16(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize negative int16', () => { + const value = -15359; + const converter = getConverter('int16'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt16(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize int32', () => { + const converter = getConverter('int32'); + const buffer = getBuffer(4); + const value = 3123123; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt32(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize int32', () => { + const value = 3123123; + const converter = getConverter('int32'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt32(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize negative int32', () => { + const converter = getConverter('int32'); + const buffer = getBuffer(4); + const value = -323432; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt32(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize negative int32', () => { + const value = -312312; + const converter = getConverter('int32'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt32(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize int64', () => { + const converter = getConverter('int64'); + const buffer = getBuffer(8); + const value = 9007199254740991n; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getBigInt64(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize int64', () => { + const value = 9007199254740991n; + const converter = getConverter('int64'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setBigInt64(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize negative int64', () => { + const converter = getConverter('int64'); + const buffer = getBuffer(8); + const value = -9007199254740991n; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getBigInt64(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize negative int64', () => { + const value = -9007199254740991n; + const converter = getConverter('int64'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setBigInt64(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize uint8', () => { + const converter = getConverter('uint8'); + const buffer = getBuffer(1); + const value = 3; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getUint8(0)).toBe(value); + }); + + it('should deserialize uint8', () => { + const value = 3; + const converter = getConverter('uint8'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setUint8(0, value); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize uint16', () => { + const converter = getConverter('uint16'); + const buffer = getBuffer(2); + const value = 3; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getUint16(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize uint16', () => { + const value = 32332; + const converter = getConverter('uint16'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setUint16(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize uint32', () => { + const converter = getConverter('uint32'); + const buffer = getBuffer(4); + const value = 5646233; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getUint32(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize uint32', () => { + const value = 5646233; + const converter = getConverter('uint32'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setUint32(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize uint64', () => { + const converter = getConverter('uint64'); + const buffer = getBuffer(8); + const value = 9007199254740991n; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getBigUint64(0, IS_LITTLE_ENDIAN)).toBe(value); + }); + + it('should deserialize uint64', () => { + const value = 9007199254740991n; + const converter = getConverter('uint64'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setBigUint64(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize float32', () => { + const converter = getConverter('float32'); + const buffer = getBuffer(4); + const value = 3.2; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getFloat32(0, IS_LITTLE_ENDIAN)).toBeCloseTo(value, 5); + }); + + it('should deserialize float32', () => { + const value = 31231.14; + const converter = getConverter('float32'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setFloat32(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBeCloseTo(value, 2); + }); + + it('should serialize float64', () => { + const converter = getConverter('float64'); + const buffer = getBuffer(8); + const value = 3123213.2; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getFloat64(0, IS_LITTLE_ENDIAN)).toBeCloseTo(value, 5); + }); + + it('should deserialize float64', () => { + const value = 3.14; + const converter = getConverter('float64'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setFloat64(0, value, IS_LITTLE_ENDIAN); + + const result = converter.deserialize(buffer); + + expect(result).toBeCloseTo(value, 5); + }); + + it('should serialize char', () => { + const converter = getConverter('char'); + const buffer = getBuffer(1); + const value = 'a'; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt8(0)).toBe(value.charCodeAt(0)); + }); + + it('should deserialize char', () => { + const value = 'a'; + const converter = getConverter('char'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt8(0, value.charCodeAt(0)); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize bool', () => { + const converter = getConverter('bool'); + const buffer = getBuffer(1); + const value = true; + + converter.serialize(value, buffer); + + expect(buffer.dataView.getInt8(0)).toBe(1); + }); + + it('should deserialize bool', () => { + const value = true; + const converter = getConverter('bool'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setInt8(0, 1); + + const result = converter.deserialize(buffer); + + expect(result).toBe(value); + }); + + it('should serialize string', () => { + const value = 'test'; + const converter = getConverter('string'); + const buffer = getBuffer(converter.size(value)); + + converter.serialize(value, buffer); + + expect(buffer.dataView.getUint32(0, IS_LITTLE_ENDIAN)).toBe(value.length); + expect(buffer.dataView.getInt8(4)).toBe(value.charCodeAt(0)); + }); + + it('should serilize bytes', () => { + const value = new Uint8Array([1, 2, 3, 4]); + const converter = getConverter('bytes'); + const buffer = getBuffer(converter.size(value)); + + converter.serialize(value, buffer); + + expect(buffer.dataView.getUint32(0, IS_LITTLE_ENDIAN)).toBe(value.length); + expect(buffer.dataView.getUint8(4)).toBe(value[0]); + }); + + it('should deserialize bytes', () => { + const value = new Uint8Array([1, 2, 3, 4]); + const converter = getConverter('bytes'); + const buffer = getBuffer(converter.size(value)); + buffer.dataView.setUint32(0, value.length, IS_LITTLE_ENDIAN); + for (let i = 0; i < value.length; i++) { + buffer.dataView.setUint8(4 + i, value[i]); + } + + const result = converter.deserialize(buffer); + + expect(result).toEqual(value); + }); + }); + + describe('::size', () => { + it('should calculate size of int8', () => { + const converter = getConverter('int8'); + const value = 3; + + const result = converter.size(value); + + expect(result).toBe(1); + }); + + it('should calculate size of negative int8', () => { + const converter = getConverter('int8'); + const value = -3; + + const result = converter.size(value); + + expect(result).toBe(1); + }); + + it('should calculate size of int16', () => { + const converter = getConverter('int16'); + const value = 32233; + + const result = converter.size(value); + + expect(result).toBe(2); + }); + + it('should calculate size of negative int16', () => { + const converter = getConverter('int16'); + const value = 32233; + + const result = converter.size(value); + + expect(result).toBe(2); + }); + + it('should calculate size of int32', () => { + const converter = getConverter('int32'); + const value = 322332; + + const result = converter.size(value); + + expect(result).toBe(4); + }); + + it('should calculate size of negative int32', () => { + const converter = getConverter('int32'); + const value = -322332; + + const result = converter.size(value); + + expect(result).toBe(4); + }); + + it('should calculate size of int64', () => { + const converter = getConverter('int64'); + const value = 322332n; + + const result = converter.size(value); + + expect(result).toBe(8); + }); + + it('should calculate size of negative int64', () => { + const converter = getConverter('int64'); + const value = -322332n; + + const result = converter.size(value); + + expect(result).toBe(8); + }); + + it('should calculate size of uint8', () => { + const converter = getConverter('uint8'); + const value = 3; + + const result = converter.size(value); + + expect(result).toBe(1); + }); + + it('should calculate size of uint16', () => { + const converter = getConverter('uint16'); + const value = 32233; + + const result = converter.size(value); + + expect(result).toBe(2); + }); + + it('should calculate size of uint32', () => { + const converter = getConverter('uint32'); + const value = 322332; + + const result = converter.size(value); + + expect(result).toBe(4); + }); + + it('should calculate size of uint64', () => { + const converter = getConverter('uint64'); + const value = 322332n; + + const result = converter.size(value); + + expect(result).toBe(8); + }); + + it('should calculate size of float32', () => { + const converter = getConverter('float32'); + const value = 3233.14; + + const result = converter.size(value); + + expect(result).toBe(4); + }); + + it('should calculate size of float64', () => { + const converter = getConverter('float64'); + const value = 3233.14; + + const result = converter.size(value); + + expect(result).toBe(8); + }); + + it('should calculate size of char', () => { + const converter = getConverter('char'); + const value = 'a'; + + const result = converter.size(value); + + expect(result).toBe(1); + }); + + it('should calculate size of bool', () => { + const converter = getConverter('bool'); + const value = true; + + const result = converter.size(value); + + expect(result).toBe(1); + }); + + it('should calculate size of string', () => { + const converter = getConverter('string'); + const value = 'test'; + + const result = converter.size(value); + + expect(result).toBe(8); + }); + + it('should calculate size of bytes', () => { + const converter = getConverter('bytes'); + const value = new Uint8Array([1, 2, 3, 4]); + + const result = converter.size(value); + + expect(result).toBe(8); + }); + }); + + it('should deserialize multiple types from the same buffer', () => { + const value1 = 3; // int8 + const value2 = 1000; // int16 + const converter1 = getConverter('int8'); + const converter2 = getConverter('int16'); + const buffer = getBuffer(converter1.size(value1) + converter2.size(value2)); + converter1.serialize(value1, buffer); + converter2.serialize(value2, buffer); + buffer.offset = 0; + + const deserializedValue1 = converter1.deserialize(buffer); + const deserializedValue2 = converter2.deserialize(buffer); + + expect(deserializedValue1).toBe(value1); + expect(deserializedValue2).toBe(value2); + }); + + function getConverter(name: IBasicType) { + return new ScalarConverter(name); + } + + function getBuffer(size: number) { + return new Buffer(new ArrayBuffer(size)); + } +}); diff --git a/port/js/tests/StructConvertert.test.ts b/port/js/tests/StructConvertert.test.ts new file mode 100644 index 00000000..b046c77a --- /dev/null +++ b/port/js/tests/StructConvertert.test.ts @@ -0,0 +1,152 @@ +import { describe, it, expect } from 'vitest'; +import type { Field, StructTypeDefinition } from '../src/types'; +import { StructConverter } from '../src/converters/base/StructConverter'; +import { Buffer } from '../src/Buffer'; +import { initGetType } from './utils'; + +describe('StructConverter', () => { + it('should serializes an object', () => { + const structConverter = createStructConverter([{ name: 'field1', type: 'string' }]); + const buffer = new Buffer(new ArrayBuffer(10)); + + const serializeFn = () => structConverter.serialize({ field1: 'value1' }, buffer); + + expect(serializeFn).not.toThrow(); + }); + + it('should updates the buffer offset', () => { + const structConverter = createStructConverter([ + { name: 'field1', type: 'string' }, + { name: 'field2', type: 'int8' }, + ]); + const value = { field1: 'value1', field2: 123 }; + const buffer = new Buffer(new ArrayBuffer(structConverter.size(value))); + + structConverter.serialize(value, buffer); + + expect(buffer.offset).toBe(11); + }); + + it('should serializes an object with multiple fields in the schema', () => { + const structConverter = createStructConverter([ + { name: 'field1', type: 'string' }, + { name: 'field2', type: 'int8' }, + { name: 'field3', type: 'bool' }, + ]); + const buffer = new Buffer(new ArrayBuffer(100)); + + const serialize = () => structConverter.serialize({ field1: 'value1', field2: 123, field3: true }, buffer); + + expect(serialize).not.toThrow(); + }); + + it('should calculates size of input object with an empty schema', () => { + const structConverter = createStructConverter([]); + const result = structConverter.size({}); + + expect(result).toBe(0); + }); + + it('should serializes an object with an empty schema', () => { + const structConverter = createStructConverter([]); + const serializedSize = structConverter.size({}); + const buffer = new Buffer(new ArrayBuffer(serializedSize)); + + structConverter.serialize({}, buffer); + + expect(serializedSize).toBe(0); + }); + + it('should deserializes input object with an empty schema', () => { + const structConverter = createStructConverter([]); + const serializedSize = structConverter.size({}); + const buffer = new Buffer(new ArrayBuffer(serializedSize)); + + structConverter.serialize({}, buffer); + const deserialized = structConverter.deserialize(buffer); + + expect(deserialized).toEqual({}); + }); + + it('should calculates the size of an input object based on the schema', () => { + const structConverter = createStructConverter([ + { name: 'field1', type: 'string' }, + { name: 'field2', type: 'int8' }, + { name: 'field3', type: 'bool' }, + ]); + const inputObject = { field1: 'value1', field2: 123, field3: true }; + + const result = structConverter.size(inputObject); + + expect(result).toBe(12); + }); + + it('serializes an input object with a schema containing reserved field names', () => { + const structConverter = createStructConverter([ + { name: 'name', type: 'string' }, + { name: 'type', type: 'string' }, + { name: 'comment', type: 'string' }, + ]); + const value = { name: 'John', type: 'Employee', comment: 'This is a test' }; + const buffer = new Buffer(new ArrayBuffer(100)); + + structConverter.serialize(value, buffer); + + expect(buffer.offset).toBeGreaterThan(0); + }); + + it('should throws an error if a converter for a field type is not found', () => { + const createConverterFn = () => createStructConverter([{ name: 'field', type: 'unknownType' }]); + + expect(createConverterFn).toThrowError(); + }); + + it('should throws an error if a required field is missing in the input object', () => { + const structConverter = createStructConverter([ + { name: 'existingField', type: 'string' }, + { name: 'missingField', type: 'string' }, + ]); + const buffer = new Buffer(new ArrayBuffer(100)); + const inputObject = { existingField: 'existingValue' }; + + const serializeFn = () => structConverter.serialize(inputObject, buffer); + + expect(serializeFn).toThrowError('Field missingField is not found in testStruct'); + }); + + it('should throws an error when input object contains null or undefined values for required fields', () => { + const structConverter = createStructConverter([ + { name: 'field1', type: 'string' }, + { name: 'field2', type: 'int32' }, + { name: 'field3', type: 'bool' }, + ]); + const value = { field1: null, field2: undefined, field3: true }; + const buffer = new Buffer(new ArrayBuffer(10)); + + const serializeFn = () => structConverter.serialize(value, buffer); + + expect(serializeFn).toThrowError('Field field1 is not found in testStruct'); + }); + + it('should handles input object with reserved prototype method names as fields', () => { + const serializeFn = () => createStructConverter([ + { name: 'toString', type: 'string' }, + { name: 'valueOf', type: 'int32' }, + { name: 'hasOwnProperty', type: 'bool' }, + ]); + + expect(serializeFn).toThrow(); + }); + + function createStructConverter( + fields: Field[], + ): StructConverter { + const schema = createSchema(fields); + const getType = initGetType(); + return new StructConverter(schema, getType); + } + + function createSchema(fields: Field[] = []): StructTypeDefinition { + return { typeClass: 'struct', fields, typeName: 'testStruct' }; + } +}); diff --git a/port/js/tests/messgen.test.js b/port/js/tests/messgen.test.js deleted file mode 100644 index bb72feb2..00000000 --- a/port/js/tests/messgen.test.js +++ /dev/null @@ -1,590 +0,0 @@ -'use strict' - -import { Buffer, Struct, HEADER_STRUCT, initializeMessages } from '../src/messgen.js' - -describe('Serialization deserialization tests', () => { - it('Basic types', () => { - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: 'Int8' }, - { name: 'type_Uint8', type: 'Uint8' }, - { name: 'type_Int16', type: 'Int16' }, - { name: 'type_Uint16', type: 'Uint16' }, - { name: 'type_Int32', type: 'Int32' }, - { name: 'type_Uint32', type: 'Uint32' }, - { name: 'type_Int64', type: 'Int64' }, - { name: 'type_Uint64', type: 'Uint64' }, - { name: 'type_String', type: 'String' }, - { name: 'type_Double', type: 'Double' }, - { name: 'type_Char', type: 'Char' } - ] - }) - - let srcData = { - type_Int8: 8, - type_Uint8: 8, - type_Int16: 8, - type_Uint16: 8, - type_Int32: 8, - type_Uint32: 8, - type_Int64: BigInt(8), - type_Uint64: BigInt(8), - type_String: 'This is test string', - type_Double: -Math.PI, - type_Char: 'A' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData) - - let res = new Buffer(b).deserialize(srcStruct) - - expect(res).toEqual(srcData) - }) - - it('Basic types fixed array size', () => { - const ARRAY_SIZE = 100 - - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: `Int8[${ARRAY_SIZE}]` }, - { name: 'type_Uint8', type: `Uint8[${ARRAY_SIZE}]` }, - { name: 'type_Int16', type: `Int16[${ARRAY_SIZE}]` }, - { name: 'type_Uint16', type: `Uint16[${ARRAY_SIZE}]` }, - { name: 'type_Int32', type: `Int32[${ARRAY_SIZE}]` }, - { name: 'type_Uint32', type: `Uint32[${ARRAY_SIZE}]` }, - { name: 'type_Int64', type: `Int64[${ARRAY_SIZE}]` }, - { name: 'type_Uint64', type: `Uint64[${ARRAY_SIZE}]` }, - { name: 'type_String', type: `String[${ARRAY_SIZE}]` }, - { name: 'type_Double', type: `Double[${ARRAY_SIZE}]` }, - { name: 'type_Char', type: `Char[${ARRAY_SIZE}]` } - ] - }) - - let srcData = { - type_Int8: new Array(ARRAY_SIZE), - type_Uint8: new Array(ARRAY_SIZE), - type_Int16: new Array(ARRAY_SIZE), - type_Uint16: new Array(ARRAY_SIZE), - type_Int32: new Array(ARRAY_SIZE), - type_Uint32: new Array(ARRAY_SIZE), - type_Int64: new Array(ARRAY_SIZE), - type_Uint64: new Array(ARRAY_SIZE), - type_String: new Array(ARRAY_SIZE), - type_Double: new Array(ARRAY_SIZE), - type_Char: new Array(ARRAY_SIZE) - } - - for (let i = 0; i < ARRAY_SIZE; i++) { - srcData.type_Int8[i] = i - srcData.type_Uint8[i] = i - srcData.type_Int16[i] = i - srcData.type_Uint16[i] = i - srcData.type_Int32[i] = i - srcData.type_Uint32[i] = i - srcData.type_Int64[i] = BigInt(-ARRAY_SIZE + i) - srcData.type_Uint64[i] = BigInt(i) - srcData.type_String[i] = 'string-' + i - srcData.type_Double[i] = (-ARRAY_SIZE + i) / 3.0 - srcData.type_Char[i] = 'a' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData) - - let res = new Buffer(b).deserialize(srcStruct) - - expect(res).toEqual(srcData) - }) - - it('Basic types dynamic array size', () => { - const ARRAY_SIZE = 100 - - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: `Int8[]` }, - { name: 'type_Uint8', type: `Uint8[]` }, - { name: 'type_Int16', type: `Int16[]` }, - { name: 'type_Uint16', type: `Uint16[]` }, - { name: 'type_Int32', type: `Int32[]` }, - { name: 'type_Uint32', type: `Uint32[]` }, - { name: 'type_Int64', type: `Int64[]` }, - { name: 'type_Uint64', type: `Uint64[]` }, - { name: 'type_String', type: `String[]` }, - { name: 'type_Double', type: `Double[]` }, - { name: 'type_Char', type: `Char[]` } - ] - }) - - let srcData = { - type_Int8: new Array(ARRAY_SIZE), - type_Uint8: new Array(ARRAY_SIZE), - type_Int16: new Array(ARRAY_SIZE), - type_Uint16: new Array(ARRAY_SIZE), - type_Int32: new Array(ARRAY_SIZE), - type_Uint32: new Array(ARRAY_SIZE), - type_Int64: new Array(ARRAY_SIZE), - type_Uint64: new Array(ARRAY_SIZE), - type_String: new Array(ARRAY_SIZE), - type_Double: new Array(ARRAY_SIZE), - type_Char: new Array(ARRAY_SIZE) - } - - for (let i = 0; i < ARRAY_SIZE; i++) { - srcData.type_Int8[i] = i - srcData.type_Uint8[i] = i - srcData.type_Int16[i] = i - srcData.type_Uint16[i] = i - srcData.type_Int32[i] = i - srcData.type_Uint32[i] = i - srcData.type_Int64[i] = BigInt(-ARRAY_SIZE + i) - srcData.type_Uint64[i] = BigInt(i) - srcData.type_String[i] = 'string-' + i - srcData.type_Double[i] = (-ARRAY_SIZE + i) / 3.0 - srcData.type_Char[i] = 'A' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData) - - let res = new Buffer(b).deserialize(srcStruct) - - expect(res).toEqual(srcData) - }) - - it('Basic types fixed typed array size', () => { - const ARRAY_SIZE = 100 - - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: `Int8[${ARRAY_SIZE}]` }, - { name: 'type_Uint8', type: `Uint8[${ARRAY_SIZE}]` }, - { name: 'type_Int16', type: `Int16[${ARRAY_SIZE}]` }, - { name: 'type_Uint16', type: `Uint16[${ARRAY_SIZE}]` }, - { name: 'type_Int32', type: `Int32[${ARRAY_SIZE}]` }, - { name: 'type_Uint32', type: `Uint32[${ARRAY_SIZE}]` }, - { name: 'type_Int64', type: `Int64[${ARRAY_SIZE}]` }, - { name: 'type_Uint64', type: `Uint64[${ARRAY_SIZE}]` }, - { name: 'type_String', type: `String[${ARRAY_SIZE}]` }, - { name: 'type_Double', type: `Double[${ARRAY_SIZE}]` }, - { name: 'type_Char', type: `Char[${ARRAY_SIZE}]` } - ] - }) - - let srcData = { - type_Int8: new Int8Array(ARRAY_SIZE), - type_Uint8: new Uint8Array(ARRAY_SIZE), - type_Int16: new Int16Array(ARRAY_SIZE), - type_Uint16: new Uint16Array(ARRAY_SIZE), - type_Int32: new Int32Array(ARRAY_SIZE), - type_Uint32: new Uint32Array(ARRAY_SIZE), - type_Int64: new BigInt64Array(ARRAY_SIZE), - type_Uint64: new BigUint64Array(ARRAY_SIZE), - type_String: new Array(ARRAY_SIZE), - type_Double: new Float64Array(ARRAY_SIZE), - type_Char: new Array(ARRAY_SIZE) - } - - for (let i = 0; i < ARRAY_SIZE; i++) { - srcData.type_Int8[i] = i - srcData.type_Uint8[i] = i - srcData.type_Int16[i] = i - srcData.type_Uint16[i] = i - srcData.type_Int32[i] = i - srcData.type_Uint32[i] = i - srcData.type_Int64[i] = BigInt(-ARRAY_SIZE + i) - srcData.type_Uint64[i] = BigInt(i) - srcData.type_String[i] = 'string-' + i - srcData.type_Double[i] = (-ARRAY_SIZE + i) / 3.0 - srcData.type_Char[i] = 'a' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData) - let res = new Buffer(b, true).deserialize(srcStruct) - - expect(res).toEqual(srcData) - }) - - it('Basic types dynamic typed array size', () => { - const ARRAY_SIZE = 100 - - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: `Int8[]` }, - { name: 'type_Uint8', type: `Uint8[]` }, - { name: 'type_Int16', type: `Int16[]` }, - { name: 'type_Uint16', type: `Uint16[]` }, - { name: 'type_Int32', type: `Int32[]` }, - { name: 'type_Uint32', type: `Uint32[]` }, - { name: 'type_Int64', type: `Int64[]` }, - { name: 'type_Uint64', type: `Uint64[]` }, - { name: 'type_String', type: `String[]` }, - { name: 'type_Double', type: `Double[]` }, - { name: 'type_Char', type: `Char[]` } - ] - }) - - let srcData = { - type_Int8: new Int8Array(ARRAY_SIZE), - type_Uint8: new Uint8Array(ARRAY_SIZE), - type_Int16: new Int16Array(ARRAY_SIZE), - type_Uint16: new Uint16Array(ARRAY_SIZE), - type_Int32: new Int32Array(ARRAY_SIZE), - type_Uint32: new Uint32Array(ARRAY_SIZE), - type_Int64: new BigInt64Array(ARRAY_SIZE), - type_Uint64: new BigUint64Array(ARRAY_SIZE), - type_String: new Array(ARRAY_SIZE), - type_Double: new Float64Array(ARRAY_SIZE), - type_Char: new Array(ARRAY_SIZE) - } - - for (let i = 0; i < ARRAY_SIZE; i++) { - srcData.type_Int8[i] = i - srcData.type_Uint8[i] = i - srcData.type_Int16[i] = i - srcData.type_Uint16[i] = i - srcData.type_Int32[i] = i - srcData.type_Uint32[i] = i - srcData.type_Int64[i] = BigInt(-ARRAY_SIZE + i) - srcData.type_Uint64[i] = BigInt(i) - srcData.type_String[i] = 'string-' + i - srcData.type_Double[i] = (-ARRAY_SIZE + i) / 3.0 - srcData.type_Char[i] = 'A' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData) - - let res = new Buffer(b, true).deserialize(srcStruct) - - expect(res).toEqual(srcData) - }) - - it('Basic message with header', () => { - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: 'Int8' }, - { name: 'type_Uint8', type: 'Uint8' }, - { name: 'type_Int16', type: 'Int16' }, - { name: 'type_Uint16', type: 'Uint16' }, - { name: 'type_Int32', type: 'Int32' }, - { name: 'type_Uint32', type: 'Uint32' }, - { name: 'type_Int64', type: 'Int64' }, - { name: 'type_Uint64', type: 'Uint64' }, - { name: 'type_String', type: 'String' }, - { name: 'type_Double', type: 'Double' }, - { name: 'type_Char', type: 'Char' } - ] - }) - - let srcData = { - type_Int8: 8, - type_Uint8: 8, - type_Int16: -16, - type_Uint16: 16, - type_Int32: -32, - type_Uint32: 32, - type_Int64: BigInt(-64), - type_Uint64: BigInt(64), - type_String: 'This is test string', - type_Double: Math.PI, - type_Char: 'A' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeMessage(srcStruct, srcData) - - let buf = new Buffer(b) - let headerRes = buf.deserialize(HEADER_STRUCT, 0) - let res = buf.deserialize(srcStruct, headerRes.__SIZE__) - - expect(res).toEqual(srcData) - }) - - it('Basic types fixed array size with header', () => { - const ARRAY_SIZE = 100 - - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: `Int8[${ARRAY_SIZE}]` }, - { name: 'type_Uint8', type: `Uint8[${ARRAY_SIZE}]` }, - { name: 'type_Int16', type: `Int16[${ARRAY_SIZE}]` }, - { name: 'type_Uint16', type: `Uint16[${ARRAY_SIZE}]` }, - { name: 'type_Int32', type: `Int32[${ARRAY_SIZE}]` }, - { name: 'type_Uint32', type: `Uint32[${ARRAY_SIZE}]` }, - { name: 'type_Int64', type: `Int64[${ARRAY_SIZE}]` }, - { name: 'type_Uint64', type: `Uint64[${ARRAY_SIZE}]` }, - { name: 'type_String', type: `String[${ARRAY_SIZE}]` }, - { name: 'type_Double', type: `Double[${ARRAY_SIZE}]` }, - { name: 'type_Char', type: `Char[${ARRAY_SIZE}]` } - ] - }) - - let srcData = { - type_Int8: new Array(ARRAY_SIZE), - type_Uint8: new Array(ARRAY_SIZE), - type_Int16: new Array(ARRAY_SIZE), - type_Uint16: new Array(ARRAY_SIZE), - type_Int32: new Array(ARRAY_SIZE), - type_Uint32: new Array(ARRAY_SIZE), - type_Int64: new Array(ARRAY_SIZE), - type_Uint64: new Array(ARRAY_SIZE), - type_String: new Array(ARRAY_SIZE), - type_Double: new Array(ARRAY_SIZE), - type_Char: new Array(ARRAY_SIZE) - } - - for (let i = 0; i < ARRAY_SIZE; i++) { - srcData.type_Int8[i] = i - srcData.type_Uint8[i] = i - srcData.type_Int16[i] = i - srcData.type_Uint16[i] = i - srcData.type_Int32[i] = i - srcData.type_Uint32[i] = i - srcData.type_Int64[i] = BigInt(i) - srcData.type_Uint64[i] = BigInt(i) - srcData.type_String[i] = 'string-' + i - srcData.type_Double[i] = i * 12.45 - srcData.type_Char[i] = 'A' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeMessage(srcStruct, srcData) - - let buf = new Buffer(b) - let headerRes = buf.deserialize(HEADER_STRUCT, 0) - let res = buf.deserialize(srcStruct, headerRes.__SIZE__) - - expect(res).toEqual(srcData) - }) - - it('Basic types dynamic array size with header', () => { - const ARRAY_SIZE = 100 - - let srcStruct = new Struct({ - id: 2, - fields: [ - { name: 'type_Int8', type: `Int8[]` }, - { name: 'type_Uint8', type: `Uint8[]` }, - { name: 'type_Int16', type: `Int16[]` }, - { name: 'type_Uint16', type: `Uint16[]` }, - { name: 'type_Int32', type: `Int32[]` }, - { name: 'type_Uint32', type: `Uint32[]` }, - { name: 'type_Int64', type: `Int64[]` }, - { name: 'type_Uint64', type: `Uint64[]` }, - { name: 'type_String', type: `String[]` }, - { name: 'type_Double', type: `Double[]` }, - { name: 'type_Char', type: `Char[]` } - ] - }) - - let srcData = { - type_Int8: new Array(ARRAY_SIZE), - type_Uint8: new Array(ARRAY_SIZE), - type_Int16: new Array(ARRAY_SIZE), - type_Uint16: new Array(ARRAY_SIZE), - type_Int32: new Array(ARRAY_SIZE), - type_Uint32: new Array(ARRAY_SIZE), - type_Int64: new Array(ARRAY_SIZE), - type_Uint64: new Array(ARRAY_SIZE), - type_String: new Array(ARRAY_SIZE), - type_Double: new Array(ARRAY_SIZE), - type_Char: new Array(ARRAY_SIZE) - } - - for (let i = 0; i < ARRAY_SIZE; i++) { - srcData.type_Int8[i] = i - srcData.type_Uint8[i] = i - srcData.type_Int16[i] = i - srcData.type_Uint16[i] = i - srcData.type_Int32[i] = i - srcData.type_Uint32[i] = i - srcData.type_Int64[i] = BigInt(i) - srcData.type_Uint64[i] = BigInt(i) - srcData.type_String[i] = 'string-' + i - srcData.type_Double[i] = i * 12.45 - srcData.type_Char[i] = 'A' - } - - //Testing proper size of the message - srcData.__SIZE__ = Buffer.calcSize(Buffer.createValueArray(srcStruct.fields, srcData)) - - let b = Buffer.serializeMessage(srcStruct, srcData) - - let buf = new Buffer(b) - let headerRes = buf.deserialize(HEADER_STRUCT, 0) - let res = buf.deserialize(srcStruct, headerRes.__SIZE__) - - expect(res).toEqual(srcData) - }) - - it('Complex type with dynamic array size', () => { - let schema = { - MyYZ: { - id: 100, - fields: [ - { name: 'y', type: 'Int8' }, - { name: 'z', type: 'Int16' }, - { name: 's', type: 'String[]' } - ] - }, - Coords: { - id: 111, - fields: [ - { name: 'x', type: 'Int16' }, - { name: 'yz', type: 'MyYZ' } - ] - }, - Param: { - id: 112, - fields: [ - { name: 'coords', type: 'Coords[]' }, - { name: 'xxx', type: 'Uint8' } - ] - } - } - - let includeMsg = initializeMessages(schema) - - let srcStruct = new Struct( - { - id: 2, - fields: [ - { name: 'a', type: 'Int8' }, - { name: 'param', type: 'Param' } - ] - }, - includeMsg - ) - - let srcData = { - a: 32, - param: { - coords: [ - { - x: 5, - yz: { - y: 1, - z: 200, - s: ['aaaaa', 'xxxx'] - } - }, - { - x: 10, - yz: { - y: 4, - z: 800, - s: ['dddddd'] - } - }, - { - x: 999, - yz: { - y: 5, - z: 1400, - s: ['e'] - } - } - ], - xxx: 16 - } - } - - let valArr = Buffer.createValueArray(srcStruct.fields, srcData, includeMsg) - srcData.__SIZE__ = Buffer.calcSize(valArr, includeMsg) - - let b = Buffer.serializeObj(srcStruct.schema.fields, srcData, includeMsg) - - let res = new Buffer(b, includeMsg).deserialize(srcStruct) - - expect(res).toEqual(srcData) - }) - - it('Error if wrong order of complex type', () => { - // wrong order - let schema = { - First: { - id: 1, - fields: [{ name: 'x', type: 'Second' }] - }, - Second: { - id: 2, - fields: [{ name: 'xxx', type: 'Uint8' }] - } - } - - expect(() => { - initializeMessages(schema) - }).toThrow('Unknown type') - - // missed type - let schema1 = { - First: { - id: 1, - fields: [{ name: 'x', type: 'Second' }] - } - } - - expect(() => { - initializeMessages(schema1) - }).toThrow('Unknown type') - - // correct location - let schema2 = { - First: { - id: 1, - fields: [{ name: 'x', type: 'Uint8' }] - }, - Second: { - id: 2, - fields: [{ name: 'xxx', type: 'First' }] - } - } - - expect(() => { - initializeMessages(schema2) - }).not.toThrow('Unknown type') - - // change order - let schema3 = { - Second: { - id: 2, - fields: [{ name: 'xxx', type: 'First' }] - }, - First: { - id: 1, - fields: [{ name: 'x', type: 'Uint8' }] - } - } - - expect(() => { - initializeMessages(schema3) - }).not.toThrow('Unknown type') - }) - - it('TODO: compose tests for not equal size arrays messages', () => { - expect(true).toBe(true) - }) -}) diff --git a/port/js/tests/utf8.test.js b/port/js/tests/utf8.test.js deleted file mode 100644 index ea231d46..00000000 --- a/port/js/tests/utf8.test.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict' - -import { encodeUTF8, decodeUTF8 } from '../src/utf8.js' - -describe('UTF8 function test', () => { - it('Encoding decoding test', () => { - let testStr = '✈✈✈ Hello world! ✈✈✈' - let byteArr = encodeUTF8(testStr) - let dstStr = decodeUTF8(byteArr) - expect(testStr).toBe(dstStr) - }) -}) diff --git a/port/js/tests/utf8.test.ts b/port/js/tests/utf8.test.ts new file mode 100644 index 00000000..6d9d9840 --- /dev/null +++ b/port/js/tests/utf8.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest'; +import { encodeUTF8, decodeUTF8 } from '../src/utils/utf8.ts'; + +describe('UTF8 function test', () => { + it('Encoding decoding test', () => { + const testStr = '✈✈✈ Hello world! ✈✈✈'; + const byteArr = encodeUTF8(testStr); + const dstStr = decodeUTF8(byteArr); + expect(testStr).toBe(dstStr); + }); +}); diff --git a/port/js/tests/utils.ts b/port/js/tests/utils.ts new file mode 100644 index 00000000..d6be1323 --- /dev/null +++ b/port/js/tests/utils.ts @@ -0,0 +1,35 @@ +import * as path from 'path'; +import { readFileSync } from 'fs'; +import { execSync } from 'child_process'; +import { ConverterFactory } from '../src/converters/ConverterFactory'; +import type { Protocol, RawType } from '../src/protocol/Protocols.types'; +import { Protocols } from '../src/protocol/Protocols'; + +const uploadFile = (filePath: string): T => { + const protocolPath = path.resolve(__dirname, filePath); + const rawData = readFileSync(protocolPath, 'utf8'); + return JSON.parse(rawData) as T; +}; + +export function uploadTypes(filePath: string): RawType[] { + return uploadFile(filePath); +} + +export function uploadProtocols(filePath: string): Protocol[] { + return uploadFile(filePath); +} + +export function uploadBinary(filePath: string): Buffer { + const binaryPath = path.resolve(__dirname, filePath); + return readFileSync(binaryPath); +} + +export function generateTestData() { + execSync('npm run gen:json'); +} + +export function initGetType() { + const protocol = new Protocols(); + const factory = new ConverterFactory(protocol); + return factory.toConverter.bind(factory); +} diff --git a/port/js/tsconfig.eslint.json b/port/js/tsconfig.eslint.json new file mode 100644 index 00000000..c666a982 --- /dev/null +++ b/port/js/tsconfig.eslint.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src", + "tests", + "benchmarks", + "./tsup.config.js" + ], + "exclude": [ + ".eslintrc.js", + ] +} diff --git a/port/js/tsconfig.json b/port/js/tsconfig.json new file mode 100644 index 00000000..62f881b8 --- /dev/null +++ b/port/js/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "ES2020", + "outDir": "dist", + "allowJs": false, + "noEmit": false, + "allowSyntheticDefaultImports": true, + "importHelpers": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": false, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "experimentalDecorators": true, + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "declaration": false, + "skipLibCheck": true, + "typeRoots": [ + "src/types", + "node_modules/@types" + ], + "lib": [ + "es2020", + "ES2021.String" + ] + }, + "exclude": [ + "node_modules" + ], + "include": [ + "src/**/*.ts", + "benchmarks/perf-process/index.ts", + ] +} diff --git a/port/js/tsup.config.js b/port/js/tsup.config.js new file mode 100644 index 00000000..4aa902f0 --- /dev/null +++ b/port/js/tsup.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'tsup'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig([ + { + entry: ['src/*.ts'], + format: ['cjs', 'esm'], + target: ['chrome91', 'firefox90', 'edge91', 'safari15', 'ios15', 'opera77'], + outDir: 'build/modern', + dts: true, + sourcemap: true, + clean: true, + }, +]); diff --git a/port/js/vite.config.js b/port/js/vite.config.js deleted file mode 100644 index 1682fc43..00000000 --- a/port/js/vite.config.js +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from 'vite' -import pkg from './package.json' -import terser from '@rollup/plugin-terser' - -const OUTPUT_NAME = `dist/messgen` - -export default defineConfig({ - build: { - lib: { - entry: 'src/messgen.js', - name: pkg.name, - fileName: `messgen` - - }, - rollupOptions: { - output: { - format: 'umd', - name: pkg.name, - sourcemap: true - } - } - }, - test: { - globals: true, - }, - benchmark: { - globals: true, - }, - plugins: [ - - terser()] -}) diff --git a/port/js/vitest.config.js b/port/js/vitest.config.js new file mode 100644 index 00000000..4036d4d3 --- /dev/null +++ b/port/js/vitest.config.js @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite' +import pkg from './package.json' +import terser from '@rollup/plugin-terser' + + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + include: 'src/**', + thresholds: { + statements: 90, + branches: 86, + functions: 94, + lines: 90 + } + }, + }, + benchmark: { + globals: true, + }, + plugins: [ + terser()] +}) diff --git a/port/python/README.txt b/port/python/README.txt new file mode 100644 index 00000000..134a3f1b --- /dev/null +++ b/port/python/README.txt @@ -0,0 +1 @@ +Use `messgen.dynamic` module. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..fd13fa35 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[tool.pylint.master] +disable = ["C0114", "C0115", "C0116"] + +[tool.pylint.format] +max-line-length = 150 + +[tool.pytest.ini_options] +testpaths = ["tests/python"] +log_cli = true +log_cli_level = "INFO" +log_cli_format = "- [%(asctime)s] - [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)" +log_cli_date_format = "%H:%M:%S" +pythonpath = ["messgen"] + +[tool.mypy] +mypy_path = ["tests/python"] +ignore_missing_imports = true +explicit_package_bases = true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..de4ea94e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pytest +pyyaml +mypy +types-PyYAML \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..8ca02cfc --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,35 @@ +include(GoogleTest) +find_package(GTest REQUIRED) + +enable_testing() + +set(MESSGEN_TYPE_DIR "${CMAKE_CURRENT_LIST_DIR}/data/types") +set(MESSGEN_PROTO_DIR "${CMAKE_CURRENT_LIST_DIR}/data/protocols") + +add_executable(test_cpp "cpp/CppTest.cpp") +messgen_add_types_library(msgs_test_types "${MESSGEN_TYPE_DIR}" "stl" "cpp_standard=20") +messgen_add_proto_library(msgs_test_proto "${MESSGEN_PROTO_DIR}" "test_proto" msgs_test_types) +messgen_add_proto_library(msgs_another_proto "${MESSGEN_PROTO_DIR}" "nested/another_proto" msgs_test_types) +target_link_libraries(test_cpp gtest msgs_test_proto msgs_another_proto GTest::gtest_main) +gtest_discover_tests(test_cpp) + +add_executable(test_cpp_nostl "cpp/CppNostlTest.cpp") +messgen_add_types_library(msgs_test_types_nostl "${MESSGEN_TYPE_DIR}" "nostl" "cpp_standard=20") +messgen_add_proto_library(msgs_test_proto_nostl "${MESSGEN_PROTO_DIR}" "test_proto" msgs_test_types_nostl) +target_link_libraries(test_cpp_nostl gtest msgs_test_proto_nostl GTest::gtest_main) +gtest_discover_tests(test_cpp_nostl) + +# Make executables form the same source files but without "cpp_standard=20" option (messgen may work differently) + +add_executable(test_cpp_17 "cpp/CppTest.cpp") +messgen_add_types_library(msgs_test_types_cpp17 "${MESSGEN_TYPE_DIR}" "stl") +messgen_add_proto_library(msgs_test_proto_cpp17 "${MESSGEN_PROTO_DIR}" "test_proto" msgs_test_types_cpp17) +messgen_add_proto_library(msgs_another_proto_cpp17 "${MESSGEN_PROTO_DIR}" "nested/another_proto" msgs_test_types_cpp17) +target_link_libraries(test_cpp_17 gtest msgs_test_proto_cpp17 msgs_another_proto_cpp17 GTest::gtest_main) +gtest_discover_tests(test_cpp_17) + +add_executable(test_cpp_17_nostl "cpp/CppNostlTest.cpp") +messgen_add_types_library(msgs_test_types_nostl_cpp17 "${MESSGEN_TYPE_DIR}" "nostl") +messgen_add_proto_library(msgs_test_proto_nostl_cpp17 "${MESSGEN_PROTO_DIR}" "test_proto" msgs_test_types_nostl_cpp17) +target_link_libraries(test_cpp_17_nostl gtest msgs_test_proto_nostl_cpp17 GTest::gtest_main) +gtest_discover_tests(test_cpp_17_nostl) diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index a016619f..00000000 --- a/tests/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -PWD := $(shell pwd) -MESSAGES_YAML := ${wildcard messages/messgen/messgen_test/*.yaml} - -CPP := g++ -CPPFLAGS = -Wall -std=c++17 -O0 -g -c -I${PWD}/cpp -I${PWD}/../port/cpp -LDFLAGS = -pthread -lgtest - -CPP_HEADERS := ${wildcard cpp/*.h} ${wildcard cpp/super/duper/project/*.h} ${wildcard cpp/messgen/msgs/messgen_test/*.h} -CPP_SOURCES := ${wildcard cpp/*.cpp} ${wildcard cpp/super/duper/project/*.cpp} ${wildcard cpp/messgen/msgs/messgen_test/*.cpp} -CPP_OBJECTS := ${CPP_SOURCES:.cpp=.o} - -.PHONY: all cpp_tests_run messages clean - -all: cpp_tests_run - -messages: .done.messages -.done.messages: $(MESSAGES_YAML) - python3 ../generate.py --basedirs $(shell pwd)/messages --modules messgen/messgen_test --outdir $(shell pwd)/cpp --lang cpp - touch .done.messages - -cpp_tests_run: cpp_tests_build - ./cpp_test.elf - -cpp_tests_build: cpp_test.elf -cpp_test.elf: $(CPP_OBJECTS) - $(CPP) -o cpp_test.elf $(CPP_OBJECTS) $(LDFLAGS) - -$(CPP_OBJECTS): .done.messages $(CPP_HEADERS) - -%.o: %.cpp - $(CPP) $< $(CPPFLAGS) -o $@ - -clean: - rm -f .done.messages - rm -f ./cpp_test.elf - rm -f $(MESSAGES_CPP) - rm -f $(CPP_OBJECTS) diff --git a/tests/cpp/CppNostlTest.cpp b/tests/cpp/CppNostlTest.cpp new file mode 100644 index 00000000..2009057d --- /dev/null +++ b/tests/cpp/CppNostlTest.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +#include + +class CppNostlTest : public ::testing::Test { +protected: + std::vector _buf; + messgen::StaticAllocator<1024 * 1024> _alloc; + + template + void test_serialization(const T &msg) { + size_t sz_check = msg.serialized_size(); + + _buf.reserve(sz_check); + size_t ser_size = msg.serialize(&_buf[0]); + EXPECT_EQ(ser_size, sz_check); + + T msg1{}; + size_t deser_size = msg1.deserialize(&_buf[0], _alloc); + EXPECT_EQ(deser_size, sz_check); + + EXPECT_TRUE(msg == msg1); + } +}; + +TEST_F(CppNostlTest, SimpleStruct) { + messgen::test::simple_struct msg{}; + msg.f0 = 1; + msg.f1 = 2; + msg.f2 = 3; + msg.f3 = 4; + msg.f4 = 5; + msg.f5 = 6; + msg.f6 = 7; + msg.f8 = 9; + + test_serialization(msg); +} + +TEST_F(CppNostlTest, StructWithEnum) { + messgen::test::struct_with_enum msg{}; + msg.f0 = 1; + msg.f1 = 2; + msg.e0 = messgen::test::simple_enum::another_value; + + test_serialization(msg); +} + +TEST_F(CppNostlTest, VarSizeStruct) { + messgen::test::var_size_struct msg{}; + std::vector v; + v.resize(2); + v[0] = 3; + v[1] = 4; + + msg.f0 = 1; + msg.f1_vec = v; + + test_serialization(msg); +} + +TEST_F(CppNostlTest, ComplexStructNpstl) { + messgen::test::complex_struct_nostl msg{}; + msg.f0 = 255; + std::vector f2_vec; + f2_vec.push_back(45.787); + msg.f2_vec = f2_vec; + + msg.s_arr[0].f3 = 3; + msg.s_arr[1].f3 = 5; + + std::vector v_vec0_0_0; + v_vec0_0_0.emplace_back(777); + std::vector v_vec0_0; + v_vec0_0.emplace_back(234, v_vec0_0_0); + std::vector> v_vec0; + v_vec0.emplace_back(v_vec0_0); + msg.v_vec0 = v_vec0; + + test_serialization(msg); +} + +TEST_F(CppNostlTest, EmptyStruct) { + messgen::test::empty_struct e{}; + ASSERT_TRUE(e.IS_FLAT); + ASSERT_EQ(e.FLAT_SIZE, 0); + ASSERT_EQ(e.serialized_size(), 0); + test_serialization(e); +} + +TEST_F(CppNostlTest, TypeConcept) { + using namespace messgen; + + struct not_a_message {}; + + EXPECT_TRUE(type); + EXPECT_FALSE(type); + EXPECT_FALSE(type); +} + +TEST_F(CppNostlTest, FlatTypeConcept) { + using namespace messgen; + + EXPECT_TRUE(flat_type); + EXPECT_FALSE(flat_type); + EXPECT_FALSE(flat_type); +} diff --git a/tests/cpp/CppTest.cpp b/tests/cpp/CppTest.cpp new file mode 100644 index 00000000..7f99c131 --- /dev/null +++ b/tests/cpp/CppTest.cpp @@ -0,0 +1,308 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class CppTest : public ::testing::Test { +protected: + std::vector _buf; + + template + void test_serialization(const T &msg) { + size_t sz_check = msg.serialized_size(); + + _buf.resize(sz_check); + size_t ser_size = msg.serialize(_buf.data()); + EXPECT_EQ(ser_size, sz_check); + + T msg1{}; + size_t deser_size = msg1.deserialize(_buf.data()); + EXPECT_EQ(deser_size, sz_check); + + EXPECT_EQ(msg, msg1); + } + + template + void test_zerocopy(const T &msg) { + size_t sz_check = msg.serialized_size(); + + EXPECT_EQ(T::FLAT_SIZE, sz_check); + + _buf.resize(sz_check); + size_t ser_size = msg.serialize(_buf.data()); + EXPECT_EQ(ser_size, sz_check); + + EXPECT_EQ(memcmp(&msg, _buf.data(), sz_check), 0); + + T msg1{}; + size_t deser_size = msg1.deserialize(_buf.data()); + EXPECT_EQ(deser_size, sz_check); + + EXPECT_EQ(msg, msg1); + } +}; + +TEST_F(CppTest, SimpleStruct) { + test_proto::simple_struct_msg msg{{ + .f0 = 1, + .f1 = 2, + .f2 = 3, + .f3 = 4, + .f4 = 5, + .f5 = 6, + .f6 = 7, + .f8 = 9, + }}; + + test_serialization(msg); +} + +TEST_F(CppTest, StructWithEnum) { + messgen::test::struct_with_enum msg{}; + msg.f0 = 1; + msg.f1 = 2; + msg.e0 = messgen::test::simple_enum::another_value; + + test_serialization(msg); +} + +TEST_F(CppTest, VarSizeStruct) { + messgen::test::var_size_struct msg{}; + std::vector v; + v.resize(2); + v[0] = 3; + v[1] = 4; + + msg.f0 = 1; + msg.f1_vec = v; + + test_serialization(msg); +} + +TEST_F(CppTest, ComplexStruct) { + messgen::test::complex_struct msg{}; + + msg.f0 = 255; + msg.f2_vec.push_back(45.787); + msg.e_vec.push_back(messgen::test::simple_enum::another_value); + msg.s_arr[0].f3 = 3; + msg.s_arr[1].f3 = 5; + msg.v_vec0.resize(1); + msg.v_vec0[0].resize(2); + msg.v_vec0[0][0].f1_vec.resize(3); + msg.v_vec0[0][0].f1_vec[2] = 3242; + msg.v_vec2.resize(2); + msg.v_vec2[1][0].resize(3); + msg.v_vec2[1][0][2] = 5; + msg.str = "Hello messgen!"; + msg.bs.assign({1, 2, 3, 4, 5}); + msg.str_vec.push_back("spam"); + msg.str_vec.push_back("eggs"); + msg.str_vec.push_back("sticks"); + msg.map_str_by_int[23] = "ping"; + msg.map_str_by_int[777] = "pong"; + msg.map_vec_by_str["cat"].push_back(1); + msg.map_vec_by_str["cat"].push_back(2); + msg.map_vec_by_str["cat"].push_back(3); + msg.map_vec_by_str["dog"].push_back(30); + msg.map_vec_by_str["dog"].push_back(40); + + test_serialization(msg); +} + +TEST_F(CppTest, FlatStruct) { + messgen::test::flat_struct msg{}; + + msg.f0 = 1; + msg.f1 = 2; + msg.f2 = 3; + msg.f3 = 4; + msg.f4 = 5; + msg.f5 = 6; + msg.f6 = 7; + msg.f7 = 7; + msg.f8 = 9; + + test_serialization(msg); +} + +TEST_F(CppTest, FlatStructZeroCopy) { + messgen::test::flat_struct msg{}; + + msg.f0 = 1; + msg.f1 = 2; + msg.f2 = 3; + msg.f3 = 4; + msg.f4 = 5; + msg.f5 = 6; + msg.f6 = 7; + msg.f7 = 7; + msg.f8 = 9; + + test_zerocopy(msg); +} + +TEST_F(CppTest, TwoMsg) { + messgen::test::simple_struct msg1{}; + msg1.f0 = 1; + msg1.f1 = 2; + msg1.f2 = 3; + msg1.f3 = 4; + msg1.f4 = 5; + msg1.f5 = 6; + msg1.f6 = 7; + msg1.f8 = 9; + + messgen::test::flat_struct msg2{}; + msg2.f0 = 1; + msg2.f1 = 2; + msg2.f2 = 3; + msg2.f3 = 4; + msg2.f4 = 5; + msg2.f5 = 6; + msg2.f6 = 7; + msg2.f7 = 7; + msg2.f8 = 9; + + size_t sz_check = msg1.serialized_size() + msg2.serialized_size(); + + _buf.resize(sz_check); + size_t ser_size = msg1.serialize(_buf.data()); + ser_size += msg2.serialize(_buf.data() + ser_size); + + EXPECT_EQ(ser_size, sz_check); + + messgen::test::simple_struct msg1c{}; + messgen::test::flat_struct msg2c{}; + size_t deser_size = msg1c.deserialize(_buf.data()); + deser_size += msg2c.deserialize(_buf.data() + deser_size); + EXPECT_EQ(deser_size, sz_check); + + EXPECT_EQ(msg1, msg1c); + EXPECT_EQ(msg2, msg2c); +} + +TEST_F(CppTest, ComplexStructWithEmpty) { + messgen::test::complex_struct_with_empty e{}; + test_serialization(e); +} + +template +constexpr void for_each(std::tuple &&obj, Func &&func) { + std::apply([&](M &&...members) { (func(members), ...); }, obj); +} + +TEST_F(CppTest, MessageReflectionFieldNames) { + using namespace messgen; + + auto message = test::complex_struct{}; + + auto names = std::vector{}; + for_each(members_of(reflect_object(message)), [&](auto &¶m) { names.push_back(name_of(param)); }); + EXPECT_EQ(names.size(), 17); + + auto expected_names = std::vector{ + "f0", "f1", "f2", "s_arr", "f1_arr", "v_arr", "f2_vec", "e_vec", "s_vec", + "v_vec0", "v_vec1", "v_vec2", "str", "bs", "str_vec", "map_str_by_int", "map_vec_by_str", + }; + EXPECT_EQ(expected_names, names); +} + +TEST_F(CppTest, MessageReflectionFieldTypes) { + using namespace messgen; + + auto message = test::complex_struct{}; + + auto types = std::vector{}; + for_each(members_of(reflect_object(message)), [&](auto &¶m) { types.push_back(name_of(type_of(param))); }); + EXPECT_EQ(types.size(), 17); + + auto expected_types = std::vector{ + "uint64_t", + "uint32_t", + "uint64_t", + "array", + "array", + "array", + "vector", + "vector", + "vector", + "vector>", + "array, 4>", + "vector, 4>>", + "string", + "vector", + "vector", + "map", + "map>", + }; + EXPECT_EQ(expected_types, types); +} + +TEST_F(CppTest, EnumReflection) { + auto enum_name = messgen::name_of(messgen::reflect_type); + EXPECT_STREQ(enum_name.data(), "messgen::test::simple_enum"); +} + +TEST_F(CppTest, DispatchMessage) { + using namespace messgen; + + auto expected = test::simple_struct{ + .f0 = 1, + .f1 = 2, + }; + _buf.resize(expected.serialized_size()); + size_t ser_size = expected.serialize(_buf.data()); + + auto invoked = false; + auto handler = [&](auto &&actual) { + using ActualType = std::decay_t; + + if constexpr (std::is_same_v) { + EXPECT_EQ(expected.f0, actual.f0); + EXPECT_EQ(expected.f1, actual.f1); + invoked = true; + } else { + FAIL() << "Unexpected message type handled."; + } + }; + + test_proto::dispatch_message(test_proto::simple_struct_msg::MESSAGE_ID, _buf.data(), handler); + + EXPECT_TRUE(invoked); +} + +TEST_F(CppTest, TypeConcept) { + using namespace messgen; + + struct not_a_message {}; + + EXPECT_TRUE(type); + EXPECT_TRUE(type); + EXPECT_FALSE(type); + EXPECT_FALSE(type); +} + +TEST_F(CppTest, FlatTypeConcept) { + using namespace messgen; + + EXPECT_TRUE(flat_type); + EXPECT_FALSE(flat_type); + EXPECT_FALSE(flat_type); +} + +TEST_F(CppTest, MessageConcept) { + using namespace messgen; + + struct not_a_message {}; + + EXPECT_FALSE(message); + EXPECT_FALSE(message); + EXPECT_TRUE(message); +} \ No newline at end of file diff --git a/tests/cpp/MessgenTest.h b/tests/cpp/MessgenTest.h deleted file mode 100644 index 6560a13e..00000000 --- a/tests/cpp/MessgenTest.h +++ /dev/null @@ -1,471 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -static constexpr int OK = 0; - - -template -inline void compact(std::vector & vec, size_t N) { - size_t to_cpy = vec.size() - N; - memmove(&vec[0], &vec[N], to_cpy); - vec.resize(to_cpy); -} - - -class TestMessgen : public ::testing::Test { -public: - TestMessgen() : - gen(SEED) { - _ser_buf.reserve(SERIALIZE_BUF_SIZE); - } - -protected: - void SetUp() final { - using namespace messgen::msgs::messgen_test; - _ser_buf.clear(); - - _simple_msg = gen_random_simple_msg(); - - for (size_t i = 0; i < sizeof(_embedded_d1_msg.f0) / sizeof(_embedded_d1_msg.f0[0]); i++) { - _embedded_d1_msg.f0[i] = i; - } - - _embedded_d1_msg.f1[0] = _simple_msg; - _embedded_d1_msg.f1[1] = _simple_msg; - - for (size_t i = 0; i < sizeof(_embedded_d1_msg.f2) / sizeof(_embedded_d1_msg.f2[0]); i++) { - _embedded_d1_msg.f2[i] = i + 20; - } - - _embedded_d1_msg.f3 = _simple_msg; - - _embedded_d2_msg.f0 = 255; - _embedded_d2_msg.f1[0] = 10; - _embedded_d2_msg.f1[1] = 20; - _embedded_d2_msg.f2 = _embedded_d1_msg; - _embedded_d2_msg.f3 = _simple_msg; - _embedded_d2_msg.f3.f3 = 4000; - _embedded_d2_msg.f4 = 2; - _embedded_d2_msg.f5 = _embedded_d1_msg; - _embedded_d2_msg.f5.f2[0] = -100; - - _simple_dynamic_msg = gen_random_simple_dynamic_msg(); - - _embedded_dyn_d1_msg.f0 = messgen::Dynamic{nullptr, 0}; - _embedded_dyn_d1_msg.f1 = make_dynamic(5); - for (size_t i = 0; i < 5; ++i) { - _embedded_dyn_d1_msg.f1[i] = gen_random_simple_dynamic_msg(); - } - - _embedded_dyn_d1_msg.f2 = make_dynamic(5); - for (size_t i = 0; i < 5; ++i) { - _embedded_dyn_d1_msg.f2[i] = gen_random_simple_msg(); - } - _embedded_dyn_d1_msg.f3 = 15; - _embedded_dyn_d1_msg.f4 = gen_random_plain_dynamic_field(); - _embedded_dyn_d1_msg.f5[0] = gen_random_simple_dynamic_msg(); - _embedded_dyn_d1_msg.f5[1] = gen_random_simple_dynamic_msg(); - _embedded_dyn_d1_msg.f6 = gen_random_simple_dynamic_msg(); - _embedded_dyn_d1_msg.f7 = ""; - _embedded_dyn_d1_msg.f8 = "Embedded string"; - - _use_one_existing.one = {42}; - - memset(&_ser_buf[0], 0, SERIALIZE_BUF_SIZE); - } - - - void TearDown() final { - delete[] _simple_dynamic_msg.f0.ptr; - delete[] _simple_dynamic_msg.f3.ptr; - delete[] _simple_dynamic_msg.f8.ptr; - delete[] _simple_dynamic_msg.f9.ptr; - } - -protected: - messgen::msgs::messgen_test::simple_message _simple_msg{}; - messgen::msgs::messgen_test::embedded_message_d1 _embedded_d1_msg{}; - messgen::msgs::messgen_test::embedded_message_d2 _embedded_d2_msg{}; - messgen::msgs::messgen_test::simple_dynamic_message _simple_dynamic_msg{}; - messgen::msgs::messgen_test::embedded_dynamic_message_d1 _embedded_dyn_d1_msg{}; - messgen::msgs::messgen_test::empty _empty_msg{}; - messgen::msgs::messgen_test::use_one_existing _use_one_existing{}; - - static constexpr size_t MEMORY_POOL_SIZE = 1024*10; - static constexpr size_t SERIALIZE_BUF_SIZE = 1024*2; - static constexpr size_t SEED = 14; - - std::vector _ser_buf; - messgen::StaticMemoryAllocator<1024*10> _messgen_alloc; - std::default_random_engine gen; - - template - static void serialize_and_assert_to_vec(const T & msg, std::vector &vec) { - size_t init_buf_size = vec.size(); - int res = messgen::stl::serialize(msg, vec); - - ASSERT_EQ(res, messgen::get_serialized_size(msg)); - ASSERT_EQ(vec.size(), init_buf_size + messgen::get_serialized_size(msg)); - } - - template - void serialize_and_assert(const T & msg) { - serialize_and_assert_to_vec(msg, _ser_buf); - } - - messgen::msgs::messgen_test::simple_dynamic_message gen_random_simple_dynamic_msg() { - messgen::msgs::messgen_test::simple_dynamic_message msg{}; - msg.f0 = gen_random_plain_dynamic_field(); - msg.f1 = random < int64_t > (); - msg.f2 = random < double > (); - msg.f3 = gen_random_plain_dynamic_field(); - msg.f4 = random < int32_t > (); - msg.f5 = random < float > (); - msg.f6 = random < uint16_t > (); - msg.f7 = random < int16_t > (); - msg.f8 = gen_random_plain_dynamic_field(); - msg.f9 = gen_random_plain_dynamic_field(); - msg.non_null_string1 = "string1"; - msg.non_null_string2 = "string2"; - - return msg; - } - - messgen::msgs::messgen_test::simple_message gen_random_simple_msg() { - messgen::msgs::messgen_test::simple_message msg{}; - msg.f0 = random < uint64_t > (); - msg.f1 = random < int64_t > (); - msg.f2 = random < double > (); - msg.f3 = random < uint32_t > (); - msg.f4 = random < int32_t > (); - msg.f5 = random < float > (); - msg.f6 = random < uint16_t > (); - msg.f7 = random < int16_t > (); - msg.f8 = random < uint8_t > (); - msg.f9 = random < int8_t > (); - - return msg; - } - - template - messgen::Dynamic gen_random_plain_dynamic_field() { - auto rand_len = random < uint16_t > (0, 10); - auto field = make_dynamic(rand_len); - - for (uint16_t i = 0; i < rand_len; ++i) { - field[i] = random < T > (); - } - - return field; - } - - template - messgen::Dynamic make_dynamic(uint16_t size) { - return messgen::Dynamic{new T[size], size}; - } - - template::value>> - T random(T MIN_LEN = std::numeric_limits::min(), - T MAX_LEN = std::numeric_limits::max(), - int hack = 0) { - std::uniform_int_distribution dist(0, MAX_LEN); - return dist(gen); - } - - template::value>> - T random(T MIN_LEN = std::numeric_limits::min(), - T MAX_LEN = std::numeric_limits::max(), - double hack = 0) { - std::uniform_real_distribution dist(0, MAX_LEN); - return dist(gen); - } -}; - - -TEST_F(TestMessgen, PlainMessageTest) { - using namespace messgen::msgs::messgen_test; - - serialize_and_assert(_simple_msg); - - messgen::MessageInfo msg_info{}; - messgen::msgs::messgen_test::simple_message parsed_msg{}; - - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, _simple_msg.TYPE); - ASSERT_EQ(messgen::parse(msg_info, parsed_msg, _messgen_alloc), msg_info.size); - ASSERT_EQ(_simple_msg, parsed_msg); -} - -TEST_F(TestMessgen, EmptyMessgenTest) { - using namespace messgen::msgs::messgen_test; - - serialize_and_assert(_empty_msg); - - messgen::MessageInfo msg_info{}; - messgen::msgs::messgen_test::empty parsed_msg{}; - - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, _empty_msg.TYPE); - ASSERT_EQ(messgen::parse(msg_info, parsed_msg, _messgen_alloc), msg_info.size); - ASSERT_EQ(_empty_msg, parsed_msg); -} - -TEST_F(TestMessgen, NestedMessagesTest) { - using namespace messgen::msgs::messgen_test; - - serialize_and_assert(_embedded_d2_msg); - - messgen::MessageInfo msg_info{}; - embedded_message_d2 parsed_msg{}; - - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, embedded_message_d2::TYPE); - ASSERT_EQ(messgen::parse(msg_info, parsed_msg, _messgen_alloc), msg_info.size); - ASSERT_EQ(_embedded_d2_msg, parsed_msg); -} - - -TEST_F(TestMessgen, OverflowTest) { - using namespace messgen::msgs::messgen_test; - - std::vector small_vec; - small_vec.reserve(12); - ASSERT_EQ(messgen::stl::serialize(_simple_msg, small_vec), -1); - ASSERT_EQ(small_vec.size(), 0); -} - - -TEST_F(TestMessgen, MultipleMessagesSerializeParse) { - using namespace messgen::msgs::messgen_test; - - messgen::MessageInfo msg_info{}; - - embedded_message_d1 d1_parsed{}; - embedded_message_d2 d2_parsed{}; - simple_message simple_parsed{}; - - serialize_and_assert(_embedded_d1_msg); - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, embedded_message_d1::TYPE); - - serialize_and_assert(_embedded_d2_msg); - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, embedded_message_d1::TYPE); - - ASSERT_EQ(messgen::parse(msg_info, d1_parsed, _messgen_alloc), msg_info.size); - ASSERT_EQ(_embedded_d1_msg, d1_parsed); - compact(_ser_buf, msg_info.get_total_size()); - - // Check that the first message is d2. - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, embedded_message_d2::TYPE); - - // Serialize new message - serialize_and_assert(_simple_msg); - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, embedded_message_d2::TYPE); - - // Parse d2 - ASSERT_EQ(messgen::parse(msg_info, d2_parsed, _messgen_alloc), msg_info.size); - ASSERT_EQ(_embedded_d2_msg, d2_parsed); - compact(_ser_buf, msg_info.get_total_size()); - - // Check that the first message is d1. - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, simple_message::TYPE); - - ASSERT_EQ(messgen::parse(msg_info, simple_parsed, _messgen_alloc), msg_info.size); - ASSERT_EQ(_simple_msg, simple_parsed); - - // All message parser -> slice must be empty. - compact(_ser_buf, msg_info.get_total_size()); - ASSERT_EQ(_ser_buf.size(), 0); -} - - -TEST_F(TestMessgen, TestSimpleDynamicMessage) { - using namespace messgen::msgs::messgen_test; - - serialize_and_assert(_simple_dynamic_msg); - - messgen::MessageInfo msg_info{}; - - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, simple_dynamic_message::TYPE); - - messgen::msgs::messgen_test::simple_dynamic_message parsed_simple_dyn_msg{}; - ASSERT_EQ(messgen::parse(msg_info, parsed_simple_dyn_msg, _messgen_alloc), msg_info.size); - ASSERT_EQ(_simple_dynamic_msg, parsed_simple_dyn_msg); -} - - -TEST_F(TestMessgen, TestEmbeddedDynamicMessage) { - using namespace messgen::msgs::messgen_test; - - serialize_and_assert(_embedded_dyn_d1_msg); - - messgen::MessageInfo msg_info{}; - messgen::msgs::messgen_test::embedded_dynamic_message_d1 parsed_embedded_dyn_msg{}; - - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, embedded_dynamic_message_d1::TYPE); - ASSERT_EQ(messgen::parse(msg_info, parsed_embedded_dyn_msg, _messgen_alloc), msg_info.size); - ASSERT_EQ(_embedded_dyn_d1_msg, parsed_embedded_dyn_msg); -} - -TEST_F(TestMessgen, SimpleDetectorIntegrals) { - static_assert(messgen::SimpleDetector::is_simple_enough, "char"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint8_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint16_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint32_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint64_t"); - static_assert(messgen::SimpleDetector::is_simple_enough,"int8_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "int16_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "int32_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "int64_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "float"); - static_assert(messgen::SimpleDetector::is_simple_enough, "double"); - SUCCEED(); -} - -TEST_F(TestMessgen, SimpleDetectorDynamics) { - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "char"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "uint8_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "uint16_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "uint32_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "uint64_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough,"int8_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "int16_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "int32_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "int64_t"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "float"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "double"); - SUCCEED(); -} - -TEST_F(TestMessgen, SimpleDetectorArrays) { - static_assert(messgen::SimpleDetector::is_simple_enough, "char"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint8_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint16_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint32_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "uint64_t"); - static_assert(messgen::SimpleDetector::is_simple_enough,"int8_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "int16_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "int32_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "int64_t"); - static_assert(messgen::SimpleDetector::is_simple_enough, "float"); - static_assert(messgen::SimpleDetector::is_simple_enough, "double"); - - static_assert(false == messgen::SimpleDetector::is_simple_enough, "string_view"); - static_assert(false == messgen::SimpleDetector[4]>::is_simple_enough, "double"); - - SUCCEED(); -} - -TEST_F(TestMessgen, SimpleDetectorStrings) { - static_assert(messgen::SimpleDetector::is_simple_enough, "char"); - - static_assert(false == messgen::SimpleDetector::is_simple_enough, "string_view"); - static_assert(false == messgen::SimpleDetector>::is_simple_enough, "dynamic char"); - - SUCCEED(); -} - -TEST_F(TestMessgen, SimpleDetectorStructs) { - static_assert(false == messgen::SimpleDetector::is_simple_enough, "simple_message"); - static_assert(false == messgen::SimpleDetector::is_simple_enough, "implicit_padding"); - static_assert(true == messgen::SimpleDetector::is_simple_enough, "explicit_padding"); - - SUCCEED(); -} - -TEST_F(TestMessgen, UseExisting) { - static_assert(true == messgen::SimpleDetector::is_simple_enough, "existing"); - static_assert(false == messgen::SimpleDetector::is_simple_enough, "use existing"); - static_assert(sizeof(uint32_t) == messgen::msgs::messgen_test::use_existing::STATIC_SIZE, "static size"); - static_assert(std::is_same::value, "aliasing"); - SUCCEED(); -} - -TEST_F(TestMessgen, UseOneExisting) { - static_assert(true == messgen::SimpleDetector::is_simple_enough, "simple enough"); - static_assert(sizeof(entities::msgs::Existing) == messgen::msgs::messgen_test::use_one_existing::STATIC_SIZE, "same size"); - - using namespace messgen::msgs::messgen_test; - - serialize_and_assert(_use_one_existing); - - messgen::MessageInfo msg_info{}; - - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, use_one_existing::TYPE); - - messgen::msgs::messgen_test::use_one_existing parsed_use_one_existing_msg{}; - ASSERT_EQ(messgen::parse(msg_info, parsed_use_one_existing_msg, _messgen_alloc), msg_info.size); - ASSERT_EQ(_use_one_existing, parsed_use_one_existing_msg); -} - -TEST_F(TestMessgen, StorageSerialization) { - using namespace messgen::msgs; - - messgen_test::simple_message simple_msg{gen_random_simple_msg()}; - messgen_test::simple_dynamic_message simple_dynamic_msg{gen_random_simple_dynamic_msg()}; - - messgen::Storage simple_storage{simple_msg}; - messgen::Storage dynamic_storage{simple_dynamic_msg}; - - std::vector vec1; - std::vector vec2; - vec1.reserve(16384); - vec2.reserve(16384); - - serialize_and_assert_to_vec(simple_msg, vec1); - serialize_and_assert_to_vec(simple_dynamic_msg, vec1); - serialize_and_assert_to_vec(simple_storage, vec2); - serialize_and_assert_to_vec(dynamic_storage, vec2); - - ASSERT_EQ(vec1, vec2); -} - -TEST_F(TestMessgen, StorageParseTest) { - using namespace messgen::msgs; - - // Serialize messages - messgen_test::simple_message simple_msg{gen_random_simple_msg()}; - messgen_test::simple_dynamic_message dynamic_msg{gen_random_simple_dynamic_msg()}; - - serialize_and_assert(simple_msg); - serialize_and_assert(dynamic_msg); - - // Parse messages - messgen::MessageInfo msg_info{}; - messgen::Storage simple_storage; - messgen::Storage dynamic_storage; - - // Parse simple msg - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, messgen_test::simple_message::TYPE); - ASSERT_EQ(messgen::parse(msg_info, simple_storage), msg_info.size); - ASSERT_EQ(simple_msg, simple_storage); - compact(_ser_buf, msg_info.get_total_size()); - - // Parse simple dynamic msg - ASSERT_EQ(messgen::stl::get_message_info(_ser_buf, msg_info), OK); - ASSERT_EQ(msg_info.msg_id, messgen_test::simple_dynamic_message::TYPE); - ASSERT_EQ(messgen::parse(msg_info, dynamic_storage), msg_info.size); - ASSERT_EQ(dynamic_msg, dynamic_storage); - compact(_ser_buf, msg_info.get_total_size()); - - // Validate that buffer is empty - ASSERT_EQ(_ser_buf.size(), 0); -} \ No newline at end of file diff --git a/tests/cpp/entities/msgs/Existing.h b/tests/cpp/entities/msgs/Existing.h deleted file mode 100644 index 4b7cf1fd..00000000 --- a/tests/cpp/entities/msgs/Existing.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -namespace entities::msgs { - using Existing = super::duper::project::existing; -} \ No newline at end of file diff --git a/tests/cpp/main.cpp b/tests/cpp/main.cpp deleted file mode 100644 index 5f153485..00000000 --- a/tests/cpp/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "MessgenTest.h" - -#include - - -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/cpp/super/duper/project/existing.cpp b/tests/cpp/super/duper/project/existing.cpp deleted file mode 100644 index a2a16751..00000000 --- a/tests/cpp/super/duper/project/existing.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "existing.h" - -namespace super::duper::project { - -static const messgen::Metadata *nested_msgs[] = {nullptr}; -const messgen::Metadata existing::METADATA = { - "Existing", - "int data;", - nested_msgs -}; - -} \ No newline at end of file diff --git a/tests/cpp/super/duper/project/existing.h b/tests/cpp/super/duper/project/existing.h deleted file mode 100644 index 2f4aa309..00000000 --- a/tests/cpp/super/duper/project/existing.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace super::duper::project { - -struct existing{ - int data; - - static const size_t STATIC_SIZE = sizeof(data); - - bool operator==(const existing& other) const { return data == other.data; } - - std::size_t get_size() const { return STATIC_SIZE; } - - static const messgen::Metadata METADATA; -}; - -} - -namespace messgen { - -template<> -struct SimpleDetector { - static const bool is_simple_enough = true; -}; - -} // messgen \ No newline at end of file diff --git a/tests/cpp/utils.h b/tests/cpp/utils.h deleted file mode 100644 index 8acc3541..00000000 --- a/tests/cpp/utils.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - - -microavia::net_device::Frame generate_random_message(uint8_t MAX_PROTO_TYPE=255, - uint8_t MAX_SRC_ADDRESS=255, uint8_t MAX_DST_ADDRESS=255, uint16_t MIN_LEN=0, uint16_t MAX_LEN=65535) { - microavia::net_device::Frame msg; - msg.proto_id = std::rand() % MAX_PROTO_TYPE; - msg.dst_addr = (std::rand() % MAX_DST_ADDRESS); - msg.src_addr = std::rand() % MAX_SRC_ADDRESS; - - size_t payload_size = MIN_LEN + (std::rand() % (MAX_LEN-MIN_LEN)); - - uint8_t *buf = nullptr; - if (payload_size != 0) { - buf = new uint8_t[payload_size]; - - for (int i = 0; i < payload_size; i++) { - buf[i] = std::rand() % 256; - } - } - - msg.payload = microavia::ConstSlice(buf, payload_size); - return msg; -} - - -void test_equal(const microavia::net_device::Frame &frame1, const microavia::net_device::Frame &frame2) { - ASSERT_EQ(frame1.src_addr, frame2.src_addr); - ASSERT_EQ(frame1.dst_addr, frame2.dst_addr); - ASSERT_EQ(frame1.proto_id, frame2.proto_id); - ASSERT_EQ(frame1.payload.size(), frame2.payload.size()); - ASSERT_EQ(memcmp(frame1.payload.begin(), frame2.payload.begin(), frame2.payload.size()), 0); - -} \ No newline at end of file diff --git a/tests/data/protocols/nested/another_proto.yaml b/tests/data/protocols/nested/another_proto.yaml new file mode 100644 index 00000000..87a82b62 --- /dev/null +++ b/tests/data/protocols/nested/another_proto.yaml @@ -0,0 +1,4 @@ +proto_id: 2 +messages: + 0: {name: "cross_proto_msg", type: "cross_proto", comment: "Cross proto message"} + diff --git a/tests/data/protocols/test_proto.yaml b/tests/data/protocols/test_proto.yaml new file mode 100644 index 00000000..ac171602 --- /dev/null +++ b/tests/data/protocols/test_proto.yaml @@ -0,0 +1,10 @@ +proto_id: 1 +messages: + 0: {name: "simple_struct_msg", type: "messgen/test/simple_struct", comment: "Simple struct message"} + 1: {name: "complex_struct_msg", type: "messgen/test/complex_struct", comment: "Complex struct message"} + 2: {name: "var_size_struct_msg", type: "messgen/test/var_size_struct", comment: "Variable size struct message"} + 3: {name: "struct_with_enum_msg", type: "messgen/test/struct_with_enum", comment: "Struct with enum message"} + 4: {name: "empty_struct_msg", type: "messgen/test/empty_struct", comment: "Empty struct message"} + 5: {name: "complex_struct_with_empty_msg", type: "messgen/test/complex_struct_with_empty", comment: "Complex struct with empty message"} + 6: {name: "complex_struct_nostl_msg", type: "messgen/test/complex_struct_nostl", comment: "Complex struct without STL message"} + 7: {name: "flat_struct_msg", type: "messgen/test/flat_struct", comment: "Flat struct message"} \ No newline at end of file diff --git a/tests/data/serialized/README.md b/tests/data/serialized/README.md new file mode 100644 index 00000000..d69f0e16 --- /dev/null +++ b/tests/data/serialized/README.md @@ -0,0 +1,2 @@ +# Serialized data +Serialized message instances filled with test data for cross-language tests. \ No newline at end of file diff --git a/tests/data/serialized/bin/complex_struct.bin b/tests/data/serialized/bin/complex_struct.bin new file mode 100644 index 00000000..dfa9d8d0 Binary files /dev/null and b/tests/data/serialized/bin/complex_struct.bin differ diff --git a/tests/data/serialized/bin/complex_struct_nostl.bin b/tests/data/serialized/bin/complex_struct_nostl.bin new file mode 100644 index 00000000..0a9cff05 Binary files /dev/null and b/tests/data/serialized/bin/complex_struct_nostl.bin differ diff --git a/tests/data/serialized/bin/complex_struct_with_empty.bin b/tests/data/serialized/bin/complex_struct_with_empty.bin new file mode 100644 index 00000000..a75add5b Binary files /dev/null and b/tests/data/serialized/bin/complex_struct_with_empty.bin differ diff --git a/tests/messages/messgen/messgen_test/_constants.yaml b/tests/data/serialized/bin/empty_struct.bin similarity index 100% rename from tests/messages/messgen/messgen_test/_constants.yaml rename to tests/data/serialized/bin/empty_struct.bin diff --git a/tests/data/serialized/bin/flat_struct.bin b/tests/data/serialized/bin/flat_struct.bin new file mode 100644 index 00000000..f8d8b6a1 --- /dev/null +++ b/tests/data/serialized/bin/flat_struct.bin @@ -0,0 +1 @@ +ͫxV4ͫxV4YB?xV4xV4R?4 \ No newline at end of file diff --git a/tests/data/serialized/bin/simple_struct.bin b/tests/data/serialized/bin/simple_struct.bin new file mode 100644 index 00000000..4c857229 --- /dev/null +++ b/tests/data/serialized/bin/simple_struct.bin @@ -0,0 +1 @@ +ͫxV4ͫxV4YB?xV4xV4R?4 \ No newline at end of file diff --git a/tests/data/serialized/bin/struct_with_enum.bin b/tests/data/serialized/bin/struct_with_enum.bin new file mode 100644 index 00000000..481de7a2 --- /dev/null +++ b/tests/data/serialized/bin/struct_with_enum.bin @@ -0,0 +1 @@ +ͫxV4ͫxV4 \ No newline at end of file diff --git a/tests/data/serialized/bin/var_size_struct.bin b/tests/data/serialized/bin/var_size_struct.bin new file mode 100644 index 00000000..35a16327 Binary files /dev/null and b/tests/data/serialized/bin/var_size_struct.bin differ diff --git a/tests/data/types/cross_proto.yaml b/tests/data/types/cross_proto.yaml new file mode 100644 index 00000000..4b7a8744 --- /dev/null +++ b/tests/data/types/cross_proto.yaml @@ -0,0 +1,5 @@ +type_class: struct +comment: "Struct that uses types from another protocol" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "cross0", type: "messgen/test/simple_enum", comment: "Type from another protocol" } diff --git a/tests/data/types/messgen/test/complex_struct.yaml b/tests/data/types/messgen/test/complex_struct.yaml new file mode 100644 index 00000000..f0f6b852 --- /dev/null +++ b/tests/data/types/messgen/test/complex_struct.yaml @@ -0,0 +1,20 @@ +type_class: struct +comment: "Simple struct example" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1", type: "uint32", comment: "Some integer field" } + - { name: "f2", type: "uint64", comment: "Some integer field" } + - { name: "s_arr", type: "simple_struct[2]" } + - { name: "f1_arr", type: "int64[4]", comment: "Another integer field" } + - { name: "v_arr", type: "messgen/test/var_size_struct[2]" } + - { name: "f2_vec", type: "float64[]" } + - { name: "e_vec", type: "messgen/test/simple_enum[]" } + - { name: "s_vec", type: "messgen/test/simple_struct[]" } + - { name: "v_vec0", type: "messgen/test/var_size_struct[][]" } + - { name: "v_vec1", type: "messgen/test/var_size_struct[][4]" } + - { name: "v_vec2", type: "int16[][4][]" } + - { name: "str", type: "string" } + - { name: "bs", type: "bytes" } + - { name: "str_vec", type: "string[]" } + - { name: "map_str_by_int", type: "string{int32}" } + - { name: "map_vec_by_str", type: "int32[]{string}" } diff --git a/tests/data/types/messgen/test/complex_struct_nostl.yaml b/tests/data/types/messgen/test/complex_struct_nostl.yaml new file mode 100644 index 00000000..4aad6495 --- /dev/null +++ b/tests/data/types/messgen/test/complex_struct_nostl.yaml @@ -0,0 +1,17 @@ +type_class: struct +comment: "Simple struct example" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1", type: "uint32", comment: "Some integer field" } + - { name: "f2", type: "uint64", comment: "Some integer field" } + - { name: "s_arr", type: "messgen/test/simple_struct[2]" } + - { name: "f1_arr", type: "int64[4]", comment: "Another integer field" } + - { name: "v_arr", type: "messgen/test/var_size_struct[2]" } + - { name: "f2_vec", type: "float64[]" } + - { name: "e_vec", type: "messgen/test/simple_enum[]" } + - { name: "s_vec", type: "messgen/test/simple_struct[]" } + - { name: "v_vec0", type: "messgen/test/var_size_struct[][]" } + - { name: "v_vec1", type: "messgen/test/var_size_struct[][4]" } + - { name: "v_vec2", type: "int16[][4][]" } + - { name: "str", type: "string" } + - { name: "str_vec", type: "string[]" } diff --git a/tests/data/types/messgen/test/complex_struct_with_empty.yaml b/tests/data/types/messgen/test/complex_struct_with_empty.yaml new file mode 100644 index 00000000..9459f468 --- /dev/null +++ b/tests/data/types/messgen/test/complex_struct_with_empty.yaml @@ -0,0 +1,10 @@ +type_class: struct +comment: "Struct with empty_struct in its fields" +fields: + - { name: "e", type: "messgen/test/empty_struct" } + - { name: "dynamic_array", type: "messgen/test/empty_struct[]" } + - { name: "static_array", type: "messgen/test/empty_struct[5]" } + - { name: "multi_array", type: "messgen/test/empty_struct[][5][]" } + - { name: "map_empty_by_int", type: "messgen/test/empty_struct{int32}" } + - { name: "map_vec_by_str", type: "messgen/test/empty_struct[]{string}" } + - { name: "array_of_size_zero", type: "int32[0]" } diff --git a/tests/data/types/messgen/test/empty_struct.yaml b/tests/data/types/messgen/test/empty_struct.yaml new file mode 100644 index 00000000..d6f053fa --- /dev/null +++ b/tests/data/types/messgen/test/empty_struct.yaml @@ -0,0 +1,3 @@ +type_class: struct +comment: "Struct without data. May be used for heartbeat, command with no args, etc" +fields: diff --git a/tests/data/types/messgen/test/flat_struct.yaml b/tests/data/types/messgen/test/flat_struct.yaml new file mode 100644 index 00000000..329b5679 --- /dev/null +++ b/tests/data/types/messgen/test/flat_struct.yaml @@ -0,0 +1,12 @@ +type_class: struct +comment: "Flat struct without paddings, for zero-copy" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1", type: "int64", comment: "Another integer field" } + - { name: "f2", type: "float64" } + - { name: "f3", type: "uint32" } + - { name: "f4", type: "int32" } + - { name: "f5", type: "float32" } + - { name: "f6", type: "uint16" } + - { name: "f7", type: "uint8" } + - { name: "f8", type: "int8" } diff --git a/tests/data/types/messgen/test/simple_enum.yaml b/tests/data/types/messgen/test/simple_enum.yaml new file mode 100644 index 00000000..7ef3cec9 --- /dev/null +++ b/tests/data/types/messgen/test/simple_enum.yaml @@ -0,0 +1,6 @@ +type_class: enum +comment: "Example of simple enum" +base_type: uint8 +values: + - { name: "one_value", value: 0, comment: "One example value" } + - { name: "another_value", value: 1, comment: "Another example value" } diff --git a/tests/data/types/messgen/test/simple_struct.yaml b/tests/data/types/messgen/test/simple_struct.yaml new file mode 100644 index 00000000..9785dfac --- /dev/null +++ b/tests/data/types/messgen/test/simple_struct.yaml @@ -0,0 +1,14 @@ +type_class: struct +comment: "Simple struct example" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1", type: "int64", comment: "Another integer field" } + - { name: "f1_pad", type: "uint8" } + - { name: "f2", type: "float64" } + - { name: "f3", type: "uint32" } + - { name: "f4", type: "int32" } + - { name: "f5", type: "float32" } + - { name: "f6", type: "uint16" } + - { name: "f7", type: "uint8" } + - { name: "f8", type: "int8" } + - { name: "f9", type: "bool" } diff --git a/tests/data/types/messgen/test/struct_with_enum.yaml b/tests/data/types/messgen/test/struct_with_enum.yaml new file mode 100644 index 00000000..5a83b1b3 --- /dev/null +++ b/tests/data/types/messgen/test/struct_with_enum.yaml @@ -0,0 +1,6 @@ +type_class: struct +comment: "Struct with enum example" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1", type: "int64", comment: "Another integer field" } + - { name: "e0", type: "messgen/test/simple_enum" } diff --git a/tests/data/types/messgen/test/var_size_struct.yaml b/tests/data/types/messgen/test/var_size_struct.yaml new file mode 100644 index 00000000..71418a7b --- /dev/null +++ b/tests/data/types/messgen/test/var_size_struct.yaml @@ -0,0 +1,6 @@ +type_class: struct +comment: "Variable size struct example" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1_vec", type: "int64[]", comment: "Variable size field" } + - { name: "str", type: "string" } diff --git a/tests/data/types/one_more_message.yaml b/tests/data/types/one_more_message.yaml new file mode 100644 index 00000000..ba472792 --- /dev/null +++ b/tests/data/types/one_more_message.yaml @@ -0,0 +1,13 @@ +type_class: struct +comment: "Simple struct example" +fields: + - { name: "f0", type: "uint64", comment: "Some integer field" } + - { name: "f1", type: "int64", comment: "Another integer field" } + - { name: "f1_pad", type: "uint8" } + - { name: "f2", type: "float64" } + - { name: "f3", type: "uint32" } + - { name: "f4", type: "int32" } + - { name: "f5", type: "float32" } + - { name: "f6", type: "uint16" } + - { name: "f7", type: "uint8" } + - { name: "f8", type: "int8" } diff --git a/tests/data/types_invalid/README.md b/tests/data/types_invalid/README.md new file mode 100644 index 00000000..47e6f890 --- /dev/null +++ b/tests/data/types_invalid/README.md @@ -0,0 +1,3 @@ +## Invalid protocols for testing generator error handling + +Each invalid message is in its own protocol so that we can test them individually \ No newline at end of file diff --git a/tests/data/types_invalid/absent_field_name/absent_field_name.yaml b/tests/data/types_invalid/absent_field_name/absent_field_name.yaml new file mode 100644 index 00000000..89f76e12 --- /dev/null +++ b/tests/data/types_invalid/absent_field_name/absent_field_name.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { type: "int8", comment: "I have type and a comment but no name" } diff --git a/tests/data/types_invalid/absent_field_type/absent_field_type.yaml b/tests/data/types_invalid/absent_field_type/absent_field_type.yaml new file mode 100644 index 00000000..a08d0044 --- /dev/null +++ b/tests/data/types_invalid/absent_field_type/absent_field_type.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { name: "a", comment: "I have a name and a comment but no type" } diff --git a/tests/data/types_invalid/cyclic_type_reference/message_a.yaml b/tests/data/types_invalid/cyclic_type_reference/message_a.yaml new file mode 100644 index 00000000..561d4eba --- /dev/null +++ b/tests/data/types_invalid/cyclic_type_reference/message_a.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { name: "b", type: "message_b" } diff --git a/tests/data/types_invalid/cyclic_type_reference/message_b.yaml b/tests/data/types_invalid/cyclic_type_reference/message_b.yaml new file mode 100644 index 00000000..554ea3be --- /dev/null +++ b/tests/data/types_invalid/cyclic_type_reference/message_b.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { name: "a", type: "message_a" } diff --git a/tests/data/types_invalid/duplicate_field_names/duplicate_field_names.yaml b/tests/data/types_invalid/duplicate_field_names/duplicate_field_names.yaml new file mode 100644 index 00000000..882b8d06 --- /dev/null +++ b/tests/data/types_invalid/duplicate_field_names/duplicate_field_names.yaml @@ -0,0 +1,5 @@ +type_class: struct +fields: + - { name: "hello", type: "int8", comment: "first field" } + - { name: "world", type: "uint8" } + - { name: "hello", type: "uint16", comment: "second field with same name as first field but different type" } diff --git a/tests/data/types_invalid/empty_field_name/empty_field_name.yaml b/tests/data/types_invalid/empty_field_name/empty_field_name.yaml new file mode 100644 index 00000000..d642f7c4 --- /dev/null +++ b/tests/data/types_invalid/empty_field_name/empty_field_name.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { name: "", type: "int8", comment: "The name of this field is empty" } diff --git a/tests/data/types_invalid/field_name_is_cpp_keyword/field_name_is_cpp_keyword.yaml b/tests/data/types_invalid/field_name_is_cpp_keyword/field_name_is_cpp_keyword.yaml new file mode 100644 index 00000000..6e6978f3 --- /dev/null +++ b/tests/data/types_invalid/field_name_is_cpp_keyword/field_name_is_cpp_keyword.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { name: "int", type: "int8", comment: "The name of this field is also a cpp keyword" } diff --git a/tests/data/types_invalid/field_name_is_not_string/field_name_is_not_string.yaml b/tests/data/types_invalid/field_name_is_not_string/field_name_is_not_string.yaml new file mode 100644 index 00000000..64666dda --- /dev/null +++ b/tests/data/types_invalid/field_name_is_not_string/field_name_is_not_string.yaml @@ -0,0 +1,3 @@ +type_class: struct +fields: + - { name: 42, type: "int8", comment: "type_class is not enum nor struct" } diff --git a/tests/data/types_invalid/invalid_type_class/invalid_type_class.yaml b/tests/data/types_invalid/invalid_type_class/invalid_type_class.yaml new file mode 100644 index 00000000..be92919f --- /dev/null +++ b/tests/data/types_invalid/invalid_type_class/invalid_type_class.yaml @@ -0,0 +1,4 @@ +type_class: not_a_struct_nor_enum +comment: "type_class is not a struct nor enum" +fields: + - { name: "a", type: "int8" } diff --git a/tests/data/types_invalid/missing_type_class/missing_type_class.yaml b/tests/data/types_invalid/missing_type_class/missing_type_class.yaml new file mode 100644 index 00000000..77a9e0a5 --- /dev/null +++ b/tests/data/types_invalid/missing_type_class/missing_type_class.yaml @@ -0,0 +1,2 @@ +fields: + - { name: "a", type: "int8", comment: "no type_class above" } diff --git a/tests/data/types_invalid/yaml_name_invalid/42.yaml b/tests/data/types_invalid/yaml_name_invalid/42.yaml new file mode 100644 index 00000000..d01780c1 --- /dev/null +++ b/tests/data/types_invalid/yaml_name_invalid/42.yaml @@ -0,0 +1,4 @@ +type_class: struct +comment: "The name of this file is 42, which is invalid" +fields: + - { name: "a", type: "int8" } diff --git a/tests/messages/messgen/messgen_test/_protocol.yaml b/tests/messages/messgen/messgen_test/_protocol.yaml deleted file mode 100644 index feb77f61..00000000 --- a/tests/messages/messgen/messgen_test/_protocol.yaml +++ /dev/null @@ -1 +0,0 @@ -proto_id: 100 diff --git a/tests/messages/messgen/messgen_test/_types.yaml b/tests/messages/messgen/messgen_test/_types.yaml deleted file mode 100644 index 2134eebd..00000000 --- a/tests/messages/messgen/messgen_test/_types.yaml +++ /dev/null @@ -1,2 +0,0 @@ -- name: Existing - namespace: entities \ No newline at end of file diff --git a/tests/messages/messgen/messgen_test/embedded_dynamic_message_d1.yaml b/tests/messages/messgen/messgen_test/embedded_dynamic_message_d1.yaml deleted file mode 100644 index 9562152f..00000000 --- a/tests/messages/messgen/messgen_test/embedded_dynamic_message_d1.yaml +++ /dev/null @@ -1,31 +0,0 @@ -id: 4 -fields: - - name: f0 - type: uint64[] - descr: "Test comment f0" - - - name: f1 - type: simple_dynamic_message[] - descr: "Test comment f1" - - - name: f2 - type: simple_message[] - - - name: f3 - type: uint8 - - - name: f4 - type: int8[] - - - name: f5 - type: simple_dynamic_message[2] - - - name: f6 - type: simple_dynamic_message - - - name: f7 - type: string - descr: "Test comment f7" - - - name: f8 - type: string \ No newline at end of file diff --git a/tests/messages/messgen/messgen_test/embedded_message_d1.yaml b/tests/messages/messgen/messgen_test/embedded_message_d1.yaml deleted file mode 100644 index c10e7e16..00000000 --- a/tests/messages/messgen/messgen_test/embedded_message_d1.yaml +++ /dev/null @@ -1,13 +0,0 @@ -id: 1 -fields: - - name: f0 - type: uint8[10] - - - name: f1 - type: simple_message[2] - - - name: f2 - type: uint64[5] - - - name: f3 - type: simple_message diff --git a/tests/messages/messgen/messgen_test/embedded_message_d2.yaml b/tests/messages/messgen/messgen_test/embedded_message_d2.yaml deleted file mode 100644 index c5b7f46d..00000000 --- a/tests/messages/messgen/messgen_test/embedded_message_d2.yaml +++ /dev/null @@ -1,20 +0,0 @@ -id: 2 -fields: - - name: f0 - type: uint8 - - - name: f1 - type: float64[2] - - - name: f2 - type: embedded_message_d1 - - - name: f3 - type: simple_message - descr: "Test comment f3" - - - name: f4 - type: uint8 - - - name: f5 - type: embedded_message_d1 diff --git a/tests/messages/messgen/messgen_test/empty.yaml b/tests/messages/messgen/messgen_test/empty.yaml deleted file mode 100644 index 7035f7f0..00000000 --- a/tests/messages/messgen/messgen_test/empty.yaml +++ /dev/null @@ -1 +0,0 @@ -id: 5 diff --git a/tests/messages/messgen/messgen_test/explicit_padding.yaml b/tests/messages/messgen/messgen_test/explicit_padding.yaml deleted file mode 100644 index 18c78303..00000000 --- a/tests/messages/messgen/messgen_test/explicit_padding.yaml +++ /dev/null @@ -1,10 +0,0 @@ -id: 11 -fields: - - name: f0 - type: int64 - - - name: f1 - type: int8 - - - name: padding - type: int8[7] \ No newline at end of file diff --git a/tests/messages/messgen/messgen_test/implicit_padding.yaml b/tests/messages/messgen/messgen_test/implicit_padding.yaml deleted file mode 100644 index e9b6977b..00000000 --- a/tests/messages/messgen/messgen_test/implicit_padding.yaml +++ /dev/null @@ -1,7 +0,0 @@ -id: 10 -fields: - - name: f0 - type: int64 - - - name: f1 - type: int8 diff --git a/tests/messages/messgen/messgen_test/simple_dynamic_message.yaml b/tests/messages/messgen/messgen_test/simple_dynamic_message.yaml deleted file mode 100644 index 8eded3cb..00000000 --- a/tests/messages/messgen/messgen_test/simple_dynamic_message.yaml +++ /dev/null @@ -1,40 +0,0 @@ -id: 3 -fields: - - name: f0 - type: uint64[] - - - name: f1 - type: int64 - - - name: f2 - type: float64 - - - name: f3 - type: uint32[] - - - name: f4 - type: int32 - - - name: f5 - type: float32 - - - name: f6 - type: uint16 - - - name: f7 - type: int16 - - - name: f8 - type: uint8[] - - - name: f9 - type: int8[] - - - name: my_null_string - type: string - - - name: non_null_string1 - type: string - - - name: non_null_string2 - type: string diff --git a/tests/messages/messgen/messgen_test/simple_message.yaml b/tests/messages/messgen/messgen_test/simple_message.yaml deleted file mode 100644 index 6bc5a6c6..00000000 --- a/tests/messages/messgen/messgen_test/simple_message.yaml +++ /dev/null @@ -1,32 +0,0 @@ -id: 0 -descr: "this is a simple message" -fields: - - name: f0 - type: uint64 - - - name: f1 - type: int64 - - - name: f2 - type: float64 - - - name: f3 - type: uint32 - - - name: f4 - type: int32 - - - name: f5 - type: float32 - - - name: f6 - type: uint16 - - - name: f7 - type: int16 - - - name: f8 - type: uint8 - - - name: f9 - type: int8 diff --git a/tests/messages/messgen/messgen_test/typed_array_dynamic_message.yaml b/tests/messages/messgen/messgen_test/typed_array_dynamic_message.yaml deleted file mode 100644 index 1f1728a5..00000000 --- a/tests/messages/messgen/messgen_test/typed_array_dynamic_message.yaml +++ /dev/null @@ -1,28 +0,0 @@ -id: 14 -fields: - - name: type_Int8 - type: int8[] - - - name: type_Uint8 - type: uint8[] - - - name: type_Int16 - type: int16[] - - - name: type_Uint16 - type: uint16[] - - - name: type_Int32 - type: int32[] - - - name: type_Uint32 - type: uint32[] - - - name: type_Int64 - type: int64[] - - - name: type_Uint64 - type: uint64[] - - - name: type_Char - type: char diff --git a/tests/messages/messgen/messgen_test/typed_array_message.yaml b/tests/messages/messgen/messgen_test/typed_array_message.yaml deleted file mode 100644 index 53053582..00000000 --- a/tests/messages/messgen/messgen_test/typed_array_message.yaml +++ /dev/null @@ -1,29 +0,0 @@ -id: 15 -fields: - - name: type_Int8 - type: int8[100] - - - name: type_Uint8 - type: uint8[100] - - - name: type_Int16 - type: int16[100] - - - name: type_Uint16 - type: uint16[100] - - - name: type_Int32 - type: int32[100] - - - name: type_Uint32 - type: uint32[100] - - - name: type_Int64 - type: int64[100] - - - name: type_Uint64 - type: uint64[100] - - - - name: type_Char - type: char diff --git a/tests/messages/messgen/messgen_test/use_existing.yaml b/tests/messages/messgen/messgen_test/use_existing.yaml deleted file mode 100644 index 0f08404d..00000000 --- a/tests/messages/messgen/messgen_test/use_existing.yaml +++ /dev/null @@ -1,6 +0,0 @@ -id: 12 -descr: "use non generated existing type" -fields: - - name: existing - type: entities/Existing[] - descr: "use non generated existing type" \ No newline at end of file diff --git a/tests/messages/messgen/messgen_test/use_one_existing.yaml b/tests/messages/messgen/messgen_test/use_one_existing.yaml deleted file mode 100644 index 7fe6ba7c..00000000 --- a/tests/messages/messgen/messgen_test/use_one_existing.yaml +++ /dev/null @@ -1,6 +0,0 @@ -id: 13 -descr: "use one non generated object" -fields: - - name: one - type: entities/Existing - descr: "use one non generated object" \ No newline at end of file diff --git a/tests/python/generate_serialized_data.py b/tests/python/generate_serialized_data.py new file mode 100644 index 00000000..1cd75f5e --- /dev/null +++ b/tests/python/generate_serialized_data.py @@ -0,0 +1,197 @@ +import sys + +from typing import Any +from pathlib import Path + +path_root = Path(__file__).parents[2] +sys.path.append(str(path_root)) + +from messgen.dynamic import Codec + +if __name__ == "__main__": + codec = Codec() + codec.load(type_dirs=['tests/data/types'], protocols=["tests/data/protocols:test_proto", "tests/data/protocols:nested/another_proto"]) + + # simple_struct + t = codec.type_converter("messgen/test/simple_struct") + msg1: dict[str, Any] = { + "f0": 0x1234567890abcdef, + "f1": 0x1234567890abcdef, + "f1_pad": 0x12, + "f2": 1.2345678901234567890, + "f3": 0x12345678, + "f4": 0x12345678, + "f5": 1.2345678901234567890, + "f6": 0x1234, + "f7": 0x12, + "f8": -0x12, + "f9": True, + } + b = t.serialize(msg1) + with open('tests/data/serialized/bin/simple_struct.bin', 'wb') as f: + f.write(b) + print("Successfully generated serialized data to tests/data/serialized/bin/simple_struct.bin") + + # var_size_struct + t = codec.type_converter("messgen/test/var_size_struct") + msg1 = { + "f0": 0x1234567890abcdef, + "f1_vec": [-0x1234567890abcdef, 5, 1], + "str": "Hello messgen!", + } + + b = t.serialize(msg1) + + with open('tests/data/serialized/bin/var_size_struct.bin', 'wb') as f: + f.write(b) + + print("Successfully generated serialized data to tests/data/serialized/bin/var_size_struct.bin") + + # struct_with_enum + t = codec.type_converter("messgen/test/struct_with_enum") + msg1 = { + "f0": 0x1234567890abcdef, + "f1": 0x1234567890abcdef, + "e0": "another_value" + } + b = t.serialize(msg1) + with open('tests/data/serialized/bin/struct_with_enum.bin', 'wb') as f: + f.write(b) + + print("Successfully generated serialized data to tests/data/serialized/bin/struct_with_enum.bin") + + # empty_struct + t = codec.type_converter("messgen/test/empty_struct") + msg1 = {} + b = t.serialize(msg1) + with open('tests/data/serialized/bin/empty_struct.bin', 'wb') as f: + f.write(b) + print("Successfully generated serialized data to tests/data/serialized/bin/empty_struct.bin") + + # complex_struct_with_empty + t = codec.type_converter("messgen/test/complex_struct_with_empty") + msg1 = { + "e": {}, # empty_struct + "dynamic_array": [{} for _ in range(3)], # list of empty_struct, replace 3 with desired length + "static_array": [{} for _ in range(5)], # list of 5 empty_struct + "multi_array": [[[{}] for _ in range(5)] for _ in range(3)], + # 2D list of empty_struct, replace 3 with desired outer list length + "map_empty_by_int": {i: {} for i in range(3)}, # map of int32 to empty_struct, replace 3 with desired map size + "map_vec_by_str": {"key" + str(i): [{}] for i in range(3)}, + # map of string to list of empty_struct, replace 3 with desired map size + "array_of_size_zero": [], # empty list of int32 + } + b = t.serialize(msg1) + with open('tests/data/serialized/bin/complex_struct_with_empty.bin', 'wb') as f: + f.write(b) + print("Successfully generated serialized data to tests/data/serialized/bin/complex_struct_with_empty.bin") + + + # complex_struct_nostl + + t = codec.type_converter("messgen/test/complex_struct_nostl") + simple_struct = { + "f0": 0x1234567890abcdef, + "f1": 0x1234567890abcdef, + "f1_pad": 0x12, + "f2": 1.2345678901234567890, + "f3": 0x12345678, + "f4": 0x12345678, + "f5": 1.2345678901234567890, + "f6": 0x1234, + "f7": 0x12, + "f8": -0x12, + "f9": True, + } + msg1 = { + "f0": 0x1234567890abcdef, + "f1": 0x12345678, + "f2": 0x1234567890abcdef, + "s_arr": [ simple_struct for _ in range(2)], + "f1_arr": [0x1234567890abcdef for _ in range(4)], + "v_arr": [{"f0": 0x1234567890abcdef, "f1_vec": [0x1234567890abcdef, 5, 1], "str": "Hello messgen!"} for _ in + range(2)], + "f2_vec": [1.2345678901234567890 for _ in range(3)], + "e_vec": ["one_value", "another_value"], + "s_vec": [ simple_struct for _ in range(3)], + "v_vec0": [[{"f0": 0x1234567890abcdef, "f1_vec": [0x1234567890abcdef, 5, 1], "str": "Hello messgen!"} for _ in + range(2)] for _ in range(3)], # replace 3 with desired outer list length + "v_vec1": [[{"f0": 0x1234567890abcdef, "f1_vec": [0x1234567890abcdef, 5, 1], "str": "Hello messgen!"} for _ in + range(3)] for _ in range(4)], # replace 3 with desired outer list length + "v_vec2": [[[0x1234 for _ in range(3)] for _ in range(4)] for _ in range(2)], + "str": "Example String", + "str_vec": ["string1", "string2", "string3"], + } + b = t.serialize(msg1) + with open('tests/data/serialized/bin/complex_struct_nostl.bin', 'wb') as f: + f.write(b) + print("Successfully generated serialized data to tests/data/serialized/bin/complex_struct_nostl.bin") + + + # complex_struct + t = codec.type_converter("messgen/test/complex_struct") + + simple_struct = { + "f0": 0x1234567890abcdef, + "f1": 0x1234567890abcdef, + "f1_pad": 0x12, + "f2": 1.2345678901234567890, + "f3": 0x12345678, + "f4": 0x12345678, + "f5": 1.2345678901234567890, + "f6": 0x1234, + "f7": 0x12, + "f8": -0x12, + "f9": True, + } + msg1 = { + "f0": 0x1234567890abcdef, + "f1": 0x12345678, + "f2": 0x1234567890abcdef, + "s_arr": [simple_struct for _ in range(2)], + "f1_arr": [0x1234567890abcdef for _ in range(4)], + "v_arr": [{"f0": 0x1234567890abcdef, "f1_vec": [0x1234567890abcdef, 5, 1], "str": "Hello messgen!"} for _ in + range(2)], + "f2_vec": [1.2345678901234567890 for _ in range(3)], + "e_vec": ["one_value", "another_value"], + "s_vec": [simple_struct for _ in range(3)], + "v_vec0": [[{"f0": 0x1234567890abcdef, "f1_vec": [0x1234567890abcdef, 5, 1], "str": "Hello messgen!"} for _ in + range(2)] for _ in range(3)], # replace 3 with desired outer list length + "v_vec1": [[{"f0": 0x1234567890abcdef, "f1_vec": [0x1234567890abcdef, 5, 1], "str": "Hello messgen!"} for _ in + range(3)] for _ in range(4)], # replace 3 with desired outer list length + "v_vec2": [[[0x1234 for _ in range(3)] for _ in range(4)] for _ in range(2)], + "str": "Example String", + "bs": b"byte string", + "str_vec": ["string1", "string2", "string3"], + "map_str_by_int": {i: "string" + str(i) for i in range(3)}, + "map_vec_by_str": {"key" + str(i): [0x1234 for _ in range(3)] for i in range(3)}, + } + b = t.serialize(msg1) + with open('tests/data/serialized/bin/complex_struct.bin', 'wb') as f: + f.write(b) + print("Successfully generated serialized data to tests/data/serialized/bin/complex_struct.bin") + + + # flat_struct + + t = codec.type_converter("messgen/test/flat_struct") + msg1 = { + "f0": 0x1234567890abcdef, + "f1": 0x1234567890abcdef, + "f1_pad": 0x12, + "f2": 1.2345678901234567890, + "f3": 0x12345678, + "f4": 0x12345678, + "f5": 1.2345678901234567890, + "f6": 0x1234, + "f7": 0x12, + "f8": -0x12, + "f9": True, + } + + b = t.serialize(msg1) + with open('tests/data/serialized/bin/flat_struct.bin', 'wb') as f: + f.write(b) + print("Successfully generated serialized data to tests/data/serialized/bin/flat_struct.bin") + + print("Successfully") diff --git a/tests/python/test_serialization.py b/tests/python/test_serialization.py new file mode 100644 index 00000000..4f29220f --- /dev/null +++ b/tests/python/test_serialization.py @@ -0,0 +1,74 @@ +import pytest + +from messgen.dynamic import Codec + + +@pytest.fixture +def codec(): + codec_ = Codec() + codec_.load(type_dirs=['tests/data/types'], protocols=["tests/data/protocols:test_proto"]) + yield codec_ + + +@pytest.fixture +def simple_struct(): + return { + "f0": 0x1234567890abcdef, + "f2": 1.2345678901234567890, + "f3": 0x12345678, + "f5": 1.2345678901234567890, + "f6": 0x1234, + "f7": 0x12, + "f8": -0x12, + "f9": True, + } + + +def test_serialization1(codec, simple_struct): + type_def = codec.type_converter("messgen/test/simple_struct") + expected_msg = simple_struct + expected_bytes = type_def.serialize(expected_msg) + assert expected_bytes + + actual_msg = type_def.deserialize(expected_bytes) + for key in expected_msg: + assert actual_msg[key] == pytest.approx(expected_msg[key]) + + +def test_serialization2(codec): + type_def = codec.type_converter("messgen/test/var_size_struct") + expected_msg = { + "f0": 0x1234567890abcdef, + "f1_vec": [-0x1234567890abcdef, 5, 1], + "str": "Hello messgen!", + } + + expected_bytes = type_def.serialize(expected_msg) + assert expected_bytes + + actual_msg = type_def.deserialize(expected_bytes) + assert actual_msg == expected_msg + + +def test_protocol_deserialization(codec, simple_struct): + message_info_by_name = codec.message_info_by_name(proto_name="test_proto", message_name="simple_struct_msg") + expected_bytes = message_info_by_name.type_converter().serialize(simple_struct) + assert expected_bytes + + message_info_by_id = codec.message_info_by_id(proto_id=message_info_by_name.proto_id(), message_id=message_info_by_name.message_id()) + actual_msg = message_info_by_id.type_converter().deserialize(expected_bytes) + + assert message_info_by_name.proto_id() == 1 + assert message_info_by_name.message_id() == 0 + assert message_info_by_name.proto_name() == "test_proto" + assert message_info_by_name.message_name() == "simple_struct_msg" + assert message_info_by_name.type_name() == "messgen/test/simple_struct" + + assert message_info_by_name.proto_id() == message_info_by_id.proto_id() + assert message_info_by_name.message_id() == message_info_by_id.message_id() + assert message_info_by_name.proto_name() == message_info_by_id.proto_name() + assert message_info_by_name.message_name() == message_info_by_id.message_name() + assert message_info_by_name.type_name() == message_info_by_id.type_name() + + for key in simple_struct: + assert actual_msg[key] == pytest.approx(simple_struct[key]) diff --git a/tests/python/test_validation.py b/tests/python/test_validation.py new file mode 100644 index 00000000..9a8d654d --- /dev/null +++ b/tests/python/test_validation.py @@ -0,0 +1,92 @@ +import getpass +import os +import pytest + +from itertools import product +from pathlib import Path + +from messgen import ( + generator, + yaml_parser, + model, + validation, +) + + +_OUTPUT_DIR = Path("/") / "tmp" / getpass.getuser() / "messgen_tests" +_TYPE_DIRS = list((Path() / "tests" / "data" / "types_invalid").glob("*")) + + +@pytest.mark.parametrize("lang, types_dir", product(["cpp"], _TYPE_DIRS)) +def test_yaml_parser_validation(lang, types_dir): + with pytest.raises(Exception): + types = yaml_parser.parse_types([types_dir]) + generator.get_generator(lang, {}).generate_types(types, _OUTPUT_DIR) + + +def test_validate_protocol_correct(): + test_type = model.StructType(type="types/test", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + proto = model.Protocol( + name="test", + proto_id=1, + messages={0: model.Message(message_id=0, name="some_msg", type=test_type.type, comment=""), + 1: model.Message(message_id=1, name="other_msg", type=test_type.type, comment="")}, + ) + + validation.validate_protocol(protocol=proto, types={test_type.type: test_type.type}) + + +def test_validate_protocol_id_mismatch(): + test_type = model.StructType(type="types/test", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + proto = model.Protocol( + name="test", + proto_id=1, + messages={0: model.Message(message_id=0, name="some_msg", type=test_type.type, comment=""), + 1: model.Message(message_id=0, name="other_msg", type=test_type.type, comment="")}, + ) + + with pytest.raises(RuntimeError, match="Message other_msg has different message_id=0 than key=1 in protocol=test"): + validation.validate_protocol(protocol=proto, types={test_type.type: test_type.type}) + + +def test_validate_protocol_missing_type(): + test_type = model.StructType(type="types/test", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + proto = model.Protocol( + name="test", + proto_id=1, + messages={0: model.Message(message_id=0, name="some_msg", type="types/missing", comment="")}, + ) + + with pytest.raises(RuntimeError, match="Type types/missing required by message=some_msg protocol=test not found"): + validation.validate_protocol(protocol=proto, types={test_type.type: test_type}) + + +def test_validate_protocol_duplicated_msg_name(): + test_type = model.StructType(type="types/test", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + proto = model.Protocol( + name="test", + proto_id=1, + messages={0: model.Message(message_id=0, name="some_msg", type=test_type.type, comment=""), + 1: model.Message(message_id=1, name="some_msg", type=test_type.type, comment="")}, + ) + + with pytest.raises(RuntimeError, match="Message with name=some_msg appears multiple times in protocol=test"): + validation.validate_protocol(protocol=proto, types={test_type.type: test_type.type}) + + +def test_validate_types_no_conflict(): + type1 = model.StructType(type="types/test1", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + type2 = model.StructType(type="types/test2", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + + try: + validation.validate_types({type1.type: type1, type2.type: type2}) + except RuntimeError: + pytest.fail("validate_types raised RuntimeError unexpectedly!") + + +def test_validate_types_with_conflict(): + type1 = model.StructType(type="types/test1", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + type2 = model.StructType(type="types/test2", type_class=model.TypeClass.struct, comment="", fields=[], size=None) + + with pytest.raises(RuntimeError, match="Type types/test2 has the same hash as types/test1"): + validation.validate_types({type1.type: type1, type2.type: type1})