Skip to content

Commit 3287c17

Browse files
seefeldbclaude
andauthored
Code health improvement: Replace many uses of any + other stricter types (#1211)
* 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. * use isRecord/isObject instead of manual checks wherever possible * tightened tights even further + related fixes * tightened deepCopy and mergeObjects * post rebase fix for env --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c1dcd19 commit 3287c17

File tree

20 files changed

+306
-252
lines changed

20 files changed

+306
-252
lines changed

packages/builder/src/built-in.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface BuiltInLLMState<T> {
4040
pending: boolean;
4141
result?: T;
4242
partial?: string;
43-
error: any;
43+
error: unknown;
4444
}
4545

4646
export const llm = createNodeFactory({
@@ -60,7 +60,7 @@ export const fetchData = createNodeFactory({
6060
options?: RequestInit;
6161
result?: T;
6262
}>,
63-
) => Opaque<{ pending: boolean; result: T; error: any }>;
63+
) => Opaque<{ pending: boolean; result: T; error: unknown }>;
6464

6565
export const streamData = createNodeFactory({
6666
type: "ref",
@@ -71,9 +71,9 @@ export const streamData = createNodeFactory({
7171
options?: RequestInit;
7272
result?: T;
7373
}>,
74-
) => Opaque<{ pending: boolean; result: T; error: any }>;
74+
) => Opaque<{ pending: boolean; result: T; error: unknown }>;
7575

76-
export function ifElse<T = any, U = any, V = any>(
76+
export function ifElse<T = unknown, U = unknown, V = unknown>(
7777
condition: Opaque<T>,
7878
ifTrue: Opaque<U>,
7979
ifFalse: Opaque<V>,
@@ -82,30 +82,30 @@ export function ifElse<T = any, U = any, V = any>(
8282
type: "ref",
8383
implementation: "ifElse",
8484
});
85-
return ifElseFactory([condition, ifTrue, ifFalse]);
85+
return ifElseFactory([condition, ifTrue, ifFalse]) as OpaqueRef<U | V>;
8686
}
8787

88-
let ifElseFactory: NodeFactory<[any, any, any], any> | undefined;
88+
let ifElseFactory: NodeFactory<[unknown, unknown, unknown], any> | undefined;
8989

9090
export const navigateTo = createNodeFactory({
9191
type: "ref",
9292
implementation: "navigateTo",
93-
}) as (cell: OpaqueRef<any>) => OpaqueRef<string>;
93+
}) as (cell: OpaqueRef<unknown>) => OpaqueRef<string>;
9494

9595
// Example:
9696
// str`Hello, ${name}!`
9797
//
9898
// TODO(seefeld): This should be a built-in module
9999
export function str(
100100
strings: TemplateStringsArray,
101-
...values: any[]
101+
...values: unknown[]
102102
): OpaqueRef<string> {
103103
const interpolatedString = ({
104104
strings,
105105
values,
106106
}: {
107107
strings: TemplateStringsArray;
108-
values: any[];
108+
values: unknown[];
109109
}) =>
110110
strings.reduce(
111111
(result, str, i) => result + str + (i < values.length ? values[i] : ""),

packages/builder/src/opaque-ref.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getTopFrame, recipe } from "./recipe.ts";
1616
import { createNodeFactory } from "./module.ts";
1717
import { SchemaWithoutCell } from "./schema-to-ts.ts";
1818
import { ContextualFlowControl } from "../../runner/src/index.ts";
19+
import { isRecord } from "@commontools/utils/types";
1920

2021
let mapFactory: NodeFactory<any, any>;
2122

@@ -110,8 +111,11 @@ export function opaqueRef<T>(
110111
unsafe_getExternal: () => {
111112
if (!unsafe_binding) return proxy;
112113
const value = unsafe_materialize(unsafe_binding, path);
113-
if (typeof value === "object" && value !== null && value[toOpaqueRef]) {
114-
return value[toOpaqueRef]();
114+
if (
115+
isRecord(value) && value[toOpaqueRef] &&
116+
typeof value[toOpaqueRef] === "function"
117+
) {
118+
return (value[toOpaqueRef] as () => OpaqueRef<any>)();
115119
} else return proxy;
116120
},
117121
map: <S>(

packages/builder/src/recipe.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
traverseValue,
3232
} from "./utils.ts";
3333
import { SchemaWithoutCell } from "./schema-to-ts.ts";
34+
import { isRecord } from "@commontools/utils/types";
3435

3536
/** Declare a recipe
3637
*
@@ -187,13 +188,11 @@ function factoryFromRecipe<T, R>(
187188
// Fill in reasonable names for all cells, where possible:
188189

189190
// First from results
190-
if (typeof outputs === "object" && outputs !== null) {
191-
Object.entries(outputs).forEach(([key, value]) => {
192-
if (
193-
isOpaqueRef(value) && !value.export().path.length &&
194-
!value.export().name
195-
) {
196-
value.setName(key);
191+
if (isRecord(outputs)) {
192+
Object.entries(outputs).forEach(([key, value]: [string, unknown]) => {
193+
if (isOpaqueRef(value)) {
194+
const ref = value; // Typescript needs this to avoid type errors
195+
if (!ref.export().path.length && !ref.export().name) ref.setName(key);
197196
}
198197
});
199198
}
@@ -202,7 +201,7 @@ function factoryFromRecipe<T, R>(
202201
cells.forEach((cell) => {
203202
if (cell.export().path.length) return;
204203
cell.export().nodes.forEach((node: NodeRef) => {
205-
if (typeof node.inputs === "object" && node.inputs !== null) {
204+
if (isRecord(node.inputs)) {
206205
Object.entries(node.inputs).forEach(([key, input]) => {
207206
if (
208207
isOpaqueRef(input) && input.cell === cell && !cell.export().name

packages/builder/src/types.ts

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export type OpaqueRefMethods<T> = {
3232
set(value: Opaque<T> | T): void;
3333
key<K extends keyof T>(key: K): OpaqueRef<T[K]>;
3434
setDefault(value: Opaque<T> | T): void;
35-
setPreExisting(ref: any): void;
35+
setPreExisting(ref: unknown): void;
3636
setName(name: string): void;
3737
setSchema(schema: JSONSchema): void;
3838
connect(node: NodeRef): void;
@@ -42,7 +42,7 @@ export type OpaqueRefMethods<T> = {
4242
value?: Opaque<T>;
4343
defaultValue?: Opaque<T>;
4444
nodes: Set<NodeRef>;
45-
external?: any;
45+
external?: unknown;
4646
name?: string;
4747
schema?: JSONSchema;
4848
rootSchema?: JSONSchema;
@@ -60,16 +60,17 @@ export type OpaqueRefMethods<T> = {
6060
array: T,
6161
) => Opaque<S>,
6262
): Opaque<S[]>;
63-
toJSON(): any;
63+
toJSON(): unknown;
6464
[Symbol.iterator](): Iterator<T>;
6565
[Symbol.toPrimitive](hint: string): T;
6666
[isOpaqueRefMarker]: true;
6767
};
6868

6969
export const isOpaqueRefMarker = Symbol("isOpaqueRef");
7070

71-
export function isOpaqueRef(value: any): value is OpaqueRef<any> {
72-
return value && typeof value[isOpaqueRefMarker] === "boolean";
71+
export function isOpaqueRef<T = any>(value: unknown): value is OpaqueRef<T> {
72+
return !!value &&
73+
typeof (value as OpaqueRef<T>)[isOpaqueRefMarker] === "boolean";
7374
}
7475

7576
export type NodeRef = {
@@ -80,7 +81,7 @@ export type NodeRef = {
8081
};
8182

8283
export type toJSON = {
83-
toJSON(): any;
84+
toJSON(): unknown;
8485
};
8586

8687
export type NodeFactory<T, R> =
@@ -118,16 +119,16 @@ export interface JSONObject extends Record<string, JSONValue> {}
118119
// Annotations when writing data that help determine the entity id. They are
119120
// removed before sending to storage.
120121
export interface IDFields {
121-
[ID]?: any;
122-
[ID_FIELD]?: any;
122+
[ID]?: unknown;
123+
[ID_FIELD]?: unknown;
123124
}
124125

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

159160
export type Alias = {
160161
$alias: {
161-
cell?: any;
162+
cell?: unknown;
162163
path: PropertyKey[];
163164
schema?: JSONSchema;
164165
rootSchema?: JSONSchema;
165166
};
166167
};
167168

168-
export function isAlias(value: any): value is Alias {
169+
export function isAlias(value: unknown): value is Alias {
169170
return isObject(value) && "$alias" in value && isObject(value.$alias) &&
170171
"path" in value.$alias &&
171172
Array.isArray(value.$alias.path);
@@ -175,8 +176,8 @@ export type StreamAlias = {
175176
$stream: true;
176177
};
177178

178-
export function isStreamAlias(value: any): value is StreamAlias {
179-
return !!value && typeof value.$stream === "boolean" && value.$stream;
179+
export function isStreamAlias(value: unknown): value is StreamAlias {
180+
return isObject(value) && "$stream" in value && value.$stream === true;
180181
}
181182

182183
export type Module = {
@@ -191,10 +192,10 @@ export type Handler<T = any, R = any> = Module & {
191192
with: (inputs: Opaque<T>) => OpaqueRef<R>;
192193
};
193194

194-
export function isModule(value: any): value is Module {
195+
export function isModule(value: unknown): value is Module {
195196
return (
196197
(typeof value === "function" || typeof value === "object") &&
197-
typeof value.type === "string"
198+
value !== null && typeof (value as unknown as Module).type === "string"
198199
);
199200
}
200201

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

224-
export function isRecipe(value: any): value is Recipe {
225+
export function isRecipe(value: unknown): value is Recipe {
225226
return (
226227
(typeof value === "function" || typeof value === "object") &&
227228
value !== null &&
228-
!!value.argumentSchema &&
229-
!!value.resultSchema &&
230-
!!value.nodes &&
231-
Array.isArray(value.nodes)
229+
!!(value as any).argumentSchema &&
230+
!!(value as any).resultSchema &&
231+
!!(value as any).nodes &&
232+
Array.isArray((value as any).nodes)
232233
);
233234
}
234235

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

237-
export function canBeOpaqueRef(value: any): value is CanBeOpaqueRef {
238+
export function canBeOpaqueRef(value: unknown): value is CanBeOpaqueRef {
238239
return (
239240
(typeof value === "object" || typeof value === "function") &&
240241
value !== null &&
241-
typeof value[toOpaqueRef] === "function"
242+
typeof (value as any)[toOpaqueRef] === "function"
242243
);
243244
}
244245

@@ -252,12 +253,13 @@ export type ShadowRef = {
252253
shadowOf: OpaqueRef<any> | ShadowRef;
253254
};
254255

255-
export function isShadowRef(value: any): value is ShadowRef {
256+
export function isShadowRef(value: unknown): value is ShadowRef {
256257
return (
257258
!!value &&
258259
typeof value === "object" &&
259260
"shadowOf" in value &&
260-
(isOpaqueRef(value.shadowOf) || isShadowRef(value.shadowOf))
261+
(isOpaqueRef((value as ShadowRef).shadowOf) ||
262+
isShadowRef((value as ShadowRef).shadowOf))
261263
);
262264
}
263265

@@ -269,7 +271,7 @@ export type UnsafeBinding = {
269271

270272
export type Frame = {
271273
parent?: Frame;
272-
cause?: any;
274+
cause?: unknown;
273275
generatedIdCounter: number;
274276
opaqueRefs: Set<OpaqueRef<any>>;
275277
unsafe_binding?: UnsafeBinding;
@@ -281,12 +283,12 @@ export type Static = {
281283
[isStaticMarker]: true;
282284
};
283285

284-
export function isStatic(value: any): value is Static {
286+
export function isStatic(value: unknown): value is Static {
285287
return typeof value === "object" && value !== null &&
286-
value[isStaticMarker] === true;
288+
(value as any)[isStaticMarker] === true;
287289
}
288290

289-
export function markAsStatic(value: any): any {
290-
value[isStaticMarker] = true;
291+
export function markAsStatic(value: unknown): unknown {
292+
(value as any)[isStaticMarker] = true;
291293
return value;
292294
}

packages/builder/src/utils.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
isCellLink,
2828
isDoc,
2929
} from "@commontools/runner";
30+
import { isObject, isRecord } from "@commontools/utils/types";
3031

3132
/**
3233
* Traverse a value, _not_ entering cells
@@ -52,8 +53,7 @@ export function traverseValue(
5253
(!isOpaqueRef(value) &&
5354
!canBeOpaqueRef(value) &&
5455
!isShadowRef(value) &&
55-
typeof value === "object" &&
56-
value !== null) ||
56+
isRecord(value)) ||
5757
isRecipe(value)
5858
) {
5959
return staticWrap(
@@ -105,7 +105,7 @@ export function getValueAtPath(obj: any, path: PropertyKey[]): any {
105105
export function hasValueAtPath(obj: any, path: PropertyKey[]): boolean {
106106
let current = obj;
107107
for (const key of path) {
108-
if (!current || typeof current !== "object" || !(key in current)) {
108+
if (!isRecord(current) || !(key in current)) {
109109
return false;
110110
}
111111
current = current[key];
@@ -115,7 +115,7 @@ export function hasValueAtPath(obj: any, path: PropertyKey[]): boolean {
115115

116116
export const deepEqual = (a: any, b: any): boolean => {
117117
if (a === b) return true;
118-
if (a && b && typeof a === "object" && typeof b === "object") {
118+
if (isRecord(a) && isRecord(b)) {
119119
if (a.constructor !== b.constructor) return false;
120120
const keysA = Object.keys(a);
121121
const keysB = Object.keys(b);
@@ -138,8 +138,8 @@ export function toJSONWithAliases(
138138
): JSONValue | undefined {
139139
if (isStatic(value) && !processStatic) {
140140
return markAsStatic(
141-
toJSONWithAliases(value, paths, ignoreSelfAliases, path, true),
142-
);
141+
toJSONWithAliases(value, paths, ignoreSelfAliases, path, true) as any,
142+
) as JSONValue;
143143
} // Convert regular cells to opaque refs
144144
else if (canBeOpaqueRef(value)) value = makeOpaqueRef(value);
145145
// Convert parent opaque refs to shadow refs
@@ -196,7 +196,7 @@ export function toJSONWithAliases(
196196
} else if (!("cell" in alias) || typeof alias.cell === "number") {
197197
return {
198198
$alias: {
199-
cell: (alias.cell ?? 0) + 1,
199+
cell: ((alias.cell as number) ?? 0) + 1,
200200
path: alias.path as (string | number)[],
201201
},
202202
} satisfies Alias;
@@ -211,7 +211,7 @@ export function toJSONWithAliases(
211211
);
212212
}
213213

214-
if (typeof value === "object" || isRecipe(value)) {
214+
if (isRecord(value) || isRecipe(value)) {
215215
const result: any = {};
216216
let hasValue = false;
217217
for (const key in value as any) {
@@ -278,11 +278,11 @@ export function createJsonSchema(
278278
schema.items = {};
279279
} else {
280280
const first = value[0];
281-
if (first && typeof first === "object" && !Array.isArray(first)) {
281+
if (isObject(first)) {
282282
const properties: { [key: string]: any } = {};
283283
for (let i = 0; i < value.length; i++) {
284284
const item = value?.[i];
285-
if (typeof item === "object" && item !== null) {
285+
if (isRecord(item)) {
286286
Object.keys(item).forEach((key) => {
287287
if (!(key in properties)) {
288288
properties[key] = analyzeType(
@@ -426,7 +426,7 @@ function attachCfcToOutputs<T, R>(
426426
const cfcSchema: JSONSchema = { ...outputSchema, ifc };
427427
(outputs as OpaqueRef<T>).setSchema(cfcSchema);
428428
return;
429-
} else if (typeof outputs === "object" && outputs !== null) {
429+
} else if (isRecord(outputs)) {
430430
// Descend into objects and arrays
431431
for (const [key, value] of Object.entries(outputs)) {
432432
attachCfcToOutputs(value, cfc, lubClassification);

0 commit comments

Comments
 (0)