diff --git a/client-solid/.dockerignore b/client-solid/.dockerignore deleted file mode 100644 index f06235c..0000000 --- a/client-solid/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/client-solid/.gitignore b/client-solid/.gitignore deleted file mode 100644 index 76add87..0000000 --- a/client-solid/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/client-solid/Dockerfile b/client-solid/Dockerfile deleted file mode 100644 index a3b1bed..0000000 --- a/client-solid/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM node:18 -RUN corepack enable && corepack prepare pnpm@7.9.3 --activate -WORKDIR /client -COPY package.json pnpm-lock.yaml ./ - -RUN pnpm install - -COPY . . - -CMD ["pnpm", "run", "build"] diff --git a/client-solid/README.md b/client-solid/README.md deleted file mode 100644 index 8a7c694..0000000 --- a/client-solid/README.md +++ /dev/null @@ -1,35 +0,0 @@ -## Usage - -Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. - -This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely -be removed once you clone a template. - -```bash -$ npm install # or pnpm install or yarn install -``` - -### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) - -## Available Scripts - -In the project directory, you can run: - -### `npm dev` or `npm start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
- -### `npm run build` - -Builds the app for production to the `dist` folder.
-It correctly bundles Solid in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -## Deployment - -You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) diff --git a/client-solid/index.html b/client-solid/index.html deleted file mode 100644 index 553ddf9..0000000 --- a/client-solid/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - Companies Stream - - - - -
- - - - diff --git a/client-solid/package.json b/client-solid/package.json deleted file mode 100644 index 70e28e8..0000000 --- a/client-solid/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "vite-template-solid", - "version": "0.0.0", - "description": "", - "scripts": { - "dev": "vite", - "build": "vite build", - "serve": "vite preview" - }, - "license": "MIT", - "devDependencies": { - "sass": "^1.54.5", - "typescript": "^4.7.4", - "vite": "^3.0.9", - "vite-plugin-solid": "^2.3.0" - }, - "dependencies": { - "solid-js": "^1.4.8", - "temporal-polyfill": "^0.0.7" - } -} diff --git a/client-solid/pnpm-lock.yaml b/client-solid/pnpm-lock.yaml deleted file mode 100644 index 8706504..0000000 --- a/client-solid/pnpm-lock.yaml +++ /dev/null @@ -1,1037 +0,0 @@ -lockfileVersion: 5.4 - -specifiers: - sass: ^1.54.5 - solid-js: ^1.4.8 - temporal-polyfill: ^0.0.7 - typescript: ^4.7.4 - vite: ^3.0.9 - vite-plugin-solid: ^2.3.0 - -dependencies: - solid-js: 1.4.8 - temporal-polyfill: 0.0.7 - -devDependencies: - sass: 1.54.5 - typescript: 4.7.4 - vite: 3.0.9_sass@1.54.5 - vite-plugin-solid: 2.3.0_solid-js@1.4.8+vite@3.0.9 - -packages: - - /@ampproject/remapping/2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.15 - dev: true - - /@babel/code-frame/7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - dev: true - - /@babel/compat-data/7.18.8: - resolution: {integrity: sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/core/7.18.10: - resolution: {integrity: sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.18.12 - '@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.10 - '@babel/helper-module-transforms': 7.18.9 - '@babel/helpers': 7.18.9 - '@babel/parser': 7.18.11 - '@babel/template': 7.18.10 - '@babel/traverse': 7.18.11 - '@babel/types': 7.18.10 - convert-source-map: 1.8.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/generator/7.18.12: - resolution: {integrity: sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 - dev: true - - /@babel/helper-annotate-as-pure/7.18.6: - resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.10: - resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.18.8 - '@babel/core': 7.18.10 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.3 - semver: 6.3.0 - dev: true - - /@babel/helper-create-class-features-plugin/7.18.9_@babel+core@7.18.10: - resolution: {integrity: sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.18.10 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.18.9 - '@babel/helper-member-expression-to-functions': 7.18.9 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-replace-supers': 7.18.9 - '@babel/helper-split-export-declaration': 7.18.6 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-environment-visitor/7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-function-name/7.18.9: - resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-hoist-variables/7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-member-expression-to-functions/7.18.9: - resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-module-imports/7.16.0: - resolution: {integrity: sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-module-imports/7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-module-transforms/7.18.9: - resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.18.6 - '@babel/template': 7.18.10 - '@babel/traverse': 7.18.11 - '@babel/types': 7.18.10 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-optimise-call-expression/7.18.6: - resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-plugin-utils/7.18.9: - resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-replace-supers/7.18.9: - resolution: {integrity: sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-member-expression-to-functions': 7.18.9 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/traverse': 7.18.11 - '@babel/types': 7.18.10 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-simple-access/7.18.6: - resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-split-export-declaration/7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/helper-string-parser/7.18.10: - resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-validator-identifier/7.18.6: - resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-validator-option/7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helpers/7.18.9: - resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/traverse': 7.18.11 - '@babel/types': 7.18.10 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/highlight/7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.18.6 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - - /@babel/parser/7.18.11: - resolution: {integrity: sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.18.10 - dev: true - - /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.18.10: - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.18.10 - '@babel/helper-plugin-utils': 7.18.9 - dev: true - - /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.18.10: - resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.18.10 - '@babel/helper-plugin-utils': 7.18.9 - dev: true - - /@babel/plugin-transform-typescript/7.18.12_@babel+core@7.18.10: - resolution: {integrity: sha512-2vjjam0cum0miPkenUbQswKowuxs/NjMwIKEq0zwegRxXk12C9YOF9STXnaUptITOtOJHKHpzvvWYOjbm6tc0w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.18.10 - '@babel/helper-create-class-features-plugin': 7.18.9_@babel+core@7.18.10 - '@babel/helper-plugin-utils': 7.18.9 - '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.18.10 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/preset-typescript/7.18.6_@babel+core@7.18.10: - resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.18.10 - '@babel/helper-plugin-utils': 7.18.9 - '@babel/helper-validator-option': 7.18.6 - '@babel/plugin-transform-typescript': 7.18.12_@babel+core@7.18.10 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/template/7.18.10: - resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.18.11 - '@babel/types': 7.18.10 - dev: true - - /@babel/traverse/7.18.11: - resolution: {integrity: sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.18.12 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.18.9 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.18.11 - '@babel/types': 7.18.10 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/types/7.18.10: - resolution: {integrity: sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.18.10 - '@babel/helper-validator-identifier': 7.18.6 - to-fast-properties: 2.0.0 - dev: true - - /@esbuild/linux-loong64/0.14.54: - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@jridgewell/gen-mapping/0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - - /@jridgewell/gen-mapping/0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.15 - dev: true - - /@jridgewell/resolve-uri/3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/set-array/1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: true - - /@jridgewell/sourcemap-codec/1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true - - /@jridgewell/trace-mapping/0.3.15: - resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - - /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: true - - /anymatch/3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - - /babel-plugin-jsx-dom-expressions/0.33.14_@babel+core@7.18.10: - resolution: {integrity: sha512-91T8uEz6Wb42bUm5vxRBawY05fBHiwUxah/xWBimuWpH3nf7E0KJ0Wm/s8R7lxRIZzwGCILv1IBlUCqA50WOVw==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.18.10 - '@babel/helper-module-imports': 7.16.0 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.18.10 - '@babel/types': 7.18.10 - html-entities: 2.3.2 - dev: true - - /babel-preset-solid/1.4.8_@babel+core@7.18.10: - resolution: {integrity: sha512-Qv1yoE7yIux68egUsUUEV26t7B0KLNyXKz1MTk89GJDc6mt+2s7+lDVr4tXa29PTZ/hXDTu2uLbEN/1OtmFFBg==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.18.10 - babel-plugin-jsx-dom-expressions: 0.33.14_@babel+core@7.18.10 - dev: true - - /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: true - - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /browserslist/4.21.3: - resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001379 - electron-to-chromium: 1.4.225 - node-releases: 2.0.6 - update-browserslist-db: 1.0.5_browserslist@4.21.3 - dev: true - - /caniuse-lite/1.0.30001379: - resolution: {integrity: sha512-zXf+qxuN8OJrK5Bl5HbJg8cc5/Zm01WNW4ooVWUh92YlKqQZW3fwN5lXLB+kI8wkP5vTWkIIN+rutZuJhf4ykw==} - dev: true - - /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: true - - /chokidar/3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: true - - /color-name/1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true - - /convert-source-map/1.8.0: - resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} - dependencies: - safe-buffer: 5.1.2 - dev: true - - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: true - - /electron-to-chromium/1.4.225: - resolution: {integrity: sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw==} - dev: true - - /esbuild-android-64/0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64/0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64/0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64/0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64/0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64/0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32/0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64/0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm/0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64/0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le/0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le/0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64/0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x/0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64/0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64/0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64/0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32/0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64/0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64/0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild/0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - dev: true - - /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: true - - /escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: true - - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true - - /gensync/1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: true - - /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /globals/11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: true - - /has-flag/3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - dev: true - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true - - /html-entities/2.3.2: - resolution: {integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==} - dev: true - - /immutable/4.1.0: - resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==} - dev: true - - /is-binary-path/2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - - /is-core-module/2.10.0: - resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} - dependencies: - has: 1.0.3 - dev: true - - /is-extglob/2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-what/4.1.7: - resolution: {integrity: sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==} - engines: {node: '>=12.13'} - dev: true - - /js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true - - /jsesc/2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /json5/2.2.1: - resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} - engines: {node: '>=6'} - hasBin: true - dev: true - - /merge-anything/5.0.2: - resolution: {integrity: sha512-POPQBWkBC0vxdgzRJ2Mkj4+2NTKbvkHo93ih+jGDhNMLzIw+rYKjO7949hOQM2X7DxMHH1uoUkwWFLIzImw7gA==} - engines: {node: '>=12.13'} - dependencies: - is-what: 4.1.7 - ts-toolbelt: 9.6.0 - dev: true - - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /nanoid/3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /node-releases/2.0.6: - resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} - dev: true - - /normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true - - /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - - /picocolors/1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - - /picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /postcss/8.4.16: - resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - - /resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.10.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /rollup/2.77.3: - resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: true - - /sass/1.54.5: - resolution: {integrity: sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==} - engines: {node: '>=12.0.0'} - hasBin: true - dependencies: - chokidar: 3.5.3 - immutable: 4.1.0 - source-map-js: 1.0.2 - dev: true - - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: true - - /solid-js/1.4.8: - resolution: {integrity: sha512-XErZdnnYYXF7OwGSUAPcua2y5/ELB/c53zFCpWiEGqxTNoH1iQghzI8EsHJXk06sNn+Z/TGhb8bPDNNGSgimag==} - - /solid-refresh/0.4.1_solid-js@1.4.8: - resolution: {integrity: sha512-v3tD/OXQcUyXLrWjPW1dXZyeWwP7/+GQNs8YTL09GBq+5FguA6IejJWUvJDrLIA4M0ho9/5zK2e9n+uy+4488g==} - peerDependencies: - solid-js: ^1.3 - dependencies: - '@babel/generator': 7.18.12 - '@babel/helper-module-imports': 7.18.6 - '@babel/types': 7.18.10 - solid-js: 1.4.8 - dev: true - - /source-map-js/1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: true - - /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: true - - /supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true - - /temporal-polyfill/0.0.7: - resolution: {integrity: sha512-GMPmZt+ubSFrtG8ER4JkTN8gF3aiKr4on2zbuu+Iyg9e5nIMFuqJOmLRo7pJz0dufmIUmQt55vGqLlf1DcqDNg==} - dependencies: - temporal-spec: 0.0.2 - dev: false - - /temporal-spec/0.0.2: - resolution: {integrity: sha512-eyvI20a6J6gb2zSVkKZGg/0QGiMPMte3ty9dOg3WpveQfcBpgiyqthJ3KM2xzis1WvPaSO2OAh5u2wnu7EOd7g==} - dev: false - - /to-fast-properties/2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: true - - /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /ts-toolbelt/9.6.0: - resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} - dev: true - - /typescript/4.7.4: - resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /update-browserslist-db/1.0.5_browserslist@4.21.3: - resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.3 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - - /vite-plugin-solid/2.3.0_solid-js@1.4.8+vite@3.0.9: - resolution: {integrity: sha512-N2sa54C3UZC2nN5vpj5o6YP+XdIAZW6n6xv8OasxNAcAJPFeZT7EOVvumL0V4c8hBz1yuYniMWdESY8807fVSg==} - peerDependencies: - solid-js: ^1.3.17 - vite: ^3.0.0 - dependencies: - '@babel/core': 7.18.10 - '@babel/preset-typescript': 7.18.6_@babel+core@7.18.10 - babel-preset-solid: 1.4.8_@babel+core@7.18.10 - merge-anything: 5.0.2 - solid-js: 1.4.8 - solid-refresh: 0.4.1_solid-js@1.4.8 - vite: 3.0.9_sass@1.54.5 - transitivePeerDependencies: - - supports-color - dev: true - - /vite/3.0.9_sass@1.54.5: - resolution: {integrity: sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - less: '*' - sass: '*' - stylus: '*' - terser: ^5.4.0 - peerDependenciesMeta: - less: - optional: true - sass: - optional: true - stylus: - optional: true - terser: - optional: true - dependencies: - esbuild: 0.14.54 - postcss: 8.4.16 - resolve: 1.22.1 - rollup: 2.77.3 - sass: 1.54.5 - optionalDependencies: - fsevents: 2.3.2 - dev: true diff --git a/client-solid/src/App.tsx b/client-solid/src/App.tsx deleted file mode 100644 index f336d67..0000000 --- a/client-solid/src/App.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Component, createSignal, For, onCleanup } from "solid-js" -import "./styles/column_layout.scss" -import { Clock } from "./components/Clock" -import { createStore } from "solid-js/store" -import { FilingEventCard } from "./eventCards/FilingEventCard" -import type { AnyEvent, PscEvent } from "./types/eventTypes" -import { CompanyProfileEventCard } from "./eventCards/CompanyProfileEventCard" -import { OfficerEventCard } from "./eventCards/OfficerEventCard" -import { PscEventCard } from "./eventCards/PscEventCard" -import { ChargesEventCard } from "./eventCards/ChargesEventCard" -import { InsolvencyEventCard } from "./eventCards/InsolvencyEventCard" -import { ConnectedIcon } from "./components/ConnectedIcon" -import { DisqualifiedOfficerEventCard } from "./eventCards/DisqualifiedOfficerEventCard" - -interface Health { - "companies": boolean, - "filings": boolean, - "officers": boolean, - "persons-with-significant-control": boolean, - "charges": boolean, - "insolvency-cases": boolean -} -const pscKinds = new Set(["company-psc-corporate", "company-psc-individual", "company-psc-legal", "company-psc-supersecure", 'corporate-entity-beneficial-owner', 'individual-beneficial-owner', 'legal-person-beneficial-owner', 'super-secure-beneficial-owner']) -const App: Component = () => { - const [connected, setConnected] = createSignal(false) - const [count, setCount] = createSignal(0) - const [online, setOnline] = createSignal(true) - addEventListener("online", () => setOnline(true)) - addEventListener("offline", () => setOnline(false)) - const [events, setEvents] = createStore([]) - setInterval(() => setEvents(e => e.slice(0, 100)), 15000) - const [health, setHealth] = createSignal() - setInterval(() => { - fetch("/events/health").then(r => r.json()).then(setHealth).catch() - }, 30_000) - fetch("/events/health").then(r => r.json()).then(setHealth).catch() - - function openSocket() { - const { host } = window.location - const protocol = host === 'localhost' ? 'ws' : 'wss' - const socket = new WebSocket(`${protocol}://${host}/events`) -// Connection opened - socket.addEventListener("open", function(event) { - setConnected(true) - }) - - socket.addEventListener("close", function(event) { - setConnected(false) - }) - -// Listen for messages - socket.addEventListener("message", async function(event) { - setCount(prev => ++prev) - const data: AnyEvent = JSON.parse(event.data) - setEvents(e => [data, ...e]) - setTimeout(() => { - //remove event from events store after 15 seconds - setEvents(ev => ev.filter(e => e?.resource_id !== data.resource_id || e.resource_kind === "disqualified-officer-natural")) - }, 15000) - }) - return () => socket.close() - } - - let socket = openSocket() - onCleanup(() => { - socket() - }) - return ( - <> -
-

Stream data from companies house in realtime

- -
- - -
-
Event count: {count()}
-
-
-
- {/*
Events on screen: {events.length}
*/} -
{JSON.stringify(health, null, 2)}
-
-

Company events

- /companies - e?.resource_kind === "company-profile")}>{event => event.resource_kind === "company-profile" ? - : ""}
-

Filing events

- /filings - e?.resource_kind === "filing-history")}>{event => event.resource_kind === "filing-history" ? - : ""}
-

