← Files Google DriveARCHIVED FILE

skills/google-docs/runtime/dropdown-common.mjs

3.26 KB · Sep 30, 2026 · 23:18 UTC

↓ Download file

import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";

export function assert(condition, message, path = null) {
  if (!condition) throw new Error(path ? `${path}: ${message}` : message);
}

export function isRecord(value) {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

export function requireRecord(value, path) {
  assert(isRecord(value), "must be an object", path);
  return value;
}

export function requireArray(value, path) {
  assert(Array.isArray(value), "must be an array", path);
  return value;
}

export function requireString(value, path, { allowEmpty = false } = {}) {
  assert(typeof value === "string" && (allowEmpty || value.length > 0), "must be a non-empty string", path);
  return value;
}

export function requireInteger(value, path, { minimum = null } = {}) {
  assert(Number.isInteger(value), "must be an integer", path);
  if (minimum !== null) assert(value >= minimum, `must be >= ${minimum}`, path);
  return value;
}

export function stableStringify(value) {
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
  if (isRecord(value)) {
    return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
  }
  return JSON.stringify(value);
}

export function sha256Text(value) {
  return createHash("sha256").update(String(value)).digest("hex");
}

export function sha256Json(value) {
  return sha256Text(stableStringify(value));
}

export async function sha256File(path) {
  return createHash("sha256").update(await readFile(path)).digest("hex");
}

export async function readJson(path) {
  return JSON.parse(await readFile(path, "utf8"));
}

export async function writeJson(path, value) {
  await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
}

export function rangesOverlap(left, right) {
  if ((left.tabId ?? null) !== (right.tabId ?? null) || (left.segmentId ?? null) !== (right.segmentId ?? null)) return false;
  return left.startIndex < right.endIndex && right.startIndex < left.endIndex;
}

export function rangeContains(outer, inner) {
  return (outer.tabId ?? null) === (inner.tabId ?? null) &&
    (outer.segmentId ?? null) === (inner.segmentId ?? null) &&
    outer.startIndex <= inner.startIndex && outer.endIndex >= inner.endIndex;
}

export function findFirstRecord(value, predicate) {
  const seen = new Set();
  const visit = (candidate) => {
    if (!candidate || typeof candidate !== "object" || seen.has(candidate)) return null;
    seen.add(candidate);
    if (isRecord(candidate) && predicate(candidate)) return candidate;
    for (const child of Array.isArray(candidate) ? candidate : Object.values(candidate)) {
      const found = visit(child);
      if (found) return found;
    }
    return null;
  };
  return visit(value);
}

export function collectRecords(value, predicate) {
  const output = [];
  const seen = new Set();
  const visit = (candidate) => {
    if (!candidate || typeof candidate !== "object" || seen.has(candidate)) return;
    seen.add(candidate);
    if (isRecord(candidate) && predicate(candidate)) output.push(candidate);
    for (const child of Array.isArray(candidate) ? candidate : Object.values(candidate)) visit(child);
  };
  visit(value);
  return output;
}

SHA-256: f845319bd56c8bf07f74cc62cfdf10421d58464f0effccf8a720319b091cfba7