This commit is contained in:
seb
2026-10-23 22:22:32 +02:00
parent 82ab27ef2b
commit c172f2f4b1
6 changed files with 285 additions and 91 deletions

View File

@@ -209,22 +209,26 @@ Returns the change history of an article by its SKU.
| --- | --- | --- | | --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. | | `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. | | `sku` | `string` | The article SKU. |
| `[opts]` | `object` | Optional options. | | `[opts]` | `object` | Optional pagination and filter options. |
| `[opts.limit]` | `number` | Maximum number of entries to return (newest first). | | `[opts.offset=0]` | `number` | Number of entries to skip. |
| `[opts.limit=50]` | `number` | Number of entries to return. |
| `[opts.search]` | `string` | Optional search filter for history text. |
| `[opts.seekMonths]` | `number` | Negative integer cutoff months ago. |
| `[opts.paged]` | `boolean` | Explicitly return paged result object. |
| `[tx]` | `object` | Optional transaction. | | `[tx]` | `object` | Optional transaction. |
**Returns** **Returns**
`Promise<Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>` — The article history entries. `Promise<{history: Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>` — The article history entries.
**Example** **Example**
```js ```js
const result = await store.article.getHistoryBySku(42, 'SKU-1', { offset: 0, limit: 50, search: 'Tee' });
// => { history: [...], total, unfilteredTotal, offset, limit }
const history = await store.article.getHistoryBySku(42, 'SKU-1'); const history = await store.article.getHistoryBySku(42, 'SKU-1');
// => [{ id, companyuserId, date, text, meta }] // => [{ id, companyuserId, date, text, meta }]
const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 });
// => up to 5 most recent entries
``` ```
## `article.importRows` ## `article.importRows`

2
API.md
View File

@@ -112,7 +112,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- `delete(googleAuthId: number, sku: string, optionsOrTx?: object|*, maybeTx?: object): Promise<void>` - `delete(googleAuthId: number, sku: string, optionsOrTx?: object|*, maybeTx?: object): Promise<void>`
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>` - `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>`
- `getBySku(googleAuthId: number): Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null, incoming: Array<{date: string, amount: number}>}>, warehouses: Array<{id: number, name: string, sort: number, stock: number}>, undeliveredOrders: Array<{orderId: number, orderNumber: string, date: string, customerName: string|null, amount: number}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>` - `getBySku(googleAuthId: number): Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null, incoming: Array<{date: string, amount: number}>}>, warehouses: Array<{id: number, name: string, sort: number, stock: number}>, undeliveredOrders: Array<{orderId: number, orderNumber: string, date: string, customerName: string|null, amount: number}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>`
- `getHistoryBySku(googleAuthId: number, sku: string, opts?: {limit?: number}, tx?: object): Promise<Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>` - `getHistoryBySku(googleAuthId: number, sku: string, opts?: {offset?: number, limit?: number, search?: string, seekMonths?: number, paged?: boolean}, tx?: object): Promise<{history: Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>`
- `importRows(input: {googleAuthId: number, rows: Array<object>}, tx?: object): Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>` - `importRows(input: {googleAuthId: number, rows: Array<object>}, tx?: object): Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>`
- `listChildren(googleAuthId: number, parentSku: string, tx?: object): Promise<Array<{id: number, sku: string, parentsort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>` - `listChildren(googleAuthId: number, parentSku: string, tx?: object): Promise<Array<{id: number, sku: string, parentsort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>`
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>` - `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`

162
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"dotenv": "^18.0.8", "dotenv": "^18.0.8",
"pdfconvert-wasm": "^0.3.0", "pdfconvert-wasm": "^0.3.0",
"sharp": "^0.35.5", "sharp": "^0.35.5",
"skelehash-wasm": "^0.1.0", "skelehash-wasm": "file:../skelehash-wasm",
"thumbhash": "^0.1.1" "thumbhash": "^0.1.1"
}, },
"devDependencies": { "devDependencies": {
@@ -32,6 +32,13 @@
} }
} }
}, },
"../skelehash-wasm": {
"version": "0.1.0",
"license": "MIT",
"devDependencies": {
"sharp": "^0.35.5"
}
},
"node_modules/@azure-rest/core-client": { "node_modules/@azure-rest/core-client": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.9.0.tgz", "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.9.0.tgz",
@@ -184,9 +191,9 @@
} }
}, },
"node_modules/@azure/identity": { "node_modules/@azure/identity": {
"version": "4.13.3", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.3.tgz", "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.14.0.tgz",
"integrity": "sha512-zGQPtqvXPgSA8yfV2CkIQ1qirqk0p9AIVpC5uEkdXQYcKl07QHvyaGYRnZOk0AsQUmxNb4wfkcwY5di8Z5xa9A==", "integrity": "sha512-8utCLdTZn14CiAe0HW4JbtzUMsc0mdDwnjUvStFMrR4IxQP8S2eL9zVAdplpfORVJGYmidZElpeXRYjJQ4UyUw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -200,7 +207,7 @@
"@azure/logger": "^1.0.0", "@azure/logger": "^1.0.0",
"@azure/msal-browser": "^5.5.0", "@azure/msal-browser": "^5.5.0",
"@azure/msal-node": "^6.0.0", "@azure/msal-node": "^6.0.0",
"open": "^10.1.0", "open": "^11.0.0",
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
"engines": { "engines": {
@@ -265,22 +272,22 @@
} }
}, },
"node_modules/@azure/msal-browser": { "node_modules/@azure/msal-browser": {
"version": "5.22.0", "version": "5.26.0",
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.22.0.tgz", "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.26.0.tgz",
"integrity": "sha512-5kgu9xeEKgGc2JeidxAtU15NJTqiH/CMCRRQAJ4Rac56kB7KVg91vbNmn+z3RO1vNomPN69UvjKG9h1Pghx6dQ==", "integrity": "sha512-GYKRquzDZmZggCdStWy2bNJKnPui1upssSuznJgP+/h6Mb0ldkUkaGvaY6uSGGmZOm0CGiBQ3b5BiGy43BpXSg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@azure/msal-common": "16.14.1" "@azure/msal-common": "16.14.3"
}, },
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/@azure/msal-common": { "node_modules/@azure/msal-common": {
"version": "16.14.1", "version": "16.14.3",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.1.tgz", "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.3.tgz",
"integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==", "integrity": "sha512-tz3GvMrR9V/plb2lDdW8qaO8CPpfZevdExBKjltzzbAtyhK0eB2UIvji1aU+gkyHj3fxQ2ie+LNHyMXdmo8wOg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -301,6 +308,16 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@azure/msal-node/node_modules/@azure/msal-common": {
"version": "16.14.1",
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.1.tgz",
"integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@clack/core": { "node_modules/@clack/core": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz",
@@ -340,9 +357,9 @@
} }
}, },
"node_modules/@hpcc-js/wasm-graphviz": { "node_modules/@hpcc-js/wasm-graphviz": {
"version": "1.29.2", "version": "1.30.0",
"resolved": "https://registry.npmjs.org/@hpcc-js/wasm-graphviz/-/wasm-graphviz-1.29.2.tgz", "resolved": "https://registry.npmjs.org/@hpcc-js/wasm-graphviz/-/wasm-graphviz-1.30.0.tgz",
"integrity": "sha512-AkArBAZERT5OxH52/+kvCHB07cZ6ZNd9GnOvjw+S880bR88D6i27XfK6xj4dBhUDxNA2QPPMdgEcky3x4TJ3KA==", "integrity": "sha512-T20IKosOZuex2qV049fQokVJy2MWv8w9Md5V/4mRKCXDf49fBCR50VD1yBlPYHaQlHPsFGRdqXN//r59vZliWQ==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@img/colour": { "node_modules/@img/colour": {
@@ -894,9 +911,9 @@
} }
}, },
"node_modules/@js-joda/core": { "node_modules/@js-joda/core": {
"version": "6.1.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.3.0.tgz",
"integrity": "sha512-H8NTMRDJqad/leyv/D/A3kSOsf5/58Ydj4DJGDyaCWk9OU/zuZOLhndVffJgQjsgrn5GC0znHMHie7TfvPPG4w==", "integrity": "sha512-mdmjc6lJkTCORsHlMxBT9XM1yLqgtsNZmv/Hwme2v/HIb15D2XcmklZKTnynqBgsSizA9SkFvzpuksfIXhDV/w==",
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
@@ -908,9 +925,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "26.6.2", "version": "26.6.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz",
"integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==", "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -918,9 +935,9 @@
} }
}, },
"node_modules/@types/readable-stream": { "node_modules/@types/readable-stream": {
"version": "4.0.24", "version": "4.0.25",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.24.tgz", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.25.tgz",
"integrity": "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==", "integrity": "sha512-7GsxL8Wd5X3jh73/qgW7WCnbBvGSdD8U3E4Jiuus3/8I+PN20DtlkJtwFhr/sImXfDDQ2Z03i/9wNiIF3TVSsw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1045,9 +1062,9 @@
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/bundle-name": { "node_modules/bundle-name": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz",
"integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1153,9 +1170,9 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "18.0.8", "version": "18.0.9",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.8.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.9.tgz",
"integrity": "sha512-2KDeoFoK/zRtcnbunCDlLG5VWVMcyqeQc6iK65o231nfNAcuGrKoqUQfJFUtBI+QlShk5HKIHxwebBtUl3rEfA==", "integrity": "sha512-3fVOW4cB02R066PUSwn8O4TJyz9NckQl7F81blIbaq9jKBm2tSmOKky7ZHIXzGvkVPXCSn6jkpziBPZdGFs8Tw==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"bin": { "bin": {
"dotenv": "dist/index.cjs" "dotenv": "dist/index.cjs"
@@ -1310,6 +1327,19 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-in-ssh": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
"integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-inside-container": { "node_modules/is-inside-container": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@@ -1455,9 +1485,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/mssql": { "node_modules/mssql": {
"version": "12.7.9", "version": "12.7.11",
"resolved": "https://registry.npmjs.org/mssql/-/mssql-12.7.9.tgz", "resolved": "https://registry.npmjs.org/mssql/-/mssql-12.7.11.tgz",
"integrity": "sha512-ryuy8HyUpsUgg16mkjNumuGK0/kzZ9TNkKSm/lfNcb8v5RII/U4ErOquH/kAvUqXbap9BWV0EnN5JxdewGrx7g==", "integrity": "sha512-O24UQA++JLvMUy5T6xL73kCHP4YjLMS6DBxKFeiWyc9kZW2mrVAZ6uqly2d3hGtBTXHRFRImfM+3EdCZVE5bWQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1492,19 +1522,21 @@
} }
}, },
"node_modules/open": { "node_modules/open": {
"version": "10.2.0", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz",
"integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"default-browser": "^5.2.1", "default-browser": "^5.5.1",
"define-lazy-prop": "^3.0.0", "define-lazy-prop": "^3.0.0",
"is-in-ssh": "^1.0.0",
"is-inside-container": "^1.0.0", "is-inside-container": "^1.0.0",
"wsl-utils": "^0.1.0" "powershell-utils": "^0.2.1",
"wsl-utils": "^1.0.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=20"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
@@ -1516,6 +1548,19 @@
"integrity": "sha512-FbEk+Grg0UST4kOPuN7OFfVsut1FvDOJNPS3pt4rzCS8VlFsFzD7ptg7YtFZjEOKgJ4MgqlfCgjK4RYaRBpT0Q==", "integrity": "sha512-FbEk+Grg0UST4kOPuN7OFfVsut1FvDOJNPS3pt4rzCS8VlFsFzD7ptg7YtFZjEOKgJ4MgqlfCgjK4RYaRBpT0Q==",
"license": "GPLv3" "license": "GPLv3"
}, },
"node_modules/powershell-utils": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz",
"integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/process": { "node_modules/process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -1527,9 +1572,9 @@
} }
}, },
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "4.7.0", "version": "4.8.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz",
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1652,17 +1697,8 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/skelehash-wasm": { "node_modules/skelehash-wasm": {
"version": "0.1.0", "resolved": "../skelehash-wasm",
"resolved": "https://registry.npmjs.org/skelehash-wasm/-/skelehash-wasm-0.1.0.tgz", "link": true
"integrity": "sha512-QdxF9YsJvKlT93O70dXFaWnsKVX7KDD0RgYf9cwgOT6kGnSMOdsO0rsdge+5Tg+vv6BjivbJ/2o5P1KcQsavEw==",
"license": "MIT"
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"dev": true,
"license": "BSD-3-Clause"
}, },
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.3.0", "version": "1.3.0",
@@ -1685,9 +1721,9 @@
} }
}, },
"node_modules/tedious": { "node_modules/tedious": {
"version": "20.0.0", "version": "20.3.8",
"resolved": "https://registry.npmjs.org/tedious/-/tedious-20.0.0.tgz", "resolved": "https://registry.npmjs.org/tedious/-/tedious-20.3.8.tgz",
"integrity": "sha512-bTR0aou0Ghucf0ytvZUJjnKHGKDV8tT57jPYtEkSpfTWFe++4uR1wxJLQ4mh5wlSvAYXzmgBxbI0vaE56qigXw==", "integrity": "sha512-ZrJiwyGn340Sa0V0g37Xqj5bvmJ1riYwvEtg7zdm/lpw70f2ZNIQ2fvHmntU5fEOorWDa48Jo1oKYAuUP+rh7Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1699,8 +1735,7 @@
"bl": "^6.1.4", "bl": "^6.1.4",
"iconv-lite": "^0.7.0", "iconv-lite": "^0.7.0",
"js-md4": "^0.3.2", "js-md4": "^0.3.2",
"native-duplexpair": "^1.0.0", "native-duplexpair": "^1.0.0"
"sprintf-js": "^1.1.3"
}, },
"engines": { "engines": {
"node": ">=22" "node": ">=22"
@@ -1727,16 +1762,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/wsl-utils": { "node_modules/wsl-utils": {
"version": "0.1.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz",
"integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "integrity": "sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-wsl": "^3.1.0" "is-wsl": "^3.1.0",
"powershell-utils": "^0.2.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=20"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"

View File

@@ -31,7 +31,7 @@
"dotenv": "^18.0.8", "dotenv": "^18.0.8",
"pdfconvert-wasm": "^0.3.0", "pdfconvert-wasm": "^0.3.0",
"sharp": "^0.35.5", "sharp": "^0.35.5",
"skelehash-wasm": "^0.1.0", "skelehash-wasm": "file:../skelehash-wasm",
"thumbhash": "^0.1.1" "thumbhash": "^0.1.1"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -1,37 +1,64 @@
import { LIST_BY_ARTICLE_ID as MSSQL_QUERY, LIST_BY_ARTICLE_ID_LIMITED as MSSQL_QUERY_LIMITED } from '../../providers/mssql/queries/articlehistory.js';
import { LIST_BY_ARTICLE_ID as SQLITE_QUERY, LIST_BY_ARTICLE_ID_LIMITED as SQLITE_QUERY_LIMITED } from '../../providers/sqlite3/queries/articlehistory.js';
import resolveArticleBySku from './resolveArticleBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function getHistoryBySku(provider) { export default function getHistoryBySku(provider) {
const resolve = resolveArticleBySku(provider); const resolve = resolveArticleBySku(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const sql = isSqlite ? SQLITE_QUERY : MSSQL_QUERY;
const sqlLimited = isSqlite ? SQLITE_QUERY_LIMITED : MSSQL_QUERY_LIMITED;
/** /**
* Returns the change history of an article by its SKU. * Returns the change history of an article by its SKU.
* *
* @param {number} googleAuthId - The id of the Google auth user. * @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU. * @param {string} sku - The article SKU.
* @param {object} [opts] - Optional options. * @param {object} [opts] - Optional pagination and filter options.
* @param {number} [opts.limit] - Maximum number of entries to return (newest first). * @param {number} [opts.offset=0] - Number of entries to skip.
* @param {number} [opts.limit=50] - Number of entries to return.
* @param {string} [opts.search] - Optional search filter for history text.
* @param {number} [opts.seekMonths] - Negative integer cutoff months ago.
* @param {boolean} [opts.paged] - Explicitly return paged result object.
* @param {object} [tx] - Optional transaction. * @param {object} [tx] - Optional transaction.
* @returns {Promise<Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>} The article history entries. * @returns {Promise<{history: Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>} The article history entries.
* *
* @example * @example
* const result = await store.article.getHistoryBySku(42, 'SKU-1', { offset: 0, limit: 50, search: 'Tee' });
* // => { history: [...], total, unfilteredTotal, offset, limit }
*
* const history = await store.article.getHistoryBySku(42, 'SKU-1'); * const history = await store.article.getHistoryBySku(42, 'SKU-1');
* // => [{ id, companyuserId, date, text, meta }] * // => [{ id, companyuserId, date, text, meta }]
*
* const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 });
* // => up to 5 most recent entries
*/ */
return async (googleAuthId, sku, opts, tx) => { return async (googleAuthId, sku, opts, tx) => {
// Support legacy call signature: getHistoryBySku(googleAuthId, sku, tx) let offset = 0;
let limit = null; let limit = 50;
let isPaged = false;
let resolvedTx = tx; let resolvedTx = tx;
const hasFilter = opts != null && typeof opts === 'object' && !opts._isTx && (
opts.offset !== undefined ||
opts.paged === true ||
(typeof opts.search === 'string' && opts.search.trim() !== '') ||
opts.seekMonths !== undefined
);
if (opts != null && typeof opts === 'object' && !opts._isTx) { if (opts != null && typeof opts === 'object' && !opts._isTx) {
limit = opts.limit != null ? Number(opts.limit) : null; if (hasFilter || opts.paged === true) {
isPaged = true;
if (opts.offset !== undefined) {
const parsedOffset = Number(opts.offset);
if (!Number.isInteger(parsedOffset) || parsedOffset < 0) {
throw new Error('Invalid offset');
}
offset = parsedOffset;
}
if (opts.limit !== undefined) {
const parsedLimit = Number(opts.limit);
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
throw new Error('Invalid limit');
}
limit = parsedLimit;
}
} else if (opts.limit != null) {
limit = Number(opts.limit);
}
} else if (opts != null) { } else if (opts != null) {
resolvedTx = opts; resolvedTx = opts;
} }
@@ -39,17 +66,124 @@ export default function getHistoryBySku(provider) {
const runner = makeRunner(provider, resolvedTx); const runner = makeRunner(provider, resolvedTx);
const { articleId } = await resolve(googleAuthId, sku, runner); const { articleId } = await resolve(googleAuthId, sku, runner);
let result; const conditions = ['ah.article_id = @articleId'];
if (limit != null && Number.isInteger(limit) && limit > 0) { const params = {
result = await runner.query(sqlLimited, { articleId, limit }); articleId,
} else { };
result = await runner.query(sql, { articleId });
if (opts?.seekMonths !== undefined) {
if (typeof opts.seekMonths !== 'number' || !Number.isInteger(opts.seekMonths)) {
throw new Error('Invalid seekMonths');
}
const d = new Date();
d.setMonth(d.getMonth() - Math.abs(opts.seekMonths));
params.dateTo = d.toISOString();
conditions.push(`ah.${isSqlite ? '"date"' : '[date]'} <= @dateTo`);
} }
if (typeof opts?.search === 'string' && opts.search.trim() !== '') {
params.searchPattern = `%${opts.search.trim()}%`;
conditions.push(`ah.${isSqlite ? '"text"' : '[text]'} LIKE @searchPattern`);
}
const whereClause = conditions.join('\n AND ');
const sqlCount = `
SELECT COUNT(*) AS total
FROM articlehistory ah
WHERE ${whereClause}
`;
const sqlCountBase = `
SELECT COUNT(*) AS total
FROM articlehistory ah
WHERE ah.article_id = @articleId
`;
let sqlList;
if (isSqlite) {
sqlList = `
SELECT
ah.id,
ah.companyuser_id AS companyuserId,
ah."date",
ah."text",
ah.meta
FROM articlehistory ah
WHERE ${whereClause}
ORDER BY ah."date" DESC, ah.id DESC
${isPaged ? 'LIMIT @limit OFFSET @offset' : (limit != null && limit > 0 ? 'LIMIT @limit' : '')}
`;
} else {
sqlList = `
SELECT
${!isPaged && limit != null && limit > 0 ? 'TOP (@limit)' : ''}
ah.id,
ah.companyuser_id AS companyuserId,
ah.[date],
ah.[text],
ah.meta
FROM articlehistory ah
WHERE ${whereClause}
ORDER BY ah.[date] DESC, ah.id DESC
${isPaged ? 'OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY' : ''}
`;
}
if (isPaged) {
params.offset = offset;
params.limit = limit;
const hasUserFilters = (
opts?.seekMonths !== undefined ||
(typeof opts?.search === 'string' && opts.search.trim() !== '')
);
const [countResult, listResult, unfilteredCountResult] = await Promise.all([
runner.query(sqlCount, params),
runner.query(sqlList, params),
hasUserFilters ? runner.query(sqlCountBase, { articleId }) : null,
]);
const total = Number(countResult.rows[0]?.total ?? 0);
const unfilteredTotal = hasUserFilters
? Number(unfilteredCountResult?.rows[0]?.total ?? 0)
: total;
return {
history: listResult.rows.map((row) => ({
id: row.id,
companyuserId: row.companyuserId ?? null,
date:
row.date instanceof Date
? row.date.toISOString()
: row.date
? new Date(row.date).toISOString()
: null,
text: row.text,
meta: row.meta ?? null,
})),
total,
unfilteredTotal,
offset,
limit,
};
}
if (limit != null && limit > 0) {
params.limit = limit;
}
const result = await runner.query(sqlList, params);
return result.rows.map((row) => ({ return result.rows.map((row) => ({
id: row.id, id: row.id,
companyuserId: row.companyuserId ?? null, companyuserId: row.companyuserId ?? null,
date: row.date, date:
row.date instanceof Date
? row.date.toISOString()
: row.date
? new Date(row.date).toISOString()
: null,
text: row.text, text: row.text,
meta: row.meta ?? null, meta: row.meta ?? null,
})); }));

View File

@@ -307,6 +307,26 @@ describe('store.article', () => {
const limited = await store.article.getHistoryBySku(gid, art.sku, { limit: 1 }); const limited = await store.article.getHistoryBySku(gid, art.sku, { limit: 1 });
assert.equal(limited.length, 1); assert.equal(limited.length, 1);
}); });
it('supports pagination with total and unfilteredTotal', async () => {
const art = await createArticle(store, tenant, { name: 'PagedH0' });
await store.article.updateBySku(gid, art.sku, { name: 'PagedH1' });
await store.article.updateBySku(gid, art.sku, { name: 'PagedH2' });
const paged = await store.article.getHistoryBySku(gid, art.sku, { offset: 0, limit: 2 });
assert.equal(typeof paged, 'object');
assert.ok(Array.isArray(paged.history));
assert.equal(paged.history.length, 2);
assert.equal(paged.offset, 0);
assert.equal(paged.limit, 2);
assert.equal(paged.total, 3);
assert.equal(paged.unfilteredTotal, 3);
const filtered = await store.article.getHistoryBySku(gid, art.sku, { offset: 0, limit: 50, search: 'PagedH2' });
assert.equal(filtered.total, 1);
assert.equal(filtered.unfilteredTotal, 3);
assert.equal(filtered.history.length, 1);
});
}); });
describe('listSearchIndex / listExport', () => { describe('listSearchIndex / listExport', () => {