@sinequa/atomic

Document upload

Containers of user-uploaded documents — their roles, their locks, and the delete call that reports per-id results.

A container is a collection of documents a user uploaded, which the platform then indexes so they turn up in search. The library covers managing containers and listing their contents; the upload itself is not part of these endpoints.

Concept — a container

A named collection with an owner, readers, and a document count. It is a unit of sharing: rights are held on the container, not on each file. currentUserRole tells you what the signed-in user may do with the one you are looking at.

Roles decide what you may render

ContainerRole is "creator" | "owner" | "reader", and it comes back on every container as currentUserRole — so an interface never has to guess:

RoleMay
creatorEverything, including deleting
ownerUpdate, manage documents
readerList and read

Listing and creating

List containers, create one, list its documents
src/containers.ts
import { createAtomicClient } from "@sinequa/atomic";
import type { Container } from "@sinequa/atomic";

const client = createAtomicClient({
  app: "<your-app>",
  backendUrl: "https://<your-sinequa-server>",
});

await client.initializeConfig();
await client.auth.login();

/** Paginated, so show the total rather than implying the page is everything. */
export async function list(page = 1) {
  const response = await client.api.containers.list({ page, pageSize: 20 });

  console.log(`${response.containers.length} of ${response.total}`);
  return response;
}

/** Owners and readers are principal ids — see Finding principals. */
export function create(name: string, owners: string[], readers: string[]) {
  return client.api.containers.create({
    name,
    description: "",
    owners,
    readers,
    tags: [],
  });
}

export async function documentsOf(container: Container) {
  const response = await client.api.containers.listDocuments(container.id, {
    page: 1,
    pageSize: 50,
  });

  for (const document of response.documents) {
    // Note what a container document does NOT carry: no size, no source, no
    // uploader. Only what the API returns is available.
    console.log(document.name, document.createdAt);
  }

  return response;
}

/** Render controls from the role the server reported, not from a guess. */
export function mayEdit(container: Container) {
  return container.currentUserRole === "creator" || container.currentUserRole === "owner";
}

Use Finding principals to pick owners and readers, rather than asking for identifiers.

Deleting is bulk-only, and reports per id

There is no single-container route. deleteContainers takes an array and answers with what succeeded and what did not:

partial success is the normal case
const { succeeded, failed } = await client.api.containers.delete([a.id, b.id]);

for (const { containerId, error } of failed) {
  console.error(`${containerId} was not deleted: ${error}`);
}

So deleting three containers can leave one behind, and the promise still resolves. Report the failures rather than assuming the operation was atomic.

Locks during indexing

An upload or an indexing job holds the container. While it does, updateContainer and deleteContainerDocuments throw an ApiError with status === 409:

a lock is a temporary state, not an error to surface raw
import { ApiError } from "@sinequa/atomic";

try {
  await client.api.containers.update(container.id, { name });
} catch (error) {
  if (error instanceof ApiError && error.status === 409) {
    notify("This container is being indexed. Try again in a moment.");
  } else {
    throw error;
  }
}

Pitfalls

What's next

On this page