Rewrite store JSDoc, split quote/order into modules, and generate typed API docs

- Rewrite JSDoc across all store methods to document the actual async
  methods (real params, returns, throws) instead of the internal wrappers
- Split quote/order into individual module files backed by shared
  manifestDoc helpers; remove the createStore factory and thin wrappers
- Deduplicate manifest line helpers into src/store/manifestLines.js
- Extend generate-api-doc.js to render parameter/return/throws types and
  emit per-namespace API.<module>.md files with an API.md index
- Regenerate all API docs
This commit is contained in:
seb
2026-08-07 06:23:17 +02:00
parent 396389457f
commit aefd97a171
127 changed files with 3956 additions and 1684 deletions

View File

@@ -5,20 +5,21 @@ import newFullHash from '../../util/newFullHash.js';
import request from '../request.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Adds an image to an article by its SKU, processing it into AVIF variants.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that adds an article image.
*
* @example
* const fn = addImage(pool);
* const result = await fn(42, 'SKU-1', imageBuffer);
* // => { picId, sort, full: { w, h, size }, thumb: { w, h }, fullHash, thumbHash }
*/
export default function addImage(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Adds an image to an article by its SKU, processing it into AVIF variants.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Buffer} image - The source image buffer.
* @returns {Promise<{picId: number, sort: number, full: {w: number, h: number, size: number}, thumb: {w: number, h: number}, fullHash: string, thumbHash: string}>} The added image info.
*
* @example
* const result = await store.article.addImage(42, 'SKU-1', imageBuffer);
* // => { picId, sort, full: { w, h, size }, thumb: { w, h }, fullHash, thumbHash }
*/
return async (googleAuthId, sku, image) => {
const { articleId } = await resolve(googleAuthId, sku);

View File

@@ -7,23 +7,25 @@ import nextSku from '../numbers/nextSku.js';
import request from '../request.js';
import slugify from '../slugify.js';
/**
* Creates a new article under a given parent category, allocating a SKU and
* a default unit.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates an article.
*
* @example
* const fn = createUnderParent(pool);
* const article = await fn({ googleAuthId: 42, parentId: 5, name: 'Cola' });
* // => { id, parentId, name, sku }
*/
export default function createUnderParent(pool) {
const access = resolveCategoryAccess(pool);
const ensureUnit = ensureDefaultUnit(pool);
const allocateSku = nextSku(pool);
/**
* Creates a new article under a given parent category, allocating a SKU and
* a default unit.
*
* @param {object} input - The article creation input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.parentId - The id of the parent category.
* @param {string} input.name - The article name.
* @returns {Promise<{id: number, parentId: number, name: string, sku: string}>} The created article.
*
* @example
* const article = await store.article.createUnderParent({ googleAuthId: 42, parentId: 5, name: 'Cola' });
* // => { id, parentId, name, sku }
*/
return async ({ googleAuthId, parentId, name }) => {
const { companyId, languageId } = await access(googleAuthId, parentId);

View File

@@ -11,19 +11,20 @@ import {
import resolveArticleBySku from './resolveArticleBySku.js';
import request from '../request.js';
/**
* Deletes an article by its SKU, refusing when it still has children.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an article.
*
* @example
* const fn = del(pool);
* await fn(42, 'SKU-1');
*/
export default function del(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Deletes an article by its SKU, refusing when it still has children.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @returns {Promise<void>}
* @throws {Error} If the article has children or is not found.
*
* @example
* await store.article.delete(42, 'SKU-1');
*/
return async (googleAuthId, sku) => {
const { articleId, languageId } = await resolve(googleAuthId, sku);

View File

@@ -1,19 +1,21 @@
import { DELETE_BY_ARTICLE_AND_HASH } from '../../sql/pic.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Deletes an image from an article by its SKU and full image hash.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an article image.
*
* @example
* const fn = deleteImage(pool);
* await fn(42, 'SKU-1', 'abc123');
*/
export default function deleteImage(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Deletes an image from an article by its SKU and full image hash.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {string} fullHash - The full image hash to delete.
* @returns {Promise<void>}
* @throws {Error} If the image is not found.
*
* @example
* await store.article.deleteImage(42, 'SKU-1', 'abc123');
*/
return async (googleAuthId, sku, fullHash) => {
const { articleId } = await resolve(googleAuthId, sku);

View File

@@ -6,19 +6,20 @@ function parseJsonArray(raw) {
return Array.isArray(arr) ? arr : [];
}
/**
* Returns the full detail of an article by its SKU, including unit, brand,
* categories, photos, EANs and prices.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches an article by SKU.
*
* @example
* const fn = getBySku(pool);
* const article = await fn(42, 'SKU-1');
* // => { article: { id, sku, name, unit, brand, categoryIds, photos, eans, prices, ... } }
*/
export default function getBySku(pool) {
/**
* Returns the full detail of an article by its SKU, including unit, brand,
* categories, photos, EANs and prices.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @returns {Promise<{article: object}>} The full article detail.
* @throws {Error} If the article is not found or the user is not authorized.
*
* @example
* const article = await store.article.getBySku(42, 'SKU-1');
* // => { article: { id, sku, name, unit, brand, categoryIds, photos, eans, prices, ... } }
*/
return async (googleAuthId, sku) => {
const t0 = performance.now();
const result = await pool

View File

@@ -1,20 +1,20 @@
import { LIST_BY_ARTICLE_ID } from '../../sql/articlehistory.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Returns the change history of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches the article history.
*
* @example
* const fn = getHistoryBySku(pool);
* const history = await fn(42, 'SKU-1');
* // => [{ id, companyuserId, date, text }]
*/
export default function getHistoryBySku(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Returns the change history of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @returns {Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>} The article history entries.
*
* @example
* const history = await store.article.getHistoryBySku(42, 'SKU-1');
* // => [{ id, companyuserId, date, text }]
*/
return async (googleAuthId, sku) => {
const { articleId } = await resolve(googleAuthId, sku);

View File

@@ -10,22 +10,23 @@ function parseJsonArray(raw) {
return Array.isArray(arr) ? arr : [];
}
/**
* Returns articles for export, optionally scoped to a category, including
* category breadcrumbs and image hashes.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists articles for export.
*
* @example
* const fn = listExport(pool);
* const articles = await fn(42, null);
* // => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
*/
export default function listExport(pool) {
const companyAccess = resolveSelectedCompanyAccess(pool);
const categoryAccess = resolveCategoryAccess(pool);
/**
* Returns articles for export, optionally scoped to a category, including
* category breadcrumbs and image hashes.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number|null} [categoryId] - Optional category id to scope the export to; defaults to the company root.
* @returns {Promise<Array<object>>} The articles for export.
* @throws {Error} If the category is not reachable from the selected company.
*
* @example
* const articles = await store.article.listExport(42, null);
* // => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
*/
return async (googleAuthId, categoryId) => {
const { languageId, posCustomerGroupId, rootCategoryId } =
await companyAccess(googleAuthId);

View File

@@ -1,20 +1,19 @@
import { SEARCH_ARTICLES } from '../../sql/article.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Returns a lightweight search index of all articles for a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists the article search index.
*
* @example
* const fn = listSearchIndex(pool);
* const articles = await fn(42);
* // => [{ id, sku, name, eans, categoryIds }]
*/
export default function listSearchIndex(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns a lightweight search index of all articles for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>} The article search index.
*
* @example
* const articles = await store.article.listSearchIndex(42);
* // => [{ id, sku, name, eans, categoryIds }]
*/
return async (googleAuthId) => {
const { companyId, rootCategoryId, languageId } = await access(googleAuthId);

View File

@@ -6,20 +6,23 @@ import {
import resolveArticleBySku from './resolveArticleBySku.js';
import request from '../request.js';
/**
* Reorders the images of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that reorders article images.
*
* @example
* const fn = orderImages(pool);
* const result = await fn(42, 'SKU-1', { fullHashes: ['a', 'b'] });
* // => { fullHashes: ['a', 'b'] }
*/
export default function orderImages(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Reorders the images of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {object} input - The reorder input.
* @param {Array<string>} input.fullHashes - The full image hashes in their new order.
* @returns {Promise<{fullHashes: Array<string>}>} The applied order.
* @throws {Error} If the order is invalid.
*
* @example
* const result = await store.article.orderImages(42, 'SKU-1', { fullHashes: ['a', 'b'] });
* // => { fullHashes: ['a', 'b'] }
*/
return async (googleAuthId, sku, { fullHashes }) => {
if (!Array.isArray(fullHashes)) {
throw new Error('Invalid image order');

View File

@@ -5,21 +5,23 @@ import {
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Sets (or clears) the brand of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the article brand.
*
* @example
* const fn = setBrandIdBySku(pool);
* const article = await fn(42, 'SKU-1', 3);
* // => { id, sku, name, ... }
*/
export default function setBrandIdBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Sets (or clears) the brand of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {number|null} brandId - The brand id, or `null` to clear the brand.
* @returns {Promise<object>} The updated article.
* @throws {Error} If the brand or article is not found.
*
* @example
* const article = await store.article.setBrandIdBySku(42, 'SKU-1', 3);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, brandId) => {
const { articleId, languageId, companyId } = await resolve(
googleAuthId,

View File

@@ -7,22 +7,24 @@ import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Replaces the category membership of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the article categories.
*
* @example
* const fn = setCategoryIdsBySku(pool);
* const article = await fn(42, 'SKU-1', [5, 9]);
* // => { id, sku, name, ... }
*/
export default function setCategoryIdsBySku(pool) {
const resolve = resolveArticleBySku(pool);
const categoryAccess = resolveCategoryAccess(pool);
const load = queryBySku(pool);
/**
* Replaces the category membership of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<number>} categoryIds - The category ids the article should belong to (at least one).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the category ids are invalid or the article is not found.
*
* @example
* const article = await store.article.setCategoryIdsBySku(42, 'SKU-1', [5, 9]);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, categoryIds) => {
if (!Array.isArray(categoryIds)) {
throw new Error('Invalid categoryIds');

View File

@@ -6,21 +6,23 @@ import request from '../request.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Replaces the EAN list of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets article EANs.
*
* @example
* const fn = setEansBySku(pool);
* const article = await fn(42, 'SKU-1', ['4001234567890']);
* // => { id, sku, name, ... }
*/
export default function setEansBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Replaces the EAN list of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<string>} eans - The EANs to set (must be unique, non-empty strings).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the EANs are invalid or the article is not found.
*
* @example
* const article = await store.article.setEansBySku(42, 'SKU-1', ['4001234567890']);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, eans) => {
if (!Array.isArray(eans)) {
throw new Error('Invalid eans');

View File

@@ -42,22 +42,24 @@ function normalizePrices(prices) {
return normalized;
}
/**
* Replaces the price list of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets article prices.
*
* @example
* const fn = setPricesBySku(pool);
* const article = await fn(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
* // => { id, sku, name, ... }
*/
export default function setPricesBySku(pool) {
const resolve = resolveArticleBySku(pool);
const ensureTax = taxEnsure(pool);
const load = queryBySku(pool);
/**
* Replaces the price list of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<{price: number, tax: number, minmultiplier?: number}>} prices - The price entries (minmultiplier defaults to 1).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the prices are invalid or the article is not found.
*
* @example
* const article = await store.article.setPricesBySku(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, prices) => {
const normalized = normalizePrices(prices);

View File

@@ -2,21 +2,23 @@ import { UPDATE_WEIGHT } from '../../sql/article.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Sets the weight of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the article weight.
*
* @example
* const fn = setWeightBySku(pool);
* const article = await fn(42, 'SKU-1', 1.5);
* // => { id, sku, name, ... }
*/
export default function setWeightBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Sets the weight of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {number} weight - The weight (must be >= 0).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the weight is invalid or the article is not found.
*
* @example
* const article = await store.article.setWeightBySku(42, 'SKU-1', 1.5);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, weight) => {
if (!Number.isFinite(weight) || weight < 0) {
throw new Error('Invalid weight');

View File

@@ -20,21 +20,26 @@ function formatUnitLabel(amount, symbol) {
return `${Number(amount)}${sym}`;
}
/**
* Updates an article's name and/or unit (SI and amount), recording history.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates an article by SKU.
*
* @example
* const fn = updateBySku(pool);
* const article = await fn(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1 });
* // => { id, sku, name, ... }
*/
export default function updateBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Updates an article's name and/or unit (SI and amount), recording history.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {object} patch - The update patch.
* @param {string} [patch.name] - The new article name.
* @param {string} [patch.unitSi] - The new unit SI code.
* @param {number} [patch.unitamount] - The new unit amount (must be > 0).
* @returns {Promise<object>} The updated article.
* @throws {Error} If no valid field is provided or the article is not found.
*
* @example
* const article = await store.article.updateBySku(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1 });
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, patch = {}) => {
const hasName = Object.prototype.hasOwnProperty.call(patch, 'name');
const hasUnitamount = Object.prototype.hasOwnProperty.call(patch, 'unitamount');