diff --git a/.changeset/cuddly-kids-love.md b/.changeset/cuddly-kids-love.md new file mode 100644 index 00000000..c694891d --- /dev/null +++ b/.changeset/cuddly-kids-love.md @@ -0,0 +1,5 @@ +--- +'@viamrobotics/svelte-sdk': minor +--- + +Add createResourceStream hook diff --git a/package.json b/package.json index 1035bc03..31e4eb74 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@testing-library/svelte": "^5.2.8", "@viamrobotics/eslint-config": "^1.1.0", "@viamrobotics/prettier-config-svelte": "^1.1.0", - "@viamrobotics/sdk": "^0.45.0", + "@viamrobotics/sdk": "^0.49.1", "@viamrobotics/typescript-config": "^0.1.1", "eslint": "^9.30.0", "eslint-config-prettier": "^10.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c80f2b3..5eb79cd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,8 +58,8 @@ importers: specifier: ^1.1.0 version: 1.1.0(prettier@3.6.2)(svelte@5.25.10)(tailwindcss@4.1.11) '@viamrobotics/sdk': - specifier: ^0.45.0 - version: 0.45.0 + specifier: ^0.49.1 + version: 0.49.1 '@viamrobotics/typescript-config': specifier: ^0.1.1 version: 0.1.1(typescript@5.9.2) @@ -939,8 +939,8 @@ packages: peerDependencies: prettier: '>=3 <4' - '@viamrobotics/sdk@0.45.0': - resolution: {integrity: sha512-zl272isirvqI26abFCj/CboIgdrnrlj91KiWzCrSLng6lEo6xDX4ydECm56fVScVQOKLjmddscYjlA5FwJsATw==} + '@viamrobotics/sdk@0.49.1': + resolution: {integrity: sha512-0nkBLmqCVrYBCboO1VvHCkf5w10yGyE2kRPpBN70l3l3kl2UUvChwyQSau93AaAHvJ8HWrpIzgVFBj50N/6Gzg==} '@viamrobotics/typescript-config@0.1.1': resolution: {integrity: sha512-cIbEOlldP4r+F3/nESoXz3zXtYGnJNhRwPWtDjLRwswDJkz2i+moKO1DE2pRib/Z3+B1Qo+TzLYkkzEZJxevcw==} @@ -3346,7 +3346,7 @@ snapshots: dependencies: prettier: 3.6.2 - '@viamrobotics/sdk@0.45.0': + '@viamrobotics/sdk@0.49.1': dependencies: '@bufbuild/protobuf': 1.10.1 '@connectrpc/connect': 1.6.1(@bufbuild/protobuf@1.10.1) diff --git a/src/lib/hooks/create-resource-stream.svelte.ts b/src/lib/hooks/create-resource-stream.svelte.ts new file mode 100644 index 00000000..894ed486 --- /dev/null +++ b/src/lib/hooks/create-resource-stream.svelte.ts @@ -0,0 +1,106 @@ +import { + experimental_streamedQuery as streamedQuery, + createQuery, + type QueryObserverResult, + queryOptions as createQueryOptions, +} from '@tanstack/svelte-query'; +import type { Resource } from '@viamrobotics/sdk'; +import { toStore, fromStore } from 'svelte/store'; +import { useQueryLogger } from '../query-logger'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ArgumentsType = T extends (...args: infer U) => any ? U : never; + +export type StreamItemType = T extends ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...args: any[] + // eslint-disable-next-line @typescript-eslint/no-explicit-any +) => AsyncGenerator + ? R + : never; + +interface QueryOptions { + // enabled defaults to true if unspecified + enabled?: boolean; + refetchMode?: 'append' | 'reset' | 'replace'; + maxChunks?: number; +} + +type QueryResult = QueryObserverResult; + +export const createResourceStream = ( + client: { current: T | undefined }, + method: K, + ...additional: + | [ + args?: (() => ArgumentsType) | ArgumentsType, + options?: (() => QueryOptions) | QueryOptions, + ] + | [options?: (() => QueryOptions) | QueryOptions] +): { current: QueryResult> } => { + const debug = useQueryLogger(); + + let [args, options] = additional; + + if (options === undefined && args !== undefined) { + options = args as QueryOptions; + args = undefined; + } + + const _options = $derived( + typeof options === 'function' ? options() : options + ); + + const _args = $derived(typeof args === 'function' ? args() : args); + const name = $derived(client.current?.name); + const methodName = $derived(String(method)); + const queryKey = $derived([ + 'viam-svelte-sdk', + 'partID', + (client.current as T & { partID: string })?.partID, + 'resource', + name, + methodName, + ...(_args ? [_args] : []), + ]); + + function processStream() { + const clientFunc = client.current?.[method]; + + if (typeof clientFunc !== 'function') { + throw new TypeError( + `${String(method)} is not a method on the resource client.` + ); + } + + const logger = debug.createLogger(); + logger('REQ', name, methodName, _args); + + try { + const response = clientFunc?.apply( + client.current, + _args + ) as AsyncGenerator>; + console.log('response', typeof response); + + logger('RES', name, methodName, response); + return response; + } catch (error) { + logger('ERR', name, methodName, error); + throw error; + } + } + + const queryOptions = $derived( + createQueryOptions({ + queryKey, + enabled: client.current !== undefined && _options?.enabled !== false, + queryFn: streamedQuery>({ + queryFn: processStream, + ..._options, + }), + }) + ); + + return fromStore(createQuery(toStore(() => queryOptions))); +}; diff --git a/src/lib/index.ts b/src/lib/index.ts index fa87efe6..5eadd489 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -16,6 +16,7 @@ export { createRobotMutation } from './hooks/create-robot-mutation.svelte'; export { createResourceClient } from './hooks/create-resource-client.svelte'; export { createResourceQuery } from './hooks/create-resource-query.svelte'; export { createResourceMutation } from './hooks/create-resource-mutation.svelte'; +export { createResourceStream } from './hooks/create-resource-stream.svelte'; export { createStreamClient } from './hooks/create-stream-client.svelte'; export { useMachineStatus } from './hooks/machine-status.svelte'; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 1287e79a..97d2bcdd 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -43,10 +43,7 @@ let { children }: Props = $props(); {/each} - + {@render children()}