@reintersect/sdk
v0.1.3
Published
The official Reintersect SDK for TypeScript applications.
Downloads
774
Maintainers
Readme
@reintersect/sdk
The official TypeScript SDK for reading Reintersect, authoring rich reply content with JSX, and
creating or updating replies. The default entrypoint is an async client that returns discriminated
{ data, error } results. Applications already using Effect can use the Effect-native client from
@reintersect/sdk/effect.
The package also includes the complete document JSX surface, API response types, schemas, and
direct semantic errors. The async entrypoint bundles its Effect runtime dependencies, so most
applications only install @reintersect/sdk.
[!IMPORTANT] Choose one SDK family for the whole application. Async imports and JSX use
@reintersect/sdk; Effect imports and JSX use@reintersect/sdk/effect. Values and components from the two bundled families are intentionally incompatible, so mixing them will not work.
- Install
- Configure TypeScript JSX
- Quick start
- Async and Effect clients
- Client operations
- Create and edit replies
- JSX component reference
- Files and downloads
- Cancellation and cleanup
- Semantic errors
- Public response types and schemas
- Package entrypoints
Install
pnpm add @reintersect/sdkCreate a read-write API key in Settings → Developer → API keys, then expose it to your
server-side application as REINTERSECT_API_KEY. Do not ship a Reintersect API key in public
browser code.
Effect applications also install the optional peers used by @reintersect/sdk/effect:
pnpm add @reintersect/sdk @effect/platform effectConfigure TypeScript JSX
Async applications use the root package for the automatic JSX transform:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@reintersect/sdk"
}
}For a single file, use a pragma instead:
/** @jsxImportSource @reintersect/sdk */Effect applications must select the Effect entrypoint instead:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@reintersect/sdk/effect"
}
}/** @jsxImportSource @reintersect/sdk/effect */Quick start
Async client
/** @jsxImportSource @reintersect/sdk */
import { Bold, Mention, createReintersectClient } from "@reintersect/sdk";
await using reintersect = createReintersectClient();
const { data, error } = await reintersect.replies.create({
conversationId,
content: (
<>
Hello <Bold>world</Bold>! Thanks <Mention type="person" id={memberId} />.
</>
),
});
if (error) {
console.error(error._tag, error.message, error.path);
} else {
console.log(data.id);
}Expected document, SDK, network, and API failures are returned as { data: null, error }.
Successes are returned as { data, error: null }. Caller cancellation and unexpected defects
reject the Promise.
Effect client
/** @jsxImportSource @reintersect/sdk/effect */
import { Bold, Mention, Reintersect, ReintersectClient } from "@reintersect/sdk/effect";
import { Effect } from "effect";
const program = Effect.gen(function* () {
const reintersect = yield* Reintersect;
return yield* reintersect.replies.create({
conversationId,
content: (
<>
Hello <Bold>world</Bold>! Thanks <Mention type="person" id={memberId} />.
</>
),
});
});
const reply = await Effect.runPromise(
program.pipe(Effect.provide(ReintersectClient.layerConfig())),
);layerConfig() reads REINTERSECT_API_KEY, optionally reads REINTERSECT_BASE_URL, and uses the
global Fetch implementation. Effect callers receive ordinary typed effects and use fiber
interruption, schedules, tracing, and error operators normally.
Async and Effect clients
Both clients expose the same resource namespaces and decode the same response schemas. Request objects are strict: unknown keys are rejected before the request is sent. Successful response DTOs are forward-compatible: additive unknown object properties are removed recursively, including from page items and nested authors. Missing fields, invalid known-field values, unknown closed literals, and malformed JSON remain invalid-response failures.
| Concern | Async client | Effect client |
| --- | --- | --- |
| Import | @reintersect/sdk | @reintersect/sdk/effect |
| Construct | createReintersectClient() | ReintersectClient.layer() or .layerConfig() |
| Access | returned client object | yield* Reintersect |
| Success | { data, error: null } | Effect success channel |
| Expected failure | { data: null, error } | typed Effect error channel |
| Cancellation | pass { signal }; rejection is AbortError | interrupt the fiber |
| Cleanup | close() or await using | layer scope |
| Composition | ordinary async/await | Effect operators and layers |
| Effect packages | bundled | optional peers supplied by the application |
Plain configuration
createReintersectClient() accepts plain values:
import { createReintersectClient } from "@reintersect/sdk";
const options = {
apiKey: "rei_…",
baseUrl: "https://api.reintersect.com",
fetch: customFetch,
};
const client = createReintersectClient(options);ReintersectClient.layer() accepts the same values from the Effect entrypoint:
import { ReintersectClient } from "@reintersect/sdk/effect";
const layer = ReintersectClient.layer(options);| Option | Fallback |
| --- | --- |
| apiKey | REINTERSECT_API_KEY in Node-like environments |
| baseUrl | REINTERSECT_BASE_URL, then https://api.reintersect.com |
| fetch | globalThis.fetch |
Missing keys, missing Fetch support, and malformed base URLs are semantic configuration errors. The async client throws them immediately during construction. The Effect layer exposes them while the layer is built.
Effect Config
Use layerConfig() when configuration should remain in Effect:
import { ReintersectClient } from "@reintersect/sdk/effect";
import { Config, Effect } from "effect";
const layer = ReintersectClient.layerConfig({
apiKey: Config.redacted("REINTERSECT_API_KEY"),
baseUrl: Config.string("REINTERSECT_BASE_URL"),
});
await Effect.runPromise(program.pipe(Effect.provide(layer)));Client operations
| Namespace | Method | Input | Result |
| --- | --- | --- | --- |
| identity | whoami() | none | API-key identity and access |
| circles | list() | { cursor?, limit? } | circle page |
| circles | get() | { slug } | circle |
| conversations | list() | { slug, cursor?, limit? } | conversation-summary page |
| conversations | get() | { conversationId } | conversation with content |
| replies | list() | { conversationId, cursor?, limit? } | top-level reply page |
| replies | listSubreplies() | { replyId, cursor?, limit? } | direct subreply page |
| replies | get() | { replyId } | reply |
| replies | create() | { conversationId, content, parentReplyId? } | created reply |
| replies | update() | { replyId, content } | updated reply |
| files | get() | { fileId } | file metadata and fresh URL |
| files | download() | { fileId } | { file, bytes: Uint8Array } |
Async methods take a second { signal? } argument. replies.create and replies.update
additionally accept strict there. Effect service methods return ordinary Effect values.
Pagination
List operations return { items, nextCursor }. The default page size is 50 and limit may be
between 1 and 100. nextCursor is null on the final page; otherwise, pass it back unchanged.
const firstResult = await reintersect.conversations.list({ slug: "all-hands", limit: 25 });
if (firstResult.error) throw firstResult.error;
const first = firstResult.data;
if (first.nextCursor !== null) {
const secondResult = await reintersect.conversations.list({
slug: "all-hands",
cursor: first.nextCursor,
limit: 25,
});
if (secondResult.error) throw secondResult.error;
}Response timestamps such as createdAt, updatedAt, and downloadUrlExpiresAt are decoded to
JavaScript Date values.
Create and edit replies
Reply mutations accept either JSX or a typed ReintersectDocument. JSX is the ergonomic default:
const createdResult = await reintersect.replies.create({
conversationId,
content: <>A concise reply with <Bold>one important point</Bold>.</>,
});
if (createdResult.error) throw createdResult.error;
const updatedResult = await reintersect.replies.update({
replyId: createdResult.data.id,
content: <>A clearer replacement.</>,
});
if (updatedResult.error) throw updatedResult.error;Set parentReplyId during creation to create a subreply. Reintersect supports one subreply level;
the API returns ReintersectApiReplyNestingLimitError when a parent would exceed it.
Updates replace the complete reply content. A raw ReintersectDocument is submitted as supplied;
the strict option only affects JSX.
Automatic paragraphs
Freestanding inline runs at the root, inside Document, inside Blockquote, and inside ListItem
are grouped into paragraphs automatically:
const shorthand = (
<>
Hello <Bold>world</Bold>.
<Blockquote>This also becomes a paragraph.</Blockquote>
</>
);Explicit paragraphs remain supported and preserve exact boundaries:
const explicit = (
<Document>
<Paragraph>First paragraph.</Paragraph>
<Paragraph>Second paragraph.</Paragraph>
</Document>
);Strict JSX
Pass { strict: true } when JSX must include an explicit Document root, explicit paragraphs in
block containers, and a leading Paragraph in every ListItem.
const content = (
<Document>
<Paragraph>Everything is explicit.</Paragraph>
</Document>
);
const result = await reintersect.replies.create(
{ conversationId, content },
{ signal: controller.signal, strict: true },
);The Effect client accepts the same strict option without signal; interrupt its fiber to cancel:
const reply = yield* reintersect.replies.update(
{ replyId, content },
{ strict: true },
);JSX is validated before source-backed assets are uploaded or the reply request begins.
JSX component reference
JSX components create opaque authoring values; they do not render browser UI. Fragments, nested arrays, mapped arrays, nullable conditionals, strings, finite numbers, and function components are supported where their parent allows them. Lowercase HTML elements are intentionally rejected.
Blocks
Document
An optional explicit root. It requires meaningful content. The SDK supplies a root in default mode; strict mode requires this component.
<Document>
<Paragraph>Hello.</Paragraph>
</Document>Paragraph
An explicit paragraph and paragraph boundary.

