Skip to content

Commit d37574f

Browse files
committed
use isRecord/isObject instead of manual checks wherever possible
1 parent 1c2e118 commit d37574f

File tree

13 files changed

+86
-74
lines changed

13 files changed

+86
-74
lines changed

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: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ export type OpaqueRefMethods<T> = {
6868

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

71-
export function isOpaqueRef(value: unknown): value is OpaqueRef<any> {
72-
return !!value && typeof (value as any)[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 = {
@@ -176,7 +177,8 @@ export type StreamAlias = {
176177
};
177178

178179
export function isStreamAlias(value: unknown): value is StreamAlias {
179-
return !!value && typeof (value as any).$stream === "boolean" && (value as any).$stream;
180+
return !!value && typeof (value as StreamAlias).$stream === "boolean" &&
181+
(value as StreamAlias).$stream;
180182
}
181183

182184
export type Module = {
@@ -257,7 +259,8 @@ export function isShadowRef(value: unknown): value is ShadowRef {
257259
!!value &&
258260
typeof value === "object" &&
259261
"shadowOf" in value &&
260-
(isOpaqueRef((value as any).shadowOf) || isShadowRef((value as any).shadowOf))
262+
(isOpaqueRef((value as ShadowRef).shadowOf) ||
263+
isShadowRef((value as ShadowRef).shadowOf))
261264
);
262265
}
263266

packages/builder/src/utils.ts

Lines changed: 8 additions & 8 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);
@@ -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);

packages/runner/src/cell.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from "./utils.ts";
1919
import type { ReactivityLog } from "./scheduler.ts";
2020
import { type EntityId } from "./doc-map.ts";
21+
import { isObject, isRecord } from "@commontools/utils/types";
2122
import { type Cancel, isCancel, useCancelGroup } from "./cancel.ts";
2223
import { validateAndTransform } from "./schema.ts";
2324
import { type Schema } from "@commontools/builder";
@@ -303,7 +304,7 @@ function createRegularCell<T>(
303304
),
304305
send: (newValue: Cellify<T>) => self.set(newValue),
305306
update: (values: Cellify<Partial<T>>) => {
306-
if (typeof values !== "object" || values === null) {
307+
if (!isRecord(values)) {
307308
throw new Error("Can't update with non-object value");
308309
}
309310
for (const [key, value] of Object.entries(values)) {
@@ -331,9 +332,8 @@ function createRegularCell<T>(
331332
const valuesToWrite = values.map((value: any) => {
332333
if (
333334
!isCell(value) && !isCellLink(value) && !isDoc(value) &&
334-
!Array.isArray(value) && typeof value === "object" &&
335-
value !== null &&
336-
value[ID] === undefined && getTopFrame()
335+
isObject(value) &&
336+
(value as { [ID]?: unknown })[ID] === undefined && getTopFrame()
337337
) {
338338
return {
339339
[ID]: getTopFrame()!.generatedIdCounter++,
@@ -498,8 +498,7 @@ function subscribeToReferencedDocs<T>(
498498
* @returns {boolean}
499499
*/
500500
export function isCell(value: any): value is Cell<any> {
501-
return typeof value === "object" && value !== null &&
502-
value[isCellMarker] === true;
501+
return isRecord(value) && value[isCellMarker] === true;
503502
}
504503

505504
const isCellMarker = Symbol("isCell");
@@ -510,8 +509,7 @@ const isCellMarker = Symbol("isCell");
510509
* @returns True if the value is a Stream
511510
*/
512511
export function isStream(value: any): value is Stream<any> {
513-
return typeof value === "object" && value !== null &&
514-
value[isStreamMarker] === true;
512+
return isRecord(value) && value[isStreamMarker] === true;
515513
}
516514

517515
const isStreamMarker = Symbol("isStream");
@@ -524,7 +522,6 @@ const isStreamMarker = Symbol("isStream");
524522
*/
525523
export function isCellLink(value: any): value is CellLink {
526524
return (
527-
typeof value === "object" && value !== null && isDoc(value.cell) &&
528-
Array.isArray(value.path)
525+
isRecord(value) && isDoc(value.cell) && Array.isArray(value.path)
529526
);
530527
}

packages/runner/src/doc-map.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { type CellLink, isCell, isCellLink } from "./cell.ts";
88
import { refer } from "merkle-reference";
99
import type { IDocumentMap, IRuntime } from "./runtime.ts";
10+
import { isRecord } from "@commontools/utils/types";
1011

1112
export type EntityId = {
1213
"/": string | Uint8Array;
@@ -34,11 +35,11 @@ export function createRef(
3435
seen.add(obj);
3536

3637
// Don't traverse into ids.
37-
if (typeof obj === "object" && obj !== null && "/" in obj) return obj;
38+
if (isRecord(obj) && "/" in obj) return obj;
3839

3940
// If there is a .toJSON method, replace obj with it, then descend.
4041
if (
41-
(typeof obj === "object" || typeof obj === "function") && obj !== null &&
42+
isRecord(obj) &&
4243
typeof obj.toJSON === "function"
4344
) {
4445
obj = obj.toJSON() ?? obj;
@@ -54,7 +55,7 @@ export function createRef(
5455
// If referencing other docs, return their ids (or random as fallback).
5556
if (isDoc(obj) || isCell(obj)) return obj.entityId ?? crypto.randomUUID();
5657
else if (Array.isArray(obj)) return obj.map(traverse);
57-
else if (typeof obj === "object" && obj !== null) {
58+
else if (isRecord(obj)) {
5859
return Object.fromEntries(
5960
Object.entries(obj).map(([key, value]) => [key, traverse(value)]),
6061
);
@@ -76,7 +77,7 @@ export function getEntityId(value: any): { "/": string } | undefined {
7677
if (typeof value === "string") {
7778
return value.startsWith("{") ? JSON.parse(value) : { "/": value };
7879
}
79-
if (typeof value === "object" && value !== null && "/" in value) {
80+
if (isRecord(value) && "/" in value) {
8081
return JSON.parse(JSON.stringify(value));
8182
}
8283

@@ -225,7 +226,7 @@ export class DocumentMap implements IDocumentMap {
225226

226227
private generateEntityId(value: any, cause?: any): EntityId {
227228
return createRef(
228-
typeof value === "object" && value !== null
229+
isRecord(value)
229230
? (value as object)
230231
: value !== undefined
231232
? { value }

packages/runner/src/doc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { type EntityId } from "./doc-map.ts";
1919
import type { IRuntime } from "./runtime.ts";
2020
import { type ReactivityLog } from "./scheduler.ts";
21+
import { isRecord } from "@commontools/utils/types";
2122
import { type Cancel } from "./cancel.ts";
2223
import { arrayEqual } from "./utils.ts";
2324
import { ContextualFlowControl } from "./index.ts";
@@ -422,8 +423,7 @@ export function makeOpaqueRef(
422423
* @returns {boolean}
423424
*/
424425
export function isDoc(value: any): value is DocImpl<any> {
425-
return typeof value === "object" && value !== null &&
426-
value[isDocMarker] === true;
426+
return isRecord(value) && value[isDocMarker] === true;
427427
}
428428

429429
const isDocMarker = Symbol("isDoc");

packages/runner/src/query-result-proxy.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type DocImpl, makeOpaqueRef } from "./doc.ts";
33
import { type CellLink } from "./cell.ts";
44
import { type ReactivityLog } from "./scheduler.ts";
55
import { diffAndUpdate, resolveLinkToValue, setNestedValue } from "./utils.ts";
6+
import { isRecord } from "@commontools/utils/types";
67

78
// Array.prototype's entries, and whether they modify the array
89
enum ArrayMethodType {
@@ -60,7 +61,7 @@ export function createQueryResultProxy<T>(
6061
log?.reads.push({ cell: valueCell, path: valuePath });
6162
const target = valueCell.getAtPath(valuePath) as any;
6263

63-
if (typeof target !== "object" || target === null) return target;
64+
if (!isRecord(target)) return target;
6465

6566
return new Proxy(target as object, {
6667
get: (target, prop, receiver) => {
@@ -199,7 +200,10 @@ export function createQueryResultProxy<T>(
199200
) {
200201
log?.writes.push({ cell: valueCell, path: [...valuePath, i] });
201202
if (valueCell.runtime) {
202-
valueCell.runtime.scheduler.queueEvent({ cell: valueCell, path: [...valuePath, i] }, undefined);
203+
valueCell.runtime.scheduler.queueEvent({
204+
cell: valueCell,
205+
path: [...valuePath, i],
206+
}, undefined);
203207
}
204208
}
205209
}
@@ -247,7 +251,7 @@ const createProxyForArrayValue = (
247251
};
248252

249253
function isProxyForArrayValue(value: any): value is ProxyForArrayValue {
250-
return typeof value === "object" && value !== null && originalIndex in value;
254+
return isRecord(value) && originalIndex in value;
251255
}
252256

253257
/**
@@ -280,8 +284,7 @@ export function getCellLinkOrThrow(value: any): CellLink {
280284
* @returns {boolean}
281285
*/
282286
export function isQueryResult(value: any): value is QueryResult<any> {
283-
return typeof value === "object" && value !== null &&
284-
value[getCellLink] !== undefined;
287+
return isRecord(value) && value[getCellLink] !== undefined;
285288
}
286289

287290
const getCellLink = Symbol("isQueryResultProxy");

packages/runner/src/runner.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { type CellLink, isCell, isCellLink } from "./cell.ts";
3838
import { isQueryResultForDereferencing } from "./query-result-proxy.ts";
3939
import { getCellLinkOrThrow } from "./query-result-proxy.ts";
4040
import type { IRunner, IRuntime } from "./runtime.ts";
41+
import { isRecord } from "@commontools/utils/types";
4142

4243
const moduleWrappers = {
4344
handler: (fn: (event: any, ...props: any[]) => any) => (props: any) =>
@@ -289,7 +290,7 @@ export class Runner implements IRunner {
289290
if (link && link.cell) {
290291
const maybePromise = this.runtime.storage.syncCell(link.cell);
291292
if (maybePromise instanceof Promise) promises.add(maybePromise);
292-
} else if (typeof value === "object" && value !== null) {
293+
} else if (isRecord(value)) {
293294
for (const key in value) syncAllMentionedCells(value[key]);
294295
}
295296
};

0 commit comments

Comments
 (0)