Appwrite: Bulk-Create, Bereichsabfragen, seitenweises Laden, genauerer Nachbau

appwriteAdmin: adminFetch kennt die Option raw (Body unverpackt), createDocuments
(Bulk-Create, alles oder nichts, 409 bei vorhandener ID), listAllDocuments (seitenweise
per cursorAfter, sortiert nach $id, Fehler ueber max) und Query.between/greaterThanEqual/
lessThanEqual/cursorAfter.

Nachbau: Bulk-Create mit Alles-oder-nichts (409/400, hoechstens 100), die neuen Abfragen
samt cursorAfter, Zeitpunktvergleich fuer ISO-Datumswerte, Optionen datetimes (Normalisierung
auf +00:00, leeres Datum => 400) und defaultLimit (Standard-Limit wie Appwrite). Ohne die
Optionen unveraendert. testApp exportiert send(method, path, opts).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:43 +00:00
1 parent 334dc1e669
commit a5e12d7f34
4 files changed
+407 -23

No files matched your search

+48 -2
View File
@@ -25,7 +25,8 @@ function formatRequestBody(body, method, { raw = false } = {}) {
return payload
}
async function adminFetch(path, { method = 'GET', body, queries = [] } = {}) {
/** raw: true schickt den Body unverpackt (sonst wird er zu { data, documentId }; Pfade unter /users sind immer roh). */
async function adminFetch(path, { method = 'GET', body, queries = [], raw = false } = {}) {
if (!config.appwrite.apiKey) {
const error = new Error('APPWRITE_API_KEY fehlt in .env')
error.status = 500
@@ -37,7 +38,7 @@ async function adminFetch(path, { method = 'GET', body, queries = [] } = {}) {
url.searchParams.append('queries[]', q)
}
const useRawBody = path.startsWith('/users')
const useRawBody = raw || path.startsWith('/users')
const requestBody = formatRequestBody(body, method, { raw: useRawBody })
const response = await fetch(url.toString(), {
@@ -77,6 +78,12 @@ export const Query = {
limit: (n) => ({ method: 'limit', values: [n] }),
greaterThan: (attribute, value) => ({ method: 'greaterThan', attribute, values: [value] }),
lessThan: (attribute, value) => ({ method: 'lessThan', attribute, values: [value] }),
greaterThanEqual: (attribute, value) => ({ method: 'greaterThanEqual', attribute, values: [value] }),
lessThanEqual: (attribute, value) => ({ method: 'lessThanEqual', attribute, values: [value] }),
/** Beide Grenzen inklusiv */
between: (attribute, start, end) => ({ method: 'between', attribute, values: [start, end] }),
/** Seite nach dem Dokument mit dieser ID (zusammen mit orderAsc/orderDesc und limit) */
cursorAfter: (documentId) => ({ method: 'cursorAfter', values: [documentId] }),
orderDesc: (attribute) => ({ method: 'orderDesc', attribute }),
orderAsc: (attribute) => ({ method: 'orderAsc', attribute }),
}
@@ -157,6 +164,34 @@ export async function listDocuments(collectionId, queries = []) {
return result.documents
}
/**
* Ein Aufruf mit allen Seiten: sortiert nach $id, Seite fuer Seite per cursorAfter, bis eine Seite kuerzer als
* pageSize ist. queries enthalten nur Filter, nie limit/order/cursor (die setzt die Funktion selbst). Mehr als max
* Treffer ist ein Fehler (Schutz vor unbeabsichtigtem Vollabzug).
*/
export async function listAllDocuments(collectionId, queries = [], { pageSize = 500, max = 20000 } = {}) {
if (!Number.isInteger(pageSize) || pageSize < 1) throw new Error(`listAllDocuments: pageSize muss eine ganze Zahl ab 1 sein, nicht ${pageSize}`)
const all = []
let cursor = null
for (;;) {
const page = await listDocuments(collectionId, [
...queries,
Query.limit(pageSize),
Query.orderAsc('$id'),
...(cursor ? [Query.cursorAfter(cursor)] : []),
])
all.push(...page)
if (all.length > max) {
const error = new Error(`listAllDocuments: mehr als ${max} Dokumente in ${collectionId}`)
error.status = 500
error.type = 'list_limit_exceeded'
throw error
}
if (page.length < pageSize) return all
cursor = page[page.length - 1].$id
}
}
export async function getCustomerByAppwriteUserId(appwriteUserId) {
const docs = await listDocuments(config.collections.customers, [
Query.equal('appwriteUserId', appwriteUserId),
@@ -218,6 +253,17 @@ export async function createDocument(collectionId, data, documentId = ID.unique(
})
}
/**
* Mehrere Dokumente in einer Anfrage (Appwrite 1.8.1 Bulk-Create, hoechstens 100): alles oder nichts. Ist eine
* $id schon vergeben, antwortet Appwrite 409 document_already_exists und schreibt nichts (err.status === 409).
* Jedes Dokument bringt seine $id mit: [{ $id, ...felder }]. Liefert die angelegten Dokumente.
*/
export async function createDocuments(collectionId, documents) {
if (documents.length === 0) return []
const result = await adminFetch(collectionPath(collectionId), { method: 'POST', body: { documents }, raw: true })
return result.documents
}
export async function deleteDocument(collectionId, documentId) {
return adminFetch(`${collectionPath(collectionId)}/${documentId}`, {
method: 'DELETE',