<Paragraph>A complete thought.</Paragraph>Blockquote
An indented quotation. Inline content inside it receives an inferred paragraph in default mode.

<Blockquote>Clarity is kindness.</Blockquote>UnorderedList
A bulleted list. Its children must be ListItem elements.

<UnorderedList>
<ListItem>First item</ListItem>
<ListItem>Second item</ListItem>
</UnorderedList>OrderedList
A numbered list. start defaults to 1 and must be a positive integer.

<OrderedList start={3}>
<ListItem>Third item</ListItem>
<ListItem>Fourth item</ListItem>
</OrderedList>ListItem
An item for either list type. Inline-first content becomes its leading paragraph automatically.
When a valid block appears first, default rendering inserts the required empty leading paragraph.
Strict mode requires an explicit leading Paragraph.

<ListItem>
An inferred paragraph
<UnorderedList>
<ListItem>A nested item</ListItem>
</UnorderedList>
</ListItem>Heading
A heading with level={1 | 2 | 3}. The default level is 1.

<Heading level={2}>Implementation notes</Heading>CodeBlock
A plain-text block with an optional supported language. language is statically typed as
ReintersectCodeBlockLanguage and checked again at runtime. Elements and marks are not valid
children.

<CodeBlock language="typescript">{"const answer = 42;"}</CodeBlock>Use ReintersectCodeBlockLanguages to inspect the generated catalog at runtime.
HorizontalRule
A thematic divider with no children.

