diff --git a/jest.config.js b/jest.config.js index 283e26b22..b201dbdbc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,14 @@ module.exports = { projects: [ "/packages/api-client-core/jest.config.js", + "/packages/client-hooks/jest.config.js", "/packages/core/jest.config.js", + "/packages/preact/jest.config.js", "/packages/react/jest.config.js", + "/packages/react-bigcommerce/jest.config.js", "/packages/react-shopify-app-bridge/jest.config.js", - "/packages/tiny-graphql-query-compiler/jest.config.js", "/packages/shopify-extensions/jest.config.js", - "/packages/react-bigcommerce/jest.config.js", + "/packages/tiny-graphql-query-compiler/jest.config.js", + "/packages/utils/jest.config.js", ], }; diff --git a/package.json b/package.json index e509904e6..5695f6b53 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint:eslint": "NODE_OPTIONS=\"--max-old-space-size=4096\" eslint --quiet --ext ts,tsx packages scripts", "lint:fix": "NODE_OPTIONS=\"--max-old-space-size=4096\" prettier --write --check \"(packages|scripts)/**/*.{js,ts,tsx}\" && eslint --ext ts,tsx --fix packages scripts", "typecheck": "pnpm -r --no-bail run --if-present typecheck", - "build": "pnpm -r --no-bail run --if-present build", + "build": "pnpm --filter=@gadgetinc/utils build && pnpm -r --no-bail run --if-present build", "prerelease": "pnpm -r --no-bail run --if-present prerelease", "watch": "run-p --print-label watch:*", "watch:client": "pnpm --filter=@gadgetinc/api-client-core watch", diff --git a/packages/blog-example/src/main.tsx b/packages/blog-example/src/main.tsx index 31510bc50..c1f3b37c0 100644 --- a/packages/blog-example/src/main.tsx +++ b/packages/blog-example/src/main.tsx @@ -7,7 +7,7 @@ import "./styles/index.css"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + diff --git a/packages/client-hooks/jest.config.js b/packages/client-hooks/jest.config.js new file mode 100644 index 000000000..5073898e9 --- /dev/null +++ b/packages/client-hooks/jest.config.js @@ -0,0 +1,186 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html + +export default { + displayName: "client-hooks", + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: process.env.LAYERCI ? "/tmp/jest-cache" : undefined, + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + // coverageDirectory: undefined, + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + extensionsToTreatAsEsm: [".ts", ".tsx"], + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: "/../api/spec/jest.globalsetup.ts", + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "json", + // "jsx", + // "ts", + // "tsx", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: "ts-jest", + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state between every test + restoreMocks: true, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + roots: [""], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: ["./spec/setup.ts"], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: ["/spec/jest.setup.ts"], + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "setup-polly-jest/jest-environment-jsdom", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [path.join(__dirname, "spec/(*.)+(spec|test).[tj]s?(x)")], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + testPathIgnorePatterns: ["/node_modules/"], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jasmine2", + testRunner: "jest-circus/runner", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + // transform: undefined, + transform: { "^.+\\.(t|j)sx?$": ["@swc/jest"] }, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/" + // ], + // transformIgnorePatterns: ["/node_modules/(?!lodash)"], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/packages/client-hooks/package.json b/packages/client-hooks/package.json new file mode 100644 index 000000000..155636e8a --- /dev/null +++ b/packages/client-hooks/package.json @@ -0,0 +1,40 @@ +{ + "name": "@gadgetinc/client-hooks", + "version": "0.1.0", + "files": [ + "README.md", + "dist/**/*" + ], + "license": "MIT", + "repository": "github:gadget-inc/js-clients", + "homepage": "https://github.com/gadget-inc/js-clients/tree/main/packages/client-hooks", + "type": "module", + "exports": { + "./package.json": "./package.json", + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "default": "./dist/esm/index.js" + } + }, + "source": "src/index.ts", + "main": "dist/cjs/index.js", + "sideEffects": false, + "scripts": { + "typecheck:main": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist/ *.tsbuildinfo **/*.tsbuildinfo", + "prebuild": "mkdir -p dist/cjs dist/esm && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && echo '{\"type\": \"module\"}' > dist/esm/package.json", + "build": "pnpm clean && pnpm prebuild && tsc -b tsconfig.cjs.json tsconfig.esm.json", + "prepublishOnly": "pnpm build", + "prerelease": "gitpkg publish" + }, + "dependencies": { + "@gadgetinc/utils": "^0.1.0" + }, + "devDependencies": { + "@gadgetinc/utils": "workspace:*", + "@gadgetinc/api-client-core": "workspace:*", + "@urql/core": "*" + } +} diff --git a/packages/client-hooks/src/adapter.ts b/packages/client-hooks/src/adapter.ts new file mode 100644 index 000000000..ccc4ef650 --- /dev/null +++ b/packages/client-hooks/src/adapter.ts @@ -0,0 +1,97 @@ +import type { AnyClient, GadgetConnection } from "@gadgetinc/api-client-core"; +import type { + AnyVariables, + Client, + CombinedError, + DocumentInput, + GraphQLRequestParams, + Operation, + OperationContext, + OperationResult, + RequestPolicy, +} from "@urql/core"; + +type Dispose = void | (() => void); +type AnyActionArg = [] | [any]; +type ActionDispatch = (...args: ActionArg) => void; + +type Context<_T> = { + Provider: unknown; + Consumer: unknown; +}; + +interface FrameworkBindings { + deepEqual: (a: A, b: B) => boolean; + useEffect: (fn: () => Dispose, deps?: any[]) => void; + useMemo: (factory: () => T, deps: any[]) => T; + useRef: (initial: T) => { current: T }; + useState: (initial: T | (() => T)) => [T, (next: T) => void]; + useContext: (ctx: any) => T; + createContext: (defaultValue: T) => Context; + useCallback: any>(fn: T, deps: any[]) => T; + useReducer: ( + reducer: (prevState: S, ...args: A) => S, + initialArg: I, + init?: (i: I) => S + ) => [S, ActionDispatch]; + Fragment: unknown; +} + +export type UseQueryArgs = { + requestPolicy?: RequestPolicy; + context?: Partial; + pause?: boolean; +} & GraphQLRequestParams; + +export interface UseQueryState { + fetching: boolean; + stale: boolean; + data?: Data; + error?: CombinedError; + extensions?: Record; + operation?: Operation; +} + +type UseQueryExecute = (opts?: Partial) => void; + +export type UseQueryResponse = [UseQueryState, UseQueryExecute]; + +export interface UseMutationState { + fetching: boolean; + stale: boolean; + data?: Data; + error?: CombinedError; + extensions?: Record; + operation?: Operation; +} + +type UseMutationExecute = ( + variables: Variables, + context?: Partial +) => Promise>; + +export type UseMutationResponse = [ + UseMutationState, + UseMutationExecute +]; + +interface UrqlBindings { + Provider: (props: { client: Client; children: any }) => any; + useQuery: ( + args: UseQueryArgs + ) => UseQueryResponse; + useMutation: ( + query: DocumentInput + ) => UseMutationResponse; +} + +export interface GadgetApiContext { + api: AnyClient; + connection: GadgetConnection; +} + +export interface RuntimeAdapter { + GadgetApiContext: Context; + framework: FrameworkBindings; + urql: UrqlBindings; +} diff --git a/packages/client-hooks/src/createHooks.ts b/packages/client-hooks/src/createHooks.ts new file mode 100644 index 000000000..8f5ce4ca1 --- /dev/null +++ b/packages/client-hooks/src/createHooks.ts @@ -0,0 +1,119 @@ +import type { AnyVariables, DocumentInput } from "@urql/core"; +import type { GadgetApiContext, RuntimeAdapter } from "./adapter.js"; +import { CoreHooks, UseApi, UseConnection, UseGadgetMutation, UseGadgetQuery, UseGadgetQueryArgs, type QueryOptions } from "./types.js"; +import { noProviderErrorMessage } from "./utils.js"; + +const RegisteredHooks: ((adapter: RuntimeAdapter, coreHooks: CoreHooks) => void)[] = []; + +export const createHookStub = (hook: string, registerFn?: (adapter: RuntimeAdapter, coreHooks: CoreHooks) => void) => { + if (registerFn) { + RegisteredHooks.push(registerFn); + } + return () => { + throw new Error( + `You are attempting to use the ${hook} hook, but you are not calling it from a component that is wrapped in a Gadget component. Please ensure you are wrapping this hook with the component from either @gadgetinc/react or @gadgetinc/preact.` + ); + }; +}; + +export let useApi: UseApi = createHookStub("useApi"); +export let useConnection: UseConnection = createHookStub("useConnection"); +export let useQuery: UseGadgetQuery = createHookStub("useQuery"); +export let useMutation: UseGadgetMutation = createHookStub("useMutation"); + +export const createHooks = (adapter: RuntimeAdapter) => { + const coreHooks = createCoreHooks(adapter); + + useQuery = coreHooks.useGadgetQuery; + useMutation = coreHooks.useGadgetMutation; + useApi = coreHooks.useApi; + useConnection = coreHooks.useConnection; + + for (const registration of RegisteredHooks) { + registration(adapter, coreHooks); + } +}; + +const createCoreHooks = (adapter: RuntimeAdapter): CoreHooks => { + const { GadgetApiContext } = adapter; + + const useConnection: UseConnection = () => { + const { connection } = adapter.framework.useContext(GadgetApiContext); + + if (!connection) { + throw new Error( + `urql client found in context was not set up by the Gadget API client. Please ensure you are wrapping this hook with the component from either @gadgetinc/react or @gadgetinc/preact. + + Possible remedies: + - ensuring you have the component wrapped around your hook invocation + - ensuring you are passing a value to the provider, usually + - ensuring your @gadget-client/ package and your @gadgetinc/react or @gadgetinc/preact package are up to date` + ); + } + + return connection; + }; + + const useApi: UseApi = () => { + const { api } = adapter.framework.useContext(GadgetApiContext); + + if (!api) { + throw new Error( + `useApi hook called in context where no Gadget API client is available. Please ensure you are wrapping this hook with the component from @gadgetinc/react. + + Possible remedies: + - ensuring you have the component wrapped around your hook invocation + - ensuring you are passing an api client instance to the provider, usually + - ensuring your @gadget-client/ package and your @gadgetinc/react package are up to date` + ); + } + + return api; + }; + + const useMemoizedQueryOptions = (options?: Options): Options => { + const { context: _context, suspense: _suspense, ...rest } = options ?? {}; + + // use a memo as urql rerenders on context identity changes + const context = adapter.framework.useMemo(() => { + return { + suspense: !!options?.suspense, + ...options?.context, + }; + }, [options?.suspense, options?.context]); + + return { + ...rest, + context, + } as unknown as Options; + }; + + const useStructuralMemo = (value: T) => { + const ref = adapter.framework.useRef(value); + + if (!adapter.framework.deepEqual(value, ref.current)) { + ref.current = value; + } + + return ref.current; + }; + + const useGadgetQuery = (args: UseGadgetQueryArgs) => { + if (!adapter.framework.useContext(GadgetApiContext)) throw new Error(noProviderErrorMessage); + const options = useMemoizedQueryOptions(args); + return adapter.urql.useQuery(options); + }; + + const useGadgetMutation = (query: DocumentInput) => { + if (!adapter.framework.useContext(GadgetApiContext)) throw new Error(noProviderErrorMessage); + return adapter.urql.useMutation(query); + }; + + return { + useConnection, + useApi, + useStructuralMemo, + useGadgetQuery, + useGadgetMutation, + }; +}; diff --git a/packages/client-hooks/src/index.ts b/packages/client-hooks/src/index.ts new file mode 100644 index 000000000..b2998faf1 --- /dev/null +++ b/packages/client-hooks/src/index.ts @@ -0,0 +1,17 @@ +export * from "./adapter.js"; +export { useApi, useConnection, useMutation, useQuery } from "./createHooks.js"; +export { registerClientHooks } from "./provider.js"; +export * from "./types.js"; +export { useAction } from "./useAction.js"; +export { useBulkAction } from "./useBulkAction.js"; +export { useEnqueue } from "./useEnqueue.js"; +export { useFetch } from "./useFetch.js"; +export { useFindBy } from "./useFindBy.js"; +export { useFindFirst } from "./useFindFirst.js"; +export { useFindMany } from "./useFindMany.js"; +export { useFindOne } from "./useFindOne.js"; +export { useGet } from "./useGet.js"; +export { useGlobalAction } from "./useGlobalAction.js"; +export { useMaybeFindFirst } from "./useMaybeFindFirst.js"; +export { useMaybeFindOne } from "./useMaybeFindOne.js"; +export { useView } from "./useView.js"; diff --git a/packages/client-hooks/src/provider.ts b/packages/client-hooks/src/provider.ts new file mode 100644 index 000000000..91c4868b3 --- /dev/null +++ b/packages/client-hooks/src/provider.ts @@ -0,0 +1,36 @@ +import type { AnyClient, GadgetConnection } from "@gadgetinc/api-client-core"; +import type { Client as UrqlClient } from "@urql/core"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHooks } from "./createHooks.js"; + +const isGadgetClient = (client: any): client is AnyClient => { + return client && "connection" in client && client.connection && "endpoint" in client.connection; +}; + +export const registerClientHooks = (api: AnyClient, adapter: RuntimeAdapter) => { + let gadgetClient: AnyClient; + let gadgetConnection: GadgetConnection; + let urqlClient: UrqlClient; + + if (!api) { + throw new Error( + "No Gadget API client passed to component -- please pass an instance of your generated client, like !" + ); + } + + if (!isGadgetClient(api)) { + throw new Error( + "Invalid Gadget API client passed to component -- please pass an instance of your generated client, like !" + ); + } + gadgetClient = api; + urqlClient = api.connection.currentClient; + gadgetConnection = api.connection; + createHooks(adapter); + + return { + gadgetClient, + gadgetConnection, + urqlClient, + }; +}; diff --git a/packages/client-hooks/src/types.ts b/packages/client-hooks/src/types.ts new file mode 100644 index 000000000..e815b8570 --- /dev/null +++ b/packages/client-hooks/src/types.ts @@ -0,0 +1,800 @@ +import type { + ActionFunction, + AnyActionFunction, + AnyBulkActionFunction, + AnyClient, + BackgroundActionHandle, + BulkActionFunction, + DefaultSelection, + EnqueueBackgroundActionOptions, + FieldSelection, + FindFirstFunction, + FindManyFunction, + FindOneFunction, + GadgetConnection, + GadgetRecord, + GadgetRecordList, + GetFunction, + GlobalActionFunction, + LimitToKnownKeys, + Select, + ViewFunction, + ViewFunctionWithVariables, + ViewFunctionWithoutVariables, + ViewResult, +} from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; +import type { AnyVariables, DocumentInput, Operation, OperationContext, RequestPolicy } from "@urql/core"; +import type { UseMutationResponse, UseQueryArgs, UseQueryResponse } from "./adapter.js"; + +export interface QueryOptions { + context?: Partial; + pause?: boolean; + requestPolicy?: RequestPolicy; + suspense?: boolean; +} + +/** + * All the options controlling how this query will be managed by urql + * */ +export declare type ReadOperationOptions = { + /** Updates the {@link RequestPolicy} for the executed GraphQL query operation. + * + * @remarks + * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation + * that `useQuery` executes, and indicates a caching strategy for cache exchanges. + * + * For example, when set to `'cache-and-network'`, {@link useQuery} will + * receive a cached result with `stale: true` and an API request will be + * sent in the background. + * + * @see {@link OperationContext.requestPolicy} for where this value is set. + */ + requestPolicy?: RequestPolicy; + /** Updates the {@link OperationContext} for the executed GraphQL query operation. + * + * @remarks + * `context` may be passed to {@link useQuery}, to update the {@link OperationContext} + * of a query operation. This may be used to update the `context` that exchanges + * will receive for a single hook. + * + * Hint: This should be wrapped in a `useMemo` hook, to make sure that your + * component doesn’t infinitely update. + * + * @example + * ```ts + * const [result, reexecute] = useQuery({ + * query, + * context: useMemo(() => ({ + * additionalTypenames: ['Item'], + * }), []) + * }); + * ``` + */ + context?: Partial; + /** Prevents {@link useQuery} from automatically executing GraphQL query operations. + * + * @remarks + * `pause` may be set to `true` to stop {@link useQuery} from executing + * automatically. The hook will stop receiving updates from the {@link Client} + * and won’t execute the query operation, until either it’s set to `false` + * or the {@link UseQueryExecute} function is called. + * + * @see {@link https://urql.dev/goto/docs/basics/react-preact/#pausing-usequery} for + * documentation on the `pause` option. + */ + pause?: boolean; + /** + * Marks this query as one that should suspend the react component rendering while executing, instead of returning `{fetching: true}` to the caller. + * Useful if you want to allow components higher in the tree to show spinners instead of having every component manage its own loading state. + */ + suspense?: boolean; + /** + * Marks this query as a live query that will subscribe to changes from the backend and re-render when backend data changes with the newest data. + */ + live?: boolean; +}; + +export type OptionsType = { + [key: string]: any; + /** What fields to select from the resulting object */ + select?: FieldSelection; + /** Subscribe to changes from the backend and return a new result as it changes */ + live?: boolean; +}; + +/** + * The inner result object returned from a query result + **/ +export interface ReadHookState> { + fetching: boolean; + stale: boolean; + data?: Data; + error?: ErrorWrapper; + extensions?: Record; + operation?: Operation; +} + +/** + * The return value of a `useGet`, `useFindMany`, `useFindOne` etc hook. + * Includes the data result object and a refetch function. + **/ +export declare type ReadHookResult = [ + ReadHookState, + (opts?: Partial) => void +]; + +export type RequiredKeysOf = Exclude< + { + [Key in keyof BaseType]: BaseType extends Record ? Key : never; + }[keyof BaseType], + undefined +>; + +/** + * The inner result object returned from a mutation result + */ +export interface ActionHookState> { + fetching: boolean; + stale: boolean; + data?: Data; + error?: ErrorWrapper; + extensions?: Record; + operation?: Operation; +} + +/** + * The return value of a `useAction`, `useGlobalAction`, `useBulkAction` etc hook. + * Includes the data result object and a function for running the mutation. + **/ +export type ActionHookResult = RequiredKeysOf extends never + ? ActionHookResultWithOptionalCallbackVariables + : ActionHookResultWithRequiredCallbackVariables; + +export type ActionHookResultWithOptionalCallbackVariables = [ + ActionHookState, + (variables?: Variables, context?: Partial) => Promise> +]; + +export type ActionHookResultWithRequiredCallbackVariables = [ + ActionHookState, + (variables: Variables, context?: Partial) => Promise> +]; + +export type UseGadgetQueryArgs = UseQueryArgs & { + /** + * Marks this query as one that should suspend the react component rendering while executing, instead of returning `{fetching: true}` to the caller. + * Useful if you want to allow components higher in the tree to show spinners instead of having every component manage its own loading state. + */ + suspense?: boolean; +}; + +/** + * Get the current `GadgetConnection` object from context. + * Must be called within a component wrapped by ``. + **/ +export type UseConnection = () => GadgetConnection; +/** + * Get the current `api` object from context + * Must be called within a component wrapped by the `` component. + **/ +export type UseApi = () => AnyClient; +/** + * Memoize and ensure a stable identity on a given value as long as it remains the same, structurally. + */ +export type UseStructuralMemo = (value: T) => T; +export type UseGadgetQuery = ( + args: UseGadgetQueryArgs +) => UseQueryResponse; +export type UseGadgetMutation = ( + query: DocumentInput +) => UseMutationResponse; + +export type CoreHooks = { + useConnection: UseConnection; + useApi: UseApi; + useStructuralMemo: UseStructuralMemo; + useGadgetQuery: UseGadgetQuery; + useGadgetMutation: UseGadgetMutation; +}; + +/** + * The inner result object returned from a mutation result + */ +export type EnqueueHookState = Action extends AnyBulkActionFunction + ? { + fetching: boolean; + stale: boolean; + handles: BackgroundActionHandle[] | null; + error?: ErrorWrapper; + extensions?: Record; + operation?: Operation<{ backgroundAction: { id: string } }, Action["variablesType"]>; + } + : { + fetching: boolean; + stale: boolean; + handle: BackgroundActionHandle | null; + error?: ErrorWrapper; + extensions?: Record; + operation?: Operation<{ backgroundAction: { id: string } }, Action["variablesType"]>; + }; + +/** + * The return value of a `useEnqueue` hook. + * Returns a two-element array: + * - the result object, with the keys like `handle`, `fetching`, and `error` + * - and a function for running the enqueue mutation. + **/ +export type EnqueueHookResult = RequiredKeysOf< + Exclude +> extends never + ? [ + EnqueueHookState, + ( + variables?: Action["variablesType"], + backgroundOptions?: EnqueueBackgroundActionOptions, + context?: Partial + ) => Promise> + ] + : [ + EnqueueHookState, + ( + variables: Action["variablesType"], + backgroundOptions?: EnqueueBackgroundActionOptions, + context?: Partial + ) => Promise> + ]; + +/** + * Hook to run a Gadget model action. `useAction` must be passed an action function from an instance of your generated API client library, like `api.user.create` or `api.blogPost.publish`. `useAction` doesn't actually run the action when invoked, but instead returns an action function as the second result for running the action in response to an event. + * + * @param action an action function from a model manager in your application's client, like `api.user.create` + * @param options action options, like selecting the fields in the result + * + * @example + * export function CreateUserButton(props: { name: string; email: string }) { + * const [{error, fetching, data}, createUser] = useAction(api.user.create, { + * select: { + * id: true, + * }, + * }); + * + * const onClick = () => createUser({ + * name: props.name, + * email: props.email, + * }); + * + * return ( + * <> + * {error && <>Failed to create user: {error.toString()}} + * {fetching && <>Creating user...} + * {data && <>Created user with id={data.id}} + * + * + * ); + * } + */ +export type UseAction = < + GivenOptions extends OptionsType, + SchemaT, + F extends ActionFunction, + Options extends F["optionsType"] +>( + action: F, + options?: LimitToKnownKeys +) => ActionHookResult< + F["hasReturnType"] extends true + ? any + : GadgetRecord< + Select, DefaultSelection> + >, + Exclude +>; + +/** + * Hook to run a Gadget model bulk action. + * + * @param action any bulk action function from a Gadget manager + * @param options action options, like selecting the fields in the result + * + * @example + * ``` + * export function BulkFinish(props: { ids: string[]; }) { + * const [result, bulkFinish] = useBulkAction(Client.todo.bulkFinish, { + * select: { + * id: true, + * }, + * }); + * + * const onClick = () => ; + * + * return ( + * <> + * {result.error && <>Failed to create user: {result.error.toString()}} + * {result.fetching && <>Creating user...} + * {result.data && <>Finished TODOs with ids={props.ids}} + * + * + * ); + * } + */ +export type UseBulkAction = < + GivenOptions extends OptionsType, + SchemaT, + F extends BulkActionFunction, + Options extends F["optionsType"] +>( + action: F, + options?: LimitToKnownKeys +) => ActionHookResult< + F["hasReturnType"] extends true + ? any[] + : GadgetRecord< + Select, DefaultSelection> + >[], + Exclude +>; + +/** + * Hook to enqueue a Gadget action in the background. `useEnqueue` must be passed an action function from an instance of your generated API client library, like `useEnqueue(api.user.create)` or `useEnqueue(api.someGlobalAction)`. `useEnqueue` doesn't actually submit the background action when invoked, but instead returns a function for enqueuing the action in response to an event. + * + * @param action a model action or global action in your application's client, like `api.user.create` or `api.someGlobalAction` + * @param options action options, like selecting the fields in the result + * + * @example + * export function CreateUserButton(props: { name: string; email: string }) { + * const [{error, fetching, handle}, enqueue] = useEnqueue(api.user.create)); + * + * const onClick = () => enqueue( + * { + * name: props.name, + * email: props.email, + * }, { + * id: `send-email-action-${props.email}` + * } + * ); + * + * return ( + * <> + * {error && <>Failed to enqueue user create: {error.toString()}} + * {fetching && <>Enqueuing action...} + * {data && <>Enqueued action with background action id={handle.id}} + * + * + * ); + * } + */ +export type UseEnqueue = ( + action: Action, + baseBackgroundOptions?: EnqueueBackgroundActionOptions +) => EnqueueHookResult; + +export interface FetchHookOptions extends RequestInit { + stream?: boolean | string; + json?: boolean; + sendImmediately?: boolean; + onStreamComplete?: (value: string) => void; +} + +export interface FetchHookState { + data?: T; + response?: Response; + error?: ErrorWrapper; + fetching: boolean; + streaming: boolean; + options: FetchHookOptions; +} + +export type FetchHookResult = [FetchHookState, (opts?: Partial) => Promise]; + +/** + * Hook to make an HTTP request to a Gadget backend HTTP route. Preserves client side session information and ensures it's passed along to the backend. + * + * Returns a tuple with the current state of the request and a function to send or re-send the request. The state is an object with the following fields: + * - `data`: the response data, if the request was successful + * - `fetching`: a boolean describing if the fetch request is currently in progress + * - `streaming`: a boolean describing if the fetch request is currently streaming. This is only set when the option `{ stream: "string" }` is passed + * - `error`: an error object if the request failed in any way + * + * The second return value is a function for executing the fetch request. It returns a promise for the response body. + * + * By default, `GET` requests are sent as soon as the hook executes. Any other request methods are not sent automatically, and must be triggered by calling the `execute` function returned in the second argument. + * + * Pass the `{ json: true }` option to expect a JSON response from the server, and to automatically parse the response as JSON. Otherwise, the response will be returned as a `string` object. + * + * Pass the `{ stream: true }` to get a `ReadableStream` object as a response from the server, allowing you to work with the response as it arrives. + * + * Pass the `{ stream: "string" }` to decode the `ReadableStream` as a string and update data as it arrives. If the stream is in an encoding other than utf8 use i.e. `{ stream: "utf-16" }`. + * + * When `{ stream: "string" }` is used, the `streaming` field in the state will be set to `true` while the stream is active, and `false` when the stream is complete. You can use this to show a loading indicator while the stream is active. + * You can also pass an `onStreamComplete` callback that will be called with the value of the streamed string once it has completed. + * + * If you want to read model data, see the `useFindMany` function and similar. If you want to invoke a backend Action, use the `useAction` hook instead. + * + * @param path the backend path to fetch + * @param options the `fetch` options for the request + * + * @example + * ``` + * export function UserByEmail(props: { email: string }) { + * const [{data, fetching, error}, refresh] = useFetch("/users/get", { + * method: "GET", + * body: JSON.stringify({ email: props.email }}) + * headers: { + * "content-type": "application/json", + * } + * json: true, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No user found with id={props.id}; + * + * return
{result.data.name}
; + * } + */ +export interface UseFetch { + (path: string, options: { stream: string } & FetchHookOptions): FetchHookResult>; + (path: string, options: { stream: true } & FetchHookOptions): FetchHookResult>; + >(url: string, options: { json: true } & FetchHookOptions): FetchHookResult; + (path: string, options?: FetchHookOptions): FetchHookResult; +} + +/** + * Hook to fetch a Gadget record using the `findByXYZ` method of a given model manager. Useful for finding records by key fields which are used for looking up records by. Gadget autogenerates the `findByXYZ` methods on your model managers, and `useFindBy` can only be used with models that have these generated finder functions. + * + * @param finder `findByXYZ` function from a Gadget manager that will be used + * @param value field value of the record to fetch + * @param options options for selecting the fields in the result + * + * @example + * ``` + * export function UserByEmail(props: { email: string }) { + * const [result, refresh] = useFindBy(api.user.findByEmail, props.email, { + * select: { + * name: true, + * }, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No user found with id={props.id}; + * + * return
{result.data.name}
; + * } + */ +export type UseFindBy = < + GivenOptions extends OptionsType, + SchemaT, + F extends FindOneFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + finder: F, + value: string, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecord, DefaultSelection>> +>; + +/** + * Hook to fetch the first backend record matching a given filter and sort. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the first record found if there is one, and null otherwise. + * + * @param manager Gadget model manager to use + * @param options options for filtering and searching records, and selecting the fields in each record of the result + * + * @example + * + * ``` + * export function Users() { + * const [result, refresh] = useFindFirst(api.user, { + * select: { + * name: true, + * }, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No user found; + * + * return
{result.data.name}
; + * } + * ``` + */ +export type UseFindFirst = < + GivenOptions extends OptionsType, + SchemaT, + F extends FindFirstFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + manager: { findFirst: F }, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecord, DefaultSelection>> +>; + +/** + * Hook to fetch a page of Gadget records from the backend, optionally sorted, filtered, searched, and selected from. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be a `GadgetRecordList` object holding the list of returned records and pagination info. + * + * @param manager Gadget model manager to use + * @param options options for filtering and searching records, and selecting the fields in each record of the result + * + * @example + * + * ``` + * export function Users() { + * const [result, refresh] = useFindMany(api.user, { + * select: { + * name: true, + * }, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No users found; + * + * return <>{result.data.map((user) =>
{user.name}
)}; + * } + * ``` + */ +export type UseFindMany = < + GivenOptions extends OptionsType, + SchemaT, + F extends FindManyFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + manager: { findMany: F }, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecordList, DefaultSelection>> +>; + +/** + * Hook to fetch one Gadget record by `id` from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the record if it was found, and `null` otherwise. + * + * @param manager Gadget model manager to use + * @param id id of the record to fetch + * @param options options for selecting the fields in the result + * + * @example + * ``` + * export function User(props: { id: string }) { + * const [result, refresh] = useFindOne(api.user, props.id, { + * select: { + * name: true, + * }, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No user found with id={props.id}; + * + * return
{result.data.name}
; + * } + * ``` + */ +export type UseFindOne = < + GivenOptions extends OptionsType, + SchemaT, + F extends FindOneFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + manager: { findOne: F }, + id: string, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecord, DefaultSelection>> +>; + +/** + * Hook that fetches a singleton record for an `api.currentSomething` style model manager. `useGet` fetches one global record, which is most often the current session. `useGet` doesn't require knowing the record's ID in order to fetch it, and instead returns the one current record for the current context. + * + * @param manager Gadget model manager to use, like `api.currentSomething` + * @param options options for selecting the fields in the result + * + * @example + * ``` + * export function CurrentSession() { + * const [{error, data, fetching}, refresh] = useGet(api.currentSession, { + * select: { + * id: true, + * userId: true, + * }, + * }); + * + * if (error) return <>Error: {error.toString()}; + * if (fetching && !data) return <>Fetching...; + * if (!data) return <>No current session found; + * + * return
Current session ID={data.id} and userId={data.userId}
; + * } + * ``` + */ +export type UseGet = < + GivenOptions extends OptionsType, + SchemaT, + F extends GetFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + manager: { get: F }, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecord, DefaultSelection>> +>; + +/** + * Hook to run a Gadget model action. + * + * @param action any action function from a Gadget manager + * + * @example + * ``` + * export function FlipAllWidgets(props: { name: string; email: string }) { + * const [result, flipAllWidgets] = useGlobalAction(Client.flipAllWidgets); + * + * return ( + * <> + * {result.error && <>Failed to flip all widgets: {result.error.toString()}} + * {result.fetching && <>Flipping all widgets...} + * {result.data && <>Flipped all widgets} + * + * + * ); + * } + */ +export type UseGlobalAction = >( + action: F +) => ActionHookResultWithOptionalCallbackVariables>; + +/** + * Hook to fetch many Gadget records using the `maybeFindFirst` method of a given manager. + * + * @param manager Gadget model manager to use + * @param options options for filtering and searching records, and selecting the fields in each record of the result + * + * @example + * + * ``` + * export function Users() { + * const [result, refresh] = useMaybeFindFirst(Client.user, { + * select: { + * name: true, + * }, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No user found; + * + * return
{result.data.name}
; + * } + * ``` + */ +export type UseMaybeFindFirst = < + GivenOptions extends OptionsType, + SchemaT, + F extends FindFirstFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + manager: { findFirst: F }, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecord, DefaultSelection>> +>; + +/** + * Hook to fetch a Gadget record using the `maybeFindOne` method of a given manager. + * + * @param manager Gadget model manager to use + * @param id id of the record to fetch + * @param options options for selecting the fields in the result + * + * @example + * ``` + * export function User(props: { id: string }) { + * const [result, refresh] = useMaybeFindOne(Client.user, props.id, { + * select: { + * name: true, + * }, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No user found with id={props.id}; + * + * return
{result.data.name}
; + * } + * ``` + */ +export type UseMaybeFindOne = < + GivenOptions extends OptionsType, + SchemaT, + F extends FindOneFunction, + Options extends F["optionsType"] & ReadOperationOptions +>( + manager: { findOne: F }, + id: string, + options?: LimitToKnownKeys +) => ReadHookResult< + GadgetRecord, DefaultSelection>> +>; + +export interface UseView { + /** + * Hook to fetch the result of a computed view from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the shape of the computed view's result. + * + * @param view Gadget view function to run, like `api.leaderboard` or `api.todos.summary` + * @param options options for controlling client side execution + * + * @example + * + * ``` + * export function Leaderboard() { + * const [result, refresh] = useView(api.leaderboard); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No data found; + * + * return <>{result.data.map((leaderboard) =>
{leaderboard.name}: {leaderboard.score}
)}; + * } + * ``` + */ + >(view: F, options?: Omit): ReadHookResult>; + /** + * Hook to fetch the result of a computed view with variables from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the shape of the computed view's result. + * + * @param manager Gadget view function to run + * @param variables variables to pass to the backend view + * @param options options for controlling client side execution + * + * @example + * + * ``` + * export function Leaderboard() { + * const [result, refresh] = useView(api.leaderboard, { + * first: 10, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No data found; + * + * return <>{result.data.map((leaderboard) =>
{leaderboard.name}: {leaderboard.score}
)}; + * } + * ``` + */ + >( + view: F, + variables: F["variablesType"], + options?: Omit + ): ReadHookResult>; + + /** + * Hook to fetch the result of an inline computed view with variables from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the shape of the computed view's result. + * + * Does not know the type of the result from the input string -- for type safety, use a named view defined in a .gelly file in the backend. + * + * @param view Gelly query string to run, like `{ count(todos) }` + * @param variables variables to pass to the backend view + * @param options options for controlling client side execution + * + * @example + * + * ``` + * export function Leaderboard() { + * const [result, refresh] = useView("{ count(todos) }", { + * first: 10, + * }); + * + * if (result.error) return <>Error: {result.error.toString()}; + * if (result.fetching && !result.data) return <>Fetching...; + * if (!result.data) return <>No data found; + * + * return <>{result.data.map((leaderboard) =>
{leaderboard.name}: {leaderboard.score}
)}; + * } + * ``` + */ + (gellyQuery: string, variables?: Record, options?: Omit): ReadHookResult< + ViewResult> + >; +} diff --git a/packages/client-hooks/src/useAction.ts b/packages/client-hooks/src/useAction.ts new file mode 100644 index 000000000..b4f0f3076 --- /dev/null +++ b/packages/client-hooks/src/useAction.ts @@ -0,0 +1,101 @@ +import type { ActionFunction, GadgetConnection, StubbedActionFunction } from "@gadgetinc/api-client-core"; +import { ErrorWrapper, capitalizeIdentifier, disambiguateActionVariables, get, namespaceDataPath } from "@gadgetinc/utils"; +import { AnyVariables, OperationContext } from "@urql/core"; +import { UseMutationState } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { ActionHookState, UseAction } from "./types.js"; + +export let useAction: UseAction = createHookStub("useAction", (adapter, coreHooks) => { + useAction = (action, options) => { + const connection = coreHooks.useConnection(); + + adapter.framework.useEffect(() => { + if (action.type === ("stubbedAction" as string)) { + const stubbedAction = action as unknown as StubbedActionFunction; + if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction) || !("actionApiIdentifier" in stubbedAction)) { + // Don't dispatch an event if the generated client has not yet been updated with the updated parameters + return; + } + + const event = new CustomEvent("gadget:devharness:stubbedActionError", { + detail: { + reason: stubbedAction.reason, + action: { + functionName: stubbedAction.functionName, + actionApiIdentifier: stubbedAction.actionApiIdentifier, + modelApiIdentifier: stubbedAction.modelApiIdentifier, + dataPath: stubbedAction.dataPath, + }, + }, + }); + globalThis.dispatchEvent(event); + } + }, []); + + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.actionOperation( + action.operationName, + action.defaultSelection, + action.modelApiIdentifier, + action.modelSelectionField, + action.variables, + memoizedOptions, + action.namespace, + action.isBulk, + action.hasReturnType + ); + }, [action, memoizedOptions]); + + const [result, runMutation] = coreHooks.useGadgetMutation(plan.query); + + const transformedResult = adapter.framework.useMemo(() => processResult(connection, result, action), [result, action]); + + return [ + transformedResult, + adapter.framework.useCallback( + async (input: (typeof action)["variablesType"], context?: Partial) => { + const variables = disambiguateActionVariables(action, input); + + const result = await runMutation(variables, { + ...context, + // Adding the model's additional typename ensures document cache will properly refresh, regardless of whether __typename was selected (and sometimes we can't even select it, like delete actions!) + additionalTypenames: [...(context?.additionalTypenames ?? []), capitalizeIdentifier(action.modelApiIdentifier)], + }); + + return processResult(connection, { fetching: false, ...result }, action); + }, + [action, runMutation] + ), + ]; + }; +}); + +const processResult = ( + connection: GadgetConnection, + result: UseMutationState, + action: ActionFunction +): ActionHookState => { + let error = ErrorWrapper.forMaybeCombinedError(result.error); + let data = null; + if (result.data) { + const dataPath = namespaceDataPath([action.operationName], action.namespace); + const mutationData = get(result.data, dataPath); + if (mutationData) { + const errors = mutationData["errors"]; + if (errors && errors[0]) { + error = ErrorWrapper.forErrorsResponse(errors, error?.response); + } else { + data = connection.processActionResponse( + action.defaultSelection, + result, + mutationData, + action.modelSelectionField, + action.hasReturnType + ); + } + } + } + + return { ...result, error, data }; +}; diff --git a/packages/client-hooks/src/useBulkAction.ts b/packages/client-hooks/src/useBulkAction.ts new file mode 100644 index 000000000..d629bafaa --- /dev/null +++ b/packages/client-hooks/src/useBulkAction.ts @@ -0,0 +1,102 @@ +import type { BulkActionFunction, GadgetConnection, StubbedActionFunction } from "@gadgetinc/api-client-core"; +import { ErrorWrapper, capitalizeIdentifier, disambiguateBulkActionVariables, get, namespaceDataPath } from "@gadgetinc/utils"; +import { OperationContext } from "@urql/core"; +import { UseMutationState } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { UseBulkAction } from "./types.js"; + +export let useBulkAction: UseBulkAction = createHookStub("useBulkAction", (adapter, coreHooks) => { + useBulkAction = (action, options) => { + const connection = coreHooks.useConnection(); + adapter.framework.useEffect(() => { + if (action.type === ("stubbedAction" as string)) { + const stubbedAction = action as unknown as StubbedActionFunction; + if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction) || !("actionApiIdentifier" in stubbedAction)) { + // Don't dispatch an event if the generated client has not yet been updated with the updated parameters + return; + } + + const event = new CustomEvent("gadget:devharness:stubbedActionError", { + detail: { + reason: stubbedAction.reason, + action: { + functionName: stubbedAction.functionName, + actionApiIdentifier: stubbedAction.actionApiIdentifier, + modelApiIdentifier: stubbedAction.modelApiIdentifier, + dataPath: stubbedAction.dataPath, + }, + }, + }); + globalThis.dispatchEvent(event); + } + }, []); + + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.actionOperation( + action.operationName, + action.defaultSelection, + action.modelApiIdentifier, + action.modelSelectionField, + action.variables, + memoizedOptions, + action.namespace, + action.isBulk, + action.hasReturnType + ); + }, [action, memoizedOptions]); + + const [result, runMutation] = coreHooks.useGadgetMutation(plan.query); + + const transformedResult = adapter.framework.useMemo(() => processResult(connection, result, action), [result, action]); + + return [ + transformedResult, + adapter.framework.useCallback( + async (inputs: (typeof action)["variablesType"], context?: Partial) => { + const variables = disambiguateBulkActionVariables(action, inputs); + + const result = await runMutation(variables, { + ...context, + // Adding the model's additional typename ensures document cache will properly refresh, regardless of whether __typename was selected (and sometimes we can't even select it, like delete actions!) + additionalTypenames: [...(context?.additionalTypenames ?? []), capitalizeIdentifier(action.modelApiIdentifier)], + }); + return processResult(connection, { fetching: false, ...result }, action); + }, + [action, runMutation] + ), + ]; + }; +}); + +const processResult = ( + connection: GadgetConnection, + result: UseMutationState, + action: BulkActionFunction +) => { + let error = ErrorWrapper.forMaybeCombinedError(result.error); + let data = undefined; + + if (result.data && !error) { + const dataPath = namespaceDataPath([action.operationName], action.namespace); + const mutationData = get(result.data, dataPath); + + if (mutationData) { + const isDeleteAction = (action as any).isDeleter; + if (!isDeleteAction) { + const errors = mutationData["errors"]; + if (errors && errors[0]) { + error = ErrorWrapper.forErrorsResponse(errors, (error as any)?.response); + } else { + data = action.hasReturnType + ? mutationData.results + : connection.hydrateRecordArray(result, mutationData[action.modelSelectionField]); + } + } else { + // Delete action + data = mutationData; + } + } + } + return { ...result, error, data }; +}; diff --git a/packages/client-hooks/src/useEnqueue.ts b/packages/client-hooks/src/useEnqueue.ts new file mode 100644 index 000000000..9b1aad639 --- /dev/null +++ b/packages/client-hooks/src/useEnqueue.ts @@ -0,0 +1,83 @@ +import type { + AnyActionFunction, + BackgroundActionHandle, + EnqueueBackgroundActionOptions, + GadgetConnection, +} from "@gadgetinc/api-client-core"; +import { ErrorWrapper, disambiguateActionVariables, disambiguateBulkActionVariables, get, namespaceDataPath } from "@gadgetinc/utils"; +import type { UseMutationState } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { EnqueueHookState, UseEnqueue } from "./types.js"; + +export let useEnqueue: UseEnqueue = createHookStub("useEnqueue", (adapter, coreHooks) => { + useEnqueue = (action, baseBackgroundOptions) => { + const connection = coreHooks.useConnection(); + const plan = adapter.framework.useMemo( + () => connection.enqueueActionOperation(action.operationName, action.variables, action.namespace, null, action.isBulk), + [action] + ); + + const [rawState, runMutation] = coreHooks.useGadgetMutation(plan.query); + + const state = adapter.framework.useMemo(() => processResult(connection, rawState, action), [rawState, action]); + + return [ + state, + adapter.framework.useCallback( + async (input: (typeof action)["variablesType"], options?: EnqueueBackgroundActionOptions) => { + const variables = action.isBulk ? disambiguateBulkActionVariables(action, input) : disambiguateActionVariables(action, input); + + const fullContext = { ...baseBackgroundOptions, ...options }; + variables.backgroundOptions = connection.graphqlizeBackgroundOptions(fullContext); + + const rawState = await runMutation(variables, fullContext); + + return processResult(connection, { fetching: false, ...rawState }, action); + }, + [action, connection, runMutation] + ), + ]; + }; +}); + +/** Processes urql's result object into the fancier Gadget result object */ +const processResult = ( + connection: GadgetConnection, + rawResult: UseMutationState, + action: Action +): EnqueueHookState => { + const { data, ...result } = rawResult; + let error = ErrorWrapper.forMaybeCombinedError(result.error); + let handle: BackgroundActionHandle | null = null; + let handles: BackgroundActionHandle[] | null = null; + const isBulk = action.isBulk; + + if (data) { + const dataPath = ["background", ...namespaceDataPath([action.operationName], action.namespace)]; + + const mutationData = get(data, dataPath); + if (mutationData) { + const errors = mutationData["errors"]; + if (errors && errors[0]) { + error = ErrorWrapper.forErrorsResponse(errors, error?.response); + } else { + if (isBulk) { + handles = mutationData.backgroundActions.map((result: { id: string }) => + connection.createBackgroundActionHandle(action, result.id) + ); + } else { + handle = connection.createBackgroundActionHandle(action, mutationData.backgroundAction.id) as BackgroundActionHandle< + SchemaT, + Action + >; + } + } + } + } + + if (isBulk) { + return { ...result, error, handles } as EnqueueHookState; + } else { + return { ...result, error, handle } as EnqueueHookState; + } +}; diff --git a/packages/client-hooks/src/useFetch.ts b/packages/client-hooks/src/useFetch.ts new file mode 100644 index 000000000..278201b5d --- /dev/null +++ b/packages/client-hooks/src/useFetch.ts @@ -0,0 +1,198 @@ +import { ErrorWrapper } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, FetchHookOptions, FetchHookResult, FetchHookState, UseFetch } from "./types.js"; + +type FetchAction = + | { type: "fetching" } + | { type: "streaming" } + | { type: "fetched"; payload: T } + | { type: "streamed" } + | { type: "update"; payload: T } + | { type: "error"; payload: ErrorWrapper }; + +const reducer = (state: FetchHookState, action: FetchAction): FetchHookState => { + switch (action.type) { + case "fetching": + return { ...state, fetching: true, streaming: false, error: undefined }; + case "streaming": + return { ...state, streaming: true }; + case "streamed": + return { ...state, streaming: false }; + case "fetched": + return { ...state, fetching: false, data: action.payload, error: undefined }; + case "update": + return { ...state, data: action.payload }; + case "error": + return { ...state, fetching: false, error: action.payload }; + default: + return state; + } +}; + +const startRequestByDefault = (options?: FetchHookOptions) => { + if (typeof options?.sendImmediately != "undefined") { + return options.sendImmediately; + } else { + return !options?.method || options.method === "GET"; + } +}; + +const dispatchError = ( + mounted: { current: boolean }, + dispatch: (action: FetchAction) => void, + abortController: AbortController, + error: any, + response?: Response +) => { + if (!mounted.current || abortController.signal.aborted) return null; + + const wrapped = ErrorWrapper.forClientSideError(error, response); + dispatch({ type: "error", payload: wrapped }); + + return wrapped; +}; + +export let useFetch: UseFetch = createHookStub("useFetch", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useFetch = (path: string, options?: FetchHookOptions): FetchHookResult => { + // Used to prevent state update if the component is unmounted + const mounted = adapter.framework.useRef(true); + const { onStreamComplete, ...optionsToMemoize } = options ?? {}; + const memoizedOptions = coreHooks.useStructuralMemo(optionsToMemoize); + const connection = coreHooks.useConnection(); + const startRequestOnMount = startRequestByDefault(memoizedOptions); + const controller = adapter.framework.useRef(null); + + const [state, dispatch] = adapter.framework.useReducer, FetchHookOptions, [FetchAction]>( + reducer, + memoizedOptions, + (memoizedOptions) => { + return { fetching: startRequestOnMount, streaming: false, options: memoizedOptions }; + } + ); + + const send = adapter.framework.useCallback( + async (sendOptions?: Partial): Promise => { + if (controller.current && !controller.current.signal.aborted) { + controller.current.abort("useFetch is starting a new request, aborting the previous one"); + } + + const abortContoller = new AbortController(); + controller.current = abortContoller; + + dispatch({ type: "fetching" }); + + let data: any; + let response: Response | undefined = undefined; + + const mergedOptions = { ...memoizedOptions, onStreamComplete, ...sendOptions }; + + // add implicit headers from options, being careful not to mutate any inputs + if (mergedOptions.json) { + mergedOptions.headers = { ...mergedOptions.headers }; + (mergedOptions.headers as any)["accept"] ??= "application/json"; + } + + try { + const { json: _json, stream: _stream, onStreamComplete: _onStreamComplete, ...fetchOptions } = mergedOptions; + // make the fetch call using GadgetConnection to pass along auth and other headers + response = await connection.fetch(path, { signal: abortContoller.signal, ...fetchOptions }); + if (!response.ok) { + throw new Error(response.statusText); + } + + let dispatchData = true; + + if (mergedOptions.json) { + data = await response.json(); + } else if (typeof mergedOptions.stream === "string") { + dispatchData = false; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const decodedStream = response.body!.pipeThrough( + new TextDecoderStream(mergedOptions.stream === "string" ? "utf8" : mergedOptions.stream) + ); + + const [responseStream, updateStream] = decodedStream.tee(); + + data = responseStream; + const decodedStreamReader = updateStream.getReader(); + + decodedStreamReader.closed.catch((error) => { + dispatchError(mounted, dispatch, abortContoller, error, response); + }); + + dispatch({ type: "fetched", payload: "" as any }); + + (async () => { + let responseText = ""; + let done = false; + + dispatch({ type: "streaming" }); + + while (!done) { + const { value, done: _done } = await decodedStreamReader.read(); + done = _done; + + if (value) { + responseText += value; + + if (!abortContoller.signal.aborted) { + dispatch({ type: "update", payload: responseText as any }); + } + } + } + + mergedOptions.onStreamComplete?.(responseText); + })() + .catch((error) => { + dispatchError(mounted, dispatch, abortContoller, error, response); + }) + .finally(() => { + dispatch({ type: "streamed" }); + }); + } else if (mergedOptions.stream) { + data = response.body; + } else { + data = await response.text(); + } + + if (!mounted.current || !dispatchData) return data; + + dispatch({ type: "fetched", payload: data }); + } catch (error: any) { + const wrapped = dispatchError(mounted, dispatch, abortContoller, error, response); + if (!wrapped) return null as any; + throw wrapped; + } + return data; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [connection, memoizedOptions, path] + ); + + // track if we're mounted or not + adapter.framework.useEffect(() => { + mounted.current = true; + + return () => { + mounted.current = false; + }; + }, []); + + // execute the initial request on mount if needed + adapter.framework.useEffect(() => { + if (startRequestOnMount) { + void send().catch(() => { + // error will be reported via the return value of the hook + }); + } + + // abort if the component is unmounted, or if one of the key elements of the request changes such that we don't want an outstanding request's result anymore + return () => { + controller.current?.abort(); + }; + }, [path, startRequestOnMount, send]); + + return [state, send]; + }; +}); diff --git a/packages/client-hooks/src/useFindBy.ts b/packages/client-hooks/src/useFindBy.ts new file mode 100644 index 000000000..ca0b195a6 --- /dev/null +++ b/packages/client-hooks/src/useFindBy.ts @@ -0,0 +1,53 @@ +import { ErrorWrapper, GadgetNotFoundError, get, getNonUniqueDataError, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseFindBy } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useFindBy: UseFindBy = createHookStub("useFindBy", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useFindBy = (finder, value, options) => { + const connection = coreHooks.useConnection(); + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.findOneByFieldOperation( + finder.operationName, + finder.findByVariableName, + value, + finder.defaultSelection, + finder.modelApiIdentifier, + memoizedOptions, + finder.namespace + ); + }, [finder, value, memoizedOptions]); + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, options)); + + const result = adapter.framework.useMemo(() => { + const dataPath = namespaceDataPath([finder.operationName], finder.namespace); + + let data = rawResult.data; + let records = []; + if (data) { + const gqlConnection = get(rawResult.data, dataPath); + if (gqlConnection) { + records = connection.hydrateConnection(rawResult, gqlConnection); + data = records[0]; + } + } + + let error = ErrorWrapper.forMaybeCombinedError(rawResult.error); + if (!error) { + if (records.length > 1) { + error = ErrorWrapper.forClientSideError(getNonUniqueDataError(finder.modelApiIdentifier, finder.findByVariableName, value)); + } else if (rawResult.data && !records[0]) { + error = ErrorWrapper.forClientSideError( + new GadgetNotFoundError(`${finder.modelApiIdentifier} record with ${finder.findByVariableName}=${value} not found`) + ); + } + } + + return { ...rawResult, data, error }; + }, [rawResult, finder, value]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useFindFirst.ts b/packages/client-hooks/src/useFindFirst.ts new file mode 100644 index 000000000..d6f4bc286 --- /dev/null +++ b/packages/client-hooks/src/useFindFirst.ts @@ -0,0 +1,37 @@ +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseFindFirst } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useFindFirst: UseFindFirst = createHookStub("useFindFirst", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useFindFirst = (manager, options) => { + const connection = coreHooks.useConnection(); + const firstOptions = { ...options, first: 1 }; + const memoizedOptions = coreHooks.useStructuralMemo(firstOptions); + const plan = adapter.framework.useMemo(() => { + return connection.findManyOperation( + manager.findFirst.operationName, + manager.findFirst.defaultSelection, + manager.findFirst.modelApiIdentifier, + memoizedOptions, + manager.findFirst.namespace + ); + }, [manager, memoizedOptions]); + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, firstOptions)); + + const result = adapter.framework.useMemo(() => { + const dataPath = namespaceDataPath([manager.findFirst.operationName], manager.findFirst.namespace); + let data = rawResult.data && get(rawResult.data, dataPath); + if (data) { + data = connection.hydrateConnection(rawResult, data)[0]; + } + + const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); + + return { ...rawResult, data, error }; + }, [manager.findFirst.operationName, options?.pause, rawResult]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useFindMany.ts b/packages/client-hooks/src/useFindMany.ts new file mode 100644 index 000000000..0b174655d --- /dev/null +++ b/packages/client-hooks/src/useFindMany.ts @@ -0,0 +1,39 @@ +import type { AnyModelManager } from "@gadgetinc/api-client-core"; +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseFindMany } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useFindMany: UseFindMany = createHookStub("useFindMany", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useFindMany = (manager, options) => { + const connection = coreHooks.useConnection(); + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.findManyOperation( + manager.findMany.operationName, + manager.findMany.defaultSelection, + manager.findMany.modelApiIdentifier, + memoizedOptions, + manager.findMany.namespace + ); + }, [manager, memoizedOptions]); + + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, options)); + + const result = adapter.framework.useMemo(() => { + const dataPath = namespaceDataPath([manager.findMany.operationName], manager.findMany.namespace); + let data = rawResult.data; + let gqlConnection = rawResult.data && get(rawResult.data, dataPath); + if (gqlConnection) { + data = connection.hydrateRecordListFromConnection(manager as unknown as AnyModelManager, rawResult, gqlConnection); + } + + const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); + + return { ...rawResult, data, error }; + }, [manager, options?.pause, rawResult]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useFindOne.ts b/packages/client-hooks/src/useFindOne.ts new file mode 100644 index 000000000..ab8689515 --- /dev/null +++ b/packages/client-hooks/src/useFindOne.ts @@ -0,0 +1,37 @@ +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import type { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseFindOne } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useFindOne: UseFindOne = createHookStub("useFindOne", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useFindOne = (manager, id, options) => { + const connection = coreHooks.useConnection(); + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.findOneOperation( + manager.findOne.operationName, + id, + manager.findOne.defaultSelection, + manager.findOne.modelApiIdentifier, + memoizedOptions, + manager.findOne.namespace + ); + }, [manager, id, memoizedOptions]); + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, options)); + + const result = adapter.framework.useMemo(() => { + const dataPath = namespaceDataPath([manager.findOne.operationName], manager.findOne.namespace); + + let data = rawResult.data && get(rawResult.data, dataPath); + if (data) { + data = connection.hydrateRecord(rawResult, data); + } + const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); + + return { ...rawResult, data, error }; + }, [manager.findOne.operationName, rawResult, options?.pause]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useGet.ts b/packages/client-hooks/src/useGet.ts new file mode 100644 index 000000000..e29809c2a --- /dev/null +++ b/packages/client-hooks/src/useGet.ts @@ -0,0 +1,41 @@ +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseGet } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useGet: UseGet = createHookStub("useGet", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useGet = (manager, options) => { + const connection = coreHooks.useConnection(); + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.findOneOperation( + manager.get.operationName, + undefined, + manager.get.defaultSelection, + manager.get.modelApiIdentifier, + memoizedOptions + ); + }, [manager, memoizedOptions]); + + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, options)); + const dataPath = namespaceDataPath([manager.get.operationName], manager.get.namespace); + + const result = adapter.framework.useMemo(() => { + let data = null; + const rawRecord = rawResult.data && get(rawResult.data, dataPath); + if (rawRecord) { + data = connection.hydrateRecord(rawResult, rawRecord); + } + const error = ErrorWrapper.forMaybeCombinedError(rawResult.error); + + return { + ...rawResult, + error, + data, + }; + }, [rawResult, manager]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useGlobalAction.ts b/packages/client-hooks/src/useGlobalAction.ts new file mode 100644 index 000000000..17af66163 --- /dev/null +++ b/packages/client-hooks/src/useGlobalAction.ts @@ -0,0 +1,69 @@ +import type { GlobalActionFunction, StubbedActionFunction } from "@gadgetinc/api-client-core"; +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { OperationContext } from "@urql/core"; +import { RuntimeAdapter, UseMutationState } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseGlobalAction } from "./types.js"; + +export let useGlobalAction: UseGlobalAction = createHookStub("useGlobalAction", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useGlobalAction = (action) => { + const connection = coreHooks.useConnection(); + adapter.framework.useEffect(() => { + if (action.type === ("stubbedAction" as string)) { + const stubbedAction = action as unknown as StubbedActionFunction; + if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction)) { + // Don't dispatch an event if the generated client has not yet been updated with the updated parameters + return; + } + + const event = new CustomEvent("gadget:devharness:stubbedActionError", { + detail: { + reason: stubbedAction.reason, + action: { + functionName: stubbedAction.functionName, + actionApiIdentifier: stubbedAction.actionApiIdentifier, + dataPath: stubbedAction.dataPath, + }, + }, + }); + globalThis.dispatchEvent(event); + } + }, []); + + const plan = adapter.framework.useMemo(() => { + return connection.globalActionOperation(action.operationName, action.variables, action.namespace); + }, [action]); + + const [result, runMutation] = coreHooks.useGadgetMutation(plan.query); + + const transformedResult = adapter.framework.useMemo(() => processResult(result, action), [result, action]); + + return [ + transformedResult, + adapter.framework.useCallback( + async (variables?: (typeof action)["variablesType"], context?: Partial) => { + const result = await runMutation(variables ?? {}, context); + return processResult({ fetching: false, ...result }, action); + }, + [action, runMutation] + ), + ]; + }; +}); + +const processResult = (result: UseMutationState, action: GlobalActionFunction) => { + let error = ErrorWrapper.forMaybeCombinedError(result.error); + let data = undefined; + if (result.data) { + const dataPath = namespaceDataPath([action.operationName], action.namespace); + data = get(result.data, dataPath); + if (data) { + const errors = data.errors; + data = data.result; + if (errors && errors[0]) { + error = ErrorWrapper.forErrorsResponse(errors); + } + } + } + return { ...result, error, data }; +}; diff --git a/packages/client-hooks/src/useMaybeFindFirst.ts b/packages/client-hooks/src/useMaybeFindFirst.ts new file mode 100644 index 000000000..cd0997673 --- /dev/null +++ b/packages/client-hooks/src/useMaybeFindFirst.ts @@ -0,0 +1,42 @@ +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseMaybeFindFirst } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useMaybeFindFirst: UseMaybeFindFirst = createHookStub("useMaybeFindFirst", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useMaybeFindFirst = (manager, options) => { + const connection = coreHooks.useConnection(); + const firstOptions = { ...options, first: 1 }; + const memoizedOptions = coreHooks.useStructuralMemo(firstOptions); + const plan = adapter.framework.useMemo(() => { + return connection.findManyOperation( + manager.findFirst.operationName, + manager.findFirst.defaultSelection, + manager.findFirst.modelApiIdentifier, + memoizedOptions, + manager.findFirst.namespace + ); + }, [manager, memoizedOptions]); + + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, firstOptions)); + + const result = adapter.framework.useMemo(() => { + const dataPath = namespaceDataPath([manager.findFirst.operationName], manager.findFirst.namespace); + let data = (rawResult.data && get(rawResult.data, dataPath)) ?? null; + if (data) { + data = connection.hydrateConnection(rawResult, data)[0] ?? null; + } + + const error = ErrorWrapper.forMaybeCombinedError(rawResult.error); + + return { + ...rawResult, + error, + data, + }; + }, [rawResult, manager]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useMaybeFindOne.ts b/packages/client-hooks/src/useMaybeFindOne.ts new file mode 100644 index 000000000..ee53be2c9 --- /dev/null +++ b/packages/client-hooks/src/useMaybeFindOne.ts @@ -0,0 +1,40 @@ +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, UseMaybeFindOne } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useMaybeFindOne: UseMaybeFindOne = createHookStub("useMaybeFindOne", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useMaybeFindOne = (manager, id, options) => { + const connection = coreHooks.useConnection(); + const memoizedOptions = coreHooks.useStructuralMemo(options); + const plan = adapter.framework.useMemo(() => { + return connection.findOneOperation( + manager.findOne.operationName, + id, + manager.findOne.defaultSelection, + manager.findOne.modelApiIdentifier, + memoizedOptions, + manager.findOne.namespace + ); + }, [manager, id, memoizedOptions]); + + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, options)); + + const result = adapter.framework.useMemo(() => { + const dataPath = namespaceDataPath([manager.findOne.operationName], manager.findOne.namespace); + let data = (rawResult.data && get(rawResult.data, dataPath)) ?? null; + if (data) { + data = data && "id" in data ? connection.hydrateRecord(rawResult, data) : null; + } + const error = ErrorWrapper.forMaybeCombinedError(rawResult.error); + return { + ...rawResult, + error, + data, + }; + }, [rawResult, manager]); + + return [result, refresh]; + }; +}); diff --git a/packages/client-hooks/src/useView.ts b/packages/client-hooks/src/useView.ts new file mode 100644 index 000000000..49add8b66 --- /dev/null +++ b/packages/client-hooks/src/useView.ts @@ -0,0 +1,73 @@ +import type { GQLBuilderResult, VariablesOptions, ViewFunction, ViewResult } from "@gadgetinc/api-client-core"; +import { ErrorWrapper, get, namespaceDataPath } from "@gadgetinc/utils"; +import { RuntimeAdapter } from "./adapter.js"; +import { createHookStub } from "./createHooks.js"; +import type { CoreHooks, ReadHookResult, ReadOperationOptions, UseView } from "./types.js"; +import { useQueryArgs } from "./utils.js"; + +export let useView: UseView = createHookStub("useView", (adapter: RuntimeAdapter, coreHooks: CoreHooks) => { + useView = >( + view: F | string, + variablesOrOptions?: VariablesT | Omit, + maybeOptions?: Omit + ): ReadHookResult> => { + let variables: VariablesT | undefined; + let options: Omit | undefined; + + if (typeof view == "string" || "variables" in view) { + variables = variablesOrOptions as VariablesT; + options = maybeOptions; + } else if (variablesOrOptions) { + options = variablesOrOptions as Omit; + } + + const memoizedVariables = coreHooks.useStructuralMemo(variables); + const memoizedOptions = coreHooks.useStructuralMemo({ + ...options, + context: { + ...options?.context, + // if the view exports the typenames it references, add them to the context so urql will refresh the view when mutations are made against these typenames + additionalTypenames: [ + ...(options?.context?.additionalTypenames ?? []), + ...(typeof view == "string" ? [] : view.referencedTypenames ?? []), + ], + }, + }); + + const [plan, dataPath] = adapter.framework.useMemo((): [plan: GQLBuilderResult, dataPath: string[]] => { + if (typeof view == "string") { + return [{ query: inlineViewQuery, variables: { query: view, variables: memoizedVariables } }, ["gellyView"]]; + } else { + const variablesOptions: VariablesOptions = {}; + if ("variables" in view && memoizedVariables) { + for (const [name, variable] of Object.entries(view.variables)) { + const value = memoizedVariables[name as keyof typeof memoizedVariables] as unknown; + if (typeof value != "undefined" && value !== null) { + variablesOptions[name] = { + value, + ...variable, + }; + } + } + } + + return [view.plan(variablesOptions), namespaceDataPath([view.gqlFieldName], view.namespace)]; + } + }, [view, memoizedVariables]); + + const [rawResult, refresh] = coreHooks.useGadgetQuery(useQueryArgs(plan, memoizedOptions)); + + const result = adapter.framework.useMemo(() => { + const data = get(rawResult.data, dataPath); + const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); + + return { ...rawResult, data, error }; + }, [dataPath, options?.pause, rawResult]); + + return [result, refresh]; + }; +}); + +const inlineViewQuery = `query InlineView($query: String!, $variables: JSONObject) { + gellyView(query: $query, variables: $variables) +}`; diff --git a/packages/client-hooks/src/utils.ts b/packages/client-hooks/src/utils.ts new file mode 100644 index 000000000..feaab5655 --- /dev/null +++ b/packages/client-hooks/src/utils.ts @@ -0,0 +1,24 @@ +import type { OperationContext, RequestPolicy } from "@urql/core"; + +interface QueryPlan { + variables: any; + query: string; +} + +interface QueryOptions { + context?: Partial; + pause?: boolean; + requestPolicy?: RequestPolicy; + suspense?: boolean; +} + +/** + * Given a plan from a gadget query plan generator, create the query options object to pass to `urql`'s `useQuery` hook + **/ +export const useQueryArgs = (plan: Plan, options?: Options): any => ({ + query: plan.query, + variables: plan.variables, + ...options, +}); + +export const noProviderErrorMessage = `Could not find a client in the context of Provider. Please ensure you wrap the root component in a `; diff --git a/packages/client-hooks/tsconfig.base.json b/packages/client-hooks/tsconfig.base.json new file mode 100644 index 000000000..37dd6956f --- /dev/null +++ b/packages/client-hooks/tsconfig.base.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": ["es2020", "DOM"], + "jsx": "react", + "baseUrl": "./", + "target": "es2020", + "types": ["jest", "node"], + "importHelpers": true + } +} diff --git a/packages/client-hooks/tsconfig.cjs.json b/packages/client-hooks/tsconfig.cjs.json new file mode 100644 index 000000000..25c2e42ab --- /dev/null +++ b/packages/client-hooks/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/cjs", + "module": "CommonJS", + "moduleResolution": "node" + }, + "include": ["./src"] +} diff --git a/packages/client-hooks/tsconfig.esm.json b/packages/client-hooks/tsconfig.esm.json new file mode 100644 index 000000000..06e3c57bb --- /dev/null +++ b/packages/client-hooks/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/esm", + "module": "nodenext", + "moduleResolution": "nodenext" + }, + "include": ["./src"] +} diff --git a/packages/client-hooks/tsconfig.json b/packages/client-hooks/tsconfig.json new file mode 100644 index 000000000..ac15ade5f --- /dev/null +++ b/packages/client-hooks/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": ["es2020", "DOM"], + "jsx": "react", + "baseUrl": "./", + "moduleResolution": "nodenext", + "module": "nodenext", + "target": "es2020", + "types": ["jest", "node"], + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "../../" + }, + "include": ["./src", "./spec"] +} diff --git a/packages/preact/jest.config.js b/packages/preact/jest.config.js new file mode 100644 index 000000000..bbf51209b --- /dev/null +++ b/packages/preact/jest.config.js @@ -0,0 +1,43 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html + +export default { + displayName: "preact", + clearMocks: true, + extensionsToTreatAsEsm: [".ts", ".tsx"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + "^preact(/(.*)|$)": "preact$1", + "^react$": "preact/compat", + "^react-dom/test-utils$": "preact/test-utils", + "^react-dom$": "preact/compat", + "^react/jsx-runtime$": "preact/jsx-runtime", + "^.+\\.(css|sass|scss|less)$": "identity-obj-proxy", + }, + restoreMocks: true, + roots: [""], + setupFilesAfterEnv: ["/spec/jest.setup.ts"], + testEnvironment: "jsdom", + testPathIgnorePatterns: ["/node_modules/"], + testRunner: "jest-circus/runner", + transform: { + "^.+\\.(t|j)sx?$": [ + "@swc/jest", + { + configFile: false, + env: { targets: { node: 20 } }, + jsc: { + parser: { syntax: "typescript", tsx: true }, + transform: { + react: { + runtime: "automatic", + importSource: "preact", + }, + }, + }, + module: { type: "es6" }, + sourceMaps: true, + }, + ], + }, +}; diff --git a/packages/preact/package.json b/packages/preact/package.json new file mode 100644 index 000000000..769f03361 --- /dev/null +++ b/packages/preact/package.json @@ -0,0 +1,46 @@ +{ + "name": "@gadgetinc/preact", + "version": "0.1.0", + "files": [ + "README.md", + "dist/**/*" + ], + "license": "MIT", + "repository": "github:gadget-inc/js-clients", + "homepage": "https://github.com/gadget-inc/js-clients/tree/main/packages/preact", + "type": "module", + "exports": { + "./package.json": "./package.json", + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "default": "./dist/esm/index.js" + } + }, + "source": "src/index.ts", + "main": "dist/cjs/index.js", + "sideEffects": false, + "scripts": { + "typecheck:main": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist/ *.tsbuildinfo **/*.tsbuildinfo", + "prebuild": "mkdir -p dist/cjs dist/esm && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && echo '{\"type\": \"module\"}' > dist/esm/package.json", + "build": "pnpm clean && pnpm prebuild && tsc -b tsconfig.cjs.json tsconfig.esm.json", + "prepublishOnly": "pnpm build", + "prerelease": "gitpkg publish" + }, + "dependencies": { + "@gadgetinc/client-hooks": "^0.1.0", + "@urql/preact": "^5.0.0", + "fast-deep-equal": "^3.1.3" + }, + "devDependencies": { + "@gadgetinc/api-client-core": "workspace:*", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/preact": "^3.2.4", + "preact": "^10.27.2" + }, + "peerDependencies": { + "preact": "^10.0.0" + } +} diff --git a/packages/preact/spec/jest.setup.ts b/packages/preact/spec/jest.setup.ts new file mode 100644 index 000000000..47ee0a3d1 --- /dev/null +++ b/packages/preact/spec/jest.setup.ts @@ -0,0 +1,13 @@ +import { jest } from "@jest/globals"; +import "@testing-library/jest-dom"; +import crossFetch from "cross-fetch"; +import { act } from "preact/test-utils"; + +(global as any).act = act; + +jest.setTimeout(process.env.CI == "vscode-jest-tests" ? 20 * 60 * 1000 : 5 * 1000); + +// nock's fetch support is forthcoming and still broken, so we mock the global fetch to use cross-fetch, which nock supports mocking just fine +global.fetch = crossFetch; + +export {}; diff --git a/packages/preact/spec/testWrappers.tsx b/packages/preact/spec/testWrappers.tsx new file mode 100644 index 000000000..71412ffb4 --- /dev/null +++ b/packages/preact/spec/testWrappers.tsx @@ -0,0 +1,16 @@ +import type { AnyClient } from "@gadgetinc/api-client-core"; +import { jest } from "@jest/globals"; +import { ComponentChildren } from "preact"; +import { mockUrqlClient, type MockUrqlClient } from "../../api-client-core/spec/mockUrqlClient.js"; +import { Provider } from "../src/GadgetProvider.js"; + +export const MockClientWrapper = (api: AnyClient, urqlClient?: MockUrqlClient) => (props: { children: ComponentChildren }) => { + const urql = urqlClient ?? mockUrqlClient; + + jest.spyOn(api.connection, "currentClient" as any, "get").mockReturnValue(urql); + api.connection.fetch = urql.mockFetch as any; + + return {props.children}; +}; + +export { mockUrqlClient }; diff --git a/packages/preact/spec/useGadgetQuery.spec.ts b/packages/preact/spec/useGadgetQuery.spec.ts new file mode 100644 index 000000000..6d647481a --- /dev/null +++ b/packages/preact/spec/useGadgetQuery.spec.ts @@ -0,0 +1,48 @@ +import { Client } from "@gadget-client/related-products-example"; +import { renderHook } from "@testing-library/preact"; +import { gql } from "@urql/preact"; +import { act } from "preact/test-utils"; +import { useQuery as useGadgetQuery } from "../src/hooks.js"; +import { MockClientWrapper, mockUrqlClient } from "./testWrappers.js"; + +const api = new Client({ environment: "Development" }); + +describe("useGadgetQuery", () => { + test("can query data", async () => { + const { result } = renderHook( + () => + useGadgetQuery({ + query: gql` + query { + gadgetMeta { + name + } + } + `, + }), + { wrapper: MockClientWrapper(api) } + ); + + expect(result.current[0].data).toBeFalsy(); + expect(result.current[0].fetching).toBe(true); + expect(result.current[0].error).toBeFalsy(); + + expect(mockUrqlClient.executeQuery).toHaveBeenCalledTimes(1); + + act(() => { + mockUrqlClient.executeQuery.pushResponse("gadgetMeta", { + data: { + gadgetMeta: { + name: "Test App", + }, + }, + stale: false, + hasNext: false, + }); + }); + + expect(result.current[0].data.gadgetMeta.name).toEqual("Test App"); + expect(result.current[0].fetching).toBe(false); + expect(result.current[0].error).toBeFalsy(); + }); +}); diff --git a/packages/preact/src/GadgetProvider.tsx b/packages/preact/src/GadgetProvider.tsx new file mode 100644 index 000000000..ca2b10743 --- /dev/null +++ b/packages/preact/src/GadgetProvider.tsx @@ -0,0 +1,28 @@ +import type { AnyClient } from "@gadgetinc/api-client-core"; +import type { GadgetApiContext as GadgetApiContextType } from "@gadgetinc/client-hooks"; +import { registerClientHooks } from "@gadgetinc/client-hooks"; +import { Provider as UrqlProvider } from "@urql/preact"; +import type { ComponentChildren } from "preact"; +import { createContext } from "preact"; +import { preactAdapter } from "./adapter.js"; + +const GadgetApiContext = createContext(null as unknown as GadgetApiContextType); + +export interface ProviderProps { + api: AnyClient; + children: ComponentChildren; +} + +export function Provider(props: ProviderProps) { + const { gadgetClient, gadgetConnection, urqlClient } = registerClientHooks(props.api, { ...preactAdapter, GadgetApiContext }); + return ( + + {props.children} + + ); +} diff --git a/packages/preact/src/adapter.tsx b/packages/preact/src/adapter.tsx new file mode 100644 index 000000000..af80fc97b --- /dev/null +++ b/packages/preact/src/adapter.tsx @@ -0,0 +1,25 @@ +import type { Client, DocumentInput } from "@urql/core"; +import { Provider as UrqlProvider, UseQueryArgs, useMutation, useQuery } from "@urql/preact"; +import deepEqual from "fast-deep-equal"; +import { Fragment, createContext } from "preact"; +import { useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from "preact/hooks"; + +export const preactAdapter = { + framework: { + deepEqual, + useEffect, + useMemo, + useRef, + useState, + useContext, + createContext, + useCallback, + useReducer, + Fragment, + }, + urql: { + Provider: (props: { client: Client; children: any }) => {props.children}, + useQuery: (args: UseQueryArgs) => useQuery(args), + useMutation: (query: DocumentInput) => useMutation(query), + }, +}; diff --git a/packages/preact/src/hooks.ts b/packages/preact/src/hooks.ts new file mode 100644 index 000000000..1b34b0d69 --- /dev/null +++ b/packages/preact/src/hooks.ts @@ -0,0 +1,19 @@ +export { + useAction, + useApi, + useBulkAction, + useConnection, + useEnqueue, + useFetch, + useFindBy, + useFindFirst, + useFindMany, + useFindOne, + useGet, + useGlobalAction, + useMaybeFindFirst, + useMaybeFindOne, + useMutation, + useQuery, + useView, +} from "@gadgetinc/client-hooks"; diff --git a/packages/preact/src/index.ts b/packages/preact/src/index.ts new file mode 100644 index 000000000..4dc4724ff --- /dev/null +++ b/packages/preact/src/index.ts @@ -0,0 +1,2 @@ +export * from "./GadgetProvider.js"; +export * from "./hooks.js"; diff --git a/packages/preact/tsconfig.base.json b/packages/preact/tsconfig.base.json new file mode 100644 index 000000000..318adebd6 --- /dev/null +++ b/packages/preact/tsconfig.base.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": ["es2020", "DOM"], + "jsx": "react-jsx", + "jsxImportSource": "preact", + "baseUrl": "./", + "target": "es2020", + "types": ["jest", "node"], + "importHelpers": true + } +} diff --git a/packages/preact/tsconfig.cjs.json b/packages/preact/tsconfig.cjs.json new file mode 100644 index 000000000..25c2e42ab --- /dev/null +++ b/packages/preact/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/cjs", + "module": "CommonJS", + "moduleResolution": "node" + }, + "include": ["./src"] +} diff --git a/packages/preact/tsconfig.esm.json b/packages/preact/tsconfig.esm.json new file mode 100644 index 000000000..06e3c57bb --- /dev/null +++ b/packages/preact/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/esm", + "module": "nodenext", + "moduleResolution": "nodenext" + }, + "include": ["./src"] +} diff --git a/packages/preact/tsconfig.json b/packages/preact/tsconfig.json new file mode 100644 index 000000000..a47458418 --- /dev/null +++ b/packages/preact/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": ["es2020", "DOM"], + "jsx": "react-jsx", + "jsxImportSource": "preact", + "baseUrl": "./", + "moduleResolution": "nodenext", + "module": "nodenext", + "target": "es2020", + "types": ["jest", "node"], + "outDir": "./dist", + "resolveJsonModule": true, + "rootDir": "../../" + }, + "include": ["./src", "./spec"] +} diff --git a/packages/react-bigcommerce/jest.config.js b/packages/react-bigcommerce/jest.config.js index 33564a94b..dd29bf662 100644 --- a/packages/react-bigcommerce/jest.config.js +++ b/packages/react-bigcommerce/jest.config.js @@ -126,7 +126,7 @@ export default { // setupFiles: ["./spec/setup.ts"], // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: ["/spec/jest.setup.ts"], + setupFilesAfterEnv: ["/spec/jest.setup.ts"], // A list of paths to snapshot serializer modules Jest should use for snapshot testing // snapshotSerializers: [], diff --git a/packages/react-bigcommerce/package.json b/packages/react-bigcommerce/package.json index 9e3d9e1a2..516cfe7e8 100644 --- a/packages/react-bigcommerce/package.json +++ b/packages/react-bigcommerce/package.json @@ -1,6 +1,6 @@ { "name": "@gadgetinc/react-bigcommerce", - "version": "0.4.1", + "version": "0.5.0", "files": [ "README.md", "dist/**/*" @@ -26,9 +26,7 @@ "prepublishOnly": "pnpm build", "prerelease": "gitpkg publish" }, - "dependencies": { - "@gadgetinc/api-client-core": "^0.15.46" - }, + "dependencies": {}, "devDependencies": { "@gadgetinc/api-client-core": "workspace:*", "@gadgetinc/react": "workspace:*", @@ -41,6 +39,6 @@ "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0", - "@gadgetinc/react": "^0.22.0" + "@gadgetinc/react": "^0.23.0" } } diff --git a/packages/react-bigcommerce/spec/jest.setup.ts b/packages/react-bigcommerce/spec/jest.setup.ts new file mode 100644 index 000000000..b31ef4faf --- /dev/null +++ b/packages/react-bigcommerce/spec/jest.setup.ts @@ -0,0 +1,3 @@ +import { act } from "react"; + +(global as any).act = act; diff --git a/packages/react-shopify-app-bridge/jest.config.js b/packages/react-shopify-app-bridge/jest.config.js index 2c9a6b33a..1f4369f2b 100644 --- a/packages/react-shopify-app-bridge/jest.config.js +++ b/packages/react-shopify-app-bridge/jest.config.js @@ -126,7 +126,7 @@ export default { // setupFiles: ["./spec/setup.ts"], // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: ["/spec/jest.setup.ts"], + setupFilesAfterEnv: ["/spec/jest.setup.ts"], // A list of paths to snapshot serializer modules Jest should use for snapshot testing // snapshotSerializers: [], diff --git a/packages/react-shopify-app-bridge/package.json b/packages/react-shopify-app-bridge/package.json index 8cecee9a3..70adf36ef 100644 --- a/packages/react-shopify-app-bridge/package.json +++ b/packages/react-shopify-app-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@gadgetinc/react-shopify-app-bridge", - "version": "0.19.1", + "version": "0.20.0", "files": [ "README.md", "dist/**/*" @@ -27,7 +27,6 @@ "prerelease": "gitpkg publish" }, "dependencies": { - "@gadgetinc/api-client-core": "^0.15.46", "crypto-js": "^4.2.0", "tslib": "^2.6.2" }, @@ -45,7 +44,7 @@ "react-dom": "^19.1.1" }, "peerDependencies": { - "@gadgetinc/react": "^0.22.0", + "@gadgetinc/react": "^0.23.0", "@shopify/app-bridge-react": "^4.2.0", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/packages/react-shopify-app-bridge/spec/jest.setup.ts b/packages/react-shopify-app-bridge/spec/jest.setup.ts new file mode 100644 index 000000000..b31ef4faf --- /dev/null +++ b/packages/react-shopify-app-bridge/spec/jest.setup.ts @@ -0,0 +1,3 @@ +import { act } from "react"; + +(global as any).act = act; diff --git a/packages/react/package.json b/packages/react/package.json index 85e1a1bf9..22722e79c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@gadgetinc/react", - "version": "0.22.1", + "version": "0.23.0", "files": [ "README.md", "dist/**/*", @@ -49,23 +49,26 @@ "cypress:run": "TZ=UTC cypress run --component" }, "dependencies": { - "@0no-co/graphql.web": "^1.0.4", - "@gadgetinc/api-client-core": "^0.15.46", + "@gadgetinc/utils": "^0.1.0", + "@gadgetinc/client-hooks": "^0.1.0", "@hookform/resolvers": "^5.2.1", "filesize": "^10.1.2", "pluralize": "^8.0.0", "react-fast-compare": "^3.2.2", "react-hook-form": "~7.62.0", "tslib": "^2.6.2", - "urql": "^4.0.4", + "urql": "^5.0.1", "yup": "^1.2.0" }, "devDependencies": { + "@gadgetinc/utils": "workspace:*", + "@0no-co/graphql.web": "^1.0.4", "@changesets/cli": "^2.27.7", "@chromatic-com/storybook": "^3.2.7", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@gadgetinc/api-client-core": "workspace:*", + "@gadgetinc/client-hooks": "workspace:*", "@graphql-codegen/cli": "^5.0.0", "@graphql-codegen/client-preset": "^4.1.0", "@graphql-typed-document-node/core": "^3.2.0", @@ -108,7 +111,7 @@ "@types/react-dom": "^19.1.1", "@types/setup-polly-jest": "^0.5.5", "@types/tmp": "^0.2.6", - "@urql/core": "^4.0.10", + "@urql/core": "^6.0.1", "autoprefixer": "^10.4.20", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/packages/react/spec/ErrorWrapper.spec.ts b/packages/react/spec/ErrorWrapper.spec.ts deleted file mode 100644 index de82d66cd..000000000 --- a/packages/react/spec/ErrorWrapper.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { CombinedError } from "@urql/core"; -import { ErrorWrapper } from "../src/utils.js"; - -describe("ErrorWrapper", () => { - test("errorDataIfAbsent should return an error wrapper if data isn't found at a datapath", () => { - const error = ErrorWrapper.errorIfDataAbsent( - { - data: { - foo: {}, - }, - fetching: false, - stale: false, - }, - ["foo", "bar"] - ); - - expect(error).toBeTruthy(); - expect(error!.message).toMatchInlineSnapshot( - `"[GraphQL] Internal Error: Gadget API didn't return expected data. Nothing found in response at foo.bar"` - ); - }); - - test("errorDataIfAbsent should return null if data is found at a datapath", () => { - const error = ErrorWrapper.errorIfDataAbsent( - { - data: { - foo: { - bar: true, - }, - }, - fetching: false, - stale: false, - }, - ["foo", "bar"] - ); - - expect(error).toBeFalsy(); - }); - - test("forMaybeCombinedError should return a wrapper which reports all of urqls errors", () => { - const error = ErrorWrapper.forMaybeCombinedError( - new CombinedError({ - graphQLErrors: ["some server side graphql error"], - response: { statusCode: 500 }, - }) - ); - - expect(error!.response).toBeTruthy(); - expect(error!.executionErrors).toHaveLength(1); - expect(error!.networkError).toBeUndefined(); - expect(error!.message).toMatchInlineSnapshot(`"[GraphQL] some server side graphql error"`); - }); - - test("forErrorsResponse should return a wrapper which reports server errors", () => { - const error = ErrorWrapper.forErrorsResponse([{ code: "GGT_UNKNOWN", message: "Unknown server error occurred" }], { response: true }); - - expect(error.response).toBeTruthy(); - expect(error.executionErrors).toHaveLength(1); - expect(error.networkError).toBeUndefined(); - expect(error.message).toMatchInlineSnapshot(`"[GraphQL] GGT_UNKNOWN: Unknown server error occurred"`); - }); - - test("forErrorsResponse should return a wrapper which reports validation errors", () => { - const error = ErrorWrapper.forErrorsResponse( - [ - { - code: "GGT_INVALID_RECORD", - message: "widget record is invalid and can't be saved. name is not unique.", - validationErrors: [{ apiIdentifier: "name", message: "is not unique" }], - }, - ], - { response: true } - ); - - expect(error.response).toBeTruthy(); - expect(error.executionErrors).toHaveLength(1); - expect(error.networkError).toBeUndefined(); - expect(error.message).toMatchInlineSnapshot(`"[GraphQL] widget record is invalid and can't be saved. name is not unique."`); - expect(error.validationErrors).toMatchInlineSnapshot(` - [ - { - "apiIdentifier": "name", - "message": "is not unique", - }, - ] - `); - }); -}); diff --git a/packages/react/spec/GadgetProvider.spec.tsx b/packages/react/spec/GadgetProvider.spec.tsx index bba720123..2fb666f5b 100644 --- a/packages/react/spec/GadgetProvider.spec.tsx +++ b/packages/react/spec/GadgetProvider.spec.tsx @@ -4,8 +4,8 @@ import { GadgetConnection } from "@gadgetinc/api-client-core"; import { renderHook } from "@testing-library/react"; import type { ReactNode } from "react"; import React from "react"; -import { Provider, useApi, useConnection } from "../src/GadgetProvider.js"; -import { mockUrqlClient } from "./testWrappers.js"; +import { Provider } from "../src/GadgetProvider.js"; +import { useApi, useConnection } from "../src/hooks.js"; describe("GadgetProvider", () => { let mockApiClient: AnyClient; @@ -27,16 +27,6 @@ describe("GadgetProvider", () => { expect(result).toBeTruthy(); }); - test("internal components can access the urql client when wrapped in the provider which is passed an urql client", () => { - const { result } = renderHook(() => useConnection(), { - wrapper: (props: { children: ReactNode }) => { - return {props.children}; - }, - }); - - expect(result).toBeTruthy(); - }); - test("internal components can access the api client when wrapped in the provider which is passed an api client", () => { const { result } = renderHook(() => useApi(), { wrapper: (props: { children: ReactNode }) => { @@ -72,18 +62,4 @@ describe("GadgetProvider", () => { `"Invalid Gadget API client passed to component -- please pass an instance of your generated client, like !"` ); }); - - test("internal components can't access the api client when wrapped in the provider which is passed an urql client", () => { - expect(() => - renderHook(() => useApi(), { - wrapper: (props: { children: ReactNode }) => { - return {props.children}; - }, - }) - ).toThrowErrorMatchingInlineSnapshot(` - "useApi hook called in context with deprecated convention. Please ensure you are wrapping this hook with the component from @gadgetinc/react and passing it an instance of your api client, like . - - The component is currently being passed a value, like . Please update this to ." - `); - }); }); diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship-with-support-for-Id-pre_1709343723/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship-with-support-for-Id-pre_1709343723/recording.har index fde9dcf21..f302d79ca 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship-with-support-for-Id-pre_1709343723/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship-with-support-for-Id-pre_1709343723/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 656, - "text": "[\"H4sIAAAAAAAAA4RSXY/aMBD8K6d9zoGTFHr4DdFTxUlXocK9XFWdFnsT0gbbsjeICPHfK/NxfAjRN2d2djO7MxvQyAhyA2FhXVW0E291ozhEyGFJY1PY+F5g+EFrnmBJIAusAyURm3haVbYJF3hg9DxqfLAeJFD74t5H4/74z3P72r4MIAEy+l7544NbRwaXBBImRxHbBEiXFED+2oC6026spij5Ysz0Yj1IoNIgIc1ySGBudRtHrXHpanpYYd3QQ2H9w66QgLJLh56GPPGVop9o4q6mqesElCdk0kMGCZnI8sc0e0yzWdqXWSazXkeI9B3ipYyuP5vcXsQImUrr2yt41roTs5nXVVjsf3AFTZU9EQ/ujW7IEelM9KQQUoiOECLKObDfnD6yD1MYuQnHL8by9Kalq5Fp2hRFtf5EKz5t1Zym3TvFioyOzsWmbXLPpGddEmx/3+WMrDGkuLIm5qNEXRK/0j7Ri1Z7jKVdmM+d+oZMs2pJcKn6DL5xz7PqhS032t5uDb1e9vtO7NC5ulI7mTvd2xjzNZMJR+G1jTbAgtkF2e2WJXfqyvztRryb9kRfPHWVFvlc50rl6eDrEw7SVGv9JS+0ygZIqYYE2KOicQz9f8nb7T8AAAD//wMAExQorRcEAAA=\"]" + "text": "[\"H4sIAAAAAAAAA4RS227iMBD9FTTPKThG4eI3xFYVlbpCC33palU58SSkG2zLniAilH9fmUu5CLFvzpkzkzNzzg6UJAliB35lbJk3c2dUnZEPkJUFznRuwnsl/U/c0lwWCCKXlccoYHOHm9LU/gr3JB1Na+eNAwHYvNqP6Www+3pu3prXMUSAWj0qf35SY1HLNYKA+UlEGwGqAj2I3zvIHrRrozBIvhqzuFoPIigVCIh5HyJIjWrCqK1c2wo7G1nV2MmN6+wLEWRmbaXDCc1dmeEvqcOuuq6qCDKHklBNCARwxvtPMX+K+TIeCM4FT7qMxR8QLqVV9d1kDyKmkrAwrrmBl409M+u0Kv3q8IMbaJGZM/Ho3vSOHBYvWSIYE4x1GWNBzpH9btWJfZxCkmp/+iJZnN+4tpUkXNR5Xm6/0ZLOW9XnaY9OsUGtgnOhqY0emfSsCoT2z0PO1GiNGZVGh3wUUhVIb3hI9KpRTobSPsyXTv2QhMtyjXCt+gK+c8+L6pUtd9re7w29XfZlL3ZibVVme5l73W2I+ZZQ+5PwygQbYEVkvej1ioK6Van/9gLeixM2YKMexkOWcsnGw0E6inkfkzTNEj4cj3jGE5VABORkhrMQ+v+S2/YfAAAA//8DADV3PsYXBAAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:10 GMT" + "value": "Thu, 02 Oct 2025 21:11:02 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "981" }, { "name": "x-request-id", - "value": "ff5c4af7710aa175daccedc095e3d818" + "value": "f5ee9f5b00fd76ffeff40fa35197d052" }, { "name": "x-trace-id", - "value": "cd03bd3cc31978a911ddd43fdc29ae1d" + "value": "e170b2a0976b8123e5bbc527982c25d5" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12965c2eab46-YYZ" + "value": "988730a1cca47290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:10.505Z", - "time": 130, + "startedDateTime": "2025-10-02T21:11:02.676Z", + "time": 69, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 130 + "wait": 69 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQzW7CMBCEX6XasyGO80PiG2qlqoceWnHqBRl7CREmCfZagqK8e5WEUpULx13NzLezFzCKFMgLaIeK8CPU38Pkg9boPUhyARmgc63zIJtgLYPjVbRe07nDRh0QJIxOBrUBCXGWJsCukWZJIEFwkc14MRNixYXMhIz5vEizL2BAeBoUhJ6ejlNK6MxjZ8/+X/B8a/CJPlgaBJUyFdI7Th13Z+MU1W3j/xpPkBdFuKoPeMe+re9Zr2PusutsrcfEEdH3DPBE2Phfhm0rDxJ2RJ2XUVRVNLd1s4+GfRRnPOdFhCmWmcEyEYnZbraLdFOqfJEorXWax7kaXuSUxrfhtQ/Fff8DAAD//wMAxMzvEtQBAAA=\"]" + "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syF2gkPiDbVS1aFDK6YuyLWPYNUkwb5IUJT/XiWhVGVhvNN777t3Z7CaNKgzmICa8K1z38MUO2MwRlAUOmSAITQhgqo77xkcLqLNhk4t1nqPoGB0MnAWFIhcSGCXSLsiUJDyVM4En/F0nQolhOLpvBT8AxgQHgcFYaSHw5TStfa+s2f/L3i8NnjH2HkaBJW2FdIrTh13Jxs0uaaOf40nyJMmXLs93rCv61vW85i7alvvzJg4IvqeAR4J6/jL8E0VQcGOqI0qSaqK5t7VX8mwT4TkOS8SI8VyaWVhTZFnelHIDD9FXi6yrS63WS6GFwVt8GV47V1x3/8AAAD//wMAMALc5tQBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:10 GMT" + "value": "Thu, 02 Oct 2025 21:11:02 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "992" }, { "name": "x-request-id", - "value": "b7e147003223a12a45e20c789318f806" + "value": "dbd90b4a343a703e0050713111955e07" }, { "name": "x-trace-id", - "value": "e4e95de9323dfbf74b9a673accc4616a" + "value": "c5177d58dc863a4853eb16943fa9f361" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12974d60ebba-YYZ" + "value": "988730a26d357290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:10.658Z", - "time": 189, + "startedDateTime": "2025-10-02T21:11:02.781Z", + "time": 204, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 189 + "wait": 204 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship_3235963512/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship_3235963512/recording.har index 287f2efe7..c787e50b0 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship_3235963512/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-BelongsTo-relationship_3235963512/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 656, - "text": "[\"H4sIAAAAAAAAA4RSXY/aMBD8K2ifc+AEETi/IXqqOOkqVLiXq6qTsTchrbEte4OIUP57ZT6ODyH65szObmZ3ZgdKkAC+g7CyriqambeqlhQi5ESJU1PY+F6J8AO3NBMlAi+EDphEbOZxU9k6XOGBhKdJ7YP1wAGbV/cxmebTPy/NW/P6DAmgUY/Kn5/UODRijcBhdhLRJoCqxAD81w7kg3ZjFUbJV2PmV+tBApUCDmnWhwSWVjVx1FasncbORugaO4X1nX0hAWnXTngc08xXEn8KE3c1tdYJSI+CUI0JOGQs6z+l2VOaLdKcZxnPBl3G0g+IlzJKfzW5g4iJICytb27gRePOzHqpq7A6/OAGmkt7Jh7dm9yRw9IFG3DGOGNdxliUc2S/O3ViH6eQoDqcvkiU5zeunRaE87ooqu0XWtF5q/o87dEpNmhUdC42tckjk15UidD+fsiZWGNQUmVNzEcpVIn0hodErxrlRSztw3zp1DdBuKjWCNeqL+A797yoXtlyp+393tDbZb/vxY6d05Xcy9zrbmPMt4QmnIRrG22AFZELvNcrS+rqyvztRbyXDljORr1hpoajImejvD8aLmXWL4Z9hqOCFew5X+YSEiAvJE5j6P9Lbtt/AAAA//8DAKV+9owXBAAA\"]" + "text": "[\"H4sIAAAAAAAAA4RSXY/aMBD8K2ifc+AEhQO/IXqqOOkqVLiXq6rTEm9C2mBb9gYRofz3ynwcH0L0zZmd3czuzA4UMoLcgV8ZW+bNzBlVZ+wDZLGgqc5NeK/Q/6Atz7AgkDlWnqKAzRxtSlP7K9wzOp7UzhsHEqh5tR+T6WD656V5a15HEAFp9aj8+cmNJY1rAgmzk4g2AlIFeZC/dpA9aNdGUZB8NWZ+tR5EUCqQECd9iGBpVBNGbXFtK+pssKqpkxvX2RciyMzaoqMxz1yZ0U/UYVddV1UEmSNkUmMGCYlI+k9x8hQni3ggk0QmaVeI+APCpbSqvprsQcQEmQrjmht40dgzs15WpV8dfnADzTNzJh7dm9yRI+KFSKUQUoiuECLIObLfrTqxj1MYufanL8bi/Ka1rZBpXud5uf1CSz5vVZ+nPTrFhrQKzoWmNnpk0osqCNrfDzkTozVlXBod8lGgKojf6JDoVaMchtI+zJdOfUOmRbkmuFZ9Ad+550X1ypY7be/3ht4u+30vdmxtVWZ7mXvdbYj5lkn7k/DKBBtgxWy97PWKgrtVqf/2At6LUzEQw96wn6ZxP35OlkqowShWapnmCeFIDJfPmCmIgB1mNA2h/y+5bf8BAAD//wMAhMZp1RcEAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:09 GMT" + "value": "Thu, 02 Oct 2025 21:11:01 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "982" }, { "name": "x-request-id", - "value": "2b74be7a4fce02b13626fe29452d4a98" + "value": "43658dc6c1b7bfd949c25301578a6270" }, { "name": "x-trace-id", - "value": "72d78f6086387bc23f730e8f0f096b6c" + "value": "835513172bd0d691ddb5f2ea908b7acd" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f128f8811ab33-YYZ" + "value": "9887309cefeb7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:09.419Z", - "time": 144, + "startedDateTime": "2025-10-02T21:11:01.891Z", + "time": 90, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 144 + "wait": 90 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syG2gwl4Q61UdejQiqkLcu0jRA1JsM8SFOW/V04oVVkY7/Te++7dGZwhA/oM1qMhfIvVd5pCtBZDAE0+IgP0vvUBdBPrmsHhItps6NRhY/YIGgYng8qBBqFmAtgl0q0INEgu1YQvJlKuudRKasGnXBYfwIDwmBSEgR4OY0rs3H1nz/5f8Hht8I4h1pQEpXEl0iuOHXcn5w1VbRP+Go+QJ0O4rvZ4w76ub1nPQ+6q6+rKDokDou8Z4JGwCb+Mui0DaNgRdUFnWVnStK6aryztM6H4nC8ynn8ql9stLrdLa7gqCuW4yA0u5mJm0KUXeWPxJb32rrjvfwAAAP//AwDkJf8S1AEAAA==\"]" + "text": "[\"H4sIAAAAAAAAA4SQMW+DMBCF/0p1sxNsA4F6i1qp6tChVaYukbEPgkqA2IeUNOK/V4Y0VbNkvNN777t3Z7CaNKgzGIea8H2ov8PkB2PQe1DkBmSAznXOg2qHpmFwuIi2Wzr12Oo9goLJyaC2oECsRAzsEmnXBAokl+lC8AWXGymUEIrLpeTiExgQHoOC0NPDYU4ZenvfObL/FzxdG3ygHxoKgkrbCukN5467k3Wa6q71f41nyLMm3NR7vGFf17eslyl33fdNbabECTGODPBI2PpfRtNVHhTsiHqvoqiqaNnU7VcU9pFI+YrnUZYVmS3LWBY8tbEt8yJPSqsxEXEiH034JDlt8DW89q54HH8AAAD//wMAEPSw1tQBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:10 GMT" + "value": "Thu, 02 Oct 2025 21:11:02 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "998" }, { "name": "x-request-id", - "value": "8f8087b363b4f4cabc6bbc6bc7126f4d" + "value": "d4d9bda220b837fa777e2073ac07187f" }, { "name": "x-trace-id", - "value": "03b5d3cfe9f9ca05775d013ae8614aed" + "value": "77b7dff32b05d3df8b84fdae413429c3" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f1290c977ac25-YYZ" + "value": "9887309d88a57290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:09.623Z", - "time": 412, + "startedDateTime": "2025-10-02T21:11:01.996Z", + "time": 305, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 412 + "wait": 305 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship-with-support-f_3501493881/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship-with-support-f_3501493881/recording.har index 3773f1841..45260ce99 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship-with-support-f_3501493881/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship-with-support-f_3501493881/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 656, - "text": "[\"H4sIAAAAAAAAA4RSXY/aMBD8K2ifc+CEhg+/IXqqOOkqVLiXO1Wnxd6EtMG2bAcRofz3ynwcBCH65szObmZ3Zg8SPQLfg1trU2T13GpZCe8CZDCnmcp0eK/R/aSdn2NOwDMsHUUBm1vaFrpyLdx5tH5aWactcKD6xbxPZ4PZn+f6tX4ZQwSk5KPy56evDSncEHCYn0U0EZDMyQH/2IN40K60pCC5NWbRWg8iKCRwiJM+RLDSsg6jdrgxJXW2WFbUybTtHAoRCL0xaGni57YQ9AtV2FVVZRmBsISe5MQDh4Ql/ac4eYqTZTzgScKTtMtY/A7hUkqWX03mKGKKnnJt6xt4WZsLs1qVhVsff3ADLYS+EE/uTe/IYfGSpZwxzliXMRbknNhvRp7ZpykefeXOXx7zy5s2pkRPiyrLit0XWvjLVtVl2qNTbEnJ4FxoaqJHJj3LnKD5/ZAz1UqR8IVWIR85ypz8Kx0Tva6lxVA6hPnaqe/oaVlsCNqqr+A797yqtmy50/Z2b+jtsj8OYifGlIU4yDzobkLMd56UOwsvdbAB1t4bx3u9PPfdslB/ewHvxSkbsFHv25DGMu2n/WEiUzEc4ShJsmwVZ7QaCxQh5N6ioFkI/X/JTfMPAAD//wMAkibD8xcEAAA=\"]" + "text": "[\"H4sIAAAAAAAAA4RSXW/iMBD8K2ifU3CSgwa/IVqdqNQTOuhLq1Nl4k1wL9iWvUFEKP/9ZD7KhxD35szObmZ3ZgtSkAC+Bb80VhXN1BlZ5+QDZEWJE12Y8F4K/ws3NBUlAi9E5TEK2NThWpnaX+CehKNx7bxxwAGbF/s+ngwmX8/Na/MyhAhQy3vlz09qLGqxQuAwPYpoI0BZogf+sYX8Trs2EoPkizGzi/UgAiWBQ5ykEMHCyCaM2oiVrbCzFlWNncK4zq4QQW5WVjgc0dSpHH8LHXbVdVVFkDsUhHJEwCFhSfoQJw9xMo8HPEl40u8yFr9DuJSW1XeT3YsYC8LSuOYKnjf2xKwXlfLL/Q+uoFluTsSDe+Mbclg8Z33OGGesyxgLcg7sNyuP7MMUElT74xeJ8vTGla0E4awuCrX5RhWdtqpP0+6dYo1aBudCUxvdM+lZlgjtn7ucsdEac1JGh3yUQpZIr7hP9LKRToTSLsznTj0JwrlaIVyqPoNv3POsemHLjba3W0Ovl/25EzuytlL5TuZOdxtiviHU/ii8MsEGWBJZz3u9sqRupfTfXsB7cZ8NWNYbJmzw43HA5DDrLzDNRMYWWZo/IhZpNkyDNHIix0kI/X/JbfsPAAD//wMAUC2cfhcEAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:51:35 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "979" }, { "name": "x-request-id", - "value": "a97dff8dcc6af6fded71b2ace8096698" + "value": "f9d180da84600499964111a0affdbd2b" }, { "name": "x-trace-id", - "value": "47e9d535372d5c78a822ffb1feb9cac3" + "value": "92064760d985be38a80b83c7eef3893e" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f11bc8e62abee-YYZ" + "value": "988730a3ce927290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:51:35.609Z", - "time": 234, + "startedDateTime": "2025-10-02T21:11:02.993Z", + "time": 105, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 234 + "wait": 105 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQvW6DQBCEXyXa+mzuBzC5zkqkKEWKRK7SWMexYBQM+G5PsmPx7hHgOIobl7uamW9nz1AYMqDPYB0awvdQf4+TD9ai96DJBWSAznXOg25D0zA4XETbLZ16bM0eQcPkZFAXoEEkKgZ2iSzWBBokl8mCZwspN1zqRGiVLsWj/AQGhMdRQejp4TCnhL647xzY/wuerg0+0IeGRkFligrpDeeOu1PhDNVd6/8az5BnQ7ip93jDvq5vWS9T7rrvm9pOiRNiGBjgkbD1v4ymqzxo2BH1XkdRVdGyqduvaNxHIuEpz6IShV3FPM/TUqlMpYkSJY/VSiarNM4zMb7IGYuv42vviofhBwAA//8DAHjg093UAQAA\"]" + "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syG20wTiDbVS1aFDK6YuyI2PYGFCsM8SFOW/V04oVVkY7/Te++7dGYwmDeoMtUdN+B7td5pCrGsMART5iAzQ+70PoNroHIPDRbRa0anDVu8QFAxOBtaAAlGKEtgl0iwIFEgui4ngEy6XUighFM+nMi8/gQHhMSkIAz0cxpTYmfvOnv2/4Ona4ANDdJQEjTYN0huOHTcn4zXZfRv+Go+QZ024tDu8YV/Xt6yXIXfRdc7WQ+KA6HsGeCRswy/D7ZsACjZEXVBZ1jQ0dbbdZmmfiYKXfJ7lZWWk5jMhv6oZVqbAEud8/VjkXBc8X6cXeV3ja3rtXXHf/wAAAP//AwDM7F0N1AEAAA==\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:51:36 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "986" }, { "name": "x-request-id", - "value": "9d4eb315d66a510cc4b0c76444e24444" + "value": "4e6cfcb5c87d170b19f9dae55653cee5" }, { "name": "x-trace-id", - "value": "fe1c740bb6f33836531f043725764b81" + "value": "369d2a0712b97e9d5e6e80f4530a503f" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f11be2e1cebb9-YYZ" + "value": "988730a4bf597290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:51:35.916Z", - "time": 310, + "startedDateTime": "2025-10-02T21:11:03.148Z", + "time": 165, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 310 + "wait": 165 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship_1667904710/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship_1667904710/recording.har index eb9f64945..fb1118a1c 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship_1667904710/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-multiple-BelongsTo-relationship_1667904710/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 656, - "text": "[\"H4sIAAAAAAAAA4RSXW/aQBD8K9E+O3A2mIZ7QzSqiJQKFfKSqIoW39q4NXenuzXCQv7v1fERPoTo23l2dj27M1tQyAhyC35pbJk3U2dUnbEPkMWCJjo34b1E/5M2PMWCQOZYeYoCNnW0Lk3tL3DP6HhcO28cSKDmxb6PJ4PJn+fmtXkZQgSk1b3y5yc3ljSuCCRMjyLaCEgV5EF+bCG7066NoiD5YszsYj2IoFQgIU56EMHCqCaM2uDKVvSwxqqmh9y4h10hgsysLDoa8dSVGf1CHXbVdVVFkDlCJjVikJCIpPcYJ49xMo8HMklkknaEiN8hXEqr6qvJ7kWMkakwrrmC5409MetFVfrl/gdX0CwzJ+LBvfENOSKei1QKIYXoCCGCnAP7zaoj+zCFkWt//GIsTm9a2QqZZnWel5svtOTTVvVp2r1TrEmr4FxoaqN7Jj2rgqD9fZczNlpTxqXRIR8FqoL4lfaJXjbKYSjtwnzu1HdkmpcrgkvVZ/CNe55VL2y50fZ2a+j1sj92YkfWVmW2k7nT3YaYb5i0PwqvTLABlszWy263KLhTlfpvN+DdOBUD8dQV/VQNv/V7gx6lT4noK5UgilwJyofpYiAgAnaY0SSE/r/ktv0HAAD//wMAkEKGYhcEAAA=\"]" + "text": "[\"H4sIAAAAAAAAA4RSXY/aMBD8K2ifc+CYht75DdFTxUlXocK9XFWdjL0JaYNt2RtEhPLfK/NxEITomzM7u5ndmR1oSRLEDsLKujJvZt7qWlGIkJMFTk1u43slww/c0kwWCCKXVcAkYjOPm9LWoYMHkp4mtQ/WgwBsXtz7ZDqa/nluXpuXJ0gAjb5X/vigxqGRawQBs5OINgHUBQYQv3ag7rQbqzFK7oyZd9aDBEoNAlI+hASWVjdx1FauXYW9jaxq7OXW9/aFBJRdO+lxTDNfKvwpTdzV1FWVgPIoCfWYQABnfPiQ8oeUL9KR4FzwrM9Y+g7xUkZXn03uIGIiCQvrmyt40bgzs15WZVgdfnAFzZU9E4/uTW7IYemCZYIxwVifMRblHNlvTp/YxykkqQ6nL5LF+Y1rV0nCeZ3n5fYTLem8VX2edu8UGzQ6Oheb2uSeSc+6QGh/3+VMrDGoqLQm5qOQukB6xUOiV432Mpb2Yb506pskXJRrhK7qC/jGPS+qHVtutL3dGnq97Pe92LFzVan2Mve62xjzLaEJJ+GVjTbAisgFMRgUBfWr0vwdRHyQZmzEHgcj/iQZqiVPl8OMq1wxHGUZ+8pT+WWYPaaQAHmpcBpD/19y2/4DAAD//wMAIwWoohcEAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:10 GMT" + "value": "Thu, 02 Oct 2025 21:11:02 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "990" }, { "name": "x-request-id", - "value": "7d787bf293f560eb0f21892b7f901fc0" + "value": "56bcf5bed328611df5ff6766d7f7e539" }, { "name": "x-trace-id", - "value": "045d974363e58204dd2aa0fd0ef95b60" + "value": "629a0ecb21b352cfc0e6550721a43581" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12937f2eac6f-YYZ" + "value": "9887309f9a9f7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:10.044Z", - "time": 212, + "startedDateTime": "2025-10-02T21:11:02.323Z", + "time": 98, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 212 + "wait": 98 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syGOg1Hwhlqp6tChFVMX5NjXYNUkwb5IUJT/XjmhVGVhvNN777t3Z7CaNKgzmICa8K1332mKvTEYIygKPTLAENoQQTW99wwOF9F2S6cOG71HUDA6GTgLCnK5EMAukXZNoEBwIWe8nAmx4UJJoXI+X6zEBzAgPCYFYaSHw5TSd/a+c2D/L3i8NnjH2HtKglrbGukVp467kw2aXNvEv8YT5EkTbtweb9jX9S3recxdd513ZkwcEcPAAI+ETfxl+LaOoGBH1EWVZXVNc++aryzts1zyJS8zaQpZYamr1cpwK3NZWF5US8M/eWGl0elFQRt8Sa+9Kx6GHwAAAP//AwCXcnuq1AEAAA==\"]" + "text": "[\"H4sIAAAAAAAAA4SQzW7CMBCEX6XasyG2W+fHN9RKVQ89tOLUCzL2EixCEuyNBEV59yoJpSoXjruamW9nz+AMGdBnsAEN4Ufnv4cpdtZijKApdMgAQ2hCBF13VcXgcBGtVnRqsTZ7BA2jk4F3oEGk4gnYJdItCDRILtVM8BmXSym0EJrLuSryL2BAeBwUhJEeDlNK17r7zp79v+D52uATY1fRICiNK5Heceq4PblgyDd1/Gs8QV4M4dLv8YZ9Xd+yXsfcRdtW3o6JI6LvGeCRsI6/jKopI2jYErVRJ0lZ0rzy9S4Z9olQPOV5ssmMdZngeSbRFiozBWZis5b8UaXKqPXwomAsvg2vvSvu+x8AAAD//wMAwgXZ49QBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:10 GMT" + "value": "Thu, 02 Oct 2025 21:11:02 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "992" }, { "name": "x-request-id", - "value": "f80c67439f250bc5c2d0400298fe72ef" + "value": "bd0d8997b94fefaa227bdd116e8fc21f" }, { "name": "x-trace-id", - "value": "5c35be8ab99c0d5153d03b6c0f03d5ca" + "value": "f7acd710872ec957a9e71fb203565a5b" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f129509a374a7-YYZ" + "value": "988730a08b7e7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:10.300Z", - "time": 195, + "startedDateTime": "2025-10-02T21:11:02.477Z", + "time": 191, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 195 + "wait": 191 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-relationship_933332662/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-relationship_933332662/recording.har index 710f397b5..292daeb25 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-relationship_933332662/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-HasOne-relationship_933332662/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=createQuiz" }, "response": { - "bodySize": 384, + "bodySize": 380, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syF2giPjDbVS1aFDK6YuyLWPYDUkwT5LUJT/XiWhVGVhvNN777t3Z3CGDOgz2ICG8C3572GKyVqMETSFhAwwhDZE0E2qawaHi2izoVOHjdkjaBidDLwDDUIWS2CXSLci0JDzXM64muX5muda5pqrueTlBzAgPA4KwkgPhyklde6+s2f/L3i8NnjHmGoaBJVxFdIrTh13JxcM+baJf40nyJMhXPs93rCv61vW85i76rra2zFxRPQ9AzwSNvGXUbdVBA07oi7qLKsqmte++cqGfSYkL7nKPovCOrHd2mWBQiglFRZ2sZBWKJSl4cOLgrH4Mrz2rrjvfwAAAP//AwDevGUa1AEAAA==\"]" + "size": 380, + "text": "[\"H4sIAAAAAAAAA4SQzW7CMBCEX6XasyG2lR/iG2qlqoceWnHqBVnxEixMEuy1BEV598qBUpULx13NzLezZzCaNKgzNB414Ue032kKsWkwBFDkIzJA73sfQHXROQaHq2i9ptOAnd4jKJicDKwBBaIUAtg10iwJFEgui5ngMy5XUighFBfzoiq+gAHhMSkIAz0dLilxMI+dI/t/wfOtwSeG6CgJWm1apHe8dNyejNdk+y78Nb5AXjThyu7xjn1b37Nep9zlMDjbTIkTYhwZ4JGwC78M17cBFGyJhqCyrG1p7my3y9I+EwUv+SLLZW1qrDTKGkssdcE3Da9QLPINr/PSpBd53eBbeu1D8Tj+AAAA//8DAD08gV/UAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:08 GMT" + "value": "Thu, 02 Oct 2025 21:11:01 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "988" }, { "name": "x-request-id", - "value": "d79fd6778adb38aea96273eeb1d6e708" + "value": "fe1c17d2641274592790d88945c0d314" }, { "name": "x-trace-id", - "value": "b33cd1ffc93e118858e3c445c18e56a0" + "value": "429d9e7ae29e6e6a50fc07e184f0946d" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12884efdb407-YYZ" + "value": "9887309a2d737290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:08.251Z", - "time": 278, + "startedDateTime": "2025-10-02T21:11:01.460Z", + "time": 200, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 278 + "wait": 200 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-relationship_2952094097/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-relationship_2952094097/recording.har index c6159b245..f6a4a7885 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-relationship_2952094097/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-HasMany-relationship_2952094097/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=createQuiz" }, "response": { - "bodySize": 391, + "bodySize": 380, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 391, - "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syGOk5DEG2qlqkOHVkxdkLGvwapJgn2RoCj/vXKgVGVhvNN777t3JzCKFMgTaI+K8G2w33EKg9YYAkjyAzJA7zsfQLaDcwz2F9F6TcceW7VDkDA5GVgDEtIiK4FdIs2SQILgopjxaibEigtZCMnLeZ2XH8CA8BAVhIEe9ueUoTf3nSP7f8HjtcE7hsFRFDTKNEiveO64PRqvyHZt+Gt8hjwpwpXd4Q37ur5lPU+5y753Vk+JE2IcGeCBsA2/DNc1ASRsifogk6RpaO5s+5XEfZIWfMGrpEaz2JhMF6LMM8yyDTd1Kj6rSpeY5sbEF3ml8SW+9q54HH8AAAD//w==\",\"AwBCfTag1AEAAA==\"]" + "size": 380, + "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syF2Shzwhlqp6tChFVMX5NrXYNWEYJ8lKMp/r5xQqrIw3um99927E1hNGtQJTEBN+Jrcd55iMgZjBEUhIQMMYRciqDZ5z2B/Fq3XdOyw1VsEBYOTgbOgQEi+AHaOtEsCBSUvq4ngE16uSqGEUFxMuRDvwIDwkBWEke72Y0rq7G1nz/5f8HBp8IYxecqCRtsG6QXHjpujDZrcro1/jUfIoyZcuS1esS/ra9bTkLvsOu/MkDgg+p4BHgjb+MvwuyaCgg1RF1VRNA1NvWu/irwvRMUlnxey/qhnGs2n4ShFLe41zrGelXWlsVpImV8UtMHn/Nqb4r7/AQAA//8DAEOglDjUAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:08 GMT" + "value": "Thu, 02 Oct 2025 21:11:01 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "989" }, { "name": "x-request-id", - "value": "a489bf1e8745f0e1009b1f0e2d149045" + "value": "0d983fa924ed29a018bb2e980dd937eb" }, { "name": "x-trace-id", - "value": "9ed6bd3c52743e33b0d912f88c7e14dd" + "value": "67b74aecfc0e61713ae8e74275ae5966" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f1284ab8aab96-YYZ" + "value": "9887309609277290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:07.675Z", - "time": 279, + "startedDateTime": "2025-10-02T21:11:00.792Z", + "time": 306, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 279 + "wait": 306 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-relationship_3875503635/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-relationship_3875503635/recording.har index d828e484d..74fe75164 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-relationship_3875503635/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-a-single-HasMany-relationship_3875503635/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=createQuiz" }, "response": { - "bodySize": 384, + "bodySize": 380, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMU/DMBCF/wq62W0Su04ibxVIiIEB1ImlcuwjjUiT1D5LLVX+O3JSiujS8U7vve/encFq0qDOYBxqwrfQfMfJB2PQe1DkAjJA53rnQXWhbRkcLqLtlk4DdnqPoGByMmgsKMikkMAukXZNoICnXC7ScsH5JuVKcpUWy9Wq+AAGhMeoIPT0cJhTwmDvO0f2/4LHa4N39KGlKKi1rZFece64O1mnqek7/9d4hjxpwk2zxxv2dX3Lep5y18PQNmZKnBDjyACPhJ3/ZbR97UHBjmjwKknqmpZt030lcZ9kMs3TMhF5bksupEBpRJV9VllRSVFwzYXRVvL4IqcNvsTX3hWP4w8AAAD//wMAXBizVdQBAAA=\"]" + "size": 380, + "text": "[\"H4sIAAAAAAAAA4SQvW7DMAyEX6XgrMSU/BttQQsUHTq0yNQlUC3CEerYjkQDSQO/e2E7TdEsGXk43sfjGaxhA/oMpSfD9Na773EKfVlSCKDZ9ySAvG99AN30dS3gcDFtt3zqqDF7Ag3TpgBnQYPMMAdxibRrBg0KVbqQuEC1UVJLqRGXqIoPEMB0HB1MgR8Oc0rf2fubg/h/weO1wTuFvubRUBlbEb/S3HF3st6wa5vw13iGPBmmjdvTDfsq37Kep9x119WunBInxDAIoCNTE34ZdVsF0LBj7oKOoqriZe2ar2jUI5lihkWkJKJBaZK8VGRJ2jxJ4jiL03yFn3Ex3sTelPQyvvaueRh+AAAA//8DALwkrYPUAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:07 GMT" + "value": "Thu, 02 Oct 2025 21:11:00 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "999" }, { "name": "x-request-id", - "value": "d60e7261a574490202bba7d66384cf8f" + "value": "3f8565ea24511a4279f1063eae9c8fd8" }, { "name": "x-trace-id", - "value": "366d82353e5c3b1fb17b5372a23cad52" + "value": "2100a01a47c2ede1d74433635790b38e" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f1281ff57ac25-YYZ" + "value": "988730801bf87290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:07.210Z", - "time": 228, + "startedDateTime": "2025-10-02T21:10:57.204Z", + "time": 2957, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 228 + "wait": 2957 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-HasOne-relationships_2856696492/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-HasOne-relationships_2856696492/recording.har index 794d320e0..4ceb89003 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-HasOne-relationships_2856696492/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-HasOne-relationships_2856696492/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syHG1GniDbVS1aFDK6YuyLWPYNWEYJ8lKMp/r5xQqrIw3um99927E1hNGtQJTEBN+Jbcd55iMgZjBEUhIQMMYRciqDZ5z2B/Fq1WdOyw1VsEBYOTgbOgYCbvObBzpF0QKBBcyAmvJkIsuVBSKF5PRVl+AAPCQ1YQRrrbjymps7edPft/weOlwTvG5CkLGm0bpFccO26ONmhyuzb+NR4hT5pw6bZ4xb6sr1nPQ+6i67wzQ+KA6HsGeCBs4y/D75oICjZEXVRF0TQ09a79KvK+mEle8qrgtZ5LjdW8lqZe21qXRlYPwqxF9YnC6vyioA2+5NfeFPf9DwAAAP//AwCE9dUi1AEAAA==\"]" + "text": "[\"H4sIAAAAAAAAA4SQzW7CMBCEX6XasyG2Q37wDbVS1UMPrTj1goy9DVFDEuy1BEV598oJpSoXjruamW9nz2A1aVBnMA414Vuov+PkgzHoPShyARmgc53zoNrQNAwOF9FmQ6ceW71HUDA6GdQWFIhcSGCXSLsiUCC5zGaCz7hcS6GEUFzMi7L4AAaEx6gg9PRwmFJCb+87B/b/gsdrg3f0oaEoqLStkF5x6rg7Waep7lr/13iCPGnCdb3HG/Z1fct6HnNXfd/UZkwcEcPAAI+Erf9lNF3lQcGOqPcqSaqK5k3dfiVxn4iM57xMPrcSpcRFmhZLqfOyQL7UIjf5YlsYmaXxRU4bfImvvSsehh8AAAD//wMAKUi73dQBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:09 GMT" + "value": "Thu, 02 Oct 2025 21:11:01 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "991" }, { "name": "x-request-id", - "value": "e658edf5cb5f6ba140586ea4e78abab6" + "value": "4907419581bd4d1469d659222bedd3a5" }, { "name": "x-trace-id", - "value": "09a35ae8395c9fd9a6c5872cf28be2da" + "value": "fb2e22e433792a687e09a16c64b7c253" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f128a08d3abe2-YYZ" + "value": "9887309b7ea57290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:08.538Z", - "time": 871, + "startedDateTime": "2025-10-02T21:11:01.668Z", + "time": 215, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 871 + "wait": 215 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-relationships_3250074585/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-relationships_3250074585/recording.har index b0d962011..9cd9c3e96 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-relationships_3250074585/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-HasMany-relationships_3250074585/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=createQuiz" }, "response": { - "bodySize": 384, + "bodySize": 380, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMU/DMBCF/wq62W0cm1DjrQIJMTCAOrFUjn1KLdI0tc9SS5X/jpyUIrp0vNN777t3J3CGDOgT2ICG8D357zzFZC3GCJpCQgYYwi5E0F1qWwb7s2i9pmOPndkiaBidDLwDDWUlFbBzpFsSaBBcVDOuZkKsuNCV0FzNhZSfwIDwkBWEke72U0rq3W3nwP5f8HRp8IExtZQFjXEN0htOHTdHFwz5XRf/Gk+QZ0O48lu8Yl/W16yXMXfZ9623Y+KIGAYGeCDs4i+j3TURNGyI+qiLomlo3vruq8j7oqz4A1eFqk0tH7nh92ohZVnzkleVLK3jvBYLU+cXBWPxNb/2pngYfgAAAP//AwChY6p41AEAAA==\"]" + "size": 380, + "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syG2E9PiDbVS1aFDK6YuyLWPYDWEYJ8lKMp/r5xQqrIw3um99927EzhDBvQJbEBD+Jb8d55ishZjBE0hIQMMYRci6DY1DYP9WbRa0bHD1mwRNAxOBt6BBjETHNg50i0INEgu1UTwCZdLKbQQmotpKeYfwIDwkBWEke72Y0rq3G1nz/5f8Hhp8I4xNZQFtXE10iuOHTdHFwz5XRv/Go+QJ0O49Fu8Yl/W16znIXfRdY23Q+KA6HsGeCBs4y+j2dURNGyIuqiLoq5p2vj2q8j7Qig+4w8Frsu1ctVclfK+lLJylf1EdEIJXilnXH5RMBZf8mtvivv+BwAA//8DADvameHUAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:08 GMT" + "value": "Thu, 02 Oct 2025 21:11:01 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "999" }, { "name": "x-request-id", - "value": "8a3b2770b91384b799c789f8545d09ff" + "value": "13beac595a7cab780747b90a9882bfbb" }, { "name": "x-trace-id", - "value": "8bab390a0487331b0105531cd00b27ab" + "value": "ef3f5d4953273224d4cbeed151045dad" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12867f66ab54-YYZ" + "value": "98873097fb507290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:07.963Z", - "time": 279, + "startedDateTime": "2025-10-02T21:11:01.109Z", + "time": 341, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 279 + "wait": 341 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-relationships_2510062961/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-relationships_2510062961/recording.har index 77962dcbf..a5ce5d772 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-relationships_2510062961/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-create-multiple-HasMany-relationships_2510062961/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=createQuiz" }, "response": { - "bodySize": 384, + "bodySize": 380, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 384, - "text": "[\"H4sIAAAAAAAAA4SQMW+DMBCF/0p1sxOMiQF5i1qp6tChVaYukbEPgkKA2GcpacR/r4A0VbNkvNN777t3F7CaNKgLGIea8CPU3+PkgzHoPShyARmgc53zoNrQNAyOV9F2S+ceW31AUDA5GdQWFMQySYFdI+2aQIHgQi54vhBiw4WSQvFsmabJFzAgPI0KQk9Pxzkl9Paxc2D/L3i+NfhEHxoaBZW2FdI7zh13Z+s01V3r/xrPkBdNuKkPeMe+re9Zr1Puuu+b2kyJE2IYGOCJsPW/jKarPCjYEfVeRVFV0bKp23007qNY8pTnkU3KleRxUYq8EDYTRWGyPEtkXpZW8NX4SXLa4Nv42ofiYfgBAAD//wMATbjCyNQBAAA=\"]" + "size": 380, + "text": "[\"H4sIAAAAAAAAA4SQMW/CMBCF/0p1syG2oziRN9RKVYcOrZi6IGOfQkRIgn2WoCj/vXJCqcrCeKf33nfvLuAMGdAXsB4N4UdsvtMUorUYAmjyERmg970PoLvYtgyOV9FmQ+cBO3NA0DA5GTQONAjFK2DXSLci0CC5LBaCL7hcS6GF0JwvVSG+gAHhKSkIAz0d55Q4uMfOkf2/4PnW4BNDbCkJauNqpHecO+7Ozhtq+i78NZ4hL4Zw3Rzwjn1b37Nep9zVMLSNnRInxDgywBNhF34ZbV8H0LAjGoLOsrqmZdt0+yztM1FwxavMlii3pRDIyxxdaXJVGs5lXqltIZQR6UXeWHxLr30oHscfAAAA//8DAHDid5PUAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:07 GMT" + "value": "Thu, 02 Oct 2025 21:11:00 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "989" }, { "name": "x-request-id", - "value": "66bba495002ce5717099e92a5d81a587" + "value": "0435724819ff9388e461996617857b3a" }, { "name": "x-trace-id", - "value": "d3f4501bf28b2d72bbc787358ffd2046" + "value": "c7e2b711e073ed7a367a002386b516a1" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12834f24ac31-YYZ" + "value": "988730922da37290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:07.451Z", - "time": 214, + "startedDateTime": "2025-10-02T21:11:00.177Z", + "time": 605, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 214 + "wait": 605 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship-with-support-for-Id-prefix-test_2503398001/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship-with-support-for-Id-prefix-test_2503398001/recording.har index 3117986a2..27029a0ea 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship-with-support-for-Id-prefix-test_2503398001/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship-with-support-for-Id-prefix-test_2503398001/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=answer" }, "response": { - "bodySize": 324, + "bodySize": 320, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 324, - "text": "[\"H4sIAAAAAAAAA4SQwWrDMBBEfyXMWUR27NiuboZCyaG33sPKWhxTR3GtDU0I+vciuz20l952h5l57D7gSAjmAfLhk+c0DQ4GeVNDQfgmMBAOslkNYXOdHAk7KHxcOchw8YclsCugcDzKfWJPZ4ZBu1ZGhZ5cz/LKK+p0dzOlYEhbN3PqaxPomYTfhjND4RvzW45/CC9LbztN49AtjQsiRgW+CfvwwxgvfYDBSWQKRuu+l+04+HeddJ3vsyprdF4VdcZU7jizXV3ZuiyKsnpqmtyS3dvlHTN1vF77nznGLwAAAP//AwCQ529IWwEAAA==\"]" + "size": 320, + "text": "[\"H4sIAAAAAAAAA4SPS2vDMBCE/0qYs6j8wA90MwRKD731HtbSxjF1HNfa0ISg/14kt4f20tvuMDMf84AjIZgHaPafvMZrdDDI2wYKwjeBgbCX3Wbwu+viSNhB4ePKXsbL/JICRQmFw0HuC890Zhh0W2VQGMgNLK+8oU53t1IM+vjZlWNfF0F7En4bzwyFb8xvOfwhPKfeblmm0abGhAhBgW/Cs/9hTJfBw+Aksnij9TDI0zTO7zrqOq+yOms1FTlxf2xqm5d1fuz7tmyqjG1ZuIqdjYNlJctp7b/mEL4AAAD//wMAYPC9glsBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:14 GMT" + "value": "Thu, 02 Oct 2025 21:11:05 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "987" }, { "name": "x-request-id", - "value": "6a89b9042b2901b4c2f9d91c0882d599" + "value": "ad609d1321f701a89e876397ef1f4242" }, { "name": "x-trace-id", - "value": "16370ea42e0bc76b7433469881bab5b7" + "value": "a21aebf76c1361fbb83750ec32d5edcd" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12ab4afcab9f-YYZ" + "value": "988730b52e467290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:13.853Z", - "time": 131, + "startedDateTime": "2025-10-02T21:11:05.772Z", + "time": 98, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 131 + "wait": 98 } }, { @@ -223,18 +223,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=updateAnswer" }, "response": { - "bodySize": 364, + "bodySize": 368, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 364, - "text": "[\"H4sIAAAAAAAAA4SQQWvDMAyF/0rR2Sxp2sapb4HB2GGXsZ2LZ6tpqOtklsxaSv77sNON0cuOejy970lXsJo1qCvE0WrG1tMXhjRTNAaJQHGIKABDGAKB8tE5AfrX1ltQsGwkCGA8MyhgJF7MBlrMqRYEfEYk7gf/nBeqFQjY7fgyotcnBAU38nQnv/+p9YoUHSdLp22H/IJz9cPFBp2yKU0mYEK2qcujZnzrTwjidt+dfE97yrntOLre5MSMmCYBeGb09MNwQ0eg4MA8kiqKruMH1/tjkfRiuSnrsilkXW31ZlXVct/ourH79VpKaT7qssJmi/ljQRvMD/nXPE3fAAAA//8DAFVMsmOrAQAA\"]" + "size": 368, + "text": "[\"H4sIAAAAAAAAA4SQzWrDMBCEXyXsWcQ/qW1ZN0Mh9NBLac9BsbaOqSK72hVNCH73IjstxZced5idb3ZvYDRrUDcIo9GMjaMv9HGm0LZIBIp9QAHo/eAJlAvWCtC/tt6AgkxWIIDxwqCAkXizGGizpBoQ8BmQuB/c07yQ70DA4cDXEZ0+Iyi4k6eV/Pan1gtSsBwtnTYd8jMu1U9X43XMpji1HiOyiV0eNeNrf0YQ9/tW8pq2n3ObcbR9OyfOiGkSgBdGRz8MO3QECk7MI6kk6Tre2t59JFFPsiItU5nIh+xY1bKsj6k0RYVlUaOU70WZV3la7+r4Ma9bnB/yr3mavgEAAP//AwBGz25hqwEAAA==\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:14 GMT" + "value": "Thu, 02 Oct 2025 21:11:06 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "982" }, { "name": "x-request-id", - "value": "5ff4493125435ad66095dd2eee84caef" + "value": "798f21d7b78702e87d2c9cf90865f878" }, { "name": "x-trace-id", - "value": "7629a53267f8a68df44777cb602e89e7" + "value": "841b79869b08d57e659e88f562720939" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12ac3919abd3-YYZ" + "value": "988730b61f0a7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:14.005Z", - "time": 174, + "startedDateTime": "2025-10-02T21:11:05.926Z", + "time": 140, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 174 + "wait": 140 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship_3741776720/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship_3741776720/recording.har index 307b9b941..f7e0e1d58 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship_3741776720/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-BelongsTo-relationship_3741776720/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 332, - "text": "[\"H4sIAAAAAAAAA4SQzWrDMBCEXyXMWVSyg1pbN0Oh9NBDofewtTaOqOO41oYmBL17kd1Qmktv+zO73zAXeBKCu4CG+MVTroKHQ1E9QEH4JHAQjrJaBHF1HD0Jeyh8HjlKOAy/R+UaCpuNnEceaM9weL1q0s2iWXhJoSPfsbzw4mN39hPli5i7duIMa7KLRxJ+C3uGwo+Hv+NbwtP8txnHPrTzxxmRkgKfhId4ZfSHLsJhJzJGp3XXyV0fhg+d57qw5t5U2lDra1OW9dZ6suW2rta+rIr3yrKta2NzVhO1/Jxj+Fec0jcAAAD//wMAVc+PtHgBAAA=\"]" + "text": "[\"H4sIAAAAAAAAA4SQT0vDQBDFv0p558VkYxOTvQUE8eBB8F7G3TENTdM1O8WWst9dNrGIvXibP2/m93gXOBKCuYDG8MVTqnoHA10/QEH4JDAQDrJaBGF19I6EHRQ+jxykP4y/R8U9FDYbOXseac8weL1q4s2iXXhRoSPXsbzw4mN7dhOli5A6O3GCtcnFIwm/9XuGwo+Hv+NbwtP8t/V+6O38cUbEqMAn4TFcGcOhCzDYivhgsqzr5G7ox12W5pku8yqvM6fLJnflO+uKiqbJ1zZ3ev1RW10VNbk6ZTWR5ecUw7/iGL8BAAD//wMAPF9pW3gBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:13 GMT" + "value": "Thu, 02 Oct 2025 21:11:05 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "983" }, { "name": "x-request-id", - "value": "aeaf7e05faaade76bb0717c76cb5984e" + "value": "c32c9a813accfda0a0babb20bcd4f3ac" }, { "name": "x-trace-id", - "value": "0acd90229f5da52f983d281b85e59905" + "value": "d1590d5be16a29904c0d14f8c1628ad8" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12a94c6cac45-YYZ" + "value": "988730b37caa7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:13.540Z", - "time": 121, + "startedDateTime": "2025-10-02T21:11:05.504Z", + "time": 70, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 121 + "wait": 70 } }, { @@ -223,18 +223,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=updateAnswer" }, "response": { - "bodySize": 379, + "bodySize": 372, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 379, - "text": "[\"H4sIAAAAAAAAA4SQwU7DMBBEf6Xas0Wapk1S3yIhIQ4cQHCuXHuTRnWd4F2LVlX+HTlpAeXCccfPM7N7BaNYgbxC6I1irBx9oY8zBa2RCCT7gALQ+84TSBesFaB+sNaAhLQsQADjmUECI/FiAmgxuRoQ8BmQuO3c76dVBgJ2O7706NQJQcLrnRlmD7dac/njT+c3pGA5Io0yDfILTnsdLsaraEpx0h5jnyoWfVSM7+0JQdyWn8nztKfRt+p72+rRcYwYBgF4ZnR0z7BdQyDhwNyTTJKm4QfbumMS9STdLPNlmZi13mis96tC5Xla1Fmxr01abtfptsxKFS/DXml8jpf6Fx6GbwAAAP//\",\"AwCH6UttyAEAAA==\"]" + "size": 372, + "text": "[\"H4sIAAAAAAAAA4SQzU7DMBCEX6Xas0V+gCT1LRIS4sABBOfKstdphOsE71q0qvLuyEkLKBeOO/48M7tnMIoVyDPE0SjG1tMXhjRT1BqJQHKIKABDGAKB9NE5AeoH6w1IKJoaBDAeGSQwEm8WgDaLqwEBnxGJ+8H/fipvQcBux6cRvTogSHi5MtPq4VJrLb//6fyKFB0npFOmQ37GZa/9yQSVTClNOmDq06aiD4rxrT8giMvyK3md9jj7tuPoej07zhHTJACPjJ6uGW7oCCTsmUeSWdZ1fON6/5ElPSvu8ypvsu2dzW1dFHVTqmqLNs9tVVrTaFtjVVQ2nTMojU/pUv/C0/QNAAD//wMAsS3y9cgBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:13 GMT" + "value": "Thu, 02 Oct 2025 21:11:05 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "982" }, { "name": "x-request-id", - "value": "4465c74c89d224c29fe1f471d2bd158b" + "value": "97e8502ece673d5c9eeb3724efb0707f" }, { "name": "x-trace-id", - "value": "d4c5cefb27a6617f37bfd189419838a3" + "value": "94f0f711782a69ef00f62fd8cf7e616f" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12aa3d0cab33-YYZ" + "value": "988730b41d3d7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:13.695Z", - "time": 152, + "startedDateTime": "2025-10-02T21:11:05.607Z", + "time": 158, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 152 + "wait": 158 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-Multiple-HasMany-relationship_4201319124/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-Multiple-HasMany-relationship_4201319124/recording.har index d031e1132..cdfe3e636 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-Multiple-HasMany-relationship_4201319124/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-Multiple-HasMany-relationship_4201319124/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=quiz" }, "response": { - "bodySize": 448, + "bodySize": 444, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 448, - "text": "[\"H4sIAAAAAAAAA7SSTU/CQBCG/0oz52oLhS7sjagxHjyYeDOErDvDshG2pTtEPtL/braARj4TE0+bzMfzzryzG0DFCuQG5gu7Dq9FkNDqtyEGpiWDBCbPUUhHixIVE0IM8wV5toXzoYXQkAf5tgFXIP1AhMiPKNu2PSm6iVoQg3L+k6qLrCzvHrC2TQek0YhXJTk1I5AwaCqgPhl+QENQx8c6+TWd9h90hidzd4VzpIMhR80vO6fOJs7ML4S47nn7tOf/NeTwTPYi2a5D0Cg0xM+0/aOTFVbq+9vpisI+g7DpvWJ6tTOCGHZr/g4f8h8b7qAsp1Y3xEairmOgJZPze41pYTxImDCXXiaJMXw7te4jCfGk1U3ztJekGsc01u+9fibeqY9ZJ08zzDMluqg7WSfco1KansKBrhbX9RcAAAD//wMAVYu2m5QDAAA=\"]" + "size": 444, + "text": "[\"H4sIAAAAAAAAA7SSy2rDMBBFf8XM2q1tJbYS7UJbShddFLorIQhpoogmsmNNaB7434ucpKV5QqArwTzOnbmjDWhJEsQG5gu7Dq/VICDrM4iBcEkggNBTFNLRotKSUEMM8wV6sqXzoQW1QQ/iYwOu1PgL4bw4omzb9qToLsogBun8F9YXWZ0iP2Btmw5IoxGtKnRyhiBg0FZAczL8pA1CEx/rFNd02A06w5O5h9I5VMGQo+a3nVNnE2fm55xf95yd9vy/hhyeyV4k23UIGqkN0itu/+hkpWv58+1UjWGfQdj0URK+2xlCDLs1/4YP+c8td1BVU6taYivRNDHgktD5vca0NB4ETIgqL5LEGLqfWveZhHiS5WmR9hIuEce5xnHKGPazvKc6qLqp7rJuP2NchXvUUuFLONDV4qb5BgAA//8DAJRHSeWUAwAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:12 GMT" + "value": "Thu, 02 Oct 2025 21:11:04 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "983" }, { "name": "x-request-id", - "value": "c7a4b4260fe732d73ba56caf3a7563e7" + "value": "7f1aae21614a41d1671a161a8ca35349" }, { "name": "x-trace-id", - "value": "0cdfefcb8937be9d34603d63a75dc434" + "value": "7aeef5def022e9158c3ec40d4249127c" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12a45eefac4b-YYZ" + "value": "988730aef8857290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:12.744Z", - "time": 127, + "startedDateTime": "2025-10-02T21:11:04.788Z", + "time": 72, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 127 + "wait": 72 } }, { @@ -223,18 +223,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=updateQuiz" }, "response": { - "bodySize": 492, + "bodySize": 488, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 492, - "text": "[\"H4sIAAAAAAAAA7STy07DMBBFfyWadSBNQvPwrgKEWLAoghWqKsuephGpE+yJ6EP5d+SkLVLSh4TEMjN3zrWvJzuQnDiwHdSV5ITTOt/aL1MLgcYAI12jC6h1qQ0wVReFC197US6BgZ8G4ALhmoABoSHHtp0OJ8Gq0VBeKmNHUGZogH3sQJUSfyFxHA0o3diB5Nw4PrjAlflGfZEVRuMeqxvqkeZz2lSo+AqBwaRVQHOy/CgzhMYd+kTXfII/+MxO9u5LpVDYQAbD031SZxtnzh/H8fXMg9OZ/9chZ2e6F8n5dlB8P27zK5q6ICvIuMyQXrDb9+VGan7cS6HRXnhio3jghG/5CsHd/xS9ct/rqeVOqqrIRUtsLZrGBVwTKnPwKMrMAIMlUWWY52UZ3Ra5+vRs3fPHo2iUeKmP3OfjRRpLDBKxSOMwFmEQJHcLHqajxD6Y5gKf7QteFTfNDwAAAP//AwCNg+uM4AMAAA==\"]" + "size": 488, + "text": "[\"H4sIAAAAAAAAA7STy26DMBBFfwXNmhYSAgTvoraquugiVbuqosjBA0ElhtqDmof498qQhwR5SJW69Mydc81lvAPBiQPbQVUKTjitsq056SqOUWtgpCq0AZUqlAYmqzy34XsvygQwGERDsIFwTcCAUJNl2laLE2DUqCkrpDYjKFLUwD53IAuBJ0gYBj1KO3YgWXfWAGzgUv+gusryAr/Daoc6pPmcNiVKvkJgMGkUUJ8tP4kUobb7PsEtn+EffGZnew+FlBibQHrD031SFxsX7h+G4e3Mh+cz/69Lzi50r5Kzba/4cdzmN9RVTkaQcpEivWK778uNUPy4l7FC88ETE8UjJ3zPVgj2/lF0yl2v54Y7Kcs8ixtiY1HXNuCaUOqDR16kGhgsiUrNHCdN6T7P5Jdj6s7AdwN37CToeX404pgkUZh440W0wCD0XXe08EfIm8VWPMYX8wdviuv6FwAA//8DALjCovLgAwAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:13 GMT" + "value": "Thu, 02 Oct 2025 21:11:05 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "990" }, { "name": "x-request-id", - "value": "10ea02dbc7af69b7110e376025b45a16" + "value": "b031fba90294270a6cad0260781aa877" }, { "name": "x-trace-id", - "value": "91ea1a5f97de28cf9737c32284fa3908" + "value": "fe33594aeff97f38b9be675004b54ea5" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12a548dbabf4-YYZ" + "value": "988730af99107290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:12.896Z", - "time": 270, + "startedDateTime": "2025-10-02T21:11:04.890Z", + "time": 299, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 270 + "wait": 299 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-relationship_2440118782/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-relationship_2440118782/recording.har index c62804c98..450799450 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-relationship_2440118782/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasMany-relationship_2440118782/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 368, - "text": "[\"H4sIAAAAAAAAA4SQy2rDQAxFfyVobWo78auzC00pXXRR6K6UMA9lMsQZOx4Zkpj59zJO+sZ0JbhXOrrSAIoTBzbAoTfnUI0CBmmRQQSERwIGhI5mwZ71reKECiI49OjINNaFEVQaHbDXAWyj8AtSlos/lMvYN9J6TacWLd8jMHi++uAnjHulEfzbhHvXWItyAmDOQdRcaaQnvBy9PamOf94hOwyhliHuihO+mD1CBNesP+Xf/IeRu2zb2siROK7wPgI8Elr3saNutAMGW6LWsTjWmm5qY3dx0OM0T4qkivFWVhJRiCwrRJ4JmaT5XC1EtZmnebkpwlM7LvExfPnfZu/fAQAA//8DAEiZsQflAQAA\"]" + "text": "[\"H4sIAAAAAAAAA4SQT2vDMAzFv0rROSzJ6qaJb2UbY4cdBruNUTxLc81SJ40VaBv83YfT7j9hJ8F70k9PGgAVK5AD7Hp7jNUiSMgLAQkw7RkkMHmeRXvWt6iYEBLY9eTZNs7HEUJDHuTTAK5B+oIsl/M/lNPYN9J6zYeWnNoSSHg4+xAmjBs0BOF5wr1qnCM9AbDHKBqFhvieTkdvDtipzzt0RzHUKsa9VkyPdkuQwDnrT/k3/3bkrtq2tnokjitCSID2TM5/7Kgb40HChrn1Mk2N4Yvaurc06mm+yIqsTBel1mVVZEJQhVlOc8r0y6UQ1WulBGoVn9opTXfxy/82h/AOAAD//wMAn7shl+UBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:11 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "984" }, { "name": "x-request-id", - "value": "742573e0fff1058b82a4619f7cdad7f6" + "value": "e07a27fedba5f6b713fcea300a6a5604" }, { "name": "x-trace-id", - "value": "e9c8ceebb446b54bc0152d3b8f2157f6" + "value": "58cc896044e9d01e3e0cb2449f9a4dca" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f129d3d82ac2d-YYZ" + "value": "988730a96b897290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:11.609Z", - "time": 125, + "startedDateTime": "2025-10-02T21:11:03.897Z", + "time": 86, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 125 + "wait": 86 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 412, - "text": "[\"H4sIAAAAAAAAA4SRzU7DMBCEX6Xac0QaQpvWtwoQ4sChCE4IVa49uBapE+yN1B/l3ZHTUFCriqN3Zr/dHe9JS5Yk9tTUWjLmjd3FV2iUQggk2DdICN5XPpBwTVkm9NWbrCZB2fiGEmJsmAQxAg+iPDjgNEU3AtvKhdgCbRBIvO3JVRq/kKLIzyiHtj+kxYK3NZxcgwTNe53aC8K9NqD2/YJ6WzkHdQFgd2fF12M8zwhNydFgpDbgJxwCXG21l8dDlUfcehbvuZOMF7sGJX3KJ+XTWQ8dd1bXpVUdsRvRtglhw3DhZ0ZZmUCCVsx1EGlqDF+V1n2msZ5mo+F4OEmz0RK6mCotJ0rlo7HKl7mW2RTF9LrIPmKs7KXCY/cN/5nb9hsAAP//AwANk6z2MQIAAA==\"]" + "text": "[\"H4sIAAAAAAAAA4SRQU/DMAyF/8rkc0W7trSQ2wQIceAwBCeEppC4WUSWlsSVtlX97yhdGdOmimP8nj/bLx1IThxYB20jOeGy1fvw8q0Q6D0wci1GgM7VzgOzrTERfI8mLYHBvMghAsItAQNCT7Mgzw44CcGNnnRtfWhBqdADe+/A1hL/IGWZXVAObSek1Yp2DVq+QWCwHHXoJ4QHqRD6jwn1rrYWxQRA7y+Kb8d4XtC3hoJBcamQnvEQ4HonHT8eKhyGrRfhnntO+Ko3CNGY8ln5fNbjwF00jdFiIA4j+j4C3BJa/zvD1MoDgzVR41kcK0VXRtuvONTj+XVSJDexzMsiE2VVYppUQuTJZ55WZZ7mWcZv00KE1B0X+BS+4V9z3/8AAAD//wMAW9n9wDECAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:12 GMT" + "value": "Thu, 02 Oct 2025 21:11:04 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "986" }, { "name": "x-request-id", - "value": "e6b8e22283cab584efcbf3e10cf8811d" + "value": "159e2d6de00cbac7d4d26e4140c38c39" }, { "name": "x-trace-id", - "value": "15bed79cda8cc356c3b3da19e79271fd" + "value": "d4763c7f7e20fcc40b42f742433a926c" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f129e3d07ac9f-YYZ" + "value": "988730aa0c247290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:11.762Z", - "time": 223, + "startedDateTime": "2025-10-02T21:11:03.999Z", + "time": 177, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 223 + "wait": 177 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasOne-relationship_3220970997/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasOne-relationship_3220970997/recording.har index 598bbcfa9..93fd3adc5 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasOne-relationship_3220970997/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-a-single-HasOne-relationship_3220970997/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=answer" }, "response": { - "bodySize": 348, + "bodySize": 344, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 348, - "text": "[\"H4sIAAAAAAAAA4SQS0/DMBCE/0o1Z4ukTfOob5GQEAc4ca829Sa1SJ0oXkSryv8dOSkIeuG2j5mdT3uFISHoK8j5T55iZQ001lUJBeGzQEPYy2oR+NXHaEjYQMENYlt7ILGD+zEWKRTYUdOzgW6p96yw38tlZEcnhsbrb1u4W9YLRlDoyHQsL7zgHS9mmh0+doeJI0Md4R5J+M2eGAo3tL/j+4Sn+W49jv2NYY4IQYHPws5/Z/RD56FxFBm9TpKuk4feuvckzpN1nhZpleQb2u6afMdlVXC2zYqsStOWTGaKps3KTXzhRAd+jp/5VxzCFwAAAP//AwD51gY8jwEAAA==\"]" + "size": 344, + "text": "[\"H4sIAAAAAAAAA4SQzU7DMBCEX6Was0XcojjFt0hIiAOcuFcbe5tapE4UL6JVlXdHTgqCXrjtz8zOp73AkxDsBRTTJ4+5Ch4W620FBeGTwEI4yWoRpNXH4EnYQyH2EvbBkYQ+/hiNhgJHajr2sHvqEivsdnIeONKRYfH62zbdLOsFY1JoybcsL7zgHc5+nB0pd27kzFBnuEcSfgtHhsIV7e/4NuFpvlsPQ3dlmCOmSYFPwjF9Z3R9m2BxEBmSLYq2lbsuxPciz4t1qY3eFt5XpmwclUaXG9c488Abuq+00ZXW1OzzC0dy/Jw/8694mr4AAAD//wMAQ6l5B48BAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:13 GMT" + "value": "Thu, 02 Oct 2025 21:11:05 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "982" }, { "name": "x-request-id", - "value": "2d976eab3d67043522103618cbef9122" + "value": "255faaa1effeb50fcda5ab0a52b8bba6" }, { "name": "x-trace-id", - "value": "52a49b59e786e34363800fad3d6bf372" + "value": "dd765bca56052cbc69e2a37060700abf" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12a70aebac7b-YYZ" + "value": "988730b18abb7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:13.172Z", - "time": 131, + "startedDateTime": "2025-10-02T21:11:05.196Z", + "time": 77, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 131 + "wait": 77 } }, { @@ -223,18 +223,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=updateAnswer" }, "response": { - "bodySize": 384, + "bodySize": 388, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 384, - "text": "[\"H4sIAAAAAAAAA4RQy2rDMBD8lbBnUdt5OEY3Q6H00B5Kew6yNXFMFdlo1zQh+N+LHw3Blx53dnZmdm5kjRjSN+paawS55x+EYeauLMFMWkIHRQihCUzad84pMndabUlTku1JkeAipEnAspoIvJpULSnyjdTHujRSN/5+mMakCN4UDpb00TiGosNBri28OYM0vT+e9YvlnHYJfz288gHunAyUytgK8obp3dPVhlGUh6kMGGLmQ/5nI/iszyA1d7KAl24vo27etm6OOVr0vSJcBJ7/PFxTMWk6ibSso6iq5MnV/jsa8CjZxWmcRWmC9RqbBHa/RZpkcVagsHFWItnE293YcjAlXsfy/iP3/S8AAAD//wMAMjHQOt8BAAA=\"]" + "size": 388, + "text": "[\"H4sIAAAAAAAAA4RQQW7CMBD8Cpqz1SRAAvUNqVLVQ3uo2jNy4iVEGCfyblQQyt8rQ4pQLj3u7OzM7FxgjRjoC/rOGqGN5x8Kcea+qogZWkJPChRCGxja984pmDutsdDI1isoCJ0EGkIssxuBZzdVCwXfSrNrKiNN6++HRQoF8qZ0ZKF3xjEpbLdy7sibI0Hj4/FsmCzHtFP4++GVT+LeSaTUxtYk73R7d3+24SrKcaoCxZibmP/FCH01R4IaO5nAU7fXq+6m69wY82oxDAp0EvL85+HamqGxF+lYJ0ldy5Nr/CGJeJLlaZGuk3w1t3lKVJTzhc2ejbXZIl1U82W+XJY7KmLLwVT0Fsv7lzwMvwAAAP//AwB/Q2m93wEAAA==\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:13 GMT" + "value": "Thu, 02 Oct 2025 21:11:05 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "982" }, { "name": "x-request-id", - "value": "ce75704afaf3f459febf7c8fc7844433" + "value": "4cc6a140da23001f602efcf53dc769f2" }, { "name": "x-trace-id", - "value": "61e22e31ed74e61808bebd08ce130457" + "value": "572d50ee6b23d19add1303c24544bfe6" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12a7e814ac15-YYZ" + "value": "988730b22b607290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:13.325Z", - "time": 208, + "startedDateTime": "2025-10-02T21:11:05.299Z", + "time": 197, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 208 + "wait": 197 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships-that-are-reordered_1591436327/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships-that-are-reordered_1591436327/recording.har index 3e5a87aea..21669364e 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships-that-are-reordered_1591436327/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships-that-are-reordered_1591436327/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=quiz" }, "response": { - "bodySize": 395, + "bodySize": 392, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 395, - "text": "[\"H4sIAAAAAAAAA5yRX0vDMBTFv0q5z9U2s3/WvA0V8cEHwTeREZNrFuzSrrmFbSXfXdJtio4y8Clwzr2/czkZQAkSwAfY9GYfXqOAAyvmEAPhloADoaMo2FHfKkGoIIZNj45MY11YQaXRAX8dwDYKfyBlmZ1RDmsnUnQVMYhhuaRdi1asETg8H2fATxj3SiP4+Dwtv5w2+2fa24R721iLcgJg9kHUQmmkJzzUvNqpTnw3JzsMhy3CyXeC8MWsEWI43vtb/st/GLmLtq2NHIljhPcx4JbQulNG3WgHHFZEreNJojVd18Z+JkFPWJ4W6TzJkX1khVR5VspZxVhavIsqZRVmmbzJi/Dj1AmJj6Hpi8PefwEAAP//\",\"AwDspJRwVwIAAA==\"]" + "size": 392, + "text": "[\"H4sIAAAAAAAAA5yQQWvCQBCF/0qY87ZJNNFkb9KW0kMPhd5Kkc3uuC6Nm5idgBry38tGbakShJ4G3sx87/E6UIIE8A62rTn4aRRwiGcZMCDcEXAgdBT4ddDWShAqYLBt0ZGprPMvqDQ64B8d2ErhL2Q+T64ox7czKbgLYmCwXNK+Ris2CBzeTjfQjyyelEbo2bVbettt8k+3z5HtQ2UtyhGAOXhRC6WRXvFY83qvGvHTnGzQB1v4yI+C8N1sEBic8v6VL/nPA3dR16WRA3Gw6HsGuCO07uxRVtoBhzVR7XgYak33pbFfodfDOI1mURamcjotZJrlRZ5EKkqy1UpORB4liKnIiqHYRkh88U3fPO77bwAAAP//AwD79dpRVwIAAA==\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:12 GMT" + "value": "Thu, 02 Oct 2025 21:11:04 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "981" }, { "name": "x-request-id", - "value": "0a56b55e3e4c645c2302bc4e613cc7e9" + "value": "3f0466ccec5f8a5bacdf720f9b93634c" }, { "name": "x-trace-id", - "value": "5e1f46cd547c291106ba9019e44c356d" + "value": "5c33bc589b940d048ffc2a904ee5a8b5" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f12a20c56ab45-YYZ" + "value": "988730ad3efe7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:12.371Z", - "time": 137, + "startedDateTime": "2025-10-02T21:11:04.504Z", + "time": 67, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 137 + "wait": 67 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 432, - "text": "[\"H4sIAAAAAAAAA5yRT2vDMAzFv0rQ2VucJmlS38o2xg47dGynMYobC9csdTJbgbYh3324/wYtpbCjpaffk/V6UJIkiB66VknCWWe24eW7qkLvQZDrkAE61zgPwnZ1zeDnIDIKBCTjEhgQrgkEEHqKQjva4xQENXoyjfVhBJVGD+KzB9so/IMURXZB2Y8dSdFdlACD+Zw2LVq5QhAwO2hguNJ4UhphYJdu+W230T/dvq50HxprsboCMNuL4scpkDf0XU1BoKXSSK+4j2y5UU6eTls5DJtPw58eJeG7WSGwQ65n5XOv5x132ra1qXbEncUwMMA1ofVHj7rRHgQsiVov4lhruq+N/Y5DPU5yPuZlrFSxmOQ5ZgnnnGeqwKJMxynPFkqmI5yEyztZ4UuI4qZ4GH4BAAD//wMAhIefjKMCAAA=\"]" + "text": "[\"H4sIAAAAAAAAA5yRy2rDMBBFf8XMWq3jOIlt7UJbShddpLSrUoIiTRRRR3alMeSB/73IeRRiQqBLzdw5dzR3D0qQAL6HplaCcNaYXXj5Rkr0Hji5Bhmgc5XzwG1Tlgx+jiKjgEMyyYEB4YaAA6GnKLSjA05BUKMnU1kfRlBp9MA/92ArhX+QLBv1KIexEym6ixJgMJ/TtkYr1ggcZkcNtFcaT0ojtKzvNr7tNvyn29eV7kNlLcorALPrFT/Ogbyhb0oKAi2URnrFQ2SrrXLifFrpMGw+DX96FITvZo3AjrlelC+9njvutK5LIztiZ9G2DHBDaP3Jo6y0Bw4rotrzONaa7ktjv+NQj5PxYDLI42WqlmMhRIYTMUoXosiHaZIPF5lURbFIu8s7IfElRHFT3La/AAAA//8DAFcriA+jAgAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:12 GMT" + "value": "Thu, 02 Oct 2025 21:11:04 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "989" }, { "name": "x-request-id", - "value": "4238dd488ce57cc3595fbcc24963873c" + "value": "717a71f4073e5ef3a15b24aec1a6e6b9" }, { "name": "x-trace-id", - "value": "dd7b955e4100004d7e7836304bda32e9" + "value": "f3df5aaa7e6a43ba9823182b7cd99b35" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12a2f91cabf7-YYZ" + "value": "988730addf9c7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:12.524Z", - "time": 211, + "startedDateTime": "2025-10-02T21:11:04.607Z", + "time": 174, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 211 + "wait": 174 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships_1125172282/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships_1125172282/recording.har index 44dc28ee6..5a951edbd 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships_1125172282/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-multiple-HasMany-relationships_1125172282/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=quiz" }, "response": { - "bodySize": 388, + "bodySize": 392, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 388, - "text": "[\"H4sIAAAAAAAAA5yQQUvDQBCF/0qYczSJTTbp3oqKePAgeBMpY3bcLqabNDuBtiH/XTZtFS2h4Gngzcz3Hq8HhYwge9h0Zu+nUSAhEQWEwLRlkMDkOPDroGsUMikIYdORY1Nb519IaXIgX3uwtaIfSJ6nZ5TD24kUXAUJhLBc8q4hi2sCCc/HGxgmFvdKEwzhuVt22e3mn25vE9vb2loqJwBm70WNShM/0aHm1U61+N1c2ZIPtvCR75DpxawJQjjm/S3/5T+M3EXTVKYciaPFMIRAWybrTh5VrR1IWDE3TkaR1nxdGfsZeT1KsljERVTGAnEWF3kuylTg/IMUZjif4bvIijQdi22xpEff9MXjYfgCAAD//wMAdNb0slcCAAA=\"]" + "size": 392, + "text": "[\"H4sIAAAAAAAAA5yQT0vDQBDFv0qY82qa/3FvRUU8eBC8iZRNdrJdTDdpdgJtQ767bNoqWkLB08Cbmd97vAGkIAF8gG2vD25qCRyCNAcGhDsCDoSWPLf2+lYKQgkMtj1a0o2x7gWlQgv8fQDTSPyBZFl8QTm+nUnejRcAg9WK9i0asUHg8Hq6gXFm8SgVwsgu3ZLrbuE/3T5mtveNMVjOAPTBiUpIhfSCx5rXe9mJ7+bKDl2wpYv8IAjf9AaBwSnvb/kv/2niLtu21uVEnCzGkQHuCI09e9SNssBhTdRa7vtK0W2tzafvdD9IFuki94sozO7CKI+DrMjiApNqkVZBIqKkSjFNMldsJ0p8dk1fPR7HLwAAAP//AwBxajNaVwIAAA==\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:12 GMT" + "value": "Thu, 02 Oct 2025 21:11:04 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "972" }, { "name": "x-request-id", - "value": "ffc69c80eeea8612be85250b47b89883" + "value": "8e5696292da25ed2fb9289ebf165f4e5" }, { "name": "x-trace-id", - "value": "c06aa308776c46a9feda5a93ab658445" + "value": "b3279238417b74be5f06f15a35f6e657" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f129facabebb5-YYZ" + "value": "988730ab3d4f7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:11.991Z", - "time": 116, + "startedDateTime": "2025-10-02T21:11:04.182Z", + "time": 78, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 116 + "wait": 78 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 432, - "text": "[\"H4sIAAAAAAAAA5yRT2vDMAzFv0rQ2VvSdEmDb2UbY4cdOrbTGMW1FdcsdTJbgbYh3304/TNoKYUeLT39nqzXgRIkgHfQNkoQzlqzDS/fSoneAyfXIgN0rnYeuG2risHvXmQUcBjlBTAgXBNwIPQUhXa0wykIavRkauvDCCqNHvhXB7ZW+A+ZTB7OKLuxAym6i0bAYD6nTYNWrBA4zPYa6C80npVG6Nm5W3bdLb3R7ftC97G2FuUFgNmeFT+PgbyjbysKAi2URnrDXWTLjXLieFrpMGw+DX96EoQfZoXA9rmelE+9XgbutGkqIwfiYNH3DHBNaP3Bo6q1Bw5LosbzONaa7itjf+JQj0dZkidFvCjztEiUKMdZmiVpmScJSpEVqVDlIpuMw+WdkPgaorgq7vs/AAAA//8DAHSkxRajAgAA\"]" + "text": "[\"H4sIAAAAAAAAA5yRy2rDMBBFf8XMWq0dU9tCu9CW0kUXKe2qlCBZE0XUkV1pDHngfy/Kq5AQAllq5s65o7kb0JIkiA30nZaEk96u4yv0dY0hgCDfIwP0vvUBhOubhsHvXmQ1CBiVHBgQLgkEEAZKYjvZ4TRENQayrQtxBLXBAOJrA67V+A+pqoczym7sQErukhEwmE5p1aGTCwQBk70GhguNZ20QBnbuVlx3y290+77QfWydw/oCwK7Pip/HQN4x9A1FgZHaIL3hLrL5Snt5PG3tMW4+jn96koQfdoHA9rmelE+9Xrbccdc1tt4StxbDwACXhC4cPJrWBBAwJ+qCSFNj6L6x7ieN9XRUZGXG00pVGRZ5xatMzgpVlDzn5SznKlel4krFy3tZ42uM4qp4GP4AAAD//wMAGC55G6MCAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:12 GMT" + "value": "Thu, 02 Oct 2025 21:11:04 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "983" }, { "name": "x-request-id", - "value": "16d50df2ad6e254b5204e7a25a190dc8" + "value": "5ab701d170e62ee0369bb5897e109296" }, { "name": "x-trace-id", - "value": "bf6280daf352502f600eca582adfb573" + "value": "7b70e527870af5b568286f28b2b6b8bb" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f12a09b3cac96-YYZ" + "value": "988730abdde47290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:12.143Z", - "time": 221, + "startedDateTime": "2025-10-02T21:11:04.285Z", + "time": 211, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 221 + "wait": 211 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-even-if-nested_698860812/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-even-if-nested_698860812/recording.har index 16ee6626e..bd224b336 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-even-if-nested_698860812/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-even-if-nested_698860812/recording.har @@ -67,18 +67,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=quiz" }, "response": { - "bodySize": 315, + "bodySize": 308, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 315, - "text": "[\"H4sIAAAAAAAAA4SQT2vDMAzFv0p5ZzM7af60vhUGY4cdBrsXESupaZp6sQrtir/7cLYdtstOQk96v4d0hyMh2DveL/4jV+9gUTRrKAhfBRbCUVZ5vLoER8IOCvu93AJPdGJYvGZrUhjIDSwv/AU83NxM4s9TzF03c7buMvCRhN/8iaHwTfwtpz/8p4W7C2H03UJcIlJS4KvwFH8yxvMQYXEQCdFqPQzyMPrpqLOui9o0ZqMrU/R9aagkNu2W12Vfb5tN3VLr6tZVVT57po6f8xv+XU7pEwAA//8=\",\"AwDQIDToQQEAAA==\"]" + "size": 308, + "text": "[\"H4sIAAAAAAAAA4SQMWvDMBCF/0p4s6hkpTZGW6BQOnQodA+SdVZEHUe1LpA06L8Xue3QLp2Oe3fve9zd4C1bmBvez/Gj1uhh0HRbCDBdGAZMmTd1vDknb5k8BPZ7viaa7ZFg8FKtRSBYH4if6Qt4uPrFcjzNuXbDQtW6q8AHy/QajwSBb+JvufzhP67cXUpTHFbiGlGKAF2Y5vyTMZ1ChsGBOWUjZQh8N8X5TVZdNq3qVC+7cTv0qh2d17pp6F6NvnfOOa2dH1ut6tmLHeipvuHf5VI+AQAA//8DADWyp89BAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:11 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "993" }, { "name": "x-request-id", - "value": "e34f762f7fea5fd0a38f457c93d5ad2c" + "value": "e0e898cb25f2bbd341247aee7e92fd62" }, { "name": "x-trace-id", - "value": "401ff20a2ae079e32f596857a7d57d44" + "value": "6f3c805fbd2211e40fd8bbbb22bdf520" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f129b1dddaba0-YYZ" + "value": "988730a7aa087290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:11.272Z", - "time": 132, + "startedDateTime": "2025-10-02T21:11:03.619Z", + "time": 67, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 132 + "wait": 67 } }, { @@ -228,13 +228,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 348, - "text": "[\"H4sIAAAAAAAAA4SQMU/DMBCF/0r1ZoskDSStt0pIiIEBBHPl2JfUwk2CfZZaqvx35LQwZGG856fv890FRrGCvCCORjG9RvudphC1phAg2UcSIO8HHyD76JzA161kDSSKqoQA04khwRR4lZ5XV5yBwH7P55F6dSRIzPxpEX78qd8oRMep0CnTEb/Q9XOHs/GK7dCHNGlPib1LxkfF9G6PBHHbYBEvXU8zdzeOzuqZOCumSYBOTH34dbihC5A4MI9BZlnX8Z2z/WeW8qx4yKt8k9GmyRW1TV6praGiLuumprYpi/W2rfR9ne7ilabndKd/y9P0AwAA//8DALAF1j2NAQAA\"]" + "text": "[\"H4sIAAAAAAAAA4SQMU/DMBCF/0r1ZovURA6Jt0pIiIEBBHNl4ktq1U2CfZZaqvx35LQwdGG856fv890Z1rCBPiNN1jC9Jvedp5jalmKE5pBIgEIYQ4QekvcCX9eSs9CQVQkBpiNDgynyKj+vLjgLge2WTxMN5kDQWPjzTfjxp36jmDznQm9sT/xCl8/tTjYYduMQ89QGyuxNNj4apnd3IIjrBjfxretp4W6mybt2IS6KeRagI9MQfx1+7CM0dsxT1EXR93zn3bAvcl5Ita7WdaGUpFopq0r52dSqKanrZPUgzb2suqbp8l2Caek53+nf8jz/AAAA//8DAMMUk/qNAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:11 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "991" }, { "name": "x-request-id", - "value": "9027f6508565a5d63f7803f6a17e2c80" + "value": "cfe8acb77dea8de34428bc46ba4ed189" }, { "name": "x-trace-id", - "value": "e8b0aefb06a9de1737b7efb3129f6c47" + "value": "551e855d531b98593eff1671a216f99f" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f129c1c3cabac-YYZ" + "value": "988730a84a8f7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:11.425Z", - "time": 176, + "startedDateTime": "2025-10-02T21:11:03.720Z", + "time": 170, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 176 + "wait": 170 } } ], diff --git a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-object_3536142481/recording.har b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-object_3536142481/recording.har index 4b362cb77..23a72f532 100644 --- a/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-object_3536142481/recording.har +++ b/packages/react/spec/__recordings__/useActionFormNested_1974165259/with-polly_2612993081/can-update-root-object_3536142481/recording.har @@ -72,13 +72,13 @@ "encoding": "base64", "mimeType": "application/json; charset=utf-8", "size": 308, - "text": "[\"H4sIAAAAAAAAA4SQQU/DMAyF/8r0zhFpadOV3CYhIQ4ckLhPofGyiK4LjSdtTPnvyAUOcOFk+dnve7Kv8I4d7BXvp/ghNXpY1F0DBaYzw4Ip80rGq1PyjslDYbvlS6LJHQgWz2ItCsH5QPxEX8D9xc+O43HK0g0ziXUjwHvH9BIPBIVv4m+5/OE/LNxNSmMcFuISUYoCnZmm/JMxHkOGxZ45Zat1CHwzxulNi65rU3VVr01T133Tua6/o9a8tsbc7tbkfUtVuxtoLWfPbqBHecO/y6V8AgAA//8DAFx2WPRBAQAA\"]" + "text": "[\"H4sIAAAAAAAAA4SQQWvDMAyF/0p55zAnHTGJb4XB2KGHwe5FsVXXLE3dWIV2xf99ONsO22UnoSe97yHd4UgI5o7zJXyUGhwMGv2ICsJXgYFwklUZry7RkbBDhd1ObpEnOjIMXos1V/DkPMuWv4CHm5tJwmlKpbMzF+umAJ9I+C0cGRW+ib/l/If/vHA3MY7BLsQlIucKfBWe0k/GePIJBgeRmIxS3svDGKZ3VXTVtLWuO7Uehq5pW2dbq5lYDz1rt6b9vnOW+rovZ89k+aW84d/lnD8BAAD//wMAv6Dg4kEBAAA=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:11 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -90,7 +90,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -110,15 +110,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "990" }, { "name": "x-request-id", - "value": "6958af06be39ee543cc09ba356e157cf" + "value": "0ed3b76682ecbbc9185dd94a5b95d550" }, { "name": "x-trace-id", - "value": "5311836a689e45b4552f7edd4e04fce7" + "value": "2bb8155dc5c6eae6b9e6d2aff8dca909" }, { "name": "strict-transport-security", @@ -138,21 +138,21 @@ }, { "name": "cf-ray", - "value": "972f1298eba5ac09-YYZ" + "value": "988730a5c85f7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:10.915Z", - "time": 116, + "startedDateTime": "2025-10-02T21:11:03.320Z", + "time": 76, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +160,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 116 + "wait": 76 } }, { @@ -223,18 +223,18 @@ "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=updateQuiz" }, "response": { - "bodySize": 363, + "bodySize": 348, "content": { "encoding": "base64", "mimeType": "application/json; charset=utf-8", - "size": 363, - "text": "[\"H4sIAAAAAAAAAwAAAP//hJAxa8MwEIX/SnizqK0GO7a2QKF06NDSzkFIZ0dEsV3pBEmD/3uRk3bI0vGeHt+nuwusZg11QZqsZnpL7jtPMRlDMUJxSCRAIYwhQg3Je4GvW8lZKMh6DQGmE0OBKfIqP6+uOAuB3Y7PEw36SFBY+PNd+PmnfqeYPOdCr21P/ErXz+3PNmh24xDzZAJl9jYbnzTThzsSxG2Du/je9bxwt9PknVmIi2KeBejENMRfhx/7CIU98xRVUfQ9P3g3HIqcF7Iq67Ipuq41ZSVbqxu5kdS1j7aubN2ZRm/adSnzXYI29JLv9G95nn8AAAD//w==\",\"AwCbgLFEjQEAAA==\"]" + "size": 348, + "text": "[\"H4sIAAAAAAAAA4SQsU7DMBCGX6W62cINTkrjrRISYmAAwVxd7atr4SbBPkstVd4dOS0MWRjv96/v890FLDKCvkAeLDK9Zv9dppSNoZRAc8wkgGLsYwLd5RAEfN1K3oKGaqVAANOJQQNT4kV5XlxxFgRst3weqMMjgYaJP87Cjz/1G6UcuBQcWkf8QtfPHc42Ivu+S2UykQp7U4yPyPTujwTitsEsnrueJu5mGII3E3FSjKMAOjF16dcRepdAw4F5SFpK5/gu+O5TllxWzXK1XEtVGVTYmrqtaWcbq4yyVb1u8WG3v6+wKXeJaOi53Onf8jj+AAAA//8DAEh7bPCNAQAA\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Fri, 22 Aug 2025 02:52:11 GMT" + "value": "Thu, 02 Oct 2025 21:11:03 GMT" }, { "name": "content-type", @@ -246,7 +246,7 @@ }, { "name": "connection", - "value": "close" + "value": "keep-alive" }, { "name": "content-encoding", @@ -266,15 +266,15 @@ }, { "name": "x-rate-limit-remaining", - "value": "2999" + "value": "985" }, { "name": "x-request-id", - "value": "48528d30a03ce76f715dc6044485e85f" + "value": "105119ed21f56524cc10ddc1af4d4350" }, { "name": "x-trace-id", - "value": "ff9c0519da8171ef92d65d6fc8a79301" + "value": "31ca3a9c494ebd5d3c3d1489a7bf21a5" }, { "name": "strict-transport-security", @@ -294,21 +294,21 @@ }, { "name": "cf-ray", - "value": "972f1299db60ab4e-YYZ" + "value": "988730a678ed7290-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 629, + "headersSize": 633, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2025-08-22T02:52:11.067Z", - "time": 197, + "startedDateTime": "2025-10-02T21:11:03.423Z", + "time": 189, "timings": { "blocked": -1, "connect": -1, @@ -316,7 +316,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 197 + "wait": 189 } } ], diff --git a/packages/react/spec/auto/hooks/useTable.spec.tsx b/packages/react/spec/auto/hooks/useTable.spec.tsx index 821eb65a3..5dc53ef10 100644 --- a/packages/react/spec/auto/hooks/useTable.spec.tsx +++ b/packages/react/spec/auto/hooks/useTable.spec.tsx @@ -1,6 +1,6 @@ import { render, renderHook } from "@testing-library/react"; import React from "react"; -import { useAction } from "../../../src/useAction.js"; +import { useAction } from "../../../src/hooks.js"; import { useTable } from "../../../src/useTable.js"; import { testApi as api } from "../../apis.js"; import { mockUrqlClient } from "../../testWrappers.js"; diff --git a/packages/react/spec/auto/shadcn-defaults/__snapshots__/shadcnClassnameSafelist.spec.ts.snap b/packages/react/spec/auto/shadcn-defaults/__snapshots__/shadcnClassnameSafelist.spec.ts.snap index 025356f21..da30f5455 100644 --- a/packages/react/spec/auto/shadcn-defaults/__snapshots__/shadcnClassnameSafelist.spec.ts.snap +++ b/packages/react/spec/auto/shadcn-defaults/__snapshots__/shadcnClassnameSafelist.spec.ts.snap @@ -1447,10 +1447,6 @@ body { animation-play-state: running; } -.\\!paused { - animation-play-state: paused !important; -} - .paused { animation-play-state: paused; } diff --git a/packages/react/spec/auto/storybook/form/AutoFormExistingRecord.stories.tsx b/packages/react/spec/auto/storybook/form/AutoFormExistingRecord.stories.tsx index a3cd7109d..957362a24 100644 --- a/packages/react/spec/auto/storybook/form/AutoFormExistingRecord.stories.tsx +++ b/packages/react/spec/auto/storybook/form/AutoFormExistingRecord.stories.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Provider } from "../../../../src/GadgetProvider.js"; -import { useFindOne } from "../../../../src/useFindOne.js"; +import { useFindOne } from "../../../../src/hooks.js"; import { testApi as api } from "../../../apis.js"; import { SelectableDesignSystemAutoFormStory } from "./SelectableDesignSystemAutoFormStory.js"; diff --git a/packages/react/spec/auto/storybook/form/SelectableDesignSystemAutoFormStory.tsx b/packages/react/spec/auto/storybook/form/SelectableDesignSystemAutoFormStory.tsx index 16ce6a67f..5616ee284 100644 --- a/packages/react/spec/auto/storybook/form/SelectableDesignSystemAutoFormStory.tsx +++ b/packages/react/spec/auto/storybook/form/SelectableDesignSystemAutoFormStory.tsx @@ -1,4 +1,5 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import { BlockStack, Box, Button as PolarisButton, Card as PolarisCard, Label as PolarisLabel } from "@shopify/polaris"; import React from "react"; import { SUITE_NAMES } from "../../../../cypress/support/constants.js"; @@ -18,7 +19,6 @@ import { PolarisAutoHasOneForm } from "../../../../src/auto/polaris/inputs/relat import { PolarisAutoSubmit } from "../../../../src/auto/polaris/submit/PolarisAutoSubmit.js"; import { PolarisSubmitResultBanner } from "../../../../src/auto/polaris/submit/PolarisSubmitResultBanner.js"; import { makeAutocomponents } from "../../../../src/auto/shadcn/index.js"; -import { type OptionsType } from "../../../../src/utils.js"; import { Card as ShadcnCard } from "../../shadcn-defaults/components/Card.js"; import { Label as ShadcnLabel } from "../../shadcn-defaults/components/Label.js"; import { elements } from "../../shadcn-defaults/index.js"; diff --git a/packages/react/spec/auto/storybook/table/AutoTable.stories.tsx b/packages/react/spec/auto/storybook/table/AutoTable.stories.tsx index a65472545..ec8fcd912 100644 --- a/packages/react/spec/auto/storybook/table/AutoTable.stories.tsx +++ b/packages/react/spec/auto/storybook/table/AutoTable.stories.tsx @@ -2,7 +2,7 @@ import { Button, Checkbox } from "@shopify/polaris"; import { DeleteIcon } from "@shopify/polaris-icons"; import React, { useEffect } from "react"; import { Provider } from "../../../../src/GadgetProvider.js"; -import { useAction } from "../../../../src/useAction.js"; +import { useAction } from "../../../../src/hooks.js"; import { testApi as api } from "../../../apis.js"; import { StorybookErrorBoundary } from "../StorybookErrorBoundary.js"; import { SelectableDesignSystemAutoTableStory } from "./SelectableDesignSystemAutoTableStory.js"; diff --git a/packages/react/spec/auto/storybook/table/SelectableDesignSystemAutoTableStory.tsx b/packages/react/spec/auto/storybook/table/SelectableDesignSystemAutoTableStory.tsx index f3c44ff95..e906a3c86 100644 --- a/packages/react/spec/auto/storybook/table/SelectableDesignSystemAutoTableStory.tsx +++ b/packages/react/spec/auto/storybook/table/SelectableDesignSystemAutoTableStory.tsx @@ -1,11 +1,11 @@ import type { FindManyFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import { BlockStack, Box, LegacyCard } from "@shopify/polaris"; import React from "react"; import { SUITE_NAMES } from "../../../../cypress/support/constants.js"; import { type AutoTableProps } from "../../../../src/auto/AutoTable.js"; import { PolarisAutoTable } from "../../../../src/auto/polaris/PolarisAutoTable.js"; import { makeAutocomponents } from "../../../../src/auto/shadcn/index.js"; -import { type OptionsType } from "../../../../src/utils.js"; import { elements } from "../../shadcn-defaults/index.js"; import { DesignSystemSelectionControl, ShadcnAutoComponentsThemeControlWrapper, useDesignSystem } from "../SelectableDesignSystemUtils.js"; diff --git a/packages/react/spec/jest.setup.ts b/packages/react/spec/jest.setup.ts index 17bb56b6d..86c587d1b 100644 --- a/packages/react/spec/jest.setup.ts +++ b/packages/react/spec/jest.setup.ts @@ -1,6 +1,9 @@ import { jest } from "@jest/globals"; import "@testing-library/jest-dom"; import crossFetch from "cross-fetch"; +import { act } from "react"; + +(global as any).act = act; jest.setTimeout(process.env.CI == "vscode-jest-tests" ? 20 * 60 * 1000 : 5 * 1000); diff --git a/packages/react/spec/liveQueries.spec.tsx b/packages/react/spec/liveQueries.spec.tsx index 3faece93b..ade174e8d 100644 --- a/packages/react/spec/liveQueries.spec.tsx +++ b/packages/react/spec/liveQueries.spec.tsx @@ -5,7 +5,7 @@ import { render, renderHook, waitFor } from "@testing-library/react"; import type { Operation, Client as UrqlClient } from "@urql/core"; import React from "react"; import { pipe, subscribe } from "wonka"; -import { useFindMany } from "../src/useFindMany.js"; +import { useFindMany } from "../src/hooks.js"; import { testApi } from "./apis.js"; import { MockGraphQLWSClientWrapper, mockGraphQLWSClient } from "./testWrappers.js"; import { sleep } from "./utils.js"; diff --git a/packages/react/spec/testWrappers.tsx b/packages/react/spec/testWrappers.tsx index f9feb64c6..afe9d8762 100644 --- a/packages/react/spec/testWrappers.tsx +++ b/packages/react/spec/testWrappers.tsx @@ -19,6 +19,7 @@ export const MockClientWrapper = const urql = urqlClient ?? mockUrqlClient; jest.spyOn(api.connection, "currentClient" as any, "get").mockReturnValue(urql); + api.connection.fetch = urql.mockFetch as any; return ( diff --git a/packages/react/spec/useAction.spec.tsx b/packages/react/spec/useAction.spec.tsx index 04f43227e..a71bf3cee 100644 --- a/packages/react/spec/useAction.spec.tsx +++ b/packages/react/spec/useAction.spec.tsx @@ -1,13 +1,11 @@ import type { GadgetRecord } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { act, renderHook } from "@testing-library/react"; - import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; -import React from "react"; import type { AnyVariables } from "urql"; -import { Provider } from "../src/GadgetProvider.js"; +import { GadgetConnection } from "../../api-client-core/dist/cjs/GadgetConnection.js"; import { useAction } from "../src/index.js"; -import type { ErrorWrapper } from "../src/utils.js"; import { fullAuthApi, kitchenSinkApi, relatedProductsApi } from "./apis.js"; import { MockClientWrapper, createMockUrqlClient, mockUrqlClient } from "./testWrappers.js"; @@ -567,10 +565,14 @@ describe("useAction", () => { }, }); - const wrapper = (props: { children: React.ReactNode }) => {props.children}; + const api: any = { + connection: new GadgetConnection({ + endpoint: "https://api.gadget.dev", + }), + }; const { result } = renderHook(() => useAction(relatedProductsApi.unambiguous.update), { - wrapper, + wrapper: MockClientWrapper(api, client), }); let mutationPromise: any; @@ -651,10 +653,14 @@ describe("useAction", () => { }, }); - const wrapper = (props: { children: React.ReactNode }) => {props.children}; + const api: any = { + connection: new GadgetConnection({ + endpoint: "https://api.gadget.dev", + }), + }; const { result } = renderHook(() => useAction(fullAuthApi.user.signUp), { - wrapper, + wrapper: MockClientWrapper(api, client), }); let mutationPromise: any; @@ -697,10 +703,14 @@ describe("useAction", () => { }, }); - const wrapper = (props: { children: React.ReactNode }) => {props.children}; + const api: any = { + connection: new GadgetConnection({ + endpoint: "https://api.gadget.dev", + }), + }; const { result } = renderHook(() => useAction(relatedProductsApi.ambiguous.update), { - wrapper, + wrapper: MockClientWrapper(api, client), }); let mutationPromise: any; diff --git a/packages/react/spec/useActionFormNested.spec.tsx b/packages/react/spec/useActionFormNested.spec.tsx index 0eb6db9dd..31f36f8ad 100644 --- a/packages/react/spec/useActionFormNested.spec.tsx +++ b/packages/react/spec/useActionFormNested.spec.tsx @@ -2,8 +2,8 @@ import { $modelRelationships } from "@gadgetinc/api-client-core"; import { jest } from "@jest/globals"; import type { MODE } from "@pollyjs/core"; import { act, renderHook, waitFor } from "@testing-library/react"; +import { useFindFirst } from "../src/hooks.js"; import { useActionForm, useFieldArray } from "../src/useActionForm.js"; -import { useFindFirst } from "../src/useFindFirst.js"; import { bulkExampleApi, hasManyThroughApi, nestedExampleApi, testApi } from "./apis.js"; import { startPolly } from "./polly.js"; import { LiveClientWrapper, MockClientWrapper, mockUrqlClient } from "./testWrappers.js"; diff --git a/packages/react/spec/useBulkAction.spec.ts b/packages/react/spec/useBulkAction.spec.ts index 9aaa2f3cb..ff8a77b6e 100644 --- a/packages/react/spec/useBulkAction.spec.ts +++ b/packages/react/spec/useBulkAction.spec.ts @@ -1,10 +1,10 @@ import type { GadgetRecord } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { renderHook } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { act } from "react"; import { useBulkAction } from "../src/index.js"; -import type { ErrorWrapper } from "../src/utils.js"; import { bulkExampleApi, kitchenSinkApi } from "./apis.js"; import { MockClientWrapper, createMockUrqlClient, mockUrqlClient } from "./testWrappers.js"; diff --git a/packages/react/spec/useEnqueue.spec.tsx b/packages/react/spec/useEnqueue.spec.tsx index e8357e2e0..6bf266588 100644 --- a/packages/react/spec/useEnqueue.spec.tsx +++ b/packages/react/spec/useEnqueue.spec.tsx @@ -1,12 +1,11 @@ import type { BackgroundActionHandle } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { act, renderHook } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; -import React from "react"; import { type AnyVariables } from "urql"; -import { Provider } from "../src/index.js"; -import { useEnqueue } from "../src/useEnqueue.js"; -import type { ErrorWrapper } from "../src/utils.js"; +import { GadgetConnection } from "../../api-client-core/dist/cjs/GadgetConnection.js"; +import { useEnqueue } from "../src/hooks.js"; import { bulkExampleApi, kitchenSinkApi, relatedProductsApi } from "./apis.js"; import { MockClientWrapper, createMockUrqlClient, mockUrqlClient } from "./testWrappers.js"; @@ -704,10 +703,14 @@ describe("useEnqueue", () => { }, }); - const wrapper = (props: { children: React.ReactNode }) => {props.children}; + const api: any = { + connection: new GadgetConnection({ + endpoint: "https://api.gadget.dev", + }), + }; const { result } = renderHook(() => useEnqueue(relatedProductsApi.unambiguous.update), { - wrapper, + wrapper: MockClientWrapper(api, client), }); let mutationPromise: any; @@ -789,10 +792,14 @@ describe("useEnqueue", () => { }, }); - const wrapper = (props: { children: React.ReactNode }) => {props.children}; + const api: any = { + connection: new GadgetConnection({ + endpoint: "https://api.gadget.dev", + }), + }; const { result } = renderHook(() => useEnqueue(relatedProductsApi.ambiguous.update), { - wrapper, + wrapper: MockClientWrapper(api, client), }); let mutationPromise: any; diff --git a/packages/react/spec/useFetch.spec.tsx b/packages/react/spec/useFetch.spec.tsx index 86353ef42..6bd96ddf3 100644 --- a/packages/react/spec/useFetch.spec.tsx +++ b/packages/react/spec/useFetch.spec.tsx @@ -2,14 +2,14 @@ * @jest-environment ./spec/jsdom-environment.ts */ +import type { ErrorWrapper } from "@gadgetinc/utils"; import { render, renderHook, screen } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import React, { act } from "react"; import { Readable } from "stream"; -import { useFetch } from "../src/useFetch.js"; -import type { ErrorWrapper } from "../src/utils.js"; +import { useFetch } from "../src/hooks.js"; import { relatedProductsApi } from "./apis.js"; import { MockClientWrapper, mockUrqlClient } from "./testWrappers.js"; diff --git a/packages/react/spec/useFindBy.spec.ts b/packages/react/spec/useFindBy.spec.ts index c065d2b0b..3d0d1e8ac 100644 --- a/packages/react/spec/useFindBy.spec.ts +++ b/packages/react/spec/useFindBy.spec.ts @@ -1,10 +1,10 @@ import type { GadgetRecord } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { diff } from "@n1ru4l/json-patch-plus"; import { act, renderHook, waitFor } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { useFindBy } from "../src/index.js"; -import type { ErrorWrapper } from "../src/utils.js"; import { kitchenSinkApi, relatedProductsApi } from "./apis.js"; import { MockClientWrapper, diff --git a/packages/react/spec/useFindFirst.spec.ts b/packages/react/spec/useFindFirst.spec.ts index 9f923efe6..ddee5efaa 100644 --- a/packages/react/spec/useFindFirst.spec.ts +++ b/packages/react/spec/useFindFirst.spec.ts @@ -1,10 +1,10 @@ import type { GadgetRecord } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { diff } from "@n1ru4l/json-patch-plus"; import { act, renderHook, waitFor } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { useFindFirst } from "../src/index.js"; -import type { ErrorWrapper } from "../src/utils.js"; import { kitchenSinkApi, relatedProductsApi } from "./apis.js"; import { MockClientWrapper, diff --git a/packages/react/spec/useFindMany.spec.ts b/packages/react/spec/useFindMany.spec.ts index 4dcbec97b..c0a9c6e26 100644 --- a/packages/react/spec/useFindMany.spec.ts +++ b/packages/react/spec/useFindMany.spec.ts @@ -1,13 +1,13 @@ import { GraphQLError } from "@0no-co/graphql.web"; import { Client } from "@gadget-client/related-products-example"; import type { GadgetRecordList } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { diff } from "@n1ru4l/json-patch-plus"; import { renderHook, waitFor } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { act } from "react"; -import { useFindMany } from "../src/useFindMany.js"; -import type { ErrorWrapper } from "../src/utils.js"; +import { useFindMany } from "../src/hooks.js"; import { kitchenSinkApi, relatedProductsApi } from "./apis.js"; import { MockClientWrapper, diff --git a/packages/react/spec/useFindOne.spec.ts b/packages/react/spec/useFindOne.spec.ts index 09da917c9..f26a7794b 100644 --- a/packages/react/spec/useFindOne.spec.ts +++ b/packages/react/spec/useFindOne.spec.ts @@ -1,9 +1,9 @@ import type { GadgetRecord } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { act, renderHook, waitFor } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { useFindOne } from "../src/index.js"; -import type { ErrorWrapper } from "../src/utils.js"; import { kitchenSinkApi, relatedProductsApi } from "./apis.js"; import { MockClientWrapper, diff --git a/packages/react/spec/useGadgetMutation.spec.ts b/packages/react/spec/useGadgetMutation.spec.ts index 9a3579db4..2ed8b96bf 100644 --- a/packages/react/spec/useGadgetMutation.spec.ts +++ b/packages/react/spec/useGadgetMutation.spec.ts @@ -1,7 +1,6 @@ import { act, renderHook } from "@testing-library/react"; import { gql } from "urql"; -import { useGadgetMutation } from "../src/useGadgetMutation.js"; -import { noProviderErrorMessage } from "../src/utils.js"; +import { useMutation as useGadgetMutation } from "../src/hooks.js"; import { relatedProductsApi } from "./apis.js"; import { MockClientWrapper, mockUrqlClient } from "./testWrappers.js"; @@ -11,7 +10,9 @@ describe("useGadgetMutation", () => { renderHook(() => useGadgetMutation("{__typename}")); } catch (error: any) { expect(error).toBeInstanceOf(Error); - expect(error.message).toBe(noProviderErrorMessage); + expect(error.message).toBe( + `You are attempting to use the useMutation hook, but you are not calling it from a component that is wrapped in a Gadget component. Please ensure you are wrapping this hook with the component from either @gadgetinc/react or @gadgetinc/preact.` + ); } }); diff --git a/packages/react/spec/useGadgetQuery.spec.ts b/packages/react/spec/useGadgetQuery.spec.ts index ee562938f..82cfbb357 100644 --- a/packages/react/spec/useGadgetQuery.spec.ts +++ b/packages/react/spec/useGadgetQuery.spec.ts @@ -1,7 +1,6 @@ import { act, renderHook } from "@testing-library/react"; import { gql } from "urql"; -import { useGadgetQuery } from "../src/useGadgetQuery.js"; -import { noProviderErrorMessage } from "../src/utils.js"; +import { useQuery as useGadgetQuery } from "../src/hooks.js"; import { relatedProductsApi } from "./apis.js"; import { MockClientWrapper, mockUrqlClient } from "./testWrappers.js"; @@ -11,7 +10,9 @@ describe("useGadgetQuery", () => { renderHook(() => useGadgetQuery({ query: "{__typename}" })); } catch (error: any) { expect(error).toBeInstanceOf(Error); - expect(error.message).toBe(noProviderErrorMessage); + expect(error.message).toBe( + `You are attempting to use the useQuery hook, but you are not calling it from a component that is wrapped in a Gadget component. Please ensure you are wrapping this hook with the component from either @gadgetinc/react or @gadgetinc/preact.` + ); } }); diff --git a/packages/react/spec/useGet.spec.ts b/packages/react/spec/useGet.spec.ts index 55251fac2..b11356624 100644 --- a/packages/react/spec/useGet.spec.ts +++ b/packages/react/spec/useGet.spec.ts @@ -1,10 +1,10 @@ import type { GadgetRecord } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { renderHook } from "@testing-library/react"; import type { Has, IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { act } from "react"; -import { useGet } from "../src/useGet.js"; -import type { ErrorWrapper } from "../src/utils.js"; +import { useGet } from "../src/hooks.js"; import { relatedProductsApi } from "./apis.js"; import { MockClientWrapper, mockUrqlClient } from "./testWrappers.js"; diff --git a/packages/react/spec/useGlobalAction.spec.ts b/packages/react/spec/useGlobalAction.spec.ts index c06fb7a32..51cbb62a1 100644 --- a/packages/react/spec/useGlobalAction.spec.ts +++ b/packages/react/spec/useGlobalAction.spec.ts @@ -1,9 +1,9 @@ +import type { ErrorWrapper } from "@gadgetinc/utils"; import { renderHook } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import { act } from "react"; import { useGlobalAction } from "../src/index.js"; -import type { ErrorWrapper } from "../src/utils.js"; import { bulkExampleApi, kitchenSinkApi } from "./apis.js"; import { MockClientWrapper, createMockUrqlClient, mockUrqlClient } from "./testWrappers.js"; diff --git a/packages/react/spec/useList.spec.ts b/packages/react/spec/useList.spec.ts index b449bbc6a..a0443b24a 100644 --- a/packages/react/spec/useList.spec.ts +++ b/packages/react/spec/useList.spec.ts @@ -4,11 +4,11 @@ import { act, renderHook, waitFor } from "@testing-library/react"; let data: any = [{ id: 1 }, { id: 2 }]; let fetching = false; let error = undefined as any; -jest.unstable_mockModule("../src/useFindMany", () => ({ +jest.unstable_mockModule("../src/hooks", () => ({ useFindMany: jest.fn().mockImplementation(() => [{ data, fetching, error, stale: false }, jest.fn()]), })); -const { useFindMany } = await import("../src/useFindMany.js"); +const { useFindMany } = await import("../src/hooks.js"); const { useList } = await import("../src/useList.js"); describe("useList", () => { diff --git a/packages/react/spec/useView.spec.tsx b/packages/react/spec/useView.spec.tsx index 833f8289d..f8f92d840 100644 --- a/packages/react/spec/useView.spec.tsx +++ b/packages/react/spec/useView.spec.tsx @@ -1,9 +1,9 @@ +import type { ErrorWrapper } from "@gadgetinc/utils"; import { act, renderHook } from "@testing-library/react"; import type { IsExact } from "conditional-type-checks"; import { assert } from "conditional-type-checks"; import type { AnyVariables } from "urql"; -import { useView } from "../src/useView.js"; -import type { ErrorWrapper } from "../src/utils.js"; +import { useView } from "../src/hooks.js"; import { testApi } from "./apis.js"; import { MockClientWrapper, createMockUrqlClient, mockUrqlClient } from "./testWrappers.js"; diff --git a/packages/react/src/GadgetProvider.tsx b/packages/react/src/GadgetProvider.tsx index b87e8ed3c..b33fafbc5 100644 --- a/packages/react/src/GadgetProvider.tsx +++ b/packages/react/src/GadgetProvider.tsx @@ -1,16 +1,10 @@ -import type { AnyClient, GadgetConnection } from "@gadgetinc/api-client-core"; -import { $gadgetConnection, isGadgetClient } from "@gadgetinc/api-client-core"; +import type { AnyClient } from "@gadgetinc/api-client-core"; +import type { GadgetApiContext as CoreGadgetApiContext } from "@gadgetinc/client-hooks"; +import { registerClientHooks } from "@gadgetinc/client-hooks"; import type { ReactNode } from "react"; -import React, { useContext } from "react"; -import type { Client as UrqlClient } from "urql"; +import React from "react"; import { Provider as UrqlProvider } from "urql"; - -/** - * React context that stores the current urql client - * - * urql doesn't have its own useClient hook, so we store it on our own context to get at the client object later - **/ -export const GadgetUrqlClientContext = React.createContext(undefined); +import { reactAdapter } from "./adapter.js"; /** Provides the Gadget auth configuration used in the auth hooks */ export interface GadgetAuthConfiguration { @@ -23,16 +17,12 @@ export interface GadgetAuthConfiguration { } /** Provides the api client instance, if present, as well as the Gadget auth configuration for the application. */ -export interface GadgetConfigurationContext { - api: AnyClient | undefined; +export interface GadgetApiContext extends CoreGadgetApiContext { auth: GadgetAuthConfiguration; navigate?: (path: string) => void; } -/** - * React context that stores an instance of the JS Client for an app (AKA the `api` object) - */ -export const GadgetConfigurationContext = React.createContext(undefined); +export const GadgetApiContext = React.createContext(null as unknown as GadgetApiContext); interface BaseProviderProps { children: ReactNode; @@ -57,15 +47,6 @@ export interface ProviderProps extends BaseProviderProps { auth?: Partial; } -/** @deprecated -- pass an instance of your app's api client instead with the `api` prop */ -export interface DeprecatedProviderProps extends BaseProviderProps { - /** - * an urql client object from your current Gadget client, like `api.connection.currentClient` - * @deprecated -- pass an instance of your app's api client instead with the `api` prop - */ - value: UrqlClient; -} - const defaultSignInPath = "/"; const defaultSignOutApiIdentifier = "signOut"; const defaultRedirectOnSuccessfulSignInPath = "/"; @@ -92,25 +73,8 @@ const defaultRedirectOnSuccessfulSignInPath = "/"; * * */ -export function Provider(props: ProviderProps | DeprecatedProviderProps) { - let gadgetClient: AnyClient | undefined = undefined; - - let urqlClient: UrqlClient; - if ("api" in props) { - if (!isGadgetClient(props.api)) { - throw new Error( - "Invalid Gadget API client passed to component -- please pass an instance of your generated client, like !" - ); - } - gadgetClient = props.api; - urqlClient = props.api.connection.currentClient; - } else if (props.value) { - urqlClient = props.value; - } else { - throw new Error( - "No Gadget API client passed to component -- please pass an instance of your generated client, like !" - ); - } +export function Provider(props: ProviderProps) { + const { gadgetClient, gadgetConnection, urqlClient } = registerClientHooks(props.api, { ...reactAdapter, GadgetApiContext }); // hack: make the client support suspending some queries when used by the react provider // this flag is safe to mutably set here because it just serves as a flag for the urql react hooks, and doesn't affect imperative api client functioning urqlClient.suspense = true; @@ -127,72 +91,19 @@ export function Provider(props: ProviderProps | DeprecatedProviderProps) { } return ( - - - {props.children} - - + + {props.children} + ); } - -/** - * Get the current `GadgetConnection` object from React context. - * Must be called within a component wrapped by the `` component. - **/ -export const useConnection = () => { - const urqlClient = useContext(GadgetUrqlClientContext); - if (!urqlClient) { - throw new Error("No urql client object in React context, have you added the wrapper component from @gadgetinc/react?"); - } - const connection = (urqlClient as any)[$gadgetConnection] as unknown as GadgetConnection | undefined; - if (!connection) { - throw new Error( - `urql client found in context was not set up by the Gadget API client. Please ensure you are wrapping this hook with the component from @gadgetinc/react. - - Possible remedies: - - ensuring you have the component wrapped around your hook invocation - - ensuring you are passing a value to the provider, usually - - ensuring your @gadget-client/ package and your @gadgetinc/react package are up to date` - ); - } - - return connection; -}; - -/** - * Get the current `api` object from React context - * Must be called within a component wrapped by the `` component. - **/ -export const useApi = () => { - const gadgetContext = useContext(GadgetConfigurationContext); - const urqlClient = useContext(GadgetUrqlClientContext); - if (!gadgetContext || !gadgetContext.api) { - if (urqlClient) { - throw new Error( - `useApi hook called in context with deprecated convention. Please ensure you are wrapping this hook with the component from @gadgetinc/react and passing it an instance of your api client, like . - - The component is currently being passed a value, like . Please update this to .` - ); - } else { - throw new Error( - `useApi hook called in context where no Gadget API client is available. Please ensure you are wrapping this hook with the component from @gadgetinc/react. - - Possible remedies: - - ensuring you have the component wrapped around your hook invocation - - ensuring you are passing an api client instance to the provider, usually - - ensuring your @gadget-client/ package and your @gadgetinc/react package are up to date` - ); - } - } - return gadgetContext.api; -}; diff --git a/packages/react/src/adapter.tsx b/packages/react/src/adapter.tsx new file mode 100644 index 000000000..fde28a741 --- /dev/null +++ b/packages/react/src/adapter.tsx @@ -0,0 +1,24 @@ +import type { Client, DocumentInput } from "@urql/core"; +import * as React from "react"; +import deepEqual from "react-fast-compare"; +import { Provider as UrqlProvider, UseQueryArgs, useMutation, useQuery } from "urql"; + +export const reactAdapter = { + framework: { + deepEqual, + useEffect: React.useEffect, + useMemo: React.useMemo, + useRef: React.useRef, + useState: React.useState, + useContext: React.useContext, + createContext: React.createContext, + useCallback: React.useCallback, + useReducer: React.useReducer, + Fragment: React.Fragment, + }, + urql: { + Provider: (props: { client: Client; children: React.ReactNode }) => {props.children}, + useQuery: (args: UseQueryArgs) => useQuery(args), + useMutation: (query: DocumentInput) => useMutation(query), + }, +}; diff --git a/packages/react/src/auth/SignedInOrRedirect.tsx b/packages/react/src/auth/SignedInOrRedirect.tsx index ab2f278aa..9a8a2cec1 100644 --- a/packages/react/src/auth/SignedInOrRedirect.tsx +++ b/packages/react/src/auth/SignedInOrRedirect.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; import React, { useContext, useEffect, useRef } from "react"; -import { GadgetConfigurationContext } from "../GadgetProvider.js"; +import { GadgetApiContext } from "../GadgetProvider.js"; import { useAuth } from "./useAuth.js"; import { windowNavigate } from "./utils.js"; @@ -11,7 +11,7 @@ export const SignedInOrRedirect = (props: { path?: string; children: ReactNode } const redirected = useRef(false); const { user, isSignedIn } = useAuth(); - const context = useContext(GadgetConfigurationContext); + const context = useContext(GadgetApiContext); const { auth } = context ?? {}; useEffect(() => { diff --git a/packages/react/src/auth/SignedOutOrRedirect.tsx b/packages/react/src/auth/SignedOutOrRedirect.tsx index e306b25a5..4a3db5344 100644 --- a/packages/react/src/auth/SignedOutOrRedirect.tsx +++ b/packages/react/src/auth/SignedOutOrRedirect.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; import React, { useContext, useEffect, useState } from "react"; -import { GadgetConfigurationContext } from "../GadgetProvider.js"; +import { GadgetApiContext } from "../GadgetProvider.js"; import { useAuth } from "./useAuth.js"; import { windowNavigate } from "./utils.js"; @@ -12,7 +12,7 @@ export const SignedOutOrRedirect = (props: { path?: string; children: ReactNode const { path, children } = props; const { user, isSignedIn } = useAuth(); - const context = useContext(GadgetConfigurationContext); + const context = useContext(GadgetApiContext); const { auth } = context ?? {}; useEffect(() => { diff --git a/packages/react/src/auth/useAuth.ts b/packages/react/src/auth/useAuth.ts index 135020dad..786790efd 100644 --- a/packages/react/src/auth/useAuth.ts +++ b/packages/react/src/auth/useAuth.ts @@ -1,8 +1,8 @@ import type { DefaultSelection, GadgetRecord, Select } from "@gadgetinc/api-client-core"; +import type { OptionsType, ReadOperationOptions } from "@gadgetinc/client-hooks"; import { useContext } from "react"; import type { GadgetAuthConfiguration } from "../GadgetProvider.js"; -import { GadgetConfigurationContext } from "../GadgetProvider.js"; -import type { OptionsType, ReadOperationOptions } from "../utils.js"; +import { GadgetApiContext } from "../GadgetProvider.js"; import type { ClientWithSessionAndUserManagers, GadgetSession, GadgetUser } from "./useSession.js"; import { useSession } from "./useSession.js"; import { useUser } from "./useUser.js"; @@ -54,7 +54,7 @@ export const useAuth = < } => { const session = useSession(client); const user = useUser(client); - const context = useContext(GadgetConfigurationContext); + const context = useContext(GadgetApiContext); if (!context) { throw new Error("useAuth must be used within a GadgetProvider"); diff --git a/packages/react/src/auth/useSession.ts b/packages/react/src/auth/useSession.ts index c5e6bfab6..f0ab93abc 100644 --- a/packages/react/src/auth/useSession.ts +++ b/packages/react/src/auth/useSession.ts @@ -7,9 +7,8 @@ import type { LimitToKnownKeys, Select, } from "@gadgetinc/api-client-core"; -import { useApi } from "../GadgetProvider.js"; -import { useGet } from "../useGet.js"; -import type { OptionsType, ReadOperationOptions } from "../utils.js"; +import type { OptionsType, ReadOperationOptions } from "@gadgetinc/client-hooks"; +import { useApi, useGet } from "../hooks.js"; export type GadgetSession = GadgetRecord>; diff --git a/packages/react/src/auth/useSignOut.ts b/packages/react/src/auth/useSignOut.ts index b9a85bd32..1d87f6c10 100644 --- a/packages/react/src/auth/useSignOut.ts +++ b/packages/react/src/auth/useSignOut.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useEffect } from "react"; -import { GadgetConfigurationContext, useApi } from "../GadgetProvider.js"; -import { useAction } from "../useAction.js"; +import { GadgetApiContext } from "../GadgetProvider.js"; +import { useAction, useApi } from "../hooks.js"; import { useUser } from "./useUser.js"; import { windowNavigate } from "./utils.js"; @@ -14,9 +14,9 @@ export const useSignOut = (opts?: { redirectOnSuccess?: boolean; redirectToPath? const redirectToPath = opts?.redirectToPath; const api = useApi(); const user = useUser(); - const context = useContext(GadgetConfigurationContext); + const context = useContext(GadgetApiContext); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { signOutActionApiIdentifier, signInPath } = context!.auth; + const { signOutActionApiIdentifier, signInPath } = context.auth; if (signOutActionApiIdentifier && (api as any).user[signOutActionApiIdentifier]) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/react/src/auth/useUser.ts b/packages/react/src/auth/useUser.ts index 61689181d..d51502ddd 100644 --- a/packages/react/src/auth/useUser.ts +++ b/packages/react/src/auth/useUser.ts @@ -1,6 +1,6 @@ import type { DefaultSelection, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { useApi } from "../GadgetProvider.js"; -import type { OptionsType, ReadOperationOptions } from "../utils.js"; +import type { OptionsType, ReadOperationOptions } from "@gadgetinc/client-hooks"; +import { useApi } from "../hooks.js"; import type { ClientWithSessionAndUserManagers } from "./useSession.js"; import { useSession } from "./useSession.js"; diff --git a/packages/react/src/auto/AutoForm.ts b/packages/react/src/auto/AutoForm.ts index 2fa7fc0f1..f8f7aa79a 100644 --- a/packages/react/src/auto/AutoForm.ts +++ b/packages/react/src/auto/AutoForm.ts @@ -6,6 +6,8 @@ import type { GlobalActionFunction, Select, } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import { yupResolver } from "@hookform/resolvers/yup"; import type { ReactNode } from "react"; import React, { useEffect, useMemo, useRef } from "react"; @@ -23,7 +25,7 @@ import { isPlainObject, processDefaultValues, toDefaultValues } from "../use-act import { getRelatedModelFields, isHasManyOrHasManyThroughField, isRelationshipField, pathListToSelection } from "../use-table/helpers.js"; import type { FieldErrors, FieldValues, UseFormReturn } from "../useActionForm.js"; import { useActionForm } from "../useActionForm.js"; -import { get, getFlattenedObjectKeys, set, type ErrorWrapper, type OptionsType } from "../utils.js"; +import { get, getFlattenedObjectKeys, set } from "../utils.js"; import { validationSchema } from "../validationSchema.js"; import { validateFindByObjectWithMetadata, diff --git a/packages/react/src/auto/AutoFormActionValidators.ts b/packages/react/src/auto/AutoFormActionValidators.ts index 7c41bff27..f6f5e6566 100644 --- a/packages/react/src/auto/AutoFormActionValidators.ts +++ b/packages/react/src/auto/AutoFormActionValidators.ts @@ -1,9 +1,9 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import type { FieldMetadata } from "../metadata.js"; import { type GlobalActionMetadata, type ModelWithOneActionMetadata } from "../metadata.js"; import type { RecordIdentifier } from "../use-action-form/types.js"; import { isPlainObject } from "../use-action-form/utils.js"; -import type { OptionsType } from "../utils.js"; import type { AutoFormProps } from "./AutoForm.js"; export const validateNonBulkAction = (action: ActionFunction | GlobalActionFunction) => { diff --git a/packages/react/src/auto/AutoTable.tsx b/packages/react/src/auto/AutoTable.tsx index 621e5d069..39a7404ec 100644 --- a/packages/react/src/auto/AutoTable.tsx +++ b/packages/react/src/auto/AutoTable.tsx @@ -1,7 +1,7 @@ import type { DefaultSelection, FindManyFunction, GadgetRecord, Select } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import type { ReactNode } from "react"; import type { TableOptions, TableRow } from "../use-table/types.js"; -import type { OptionsType } from "../utils.js"; /** * Props for AutoTable, including Gadget-land and adapter-specific props. diff --git a/packages/react/src/auto/hooks/useAutoButtonController.tsx b/packages/react/src/auto/hooks/useAutoButtonController.tsx index 6d6794bb0..e0fb89127 100644 --- a/packages/react/src/auto/hooks/useAutoButtonController.tsx +++ b/packages/react/src/auto/hooks/useAutoButtonController.tsx @@ -1,10 +1,8 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { ActionHookState, OptionsType } from "@gadgetinc/client-hooks"; import { useCallback } from "react"; +import { useAction, useGlobalAction } from "../../hooks.js"; import { isModelActionMetadata, useActionMetadata } from "../../metadata.js"; -import { useAction } from "../../useAction.js"; -import { useGlobalAction } from "../../useGlobalAction.js"; -import type { ActionHookState } from "../../utils.js"; -import { type OptionsType } from "../../utils.js"; export type AutoButtonProps< GivenOptions extends OptionsType, diff --git a/packages/react/src/auto/hooks/useAutoRelationship.tsx b/packages/react/src/auto/hooks/useAutoRelationship.tsx index 2cc53fd6d..80bbd8baf 100644 --- a/packages/react/src/auto/hooks/useAutoRelationship.tsx +++ b/packages/react/src/auto/hooks/useAutoRelationship.tsx @@ -1,4 +1,4 @@ -import { assert } from "@gadgetinc/api-client-core"; +import { assert } from "@gadgetinc/utils"; import React from "react"; import type { useFieldArray } from "../../useActionForm.js"; import { useAutoFormMetadata } from "../AutoFormContext.js"; diff --git a/packages/react/src/auto/hooks/useFileInputController.tsx b/packages/react/src/auto/hooks/useFileInputController.tsx index c630b4abc..e5828a14e 100644 --- a/packages/react/src/auto/hooks/useFileInputController.tsx +++ b/packages/react/src/auto/hooks/useFileInputController.tsx @@ -1,6 +1,6 @@ import { filesize } from "filesize"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { useApi } from "../../GadgetProvider.js"; +import { useApi } from "../../hooks.js"; import type { GadgetOnlyImageFileFieldValidation, GadgetRangeFieldValidation } from "../../internal/gql/graphql.js"; import { GadgetFieldType } from "../../internal/gql/graphql.js"; import { useController, useFormContext, type Control } from "../../useActionForm.js"; diff --git a/packages/react/src/auto/hooks/useHasManyThroughController.tsx b/packages/react/src/auto/hooks/useHasManyThroughController.tsx index 60fcbf554..788749b77 100644 --- a/packages/react/src/auto/hooks/useHasManyThroughController.tsx +++ b/packages/react/src/auto/hooks/useHasManyThroughController.tsx @@ -1,4 +1,4 @@ -import { assert } from "@gadgetinc/api-client-core"; +import { assert } from "@gadgetinc/utils"; import React, { createContext, useCallback, useContext, useMemo, type ReactNode } from "react"; import { GadgetFieldType, type GadgetHasManyThroughConfig } from "../../internal/gql/graphql.js"; import { useFieldArray } from "../../useActionForm.js"; diff --git a/packages/react/src/auto/hooks/useModelManager.tsx b/packages/react/src/auto/hooks/useModelManager.tsx index 4aed947dd..a64894981 100644 --- a/packages/react/src/auto/hooks/useModelManager.tsx +++ b/packages/react/src/auto/hooks/useModelManager.tsx @@ -1,4 +1,4 @@ -import { useApi } from "../../GadgetProvider.js"; +import { useApi } from "../../hooks.js"; import { getModelManager } from "../../utils.js"; export const useModelManager = (props: { apiIdentifier: string; namespace?: string[] | string | null }) => { diff --git a/packages/react/src/auto/hooks/useRelatedModel.tsx b/packages/react/src/auto/hooks/useRelatedModel.tsx index 602a6f97b..411706ba7 100644 --- a/packages/react/src/auto/hooks/useRelatedModel.tsx +++ b/packages/react/src/auto/hooks/useRelatedModel.tsx @@ -1,9 +1,10 @@ -import { assert, type FieldSelection } from "@gadgetinc/api-client-core"; +import type { FieldSelection } from "@gadgetinc/api-client-core"; +import { assert } from "@gadgetinc/utils"; import React, { useCallback, useEffect, useState } from "react"; +import { useFindMany } from "../../hooks.js"; import { FieldType } from "../../metadata.js"; import { type RecordIdentifier } from "../../use-action-form/types.js"; import { useDebouncedSearch } from "../../useDebouncedSearch.js"; -import { useFindMany } from "../../useFindMany.js"; import { sortByProperty, uniqByProperty } from "../../utils.js"; import { useAutoFormMetadata } from "../AutoFormContext.js"; import { diff --git a/packages/react/src/auto/hooks/useTableBulkActions.tsx b/packages/react/src/auto/hooks/useTableBulkActions.tsx index 16e61bc3e..bcee63dfa 100644 --- a/packages/react/src/auto/hooks/useTableBulkActions.tsx +++ b/packages/react/src/auto/hooks/useTableBulkActions.tsx @@ -1,8 +1,8 @@ -import { type GadgetRecord } from "@gadgetinc/api-client-core"; +import type { GadgetRecord } from "@gadgetinc/api-client-core"; import React, { useCallback, useEffect, useMemo } from "react"; import deepEqual from "react-fast-compare"; +import { useBulkAction } from "../../hooks.js"; import type { ActionCallback, TableOptions, TableRow } from "../../use-table/types.js"; -import { useBulkAction } from "../../useBulkAction.js"; import { humanizeCamelCase } from "../../utils.js"; import { validateAutoTableProps } from "../AutoTableValidators.js"; diff --git a/packages/react/src/auto/polaris/PolarisAutoButton.tsx b/packages/react/src/auto/polaris/PolarisAutoButton.tsx index 7e604f04c..2442f8907 100644 --- a/packages/react/src/auto/polaris/PolarisAutoButton.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoButton.tsx @@ -1,8 +1,8 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import { Button } from "@shopify/polaris"; import type { ComponentProps } from "react"; import React from "react"; -import type { OptionsType } from "../../utils.js"; import type { AutoButtonProps } from "../hooks/useAutoButtonController.js"; import { useAutoButtonController } from "../hooks/useAutoButtonController.js"; diff --git a/packages/react/src/auto/polaris/PolarisAutoForm.tsx b/packages/react/src/auto/polaris/PolarisAutoForm.tsx index be18c35ca..642ce4ac7 100644 --- a/packages/react/src/auto/polaris/PolarisAutoForm.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoForm.tsx @@ -1,9 +1,10 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import type { FormProps } from "@shopify/polaris"; import { BlockStack, Form, FormLayout, SkeletonBodyText, SkeletonDisplayText, Text } from "@shopify/polaris"; import React from "react"; import { FormProvider } from "../../useActionForm.js"; -import { humanizeCamelCase, type OptionsType } from "../../utils.js"; +import { humanizeCamelCase } from "../../utils.js"; import type { AutoFormProps } from "../AutoForm.js"; import { useAutoForm } from "../AutoForm.js"; import { validateAutoFormProps } from "../AutoFormActionValidators.js"; diff --git a/packages/react/src/auto/polaris/PolarisAutoTable.tsx b/packages/react/src/auto/polaris/PolarisAutoTable.tsx index ba2907d14..da934d2fc 100644 --- a/packages/react/src/auto/polaris/PolarisAutoTable.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoTable.tsx @@ -1,4 +1,5 @@ import type { FindManyFunction, GadgetRecord, SortOrder } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import type { IndexTableProps } from "@shopify/polaris"; import { Banner, @@ -16,7 +17,7 @@ import type { ReactNode } from "react"; import React, { useCallback, useMemo } from "react"; import type { TableColumn, TableRow } from "../../use-table/types.js"; import { useTable } from "../../useTable.js"; -import type { ColumnValueType, OptionsType } from "../../utils.js"; +import type { ColumnValueType } from "../../utils.js"; import type { AutoTableProps } from "../AutoTable.js"; import { AutoTableContext } from "../AutoTableContext.js"; import { validateAutoTableProps } from "../AutoTableValidators.js"; diff --git a/packages/react/src/auto/shadcn/ShadcnAutoButton.tsx b/packages/react/src/auto/shadcn/ShadcnAutoButton.tsx index b687623be..c383521ec 100644 --- a/packages/react/src/auto/shadcn/ShadcnAutoButton.tsx +++ b/packages/react/src/auto/shadcn/ShadcnAutoButton.tsx @@ -1,6 +1,6 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import React from "react"; -import type { OptionsType } from "../../utils.js"; import type { AutoButtonProps } from "../hooks/useAutoButtonController.js"; import { useAutoButtonController } from "../hooks/useAutoButtonController.js"; import type { ButtonProps, ShadcnElements } from "./elements.js"; diff --git a/packages/react/src/auto/shadcn/ShadcnAutoForm.tsx b/packages/react/src/auto/shadcn/ShadcnAutoForm.tsx index ed8dd1047..366c9883b 100644 --- a/packages/react/src/auto/shadcn/ShadcnAutoForm.tsx +++ b/packages/react/src/auto/shadcn/ShadcnAutoForm.tsx @@ -1,8 +1,9 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import type { ComponentProps } from "react"; import React, { forwardRef } from "react"; import { FormProvider } from "../../useActionForm.js"; -import { humanizeCamelCase, type OptionsType } from "../../utils.js"; +import { humanizeCamelCase } from "../../utils.js"; import type { AutoFormProps } from "../AutoForm.js"; import { useAutoForm } from "../AutoForm.js"; import { validateAutoFormProps } from "../AutoFormActionValidators.js"; diff --git a/packages/react/src/auto/shadcn/ShadcnAutoTable.tsx b/packages/react/src/auto/shadcn/ShadcnAutoTable.tsx index 86e3bd30c..744004bab 100644 --- a/packages/react/src/auto/shadcn/ShadcnAutoTable.tsx +++ b/packages/react/src/auto/shadcn/ShadcnAutoTable.tsx @@ -1,10 +1,11 @@ import { type FindManyFunction, type GadgetRecord } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import * as React from "react"; import { useCallback, useMemo } from "react"; import type { SortState, TableColumn, TableRow } from "../../use-table/types.js"; import { SelectionType, type RecordSelection } from "../../useSelectedRecordsController.js"; import { useTable } from "../../useTable.js"; -import { type ColumnValueType, type OptionsType } from "../../utils.js"; +import { type ColumnValueType } from "../../utils.js"; import { type AutoTableProps } from "../AutoTable.js"; import { validateAutoTableProps } from "../AutoTableValidators.js"; import { useHover } from "../hooks/useHover.js"; diff --git a/packages/react/src/auto/shadcn/ShadcnDefaultPreventedButton.tsx b/packages/react/src/auto/shadcn/ShadcnDefaultPreventedButton.tsx index c78fa7868..58efcf4b9 100644 --- a/packages/react/src/auto/shadcn/ShadcnDefaultPreventedButton.tsx +++ b/packages/react/src/auto/shadcn/ShadcnDefaultPreventedButton.tsx @@ -1,7 +1,7 @@ import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; import type { ComponentProps } from "react"; import React from "react"; -import type { OptionsType } from "../../utils.js"; import type { AutoFormProps } from "../AutoForm.js"; import type { ButtonProps, ShadcnElements } from "./elements.js"; diff --git a/packages/react/src/auto/shadcn/index.ts b/packages/react/src/auto/shadcn/index.ts index 0e2e5bc3e..ed9df9981 100644 --- a/packages/react/src/auto/shadcn/index.ts +++ b/packages/react/src/auto/shadcn/index.ts @@ -1,4 +1,4 @@ -export type { OptionsType } from "../../utils.js"; +export type { OptionsType } from "@gadgetinc/client-hooks"; export { type AutoInputComponent } from "../AutoInput.js"; export type { AutoButtonProps } from "../hooks/useAutoButtonController.js"; export type { diff --git a/packages/react/src/auto/shadcn/table/ShadcnAutoTableBulkActionSelector.tsx b/packages/react/src/auto/shadcn/table/ShadcnAutoTableBulkActionSelector.tsx index 1824f18e8..731be1b31 100644 --- a/packages/react/src/auto/shadcn/table/ShadcnAutoTableBulkActionSelector.tsx +++ b/packages/react/src/auto/shadcn/table/ShadcnAutoTableBulkActionSelector.tsx @@ -1,4 +1,4 @@ -import { type GadgetRecord } from "@gadgetinc/api-client-core"; +import type { GadgetRecord } from "@gadgetinc/api-client-core"; import { ChevronsUpDown } from "lucide-react"; import React, { useMemo } from "react"; import { type TableRow } from "../../../use-table/types.js"; diff --git a/packages/react/src/hooks.ts b/packages/react/src/hooks.ts new file mode 100644 index 000000000..1b34b0d69 --- /dev/null +++ b/packages/react/src/hooks.ts @@ -0,0 +1,19 @@ +export { + useAction, + useApi, + useBulkAction, + useConnection, + useEnqueue, + useFetch, + useFindBy, + useFindFirst, + useFindMany, + useFindOne, + useGet, + useGlobalAction, + useMaybeFindFirst, + useMaybeFindOne, + useMutation, + useQuery, + useView, +} from "@gadgetinc/client-hooks"; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index c35067044..4fc7db00c 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,5 +1,5 @@ export { Consumer, Context } from "urql"; -export { Provider, useApi, useConnection } from "./GadgetProvider.js"; +export { Provider } from "./GadgetProvider.js"; export * from "./auth/SignedIn.js"; export * from "./auth/SignedInOrRedirect.js"; export * from "./auth/SignedOut.js"; @@ -30,21 +30,6 @@ export type { AutoTextInputProps, } from "./auto/shared/AutoInputTypes.js"; export type { AutoRichTextInputProps } from "./auto/shared/AutoRichTextInputProps.js"; -export * from "./useAction.js"; -export * from "./useActionForm.js"; -export * from "./useBulkAction.js"; -export * from "./useEnqueue.js"; -export * from "./useFetch.js"; -export * from "./useFindBy.js"; -export * from "./useFindFirst.js"; -export * from "./useFindMany.js"; -export * from "./useFindOne.js"; -export { useGadgetMutation as useMutation } from "./useGadgetMutation.js"; -export { useGadgetQuery as useQuery } from "./useGadgetQuery.js"; -export * from "./useGet.js"; -export * from "./useGlobalAction.js"; +export * from "./hooks.js"; export * from "./useList.js"; -export * from "./useMaybeFindFirst.js"; -export * from "./useMaybeFindOne.js"; export * from "./useTable.js"; -export * from "./useView.js"; diff --git a/packages/react/src/metadata.tsx b/packages/react/src/metadata.tsx index 0f9788a4e..6ef40a903 100644 --- a/packages/react/src/metadata.tsx +++ b/packages/react/src/metadata.tsx @@ -1,12 +1,11 @@ import type { ActionFunction, AnyClient, GlobalActionFunction } from "@gadgetinc/api-client-core"; -import { assert } from "@gadgetinc/api-client-core"; +import { ErrorWrapper, assert } from "@gadgetinc/utils"; import type { ResultOf } from "@graphql-typed-document-node/core"; import type { DocumentNode } from "graphql"; -import { useApi } from "./GadgetProvider.js"; +import { useApi, useQuery as useGadgetQuery } from "./hooks.js"; import { graphql } from "./internal/gql/gql.js"; import { GadgetFieldType, type FieldMetadataFragment as FieldMetadataFragmentType } from "./internal/gql/graphql.js"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { ErrorWrapper, getModelManager, groupPaths } from "./utils.js"; +import { getModelManager, groupPaths } from "./utils.js"; /** * The enum of all possible field types in Gadget's type system diff --git a/packages/react/src/use-action-form/types.ts b/packages/react/src/use-action-form/types.ts index 0edc8b741..4d2fbc6c4 100644 --- a/packages/react/src/use-action-form/types.ts +++ b/packages/react/src/use-action-form/types.ts @@ -8,10 +8,10 @@ import type { GlobalActionFunction, Select, } from "@gadgetinc/api-client-core"; +import type { ActionHookState, OptionsType } from "@gadgetinc/client-hooks"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import type { FieldValues, UseFormReturn } from "react-hook-form"; -import type { useAction } from "../useAction.js"; -import type { useGlobalAction } from "../useGlobalAction.js"; -import type { ActionHookState, ErrorWrapper, OptionsType } from "../utils.js"; +import type { useAction, useGlobalAction } from "../hooks.js"; import { type useFindExistingRecord } from "./utils.js"; /** diff --git a/packages/react/src/use-action-form/utils.ts b/packages/react/src/use-action-form/utils.ts index 4c6b35f30..82bb0a126 100644 --- a/packages/react/src/use-action-form/utils.ts +++ b/packages/react/src/use-action-form/utils.ts @@ -1,8 +1,8 @@ import type { AnyClient, AnyModelManager, GadgetRecord } from "@gadgetinc/api-client-core"; -import { $modelRelationships, camelize, isEqual } from "@gadgetinc/api-client-core"; -import { useFindBy } from "../useFindBy.js"; -import { useFindOne } from "../useFindOne.js"; -import { get, omit, set, unset, type ErrorWrapper } from "../utils.js"; +import type { ErrorWrapper } from "@gadgetinc/utils"; +import { camelize, isEqual } from "@gadgetinc/utils"; +import { useFindBy, useFindOne } from "../hooks.js"; +import { get, omit, set, unset } from "../utils.js"; const noopUseFindExistingRecordResponse = [ { fetching: false }, @@ -251,7 +251,7 @@ const storedFileToGraphqlApiInput = (input: any) => { }; export const reshapeDataForGraphqlApi = async (client: AnyClient, defaultValues: any, data: any) => { - const referencedTypes = client[$modelRelationships]; + const referencedTypes = (client as any)[Symbol.for("gadget/modelRelationships")]; if (!referencedTypes) { throw new Error("No Gadget model metadata found -- please ensure you are using the latest version of the API client for your app"); diff --git a/packages/react/src/use-table/types.ts b/packages/react/src/use-table/types.ts index 8f6e9603f..cf7bc527c 100644 --- a/packages/react/src/use-table/types.ts +++ b/packages/react/src/use-table/types.ts @@ -1,4 +1,5 @@ import type { GadgetRecord, SortOrder } from "@gadgetinc/api-client-core"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import type { OperationContext } from "@urql/core"; import type { ReactNode } from "react"; import type { GadgetFieldType } from "../internal/gql/graphql.js"; @@ -6,7 +7,7 @@ import type { FieldMetadata, ModelMetadata } from "../metadata.js"; import type { SearchResult } from "../useDebouncedSearch.js"; import type { PaginationResult } from "../useList.js"; import type { RecordSelection } from "../useSelectedRecordsController.js"; -import type { ColumnValueType, ErrorWrapper } from "../utils.js"; +import type { ColumnValueType } from "../utils.js"; export type ColumnType = GadgetFieldType | "CustomRenderer"; diff --git a/packages/react/src/useAction.ts b/packages/react/src/useAction.ts deleted file mode 100644 index 3f37c84cb..000000000 --- a/packages/react/src/useAction.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { - ActionFunction, - DefaultSelection, - GadgetRecord, - LimitToKnownKeys, - Select, - StubbedActionFunction, -} from "@gadgetinc/api-client-core"; -import { - actionOperation, - capitalizeIdentifier, - disambiguateActionVariables, - get, - namespaceDataPath, - processActionResponse, -} from "@gadgetinc/api-client-core"; -import { useCallback, useContext, useEffect, useMemo } from "react"; -import type { AnyVariables, OperationContext, UseMutationState } from "urql"; -import { GadgetUrqlClientContext } from "./GadgetProvider.js"; -import { useGadgetMutation } from "./useGadgetMutation.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { ActionHookResult, ActionHookState, OptionsType } from "./utils.js"; -import { ErrorWrapper, noProviderErrorMessage } from "./utils.js"; - -/** - * React hook to run a Gadget model action. `useAction` must be passed an action function from an instance of your generated API client library, like `api.user.create` or `api.blogPost.publish`. `useAction` doesn't actually run the action when invoked, but instead returns an action function as the second result for running the action in response to an event. - * - * @param action an action function from a model manager in your application's client, like `api.user.create` - * @param options action options, like selecting the fields in the result - * - * @example - * export function CreateUserButton(props: { name: string; email: string }) { - * const [{error, fetching, data}, createUser] = useAction(api.user.create, { - * select: { - * id: true, - * }, - * }); - * - * const onClick = () => createUser({ - * name: props.name, - * email: props.email, - * }); - * - * return ( - * <> - * {error && <>Failed to create user: {error.toString()}} - * {fetching && <>Creating user...} - * {data && <>Created user with id={data.id}} - * - * - * ); - * } - */ -export const useAction = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends ActionFunction, - Options extends F["optionsType"] ->( - action: F, - options?: LimitToKnownKeys -): ActionHookResult< - F["hasReturnType"] extends true - ? any - : GadgetRecord< - Select, DefaultSelection> - >, - Exclude -> => { - if (!useContext(GadgetUrqlClientContext)) throw new Error(noProviderErrorMessage); - - useEffect(() => { - if (action.type === ("stubbedAction" as string)) { - const stubbedAction = action as unknown as StubbedActionFunction; - if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction) || !("actionApiIdentifier" in stubbedAction)) { - // Don't dispatch an event if the generated client has not yet been updated with the updated parameters - return; - } - - const event = new CustomEvent("gadget:devharness:stubbedActionError", { - detail: { - reason: stubbedAction.reason, - action: { - functionName: stubbedAction.functionName, - actionApiIdentifier: stubbedAction.actionApiIdentifier, - modelApiIdentifier: stubbedAction.modelApiIdentifier, - dataPath: stubbedAction.dataPath, - }, - }, - }); - globalThis.dispatchEvent(event); - } - }, []); - - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return actionOperation( - action.operationName, - action.defaultSelection, - action.modelApiIdentifier, - action.modelSelectionField, - action.variables, - memoizedOptions, - action.namespace, - action.isBulk, - action.hasReturnType - ); - }, [action, memoizedOptions]); - - const [result, runMutation] = useGadgetMutation< - GadgetRecord, DefaultSelection>>, - F["variablesType"] - >(plan.query); - - const transformedResult = useMemo(() => processResult(result, action), [result, action]); - - return [ - transformedResult, - useCallback( - async (input: F["variablesType"], context?: Partial) => { - const variables = disambiguateActionVariables(action, input); - - const result = await runMutation(variables, { - ...context, - // Adding the model's additional typename ensures document cache will properly refresh, regardless of whether __typename was selected (and sometimes we can't even select it, like delete actions!) - additionalTypenames: [...(context?.additionalTypenames ?? []), capitalizeIdentifier(action.modelApiIdentifier)], - }); - - return processResult({ fetching: false, ...result }, action); - }, - [action, runMutation] - ), - ]; -}; - -/** Processes urql's result object into the fancier Gadget result object */ -const processResult = ( - result: UseMutationState, - action: ActionFunction -): ActionHookState => { - let error = ErrorWrapper.forMaybeCombinedError(result.error); - let data = null; - if (result.data) { - const dataPath = namespaceDataPath([action.operationName], action.namespace); - const mutationData = get(result.data, dataPath); - if (mutationData) { - const errors = mutationData["errors"]; - if (errors && errors[0]) { - error = ErrorWrapper.forErrorsResponse(errors, error?.response); - } else { - data = processActionResponse(action.defaultSelection, result, mutationData, action.modelSelectionField, action.hasReturnType); - } - } - } - - return { ...result, error, data }; -}; diff --git a/packages/react/src/useActionForm.ts b/packages/react/src/useActionForm.ts index de9054444..faf59c437 100644 --- a/packages/react/src/useActionForm.ts +++ b/packages/react/src/useActionForm.ts @@ -1,8 +1,10 @@ -import { disambiguateActionVariables, type ActionFunction, type GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { ActionFunction, GlobalActionFunction } from "@gadgetinc/api-client-core"; +import type { OptionsType } from "@gadgetinc/client-hooks"; +import { disambiguateActionVariables, type ErrorWrapper } from "@gadgetinc/utils"; import { useCallback, useEffect, useRef } from "react"; import type { DeepPartial, FieldErrors, FieldValues, UseFormProps } from "react-hook-form"; import { useForm } from "react-hook-form"; -import { useApi } from "./GadgetProvider.js"; +import { useAction, useApi, useGlobalAction } from "./hooks.js"; import type { AnyActionWithId, ContextAwareSelect, @@ -23,9 +25,6 @@ import { transformContextAwareToSelect, useFindExistingRecord, } from "./use-action-form/utils.js"; -import { useAction } from "./useAction.js"; -import { useGlobalAction } from "./useGlobalAction.js"; -import type { ErrorWrapper, OptionsType } from "./utils.js"; import { get, getModelManager, set } from "./utils.js"; export * from "react-hook-form"; diff --git a/packages/react/src/useBulkAction.ts b/packages/react/src/useBulkAction.ts deleted file mode 100644 index ca9ce38e9..000000000 --- a/packages/react/src/useBulkAction.ts +++ /dev/null @@ -1,156 +0,0 @@ -import type { - BulkActionFunction, - DefaultSelection, - GadgetRecord, - LimitToKnownKeys, - Select, - StubbedActionFunction, -} from "@gadgetinc/api-client-core"; -import { - actionOperation, - capitalizeIdentifier, - disambiguateBulkActionVariables, - get, - hydrateRecordArray, - namespaceDataPath, -} from "@gadgetinc/api-client-core"; -import { useCallback, useEffect, useMemo } from "react"; -import type { OperationContext, UseMutationState } from "urql"; -import { useGadgetMutation } from "./useGadgetMutation.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { ActionHookResult, OptionsType } from "./utils.js"; -import { ErrorWrapper } from "./utils.js"; - -/** - * React hook to run a Gadget model bulk action. - * - * @param action any bulk action function from a Gadget manager - * @param options action options, like selecting the fields in the result - * - * @example - * ``` - * export function BulkFinish(props: { ids: string[]; }) { - * const [result, bulkFinish] = useBulkAction(Client.todo.bulkFinish, { - * select: { - * id: true, - * }, - * }); - * - * const onClick = () => ; - * - * return ( - * <> - * {result.error && <>Failed to create user: {result.error.toString()}} - * {result.fetching && <>Creating user...} - * {result.data && <>Finished TODOs with ids={props.ids}} - * - * - * ); - * } - */ -export const useBulkAction = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends BulkActionFunction, - Options extends F["optionsType"] ->( - action: F, - options?: LimitToKnownKeys -): ActionHookResult< - F["hasReturnType"] extends true - ? any[] - : GadgetRecord< - Select, DefaultSelection> - >[], - Exclude -> => { - useEffect(() => { - if (action.type === ("stubbedAction" as string)) { - const stubbedAction = action as unknown as StubbedActionFunction; - if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction) || !("actionApiIdentifier" in stubbedAction)) { - // Don't dispatch an event if the generated client has not yet been updated with the updated parameters - return; - } - - const event = new CustomEvent("gadget:devharness:stubbedActionError", { - detail: { - reason: stubbedAction.reason, - action: { - functionName: stubbedAction.functionName, - actionApiIdentifier: stubbedAction.actionApiIdentifier, - modelApiIdentifier: stubbedAction.modelApiIdentifier, - dataPath: stubbedAction.dataPath, - }, - }, - }); - globalThis.dispatchEvent(event); - } - }, []); - - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return actionOperation( - action.operationName, - action.defaultSelection, - action.modelApiIdentifier, - action.modelSelectionField, - action.variables, - memoizedOptions, - action.namespace, - action.isBulk, - action.hasReturnType - ); - }, [action, memoizedOptions]); - - const [result, runMutation] = useGadgetMutation< - GadgetRecord< - Select, DefaultSelection> - >[], - F["variablesType"] - >(plan.query); - - const transformedResult = useMemo(() => processResult(result, action), [result, action]); - - return [ - transformedResult, - useCallback( - async (inputs: F["variablesType"], context?: Partial) => { - const variables = disambiguateBulkActionVariables(action, inputs); - - const result = await runMutation(variables, { - ...context, - // Adding the model's additional typename ensures document cache will properly refresh, regardless of whether __typename was selected (and sometimes we can't even select it, like delete actions!) - additionalTypenames: [...(context?.additionalTypenames ?? []), capitalizeIdentifier(action.modelApiIdentifier)], - }); - return processResult({ fetching: false, ...result }, action); - }, - [action, runMutation] - ), - ]; -}; - -const processResult = (result: UseMutationState, action: BulkActionFunction) => { - let error = ErrorWrapper.forMaybeCombinedError(result.error); - let data = undefined; - - if (result.data && !error) { - const dataPath = namespaceDataPath([action.operationName], action.namespace); - const mutationData = get(result.data, dataPath); - - if (mutationData) { - const isDeleteAction = (action as any).isDeleter; - if (!isDeleteAction) { - const errors = mutationData["errors"]; - if (errors && errors[0]) { - error = ErrorWrapper.forErrorsResponse(errors, (error as any)?.response); - } else { - data = action.hasReturnType ? mutationData.results : hydrateRecordArray(result, mutationData[action.modelSelectionField]); - } - } else { - // Delete action - data = mutationData; - } - } - } - return { ...result, error, data }; -}; diff --git a/packages/react/src/useEnqueue.ts b/packages/react/src/useEnqueue.ts deleted file mode 100644 index 9c497130f..000000000 --- a/packages/react/src/useEnqueue.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { AnyActionFunction, EnqueueBackgroundActionOptions, GadgetConnection } from "@gadgetinc/api-client-core"; -import { - BackgroundActionHandle, - disambiguateActionVariables, - disambiguateBulkActionVariables, - enqueueActionOperation, - get, - graphqlizeBackgroundOptions, - namespaceDataPath, -} from "@gadgetinc/api-client-core"; -import { useCallback, useContext, useMemo } from "react"; -import type { UseMutationState } from "urql"; -import { GadgetUrqlClientContext, useConnection } from "./GadgetProvider.js"; -import { useGadgetMutation } from "./useGadgetMutation.js"; -import type { EnqueueHookResult, EnqueueHookState } from "./utils.js"; -import { ErrorWrapper, noProviderErrorMessage } from "./utils.js"; - -/** - * React hook to enqueue a Gadget action in the background. `useEnqueue` must be passed an action function from an instance of your generated API client library, like `useEnqueue(api.user.create)` or `useEnqueue(api.someGlobalAction)`. `useEnqueue` doesn't actually submit the background action when invoked, but instead returns a function for enqueuing the action in response to an event. - * - * @param action a model action or global action in your application's client, like `api.user.create` or `api.someGlobalAction` - * @param options action options, like selecting the fields in the result - * - * @example - * export function CreateUserButton(props: { name: string; email: string }) { - * const [{error, fetching, handle}, enqueue] = useEnqueue(api.user.create)); - * - * const onClick = () => enqueue( - * { - * name: props.name, - * email: props.email, - * }, { - * id: `send-email-action-${props.email}` - * } - * ); - * - * return ( - * <> - * {error && <>Failed to enqueue user create: {error.toString()}} - * {fetching && <>Enqueuing action...} - * {data && <>Enqueued action with background action id={handle.id}} - * - * - * ); - * } - */ -export const useEnqueue = ( - action: Action, - baseBackgroundOptions?: EnqueueBackgroundActionOptions -): EnqueueHookResult => { - if (!useContext(GadgetUrqlClientContext)) throw new Error(noProviderErrorMessage); - - const plan = useMemo( - () => enqueueActionOperation(action.operationName, action.variables, action.namespace, null, action.isBulk), - [action] - ); - const connection = useConnection(); - - const [rawState, runMutation] = useGadgetMutation(plan.query); - - const state: EnqueueHookState = useMemo(() => processResult(connection, rawState, action), [rawState, action]); - - return [ - state, - useCallback( - async (input: Action["variablesType"], options?: EnqueueBackgroundActionOptions) => { - const variables = action.isBulk ? disambiguateBulkActionVariables(action, input) : disambiguateActionVariables(action, input); - - const fullContext = { ...baseBackgroundOptions, ...options }; - variables.backgroundOptions = graphqlizeBackgroundOptions(fullContext); - - const rawState = await runMutation(variables, fullContext); - - return processResult(connection, { fetching: false, ...rawState }, action); - }, - [action, connection, runMutation] - ), - ]; -}; - -/** Processes urql's result object into the fancier Gadget result object */ -const processResult = ( - connection: GadgetConnection, - rawResult: UseMutationState, - action: Action -): EnqueueHookState => { - const { data, ...result } = rawResult; - let error = ErrorWrapper.forMaybeCombinedError(result.error); - let handle: BackgroundActionHandle | null = null; - let handles: BackgroundActionHandle[] | null = null; - const isBulk = action.isBulk; - - if (data) { - const dataPath = ["background", ...namespaceDataPath([action.operationName], action.namespace)]; - - const mutationData = get(data, dataPath); - if (mutationData) { - const errors = mutationData["errors"]; - if (errors && errors[0]) { - error = ErrorWrapper.forErrorsResponse(errors, error?.response); - } else { - if (isBulk) { - handles = mutationData.backgroundActions.map( - (result: { id: string }) => new BackgroundActionHandle(connection, action, result.id) - ); - } else { - handle = new BackgroundActionHandle(connection, action, mutationData.backgroundAction.id); - } - } - } - } - - if (isBulk) { - return { ...result, error, handles } as EnqueueHookState; - } else { - return { ...result, error, handle } as EnqueueHookState; - } -}; diff --git a/packages/react/src/useFetch.ts b/packages/react/src/useFetch.ts deleted file mode 100644 index 1e68683fe..000000000 --- a/packages/react/src/useFetch.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { useCallback, useEffect, useReducer, useRef } from "react"; -import { useConnection } from "./GadgetProvider.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import { ErrorWrapper } from "./utils.js"; - -export interface FetchHookState { - data?: T; - response?: Response; - error?: ErrorWrapper; - fetching: boolean; - streaming: boolean; - options: FetchHookOptions; -} - -export type FetchHookResult = [FetchHookState, (opts?: Partial) => Promise]; - -type FetchAction = - | { type: "fetching" } - | { type: "streaming" } - | { type: "fetched"; payload: T } - | { type: "streamed" } - | { type: "update"; payload: T } - | { type: "error"; payload: ErrorWrapper }; - -const reducer = (state: FetchHookState, action: FetchAction): FetchHookState => { - switch (action.type) { - case "fetching": - return { ...state, fetching: true, streaming: false, error: undefined }; - case "streaming": - return { ...state, streaming: true }; - case "streamed": - return { ...state, streaming: false }; - case "fetched": - return { ...state, fetching: false, data: action.payload, error: undefined }; - case "update": - return { ...state, data: action.payload }; - case "error": - return { ...state, fetching: false, error: action.payload }; - default: - return state; - } -}; - -export interface FetchHookOptions extends RequestInit { - stream?: boolean | string; - json?: boolean; - sendImmediately?: boolean; - onStreamComplete?: (value: string) => void; -} - -const startRequestByDefault = (options?: FetchHookOptions) => { - if (typeof options?.sendImmediately != "undefined") { - return options.sendImmediately; - } else { - return !options?.method || options.method === "GET"; - } -}; - -const dispatchError = ( - mounted: React.RefObject, - dispatch: React.Dispatch>, - abortController: AbortController, - error: any, - response?: Response -) => { - if (!mounted.current || abortController.signal.aborted) return null; - - const wrapped = ErrorWrapper.forClientSideError(error, response); - dispatch({ type: "error", payload: wrapped }); - - return wrapped; -}; - -/** - * React hook to make an HTTP request to a Gadget backend HTTP route. Preserves client side session information and ensures it's passed along to the backend. - * - * Returns a tuple with the current state of the request and a function to send or re-send the request. The state is an object with the following fields: - * - `data`: the response data, if the request was successful - * - `fetching`: a boolean describing if the fetch request is currently in progress - * - `streaming`: a boolean describing if the fetch request is currently streaming. This is only set when the option `{ stream: "string" }` is passed - * - `error`: an error object if the request failed in any way - * - * The second return value is a function for executing the fetch request. It returns a promise for the response body. - * - * By default, `GET` requests are sent as soon as the hook executes. Any other request methods are not sent automatically, and must be triggered by calling the `execute` function returned in the second argument. - * - * Pass the `{ json: true }` option to expect a JSON response from the server, and to automatically parse the response as JSON. Otherwise, the response will be returned as a `string` object. - * - * Pass the `{ stream: true }` to get a `ReadableStream` object as a response from the server, allowing you to work with the response as it arrives. - * - * Pass the `{ stream: "string" }` to decode the `ReadableStream` as a string and update data as it arrives. If the stream is in an encoding other than utf8 use i.e. `{ stream: "utf-16" }`. - * - * When `{ stream: "string" }` is used, the `streaming` field in the state will be set to `true` while the stream is active, and `false` when the stream is complete. You can use this to show a loading indicator while the stream is active. - * You can also pass an `onStreamComplete` callback that will be called with the value of the streamed string once it has completed. - * - * If you want to read model data, see the `useFindMany` function and similar. If you want to invoke a backend Action, use the `useAction` hook instead. - * - * @param path the backend path to fetch - * @param options the `fetch` options for the request - * - * @example - * ``` - * export function UserByEmail(props: { email: string }) { - * const [{data, fetching, error}, refresh] = useFetch("/users/get", { - * method: "GET", - * body: JSON.stringify({ email: props.email }}) - * headers: { - * "content-type": "application/json", - * } - * json: true, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No user found with id={props.id}; - * - * return
{result.data.name}
; - * } - */ -export function useFetch(path: string, options: { stream: string } & FetchHookOptions): FetchHookResult>; -export function useFetch(path: string, options: { stream: true } & FetchHookOptions): FetchHookResult>; -export function useFetch>(url: string, options: { json: true } & FetchHookOptions): FetchHookResult; -export function useFetch(path: string, options?: FetchHookOptions): FetchHookResult; -export function useFetch(path: string, options?: FetchHookOptions): FetchHookResult { - // Used to prevent state update if the component is unmounted - const mounted = useRef(true); - const { onStreamComplete, ...optionsToMemoize } = options ?? {}; - const memoizedOptions = useStructuralMemo(optionsToMemoize); - const connection = useConnection(); - const startRequestOnMount = startRequestByDefault(memoizedOptions); - const controller = useRef(null); - - const [state, dispatch] = useReducer, FetchHookOptions, [FetchAction]>( - reducer, - memoizedOptions, - (memoizedOptions) => { - return { fetching: startRequestOnMount, streaming: false, options: memoizedOptions }; - } - ); - - const send = useCallback( - async (sendOptions?: Partial): Promise => { - if (controller.current && !controller.current.signal.aborted) { - controller.current.abort("useFetch is starting a new request, aborting the previous one"); - } - - const abortContoller = new AbortController(); - controller.current = abortContoller; - - dispatch({ type: "fetching" }); - - let data: any; - let response: Response | undefined = undefined; - - const mergedOptions = { ...memoizedOptions, onStreamComplete, ...sendOptions }; - - // add implicit headers from options, being careful not to mutate any inputs - if (mergedOptions.json) { - mergedOptions.headers = { ...mergedOptions.headers }; - (mergedOptions.headers as any)["accept"] ??= "application/json"; - } - - try { - const { json: _json, stream: _stream, onStreamComplete: _onStreamComplete, ...fetchOptions } = mergedOptions; - // make the fetch call using GadgetConnection to pass along auth and other headers - response = await connection.fetch(path, { signal: abortContoller.signal, ...fetchOptions }); - if (!response.ok) { - throw new Error(response.statusText); - } - - let dispatchData = true; - - if (mergedOptions.json) { - data = await response.json(); - } else if (typeof mergedOptions.stream === "string") { - dispatchData = false; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const decodedStream = response.body!.pipeThrough( - new TextDecoderStream(mergedOptions.stream === "string" ? "utf8" : mergedOptions.stream) - ); - - const [responseStream, updateStream] = decodedStream.tee(); - - data = responseStream; - const decodedStreamReader = updateStream.getReader(); - - decodedStreamReader.closed.catch((error) => { - dispatchError(mounted, dispatch, abortContoller, error, response); - }); - - dispatch({ type: "fetched", payload: "" as any }); - - (async () => { - let responseText = ""; - let done = false; - - dispatch({ type: "streaming" }); - - while (!done) { - const { value, done: _done } = await decodedStreamReader.read(); - done = _done; - - if (value) { - responseText += value; - - if (!abortContoller.signal.aborted) { - dispatch({ type: "update", payload: responseText as any }); - } - } - } - - mergedOptions.onStreamComplete?.(responseText); - })() - .catch((error) => { - dispatchError(mounted, dispatch, abortContoller, error, response); - }) - .finally(() => { - dispatch({ type: "streamed" }); - }); - } else if (mergedOptions.stream) { - data = response.body; - } else { - data = await response.text(); - } - - if (!mounted.current || !dispatchData) return data; - - dispatch({ type: "fetched", payload: data }); - } catch (error: any) { - const wrapped = dispatchError(mounted, dispatch, abortContoller, error, response); - if (!wrapped) return null as any; - throw wrapped; - } - return data; - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [connection, memoizedOptions, path] - ); - - // track if we're mounted or not - useEffect(() => { - mounted.current = true; - - return () => { - mounted.current = false; - }; - }, []); - - // execute the initial request on mount if needed - useEffect(() => { - if (startRequestOnMount) { - void send().catch(() => { - // error will be reported via the return value of the hook - }); - } - - // abort if the component is unmounted, or if one of the key elements of the request changes such that we don't want an outstanding request's result anymore - return () => { - controller.current?.abort(); - }; - }, [path, startRequestOnMount, send]); - - return [state, send]; -} diff --git a/packages/react/src/useFindBy.ts b/packages/react/src/useFindBy.ts deleted file mode 100644 index 2122ce8c4..000000000 --- a/packages/react/src/useFindBy.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { DefaultSelection, FindOneFunction, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { - GadgetNotFoundError, - findOneByFieldOperation, - get, - getNonUniqueDataError, - hydrateConnection, - namespaceDataPath, -} from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch a Gadget record using the `findByXYZ` method of a given model manager. Useful for finding records by key fields which are used for looking up records by. Gadget autogenerates the `findByXYZ` methods on your model managers, and `useFindBy` can only be used with models that have these generated finder functions. - * - * @param finder `findByXYZ` function from a Gadget manager that will be used - * @param value field value of the record to fetch - * @param options options for selecting the fields in the result - * - * @example - * ``` - * export function UserByEmail(props: { email: string }) { - * const [result, refresh] = useFindBy(api.user.findByEmail, props.email, { - * select: { - * name: true, - * }, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No user found with id={props.id}; - * - * return
{result.data.name}
; - * } - */ -export const useFindBy = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends FindOneFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - finder: F, - value: string, - options?: LimitToKnownKeys -): ReadHookResult< - GadgetRecord, DefaultSelection>> -> => { - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return findOneByFieldOperation( - finder.operationName, - finder.findByVariableName, - value, - finder.defaultSelection, - finder.modelApiIdentifier, - memoizedOptions, - finder.namespace - ); - }, [finder, value, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, options)); - - const result = useMemo(() => { - const dataPath = namespaceDataPath([finder.operationName], finder.namespace); - - let data = rawResult.data; - let records = []; - if (data) { - const connection = get(rawResult.data, dataPath); - if (connection) { - records = hydrateConnection(rawResult, connection); - data = records[0]; - } - } - - let error = ErrorWrapper.forMaybeCombinedError(rawResult.error); - if (!error) { - if (records.length > 1) { - error = ErrorWrapper.forClientSideError(getNonUniqueDataError(finder.modelApiIdentifier, finder.findByVariableName, value)); - } else if (rawResult.data && !records[0]) { - error = ErrorWrapper.forClientSideError( - new GadgetNotFoundError(`${finder.modelApiIdentifier} record with ${finder.findByVariableName}=${value} not found`) - ); - } - } - - return { ...rawResult, data, error }; - }, [rawResult, finder, value]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useFindFirst.ts b/packages/react/src/useFindFirst.ts deleted file mode 100644 index cfae2d3f7..000000000 --- a/packages/react/src/useFindFirst.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { DefaultSelection, FindFirstFunction, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { findManyOperation, get, hydrateConnection, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch the first backend record matching a given filter and sort. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the first record found if there is one, and null otherwise. - * - * @param manager Gadget model manager to use - * @param options options for filtering and searching records, and selecting the fields in each record of the result - * - * @example - * - * ``` - * export function Users() { - * const [result, refresh] = useFindFirst(api.user, { - * select: { - * name: true, - * }, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No user found; - * - * return
{result.data.name}
; - * } - * ``` - */ -export const useFindFirst = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends FindFirstFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - manager: { findFirst: F }, - options?: LimitToKnownKeys -): ReadHookResult< - GadgetRecord, DefaultSelection>> -> => { - const firstOptions = { ...options, first: 1 }; - const memoizedOptions = useStructuralMemo(firstOptions); - const plan = useMemo(() => { - return findManyOperation( - manager.findFirst.operationName, - manager.findFirst.defaultSelection, - manager.findFirst.modelApiIdentifier, - memoizedOptions, - manager.findFirst.namespace - ); - }, [manager, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, firstOptions)); - - const result = useMemo(() => { - const dataPath = namespaceDataPath([manager.findFirst.operationName], manager.findFirst.namespace); - let data = rawResult.data; - if (data) { - const connection = get(rawResult.data, dataPath); - if (connection) { - data = hydrateConnection(rawResult, connection)[0]; - } else { - data = data[0]; - } - } - - const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); - - return { ...rawResult, data, error }; - }, [manager.findFirst.operationName, options?.pause, rawResult]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useFindMany.ts b/packages/react/src/useFindMany.ts deleted file mode 100644 index e71ebddb3..000000000 --- a/packages/react/src/useFindMany.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { AnyModelManager, DefaultSelection, FindManyFunction, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { GadgetRecordList, findManyOperation, get, hydrateConnection, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch a page of Gadget records from the backend, optionally sorted, filtered, searched, and selected from. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be a `GadgetRecordList` object holding the list of returned records and pagination info. - * - * @param manager Gadget model manager to use - * @param options options for filtering and searching records, and selecting the fields in each record of the result - * - * @example - * - * ``` - * export function Users() { - * const [result, refresh] = useFindMany(api.user, { - * select: { - * name: true, - * }, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No users found; - * - * return <>{result.data.map((user) =>
{user.name}
)}; - * } - * ``` - */ -export const useFindMany = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends FindManyFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - manager: { findMany: F }, - options?: LimitToKnownKeys -): ReadHookResult< - GadgetRecordList, DefaultSelection>> -> => { - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return findManyOperation( - manager.findMany.operationName, - manager.findMany.defaultSelection, - manager.findMany.modelApiIdentifier, - memoizedOptions, - manager.findMany.namespace - ); - }, [manager, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, options)); - - const result = useMemo(() => { - const dataPath = namespaceDataPath([manager.findMany.operationName], manager.findMany.namespace); - let data = rawResult.data; - if (data) { - const connection = get(rawResult.data, dataPath); - if (connection) { - const records = hydrateConnection(rawResult, connection); - data = GadgetRecordList.boot(manager as unknown as AnyModelManager, records, connection); - } - } - - const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); - - return { ...rawResult, data, error }; - }, [manager, options?.pause, rawResult]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useFindOne.ts b/packages/react/src/useFindOne.ts deleted file mode 100644 index acd65b4d0..000000000 --- a/packages/react/src/useFindOne.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { DefaultSelection, FindOneFunction, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { findOneOperation, get, hydrateRecord, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch one Gadget record by `id` from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the record if it was found, and `null` otherwise. - * - * @param manager Gadget model manager to use - * @param id id of the record to fetch - * @param options options for selecting the fields in the result - * - * @example - * ``` - * export function User(props: { id: string }) { - * const [result, refresh] = useFindOne(api.user, props.id, { - * select: { - * name: true, - * }, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No user found with id={props.id}; - * - * return
{result.data.name}
; - * } - * ``` - */ -export const useFindOne = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends FindOneFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - manager: { findOne: F }, - id: string, - options?: LimitToKnownKeys -): ReadHookResult< - GadgetRecord, DefaultSelection>> -> => { - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return findOneOperation( - manager.findOne.operationName, - id, - manager.findOne.defaultSelection, - manager.findOne.modelApiIdentifier, - memoizedOptions, - manager.findOne.namespace - ); - }, [manager, id, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, options)); - - const result = useMemo(() => { - const dataPath = namespaceDataPath([manager.findOne.operationName], manager.findOne.namespace); - - let data = rawResult.data && get(rawResult.data, dataPath); - if (data) { - data = hydrateRecord(rawResult, data); - } - const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); - - return { ...rawResult, data, error }; - }, [manager.findOne.operationName, rawResult, options?.pause]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useGadgetMutation.ts b/packages/react/src/useGadgetMutation.ts deleted file mode 100644 index 9ac00374e..000000000 --- a/packages/react/src/useGadgetMutation.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useContext } from "react"; -import { useMutation } from "urql"; -import { GadgetUrqlClientContext } from "./GadgetProvider.js"; -import { noProviderErrorMessage } from "./utils.js"; - -export const useGadgetMutation: typeof useMutation = (query) => { - if (!useContext(GadgetUrqlClientContext)) throw new Error(noProviderErrorMessage); - return useMutation(query); -}; diff --git a/packages/react/src/useGadgetQuery.ts b/packages/react/src/useGadgetQuery.ts deleted file mode 100644 index ca010a651..000000000 --- a/packages/react/src/useGadgetQuery.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useContext } from "react"; -import type { AnyVariables, UseQueryArgs, UseQueryResponse } from "urql"; -import { useQuery } from "urql"; -import { GadgetUrqlClientContext } from "./GadgetProvider.js"; -import { noProviderErrorMessage, useMemoizedQueryOptions } from "./utils.js"; - -export type UseGadgetQueryArgs = UseQueryArgs & { - /** - * Marks this query as one that should suspend the react component rendering while executing, instead of returning `{fetching: true}` to the caller. - * Useful if you want to allow components higher in the tree to show spinners instead of having every component manage its own loading state. - */ - suspense?: boolean; -}; - -export const useGadgetQuery = ( - args: UseGadgetQueryArgs -): UseQueryResponse => { - if (!useContext(GadgetUrqlClientContext)) throw new Error(noProviderErrorMessage); - const options = useMemoizedQueryOptions(args); - return useQuery(options); -}; diff --git a/packages/react/src/useGet.ts b/packages/react/src/useGet.ts deleted file mode 100644 index 104934419..000000000 --- a/packages/react/src/useGet.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { DefaultSelection, GadgetRecord, GetFunction, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { findOneOperation, get, hydrateRecord, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook that fetches a singleton record for an `api.currentSomething` style model manager. `useGet` fetches one global record, which is most often the current session. `useGet` doesn't require knowing the record's ID in order to fetch it, and instead returns the one current record for the current context. - * - * @param manager Gadget model manager to use, like `api.currentSomething` - * @param options options for selecting the fields in the result - * - * @example - * ``` - * export function CurrentSession() { - * const [{error, data, fetching}, refresh] = useGet(api.currentSession, { - * select: { - * id: true, - * userId: true, - * }, - * }); - * - * if (error) return <>Error: {error.toString()}; - * if (fetching && !data) return <>Fetching...; - * if (!data) return <>No current session found; - * - * return
Current session ID={data.id} and userId={data.userId}
; - * } - * ``` - */ -export const useGet = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends GetFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - manager: { get: F }, - options?: LimitToKnownKeys -): ReadHookResult< - GadgetRecord, DefaultSelection>> -> => { - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return findOneOperation( - manager.get.operationName, - undefined, - manager.get.defaultSelection, - manager.get.modelApiIdentifier, - memoizedOptions - ); - }, [manager, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, options)); - const dataPath = namespaceDataPath([manager.get.operationName], manager.get.namespace); - - const result = useMemo(() => { - let data = null; - const rawRecord = rawResult.data && get(rawResult.data, dataPath); - if (rawRecord) { - data = hydrateRecord(rawResult, rawRecord); - } - const error = ErrorWrapper.forMaybeCombinedError(rawResult.error); - - return { - ...rawResult, - error, - data, - }; - }, [rawResult, manager]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useGlobalAction.ts b/packages/react/src/useGlobalAction.ts deleted file mode 100644 index 8794021fb..000000000 --- a/packages/react/src/useGlobalAction.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { GlobalActionFunction, StubbedActionFunction } from "@gadgetinc/api-client-core"; -import { get, globalActionOperation, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useCallback, useEffect, useMemo } from "react"; -import type { OperationContext, UseMutationState } from "urql"; -import { useGadgetMutation } from "./useGadgetMutation.js"; -import type { ActionHookResultWithOptionalCallbackVariables } from "./utils.js"; -import { ErrorWrapper } from "./utils.js"; - -/** - * React hook to run a Gadget model action. - * - * @param action any action function from a Gadget manager - * - * @example - * ``` - * export function FlipAllWidgets(props: { name: string; email: string }) { - * const [result, flipAllWidgets] = useGlobalAction(Client.flipAllWidgets); - * - * return ( - * <> - * {result.error && <>Failed to flip all widgets: {result.error.toString()}} - * {result.fetching && <>Flipping all widgets...} - * {result.data && <>Flipped all widgets} - * - * - * ); - * } - */ -export const useGlobalAction = >( - action: F -): ActionHookResultWithOptionalCallbackVariables> => { - useEffect(() => { - if (action.type === ("stubbedAction" as string)) { - const stubbedAction = action as unknown as StubbedActionFunction; - if (!("reason" in stubbedAction) || !("dataPath" in stubbedAction)) { - // Don't dispatch an event if the generated client has not yet been updated with the updated parameters - return; - } - - const event = new CustomEvent("gadget:devharness:stubbedActionError", { - detail: { - reason: stubbedAction.reason, - action: { - functionName: stubbedAction.functionName, - actionApiIdentifier: stubbedAction.actionApiIdentifier, - dataPath: stubbedAction.dataPath, - }, - }, - }); - globalThis.dispatchEvent(event); - } - }, []); - - const plan = useMemo(() => { - return globalActionOperation(action.operationName, action.variables, action.namespace); - }, [action]); - - const [result, runMutation] = useGadgetMutation(plan.query); - - const transformedResult = useMemo(() => processResult(result, action), [result, action]); - - return [ - transformedResult, - useCallback( - async (variables?: F["variablesType"], context?: Partial) => { - const result = await runMutation(variables ?? {}, context); - return processResult({ fetching: false, ...result }, action); - }, - [action, runMutation] - ), - ]; -}; - -const processResult = (result: UseMutationState, action: GlobalActionFunction) => { - let error = ErrorWrapper.forMaybeCombinedError(result.error); - let data = undefined; - if (result.data) { - const dataPath = namespaceDataPath([action.operationName], action.namespace); - data = get(result.data, dataPath); - if (data) { - const errors = data.errors; - data = data.result; - if (errors && errors[0]) { - error = ErrorWrapper.forErrorsResponse(errors); - } - } - } - return { ...result, error, data }; -}; diff --git a/packages/react/src/useHasManyThroughForm.ts b/packages/react/src/useHasManyThroughForm.ts index ee43ee942..35ed9aa7b 100644 --- a/packages/react/src/useHasManyThroughForm.ts +++ b/packages/react/src/useHasManyThroughForm.ts @@ -1,4 +1,4 @@ -import { assert } from "@gadgetinc/api-client-core"; +import { assert } from "@gadgetinc/utils"; import { useEffect, useMemo } from "react"; import { useAutoRelationship, useRelationshipContext } from "./auto/hooks/useAutoRelationship.js"; import { useHasManyThroughController } from "./auto/hooks/useHasManyThroughController.js"; diff --git a/packages/react/src/useList.ts b/packages/react/src/useList.ts index 49958bae7..a57044994 100644 --- a/packages/react/src/useList.ts +++ b/packages/react/src/useList.ts @@ -1,12 +1,13 @@ import type { DefaultSelection, FindManyFunction, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; +import type { OptionsType, ReadOperationOptions } from "@gadgetinc/client-hooks"; +import type { ErrorWrapper } from "@gadgetinc/utils"; import type { OperationContext } from "@urql/core"; import { useCallback, useMemo, useState } from "react"; +import { useFindMany } from "./hooks.js"; import type { SearchResult } from "./useDebouncedSearch.js"; import { useDebouncedSearch } from "./useDebouncedSearch.js"; -import { useFindMany } from "./useFindMany.js"; import type { RecordSelection } from "./useSelectedRecordsController.js"; import { useSelectedRecordsController } from "./useSelectedRecordsController.js"; -import type { ErrorWrapper, OptionsType, ReadOperationOptions } from "./utils.js"; import { omit } from "./utils.js"; export interface PaginationResult { diff --git a/packages/react/src/useMaybeFindFirst.ts b/packages/react/src/useMaybeFindFirst.ts deleted file mode 100644 index e2702519b..000000000 --- a/packages/react/src/useMaybeFindFirst.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { DefaultSelection, FindFirstFunction, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { findManyOperation, get, hydrateConnection, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch many Gadget records using the `maybeFindFirst` method of a given manager. - * - * @param manager Gadget model manager to use - * @param options options for filtering and searching records, and selecting the fields in each record of the result - * - * @example - * - * ``` - * export function Users() { - * const [result, refresh] = useMaybeFindFirst(Client.user, { - * select: { - * name: true, - * }, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No user found; - * - * return
{result.data.name}
; - * } - * ``` - */ -export const useMaybeFindFirst = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends FindFirstFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - manager: { findFirst: F }, - options?: LimitToKnownKeys -): ReadHookResult, DefaultSelection> ->> => { - const firstOptions = { ...options, first: 1 }; - const memoizedOptions = useStructuralMemo(firstOptions); - const plan = useMemo(() => { - return findManyOperation( - manager.findFirst.operationName, - manager.findFirst.defaultSelection, - manager.findFirst.modelApiIdentifier, - memoizedOptions, - manager.findFirst.namespace - ); - }, [manager, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, firstOptions)); - - const result = useMemo(() => { - const dataPath = namespaceDataPath([manager.findFirst.operationName], manager.findFirst.namespace); - let data = rawResult.data ?? null; - if (data) { - const connection = get(rawResult.data, dataPath); - if (connection) { - data = hydrateConnection(rawResult, connection)[0] ?? null; - } else { - data = data[0] ?? null; - } - } - - const error = ErrorWrapper.forMaybeCombinedError(rawResult.error); - - return { - ...rawResult, - error, - data, - }; - }, [rawResult, manager]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useMaybeFindOne.ts b/packages/react/src/useMaybeFindOne.ts deleted file mode 100644 index cf9477db8..000000000 --- a/packages/react/src/useMaybeFindOne.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { DefaultSelection, FindOneFunction, GadgetRecord, LimitToKnownKeys, Select } from "@gadgetinc/api-client-core"; -import { findOneOperation, get, hydrateRecord, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { OptionsType, ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch a Gadget record using the `maybeFindOne` method of a given manager. - * - * @param manager Gadget model manager to use - * @param id id of the record to fetch - * @param options options for selecting the fields in the result - * - * @example - * ``` - * export function User(props: { id: string }) { - * const [result, refresh] = useMaybeFindOne(Client.user, props.id, { - * select: { - * name: true, - * }, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No user found with id={props.id}; - * - * return
{result.data.name}
; - * } - * ``` - */ -export const useMaybeFindOne = < - GivenOptions extends OptionsType, // currently necessary for Options to be a narrow type (e.g., `true` instead of `boolean`) - SchemaT, - F extends FindOneFunction, - Options extends F["optionsType"] & ReadOperationOptions ->( - manager: { findOne: F }, - id: string, - options?: LimitToKnownKeys -): ReadHookResult, DefaultSelection> ->> => { - const memoizedOptions = useStructuralMemo(options); - const plan = useMemo(() => { - return findOneOperation( - manager.findOne.operationName, - id, - manager.findOne.defaultSelection, - manager.findOne.modelApiIdentifier, - memoizedOptions, - manager.findOne.namespace - ); - }, [manager, id, memoizedOptions]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, options)); - - const result = useMemo(() => { - let data = rawResult.data ?? null; - if (data) { - const dataPath = namespaceDataPath([manager.findOne.operationName], manager.findOne.namespace); - const value = get(rawResult.data, dataPath); - data = value && "id" in value ? hydrateRecord(rawResult, value) : null; - } - const error = ErrorWrapper.forMaybeCombinedError(rawResult.error); - return { - ...rawResult, - error, - data, - }; - }, [rawResult, manager]); - - return [result, refresh]; -}; diff --git a/packages/react/src/useStructuralMemo.ts b/packages/react/src/useStructuralMemo.ts deleted file mode 100644 index eb17ed0cf..000000000 --- a/packages/react/src/useStructuralMemo.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useRef } from "react"; -import deepEqual from "react-fast-compare"; - -/** - * Memoize and ensure a stable identity on a given value as long as it remains the same, structurally. - */ -export const useStructuralMemo = (value: T): T => { - const ref = useRef(value); - - if (!deepEqual(value, ref.current)) { - ref.current = value; - } - - return ref.current; -}; diff --git a/packages/react/src/useTable.tsx b/packages/react/src/useTable.tsx index 6601d1224..7f8881bc7 100644 --- a/packages/react/src/useTable.tsx +++ b/packages/react/src/useTable.tsx @@ -1,18 +1,11 @@ -import type { SortOrder } from "@gadgetinc/api-client-core"; -import { - type DefaultSelection, - type FindManyFunction, - type GadgetRecord, - type LimitToKnownKeys, - type Select, -} from "@gadgetinc/api-client-core"; +import type { DefaultSelection, FindManyFunction, GadgetRecord, LimitToKnownKeys, Select, SortOrder } from "@gadgetinc/api-client-core"; +import type { OptionsType, ReadOperationOptions } from "@gadgetinc/client-hooks"; import { useCallback, useMemo, useState } from "react"; import { validateAutoTableOptions, validateAutoTableProps } from "./auto/AutoTableValidators.js"; import { useModelMetadata } from "./metadata.js"; import { getTableColumns, getTableRows, getTableSelectionMap, getTableSpec } from "./use-table/helpers.js"; import type { TableOptions, TableResult } from "./use-table/types.js"; import { useList } from "./useList.js"; -import { type OptionsType, type ReadOperationOptions } from "./utils.js"; const getNextDirection = (sortDirection: SortOrder | undefined) => { switch (sortDirection) { diff --git a/packages/react/src/useView.ts b/packages/react/src/useView.ts deleted file mode 100644 index 5af3dda91..000000000 --- a/packages/react/src/useView.ts +++ /dev/null @@ -1,161 +0,0 @@ -import type { - GQLBuilderResult, - VariablesOptions, - ViewFunction, - ViewFunctionWithoutVariables, - ViewFunctionWithVariables, - ViewResult, -} from "@gadgetinc/api-client-core"; -import { get, namespaceDataPath } from "@gadgetinc/api-client-core"; -import { useMemo } from "react"; -import { useGadgetQuery } from "./useGadgetQuery.js"; -import { useStructuralMemo } from "./useStructuralMemo.js"; -import type { ReadHookResult, ReadOperationOptions } from "./utils.js"; -import { ErrorWrapper, useQueryArgs } from "./utils.js"; - -/** - * React hook to fetch the result of a computed view from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the shape of the computed view's result. - * - * @param view Gadget view function to run, like `api.leaderboard` or `api.todos.summary` - * @param options options for controlling client side execution - * - * @example - * - * ``` - * export function Leaderboard() { - * const [result, refresh] = useView(api.leaderboard); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No data found; - * - * return <>{result.data.map((leaderboard) =>
{leaderboard.name}: {leaderboard.score}
)}; - * } - * ``` - */ -export function useView>( - view: F, - options?: Omit -): ReadHookResult>; -/** - * React hook to fetch the result of a computed view with variables from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the shape of the computed view's result. - * - * @param manager Gadget view function to run - * @param variables variables to pass to the backend view - * @param options options for controlling client side execution - * - * @example - * - * ``` - * export function Leaderboard() { - * const [result, refresh] = useView(api.leaderboard, { - * first: 10, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No data found; - * - * return <>{result.data.map((leaderboard) =>
{leaderboard.name}: {leaderboard.score}
)}; - * } - * ``` - */ -export function useView>( - view: F, - variables: F["variablesType"], - options?: Omit -): ReadHookResult>; -/** - * React hook to fetch the result of an inline computed view with variables from the backend. Returns a standard hook result set with a tuple of the result object with `data`, `fetching`, and `error` keys, and a `refetch` function. `data` will be the shape of the computed view's result. - * - * Does not know the type of the result from the input string -- for type safety, use a named view defined in a .gelly file in the backend. - * - * @param view Gelly query string to run, like `{ count(todos) }` - * @param variables variables to pass to the backend view - * @param options options for controlling client side execution - * - * @example - * - * ``` - * export function Leaderboard() { - * const [result, refresh] = useView("{ count(todos) }", { - * first: 10, - * }); - * - * if (result.error) return <>Error: {result.error.toString()}; - * if (result.fetching && !result.data) return <>Fetching...; - * if (!result.data) return <>No data found; - * - * return <>{result.data.map((leaderboard) =>
{leaderboard.name}: {leaderboard.score}
)}; - * } - * ``` - */ -export function useView( - gellyQuery: string, - variables?: Record, - options?: Omit -): ReadHookResult>>; -export function useView>( - view: F | string, - variablesOrOptions?: VariablesT | Omit, - maybeOptions?: Omit -): ReadHookResult> { - let variables: VariablesT | undefined; - let options: Omit | undefined; - - if (typeof view == "string" || "variables" in view) { - variables = variablesOrOptions as VariablesT; - options = maybeOptions; - } else if (variablesOrOptions) { - options = variablesOrOptions as Omit; - } - - const memoizedVariables = useStructuralMemo(variables); - const memoizedOptions = useStructuralMemo({ - ...options, - context: { - ...options?.context, - // if the view exports the typenames it references, add them to the context so urql will refresh the view when mutations are made against these typenames - additionalTypenames: [ - ...(options?.context?.additionalTypenames ?? []), - ...(typeof view == "string" ? [] : view.referencedTypenames ?? []), - ], - }, - }); - - const [plan, dataPath] = useMemo((): [plan: GQLBuilderResult, dataPath: string[]] => { - if (typeof view == "string") { - return [{ query: inlineViewQuery, variables: { query: view, variables: memoizedVariables } }, ["gellyView"]]; - } else { - const variablesOptions: VariablesOptions = {}; - if ("variables" in view && memoizedVariables) { - for (const [name, variable] of Object.entries(view.variables)) { - const value = memoizedVariables[name as keyof typeof memoizedVariables] as unknown; - if (typeof value != "undefined" && value !== null) { - variablesOptions[name] = { - value, - ...variable, - }; - } - } - } - - return [view.plan(variablesOptions), namespaceDataPath([view.gqlFieldName], view.namespace)]; - } - }, [view, memoizedVariables]); - - const [rawResult, refresh] = useGadgetQuery(useQueryArgs(plan, memoizedOptions)); - - const result = useMemo(() => { - const data = get(rawResult.data, dataPath); - const error = ErrorWrapper.errorIfDataAbsent(rawResult, dataPath, options?.pause); - - return { ...rawResult, data, error }; - }, [dataPath, options?.pause, rawResult]); - - return [result, refresh]; -} - -const inlineViewQuery = `query InlineView($query: String!, $variables: JSONObject) { - gellyView(query: $query, variables: $variables) -}`; diff --git a/packages/react/src/utils.ts b/packages/react/src/utils.ts index bc0c088d3..f03f1b021 100644 --- a/packages/react/src/utils.ts +++ b/packages/react/src/utils.ts @@ -1,375 +1,6 @@ -import { GraphQLError } from "@0no-co/graphql.web"; -import type { - AnyActionFunction, - AnyBulkActionFunction, - AnyClient, - AnyModelManager, - BackgroundActionHandle, - EnqueueBackgroundActionOptions, - FieldSelection, - GadgetError, - InvalidFieldError, - InvalidRecordError, -} from "@gadgetinc/api-client-core"; -import { gadgetErrorFor, getNonNullableError, namespaceDataPath } from "@gadgetinc/api-client-core"; -import type { CombinedError, RequestPolicy } from "@urql/core"; +import type { AnyClient, AnyModelManager } from "@gadgetinc/api-client-core"; +import { namespaceDataPath } from "@gadgetinc/utils"; import type { RefCallback, RefObject } from "react"; -import { useMemo } from "react"; -import type { AnyVariables, Operation, OperationContext, UseQueryArgs, UseQueryState } from "urql"; - -/** - * All the options controlling how this query will be managed by urql - * */ -export declare type ReadOperationOptions = { - /** Updates the {@link RequestPolicy} for the executed GraphQL query operation. - * - * @remarks - * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation - * that `useQuery` executes, and indicates a caching strategy for cache exchanges. - * - * For example, when set to `'cache-and-network'`, {@link useQuery} will - * receive a cached result with `stale: true` and an API request will be - * sent in the background. - * - * @see {@link OperationContext.requestPolicy} for where this value is set. - */ - requestPolicy?: RequestPolicy; - /** Updates the {@link OperationContext} for the executed GraphQL query operation. - * - * @remarks - * `context` may be passed to {@link useQuery}, to update the {@link OperationContext} - * of a query operation. This may be used to update the `context` that exchanges - * will receive for a single hook. - * - * Hint: This should be wrapped in a `useMemo` hook, to make sure that your - * component doesn’t infinitely update. - * - * @example - * ```ts - * const [result, reexecute] = useQuery({ - * query, - * context: useMemo(() => ({ - * additionalTypenames: ['Item'], - * }), []) - * }); - * ``` - */ - context?: Partial; - /** Prevents {@link useQuery} from automatically executing GraphQL query operations. - * - * @remarks - * `pause` may be set to `true` to stop {@link useQuery} from executing - * automatically. The hook will stop receiving updates from the {@link Client} - * and won’t execute the query operation, until either it’s set to `false` - * or the {@link UseQueryExecute} function is called. - * - * @see {@link https://urql.dev/goto/docs/basics/react-preact/#pausing-usequery} for - * documentation on the `pause` option. - */ - pause?: boolean; - /** - * Marks this query as one that should suspend the react component rendering while executing, instead of returning `{fetching: true}` to the caller. - * Useful if you want to allow components higher in the tree to show spinners instead of having every component manage its own loading state. - */ - suspense?: boolean; - /** - * Marks this query as a live query that will subscribe to changes from the backend and re-render when backend data changes with the newest data. - */ - live?: boolean; -}; - -/** - * The inner result object returned from a query result - **/ -export interface ReadHookState> { - fetching: boolean; - stale: boolean; - data?: Data; - error?: ErrorWrapper; - extensions?: Record; - operation?: Operation; -} - -/** - * The return value of a `useGet`, `useFindMany`, `useFindOne` etc hook. - * Includes the data result object and a refetch function. - **/ -export declare type ReadHookResult = [ - ReadHookState, - (opts?: Partial) => void -]; - -/** - * The inner result object returned from a mutation result - */ -export interface ActionHookState> { - fetching: boolean; - stale: boolean; - data?: Data; - error?: ErrorWrapper; - extensions?: Record; - operation?: Operation; -} - -export type RequiredKeysOf = Exclude< - { - [Key in keyof BaseType]: BaseType extends Record ? Key : never; - }[keyof BaseType], - undefined ->; - -/** - * The return value of a `useAction`, `useGlobalAction`, `useBulkAction` etc hook. - * Includes the data result object and a function for running the mutation. - **/ -export type ActionHookResult = RequiredKeysOf extends never - ? ActionHookResultWithOptionalCallbackVariables - : ActionHookResultWithRequiredCallbackVariables; - -export type ActionHookResultWithOptionalCallbackVariables = [ - ActionHookState, - (variables?: Variables, context?: Partial) => Promise> -]; - -export type ActionHookResultWithRequiredCallbackVariables = [ - ActionHookState, - (variables: Variables, context?: Partial) => Promise> -]; - -/** - * The inner result object returned from a mutation result - */ -export type EnqueueHookState = Action extends AnyBulkActionFunction - ? { - fetching: boolean; - stale: boolean; - handles: BackgroundActionHandle[] | null; - error?: ErrorWrapper; - extensions?: Record; - operation?: Operation<{ backgroundAction: { id: string } }, Action["variablesType"]>; - } - : { - fetching: boolean; - stale: boolean; - handle: BackgroundActionHandle | null; - error?: ErrorWrapper; - extensions?: Record; - operation?: Operation<{ backgroundAction: { id: string } }, Action["variablesType"]>; - }; - -/** - * The return value of a `useEnqueue` hook. - * Returns a two-element array: - * - the result object, with the keys like `handle`, `fetching`, and `error` - * - and a function for running the enqueue mutation. - **/ -export type EnqueueHookResult = RequiredKeysOf< - Exclude -> extends never - ? [ - EnqueueHookState, - ( - variables?: Action["variablesType"], - backgroundOptions?: EnqueueBackgroundActionOptions, - context?: Partial - ) => Promise> - ] - : [ - EnqueueHookState, - ( - variables: Action["variablesType"], - backgroundOptions?: EnqueueBackgroundActionOptions, - context?: Partial - ) => Promise> - ]; - -export const noProviderErrorMessage = `Could not find a client in the context of Provider. Please ensure you wrap the root component in a `; - -const generateErrorMessage = (networkErr?: Error, graphQlErrs?: GraphQLError[]) => { - let error = ""; - if (networkErr !== undefined) { - error = `[Network] ${networkErr.message}`; - } else if (graphQlErrs !== undefined) { - graphQlErrs.forEach((err) => { - error += `[GraphQL] ${err.message}\n`; - }); - } else { - error = "Unknown error"; - } - - return error.trim(); -}; - -const rehydrateGraphQlError = (error: any): GraphQLError => { - if (typeof error === "string") { - return new GraphQLError(error); - } else if (error?.message && !error.code) { - return new GraphQLError(error.message, error.nodes, error.source, error.positions, error.path, error, error.extensions || {}); - } else { - return error; - } -}; - -/** - * An error returned by any of the Gadget react hooks. - * Always has a message, but can be inspected to retrieve more detailed errors from either the network, the raw GraphQL layer, or Gadget specific errors like validation errors. - * Not intended for creating outside of Gadget-owned code. - **/ -export class ErrorWrapper extends Error { - /** @private */ - static forClientSideError(error: Error, response?: any) { - return new ErrorWrapper({ - executionErrors: [error], - response, - }); - } - /** @private */ - static forErrorsResponse(errors: Record[], response?: any) { - return new ErrorWrapper({ - executionErrors: errors.map(gadgetErrorFor), - response, - }); - } - /** @private */ - static forMaybeCombinedError(error?: CombinedError | null) { - if (!error) return undefined; - return new ErrorWrapper({ - networkError: error.networkError, - executionErrors: error.graphQLErrors, - response: error.response, - }); - } - /** @private */ - static errorIfDataAbsent(result: UseQueryState, dataPath: string[], paused = false) { - const nonNullableError = getNonNullableError(result, dataPath); - let error = ErrorWrapper.forMaybeCombinedError(result.error); - if (!error && nonNullableError && !paused) { - error = ErrorWrapper.forClientSideError(nonNullableError); - } - return error; - } - - /** Error message for this error. Derived from the other errors this wraps. */ - public message: string; - /** - * A list of errors encountered by the backend when processing a request. Populated if the client successfully communicated with the backend, but the backend was unable to process the request and rejected it with an error. - * Includes GraphQL syntax errors, missing or invalid argument errors, data validation errors, or unexpected errors encountered when running backend logic. - **/ - public executionErrors: (GraphQLError | GadgetError)[]; - /** - * An error encountered when trying to communicate with the backend from the client. Includes things like connection timeouts, connection interrupts, or no internet connection errors - **/ - public networkError?: Error; - - /** - * A list of errors encountered by the backend when processing a request. Populated if the client successfully communicated with the backend, but the backend was unable to process the request and rejected it with an error. - * Includes GraphQL syntax errors, missing or invalid argument errors, data validation errors, or unexpected errors encountered when running backend logic. - * - * This property allows this object to match the interface of urql's `CombinedError` object. - * - * @deprecated use `executionErrors` instead for a list of the errors that the GraphQL backend API returned *and* client side errors from unexpected responses. - **/ - public graphQLErrors: GraphQLError[]; - - /** - * The response from the server, if any was retrieved. - */ - public response?: any; - - constructor({ - networkError, - executionErrors, - response, - }: { - networkError?: Error; - executionErrors?: Array | Error>; - validationErrors?: InvalidFieldError[]; - response?: any; - }) { - const normalizedExecutionErrors = (executionErrors || []).map(rehydrateGraphQlError); - const message = generateErrorMessage(networkError, normalizedExecutionErrors); - - super(message); - - this.message = message; - this.executionErrors = normalizedExecutionErrors; - this.graphQLErrors = normalizedExecutionErrors; - this.networkError = networkError; - this.response = response; - } - - /** Class name of this error -- always `ErrorWrapper` */ - get name() { - return "ErrorWrapper"; - } - - toString() { - return this.message; - } - - /** - * A list of errors the backend reported for specific fields being invalid for the records touched by an action. Is a shortcut for accessing the validation errors of a `GadgetInvalidRecordError` if that's what is in the `executionErrors`. - **/ - public get validationErrors(): InvalidFieldError[] | null { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const firstInvalidRecordError = this.executionErrors.find((err) => (err as any).code == "GGT_INVALID_RECORD") as - | InvalidRecordError - | undefined; - - return firstInvalidRecordError?.validationErrors ?? null; - } -} - -interface QueryPlan { - variables: any; - query: string; -} - -interface QueryOptions { - context?: Partial; - pause?: boolean; - requestPolicy?: RequestPolicy; - suspense?: boolean; -} - -/** - * Generate the args for an `urql` useQuery hook, applying Gadget's defaults - * - * Gadget's React hooks support using the `suspense: true` option to enable React Suspense selectively per query. This means suspense is on at the client level, and then disabled by default for each hook until you opt in with `suspense: true`. This differs from urql, which has suspense on for hooks by default when it is enabled at the client level. So, this hook applies Gadget's (we think better) default to turn suspense off for each hook until you opt in, even when enabled at the client level. - */ -export const useMemoizedQueryOptions = (options?: Options): Options => { - const { context: _context, suspense: _suspense, ...rest } = options ?? {}; - - // use a memo as urql rerenders on context identity changes - const context = useMemo(() => { - return { - suspense: !!options?.suspense, - ...options?.context, - }; - }, [options?.suspense, options?.context]); - - return { - ...rest, - context, - } as unknown as Options; -}; - -/** - * Given a plan from a gadget query plan generator, create the query options object to pass to `urql`'s `useQuery` hook - **/ -export const useQueryArgs = (plan: Plan, options?: Options): UseQueryArgs => ({ - query: plan.query, - variables: plan.variables, - ...options, -}); - -export type OptionsType = { - [key: string]: any; - /** What fields to select from the resulting object */ - select?: FieldSelection; - /** Subscribe to changes from the backend and return a new result as it changes */ - live?: boolean; -}; /** * Get a list of path segments from a dot-separated path diff --git a/packages/shopify-extensions/package.json b/packages/shopify-extensions/package.json index 540978bbd..1d21f208e 100644 --- a/packages/shopify-extensions/package.json +++ b/packages/shopify-extensions/package.json @@ -1,6 +1,6 @@ { "name": "@gadgetinc/shopify-extensions", - "version": "0.6.1", + "version": "0.7.0", "files": [ "README.md", "dist/**/*", @@ -32,9 +32,7 @@ "prepublishOnly": "pnpm build", "prerelease": "gitpkg publish" }, - "dependencies": { - "@gadgetinc/api-client-core": "^0.15.46" - }, + "dependencies": {}, "devDependencies": { "@gadgetinc/api-client-core": "workspace:*", "@gadgetinc/react": "workspace:*", @@ -45,8 +43,27 @@ "react-dom": "^19.1.1" }, "peerDependencies": { + "@gadgetinc/react": "^0.23.0", + "@gadgetinc/preact": "^0.1.0", + "preact": "^10.0.0", "react": "^19.0.0", - "react-dom": "^19.0.0", - "@gadgetinc/react": "^0.22.0" + "react-dom": "^19.0.0" + }, + "peerDependenciesMeta": { + "@gadgetinc/preact": { + "optional": true + }, + "@gadgetinc/react": { + "optional": true + }, + "preact": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react": { + "optional": true + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6026240a..1d20cd846 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,10 +124,13 @@ importers: dependencies: '@0no-co/graphql.web': specifier: ^1.0.4 - version: 1.0.4(graphql@16.8.1) + version: 1.0.4(graphql@16.11.0) + '@gadgetinc/utils': + specifier: ^0.1.0 + version: link:../utils '@n1ru4l/graphql-live-query': specifier: ^0.10.0 - version: 0.10.0(graphql@16.8.1) + version: 0.10.0(graphql@16.11.0) '@n1ru4l/json-patch-plus': specifier: ^0.2.0 version: 0.2.0 @@ -135,17 +138,17 @@ importers: specifier: ^3.2.0 version: 3.2.0(patch_hash=ucctwfdcmjhobzxki7ytrq6ei4) '@urql/core': - specifier: ^4.0.10 - version: 4.0.10(graphql@16.8.1) + specifier: ^6.0.1 + version: 6.0.1(graphql@16.11.0) cross-fetch: specifier: ^4.0.0 version: 4.0.0 graphql: - specifier: ^16.8.1 - version: 16.8.1 + specifier: ^16.11.0 + version: 16.11.0 graphql-ws: - specifier: ^5.13.1 - version: 5.13.1(graphql@16.8.1) + specifier: ^6.0.6 + version: 6.0.6(graphql@16.11.0)(ws@8.18.2) isomorphic-ws: specifier: ^5.0.0 version: 5.0.0(ws@8.18.2) @@ -186,12 +189,6 @@ importers: p-retry: specifier: ^4.5.0 version: 4.6.2 - react: - specifier: ^19.1.1 - version: 19.1.1 - react-dom: - specifier: ^19.1.1 - version: 19.1.1(react@19.1.1) typescript: specifier: 5.4.5 version: 5.4.5 @@ -227,39 +224,52 @@ importers: specifier: ^7.1.4 version: 7.1.4(@types/node@22.18.1)(tsx@4.9.3) - packages/core: + packages/client-hooks: dependencies: + '@gadgetinc/utils': + specifier: ^0.1.0 + version: link:../utils + devDependencies: + '@gadgetinc/api-client-core': + specifier: workspace:* + version: link:../api-client-core '@urql/core': specifier: '*' - version: 4.0.10(graphql@16.11.0) - graphql: - specifier: '*' - version: 16.11.0 - graphql-ws: - specifier: '*' - version: 5.16.0(graphql@16.11.0) - klona: - specifier: ^2.0.6 - version: 2.0.6 - devDependencies: - conditional-type-checks: - specifier: ^1.0.6 - version: 1.0.6 - type-fest: - specifier: ^3.13.1 - version: 3.13.1 - typescript: - specifier: 5.4.5 - version: 5.4.5 + version: 6.0.1(graphql@16.8.1) - packages/react: + packages/preact: dependencies: - '@0no-co/graphql.web': - specifier: ^1.0.4 - version: 1.0.4(graphql@16.8.1) + '@gadgetinc/client-hooks': + specifier: ^0.1.0 + version: link:../client-hooks + '@urql/preact': + specifier: ^5.0.0 + version: 5.0.0(@urql/core@6.0.1)(preact@10.27.2) + fast-deep-equal: + specifier: ^3.1.3 + version: 3.1.3 + devDependencies: '@gadgetinc/api-client-core': specifier: workspace:* version: link:../api-client-core + '@testing-library/jest-dom': + specifier: ^6.8.0 + version: 6.8.0 + '@testing-library/preact': + specifier: ^3.2.4 + version: 3.2.4(preact@10.27.2) + preact: + specifier: ^10.27.2 + version: 10.27.2 + + packages/react: + dependencies: + '@gadgetinc/client-hooks': + specifier: ^0.1.0 + version: link:../client-hooks + '@gadgetinc/utils': + specifier: ^0.1.0 + version: link:../utils '@hookform/resolvers': specifier: ^5.2.1 version: 5.2.1(react-hook-form@7.62.0) @@ -279,12 +289,15 @@ importers: specifier: ^2.6.2 version: 2.6.2 urql: - specifier: ^4.0.4 - version: 4.0.4(graphql@16.8.1)(react@19.1.1) + specifier: ^5.0.1 + version: 5.0.1(@urql/core@6.0.1)(react@19.1.1) yup: specifier: ^1.2.0 version: 1.4.0 devDependencies: + '@0no-co/graphql.web': + specifier: ^1.0.4 + version: 1.0.4(graphql@16.8.1) '@changesets/cli': specifier: ^2.27.7 version: 2.27.7 @@ -297,6 +310,9 @@ importers: '@emotion/styled': specifier: ^11.11.0 version: 11.11.5(@emotion/react@11.11.4)(@types/react@19.1.10)(react@19.1.1) + '@gadgetinc/api-client-core': + specifier: workspace:* + version: link:../api-client-core '@graphql-codegen/cli': specifier: ^5.0.0 version: 5.0.2(@types/node@22.18.1)(graphql@16.8.1)(typescript@5.4.5) @@ -424,8 +440,8 @@ importers: specifier: ^0.2.6 version: 0.2.6 '@urql/core': - specifier: ^4.0.10 - version: 4.0.10(graphql@16.8.1) + specifier: ^6.0.1 + version: 6.0.1(graphql@16.8.1) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) @@ -512,11 +528,10 @@ importers: version: 6.3.2 packages/react-bigcommerce: - dependencies: + devDependencies: '@gadgetinc/api-client-core': specifier: workspace:* version: link:../api-client-core - devDependencies: '@gadgetinc/react': specifier: workspace:* version: link:../react @@ -538,9 +553,6 @@ importers: packages/react-shopify-app-bridge: dependencies: - '@gadgetinc/api-client-core': - specifier: workspace:* - version: link:../api-client-core crypto-js: specifier: ^4.2.0 version: 4.2.0 @@ -548,6 +560,9 @@ importers: specifier: ^2.6.2 version: 2.6.2 devDependencies: + '@gadgetinc/api-client-core': + specifier: workspace:* + version: link:../api-client-core '@gadgetinc/react': specifier: workspace:* version: link:../react @@ -581,10 +596,16 @@ importers: packages/shopify-extensions: dependencies: + '@gadgetinc/preact': + specifier: ^0.1.0 + version: link:../preact + preact: + specifier: ^10.0.0 + version: 10.27.2 + devDependencies: '@gadgetinc/api-client-core': specifier: workspace:* version: link:../api-client-core - devDependencies: '@gadgetinc/react': specifier: workspace:* version: link:../react @@ -659,8 +680,29 @@ importers: specifier: ^19.1.1 version: 19.1.1(react@19.1.1) + packages/utils: + dependencies: + '@0no-co/graphql.web': + specifier: ^1.0.4 + version: 1.2.0(graphql@16.8.1) + devDependencies: + '@urql/core': + specifier: '*' + version: 6.0.1(graphql@16.8.1) + packages: + /@0no-co/graphql.web@1.0.4(graphql@16.11.0): + resolution: {integrity: sha512-W3ezhHGfO0MS1PtGloaTpg0PbaT8aZSmmaerL7idtU5F7oCI+uu25k+MsMS31BVFlp4aMkHSrNRxiD72IlK8TA==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + dependencies: + graphql: 16.11.0 + dev: false + /@0no-co/graphql.web@1.0.4(graphql@16.8.1): resolution: {integrity: sha512-W3ezhHGfO0MS1PtGloaTpg0PbaT8aZSmmaerL7idtU5F7oCI+uu25k+MsMS31BVFlp4aMkHSrNRxiD72IlK8TA==} peerDependencies: @@ -670,6 +712,28 @@ packages: optional: true dependencies: graphql: 16.8.1 + dev: true + + /@0no-co/graphql.web@1.2.0(graphql@16.11.0): + resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + dependencies: + graphql: 16.11.0 + dev: false + + /@0no-co/graphql.web@1.2.0(graphql@16.8.1): + resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + dependencies: + graphql: 16.8.1 /@0no-co/graphql.web@1.2.0(graphql@16.11.0): resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} @@ -701,7 +765,7 @@ packages: engines: {node: '>=6.0.0'} dependencies: '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.30 dev: true /@andrewbranch/untar.js@1.0.3: @@ -714,13 +778,13 @@ packages: peerDependencies: graphql: '*' dependencies: - '@babel/core': 7.19.0 - '@babel/generator': 7.19.0 - '@babel/parser': 7.19.0 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 '@babel/runtime': 7.24.7 - '@babel/traverse': 7.19.0 - '@babel/types': 7.19.0 - babel-preset-fbjs: 3.4.0(@babel/core@7.19.0) + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + babel-preset-fbjs: 3.4.0(@babel/core@7.28.3) chalk: 4.1.2 fb-watchman: 2.0.1 fbjs: 3.0.5 @@ -797,67 +861,11 @@ packages: picocolors: 1.1.1 dev: true - /@babel/compat-data@7.19.0: - resolution: {integrity: sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/compat-data@7.24.7: - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/compat-data@7.28.0: resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} dev: true - /@babel/core@7.19.0: - resolution: {integrity: sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.19.0 - '@babel/helper-compilation-targets': 7.19.0(@babel/core@7.19.0) - '@babel/helper-module-transforms': 7.19.0 - '@babel/helpers': 7.19.0 - '@babel/parser': 7.19.0 - '@babel/template': 7.18.10 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - convert-source-map: 1.8.0 - debug: 4.3.4(supports-color@8.1.1) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/core@7.24.7: - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/core@7.28.3: resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} engines: {node: '>=6.9.0'} @@ -890,16 +898,6 @@ packages: jsesc: 2.5.2 dev: true - /@babel/generator@7.24.7: - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - dev: true - /@babel/generator@7.28.3: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} @@ -911,35 +909,11 @@ packages: jsesc: 3.1.0 dev: true - /@babel/helper-annotate-as-pure@7.24.7: - resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-compilation-targets@7.19.0(@babel/core@7.19.0): - resolution: {integrity: sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.19.0 - '@babel/core': 7.19.0 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.23.1 - semver: 6.3.1 - dev: true - - /@babel/helper-compilation-targets@7.24.7: - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + /@babel/helper-annotate-as-pure@7.27.3: + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 - lru-cache: 5.1.1 - semver: 6.3.1 + '@babel/types': 7.28.2 dev: true /@babel/helper-compilation-targets@7.27.2: @@ -953,19 +927,19 @@ packages: semver: 6.3.1 dev: true - /@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.19.0): + /@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 '@babel/helper-member-expression-to-functions': 7.24.7 '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.19.0) + '@babel/helper-replace-supers': 7.24.7(@babel/core@7.28.3) '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 semver: 6.3.1 @@ -982,23 +956,23 @@ packages: resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true /@babel/helper-function-name@7.19.0: resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.24.7 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 dev: true /@babel/helper-function-name@7.24.7: resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 dev: true /@babel/helper-globals@7.28.0: @@ -1010,22 +984,15 @@ packages: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-hoist-variables@7.24.7: - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true /@babel/helper-member-expression-to-functions@7.24.7: resolution: {integrity: sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color dev: true @@ -1037,16 +1004,6 @@ packages: '@babel/types': 7.19.0 dev: true - /@babel/helper-module-imports@7.24.7: - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-module-imports@7.27.1: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -1057,54 +1014,6 @@ packages: - supports-color dev: true - /@babel/helper-module-transforms@7.19.0: - resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.22.20 - '@babel/template': 7.18.10 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-module-transforms@7.24.7(@babel/core@7.19.0): - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.0 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7): - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3): resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -1123,26 +1032,26 @@ packages: resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true - /@babel/helper-plugin-utils@7.18.9: - resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==} + /@babel/helper-plugin-utils@7.24.7: + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-plugin-utils@7.24.7: - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + /@babel/helper-plugin-utils@7.27.1: + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-replace-supers@7.24.7(@babel/core@7.19.0): + /@babel/helper-replace-supers@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.19.0 + '@babel/core': 7.28.3 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-member-expression-to-functions': 7.24.7 '@babel/helper-optimise-call-expression': 7.24.7 @@ -1150,19 +1059,12 @@ packages: - supports-color dev: true - /@babel/helper-simple-access@7.18.6: - resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - dev: true - /@babel/helper-simple-access@7.24.7: resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color dev: true @@ -1171,8 +1073,8 @@ packages: resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color dev: true @@ -1181,14 +1083,14 @@ packages: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true /@babel/helper-split-export-declaration@7.24.7: resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true /@babel/helper-string-parser@7.18.10: @@ -1221,40 +1123,11 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-option@7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-validator-option@7.24.7: - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/helper-validator-option@7.27.1: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} dev: true - /@babel/helpers@7.19.0: - resolution: {integrity: sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helpers@7.24.7: - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - dev: true - /@babel/helpers@7.28.3: resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} engines: {node: '>=6.9.0'} @@ -1267,7 +1140,7 @@ packages: resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.27.1 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -1277,7 +1150,7 @@ packages: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-validator-identifier': 7.27.1 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -1288,7 +1161,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.19.0 + '@babel/types': 7.28.2 dev: true /@babel/parser@7.24.7: @@ -1296,7 +1169,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true /@babel/parser@7.28.3: @@ -1307,424 +1180,424 @@ packages: '@babel/types': 7.28.2 dev: true - /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.19.0): + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.28.3): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.19.0) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.19.0): + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.28.3): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.19.0 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.19.0) + '@babel/compat-data': 7.28.0 + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.28.3) dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.19.0): + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.19.0): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.19.0): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-flow@7.24.7(@babel/core@7.19.0): + /@babel/plugin-syntax-flow@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7): + /@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.19.0): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.19.0): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.19.0): - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} + /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.28.3): + resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.19.0): - resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} + /@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3): + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.19.0): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.19.0): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.19.0): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.19.0): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.19.0): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.19.0): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.19.0): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-syntax-typescript@7.17.12(@babel/core@7.19.0): + /@babel/plugin-syntax-typescript@7.17.12(@babel/core@7.28.3): resolution: {integrity: sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.18.9 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-classes@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-classes@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.19.0) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.24.7(@babel/core@7.28.3) '@babel/helper-split-export-declaration': 7.24.7 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/template': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/template': 7.27.2 dev: true - /@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-flow-strip-types@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-flow-strip-types@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-cjRKJ7FobOH2eakx7Ja+KpJRj8+y+/SiB3ooYm/n2UJfxu0oEaOoxOinitkJcPqv9KxS0kxTGPUaR7L2XcXDXA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.19.0) + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.28.3) dev: true - /@babel/plugin-transform-for-of@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-for-of@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-function-name@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-function-name@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-compilation-targets': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-literals@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-literals@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.19.0) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-simple-access': 7.24.7 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-object-super@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-object-super@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.19.0) + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.24.7(@babel/core@7.28.3) transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-parameters@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-parameters@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.19.0): - resolution: {integrity: sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==} + /@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.3): + resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.19.0) - '@babel/types': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true - /@babel/plugin-transform-spread@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-spread@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.19.0): + /@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.28.3): resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.19.0 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 dev: true /@babel/runtime@7.24.4: @@ -1764,40 +1637,22 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.3 - '@babel/types': 7.28.2 - dev: true - - /@babel/traverse@7.19.0: - resolution: {integrity: sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.19.0 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.19.0 - '@babel/types': 7.24.7 - debug: 4.3.4(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 dev: true - /@babel/traverse@7.24.7: - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + /@babel/traverse@7.19.0: + resolution: {integrity: sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: @@ -3823,11 +3678,11 @@ packages: peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.28.3 '@babel/parser': 7.19.0 - '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.28.3) '@babel/traverse': 7.19.0 - '@babel/types': 7.19.0 + '@babel/types': 7.28.2 '@graphql-tools/utils': 10.2.2(graphql@16.8.1) graphql: 16.8.1 tslib: 2.6.2 @@ -4209,7 +4064,7 @@ packages: collect-v8-coverage: 1.0.1 exit: 0.1.2 glob: 7.2.3 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.0 istanbul-lib-instrument: 6.0.0 istanbul-lib-report: 3.0.0 @@ -4266,14 +4121,14 @@ packages: resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.19.0 + '@babel/core': 7.28.3 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.19 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.7.0 jest-regex-util: 29.6.3 jest-util: 29.7.0 @@ -4324,8 +4179,8 @@ packages: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.5 dev: true /@jridgewell/gen-mapping@0.3.13: @@ -4841,12 +4696,12 @@ packages: react-dom: 19.1.1(react@19.1.1) dev: true - /@n1ru4l/graphql-live-query@0.10.0(graphql@16.8.1): + /@n1ru4l/graphql-live-query@0.10.0(graphql@16.11.0): resolution: {integrity: sha512-qZ7OHH/NB0NcG/Xa7irzgjE63UH0CkofZT0Bw4Ko6iRFagPRHBM8RgFXwTt/6JbFGIEUS4STRtaFoc/Eq/ZtzQ==} peerDependencies: graphql: ^15.4.0 || ^16.0.0 dependencies: - graphql: 16.8.1 + graphql: 16.11.0 dev: false /@n1ru4l/json-patch-plus@0.2.0: @@ -7633,6 +7488,20 @@ packages: pretty-format: 27.5.1 dev: true + /@testing-library/dom@8.20.1: + resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==} + engines: {node: '>=12'} + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.24.7 + '@types/aria-query': 5.0.4 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: true + /@testing-library/jest-dom@6.5.0: resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} @@ -7658,6 +7527,16 @@ packages: redent: 3.0.0 dev: true + /@testing-library/preact@3.2.4(preact@10.27.2): + resolution: {integrity: sha512-F+kJ243LP6VmEK1M809unzTE/ijg+bsMNuiRN0JEDIJBELKKDNhdgC/WrUSZ7klwJvtlO3wQZ9ix+jhObG07Fg==} + engines: {node: '>= 12'} + peerDependencies: + preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' + dependencies: + '@testing-library/dom': 8.20.1 + preact: 10.27.2 + dev: true + /@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.7)(@types/react@19.1.10)(react-dom@19.1.1)(react@19.1.1): resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} engines: {node: '>=18'} @@ -7727,43 +7606,27 @@ packages: resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} dev: true - /@types/babel__core@7.1.19: - resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==} - dependencies: - '@babel/parser': 7.19.0 - '@babel/types': 7.24.7 - '@types/babel__generator': 7.6.4 - '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.20.6 - dev: true - /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.20.6 + '@types/babel__traverse': 7.28.0 dev: true /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.28.2 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.19.0 - '@babel/types': 7.24.7 - dev: true - - /@types/babel__traverse@7.20.6: - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - dependencies: - '@babel/types': 7.24.7 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 dev: true /@types/babel__traverse@7.28.0: @@ -8127,7 +7990,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.2 + semver: 7.7.2 tsutils: 3.21.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: @@ -8148,7 +8011,7 @@ packages: '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.4.5) eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.6.2 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -8166,8 +8029,17 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@urql/core@4.0.10(graphql@16.11.0): - resolution: {integrity: sha512-Vs3nOSAnYftqOCg034Ostp/uSqWlQg5ryLIzcOrm8+O43s4M+Ew4GQAuemIH7ZDB8dek6h61zzWI3ujd8FH3NA==} + /@urql/core@6.0.1: + resolution: {integrity: sha512-FZDiQk6jxbj5hixf2rEPv0jI+IZz0EqqGW8mJBEug68/zHTtT+f34guZDmyjJZyiWbj0vL165LoMr/TkeDHaug==} + dependencies: + '@0no-co/graphql.web': 1.2.0(graphql@16.8.1) + wonka: 6.3.2 + transitivePeerDependencies: + - graphql + dev: false + + /@urql/core@6.0.1(graphql@16.11.0): + resolution: {integrity: sha512-FZDiQk6jxbj5hixf2rEPv0jI+IZz0EqqGW8mJBEug68/zHTtT+f34guZDmyjJZyiWbj0vL165LoMr/TkeDHaug==} dependencies: '@0no-co/graphql.web': 1.2.0(graphql@16.11.0) wonka: 6.3.2 @@ -8175,14 +8047,25 @@ packages: - graphql dev: false - /@urql/core@4.0.10(graphql@16.8.1): - resolution: {integrity: sha512-Vs3nOSAnYftqOCg034Ostp/uSqWlQg5ryLIzcOrm8+O43s4M+Ew4GQAuemIH7ZDB8dek6h61zzWI3ujd8FH3NA==} + /@urql/core@6.0.1(graphql@16.8.1): + resolution: {integrity: sha512-FZDiQk6jxbj5hixf2rEPv0jI+IZz0EqqGW8mJBEug68/zHTtT+f34guZDmyjJZyiWbj0vL165LoMr/TkeDHaug==} dependencies: - '@0no-co/graphql.web': 1.0.4(graphql@16.8.1) + '@0no-co/graphql.web': 1.2.0(graphql@16.8.1) wonka: 6.3.2 transitivePeerDependencies: - graphql + /@urql/preact@5.0.0(@urql/core@6.0.1)(preact@10.27.2): + resolution: {integrity: sha512-mGQQB9vfRHRGZb+U5le6ZwlblIMNOZSQCOwnABCMEY0LNM79N6pvoih/KOs/kwwAd7jrHMDk74q9TsgdrmyWEw==} + peerDependencies: + '@urql/core': ^6.0.0 + preact: '>= 10.0.0' + dependencies: + '@urql/core': 6.0.1 + preact: 10.27.2 + wonka: 6.3.2 + dev: false + /@vitejs/plugin-react-swc@3.11.0(vite@7.1.4): resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==} peerDependencies: @@ -8472,6 +8355,12 @@ packages: tslib: 2.6.2 dev: true + /aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + dependencies: + deep-equal: 2.2.3 + dev: true + /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -8638,17 +8527,17 @@ packages: resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==} dev: true - /babel-jest@29.7.0(@babel/core@7.19.0): + /babel-jest@29.7.0(@babel/core@7.28.3): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.19.0 + '@babel/core': 7.28.3 '@jest/transform': 29.7.0 - '@types/babel__core': 7.1.19 + '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.19.0) + babel-preset-jest: 29.6.3(@babel/core@7.28.3) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -8660,7 +8549,7 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} dependencies: - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.27.1 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.0 @@ -8673,10 +8562,10 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.24.7 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.6 + '@types/babel__traverse': 7.28.0 dev: true /babel-plugin-macros@3.1.0: @@ -8692,72 +8581,72 @@ packages: resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.19.0): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.28.3): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.19.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.19.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.19.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.19.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.19.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.19.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.19.0) - dev: true - - /babel-preset-fbjs@3.4.0(@babel/core@7.19.0): + '@babel/core': 7.28.3 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.3) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) + dev: true + + /babel-preset-fbjs@3.4.0(@babel/core@7.28.3): resolution: {integrity: sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.19.0 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.19.0) - '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.19.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.19.0) - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.19.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.19.0) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-flow-strip-types': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.19.0) - '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.19.0) + '@babel/core': 7.28.3 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.3) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.28.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-flow-strip-types': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.28.3) babel-plugin-syntax-trailing-function-commas: 7.0.0-beta.0 transitivePeerDependencies: - supports-color dev: true - /babel-preset-jest@29.6.3(@babel/core@7.19.0): + /babel-preset-jest@29.6.3(@babel/core@7.28.3): resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.19.0 + '@babel/core': 7.28.3 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.19.0) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.28.3) dev: true /balanced-match@1.0.2: @@ -8909,17 +8798,6 @@ packages: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: true - /browserslist@4.23.1: - resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001632 - electron-to-chromium: 1.4.798 - node-releases: 2.0.14 - update-browserslist-db: 1.0.16(browserslist@4.23.1) - dev: true - /browserslist@4.24.4: resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -8962,7 +8840,7 @@ packages: /builtins@5.1.0: resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} dependencies: - semver: 7.6.2 + semver: 7.7.2 dev: true /busboy@1.6.0: @@ -9026,6 +8904,16 @@ packages: set-function-length: 1.2.2 dev: true + /call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + dev: true + /call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -9061,10 +8949,6 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001632: - resolution: {integrity: sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg==} - dev: true - /caniuse-lite@1.0.30001692: resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} dev: true @@ -9582,7 +9466,7 @@ packages: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-config: 29.7.0(@types/node@22.18.1) jest-util: 29.7.0 prompts: 2.4.2 @@ -9866,6 +9750,30 @@ packages: engines: {node: '>=6'} dev: true + /deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.7 + es-get-iterator: 1.1.3 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.4 + is-date-object: 1.0.5 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + side-channel: 1.1.0 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + dev: true + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -10101,10 +10009,6 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true - /electron-to-chromium@1.4.798: - resolution: {integrity: sha512-by9J2CiM9KPGj9qfp5U4FcPSbXJG7FNzqnYaY4WLzX+v2PHieVGmnsA4dxfpGE3QEC7JofpPZmn7Vn1B9NR2+Q==} - dev: true - /electron-to-chromium@1.5.80: resolution: {integrity: sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==} dev: true @@ -10230,6 +10134,20 @@ packages: engines: {node: '>= 0.4'} dev: true + /es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.1.0 + dev: true + /es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -10972,7 +10890,6 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} @@ -11173,6 +11090,13 @@ packages: is-callable: 1.2.7 dev: true + /for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + dev: true + /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} @@ -11652,6 +11576,7 @@ packages: graphql: '>=0.11 <=16' dependencies: graphql: 16.8.1 + dev: true /graphql-ws@5.16.0(graphql@16.11.0): resolution: {integrity: sha512-Ju2RCU2dQMgSKtArPbEtsK5gNLnsQyTNIo/T7cZNp96niC1x0KdJNZV0TIoilceBPQwfb5itrGl8pkFeOUMl4A==} @@ -11671,6 +11596,29 @@ packages: graphql: 16.8.1 dev: true + /graphql-ws@6.0.6(graphql@16.11.0)(ws@8.18.2): + resolution: {integrity: sha512-zgfER9s+ftkGKUZgc0xbx8T7/HMO4AV5/YuYiFc+AtgcO5T0v8AxYYNQ+ltzuzDZgNkYJaFspm5MMYLjQzrkmw==} + engines: {node: '>=20'} + peerDependencies: + '@fastify/websocket': ^10 || ^11 + crossws: ~0.3 + graphql: ^15.10.1 || ^16 + uWebSockets.js: ^20 + ws: ^8 + peerDependenciesMeta: + '@fastify/websocket': + optional: true + crossws: + optional: true + uWebSockets.js: + optional: true + ws: + optional: true + dependencies: + graphql: 16.11.0 + ws: 8.18.2 + dev: false + /graphql@16.11.0: resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12005,6 +11953,15 @@ packages: side-channel: 1.0.6 dev: true + /internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + dev: true + /intersection-observer@0.10.0: resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==} dev: true @@ -12194,6 +12151,11 @@ packages: tslib: 2.6.2 dev: true + /is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + dev: true + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -12250,6 +12212,11 @@ packages: is-unc-path: 1.0.0 dev: true + /is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + dev: true + /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -12315,12 +12282,25 @@ packages: tslib: 2.6.2 dev: true + /is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + dev: true + /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.7 dev: true + /is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + dev: true + /is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -12341,6 +12321,10 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -12374,8 +12358,8 @@ packages: resolution: {integrity: sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.24.7 - '@babel/parser': 7.19.0 + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 @@ -12387,11 +12371,11 @@ packages: resolution: {integrity: sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==} engines: {node: '>=10'} dependencies: - '@babel/core': 7.19.0 - '@babel/parser': 7.19.0 + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.6.2 + semver: 7.7.2 transitivePeerDependencies: - supports-color dev: true @@ -12511,16 +12495,16 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.19.0 + '@babel/core': 7.28.3 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 '@types/node': 22.18.1 - babel-jest: 29.7.0(@babel/core@7.19.0) + babel-jest: 29.7.0(@babel/core@7.28.3) chalk: 4.1.2 ci-info: 3.3.1 deepmerge: 4.3.1 glob: 7.2.3 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-circus: 29.7.0 jest-environment-node: 29.7.0 jest-get-type: 29.6.3 @@ -12616,7 +12600,7 @@ packages: '@types/node': 22.18.1 anymatch: 3.1.2 fb-watchman: 2.0.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-regex-util: 29.6.3 jest-util: 29.7.0 jest-worker: 29.7.0 @@ -12710,7 +12694,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.7.0 jest-pnp-resolver: 1.2.2(jest-resolve@29.7.0) jest-util: 29.7.0 @@ -12732,7 +12716,7 @@ packages: '@types/node': 22.18.1 chalk: 4.1.2 emittery: 0.13.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-docblock: 29.7.0 jest-environment-node: 29.7.0 jest-haste-map: 29.7.0 @@ -12765,7 +12749,7 @@ packages: cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 glob: 7.2.3 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-mock: 29.7.0 @@ -12783,18 +12767,18 @@ packages: resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.19.0 - '@babel/generator': 7.19.0 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.19.0) - '@babel/plugin-syntax-typescript': 7.17.12(@babel/core@7.19.0) - '@babel/types': 7.19.0 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.28.3) + '@babel/plugin-syntax-typescript': 7.17.12(@babel/core@7.28.3) + '@babel/types': 7.28.2 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.19.0) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.28.3) chalk: 4.1.2 expect: 29.7.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-diff: 29.7.0 jest-get-type: 29.6.3 jest-matcher-utils: 29.7.0 @@ -12802,7 +12786,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.2 + semver: 7.7.2 transitivePeerDependencies: - supports-color dev: true @@ -13201,7 +13185,7 @@ packages: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 @@ -14263,10 +14247,6 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true - /node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - dev: true - /node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} dev: true @@ -14300,7 +14280,7 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} dependencies: hosted-git-info: 7.0.2 - semver: 7.6.2 + semver: 7.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -14437,6 +14417,14 @@ packages: engines: {node: '>= 0.4'} dev: true + /object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + dev: true + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -15010,6 +14998,9 @@ packages: picocolors: 1.1.1 source-map-js: 1.2.1 + /preact@10.27.2: + resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==} + /preferred-pm@3.1.4: resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} engines: {node: '>=10'} @@ -16245,6 +16236,14 @@ packages: engines: {node: '>= 0.8'} dev: true + /stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + dev: true + /storybook@8.6.14(prettier@2.8.8): resolution: {integrity: sha512-sVKbCj/OTx67jhmauhxc2dcr1P+yOgz/x3h0krwjyMgdc5Oubvxyg4NYDZmzAw+ym36g/lzH8N0Ccp4dwtdfxw==} hasBin: true @@ -17042,17 +17041,6 @@ packages: engines: {node: '>=8'} dev: true - /update-browserslist-db@1.0.16(browserslist@4.23.1): - resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.23.1 - escalade: 3.1.2 - picocolors: 1.1.1 - dev: true - /update-browserslist-db@1.1.2(browserslist@4.24.4): resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -17097,16 +17085,15 @@ packages: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} dev: true - /urql@4.0.4(graphql@16.8.1)(react@19.1.1): - resolution: {integrity: sha512-C5P4BMnAsk+rbytCWglit5ijXbIKXsa9wofSGPbuMyJKsDdL+9GfipS362Nff/Caag+eYOK5W+sox8fwEILT6Q==} + /urql@5.0.1(@urql/core@6.0.1)(react@19.1.1): + resolution: {integrity: sha512-r58gYlWvCTC19QvkTaARaCLV9/bp870byH/qbLaw3S7f8i/bC6x2Szub8RVXptiMxWmqq5dyVBjUL9G+xPEuqg==} peerDependencies: + '@urql/core': ^6.0.0 react: '>= 16.8.0' dependencies: - '@urql/core': 4.0.10(graphql@16.8.1) + '@urql/core': 6.0.1(graphql@16.8.1) react: 19.1.1 wonka: 6.3.2 - transitivePeerDependencies: - - graphql dev: false /use-callback-ref@1.3.3(@types/react@19.1.10)(react@19.1.1): @@ -17435,6 +17422,16 @@ packages: is-symbol: 1.0.4 dev: true + /which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + dev: true + /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: true @@ -17447,6 +17444,19 @@ packages: path-exists: 4.0.0 dev: true + /which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + dev: true + /which-typed-array@1.1.9: resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} engines: {node: '>= 0.4'} diff --git a/recordings/useActionFormNested_1974165259/should-record_255246347/recording.har b/recordings/useActionFormNested_1974165259/should-record_255246347/recording.har deleted file mode 100644 index d11c6d485..000000000 --- a/recordings/useActionFormNested_1974165259/should-record_255246347/recording.har +++ /dev/null @@ -1,175 +0,0 @@ -{ - "log": { - "_recordingName": "useActionFormNested/should record", - "creator": { - "comment": "persister:fs", - "name": "Polly.JS", - "version": "6.0.6" - }, - "entries": [ - { - "_id": "facdf77e90ec230c8ef1835f2b48047c", - "_order": 0, - "cache": {}, - "request": { - "bodySize": 577, - "cookies": [], - "headers": [ - { - "_fromType": "array", - "name": "x-gadget-environment", - "value": "Development" - }, - { - "_fromType": "array", - "name": "accept", - "value": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed" - }, - { - "_fromType": "array", - "name": "content-type", - "value": "application/json" - }, - { - "_fromType": "array", - "name": "content-length", - "value": "577" - }, - { - "_fromType": "array", - "name": "user-agent", - "value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" - }, - { - "_fromType": "array", - "name": "accept-encoding", - "value": "gzip,deflate" - }, - { - "_fromType": "array", - "name": "connection", - "value": "close" - }, - { - "name": "host", - "value": "zxcv-deeply-nested--development.gadget.app" - } - ], - "headersSize": 477, - "httpVersion": "HTTP/1.1", - "method": "POST", - "postData": { - "mimeType": "application/json", - "params": [], - "text": "{\"operationName\":\"quizzes\",\"query\":\"query quizzes($after: String, $first: Int, $before: String, $last: Int) {\\n quizzes(after: $after, first: $first, before: $before, last: $last) {\\n pageInfo {\\n hasNextPage\\n hasPreviousPage\\n startCursor\\n endCursor\\n __typename\\n }\\n edges {\\n cursor\\n node {\\n __typename\\n createdAt\\n id\\n text\\n updatedAt\\n }\\n __typename\\n }\\n __typename\\n }\\n gadgetMeta {\\n hydrations(modelName: \\n\\\"quiz\\\")\\n __typename\\n }\\n}\",\"variables\":{}}" - }, - "queryString": [ - { - "name": "operation", - "value": "quizzes" - } - ], - "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=quizzes" - }, - "response": { - "bodySize": 863, - "content": { - "encoding": "base64", - "mimeType": "application/json; charset=utf-8", - "size": 863, - "text": "[\"H4sIAAAAAAAAA7yXTW+bQBCG7/4Vqz03eD/5WCmHyI0qV0qVSM6hqapoBQOhcYDCurUT5b9XkGCDY+SoInuD2Xf2nXkEq52nCUI40kZjhZ4mCCGEf6/Sx0eotgGEcKETmGdx3okhhO909Q3W5lIngBWK9bKCT73VyxL+pPmqGlBURpdmtiqrvMQKYdh8LW5mc3f+a57GV6enuCOFLDokPN/MM9IT3t6aTQGZfqgN8WVb96vguVViiJKmxR/b1F1jCOHwPVUhhLM8gh6UA0VcrdLHXla9fwnaQHRmagEjjJ9QdkK8BQ0UF4oFjueKm/2kNGrU+2ED62abBVQG1Wbouojq3feFq5fwIVPhK8IcQf0b3Ml57vX6tqvzKIGdvqM+yvLCAkvGlWSKMyfw2QBLfpTlexn2zawwvLLGUDJHeHyAoRiZYWtmheG1JYZcEe4Q4g8wlKMy3JlZYfj9wxn6CyqVDOojSvKh79AdieG+mRWGoTWGlDqeJAMMvZEZtmZWGCbWGArPof7Qv+yPzLA1s8Lw3hpD6TrclQMMg5EZtmZWGJ7/3Q==\",\"u5R+BENX0UBJ4QjPG2BIyXgQe252IK6tQGREEeHIYOhApHRMiB03OxA3tiBS3wnE0IlI2cgQW7f/h/j69HM7D77NmOVZBqFJ8+wl79UBJzpKwFxAZ2Sup91NVOpaXPVn5B62z9rAIn2A7sDa63QreDOv9uv70tRwVhTLNGxcm3ImbWtNGoa1gazqlYSXeVK/4TtjikpNp0linGWa3U/rhSmVxCX+1PXDQMSuJH4cxdyNvYiGAXANgvpBzNqhCZtShzB/uXwdS6mrmjz/AwAA//8DAJXEpN1uEAAA\"]" - }, - "cookies": [], - "headers": [ - { - "name": "date", - "value": "Fri, 08 Dec 2023 23:08:12 GMT" - }, - { - "name": "content-type", - "value": "application/json; charset=utf-8" - }, - { - "name": "transfer-encoding", - "value": "chunked" - }, - { - "name": "connection", - "value": "close" - }, - { - "name": "vary", - "value": "Origin" - }, - { - "name": "access-control-allow-credentials", - "value": "true" - }, - { - "name": "access-control-expose-headers", - "value": "x-set-authorization, x-gadget-environment" - }, - { - "name": "x-rate-limit-remaining", - "value": "2249" - }, - { - "name": "x-request-id", - "value": "98771173d73b8944fd300454698c005a" - }, - { - "name": "x-trace-id", - "value": "68c94f6508fdf36f7d1c9e3ae4189f23" - }, - { - "name": "strict-transport-security", - "value": "max-age=15724800; includeSubDomains" - }, - { - "name": "cf-cache-status", - "value": "DYNAMIC" - }, - { - "name": "report-to", - "value": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=qcIht0KmyR3KGt6FwEAY6ZH%2FNcHRSKUl6DZnF%2BzTS802dLy%2FmrjKbpOhDXGcqnF0lrYZ4w%2B5Sac63zkARF35XLIa2P2ZPHlgU6Mm8JF3HKNUKl%2BD%2FDlRNsylJmI3NU9F2ZbQ5szVWgFx%2BvOzcjSFqgL1%2FGVjP%2BCss5%2FhOw%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" - }, - { - "name": "nel", - "value": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}" - }, - { - "name": "server", - "value": "cloudflare" - }, - { - "name": "cf-ray", - "value": "8328ab3f7e5c0c76-EWR" - }, - { - "name": "content-encoding", - "value": "gzip" - } - ], - "headersSize": 926, - "httpVersion": "HTTP/1.1", - "redirectURL": "", - "status": 200, - "statusText": "OK" - }, - "startedDateTime": "2023-12-08T23:08:11.960Z", - "time": 253, - "timings": { - "blocked": -1, - "connect": -1, - "dns": -1, - "receive": 0, - "send": 0, - "ssl": -1, - "wait": 253 - } - } - ], - "pages": [], - "version": "1.2" - } -} diff --git a/recordings/useActionFormNested_1974165259/update-HasMany-HasMany-HasOne-HasOne-BelongsTo_2250701809/recording.har b/recordings/useActionFormNested_1974165259/update-HasMany-HasMany-HasOne-HasOne-BelongsTo_2250701809/recording.har deleted file mode 100644 index 810f51572..000000000 --- a/recordings/useActionFormNested_1974165259/update-HasMany-HasMany-HasOne-HasOne-BelongsTo_2250701809/recording.har +++ /dev/null @@ -1,175 +0,0 @@ -{ - "log": { - "_recordingName": "useActionFormNested/update HasMany -> HasMany -> HasOne -> HasOne -> BelongsTo", - "creator": { - "comment": "persister:fs", - "name": "Polly.JS", - "version": "6.0.6" - }, - "entries": [ - { - "_id": "b69a62c6ea7ef9f7b0bc26035cbe7636", - "_order": 0, - "cache": {}, - "request": { - "bodySize": 254, - "cookies": [], - "headers": [ - { - "_fromType": "array", - "name": "x-gadget-environment", - "value": "Development" - }, - { - "_fromType": "array", - "name": "accept", - "value": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed" - }, - { - "_fromType": "array", - "name": "content-type", - "value": "application/json" - }, - { - "_fromType": "array", - "name": "content-length", - "value": "254" - }, - { - "_fromType": "array", - "name": "user-agent", - "value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)" - }, - { - "_fromType": "array", - "name": "accept-encoding", - "value": "gzip,deflate" - }, - { - "_fromType": "array", - "name": "connection", - "value": "close" - }, - { - "name": "host", - "value": "zxcv-deeply-nested--development.gadget.app" - } - ], - "headersSize": 474, - "httpVersion": "HTTP/1.1", - "method": "POST", - "postData": { - "mimeType": "application/json", - "params": [], - "text": "{\"operationName\":\"quiz\",\"query\":\"query quiz($id: GadgetID!) {\\n quiz(id: $id) {\\n __typename\\n createdAt\\n id\\n text\\n updatedAt\\n }\\n gadgetMeta {\\n hydrations(modelName: \\n\\\"quiz\\\")\\n __typename\\n }\\n}\",\"variables\":{\"id\":\"12\"}}" - }, - "queryString": [ - { - "name": "operation", - "value": "quiz" - } - ], - "url": "https://zxcv-deeply-nested--development.gadget.app/api/graphql?operation=quiz" - }, - "response": { - "bodySize": 376, - "content": { - "encoding": "base64", - "mimeType": "application/json; charset=utf-8", - "size": 376, - "text": "[\"H4sIAAAAAAAAA4yQT2+DMAzF73wKy+e2JIEgyq3SpGmHHSZx2qXKiEejUcrAldpVfPeJlNE/mrQd7fee/bNPAQBawwYzOAUAAPi5d19TBYDrNR8bqs2WMAN8GdTZj1a0ZJjsigdJCRXNpZqLNJdJpkQm08UyTl8vdmcHn1SXDtPBZ3PqGG5n7xv7j9ne3Z9DWBpbEj/T1TkAuDna1rDb1d1V9x7+wTDlbkvT/nuCyTDq/ez3Bz16hlXTVK7wWz3OGTMYY0gHprq7QcJqVw4VbpibLgvDsuRF5eqPcBBCqUUi0tAUIopFQklCkdbC6jf9rq2KYquWUpEc8ZFbU9CTf/efkYEq6L8BAAD//wMAaQMtbwoCAAA=\"]" - }, - "cookies": [], - "headers": [ - { - "name": "date", - "value": "Fri, 08 Dec 2023 23:08:12 GMT" - }, - { - "name": "content-type", - "value": "application/json; charset=utf-8" - }, - { - "name": "transfer-encoding", - "value": "chunked" - }, - { - "name": "connection", - "value": "close" - }, - { - "name": "vary", - "value": "Origin" - }, - { - "name": "access-control-allow-credentials", - "value": "true" - }, - { - "name": "access-control-expose-headers", - "value": "x-set-authorization, x-gadget-environment" - }, - { - "name": "x-rate-limit-remaining", - "value": "2249" - }, - { - "name": "x-request-id", - "value": "45a76658d67c40c366dc64f4b179d2ce" - }, - { - "name": "x-trace-id", - "value": "ac03406e66e3550d5b5f5d234d2912e1" - }, - { - "name": "strict-transport-security", - "value": "max-age=15724800; includeSubDomains" - }, - { - "name": "cf-cache-status", - "value": "DYNAMIC" - }, - { - "name": "report-to", - "value": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=XTYTLlWvgpdf%2FElO8SDIqjlxKjWZhMXDiozcg%2F1MvQ7Hadl9SSe%2BFARM%2BqB%2F5XP6iRUBr38dMzD0NGIeB8X9qf75o36TmyRNwMc9qc7T%2FM2rKKvUdEn%2Ffv9V45t3PYyGhAqsiKr65lbAXubov0M2wzBpTXFEth1d3d7MwA%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" - }, - { - "name": "nel", - "value": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}" - }, - { - "name": "server", - "value": "cloudflare" - }, - { - "name": "cf-ray", - "value": "8328ab411dee41e3-EWR" - }, - { - "name": "content-encoding", - "value": "gzip" - } - ], - "headersSize": 920, - "httpVersion": "HTTP/1.1", - "redirectURL": "", - "status": 200, - "statusText": "OK" - }, - "startedDateTime": "2023-12-08T23:08:12.269Z", - "time": 287, - "timings": { - "blocked": -1, - "connect": -1, - "dns": -1, - "receive": 0, - "send": 0, - "ssl": -1, - "wait": 287 - } - } - ], - "pages": [], - "version": "1.2" - } -}