Skip to content

Production Limits and Errors

Jayden Smith edited this page Sep 7, 2026 · 8 revisions

Production Limits and Errors

Set limits deliberately before exposing user-controlled documents, collaboration rooms, or remote images. The package validates JavaScript input at its boundary and native code enforces compatible limits again. A limit is a safety budget, not a way to make an otherwise invalid document valid.

Set creation-time resource limits

limits.resource belongs to createNativeEditorDocumentHandle(). It is fixed for the life of that handle, so choose it before mounting an editor or joining a collaboration room.

import { createNativeEditorDocumentHandle } from '@apollohg/react-native-rich-text-editor';

const documentHandle = createNativeEditorDocumentHandle({
  initialization: { type: 'localEmpty' },
  limits: {
    resource: {
      maxInputBytes: 8 * 1024 * 1024,
      maxDocumentNodes: 25_000,
      maxDocumentDepth: 128,
      maxSchemaNodes: 512,
      maxSchemaExpressionBytes: 32 * 1024,
      maxCollaborationMessageBytes: 4 * 1024 * 1024,
      maxEncodedStateBytes: 16 * 1024 * 1024,
    },
  },
});

Each supplied value must be a positive safe integer no larger than its hard ceiling. Invalid limits.resource on createNativeEditorDocumentHandle() throws NativeEditorEngineBoundaryError with domain: "boundary" and code "INVALID_RESOURCE_LIMIT"; it is not silently clamped. Its limit and actual fields are canonical decimal strings or null, as for other typed document API errors. A custom schema that exceeds its configured schema-work budget fails with "SCHEMA_INVALID".

Resource budget Default Hard ceiling What it bounds
maxInputBytes 20 MiB 64 MiB Serialized input crossing the editor boundary.
maxDocumentNodes 100,000 1,000,000 Nodes admitted in one document.
maxDocumentDepth 256 1,024 Semantic nesting depth.
maxSchemaNodes 1,024 10,000 Node specs admitted while resolving a schema; marks contribute to schema-work validation but not this count.
maxSchemaExpressionBytes 64 KiB 1 MiB Total content-expression input.
maxCollaborationMessageBytes 10 MiB 64 MiB One native collaboration message.
maxEncodedStateBytes 50 MiB 256 MiB Encoded collaboration state or snapshot payload.

Use limits appropriate to the product rather than copying the hard ceilings. For example, an issue-comment editor can cap documents far below a long-form publishing editor; a collaboration room also needs an encoded-state budget that accommodates its expected history. A lower cap can reject formerly accepted documents, so keep limits stable for a persisted document family and communicate migrations explicitly.

RichTextViewer has no editable handle, but it accepts the same resourceLimits object directly. Give a viewer rendering untrusted content the same deliberate resource budget as the source that produces it. Its prop resolver is a separate JavaScript path and can throw the legacy NativeEditorBoundaryError; do not use that class to catch invalid handle-create limits.

<RichTextViewer
  contentJSON={document}
  resourceLimits={{ maxInputBytes: 8 * 1024 * 1024, maxDocumentNodes: 25_000 }}
/>

Bound remote image work

imageLoadingPolicy is a per-view loading budget for both RichTextEditor and RichTextViewer. It constrains remote image source size, time spent connecting and reading, total request lifetime, queued work, concurrent work, and decode dimensions. It does not turn an untrusted host into a trusted one: use your own URL allowlist or proxy when the product requires it.

const imageLoadingPolicy = {
  maxSourceBytes: 4 * 1024 * 1024,
  connectTimeoutMs: 5_000,
  readTimeoutMs: 10_000,
  requestTimeoutMs: 20_000,
  maxConcurrentRequests: 2,
  maxPendingRequests: 16,
  maxDecodeDimensionPx: 2_048,
  maxDecodedBytes: 16 * 1024 * 1024,
};

<RichTextEditor
  documentHandle={documentHandle}
  imageLoadingPolicy={imageLoadingPolicy}
/>
Image policy Default Hard ceiling
maxSourceBytes 10 MiB 64 MiB
connectTimeoutMs 10 s 10 min
readTimeoutMs 20 s 10 min
requestTimeoutMs 60 s 10 min
maxConcurrentRequests 2 16
maxPendingRequests 64 512
maxDecodeDimensionPx 2,048 px 8,192 px
maxDecodedBytes 32 MiB 256 MiB

Every image-policy value follows the same positive-safe-integer rule. An invalid policy throws NativeEditorBoundaryError code "IMAGE_POLICY_INVALID". For a text-first preview, set renderImages={false} on the viewer: image blocks remain in layout but native code makes no image resource requests. allowBase64Images is separately opt-in and defaults to false; enable it only when your input and size policy explicitly permit data URLs.

maxDecodedBytes bounds the decoded pixel memory retained by one editor or viewer, independently of compressed maxSourceBytes and pixel dimensions. Android also coordinates process and request ownership around this per-view budget.

Treat timeouts, source bytes, decoded bytes, dimensions, queue capacity, and URL trust as separate controls. Increasing only a timeout does not raise the source-size ceiling; increasing concurrency does not make oversized images valid.

Parse legacy boundary envelopes

NativeEditorBoundaryError is the legacy JavaScript boundary error class. It has code, message, and optional numeric limit, actual, and details fields. parseNativeBoundaryError() recognizes only the envelope { error: { code, message, ... } }; it returns null for an unrecognized value so application error handling can preserve the original failure.

