Skip to content

Low Level Function Reference

유용태 edited this page Jun 18, 2026 · 1 revision

Low-level Function Reference

이 페이지는 JSONDocument<T> instance 없이도 사용할 수 있는 function 중심 API를 설명한다.

대부분의 제품 코드는 먼저 JSONDocument<T> command를 사용한다. Low-level function은 adapter, extension package, server-side transform, import pipeline, test, custom editor primitive를 만들 때 사용한다.

Factory와 adapter

createJSONDocument(schema, initial, options?)

function createJSONDocument<S extends z.ZodType>(
  schema: S,
  initial: z.output<S>,
  options: JSONDocumentOptions & { trustedInitial: true },
): JSONDocument<z.output<S>>;

function createJSONDocument<S extends z.ZodType>(
  schema: S,
  initial: z.input<S>,
  options?: JSONDocumentOptions & { trustedInitial?: false | undefined },
): JSONDocument<z.output<S>>;

Headless document instance를 만든다.

기본 호출은 initial value를 schema로 parse한다. trustedInitial: true는 호출자가 이미 schema 검증 경계를 소유할 때만 사용한다.

const doc = createJSONDocument(Card, input, {
  history: 100,
  selection: true,
});

useJSONDocument(schema, initial, options?)

function useJSONDocument<S extends z.ZodType>(
  schema: S,
  initial: z.output<S>,
  options: JSONDocumentOptions & { trustedInitial: true },
): JSONDocument<z.output<S>>;

function useJSONDocument<S extends z.ZodType>(
  schema: S,
  initial: z.input<S>,
  options?: JSONDocumentOptions & { trustedInitial?: false | undefined },
): JSONDocument<z.output<S>>;

React lifecycle 안에서 같은 JSONDocument<T> surface를 제공한다.

Import 경로는 root가 아니라 React entrypoint다.

import { useJSONDocument } from "@interactive-os/json-document/react";

Patch helper

Patch helper는 document instance 없이 schema와 state만으로 patch 결과를 계산한다.

interface ApplyResult<S extends z.ZodTypeAny> {
  state: z.output<S>;
  result: JSONResult;
  applied: ReadonlyArray<JSONPatchOperation>;
}

applyOperation(schema, state, op)

function applyOperation<S extends z.ZodTypeAny>(
  schema: S,
  state: z.output<S>,
  op: JSONPatchOperation,
): ApplyResult<S>;

Operation 하나를 적용한다.

검증 순서:

  1. 기존 state가 JSON-serializable인지 확인
  2. operation shape 확인
  3. raw operation 적용
  4. schema 검증

실패하면 기존 state와 빈 applied를 반환한다.

applyPatch(schema, state, ops)

function applyPatch<S extends z.ZodTypeAny>(
  schema: S,
  state: z.output<S>,
  ops: ReadonlyArray<JSONPatchOperation>,
): ApplyResult<S>;

Operation 배열을 하나의 patch로 적용한다.

전체 patch가 성공해야 state가 바뀐다. 중간 operation이 실패하거나 마지막 schema 검증이 실패하면 기존 state를 반환한다.

const result = applyPatch(CardList, state, [
  { op: "replace", path: "/items/0/title", value: "Ready" },
]);

if (result.result.ok) {
  result.state;
  result.applied;
}

applyPatchToTrustedState(schema, state, ops)

function applyPatchToTrustedState<S extends z.ZodTypeAny>(
  schema: S,
  state: z.output<S>,
  ops: ReadonlyArray<JSONPatchOperation>,
): ApplyResult<S>;

이미 z.output<S>로 신뢰한 state에 patch를 적용한다.

applyPatch와 마찬가지로 마지막 schema 검증은 수행한다. 차이는 호출자가 state의 JSON serializability와 검증 경계를 더 강하게 소유한다는 의미다.

JSON Pointer helper

Pointer는 core의 공통 주소 형식이다. Root pointer는 빈 문자열 ""이다.

parsePointer(pointer)

function parsePointer(pointer: Pointer): string[];

JSON Pointer 문자열을 segment 배열로 변환한다.

잘못된 pointer면 PointerSyntaxError를 throw한다. 사용자 입력처럼 실패가 정상 흐름인 경우 tryParsePointer를 쓴다.

parsePointer("/items/0/title"); // ["items", "0", "title"]

