@apollo/client-ai-apps 0.1.0

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.
Files changed (45) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/pr.yaml +24 -0
  4. package/.github/workflows/release.yaml +36 -0
  5. package/LICENSE +21 -0
  6. package/dist/apollo_client/client.d.ts +14 -0
  7. package/dist/apollo_client/provider.d.ts +5 -0
  8. package/dist/hooks/useOpenAiGlobal.d.ts +2 -0
  9. package/dist/hooks/useRequestDisplayMode.d.ts +4 -0
  10. package/dist/hooks/useSendFollowUpMessage.d.ts +1 -0
  11. package/dist/hooks/useToolEffect.d.ts +6 -0
  12. package/dist/hooks/useToolInput.d.ts +1 -0
  13. package/dist/hooks/useToolName.d.ts +1 -0
  14. package/dist/index.d.ts +11 -0
  15. package/dist/index.js +177 -0
  16. package/dist/types/application-manifest.d.ts +29 -0
  17. package/dist/types/openai.d.ts +73 -0
  18. package/dist/vite/index.d.ts +1 -0
  19. package/dist/vite/index.js +210 -0
  20. package/dist/vite/operation_manifest_plugin.d.ts +10 -0
  21. package/package.json +53 -0
  22. package/scripts/build-vite.mjs +18 -0
  23. package/scripts/build.mjs +7 -0
  24. package/scripts/dev.mjs +21 -0
  25. package/scripts/shared.mjs +9 -0
  26. package/src/apollo_client/client.test.ts +411 -0
  27. package/src/apollo_client/client.ts +90 -0
  28. package/src/apollo_client/provider.test.tsx +41 -0
  29. package/src/apollo_client/provider.tsx +32 -0
  30. package/src/hooks/useCallTool.test.ts +46 -0
  31. package/src/hooks/useCallTool.ts +8 -0
  32. package/src/hooks/useOpenAiGlobal.test.ts +54 -0
  33. package/src/hooks/useOpenAiGlobal.ts +26 -0
  34. package/src/hooks/useRequestDisplayMode.ts +7 -0
  35. package/src/hooks/useSendFollowUpMessage.ts +7 -0
  36. package/src/hooks/useToolEffect.tsx +41 -0
  37. package/src/hooks/useToolInput.ts +7 -0
  38. package/src/hooks/useToolName.ts +7 -0
  39. package/src/index.ts +12 -0
  40. package/src/types/application-manifest.ts +32 -0
  41. package/src/types/openai.ts +90 -0
  42. package/src/vite/index.ts +1 -0
  43. package/src/vite/operation_manifest_plugin.ts +274 -0
  44. package/vitest-setup.ts +1 -0
  45. package/vitest.config.ts +12 -0
