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
-
-
-
-You need to enable JavaScript to run this app.
-
-
-
-
-
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
-
-
-
connected() ? socket() : socket = openSocket()}>{connected() ? "Connected" : "Disconnected"}
-
-
-
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
-}
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
-
-
-
{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
-
-
-
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
-
-
-
{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
-
-
-
{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
-
-
-
{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
-
-
-
{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
-
-
-
-
-
-
-
-
-
-
-
- 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
-
-
-
-
-
-
-
- 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)