import {
  parseNativeBoundaryError,
  type NativeEditorBoundaryError,
} from '@apollohg/react-native-rich-text-editor';

function reportBoundaryFailure(value: unknown) {
  const error: NativeEditorBoundaryError | null = parseNativeBoundaryError(value);
  if (error === null) {
    reportUnexpectedError(value);
    return;
  }

  logEditorFailure({
    code: error.code,
    limit: error.limit,
    actual: error.actual,
    details: error.details,
  });

  if (error.code === 'DOCUMENT_LIMIT_EXCEEDED') {
    showDocumentTooLargeMessage();
  }
}

Useful boundary codes include INPUT_LIMIT_EXCEEDED, DOCUMENT_PARSE_FAILED, DOCUMENT_INVALID, DOCUMENT_LIMIT_EXCEEDED, SCHEMA_INVALID, REQUIRED_ATTRIBUTE_MISSING, UNKNOWN_MARK, IMAGE_POLICY_INVALID, and IMAGE_REQUEST_TIMEOUT. Codes are forward-compatible strings, so log the complete code and treat unknown ones as a safe failure rather than assuming a closed enum.

Do not use parseNativeBoundaryError() around document-handle creation or other document bridge calls. They throw typed NativeEditorErrorBase subclasses instead. In particular, invalid limits.resource at creation is NativeEditorEngineBoundaryError, while an invalid imageLoadingPolicy resolved by an editor/viewer prop is a legacy NativeEditorBoundaryError with code "IMAGE_POLICY_INVALID".

Viewer errors are different: RichTextViewer sends { domain, code, message, fatal } to its onError callback. They are rendering events, not thrown document API exceptions. A fatal viewer error needs corrected/replaced content or configuration; a non-fatal resource failure can be logged while the text remains usable.

Handle typed document API errors by domain

Imperative document-handle bridge calls normalize native v2 failures into subclasses of NativeEditorErrorBase. Every one provides domain, code, message, requestId, operationIndex, limit, actual, and details; the numeric-looking envelope fields are canonical decimal strings or null, so they do not lose 64-bit precision in JavaScript.

Class domain Typical response
NativeEditorEngineBoundaryError boundary Correct host input or configuration.
NativeEditorDocumentError document Repair, migrate, or reject the supplied document.
NativeEditorOperationError operation Inspect the command and current document revision before choosing a new operation.
NativeEditorLifecycleError lifecycle Recreate or release the session according to its state.
NativeEditorSnapshotError snapshot Check snapshot scope, lineage, schema fingerprint, and transport state.
NativeEditorTransportError transport Observe transport state and let the native collaboration owner handle eligible reconnects.
NativeEditorNonRetryableError any Stop using this handle; the failure cannot succeed on retry for that handle.

NativeEditorNonRetryableError is reserved for ENGINE_INVARIANT_FAILED, ENGINE_DESTROYING, and ENGINE_DESTROYED. Destroyed and invariant-failed sessions must not be retried; remove them from the UI and create a new handle only when the product has a valid recovery path.

import {
  NativeEditorNonRetryableError,
  NativeEditorOperationError,
} from '@apollohg/react-native-rich-text-editor';

try {
  const state = documentHandle.bridge.getState();
  documentHandle.bridge.applyCommand({
    baseDocumentRevision: state.documentRevision,
    command,
  });
} catch (error) {
  if (error instanceof NativeEditorNonRetryableError) {
    reportFatalEditorSession(error);
    removeEditorFromScreen();
  } else if (
    error instanceof NativeEditorOperationError &&
    error.code === 'REVISION_MISMATCH'
  ) {
    refreshFromCurrentHandleState();
  } else {
    reportEditorOperationFailure(error);
  }
}

Do not treat every typed document API exception as transient. REVISION_MISMATCH means an operation was built for an older revision; refresh state and decide whether the user's intended operation is still valid before issuing a new one. Document validation, snapshot-scope, policy, and configuration errors need an input or migration fix, not a looped retry. Collaboration transport eligibility is separately owned by the native/Rust transport; surface status and errors to the user instead of starting a competing JavaScript retry loop.

Handle external composition failures

External text composition keeps provisional updates outside the authoritative document. A commit still passes through normal policy and position reconciliation.

  • beginExternalTextComposition() can reject when the editor is unavailable, unsupported, not editable, or does not have a text selection.
  • commit() can reject for policy or reconciliation failures.
  • A failed commit removes provisional presentation, restores the latest authorized render, and ends with a cancelled onEnd event.
  • The terminal session error can be EXTERNAL_COMPOSITION_COMMIT_FAILED while an underlying error such as POSITION_EPOCH_INVALID is also delivered through documentHandle.addErrorListener(...).
  • Provisional updates do not create content callbacks, undo entries, or collaboration updates.
  • A changed commit creates one typed transaction; an unchanged or fully filtered commit creates no local content update.

Wait for onEnd before discarding consumer state or beginning a replacement session. See External Text Composition for the session contract.

Operational checklist

  • Keep source documents, schemas, room lineage, resource limits, and snapshot storage compatible for a document family.
  • Log code, domain, limit, actual, request/operation IDs, and safe diagnostic details; do not log document bodies, credentials, or snapshot bytes by default.
  • Use product-specific fallback UI for unrenderable viewer content, oversized documents, and a destroyed session.
  • Test limit and recovery behavior with representative production-size documents and slow/failed image hosts before changing a limit or schema in a release.

For handle construction and lifecycle, see Document API Reference. For collaboration snapshot and transport behavior, see Collaboration.

Clone this wiki locally