@@ -0,0 +1,8 @@
1
+ # Changesets
2
+
3
+ Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
+ with multi-package repos, or single-package repos to help you version and publish your code. You can
5
+ find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
+
7
+ We have a quick list of common questions to get you started engaging with this project in
8
+ [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3
+ "changelog": "@changesets/cli/changelog",
4
+ "commit": false,
5
+ "fixed": [],
6
+ "linked": [],
7
+ "access": "restricted",
8
+ "baseBranch": "main",
9
+ "updateInternalDependencies": "patch",
10
+ "ignore": []
11
+ }
@@ -0,0 +1,24 @@
1
+ name: Build on PR
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - name: Checkout code
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Set up Node.js
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: 20.x
19
+
20
+ - name: Install dependencies
21
+ run: npm ci
22
+
23
+ - name: Run build
24
+ run: npm run build
@@ -0,0 +1,36 @@
1
+ name: Release new Version
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ release:
8
+ name: Release
9
+ if: github.repository == 'apollographql/apollo-ai-apps-client'
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write # to create release (changesets/action)
13
+ issues: write # to post issue comments (changesets/action)
14
+ pull-requests: write # to create pull request (changesets/action)
15
+ id-token: write # to use OpenID Connect token for provenance (changesets/action)
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Setup Node.js 20.x
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: 20.x
23
+
24
+ - name: Install dependencies
25
+ shell: bash
26
+ run: npm ci
27
+
28
+ - name: Create Release Pull Request or Publish to npm
29
+ uses: changesets/action@v1
30
+ with:
31
+ publish: npm run build
32
+ version: changeset:version
33
+ env:
34
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
36
+ NPM_CONFIG_PROVENANCE: true
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Apollo GraphQL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ import { ApolloClient } from "@apollo/client";
2
+ import "../types/openai";
3
+ import { ApplicationManifest } from "../types/application-manifest";
4
+ type ExtendedApolloClientOptions = Omit<ApolloClient.Options, "link" | "cache"> & {
5
+ link?: ApolloClient.Options["link"];
6
+ cache?: ApolloClient.Options["cache"];
7
+ manifest: ApplicationManifest;
8
+ };
9
+ export declare class ExtendedApolloClient extends ApolloClient {
10
+ manifest: ApplicationManifest;
11
+ constructor(options: ExtendedApolloClientOptions);
12
+ prefetchData(): Promise<void>;
13
+ }
14
+ export {};
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ import { ExtendedApolloClient } from "./client";
3
+ export declare const ExtendedApolloProvider: ({ children, client, }: React.PropsWithChildren<{
4
+ client: ExtendedApolloClient;
5
+ }>) => React.JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { OpenAiGlobals } from "../types/openai";
2
+ export declare function useOpenAiGlobal<K extends keyof OpenAiGlobals>(key: K): OpenAiGlobals[K];
@@ -0,0 +1,4 @@
1
+ import { DisplayMode } from "../types/openai";
2
+ export declare const useRequestDisplayMode: () => (args: {
3
+ mode: DisplayMode;
4
+ }) => Promise<void>;
@@ -0,0 +1 @@
1
+ export declare const useSendFollowUpMessage: () => (prompt: string) => Promise<void>;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ export declare function ToolUseProvider({ children, appName }: {
3
+ children: any;
4
+ appName: string;
5
+ }): React.JSX.Element;
6
+ export declare const useToolEffect: (toolName: string | string[], effect: (toolInput: any) => void, deps?: React.DependencyList) => void;
@@ -0,0 +1 @@
1
+ export declare const useToolInput: () => any;
@@ -0,0 +1 @@
1
+ export declare const useToolName: () => any;
@@ -0,0 +1,11 @@
1
+ export * from "./types/openai";
2
+ export * from "./types/application-manifest";
3
+ export * from "./hooks/useOpenAiGlobal";
4
+ export * from "./hooks/useToolName";
5
+ export * from "./hooks/useToolInput";
6
+ export * from "./hooks/useSendFollowUpMessage";
7
+ export * from "./hooks/useRequestDisplayMode";
8
+ export * from "./hooks/useToolEffect";
9
+ export * from "@apollo/client";
10
+ export { ExtendedApolloClient as ApolloClient } from "./apollo_client/client";
11
+ export { ExtendedApolloProvider as ApolloProvider } from "./apollo_client/provider";
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ // src/types/openai.ts
2
+ var SET_GLOBALS_EVENT_TYPE = "openai:set_globals";
3
+ var SetGlobalsEvent = class extends CustomEvent {
4
+ type = SET_GLOBALS_EVENT_TYPE;
5
+ };
6
+
7
+ // src/hooks/useOpenAiGlobal.ts
8
+ import { useSyncExternalStore } from "react";
9
+ function useOpenAiGlobal(key) {
10
+ return useSyncExternalStore(
11
+ (onChange) => {
12
+ const handleSetGlobal = (event) => {
13
+ const value = event.detail.globals[key];
14
+ if (value === void 0) {
15
+ return;
16
+ }
17
+ onChange();
18
+ };
19
+ window.addEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal, {
20
+ passive: true
21
+ });
22
+ return () => {
23
+ window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal);
24
+ };
25
+ },
26
+ () => window.openai[key]
27
+ );
28
+ }
29
+
30
+ // src/hooks/useToolName.ts
31
+ var useToolName = () => {
32
+ const toolResponseMetadata = useOpenAiGlobal("toolResponseMetadata");
33
+ return toolResponseMetadata?.toolName;
34
+ };
35
+
36
+ // src/hooks/useToolInput.ts
37
+ var useToolInput = () => {
38
+ const toolInput = useOpenAiGlobal("toolInput");
39
+ return toolInput;
40
+ };
41
+
42
+ // src/hooks/useSendFollowUpMessage.ts
43
+ var useSendFollowUpMessage = () => {
44
+ return async (prompt) => {
45
+ await window.openai?.sendFollowUpMessage({
46
+ prompt
47
+ });
48
+ };
49
+ };
50
+
51
+ // src/hooks/useRequestDisplayMode.ts
52
+ var useRequestDisplayMode = () => {
53
+ return async (args) => {
54
+ await window.openai?.requestDisplayMode(args);
55
+ };
56
+ };
57
+
58
+ // src/hooks/useToolEffect.tsx
59
+ import React, { useEffect, useState } from "react";
60
+ var ToolUseContext = React.createContext(null);
61
+ function ToolUseProvider({ children, appName }) {
62
+ const [hasNavigated, setHasNavigated] = useState(false);
63
+ return /* @__PURE__ */ React.createElement(ToolUseContext.Provider, { value: { hasNavigated, setHasNavigated, appName } }, children);
64
+ }
65
+ var useToolEffect = (toolName, effect, deps = []) => {
66
+ const ctx = React.useContext(ToolUseContext);
67
+ const fullToolName = useToolName();
68
+ const toolInput = useToolInput();
69
+ if (!ctx) throw new Error("useToolEffect must be used within ToolUseProvider");
70
+ const toolNames = Array.isArray(toolName) ? toolName : [toolName];
71
+ useEffect(() => {
72
+ const matches = toolNames.some((name) => fullToolName === `${ctx.appName}--${name}`);
73
+ if (!ctx.hasNavigated && matches) {
74
+ effect(toolInput);
75
+ ctx.setHasNavigated(true);
76
+ }
77
+ }, [ctx.hasNavigated, ctx.setHasNavigated, ctx.appName, toolNames, fullToolName, toolInput, ...deps]);
78
+ };
79
+
80
+ // src/index.ts
81
+ export * from "@apollo/client";
82
+
83
+ // src/apollo_client/client.ts
84
+ import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
85
+ import * as Observable from "rxjs";
86
+ import { selectHttpOptionsAndBody } from "@apollo/client/link/http";
87
+ import { fallbackHttpConfig } from "@apollo/client/link/http";
88
+ import { DocumentTransform } from "@apollo/client";
89
+ import { removeDirectivesFromDocument } from "@apollo/client/utilities/internal";
90
+ import { parse } from "graphql";
91
+ var toolCallLink = new ApolloLink((operation) => {
92
+ const context = operation.getContext();
93
+ const contextConfig = {
94
+ http: context.http,
95
+ options: context.fetchOptions,
96
+ credentials: context.credentials,
97
+ headers: context.headers
98
+ };
99
+ const { query, variables } = selectHttpOptionsAndBody(operation, fallbackHttpConfig, contextConfig).body;
100
+ return Observable.from(window.openai.callTool("execute", { query, variables })).pipe(
101
+ Observable.map((result) => ({ data: result.structuredContent.data }))
102
+ );
103
+ });
104
+ var ExtendedApolloClient = class extends ApolloClient {
105
+ manifest;
106
+ constructor(options) {
107
+ super({
108
+ link: toolCallLink,
109
+ cache: options.cache ?? new InMemoryCache(),
110
+ // Strip out the prefetch/tool directives so they don't get sent with the operation to the server
111
+ documentTransform: new DocumentTransform((document) => {
112
+ return removeDirectivesFromDocument([{ name: "prefetch" }, { name: "tool" }], document);
113
+ })
114
+ });
115
+ this.manifest = options.manifest;
116
+ }
117
+ async prefetchData() {
118
+ this.manifest.operations.forEach((operation) => {
119
+ if (operation.prefetch && operation.prefetchID && window.openai.toolOutput.prefetch[operation.prefetchID]) {
120
+ this.writeQuery({
121
+ query: parse(operation.body),
122
+ data: window.openai.toolOutput.prefetch[operation.prefetchID].data
123
+ });
124
+ }
125
+ if (operation.tools?.find(
126
+ (tool) => `${this.manifest.name}--${tool.name}` === window.openai.toolResponseMetadata.toolName
127
+ )) {
128
+ const variables = Object.keys(window.openai.toolInput).reduce(
129
+ (obj, key) => operation.variables[key] ? { ...obj, [key]: window.openai.toolInput[key] } : obj,
130
+ {}
131
+ );
132
+ this.writeQuery({
133
+ query: parse(operation.body),
134
+ data: window.openai.toolOutput.result.data,
135
+ variables
136
+ });
137
+ }
138
+ });
139
+ }
140
+ };
141
+
142
+ // src/apollo_client/provider.tsx
143
+ import React2, { useEffect as useEffect2, useState as useState2 } from "react";
144
+ import { ApolloProvider } from "@apollo/client/react";
145
+ var ExtendedApolloProvider = ({
146
+ children,
147
+ client
148
+ }) => {
149
+ const [hasPreloaded, setHasPreloaded] = useState2(false);
150
+ useEffect2(() => {
151
+ const prefetchData = async () => {
152
+ await client.prefetchData();
153
+ setHasPreloaded(true);
154
+ window.removeEventListener(SET_GLOBALS_EVENT_TYPE, prefetchData);
155
+ };
156
+ window.addEventListener(SET_GLOBALS_EVENT_TYPE, prefetchData, {
157
+ passive: true
158
+ });
159
+ if (window.openai?.toolOutput) {
160
+ window.dispatchEvent(new CustomEvent(SET_GLOBALS_EVENT_TYPE));
161
+ }
162
+ }, [setHasPreloaded]);
163
+ return hasPreloaded ? /* @__PURE__ */ React2.createElement(ApolloProvider, { client }, children) : null;
164
+ };
165
+ export {
166
+ ExtendedApolloClient as ApolloClient,
167
+ ExtendedApolloProvider as ApolloProvider,
168
+ SET_GLOBALS_EVENT_TYPE,
169
+ SetGlobalsEvent,
170
+ ToolUseProvider,
171
+ useOpenAiGlobal,
172
+ useRequestDisplayMode,
173
+ useSendFollowUpMessage,
174
+ useToolEffect,
175
+ useToolInput,
176
+ useToolName
177
+ };
@@ -0,0 +1,29 @@
1
+ export type ApplicationManifest = {
2
+ format: "apollo-ai-app-manifest";
3
+ version: "1";
4
+ name: string;
5
+ description: string;
6
+ hash: string;
7
+ resource: string;
8
+ operations: ManifestOperation[];
9
+ };
10
+ export type ManifestOperation = {
11
+ id: string;
12
+ name: string;
13
+ type: "query" | "mutation";
14
+ body: string;
15
+ variables: Record<string, string>;
16
+ prefetch: boolean;
17
+ prefetchID?: string;
18
+ tools: ManifestTool[];
19
+ };
20
+ export type ManifestTool = {
21
+ name: string;
22
+ description: string;
23
+ extraInputs?: ManifestExtraInput[];
24
+ };
25
+ export type ManifestExtraInput = {
26
+ name: string;
27
+ description: string;
28
+ type: "string" | "boolean" | "number";
29
+ };
@@ -0,0 +1,73 @@
1
+ type UnknownObject = any;
2
+ declare global {
3
+ interface Window {
4
+ openai: API<any> & OpenAiGlobals;
5
+ }
6
+ interface WindowEventMap {
7
+ [SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent;
8
+ }
9
+ }
10
+ export type OpenAiGlobals<ToolInput extends UnknownObject = UnknownObject, ToolOutput extends UnknownObject = UnknownObject, ToolResponseMetadata extends UnknownObject = UnknownObject, WidgetState extends UnknownObject = UnknownObject> = {
11
+ theme: Theme;
12
+ userAgent: UserAgent;
13
+ locale: string;
14
+ maxHeight: number;
15
+ displayMode: DisplayMode;
16
+ safeArea: SafeArea;
17
+ toolInput: ToolInput;
18
+ toolOutput: ToolOutput | null;
19
+ toolResponseMetadata: ToolResponseMetadata | null;
20
+ widgetState: WidgetState | null;
21
+ };
22
+ export type API<WidgetState extends UnknownObject> = {
23
+ /** Calls a tool on your MCP. Returns the full response. */
24
+ callTool: (name: string, args: Record<string, unknown>) => Promise<any>;
25
+ /** Triggers a followup turn in the ChatGPT conversation */
26
+ sendFollowUpMessage: (args: {
27
+ prompt: string;
28
+ }) => Promise<void>;
29
+ /** Opens an external link, redirects web page or mobile app */
30
+ openExternal(payload: {
31
+ href: string;
32
+ }): void;
33
+ /** For transitioning an app from inline to fullscreen or pip */
34
+ requestDisplayMode: (args: {
35
+ mode: DisplayMode;
36
+ }) => Promise<{
37
+ /**
38
+ * The granted display mode. The host may reject the request.
39
+ * For mobile, PiP is always coerced to fullscreen.
40
+ */
41
+ mode: DisplayMode;
42
+ }>;
43
+ setWidgetState: (state: WidgetState) => Promise<void>;
44
+ };
45
+ export declare const SET_GLOBALS_EVENT_TYPE = "openai:set_globals";
46
+ export declare class SetGlobalsEvent extends CustomEvent<{
47
+ globals: Partial<OpenAiGlobals>;
48
+ }> {
49
+ readonly type = "openai:set_globals";
50
+ }
51
+ export type CallTool = (name: string, args: Record<string, unknown>) => Promise<any>;
52
+ export type DisplayMode = "pip" | "inline" | "fullscreen";
53
+ export type Theme = "light" | "dark";
54
+ export type SafeAreaInsets = {
55
+ top: number;
56
+ bottom: number;
57
+ left: number;
58
+ right: number;
59
+ };
60
+ export type SafeArea = {
61
+ insets: SafeAreaInsets;
62
+ };
63
+ export type DeviceType = "mobile" | "tablet" | "desktop" | "unknown";
64
+ export type UserAgent = {
65
+ device: {
66
+ type: DeviceType;
67
+ };
68
+ capabilities: {
69
+ hover: boolean;
70
+ touch: boolean;
71
+ };
72
+ };
73
+ export {};
@@ -0,0 +1 @@
1
+ export * from "./operation_manifest_plugin";
@@ -0,0 +1,210 @@
1
+ // src/vite/operation_manifest_plugin.ts
2
+ import { readFileSync, writeFileSync } from "fs";
3
+ import { glob } from "glob";
4
+ import { gqlPluckFromCodeStringSync } from "@graphql-tools/graphql-tag-pluck";
5
+ import { createHash } from "crypto";
6
+ import {
7
+ Kind,
8
+ parse,
9
+ print,
10
+ visit
11
+ } from "graphql";
12
+ import { ApolloClient, ApolloLink, InMemoryCache } from "@apollo/client";
13
+ import Observable from "rxjs";
14
+ import path from "path";
15
+ import fs from "fs";
16
+ var root = process.cwd();
17
+ var getRawValue = (node) => {
18
+ switch (node.kind) {
19
+ case Kind.STRING:
20
+ case Kind.BOOLEAN:
21
+ return node.value;
22
+ case Kind.LIST:
23
+ return node.values.map(getRawValue);
24
+ case Kind.OBJECT:
25
+ return node.fields.reduce((acc, field) => {
26
+ acc[field.name.value] = getRawValue(field.value);
27
+ return acc;
28
+ }, {});
29
+ }
30
+ };
31
+ function getTypeName(type) {
32
+ let t = type;
33
+ while (t.kind === "NonNullType" || t.kind === "ListType") {
34
+ t = t.type;
35
+ }
36
+ return t.name.value;
37
+ }
38
+ var OperationManifestPlugin = () => {
39
+ const cache = /* @__PURE__ */ new Map();
40
+ let packageJson = null;
41
+ let config = null;
42
+ const clientCache = new InMemoryCache();
43
+ const client = new ApolloClient({
44
+ cache: clientCache,
45
+ link: new ApolloLink((operation) => {
46
+ const body = print(removeClientDirective(sortTopLevelDefinitions(operation.query)));
47
+ const name = operation.operationName;
48
+ const variables = operation.query.definitions.find((d) => d.kind === "OperationDefinition").variableDefinitions?.reduce(
49
+ (obj, varDef) => ({ ...obj, [varDef.variable.name.value]: getTypeName(varDef.type) }),
50
+ {}
51
+ );
52
+ const type = operation.query.definitions.find((d) => d.kind === "OperationDefinition").operation;
53
+ const prefetch = operation.query.definitions.find((d) => d.kind === "OperationDefinition").directives?.some((d) => d.name.value === "prefetch");
54
+ const id = createHash("sha256").update(body).digest("hex");
55
+ const prefetchID = prefetch ? "__anonymous" : void 0;
56
+ const tools = operation.query.definitions.find((d) => d.kind === "OperationDefinition").directives?.filter((d) => d.name.value === "tool").map((directive) => {
57
+ const directiveArguments = directive.arguments?.reduce((obj, arg) => ({ ...obj, [arg.name.value]: getRawValue(arg.value) }), {}) ?? {};
58
+ return {
59
+ name: directiveArguments["name"],
60
+ description: directiveArguments["description"],
61
+ extraInputs: directiveArguments["extraInputs"]
62
+ };
63
+ });
64
+ return Observable.of({ data: { id, name, type, body, variables, prefetch, prefetchID, tools } });
65
+ })
66
+ });
67
+ const processFile = async (file) => {
68
+ const code = readFileSync(file, "utf-8");
69
+ if (!code.includes("gql")) return;
70
+ const fileHash = createHash("md5").update(code).digest("hex");
71
+ if (cache.get("file")?.hash === fileHash) return;
72
+ const sources = await gqlPluckFromCodeStringSync(file, code, {
73
+ modules: [
74
+ { name: "graphql-tag", identifier: "gql" },
75
+ { name: "@apollo/client", identifier: "gql" }
76
+ ]
77
+ }).map((source) => ({
78
+ node: parse(source.body),
79
+ file,
80
+ location: source.locationOffset
81
+ }));
82
+ const operations = [];
83
+ for (const source of sources) {
84
+ const type = source.node.definitions.find((d) => d.kind === "OperationDefinition").operation;
85
+ let result;
86
+ if (type === "query") {
87
+ result = await client.query({ query: source.node, fetchPolicy: "no-cache" });
88
+ } else if (type === "mutation") {
89
+ result = await client.mutate({ mutation: source.node, fetchPolicy: "no-cache" });
90
+ } else {
91
+ throw new Error("Found an unsupported operation type. Only Query and Mutation are supported.");
92
+ }
93
+ operations.push(result.data);
94
+ }
95
+ cache.set(file, {
96
+ file,
97
+ hash: fileHash,
98
+ operations
99
+ });
100
+ };
101
+ const generateManifest = async () => {
102
+ const operations = Array.from(cache.values()).flatMap((entry) => entry.operations);
103
+ if (operations.filter((o) => o.prefetch).length > 1) {
104
+ throw new Error(
105
+ "Found multiple operations marked as `@prefetch`. You can only mark 1 operation with `@prefetch`."
106
+ );
107
+ }
108
+ let resource = "";
109
+ if (config.command === "serve") {
110
+ resource = packageJson.entry?.[config.mode] ?? `http${config.server.https ? "s" : ""}://${config.server.host ?? "localhost"}:${config.server.port}`;
111
+ } else {
112
+ let entryPoint = packageJson.entry?.[config.mode];
113
+ if (entryPoint) {
114
+ resource = entryPoint;
115
+ } else if (config.mode === "production") {
116
+ resource = "index.html";
117
+ } else {
118
+ throw new Error(
119
+ `No entry point found for mode "${config.mode}". Entry points other than "development" and "production" must be defined in package.json file.`
120
+ );
121
+ }
122
+ }
123
+ const manifest = {
124
+ format: "apollo-ai-app-manifest",
125
+ version: "1",
126
+ name: packageJson.name,
127
+ description: packageJson.description,
128
+ hash: createHash("sha256").update(Date.now().toString()).digest("hex"),
129
+ operations: Array.from(cache.values()).flatMap((entry) => entry.operations),
130
+ resource
131
+ };
132
+ if (config.command === "build") {
133
+ const dest = path.resolve(root, config.build.outDir, ".application-manifest.json");
134
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
135
+ writeFileSync(dest, JSON.stringify(manifest));
136
+ }
137
+ writeFileSync(".application-manifest.json", JSON.stringify(manifest));
138
+ };
139
+ return {
140
+ name: "OperationManifest",
141
+ async configResolved(resolvedConfig) {
142
+ config = resolvedConfig;
143
+ },
144
+ async buildStart() {
145
+ packageJson = JSON.parse(readFileSync("package.json", "utf-8"));
146
+ const files = await glob("src/**/*.{ts,tsx,js,jsx}");
147
+ for (const file of files) {
148
+ const fullPath = path.resolve(root, file);
149
+ await processFile(fullPath);
150
+ }
151
+ if (config.command === "serve") {
152
+ await generateManifest();
153
+ }
154
+ },
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ configureServer(server) {
157
+ server.watcher.on("change", async (file) => {
158
+ if (file.endsWith("package.json")) {
159
+ packageJson = JSON.parse(readFileSync("package.json", "utf-8"));
160
+ await generateManifest();
161
+ } else if (file.match(/\.(jsx?|tsx?)$/)) {
162
+ await processFile(file);
163
+ await generateManifest();
164
+ }
165
+ });
166
+ },
167
+ async writeBundle() {
168
+ await generateManifest();
169
+ }
170
+ };
171
+ };
172
+ function sortTopLevelDefinitions(query) {
173
+ const definitions = [...query.definitions];
174
+ definitions.sort((a, b) => {
175
+ if (a.kind > b.kind) {
176
+ return -1;
177
+ }
178
+ if (a.kind < b.kind) {
179
+ return 1;
180
+ }
181
+ const aName = a.kind === "OperationDefinition" || a.kind === "FragmentDefinition" ? a.name?.value ?? "" : "";
182
+ const bName = b.kind === "OperationDefinition" || b.kind === "FragmentDefinition" ? b.name?.value ?? "" : "";
183
+ if (aName < bName) {
184
+ return -1;
185
+ }
186
+ if (aName > bName) {
187
+ return 1;
188
+ }
189
+ return 0;
190
+ });
191
+ return {
192
+ ...query,
193
+ definitions
194
+ };
195
+ }
196
+ function removeClientDirective(doc) {
197
+ return visit(doc, {
198
+ OperationDefinition(node) {
199
+ return {
200
+ ...node,
201
+ directives: node.directives?.filter((d) => d.name.value !== "prefetch" && d.name.value !== "tool")
202
+ };
203
+ }
204
+ });
205
+ }
206
+ export {
207
+ OperationManifestPlugin,
208
+ getTypeName,
209
+ sortTopLevelDefinitions
210
+ };
@@ -0,0 +1,10 @@
1
+ import { TypeNode, type DocumentNode } from "graphql";
2
+ export declare function getTypeName(type: TypeNode): string;
3
+ export declare const OperationManifestPlugin: () => {
4
+ name: string;
5
+ configResolved(resolvedConfig: any): Promise<void>;
6
+ buildStart(): Promise<void>;
7
+ configureServer(server: any): void;
8
+ writeBundle(): Promise<void>;
9
+ };
10
+ export declare function sortTopLevelDefinitions(query: DocumentNode): DocumentNode;