<HorizontalRule />Inline elements
HardBreak
A line break that does not end the surrounding paragraph.

<Paragraph>
First line
<HardBreak />
Second line
</Paragraph>Emoji
An emoji selected by its canonical ReintersectEmojiName.

<Emoji name="sparkles" />Use ReintersectEmojiNames to inspect the generated catalog at runtime.
Mention
A reference to a person, circle, or conversation. Every type accepts an id; only circles may use
an exact, case-sensitive slug instead. Exactly one reference is required.

<Mention type="person" id={memberId} />
<Mention type="circle" id={circleId} />
<Mention type="circle" slug="all-hands" />
<Mention type="conversation" id={conversationId} />Circle slugs contain 1–128 ASCII letters, digits, _, or -. They are not trimmed or normalized.
The API resolves a circle slug and returned replies contain its canonical ID.
Assets
Source-backed assets require source, name, and a generated ReintersectMimeType. Accepted
sources are Uint8Array, ArrayBuffer, and Blob; Node.js Buffer works through its
Uint8Array inheritance. A typed Blob must agree with the explicit type.
The SDK uploads source-backed assets before it creates or updates the reply. Use the mutually
exclusive assetId form to preserve an existing asset reference, such as while editing the same
reply.
Image
An uploaded image with optional accessible alternative text.

<Image
source={bytes}
name="architecture.png"
type="image/png"
alt="Architecture diagram"
/>
<Image assetId={existingImageId} alt="Architecture diagram" />File
An uploaded file attachment.

<File source={bytes} name="notes.txt" type="text/plain" />
<File assetId={existingFileId} />Use ReintersectMimeTypes to inspect the accepted MIME catalog at runtime.
Marks
Marks wrap inline content and may be nested unless a mark documents a specific incompatibility.
Bold

<Bold>This is important.</Bold>Italic

<Italic>A little emphasis goes a long way.</Italic>Strike

Ship on <Strike>Friday</Strike> Thursday.Underline

Remember the <Underline>important detail</Underline>.Code
Inline code. It cannot be combined with other marks.

Run <Code>pnpm typecheck</Code> before shipping.Link
A safe link with required href and optional title. HTTP, HTTPS, email, telephone, relative-path,
query, and fragment destinations are supported. Protocol-relative URLs, backslashes, control
characters, and unsupported protocols are rejected before a request begins.

