From 261f3bdb77799009344aab4a60686b7186ebd3b0 Mon Sep 17 00:00:00 2001 From: "netop://ウィビ" Date: Fri, 24 Apr 2026 14:17:38 -0700 Subject: Implement v0.4 subscriptions + v0.5 theming and plugin slots - SSE and WebSocket fetchers via graphql-sse and graphql-ws, returning AsyncIterable results - SessionStore.run consumes AsyncIterable streams with subscriptionMode "append" (timestamped) or "replace" and honors an AbortSignal for cancellation - Chrome CSS variables documented in styles/theme.scss with prefers-color-scheme light/dark gating and .graphiql-light override - Svelte 5 snippet slots on GraphiQL: toolbarExtras, tabExtras, resultFooter --- source/library/state/session.svelte.ts | 53 ++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) (limited to 'source/library/state') diff --git a/source/library/state/session.svelte.ts b/source/library/state/session.svelte.ts index d9f52ff..9345d29 100644 --- a/source/library/state/session.svelte.ts +++ b/source/library/state/session.svelte.ts @@ -3,7 +3,7 @@ /*** UTILITY ------------------------------------------ ***/ -import type { Fetcher } from "../fetcher/types.ts"; +import type { Fetcher, FetcherResult } from "../fetcher/types.ts"; import { deriveTitle, parseOperations, @@ -13,6 +13,12 @@ import type { Storage } from "./storage.ts"; const STORAGE_KEY = "session"; +function isAsyncIterable(value: unknown): value is AsyncIterable { + return typeof value === "object" && + value !== null && + typeof (value as AsyncIterable)[Symbol.asyncIterator] === "function"; +} + type Snapshot = { activeId: string; tabs: Tab[]; @@ -40,6 +46,13 @@ export type TabSeed = { variables?: string; }; +export type SubscriptionMode = "append" | "replace"; + +export type RunOptions = { + signal?: AbortSignal; + subscriptionMode?: SubscriptionMode; +}; + export class SessionStore { activeId = $state(""); tabs = $state([]); @@ -115,12 +128,15 @@ export class SessionStore { tab.titleDirty = true; } - async run(fetcher: Fetcher): Promise { + async run(fetcher: Fetcher, options: RunOptions = {}): Promise { const tab = this.active; if (!tab) return false; + const mode = options.subscriptionMode ?? "append"; + const signal = options.signal; + try { const variables = tab.variables.trim() ? JSON.parse(tab.variables) : {}; const headers = tab.headers.trim() ? JSON.parse(tab.headers) : {}; @@ -132,6 +148,39 @@ export class SessionStore { variables }); + if (isAsyncIterable(result)) { + tab.result = ""; + const iterator = result[Symbol.asyncIterator](); + + try { + while (true) { + if (signal?.aborted) { + await iterator.return?.(); + break; + } + + const step = await iterator.next(); + + if (step.done) + break; + + const payload = JSON.stringify(step.value, null, 2); + + if (mode === "append") { + const stamp = new Date().toISOString(); + const chunk = `// ${stamp}\n${payload}\n`; + tab.result = tab.result ? `${tab.result}\n${chunk}` : chunk; + } else { + tab.result = payload; + } + } + } finally { + await iterator.return?.(); + } + + return true; + } + tab.result = JSON.stringify(result, null, 2); return true; } catch(err) { -- cgit v1.2.3