Appwrite-Abfragen: select, notEqual, countDocuments und Nachbau dazu

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 08:38:21 +00:00
1 parent 8c1055e8a0
commit 2dd9c0dec7
3 files changed
+190 -7

No files matched your search

+29 -6
View File
@@ -11,9 +11,12 @@ import http from 'node:http'
*
* Bulk-Create (POST .../documents mit { documents: [{ $id, ...felder }] }, 1 bis 100 Dokumente) ist alles oder
* nichts: vorhandene oder doppelte $id => 409, ungueltige $id, unbekanntes Attribut oder ungueltiges Datum => 400,
* dann ist nichts geschrieben. Abfragen: equal, greaterThan(Equal), lessThan(Equal), between (inklusiv), orderAsc/
* orderDesc, limit, cursorAfter. Zwei Werte, die beide wie "YYYY-MM-DDT..." aussehen, vergleicht er als Zeitpunkte
* (Schreibweise Z, +00:00, +02:00 ist egal), alle anderen wie bisher.
* dann ist nichts geschrieben. Abfragen: equal, notEqual (mit Liste: keiner von; ein Dokument ohne das Feld passt),
* greaterThan(Equal), lessThan(Equal), between (inklusiv), orderAsc/orderDesc, limit, cursorAfter, select (nur die
* genannten Felder plus alle $-Felder). Die Antwort traegt total = Treffer der Filter, unabhaengig von limit und
* cursorAfter. listCalls(collection?) / resetListCalls() zeichnen die Listenabfragen auf. Zwei Werte, die beide
* wie "YYYY-MM-DDT..." aussehen, vergleicht er als Zeitpunkte (Schreibweise Z, +00:00, +02:00 ist egal), alle
* anderen wie bisher.
*
* Optionen (ohne sie verhaelt sich der Nachbau wie zuvor):
* datetimes { collection: ['feld', ...] } Datumsfelder; beim Schreiben (Anlegen, Aendern, Bulk, seed) wie
@@ -33,6 +36,8 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
let lastLocale = null
let failUserLookups = false
let sessionDeletes = 0
// Aufzeichnung der Listenabfragen (GET .../documents ohne ID): [{ collection, queries: [geparste Abfragen] }]
let listLog = []
// Wie Appwrite (Validator UID): 'unique()' ist erlaubt, sonst [A-Za-z0-9][A-Za-z0-9._-]{0,35}
const validDocumentId = (id) => id === 'unique()' || (typeof id === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,35}$/.test(id))
@@ -127,6 +132,8 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
switch (query.method) {
case 'equal':
return query.values.some((candidate) => same(value, candidate))
case 'notEqual':
return !query.values.some((candidate) => same(value, candidate))
case 'greaterThan':
return greater(value, first)
case 'greaterThanEqual':
@@ -142,7 +149,10 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
}
}
const supportedQueries = ['equal', 'greaterThan', 'greaterThanEqual', 'lessThan', 'lessThanEqual', 'between', 'orderAsc', 'orderDesc', 'limit', 'cursorAfter']
const supportedQueries = ['equal', 'greaterThan', 'greaterThanEqual', 'lessThan', 'lessThanEqual', 'between', 'notEqual', 'orderAsc', 'orderDesc', 'limit', 'cursorAfter', 'select']
// Nur die genannten Felder plus alle $-Felder (wie Appwrite); $seq ist intern und faellt in publicDoc weg
const project = (doc, attributes) => Object.fromEntries(Object.entries(doc).filter(([key]) => key.startsWith('$') || attributes.includes(key)))
const list = (collection, rawQueries) => {
const queries = rawQueries.map((q) => JSON.parse(q))
@@ -153,7 +163,11 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
if (cursorId !== undefined && !docs(collection).has(cursorId)) {
return [400, { message: 'The cursor is invalid. This can happen if the item represented by the cursor has been deleted.', code: 400, type: 'general_cursor_not_found' }]
}
let result = [...docs(collection).values()].filter((doc) => doc.$id === cursorId || queries.every((q) => matches(doc, q)))
const all = [...docs(collection).values()]
const hit = (doc) => queries.every((q) => matches(doc, q))
// total wie Appwrite: alle Treffer der Filter, ohne limit und cursorAfter
const total = all.filter(hit).length
let result = all.filter((doc) => doc.$id === cursorId || hit(doc))
for (const q of queries.filter((q) => q.method === 'orderAsc' || q.method === 'orderDesc')) {
const dir = q.method === 'orderAsc' ? 1 : -1
result.sort((a, b) => {
@@ -165,7 +179,9 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
if (cursorId !== undefined) result = result.slice(result.findIndex((doc) => doc.$id === cursorId) + 1)
const limit = queries.find((q) => q.method === 'limit')?.values[0] ?? defaultLimit
if (limit !== null) result = result.slice(0, limit)
return [200, { total: result.length, documents: result.map(publicDoc) }]
const selected = queries.filter((q) => q.method === 'select').flatMap((q) => q.values)
if (queries.some((q) => q.method === 'select')) result = result.map((doc) => project(doc, selected))
return [200, { total, documents: result.map(publicDoc) }]
}
const publicDoc = ({ $seq, ...doc }) => doc
@@ -324,6 +340,8 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
const store = docs(collection)
const notFound = [404, { message: 'Document with the requested ID could not be found.', code: 404, type: 'document_not_found' }]
if (!id && req.method === 'GET') listLog.push({ collection, queries: url.searchParams.getAll('queries[]').map((q) => JSON.parse(q)) })
// Eingeplanter Fehler (failNext): genau eine passende Anfrage scheitert
if (
pendingFailure &&
@@ -389,6 +407,11 @@ export async function startAppwriteMock({ schema = {}, datetimes = {}, defaultLi
url: `http://127.0.0.1:${server.address().port}/v1`,
docs: (collection) => new Map([...docs(collection)].map(([id, doc]) => [id, publicDoc(doc)])),
seed,
/** Aufgezeichnete Listenabfragen (nur GET auf eine Collection, auch fehlgeschlagene), je Aufruf eine Kopie; optional einer Collection */
listCalls: (collection = null) => structuredClone(listLog.filter((call) => !collection || call.collection === collection)),
resetListCalls: () => {
listLog = []
},
/** Naechste passende Dokument-Anfrage scheitert mit status (einmalig); after passende Anfragen gehen vorher durch. */
failNext: ({ collection = null, method = null, status = 500, message = '', after = 0 }) => {
pendingFailure = { collection, method, status, message, after }