(
options: Omit<InjectGenerateVideoOptions, 'onResult'> & {
onResult?: (result: VideoGenerateResult) => TTransformed
},
)
| 56 | // parameter is typed as `VideoGenerateResult` and `result` narrows to the |
| 57 | // transform's return. See issue #848. |
| 58 | export function injectGenerateVideo<TTransformed = void>( |
| 59 | options: Omit<InjectGenerateVideoOptions, 'onResult'> & { |
| 60 | onResult?: (result: VideoGenerateResult) => TTransformed |
| 61 | }, |
| 62 | ): InjectGenerateVideoResult< |
| 63 | InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed> |
| 64 | > { |
| 65 | assertInInjectionContext(injectGenerateVideo) |
| 66 | |
| 67 | type TOutput = InferGenerationOutputFromReturn< |
| 68 | VideoGenerateResult, |
| 69 | TTransformed |
| 70 | > |
| 71 | |
| 72 | const destroyRef = inject(DestroyRef) |
| 73 | const injector = inject(Injector) |
| 74 | const clientId = options.id || `injectGenerateVideo-${nextId++}` |
| 75 | |
| 76 | const result = signal<TOutput | null>(null) |
| 77 | const jobId = signal<string | null>(null) |
| 78 | const videoStatus = signal<VideoStatusInfo | null>(null) |
| 79 | const isLoading = signal(false) |
| 80 | const error = signal<Error | undefined>(undefined) |
| 81 | const status = signal<GenerationClientState>('idle') |
| 82 | |
| 83 | const bodySource = |
| 84 | options.body !== undefined ? toReactive(options.body) : undefined |
| 85 | |
| 86 | const baseOptions = { |
| 87 | id: clientId, |
| 88 | ...(bodySource !== undefined && { body: bodySource() }), |
| 89 | devtoolsBridgeFactory: createVideoDevtoolsBridge, |
| 90 | devtools: { |
| 91 | ...options.devtools, |
| 92 | framework: 'angular', |
| 93 | hookName: 'injectGenerateVideo', |
| 94 | outputKind: 'video' as const, |
| 95 | }, |
| 96 | // The transform's raw return type (`TTransformed`) and the stored output |
| 97 | // (`TOutput`, with null/void/undefined stripped) are identical at runtime; |
| 98 | // the cast bridges the relationship that the conditional type hides. |
| 99 | onResult: ((r: VideoGenerateResult) => options.onResult?.(r)) as ( |
| 100 | result: VideoGenerateResult, |
| 101 | ) => TOutput | null | void, |
| 102 | onError: (e: Error) => options.onError?.(e), |
| 103 | onProgress: (p: number, m?: string) => options.onProgress?.(p, m), |
| 104 | onChunk: (c: StreamChunk) => options.onChunk?.(c), |
| 105 | onJobCreated: (id: string) => options.onJobCreated?.(id), |
| 106 | onStatusUpdate: (s: VideoStatusInfo) => options.onStatusUpdate?.(s), |
| 107 | onResultChange: (r: TOutput | null) => result.set(r), |
| 108 | onLoadingChange: (l: boolean) => isLoading.set(l), |
| 109 | onErrorChange: (e: Error | undefined) => error.set(e), |
| 110 | onStatusChange: (s: GenerationClientState) => status.set(s), |
| 111 | onJobIdChange: (id: string | null) => jobId.set(id), |
| 112 | onVideoStatusChange: (s: VideoStatusInfo | null) => videoStatus.set(s), |
| 113 | } |
| 114 | |
| 115 | let client: VideoGenerationClient<TOutput> |
nothing calls this directly
no test coverage detected