Skip to content

Commit

Permalink
feat: update next.js / react codegen types
Browse files Browse the repository at this point in the history
  • Loading branch information
seawatts committed Jan 30, 2025
1 parent d1435d9 commit 43eb31f
Show file tree
Hide file tree
Showing 15 changed files with 614 additions and 347 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import {
import TypeBuilder from "./type_builder"
import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals"

/**
* @deprecated Use RecursivePartialNull from 'baml_client/types' instead.
*/
export type RecursivePartialNull<T> = T extends object
? {
[P in keyof T]?: RecursivePartialNull<T[P]>;
}
: T | null;

export class BamlAsyncClient {
private runtime: BamlRuntime
private ctx_manager: BamlCtxManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,13 @@ import type {
} from './types';
import type { RecursivePartialNull, Check, Checked } from '../types';
import type { Image, Audio } from "@boundaryml/baml"
import { BamlValidationError, BamlClientFinishReasonError } from "@boundaryml/baml/errors"
import * as ServerActions from './server';

import type {
{%- for t in types %}{{ t }}{% if !loop.last %}, {% endif %}{% endfor -%}
} from "../types"

// Type guard functions
function isPartialResponse<T>(obj: unknown): obj is PartialResponse<T> {
return obj && typeof obj === 'object' && 'partial' in obj && !('final' in obj);
}

function isFinalResponse<T>(obj: unknown): obj is FinalResponse<T> {
return obj && typeof obj === 'object' && 'final' in obj && !('partial' in obj);
}

/**
* Type guard to check if props are for streaming mode
*/
Expand All @@ -39,14 +31,14 @@ function isStreamingProps<Action>(

interface HookState<TPartial, TFinal> {
isSuccess: boolean;
error: Error | null;
error: Error | BamlValidationError | BamlClientFinishReasonError | null;
data: TFinal | null;
partialData: TPartial | null;
}

type HookStateAction<TPartial, TFinal> =
| { type: 'START_REQUEST' }
| { type: 'SET_ERROR'; payload: Error }
| { type: 'SET_ERROR'; payload: Error | BamlValidationError | BamlClientFinishReasonError }
| { type: 'SET_PARTIAL'; payload: TPartial }
| { type: 'SET_FINAL'; payload: TFinal }
| { type: 'RESET' };
Expand All @@ -67,7 +59,10 @@ function hookReducer<TPartial, TFinal>(
case 'SET_ERROR':
return {
...state,
isSuccess: false,
error: action.payload,
data: null,
partialData: null,
};
case 'SET_PARTIAL':
return {
Expand All @@ -79,6 +74,7 @@ function hookReducer<TPartial, TFinal>(
...state,
isSuccess: true,
data: action.payload,
partialData: null,
};
case 'RESET':
return {
Expand Down Expand Up @@ -112,7 +108,33 @@ function hookReducer<TPartial, TFinal>(
* - Partial results in `partialData` (streaming mode)
*
* 3. Error Handling
* - Type-safe error handling
* - Type-safe error handling with three possible error types:
* 1. BamlValidationError: Thrown when BAML fails to parse LLM output
* - Access error.prompt for the original prompt
* - Access error.raw_output for the LLM's raw response
* - Access error.message for parsing error details
* 2. BamlClientFinishReasonError: Thrown when LLM terminates with disallowed finish reason
* - Access error.prompt for the original prompt
* - Access error.raw_output for the LLM's raw response
* - Access error.message for error details
* - Access error.finish_reason for the specific termination reason
* 3. Error: Standard JavaScript errors for other cases
* - Example error handling:
* ```typescript
* try {
* await mutate(params);
* } catch (e) {
* if (e instanceof BamlValidationError) {
* console.error('Failed to parse LLM output:', e.raw_output);
* console.error('Original prompt:', e.prompt);
* } else if (e instanceof BamlClientFinishReasonError) {
* console.error('LLM terminated early:', e.finish_reason);
* console.error('Partial output:', e.raw_output);
* } else {
* console.error('Other error:', e);
* }
* }
* ```
* - Network error detection
* - Stream interruption handling
*
Expand Down Expand Up @@ -266,26 +288,46 @@ export function useBamlAction<Action>(
if (value) {
const chunk = decoder.decode(value, { stream: true }).trim();
try {
const parsed = JSON.parse(chunk);
if (isPartialResponse<Awaited<ReturnType<NonStreamingAction>>>(parsed) && parsed.partial !== null) {
const partialValue = parsed.partial;
dispatch({ type: 'SET_PARTIAL', payload: partialValue });
onPartial?.(partialValue);
} else if (isFinalResponse<Awaited<ReturnType<NonStreamingAction>>>(parsed)) {
const finalValue = parsed.final;
dispatch({ type: 'SET_FINAL', payload: finalValue });
onFinal?.(finalValue);
return finalValue;
const parsed: BamlStreamResponse<Awaited<ReturnType<NonStreamingAction>>> = JSON.parse(chunk);

if (parsed.error) {
// Create appropriate error type
let error: Error | BamlValidationError | BamlClientFinishReasonError;
if (parsed.error.type === "BamlValidationError") {
error = new BamlValidationError(
parsed.error.prompt,
parsed.error.raw_output,
parsed.error.message
);
} else if (parsed.error.type === "BamlClientFinishReasonError") {
error = new BamlClientFinishReasonError(
parsed.error.prompt,
parsed.error.raw_output,
parsed.error.message
);
} else {
error = new Error(parsed.error.message);
}
throw error;
}

if (parsed.partial !== undefined) {
dispatch({ type: 'SET_PARTIAL', payload: parsed.partial });
onPartial?.(parsed.partial);
}

if (parsed.final !== undefined) {
dispatch({ type: 'SET_FINAL', payload: parsed.final });
onFinal?.(parsed.final);
return parsed.final;
}
} catch (err) {
// If JSON parsing fails, treat the chunk as a raw string partial update
dispatch({ type: 'SET_PARTIAL', payload: chunk});
onPartial?.(chunk);
dispatch({ type: "SET_ERROR", payload: err });
onError?.(err);
break;
}
}
}
} catch (err) {
throw err instanceof Error ? err : new Error(String(err));
} finally {
reader.releaseLock();
}
Expand All @@ -298,10 +340,9 @@ export function useBamlAction<Action>(
});
return response;
} catch (error_) {
const error = error_ instanceof Error ? error_ : new Error(String(error_));
dispatch({ type: 'SET_ERROR', payload: error });
onError?.(error);
return null;
dispatch({ type: 'SET_ERROR', payload: error_ });
onError?.(error_);
throw error_;
}
},
[serverAction, isStreaming, onPartial, onFinal, onError],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use server'

import { b } from '../index';
import type { Check, Checked, RecursivePartialNull } from "../types"
import type { Check, Checked } from "../types"
import type { ServerAction } from "./types"
import type { Image, Audio } from "@boundaryml/baml"
import type {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BamlStream } from '@boundaryml/baml';
import type { RecursivePartialNull } from '../types';

import type { BamlValidationError, BamlClientFinishReasonError } from '@boundaryml/baml/errors';
/**
* Type for representing a partial response with type safety
* @template Output The type of the partial response data
Expand All @@ -21,6 +21,7 @@ export type FinalResponse<Output> = {

export type ServerAction<Input = any, Output = any> = (...args: Input extends any[] ? Input : [Input]) => Promise<ReadableStream<Uint8Array> | Output>;


/**
* Props for streaming mode, which provides incremental updates.
* Use this when you want to show partial results as they become available.
Expand All @@ -32,7 +33,7 @@ export type StreamingProps<Action extends ServerAction = ServerAction> = {
onPartial?: (response?: RecursivePartialNull<Awaited<ReturnType<Action>>>) => void
onFinal?: (response?: Awaited<ReturnType<Action>>) => void
/** Called if the operation fails */
onError?: (error: Error) => void
onError?: (error: Error | BamlValidationError | BamlClientFinishReasonError) => void
}

/**
Expand All @@ -44,7 +45,7 @@ export type NonStreamingProps<Action extends ServerAction = ServerAction> = {
onPartial?: never
onFinal?: (response?: Awaited<ReturnType<Action>>) => void
/** Called if the operation fails */
onError?: (error: Error) => void
onError?: (error: Error | BamlValidationError | BamlClientFinishReasonError) => void
}

/**
Expand All @@ -57,59 +58,59 @@ export type HookProps<Action extends ServerAction = ServerAction> = StreamingPro
* Base return type for all BAML hooks
*/
export type BaseHookResult<Action extends ServerAction = ServerAction> = {
/**
* The complete, final result of the operation.
* Only available after successful completion (when isSuccess is true).
* Null during loading or if an error occurred.
*/
data?: Awaited<ReturnType<Action>>;
/**
* Error details if the operation failed.
* Check this when isError is true to handle the failure.
*/
error: Error | null;
/**
* True if the operation failed.
* Use this to conditionally render error states or retry options.
*/
isError: boolean;
/**
* True while the operation is in progress.
* Use this to show loading states, spinners, or disable controls.
*/
isPending: boolean;
/**
* True if the operation completed successfully.
* Check this before accessing the final data.
*/
isSuccess: boolean;
/**
* The current phase of the operation:
* - idle: Initial state, ready to start
* - loading: Operation in progress
* - success: Completed successfully
* - error: Failed with an error
*/
status: "idle" | "pending" | "success" | "error";
/**
* The complete, final result of the operation.
* Only available after successful completion (when isSuccess is true).
* Null during loading or if an error occurred.
*/
data?: Awaited<ReturnType<Action>>;
/**
* Error details if the operation failed.
* Check this when isError is true to handle the failure.
*/
error: Error | BamlValidationError | BamlClientFinishReasonError | null;
/**
* True if the operation failed.
* Use this to conditionally render error states or retry options.
*/
isError: boolean;
/**
* True while the operation is in progress.
* Use this to show loading states, spinners, or disable controls.
*/
isPending: boolean;
/**
* True if the operation completed successfully.
* Check this before accessing the final data.
*/
isSuccess: boolean;
/**
* The current phase of the operation:
* - idle: Initial state, ready to start
* - loading: Operation in progress
* - success: Completed successfully
* - error: Failed with an error
*/
status: "idle" | "pending" | "success" | "error";
}

/**
* Return type for streaming mode BAML hooks
*/
export type StreamingHookResult<Action extends ServerAction = ServerAction> = BaseHookResult<Action> & {
/**
* The most recent partial result from the stream.
* Updates continuously while streaming, showing interim progress.
* Use this to implement real-time UI updates, typing effects,
* or progress indicators.
*/
partialData?: RecursivePartialNull<Awaited<ReturnType<Action>>>;
/**
* The most recent partial result from the stream.
* Updates continuously while streaming, showing interim progress.
* Use this to implement real-time UI updates, typing effects,
* or progress indicators.
*/
partialData?: RecursivePartialNull<Awaited<ReturnType<Action>>>;

/**
* Call this function to start the operation.
* Returns a promise that resolves with the final result or null if it failed.
*/
mutate: (...input: Parameters<Action>) => Promise<ReadableStream<Uint8Array>>;
/**
* Call this function to start the operation.
* Returns a promise that resolves with the final result or null if it failed.
*/
mutate: (...input: Parameters<Action>) => Promise<ReadableStream<Uint8Array>>;
};

/**
Expand All @@ -118,6 +119,10 @@ export type StreamingHookResult<Action extends ServerAction = ServerAction> = Ba
export type NonStreamingHookResult<Action extends ServerAction = ServerAction> = BaseHookResult<Action> & {
/** Not available in non-streaming mode */
partialData?: never;
/**
* Call this function to start the operation.
* Returns a promise that resolves with the final result or null if it failed.
*/
mutate: (...input: Parameters<Action>) => Promise<Awaited<ReturnType<Action>>>;
};

Expand All @@ -129,4 +134,22 @@ export type HookResult<
Props extends HookProps<Action> = HookProps<Action>
> = Props extends { stream: true }
? StreamingHookResult<Action>
: NonStreamingHookResult<Action>;
: NonStreamingHookResult<Action>;

/**
* Helper type to extract the non-null partial data type from a BAML hook result.
* This is useful when you want to work with the type of the partial streaming data
* without dealing with undefined or null values.
*
* @template T The server action type that defines the input/output types
*/
export type HookResultPartialData<T extends ServerAction> = NonNullable<HookResult<T>['partialData']>;

/**
* Helper type to extract the non-null final data type from a BAML hook result.
* This is useful when you want to work with the type of the final response data
* without dealing with undefined or null values.
*
* @template T The server action type that defines the input/output types
*/
export type HookResultData<T extends ServerAction> = NonNullable<HookResult<T>['data']>;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import {
import TypeBuilder from "./type_builder"
import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals"

/**
* @deprecated Use RecursivePartialNull from 'baml_client/types' instead.
*/
export type RecursivePartialNull<T> = T extends object
? {
[P in keyof T]?: RecursivePartialNull<T[P]>;
}
: T | null;

export class BamlSyncClient {
private runtime: BamlRuntime
private ctx_manager: BamlCtxManager
Expand Down
Loading

0 comments on commit 43eb31f

Please sign in to comment.