Align stock API with paginated backend and bump to v0.2.2
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-04-12 17:57:53 +02:00
parent ae8ad07d87
commit 39dd474813
9 changed files with 277 additions and 83 deletions
+80 -45
View File
@@ -1,9 +1,51 @@
import { apiRequest, getPath } from './client.js';
const DEFAULT_LIST_PAGE_LIMIT = 100;
function unwrapEntryPayload(payload) {
return payload?.data || payload?.entry || payload?.item || payload;
}
function unwrapListPayload(payload) {
if (Array.isArray(payload)) {
return payload;
}
return payload?.data || payload?.entries || payload?.items || payload?.groups || [];
}
function hasExplicitPagination(filters = {}) {
return (
(filters.limit !== undefined && filters.limit !== null)
|| (filters.offset !== undefined && filters.offset !== null)
);
}
async function fetchAllListPages(store, path, baseQuery = {}) {
const items = [];
let offset = 0;
while (true) {
const payload = await apiRequest(store, path, {
query: {
...baseQuery,
limit: DEFAULT_LIST_PAGE_LIMIT,
offset,
},
});
const pageItems = unwrapListPayload(payload);
items.push(...pageItems);
if (pageItems.length < DEFAULT_LIST_PAGE_LIMIT) {
break;
}
offset += DEFAULT_LIST_PAGE_LIMIT;
}
return items;
}
export async function searchItemDefinitions(store, query) {
if (query.trim().length <= 2) {
return [];
@@ -21,59 +63,57 @@ export async function searchItemDefinitions(store, query) {
}
export async function listStockEntries(store, filters = {}) {
const query = {};
const baseQuery = {};
const searchName = filters.searchName || filters.search_name;
if (searchName) {
query.search_name = searchName;
}
if (filters.limit !== undefined && filters.limit !== null) {
query.limit = filters.limit;
}
if (filters.offset !== undefined && filters.offset !== null) {
query.offset = filters.offset;
}
if (filters.cursor) {
query.cursor = filters.cursor;
baseQuery.search_name = searchName;
}
const payload = await apiRequest(store, getPath('items'), {
query,
});
if (hasExplicitPagination(filters)) {
const query = { ...baseQuery };
if (filters.limit !== undefined && filters.limit !== null) {
query.limit = filters.limit;
}
if (filters.offset !== undefined && filters.offset !== null) {
query.offset = filters.offset;
}
if (Array.isArray(payload)) {
return payload;
const payload = await apiRequest(store, getPath('items'), {
query,
});
return unwrapListPayload(payload);
}
return payload?.data || payload?.entries || payload?.items || [];
return fetchAllListPages(store, getPath('items'), baseQuery);
}
export async function listGroupedStockEntries(store, options = {}) {
const query = {};
const baseQuery = {};
const expanded = options.expanded ?? 1;
query.expanded = expanded;
baseQuery.expanded = expanded;
const searchName = options.searchName || options.search_name;
if (searchName) {
query.search_name = searchName;
}
if (options.limit !== undefined && options.limit !== null) {
query.limit = options.limit;
}
if (options.offset !== undefined && options.offset !== null) {
query.offset = options.offset;
}
if (options.cursor) {
query.cursor = options.cursor;
baseQuery.search_name = searchName;
}
const payload = await apiRequest(store, `${getPath('items')}/grouped`, {
query,
});
if (hasExplicitPagination(options)) {
const query = { ...baseQuery };
if (options.limit !== undefined && options.limit !== null) {
query.limit = options.limit;
}
if (options.offset !== undefined && options.offset !== null) {
query.offset = options.offset;
}
if (Array.isArray(payload)) {
return payload;
const payload = await apiRequest(store, `${getPath('items')}/grouped`, {
query,
});
return unwrapListPayload(payload);
}
return payload?.data || payload?.entries || payload?.items || payload?.groups || [];
return fetchAllListPages(store, `${getPath('items')}/grouped`, baseQuery);
}
export async function getStockEntry(store, stockId) {
@@ -183,11 +223,11 @@ export async function patchStockItem(store, uuidB64, body) {
}
export async function updateStockItem(store, uuidB64, body) {
const payload = await apiRequest(store, `${getPath('items')}/${uuidB64}/stock`, {
await apiRequest(store, `${getPath('items')}/${uuidB64}/stock`, {
method: 'POST',
body,
});
return unwrapEntryPayload(payload);
return getStockEntry(store, uuidB64);
}
export async function deleteStockItem(store, uuidB64) {
@@ -205,25 +245,20 @@ export async function useStockItem(store, uuidB64) {
return { status: 'used' };
} catch (error) {
const status = error?.status || error?.cause?.status;
if (status === 409) {
if (status === 409 || status === 404) {
return { status: 'already_gone' };
}
if (status === 404 || status === 405) {
await deleteStockItem(store, uuidB64);
return { status: 'fallback_delete' };
}
throw error;
}
}
export async function adjustStockEntry(store, stockId, body) {
const payload = await apiRequest(store, `${getPath('items')}/${stockId}/stock`, {
await apiRequest(store, `${getPath('items')}/${stockId}/stock`, {
method: 'POST',
body,
});
return unwrapEntryPayload(payload);
return getStockEntry(store, stockId);
}
export async function listKitchenChanges(store, { since, limit = 10 } = {}) {