From 4ee8536004fa00824fe4fbcdaffa71f150439496 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 17:00:25 -0700 Subject: [PATCH 01/23] feat: Add SeamQueryProvider --- src/lib/index.ts | 1 + src/lib/seam/SeamProvider.tsx | 211 ++++--------------------- src/lib/seam/SeamQueryProvider.tsx | 241 +++++++++++++++++++++++++++++ src/lib/seam/use-seam-client.ts | 4 +- src/lib/telemetry/hooks.ts | 4 +- 5 files changed, 274 insertions(+), 187 deletions(-) create mode 100644 src/lib/seam/SeamQueryProvider.tsx diff --git a/src/lib/index.ts b/src/lib/index.ts index 4842d5b89..71a7d02f1 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,4 @@ export * from './seam/components/index.js' export * from './seam/index.js' export * from './seam/SeamProvider.js' +export * from './seam/SeamQueryProvider.js' diff --git a/src/lib/seam/SeamProvider.tsx b/src/lib/seam/SeamProvider.tsx index 8c60fe9c5..22a4ffee9 100644 --- a/src/lib/seam/SeamProvider.tsx +++ b/src/lib/seam/SeamProvider.tsx @@ -1,16 +1,13 @@ -import type { - SeamHttp, - SeamHttpOptionsWithClientSessionToken, -} from '@seamapi/http/connect' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { - createContext, - type PropsWithChildren, - useContext, - useEffect, - useMemo, -} from 'react' +import type { SeamHttp } from '@seamapi/http/connect' +import type { QueryClient } from '@tanstack/react-query' +import { createContext, type PropsWithChildren, useMemo } from 'react' +import { + SeamQueryProvider, + type SeamQueryProviderPropsWithClient, + type SeamQueryProviderPropsWithClientSessionToken, + type SeamQueryProviderPropsWithPublishableKey, +} from 'lib/seam/SeamQueryProvider.js' import { useSeamFont } from 'lib/seam/use-seam-font.js' import { useSeamStyles } from 'lib/seam/use-seam-styles.js' import { @@ -19,8 +16,6 @@ import { useUserTelemetry, } from 'lib/telemetry/index.js' -import { useSeamClient } from './use-seam-client.js' - declare global { // eslint-disable-next-line no-var var seam: SeamProviderProps | undefined @@ -30,33 +25,28 @@ declare global { var seamTelemetryClient: TelemetryClient | undefined } -export interface SeamContext { - client: SeamHttp | null - clientOptions?: SeamProviderClientOptions | undefined - publishableKey?: string | undefined - userIdentifierKey?: string | undefined - clientSessionToken?: string | undefined -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SeamContext {} export type SeamProviderProps = | SeamProviderPropsWithClient | SeamProviderPropsWithPublishableKey | SeamProviderPropsWithClientSessionToken -export interface SeamProviderPropsWithClient extends SeamProviderBaseProps { - client: SeamHttp -} +export interface SeamProviderPropsWithClient + extends SeamQueryProviderPropsWithClient, + SeamProviderBaseProps {} export interface SeamProviderPropsWithPublishableKey extends SeamProviderBaseProps, - SeamProviderClientOptions { + SeamQueryProviderPropsWithPublishableKey { publishableKey: string userIdentifierKey?: string } export interface SeamProviderPropsWithClientSessionToken extends SeamProviderBaseProps, - SeamProviderClientOptions { + SeamQueryProviderPropsWithClientSessionToken { clientSessionToken: string } @@ -70,12 +60,6 @@ interface SeamProviderBaseProps extends PropsWithChildren { onSessionUpdate?: (client: SeamHttp) => void } -type SeamClientOptions = SeamHttpOptionsWithClientSessionToken - -export type SeamProviderClientOptions = Pick - -const defaultQueryClient = new QueryClient() - export const seamComponentsClassName = 'seam-components' export function SeamProvider({ @@ -84,40 +68,20 @@ export function SeamProvider({ disableCssInjection = false, disableFontInjection = false, unminifiyCss = false, - onSessionUpdate = () => {}, - queryClient, telemetryClient, ...props }: SeamProviderProps): JSX.Element { useSeamStyles({ disabled: disableCssInjection, unminified: unminifiyCss }) useSeamFont({ disabled: disableFontInjection }) + const { Provider } = seamContext + + const endpoint = 'endpoint' in props ? props.endpoint : undefined const value = useMemo(() => { const context = createSeamContextValue(props) - if ( - context.client == null && - context.publishableKey == null && - context.clientSessionToken == null - ) { - return defaultSeamContextValue - } return context }, [props]) - if ( - value.client == null && - value.publishableKey == null && - value.clientSessionToken == null - ) { - throw new Error( - `Must provide either a Seam client, clientSessionToken, or a publishableKey.` - ) - } - - const { Provider } = seamContext - - const endpoint = 'endpoint' in props ? props.endpoint : undefined - return (
- + - {children} + {children} - +
) } -function Wrapper({ - onSessionUpdate, - children, -}: Required> & - PropsWithChildren): JSX.Element | null { +function Telemetry({ children }: PropsWithChildren): JSX.Element | null { useUserTelemetry() - - const { client } = useSeamClient() - useEffect(() => { - if (client != null) onSessionUpdate(client) - }, [onSessionUpdate, client]) - return <>{children} } const createDefaultSeamContextValue = (): SeamContext => { try { if (globalThis.seam == null) { - return { client: null } + return {} } return createSeamContextValue(globalThis.seam) } catch (err) { // eslint-disable-next-line no-console console.warn(err) - return { client: null } + return {} } } -const createSeamContextValue = (options: SeamProviderProps): SeamContext => { - if (isSeamProviderPropsWithClient(options)) { - return options - } - - if (isSeamProviderPropsWithClientSessionToken(options)) { - const { clientSessionToken, ...clientOptions } = options - return { - clientSessionToken, - clientOptions, - client: null, - } - } - - if (isSeamProviderPropsWithPublishableKey(options)) { - const { publishableKey, userIdentifierKey, ...clientOptions } = options - return { - publishableKey, - userIdentifierKey, - clientOptions, - client: null, - } - } - - return { client: null } +const createSeamContextValue = (_options: SeamProviderProps): SeamContext => { + return {} } const defaultSeamContextValue = createDefaultSeamContextValue() -export const seamContext = createContext(defaultSeamContextValue) - -export function useSeamContext(): SeamContext { - return useContext(seamContext) -} - -const isSeamProviderPropsWithClient = ( - props: SeamProviderProps -): props is SeamProviderPropsWithClient => { - if (!('client' in props)) return false - - const { client, ...otherProps } = props - if (client == null) return false - - const otherNonNullProps = Object.values(otherProps).filter((v) => v != null) - if (otherNonNullProps.length > 0) { - throw new InvalidSeamProviderProps( - `The client prop cannot be used with ${otherNonNullProps.join(' or ')}.` - ) - } - - return true -} - -const isSeamProviderPropsWithPublishableKey = ( - props: SeamProviderProps -): props is SeamProviderPropsWithPublishableKey & SeamProviderClientOptions => { - if (!('publishableKey' in props)) return false - - const { publishableKey } = props - if (publishableKey == null) return false - - if ('client' in props && props.client != null) { - throw new InvalidSeamProviderProps( - 'The client prop cannot be used with the publishableKey prop.' - ) - } - - if ('clientSessionToken' in props && props.clientSessionToken != null) { - throw new InvalidSeamProviderProps( - 'The clientSessionToken prop cannot be used with the publishableKey prop.' - ) - } - - return true -} - -const isSeamProviderPropsWithClientSessionToken = ( - props: SeamProviderProps -): props is SeamProviderPropsWithClientSessionToken & - SeamProviderClientOptions => { - if (!('clientSessionToken' in props)) return false - - const { clientSessionToken } = props - if (clientSessionToken == null) return false - - if ('client' in props && props.client != null) { - throw new InvalidSeamProviderProps( - 'The client prop cannot be used with the clientSessionToken prop.' - ) - } - - if ('publishableKey' in props && props.publishableKey != null) { - throw new InvalidSeamProviderProps( - 'The publishableKey prop cannot be used with the clientSessionToken prop.' - ) - } - - if ('userIdentifierKey' in props && props.userIdentifierKey != null) { - throw new InvalidSeamProviderProps( - 'The userIdentifierKey prop cannot be used with the clientSessionToken prop.' - ) - } - - return true -} - -class InvalidSeamProviderProps extends Error { - constructor(message: string) { - super(`SeamProvider received invalid props: ${message}`) - this.name = this.constructor.name - } -} +const seamContext = createContext(defaultSeamContextValue) diff --git a/src/lib/seam/SeamQueryProvider.tsx b/src/lib/seam/SeamQueryProvider.tsx new file mode 100644 index 000000000..b65b456c3 --- /dev/null +++ b/src/lib/seam/SeamQueryProvider.tsx @@ -0,0 +1,241 @@ +import type { + SeamHttp, + SeamHttpOptionsWithClientSessionToken, +} from '@seamapi/http/connect' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { + createContext, + type PropsWithChildren, + useContext, + useEffect, + useMemo, +} from 'react' + +import { useSeamClient } from './use-seam-client.js' + +export interface SeamQueryContext { + client: SeamHttp | null + clientOptions?: SeamQueryProviderClientOptions | undefined + publishableKey?: string | undefined + userIdentifierKey?: string | undefined + clientSessionToken?: string | undefined +} + +export type SeamQueryProviderProps = + | SeamQueryProviderPropsWithClient + | SeamQueryProviderPropsWithPublishableKey + | SeamQueryProviderPropsWithClientSessionToken + +export interface SeamQueryProviderPropsWithClient + extends SeamQueryProviderBaseProps { + client: SeamHttp +} + +export interface SeamQueryProviderPropsWithPublishableKey + extends SeamQueryProviderBaseProps, + SeamQueryProviderClientOptions { + publishableKey: string + userIdentifierKey?: string +} + +export interface SeamQueryProviderPropsWithClientSessionToken + extends SeamQueryProviderBaseProps, + SeamQueryProviderClientOptions { + clientSessionToken: string +} + +interface SeamQueryProviderBaseProps extends PropsWithChildren { + queryClient?: QueryClient | undefined + onSessionUpdate?: (client: SeamHttp) => void +} + +type SeamClientOptions = SeamHttpOptionsWithClientSessionToken + +export type SeamQueryProviderClientOptions = Pick + +const defaultQueryClient = new QueryClient() + +export function SeamQueryProvider({ + children, + onSessionUpdate = () => {}, + queryClient, + ...props +}: SeamQueryProviderProps): JSX.Element { + const value = useMemo(() => { + const context = createSeamQueryContextValue(props) + if ( + context.client == null && + context.publishableKey == null && + context.clientSessionToken == null + ) { + return defaultSeamQueryContextValue + } + return context + }, [props]) + + if ( + value.client == null && + value.publishableKey == null && + value.clientSessionToken == null + ) { + throw new Error( + `Must provide either a Seam client, clientSessionToken, or a publishableKey.` + ) + } + + const { Provider } = seamContext + + return ( + + + {children} + + + ) +} + +function Session({ + onSessionUpdate, + children, +}: Required> & + PropsWithChildren): JSX.Element | null { + const { client } = useSeamClient() + useEffect(() => { + if (client != null) onSessionUpdate(client) + }, [onSessionUpdate, client]) + + return <>{children} +} + +const createDefaultSeamQueryContextValue = (): SeamQueryContext => { + try { + if (globalThis.seam == null) { + return { client: null } + } + return createSeamQueryContextValue(globalThis.seam) + } catch (err) { + // eslint-disable-next-line no-console + console.warn(err) + return { client: null } + } +} + +const createSeamQueryContextValue = ( + options: SeamQueryProviderProps +): SeamQueryContext => { + if (isSeamQueryProviderPropsWithClient(options)) { + return options + } + + if (isSeamQueryProviderPropsWithClientSessionToken(options)) { + const { clientSessionToken, ...clientOptions } = options + return { + clientSessionToken, + clientOptions, + client: null, + } + } + + if (isSeamQueryProviderPropsWithPublishableKey(options)) { + const { publishableKey, userIdentifierKey, ...clientOptions } = options + return { + publishableKey, + userIdentifierKey, + clientOptions, + client: null, + } + } + + return { client: null } +} + +const defaultSeamQueryContextValue = createDefaultSeamQueryContextValue() + +export const seamContext = createContext( + defaultSeamQueryContextValue +) + +export function useSeamQueryContext(): SeamQueryContext { + return useContext(seamContext) +} + +const isSeamQueryProviderPropsWithClient = ( + props: SeamQueryProviderProps +): props is SeamQueryProviderPropsWithClient => { + if (!('client' in props)) return false + + const { client, ...otherProps } = props + if (client == null) return false + + const otherNonNullProps = Object.values(otherProps).filter((v) => v != null) + if (otherNonNullProps.length > 0) { + throw new InvalidSeamQueryProviderProps( + `The client prop cannot be used with ${otherNonNullProps.join(' or ')}.` + ) + } + + return true +} + +const isSeamQueryProviderPropsWithPublishableKey = ( + props: SeamQueryProviderProps +): props is SeamQueryProviderPropsWithPublishableKey & + SeamQueryProviderClientOptions => { + if (!('publishableKey' in props)) return false + + const { publishableKey } = props + if (publishableKey == null) return false + + if ('client' in props && props.client != null) { + throw new InvalidSeamQueryProviderProps( + 'The client prop cannot be used with the publishableKey prop.' + ) + } + + if ('clientSessionToken' in props && props.clientSessionToken != null) { + throw new InvalidSeamQueryProviderProps( + 'The clientSessionToken prop cannot be used with the publishableKey prop.' + ) + } + + return true +} + +const isSeamQueryProviderPropsWithClientSessionToken = ( + props: SeamQueryProviderProps +): props is SeamQueryProviderPropsWithClientSessionToken & + SeamQueryProviderClientOptions => { + if (!('clientSessionToken' in props)) return false + + const { clientSessionToken } = props + if (clientSessionToken == null) return false + + if ('client' in props && props.client != null) { + throw new InvalidSeamQueryProviderProps( + 'The client prop cannot be used with the clientSessionToken prop.' + ) + } + + if ('publishableKey' in props && props.publishableKey != null) { + throw new InvalidSeamQueryProviderProps( + 'The publishableKey prop cannot be used with the clientSessionToken prop.' + ) + } + + if ('userIdentifierKey' in props && props.userIdentifierKey != null) { + throw new InvalidSeamQueryProviderProps( + 'The userIdentifierKey prop cannot be used with the clientSessionToken prop.' + ) + } + + return true +} + +class InvalidSeamQueryProviderProps extends Error { + constructor(message: string) { + super(`SeamQueryProvider received invalid props: ${message}`) + this.name = this.constructor.name + } +} diff --git a/src/lib/seam/use-seam-client.ts b/src/lib/seam/use-seam-client.ts index 2446e777a..cf3f160cb 100644 --- a/src/lib/seam/use-seam-client.ts +++ b/src/lib/seam/use-seam-client.ts @@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query' import { useEffect } from 'react' import { v4 as uuidv4 } from 'uuid' -import { useSeamContext } from 'lib/seam/SeamProvider.js' +import { useSeamQueryContext } from './SeamQueryProvider.js' export function useSeamClient(): { client: SeamHttp | null @@ -17,7 +17,7 @@ export function useSeamClient(): { publishableKey, clientSessionToken, ...context - } = useSeamContext() + } = useSeamQueryContext() const userIdentifierKey = useUserIdentifierKeyOrFingerprint( clientSessionToken != null ? '' : context.userIdentifierKey ) diff --git a/src/lib/telemetry/hooks.ts b/src/lib/telemetry/hooks.ts index 469595807..7a0c403e7 100644 --- a/src/lib/telemetry/hooks.ts +++ b/src/lib/telemetry/hooks.ts @@ -1,7 +1,7 @@ import { useEffect, useLayoutEffect } from 'react' import { useClientSession } from 'lib/seam/client-sessions/use-client-session.js' -import { useSeamContext } from 'lib/seam/SeamProvider.js' +import { useSeamQueryContext } from 'lib/seam/SeamQueryProvider.js' import type { TelemetryClient } from './client.js' import { useTelemetryContext } from './TelemetryProvider.js' @@ -23,7 +23,7 @@ export function useComponentTelemetry(name: string): void { export function useUserTelemetry(): void { const telemetry = useTelemetryClient() - const { publishableKey } = useSeamContext() + const { publishableKey } = useSeamQueryContext() const { clientSession } = useClientSession() // Ensure identify runs earlier than other effects to avoid anonymous telemetry data. From 9b136e4e9c6fbf201ad77e2975e437c3c722ebb6 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 17:04:35 -0700 Subject: [PATCH 02/23] Update message to refer to SeamQueryProvider --- src/lib/seam/use-seam-client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/use-seam-client.ts b/src/lib/seam/use-seam-client.ts index cf3f160cb..ad194737d 100644 --- a/src/lib/seam/use-seam-client.ts +++ b/src/lib/seam/use-seam-client.ts @@ -65,7 +65,7 @@ export class NullSeamClientError extends Error { super( [ 'Attempted to use a null Seam client.', - 'Either a hook using useSeamClient was called outside of a SeamProvider,', + 'Either a hook using useSeamClient was called outside of a SeamProvider or SeamQueryProvider,', 'or there was an error when creating the Seam client in useSeamClient,', 'or useSeamClient is still loading the client.', ].join(' ') @@ -81,7 +81,7 @@ function useUserIdentifierKeyOrFingerprint( useEffect(() => { if (userIdentifierKey != null) return // eslint-disable-next-line no-console - console.warn(`Using an automatically generated fingerprint for the SeamProvider userIdentifierKey! + console.warn(`Using an automatically generated fingerprint for the Seam userIdentifierKey! The user interface will show warnings when using a fingerprint. This is not recommended because the client session is now bound to this machine and is effectively ephemeral.`) }, [userIdentifierKey]) From ef74e12db2b366cba0be01f4d18aee870514fd91 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 17:12:50 -0700 Subject: [PATCH 03/23] Move @seamapi/http to peerDependencies --- package-lock.json | 64 ++++++++++--------- package.json | 3 +- .../use-generate-access-code-code.ts | 4 +- .../use-delete-thermostat-climate-preset.ts | 4 +- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6af15a715..ec1fb2126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@floating-ui/react": "^0.27.5", - "@seamapi/http": "^1.30.2", "@tanstack/react-query": "^5.27.5", "classnames": "^2.3.2", "luxon": "^3.3.0", @@ -27,6 +26,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", + "@seamapi/http": "^1.37.0", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", @@ -84,6 +84,7 @@ "npm": ">= 9.0.0" }, "peerDependencies": { + "@seamapi/http": "^1.37.0", "@seamapi/types": "^1.395.3", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", @@ -5872,22 +5873,22 @@ } }, "node_modules/@seamapi/http": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.30.2.tgz", - "integrity": "sha512-0kb1Y/d5ifXxQLLLMZ072wQCc23mmpXlpVquK3B3asFCvphCCXEE4ksFDDETEWgY+wWwPrq51GAqvHRs3rhGGg==", + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.37.0.tgz", + "integrity": "sha512-IzKOylzhY88YjrDmL3sGEEmCdIo/+cI8xTlujHteGbe1MbDx4BKE7n23bOS/Fe+S9mxrkTuIzez1zkPzttiRCQ==", + "dev": true, "license": "MIT", "dependencies": { - "@seamapi/url-search-params-serializer": "^1.2.0", - "axios": "^1.5.0", - "axios-better-stacktrace": "^2.1.7", + "@seamapi/url-search-params-serializer": "^2.0.0-beta.2", + "axios": "^1.9.0", "axios-retry": "^4.4.2" }, "engines": { - "node": ">=18.12.0", - "npm": ">= 9.0.0" + "node": ">=20.9.0", + "npm": ">=10.1.0" }, "peerDependencies": { - "@seamapi/types": "^1.384.0" + "@seamapi/types": "^1.420.2" }, "peerDependenciesMeta": { "@seamapi/types": { @@ -5896,10 +5897,10 @@ } }, "node_modules/@seamapi/types": { - "version": "1.395.3", - "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.395.3.tgz", - "integrity": "sha512-HfqkuV/au/9V/XoBNZCv2sx6QuRdms68bC60/W23AdZ19h2p7fOHaUkc4DKJy8lvzTdr/499B8D6VCdRaTZSKw==", - "devOptional": true, + "version": "1.424.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.424.0.tgz", + "integrity": "sha512-neg4BsO0Aw+DWknPOS4v75Mee++il+3xMFVB8F2kOx8BY4hjTTZGgFD6nyb6kNDtrHhu3L7TiXfp50yPqCO2xw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12.0", @@ -5910,9 +5911,10 @@ } }, "node_modules/@seamapi/url-search-params-serializer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@seamapi/url-search-params-serializer/-/url-search-params-serializer-1.3.0.tgz", - "integrity": "sha512-SyS2ioYQx/WlOvcWK1le7iCmGGIIFiLPg5edXyOYEFPiItZVYvQpy1PjafTD1WNFrEaHShbQQo33IllYr7kOeg==", + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@seamapi/url-search-params-serializer/-/url-search-params-serializer-2.0.0-beta.2.tgz", + "integrity": "sha512-ASHYo5/0IY7iB/cWcZA9b6meAa5b22NfEZyIuZQ2I90L7WeKzeWxEmusA4nfc26giQOeuEF1xAIGSdGrT+lGZg==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12.0", @@ -11078,6 +11080,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -11097,9 +11100,10 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -11107,19 +11111,11 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/axios-better-stacktrace": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/axios-better-stacktrace/-/axios-better-stacktrace-2.1.7.tgz", - "integrity": "sha512-m16wNbfb7crBpENBukoBdN1G9NwqSCkuIeKjSEP2iUoFvgNUnSW1/1Ov79EkTu29xmg+TsngJcy2lfwqBzVT7g==", - "license": "MIT", - "peerDependencies": { - "axios": "*" - } - }, "node_modules/axios-retry": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "is-retry-allowed": "^2.2.0" @@ -12226,6 +12222,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -13228,6 +13225,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -16230,6 +16228,7 @@ "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, "funding": [ { "type": "individual", @@ -16428,6 +16427,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -18085,6 +18085,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -19574,6 +19575,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -19583,6 +19585,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -21240,6 +21243,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, "license": "MIT" }, "node_modules/prr": { @@ -27535,7 +27539,7 @@ "version": "3.24.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index d1e1dc443..846ecb09a 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "npm": ">= 9.0.0" }, "peerDependencies": { + "@seamapi/http": "^1.37.0", "@seamapi/types": "^1.395.3", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", @@ -128,7 +129,6 @@ }, "dependencies": { "@floating-ui/react": "^0.27.5", - "@seamapi/http": "^1.30.2", "@tanstack/react-query": "^5.27.5", "classnames": "^2.3.2", "luxon": "^3.3.0", @@ -145,6 +145,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", + "@seamapi/http": "^1.37.0", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", diff --git a/src/lib/seam/access-codes/use-generate-access-code-code.ts b/src/lib/seam/access-codes/use-generate-access-code-code.ts index 6820718ef..8ad7f5bce 100644 --- a/src/lib/seam/access-codes/use-generate-access-code-code.ts +++ b/src/lib/seam/access-codes/use-generate-access-code-code.ts @@ -1,5 +1,5 @@ import type { - AccessCodesGenerateCodeBody, + AccessCodesGenerateCodeParams, SeamHttpApiError, } from '@seamapi/http/connect' import { useMutation, type UseMutationResult } from '@tanstack/react-query' @@ -11,7 +11,7 @@ export type UseGenerateAccessCodeCodeParams = never export type UseGenerateAccessCodeCodeData = string export type UseGenerateAccessCodeCodeMutationVariables = - AccessCodesGenerateCodeBody + AccessCodesGenerateCodeParams export function useGenerateAccessCodeCode(): UseMutationResult< UseGenerateAccessCodeCodeData, diff --git a/src/lib/seam/thermostats/use-delete-thermostat-climate-preset.ts b/src/lib/seam/thermostats/use-delete-thermostat-climate-preset.ts index 9181d7f95..3af8315e1 100644 --- a/src/lib/seam/thermostats/use-delete-thermostat-climate-preset.ts +++ b/src/lib/seam/thermostats/use-delete-thermostat-climate-preset.ts @@ -1,6 +1,6 @@ import type { SeamHttpApiError, - ThermostatsDeleteClimatePresetBody, + ThermostatsDeleteClimatePresetParams, } from '@seamapi/http/connect' import { useMutation, @@ -16,7 +16,7 @@ export type UseDeleteThermostatClimatePresetParams = never export type UseDeleteThermostatClimatePresetData = undefined export type UseDeleteThermostatClimatePresetVariables = - ThermostatsDeleteClimatePresetBody + ThermostatsDeleteClimatePresetParams export function useDeleteThermostatClimatePreset(): UseMutationResult< UseDeleteThermostatClimatePresetData, From 302082dc07a558a6460fa48d08d3e88d34547f30 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 17:32:31 -0700 Subject: [PATCH 04/23] Add endpointClient to SeamQueryContext --- src/lib/seam/SeamQueryProvider.tsx | 15 +++++++++++---- src/lib/seam/use-seam-client.ts | 31 ++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/lib/seam/SeamQueryProvider.tsx b/src/lib/seam/SeamQueryProvider.tsx index b65b456c3..41759ba68 100644 --- a/src/lib/seam/SeamQueryProvider.tsx +++ b/src/lib/seam/SeamQueryProvider.tsx @@ -1,5 +1,6 @@ import type { SeamHttp, + SeamHttpEndpoints, SeamHttpOptionsWithClientSessionToken, } from '@seamapi/http/connect' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' @@ -15,6 +16,7 @@ import { useSeamClient } from './use-seam-client.js' export interface SeamQueryContext { client: SeamHttp | null + endpointClient: SeamHttpEndpoints | null clientOptions?: SeamQueryProviderClientOptions | undefined publishableKey?: string | undefined userIdentifierKey?: string | undefined @@ -112,13 +114,13 @@ function Session({ const createDefaultSeamQueryContextValue = (): SeamQueryContext => { try { if (globalThis.seam == null) { - return { client: null } + return { client: null, endpointClient: null } } return createSeamQueryContextValue(globalThis.seam) } catch (err) { // eslint-disable-next-line no-console console.warn(err) - return { client: null } + return { client: null, endpointClient: null } } } @@ -126,7 +128,10 @@ const createSeamQueryContextValue = ( options: SeamQueryProviderProps ): SeamQueryContext => { if (isSeamQueryProviderPropsWithClient(options)) { - return options + return { + ...options, + endpointClient: null, + } } if (isSeamQueryProviderPropsWithClientSessionToken(options)) { @@ -135,6 +140,7 @@ const createSeamQueryContextValue = ( clientSessionToken, clientOptions, client: null, + endpointClient: null, } } @@ -145,10 +151,11 @@ const createSeamQueryContextValue = ( userIdentifierKey, clientOptions, client: null, + endpointClient: null, } } - return { client: null } + return { client: null, endpointClient: null } } const defaultSeamQueryContextValue = createDefaultSeamQueryContextValue() diff --git a/src/lib/seam/use-seam-client.ts b/src/lib/seam/use-seam-client.ts index ad194737d..49fe995ee 100644 --- a/src/lib/seam/use-seam-client.ts +++ b/src/lib/seam/use-seam-client.ts @@ -1,4 +1,4 @@ -import { SeamHttp } from '@seamapi/http/connect' +import { SeamHttp, SeamHttpEndpoints } from '@seamapi/http/connect' import { useQuery } from '@tanstack/react-query' import { useEffect } from 'react' import { v4 as uuidv4 } from 'uuid' @@ -7,6 +7,7 @@ import { useSeamQueryContext } from './SeamQueryProvider.js' export function useSeamClient(): { client: SeamHttp | null + endpointClient: SeamHttpEndpoints | null isPending: boolean isError: boolean error: unknown @@ -22,7 +23,9 @@ export function useSeamClient(): { clientSessionToken != null ? '' : context.userIdentifierKey ) - const { isPending, isError, error, data } = useQuery({ + const { isPending, isError, error, data } = useQuery< + [SeamHttp, SeamHttpEndpoints] + >({ queryKey: [ 'client', { @@ -34,13 +37,19 @@ export function useSeamClient(): { }, ], queryFn: async () => { - if (client != null) return client + if (client != null) + return [client, SeamHttpEndpoints.fromClient(client.client)] if (clientSessionToken != null) { - return SeamHttp.fromClientSessionToken( + const clientSessionTokenClient = SeamHttp.fromClientSessionToken( clientSessionToken, clientOptions ) + + return [ + clientSessionTokenClient, + SeamHttpEndpoints.fromClient(clientSessionTokenClient.client), + ] } if (publishableKey == null) { @@ -49,15 +58,25 @@ export function useSeamClient(): { ) } - return await SeamHttp.fromPublishableKey( + const publishableKeyClient = await SeamHttp.fromPublishableKey( publishableKey, userIdentifierKey, clientOptions ) + return [ + publishableKeyClient, + SeamHttpEndpoints.fromClient(publishableKeyClient.client), + ] }, }) - return { client: data ?? null, isPending, isError, error } + return { + client: data?.[0] ?? null, + endpointClient: data?.[1] ?? null, + isPending, + isError, + error, + } } export class NullSeamClientError extends Error { From 3bda3f4d0fba53151fbd3141e2a3b5911701306e Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 18:06:42 -0700 Subject: [PATCH 05/23] feat: Add useSeamQuery --- src/lib/seam/index.ts | 1 + src/lib/seam/use-seam-query.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/lib/seam/use-seam-query.ts diff --git a/src/lib/seam/index.ts b/src/lib/seam/index.ts index 091e8517d..6bfff8d33 100644 --- a/src/lib/seam/index.ts +++ b/src/lib/seam/index.ts @@ -12,4 +12,5 @@ export * from './devices/use-device-providers.js' export * from './devices/use-devices.js' export * from './SeamProvider.js' export * from './use-seam-client.js' +export * from './use-seam-query.js' export * from './use-seam-query-result.js' diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts new file mode 100644 index 000000000..25fb0e9d5 --- /dev/null +++ b/src/lib/seam/use-seam-query.ts @@ -0,0 +1,27 @@ +import type { SeamHttpApiError, SeamHttpEndpoints } from '@seamapi/http/connect' +import { useQuery, type UseQueryResult } from '@tanstack/react-query' + +import { useSeamClient } from 'lib/seam/use-seam-client.js' + +type Endpoints = Omit< + SeamHttpEndpoints, + Exclude +> + +export function useSeamQuery( + endpointPath: T, + params?: Parameters[0], + options?: Parameters[1] +): UseQueryResult>, SeamHttpApiError> { + const { endpointClient: client } = useSeamClient() + return useQuery({ + enabled: client != null, + queryKey: [endpointPath, params], + queryFn: async () => { + if (client == null) return null + const endpoint = client[endpointPath] as Endpoints[T] + // @ts-expect-error: The types are correct at runtime, but TypeScript can't infer the specific endpoint types + return await endpoint(params, options) + }, + }) +} From a9caedb03900d87e16420b1b190e1956f2faff13 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 18:21:21 -0700 Subject: [PATCH 06/23] Prefer Extract --- src/lib/seam/use-seam-query.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 25fb0e9d5..ba490848d 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -3,9 +3,9 @@ import { useQuery, type UseQueryResult } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -type Endpoints = Omit< +type Endpoints = Pick< SeamHttpEndpoints, - Exclude + Extract > export function useSeamQuery( From df9d475591ceb7fa7252d196e228d06ab4839ae7 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Wed, 25 Jun 2025 21:36:36 -0700 Subject: [PATCH 07/23] Use SeamHttpEndpointPaths --- package-lock.json | 8 ++++---- package.json | 2 +- src/lib/seam/use-seam-query.ts | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index ec1fb2126..a773ca1d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.37.0", + "@seamapi/http": "^1.38.0", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", @@ -5873,9 +5873,9 @@ } }, "node_modules/@seamapi/http": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.37.0.tgz", - "integrity": "sha512-IzKOylzhY88YjrDmL3sGEEmCdIo/+cI8xTlujHteGbe1MbDx4BKE7n23bOS/Fe+S9mxrkTuIzez1zkPzttiRCQ==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.0.tgz", + "integrity": "sha512-zm7aii3YPL3nZVTQA63Dh1o6LYTLzGDEjPQDOwjzwkBNjI0w8wuFyOZZzByTIUvuUru6yu798gPr9Iq+ne0LpQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 846ecb09a..353c3cf65 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.37.0", + "@seamapi/http": "^1.38.0", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index ba490848d..0233cf1fb 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -1,12 +1,13 @@ -import type { SeamHttpApiError, SeamHttpEndpoints } from '@seamapi/http/connect' +import type { + SeamHttpApiError, + SeamHttpEndpointPaths, + SeamHttpEndpoints, +} from '@seamapi/http/connect' import { useQuery, type UseQueryResult } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -type Endpoints = Pick< - SeamHttpEndpoints, - Extract -> +type Endpoints = Pick export function useSeamQuery( endpointPath: T, From a85bfde34736db76ee6cb1298c6a0ad717312162 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 00:21:21 -0700 Subject: [PATCH 08/23] Use SeamHttpEndpointQueryPaths --- package-lock.json | 8 ++++---- package.json | 2 +- src/lib/seam/use-seam-query.ts | 17 ++++++++--------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index a773ca1d9..0be4319ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.38.0", + "@seamapi/http": "^1.38.1", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", @@ -5873,9 +5873,9 @@ } }, "node_modules/@seamapi/http": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.0.tgz", - "integrity": "sha512-zm7aii3YPL3nZVTQA63Dh1o6LYTLzGDEjPQDOwjzwkBNjI0w8wuFyOZZzByTIUvuUru6yu798gPr9Iq+ne0LpQ==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.1.tgz", + "integrity": "sha512-JfQaootfbeVGUvQTEJEmOsvXCtZrX1lZRl7rt/KQe3BKyct0ngI4jIdjQsJqm5xfjyeYz+VLbxn70J5QDpognA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 353c3cf65..b7e658a23 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.38.0", + "@seamapi/http": "^1.38.1", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 0233cf1fb..3f2e48657 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -1,27 +1,26 @@ import type { SeamHttpApiError, - SeamHttpEndpointPaths, + SeamHttpEndpointQueryPaths, SeamHttpEndpoints, } from '@seamapi/http/connect' import { useQuery, type UseQueryResult } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -type Endpoints = Pick - -export function useSeamQuery( +export function useSeamQuery( endpointPath: T, - params?: Parameters[0], - options?: Parameters[1] -): UseQueryResult>, SeamHttpApiError> { + params?: Parameters[0], + options?: Parameters[1] +): UseQueryResult>, SeamHttpApiError> { const { endpointClient: client } = useSeamClient() return useQuery({ enabled: client != null, queryKey: [endpointPath, params], queryFn: async () => { if (client == null) return null - const endpoint = client[endpointPath] as Endpoints[T] - // @ts-expect-error: The types are correct at runtime, but TypeScript can't infer the specific endpoint types + // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. + // Type assertion is needed here for performance reasons. The types are correct at runtime. + const endpoint = client[endpointPath] as any return await endpoint(params, options) }, }) From a981f99ce7bf4b3228c65448fd28b142ff2cd0cc Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 00:27:04 -0700 Subject: [PATCH 09/23] Rename params to parameters --- src/lib/seam/use-seam-query.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 3f2e48657..4a29dfc13 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -9,19 +9,19 @@ import { useSeamClient } from 'lib/seam/use-seam-client.js' export function useSeamQuery( endpointPath: T, - params?: Parameters[0], + parameters?: Parameters[0], options?: Parameters[1] ): UseQueryResult>, SeamHttpApiError> { const { endpointClient: client } = useSeamClient() return useQuery({ enabled: client != null, - queryKey: [endpointPath, params], + queryKey: [endpointPath, parameters], queryFn: async () => { if (client == null) return null // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. // Type assertion is needed here for performance reasons. The types are correct at runtime. const endpoint = client[endpointPath] as any - return await endpoint(params, options) + return await endpoint(parameters, options) }, }) } From 3deb7c0e2b8aa3bb87a62ff5efe892586cb93906 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 00:38:49 -0700 Subject: [PATCH 10/23] feat: Add useSeamMutation --- src/lib/seam/index.ts | 1 + src/lib/seam/use-seam-mutation.ts | 35 +++++++++++++++++++++++++++++++ src/lib/seam/use-seam-query.ts | 11 ++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/lib/seam/use-seam-mutation.ts diff --git a/src/lib/seam/index.ts b/src/lib/seam/index.ts index 6bfff8d33..a9f896ba9 100644 --- a/src/lib/seam/index.ts +++ b/src/lib/seam/index.ts @@ -13,4 +13,5 @@ export * from './devices/use-devices.js' export * from './SeamProvider.js' export * from './use-seam-client.js' export * from './use-seam-query.js' +export * from './use-seam-mutation.js' export * from './use-seam-query-result.js' diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts new file mode 100644 index 000000000..f80e2561a --- /dev/null +++ b/src/lib/seam/use-seam-mutation.ts @@ -0,0 +1,35 @@ +import type { + SeamHttpApiError, + SeamHttpEndpointMutationPaths, + SeamHttpEndpoints, +} from '@seamapi/http/connect' +import { + useMutation, + type UseMutationOptions, + type UseMutationResult, +} from '@tanstack/react-query' + +import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' + +type MutationOptions = Omit + +export function useSeamMutation( + endpointPath: T, + options?: Parameters[1], + _mutationOptions: MutationOptions = {} +): UseMutationResult< + Awaited>, + SeamHttpApiError, + Parameters[1] +> { + const { endpointClient: client } = useSeamClient() + return useMutation({ + mutationFn: async (parameters) => { + if (client === null) throw new NullSeamClientError() + // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. + // Type assertion is needed here for performance reasons. The types are correct at runtime. + const endpoint = client[endpointPath] as any + return await endpoint(parameters, options) + }, + }) +} diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 4a29dfc13..bd411ccc1 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -3,14 +3,21 @@ import type { SeamHttpEndpointQueryPaths, SeamHttpEndpoints, } from '@seamapi/http/connect' -import { useQuery, type UseQueryResult } from '@tanstack/react-query' +import { + useQuery, + type UseQueryOptions, + type UseQueryResult, +} from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' +type QueryOptions = Omit + export function useSeamQuery( endpointPath: T, parameters?: Parameters[0], - options?: Parameters[1] + options?: Parameters[1], + _queryOptions: QueryOptions = {} ): UseQueryResult>, SeamHttpApiError> { const { endpointClient: client } = useSeamClient() return useQuery({ From be863c55203b2c0b432d5da4a9ef987ae24f08f6 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 26 Jun 2025 07:40:24 +0000 Subject: [PATCH 11/23] ci: Format code --- src/lib/seam/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/seam/index.ts b/src/lib/seam/index.ts index a9f896ba9..68df2751c 100644 --- a/src/lib/seam/index.ts +++ b/src/lib/seam/index.ts @@ -12,6 +12,6 @@ export * from './devices/use-device-providers.js' export * from './devices/use-devices.js' export * from './SeamProvider.js' export * from './use-seam-client.js' -export * from './use-seam-query.js' export * from './use-seam-mutation.js' +export * from './use-seam-query.js' export * from './use-seam-query-result.js' From a175acee5c79f16aa8cdba394828ee748be7025e Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 00:51:34 -0700 Subject: [PATCH 12/23] Pass options to useQuery and useMutation --- src/lib/seam/use-seam-mutation.ts | 25 +++++++++++++++++-------- src/lib/seam/use-seam-query.ts | 13 +++++++++---- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts index f80e2561a..5332df32f 100644 --- a/src/lib/seam/use-seam-mutation.ts +++ b/src/lib/seam/use-seam-mutation.ts @@ -11,19 +11,18 @@ import { import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' -type MutationOptions = Omit - export function useSeamMutation( endpointPath: T, options?: Parameters[1], - _mutationOptions: MutationOptions = {} -): UseMutationResult< - Awaited>, - SeamHttpApiError, - Parameters[1] -> { + mutationOptions: MutationOptions< + MutationData, + SeamHttpApiError, + MutationParameters + > = {} +): UseMutationResult, SeamHttpApiError, MutationParameters> { const { endpointClient: client } = useSeamClient() return useMutation({ + ...mutationOptions, mutationFn: async (parameters) => { if (client === null) throw new NullSeamClientError() // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. @@ -33,3 +32,13 @@ export function useSeamMutation( }, }) } + +type MutationData = Awaited< + ReturnType +> + +type MutationParameters = Parameters< + SeamHttpEndpoints[T] +>[1] + +type MutationOptions = Omit, 'mutationFn'> diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index bd411ccc1..d145c4a28 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -11,17 +11,16 @@ import { import { useSeamClient } from 'lib/seam/use-seam-client.js' -type QueryOptions = Omit - export function useSeamQuery( endpointPath: T, parameters?: Parameters[0], options?: Parameters[1], - _queryOptions: QueryOptions = {} -): UseQueryResult>, SeamHttpApiError> { + queryOptions: QueryOptions, SeamHttpApiError> = {} +): UseQueryResult, SeamHttpApiError> { const { endpointClient: client } = useSeamClient() return useQuery({ enabled: client != null, + ...queryOptions, queryKey: [endpointPath, parameters], queryFn: async () => { if (client == null) return null @@ -32,3 +31,9 @@ export function useSeamQuery( }, }) } + +type QueryData = Awaited< + ReturnType +> + +type QueryOptions = Omit, 'queryKey' | 'queryFn'> From 69fe23d8a6ec7e86bb3267425a0c0316b5199030 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 00:54:46 -0700 Subject: [PATCH 13/23] Fix linter error --- src/lib/seam/use-seam-mutation.ts | 2 +- src/lib/seam/use-seam-query.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts index 5332df32f..ceaf40722 100644 --- a/src/lib/seam/use-seam-mutation.ts +++ b/src/lib/seam/use-seam-mutation.ts @@ -27,7 +27,7 @@ export function useSeamMutation( if (client === null) throw new NullSeamClientError() // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. // Type assertion is needed here for performance reasons. The types are correct at runtime. - const endpoint = client[endpointPath] as any + const endpoint = client[endpointPath] as (...args: any) => Promise return await endpoint(parameters, options) }, }) diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index d145c4a28..9cd51e023 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -26,7 +26,7 @@ export function useSeamQuery( if (client == null) return null // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. // Type assertion is needed here for performance reasons. The types are correct at runtime. - const endpoint = client[endpointPath] as any + const endpoint = client[endpointPath] as (...args: any) => Promise return await endpoint(parameters, options) }, }) From db40fe507907d0a03c3bebe5f1f564ae54047bbc Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 00:56:00 -0700 Subject: [PATCH 14/23] Update http --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0be4319ee..d804894a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.38.1", + "@seamapi/http": "^1.38.2", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", @@ -5873,9 +5873,9 @@ } }, "node_modules/@seamapi/http": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.1.tgz", - "integrity": "sha512-JfQaootfbeVGUvQTEJEmOsvXCtZrX1lZRl7rt/KQe3BKyct0ngI4jIdjQsJqm5xfjyeYz+VLbxn70J5QDpognA==", + "version": "1.38.2", + "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.2.tgz", + "integrity": "sha512-IJLHUxrcr2/EJ08qDz9GHbUacAuOQcm1btKUtezK4ukJLWs/AosyB25eFOusDLHcY+j+dqOQR08or3BoqoKEtw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b7e658a23..7a1c27516 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.38.1", + "@seamapi/http": "^1.38.2", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", From 17a18a9cd1ae314730907e7513fe779275ffe261 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 09:32:19 -0700 Subject: [PATCH 15/23] Merge mutation and query options --- src/lib/seam/use-seam-mutation.ts | 14 +++++++------- src/lib/seam/use-seam-query.ts | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts index ceaf40722..6b8c3ce74 100644 --- a/src/lib/seam/use-seam-mutation.ts +++ b/src/lib/seam/use-seam-mutation.ts @@ -13,16 +13,16 @@ import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' export function useSeamMutation( endpointPath: T, - options?: Parameters[1], - mutationOptions: MutationOptions< - MutationData, - SeamHttpApiError, - MutationParameters - > = {} + options: Parameters[1] & + MutationOptions< + MutationData, + SeamHttpApiError, + MutationParameters + > = {} ): UseMutationResult, SeamHttpApiError, MutationParameters> { const { endpointClient: client } = useSeamClient() return useMutation({ - ...mutationOptions, + ...options, mutationFn: async (parameters) => { if (client === null) throw new NullSeamClientError() // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 9cd51e023..f536ba041 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -14,13 +14,13 @@ import { useSeamClient } from 'lib/seam/use-seam-client.js' export function useSeamQuery( endpointPath: T, parameters?: Parameters[0], - options?: Parameters[1], - queryOptions: QueryOptions, SeamHttpApiError> = {} + options: Parameters[1] & + QueryOptions, SeamHttpApiError> = {} ): UseQueryResult, SeamHttpApiError> { const { endpointClient: client } = useSeamClient() return useQuery({ enabled: client != null, - ...queryOptions, + ...options, queryKey: [endpointPath, parameters], queryFn: async () => { if (client == null) return null From 5208a04bfe15f1027c74f11bcb402600fb176d5c Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 15:17:42 -0700 Subject: [PATCH 16/23] Add UseSeamQueryResult and UseSeamMutationResult --- src/lib/seam/use-seam-mutation.ts | 5 ++++- src/lib/seam/use-seam-query.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts index 6b8c3ce74..1bec5f795 100644 --- a/src/lib/seam/use-seam-mutation.ts +++ b/src/lib/seam/use-seam-mutation.ts @@ -11,6 +11,9 @@ import { import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' +export type UseSeamMutationResult = + UseMutationResult, SeamHttpApiError, MutationParameters> + export function useSeamMutation( endpointPath: T, options: Parameters[1] & @@ -19,7 +22,7 @@ export function useSeamMutation( SeamHttpApiError, MutationParameters > = {} -): UseMutationResult, SeamHttpApiError, MutationParameters> { +): UseSeamMutationResult { const { endpointClient: client } = useSeamClient() return useMutation({ ...options, diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index f536ba041..5200b4416 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -11,12 +11,15 @@ import { import { useSeamClient } from 'lib/seam/use-seam-client.js' +export type UseSeamQueryResult = + UseQueryResult, SeamHttpApiError> + export function useSeamQuery( endpointPath: T, parameters?: Parameters[0], options: Parameters[1] & QueryOptions, SeamHttpApiError> = {} -): UseQueryResult, SeamHttpApiError> { +): UseSeamQueryResult { const { endpointClient: client } = useSeamClient() return useQuery({ enabled: client != null, From 854b7cb06e6ecd955f184bc818379562a0819697 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 15:17:55 -0700 Subject: [PATCH 17/23] Rename UseSeamQueryResult to UseSeamQueryResultLegacy --- src/lib/seam/access-codes/use-access-code.ts | 4 ++-- src/lib/seam/access-codes/use-access-codes.ts | 4 ++-- src/lib/seam/client-sessions/use-client-session.ts | 4 ++-- .../seam/components/SupportedDeviceTable/use-device-model.ts | 4 ++-- .../seam/components/SupportedDeviceTable/use-device-models.ts | 4 ++-- .../seam/components/SupportedDeviceTable/use-manufacturer.ts | 4 ++-- .../seam/components/SupportedDeviceTable/use-manufacturers.ts | 4 ++-- src/lib/seam/connected-accounts/use-connected-account.ts | 4 ++-- src/lib/seam/devices/use-device-providers.ts | 4 ++-- src/lib/seam/devices/use-device.ts | 4 ++-- src/lib/seam/devices/use-devices.ts | 4 ++-- src/lib/seam/events/use-events.ts | 4 ++-- src/lib/seam/noise-sensors/use-noise-thresholds.ts | 4 ++-- src/lib/seam/use-seam-query-result.ts | 2 +- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/lib/seam/access-codes/use-access-code.ts b/src/lib/seam/access-codes/use-access-code.ts index c07047d94..b1ee269ce 100644 --- a/src/lib/seam/access-codes/use-access-code.ts +++ b/src/lib/seam/access-codes/use-access-code.ts @@ -6,7 +6,7 @@ import type { AccessCode } from '@seamapi/types/connect' import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseAccessCodeParams = AccessCodesGetParams @@ -14,7 +14,7 @@ export type UseAccessCodeData = AccessCode | null export function useAccessCode( params: UseAccessCodeParams -): UseSeamQueryResult<'accessCode', UseAccessCodeData> { +): UseSeamQueryResultLegacy<'accessCode', UseAccessCodeData> { const { client } = useSeamClient() const { data, ...rest } = useQuery({ enabled: client != null, diff --git a/src/lib/seam/access-codes/use-access-codes.ts b/src/lib/seam/access-codes/use-access-codes.ts index 18789ceac..8eafc2d6b 100644 --- a/src/lib/seam/access-codes/use-access-codes.ts +++ b/src/lib/seam/access-codes/use-access-codes.ts @@ -6,7 +6,7 @@ import type { AccessCode } from '@seamapi/types/connect' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseAccessCodesParams = AccessCodesListParams @@ -14,7 +14,7 @@ export type UseAccessCodesData = AccessCode[] export function useAccessCodes( params: UseAccessCodesParams -): UseSeamQueryResult<'accessCodes', UseAccessCodesData> { +): UseSeamQueryResultLegacy<'accessCodes', UseAccessCodesData> { const { client } = useSeamClient() const queryClient = useQueryClient() diff --git a/src/lib/seam/client-sessions/use-client-session.ts b/src/lib/seam/client-sessions/use-client-session.ts index 94c729811..107c7168b 100644 --- a/src/lib/seam/client-sessions/use-client-session.ts +++ b/src/lib/seam/client-sessions/use-client-session.ts @@ -3,13 +3,13 @@ import type { ClientSession } from '@seamapi/types/connect' import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseClientSessionParams = never export type UseClientSessionData = ClientSession | null -export function useClientSession(): UseSeamQueryResult< +export function useClientSession(): UseSeamQueryResultLegacy< 'clientSession', UseClientSessionData > { diff --git a/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts b/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts index 10cae70af..ef150e7e4 100644 --- a/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts +++ b/src/lib/seam/components/SupportedDeviceTable/use-device-model.ts @@ -7,7 +7,7 @@ import type { import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseDeviceModelParams = DeviceModelsGetParams @@ -15,7 +15,7 @@ export type UseDeviceModelData = DeviceModel | null export function useDeviceModel( params: UseDeviceModelParams -): UseSeamQueryResult<'deviceModel', UseDeviceModelData> { +): UseSeamQueryResultLegacy<'deviceModel', UseDeviceModelData> { const { client: seam } = useSeamClient() const { data, ...rest } = useQuery({ enabled: seam != null, diff --git a/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts b/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts index 923e6d1b3..e6e3caa98 100644 --- a/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts +++ b/src/lib/seam/components/SupportedDeviceTable/use-device-models.ts @@ -7,7 +7,7 @@ import type { import { useQuery, useQueryClient } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseDeviceModelsParams = DeviceModelsListParams @@ -15,7 +15,7 @@ export type UseDeviceModelsData = DeviceModel[] export function useDeviceModels( params?: UseDeviceModelsParams -): UseSeamQueryResult<'deviceModels', UseDeviceModelsData> { +): UseSeamQueryResultLegacy<'deviceModels', UseDeviceModelsData> { const { client: seam } = useSeamClient() const queryClient = useQueryClient() diff --git a/src/lib/seam/components/SupportedDeviceTable/use-manufacturer.ts b/src/lib/seam/components/SupportedDeviceTable/use-manufacturer.ts index 28ffdf8a6..b4751cfde 100644 --- a/src/lib/seam/components/SupportedDeviceTable/use-manufacturer.ts +++ b/src/lib/seam/components/SupportedDeviceTable/use-manufacturer.ts @@ -7,7 +7,7 @@ import type { import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseManufacturerParams = ManufacturersGetParams @@ -15,7 +15,7 @@ export type UseManufacturerData = Manufacturer | null export function useManufacturer( params: UseManufacturerParams -): UseSeamQueryResult<'manufacturer', UseManufacturerData> { +): UseSeamQueryResultLegacy<'manufacturer', UseManufacturerData> { const { client: seam } = useSeamClient() const { data, ...rest } = useQuery({ enabled: seam != null, diff --git a/src/lib/seam/components/SupportedDeviceTable/use-manufacturers.ts b/src/lib/seam/components/SupportedDeviceTable/use-manufacturers.ts index 4cb394fef..18df1027e 100644 --- a/src/lib/seam/components/SupportedDeviceTable/use-manufacturers.ts +++ b/src/lib/seam/components/SupportedDeviceTable/use-manufacturers.ts @@ -7,7 +7,7 @@ import type { import { useQuery, useQueryClient } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseManufacturersParams = ManufacturersListParams @@ -15,7 +15,7 @@ export type UseManufacturersData = Manufacturer[] export function useManufacturers( params?: UseManufacturersParams -): UseSeamQueryResult<'manufacturers', UseManufacturersData> { +): UseSeamQueryResultLegacy<'manufacturers', UseManufacturersData> { const { client: seam } = useSeamClient() const queryClient = useQueryClient() diff --git a/src/lib/seam/connected-accounts/use-connected-account.ts b/src/lib/seam/connected-accounts/use-connected-account.ts index a43d9db28..b617bb2de 100644 --- a/src/lib/seam/connected-accounts/use-connected-account.ts +++ b/src/lib/seam/connected-accounts/use-connected-account.ts @@ -6,7 +6,7 @@ import type { ConnectedAccount } from '@seamapi/types/connect' import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseConnectedAccountParams = ConnectedAccountsGetParams @@ -14,7 +14,7 @@ export type UseConnectedAccountData = ConnectedAccount | null export function useConnectedAccount( params: UseConnectedAccountParams -): UseSeamQueryResult<'connectedAccount', UseConnectedAccountData> { +): UseSeamQueryResultLegacy<'connectedAccount', UseConnectedAccountData> { const { client } = useSeamClient() const { data, ...rest } = useQuery( { diff --git a/src/lib/seam/devices/use-device-providers.ts b/src/lib/seam/devices/use-device-providers.ts index 3d1938384..685005f30 100644 --- a/src/lib/seam/devices/use-device-providers.ts +++ b/src/lib/seam/devices/use-device-providers.ts @@ -6,7 +6,7 @@ import type { DeviceProvider } from '@seamapi/types/connect' import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseDeviceProvidersParams = DevicesListDeviceProvidersParams @@ -14,7 +14,7 @@ export type UseDeviceProvidersData = DeviceProvider[] export function useDeviceProviders( params?: UseDeviceProvidersParams -): UseSeamQueryResult<'deviceProviders', UseDeviceProvidersData> { +): UseSeamQueryResultLegacy<'deviceProviders', UseDeviceProvidersData> { const { client } = useSeamClient() const { data, ...rest } = useQuery({ diff --git a/src/lib/seam/devices/use-device.ts b/src/lib/seam/devices/use-device.ts index 381524f0e..70ac48307 100644 --- a/src/lib/seam/devices/use-device.ts +++ b/src/lib/seam/devices/use-device.ts @@ -3,7 +3,7 @@ import type { Device } from '@seamapi/types/connect' import { useQuery } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseDeviceParams = DevicesGetParams @@ -11,7 +11,7 @@ export type UseDeviceData = Device | null export function useDevice( params: UseDeviceParams -): UseSeamQueryResult<'device', UseDeviceData> { +): UseSeamQueryResultLegacy<'device', UseDeviceData> { const { client } = useSeamClient() const { data, ...rest } = useQuery({ enabled: client != null, diff --git a/src/lib/seam/devices/use-devices.ts b/src/lib/seam/devices/use-devices.ts index bb7fc02f3..3392678f6 100644 --- a/src/lib/seam/devices/use-devices.ts +++ b/src/lib/seam/devices/use-devices.ts @@ -3,7 +3,7 @@ import type { Device } from '@seamapi/types/connect' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseDevicesParams = DevicesListParams @@ -11,7 +11,7 @@ export type UseDevicesData = Device[] export function useDevices( params?: UseDevicesParams -): UseSeamQueryResult<'devices', UseDevicesData> { +): UseSeamQueryResultLegacy<'devices', UseDevicesData> { const { client } = useSeamClient() const queryClient = useQueryClient() diff --git a/src/lib/seam/events/use-events.ts b/src/lib/seam/events/use-events.ts index 3383c4295..ed89b3b0a 100644 --- a/src/lib/seam/events/use-events.ts +++ b/src/lib/seam/events/use-events.ts @@ -3,7 +3,7 @@ import type { SeamEvent } from '@seamapi/types/connect' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseEventsParams = EventsListParams @@ -16,7 +16,7 @@ export interface UseEventsOptions { export function useEvents( params?: UseEventsParams, { refetchInterval }: UseEventsOptions = {} -): UseSeamQueryResult<'events', UseEventsData> { +): UseSeamQueryResultLegacy<'events', UseEventsData> { const { client } = useSeamClient() const queryClient = useQueryClient() diff --git a/src/lib/seam/noise-sensors/use-noise-thresholds.ts b/src/lib/seam/noise-sensors/use-noise-thresholds.ts index 535804758..e93fa4247 100644 --- a/src/lib/seam/noise-sensors/use-noise-thresholds.ts +++ b/src/lib/seam/noise-sensors/use-noise-thresholds.ts @@ -6,7 +6,7 @@ import type { NoiseThreshold } from '@seamapi/types/connect' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useSeamClient } from 'lib/seam/use-seam-client.js' -import type { UseSeamQueryResult } from 'lib/seam/use-seam-query-result.js' +import type { UseSeamQueryResultLegacy } from 'lib/seam/use-seam-query-result.js' export type UseNoiseThresholdsParams = NoiseSensorsNoiseThresholdsListParams @@ -14,7 +14,7 @@ export type UseNoiseThresholdsData = NoiseThreshold[] export function useNoiseThresholds( params: UseNoiseThresholdsParams -): UseSeamQueryResult<'noiseThresholds', UseNoiseThresholdsData> { +): UseSeamQueryResultLegacy<'noiseThresholds', UseNoiseThresholdsData> { const { client } = useSeamClient() const queryClient = useQueryClient() diff --git a/src/lib/seam/use-seam-query-result.ts b/src/lib/seam/use-seam-query-result.ts index 834766c8b..e4d3ac75f 100644 --- a/src/lib/seam/use-seam-query-result.ts +++ b/src/lib/seam/use-seam-query-result.ts @@ -1,7 +1,7 @@ import type { SeamHttpApiError } from '@seamapi/http/connect' import type { UseQueryResult } from '@tanstack/react-query' -export type UseSeamQueryResult = Omit< +export type UseSeamQueryResultLegacy = Omit< UseQueryResult, 'data' > & { [key in Field]?: ResponsePayload } From 769795f53b0af7fead39e64430e6163cf19eb7f5 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 26 Jun 2025 22:19:06 +0000 Subject: [PATCH 18/23] ci: Format code --- src/lib/seam/use-seam-query-result.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/seam/use-seam-query-result.ts b/src/lib/seam/use-seam-query-result.ts index e4d3ac75f..79e7bf364 100644 --- a/src/lib/seam/use-seam-query-result.ts +++ b/src/lib/seam/use-seam-query-result.ts @@ -1,7 +1,9 @@ import type { SeamHttpApiError } from '@seamapi/http/connect' import type { UseQueryResult } from '@tanstack/react-query' -export type UseSeamQueryResultLegacy = Omit< - UseQueryResult, - 'data' -> & { [key in Field]?: ResponsePayload } +export type UseSeamQueryResultLegacy< + Field extends string, + ResponsePayload, +> = Omit, 'data'> & { + [key in Field]?: ResponsePayload +} From 7357f81c4e760bc783693090c9be02ea8bab66be Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 16:15:11 -0700 Subject: [PATCH 19/23] Add UseSeamMutationParameters and UseSeamQueryParameters --- src/lib/seam/use-seam-mutation.ts | 15 +++++++++------ src/lib/seam/use-seam-query.ts | 5 ++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts index 1bec5f795..ffca94a25 100644 --- a/src/lib/seam/use-seam-mutation.ts +++ b/src/lib/seam/use-seam-mutation.ts @@ -11,8 +11,15 @@ import { import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' +export type UseSeamMutationParameters = + Parameters[0] + export type UseSeamMutationResult = - UseMutationResult, SeamHttpApiError, MutationParameters> + UseMutationResult< + MutationData, + SeamHttpApiError, + UseSeamMutationParameters + > export function useSeamMutation( endpointPath: T, @@ -20,7 +27,7 @@ export function useSeamMutation( MutationOptions< MutationData, SeamHttpApiError, - MutationParameters + UseSeamMutationParameters > = {} ): UseSeamMutationResult { const { endpointClient: client } = useSeamClient() @@ -40,8 +47,4 @@ type MutationData = Awaited< ReturnType > -type MutationParameters = Parameters< - SeamHttpEndpoints[T] ->[1] - type MutationOptions = Omit, 'mutationFn'> diff --git a/src/lib/seam/use-seam-query.ts b/src/lib/seam/use-seam-query.ts index 5200b4416..1fa3a3d8d 100644 --- a/src/lib/seam/use-seam-query.ts +++ b/src/lib/seam/use-seam-query.ts @@ -11,12 +11,15 @@ import { import { useSeamClient } from 'lib/seam/use-seam-client.js' +export type UseSeamQueryParameters = + Parameters[0] + export type UseSeamQueryResult = UseQueryResult, SeamHttpApiError> export function useSeamQuery( endpointPath: T, - parameters?: Parameters[0], + parameters?: UseSeamQueryParameters, options: Parameters[1] & QueryOptions, SeamHttpApiError> = {} ): UseSeamQueryResult { From b2c8912e1343f6c96f6e8697721eef337b0e4479 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 16:31:26 -0700 Subject: [PATCH 20/23] Rename to UseSeamMutationVariables --- src/lib/seam/use-seam-mutation.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/seam/use-seam-mutation.ts b/src/lib/seam/use-seam-mutation.ts index ffca94a25..001290c78 100644 --- a/src/lib/seam/use-seam-mutation.ts +++ b/src/lib/seam/use-seam-mutation.ts @@ -11,14 +11,14 @@ import { import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' -export type UseSeamMutationParameters = +export type UseSeamMutationVariables = Parameters[0] export type UseSeamMutationResult = UseMutationResult< MutationData, SeamHttpApiError, - UseSeamMutationParameters + UseSeamMutationVariables > export function useSeamMutation( @@ -27,18 +27,18 @@ export function useSeamMutation( MutationOptions< MutationData, SeamHttpApiError, - UseSeamMutationParameters + UseSeamMutationVariables > = {} ): UseSeamMutationResult { const { endpointClient: client } = useSeamClient() return useMutation({ ...options, - mutationFn: async (parameters) => { + mutationFn: async (variables) => { if (client === null) throw new NullSeamClientError() // Using @ts-expect-error over any is preferred, but not possible here because TypeScript will run out of memory. // Type assertion is needed here for performance reasons. The types are correct at runtime. const endpoint = client[endpointPath] as (...args: any) => Promise - return await endpoint(parameters, options) + return await endpoint(variables, options) }, }) } From 9ce3d0735ad4b086d4a552585e5f612fbdf8976a Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 17:32:53 -0700 Subject: [PATCH 21/23] Update http --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d804894a8..3542caeb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.38.2", + "@seamapi/http": "^1.38.3", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", @@ -5873,9 +5873,9 @@ } }, "node_modules/@seamapi/http": { - "version": "1.38.2", - "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.2.tgz", - "integrity": "sha512-IJLHUxrcr2/EJ08qDz9GHbUacAuOQcm1btKUtezK4ukJLWs/AosyB25eFOusDLHcY+j+dqOQR08or3BoqoKEtw==", + "version": "1.38.3", + "resolved": "https://registry.npmjs.org/@seamapi/http/-/http-1.38.3.tgz", + "integrity": "sha512-KBUZpSCGs2UpnzP5480Qa4W8IioCKH0fssrTt7DyMGJ/T9o1mWuxgvqGf6l89ISNo2Z0H6I27Q/K9dWzg7sqYw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7a1c27516..0e8813f70 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@rxfork/r2wc-react-to-web-component": "^2.4.0", "@seamapi/fake-devicedb": "^1.6.1", "@seamapi/fake-seam-connect": "^1.76.0", - "@seamapi/http": "^1.38.2", + "@seamapi/http": "^1.38.3", "@seamapi/types": "^1.395.3", "@storybook/addon-designs": "^7.0.1", "@storybook/addon-essentials": "^7.0.2", From 095d5d771f873e9e0ae18fa2666964418ae4dfc9 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Thu, 26 Jun 2025 17:33:33 -0700 Subject: [PATCH 22/23] Use useSeamMutation with useCreateAccessCode --- .../access-codes/use-create-access-code.ts | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/src/lib/seam/access-codes/use-create-access-code.ts b/src/lib/seam/access-codes/use-create-access-code.ts index a7a80886e..e79d944ea 100644 --- a/src/lib/seam/access-codes/use-create-access-code.ts +++ b/src/lib/seam/access-codes/use-create-access-code.ts @@ -1,39 +1,14 @@ -import type { - AccessCodesCreateBody, - SeamHttpApiError, -} from '@seamapi/http/connect' -import type { AccessCode } from '@seamapi/types/connect' -import { - useMutation, - type UseMutationResult, - useQueryClient, -} from '@tanstack/react-query' - -import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js' - -export type UseCreateAccessCodeParams = never +import { useQueryClient } from '@tanstack/react-query' -export type UseCreateAccessCodeData = AccessCode - -export type UseCreateAccessCodeMutationVariables = AccessCodesCreateBody +import { + useSeamMutation, + type UseSeamMutationResult, +} from '../use-seam-mutation.js' -export function useCreateAccessCode(): UseMutationResult< - UseCreateAccessCodeData, - SeamHttpApiError, - UseCreateAccessCodeMutationVariables -> { - const { client } = useSeamClient() +export function useCreateAccessCode(): UseSeamMutationResult<'/access_codes/create'> { const queryClient = useQueryClient() - return useMutation< - UseCreateAccessCodeData, - SeamHttpApiError, - UseCreateAccessCodeMutationVariables - >({ - mutationFn: async (variables) => { - if (client === null) throw new NullSeamClientError() - return await client.accessCodes.create(variables) - }, + return useSeamMutation('/access_codes/create', { onSuccess: (data) => { queryClient.setQueryData( ['access_codes', 'get', { access_code_id: data.access_code_id }], From 52d0a1dd655e3a7d5bd4e92f8a3a46dacb176243 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Fri, 27 Jun 2025 11:00:13 -0700 Subject: [PATCH 23/23] Add peer dependencies section to README --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 1b5a9d68b..30daff5f5 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,24 @@ $ npm install @seamapi/react [npm]: https://www.npmjs.com/ +### Peer dependencies + +If your project uses a recent version of npm, peer dependencies will be handled automatically. + + +If you package manager does not automatically install peer dependencies, install these packages: + + +``` +@seamapi/http @seamapi/types +``` + +If using TypeScript, install these packages as development dependencies: + +``` +@seamapi/types @types/react-dom +``` + ## Usage ### With React