Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
refactor: Replace any types with unknown for improved type safety
Replace `any` types with `unknown` throughout builder, js-runtime, memory, and runner packages to improve type safety and maintainability. Add proper type guards and assertions where needed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
  • Loading branch information
seefeldb and claude committed Jun 6, 2025
commit 1c2e118da2f317a478a43527ae32a8ac32b74448
18 changes: 9 additions & 9 deletions packages/builder/src/built-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface BuiltInLLMState<T> {
pending: boolean;
result?: T;
partial?: string;
error: any;
error: unknown;
}

export const llm = createNodeFactory({
Expand All @@ -60,7 +60,7 @@ export const fetchData = createNodeFactory({
options?: RequestInit;
result?: T;
}>,
) => Opaque<{ pending: boolean; result: T; error: any }>;
) => Opaque<{ pending: boolean; result: T; error: unknown }>;

export const streamData = createNodeFactory({
type: "ref",
Expand All @@ -71,9 +71,9 @@ export const streamData = createNodeFactory({
options?: RequestInit;
result?: T;
}>,
) => Opaque<{ pending: boolean; result: T; error: any }>;
) => Opaque<{ pending: boolean; result: T; error: unknown }>;

export function ifElse<T = any, U = any, V = any>(
export function ifElse<T = unknown, U = unknown, V = unknown>(
condition: Opaque<T>,
ifTrue: Opaque<U>,
ifFalse: Opaque<V>,
Expand All @@ -82,30 +82,30 @@ export function ifElse<T = any, U = any, V = any>(
type: "ref",
implementation: "ifElse",
});
return ifElseFactory([condition, ifTrue, ifFalse]);
return ifElseFactory([condition, ifTrue, ifFalse]) as OpaqueRef<U | V>;
}

let ifElseFactory: NodeFactory<[any, any, any], any> | undefined;
let ifElseFactory: NodeFactory<[unknown, unknown, unknown], any> | undefined;

export const navigateTo = createNodeFactory({
type: "ref",
implementation: "navigateTo",
}) as (cell: OpaqueRef<any>) => OpaqueRef<string>;
}) as (cell: OpaqueRef<unknown>) => OpaqueRef<string>;

