mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
refactor(domain): one owner for the non-array record predicate (#10588)
* refactor(domain): one owner for the non-array record predicate Thirty-four source files each declared isRecord as a non-null object that is not an array, and six more spelled the same predicate as isObject, isSchemaRecord or isStorageRecord. They now import isRecord from src/domain/isRecord.ts, whose overloads keep JsonValue narrowing for the desktop and environment readers. DshCordisPatch's predicate also excludes Cordis expressions, so it is renamed isPatchRecord. architecture-guards refuses a new local copy under any name. The seven files that still hold one are listed and must leave the list when their copy goes. * fix(guards): count record predicate copies precisely * test(hopper): retry after confirmed application exit * fix: reject stale record predicate exceptions
This commit is contained in:
1 parent
d5e0747025
commit
5bf66f7500
49 files changed
+168
-205
No files matched your search
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
||||
import { readFile, readdir, writeFile } from "node:fs/promises";
|
||||
import { join, relative, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { recordPredicateCounts } from "./record-predicate-guard.mjs";
|
||||
import {
|
||||
compareCountedBaseline,
|
||||
recordLoweredBaseline,
|
||||
@@ -167,6 +168,43 @@ check(
|
||||
],
|
||||
);
|
||||
|
||||
// "Non-null object other than an array" has one owner: src/domain/isRecord.ts.
|
||||
// A local predicate with that body, under any name, is a copy. The listed files
|
||||
// still hold one and may only be removed from the list; a listed file that no
|
||||
// longer matches fails so the list shrinks with the code.
|
||||
const RECORD_PREDICATE_COPIES = [
|
||||
// Exported under a module-specific name; importers still use that name.
|
||||
"src/browser/history/CaptureRedaction.ts",
|
||||
"src/contracts/compactJsonSchema.ts",
|
||||
"src/domain/analysisView/environmentStateRules.ts",
|
||||
// Owned by the advertised-schema workstream.
|
||||
"src/contracts/inputSchemaPresentation.ts",
|
||||
// Narrows a protocol-specific parameter type, not unknown.
|
||||
"src/browser/execution/CdpRuntimeInitiator.ts",
|
||||
// Named `object`, which also spells the typeof result in these files.
|
||||
"src/pcap/PcapDnsWire.ts",
|
||||
"src/pcap/PcapOutput.ts",
|
||||
];
|
||||
const mismatchedRecordCopies = files.filter(({ path, content }) => {
|
||||
if (path === "src/domain/isRecord.ts") return false;
|
||||
const { named, bodies } = recordPredicateCounts(content);
|
||||
return (
|
||||
named !== 0 || bodies !== (RECORD_PREDICATE_COPIES.includes(path) ? 1 : 0)
|
||||
);
|
||||
});
|
||||
const pathsWithRecordCopies = new Set(files.map(({ path }) => path));
|
||||
const missingRecordCopies = RECORD_PREDICATE_COPIES.filter(
|
||||
(path) => !pathsWithRecordCopies.has(path),
|
||||
);
|
||||
if (mismatchedRecordCopies.length > 0)
|
||||
failures.push(
|
||||
`local non-array record predicate count changed (import isRecord and update the baseline):\n${mismatchedRecordCopies.map(({ path }) => ` ${path}`).join("\n")}`,
|
||||
);
|
||||
if (missingRecordCopies.length > 0)
|
||||
failures.push(
|
||||
`record predicate baseline references missing files; remove those entries:\n${missingRecordCopies.map((path) => ` ${path}`).join("\n")}`,
|
||||
);
|
||||
|
||||
// Node clamps a timer delay above the signed 32-bit bound to 1 ms. Durations
|
||||
// use MAX_NODE_TIMER_DELAY_MS or timerDurationMsSchema from their one owner.
|
||||
// The allowed files bound signed 32-bit integers that are not timer delays.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export function recordPredicateCounts(content: string): {
|
||||
named: number;
|
||||
bodies: number;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/** Count declarations named isRecord and declarations with the shared predicate. */
|
||||
export const recordPredicateCounts = (content) => {
|
||||
const arrow =
|
||||
/\bconst\s+[\w$]+\s*=\s*\(\s*([\w$]+)[^)]*\)\s*(?::[^=]+)?=>\s*typeof\s+\1\s*===\s*["']object["']\s*&&\s*\1\s*!==\s*null\s*&&\s*!Array\.isArray\(\1\)\s*(?=;|\})/gu;
|
||||
const declaration =
|
||||
/\bfunction\s+[\w$]+\s*\(\s*([\w$]+)[^)]*\)\s*(?::[^{]+)?\{\s*return\s+typeof\s+\1\s*===\s*["']object["']\s*&&\s*\1\s*!==\s*null\s*&&\s*!Array\.isArray\(\1\)\s*(?=;|\})/gu;
|
||||
return {
|
||||
named: [...content.matchAll(/\b(?:const|function)\s+isRecord\b/gu)].length,
|
||||
bodies:
|
||||
[...content.matchAll(arrow)].length +
|
||||
[...content.matchAll(declaration)].length,
|
||||
};
|
||||
};
|
||||
@@ -14,6 +14,7 @@ import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.j
|
||||
import type { ConfigurationError } from "../domain/configurationErrors.js";
|
||||
import { mergeEnvironmentOverrides } from "../process/snapshotEnvironment.js";
|
||||
import { PRODUCT_IDENTITY } from "../identity.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
/** A parsed layer retains its path so rereading the write target replaces only that layer. */
|
||||
export interface ClientConfigurationSource extends ClientConfigurationDocument {
|
||||
@@ -167,7 +168,7 @@ export const readClientConfigurationFiles = async (
|
||||
|
||||
/** OpenCode merges server objects in file order, replacing arrays and scalar values. */
|
||||
const mergeConfigurationValue = (previous: unknown, next: unknown): unknown => {
|
||||
if (!isObject(previous) || !isObject(next)) return next;
|
||||
if (!isRecord(previous) || !isRecord(next)) return next;
|
||||
return Object.fromEntries(
|
||||
[...new Set([...Object.keys(previous), ...Object.keys(next)])].map(
|
||||
(key) => [
|
||||
@@ -179,8 +180,6 @@ const mergeConfigurationValue = (previous: unknown, next: unknown): unknown => {
|
||||
),
|
||||
);
|
||||
};
|
||||
const isObject = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Native OpenCode dialect applies across all selected configuration layers. */
|
||||
export const clientWriteDialect = (
|
||||
@@ -260,12 +259,12 @@ export const mergeClientConfigurationDocuments = (
|
||||
const key = document.environmentKey;
|
||||
if (
|
||||
name === PRODUCT_IDENTITY.mcpServerKey &&
|
||||
isObject(merged) &&
|
||||
isObject(merged[key])
|
||||
isRecord(merged) &&
|
||||
isRecord(merged[key])
|
||||
) {
|
||||
const before =
|
||||
isObject(previous) && isObject(previous[key]) ? previous[key] : {};
|
||||
const after = isObject(next) && isObject(next[key]) ? next[key] : {};
|
||||
isRecord(previous) && isRecord(previous[key]) ? previous[key] : {};
|
||||
const after = isRecord(next) && isRecord(next[key]) ? next[key] : {};
|
||||
servers[name] = {
|
||||
...merged,
|
||||
[key]: mergeEnvironmentOverrides(before, after, platform),
|
||||
|
||||
@@ -11,7 +11,7 @@ export const DSH_ENTRY_ID = "rea-mcp";
|
||||
export const DSH_CLIENT_PACKAGE = "@deepseek-ai/dsh-mcp-client";
|
||||
const LAUNCHER_KEYS = new Set(["serverName", "transport", "command", "args"]);
|
||||
const SELECTOR_KEYS = new Set(["id", "name"]);
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
const isPatchRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
@@ -133,7 +133,8 @@ export class DshCordisDocument {
|
||||
: undefined;
|
||||
const config = this.#field("config");
|
||||
// Ownership survives malformed config so uninstall can remove the source row.
|
||||
this.config = insertions === 0 ? undefined : isRecord(config) ? config : {};
|
||||
this.config =
|
||||
insertions === 0 ? undefined : isPatchRecord(config) ? config : {};
|
||||
}
|
||||
|
||||
#map(node: unknown): YAMLMap<unknown, unknown> | undefined {
|
||||
@@ -209,7 +210,7 @@ export class DshCordisDocument {
|
||||
},
|
||||
),
|
||||
);
|
||||
if (config !== undefined && !isRecord(config))
|
||||
if (config !== undefined && !isPatchRecord(config))
|
||||
return err(
|
||||
new ConfigurationError("Cordis REA config must be a mapping", {
|
||||
settings: [
|
||||
@@ -221,7 +222,7 @@ export class DshCordisDocument {
|
||||
}),
|
||||
);
|
||||
const raw = this.config?.env;
|
||||
if (raw !== undefined && !isRecord(raw))
|
||||
if (raw !== undefined && !isPatchRecord(raw))
|
||||
return err(
|
||||
new ConfigurationError("Cordis server environment must be a mapping"),
|
||||
);
|
||||
@@ -268,7 +269,7 @@ export class DshCordisDocument {
|
||||
const environment = this.environment(overrides);
|
||||
if (!environment.ok) return environment;
|
||||
const env = mergeEnvironmentOverrides<unknown>(
|
||||
isRecord(this.config?.env) ? this.config.env : {},
|
||||
isPatchRecord(this.config?.env) ? this.config.env : {},
|
||||
overrides,
|
||||
);
|
||||
const desired = { ...this.config, ...dshClientRegistrationEntry(command) };
|
||||
@@ -378,7 +379,7 @@ export class DshCordisDocument {
|
||||
this.#yaml.setValue(config, key, desired[key]);
|
||||
const env = this.#map(this.#yaml.field(sourceConfig, "env"));
|
||||
const desiredEnv = desired.env;
|
||||
if (isRecord(desiredEnv)) {
|
||||
if (isPatchRecord(desiredEnv)) {
|
||||
const nextEnv =
|
||||
env === undefined
|
||||
? this.#yaml.document.createNode({})
|
||||
|
||||
@@ -78,6 +78,7 @@ import {
|
||||
type TaskSummary,
|
||||
} from "./DesktopTaskExport.js";
|
||||
import { taskReads } from "./DesktopExportReaders.js";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
const OPERATION = "export_desktop_application";
|
||||
/** Largest seed file the export copies; the state view reads only 64 MiB. */
|
||||
@@ -136,11 +137,6 @@ export interface DesktopExportResult {
|
||||
readonly consented_files: readonly string[];
|
||||
}
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** The application's dconf groups at the seed, as a dconf key file with the identity scrubbed from its strings. */
|
||||
const seedDconf = (
|
||||
state: DesktopApplicationState,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
translateValue,
|
||||
type IdMap,
|
||||
} from "./environmentCompareIds.js";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
export const DIFFERENCE_CATEGORIES = [
|
||||
"status",
|
||||
@@ -67,9 +68,6 @@ const TIME_FIELD_CAMEL =
|
||||
/(?:At|Time|Date|Timestamp|Created|Updated|Modified)$/u;
|
||||
const ID_LIST_FIELD = /^(ids|uuids|guids)$/u;
|
||||
|
||||
const isObject = (value: JsonValue | undefined): value is JsonObject =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const shown = (value: JsonValue | undefined): string => {
|
||||
const text = value === undefined ? "<absent>" : JSON.stringify(value);
|
||||
return text.length > VALUE_CHARS ? `${text.slice(0, VALUE_CHARS)}…` : text;
|
||||
@@ -517,7 +515,7 @@ class Differ {
|
||||
): void {
|
||||
if (Array.isArray(live) && Array.isArray(env))
|
||||
this.array(live, env, pointer, at.clockKey ?? at.key, generation);
|
||||
else if (isObject(live) && isObject(env))
|
||||
else if (isRecord(live) && isRecord(env))
|
||||
this.object(
|
||||
live,
|
||||
env,
|
||||
@@ -528,8 +526,8 @@ class Differ {
|
||||
else if (
|
||||
Array.isArray(live) ||
|
||||
Array.isArray(env) ||
|
||||
isObject(live) ||
|
||||
isObject(env)
|
||||
isRecord(live) ||
|
||||
isRecord(env)
|
||||
)
|
||||
this.add({
|
||||
category: "value",
|
||||
|
||||
@@ -4,13 +4,11 @@ import type { BodyAddress } from "../../domain/analysisView/environmentStateWrit
|
||||
import { isUuid } from "../../domain/analysisView/environmentStateWriteValues.js";
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
import type { ProbeToolCall } from "./environmentCompareMatch.js";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
/** Field names that hold an identifier: `id`, `uuid`, `guid`, or names ending in `id` or `_id`. */
|
||||
export const ID_FIELD = /^(id|uuid|guid)$|(_id|Id|ID|_uuid|Uuid)$/u;
|
||||
|
||||
const isObject = (value: JsonValue | undefined): value is JsonObject =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const isScalarId = (value: JsonValue | undefined): value is string | number =>
|
||||
(typeof value === "string" && value !== "") ||
|
||||
(typeof value === "number" && Number.isFinite(value));
|
||||
@@ -179,7 +177,7 @@ const pairing = (
|
||||
),
|
||||
by,
|
||||
});
|
||||
if (!live.every(isObject) || !env.every(isObject))
|
||||
if (!live.every(isRecord) || !env.every(isRecord))
|
||||
return byPosition("position");
|
||||
const liveItems = live as readonly JsonObject[];
|
||||
const envItems = env as readonly JsonObject[];
|
||||
@@ -218,7 +216,7 @@ const keyedByIds = (
|
||||
([key, value]) =>
|
||||
/^\d+$/u.test(key) ||
|
||||
isUuid(key) ||
|
||||
(isObject(value) &&
|
||||
(isRecord(value) &&
|
||||
Object.entries(value).some(
|
||||
([field, held]) =>
|
||||
IDENTITY_FIELD.test(field) &&
|
||||
@@ -237,7 +235,7 @@ export const keyedByLearnedIds = (
|
||||
if (!entries.some(([key]) => map.hasLive(key))) return false;
|
||||
return entries.some(([key]) => Object.hasOwn(env, key))
|
||||
? keyedByIds(entries)
|
||||
: entries.every(([, value]) => isObject(value)) || keyedByIds(entries);
|
||||
: entries.every(([, value]) => isRecord(value)) || keyedByIds(entries);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -258,8 +256,8 @@ const learnKeyedEntries = (
|
||||
liveEntries.length === 0 ||
|
||||
envEntries.length === 0 ||
|
||||
shared === liveEntries.length ||
|
||||
!liveEntries.every(([, value]) => isObject(value)) ||
|
||||
!envEntries.every(([, value]) => isObject(value))
|
||||
!liveEntries.every(([, value]) => isRecord(value)) ||
|
||||
!envEntries.every(([, value]) => isRecord(value))
|
||||
)
|
||||
return false;
|
||||
const paired = pairing(
|
||||
@@ -340,7 +338,7 @@ const learnAt = (
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!isObject(live) || !isObject(env)) {
|
||||
if (!isRecord(live) || !isRecord(env)) {
|
||||
if (
|
||||
context.identity &&
|
||||
place.at === context.entityAt &&
|
||||
@@ -423,7 +421,7 @@ const inEnvTerms = (
|
||||
export const translateValue = (value: JsonValue, map: IdMap): JsonValue => {
|
||||
if (Array.isArray(value))
|
||||
return value.map((item) => translateValue(item, map));
|
||||
if (isObject(value))
|
||||
if (isRecord(value))
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [
|
||||
key,
|
||||
@@ -455,7 +453,7 @@ const translateBulkBody = (
|
||||
? value.map((entry) =>
|
||||
address.by === "ids"
|
||||
? id(entry)
|
||||
: isObject(entry) && Object.hasOwn(entry, idField)
|
||||
: isRecord(entry) && Object.hasOwn(entry, idField)
|
||||
? {
|
||||
...(translateValue(entry, map) as JsonObject),
|
||||
[idField]: id(entry[idField]!),
|
||||
@@ -464,7 +462,7 @@ const translateBulkBody = (
|
||||
)
|
||||
: translateValue(value, map);
|
||||
if (address.member === null) return entries(body);
|
||||
return isObject(body)
|
||||
return isRecord(body)
|
||||
? Object.fromEntries(
|
||||
Object.entries(body).map(([key, value]) => [
|
||||
key,
|
||||
|
||||
@@ -4,12 +4,10 @@ import type { BrowserObservationOperation } from "../domain/browserObservationEr
|
||||
import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
|
||||
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
export type UnknownRecord = Readonly<Record<string, unknown>>;
|
||||
|
||||
const isRecord = (value: unknown): value is UnknownRecord =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
export const recordValue = (value: unknown): UnknownRecord | undefined =>
|
||||
isRecord(value) ? value : undefined;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from "../domain/browserDeadline.js";
|
||||
import { safeParseJson } from "../domain/safeJson.js";
|
||||
import { WEB_RUNTIME_LIMITS } from "../domain/webRuntime.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
// ws currently stores maxPayload as a signed 32-bit integer. Larger values can
|
||||
// wrap to a negative number, which disables its limit check.
|
||||
@@ -625,9 +626,6 @@ const rawBytes = (data: RawData): Buffer =>
|
||||
? data
|
||||
: Buffer.from(data);
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const waitForOpen = async (
|
||||
socket: WebSocket,
|
||||
operation: BrowserObservationOperation,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
DEFAULT_ASAR_HEADER_ADMISSION,
|
||||
type AsarHeaderAdmission,
|
||||
} from "../config/asarHeaderAdmission.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
type ElectronFileOperation =
|
||||
| "inspect_electron_page"
|
||||
@@ -374,9 +375,6 @@ const isAsarRegularFile = (
|
||||
);
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const throwIfAborted = (signal?: AbortSignal): void => {
|
||||
assertArtifactReadActive(signal);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
import {
|
||||
observeCdpFrameUrl,
|
||||
@@ -59,9 +60,6 @@ interface MutableNode extends CdpFrameTreeNode {
|
||||
readonly children: CdpFrameTreeNode[];
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is UnknownRecord =>
|
||||
recordValue(value) !== undefined;
|
||||
|
||||
const treeRecord = z.custom<UnknownRecord>(isRecord, {
|
||||
message: "Expected a frame tree object.",
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import { jsonParts } from "../domain/jsonSerialization.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
/** Reserved control-record marker, not an advertised external JSON Schema. */
|
||||
export const JSON_LINES_PROTOCOL = "jsonl/v1";
|
||||
@@ -56,9 +57,6 @@ export interface JsonLinesPlan extends Collection {
|
||||
readonly filter: string | undefined;
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const atPath = (value: unknown, path: readonly string[]): unknown => {
|
||||
for (const key of path) {
|
||||
if (!isRecord(value) || !Object.hasOwn(value, key)) return undefined;
|
||||
|
||||
+1
-3
@@ -8,9 +8,7 @@ import {
|
||||
type AnalysisErrorProjection,
|
||||
} from "./domain/analysisErrorProjection.js";
|
||||
import type { Logger } from "pino";
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
import { isRecord } from "./domain/isRecord.js";
|
||||
|
||||
type CliFailureIdentity = Pick<
|
||||
AnalysisErrorProjection,
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
schemaDepth,
|
||||
serializedBytes,
|
||||
} from "./compactJsonSchema.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
import {
|
||||
schemaArrays,
|
||||
schemaChildren,
|
||||
@@ -335,9 +336,6 @@ export const portablePatternViolations = (
|
||||
|
||||
type SchemaKind = "input" | "output";
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Visit every schema position; instance literals stay opaque. */
|
||||
const visitSchemas = (
|
||||
schema: unknown,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "./jsonSchemaKeywords.js";
|
||||
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
|
||||
import { getEvidenceInputContract } from "../domain/evidenceInputContract.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
const PROPERTY_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
||||
addresses: "Ordered provider-normalized procedure addresses to analyze.",
|
||||
@@ -171,9 +172,9 @@ export const transformAdvertisedInputJsonSchema = <Schema>(
|
||||
transform: (projected: Record<string, unknown>) => Record<string, unknown>,
|
||||
): Schema => {
|
||||
const standard = Reflect.get(schema as object, "~standard");
|
||||
if (!isSchemaRecord(standard)) return schema;
|
||||
if (!isRecord(standard)) return schema;
|
||||
const jsonSchema = Reflect.get(standard, "jsonSchema");
|
||||
if (!isSchemaRecord(jsonSchema)) return schema;
|
||||
if (!isRecord(jsonSchema)) return schema;
|
||||
const project = Reflect.get(jsonSchema, "input");
|
||||
if (typeof project !== "function") return schema;
|
||||
const byTarget = new Map<string, Record<string, unknown>>();
|
||||
@@ -187,11 +188,8 @@ export const transformAdvertisedInputJsonSchema = <Schema>(
|
||||
if (cached !== undefined) return cached;
|
||||
}
|
||||
const projected = (project as (options: unknown) => unknown)(options);
|
||||
const transformed = isSchemaRecord(projected)
|
||||
? transform(projected)
|
||||
: projected;
|
||||
if (cacheable && isSchemaRecord(transformed))
|
||||
byTarget.set(target, transformed);
|
||||
const transformed = isRecord(projected) ? transform(projected) : projected;
|
||||
if (cacheable && isRecord(transformed)) byTarget.set(target, transformed);
|
||||
return transformed;
|
||||
};
|
||||
return {
|
||||
@@ -226,7 +224,7 @@ const hasRelativeSchemaReference = (
|
||||
if (current === null || typeof current !== "object") continue;
|
||||
const { position } = entry;
|
||||
const scope =
|
||||
position !== "map" && isSchemaRecord(current)
|
||||
position !== "map" && isRecord(current)
|
||||
? references.enter(current, entry.scope)
|
||||
: entry.scope;
|
||||
if (scope === undefined) return true;
|
||||
@@ -322,9 +320,6 @@ const memoizeByTarget = (
|
||||
};
|
||||
};
|
||||
|
||||
const isSchemaRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const fallbackPropertyDescription = (property: string): string | undefined => {
|
||||
const explicit = Object.hasOwn(PROPERTY_DESCRIPTIONS, property)
|
||||
? PROPERTY_DESCRIPTIONS[property]
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { jsonPointerTokens } from "../jsonPointer.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
type JsonObject = { [key: string]: JsonValue };
|
||||
|
||||
const isObject = (value: JsonValue | undefined): value is JsonObject =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** A list counter's member names: a top-level member's name not starting with `/`, else a pointer (`/pageInfo/totalRows`, `/~1total`). */
|
||||
export const counterPath = (counter: string): string[] =>
|
||||
counter.startsWith("/") ? jsonPointerTokens(counter) : [counter];
|
||||
@@ -17,7 +15,7 @@ export const counterAt = (
|
||||
): JsonValue | undefined =>
|
||||
counterPath(counter).reduce<JsonValue | undefined>(
|
||||
(value, name) =>
|
||||
isObject(value) && Object.hasOwn(value, name) ? value[name] : undefined,
|
||||
isRecord(value) && Object.hasOwn(value, name) ? value[name] : undefined,
|
||||
body,
|
||||
);
|
||||
|
||||
@@ -30,7 +28,7 @@ export const withCounter = (
|
||||
const set = (node: JsonValue, path: readonly string[]): JsonValue => {
|
||||
const [name, ...rest] = path;
|
||||
if (name === undefined) return value;
|
||||
if (!isObject(node) || !Object.hasOwn(node, name)) return node;
|
||||
if (!isRecord(node) || !Object.hasOwn(node, name)) return node;
|
||||
return { ...node, [name]: set(node[name]!, rest) };
|
||||
};
|
||||
return set(body, counterPath(counter));
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type BrowserScenarioNormalization,
|
||||
type BrowserScenarioNormalizationRule,
|
||||
} from "./browserScenarioDiffValues.js";
|
||||
import { isRecord } from "./isRecord.js";
|
||||
|
||||
/** Canonically order and commit a parsed browser scenario normalization policy. */
|
||||
export const commitBrowserScenarioNormalization = (
|
||||
@@ -78,7 +79,7 @@ export const normalizeScenarioValue = (
|
||||
|
||||
/** Compare parsed storage records by their fixed fields without serializing them. */
|
||||
const compareStorageEntries = (left: unknown, right: unknown): number => {
|
||||
if (!isStorageRecord(left) || !isStorageRecord(right))
|
||||
if (!isRecord(left) || !isRecord(right))
|
||||
return compareUnicodeCodePoints(
|
||||
digestCanonicalValue(left, "Browser scenario"),
|
||||
digestCanonicalValue(right, "Browser scenario"),
|
||||
@@ -103,9 +104,6 @@ const compareStorageEntries = (left: unknown, right: unknown): number => {
|
||||
return 0;
|
||||
};
|
||||
|
||||
const isStorageRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const compareStorageField = (left: unknown, right: unknown): number => {
|
||||
if (left === right) return 0;
|
||||
if (left === null) return -1;
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
zipEntryAt,
|
||||
zipLeavesOutAt,
|
||||
} from "./zipView.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** One file as the app's data/get reports it: its view, or why it has none. An absent file has no entry. */
|
||||
export interface TaskFileView {
|
||||
@@ -83,11 +84,6 @@ const undecided = (file: string): Verdict =>
|
||||
const notFound = (description: string, cut: boolean): Verdict =>
|
||||
cut ? unread(description) : fail(description);
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** JSON equality: numbers by value, objects by their keys, arrays in order. */
|
||||
export const sameJson = (
|
||||
actual: JsonValue | undefined,
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import { diffNamedMembers, diffStateViews } from "./stateChanges.js";
|
||||
import { DRAFT_VIEW_CHARS } from "./valueCuts.js";
|
||||
import { resolvePointer } from "./viewPointer.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
type Version = DesktopStatePath["versions"][number];
|
||||
type Suggestion = DesktopTaskCheckInput;
|
||||
@@ -52,11 +53,6 @@ const versions = (baseline: Version, final: Version) => ({
|
||||
final: final.view,
|
||||
});
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** A JSON value cut to a size an agent can read, and whether anything was cut. */
|
||||
const bounded = (
|
||||
value: JsonValue | null,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { pairMembers } from "./memberPairing.js";
|
||||
import { elementText, isXmlElement, resolvePointer } from "./viewPointer.js";
|
||||
import { selectorOf } from "./viewSelector.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
type Suggestion = DesktopTaskCheckInput;
|
||||
|
||||
@@ -68,11 +69,6 @@ type Element = Extract<JsonValue, Record<string, JsonValue>> & {
|
||||
readonly children: JsonValue[];
|
||||
};
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const attributesOf = (element: Element) =>
|
||||
isRecord(element.attributes) ? element.attributes : {};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ const locatePointer = (view: JsonValue | null, pointer: string) =>
|
||||
const resolvePointer = (view: JsonValue | null, pointer: string) =>
|
||||
resolveView(view, pointer, "json");
|
||||
import { selectorOf, spellCondition } from "./viewSelector.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
type Suggestion = DesktopTaskCheckInput;
|
||||
|
||||
@@ -182,11 +183,6 @@ export const ownNamesUnreadOtherwise = (
|
||||
return kept;
|
||||
};
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** The array a member pointer sits in, when its parent is one in a view. The empty pointer is the view itself, as a file whose top level is an array. */
|
||||
const arrayOf = (view: JsonValue | null, pointer: string) => {
|
||||
const list = pointer.replace(/\/[^/]+$/u, "");
|
||||
|
||||
@@ -49,14 +49,10 @@ import {
|
||||
valueFormat,
|
||||
type CheckTree,
|
||||
} from "./appTaskDraftTree.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
type Suggestion = DesktopTaskCheckInput;
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Pointers without a listed descendant: a container listed with its members is suggested through them. */
|
||||
export const innermost = (pointers: readonly string[]): readonly string[] =>
|
||||
pointers.filter(
|
||||
|
||||
@@ -13,6 +13,7 @@ import { idReferencesOf, mirrorsOf } from "./appTaskDraftMirrors.js";
|
||||
import { isPositionalKey, positionalKeys } from "./positionalKeys.js";
|
||||
import type { DesktopStateDifference } from "./stateChanges.js";
|
||||
import { elementText, isXmlElement, resolvePointer } from "./viewPointer.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** Why a change is what an app writes as it saves or shows a document, rather than what its user made. */
|
||||
export const NOISE_REASONS = [
|
||||
@@ -138,11 +139,6 @@ type Element = Record<string, JsonValue> & {
|
||||
children: JsonValue[];
|
||||
};
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** The elements a pointer steps into through `children`, read from the view, outermost first. */
|
||||
const elementsAlong = (
|
||||
view: JsonValue | null,
|
||||
|
||||
@@ -4,15 +4,11 @@ import { countTaskRows } from "./appTaskChecks.js";
|
||||
import type { DesktopStatePath } from "./desktopState.js";
|
||||
import { sqliteRowIdentity } from "./stateChanges.js";
|
||||
import { appendAll } from "../appendAll.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
type Version = DesktopStatePath["versions"][number];
|
||||
type Suggestion = DesktopTaskCheckInput;
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
interface TableView {
|
||||
readonly columns: readonly string[];
|
||||
readonly key: readonly string[];
|
||||
|
||||
@@ -12,6 +12,7 @@ import { valueEqualsCheck } from "./appTaskDraftScalars.js";
|
||||
import { rankAttributes } from "./attributeRank.js";
|
||||
import type { DesktopStateDifference } from "./stateChanges.js";
|
||||
import { elementText, isXmlElement, resolvePointer } from "./viewPointer.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** The pointers a diff lists, of every kind. */
|
||||
type Listed = Pick<DesktopStateDifference, "added" | "changed" | "removed">;
|
||||
@@ -79,11 +80,6 @@ const parentBelowRoot = (pointer: string): string | undefined => {
|
||||
const namedByAttribute = (pointer: string) =>
|
||||
(pointer.split("/").at(-1) ?? "").includes("[@");
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** An element's attributes; none for any other value. */
|
||||
const attributesOf = (value: JsonValue | undefined) =>
|
||||
isXmlElement(value) && isRecord(value.attributes) ? value.attributes : {};
|
||||
|
||||
@@ -10,11 +10,7 @@ import {
|
||||
zipLeavesOutAt,
|
||||
zipView,
|
||||
} from "./zipView.js";
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/**
|
||||
* The entry a pointer into a zip view goes into, when it has a view and both
|
||||
|
||||
@@ -16,6 +16,7 @@ import { holdsFields, isRecordAt } from "./appTaskDraftText.js";
|
||||
import { attributesOf, compactValue, previewWriter } from "./compactValue.js";
|
||||
import { pairMembers, pairedAtSeed } from "./memberPairing.js";
|
||||
import { isXmlElement, locatePointer, resolvePointer } from "./viewPointer.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** Why a value's pointer cannot go into a check as it is, or why the value is noise. */
|
||||
export const VALUE_NOTES = ["by_position", ...NOISE_REASONS] as const;
|
||||
@@ -91,11 +92,6 @@ export const VALUE_LIMITS = {
|
||||
references: 5,
|
||||
} as const;
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** The longest value a reference is resolved from. */
|
||||
const MAX_REFERENCE_CHARS = 64;
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { isXmlElement } from "./viewPointer.js";
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
export const cut = (text: string, max: number) =>
|
||||
text.length > max ? `${text.slice(0, max)}…` : text;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isPlaceholder,
|
||||
} from "./attributeRank.js";
|
||||
import { MAX_INSIDE_STEPS, spellCondition } from "./viewSelector.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** A field naming a JSON record, as TreeLine's `uid`: `id`, `uid`, `uuid`, `guid`, `key`, or a name ending in `_id`, `-id` or `Id`. */
|
||||
const ID_FIELD = /^(?:_?id|uid|uuid|guid|key)$|[_-]id$|[a-z]Id$/u;
|
||||
@@ -23,11 +24,6 @@ const MAX_NAME = 64;
|
||||
/** The most kind fields and paths a version's records are counted by, each a pass over them, so odd records cost no more. */
|
||||
const MAX_TALLIES = 64;
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Whether every member holds a string at a key a selector can carry, each its own. */
|
||||
const namesEach = (members: readonly JsonValue[], key: string) => {
|
||||
const values = members.map((member) =>
|
||||
|
||||
@@ -28,11 +28,7 @@ import {
|
||||
import { pairRecordsById } from "./jsonRecordPairing.js";
|
||||
import { resolvePointer } from "./viewPointer.js";
|
||||
import { selectorOf } from "./viewSelector.js";
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
const own = (record: Record<string, JsonValue>, key: string) =>
|
||||
Object.hasOwn(record, key) ? record[key] : undefined;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { isIdentifyingAttribute } from "./attributeRank.js";
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** An identifier of the members with one tag, as `tag` NUL `key`. */
|
||||
const tagKey = (tag: string, key: string) => `${tag}\u0000${key}`;
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
isTextualFormat,
|
||||
textSyntaxFormat,
|
||||
} from "./stateFormats.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** What the export ships for one seed. */
|
||||
export interface SeedScrub {
|
||||
@@ -291,11 +292,6 @@ const scrubGVariant = (
|
||||
return { text: scrubbed, decoded: [scrubbed, ...strings].join("\n") };
|
||||
};
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/**
|
||||
* A dconf view's groups as the key file the computer compiles, each value's
|
||||
* strings scrubbed, and whether it still holds the identity, strings decoded.
|
||||
|
||||
@@ -10,11 +10,7 @@ import {
|
||||
rankAttributes,
|
||||
} from "./attributeRank.js";
|
||||
import { selectorOf, spellCondition, type Condition } from "./viewSelector.js";
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
const own = (record: Record<string, JsonValue>, key: string) =>
|
||||
Object.hasOwn(record, key) ? record[key] : undefined;
|
||||
@@ -94,7 +90,9 @@ const stepTag = (tag: JsonValue | undefined): tag is string =>
|
||||
/** The element children of a member, none for any other value. */
|
||||
const elementsOf = (member: JsonValue): Record<string, JsonValue>[] => {
|
||||
const children = isRecord(member) ? own(member, "children") : undefined;
|
||||
return (Array.isArray(children) ? children : []).filter(isRecord);
|
||||
return (Array.isArray(children) ? children : []).filter(
|
||||
(child): child is Record<string, JsonValue> => isRecord(child),
|
||||
);
|
||||
};
|
||||
|
||||
/** A child's text a condition could name an element by, and whether the child joins it from elements of its own. */
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { DesktopStateFormat } from "./stateFormats.js";
|
||||
import { holdsTagOnly, pairMembers } from "./memberPairing.js";
|
||||
import { memberLookup } from "./viewPointer.js";
|
||||
import { zipEntryAt, zipLeavesOut } from "./zipView.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
const MAX_LISTED = 100;
|
||||
|
||||
@@ -20,11 +21,6 @@ export interface DesktopStateDifference {
|
||||
readonly complete: boolean;
|
||||
}
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Longest JSON pointer listed; a longer one is only counted, so deep paths never fill the lists. */
|
||||
const MAX_LISTED_POINTER = 4096;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
selectorOf,
|
||||
type Condition,
|
||||
} from "./viewSelector.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** The arrays a pointer stepped into, each as the pointer to it. */
|
||||
interface ArraySteps {
|
||||
@@ -33,11 +34,6 @@ type Stepped =
|
||||
|
||||
const INDEX = /^(?:0|[1-9][0-9]*)$/u;
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const own = (record: Record<string, JsonValue>, key: string) =>
|
||||
Object.hasOwn(record, key) ? record[key] : undefined;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
desktopViewByteDisplay,
|
||||
type DesktopViewPolicy,
|
||||
} from "./desktopViewPolicy.js";
|
||||
import { isRecord } from "../isRecord.js";
|
||||
|
||||
/** Why an archive has no view; the exported reader words it the same. */
|
||||
export const UNREADABLE_ZIP = "not a readable zip archive";
|
||||
@@ -111,11 +112,6 @@ export const zipView = (
|
||||
truncated: count > entries.length,
|
||||
});
|
||||
|
||||
const isRecord = (
|
||||
value: JsonValue | null | undefined,
|
||||
): value is Record<string, JsonValue> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Whether an entry's view is missing or holds only part of it. */
|
||||
const entryLeavesOut = (entry: JsonValue): boolean =>
|
||||
isRecord(entry) &&
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
|
||||
/**
|
||||
* Whether a value is a non-null object other than an array. Its keys,
|
||||
* prototype and property values are not inspected.
|
||||
*/
|
||||
export function isRecord(
|
||||
value: JsonValue | null | undefined,
|
||||
): value is { [key: string]: JsonValue };
|
||||
export function isRecord(value: unknown): value is Record<string, unknown>;
|
||||
export function isRecord(value: unknown): boolean {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { safeParseJson } from "./safeJson.js";
|
||||
import { jsonStringByteLength } from "./jsonStringByteLength.js";
|
||||
import { jsonValueByteLength } from "./jsonValueByteLength.js";
|
||||
import { err, ok, type Result } from "./result.js";
|
||||
import { isRecord } from "./isRecord.js";
|
||||
|
||||
const jsonValueTypeSchema = z.enum([
|
||||
"object",
|
||||
@@ -278,9 +279,6 @@ export const jsonValueType = (value: unknown): JsonValueType => {
|
||||
}
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Reverse sibling order for the traversal stack; array elements sort first. */
|
||||
const orderedChildren = (node: ShapeNode): ShapeNode[] =>
|
||||
[...(node.children ?? [])]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { types } from "node:util";
|
||||
import { z } from "zod";
|
||||
import { isImmutableJsonSnapshot } from "./immutableJson.js";
|
||||
import { isRecord } from "./isRecord.js";
|
||||
|
||||
/** Maximum JSON nesting accepted before recursive result cloning. */
|
||||
export const MAX_JSON_DEPTH = 1000;
|
||||
@@ -14,8 +15,6 @@ export type JsonValue =
|
||||
| JsonValue[]
|
||||
| { [key: string]: JsonValue };
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
const immutableValidation = new WeakMap<object, string | undefined>();
|
||||
|
||||
/** Validate JSON meaning and nesting without cloning a trusted immutable value. */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { isRecord } from "./isRecord.js";
|
||||
|
||||
/** Artifact source declarations do not require generated-position decoding. */
|
||||
type SourceMapRecord = Readonly<Record<string, unknown>>;
|
||||
@@ -53,9 +54,6 @@ export const sourceMapJsonText = (text: string): string => {
|
||||
return newline === -1 ? "" : withoutBom.slice(newline);
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is SourceMapRecord =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Classify section ownership once; an external URL takes precedence over map. */
|
||||
export const classifySourceMapSection = (
|
||||
section: unknown,
|
||||
|
||||
+1
-3
@@ -39,6 +39,7 @@ import {
|
||||
} from "./mcpStartupPolicy.js";
|
||||
import { MCP_DOCTOR_ALIAS, MCP_DOCTOR_OPTIONS } from "./mcpDoctorOptions.js";
|
||||
import { OwnedMcpStdioTransport } from "./process/OwnedMcpStdioTransport.js";
|
||||
import { isRecord } from "./domain/isRecord.js";
|
||||
|
||||
const OUTPUT_FORMATS = CLI_DOCUMENT_OUTPUT_FORMATS;
|
||||
type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
||||
@@ -672,9 +673,6 @@ const captureStderr = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const errorMessage = (cause: unknown): string =>
|
||||
cause instanceof Error ? cause.message : String(cause);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { setImmediate as yieldToEventLoop } from "node:timers/promises";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
/**
|
||||
@@ -31,9 +32,6 @@ export type TsharkJsonFailure =
|
||||
| { readonly kind: "json-bytes"; readonly maxBytes: number }
|
||||
| { readonly kind: "json-expansion"; readonly maxDecodedBytes: number };
|
||||
|
||||
const object = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const YIELD_INTERVAL = 65_536;
|
||||
|
||||
/**
|
||||
@@ -96,9 +94,9 @@ export const parseTsharkJsonPackets = (
|
||||
const packets: TsharkPacketLayers[] = [];
|
||||
for (const [index, packet] of decoded.entries()) {
|
||||
if (
|
||||
!object(packet) ||
|
||||
!object(packet._source) ||
|
||||
!object(packet._source.layers)
|
||||
!isRecord(packet) ||
|
||||
!isRecord(packet._source) ||
|
||||
!isRecord(packet._source.layers)
|
||||
)
|
||||
return err({
|
||||
kind: "malformed-packet",
|
||||
|
||||
@@ -122,9 +122,6 @@ const hasAdvertisedInputJsonSchema = (value: unknown): boolean => {
|
||||
);
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** Recognize the SDK's argument diagnostic after its wire size is checked. */
|
||||
const sdkInputValidationDiagnostic = (
|
||||
text: string | undefined,
|
||||
@@ -289,6 +286,7 @@ export const forgetPendingMcpRequest = (
|
||||
pendingCompletions.delete(id);
|
||||
};
|
||||
import type { PromptCompletionSource } from "./promptCompletion.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
/** Standard-schema registration boundary shared with deferred SDK bindings. */
|
||||
export interface StandardToolRegistration {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type RpcError,
|
||||
type StateIntake,
|
||||
} from "./stateIntake.js";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
/** One data-plane method's outcome; undefined from a method table means the method is not served. */
|
||||
export type RpcOutcome =
|
||||
@@ -24,9 +25,6 @@ export type RpcMethods = (
|
||||
signal?: AbortSignal,
|
||||
) => Promise<RpcOutcome | undefined>;
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** The first document problem as `-32602`, located by a JSON pointer into the document. */
|
||||
const documentError = (error: AnalysisInputError): RpcError => {
|
||||
const issue = error.issues[0];
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
bufferedJsonParts,
|
||||
jsonParts,
|
||||
} from "../../domain/jsonSerialization.js";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
/** Where the environment card is served. */
|
||||
export const ENVIRONMENT_CARD_PATH = "/.well-known/agent-env.json";
|
||||
@@ -81,9 +82,6 @@ class EnvironmentMcpServer extends RequestTrackingMcpServer {
|
||||
}
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
/** At most this many messages in one data-plane batch; a larger batch is refused whole. */
|
||||
const MAX_RPC_BATCH_MESSAGES = 100;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
RegularFileCleanupFailure,
|
||||
withOwnedRegularFile,
|
||||
} from "../../filesystem/OwnedRegularFile.js";
|
||||
import { isRecord } from "../../domain/isRecord.js";
|
||||
|
||||
/** Why a named file or download could not be read, or a call was cancelled: a category naming neither the uri nor the content. */
|
||||
export type IntakeFailure =
|
||||
@@ -125,9 +126,6 @@ const fileFailure = (cause: unknown): IntakeFailure =>
|
||||
? FILE_FAILURES.get(String(cause.code))
|
||||
: undefined) ?? "read-failed";
|
||||
|
||||
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
const MAX_INLINE_BASE64_CHARACTERS =
|
||||
Math.ceil(STATE_INTAKE_LIMIT_BYTES / 3) * 4;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
|
||||
import type { ClientFeatureAvailability } from "../contracts/toolOutputSchemaPrimitives.js";
|
||||
import type { ConnectedClientIdentity } from "../serverIdentity.js";
|
||||
import { isRecord } from "../domain/isRecord.js";
|
||||
|
||||
/** Initialize-scoped client metadata the SDK keeps for a 2025-era connection. */
|
||||
export type McpConnectionClientMetadata = Pick<
|
||||
@@ -69,6 +70,3 @@ const capabilityFeatures = (value: unknown): ClientFeatureAvailability => {
|
||||
sampling: capabilities.sampling !== undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { recordPredicateCounts } from "../../scripts/verify/record-predicate-guard.mjs";
|
||||
|
||||
it("counts both function forms and does not depend on the parameter name", () => {
|
||||
const predicate =
|
||||
'typeof input === "object" && input !== null && !Array.isArray(input)';
|
||||
expect(
|
||||
recordPredicateCounts(`function isObject(input) { return ${predicate}; }`)
|
||||
.bodies,
|
||||
).toBe(1);
|
||||
expect(
|
||||
recordPredicateCounts(`const isObject = (input) => ${predicate};`).bodies,
|
||||
).toBe(1);
|
||||
expect(
|
||||
recordPredicateCounts(
|
||||
`const a = (input) => ${predicate}; const b = (input) => ${predicate};`,
|
||||
).bodies,
|
||||
).toBe(2);
|
||||
});
|
||||
Reference in new issue
Block a user