Skip to content

Commit e76bcc6

Browse files
committed
- tightened tights even further + related fixes
- tightened deepCopy and mergeObjects
1 parent d37574f commit e76bcc6

File tree

12 files changed

+97
-69
lines changed

12 files changed

+97
-69
lines changed

packages/builder/src/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ export type StreamAlias = {
177177
};
178178

179179
export function isStreamAlias(value: unknown): value is StreamAlias {
180-
return !!value && typeof (value as StreamAlias).$stream === "boolean" &&
181-
(value as StreamAlias).$stream;
180+
return isObject(value) && "$stream" in value && value.$stream === true;
182181
}
183182

184183
export type Module = {
@@ -196,7 +195,7 @@ export type Handler<T = any, R = any> = Module & {
196195
export function isModule(value: unknown): value is Module {
197196
return (
198197
(typeof value === "function" || typeof value === "object") &&
199-
typeof (value as any).type === "string"
198+
value !== null && typeof (value as unknown as Module).type === "string"
200199
);
201200
}
202201

packages/deno-vite-plugin/src/prefixPlugin.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Plugin } from "vite";
1+
import { type Plugin, type ResolvedConfig } from "vite";
22
import {
33
DenoResolveResult,
44
resolveDeno,
@@ -14,10 +14,10 @@ export default function denoPrefixPlugin(
1414
return {
1515
name: "deno:prefix",
1616
enforce: "pre",
17-
configResolved(config) {
17+
configResolved(config: ResolvedConfig) {
1818
root = config.root;
1919
},
20-
async resolveId(id, importer) {
20+
async resolveId(id: string, importer: string | undefined) {
2121
if (id.startsWith("npm:")) {
2222
const resolved = await resolveDeno(id, root);
2323
if (resolved === null) return;

packages/deno-vite-plugin/src/resolvePlugin.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Plugin } from "vite";
1+
import { type Plugin, type ResolvedConfig } from "vite";
22
import {
33
type DenoMediaType,
44
type DenoResolveResult,
@@ -17,16 +17,16 @@ export default function denoPlugin(
1717

1818
return {
1919
name: "deno",
20-
configResolved(config) {
20+
configResolved(config: ResolvedConfig) {
2121
root = config.root;
2222
},
23-
async resolveId(id, importer) {
23+
async resolveId(id: string, importer: string | undefined) {
2424
// The "pre"-resolve plugin already resolved it
2525
if (isDenoSpecifier(id)) return;
2626

2727
return await resolveViteSpecifier(id, cache, root, importer);
2828
},
29-
async load(id) {
29+
async load(id: string) {
3030
if (!isDenoSpecifier(id)) return;
3131

3232
const { loader, resolved } = parseDenoSpecifier(id);

packages/jumble/src/main.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ import { ActivityProvider } from "@/contexts/ActivityContext.tsx";
3131
import { RuntimeProvider } from "@/contexts/RuntimeContext.tsx";
3232
import { ROUTES } from "@/routes.ts";
3333

34+
declare global {
35+
interface ImportMeta {
36+
readonly env: {
37+
readonly VITE_COMMIT_SHA?: string;
38+
[key: string]: string | undefined;
39+
};
40+
}
41+
}
42+
3443
// Determine environment based on hostname
3544
const determineEnvironment = () => {
3645
const hostname = globalThis.location.hostname;

packages/memory/traverse.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export function getAtPath<K, S>(
233233
);
234234
}
235235
let cursor = fact;
236-
for (const [index, part] of path.entries()) {
236+
for (const [_index, part] of path.entries()) {
237237
// TODO(@ubik2) Call toJSON on object if it's a function?
238238
if (isPointer(cursor)) {
239239
[doc, docRoot, cursor] = followPointer(
@@ -247,7 +247,7 @@ export function getAtPath<K, S>(
247247
const cursorObj = cursor as JSONObject;
248248
cursor = cursorObj[part] as JSONValue;
249249
} else if (Array.isArray(cursor)) {
250-
cursor = elementAt(cursor, part) as JSONValue;
250+
cursor = elementAt(cursor, part) as JSONValue | undefined;
251251
} else {
252252
// we can only descend into pointers, objects, and arrays
253253
return [doc, docRoot, undefined];
@@ -368,7 +368,10 @@ function isJSONCellLink(value: unknown): value is JSONCellLink {
368368
Array.isArray(value.path));
369369
}
370370

371-
export function indexFromPath(array: unknown[], path: string): number | undefined {
371+
export function indexFromPath(
372+
array: unknown[],
373+
path: string,
374+
): number | undefined {
372375
const number = new Number(path).valueOf();
373376
return (Number.isInteger(number) && number >= 0 && number < array.length)
374377
? number

packages/runner/src/cell.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,9 @@ function createRegularCell<T>(
362362
// Hacky retry logic for push only. See storage.ts for details on this
363363
// retry approach and what we should really be doing instead.
364364
if (!ref.cell.retry) ref.cell.retry = [];
365-
ref.cell.retry.push((newBaseValue: any[]) => {
365+
ref.cell.retry.push((newBaseValue) => {
366366
// Unlikely, but maybe the conflict reset to undefined?
367-
if (newBaseValue === undefined) {
367+
if (!Array.isArray(newBaseValue)) {
368368
newBaseValue = Array.isArray(schema?.default) ? schema.default : [];
369369
}
370370

@@ -373,7 +373,7 @@ function createRegularCell<T>(
373373
const newValues = JSON.parse(JSON.stringify(appended));
374374

375375
// Reappend the new values.
376-
return [...newBaseValue, ...newValues];
376+
return [...(newBaseValue as unknown[]), ...newValues];
377377
});
378378
},
379379
equals: (other: Cell<any>) =>

packages/runner/src/doc-map.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ export function createRef(
3838
if (isRecord(obj) && "/" in obj) return obj;
3939

4040
// If there is a .toJSON method, replace obj with it, then descend.
41+
// TODO(seefeld): We have to accept functions for now as the recipe factory
42+
// is a function and has a .toJSON method. But we plan to move away from
43+
// that kind of serialization anyway, so once we did, remove this.
4144
if (
42-
isRecord(obj) &&
45+
(isRecord(obj) || typeof obj === "function") &&
4346
typeof obj.toJSON === "function"
4447
) {
4548
obj = obj.toJSON() ?? obj;

packages/runner/src/runner.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class Runner implements IRunner {
9090
let processCell: DocImpl<{
9191
[TYPE]: string;
9292
argument?: T;
93-
internal?: { [key: string]: any };
93+
internal?: JSONValue;
9494
resultRef: { cell: DocImpl<R>; path: PropertyKey[] };
9595
}>;
9696

@@ -182,18 +182,25 @@ export class Runner implements IRunner {
182182
}
183183

184184
// Walk the recipe's schema and extract all default values
185-
const defaults = extractDefaultValues(recipe.argumentSchema);
186-
187-
const internal = {
188-
...(deepCopy((defaults as { internal: any })?.internal) as any),
189-
...(deepCopy((recipe.initial as { internal: any })?.internal) as any),
190-
...processCell.get()?.internal,
191-
};
185+
const defaults = extractDefaultValues(recipe.argumentSchema) as Partial<T>;
186+
187+
// Important to use DeepCopy here, as the resulting object will be modified!
188+
const previousInternal = processCell.get()?.internal;
189+
const internal: JSONValue = Object.assign(
190+
{},
191+
deepCopy((defaults as unknown as { internal: JSONValue })?.internal),
192+
deepCopy(
193+
isRecord(recipe.initial) && isRecord(recipe.initial.internal)
194+
? recipe.initial.internal
195+
: {},
196+
),
197+
isRecord(previousInternal) ? previousInternal : {},
198+
);
192199

193200
// Still necessary until we consistently use schema for defaults.
194201
// Only do it on first load.
195202
if (!processCell.get()?.argument) {
196-
argument = mergeObjects(argument as any, defaults) as T;
203+
argument = mergeObjects<T>(argument as any, defaults);
197204
}
198205

199206
const recipeChanged = recipeId !== processCell.get()?.[TYPE];

packages/runner/src/schema.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { type CellLink, createCell, isCell, isCellLink } from "./cell.ts";
44
import { type ReactivityLog } from "./scheduler.ts";
55
import { resolveLinks, resolveLinkToAlias } from "./utils.ts";
66
import { ContextualFlowControl } from "./index.ts";
7-
import { isRecord, type Mutable } from "@commontools/utils/types";
7+
import { isObject, isRecord, type Mutable } from "@commontools/utils/types";
88

99
/**
1010
* Schemas are mostly a subset of JSONSchema.
@@ -134,7 +134,7 @@ function processDefaultValue(
134134

135135
// Handle object type defaults
136136
if (
137-
resolvedSchema?.type === "object" && isRecord(defaultValue)
137+
resolvedSchema?.type === "object" && isObject(defaultValue)
138138
) {
139139
const result: Record<string, any> = {};
140140
const processedKeys = new Set<string>();
@@ -148,7 +148,7 @@ function processDefaultValue(
148148
result[key] = processDefaultValue(
149149
doc,
150150
[...path, key],
151-
defaultValue[key],
151+
defaultValue[key as keyof typeof defaultValue],
152152
propSchema,
153153
log,
154154
rootSchema,
@@ -202,7 +202,7 @@ function processDefaultValue(
202202
result[key] = processDefaultValue(
203203
doc,
204204
[...path, key],
205-
defaultValue[key],
205+
defaultValue[key as keyof typeof defaultValue],
206206
additionalPropertiesSchema,
207207
log,
208208
rootSchema,

packages/runner/src/utils.ts

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
type Alias,
23
ID,
34
ID_FIELD,
45
isAlias,
@@ -22,7 +23,7 @@ import {
2223
import { type CellLink, isCell, isCellLink } from "./cell.ts";
2324
import { type ReactivityLog } from "./scheduler.ts";
2425
import { ContextualFlowControl } from "./index.ts";
25-
import { isObject, isRecord } from "@commontools/utils/types";
26+
import { isObject, isRecord, type Mutable } from "@commontools/utils/types";
2627

2728
/**
2829
* Extracts default values from a JSON schema object.
@@ -37,10 +38,14 @@ export function extractDefaultValues(
3738
if (
3839
schema.type === "object" && schema.properties && isObject(schema.properties)
3940
) {
40-
const obj = deepCopy(schema.default ?? {}) as Record<string, JSONValue>;
41+
// Ignore the schema.default if it's not an object, since it's not a valid
42+
// default value for an object.
43+
const obj = deepCopy(isRecord(schema.default) ? schema.default : {});
4144
for (const [propKey, propSchema] of Object.entries(schema.properties)) {
4245
const value = extractDefaultValues(propSchema);
43-
if (value !== undefined) obj[propKey] = value;
46+
if (value !== undefined) {
47+
(obj as Record<string, unknown>)[propKey] = value;
48+
}
4449
}
4550

4651
return Object.entries(obj).length > 0 ? obj : undefined;
@@ -56,12 +61,12 @@ export function extractDefaultValues(
5661
* @param objects - Objects to merge
5762
* @returns A merged object, or undefined if no objects provided
5863
*/
59-
export function mergeObjects<T extends Record<string, unknown>>(
60-
...objects: (T | undefined)[]
61-
): T | undefined {
64+
export function mergeObjects<T>(
65+
...objects: (Partial<T> | undefined)[]
66+
): T {
6267
objects = objects.filter((obj) => obj !== undefined);
63-
if (objects.length === 0) return undefined;
64-
if (objects.length === 1) return objects[0];
68+
if (objects.length === 0) return {} as T;
69+
if (objects.length === 1) return objects[0] as T;
6570

6671
const seen = new Set<PropertyKey>();
6772
const result: Record<string, unknown> = {};
@@ -80,14 +85,18 @@ export function mergeObjects<T extends Record<string, unknown>>(
8085
isCell(obj) ||
8186
isStatic(obj)
8287
) {
83-
return obj;
88+
return obj as T;
8489
}
8590

8691
// Then merge objects, only passing those on that have any values.
8792
for (const key of Object.keys(obj)) {
8893
if (seen.has(key)) continue;
8994
seen.add(key);
90-
const merged = mergeObjects(...objects.map((obj) => obj?.[key] as T));
95+
const merged = mergeObjects<T[keyof T]>(
96+
...objects.map((obj) =>
97+
(obj as Record<string, unknown>)?.[key] as T[keyof T]
98+
),
99+
);
91100
if (merged !== undefined) result[key] = merged;
92101
}
93102
}
@@ -302,7 +311,7 @@ export function findAllAliasedCells<T>(
302311
const doc = (binding.$alias.cell ?? origDoc) as DocImpl<T>;
303312
const path = binding.$alias.path;
304313
if (docs.find((c) => c.cell === doc && c.path === path)) return;
305-
docs.push({ cell: doc, path });
314+
docs.push({ cell: doc as DocImpl<unknown>, path });
306315
find(doc.getAtPath(path), doc);
307316
} else if (Array.isArray(binding)) {
308317
for (const value of binding) find(value, origDoc);
@@ -562,21 +571,21 @@ export function maybeGetCellLink<T>(
562571

563572
// Follows aliases and returns cell reference describing the last alias.
564573
// Only logs interim aliases, not the first one, and not the non-alias value.
565-
export function followAliases<T>(
566-
alias: unknown,
574+
export function followAliases<T = any>(
575+
alias: Alias,
567576
doc: DocImpl<T>,
568577
log?: ReactivityLog,
569578
): CellLink {
570-
if (!isAlias(alias)) {
579+
if (isAlias(alias)) {
580+
return followLinks(
581+
{ cell: doc, ...alias.$alias } as CellLink,
582+
log,
583+
createVisits(),
584+
true,
585+
);
586+
} else {
571587
throw new Error(`Alias expected: ${JSON.stringify(alias)}`);
572588
}
573-
574-
return followLinks(
575-
{ cell: doc, ...(alias as any).$alias },
576-
log,
577-
createVisits(),
578-
true,
579-
);
580589
}
581590

582591
/**
@@ -955,14 +964,17 @@ export function containsOpaqueRef(value: unknown): boolean {
955964
return false;
956965
}
957966

958-
export function deepCopy(value: unknown): unknown {
967+
export function deepCopy<T = unknown>(value: T): Mutable<T> {
959968
if (isQueryResultForDereferencing(value)) {
960-
return deepCopy(getCellLinkOrThrow(value));
969+
return deepCopy(getCellLinkOrThrow(value)) as unknown as Mutable<T>;
961970
}
962-
if (isDoc(value) || isCell(value)) return value;
971+
if (isDoc(value) || isCell(value)) return value as Mutable<T>;
963972
if (isRecord(value)) {
964-
return Array.isArray(value) ? value.map(deepCopy) : Object.fromEntries(
965-
Object.entries(value).map(([key, value]) => [key, deepCopy(value)]),
966-
);
967-
} else return value;
973+
return Array.isArray(value)
974+
? value.map(deepCopy) as unknown as Mutable<T>
975+
: Object.fromEntries(
976+
Object.entries(value).map(([key, value]) => [key, deepCopy(value)]),
977+
) as unknown as Mutable<T>;
978+
// Literal value:
979+
} else return value as Mutable<T>;
968980
}

0 commit comments

Comments
 (0)