// Example:
// str`Hello, ${name}!`
//
// TODO(seefeld): This should be a built-in module
export function str(
strings: TemplateStringsArray,
...values: any[]
...values: unknown[]
): OpaqueRef<string> {
const interpolatedString = ({
strings,
values,
}: {
strings: TemplateStringsArray;
values: any[];
values: unknown[];
}) =>
strings.reduce(
(result, str, i) => result + str + (i < values.length ? values[i] : ""),
Expand Down
60 changes: 30 additions & 30 deletions packages/builder/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type OpaqueRefMethods<T> = {
set(value: Opaque<T> | T): void;
key<K extends keyof T>(key: K): OpaqueRef<T[K]>;
setDefault(value: Opaque<T> | T): void;
setPreExisting(ref: any): void;
setPreExisting(ref: unknown): void;
setName(name: string): void;
setSchema(schema: JSONSchema): void;
connect(node: NodeRef): void;
Expand All @@ -42,7 +42,7 @@ export type OpaqueRefMethods<T> = {
value?: Opaque<T>;
defaultValue?: Opaque<T>;
nodes: Set<NodeRef>;
external?: any;
external?: unknown;
name?: string;
schema?: JSONSchema;
rootSchema?: JSONSchema;
Expand All @@ -60,16 +60,16 @@ export type OpaqueRefMethods<T> = {
array: T,
) => Opaque<S>,
): Opaque<S[]>;
toJSON(): any;
toJSON(): unknown;
[Symbol.iterator](): Iterator<T>;
[Symbol.toPrimitive](hint: string): T;
[isOpaqueRefMarker]: true;
};

export const isOpaqueRefMarker = Symbol("isOpaqueRef");

export function isOpaqueRef(value: any): value is OpaqueRef<any> {
return value && typeof value[isOpaqueRefMarker] === "boolean";
export function isOpaqueRef(value: unknown): value is OpaqueRef<any> {
return !!value && typeof (value as any)[isOpaqueRefMarker] === "boolean";
}

export type NodeRef = {
Expand All @@ -80,7 +80,7 @@ export type NodeRef = {
};

export type toJSON = {
toJSON(): any;
toJSON(): unknown;
};

export type NodeFactory<T, R> =
Expand Down Expand Up @@ -118,16 +118,16 @@ export interface JSONObject extends Record<string, JSONValue> {}
// Annotations when writing data that help determine the entity id. They are
// removed before sending to storage.
export interface IDFields {
[ID]?: any;
[ID_FIELD]?: any;
[ID]?: unknown;
[ID_FIELD]?: unknown;
}

// TODO(@ubik2) When specifying a JSONSchema, you can often use a boolean
// This is particularly useful for specifying the schema of a property.
// That will require reworking some things, so for now, I'm not doing it
export type JSONSchema = {
readonly [ID]?: any;
readonly [ID_FIELD]?: any;
readonly [ID]?: unknown;
readonly [ID_FIELD]?: unknown;
readonly type?:
| "object"
| "array"
Expand Down Expand Up @@ -158,14 +158,14 @@ export type JSONSchemaMutable = Mutable<JSONSchema>;

export type Alias = {
$alias: {
cell?: any;
cell?: unknown;
path: PropertyKey[];
schema?: JSONSchema;
rootSchema?: JSONSchema;
};
};

export function isAlias(value: any): value is Alias {
export function isAlias(value: unknown): value is Alias {
return isObject(value) && "$alias" in value && isObject(value.$alias) &&
"path" in value.$alias &&
Array.isArray(value.$alias.path);
Expand All @@ -175,8 +175,8 @@ export type StreamAlias = {
$stream: true;
};

export function isStreamAlias(value: any): value is StreamAlias {
return !!value && typeof value.$stream === "boolean" && value.$stream;
export function isStreamAlias(value: unknown): value is StreamAlias {
return !!value && typeof (value as any).$stream === "boolean" && (value as any).$stream;
}

export type Module = {
Expand All @@ -191,10 +191,10 @@ export type Handler<T = any, R = any> = Module & {
with: (inputs: Opaque<T>) => OpaqueRef<R>;
};

export function isModule(value: any): value is Module {
export function isModule(value: unknown): value is Module {
return (
(typeof value === "function" || typeof value === "object") &&
typeof value.type === "string"
typeof (value as any).type === "string"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing .type may throw when value is null because the earlier check allows typeof value === "object", which is true for null. A null guard should be added before the property access.

Suggested change
typeof (value as any).type === "string"
value !== null && typeof (value as any).type === "string"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a valid suggestion (either checking for null or using isObject).

);
}

Expand All @@ -221,24 +221,24 @@ export type Recipe = {
[unsafe_materializeFactory]?: (log: any) => (path: PropertyKey[]) => any;
};

export function isRecipe(value: any): value is Recipe {
export function isRecipe(value: unknown): value is Recipe {
return (
(typeof value === "function" || typeof value === "object") &&
value !== null &&
!!value.argumentSchema &&
!!value.resultSchema &&
!!value.nodes &&
Array.isArray(value.nodes)
!!(value as any).argumentSchema &&
!!(value as any).resultSchema &&
!!(value as any).nodes &&
Array.isArray((value as any).nodes)
);
}

type CanBeOpaqueRef = { [toOpaqueRef]: () => OpaqueRef<any> };

export function canBeOpaqueRef(value: any): value is CanBeOpaqueRef {
export function canBeOpaqueRef(value: unknown): value is CanBeOpaqueRef {
return (
(typeof value === "object" || typeof value === "function") &&
value !== null &&
typeof value[toOpaqueRef] === "function"
typeof (value as any)[toOpaqueRef] === "function"
);
}

Expand All @@ -252,12 +252,12 @@ export type ShadowRef = {
shadowOf: OpaqueRef<any> | ShadowRef;
};

export function isShadowRef(value: any): value is ShadowRef {
export function isShadowRef(value: unknown): value is ShadowRef {
return (
!!value &&
typeof value === "object" &&
"shadowOf" in value &&
(isOpaqueRef(value.shadowOf) || isShadowRef(value.shadowOf))
(isOpaqueRef((value as any).shadowOf) || isShadowRef((value as any).shadowOf))
);
}

Expand All @@ -269,7 +269,7 @@ export type UnsafeBinding = {

export type Frame = {
parent?: Frame;
cause?: any;
cause?: unknown;
generatedIdCounter: number;
opaqueRefs: Set<OpaqueRef<any>>;
unsafe_binding?: UnsafeBinding;
Expand All @@ -281,12 +281,12 @@ export type Static = {
[isStaticMarker]: true;
};

export function isStatic(value: any): value is Static {
export function isStatic(value: unknown): value is Static {
return typeof value === "object" && value !== null &&
value[isStaticMarker] === true;
(value as any)[isStaticMarker] === true;
}

export function markAsStatic(value: any): any {
value[isStaticMarker] = true;
export function markAsStatic(value: unknown): unknown {
(value as any)[isStaticMarker] = true;
return value;
}
6 changes: 3 additions & 3 deletions packages/builder/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ export function toJSONWithAliases(
): JSONValue | undefined {
if (isStatic(value) && !processStatic) {
return markAsStatic(
toJSONWithAliases(value, paths, ignoreSelfAliases, path, true),
);
toJSONWithAliases(value, paths, ignoreSelfAliases, path, true) as any,
) as JSONValue;
} // Convert regular cells to opaque refs
else if (canBeOpaqueRef(value)) value = makeOpaqueRef(value);
// Convert parent opaque refs to shadow refs
Expand Down Expand Up @@ -196,7 +196,7 @@ export function toJSONWithAliases(
} else if (!("cell" in alias) || typeof alias.cell === "number") {
return {
$alias: {
cell: (alias.cell ?? 0) + 1,
cell: ((alias.cell as number) ?? 0) + 1,
path: alias.path as (string | number)[],
},
} satisfies Alias;
Expand Down
4 changes: 2 additions & 2 deletions packages/js-runtime/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export class CompilerError extends Error {

// A reference to a runtime value from a `JsIsolate`.
export interface JsValue {
invoke(...args: any[]): JsValue;
inner(): any;
invoke(...args: unknown[]): JsValue;
inner(): unknown;
asObject(): object;
isObject(): boolean;
}
Expand Down
14 changes: 7 additions & 7 deletions packages/memory/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export abstract class BaseObjectTraverser<K, S>
return Object.fromEntries(
Object.entries(value).map((
[k, v],
): any => [k, this.traverseDAG(doc, docRoot, v, tracker)]),
);
) => [k, this.traverseDAG(doc, docRoot, v, tracker)]),
) as JSONValue;
} finally {
tracker.exit(value);
}
Expand Down Expand Up @@ -247,7 +247,7 @@ export function getAtPath<K, S>(
const cursorObj = cursor as JSONObject;
cursor = cursorObj[part] as JSONValue;
} else if (Array.isArray(cursor)) {
cursor = elementAt(cursor, part);
cursor = elementAt(cursor, part) as JSONValue;
} else {
// we can only descend into pointers, objects, and arrays
return [doc, docRoot, undefined];
Expand Down Expand Up @@ -352,7 +352,7 @@ export function getPointerInfo(value: JSONObject): CellTarget {
return { path: [], cellTarget: undefined };
}

export function isPointer(value: any): boolean {
export function isPointer(value: unknown): boolean {
return (isAlias(value) || isJSONCellLink(value));
}

Expand All @@ -362,20 +362,20 @@ export function isPointer(value: any): boolean {
* @param {any} value - The value to check.
* @returns {boolean}
*/
function isJSONCellLink(value: any): value is JSONCellLink {
function isJSONCellLink(value: unknown): value is JSONCellLink {
return (isObject(value) && "cell" in value && isObject(value.cell) &&
"/" in value.cell && "path" in value &&
Array.isArray(value.path));
}

export function indexFromPath(array: unknown[], path: string): any {
export function indexFromPath(array: unknown[], path: string): number | undefined {
const number = new Number(path).valueOf();
return (Number.isInteger(number) && number >= 0 && number < array.length)
? number
: undefined;
}

export function elementAt(array: unknown[], path: string): any {
export function elementAt(array: unknown[], path: string): unknown {
const index = indexFromPath(array, path);
return (index === undefined) ? undefined : array[index];
}
Expand Down
8 changes: 4 additions & 4 deletions packages/runner/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,15 @@ export class Runner implements IRunner {
const defaults = extractDefaultValues(recipe.argumentSchema);

const internal = {
...deepCopy((defaults as { internal: any })?.internal),
...deepCopy((recipe.initial as { internal: any })?.internal),
...(deepCopy((defaults as { internal: any })?.internal) as any),
...(deepCopy((recipe.initial as { internal: any })?.internal) as any),
...processCell.get()?.internal,
};

// Still necessary until we consistently use schema for defaults.
// Only do it on first load.
if (!processCell.get()?.argument) {
argument = mergeObjects(argument, defaults);
argument = mergeObjects(argument as any, defaults) as T;
}

const recipeChanged = recipeId !== processCell.get()?.[TYPE];
Expand All @@ -218,7 +218,7 @@ export class Runner implements IRunner {
// TODO(seefeld): Be smarter about merging in case result changed. But since
// we don't yet update recipes, this isn't urgent yet.
resultCell.send(
unwrapOneLevelAndBindtoDoc<R>(recipe.result as R, processCell),
unwrapOneLevelAndBindtoDoc<R, any>(recipe.result as R, processCell),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing any as an explicit generic parameter undermines the stricter typing effort; consider supplying the actual DocImpl type or allowing inference.

);
}

Expand Down
Loading