Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb6c69a71 | ||
|
|
8ff526262d | ||
|
|
e1b71f5fb6 | ||
|
|
76c064bd31 |
+10
-1
@@ -559,6 +559,15 @@ Diese Listen werden gleichzeitig geholt, und einzelne davon können scheitern, w
|
|||||||
der Rest durchgeht — ein 403 wegen fehlender Scopes, ein Rate-Limit, eine
|
der Rest durchgeht — ein 403 wegen fehlender Scopes, ein Rate-Limit, eine
|
||||||
Zeitüberschreitung.
|
Zeitüberschreitung.
|
||||||
|
|
||||||
|
**Ein zweiter Versuch, bevor etwas als fehlend gilt.** Zeitüberschreitungen und
|
||||||
|
Verbindungen, die gar nicht zustande kamen, werden einmal nachgeschlagen — einzeln
|
||||||
|
statt im Schwung, weil genau das der beobachtete Fall war: zwei von acht Aufrufen
|
||||||
|
liefen in eine Zeitüberschreitung nach 15 Sekunden, während dieselben Aufrufe einzeln
|
||||||
|
in 80 Millisekunden antworteten. Nachgeschlagen wird höchstens zweimal je Abgleich —
|
||||||
|
acht Zeitüberschreitungen hintereinander wären zwei Minuten. Bei `403`, `404` und `429`
|
||||||
|
gibt es keinen zweiten Versuch: fehlende Rechte werden durch Wiederholen nicht besser,
|
||||||
|
ein Rate-Limit wird schlimmer.
|
||||||
|
|
||||||
**Ein gescheiterter Abruf ist keine leere Liste.** Der Unterschied steht am Snapshot: zu
|
**Ein gescheiterter Abruf ist keine leere Liste.** Der Unterschied steht am Snapshot: zu
|
||||||
jedem Feld, das nicht geholt werden konnte, wird der Grund samt HTTP-Status festgehalten.
|
jedem Feld, das nicht geholt werden konnte, wird der Grund samt HTTP-Status festgehalten.
|
||||||
Daraus folgt dreierlei:
|
Daraus folgt dreierlei:
|
||||||
@@ -575,7 +584,7 @@ Das gilt ausdrücklich **nicht** als Fehlschlag: `syncError` bleibt leer, der Z
|
|||||||
das Zurückhalten wird nicht erhöht, und der nächste Durchlauf versucht es normal wieder.
|
das Zurückhalten wird nicht erhöht, und der nächste Durchlauf versucht es normal wieder.
|
||||||
Der Rest des Standes ist ja frisch.
|
Der Rest des Standes ist ja frisch.
|
||||||
|
|
||||||
> Vor v1.10.2 wurde jeder Fehlschlag in eine leere Liste verwandelt. Ein Repository mit
|
> Vor v1.11.0 wurde jeder Fehlschlag in eine leere Liste verwandelt. Ein Repository mit
|
||||||
> 65 Commits meldete „Snapshot aktualisiert: 0 Branches, 0 Tags", überschrieb dabei den
|
> 65 Commits meldete „Snapshot aktualisiert: 0 Branches, 0 Tags", überschrieb dabei den
|
||||||
> letzten guten Stand, und aus der Oberfläche war nicht zu erkennen, dass überhaupt etwas
|
> letzten guten Stand, und aus der Oberfläche war nicht zu erkennen, dass überhaupt etwas
|
||||||
> schiefgegangen war.
|
> schiefgegangen war.
|
||||||
|
|||||||
+4
-3
@@ -7,7 +7,7 @@ Stück für Stück abarbeiten. Reihenfolge ist ein Vorschlag, keine Festlegung.
|
|||||||
|
|
||||||
Was bereits steht, listet [FEATURES.md](FEATURES.md).
|
Was bereits steht, listet [FEATURES.md](FEATURES.md).
|
||||||
|
|
||||||
**Stand:** v1.10.1. Alle Issues aus dem Dashboard sind abgearbeitet.
|
**Stand:** v1.11.0. Alle Issues aus dem Dashboard sind abgearbeitet.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ Voraussetzung, falls du das Projekt veröffentlichen willst.
|
|||||||
|
|
||||||
## 3 · Aufgefallen beim Bauen
|
## 3 · Aufgefallen beim Bauen
|
||||||
|
|
||||||
Beobachtungen aus der Arbeit an v1.6.0 bis v1.10.1 — festgehalten, damit sie
|
Beobachtungen aus der Arbeit an v1.6.0 bis v1.11.0 — festgehalten, damit sie
|
||||||
nicht verloren gehen. Nichts davon ist dringend.
|
nicht verloren gehen. Nichts davon ist dringend.
|
||||||
|
|
||||||
### 3.1 Anwendungsname bricht in der Kopfleiste ab `S`
|
### 3.1 Anwendungsname bricht in der Kopfleiste ab `S`
|
||||||
@@ -108,7 +108,7 @@ Beide stehen im Änderungsverlauf, existierten aber nie in der
|
|||||||
`package.json` — dort springt die Historie von 0.1.0 auf 0.4.0. Nachgetragen
|
`package.json` — dort springt die Historie von 0.1.0 auf 0.4.0. Nachgetragen
|
||||||
wurden sie in dem Commit, der den Verlauf einführte, und der liegt *nach*
|
wurden sie in dem Commit, der den Verlauf einführte, und der liegt *nach*
|
||||||
0.4.0. Es gibt also keinen ehrlichen Commit, an dem ein Tag hängen könnte.
|
0.4.0. Es gibt also keinen ehrlichen Commit, an dem ein Tag hängen könnte.
|
||||||
Die übrigen 19 Fassungen haben seit dem 18.08.2026 ein Release in Gitea.
|
Die übrigen 20 Fassungen haben seit dem 18.08.2026 ein Release in Gitea.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -179,6 +179,7 @@ Damit klar ist, was **nicht** kommt und warum:
|
|||||||
| **v1.9.0** Geschlossene Issues haken ihre Aufgabe auch ohne Webhook ab | ✓ |
|
| **v1.9.0** Geschlossene Issues haken ihre Aufgabe auch ohne Webhook ab | ✓ |
|
||||||
| **v1.10.0** Wochenrückblick, globale Zeitleiste, Suche über Aufgaben | ✓ |
|
| **v1.10.0** Wochenrückblick, globale Zeitleiste, Suche über Aufgaben | ✓ |
|
||||||
| **v1.10.1** Issue-Kennzeichen auf der Aufgabenkarte, Token-Rechte Aufruf für Aufruf belegt | ✓ |
|
| **v1.10.1** Issue-Kennzeichen auf der Aufgabenkarte, Token-Rechte Aufruf für Aufruf belegt | ✓ |
|
||||||
|
| **v1.11.0** Karten je Spalte begrenzen; der Abgleich verschluckt keine Fehler mehr und verliert dabei nichts | ✓ |
|
||||||
|
|
||||||
Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur
|
Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur
|
||||||
beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben
|
beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "nebula",
|
"name": "nebula",
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nebula",
|
"name": "nebula",
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nebula",
|
"name": "nebula",
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Nebula – Dashboard fuer Vibe-Coding Projekte mit Git-Anbindung (GitHub, GitLab, Gitea)",
|
"description": "Nebula – Dashboard fuer Vibe-Coding Projekte mit Git-Anbindung (GitHub, GitLab, Gitea)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -41,6 +41,64 @@ export const KIND_META: Record<ChangeKind, { label: string; className: string }>
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: Release[] = [
|
export const CHANGELOG: Release[] = [
|
||||||
|
{
|
||||||
|
version: "1.11.0",
|
||||||
|
date: "2026-09-10",
|
||||||
|
headline: "Der Abgleich sagt, was er nicht holen konnte — und verliert dabei nichts mehr.",
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
kind: "behoben",
|
||||||
|
text:
|
||||||
|
"Ein fehlgeschlagener Teil-Abruf sah aus wie ein leeres Ergebnis. Ein Repository " +
|
||||||
|
"mit 65 Commits meldete „Snapshot aktualisiert: 0 Branches, 0 Tags“ und überschrieb " +
|
||||||
|
"dabei den letzten guten Stand. Jetzt steht am Repository, welcher Teil nicht kam " +
|
||||||
|
"und woran es lag — und die vorhandenen Daten bleiben stehen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "behoben",
|
||||||
|
text:
|
||||||
|
"Die Commit-Heatmap kannte bei Gitea nur die jüngsten 50 Commits und gab deren " +
|
||||||
|
"ältesten als Anfang des Zeitraums aus. Grund war eine Seitengröße, die angefragt " +
|
||||||
|
"statt gemessen wurde: Gitea deckelt jede Antwort auf 50, egal was dasteht. Bei " +
|
||||||
|
"einem Repository mit 76 Commits fehlte damit ein Drittel.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "besser",
|
||||||
|
text:
|
||||||
|
"Ein Abruf, der in eine Zeitüberschreitung läuft, wird einmal nachgeschlagen — " +
|
||||||
|
"einzeln statt zusammen mit den sieben anderen. Bei fehlenden Rechten oder einem " +
|
||||||
|
"Rate-Limit passiert das bewusst nicht; das wird durch Wiederholen nicht besser.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "neu",
|
||||||
|
text:
|
||||||
|
"Das Aufgabenbrett zeigt je Spalte höchstens so viele Karten, wie unter " +
|
||||||
|
"Admin-Einstellungen eingestellt ist. Der Rest steht hinter „n weitere anzeigen“; " +
|
||||||
|
"die Zahl an der Spalte nennt weiterhin alle.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "neu",
|
||||||
|
text:
|
||||||
|
"Eine Aufgabenkarte trägt die Nummer ihres verknüpften Git-Issues. Bisher stand " +
|
||||||
|
"die Verknüpfung nur im Bearbeiten-Dialog.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "besser",
|
||||||
|
text:
|
||||||
|
"Die Rechte, die eine Git-Verbindung braucht, stehen Aufruf für Aufruf in der " +
|
||||||
|
"README — getrennt nach reinem Abgleich und zusätzlicher Issue-Anbindung. Der " +
|
||||||
|
"Hinweis unter dem Token-Feld nannte die Rechte für Actions nicht, ohne die bei " +
|
||||||
|
"GitHub kein CI-Zustand ankommt.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "behoben",
|
||||||
|
text:
|
||||||
|
"Ein unbrauchbarer Wert in GIT_API_TIMEOUT_MS legte den ganzen Abgleich still: " +
|
||||||
|
"eine leere oder unlesbare Angabe wurde zu 0 ms, und damit brach jeder Aufruf ab, " +
|
||||||
|
"bevor er begann. Jetzt gilt die Vorgabe, mit Warnung im Log.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "1.10.1",
|
version: "1.10.1",
|
||||||
date: "2026-08-18",
|
date: "2026-08-18",
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ describe("Teilabrufe", () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.unstubAllEnvs();
|
vi.unstubAllEnvs();
|
||||||
|
// Ohne das zaehlen die Spies ueber Tests hinweg weiter – und genau an
|
||||||
|
// Aufrufzahlen haengen die Pruefungen hier.
|
||||||
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("meldet nichts, solange alles ankommt", async () => {
|
it("meldet nichts, solange alles ankommt", async () => {
|
||||||
@@ -125,3 +128,138 @@ describe("Teilabrufe", () => {
|
|||||||
expect(teil.fehler.commits).toBeTruthy();
|
expect(teil.fehler.commits).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der zweite Versuch.
|
||||||
|
*
|
||||||
|
* Beobachtet an einer laufenden Instanz: zwei von acht Teil-Abrufen liefen
|
||||||
|
* in eine Zeitueberschreitung nach 15 s, waehrend dieselben Aufrufe von
|
||||||
|
* aussen einzeln in 80 ms antworteten. Nicht die Endpunkte sind langsam –
|
||||||
|
* die Achterrunde erwuergt sich gelegentlich selbst.
|
||||||
|
*/
|
||||||
|
describe("Teilabrufe: Nachschlag", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubEnv("GIT_ALLOWED_HOSTS", "git.example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
// Ohne das zaehlen die Spies ueber Tests hinweg weiter – und genau an
|
||||||
|
// Aufrufzahlen haengen die Pruefungen hier.
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Antwortet die ersten `fehlschlaege` Male mit Abbruch, danach mit Daten. */
|
||||||
|
function zaehlend(fehlschlaege: number, status = 408) {
|
||||||
|
let aufruf = 0;
|
||||||
|
return vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
|
||||||
|
aufruf += 1;
|
||||||
|
if (aufruf <= fehlschlaege) {
|
||||||
|
if (status === 408) {
|
||||||
|
const abbruch = new Error("aborted");
|
||||||
|
abbruch.name = "AbortError";
|
||||||
|
throw abbruch;
|
||||||
|
}
|
||||||
|
return new Response("nope", { status });
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify(["da"]), { status: 200 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it("holt eine Zeitueberschreitung im zweiten Versuch nach", async () => {
|
||||||
|
const fetchSpy = zaehlend(1);
|
||||||
|
const teil = new Teilabrufe();
|
||||||
|
|
||||||
|
const wert = await teil.hol("branches", "https://git.example.com/a", []);
|
||||||
|
|
||||||
|
expect(wert).toEqual(["da"]);
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||||
|
// Geglueckt heisst: kein Eintrag. Sonst stuende am Repository ein
|
||||||
|
// Fehler, den es nicht mehr gibt.
|
||||||
|
expect(teil.fehler).toEqual({});
|
||||||
|
expect(teil.unvollstaendig).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("vermerkt es, wenn auch der zweite Versuch scheitert", async () => {
|
||||||
|
const fetchSpy = zaehlend(99);
|
||||||
|
const teil = new Teilabrufe();
|
||||||
|
|
||||||
|
const wert = await teil.hol("commits", "https://git.example.com/a", []);
|
||||||
|
|
||||||
|
expect(wert).toEqual([]);
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||||
|
expect(teil.fehler.commits).toContain("408");
|
||||||
|
expect(teil.fehler.commits).toContain("zweiten Versuch");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("schlaegt bei 403 gar nicht erst nach", async () => {
|
||||||
|
// Fehlende Scopes werden durch Wiederholen nicht besser.
|
||||||
|
const fetchSpy = zaehlend(99, 403);
|
||||||
|
const teil = new Teilabrufe();
|
||||||
|
|
||||||
|
await teil.hol("tags", "https://git.example.com/a", []);
|
||||||
|
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(teil.fehler.tags).not.toContain("zweiten Versuch");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("schlaegt bei 429 nicht nach", async () => {
|
||||||
|
// Sofortiges Wiederholen macht ein Rate-Limit schlimmer, nicht besser.
|
||||||
|
const fetchSpy = zaehlend(99, 429);
|
||||||
|
const teil = new Teilabrufe();
|
||||||
|
|
||||||
|
await teil.hol("issues", "https://git.example.com/a", []);
|
||||||
|
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gibt hoechstens zwei Nachschlaege je Abgleich", async () => {
|
||||||
|
// Acht Zeitueberschreitungen zu je 15 s waeren zwei Minuten. Wer weg
|
||||||
|
// ist, soll schnell als weg erkannt werden.
|
||||||
|
const fetchSpy = zaehlend(99);
|
||||||
|
const teil = new Teilabrufe();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
teil.hol("branches", "https://git.example.com/a", []),
|
||||||
|
teil.hol("tags", "https://git.example.com/b", []),
|
||||||
|
teil.hol("commits", "https://git.example.com/c", []),
|
||||||
|
teil.hol("releases", "https://git.example.com/d", []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Vier erste Versuche, aber nur zwei Nachschlaege.
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(6);
|
||||||
|
expect(Object.keys(teil.fehler).sort()).toEqual([
|
||||||
|
"branches",
|
||||||
|
"commits",
|
||||||
|
"releases",
|
||||||
|
"tags",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reisst die Kette nicht ab, wenn ein Nachschlag scheitert", async () => {
|
||||||
|
// Der erste Nachschlag geht schief, der zweite muss trotzdem laufen.
|
||||||
|
let aufruf = 0;
|
||||||
|
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
|
||||||
|
aufruf += 1;
|
||||||
|
// 1 und 2 sind die ersten Versuche, 3 der erste Nachschlag (scheitert),
|
||||||
|
// 4 der zweite Nachschlag (glueckt).
|
||||||
|
if (aufruf <= 3) {
|
||||||
|
const abbruch = new Error("aborted");
|
||||||
|
abbruch.name = "AbortError";
|
||||||
|
throw abbruch;
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify(["da"]), { status: 200 });
|
||||||
|
});
|
||||||
|
|
||||||
|
const teil = new Teilabrufe();
|
||||||
|
const [erst, zweit] = await Promise.all([
|
||||||
|
teil.hol("branches", "https://git.example.com/a", []),
|
||||||
|
teil.hol("tags", "https://git.example.com/b", []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(4);
|
||||||
|
expect(erst).toEqual([]);
|
||||||
|
expect(zweit).toEqual(["da"]);
|
||||||
|
expect(Object.keys(teil.fehler)).toEqual(["branches"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -234,6 +234,16 @@ export async function getJsonSafe<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wie viele zweite Versuche ein Abgleich hoechstens unternimmt.
|
||||||
|
*
|
||||||
|
* Zwei, nicht acht: jede Zeitueberschreitung kostet volle 15 Sekunden, und
|
||||||
|
* acht davon hintereinander waeren zwei Minuten, in denen "Jetzt
|
||||||
|
* synchronisieren" wie ein Absturz aussieht. Wer wirklich weg ist, soll
|
||||||
|
* schnell als weg erkannt werden.
|
||||||
|
*/
|
||||||
|
const NACHSCHLAG_MAX = 2;
|
||||||
|
|
||||||
/** Warum ein einzelner Teil-Abruf nicht geklappt hat. */
|
/** Warum ein einzelner Teil-Abruf nicht geklappt hat. */
|
||||||
export type Teilfehler = Record<string, string>;
|
export type Teilfehler = Record<string, string>;
|
||||||
|
|
||||||
@@ -252,6 +262,18 @@ export type Teilfehler = Record<string, string>;
|
|||||||
export class Teilabrufe {
|
export class Teilabrufe {
|
||||||
readonly fehler: Teilfehler = {};
|
readonly fehler: Teilfehler = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nachschlaege haengen aneinander, damit sie nacheinander laufen.
|
||||||
|
*
|
||||||
|
* Die Runde selbst ist gleichzeitig – acht Aufrufe auf einmal. Genau das
|
||||||
|
* scheint sie gelegentlich zu erwuergen: beobachtet wurden zwei von acht
|
||||||
|
* mit Zeitueberschreitung nach 15 s, waehrend dieselben Aufrufe einzeln in
|
||||||
|
* 80 ms antworteten. Der zweite Versuch soll deshalb nicht wieder in
|
||||||
|
* denselben Schwung geraten.
|
||||||
|
*/
|
||||||
|
private kette: Promise<unknown> = Promise.resolve();
|
||||||
|
private nachgeschlagen = 0;
|
||||||
|
|
||||||
async hol<T>(
|
async hol<T>(
|
||||||
feld: string,
|
feld: string,
|
||||||
url: string,
|
url: string,
|
||||||
@@ -262,11 +284,47 @@ export class Teilabrufe {
|
|||||||
const result = await getJson<T>(url, options);
|
const result = await getJson<T>(url, options);
|
||||||
return (result ?? fallback) as T;
|
return (result ?? fallback) as T;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (this.lohntNachschlag(error)) {
|
||||||
|
const zweiter = await this.nachschlag<T>(url, options);
|
||||||
|
if (zweiter.ok) return (zweiter.wert ?? fallback) as T;
|
||||||
|
this.fehler[feld] = `${beschreibe(zweiter.fehler)} (auch im zweiten Versuch)`;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
this.fehler[feld] = beschreibe(error);
|
this.fehler[feld] = beschreibe(error);
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein zweiter Versuch hilft nur bei Fehlern, die nichts ueber die Sache
|
||||||
|
* aussagen: Zeitueberschreitung (408) und eine Verbindung, die gar nicht
|
||||||
|
* zustande kam (0). Ein 403 wird durch Wiederholen nicht besser, ein 429
|
||||||
|
* durch sofortiges Wiederholen schlimmer, und ein 404 bleibt ein 404.
|
||||||
|
*/
|
||||||
|
private lohntNachschlag(error: unknown): boolean {
|
||||||
|
if (this.nachgeschlagen >= NACHSCHLAG_MAX) return false;
|
||||||
|
if (!(error instanceof GitApiError)) return false;
|
||||||
|
return error.status === 408 || error.status === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async nachschlag<T>(
|
||||||
|
url: string,
|
||||||
|
options: FetchOptions,
|
||||||
|
): Promise<{ ok: boolean; wert?: T; fehler?: unknown }> {
|
||||||
|
this.nachgeschlagen += 1;
|
||||||
|
|
||||||
|
const versuch = this.kette.then(async () => getJson<T>(url, options));
|
||||||
|
// Auch ein Fehlschlag darf die Kette nicht abreissen lassen, sonst
|
||||||
|
// scheitern die folgenden Nachschlaege gleich mit.
|
||||||
|
this.kette = versuch.catch(() => undefined);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return { ok: true, wert: await versuch };
|
||||||
|
} catch (fehler) {
|
||||||
|
return { ok: false, fehler };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** true, sobald mindestens ein Teil-Abruf gescheitert ist. */
|
/** true, sobald mindestens ein Teil-Abruf gescheitert ist. */
|
||||||
get unvollstaendig(): boolean {
|
get unvollstaendig(): boolean {
|
||||||
return Object.keys(this.fehler).length > 0;
|
return Object.keys(this.fehler).length > 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user