composite & deleted
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@ node_modules/
|
||||
.env
|
||||
certs/
|
||||
logs/
|
||||
capturedDataReference
|
||||
capturedDataReference
|
||||
decompiledReference
|
||||
357
package-lock.json
generated
357
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "0BSD",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^12.11.1",
|
||||
"chalk": "^5.6.2",
|
||||
"dotenv": "^17.4.2",
|
||||
"mssql": "^12.7.0",
|
||||
@@ -903,6 +904,29 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "12.11.1",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
|
||||
"integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz",
|
||||
@@ -972,6 +996,12 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
@@ -998,6 +1028,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/default-browser": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
|
||||
@@ -1068,6 +1122,15 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
@@ -1086,6 +1149,33 @@
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
@@ -1154,6 +1244,12 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
|
||||
@@ -1293,6 +1389,33 @@
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -1318,12 +1441,39 @@
|
||||
"node": ">=18.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/native-duplexpair": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz",
|
||||
"integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.94.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz",
|
||||
"integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
|
||||
@@ -1342,6 +1492,33 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
@@ -1351,6 +1528,31 @@
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
@@ -1466,6 +1668,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
@@ -1481,6 +1728,92 @@
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
|
||||
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/tarn": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.1.0.tgz",
|
||||
@@ -1517,12 +1850,36 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wsl-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
"test:client": "node test-client.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^12.11.1",
|
||||
"chalk": "^5.6.2",
|
||||
"dotenv": "^17.4.2",
|
||||
"mssql": "^12.7.0",
|
||||
|
||||
63
productSync.md
Normal file
63
productSync.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Product Sync Protocol
|
||||
|
||||
## The Sync Flow
|
||||
|
||||
1. **The POS client polls `/api/v1/init`** with its current `lastChangedProduct` cursor (e.g. `24599`). The server counts how many products have a row version greater than that cursor and returns the count.
|
||||
|
||||
2. **If `product_count > 0`**, the client fetches `/api/v1/product` with the same cursor and a `limit` (e.g. 20). The server returns the changed products ordered by row version ascending.
|
||||
|
||||
3. **The client updates its local cursor** to the highest `lastChanged` value it received in that batch (e.g. `24609`), so the next poll only fetches newer changes.
|
||||
|
||||
### Example from the log
|
||||
|
||||
```
|
||||
init with lastChangedProduct=24599 -> product_count=0 (no changes)
|
||||
init with lastChangedProduct=24599 -> product_count=1 (a new product appeared)
|
||||
product fetch lastChangedProduct=24599 -> returns product with lastChanged=24609
|
||||
init with lastChangedProduct=24609 -> product_count=0 (caught up)
|
||||
```
|
||||
|
||||
## What is `lastChangedProduct`?
|
||||
|
||||
It's the **MSSQL `bRowversion`** value -- an auto-incrementing binary counter that SQL Server bumps whenever a row is modified. The queries in `product-count.js` and `product-list.js` filter with:
|
||||
|
||||
```sql
|
||||
WHERE CONVERT(BIGINT, a.bRowversion) > @cursor
|
||||
```
|
||||
|
||||
The client simply persists the highest `lastChanged` value it received so it only gets new/changed rows on subsequent polls.
|
||||
|
||||
## How it's kept track
|
||||
|
||||
**Server side**: The cursor is stateless -- the client sends its last-known value every time. The server just runs the SQL query filtering rows above that value.
|
||||
|
||||
**Client side**: The POS client stores its `lastChangedProduct` (and `lastChangedCategory`, `lastChangedCustomerGroup`, etc.) persistently so it can resume syncing after a restart without re-downloading everything.
|
||||
|
||||
## Other entity types
|
||||
|
||||
The same pattern applies to:
|
||||
|
||||
- **Categories** (`lastChangedCategory` / `bRowversion` on `tKategorie`)
|
||||
- **Customer groups** (`lastChangedCustomerGroup` / `bRowversion` on `tKundenGruppe`)
|
||||
- **Composite products** (`lastChangedCompositeProduct` / `bRowversion` on `tArtikelBaugruppe`)
|
||||
- **Deleted entities** (`lastChangedDeletedEntity` / `bLastChanged` on `Pos.vDeletedEntity`)
|
||||
|
||||
## Deleted entity sync
|
||||
|
||||
When entities are deleted in JTL-Wawi, they appear in the `Pos.vDeletedEntity` view with their entity type:
|
||||
|
||||
| nEntityType | Entity |
|
||||
|---|---|
|
||||
| 1 | Artikel (Product) |
|
||||
| 2 | Kategorie (Category) |
|
||||
| 7 | Stückliste (Composite Product) |
|
||||
|
||||
The client polls `/v1/init` with `lastChangedDeletedEntity` cursor, and if `deletedEntity_count > 0`, fetches `/v1/deletedentity` to get the list of deleted entities. The response format is:
|
||||
|
||||
```json
|
||||
[
|
||||
{ "entityId": "123", "entityType": "1", "lastChanged": "208980" }
|
||||
]
|
||||
```
|
||||
|
||||
The client then removes these entities from its local database.
|
||||
10
server.js
10
server.js
@@ -36,6 +36,14 @@ function truncateUrl(url) {
|
||||
}
|
||||
|
||||
const lastInitState = { url: '', result: '', timestamp: 0 };
|
||||
let suppressedCount = 0;
|
||||
|
||||
setInterval(() => {
|
||||
if (suppressedCount > 0) {
|
||||
logger.info(`Suppressed ${suppressedCount} init log(s) in the last hour`);
|
||||
suppressedCount = 0;
|
||||
}
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
function shouldLogInit(url, statusCode, durationMs, responseBody) {
|
||||
if (statusCode !== 200) return true;
|
||||
@@ -45,6 +53,8 @@ function shouldLogInit(url, statusCode, durationMs, responseBody) {
|
||||
const timeSinceLast = now - lastInitState.timestamp;
|
||||
|
||||
if (timeSinceLast < 30_000 && url === lastInitState.url && responseBody === lastInitState.result) {
|
||||
lastInitState.timestamp = now;
|
||||
suppressedCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createImageHandler } from './image-handler.js';
|
||||
import { createImageHandler } from '../image-handler.js';
|
||||
|
||||
export const method = 'GET';
|
||||
export const path = '/v1/cimage';
|
||||
|
||||
14
src/endpoints/deleted-entity.js
Normal file
14
src/endpoints/deleted-entity.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { sendJson } from '../http.js';
|
||||
import { getDeletedEntityList } from '../queries/deleted-entity-list.js';
|
||||
|
||||
export const method = 'GET';
|
||||
export const path = '/v1/deletedentity';
|
||||
|
||||
export async function handle(_req, res, { url }) {
|
||||
const cursor = Number(url.searchParams.get('lastChangedDeletedEntity')) || 0;
|
||||
const limit = Number(url.searchParams.get('limit')) || 600;
|
||||
|
||||
const deletedEntities = await getDeletedEntityList({ cursor, limit });
|
||||
|
||||
return sendJson(res, 200, deletedEntities);
|
||||
}
|
||||
@@ -2,9 +2,11 @@ import * as category from './category.js';
|
||||
import * as cimage from './cimage.js';
|
||||
import * as client from './client.js';
|
||||
import * as customergroup from './customergroup.js';
|
||||
import * as deletedEntity from './deleted-entity.js';
|
||||
import * as init from './init.js';
|
||||
import * as order from './order.js';
|
||||
import * as pimage from './pimage.js';
|
||||
import * as product from './product.js';
|
||||
import * as productcomposite from './productcomposite.js';
|
||||
|
||||
export const endpoints = [client, init, category, product, pimage, cimage, customergroup, order];
|
||||
export const endpoints = [client, init, category, product, productcomposite, deletedEntity, pimage, cimage, customergroup, order];
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { sendJson } from '../http.js';
|
||||
import { getMaxExternalId } from '../order-log.js';
|
||||
import { getCategoryCount } from '../queries/category-count.js';
|
||||
import { getCompositeProductCount } from '../queries/composite-product-count.js';
|
||||
import { getCustomerGroupCount } from '../queries/customer-groups.js';
|
||||
import { getDeletedEntityCount } from '../queries/deleted-entity-count.js';
|
||||
import { getProductCount } from '../queries/product-count.js';
|
||||
|
||||
export const method = 'GET';
|
||||
@@ -11,11 +13,15 @@ export async function handle(_req, res, { url }) {
|
||||
const productCursor = Number(url.searchParams.get('lastChangedProduct')) || 0;
|
||||
const categoryCursor = Number(url.searchParams.get('lastChangedCategory')) || 0;
|
||||
const customerGroupCursor = Number(url.searchParams.get('lastChangedCustomerGroup')) || 0;
|
||||
const compositeProductCursor = Number(url.searchParams.get('lastChangedCompositeProduct')) || 0;
|
||||
const deletedEntityCursor = Number(url.searchParams.get('lastChangedDeletedEntity')) || 0;
|
||||
|
||||
const [productCount, categoryCount, customerGroupCount] = await Promise.all([
|
||||
const [productCount, categoryCount, customerGroupCount, compositeProductCount, deletedEntityCount] = await Promise.all([
|
||||
getProductCount({ cursor: productCursor }),
|
||||
getCategoryCount({ cursor: categoryCursor }),
|
||||
getCustomerGroupCount({ cursor: customerGroupCursor }),
|
||||
getCompositeProductCount({ cursor: compositeProductCursor }),
|
||||
getDeletedEntityCount({ cursor: deletedEntityCursor }),
|
||||
]);
|
||||
|
||||
return sendJson(res, 200, {
|
||||
@@ -24,10 +30,10 @@ export async function handle(_req, res, { url }) {
|
||||
category_count: String(categoryCount),
|
||||
customer_count: '0',
|
||||
customerGroup_count: String(customerGroupCount),
|
||||
compositeProduct_count: '0',
|
||||
compositeProduct_count: String(compositeProductCount),
|
||||
configurationGroup_count: '0',
|
||||
configurationItem_count: '0',
|
||||
deletedEntity_count: '0',
|
||||
deletedEntity_count: String(deletedEntityCount),
|
||||
max_orderId_count: getMaxExternalId(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createImageHandler } from './image-handler.js';
|
||||
import { createImageHandler } from '../image-handler.js';
|
||||
|
||||
export const method = 'GET';
|
||||
export const path = '/v1/pimage';
|
||||
|
||||
14
src/endpoints/productcomposite.js
Normal file
14
src/endpoints/productcomposite.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { sendJson } from '../http.js';
|
||||
import { getCompositeProductList } from '../queries/composite-product-list.js';
|
||||
|
||||
export const method = 'GET';
|
||||
export const path = '/v1/productcomposite';
|
||||
|
||||
export async function handle(_req, res, { url }) {
|
||||
const cursor = Number(url.searchParams.get('lastChangedCompositeProduct')) || 0;
|
||||
const limit = Number(url.searchParams.get('limit')) || 100;
|
||||
|
||||
const composites = await getCompositeProductList({ cursor, limit });
|
||||
|
||||
return sendJson(res, 200, composites);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sendBinary, sendJson } from '../http.js';
|
||||
import { resizeImage } from '../image-resize.js';
|
||||
import { getImageByHash } from '../queries/image.js';
|
||||
import { sendBinary, sendJson } from './http.js';
|
||||
import { resizeImage } from './image-resize.js';
|
||||
import { getImageByHash } from './queries/image.js';
|
||||
|
||||
export function createImageHandler() {
|
||||
return async function handle(_req, res, { url }) {
|
||||
18
src/queries/composite-product-count.js
Normal file
18
src/queries/composite-product-count.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import sql from 'mssql';
|
||||
import { getPool } from '../db.js';
|
||||
|
||||
const COMPOSITE_PRODUCT_COUNT_SQL = `
|
||||
SELECT COUNT(DISTINCT a.kArtikel) AS CompositeProductCount
|
||||
FROM dbo.tArtikel a
|
||||
INNER JOIN dbo.tStueckliste s ON s.kStueckliste = a.kStueckliste
|
||||
WHERE a.kStueckliste <> 0
|
||||
AND CONVERT(BIGINT, a.bRowversion) > @cursor;
|
||||
`;
|
||||
|
||||
export async function getCompositeProductCount({ cursor = 0 } = {}) {
|
||||
const result = await getPool()
|
||||
.request()
|
||||
.input('cursor', sql.BigInt, cursor)
|
||||
.query(COMPOSITE_PRODUCT_COUNT_SQL);
|
||||
return result.recordset[0]?.CompositeProductCount ?? 0;
|
||||
}
|
||||
30
src/queries/composite-product-list.js
Normal file
30
src/queries/composite-product-list.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import sql from 'mssql';
|
||||
import { getPool } from '../db.js';
|
||||
|
||||
const COMPOSITE_PRODUCT_LIST_SQL = `
|
||||
SELECT TOP (@limit)
|
||||
s.kVaterArtikel AS productId,
|
||||
s.kArtikel AS productIdComponent,
|
||||
CONVERT(VARCHAR(20), s.fAnzahl, 2) AS quantity,
|
||||
CONVERT(BIGINT, a.bRowversion) AS lastChanged
|
||||
FROM dbo.tStueckliste s
|
||||
INNER JOIN dbo.tArtikel a ON a.kArtikel = s.kVaterArtikel
|
||||
WHERE a.kStueckliste <> 0
|
||||
AND CONVERT(BIGINT, a.bRowversion) > @cursor
|
||||
ORDER BY lastChanged ASC;
|
||||
`;
|
||||
|
||||
export async function getCompositeProductList({ cursor = 0, limit = 100 } = {}) {
|
||||
const result = await getPool()
|
||||
.request()
|
||||
.input('cursor', sql.BigInt, cursor)
|
||||
.input('limit', sql.Int, limit)
|
||||
.query(COMPOSITE_PRODUCT_LIST_SQL);
|
||||
|
||||
return result.recordset.map((row) => ({
|
||||
productId: String(row.productId),
|
||||
productIdComponent: String(row.productIdComponent),
|
||||
quantity: row.quantity,
|
||||
lastChanged: String(row.lastChanged),
|
||||
}));
|
||||
}
|
||||
16
src/queries/deleted-entity-count.js
Normal file
16
src/queries/deleted-entity-count.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import sql from 'mssql';
|
||||
import { getPool } from '../db.js';
|
||||
|
||||
const DELETED_ENTITY_COUNT_SQL = `
|
||||
SELECT COUNT(*) AS DeletedEntityCount
|
||||
FROM Pos.vDeletedEntity
|
||||
WHERE CONVERT(BIGINT, vDeletedEntity.bLastChanged) > @cursor;
|
||||
`;
|
||||
|
||||
export async function getDeletedEntityCount({ cursor = 0 } = {}) {
|
||||
const result = await getPool()
|
||||
.request()
|
||||
.input('cursor', sql.BigInt, cursor)
|
||||
.query(DELETED_ENTITY_COUNT_SQL);
|
||||
return result.recordset[0]?.DeletedEntityCount ?? 0;
|
||||
}
|
||||
26
src/queries/deleted-entity-list.js
Normal file
26
src/queries/deleted-entity-list.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import sql from 'mssql';
|
||||
import { getPool } from '../db.js';
|
||||
|
||||
const DELETED_ENTITY_LIST_SQL = `
|
||||
SELECT TOP (@limit)
|
||||
vDeletedEntity.kEntityId,
|
||||
vDeletedEntity.nEntityType,
|
||||
CONVERT(BIGINT, vDeletedEntity.bLastChanged) AS lastChanged
|
||||
FROM Pos.vDeletedEntity
|
||||
WHERE CONVERT(BIGINT, vDeletedEntity.bLastChanged) > @cursor
|
||||
ORDER BY lastChanged ASC;
|
||||
`;
|
||||
|
||||
export async function getDeletedEntityList({ cursor = 0, limit = 600 } = {}) {
|
||||
const result = await getPool()
|
||||
.request()
|
||||
.input('cursor', sql.BigInt, cursor)
|
||||
.input('limit', sql.Int, limit)
|
||||
.query(DELETED_ENTITY_LIST_SQL);
|
||||
|
||||
return result.recordset.map((row) => ({
|
||||
entityId: String(row.kEntityId),
|
||||
entityType: String(row.nEntityType),
|
||||
lastChanged: String(row.lastChanged),
|
||||
}));
|
||||
}
|
||||
@@ -38,6 +38,7 @@ SELECT TOP (@limit)
|
||||
) AS categoryIds,
|
||||
a.nIstVater AS isParent,
|
||||
a.kVaterArtikel AS parentArticleId,
|
||||
CASE WHEN a.kStueckliste <> 0 THEN '1' ELSE '0' END AS isCompositeProduct,
|
||||
(
|
||||
SELECT TOP 1 pv.cVariantName
|
||||
FROM Pos.vProductVariant pv
|
||||
@@ -144,6 +145,7 @@ export async function getProductList({ cursor = 0, limit = 20 } = {}) {
|
||||
is_parent: product.isParent ? '1' : '0',
|
||||
parent: product.parentArticleId > 0 ? String(product.parentArticleId) : '0',
|
||||
variants: product.variantName ?? '',
|
||||
isCompositeProduct: product.isCompositeProduct,
|
||||
attributes: articleAttributes?.attributes ?? [],
|
||||
...(articleAttributes?.deposit ?? {}),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user