@valentinkolb/cloud 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentinkolb/cloud",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Modular Hono+SolidJS framework for building per-app docker services behind a dynamic gateway. Powers cloud.stuve-ulm.de.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,22 +40,20 @@
40
40
  * comment on focus-trap rationale.
41
41
  */
42
42
 
43
- import { createSignal, createEffect, createMemo, onMount, onCleanup, untrack, For, Show } from "solid-js";
44
43
  import { timed } from "@valentinkolb/stdlib/solid";
44
+ import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show, untrack } from "solid-js";
45
45
  import {
46
- type Completion,
47
- type QueryContext,
48
- type Suggestion,
49
46
  applySuggestion,
50
47
  buildSuggestContext,
51
- collectKnownLabels,
48
+ type Completion,
52
49
  detectQuery,
53
50
  displayLabel,
54
51
  plainTextHighlight,
52
+ type QueryContext,
55
53
  renderWithOverlay,
56
54
  resetCompletionState,
57
55
  resolveSuggestions,
58
- suggestSync,
56
+ type Suggestion,
59
57
  tryExpand,
60
58
  tryRestore,
61
59
  } from "../completion";
@@ -162,7 +160,6 @@ const AutocompleteEditor = (props: AutocompleteEditorProps) => {
162
160
  /* ── Memoised inputs ──────────────────────────────────── */
163
161
 
164
162
  const completions = createMemo(() => props.completions);
165
- const knownLabels = createMemo(() => collectKnownLabels(completions()));
166
163
  const useOverlay = createMemo(() => Boolean(props.highlight));
167
164
 
168
165
  /* ── External value sync ──────────────────────────────── */
@@ -214,6 +211,11 @@ const AutocompleteEditor = (props: AutocompleteEditorProps) => {
214
211
  currentAbort = null;
215
212
  };
216
213
 
214
+ const isAbortError = (e: unknown): boolean =>
215
+ e instanceof DOMException
216
+ ? e.name === "AbortError"
217
+ : Boolean(e && typeof e === "object" && "name" in e && (e as { name: string }).name === "AbortError");
218
+
217
219
  const recomputeCompletion = (): void => {
218
220
  if (!textareaEl) return;
219
221
  const ctx = detectQuery(textareaEl, completions());
@@ -230,7 +232,15 @@ const AutocompleteEditor = (props: AutocompleteEditorProps) => {
230
232
  currentAbort = new AbortController();
231
233
  const signal = currentAbort.signal;
232
234
 
233
- const result = resolveSuggestions(ctx.completion, ctx.query, suggestCtx, signal);
235
+ let result: ReturnType<typeof resolveSuggestions>;
236
+ try {
237
+ result = resolveSuggestions(ctx.completion, ctx.query, suggestCtx, signal);
238
+ } catch (e: unknown) {
239
+ if (isAbortError(e)) return;
240
+ setLoading(false);
241
+ setError(e instanceof Error ? e.message : String(e));
242
+ return;
243
+ }
234
244
 
235
245
  if (result.kind === "sync") {
236
246
  setError(null);
@@ -239,14 +249,25 @@ const AutocompleteEditor = (props: AutocompleteEditorProps) => {
239
249
  return;
240
250
  }
241
251
 
242
- // Async path: keep previous suggestions visible (dimmed via
243
- // `loading` state) and schedule the debounced fetch.
252
+ // Async path: keep previous suggestions visible only while the
253
+ // same completion token is being refined. A new token/context can
254
+ // mean a different language slot (e.g. GQL source → predicate), so
255
+ // stale rows would be actively misleading.
256
+ const prev = completionState();
257
+ if (prev && (prev.ctx.start !== ctx.start || prev.ctx.completion !== ctx.completion)) {
258
+ setCompletionState(null);
259
+ closeDropdown();
260
+ }
244
261
  setError(null);
245
262
  setLoading(true);
246
263
  lastAsyncCompletion = ctx.completion;
247
264
  lastAsyncQuery = ctx.query;
248
265
  lastAsyncCtx = suggestCtx;
249
- debouncedFetch.debouncedFn(ctx, suggestCtx, result.promise, signal);
266
+ const promise = result.promise.catch((e: unknown) => {
267
+ if (signal.aborted || isAbortError(e)) return [];
268
+ throw e;
269
+ });
270
+ debouncedFetch.debouncedFn(ctx, suggestCtx, promise, signal);
250
271
  };
251
272
 
252
273
  /** Take a fresh suggestion list, filter to usable ones, and merge
@@ -308,7 +329,7 @@ const AutocompleteEditor = (props: AutocompleteEditorProps) => {
308
329
  applySuggestionList(ctx, list);
309
330
  } catch (e: unknown) {
310
331
  if (signal.aborted) return;
311
- if (e && typeof e === "object" && "name" in e && (e as { name: string }).name === "AbortError") return;
332
+ if (isAbortError(e)) return;
312
333
  setLoading(false);
313
334
  setError(e instanceof Error ? e.message : String(e));
314
335
  }
@@ -652,5 +673,5 @@ const AutocompleteEditor = (props: AutocompleteEditorProps) => {
652
673
  };
653
674
 
654
675
  export default AutocompleteEditor;
655
- export type { Completion, Suggestion, SuggestContext } from "../completion";
676
+ export type { Completion, SuggestContext, Suggestion } from "../completion";
656
677
  export { abbreviations } from "../completion";