import { JSON_SCHEMA, dump as dumpYamlDocument, load as parseYamlDocument } from "js-yaml"; import { microsoftGraphKeepPathItem } from "./graph";import { microsoftGraphExactPathsForPresetIds, microsoftGraphPathPrefixesForPresetIds, microsoftGraphTagPrefixesForPresetIds,} from "./presets"; /** * Offline Microsoft Graph slice construction. NOT part of the runtime import * graph: the 43MB Graph source cannot be held in a 128MB Workers isolate (its * fetch alone has completed once in the last 30 days of production traces), so * slices are built where memory is free — the graph-slices workflow / a dev * machine — published as release assets, and fetched per selection at runtime * by `slices.ts`. Import this module only from scripts and tests. */ /** `components` subkeys retained whole in a slice — mirrors the runtime * splitter's `SMALL_COMPONENT_SECTIONS` (`sdk/split.ts`), which keeps these * resident for `$ref` resolution. `schemas` is pruned to the transitive * closure instead; `examples` is dropped. */const SMALL_COMPONENT_SECTIONS = [ "parameters", "requestBodies", "responses", "headers", "links", "securitySchemes",] as const; const COMPONENT_REF_PREFIX = "#/components/"; const isRecord = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value); const decodeRefSegment = (segment: string): string => segment.replace(/~1/g, "/").replace(/~0/g, "~"); interface ComponentRef { readonly section: string; readonly name: string;} const collectComponentRefs = (value: unknown, into: (ref: ComponentRef) => void): void => { if (typeof value === "string") { if (value.startsWith(COMPONENT_REF_PREFIX)) { const rest = value.slice(COMPONENT_REF_PREFIX.length); const slash = rest.indexOf("/"); if (slash > 0) { const section = rest.slice(0, slash); const name = decodeRefSegment(rest.slice(slash + 1)); if (name.length > 0) into({ section, name }); } } return; } if (Array.isArray(value)) { for (const item of value) collectComponentRefs(item, into); return; } if (isRecord(value)) { for (const item of Object.values(value)) collectComponentRefs(item, into); }}; /** * Transitive `#/components/<section>/<name>` closure over every component * section, seeded from `roots` (the kept path-items). Only components actually * reachable from a kept operation survive — Graph's ~8k schemas and its * catch-all responses/parameters sections otherwise drag nearly the whole * component graph into every slice. */const componentClosure = ( components: Record<string, unknown>, roots: readonly unknown[],): Record<string, Record<string, unknown>> => { const kept: Record<string, Record<string, unknown>> = {}; const queue: ComponentRef[] = []; const seen = new Set<string>(); const enqueue = (ref: ComponentRef): void => { const key = `${ref.section}/${ref.name}`; if (seen.has(key)) return; seen.add(key); queue.push(ref); }; for (const root of roots) collectComponentRefs(root, enqueue); for (let i = 0; i < queue.length; i += 1) { const { section, name } = queue[i]!; const sectionValues = components[section]; if (!isRecord(sectionValues)) continue; const component = sectionValues[name]; if (component === undefined) continue; (kept[section] ??= {})[name] = component; collectComponentRefs(component, enqueue); } return kept;}; export interface GraphSliceBuild { /** The slice as streamable block YAML (the same profile `structuralSplit` * accepts, so the runtime pipeline treats a slice exactly like a source). */ readonly specText: string; readonly pathCount: number; readonly operationCount: number; readonly schemaCount: number;} const HTTP_METHODS = new Set(["delete", "get", "head", "options", "patch", "post", "put", "trace"]); const countOperations = (paths: Record<string, unknown>): number => { let count = 0; for (const pathItem of Object.values(paths)) { if (!isRecord(pathItem)) continue; for (const key of Object.keys(pathItem)) { if (HTTP_METHODS.has(key.toLowerCase())) count += 1; } } return count;}; /** * Build one preset selection's slice from the parsed Graph document: keep the * selection's path-items (via the same `microsoftGraphKeepPathItem` filter the * runtime applies), retain the small component sections whole, and prune * `components.schemas` to the transitive `$ref` closure of everything kept. */export const buildGraphSliceDocument = ( doc: Record<string, unknown>, presetIds: readonly string[],): GraphSliceBuild => { const keepPathItem = microsoftGraphKeepPathItem({ coversFullGraph: false, presetIds, customScopes: [], exactPaths: microsoftGraphExactPathsForPresetIds(presetIds), pathPrefixes: microsoftGraphPathPrefixesForPresetIds(presetIds), tagPrefixes: microsoftGraphTagPrefixesForPresetIds(presetIds), }); const sourcePaths = isRecord(doc.paths) ? doc.paths : {}; const paths: Record<string, unknown> = {}; for (const [path, pathItem] of Object.entries(sourcePaths)) { if (!isRecord(pathItem)) continue; const kept = keepPathItem(path, pathItem); if (kept) paths[path] = kept; } const sourceComponents = isRecord(doc.components) ? doc.components : {}; const closure = componentClosure(sourceComponents, [paths]); const components: Record<string, unknown> = {}; for (const section of SMALL_COMPONENT_SECTIONS) { if (closure[section]) components[section] = closure[section]; } // securitySchemes are never `$ref`'d from operations by name in Graph // (operations reference them via `security` entries), so retain the section // whole — it is tiny and the runtime reads OAuth endpoints from it. if (isRecord(sourceComponents.securitySchemes)) { components.securitySchemes = sourceComponents.securitySchemes; } const schemas = closure.schemas ?? {}; components.schemas = schemas; const slice: Record<string, unknown> = { ...(doc.openapi !== undefined ? { openapi: doc.openapi } : {}), ...(doc.info !== undefined ? { info: doc.info } : {}), ...(doc.servers !== undefined ? { servers: doc.servers } : {}), ...(doc.security !== undefined ? { security: doc.security } : {}), paths, components, }; // noRefs duplicates shared subtrees instead of emitting YAML anchors, which // the streamable block profile forbids; lineWidth -1 keeps scalars on one // line so no wrapped line can be mistaken for structure. const specText = dumpYamlDocument(slice, { noRefs: true, lineWidth: -1, schema: JSON_SCHEMA }); return { specText, pathCount: Object.keys(paths).length, operationCount: countOperations(paths), schemaCount: Object.keys(schemas).length, };}; /** Parse Graph source YAML for slice building, or null when the source does * not parse to an object. Offline-only: this is the whole-document parse the * runtime can never do. */export const parseGraphSourceDocument = (sourceText: string): Record<string, unknown> | null => { const parsed = parseYamlDocument(sourceText, { json: true, schema: JSON_SCHEMA }); return isRecord(parsed) ? parsed : null;};
import { JSON_SCHEMA, dump as dumpYamlDocument, load as parseYamlDocument } from "js-yaml";
import { microsoftGraphKeepPathItem } from "./graph";
import {
microsoftGraphExactPathsForPresetIds,
microsoftGraphPathPrefixesForPresetIds,
microsoftGraphTagPrefixesForPresetIds,
} from "./presets";
/**
* Offline Microsoft Graph slice construction. NOT part of the runtime import
* graph: the 43MB Graph source cannot be held in a 128MB Workers isolate (its
* fetch alone has completed once in the last 30 days of production traces), so
* slices are built where memory is free — the graph-slices workflow / a dev
* machine — published as release assets, and fetched per selection at runtime
* by `slices.ts`. Import this module only from scripts and tests.
*/
/** `components` subkeys retained whole in a slice — mirrors the runtime
* splitter's `SMALL_COMPONENT_SECTIONS` (`sdk/split.ts`), which keeps these
* resident for `$ref` resolution. `schemas` is pruned to the transitive
* closure instead; `examples` is dropped. */
const SMALL_COMPONENT_SECTIONS = [
"parameters",
"requestBodies",
"responses",
"headers",
"links",
"securitySchemes",
] as const;
const COMPONENT_REF_PREFIX = "#/components/";
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === "object" && !Array.isArray(value);
const decodeRefSegment = (segment: string): string =>
segment.replace(/~1/g, "/").replace(/~0/g, "~");
interface ComponentRef {
readonly section: string;
readonly name: string;
}
const collectComponentRefs = (value: unknown, into: (ref: ComponentRef) => void): void => {
if (typeof value === "string") {
if (value.startsWith(COMPONENT_REF_PREFIX)) {
const rest = value.slice(COMPONENT_REF_PREFIX.length);
const slash = rest.indexOf("/");
if (slash > 0) {
const section = rest.slice(0, slash);
const name = decodeRefSegment(rest.slice(slash + 1));
if (name.length > 0) into({ section, name });
}
}
return;
}
if (Array.isArray(value)) {
for (const item of value) collectComponentRefs(item, into);
return;
}
if (isRecord(value)) {
for (const item of Object.values(value)) collectComponentRefs(item, into);
}
};
/**
* Transitive `#/components/<section>/<name>` closure over every component
* section, seeded from `roots` (the kept path-items). Only components actually
* reachable from a kept operation survive — Graph's ~8k schemas and its
* catch-all responses/parameters sections otherwise drag nearly the whole
* component graph into every slice.
*/
const componentClosure = (
components: Record<string, unknown>,
roots: readonly unknown[],
): Record<string, Record<string, unknown>> => {
const kept: Record<string, Record<string, unknown>> = {};
const queue: ComponentRef[] = [];
const seen = new Set<string>();
const enqueue = (ref: ComponentRef): void => {
const key = `${ref.section}/${ref.name}`;
if (seen.has(key)) return;
seen.add(key);
queue.push(ref);
};
for (const root of roots) collectComponentRefs(root, enqueue);
for (let i = 0; i < queue.length; i += 1) {
const { section, name } = queue[i]!;
const sectionValues = components[section];
if (!isRecord(sectionValues)) continue;
const component = sectionValues[name];
if (component === undefined) continue;
(kept[section] ??= {})[name] = component;
collectComponentRefs(component, enqueue);
}
return kept;
};
export interface GraphSliceBuild {
/** The slice as streamable block YAML (the same profile `structuralSplit`
* accepts, so the runtime pipeline treats a slice exactly like a source). */
readonly specText: string;
readonly pathCount: number;
readonly operationCount: number;
readonly schemaCount: number;
}
const HTTP_METHODS = new Set(["delete", "get", "head", "options", "patch", "post", "put", "trace"]);
const countOperations = (paths: Record<string, unknown>): number => {
let count = 0;
for (const pathItem of Object.values(paths)) {
if (!isRecord(pathItem)) continue;
for (const key of Object.keys(pathItem)) {
if (HTTP_METHODS.has(key.toLowerCase())) count += 1;
}
}
return count;
};
/**
* Build one preset selection's slice from the parsed Graph document: keep the
* selection's path-items (via the same `microsoftGraphKeepPathItem` filter the
* runtime applies), retain the small component sections whole, and prune
* `components.schemas` to the transitive `$ref` closure of everything kept.
*/
export const buildGraphSliceDocument = (
doc: Record<string, unknown>,
presetIds: readonly string[],
): GraphSliceBuild => {
const keepPathItem = microsoftGraphKeepPathItem({
coversFullGraph: false,
presetIds,
customScopes: [],
exactPaths: microsoftGraphExactPathsForPresetIds(presetIds),
pathPrefixes: microsoftGraphPathPrefixesForPresetIds(presetIds),
tagPrefixes: microsoftGraphTagPrefixesForPresetIds(presetIds),
});
const sourcePaths = isRecord(doc.paths) ? doc.paths : {};
const paths: Record<string, unknown> = {};
for (const [path, pathItem] of Object.entries(sourcePaths)) {
if (!isRecord(pathItem)) continue;
const kept = keepPathItem(path, pathItem);
if (kept) paths[path] = kept;
}
const sourceComponents = isRecord(doc.components) ? doc.components : {};
const closure = componentClosure(sourceComponents, [paths]);
const components: Record<string, unknown> = {};
for (const section of SMALL_COMPONENT_SECTIONS) {
if (closure[section]) components[section] = closure[section];
}
// securitySchemes are never `$ref`'d from operations by name in Graph
// (operations reference them via `security` entries), so retain the section
// whole — it is tiny and the runtime reads OAuth endpoints from it.
if (isRecord(sourceComponents.securitySchemes)) {
components.securitySchemes = sourceComponents.securitySchemes;
}
const schemas = closure.schemas ?? {};
components.schemas = schemas;
const slice: Record<string, unknown> = {
...(doc.openapi !== undefined ? { openapi: doc.openapi } : {}),
...(doc.info !== undefined ? { info: doc.info } : {}),
...(doc.servers !== undefined ? { servers: doc.servers } : {}),
...(doc.security !== undefined ? { security: doc.security } : {}),
paths,
components,
};
// noRefs duplicates shared subtrees instead of emitting YAML anchors, which
// the streamable block profile forbids; lineWidth -1 keeps scalars on one
// line so no wrapped line can be mistaken for structure.
const specText = dumpYamlDocument(slice, { noRefs: true, lineWidth: -1, schema: JSON_SCHEMA });
return {
specText,
pathCount: Object.keys(paths).length,
operationCount: countOperations(paths),
schemaCount: Object.keys(schemas).length,
};
};
/** Parse Graph source YAML for slice building, or null when the source does
* not parse to an object. Offline-only: this is the whole-document parse the
* runtime can never do. */
export const parseGraphSourceDocument = (sourceText: string): Record<string, unknown> | null => {
const parsed = parseYamlDocument(sourceText, { json: true, schema: JSON_SCHEMA });
return isRecord(parsed) ? parsed : null;
};