aboutsummaryrefslogtreecommitdiff
path: root/source/library/state/session.svelte.ts
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-04-24 14:17:38 -0700
committernetop://ウィビ <paul@webb.page>2026-04-24 14:17:38 -0700
commit261f3bdb77799009344aab4a60686b7186ebd3b0 (patch)
tree8e87c6610a307f15f0c4b32f68b19424273fc6ad /source/library/state/session.svelte.ts
parent8a59f92d031963e23ecc84b75feecf43eb4dd146 (diff)
downloadgraphiql-261f3bdb77799009344aab4a60686b7186ebd3b0.tar.gz
graphiql-261f3bdb77799009344aab4a60686b7186ebd3b0.zip
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
Diffstat (limited to 'source/library/state/session.svelte.ts')
-rw-r--r--source/library/state/session.svelte.ts53
1 files changed, 51 insertions, 2 deletions
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<T>(value: unknown): value is AsyncIterable<T> {
+ return typeof value === "object" &&
+ value !== null &&
+ typeof (value as AsyncIterable<T>)[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<string>("");
tabs = $state<Tab[]>([]);
@@ -115,12 +128,15 @@ export class SessionStore {
tab.titleDirty = true;
}
- async run(fetcher: Fetcher): Promise<boolean> {
+ async run(fetcher: Fetcher, options: RunOptions = {}): Promise<boolean> {
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<FetcherResult>(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) {