From a214e96b03afef018d82ca11816337fe43e1f84d Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Mon, 24 Nov 2025 13:24:08 +0200 Subject: [PATCH 1/7] Added mssql persister --- .env.template | 2 +- Dockerfile | 2 +- README.md | 9 +- package.json | 2 + pnpm-lock.yaml | 612 +++++++++++++++++++++ src/persistance/mssql/mssql-persistance.js | 153 ++++++ src/persistance/persister-factories.js | 4 +- 7 files changed, 779 insertions(+), 5 deletions(-) create mode 100644 src/persistance/mssql/mssql-persistance.js diff --git a/.env.template b/.env.template index e64e113..6c5331f 100644 --- a/.env.template +++ b/.env.template @@ -3,6 +3,6 @@ POWERSYNC_PUBLIC_KEY= POWERSYNC_URL= PORT= JWT_ISSUER= -# Either 'mongodb', 'mysql' or 'postgres'. This defaults to Postgres +# Either 'mongodb', 'mysql', 'mssql' or 'postgres'. This defaults to Postgres DATABASE_TYPE= DATABASE_URI= diff --git a/Dockerfile b/Dockerfile index 11291cd..83dc7e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:20 ENV DATABASE_URI= -# Either 'mongodb' or 'postgres'. This defaults to Postgres +# Either 'mongodb', 'postgres', 'mssql' or 'mysql'. This defaults to Postgres ENV DATABASE_TYPE= ENV POWERSYNC_PRIVATE_KEY= ENV POWERSYNC_PUBLIC_KEY= diff --git a/README.md b/README.md index d61a3e2..64a8977 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Overview -This repo contains a demo Node.js server application which has HTTP endpoints to authorize a [PowerSync](https://www.powersync.com/) enabled application to sync data between a client device and a PostgreSQL or MongoDB database. +This repo contains a demo Node.js server application which has HTTP endpoints to authorize a [PowerSync](https://www.powersync.com/) enabled application to sync data between a client device and a PostgreSQL, MySQL, MSSQL or MongoDB database. The endpoints are as follows: @@ -34,11 +34,16 @@ The endpoints are as follows: [mysql2](https://www.npmjs.com/package/mysql2) is used to interact with the MySQL database when a client performs requests to the `/api/data` endpoint. +[node-mssql](https://www.npmjs.com/package/mssql) is used to connect to a MSSQL database to perform operations from the `/api/data` endpoint. + [jose](https://github.com/panva/jose) is used to sign the JWT which PowerSync uses for authorization. ## Requirements -This app needs a Postgres instance that's hosted. For a free version for testing/demo purposes, visit [Supabase](https://supabase.com/). +Based on configuration, this app needs a Postgres, Mongo, MSSQL or MySQL instance. Easiest is probably to use docker containers for these databases. +Hosted free versions that can also be used: +1. Postgres: For a free version for testing/demo purposes, visit [Supabase](https://supabase.com/). +2. MSSQL: For a free version of Azure SQL for testing/demo purposes, visit [Azure SQL](https://learn.microsoft.com/en-us/azure/azure-sql/database/free-offer?view=azuresql). ## Running the app diff --git a/package.json b/package.json index b5f9bcf..c932ecc 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,14 @@ "express": "^4.18.2", "jose": "^5.3.0", "mongodb": "^6.9.0", + "mssql": "^12.1.0", "mysql2": "^3.11.3", "pg": "^8.11.3", "winston": "^3.11.0" }, "devDependencies": { "@types/node": "^22.7.5", + "@types/mssql": "^9.1.8", "prettier": "^3.2.4", "typescript": "^5.6.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6a97d5..eb21d64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: mongodb: specifier: ^6.9.0 version: 6.9.0 + mssql: + specifier: ^12.1.0 + version: 12.1.1 mysql2: specifier: ^3.11.3 version: 3.11.3 @@ -33,6 +36,9 @@ importers: specifier: ^3.11.0 version: 3.14.2 devDependencies: + '@types/mssql': + specifier: ^9.1.8 + version: 9.1.8 '@types/node': specifier: ^22.7.5 version: 22.7.5 @@ -45,6 +51,74 @@ importers: packages: + '@azure-rest/core-client@2.5.1': + resolution: {integrity: sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==} + engines: {node: '>=20.0.0'} + + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.10.1': + resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==} + engines: {node: '>=20.0.0'} + + '@azure/core-client@1.10.1': + resolution: {integrity: sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==} + engines: {node: '>=20.0.0'} + + '@azure/core-http-compat@2.3.1': + resolution: {integrity: sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g==} + engines: {node: '>=20.0.0'} + + '@azure/core-lro@2.7.2': + resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==} + engines: {node: '>=18.0.0'} + + '@azure/core-paging@1.6.2': + resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.22.2': + resolution: {integrity: sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==} + engines: {node: '>=20.0.0'} + + '@azure/core-tracing@1.3.1': + resolution: {integrity: sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==} + engines: {node: '>=20.0.0'} + + '@azure/core-util@1.13.1': + resolution: {integrity: sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==} + engines: {node: '>=20.0.0'} + + '@azure/identity@4.13.0': + resolution: {integrity: sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw==} + engines: {node: '>=20.0.0'} + + '@azure/keyvault-common@2.0.0': + resolution: {integrity: sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==} + engines: {node: '>=18.0.0'} + + '@azure/keyvault-keys@4.10.0': + resolution: {integrity: sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag==} + engines: {node: '>=18.0.0'} + + '@azure/logger@1.3.0': + resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==} + engines: {node: '>=20.0.0'} + + '@azure/msal-browser@4.26.2': + resolution: {integrity: sha512-F2U1mEAFsYGC5xzo1KuWc/Sy3CRglU9Ql46cDUx8x/Y3KnAIr1QAq96cIKCk/ZfnVxlvprXWRjNKoEpgLJXLhg==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@15.13.2': + resolution: {integrity: sha512-cNwUoCk3FF8VQ7Ln/MdcJVIv3sF73/OT86cRH81ECsydh7F4CNfIo2OAx6Cegtg8Yv75x4506wN4q+Emo6erOA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@3.8.3': + resolution: {integrity: sha512-Ul7A4gwmaHzYWj2Z5xBDly/W8JSC1vnKgJ898zPMZr0oSf1ah0tiL15sytjycU/PMhDZAlkWtEL1+MzNMU6uww==} + engines: {node: '>=16'} + '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} @@ -52,12 +126,24 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@js-joda/core@5.6.5': + resolution: {integrity: sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ==} + '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} + '@tediousjs/connection-string@0.6.0': + resolution: {integrity: sha512-GxlsW354Vi6QqbUgdPyQVcQjI7cZBdGV5vOYVYuCVDTylx2wl3WHR2HlhcxxHTrMigbelpXsdcZso+66uxPfow==} + + '@types/mssql@9.1.8': + resolution: {integrity: sha512-mt9h5jWj+DYE5jxnKaWSV/GqDf9FV52XYVk6T3XZF69noEe+JJV6MKirii48l81+cjmAkSq+qeKX+k61fHkYrQ==} + '@types/node@22.7.5': resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/readable-stream@4.0.22': + resolution: {integrity: sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg==} + '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} @@ -67,10 +153,22 @@ packages: '@types/whatwg-url@11.0.5': resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + '@typespec/ts-http-runtime@0.3.2': + resolution: {integrity: sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==} + engines: {node: '>=20.0.0'} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -81,6 +179,12 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@6.1.5: + resolution: {integrity: sha512-XylDt2P3JBttAwLpORq/hOEX9eJzP0r6Voa46C/WVvad8D1J0jW5876txB8FnzKtbdnU6X4Y1vOEvC6PllJrDg==} + body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -89,6 +193,16 @@ packages: resolution: {integrity: sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==} engines: {node: '>=16.20.1'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -115,6 +229,10 @@ packages: colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -138,10 +256,31 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -158,6 +297,9 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -187,6 +329,14 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + express@4.21.0: resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} engines: {node: '>= 0.10.0'} @@ -241,6 +391,14 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -249,6 +407,13 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -259,6 +424,16 @@ packages: is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} @@ -266,12 +441,50 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + jose@5.9.2: resolution: {integrity: sha512-ILI2xx/I57b20sd7rHZvgiiQrmp2mcotwsAH+5ajbpFQbrYVQdNHYlQhoA5cFb78CgtBOxtC05TeA+mcgkuCqQ==} + js-md4@0.3.2: + resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + logform@2.6.1: resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} engines: {node: '>= 12.0.0'} @@ -350,6 +563,11 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mssql@12.1.1: + resolution: {integrity: sha512-nUTXi0unU6p72YKe6KDR9vW2mSQWsmy1KZqV0JkaT2v3RSkxlwx4Y4srjYmH+DZNbyA53Ijp6o2OaLnLc4F2Qg==} + engines: {node: '>=18'} + hasBin: true + mysql2@3.11.3: resolution: {integrity: sha512-Qpu2ADfbKzyLdwC/5d4W7+5Yz7yBzCU05YWt5npWzACST37wJsB23wgOSo00qi043urkiRwXtEvJc9UnuLX/MQ==} engines: {node: '>= 8.0'} @@ -358,6 +576,9 @@ packages: resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} engines: {node: '>=12.0.0'} + native-duplexpair@1.0.0: + resolution: {integrity: sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -373,6 +594,10 @@ packages: one-time@1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -435,6 +660,10 @@ packages: engines: {node: '>=14'} hasBin: true + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -459,6 +688,14 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -469,6 +706,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -501,6 +743,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + sqlstring@2.3.3: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} @@ -515,6 +760,14 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + tarn@3.0.2: + resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} + engines: {node: '>=8.0.0'} + + tedious@19.1.3: + resolution: {integrity: sha512-6O6efTeYtcnar3Cqf/ptqJs+U10fYYjp/SHRNm3VGuCTUDys+AUgIbxWbT2kzl4baXAzuy9byV3qCgOimrRfTA==} + engines: {node: '>=18.17'} + text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -530,6 +783,9 @@ packages: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -553,6 +809,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -573,12 +833,161 @@ packages: resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} engines: {node: '>= 12.0.0'} + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} snapshots: + '@azure-rest/core-client@2.5.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@typespec/ts-http-runtime': 0.3.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.10.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-client@1.10.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-http-compat@2.3.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-client': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + transitivePeerDependencies: + - supports-color + + '@azure/core-lro@2.7.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-paging@1.6.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-rest-pipeline@1.22.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + '@typespec/ts-http-runtime': 0.3.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.3.1': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.13.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@typespec/ts-http-runtime': 0.3.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/identity@4.13.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-client': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + '@azure/msal-browser': 4.26.2 + '@azure/msal-node': 3.8.3 + open: 10.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/keyvault-common@2.0.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-client': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/keyvault-keys@4.10.0': + dependencies: + '@azure-rest/core-client': 2.5.1 + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-http-compat': 2.3.1 + '@azure/core-lro': 2.7.2 + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/keyvault-common': 2.0.0 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.3.0': + dependencies: + '@typespec/ts-http-runtime': 0.3.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/msal-browser@4.26.2': + dependencies: + '@azure/msal-common': 15.13.2 + + '@azure/msal-common@15.13.2': {} + + '@azure/msal-node@3.8.3': + dependencies: + '@azure/msal-common': 15.13.2 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + '@colors/colors@1.6.0': {} '@dabh/diagnostics@2.0.3': @@ -587,14 +996,30 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 + '@js-joda/core@5.6.5': {} + '@mongodb-js/saslprep@1.1.9': dependencies: sparse-bitfield: 3.0.3 + '@tediousjs/connection-string@0.6.0': {} + + '@types/mssql@9.1.8': + dependencies: + '@types/node': 22.7.5 + tarn: 3.0.2 + tedious: 19.1.3 + transitivePeerDependencies: + - supports-color + '@types/node@22.7.5': dependencies: undici-types: 6.19.8 + '@types/readable-stream@4.0.22': + dependencies: + '@types/node': 22.7.5 + '@types/triple-beam@1.3.5': {} '@types/webidl-conversions@7.0.3': {} @@ -603,17 +1028,40 @@ snapshots: dependencies: '@types/webidl-conversions': 7.0.3 + '@typespec/ts-http-runtime@0.3.2': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 + agent-base@7.1.4: {} + array-flatten@1.1.1: {} async@3.2.6: {} aws-ssl-profiles@1.1.2: {} + base64-js@1.5.1: {} + + bl@6.1.5: + dependencies: + '@types/readable-stream': 4.0.22 + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 4.7.0 + body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -633,6 +1081,17 @@ snapshots: bson@6.8.0: {} + buffer-equal-constant-time@1.0.1: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + bytes@3.1.2: {} call-bind@1.0.7: @@ -666,6 +1125,8 @@ snapshots: color: 3.2.1 text-hex: 1.0.0 + commander@11.1.0: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -680,12 +1141,25 @@ snapshots: dependencies: ms: 2.0.0 + debug@4.4.3: + dependencies: + ms: 2.1.3 + + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 gopd: 1.0.1 + define-lazy-prop@3.0.0: {} + denque@2.1.0: {} depd@2.0.0: {} @@ -694,6 +1168,10 @@ snapshots: dotenv@16.4.5: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} enabled@2.0.0: {} @@ -712,6 +1190,10 @@ snapshots: etag@1.8.1: {} + event-target-shim@5.0.1: {} + + events@3.3.0: {} + express@4.21.0: dependencies: accepts: 1.3.8 @@ -806,6 +1288,20 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -814,20 +1310,76 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + inherits@2.0.4: {} ipaddr.js@1.9.1: {} is-arrayish@0.3.2: {} + is-docker@3.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-property@1.0.2: {} is-stream@2.0.1: {} + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + jose@5.9.2: {} + js-md4@0.3.2: {} + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.3 + + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + kuler@2.0.0: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} + logform@2.6.1: dependencies: '@colors/colors': 1.6.0 @@ -874,6 +1426,16 @@ snapshots: ms@2.1.3: {} + mssql@12.1.1: + dependencies: + '@tediousjs/connection-string': 0.6.0 + commander: 11.1.0 + debug: 4.4.3 + tarn: 3.0.2 + tedious: 19.1.3 + transitivePeerDependencies: + - supports-color + mysql2@3.11.3: dependencies: aws-ssl-profiles: 1.1.2 @@ -890,6 +1452,8 @@ snapshots: dependencies: lru-cache: 7.18.3 + native-duplexpair@1.0.0: {} + negotiator@0.6.3: {} object-inspect@1.13.2: {} @@ -902,6 +1466,13 @@ snapshots: dependencies: fn.name: 1.1.0 + open@10.2.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + parseurl@1.3.3: {} path-to-regexp@0.1.10: {} @@ -953,6 +1524,8 @@ snapshots: prettier@3.3.3: {} + process@0.11.10: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -979,12 +1552,24 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + run-applescript@7.1.0: {} + safe-buffer@5.2.1: {} safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} + semver@7.7.3: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -1042,6 +1627,8 @@ snapshots: split2@4.2.0: {} + sprintf-js@1.1.3: {} + sqlstring@2.3.3: {} stack-trace@0.0.10: {} @@ -1052,6 +1639,23 @@ snapshots: dependencies: safe-buffer: 5.2.1 + tarn@3.0.2: {} + + tedious@19.1.3: + dependencies: + '@azure/core-auth': 1.10.1 + '@azure/identity': 4.13.0 + '@azure/keyvault-keys': 4.10.0 + '@js-joda/core': 5.6.5 + '@types/node': 22.7.5 + bl: 6.1.5 + iconv-lite: 0.7.0 + js-md4: 0.3.2 + native-duplexpair: 1.0.0 + sprintf-js: 1.1.3 + transitivePeerDependencies: + - supports-color + text-hex@1.0.0: {} toidentifier@1.0.1: {} @@ -1062,6 +1666,8 @@ snapshots: triple-beam@1.4.1: {} + tslib@2.8.1: {} + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -1077,6 +1683,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@8.3.2: {} + vary@1.1.2: {} webidl-conversions@7.0.0: {} @@ -1106,4 +1714,8 @@ snapshots: triple-beam: 1.4.1 winston-transport: 4.7.1 + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + xtend@4.0.2: {} diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js new file mode 100644 index 0000000..839b965 --- /dev/null +++ b/src/persistance/mssql/mssql-persistance.js @@ -0,0 +1,153 @@ +import { URL } from 'url'; +import sql from 'mssql'; + + +function escapeIdentifier(identifier) { + return `[${identifier}]`; +} + +/** + * Creates a MSSQL batch persister. This is used by the + * `data` api routes. + * @param {string} uri MSSQL connection URI + */ +export const createMSSQLPersister = async (uri) => { + console.debug('Using MSSQL Persister'); + + const url = new URL(uri); + + const pool = new sql.ConnectionPool({ + user: url.username, + password: url.password, + server: url.hostname, + port: parseInt(url.port), + database: url.pathname.split('/')[1], + }); + + pool.on('error', (err) => { + console.error('MSSQL pool connection failure', err); + }); + + await pool.connect(); + + /** + * @type {import('../persister-factories.js').Persister} + */ + const persister = { + updateBatch: async (batch) => { + const transaction = await pool.transaction(); + try { + + await transaction.begin(); + + for (let op of batch) { + const table = escapeIdentifier(op.table); + if (op.op == 'PUT') { + const data = op.data; + const with_id = { ...data, id: op.id ?? op.data.id }; + + // [id], [col1], [col3] + const columnsEscaped = Object.keys(with_id).map(escapeIdentifier); + const columns = columnsEscaped.join(', '); + const columnParamaters = columnsEscaped.map((c) => `@${c}`).join(', '); + const sourceColumns = columnsEscaped.map(column => `source.${column}`).join(', '); + + let updateClauses = []; + // [[col1] = source.col1, [col3] = source.col3] + for (const key of Object.keys(data)) { + if (key == 'id') { + continue; + } + updateClauses.push(`${escapeIdentifier(key)} = source.${escapeIdentifier(key)}`); + } + + const updateClause = updateClauses.length > 0 ? `WHEN MATCHED THEN UPDATE SET ${updateClauses.join(', ')}` : `DO NOTHING`; + const insertClause = `WHEN NOT MATCHED THEN INSERT (${columns}) VALUES (${sourceColumns})`; + + const statement = ` + MERGE INTO ${table} AS t + USING (SELECT ${columnParamaters}) AS source (${columns}) + ON t.id = source.id + ${updateClause} + ${insertClause} + `; + + const request = transaction.request(); + for (const column of columnsEscaped) { + request.input(column, with_id[column]); + } + const result = await request.query(statement); + console.log(result); + } else if (op.op == 'PATCH') { + const data = op.data; + const with_id = { ...data, id: op.id ?? data.id }; + + let updateClauses = []; + + for (const key of Object.keys(data)) { + if (key == 'id') { + continue; + } + updateClauses.push(`${escapeIdentifier(key)} = @${key}`); + } + + const statement = ` + UPDATE ${table} + SET ${updateClauses.join(', ')} + WHERE id = @id`; + + const request = transaction.request(); + for (const column of Object.keys(with_id)) { + request.input(column, with_id[column]); + } + + const result = await request.query(statement); + console.log(result); + } else if (op.op == 'DELETE') { + const id = op.id ?? op.data?.id; + const statement = `DELETE FROM ${table} WHERE id = @id`; + const request = transaction.request(); + request.input('id', id); + const result = await request.query(statement); + console.log(result); + } + } + await transaction.commit(); + } catch (e) { + await transaction.rollback(); + throw e; + } + }, + async createCheckpoint(user_id, client_id) { + const transaction = await pool.transaction(); + try { + await transaction.begin(); + + const statement = ` + MERGE INTO checkpoints AS t + USING (SELECT @user_id, @client_id, @checkpoint) AS source (user_id, client_id, checkpoint) + ON t.user_id = source.user_id AND t.client_id = source.client_id + WHEN MATCHED THEN + UPDATE SET t.checkpoint = t.checkpoint + 1 + WHEN NOT MATCHED THEN + INSERT (user_id, client_id, checkpoint) + VALUES (source.user_id, source.client_id, source.checkpoint) + OUTPUT INSERTED.checkpoint; + `; + const request = transaction.request(); + request.input('user_id', user_id); + request.input('client_id', client_id); + request.input('checkpoint', 1); + const response = await request.query(statement); + + await transaction.commit(); + return response.recordset[0].checkpoint; + } catch (e) { + await transaction.rollback(); + throw e; + } + + } + }; + return persister; +}; diff --git a/src/persistance/persister-factories.js b/src/persistance/persister-factories.js index d1871a6..6105d06 100644 --- a/src/persistance/persister-factories.js +++ b/src/persistance/persister-factories.js @@ -1,6 +1,7 @@ import { createMongoPersister } from './mongo/mongo-persistance.js'; import { createMySQLPersister } from './mysql/mysql-persistance.js'; import { createPostgresPersister } from './postgres/postgres-persistance.js'; +import { createMSSQLPersister } from './mssql/mssql-persistance.js'; /** * Apply a batch of PUT, PATCH and/or DELETE updates. @@ -47,5 +48,6 @@ import { createPostgresPersister } from './postgres/postgres-persistance.js'; export const factories = { mongodb: createMongoPersister, postgres: createPostgresPersister, - mysql: createMySQLPersister + mysql: createMySQLPersister, + mssql: createMSSQLPersister }; From 34da02194f1844e36fe946ba2d755dfbf5977c28 Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Mon, 24 Nov 2025 14:08:32 +0200 Subject: [PATCH 2/7] Trust certificate --- src/persistance/mssql/mssql-persistance.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js index 839b965..cad5cd6 100644 --- a/src/persistance/mssql/mssql-persistance.js +++ b/src/persistance/mssql/mssql-persistance.js @@ -22,6 +22,10 @@ export const createMSSQLPersister = async (uri) => { server: url.hostname, port: parseInt(url.port), database: url.pathname.split('/')[1], + options: { + encrypt: true, + trustServerCertificate: true + } }); pool.on('error', (err) => { From 6a46ba5b19e0140476140a9821dcde80e3382e69 Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Mon, 24 Nov 2025 21:52:24 +0200 Subject: [PATCH 3/7] Fixed escaped parameter names --- src/persistance/mssql/mssql-persistance.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js index cad5cd6..160aeaf 100644 --- a/src/persistance/mssql/mssql-persistance.js +++ b/src/persistance/mssql/mssql-persistance.js @@ -50,10 +50,13 @@ export const createMSSQLPersister = async (uri) => { const data = op.data; const with_id = { ...data, id: op.id ?? op.data.id }; + // Get column names (without escaping for parameters) + const columnNames = Object.keys(with_id); // [id], [col1], [col3] - const columnsEscaped = Object.keys(with_id).map(escapeIdentifier); + const columnsEscaped = columnNames.map(escapeIdentifier); const columns = columnsEscaped.join(', '); - const columnParamaters = columnsEscaped.map((c) => `@${c}`).join(', '); + // Parameter names should not have brackets: @id, @col1, @col3 + const columnParamaters = columnNames.map((c) => `@${c}`).join(', '); const sourceColumns = columnsEscaped.map(column => `source.${column}`).join(', '); let updateClauses = []; @@ -71,13 +74,14 @@ export const createMSSQLPersister = async (uri) => { const statement = ` MERGE INTO ${table} AS t USING (SELECT ${columnParamaters}) AS source (${columns}) - ON t.id = source.id + ON t.[id] = source.[id] ${updateClause} ${insertClause} `; const request = transaction.request(); - for (const column of columnsEscaped) { + // Use original column names (not escaped) for parameter names + for (const column of columnNames) { request.input(column, with_id[column]); } const result = await request.query(statement); From 7af1c3f2a8e00b413147a479a9972e3940c93c7a Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Mon, 24 Nov 2025 21:59:50 +0200 Subject: [PATCH 4/7] Added some more debug logging. --- src/persistance/mssql/mssql-persistance.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js index 160aeaf..fa8d190 100644 --- a/src/persistance/mssql/mssql-persistance.js +++ b/src/persistance/mssql/mssql-persistance.js @@ -79,6 +79,8 @@ export const createMSSQLPersister = async (uri) => { ${insertClause} `; + console.log(statement); + const request = transaction.request(); // Use original column names (not escaped) for parameter names for (const column of columnNames) { @@ -103,6 +105,8 @@ export const createMSSQLPersister = async (uri) => { UPDATE ${table} SET ${updateClauses.join(', ')} WHERE id = @id`; + + console.log(statement); const request = transaction.request(); for (const column of Object.keys(with_id)) { From c8744f76c3a23957274eb84f6be2b0142f7c89a2 Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Mon, 24 Nov 2025 22:05:34 +0200 Subject: [PATCH 5/7] Added missing semicolon. --- src/persistance/mssql/mssql-persistance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js index fa8d190..8fa315b 100644 --- a/src/persistance/mssql/mssql-persistance.js +++ b/src/persistance/mssql/mssql-persistance.js @@ -76,7 +76,7 @@ export const createMSSQLPersister = async (uri) => { USING (SELECT ${columnParamaters}) AS source (${columns}) ON t.[id] = source.[id] ${updateClause} - ${insertClause} + ${insertClause}; `; console.log(statement); From 58edd09a362aa90763954e1961e1092e0df4a447 Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Mon, 24 Nov 2025 23:25:16 +0200 Subject: [PATCH 6/7] Removed debug logs for mssql persister --- src/persistance/mssql/mssql-persistance.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js index 8fa315b..2088a48 100644 --- a/src/persistance/mssql/mssql-persistance.js +++ b/src/persistance/mssql/mssql-persistance.js @@ -79,15 +79,13 @@ export const createMSSQLPersister = async (uri) => { ${insertClause}; `; - console.log(statement); - const request = transaction.request(); // Use original column names (not escaped) for parameter names for (const column of columnNames) { request.input(column, with_id[column]); } - const result = await request.query(statement); - console.log(result); + await request.query(statement); + } else if (op.op == 'PATCH') { const data = op.data; const with_id = { ...data, id: op.id ?? data.id }; @@ -106,22 +104,18 @@ export const createMSSQLPersister = async (uri) => { SET ${updateClauses.join(', ')} WHERE id = @id`; - console.log(statement); - const request = transaction.request(); for (const column of Object.keys(with_id)) { request.input(column, with_id[column]); } - const result = await request.query(statement); - console.log(result); + await request.query(statement); } else if (op.op == 'DELETE') { const id = op.id ?? op.data?.id; const statement = `DELETE FROM ${table} WHERE id = @id`; const request = transaction.request(); request.input('id', id); - const result = await request.query(statement); - console.log(result); + await request.query(statement); } } await transaction.commit(); From 015201c3bb976158b700a863e23c3e988e3e4822 Mon Sep 17 00:00:00 2001 From: Roland Teichert Date: Tue, 25 Nov 2025 14:05:49 +0200 Subject: [PATCH 7/7] Fixed potential syntax problems --- src/persistance/mssql/mssql-persistance.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/persistance/mssql/mssql-persistance.js b/src/persistance/mssql/mssql-persistance.js index 2088a48..dcedbdf 100644 --- a/src/persistance/mssql/mssql-persistance.js +++ b/src/persistance/mssql/mssql-persistance.js @@ -68,14 +68,15 @@ export const createMSSQLPersister = async (uri) => { updateClauses.push(`${escapeIdentifier(key)} = source.${escapeIdentifier(key)}`); } - const updateClause = updateClauses.length > 0 ? `WHEN MATCHED THEN UPDATE SET ${updateClauses.join(', ')}` : `DO NOTHING`; + // Update clause is omitted if there are no fields to update (Can only happen if the only record in data is the id) + const updateClause = updateClauses.length > 0 ? `WHEN MATCHED THEN UPDATE SET ${updateClauses.join(', ')}` : null; const insertClause = `WHEN NOT MATCHED THEN INSERT (${columns}) VALUES (${sourceColumns})`; const statement = ` MERGE INTO ${table} AS t - USING (SELECT ${columnParamaters}) AS source (${columns}) + USING (VALUES (${columnParamaters})) AS source (${columns}) ON t.[id] = source.[id] - ${updateClause} + ${updateClause ? updateClause : ''} ${insertClause}; `; @@ -131,10 +132,10 @@ export const createMSSQLPersister = async (uri) => { const statement = ` MERGE INTO checkpoints AS t - USING (SELECT @user_id, @client_id, @checkpoint) AS source (user_id, client_id, checkpoint) + USING (VALUES (@user_id, @client_id, @checkpoint)) AS source (user_id, client_id, checkpoint) ON t.user_id = source.user_id AND t.client_id = source.client_id WHEN MATCHED THEN - UPDATE SET t.checkpoint = t.checkpoint + 1 + UPDATE SET checkpoint = t.checkpoint + 1 WHEN NOT MATCHED THEN INSERT (user_id, client_id, checkpoint) VALUES (source.user_id, source.client_id, source.checkpoint)