Visit <Link href="https://reintersect.com" title="Reintersect">Reintersect</Link>.Fragments and control flow
Fragment and the shorthand <>…</> group children without introducing a document node.
Show chooses content using JavaScript truthiness:
<Show when={member} fallback="Anonymous">
{(currentMember) => currentMember.displayName}
</Show>For maps a readonly collection and supports a fallback:
<For each={items} fallback="No items yet.">
{(item, index) => <Paragraph>{index + 1}. {item.label}</Paragraph>}
</For>Both helpers are evaluated immediately and produce transparent authoring content; they are not reactive UI primitives. User-defined function components may return any supported JSX child.
Files and downloads
files.get({ fileId }) returns metadata with a short-lived downloadUrl and
downloadUrlExpiresAt. Use files.download({ fileId }) when you need bytes; it obtains fresh
metadata and returns:
interface ReintersectFileDownload {
readonly file: ReintersectFile;
readonly bytes: Uint8Array;
}Normal requests time out after 30 seconds. Uploads and downloads time out after five minutes.
Timeouts are returned as ReintersectSdkTimeoutError.
Cancellation and cleanup
Async callers pass an AbortSignal as the second argument:
const controller = new AbortController();
const pending = reintersect.conversations.get(
{ conversationId },
{ signal: controller.signal },
);
controller.abort();
await pending; // rejects with an error whose name is "AbortError"Close the async client when it is no longer needed:
const reintersect = createReintersectClient();
try {
// use the client
} finally {
await reintersect.close();
}await using calls the same cleanup through Symbol.asyncDispose.
Effect callers cancel by interrupting the running fiber; layer scopes own client cleanup.
Semantic errors
All modeled failures are direct tagged errors with a stable _tag, human-readable message, and
precise path. Catch the semantic leaf you can handle instead of parsing messages.
Async
const { data, error } = await reintersect.replies.create({ conversationId, content });
if (error) {
switch (error._tag) {
case "ReintersectApiCircleMentionUnavailableError":
console.error(`No accessible circle has slug ${error.slug}.`);
break;
case "ReintersectDocumentUnsafeLinkError":
console.error(`Unsafe link at ${error.path.join(".")}: ${error.href}`);
break;
default:
console.error(error.message);
}
}Effect
const reply = yield* reintersect.replies.create({ conversationId, content }).pipe(
Effect.catchTag("ReintersectApiCircleMentionUnavailableError", (error) =>
Effect.fail(new CircleReferenceError({ slug: error.slug, path: error.path })),
),
Effect.catchTag("ReintersectDocumentUnsafeLinkError", (error) =>
Effect.fail(new UnsafeCustomerLinkError({ href: error.href, path: error.path })),
),
);Error families
ReintersectDocument…Errorreports local JSX root, hierarchy, attribute, mention, link, emoji, language, number, cycle, and asset failures.ReintersectSdk…Errorreports configuration, request encoding, network, timeout, response, and asset-preparation failures.ReintersectApi…Errorreports authentication, authorization, request, resource, document, asset, rate-limit, and service failures returned by Reintersect. An unrecognized non-success response becomesReintersectApiUnknownError, which exposes only a generic message, empty path, and HTTP status.- Operation types such as
ReintersectRepliesCreateErrorcompose exactly the failures modeled by that method.
Direct error tags
| Boundary | Direct tags |
| --- | --- |
| JSX structure | ReintersectDocumentInvalidRootError, ReintersectDocumentEmptyError, ReintersectDocumentInvalidElementError, ReintersectDocumentInvalidStructureError, ReintersectDocumentNonFiniteNumberError, ReintersectDocumentCyclicChildrenError |
| JSX attributes | ReintersectDocumentInvalidAttributesError, ReintersectDocumentInvalidCodeBlockLanguageError, ReintersectDocumentInvalidEmojiNameError, ReintersectDocumentInvalidMentionError, ReintersectDocumentUnsafeLinkError |
| JSX assets | ReintersectDocumentInvalidAssetError |
| SDK configuration | ReintersectSdkMissingApiKeyError, ReintersectSdkMissingFetchError, ReintersectSdkInvalidConfigurationError |
| SDK transport | ReintersectSdkInvalidInputError, ReintersectSdkNetworkError, ReintersectSdkTimeoutError, ReintersectSdkInvalidResponseError |
| SDK assets | ReintersectSdkUnreadableAssetSourceError |
| API authentication | ReintersectApiKeyMissingError, ReintersectApiKeyInvalidError, ReintersectApiKeyExpiredError |
| API authorization | ReintersectApiInsufficientAccessError, ReintersectApiMemberInactiveError, ReintersectApiSubscriptionRequiredError |
| API requests | ReintersectApiInvalidRequestError, ReintersectApiInvalidCursorError, ReintersectApiRateLimitExceededError |
| API resources | ReintersectApiCircleNotFoundError, ReintersectApiConversationNotFoundError, ReintersectApiReplyNotFoundError, ReintersectApiFileNotFoundError, ReintersectApiReplyNotOwnedError, ReintersectApiReplyNestingLimitError |
| API documents | ReintersectApiInvalidDocumentError, ReintersectApiMentionUnavailableError, ReintersectApiCircleMentionUnavailableError, ReintersectApiInvalidEmojiNameError, ReintersectApiUnsafeLinkError, ReintersectApiInvalidAssetReferenceError |
| API assets | ReintersectApiInvalidAssetNameError, ReintersectApiInvalidAssetMimeTypeError, ReintersectApiEmptyAssetError, ReintersectApiUploadFailedError |
| API services | ReintersectApiInternalServerError, ReintersectApiServiceUnavailableError, ReintersectApiUnknownError |
Runtime schema unions are exported for validation and matching:
| Family | Runtime unions |
| --- | --- |
| JSX | ReintersectDocumentStructureError, ReintersectDocumentAttributeError, ReintersectDocumentAssetError, ReintersectDocumentRenderError |
| SDK | ReintersectSdkConfigurationError, ReintersectSdkTransportError, ReintersectSdkAssetError, ReintersectSdkClientError |
| API | ReintersectApiAuthenticationError, ReintersectApiAuthorizationError, ReintersectApiRequestError, ReintersectApiResourceError, ReintersectApiDocumentError, ReintersectApiAssetError, ReintersectApiServiceError, ReintersectApiError |
Public response types and schemas
Every response type has a matching Effect Schema value, so applications can reuse the SDK contract at their own boundaries. These schemas remove additive unknown object properties recursively while continuing to validate every known field.
| Values | Types | Schemas |
| --- | --- | --- |
| Identity | ReintersectIdentity | ReintersectIdentitySchema |
| Circle | ReintersectCircle | ReintersectCircleSchema |
| Conversation summary | ReintersectConversationSummary | ReintersectConversationSummarySchema |
| Conversation detail | ReintersectConversation | ReintersectConversationSchema |
| Reply | ReintersectReply | ReintersectReplySchema |
| File | ReintersectFile | ReintersectFileSchema |
| Public author | ReintersectPublicAuthor | ReintersectPublicAuthorSchema |
| Circle page | ReintersectCirclePage | ReintersectCirclePageSchema |
| Conversation page | ReintersectConversationPage | ReintersectConversationPageSchema |
| Reply page | ReintersectReplyPage | ReintersectReplyPageSchema |
| Structured document | ReintersectDocument | ReintersectDocumentSchema |
Branded identifier types and their schemas are also exported for users, members, workspaces,
circles, conversations, replies, files, circle slugs, and cursors. ReintersectApiAccess is
"read" | "readWrite". JSON-safe raw document helpers are exported as ReintersectJsonValue and
ReintersectJsonObject.
The SDK exports opaque ReintersectElement and ReintersectDocumentInput types, child and
component prop types, RenderReintersectOptions, and the generated
ReintersectCodeBlockLanguage, ReintersectEmojiName, and ReintersectMimeType catalogs.
Package entrypoints
| Entrypoint | Purpose |
| --- | --- |
| @reintersect/sdk | self-contained async client, JSX, response schemas and types, semantic errors |
| @reintersect/sdk/effect | Effect-native client, JSX, schemas, types, and errors; requires the optional Effect peers |
| @reintersect/sdk/jsx-runtime | automatic production JSX transform |
| @reintersect/sdk/jsx-dev-runtime | automatic development JSX transform |
| @reintersect/sdk/effect/jsx-runtime | Effect-family automatic production JSX transform |
| @reintersect/sdk/effect/jsx-dev-runtime | Effect-family automatic development JSX transform |
The TypeScript JSX transform selects runtime entrypoints automatically; application code should
normally import the async client and JSX from @reintersect/sdk. Effect applications import their
client, components, schemas, and errors from @reintersect/sdk/effect and set jsxImportSource to
@reintersect/sdk/effect. Do not pass JSX elements, components, schemas, errors, or client types
between the two families.
