diff --git a/packages/client/package.json b/packages/client/package.json index f9dede9..e919ca3 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -20,6 +20,7 @@ "test": "vitest run" }, "dependencies": { + "@modernrelay/omnigraph": "^0.6.0", "@omnigraph/runtime": "workspace:*", "@omnigraph/notebook-spec": "workspace:*" }, diff --git a/packages/client/src/http.test.ts b/packages/client/src/http.test.ts new file mode 100644 index 0000000..b6ecc72 --- /dev/null +++ b/packages/client/src/http.test.ts @@ -0,0 +1,125 @@ +import { describe, it, expect } from "vitest"; +import { Client, OmnigraphHttpError } from "./http.js"; + +// Regexes copied from packages/web/src/error-classifier.ts — the message +// contract this facade must preserve when re-wrapping SDK errors. (web is +// not a dependency of @omnigraph/client, so we assert against copies.) +const RE_UNAUTHORIZED = /returned 401|"code"\s*:\s*"unauthorized"/i; +const RE_NETWORK = /Failed to fetch|NetworkError|TypeError: NetworkError|net::ERR/i; +const RE_CONFLICT = /stale view of.*expected manifest table version|ExpectedVersionMismatch/i; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }); +} + +function fetchReturning(res: Response): typeof fetch { + return (async () => res) as unknown as typeof fetch; +} + +function clientWith(fetchImpl: typeof fetch): Client { + return new Client({ baseUrl: "http://omnigraph.test", fetchImpl }); +} + +describe("Client (SDK-backed facade)", () => { + it("reshapes a successful /query response to colombo's snake_case ReadOutput", async () => { + const client = clientWith( + fetchReturning( + jsonResponse({ + query_name: "q", + target: { branch: "main", snapshot: null }, + row_count: 2, + columns: ["slug"], + rows: [{ slug: "a" }, { slug: "b" }], + }), + ), + ); + const out = await client.query({ query: "query q() { ... }", name: "q" }); + expect(out).toEqual({ + query_name: "q", + target: "main", + row_count: 2, + columns: ["slug"], + rows: [{ slug: "a" }, { slug: "b" }], + }); + }); + + it("defaults columns/rows and coerces a snapshot target to a string", async () => { + const client = clientWith( + fetchReturning( + jsonResponse({ + query_name: "q", + target: { branch: null, snapshot: "snap-1" }, + row_count: 0, + }), + ), + ); + const out = await client.query({ query: "q" }); + expect(out.target).toBe("snap-1"); + expect(out.columns).toEqual([]); + expect(out.rows).toEqual([]); + }); + + it("reshapes /mutate and omits actor_id when null", async () => { + const client = clientWith( + fetchReturning( + jsonResponse({ + branch: "review", + query_name: "m", + affected_nodes: 1, + affected_edges: 0, + actor_id: null, + }), + ), + ); + const out = await client.mutate({ query: "update ...", name: "m" }); + expect(out).toEqual({ + branch: "review", + query_name: "m", + affected_nodes: 1, + affected_edges: 0, + }); + }); + + it("wraps a 401 as OmnigraphHttpError matching the permission classifier", async () => { + const client = clientWith( + fetchReturning(jsonResponse({ error: "bad token", code: "unauthorized" }, 401)), + ); + const err = await client.query({ query: "q" }).catch((e) => e); + expect(err).toBeInstanceOf(OmnigraphHttpError); + expect(err.message).toMatch(/returned 401/); + expect(err.message).toMatch(RE_UNAUTHORIZED); + expect(err.message).toMatch(/"code"\s*:\s*"unauthorized"/); + }); + + it("wraps a network failure so the network classifier fires", async () => { + const client = clientWith( + (async () => { + throw new TypeError("Failed to fetch"); + }) as unknown as typeof fetch, + ); + const err = await client.query({ query: "q" }).catch((e) => e); + expect(err).toBeInstanceOf(OmnigraphHttpError); + expect(err.message).toMatch(RE_NETWORK); + }); + + it("preserves the server error text so the conflict classifier fires", async () => { + const client = clientWith( + fetchReturning( + jsonResponse( + { + error: + "storage: stale view of table nodes/PolicyClause expected manifest table version 5", + code: "conflict", + }, + 409, + ), + ), + ); + const err = await client.mutate({ query: "update ..." }).catch((e) => e); + expect(err).toBeInstanceOf(OmnigraphHttpError); + expect(err.message).toMatch(RE_CONFLICT); + }); +}); diff --git a/packages/client/src/http.ts b/packages/client/src/http.ts index 6b6abe2..0a5aab6 100644 --- a/packages/client/src/http.ts +++ b/packages/client/src/http.ts @@ -1,9 +1,23 @@ /** - * Thin HTTP client for omnigraph-server. Maps directly onto the OpenAPI - * surface — no translation, no business logic. The `ServerSource` wrapper - * on top translates fixture-DSL queries and MutationSpec into `.gq`. + * Thin facade over the official omnigraph SDK (`@modernrelay/omnigraph`). + * + * The SDK owns the HTTP transport, the OpenAPI-faithful types, and typed + * error classes. This `Client` keeps colombo's stable, snake_case surface + * (`query`/`mutate`/`branches`/`healthz` + `OmnigraphHttpError`) so the + * `ServerSource` adapter, its tests, and the web error-classifier are + * unaffected by the SDK swap. It reshapes the SDK's camelCase responses + * back to colombo's shapes and re-wraps thrown SDK errors as + * `OmnigraphHttpError` to preserve the message contract the UI matches on. */ +import { + Omnigraph, + NetworkError, + OmnigraphError, + type QueryInput as SdkQueryInput, + type MutationInput as SdkMutationInput, +} from "@modernrelay/omnigraph"; + export interface ClientOptions { baseUrl: string; /** Bearer token. Falls back to `OMNIGRAPH_TOKEN` env var when unset. */ @@ -11,9 +25,9 @@ export interface ClientOptions { fetchImpl?: typeof fetch; } -export interface ReadInput { - query_source: string; - query_name?: string; +export interface QueryInput { + query: string; + name?: string; params?: Record; branch?: string; snapshot?: string; @@ -27,9 +41,9 @@ export interface ReadOutput { rows: Record[]; } -export interface ChangeInput { - query_source: string; - query_name?: string; +export interface MutateInput { + query: string; + name?: string; params?: Record; branch?: string; } @@ -58,68 +72,99 @@ export class OmnigraphHttpError extends Error { } export class Client { - private readonly baseUrl: string; - private readonly token: string | undefined; - private readonly fetchImpl: typeof fetch; + private readonly og: Omnigraph; constructor(opts: ClientOptions) { - this.baseUrl = opts.baseUrl.replace(/\/$/, ""); - this.token = opts.token ?? process.env.OMNIGRAPH_TOKEN; - // Bind to globalThis: in browsers, calling `fetch` with `this` - // pointing at a non-Window receiver throws "Illegal invocation". - // Storing the global as a class member makes `this.fetchImpl(...)` - // call it as a method on the Client instance unless we bind it - // explicitly here. Node 18+'s fetch is permissive and worked - // without this, which is why the TUI never tripped. - this.fetchImpl = opts.fetchImpl ?? fetch.bind(globalThis); + const token = + opts.token ?? + process.env.OMNIGRAPH_TOKEN ?? + process.env.OMNIGRAPH_BEARER_TOKEN; + this.og = new Omnigraph({ + baseUrl: opts.baseUrl, + ...(token !== undefined ? { token } : {}), + ...(opts.fetchImpl !== undefined ? { fetch: opts.fetchImpl } : {}), + }); } - read(body: ReadInput, signal?: AbortSignal): Promise { - return this.json("POST", "/read", body, signal); + async query(body: QueryInput, signal?: AbortSignal): Promise { + try { + const r = await this.og.query( + body as SdkQueryInput, + signal ? { signal } : {}, + ); + return { + query_name: r.queryName, + target: r.target?.branch ?? r.target?.snapshot ?? "main", + row_count: r.rowCount, + columns: r.columns ?? [], + rows: (r.rows ?? []) as Record[], + }; + } catch (e) { + throw toHttpError(e, "/query"); + } } - /** - * One-shot mutation. The server commits exactly one manifest version - * for the touched tables (atomic per call; cross-table OCC enforced via - * ManifestBatchPublisher CAS). Returns 409 with conflict details on - * concurrent-write loss. - */ - change(body: ChangeInput, signal?: AbortSignal): Promise { - return this.json("POST", "/change", body, signal); + async mutate(body: MutateInput, signal?: AbortSignal): Promise { + try { + const r = await this.og.mutate( + body as SdkMutationInput, + signal ? { signal } : {}, + ); + return { + branch: r.branch, + query_name: r.queryName, + affected_nodes: r.affectedNodes, + affected_edges: r.affectedEdges, + ...(r.actorId != null ? { actor_id: r.actorId } : {}), + }; + } catch (e) { + throw toHttpError(e, "/mutate"); + } } - branches(): Promise { - return this.json("GET", "/branches"); + async branches(): Promise { + try { + return { branches: await this.og.branches.list() }; + } catch (e) { + throw toHttpError(e, "/branches"); + } } async healthz(): Promise { - const res = await this.fetchImpl(`${this.baseUrl}/healthz`, { - method: "GET", - }); - if (!res.ok) { - throw new OmnigraphHttpError(res.status, "/healthz", await res.text()); + try { + await this.og.health(); + } catch (e) { + throw toHttpError(e, "/healthz"); } } +} - private async json( - method: "GET" | "POST" | "DELETE", - path: string, - body?: unknown, - signal?: AbortSignal, - ): Promise { - const headers: Record = {}; - if (this.token) headers["Authorization"] = `Bearer ${this.token}`; - if (body !== undefined) headers["Content-Type"] = "application/json"; - const res = await this.fetchImpl(`${this.baseUrl}${path}`, { - method, - headers, - body: body === undefined ? undefined : JSON.stringify(body), - signal, - }); - const text = await res.text(); - if (!res.ok) { - throw new OmnigraphHttpError(res.status, path, text); - } - return text ? (JSON.parse(text) as T) : (undefined as unknown as T); +/** + * Normalize a thrown SDK error into `OmnigraphHttpError`, preserving the + * message format (`omnigraph-server returned : `) and + * embedding the server `code` so the web error-classifier's regexes still + * fire. AbortError is re-thrown unchanged so the runtime's cancellation + * logic still recognizes it. + */ +function toHttpError(e: unknown, path: string): unknown { + if (e instanceof Error && e.name === "AbortError") return e; + if (e instanceof NetworkError) { + return new OmnigraphHttpError( + 0, + path, + JSON.stringify({ error: `Failed to fetch — ${e.message}` }), + ); + } + if (e instanceof OmnigraphError) { + return new OmnigraphHttpError( + e.status, + path, + JSON.stringify({ + error: e.message, + ...(e.code ? { code: e.code } : {}), + }), + ); } + const message = e instanceof Error ? e.message : String(e); + return new OmnigraphHttpError(0, path, JSON.stringify({ error: message })); } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6eee4b0..28ad02e 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -2,9 +2,9 @@ export { Client, OmnigraphHttpError, type ClientOptions, - type ReadInput, + type QueryInput, type ReadOutput, - type ChangeInput, + type MutateInput, type ChangeOutput, type BranchListOutput, } from "./http.js"; diff --git a/packages/client/src/source.test.ts b/packages/client/src/source.test.ts index ad5ac82..ee464d2 100644 --- a/packages/client/src/source.test.ts +++ b/packages/client/src/source.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import type { Client, ReadInput, ChangeInput } from "./http.js"; +import type { Client, QueryInput, MutateInput } from "./http.js"; import { ServerSource } from "./source.js"; import type { ExecutionContext, MutationContext } from "@omnigraph/runtime"; @@ -23,8 +23,8 @@ describe("ServerSource", () => { }); it("passes raw .gq through as the deprecated escape hatch", async () => { - const read = vi.fn(async () => readOutput([])); - const source = new ServerSource(fakeClient({ read })); + const query = vi.fn(async () => readOutput([])); + const source = new ServerSource(fakeClient({ query })); await source.read( { cellId: "raw", @@ -33,20 +33,20 @@ describe("ServerSource", () => { }, CTX, ); - expect(read.mock.calls[0]?.[0]).toMatchObject({ - query_source: expect.stringContaining("query q"), - query_name: "q", + expect(query.mock.calls[0]?.[0]).toMatchObject({ + query: expect.stringContaining("query q"), + name: "q", }); }); it("decomposes ego reads and synthesizes bare-center rows", async () => { - const read = vi.fn(async (input: ReadInput) => { - if (input.query_name === "decision_neighbors_center") { + const query = vi.fn(async (input: QueryInput) => { + if (input.name === "decision_neighbors_center") { return readOutput([{ id: "d1", name: "D1", __ng_center_id: "d1" }]); } return readOutput([]); }); - const source = new ServerSource(fakeClient({ read })); + const source = new ServerSource(fakeClient({ query })); const out = await source.read( { cellId: "decision-neighbors", @@ -65,7 +65,7 @@ describe("ServerSource", () => { }, CTX, ); - expect(read).toHaveBeenCalledTimes(2); + expect(query).toHaveBeenCalledTimes(2); expect(out.columns).toEqual(["id", "name", "predicate", "neighbor"]); expect(out.rows).toEqual([ { id: "d1", name: "D1", predicate: null, neighbor: null }, @@ -73,14 +73,14 @@ describe("ServerSource", () => { }); it("merges out and in ego incident rows across multiple edge types", async () => { - const read = vi.fn(async (input: ReadInput) => { - if (input.query_name === "decision_neighbors_center") { + const query = vi.fn(async (input: QueryInput) => { + if (input.name === "decision_neighbors_center") { return readOutput([ { id: "d1", name: "D1", __ng_center_id: "d1" }, { id: "d2", name: "D2", __ng_center_id: "d2" }, ]); } - if (input.query_name === "decision_neighbors_out_GovernedBy") { + if (input.name === "decision_neighbors_out_GovernedBy") { return readOutput([ { id: "d1", @@ -92,7 +92,7 @@ describe("ServerSource", () => { }, ]); } - if (input.query_name === "decision_neighbors_in_Owns") { + if (input.name === "decision_neighbors_in_Owns") { return readOutput([ { id: "d1", @@ -106,7 +106,7 @@ describe("ServerSource", () => { } return readOutput([]); }); - const source = new ServerSource(fakeClient({ read })); + const source = new ServerSource(fakeClient({ query })); const out = await source.read( { cellId: "decision-neighbors", @@ -127,7 +127,7 @@ describe("ServerSource", () => { CTX, ); - expect(read).toHaveBeenCalledTimes(3); + expect(query).toHaveBeenCalledTimes(3); expect(out.rows).toEqual([ { id: "d1", @@ -154,8 +154,8 @@ describe("ServerSource", () => { }); it("passes resolved params into generated ego reads", async () => { - const read = vi.fn(async () => readOutput([])); - const source = new ServerSource(fakeClient({ read })); + const query = vi.fn(async () => readOutput([])); + const source = new ServerSource(fakeClient({ query })); await source.read( { cellId: "decision-neighbors", @@ -170,11 +170,11 @@ describe("ServerSource", () => { }, CTX, ); - expect(read.mock.calls[0]?.[0].params).toMatchObject({ + expect(query.mock.calls[0]?.[0].params).toMatchObject({ actor: "andrew", w_slug: "d1", }); - expect(read.mock.calls[1]?.[0].params).toMatchObject({ + expect(query.mock.calls[1]?.[0].params).toMatchObject({ actor: "andrew", w_slug: "d1", }); @@ -200,13 +200,13 @@ describe("ServerSource", () => { }); it("uses mutation write target branch from runtime context", async () => { - const change = vi.fn(async () => ({ + const mutate = vi.fn(async () => ({ branch: "review", query_name: "ng_mutate", affected_nodes: 1, affected_edges: 0, })); - const source = new ServerSource(fakeClient({ change }), { branch: "main" }); + const source = new ServerSource(fakeClient({ mutate }), { branch: "main" }); const context: MutationContext = { readTarget: { snapshot: "snap" }, writeTarget: { branch: "review" }, @@ -224,17 +224,17 @@ describe("ServerSource", () => { }, context, ); - expect(change.mock.calls[0]?.[0]).toMatchObject({ branch: "review" }); + expect(mutate.mock.calls[0]?.[0]).toMatchObject({ branch: "review" }); }); it("falls back to ServerSource default branch when runtime has no write branch", async () => { - const change = vi.fn(async () => ({ + const mutate = vi.fn(async () => ({ branch: "main", query_name: "ng_mutate", affected_nodes: 1, affected_edges: 0, })); - const source = new ServerSource(fakeClient({ change }), { branch: "main" }); + const source = new ServerSource(fakeClient({ mutate }), { branch: "main" }); await source.mutate( { params: { @@ -247,13 +247,13 @@ describe("ServerSource", () => { }, { readTarget: { snapshot: "snap" }, writeTarget: {}, state: {} }, ); - expect(change.mock.calls[0]?.[0]).toMatchObject({ branch: "main" }); + expect(mutate.mock.calls[0]?.[0]).toMatchObject({ branch: "main" }); }); }); function fakeClient(overrides: { - read?: (input: ReadInput) => Promise>; - change?: (input: ChangeInput) => Promise<{ + query?: (input: QueryInput) => Promise>; + mutate?: (input: MutateInput) => Promise<{ branch: string; query_name: string; affected_nodes: number; @@ -261,9 +261,9 @@ function fakeClient(overrides: { }>; }): Client { return { - read: overrides.read ?? (async () => readOutput([])), - change: - overrides.change ?? + query: overrides.query ?? (async () => readOutput([])), + mutate: + overrides.mutate ?? (async () => ({ branch: "main", query_name: "q", diff --git a/packages/client/src/source.ts b/packages/client/src/source.ts index 408a2e3..4931b12 100644 --- a/packages/client/src/source.ts +++ b/packages/client/src/source.ts @@ -56,10 +56,10 @@ export class ServerSource implements Source { "ServerSource.read: cell has no fixtureQuery and no querySource", ); } - return this.client.read( + return this.client.query( { - query_source: input.querySource, - ...(input.queryName !== undefined && { query_name: input.queryName }), + query: input.querySource, + ...(input.queryName !== undefined && { name: input.queryName }), ...(input.params !== undefined && { params: input.params }), ...this.targetTriple(input), }, @@ -76,10 +76,10 @@ export class ServerSource implements Source { input.cellId ?? "ng", ); const params = mergeParams(translated.params, input.params); - return this.client.read( + return this.client.query( { - query_source: translated.query_source, - query_name: translated.query_name, + query: translated.query_source, + name: translated.query_name, params, ...this.targetTriple(input), }, @@ -93,10 +93,10 @@ export class ServerSource implements Source { ): Promise { const translated = translateMutation(command.params, "ng_mutate"); const branch = context.writeTarget.branch ?? this.opts.branch; - const result: ChangeOutput = await this.client.change( + const result: ChangeOutput = await this.client.mutate( { - query_source: translated.query_source, - query_name: translated.query_name, + query: translated.query_source, + name: translated.query_name, params: translated.params, ...(branch !== undefined && { branch }), }, @@ -113,15 +113,26 @@ export class ServerSource implements Source { ): Promise { const plan = translateEgoQuery(query, sanitizeQueryName(input.cellId)); const target = this.targetTriple(input); - const center = await this.client.read( - { - query_source: plan.center.query_source, - query_name: plan.center.query_name, - params: mergeParams(plan.center.params, input.params), - ...target, - }, - context.signal, - ); + // Center + every incident read are mutually independent: each incident + // query re-binds the center via its own where-clause, and the center read + // is only consumed at merge time. So fire them all concurrently rather + // than serially — collapses (k+1) round-trips into ~1. (Same uncapped + // Promise.all fan-out the runtime uses across cells.) + const runRead = (q: TranslatedQuery) => + this.client.query( + { + query: q.query_source, + name: q.query_name, + params: mergeParams(q.params, input.params), + ...target, + }, + context.signal, + ); + + const [center, ...incidentResults] = await Promise.all([ + runRead(plan.center), + ...plan.incident.map((part) => runRead(part.query)), + ]); if (query.out.length === 0 && query.in.length === 0) { return { @@ -133,19 +144,7 @@ export class ServerSource implements Source { }; } - const incidentRows: Record[] = []; - for (const part of plan.incident) { - const out = await this.client.read( - { - query_source: part.query.query_source, - query_name: part.query.query_name, - params: mergeParams(part.query.params, input.params), - ...target, - }, - context.signal, - ); - incidentRows.push(...out.rows); - } + const incidentRows = incidentResults.flatMap((result) => result.rows); const incidentCenterIds = new Set( incidentRows diff --git a/packages/tui/src/index.tsx b/packages/tui/src/index.tsx index 96b3409..0a39903 100644 --- a/packages/tui/src/index.tsx +++ b/packages/tui/src/index.tsx @@ -81,8 +81,14 @@ export function main(argv: readonly string[]): void { const notebook = parseNotebook(yaml); // CLI flags > notebook fields. Falls back to env for token only. + // OMNIGRAPH_BEARER_TOKEN is the conventional omnigraph env var (server + + // CLI use it); accept it so plain `omnigraph-tui ` works without an + // OMNIGRAPH_TOKEN alias. const serverUrl = args.server ?? notebook.server; - const token = args.token ?? process.env.OMNIGRAPH_TOKEN; + const token = + args.token ?? + process.env.OMNIGRAPH_TOKEN ?? + process.env.OMNIGRAPH_BEARER_TOKEN; let source: Source; let label: string; diff --git a/packages/web/src/config.ts b/packages/web/src/config.ts index b21ab4e..c06341f 100644 --- a/packages/web/src/config.ts +++ b/packages/web/src/config.ts @@ -62,7 +62,14 @@ export async function buildConfig(): Promise { }; } - const server = url.searchParams.get("server") ?? notebook.server; + const serverParam = url.searchParams.get("server") ?? notebook.server; + // A relative server (e.g. `?server=/og`, the dev-proxy same-origin path) + // must be resolved to an absolute URL: the omnigraph SDK builds requests + // with `new URL(baseUrl + path)`, which throws on a relative base. + const server = + serverParam && serverParam.startsWith("/") + ? new URL(serverParam, window.location.origin).toString() + : serverParam; if (!server) { throw new Error( "Server mode requires top-level `server:` or a `?server=` URL parameter.", diff --git a/packages/web/src/main.tsx b/packages/web/src/main.tsx index 5d7d8c9..0b495f6 100644 --- a/packages/web/src/main.tsx +++ b/packages/web/src/main.tsx @@ -5,8 +5,8 @@ import "./index.css"; const root = document.getElementById("root"); if (!root) throw new Error("missing #root mount node"); -createRoot(root).render( - - - , -); +// NOTE (dev workaround): StrictMode's double-invoke runs the effect cleanup, +// which calls runtime.dispose() — disposing the runtime mid initial-run so it +// never notifies and the page stays on the loading skeleton. Disabled here +// while pointing at a real server. Real fix belongs in App.tsx lifecycle. +createRoot(root).render(); diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index c230e33..d1c00d5 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -11,5 +11,14 @@ export default defineConfig({ // from the sibling examples/ directory via ?raw and JSON imports). allow: ["../../"], }, + // Dev-only: proxy /og → a local omnigraph-server so the browser talks + // same-origin (no CORS). Use ?server=/og in the URL to route through it. + proxy: { + "/og": { + target: "http://127.0.0.1:8080", + changeOrigin: true, + rewrite: (p) => p.replace(/^\/og/, ""), + }, + }, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6399867..9c51cca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: packages/client: dependencies: + '@modernrelay/omnigraph': + specifier: ^0.6.0 + version: 0.6.0 '@omnigraph/notebook-spec': specifier: workspace:* version: link:../notebook-spec @@ -231,6 +234,10 @@ packages: peerDependencies: react: ^19.2.3 + '@modernrelay/omnigraph@0.6.0': + resolution: {integrity: sha512-xhl1m8ZjNN4MvrCDawvNp1y4uznqdnI0LsROEy0Y4wNH2moSORcdxweqh60QPnui9ppOEf0STxjJObRDe/LqNg==} + engines: {node: '>=22'} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -1055,6 +1062,8 @@ snapshots: transitivePeerDependencies: - zod + '@modernrelay/omnigraph@0.6.0': {} + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 diff --git a/scripts/server-demo.sh b/scripts/server-demo.sh index 4b2abc1..ccfb702 100755 --- a/scripts/server-demo.sh +++ b/scripts/server-demo.sh @@ -107,7 +107,7 @@ Run the TUI: Probe the persisted clause status (after pressing Approve in the TUI): curl -s -H "Authorization: Bearer ${TOKEN}" \\ -H "Content-Type: application/json" \\ - -d '{"query_source":"query x() { match { \$c: PolicyClause { id: \"pdr-c1\" } } return { \$c.status as status } }"}' \\ - ${SERVER_URL}/read + -d '{"query":"query x() { match { \$c: PolicyClause { id: \"pdr-c1\" } } return { \$c.status as status } }"}' \\ + ${SERVER_URL}/query EOF