Officer events

- /officers - e?.resource_kind === "company-officers")}>{event => event.resource_kind === "company-officers" ? - : ""}
-

PSC events

- /persons-with-significant-control - pscKinds.has(e.resource_kind))}>{event => pscKinds.has(event.resource_kind) ? - : ""}
-

Charge events

- /charges - e?.resource_kind === "company-charges")}>{event => event.resource_kind === "company-charges" ? - : ""}
-

Insolvency events

- /insolvency-cases - e?.resource_kind === "company-insolvency")}>{event => event.resource_kind === "company-insolvency" ? - : ""}
-

Disqualified officers

- /disqualified-officers - e?.resource_kind === "disqualified-officer-natural")}>{event => event.resource_kind === "disqualified-officer-natural" ? - : ""} -
-
- - ) -} - -export default App diff --git a/client-solid/src/components/Clock.tsx b/client-solid/src/components/Clock.tsx deleted file mode 100644 index 8808a7b..0000000 --- a/client-solid/src/components/Clock.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, createSignal } from "solid-js" -import { Temporal } from "temporal-polyfill" - -const getTime = () => Temporal.Now.plainTimeISO().toString({ fractionalSecondDigits: 0 }) - -export const Clock: Component = () => { - const [time, setTime] = createSignal(getTime()) - setInterval(() => setTime(getTime()), 1000) - return
-
{time()}
-
-} diff --git a/client-solid/src/components/ConnectedIcon.tsx b/client-solid/src/components/ConnectedIcon.tsx deleted file mode 100644 index fd667ba..0000000 --- a/client-solid/src/components/ConnectedIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from "solid-js" - -interface ConnectedIconProps { - connected: boolean -} - -export const ConnectedIcon: Component = (props) => { - return -} - diff --git a/client-solid/src/eventCards/ChargesEventCard.tsx b/client-solid/src/eventCards/ChargesEventCard.tsx deleted file mode 100644 index c6e17c1..0000000 --- a/client-solid/src/eventCards/ChargesEventCard.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, createMemo } from "solid-js" -import type { ChargesEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" -import { DeepReadonly } from "solid-js/store" - -interface ChargesEventCardProps { - event: DeepReadonly -} - -export const ChargesEventCard: Component = ({ event }) => { - const companyNumber = createMemo(() => event.resource_uri.match( - /^\/company\/([A-Z0-9]{6,8})\/charges/ - )[1]) - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{companyNumber()}

- {companyNumber()} -
-

{event.data.classification.description}

-

Charge published at {published.toPlainTime().toString()}

-
-
-} diff --git a/client-solid/src/eventCards/CompanyProfileEventCard.tsx b/client-solid/src/eventCards/CompanyProfileEventCard.tsx deleted file mode 100644 index 787e3eb..0000000 --- a/client-solid/src/eventCards/CompanyProfileEventCard.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, createMemo } from "solid-js" -import type { CompanyProfileEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" -import { DeepReadonly } from "solid-js/store" - -interface CompanyProfileEventCardProps { - event: DeepReadonly -} - -export const CompanyProfileEventCard: Component = ({ event }) => { - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{event.data.company_number}

- {event.data.company_number} -
-

Changed {event.event.fields_changed?.join(", ")}

-

{event.resource_kind} published at {published.toPlainTime().toString()}

-
-
-} diff --git a/client-solid/src/eventCards/DisqualifiedOfficerEventCard.tsx b/client-solid/src/eventCards/DisqualifiedOfficerEventCard.tsx deleted file mode 100644 index 79c5668..0000000 --- a/client-solid/src/eventCards/DisqualifiedOfficerEventCard.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Component } from "solid-js" -import type { DisqualifiedOfficerEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" -import { DeepReadonly } from "solid-js/store" - -interface DisqualifiedOfficerEventCardProps { - event: DeepReadonly -} - -export const DisqualifiedOfficerEventCard: Component = ({ event }) => { - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{event.data.title} {event.data.forename} {event.data.other_forenames} {event.data.surname}

-
-

{event.data.forename} {event.data.surname} disqualified - from {event.data.disqualifications.at(-1).disqualified_from} {" "} - until {event.data.disqualifications.at(-1).disqualified_until}. - Company {event.data.disqualifications.at(-1).company_names.join(", ")}

- {event.data.disqualifications.length > 1 ? More than 1 disqualification for this officer : ""} -

Born on {event.data.date_of_birth}. {event.data.nationality} nationality

-

{event.data.kind} published at {published.toPlainTime().toLocaleString()}

-
-
-} diff --git a/client-solid/src/eventCards/FilingEventCard.tsx b/client-solid/src/eventCards/FilingEventCard.tsx deleted file mode 100644 index 42962b6..0000000 --- a/client-solid/src/eventCards/FilingEventCard.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, createMemo } from "solid-js" -import type { FilingEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" -import { DeepReadonly } from "solid-js/store" - -interface FilingEventCardProps { - event: DeepReadonly -} - -export const FilingEventCard: Component = ({ event }) => { - const companyNumber = createMemo(() => event.resource_uri.match( - /^\/company\/([A-Z0-9]{6,8})\/filing-history/ - )[1]) - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{companyNumber()}

- {companyNumber()} -
-

{event.data.description}

-

{event.data.category} published at {published.toPlainTime().toString()}

-
-
-} diff --git a/client-solid/src/eventCards/InsolvencyEventCard.tsx b/client-solid/src/eventCards/InsolvencyEventCard.tsx deleted file mode 100644 index 22d4e1c..0000000 --- a/client-solid/src/eventCards/InsolvencyEventCard.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Component, createMemo } from "solid-js" -import type { InsolvencyEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" -import { DeepReadonly } from "solid-js/store" - -interface InsolvencyEventCardProps { - event: DeepReadonly -} - -export const InsolvencyEventCard: Component = ({ event }) => { - const companyNumber = event.resource_id - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{companyNumber}

- {companyNumber} -
-

{event.data.cases[0].type}

-

Insolvency published at {published.toPlainTime().toString()}

-
-
-} diff --git a/client-solid/src/eventCards/OfficerEventCard.tsx b/client-solid/src/eventCards/OfficerEventCard.tsx deleted file mode 100644 index 962aede..0000000 --- a/client-solid/src/eventCards/OfficerEventCard.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Component, createMemo } from "solid-js" -import type { OfficerEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" - -interface OfficerEventCardProps { - event: OfficerEvent.OfficerEvent -} - -export const OfficerEventCard: Component = ({ event }) => { - const companyNumber = createMemo(() => event.resource_uri.match( - /^\/company\/([A-Z0-9]{6,8})\// - )[1]) - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{companyNumber()}

- {companyNumber()} -
-

{event.data.name} appointed {event.data.officer_role} on { - event.data.appointed_on - }

- {event.data.resigned_on !== undefined ? Resigned on {event.data.resigned_on} : ""} -

{event.resource_kind} published at {published.toPlainTime().toString()}

-
-
-} diff --git a/client-solid/src/eventCards/PscEventCard.tsx b/client-solid/src/eventCards/PscEventCard.tsx deleted file mode 100644 index 21e54c0..0000000 --- a/client-solid/src/eventCards/PscEventCard.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, createMemo } from "solid-js" -import type { PscEvent } from "../types/eventTypes" -import { Temporal } from "temporal-polyfill" -import { DeepReadonly } from "solid-js/store" - -interface PscEventCardProps { - event: DeepReadonly -} - -export const PscEventCard: Component = ({ event }) => { - const companyNumber = createMemo(() => event.resource_uri.match( - /^\/company\/([A-Z0-9]{6,8})\// - )[1]) - const published = Temporal.PlainDateTime.from(event.event.published_at) - return
-
-
-

{companyNumber()}

- {companyNumber()} -
-

{event.data.name} notified on {event.data.notified_on}

- {event.data.ceased_on !== undefined ? Resigned on {event.data.ceased_on} : ""} -

{event.resource_kind} published at {published.toPlainTime().toString()}

-
-
-} diff --git a/client-solid/src/index.tsx b/client-solid/src/index.tsx deleted file mode 100644 index 4fa2232..0000000 --- a/client-solid/src/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -/* @refresh reload */ -import { render } from "solid-js/web" - -import App from "./App" - -render(() => , document.getElementById("root") as HTMLElement) diff --git a/client-solid/src/styles/column_layout.scss b/client-solid/src/styles/column_layout.scss deleted file mode 100644 index 0610531..0000000 --- a/client-solid/src/styles/column_layout.scss +++ /dev/null @@ -1,25 +0,0 @@ -@import "./common.scss"; - -#events { - display: grid; - grid-template-columns: repeat(7, 1fr); - font-family: 'Poppins', sans-serif; - background-color: aliceblue; - height: 100vh; -} - -/*event column, eg companies,filings*/ -#events > div { - display: flex; - flex-direction: column; - flex-wrap: nowrap; - align-items: center; - overflow-y: hidden; - scroll-behavior: smooth; - overflow-x: hidden; - height: 100%; -} - -.event { - animation: popHeight 0.5s ease-in; -} diff --git a/client-solid/src/styles/common.scss b/client-solid/src/styles/common.scss deleted file mode 100644 index 3db1218..0000000 --- a/client-solid/src/styles/common.scss +++ /dev/null @@ -1,207 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&family=JetBrains+Mono:wght@500&display=swap'); - -/* styles for both mobile and desktop */ -* { - box-sizing: border-box; -} - -a { - color: royalblue; -} - -#connection-status.connected { - background-color: dodgerblue; - color: aliceblue; -} - -#connection-status.disconnected { - background-color: tomato; - color: honeydew; -} - -#connection-status { - border-radius: 0.25em; - padding: 0.25em; - margin: 0.25em; - display: inline-block; - border: none; - font-size: inherit; -} - -body { - margin: 0; -} - - -.view-source { - margin-bottom: 1.5em; - display: grid; - place-items: center; -} - -div.bubble { - display: inline-block; - margin: 0.25em; - padding: 0.25em; - border-radius: 0.25em; - background-color: darksalmon; - color: #801c00; - transition: all 0.5s ease-in; -} - -code { - font-family: 'Roboto Mono', monospace; - background-color: rgba(0, 0, 0, 0.2); - border-radius: 0.1em; - padding: 0.1em; - margin: 0.3em; -} - -p.new-company { - color: green; - font-weight: 600; - margin: 0; -} - - -.row h3 { - margin-bottom: 0; -} - -#clock { - transform: scale(2); -} - -#clock-container:hover #clock { - transform: scale(3); -} - -#counter-container:hover .bubble { - transform: scale(2) translateX(-25%); -} - -#clock-container { - display: grid; - place-items: center; - width: 500px; -} - -sup.event-timestamp { - align-self: flex-end; -} - -h1 { - font-family: 'JetBrains Mono', monospace; - font-weight: 500; - text-align: center; - margin: 1.5em 0 0 0; -} - -.event { - transition: all 1s ease-in-out; - animation: popHeight 0.5s ease-in; - max-width: 100%; -} - -.event > div { - border-width: 2px; - border-style: solid; - border-radius: 0.25em; - margin: 1em; - padding: 1em; - overflow: hidden; - white-space: nowrap; - box-shadow: 0 0 15px #0001; -} - -.event > div p.wrap { - white-space: normal; -} - -.filing-card { - background: rgb(189,200,210); - background: linear-gradient(63deg, rgba(189,200,210,1) 0%, rgba(209,218,227,1) 100%); - border-color: #8395a7; -} - -.companies-card { - background: rgb(236,207,130); - background: linear-gradient(63deg, rgba(236,207,130,1) 0%, rgba(246,223,158,1) 100%); - border-color: #e1b12c; -} - -.insolvency-card { - background: rgb(237,116,117); - background: linear-gradient(63deg, rgba(237,116,117,1) 0%, rgba(255,157,157,1) 100%); - border-color: #ee5253; -} - -.charges-card { - background: rgb(176, 196, 222); - background: linear-gradient(63deg, rgba(176, 196, 222, 1) 0%, rgba(191, 199, 209, 1) 100%); - border-color: steelblue; -} - -.psc-card { - background-color: #85FFBD; - background-image: linear-gradient(63deg, #85FFBD 0%, #FFFB7D 100%); - border-color: #85FFBD; -} - -.officer-card { - background-color: #FBAB7E; - background-image: linear-gradient(63deg, #FBAB7E 0%, #F7CE68 100%); - border-color: #FBAB7E; -} - -.disqualified-officer-card { - background: #fb8f7e; - background: linear-gradient(63deg, #fb8f7e 0%, #f7ac68 100%); - border-color: #FBAB7E; -} - -.alert { - background-color: tomato; - border-color: firebrick; -} - - -@keyframes popWidth { - 0% { - opacity: 0; - max-width: 0; - } - 90% { - opacity: 0.5; - max-width: 100%; - transform: scale(1.2); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - -@keyframes popHeight { - 0% { - opacity: 0; - max-height: 0; - } - 90% { - opacity: 0.9; - max-height: 100%; - transform: scale(1.1); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - -.row { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - justify-content: space-between; -} diff --git a/client-solid/src/types/eventTypes.ts b/client-solid/src/types/eventTypes.ts deleted file mode 100644 index 81329bd..0000000 --- a/client-solid/src/types/eventTypes.ts +++ /dev/null @@ -1,626 +0,0 @@ -declare module CompanyProfileEvent { - interface AccountingReferenceDate { - day: number | string - month: number | string - } - - interface LastAccounts { - made_up_to?: string - period_end_on: string - period_start_on: string - type: - | "full" - | "small" - | "medium" - | "group" - | "dormant" - | "interim" - | "initial" - | "total-exemption-full" - | "total-exemption-small" - | "partial-exemption" - | "audit-exemption-subsidiary" - | "filing-exemption-subsidiary" - | "micro-entity" - | "null" - | "unaudited-abridged" - } - - interface NextAccounts { - due_on: string - period_end_on: string - period_start_on: string - } - - interface Accounts { - accounting_reference_date: AccountingReferenceDate - last_accounts: LastAccounts | { type: "null" } - next_accounts: NextAccounts - next_due: string - next_made_up_to: string - overdue?: boolean - } - - interface AnnualReturn { - last_made_up_to: string - next_due: string - next_made_up_to: string - overdue: boolean - } - - interface BranchCompanyDetails { - business_activity: string - parent_company_name: string - parent_company_number: string - } - - interface ConfirmationStatement { - last_made_up_to?: string - next_due: string - next_made_up_to: string - overdue?: boolean - } - - interface AccountingRequirement { - foreign_account_type: string - terms_of_account_publication: string - } - - interface AccountPeriodFrom { - day: number - month: number - } - - interface AccountPeriodTo { - day: number - month: number - } - - interface MustFileWithin { - months: number - } - - interface Accounts2 { - account_period_from: AccountPeriodFrom - account_period_to: AccountPeriodTo - must_file_within: MustFileWithin - } - - interface OriginatingRegistry { - country: string - name: string - } - - interface ForeignCompanyDetails { - accounting_requirement: AccountingRequirement - accounts: Accounts2 - business_activity: string - company_type: string - governed_by: string - is_a_credit_finance_institution: boolean - originating_registry: OriginatingRegistry - registration_number: string - } - - interface Links { - persons_with_significant_control?: string - persons_with_significant_control_statements?: string - registers?: string - self: string - charges?: string - filing_history: string - officers?: string - } - - interface PreviousCompanyName { - ceased_on: string - effective_from: string - name: string - } - - interface RegisteredOfficeAddress { - address_line_1: string - address_line_2?: string - care_of?: string - country?: string - locality: string - po_box?: string - postal_code: string - premises?: string - region?: string - } - - interface Data { - accounts: Accounts - annual_return?: AnnualReturn - branch_company_details?: BranchCompanyDetails - can_file: boolean - company_name: string - company_number: string - company_status: string - company_status_detail?: string - confirmation_statement: ConfirmationStatement - date_of_cessation?: string - date_of_creation: string - etag: string - foreign_company_details?: ForeignCompanyDetails - has_been_liquidated?: boolean - has_charges?: boolean - has_insolvency_history?: boolean - is_community_interest_company?: boolean - jurisdiction: string - last_full_members_list_date?: string - links: Links - previous_company_names?: PreviousCompanyName[] | [[Object]] - registered_office_address: RegisteredOfficeAddress - registered_office_is_in_dispute?: boolean - sic_codes: string[] - type: string - undeliverable_registered_office_address?: boolean - } - - interface Event { - fields_changed?: string[] - published_at: string - timepoint: number - type: string - } - - interface CompanyProfileEvent { - data: Data - event: Event - resource_id: string - resource_kind: "company-profile" - resource_uri: string - } -} - -declare module FilingEvent { - interface Annotation { - annotation: string - date: string - description: string - category: "annotation" - description_values: { description: string } - type: "ANNOTATION" - } - - interface AssociatedFiling { - date: string - description: string - type: string - } - - interface Links { - document_metadata?: string - self: string - } - - interface Resolution { - category: string - description: string - document_id: string - receive_date: string - subcategory: string - type: string - } - - interface Data { - annotations?: Annotation[] - associated_filings?: AssociatedFiling[] - barcode: string - category: - | "accounts" - | "address" - | "annual-return" - | "capital" - | "change-of-name" - | "incorporation" - | "liquidation" - | "miscellaneous" - | "mortgage" - | "officers" - | "resolution" - | "confirmation-statement" - date: string - description?: string - description_values?: {} - links: Links - pages?: number - paper_filed?: boolean - resolutions?: Resolution[] - subcategory?: string - transaction_id: string - type: string - } - - interface Event { - fields_changed?: string[] - published_at: string - timepoint: number - type: "changed" | "deleted" - } - - interface FilingEvent { - data: Data - event: Event - resource_id: string - resource_kind: "filing-history" - resource_uri: string - } -} - -declare module InsolvencyEvent { - interface Date { - date: string - type: string - } - - interface Links { - charge: string - } - - interface Address { - address_line_1: string - address_line_2?: string - country?: string - locality: string - postal_code?: string - region?: string - } - - interface Practitioner { - address: Address - appointed_on?: string - ceased_to_act_on?: string - name: string - role: string - } - - interface Case { - dates?: Date[] - links?: Links - notes?: string[] - number: number | string - practitioners?: Practitioner[] - type: string - } - - interface Data { - cases: Case[] - etag: string - status?: string - } - - interface Event { - fields_changed?: string[] - published_at: string - timepoint: number - type: string - } - - interface InsolvencyEvent { - data: Data - event: Event - resource_id: string - resource_kind: "company-insolvency" - resource_uri: string - } -} - -declare module ChargesEvent { - interface Classification { - description: string - type: string - } - - interface Link { - case: string - } - - interface InsolvencyCas { - case_number: number - links: Link[] - transaction_id: number - } - - interface Link2 { - self: string - } - - interface Particular { - chargor_acting_as_bare_trustee?: boolean - contains_fixed_charge?: boolean - contains_floating_charge?: boolean - contains_negative_pledge: boolean - description?: string - floating_charge_covers_all?: boolean - type?: string - } - - interface PersonsEntitled { - name: string - } - - interface ScottishAlteration { - description: string - has_alterations_to_order: boolean - has_alterations_to_prohibitions: boolean - has_alterations_to_provisions: boolean - type: string - } - - interface SecuredDetail { - description: string - type: string - } - - interface Link3 { - filing: string - insolvency_case?: string - } - - interface Transaction { - delivered_on: string - filing_type: string - insolvency_case_number?: number - links: Link3 - transaction_id?: number - } - - interface Data { - acquired_on?: string - assests_ceased_released?: string - charge_code: string - charge_number: number - classification: Classification - covering_instrument_date?: string - created_on: string - delivered_on: string - etag: string - id?: string - insolvency_cases?: InsolvencyCas[] - links: Link2 - more_than_four_persons_entitled?: boolean - particulars: Particular - persons_entitled: PersonsEntitled[] - resolved_on?: string - satisfied_on?: string - scottish_alterations?: ScottishAlteration[] - secured_details?: SecuredDetail[] - status: string - transactions: Transaction[] - } - - interface Event { - fields_changed?: string[] - published_at: string - timepoint: number - type: string - } - - interface ChargesEvent { - data: Data - event: Event - resource_id: string - resource_kind: "company-charges" - resource_uri: string - } -} - -declare module PscEvent { - /** the psc address - * - */ - interface Address { - address_line_1: string - address_line_2?: string - care_of?: string - country?: string - locality?: string - po_box?: string - postal_code?: string - premises: string - region?: string - } - - interface DateOfBirth { - day?: number - month: number - year: number - } - - interface Identification { - country_registered?: string - legal_authority: string - legal_form: string - place_registered?: string - registration_number?: string - } - - interface Links { - self: string - statement?: string - } - - interface NameElements { - forename?: string - other_forenames?: string - surname: string - title?: string - } - - interface Data { - address: Address - ceased?: boolean - ceased_on?: string - country_of_residence?: string - date_of_birth?: DateOfBirth - description?: "super-secure-persons-with-significant-control" - etag: string - identification?: Identification - kind?: - | "individual-person-with-significant-control" - | "corporate-entity-person-with-significant-control" - | "legal-person-with-significant-control" - | "super-secure-person-with-significant-control" - links: Links - name: string - name_elements?: NameElements - nationality?: string - natures_of_control: string[] - notified_on: string - } - - interface Event { - fields_changed?: string[] - published_at: string - timepoint: number - type: "changed" | "deleted" - } - - interface PscEvent { - data: Data - event: Event - resource_id: string - resource_kind: "company-psc-corporate" | "company-psc-individual" - resource_uri: string - } -} - -declare module OfficerEvent { - interface OfficerEvent { - resource_kind: "company-officers" - resource_uri: string - resource_id: string - data: IOfficerData - event: IOfficerEvent - } - - interface IOfficerData { - address: IOfficerAddress - appointed_on?: string - country_of_residence?: string - date_of_birth?: IOfficerDate_of_birth - links: IOfficerLinks - name: string - nationality?: string - occupation?: string - officer_role: string - resigned_on?: string - identification?: IOfficerIdentification - } - - interface IOfficerIdentification { - identification_type: string - registration_number: string - legal_authority?: string - legal_form?: string - place_registered?: string - } - - interface IOfficerAddress { - address_line_1?: string - address_line_2?: string - country?: string - locality: string - postal_code: string - premises?: string - region?: string - care_of?: string - } - - interface IOfficerDate_of_birth { - month: number - year: number - } - - interface IOfficerLinks { - self: string - } - - interface IOfficerEvent { - timepoint: number - published_at: string - type: string - } -} - -declare module DisqualifiedOfficerEvent { - - export interface Address { - address_line_1: string; - country: string; - locality: string; - postal_code: string; - } - - export interface Reason { - act: string; - description_identifier: string; - section: string; - } - - export interface Disqualification { - address: Address; - case_identifier: string; - company_names: string[]; - disqualification_type: string; - disqualified_from: string; - disqualified_until: string; - reason: Reason; - undertaken_on: string; - } - - export interface Links { - self: string; - } - - export interface Data { - date_of_birth: string; - disqualifications: Disqualification[]; - etag: string; - forename: string; - kind: string; - links: Links; - nationality: string; - other_forenames: string; - surname: string; - title: string; - } - - export interface Event { - timepoint: number; - published_at: string; - type: string; - } - - export interface DisqualifiedOfficerEvent { - resource_kind: "disqualified-officer-natural"; - resource_uri: string; - resource_id: string; - data: Data; - event: Event; - } - -} - -type AnyEvent = - CompanyProfileEvent.CompanyProfileEvent - | FilingEvent.FilingEvent - | PscEvent.PscEvent - | InsolvencyEvent.InsolvencyEvent - | ChargesEvent.ChargesEvent - | OfficerEvent.OfficerEvent - | DisqualifiedOfficerEvent.DisqualifiedOfficerEvent -export type { - CompanyProfileEvent, - FilingEvent, - PscEvent, - InsolvencyEvent, - ChargesEvent, - OfficerEvent, - DisqualifiedOfficerEvent, - AnyEvent -} -const a: AnyEvent = null diff --git a/client-solid/tsconfig.json b/client-solid/tsconfig.json deleted file mode 100644 index e11d595..0000000 --- a/client-solid/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", - "types": [ - "vite/client" - ], - "noEmit": true, - "isolatedModules": true - } -} diff --git a/client-solid/vite.config.ts b/client-solid/vite.config.ts deleted file mode 100644 index ef415b8..0000000 --- a/client-solid/vite.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vite" -import solidPlugin from "vite-plugin-solid" - -export default defineConfig({ - plugins: [solidPlugin()], - build: { - target: "esnext", - polyfillDynamicImport: false - } -}) diff --git a/docker-compose.yml b/docker-compose.yml index 48d25e1..4597b35 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: - redisdata:/data logging: driver: local + ports: + - "6379:6379" webserver: image: caddy diff --git a/docs/Companies-stream-caching.drawio b/docs/Companies-stream-caching.drawio deleted file mode 100644 index 8efe4c8..0000000 --- a/docs/Companies-stream-caching.drawio +++ /dev/null @@ -1 +0,0 @@ -1Vhbd+I2EP41POLjS3zhMUBo95zdbdr0nN0+cYQtbDWy5cgygf31nbElbGOSstuktDyANMzI0nzfjGY88Rb5/idJyuyTSCifuHayn3jLies6QRjBD0oOrSSaha0glSzRSp3ggX2jWmhrac0SWg0UlRBcsXIojEVR0FgNZERK8TxU2wo+fGpJUjoSPMSEj6VfWKIyfQo37OQ/U5Zm5slOMGv/yYlR1iepMpKI557Iu5t4CymEakf5fkE5Os/4pbVbvfDvcWOSFuoSg8fl5vPhjnwOshWdFbP10678MPXaVXaE1/rAv9GEVXrH6mDcQKqy9e6W7SksOM9UzmHqwLAUrFCNm/35xF+ChHCWFiCIYWtUgoDljZvnW1EojbHjdvIly1PYOGcb+M6rmFD4XZA4o+tmO+t7KZI6Vla1S8FK75lKRfcvOsM5uhi4SUVOlTyAijEwqGhaBraeP3cgOzcayqwPsOtbTqD5pbmVHpfv3I8HaRH4DjT8ERqfRJEKEC3nI0gSUmUIxdIGl4hacVbQxTEK7CFGI0Q42VB+LyqmmBj8gW5lwP+PJwoboZTIewq3ekklSpACu0vcWL5PMRFYz3TDRSoqK8cTJJs3wMyfDTHzzkEWWJ4/Rs2zA8t9J9BMpuqhNnEDDk+eJ2wHwxSHFZU7yGOwW7oBn1KjAo/saZ0zVATcjamL8WaBiTvXy4j4kaqXFjphC00grelpIQoMRinqImkYhAwRUmUAWAHIC0S0Ef5JlTrogCW1EkNS0T1TX3vjP3rj5V6TsJkczKQAj3/tT9DGtmzXMYLOsJkNLO+pZAAa8rQRtqfEo71OKfCEqGVMX0FRh7QiMqXqFb3oPEUl5QDTbriPt6eacwnVYkycLWFYkeLdS6tYshIDubqYd4/0MG2fgw5l8mi5kadWV6SefTH1rsAV76pccUdcMcSIRV6S4tAwQxHGx9f9v5Ut7P8aZP5VIRtXY2cCc0tVnCF2RJHLY3K0DFzqwATNiJxVL2aGK0Xz69ToLhIr9Id3SRD+T66S8NVqx7Yc258NCh4d0ReTUa99j/V5t7BnD8soN7SHS7Qb11b9hsIsZBTFdltRNaL98Sw/HgnBKBCWLGWKoOEvMSXI3aUUJdeP7xPUVKKm5+jx7Hzp+rcVb1Mrz0n8mDZMXwguZPMsb9t8TCNza5okJNSoYzK9TqYU9q636DR3FSfFjcWge90yiCFpQW7GAGxie4XyChsjrKSnpEimGwnfKPIxv6+i2Vo7Zt24ZY2a61bdcSOrxPv/HxfeXnhSeEdnKm83OtcsRe+UKsMXM2VVNuzoCBE81ULp3nP6rHcH3rcLIXNklFE4TZZo0NyWBu5OEWIIP2PbBRd1AkYPv37s5dN2pVGCbrdqxDZQT6WSjm/jHoOhpuM9/vlOdLPymi5Bikdq/tF5+Tzdf7zzGzWRcIAYSszfmzQ/9eyTDt8xc30UM+8dYNZ8zvSPaVy6Vkb3H2Lcw7yU7WAZo4PX1RN/A16fvAMIx6z2vGDMancWWtF7dZPRiNg1tI4g2eC7LBxd780M+VZLiudLaUElhs5q3u7q7V7M+NEQFD9yLD8aAeParnWmz3eC78cFpt1LuPYC615lend/AQ== \ No newline at end of file diff --git a/docs/caching-diagram.svg b/docs/caching-diagram.svg deleted file mode 100644 index bc12ff2..0000000 --- a/docs/caching-diagram.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - -
-
-
- Redis -
-
-
-
- - Redis - -
-
- - - - - - -
-
-
- Mongo DB -
-
-
-
- Mongo - DB - -
-
- - - - - -
-
-
-
serves website
-
static files + websocket
-
-
-
-
- - serves website... - -
-
- - - - - -
-
-
-
cache filing descriptions
-
key-value pairs -
-
-
-
-
-
- cache - filing descriptions... - -
-
- - - - - -
-
-
- cache company details -
-
-
-
- cache - company details - -
-
- - - - - -
-
-
-
fetch data -
-
-
on cache miss
-
-
-
-
- fetch - data... - -
-
- - - - -
-
-
- Digital Ocean Droplet -
-
-
-
- - Digital Ocean Droplet - -
-
- - - - - - - -
-
-
- - Cloud SQL - - Postgres -
-
-
-
- Cloud SQL Postgres - -
-
- - - - -
-
-
- user browser -
-
-
-
- user - browser - -
-
-
- - - - Viewer does not support full SVG 1.1 - - -
\ No newline at end of file diff --git a/readme.md b/readme.md index d02e6ca..3366022 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ Companies House offers a streaming API, which sends events over a HTTPS connecti A [Docker compose](https://docs.docker.com/compose/) application with 3 main components for the backend: 1. A container to listen on the Companies House streaming API and publish events to Redis ( - see [streamToRedis.ts](server/src/redis/streamToRedis.ts)). + see [streamToRedis.ts](server/src/chStreamToRedis/streamToRedis.ts)). 2. A Redis instance, mostly for facilitating Pub/Sub communication of events using Streams, and also for storing most recent timepoint to avoid missing any events. @@ -64,7 +64,7 @@ get(options, (res) => { For a more complete working example of listening on a stream in Javascript, see [server/src/streams/splitStream.ts](server/src/streams/splitStream.ts) and -then [server/src/redis/streamToRedis.ts](server/src/redis/streamToRedis.ts). +then [server/src/redis/streamToRedis.ts](server/src/chStreamToRedis/streamToRedis.ts). To test the streaming API from the command line with CURL, you can use the cmdline utility (after compiling): diff --git a/server/Api.Dockerfile b/server/Api.Dockerfile index 421a45b..3414044 100644 --- a/server/Api.Dockerfile +++ b/server/Api.Dockerfile @@ -9,4 +9,4 @@ COPY . . EXPOSE 3000 -CMD ["bun", "--smol", "run", "src/redis/streamFromRedis.ts"] +CMD ["bun", "--smol", "run", "src/api/streamFromRedis.ts"] diff --git a/server/Publisher.Dockerfile b/server/Publisher.Dockerfile index 3e21ca8..5091595 100644 --- a/server/Publisher.Dockerfile +++ b/server/Publisher.Dockerfile @@ -7,7 +7,7 @@ RUN bun install --frozen-lockfile COPY . . -RUN bun build src/redis/streamToRedis.ts --outdir dist --target=node +RUN bun build src/chStreamToRedis/streamToRedis.ts --outdir dist --target=node # hack to fix Bun bundler https://github.com/oven-sh/bun/issues/6168 RUN echo 'import { createRequire as createImportMetaRequire } from "module"; import.meta.require ||= (id) => createImportMetaRequire(import.meta.url)(id);' | cat - dist/streamToRedis.js > temp && mv temp dist/streamToRedis.js diff --git a/server/package.json b/server/package.json index d3a6e4b..9988a96 100644 --- a/server/package.json +++ b/server/package.json @@ -7,8 +7,8 @@ "watch": "tsc --build --watch", "clean": "tsc --build --clean", "status": "bun src/testStreamStatus.ts", - "stream-pub": "bun src/redis/streamToRedis.ts", - "stream-sub": "bun src/redis/streamFromRedis.ts" + "stream-pub": "bun src/chStreamToRedis/streamToRedis.ts", + "stream-sub": "bun src/api/streamFromRedis.ts" }, "devDependencies": { "@types/node": "^18.11.9", diff --git a/server/src/redis/listenRedisStream.ts b/server/src/api/listenRedisStream.ts similarity index 66% rename from server/src/redis/listenRedisStream.ts rename to server/src/api/listenRedisStream.ts index 560b7ac..5838161 100644 --- a/server/src/redis/listenRedisStream.ts +++ b/server/src/api/listenRedisStream.ts @@ -1,4 +1,4 @@ -import { getRedisClient } from "./getRedisClient.js" +import { getRedisClient } from "../utils/getRedisClient.js" import { AbortError, commandOptions } from "redis" import { streamFromRedisLogger as logger } from "../utils/loggers.js" @@ -12,7 +12,7 @@ interface ListenRedisStreamProps { * Listen for events on a Redis stream. Keys prefixed with "events". Optionally pick up from a specific time point. */ export async function* listenRedisStream>(props: ListenRedisStreamProps) { - const redis = await getRedisClient() + const streamingClient = await getRedisClient("streaming") const streams = props.streamKeys.map(({ stream, timepoint }) => ({ key: "events:" + stream, id: timepoint ?? "$" @@ -20,26 +20,27 @@ export async function* listenRedisStream ac.abort() - props.signal?.addEventListener('abort', triggerAbort) - const event = await redis.xRead(options, streams, readOptions) - props.signal?.removeEventListener('abort', triggerAbort) + props.signal?.addEventListener("abort", triggerAbort) + const event = await streamingClient.xRead(options, streams, readOptions) + props.signal?.removeEventListener("abort", triggerAbort) if (event && event.length) { const { name: stream, messages: items } = event[0] const { id: eventId, message: data } = items[0] yield { stream, eventId, data: data as EventType } - }else{ - console.log('Empty event in stream, breaking', {event}) + } else { + console.log("Empty event in stream, breaking", { event }) break // empty event means end of stream } } catch (e) { - if(!(e instanceof AbortError)) + if (!(e instanceof AbortError)) logger.error(e, "Error READing from Redis Stream(s)") break } } + await streamingClient.quit() //TODO: this isn't being awaited on shutdown, not serious but requires investigation } diff --git a/server/src/schemas/maintainSchemas.ts b/server/src/api/maintainSchemas.ts similarity index 56% rename from server/src/schemas/maintainSchemas.ts rename to server/src/api/maintainSchemas.ts index 1d2ab68..d710a76 100644 --- a/server/src/schemas/maintainSchemas.ts +++ b/server/src/api/maintainSchemas.ts @@ -3,32 +3,29 @@ Listen on the Redis stream for events, and generate JSON schemas for each event Keeps updating the schema based on each event, so they stay up to date. */ -import { getRedisClient } from "../redis/getRedisClient.js" -import { listenRedisStream } from "../redis/listenRedisStream.js" +import { redisClient } from "../utils/getRedisClient.js" +import { listenRedisStream } from "./listenRedisStream.js" import { streamPaths } from "../streams/streamPaths.js" import { extendSchema, createSchema } from "genson-js" import { streamFromRedisLogger } from "../utils/loggers.js" /** When an event arrives, merge it with the existing schema in redis (IF EXISTS) and save new schema. */ -export async function updateSchemaForEvent(event, commandClient){ +export async function updateSchemaForEvent(event, commandClient) { // schemas are specific to each resource kind - const {resource_kind,data} = event + const { resource_kind, data } = event // schemas are stored as stringified JSON in the `schemas` hash in redis - const savedSchema = await commandClient.hGet('schemas', resource_kind).then(String).then(JSON.parse) - if(!savedSchema) streamFromRedisLogger.info({resource_kind},"Schema did not exist for event type %s", resource_kind) + const savedSchema = await commandClient.hGet("schemas", resource_kind).then(String).then(JSON.parse) + if (!savedSchema) streamFromRedisLogger.info({ resource_kind }, "Schema did not exist for event type %s", resource_kind) const newSchema = savedSchema ? extendSchema(savedSchema, data, { noRequired: false }) : createSchema(data) - await commandClient.hSet('schemas', resource_kind, JSON.stringify(newSchema)) + await commandClient.hSet("schemas", resource_kind, JSON.stringify(newSchema)) } -async function maintainSchemas(signal?: AbortSignal){ - const commandClient = await getRedisClient() - const eventStream = listenRedisStream({streamKeys: [...streamPaths].map(stream=>({stream})),signal}) +async function maintainSchemas(signal?: AbortSignal) { + const eventStream = listenRedisStream({ streamKeys: [...streamPaths].map(stream => ({ stream })), signal }) for await(const event of eventStream) { const parsedEvent = JSON.parse(event.data.event) - await updateSchemaForEvent(parsedEvent,commandClient) + await updateSchemaForEvent(parsedEvent, redisClient) } - - await commandClient.quit() } diff --git a/server/src/api/routes/eventHistory.ts b/server/src/api/routes/eventHistory.ts new file mode 100644 index 0000000..125de35 --- /dev/null +++ b/server/src/api/routes/eventHistory.ts @@ -0,0 +1,64 @@ +import { Elysia } from "elysia" +import { streamPaths } from "../../streams/streamPaths" +import { redisClient } from "../../utils/getRedisClient" +import { get } from "https" + +export const eventHistoryRouter = (app: Elysia) => { + + /** Returns the `qty` most recent events in the `streamPath` stream. Eg last 100 filing events. */ + return app.get("/downloadHistory/:streamPath", async ({ query, params, set }) => { + const { streamPath } = params + const { qty } = query + const COUNT = parseInt(String(qty)) || 100 // default to send 100 events, unless specified + if (COUNT > 10_000) { + set.status = 400 + return { + statusCode: 400, + message: "Qty exceeds maximum. Must be less than 10,000. Received: " + COUNT + } + } + if (streamPaths.has(streamPath)) { + const history = await redisClient.xRevRange("events:" + streamPath, "+", "-", { COUNT }) + return history.map(h => JSON.parse(h.message.event)) + } else { + set.status = 400 + return { + statusCode: 400, + message: "Invalid stream path: " + streamPath, + possibleOptions: [...streamPaths] + } + } + }) + + .get("/stats/:streamPath", async ({ params, set }) => { + const { streamPath } = params + if (streamPaths.has(streamPath)) { + const rawCounts = await redisClient.hGetAll(`counts:${streamPath}:daily`) + const counts = Object.fromEntries(Object.entries(rawCounts).map(([date, count]) => [date, parseInt(count)])) + return counts + } else { + set.status = (400) + return ({ + statusCode: 400, + message: "Invalid stream path: " + streamPath, + possibleOptions: [...streamPaths] + }) + } + }) + .get("/resourceKinds/:streamPath", async ({ params, set }) => { + const { streamPath } = params + if (streamPaths.has(streamPath)) { + const rawCounts = await redisClient.hGetAll(`resourceKinds:${streamPath}`) + const counts = Object.fromEntries(Object.entries(rawCounts).map(([date, count]) => [date, parseInt(count)])) + return counts + } else { + set.status = (400) + return ({ + statusCode: 400, + message: "Invalid stream path: " + streamPath, + possibleOptions: [...streamPaths] + }) + } + }) + +} diff --git a/server/src/api/routes/eventsWebsocket.ts b/server/src/api/routes/eventsWebsocket.ts new file mode 100644 index 0000000..3feec2e --- /dev/null +++ b/server/src/api/routes/eventsWebsocket.ts @@ -0,0 +1,47 @@ +import { Elysia } from "elysia" +import { streamPaths } from "../../streams/streamPaths" +import { streamFromRedisLogger as logger } from "../../utils/loggers" +import { VisitorCounterService } from "../visitorCounter" +import { setTimeout } from "node:timers/promises" +import { redisClient } from "../../utils/getRedisClient" + +export const eventWebsocketRouter = async (app: Elysia) => { + const visitorCounter = new VisitorCounterService(redisClient) + const ac = new AbortController() + const { signal } = ac + let unclosedWsCount = 0 + app.on("stop", async () => { + const requestTime = Bun.nanoseconds() + ac.abort() + while (unclosedWsCount) { + if (Bun.nanoseconds() - requestTime > 2 * 1_000_000_000) break // don't keep trying if its not working after 2 sec + await setTimeout(5) // wait for websockets to be closed gracefully before quiting the Redis client + } + const waitingNs = Bun.nanoseconds() - requestTime + console.log("Closed all client connections after", waitingNs / 1000 / 1000, "ms") + }) +// web socket server for sending events to client + return app.ws("/events", { + async open(ws) { + unclosedWsCount++ + // subscribe to all streams and increment counter of connections + for (const streamPath of streamPaths) { + ws.subscribe(streamPath) + } + const ipAddress = String(ws.data.headers["x-forwarded-for"]) + if (ipAddress) await visitorCounter.count(ipAddress) + else logger.info("No IP Address forwarded, skipping update to visitor statistics") + const redisCount = await redisClient.incr("currentWsConnections") + console.log("Websocket connected.", { clients: app.server?.pendingWebSockets, redisCount }) + signal.addEventListener("abort", () => ws.close()) + }, + async close(ws, code, reason) { + // decrement connections counter and unsubscribe from all streams + for (const streamPath of streamPaths) + ws.unsubscribe(streamPath) // don't think this is necessary, surely its done automatically when a socket terminates + const redisCount = await redisClient.decr("currentWsConnections") + console.log("Websocket disconnected with code.", code, { clients: app.server?.pendingWebSockets, redisCount }) + unclosedWsCount-- + } + }) +} diff --git a/server/src/api/routes/healthCheck.ts b/server/src/api/routes/healthCheck.ts new file mode 100644 index 0000000..f58b4e2 --- /dev/null +++ b/server/src/api/routes/healthCheck.ts @@ -0,0 +1,13 @@ +import { Elysia } from "elysia" +import { redisClient } from "../../utils/getRedisClient" +import { streamPaths } from "../../streams/streamPaths" + +export const healthCheckRouter = (app: Elysia) => app.get("/health", async () => { + const health = { currentWsConnections: 0, connections: app.server?.pendingWebSockets } + for (const streamPath of streamPaths) { + const lastHeartbeat = await redisClient.hGet("heartbeats", streamPath).then(t => new Date(parseInt(t || "0"))) + health[streamPath] = Date.now() - lastHeartbeat.getTime() < 60_000 // more than 60 seconds indicates stream offline + } + health.currentWsConnections = await redisClient.get("currentWsConnections").then(value => value ? parseInt(value) : 0) + return health +}) diff --git a/server/src/api/routes/misc.ts b/server/src/api/routes/misc.ts new file mode 100644 index 0000000..ba5d13f --- /dev/null +++ b/server/src/api/routes/misc.ts @@ -0,0 +1,16 @@ +import { Elysia } from "elysia" +import { redisClient } from "../../utils/getRedisClient" + +export const miscRouter = (app: Elysia) => { + /** Returns an array of random company numbers */ + return app.get("/randomCompanyNumbers", async () => { + const qty = 1 // this could be a search query param + const companyNumbers = await redisClient.sRandMemberCount("companyNumbers", qty) + return companyNumbers + }) + .get("/schemas", async () => { + const schemasRaw = await redisClient.hGetAll("schemas") + const schemas = Object.fromEntries(Object.entries(schemasRaw).map(([schemaName, schemaString]) => [schemaName, JSON.parse(schemaString)])) + return schemas + }) +} diff --git a/server/src/api/routes/visitors.ts b/server/src/api/routes/visitors.ts new file mode 100644 index 0000000..d7d0136 --- /dev/null +++ b/server/src/api/routes/visitors.ts @@ -0,0 +1,30 @@ +import { Elysia } from "elysia" +import { VisitorCounterService } from "../visitorCounter" +import { redisClient } from "../../utils/getRedisClient" + +export const visitorsRouter = async (app: Elysia) => { + const visitorCounter = new VisitorCounterService(redisClient) + return app.get("/visitors", async () => { + const total = await visitorCounter.getTotalCount() + const today = await visitorCounter.getCount(new Date().toISOString().split("T")[0]) + return { total, today } + }) + .get("/visitors/:date", async ({ params, set }) => { + const { date } = params + if (!/[0-9-]{10}/.test(date)) { + set.status = (416) + return ({ statusCode: 400, message: "Bad date format" }) + } else { + if (new Date(date) < new Date("2023-09-12")) { + set.status = (416) + return ({ + statusCode: 416, + message: "Records only began on 2023-09-12. Request a date after that" + }) + } else { + const count = await visitorCounter.getCount(date) + return ({ [date]: count }) + } + } + }) +} diff --git a/server/src/redis/saveCompanyNumber.ts b/server/src/api/saveCompanyNumber.ts similarity index 65% rename from server/src/redis/saveCompanyNumber.ts rename to server/src/api/saveCompanyNumber.ts index 70c2034..539e606 100644 --- a/server/src/redis/saveCompanyNumber.ts +++ b/server/src/api/saveCompanyNumber.ts @@ -1,43 +1,43 @@ -import { RedisClient } from "./getRedisClient.js" +import { RedisClient } from "../utils/getRedisClient.js" /** returns true if a company number was added, false if it already existed or couldn't be parsed */ -export async function saveCompanyNumber(redis: RedisClient, event, streamPath){ +export async function saveCompanyNumber(redis: RedisClient, event, streamPath) { const companyNumber = getCompanyNumber(event, streamPath) - if(companyNumber) { + if (companyNumber) { const existed = await redis.sAdd("companyNumbers", companyNumber).then(res => res === 1) + //TODO: check the size of the set and if its greater than a threshold (eg 5000) then delete a random item return existed } return false } - /** Gets the company number from an event, based on which stream the event was sent on */ -function getCompanyNumber(event, streamPath: string){ +function getCompanyNumber(event, streamPath: string) { switch (streamPath) { - case 'companies': + case "companies": return event.data.company_number - case 'filings': { - const [,companyNumber] = event.resource_uri.match(/^\/company\/([A-Z0-9]{8})\/filing-history/) + case "filings": { + const [, companyNumber] = event.resource_uri.match(/^\/company\/([A-Z0-9]{8})\/filing-history/) return companyNumber } - case 'officers': { + case "officers": { const [, companyNumber] = event.resource_uri.match(/^\/company\/([A-Z0-9]{8})\/appointments/) return companyNumber } - case 'persons-with-significant-control': { + case "persons-with-significant-control": { const [, companyNumber] = event.resource_uri.match( /^\/company\/([A-Z0-9]{6,8})\/persons-with-significant-control/ ) return companyNumber } - case 'charges': { + case "charges": { const [, companyNumber] = event.resource_uri.match(/^\/company\/([A-Z0-9]{8})\/charges/) return companyNumber } - case 'insolvency-cases': + case "insolvency-cases": return event.resource_id default: - return '' + return "" } } diff --git a/server/src/api/streamFromRedis.ts b/server/src/api/streamFromRedis.ts new file mode 100644 index 0000000..a16acb9 --- /dev/null +++ b/server/src/api/streamFromRedis.ts @@ -0,0 +1,62 @@ +import { listenRedisStream } from "./listenRedisStream.js" +import { streamFromRedisLogger as logger } from "../utils/loggers.js" +import { saveCompanyNumber } from "./saveCompanyNumber.js" +import { streamPaths } from "../streams/streamPaths.js" +import { updateSchemaForEvent } from "./maintainSchemas.js" +import { Elysia } from "elysia" +import { healthCheckRouter } from "./routes/healthCheck" +import { miscRouter } from "./routes/misc" +import { eventHistoryRouter } from "./routes/eventHistory" +import { visitorsRouter } from "./routes/visitors" +import { eventWebsocketRouter } from "./routes/eventsWebsocket" +import { redisClient } from "../utils/getRedisClient" +import { setTimeout } from "node:timers/promises" + +const app = new Elysia() + .use(healthCheckRouter) + .use(miscRouter) + .use(eventHistoryRouter) + .use(visitorsRouter) + .use(eventWebsocketRouter) + .on("request", ({ request }) => console.log("Request to server", request.url)) + .on("stop", async () => { + logger.flush() + await redisClient.quit() + }) + .listen(3000, () => console.log("Elysia Listening on port 3000")) + + +const ac = new AbortController() +const { signal } = ac + +const eventStream = listenRedisStream({ streamKeys: [...streamPaths].map(stream => ({ stream })), signal }) + + +async function shutdown() { + const requestTime = Bun.nanoseconds() + try { + console.log("Graceful shutdown commenced", new Date()) + ac.abort() + await app.stop() + await setTimeout(500) + } finally { + const waitingNs = Bun.nanoseconds() - requestTime + console.log("Graceful shutdown finished", new Date(), "in", waitingNs / 1000 / 1000, "ms") + process.exit() + } +} + +process.on("SIGINT", shutdown) // quit on ctrl-c when running docker in terminal +process.on("SIGTERM", shutdown)// quit properly on docker stop + +for await(const event of eventStream) { + const streamPath = event.stream.split(":")[1] + let parsedEvent = JSON.parse(event.data.event) + app.server?.publish(streamPath, JSON.stringify({ streamPath, ...parsedEvent })) + if (streamPath === "companies") + await saveCompanyNumber(redisClient, parsedEvent, streamPath) + .catch(e => logger.error(e, "Error saving company number")) + await updateSchemaForEvent(parsedEvent, redisClient) +} + +console.log("Async iterator of events exited", new Date()) diff --git a/server/src/redis/visitorCounter.ts b/server/src/api/visitorCounter.ts similarity index 94% rename from server/src/redis/visitorCounter.ts rename to server/src/api/visitorCounter.ts index 222be1c..a118539 100644 --- a/server/src/redis/visitorCounter.ts +++ b/server/src/api/visitorCounter.ts @@ -1,4 +1,4 @@ -import type { RedisClient } from "./getRedisClient" +import type { RedisClient } from "../utils/getRedisClient" /** diff --git a/server/src/redis/streamToRedis.ts b/server/src/chStreamToRedis/streamToRedis.ts similarity index 73% rename from server/src/redis/streamToRedis.ts rename to server/src/chStreamToRedis/streamToRedis.ts index 3f0ae08..dace6e6 100644 --- a/server/src/redis/streamToRedis.ts +++ b/server/src/chStreamToRedis/streamToRedis.ts @@ -1,5 +1,5 @@ import { stream } from "../streams/listenOnStream.js" -import { getRedisClient } from "./getRedisClient.js" +import { redisClient } from "../utils/getRedisClient.js" import { restKeyHolder, streamKeyHolder } from "../utils/KeyHolder.js" import { setTimeout } from "node:timers/promises" import pino from "pino" @@ -17,19 +17,19 @@ const keys = [process.env.STREAM_KEY1, process.env.STREAM_KEY2, process.env.STRE for (const key of keys) streamKeyHolder.addKey(key) restKeyHolder.addKey(process.env.REST_KEY1) const logger = pino() -const client = await getRedisClient() -const sendEvent = streamPath => event => client.xAdd("events:" + streamPath, event.event.timepoint + "-*", { "event": JSON.stringify(event) }, { + +const sendEvent = streamPath => event => redisClient.xAdd("events:" + streamPath, event.event.timepoint + "-*", { "event": JSON.stringify(event) }, { TRIM: { strategy: "MAXLEN", threshold: 10000, strategyModifier: "~" } }) -const incrEventCount = streamPath => event => client.hIncrBy(`counts:${streamPath}:daily`, new Date().toISOString().split("T")[0], 1) -const incrResourceKindCount = streamPath => event => client.hIncrBy(`resourceKinds:${streamPath}`, event.resource_kind, 1) -const updateTimepoint = streamPath => event => client.hSet("timepoints", streamPath, JSON.stringify(event.event)) -const heartbeat = streamPath => () => client.hSet("heartbeats", streamPath, Date.now()) // keeps track of which are alive -const getMostRecentTimepoint = streamPath => client.hGet("timepoints", streamPath).then(r => r ? JSON.parse(r)?.timepoint : undefined) +const incrEventCount = streamPath => event => redisClient.hIncrBy(`counts:${streamPath}:daily`, new Date().toISOString().split("T")[0], 1) +const incrResourceKindCount = streamPath => event => redisClient.hIncrBy(`resourceKinds:${streamPath}`, event.resource_kind, 1) +const updateTimepoint = streamPath => event => redisClient.hSet("timepoints", streamPath, JSON.stringify(event.event)) +const heartbeat = streamPath => () => redisClient.hSet("heartbeats", streamPath, Date.now()) // keeps track of which are alive +const getMostRecentTimepoint = streamPath => redisClient.hGet("timepoints", streamPath).then(r => r ? JSON.parse(r)?.timepoint : undefined) const startStream = streamPath => getMostRecentTimepoint(streamPath) .then((timepoint) => stream(streamPath, timepoint) .on("data", sendEvent(streamPath)) @@ -59,7 +59,7 @@ async function shutdown() { for (const stream of streams) { stream.destroy() } - await client.quit() + await redisClient.quit() logger.flush() } finally { const waitingNs = performance.now() - requestTime diff --git a/server/src/redis/getRedisClient.ts b/server/src/redis/getRedisClient.ts deleted file mode 100644 index 6edda27..0000000 --- a/server/src/redis/getRedisClient.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createClient, RedisClientType, RedisFunctions, RedisModules, RedisScripts } from "redis" - -export async function getRedisClient(): Promise { - const client = createClient({ - url: `redis://${process.env.PUBSUB_REDIS_IP}:6379`, - password: process.env.PUBSUB_REDIS_PASS - }) - await client.connect() - return client -} - -export type RedisClient = RedisClientType diff --git a/server/src/redis/streamFromRedis.ts b/server/src/redis/streamFromRedis.ts deleted file mode 100644 index 13c23f6..0000000 --- a/server/src/redis/streamFromRedis.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { getRedisClient } from "./getRedisClient.js" -import { listenRedisStream } from "./listenRedisStream.js" -import { streamFromRedisLogger as logger } from "../utils/loggers.js" -import { setTimeout } from "node:timers/promises" -import { saveCompanyNumber } from "./saveCompanyNumber.js" -import { streamPaths } from "../streams/streamPaths.js" -import { updateSchemaForEvent } from "../schemas/maintainSchemas.js" -import { VisitorCounterService } from "./visitorCounter.js" -import { Elysia } from "elysia" - -const app = new Elysia() -app.get("/health", async ({ request }) => { - const commandClient = await getRedisClient() - const health = { currentWsConnections: 0, connections: app.server?.pendingWebSockets } - for (const streamPath of streamPaths) { - const lastHeartbeat = await commandClient.hGet("heartbeats", streamPath).then(t => new Date(parseInt(t || "0"))) - health[streamPath] = Date.now() - lastHeartbeat.getTime() < 60_000 // more than 60 seconds indicates stream offline - } - health.currentWsConnections = await commandClient.get("currentWsConnections").then(value => value ? parseInt(value) : 0) - await commandClient.quit() - return health -}) - -/** Returns an array of random company numbers */ -app.get("/randomCompanyNumbers", async () => { - const qty = 1 // this could be a search query param - const companyNumbers = await counterClient.sRandMemberCount("companyNumbers", qty) - return companyNumbers -}) -/** Returns the `qty` most recent events in the `streamPath` stream. Eg last 100 filing events. */ -app.get("/downloadHistory/:streamPath", async ({ query, params, set }) => { - const { streamPath } = params - const { qty } = query - const COUNT = parseInt(String(qty)) || 100 // default to send 100 events, unless specified - if (COUNT > 10_000) { - set.status = 400 - return { - statusCode: 400, - message: "Qty exceeds maximum. Must be less than 10,000. Received: " + COUNT - } - } - if (streamPaths.has(streamPath)) { - const historyClient = await getRedisClient() - const history = await historyClient.xRevRange("events:" + streamPath, "+", "-", { COUNT }) - await historyClient.quit() - return history.map(h => JSON.parse(h.message.event)) - } else { - set.status = 400 - return { - statusCode: 400, - message: "Invalid stream path: " + streamPath, - possibleOptions: [...streamPaths] - } - } -}) -app.get("/stats/:streamPath", async ({ params, set }) => { - const { streamPath } = params - if (streamPaths.has(streamPath)) { - const client = await getRedisClient() - const rawCounts = await client.hGetAll(`counts:${streamPath}:daily`) - const counts = Object.fromEntries(Object.entries(rawCounts).map(([date, count]) => [date, parseInt(count)])) - await client.quit() - return counts - } else { - set.status = (400) - return ({ - statusCode: 400, - message: "Invalid stream path: " + streamPath, - possibleOptions: [...streamPaths] - }) - } -}) -app.get("/resourceKinds/:streamPath", async ({ params, set }) => { - const { streamPath } = params - if (streamPaths.has(streamPath)) { - const client = await getRedisClient() - const rawCounts = await client.hGetAll(`resourceKinds:${streamPath}`) - const counts = Object.fromEntries(Object.entries(rawCounts).map(([date, count]) => [date, parseInt(count)])) - await client.quit() - return counts - } else { - set.status = (400) - return ({ - statusCode: 400, - message: "Invalid stream path: " + streamPath, - possibleOptions: [...streamPaths] - }) - } -}) - -app.get("/schemas", async () => { - const schemasRaw = await counterClient.hGetAll("schemas") - const schemas = Object.fromEntries(Object.entries(schemasRaw).map(([schemaName, schemaString]) => [schemaName, JSON.parse(schemaString)])) - return schemas -}) - -const counterClient = await getRedisClient() -const visitorCounter = new VisitorCounterService(counterClient) -app.get("/visitors", async () => { - const total = await visitorCounter.getTotalCount() - const today = await visitorCounter.getCount(new Date().toISOString().split("T")[0]) - return { total, today } -}) -app.get("/visitors/:date", async ({ params, set }) => { - const { date } = params - if (!/[0-9-]{10}/.test(date)) { - set.status = (416) - return ({ statusCode: 400, message: "Bad date format" }) - } else { - if (new Date(date) < new Date("2023-09-12")) { - set.status = (416) - return ({ - statusCode: 416, - message: "Records only began on 2023-09-12. Request a date after that" - }) - } else { - const count = await visitorCounter.getCount(date) - return ({ [date]: count }) - } - } -}) - -const ac = new AbortController() -const { signal } = ac -let unclosedWsCount = 0 -// web socket server for sending events to client -app.ws("/events", { - async open(ws) { - unclosedWsCount++ - // subscribe to all streams and increment counter of connections - for (const streamPath of streamPaths) { - ws.subscribe(streamPath) - } - const ipAddress = String(ws.data.headers["x-forwarded-for"]) - if (ipAddress) await visitorCounter.count(ipAddress) - else logger.info("No IP Address forwarded, skipping update to visitor statistics") - const redisCount = await counterClient.incr("currentWsConnections") - console.log("Websocket connected.", { clients: app.server?.pendingWebSockets, redisCount }) - signal.addEventListener("abort", () => ws.close()) - }, - async close(ws, code, reason) { - // decrement connections counter and unsubscribe from all streams - for (const streamPath of streamPaths) - ws.unsubscribe(streamPath) // don't think this is necessary, surely its done automatically when a socket terminates - const redisCount = await counterClient.decr("currentWsConnections") - console.log("Websocket disconnected with code.", code, { clients: app.server?.pendingWebSockets, redisCount }) - unclosedWsCount-- - } -}) - -app.on("request", ({ request }) => console.log("Request to server", request.url)) -app.listen(3000, () => console.log("Elysia Listening on port 3000")) - - -async function shutdown() { - const requestTime = Bun.nanoseconds() - try { - logger.flush() - console.log("Graceful shutdown commenced", new Date()) - ac.abort() - while (unclosedWsCount) { - if (Bun.nanoseconds() - requestTime > 2 * 1_000_000_000) break // don't keep trying if its not working after 2 sec - await setTimeout(5) // wait for websockets to be closed gracefully before quiting the Redis client - } - const waitingNs = Bun.nanoseconds() - requestTime - console.log("Closed all client connections after", waitingNs / 1000 / 1000, "ms") - await app.stop() - await counterClient.quit() - logger.flush() - } finally { - const waitingNs = Bun.nanoseconds() - requestTime - console.log("Graceful shutdown finished", new Date(), "in", waitingNs / 1000 / 1000, "ms") - process.exit() - } -} - -process.on("SIGINT", shutdown) // quit on ctrl-c when running docker in terminal -process.on("SIGTERM", shutdown)// quit properly on docker stop - -const eventStream = listenRedisStream({ streamKeys: [...streamPaths].map(stream => ({ stream })), signal }) - -for await(const event of eventStream) { - const streamPath = event.stream.split(":")[1] - let parsedEvent = JSON.parse(event.data.event) - app.server?.publish(streamPath, JSON.stringify({ streamPath, ...parsedEvent })) - if (streamPath === "companies") - await saveCompanyNumber(counterClient, parsedEvent, streamPath) - .catch(e => logger.error(e, "Error saving company number")) - await updateSchemaForEvent(parsedEvent, counterClient) -} - diff --git a/server/src/utils/getRedisClient.ts b/server/src/utils/getRedisClient.ts new file mode 100644 index 0000000..96534c5 --- /dev/null +++ b/server/src/utils/getRedisClient.ts @@ -0,0 +1,21 @@ +import { createClient, RedisClientType, RedisFunctions, RedisModules, RedisScripts } from "redis" +import { streamFromRedisLogger } from "./loggers" + +export async function getRedisClient(clientName?: string): Promise { + const logger = streamFromRedisLogger.child({ clientName }) + const client = createClient({ + url: `redis://${process.env.PUBSUB_REDIS_IP}:6379`, + password: process.env.PUBSUB_REDIS_PASS + }) + client.on("error", err => logger.error({ err }, "Redis Client Error")) + client.on("end", () => logger.info("Redis Client closed")) + await client.connect() + logger.info({ ready: client.isReady, open: client.isOpen }, "Redis client connected") + return client +} + +export type RedisClient = RedisClientType + + +const redisClient = await getRedisClient("shared") +export { redisClient } diff --git a/server/test/index.test.ts b/server/test/index.test.ts index 3668136..c215b88 100644 --- a/server/test/index.test.ts +++ b/server/test/index.test.ts @@ -2,11 +2,12 @@ import assert, { equal } from "node:assert/strict" import { describe, it } from "bun:test" // sends a request to each REST endpoint and checks the response code and the basic structure of the body -const url = "https://companies.stream" +// const url = "https://companies.stream" +const url = "http://localhost:3000" describe("request endpoints from API", () => { it("should respond to a health check", async () => { - const res = await fetch(new URL("/events/health", url)) + const res = await fetch(new URL("/health", url)) equal(res.status, 200, "Response status not 200") const health = await res.json() assert("filings" in health) @@ -15,35 +16,35 @@ describe("request endpoints from API", () => { }) it("should provide random company numbers", async () => { - const res = await fetch(new URL("/events/randomCompanyNumbers", url)) + const res = await fetch(new URL("/randomCompanyNumbers", url)) equal(res.status, 200, "Response status not 200") const [companyNumber] = await res.json() equal(companyNumber.length, 8) }) it("should be able to download filings stream history", async () => { - const res = await fetch(new URL("/events/downloadHistory/filings?qty=1", url)) + const res = await fetch(new URL("/downloadHistory/filings?qty=1", url)) equal(res.status, 200, "Response status not 200") const events = await res.json() equal(events.length, 1) assert("resource_uri" in events.at(0), "Key missing from event returned in history") }) it("should be able get stream stats for filings", async () => { - const res = await fetch(new URL("/events/stats/filings", url)) + const res = await fetch(new URL("/stats/filings", url)) equal(res.status, 200, "Response status not 200") const stats = await res.json() const dates = Object.keys(stats) equal(typeof stats[dates[0]], "number") }) it("should be able get stream resource_kinds for PSCs", async () => { - const res = await fetch(new URL("/events/resourceKinds/persons-with-significant-control", url)) + const res = await fetch(new URL("/resourceKinds/persons-with-significant-control", url)) equal(res.status, 200, "Response status not 200") const resourceKindStats = await res.json() const resourceKinds = Object.keys(resourceKindStats) equal(typeof resourceKindStats[resourceKinds[0]], "number") }) it("should be able get schema for filing events", async () => { - const res = await fetch(new URL("/events/schemas", url)) + const res = await fetch(new URL("/schemas", url)) equal(res.status, 200, "Response status not 200") const schemas = await res.json() assert("filing-history" in schemas) @@ -52,7 +53,7 @@ describe("request endpoints from API", () => { equal(schema.properties.description.type, "string") }) it("should be able get visitors total stats", async () => { - const res = await fetch(new URL(`/events/visitors`, url)) + const res = await fetch(new URL(`/visitors`, url)) equal(res.status, 200, "Response status not 200") const visitors = await res.json() assert("today" in visitors) @@ -62,7 +63,7 @@ describe("request endpoints from API", () => { }) it("should be able get visitors for a date", async () => { const date = new Date().toISOString().slice(0, 10) - const res = await fetch(new URL(`/events/visitors/${date}`, url)) + const res = await fetch(new URL(`/visitors/${date}`, url)) equal(res.status, 200, "Response status not 200") const visitors = await res.json() assert(date in visitors)