diff --git a/.github/actions/unit-tests/action.yml b/.github/actions/unit-tests/action.yml
index 02ac242d..1850b066 100644
--- a/.github/actions/unit-tests/action.yml
+++ b/.github/actions/unit-tests/action.yml
@@ -12,3 +12,7 @@ runs:
run: |
npm run test:unit
shell: bash
+ - name: Run type tests
+ run: |
+ npm run test:types
+ shell: bash
diff --git a/doc/7/getting-started/react/index.md b/doc/7/getting-started/react/index.md
new file mode 100644
index 00000000..2bf486b7
--- /dev/null
+++ b/doc/7/getting-started/react/index.md
@@ -0,0 +1,7 @@
+---
+code: false
+type: branch
+title: React
+description: Get started with the Javascript SDK and React
+order: 200
+---
diff --git a/doc/7/getting-started/react/standalone/index.md b/doc/7/getting-started/react/standalone/index.md
new file mode 100644
index 00000000..fc07266b
--- /dev/null
+++ b/doc/7/getting-started/react/standalone/index.md
@@ -0,0 +1,106 @@
+---
+code: false
+type: page
+title: Standalone
+description: Getting started with Kuzzle and React
+order: 100
+---
+
+# Getting Started with Kuzzle and React
+
+This tutorial explains how to use **Kuzzle** with the **Javascript SDK 7** and **React**.
+
+You are going to write a realtime chat: messages are stored as **documents** in Kuzzle, and every client is kept up to date through [document notifications](/sdk/js/7/essentials/realtime-notifications#document-messages).
+
+To follow this tutorial, you must have a Kuzzle Server up and running. Follow these instructions if this is not already the case: [Running Kuzzle](/core/2/guides/getting-started/run-kuzzle).
+
+:::info
+Having trouble? Get in touch with us on [Discord](http://join.discord.kuzzle.io)!
+:::
+
+## Requirements
+
+- **Node.js** >= 20 ([download page](https://nodejs.org/en/download/))
+- a **running Kuzzle V2 stack** ([instructions here](/core/2/guides/getting-started/run-kuzzle))
+
+## Prepare your environment
+
+Create a React application with [Vite](https://vite.dev/) and install the Javascript SDK:
+
+```bash
+npm create vite@latest kuzzle-playground -- --template react
+cd kuzzle-playground
+npm install
+npm install kuzzle-sdk@7
+```
+
+You can now empty `src/App.jsx` and `src/App.css`: we are going to rewrite them.
+
+## Instantiating the SDK
+
+The SDK client holds the network connection, so the whole application must share a single instance.
+
+Create a `src/services/kuzzle.js` file:
+
+<<< ./snippets/kuzzle.js
+
+:::info
+Replace `localhost` with the hostname of the machine running your Kuzzle server.
+:::
+
+## Connecting and loading the messages
+
+Everything that talks to Kuzzle lives in a single custom hook. Create a `src/useChat.js` file:
+
+<<< ./snippets/useChat.js
+
+This hook does the following, once the user has chosen a nickname:
+
+- [connects](/sdk/js/7/core-classes/kuzzle/connect) the SDK to Kuzzle,
+- creates the `chat` [index](/sdk/js/7/controllers/index/create) and the `messages` [collection](/sdk/js/7/controllers/collection/create) if they don't [exist](/sdk/js/7/controllers/index/exists) yet,
+- [subscribes](/sdk/js/7/controllers/realtime/subscribe) to the collection, so that every message created by any client is prepended to the local state,
+- [searches](/sdk/js/7/controllers/document/search) for the hundred most recent messages to fill the history,
+- exposes a `sendMessage` function that [creates](/sdk/js/7/controllers/document/create) a new document.
+
+:::info
+The subscription is opened **before** the history is fetched, and the cleanup function [unsubscribes](/sdk/js/7/controllers/realtime/unsubscribe) when the component unmounts. This way no message can slip through between the two calls, and React's Strict Mode does not leave a dangling room behind.
+:::
+
+Note that `sendMessage` does not touch the local state: the new message comes back through the realtime notification, exactly like the ones sent by the other clients.
+
+## Displaying the messages
+
+Create a `src/Message.jsx` component to render a single message:
+
+<<< ./snippets/Message.jsx
+
+Then add the styles in `src/App.css`:
+
+<<< ./snippets/App.css
+
+## Putting it together
+
+Finally, rewrite `src/App.jsx`. It asks for a nickname, then displays the message list and the input used to send new ones:
+
+<<< ./snippets/App.jsx
+
+Launch the application:
+
+```bash
+npm run dev
+```
+
+:::success
+Open the printed URL in two different browser tabs, pick a different nickname in each one, and send a message: it shows up in both tabs instantly, pushed by Kuzzle.
+:::
+
+## Going further
+
+Now that you are more familiar with Kuzzle, dive even deeper to learn how to leverage its full capabilities:
+
+- Follow the [React with Redux](/sdk/js/7/getting-started/react/with-redux) tutorial to move this state into a Redux store
+- Discover what this SDK has to offer by browsing other sections of this documentation
+- Learn more about Kuzzle [realtime engine](/core/2/guides/main-concepts/realtime-engine)
+- Learn how to use the Kuzzle [Admin Console](http://console.kuzzle.io) to manage your users and data
+- Learn how to use [Koncorde](/core/2/api/koncorde-filters-syntax) to create incredibly fine-grained and blazing-fast subscriptions
+- Follow our guide to learn how to [manage users, and how to set up fine-grained access control](/core/2/guides/main-concepts/permissions)
diff --git a/doc/7/getting-started/react/standalone/snippets/App.css b/doc/7/getting-started/react/standalone/snippets/App.css
new file mode 100644
index 00000000..86e1142b
--- /dev/null
+++ b/doc/7/getting-started/react/standalone/snippets/App.css
@@ -0,0 +1,28 @@
+.wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 15px;
+}
+
+.messages {
+ padding: 10px;
+ margin: 10px;
+ width: 45vw;
+ border-radius: 10px;
+}
+
+.fromMe {
+ text-align: right;
+ float: right;
+ margin-left: 49vw;
+ background-color: rgb(0 40 53 / 30%);
+}
+
+.fromOthers {
+ text-align: left;
+ float: left;
+ margin-right: 49vw;
+ color: #eeefff;
+ background-color: rgb(0 40 53 / 90%);
+}
diff --git a/doc/7/getting-started/react/standalone/snippets/App.jsx b/doc/7/getting-started/react/standalone/snippets/App.jsx
new file mode 100644
index 00000000..2d728340
--- /dev/null
+++ b/doc/7/getting-started/react/standalone/snippets/App.jsx
@@ -0,0 +1,57 @@
+import { useState } from "react";
+
+import Message from "./Message";
+import { useChat } from "./useChat";
+import "./App.css";
+
+export default function App() {
+ const [username, setUsername] = useState(null);
+ const [draft, setDraft] = useState("");
+ const { messages, ready, sendMessage } = useChat(username);
+
+ // Ask for a nickname before joining the chat
+ if (!username) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+ {messages.map((message) => (
+
+ ))}
+
+
+ );
+}
diff --git a/doc/7/getting-started/react/standalone/snippets/Message.jsx b/doc/7/getting-started/react/standalone/snippets/Message.jsx
new file mode 100644
index 00000000..f7b79917
--- /dev/null
+++ b/doc/7/getting-started/react/standalone/snippets/Message.jsx
@@ -0,0 +1,13 @@
+export default function Message({ message, username }) {
+ const origin = message.username === username ? "fromMe" : "fromOthers";
+
+ return (
+
+
+ User: {message.username}
+
+
({new Date(message.createdAt).toLocaleString()})
+
{message.value}
+
+ );
+}
diff --git a/doc/7/getting-started/react/standalone/snippets/kuzzle.js b/doc/7/getting-started/react/standalone/snippets/kuzzle.js
new file mode 100644
index 00000000..3d01052d
--- /dev/null
+++ b/doc/7/getting-started/react/standalone/snippets/kuzzle.js
@@ -0,0 +1,10 @@
+import { Kuzzle, WebSocket } from "kuzzle-sdk";
+
+// Replace 'localhost' with the hostname of your Kuzzle server
+const kuzzle = new Kuzzle(new WebSocket("localhost"));
+
+kuzzle.on("networkError", (error) => {
+ console.error("Network Error:", error);
+});
+
+export default kuzzle;
diff --git a/doc/7/getting-started/react/standalone/snippets/useChat.js b/doc/7/getting-started/react/standalone/snippets/useChat.js
new file mode 100644
index 00000000..40b0df79
--- /dev/null
+++ b/doc/7/getting-started/react/standalone/snippets/useChat.js
@@ -0,0 +1,96 @@
+import { useCallback, useEffect, useRef, useState } from "react";
+
+import kuzzle from "./services/kuzzle";
+
+// Turns a Kuzzle document into the shape our components expect
+const toMessage = (document) => ({
+ _id: document._id,
+ value: document._source.value,
+ username: document._source.username,
+ createdAt: document._source._kuzzle_info.createdAt,
+});
+
+export function useChat(username) {
+ const [messages, setMessages] = useState([]);
+ const [ready, setReady] = useState(false);
+ const roomId = useRef(null);
+
+ useEffect(() => {
+ // Nothing to do until the user has picked a nickname
+ if (!username) {
+ return;
+ }
+
+ let cancelled = false;
+
+ const start = async () => {
+ await kuzzle.connect();
+
+ // Creates the index and the collection on first run
+ if (!(await kuzzle.index.exists("chat"))) {
+ await kuzzle.index.create("chat");
+ await kuzzle.collection.create("chat", "messages");
+ }
+
+ // Receives a notification for every new message
+ roomId.current = await kuzzle.realtime.subscribe(
+ "chat",
+ "messages",
+ {},
+ (notification) => {
+ if (notification.type !== "document") {
+ return;
+ }
+
+ if (notification.action !== "create") {
+ return;
+ }
+
+ setMessages((previous) => [
+ toMessage(notification.result),
+ ...previous,
+ ]);
+ },
+ );
+
+ // Loads the hundred most recent messages
+ const results = await kuzzle.document.search(
+ "chat",
+ "messages",
+ { sort: { "_kuzzle_info.createdAt": "desc" } },
+ { size: 100 },
+ );
+
+ if (cancelled) {
+ return;
+ }
+
+ setMessages(results.hits.map(toMessage));
+ setReady(true);
+ };
+
+ start().catch((error) => console.error(error.message));
+
+ return () => {
+ cancelled = true;
+
+ if (roomId.current) {
+ kuzzle.realtime.unsubscribe(roomId.current);
+ roomId.current = null;
+ }
+ };
+ }, [username]);
+
+ const sendMessage = useCallback(
+ async (value) => {
+ if (!value) {
+ return;
+ }
+
+ await kuzzle.document.create("chat", "messages", { value, username });
+ },
+ [username],
+ );
+
+ return { messages, ready, sendMessage };
+}
diff --git a/doc/7/getting-started/react/with-redux/index.md b/doc/7/getting-started/react/with-redux/index.md
new file mode 100644
index 00000000..e5bfc251
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/index.md
@@ -0,0 +1,126 @@
+---
+code: false
+type: page
+title: React with Redux
+description: Getting started with Kuzzle and React with Redux Toolkit
+order: 200
+---
+
+# Getting Started with Kuzzle and React with Redux
+
+This tutorial explains how to use **Kuzzle** with the **Javascript SDK 7**, **React** and **Redux** (through [Redux Toolkit](https://redux-toolkit.js.org/)).
+
+It builds the same realtime chat as the [standalone React tutorial](/sdk/js/7/getting-started/react/standalone), but the messages live in a Redux store instead of a component state. Messages are stored as **documents** in Kuzzle, and every client is kept up to date through [document notifications](/sdk/js/7/essentials/realtime-notifications#document-messages).
+
+To follow this tutorial, you must have a Kuzzle Server up and running. Follow these instructions if this is not already the case: [Running Kuzzle](/core/2/guides/getting-started/run-kuzzle).
+
+:::info
+Having trouble? Get in touch with us on [Discord](http://join.discord.kuzzle.io)!
+:::
+
+## Requirements
+
+- **Node.js** >= 20 ([download page](https://nodejs.org/en/download/))
+- a **running Kuzzle V2 stack** ([instructions here](/core/2/guides/getting-started/run-kuzzle))
+
+## Prepare your environment
+
+Create a React application with [Vite](https://vite.dev/) and install the Javascript SDK along with Redux:
+
+```bash
+npm create vite@latest kuzzle-playground -- --template react
+cd kuzzle-playground
+npm install
+npm install kuzzle-sdk@7 @reduxjs/toolkit react-redux
+```
+
+:::info
+This tutorial uses **Redux Toolkit**, which is the approach [recommended by the Redux team](https://redux.js.org/introduction/why-rtk-is-redux-today). The hand-written action types, switch reducers and `redux-saga` middleware of the older tutorials are no longer needed.
+:::
+
+## Instantiating the SDK
+
+The SDK client holds the network connection, so the whole application must share a single instance.
+
+Create a `src/services/kuzzle.js` file:
+
+<<< ./snippets/kuzzle.js
+
+:::info
+Replace `localhost` with the hostname of the machine running your Kuzzle server.
+:::
+
+## Creating the store
+
+The store holds the message list. We need a slice with:
+
+- a `messageReceived` reducer, fed by the realtime subscription,
+- a `fetchMessages` thunk that [searches](/sdk/js/7/controllers/document/search) for the existing messages,
+- a `sendMessage` thunk that [creates](/sdk/js/7/controllers/document/create) a document.
+
+Create a `src/state/messagesSlice.js` file:
+
+<<< ./snippets/messagesSlice.js
+
+Note that `sendMessage` does not add anything to the store: the new message comes back through the realtime notification, exactly like the ones sent by the other clients. There is a single code path for every message, wherever it comes from.
+
+Then declare the store itself in `src/state/store.js`:
+
+<<< ./snippets/store.js
+
+And make it available to the whole application by wrapping it in a `Provider`, in `src/main.jsx`:
+
+<<< ./snippets/main.jsx
+
+## Connecting to Kuzzle
+
+All the Kuzzle lifecycle lives in a single hook, which dispatches into the store. Create a `src/useKuzzleSync.js` file:
+
+<<< ./snippets/useKuzzleSync.js
+
+Once the user has chosen a nickname, this hook:
+
+- [connects](/sdk/js/7/core-classes/kuzzle/connect) the SDK to Kuzzle,
+- creates the `chat` [index](/sdk/js/7/controllers/index/create) and the `messages` [collection](/sdk/js/7/controllers/collection/create) if they don't [exist](/sdk/js/7/controllers/index/exists) yet,
+- [subscribes](/sdk/js/7/controllers/realtime/subscribe) to the collection and dispatches `messageReceived` on every document creation,
+- dispatches `fetchMessages` to fill the history.
+
+:::info
+The subscription is opened **before** the history is fetched, and the cleanup function [unsubscribes](/sdk/js/7/controllers/realtime/unsubscribe) when the component unmounts. This way no message can slip through between the two calls, and React's Strict Mode does not leave a dangling room behind.
+:::
+
+## Displaying the messages
+
+Create a `src/Message.jsx` component to render a single message:
+
+<<< ./snippets/Message.jsx
+
+Then add the styles in `src/App.css`:
+
+<<< ./snippets/App.css
+
+## Putting it together
+
+Finally, rewrite `src/App.jsx`. It reads the messages from the store with `useSelector`, and sends new ones by dispatching the `sendMessage` thunk:
+
+<<< ./snippets/App.jsx
+
+Launch the application:
+
+```bash
+npm run dev
+```
+
+:::success
+Open the printed URL in two different browser tabs, pick a different nickname in each one, and send a message: it shows up in both tabs instantly, pushed by Kuzzle. Install the [Redux DevTools](https://github.com/reduxjs/redux-devtools) extension to watch the `messages/messageReceived` actions as they arrive.
+:::
+
+## Going further
+
+Now that you are more familiar with Kuzzle, dive even deeper to learn how to leverage its full capabilities:
+
+- Discover what this SDK has to offer by browsing other sections of this documentation
+- Learn more about Kuzzle [realtime engine](/core/2/guides/main-concepts/realtime-engine)
+- Learn how to use the Kuzzle [Admin Console](http://console.kuzzle.io) to manage your users and data
+- Learn how to use [Koncorde](/core/2/api/koncorde-filters-syntax) to create incredibly fine-grained and blazing-fast subscriptions
+- Follow our guide to learn how to [manage users, and how to set up fine-grained access control](/core/2/guides/main-concepts/permissions)
diff --git a/doc/7/getting-started/react/with-redux/snippets/App.css b/doc/7/getting-started/react/with-redux/snippets/App.css
new file mode 100644
index 00000000..86e1142b
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/App.css
@@ -0,0 +1,28 @@
+.wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 15px;
+}
+
+.messages {
+ padding: 10px;
+ margin: 10px;
+ width: 45vw;
+ border-radius: 10px;
+}
+
+.fromMe {
+ text-align: right;
+ float: right;
+ margin-left: 49vw;
+ background-color: rgb(0 40 53 / 30%);
+}
+
+.fromOthers {
+ text-align: left;
+ float: left;
+ margin-right: 49vw;
+ color: #eeefff;
+ background-color: rgb(0 40 53 / 90%);
+}
diff --git a/doc/7/getting-started/react/with-redux/snippets/App.jsx b/doc/7/getting-started/react/with-redux/snippets/App.jsx
new file mode 100644
index 00000000..e3f5921d
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/App.jsx
@@ -0,0 +1,68 @@
+import { useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+
+import Message from "./Message";
+import { useKuzzleSync } from "./useKuzzleSync";
+import { sendMessage } from "./state/messagesSlice";
+import "./App.css";
+
+export default function App() {
+ const [username, setUsername] = useState(null);
+ const [draft, setDraft] = useState("");
+ const dispatch = useDispatch();
+ const { list: messages, ready } = useSelector((state) => state.messages);
+
+ useKuzzleSync(username);
+
+ // Ask for a nickname before joining the chat
+ if (!username) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+ {messages.map((message) => (
+
+ ))}
+
+
+ );
+}
diff --git a/doc/7/getting-started/react/with-redux/snippets/Message.jsx b/doc/7/getting-started/react/with-redux/snippets/Message.jsx
new file mode 100644
index 00000000..f7b79917
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/Message.jsx
@@ -0,0 +1,13 @@
+export default function Message({ message, username }) {
+ const origin = message.username === username ? "fromMe" : "fromOthers";
+
+ return (
+
+
+ User: {message.username}
+
+
({new Date(message.createdAt).toLocaleString()})
+
{message.value}
+
+ );
+}
diff --git a/doc/7/getting-started/react/with-redux/snippets/kuzzle.js b/doc/7/getting-started/react/with-redux/snippets/kuzzle.js
new file mode 100644
index 00000000..3d01052d
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/kuzzle.js
@@ -0,0 +1,10 @@
+import { Kuzzle, WebSocket } from "kuzzle-sdk";
+
+// Replace 'localhost' with the hostname of your Kuzzle server
+const kuzzle = new Kuzzle(new WebSocket("localhost"));
+
+kuzzle.on("networkError", (error) => {
+ console.error("Network Error:", error);
+});
+
+export default kuzzle;
diff --git a/doc/7/getting-started/react/with-redux/snippets/main.jsx b/doc/7/getting-started/react/with-redux/snippets/main.jsx
new file mode 100644
index 00000000..3de5c932
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/main.jsx
@@ -0,0 +1,14 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { Provider } from "react-redux";
+
+import App from "./App";
+import store from "./state/store";
+
+createRoot(document.getElementById("root")).render(
+
+
+
+
+ ,
+);
diff --git a/doc/7/getting-started/react/with-redux/snippets/messagesSlice.js b/doc/7/getting-started/react/with-redux/snippets/messagesSlice.js
new file mode 100644
index 00000000..875cd2cc
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/messagesSlice.js
@@ -0,0 +1,53 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+
+import kuzzle from "../services/kuzzle";
+
+// Turns a Kuzzle document into the shape our components expect
+export const toMessage = (document) => ({
+ _id: document._id,
+ value: document._source.value,
+ username: document._source.username,
+ createdAt: document._source._kuzzle_info.createdAt,
+});
+
+export const fetchMessages = createAsyncThunk("messages/fetch", async () => {
+ const results = await kuzzle.document.search(
+ "chat",
+ "messages",
+ { sort: { "_kuzzle_info.createdAt": "desc" } },
+ { size: 100 },
+ );
+
+ return results.hits.map(toMessage);
+});
+
+export const sendMessage = createAsyncThunk(
+ "messages/send",
+ async ({ value, username }) => {
+ await kuzzle.document.create("chat", "messages", { value, username });
+ },
+);
+
+const messagesSlice = createSlice({
+ name: "messages",
+ initialState: {
+ list: [],
+ ready: false,
+ },
+ reducers: {
+ // Dispatched by the realtime subscription, for our own messages as well
+ messageReceived(state, action) {
+ state.list.unshift(action.payload);
+ },
+ },
+ extraReducers: (builder) => {
+ builder.addCase(fetchMessages.fulfilled, (state, action) => {
+ state.list = action.payload;
+ state.ready = true;
+ });
+ },
+});
+
+export const { messageReceived } = messagesSlice.actions;
+
+export default messagesSlice.reducer;
diff --git a/doc/7/getting-started/react/with-redux/snippets/store.js b/doc/7/getting-started/react/with-redux/snippets/store.js
new file mode 100644
index 00000000..73d2c7e9
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/store.js
@@ -0,0 +1,9 @@
+import { configureStore } from "@reduxjs/toolkit";
+
+import messagesReducer from "./messagesSlice";
+
+export default configureStore({
+ reducer: {
+ messages: messagesReducer,
+ },
+});
diff --git a/doc/7/getting-started/react/with-redux/snippets/useKuzzleSync.js b/doc/7/getting-started/react/with-redux/snippets/useKuzzleSync.js
new file mode 100644
index 00000000..e6ff3326
--- /dev/null
+++ b/doc/7/getting-started/react/with-redux/snippets/useKuzzleSync.js
@@ -0,0 +1,60 @@
+import { useEffect, useRef } from "react";
+import { useDispatch } from "react-redux";
+
+import kuzzle from "./services/kuzzle";
+import {
+ fetchMessages,
+ messageReceived,
+ toMessage,
+} from "./state/messagesSlice";
+
+export function useKuzzleSync(username) {
+ const dispatch = useDispatch();
+ const roomId = useRef(null);
+
+ useEffect(() => {
+ // Nothing to do until the user has picked a nickname
+ if (!username) {
+ return;
+ }
+
+ const start = async () => {
+ await kuzzle.connect();
+
+ // Creates the index and the collection on first run
+ if (!(await kuzzle.index.exists("chat"))) {
+ await kuzzle.index.create("chat");
+ await kuzzle.collection.create("chat", "messages");
+ }
+
+ // Every new message is pushed into the store by the reducer
+ roomId.current = await kuzzle.realtime.subscribe(
+ "chat",
+ "messages",
+ {},
+ (notification) => {
+ if (notification.type !== "document") {
+ return;
+ }
+
+ if (notification.action !== "create") {
+ return;
+ }
+
+ dispatch(messageReceived(toMessage(notification.result)));
+ },
+ );
+
+ dispatch(fetchMessages());
+ };
+
+ start().catch((error) => console.error(error.message));
+
+ return () => {
+ if (roomId.current) {
+ kuzzle.realtime.unsubscribe(roomId.current);
+ roomId.current = null;
+ }
+ };
+ }, [dispatch, username]);
+}
diff --git a/package-lock.json b/package-lock.json
index 051930ec..463dbbea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "7.17.1",
"license": "Apache-2.0",
"dependencies": {
+ "kuzzle-types": "^1.0.0",
"ws": "8.18.3"
},
"devDependencies": {
@@ -6280,6 +6281,12 @@
"seed-random": "~2.2.0"
}
},
+ "node_modules/kuzzle-types": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/kuzzle-types/-/kuzzle-types-1.0.0.tgz",
+ "integrity": "sha512-IrlI4urlQYtsF+X6UYhZSRkc9K5t7risFVDUC/e/yduFRX501BhODdU5ga6dKw4lgNBQN82NU4wxU9TXAiKNZQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
diff --git a/package.json b/package.json
index c13d0958..4552bcfb 100644
--- a/package.json
+++ b/package.json
@@ -19,8 +19,9 @@
],
"scripts": {
"prepublishOnly": "npm run build",
- "test": "npm run test:lint && npm run test:unit && npm run test:functional",
+ "test": "npm run test:lint && npm run test:unit && npm run test:types && npm run test:functional",
"test:unit": "nyc --reporter=text-summary --reporter=lcov mocha",
+ "test:types": "tsc -p test/types/tsconfig.json",
"test:functional": "cucumber-js --exit --fail-fast -c cucumber.config.cjs",
"test:lint": "eslint . --fix",
"build": "tsc --build tsconfig.json && vite build",
@@ -42,6 +43,7 @@
},
"license": "Apache-2.0",
"dependencies": {
+ "kuzzle-types": "^1.0.0",
"ws": "8.18.3"
},
"devDependencies": {
diff --git a/src/types/ApiKey.ts b/src/types/ApiKey.ts
index 49bed04a..c950bb3a 100644
--- a/src/types/ApiKey.ts
+++ b/src/types/ApiKey.ts
@@ -1,36 +1,3 @@
-/**
- * ApiKey
- *
- * @see https://docs.kuzzle.io/core/2/guides/advanced/api-keys/
- */
-export type ApiKey = {
- /**
- * ApiKey unique ID
- */
- _id: string;
- /**
- * ApiKey content
- */
- _source: {
- /**
- * User kuid
- */
- userId: string;
- /**
- * Expiration date in Epoch-millis format (-1 if the token never expires)
- */
- expiresAt: number;
- /**
- * Original TTL in ms
- */
- ttl: number;
- /**
- * API key description
- */
- description: string;
- /**
- * Authentication token associated with this API key
- */
- token: string;
- };
-};
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { ApiKey } from "kuzzle-types";
diff --git a/src/types/ArgsDefault.ts b/src/types/ArgsDefault.ts
index 0b5c6527..ab92801e 100644
--- a/src/types/ArgsDefault.ts
+++ b/src/types/ArgsDefault.ts
@@ -1,10 +1,3 @@
-/**
- * Generic API action arguments
- */
-export interface ArgsDefault {
- queuable?: boolean;
-
- timeout?: number;
-
- [name: string]: any;
-}
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { ArgsDefault } from "kuzzle-types";
diff --git a/src/types/BaseRequest.ts b/src/types/BaseRequest.ts
index 419a46f9..f2718217 100644
--- a/src/types/BaseRequest.ts
+++ b/src/types/BaseRequest.ts
@@ -1,9 +1,3 @@
-import { JSONObject } from "./JSONObject";
-
-export interface BaseRequest extends JSONObject {
- controller: string;
-
- action: string;
-
- body?: JSONObject;
-}
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { BaseRequest } from "kuzzle-types";
diff --git a/src/types/Document.ts b/src/types/Document.ts
index 339bc939..fe344468 100644
--- a/src/types/Document.ts
+++ b/src/types/Document.ts
@@ -1,23 +1,9 @@
-import { JSONObject } from "./JSONObject";
-import { KDocumentKuzzleInfo } from "./KDocument";
+import type { DocumentContent, JSONObject } from "kuzzle-types";
-/**
- * Kuzzle metadata
- *
- * @deprecated Use "KDocumentKuzzleInfo"
- */
-export interface DocumentMetadata {
- _kuzzle_info?: Partial;
-}
-
-/**
- * Represents the `_source` property of the document
- *
- * @deprecated Create an interface extending "KDocumentContent"
- */
-export interface DocumentContent extends DocumentMetadata {
- [key: string]: JSONObject | any;
-}
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). `Document` stays here: it is a class, a runtime
+// value, which the types-only kuzzle-types declares as an interface.
+export type { DocumentContent, DocumentMetadata } from "kuzzle-types";
/**
* Kuzzle document
diff --git a/src/types/HttpRoutes.ts b/src/types/HttpRoutes.ts
index 0ccb3713..53743cbb 100644
--- a/src/types/HttpRoutes.ts
+++ b/src/types/HttpRoutes.ts
@@ -1,39 +1,3 @@
-/**
- * HTTP routes definition format
- *
- * @see https://docs.kuzzle.io/core/2/guides/develop-on-kuzzle/api-controllers/#http-routes
- *
- * @example
- * {
- * : {
- * : { verb: , url: }
- * }
- * }
- *
- * {
- * 'my-plugin/my-controller': {
- * action: { verb: 'GET', url: '/some/url' },
- * action2: { verb: 'GET', url: '/some/url/with/:parameter' }
- * }
- * }
- */
-export type HttpRoutes = {
- /**
- * Controller name
- */
- [controller: string]: {
- /**
- * Action name
- */
- [action: string]: {
- /**
- * HTTP verb
- */
- verb: string;
- /**
- * URL
- */
- url: string;
- };
- };
-};
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { HttpRoutes } from "kuzzle-types";
diff --git a/src/types/JSONObject.ts b/src/types/JSONObject.ts
index f87e9d53..5207b9a9 100644
--- a/src/types/JSONObject.ts
+++ b/src/types/JSONObject.ts
@@ -1,4 +1,3 @@
-/**
- * An interface representing an object with string key and any value
- */
-export type JSONObject = Record;
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { JSONObject } from "kuzzle-types";
diff --git a/src/types/KDocument.ts b/src/types/KDocument.ts
index 86236094..002c923e 100644
--- a/src/types/KDocument.ts
+++ b/src/types/KDocument.ts
@@ -1,83 +1,9 @@
-import { JSONObject } from "./JSONObject";
-
-/**
- * Represents Kuzzle Metadata.
- */
-export interface KDocumentKuzzleInfo {
- /**
- * Kuid of the user who created the document
- */
- author: string;
-
- /**
- * Creation date in micro-timestamp
- */
- createdAt: number;
-
- /**
- * Kuid of the user who last updated the document
- */
- updater: string | null;
-
- /**
- * Update date in micro-timestamp
- */
- updatedAt: number | null;
-}
-
-/**
- * Base interface for a Kuzzle document content
- */
-export interface KDocumentContent {
- _kuzzle_info?: KDocumentKuzzleInfo;
-}
-
-/**
- * Generic kuzzle document content
- */
-export interface KDocumentContentGeneric extends KDocumentContent, JSONObject {}
-
-/**
- * Represents a Kuzzle document
- *
- * Type argument represents the document content in the "_source" property
- */
-export interface KDocument {
- /**
- * Unique ID
- */
- _id: string;
-
- /**
- * Content
- */
- _source: TKDocumentContent;
-
- created?: boolean;
-
- _version?: number;
-}
-
-/**
- * Represents a Kuzzle document retrieved from search
- */
-export interface KHit<
- TKDocumentContent extends KDocumentContent,
-> extends KDocument {
- /**
- * Elasticsearch relevance score
- */
- _score: number;
-
- /**
- * Document index
- * Present only in the case of a multi search
- */
- index?: string;
-
- /**
- * Document collection
- * Present only in the case of a multi search
- */
- collection?: string;
-}
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type {
+ KDocumentKuzzleInfo,
+ KDocumentContent,
+ KDocumentContentGeneric,
+ KDocument,
+ KHit,
+} from "kuzzle-types";
diff --git a/src/types/Mappings.ts b/src/types/Mappings.ts
index 61193788..d7d1615e 100644
--- a/src/types/Mappings.ts
+++ b/src/types/Mappings.ts
@@ -1,65 +1,3 @@
-import { JSONObject } from "./JSONObject";
-
-type PropertyObject = {
- properties?: MappingsProperties;
-};
-
-type PropertyDynamic = {
- /**
- * Dynamic mapping policy
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#mappings-dynamic-policy
- */
- dynamic?: "true" | "false" | "strict";
-};
-
-type PropertyType = {
- [name: string]: { type?: string } | PropertyObject | JSONObject;
-};
-
-export type MappingsProperties =
- | PropertyObject
- | PropertyDynamic
- | PropertyType;
-
-/**
- * Collection mappings definition
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#collection-mappings
- *
- * @example
- * ```
- * {
- * properties: {
- * name: { type: 'keyword' },
- * address: {
- * properties: {
- * zipcode: { type: 'integer' }
- * }
- * }
- * }
- * }
- * ```
- */
-export type CollectionMappings = {
- /**
- * Collection metadata
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#mappings-metadata
- */
- _meta?: JSONObject;
-
- /**
- * Properties types definition
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#mappings-properties
- */
- properties?: MappingsProperties;
-
- /**
- * Dynamic mapping policy
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#mappings-dynamic-polic
- */
- dynamic?: "true" | "false" | "strict";
-};
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { MappingsProperties, CollectionMappings } from "kuzzle-types";
diff --git a/src/types/Notification.ts b/src/types/Notification.ts
index c45c6139..9dadffe6 100644
--- a/src/types/Notification.ts
+++ b/src/types/Notification.ts
@@ -1,125 +1,10 @@
-import { JSONObject } from "./JSONObject";
-import { KDocumentContentGeneric } from ".";
-
-/**
- * Enum for notification types
- */
-export type NotificationType = "document" | "user" | "TokenExpired";
-
-export interface BaseNotification {
- /**
- * Notification type
- */
- type: NotificationType;
-
- /**
- * Controller that triggered the notification
- */
- controller: string;
- /**
- * Action that triggered the notification
- */
- action: string;
- /**
- * Event type according to API action
- */
- event: "write" | "delete" | "publish";
- /**
- * Index name
- */
- index: string;
- /**
- * Collection name
- */
- collection: string;
- /**
- * Network protocol used to trigger the notification
- */
- protocol: string;
- /**
- * Subscription channel identifier.
- * Can be used to link a notification to its corresponding subscription
- */
- room: string;
- /**
- * Timestamp of the event, in Epoch-millis format
- */
- timestamp: number;
- /**
- * Request volatile data
- * @see https://docs.kuzzle.io/core/2/guides/essentials/volatile-data/
- */
- volatile: JSONObject;
-}
-
-/**
- * Notification triggered by a document change.
- * (create, update, delete)
- */
-export interface DocumentNotification<
- TDocContent extends KDocumentContentGeneric = KDocumentContentGeneric,
-> extends BaseNotification {
- /**
- * Updated document that triggered the notification
- */
- result: {
- /**
- * The message or full document content.
- */
- _source: TDocContent;
- /**
- * Document unique ID.
- * `null` if the notification is from a real-time message.
- */
- _id: string | null;
- /**
- * List of fields that have been updated (only available on document partial updates).
- */
- _updatedFields?: string[];
- };
- /**
- * State of the document regarding the scope (`in` or `out`)
- */
- scope: "in" | "out";
-
- type: "document";
-}
-
-/**
- * Notification triggered by an user joining or leaving a subscription room
- */
-export interface UserNotification extends BaseNotification {
- /**
- * Tell wether an user leave or join the subscription room (`in` or `out`)
- */
- user: "in" | "out";
-
- /**
- * Contains the actual number of users in the subscription room
- */
- result: {
- /**
- * Updated users count sharing the same subscription room
- */
- count: number;
- };
-
- type: "user";
-}
-
-export interface ServerNotification extends BaseNotification {
- /**
- * Server message explaining why this notifications has been triggered.
- */
- message: string;
-
- type: "TokenExpired";
-}
-
-/**
- * Real-time notifications sent by Kuzzle.
- */
-export type Notification =
- | DocumentNotification
- | UserNotification
- | ServerNotification;
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type {
+ NotificationType,
+ BaseNotification,
+ DocumentNotification,
+ UserNotification,
+ ServerNotification,
+ Notification,
+} from "kuzzle-types";
diff --git a/src/types/ProfilePolicy.ts b/src/types/ProfilePolicy.ts
index 876cd643..211e39c0 100644
--- a/src/types/ProfilePolicy.ts
+++ b/src/types/ProfilePolicy.ts
@@ -1,42 +1,3 @@
-/**
- * A profile policy is composed of a roleId to define API rights
- * and an optional array of restrictions on index and collections
- *
- * @example
- * {
- * "roleId": "editor",
- * "restrictedTo": {
- * "index": "blog",
- * "collections": [
- * "articles"
- * ]
- * }
- * }
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/permissions/#policies
- */
-export type ProfilePolicy = {
- /**
- * Role unique ID used by this policy
- */
- roleId: string;
-
- /**
- * Optional array of restrictions on which the rights are gonne be applied
- */
- restrictedTo?: [
- {
- /**
- * Index name.
- * Rights will only be applied on this index.
- */
- index: string;
-
- /**
- * Collection names.
- * Rights will only be applied on those collections.
- */
- collections?: Array;
- },
- ];
-};
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { ProfilePolicy } from "kuzzle-types";
diff --git a/src/types/RequestPayload.ts b/src/types/RequestPayload.ts
index 7efe9065..bc004db5 100644
--- a/src/types/RequestPayload.ts
+++ b/src/types/RequestPayload.ts
@@ -1,55 +1,3 @@
-import { JSONObject } from "./JSONObject";
-
-/**
- * Kuzzle API request payload
- *
- * @see https://docs.kuzzle.io/core/2/api/payloads/request
- */
-export interface RequestPayload {
- /**
- * API controller name
- */
- controller: string;
-
- /**
- * API action name
- */
- action: string;
-
- /**
- * Index name
- */
- index?: string;
-
- /**
- * Collection name
- */
- collection?: string;
-
- /**
- * Document unique identifier
- */
- _id?: string;
-
- /**
- * Authentication token
- */
- jwt?: string;
-
- /**
- * Volatile data
- */
- volatile?: JSONObject;
-
- /**
- * Request body
- */
- body?: JSONObject;
-
- /**
- * Request unique identifier
- */
- requestId?: string;
-
- [key: string]: any;
-}
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { RequestPayload } from "kuzzle-types";
diff --git a/src/types/ResponsePayload.ts b/src/types/ResponsePayload.ts
index f216fd48..7df186bf 100644
--- a/src/types/ResponsePayload.ts
+++ b/src/types/ResponsePayload.ts
@@ -1,103 +1,3 @@
-import { JSONObject } from "./JSONObject";
-
-/**
- * Kuzzle API response payload
- *
- * @see https://docs.kuzzle.io/core/2/api/payloads/response
- */
-export interface ResponsePayload {
- /**
- * API controller name
- */
- controller: string;
-
- /**
- * API action name
- */
- action: string;
-
- /**
- * Index name
- */
- index?: string;
-
- /**
- * Collection name
- */
- collection?: string;
-
- /**
- * Document unique identifier
- */
- _id?: string;
-
- /**
- * Array of deprecation warnings (hidden if NODE_ENV=production)
- */
- deprecations?: Array<{
- /**
- * Deprecation description
- */
- message: string;
-
- /**
- * Deprecated since this version
- */
- version: string;
- }>;
-
- /**
- * API error
- */
- error?: {
- /**
- * Error human readable identifier
- */
- id: string;
-
- /**
- * Error identifier
- */
- code: number;
-
- /**
- * Error message
- */
- message: string;
-
- /**
- * HTTP status error code
- */
- status: number;
-
- /**
- * Error stacktrace (only if NODE_ENV=development)
- */
- stack?: string;
- };
-
- /**
- * Request unique identifier
- */
- requestId: string;
-
- /**
- * API action result
- */
- result: TResult;
-
- /**
- * HTTP status code
- */
- status: number;
-
- /**
- * Volatile data
- */
- volatile?: JSONObject;
-
- /**
- * Room unique identifier
- */
- room?: string;
-}
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { ResponsePayload } from "kuzzle-types";
diff --git a/src/types/RoleRightsDefinition.ts b/src/types/RoleRightsDefinition.ts
index 7b46de6f..550bdd4b 100644
--- a/src/types/RoleRightsDefinition.ts
+++ b/src/types/RoleRightsDefinition.ts
@@ -1,36 +1,3 @@
-/**
- * Role list of rights definition for controllers and actions.
- *
- * @example
- *
- * {
- * auth: {
- * actions: {
- * getCurrentUser: true,
- * getMyCredentials: true,
- * getMyRights: true,
- * logout: true
- * }
- * },
- * realtime: {
- * actions: {
- * "*": true
- * }
- * }
- * }
- *
- * @see https://docs.kuzzle.io/core/2/guides/main-concepts/permissions/#roles
- */
-export type RoleRightsDefinition = {
- /**
- * API controller name
- */
- [controller: string]: {
- actions: {
- /**
- * API action name
- */
- [action: string]: boolean;
- };
- };
-};
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type { RoleRightsDefinition } from "kuzzle-types";
diff --git a/src/types/mRequests.ts b/src/types/mRequests.ts
index ed21f3d9..0bf6edc2 100644
--- a/src/types/mRequests.ts
+++ b/src/types/mRequests.ts
@@ -1,53 +1,10 @@
-import { KDocumentContentGeneric } from "./KDocument";
-
-export type mCreateRequest =
- Array<{
- /**
- * Document unique identifier
- */
- _id?: string;
-
- /**
- * Document content
- */
- body: Partial;
- }>;
-
-export type mCreateOrReplaceRequest<
- TKDocumentContent extends KDocumentContentGeneric,
-> = Array<{
- /**
- * Document unique identifier
- */
- _id: string;
-
- /**
- * Document content
- */
- body: Partial;
-}>;
-
-export type mReplaceRequest =
- mCreateOrReplaceRequest;
-export type mUpdateRequest =
- mCreateOrReplaceRequest;
-
-export type mUpsertRequest =
- Array<{
- /**
- * Document unique identifier
- */
- _id: string;
-
- /**
- * Document partial changes
- */
- changes: Partial;
-
- /**
- * Document fields to add to the "update" part if the document is created
- */
- default?: Partial;
- }>;
-
-export type mDeleteRequest = string[];
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type {
+ mCreateRequest,
+ mCreateOrReplaceRequest,
+ mReplaceRequest,
+ mUpdateRequest,
+ mUpsertRequest,
+ mDeleteRequest,
+} from "kuzzle-types";
diff --git a/src/types/mResponses.ts b/src/types/mResponses.ts
index 3384bc98..cfa3acc1 100644
--- a/src/types/mResponses.ts
+++ b/src/types/mResponses.ts
@@ -1,104 +1,10 @@
-import { JSONObject } from "./JSONObject";
-
-type mResponseErrors = Array<{
- /**
- * Original document that caused the error
- */
- document: {
- _id: string;
- body: JSONObject;
- };
-
- /**
- * HTTP error status code
- */
- status: number;
-
- /**
- * Human readable reason
- */
- reason: string;
-}>;
-
-export type mCreateResponse = {
- /**
- * Array of succeeded operations
- */
- successes: Array<{
- /**
- * Document unique identifier
- */
- _id: string;
-
- /**
- * Document content
- */
- _source: JSONObject;
-
- /**
- * Document version number
- */
- _version: number;
-
- /**
- * `true` if document is created
- */
- created: boolean;
- }>;
-
- /**
- * Arrays of errored operations
- */
- errors: mResponseErrors;
-};
-
-export type mCreateOrReplaceResponse = mCreateResponse;
-export type mUpsertResponse = mCreateResponse;
-
-export type mReplaceResponse = {
- /**
- * Array of succeeded operations
- */
- successes: Array<{
- /**
- * Document unique identifier
- */
- _id: string;
-
- /**
- * Document content
- */
- _source: JSONObject;
-
- /**
- * Document version number
- */
- _version: number;
- }>;
-
- /**
- * Arrays of errored operations
- */
- errors: mResponseErrors;
-};
-
-export type mUpdateResponse = mReplaceResponse;
-
-export type mDeleteResponse = {
- /**
- * IDs of deleted documents
- */
- successes: string[];
-
- errors: Array<{
- /**
- * Document unique identifier
- */
- _id: string;
-
- /**
- * Human readable reason
- */
- reason: string;
- }>;
-};
+// Moved to kuzzle-types, the API contract shared by kuzzle and kuzzle-sdk
+// (Kuzzle ADR-0002 step 03). Kept so that imports of this path still work.
+export type {
+ mCreateResponse,
+ mCreateOrReplaceResponse,
+ mUpsertResponse,
+ mReplaceResponse,
+ mUpdateResponse,
+ mDeleteResponse,
+} from "kuzzle-types";
diff --git a/test/types/kuzzle-types.ts b/test/types/kuzzle-types.ts
new file mode 100644
index 00000000..d95e3057
--- /dev/null
+++ b/test/types/kuzzle-types.ts
@@ -0,0 +1,87 @@
+// The API contract types moved to kuzzle-types (Kuzzle ADR-0002 step 03): the
+// SDK must still export every one of them, under the same name, as exactly
+// kuzzle-types' type — kuzzle-types itself asserts those are the types
+// kuzzle-sdk 7.17.1 exported. `Document` is the exception: the SDK keeps its
+// class, whose instances kuzzle-types' interface describes.
+// Type-checked by `npm run test:types` (after `npm run build`); nothing here runs.
+import type * as Types from "kuzzle-types";
+
+// The built declarations — what users get — not the non-strict sources.
+import type * as Sdk from "../../out";
+
+/** `true` only if A and B are identical types (not merely assignable). */
+type Equals =
+ (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2
+ ? true
+ : false;
+
+/** Assignable both ways. */
+type Mutual = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
+
+function assert(): T | void {}
+
+type Content = Types.KDocumentContent & { name: string };
+
+// src/types/ApiKey.ts
+assert>();
+// src/types/ArgsDefault.ts
+assert>();
+// src/types/BaseRequest.ts
+assert>();
+// src/types/Document.ts
+assert>();
+assert>();
+// src/types/HttpRoutes.ts
+assert>();
+// src/types/JSONObject.ts
+assert>();
+// src/types/KDocument.ts
+assert>();
+assert>();
+assert>();
+assert, Types.KDocument>>();
+assert, Types.KHit>>();
+// src/types/Mappings.ts
+assert>();
+assert>();
+// src/types/Notification.ts
+assert>();
+assert>();
+assert<
+ Equals, Types.DocumentNotification>
+>();
+assert>();
+assert>();
+assert>();
+// src/types/ProfilePolicy.ts
+assert>();
+// src/types/RequestPayload.ts
+assert>();
+// src/types/ResponsePayload.ts
+assert, Types.ResponsePayload>>();
+// src/types/RoleRightsDefinition.ts
+assert>();
+// src/types/mRequests.ts
+assert, Types.mCreateRequest>>();
+assert<
+ Equals<
+ Sdk.mCreateOrReplaceRequest,
+ Types.mCreateOrReplaceRequest
+ >
+>();
+assert, Types.mReplaceRequest>>();
+assert, Types.mUpdateRequest>>();
+assert, Types.mUpsertRequest>>();
+assert>();
+// src/types/mResponses.ts
+assert>();
+assert>();
+assert>();
+assert>();
+assert>();
+assert>();
+
+// src/types/Document.ts — the class stays in the SDK, a runtime value.
+assert>();
+assert>();
+export const document: typeof Sdk.Document = {} as typeof Sdk.Document;
diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json
new file mode 100644
index 00000000..2986085b
--- /dev/null
+++ b/test/types/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true
+ },
+ "include": ["./**/*.ts"]
+}