-
Notifications
You must be signed in to change notification settings - Fork 2
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.
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 }}
/>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.
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.
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.
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
onEndevent. - The terminal session error can be
EXTERNAL_COMPOSITION_COMMIT_FAILEDwhile an underlying error such asPOSITION_EPOCH_INVALIDis also delivered throughdocumentHandle.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.
- 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.
React Native Rich Text Editor · Documentation · Migration Guide