tryParsePointer(pointer)

function tryParsePointer(pointer: Pointer): string[] | null;

Pointer를 parse하고, 실패하면 null을 반환한다.

buildPointer(segments, options?)

function buildPointer(
  segments: ReadonlyArray<string | number>,
  options?: { uriFragment?: boolean },
): Pointer;

Segment 배열을 pointer 문자열로 만든다. ~/ escape를 자동 처리한다.

buildPointer(["items", 0, "a/b"]); // "/items/0/a~1b"
buildPointer([], { uriFragment: true }); // "#"

escapeSegment(segment) / unescapeSegment(segment)

function escapeSegment(segment: string): string;
function unescapeSegment(segment: string): string;

Pointer segment 단위 escape/unescape를 수행한다.

직접 pointer 문자열을 이어 붙이기보다 buildPointerappendSegment를 우선 사용한다.

parentPointer(pointer)

function parentPointer(pointer: Pointer): Pointer | null;

Parent pointer를 반환한다.

parentPointer("/items/0/title"); // "/items/0"
parentPointer("/items"); // ""
parentPointer(""); // null

lastSegment(pointer)

function lastSegment(pointer: Pointer): string | null;

마지막 segment를 unescape된 문자열로 반환한다.

lastSegmentIndex(pointer)

function lastSegmentIndex(pointer: Pointer): number | null;

마지막 segment가 array index면 number를 반환하고, record key나 root면 null을 반환한다.

appendSegment(pointer, segment)

function appendSegment(pointer: Pointer, segment: string | number): Pointer;

Pointer 끝에 segment를 추가한다.

appendSegment("/items", 0); // "/items/0"

withLastSegment(pointer, segment)

function withLastSegment(pointer: Pointer, segment: string | number): Pointer | null;

마지막 segment를 교체한다. Root pointer면 null이다.

withLastSegment("/items/0", 1); // "/items/1"

PointerSyntaxError

class PointerSyntaxError extends Error {}

parsePointer가 잘못된 pointer에서 throw하는 error class다.

Pointer tracking

trackPointer(pointer, applied)

function trackPointer(
  pointer: Pointer,
  applied: ReadonlyArray<JSONPatchOperation>,
): Pointer | null;

Applied patch 이후 기존 pointer가 어디를 가리키는지 계산한다.

삭제된 target이면 null을 반환한다.

const next = trackPointer("/items/0", doc.lastPatch);

사용 맥락:

  • selection tracking
  • annotation/comment anchor tracking
  • external id mapping
  • review marker tracking

주의: applied는 실제 적용된 normalized operation이어야 한다. Hand-built operation에 /-가 남아 있으면 추적 결과가 null이 될 수 있다.

Sibling range

resolveSiblingRange(source, options?)

function resolveSiblingRange(
  source: Pointer | ReadonlyArray<Pointer>,
  options?: ResolveSiblingRangeOptions,
): SiblingRangeResult;

같은 parent array 아래의 item pointer들을 parent와 index run으로 정규화한다.

const range = resolveSiblingRange(["/items/2", "/items/0", "/items/1"], {
  requireContiguous: true,
});

if (range.ok) {
  range.parent; // "/items"
  range.locations.map((location) => location.index); // [0, 1, 2]
}

Options:

interface ResolveSiblingRangeOptions {
  dedupe?: boolean;
  pruneDescendants?: boolean;
  requireContiguous?: boolean;
}

성공 result:

{
  ok: true;
  parent: Pointer;
  locations: ReadonlyArray<SiblingLocation>;
  contiguous: boolean;
}

실패 code:

  • empty_selection
  • invalid_pointer
  • not_array_item
  • mixed_parent
  • non_contiguous

이 function은 document state를 읽지 않는다. Parent가 실제 array인지 확인해야 하는 경우 호출자가 doc.at(parent)나 schema API로 별도 검증한다.

사용 기준

  • 제품 UI command는 먼저 JSONDocument<T> method를 사용한다.
  • Adapter나 extension이 patch 계획만 필요하면 applyPatch 계열을 사용한다.
  • Path 문자열을 직접 다루는 코드는 pointer helper를 사용한다.
  • Document 외부의 anchor를 patch 이후 따라가야 하면 trackPointer를 사용한다.
  • 같은 parent array의 선택 item을 정규화해야 하면 resolveSiblingRange를 사용한다.