Aller au contenu

NOUVEAUIshedDB 1.0 : venez de MongoDB avec vos données, copie vérifiée document par document. En savoir plus →

La bibliothèque Node.js

@ished/client parle à IshedDB depuis vos serveurs Node.js (version 22 ou plus récente). Elle s'écrit comme le pilote MongoDB pour Node.js : le code qui l'utilise change à peine. Elle n'a aucune dépendance. Installation : voir Premiers pas.

Dans les exemples de cette page, client est la connexion de la première section et db une base : const db = client.db('Clinique'). Chaque exemple est exécuté contre un vrai serveur IshedDB par nos tests.

Se connecter

import { connect } from '@ished/client';

const client = connect({
  url: process.env.ISHED_URL, // https://… (http seulement vers votre propre machine)
  key: process.env.ISHED_KEY, // ished_sk_… : une clé du projet (page Accès)
  cluster: 'Cluster0', // facultatif : sans nom, le premier cluster du projet
  timeoutMs: 30_000, // facultatif : la durée maximale d'une requête
});
const db = client.db('Clinique'); // ou client.base('Clinique')

connect ne fait aucune requête : la première part avec le premier appel. Une clé ne doit jamais aller dans un navigateur ni dans une application mobile : appelez IshedDB depuis vos serveurs.

Les valeurs

Un document est un objet JavaScript. Les dates (Date), les entiers au-delà de 2^53 (BigInt) et les octets (Buffer, Uint8Array) gardent leur type à l'aller comme au retour. L'identifiant _id est un texte : donné, il est gardé ; absent, IshedDB en fabrique un.

const db = client.db('Clinique');
const r = await db.collection('dossiers').insertOne({ _id: 'd-001', ouvert: new Date('2026-11-12T08:00:00Z'), numero: 9007199254740993n, scan: Buffer.from('PDF…') });
const d = await db.collection('dossiers').findOne({ _id: r.insertedId });
console.log(d.ouvert instanceof Date, typeof d.numero, Buffer.isBuffer(d.scan)); // true bigint true

Bases et collections

AppelCe qu'il fait
client.listDatabases()les bases du cluster : { databases: [{ name, sizeOnDisk, empty }], totalSize } (une clé limitée : les siennes)
db.listCollections().toArray()les collections et les vues : name, type (collection ou view), options
db.collection(nom)une collection (créée avec son premier document)
db.createCollection(nom, options)une collection vide, avec son validator ; ou une vue avec viewOn et pipeline
db.dropCollection(nom)supprime la collection et ses documents, ou la vue
db.command({ collMod, … })change une vue ou les règles de validation d'une collection

Lire

const patients = client.db('Clinique').collection('patients');
await patients.insertMany([
  { nom: 'Awa Diop', ville: 'Dakar', âge: 34, tags: ['cardio'] },
  { nom: 'Ibrahima Sow', ville: 'Dakar', âge: 61, tags: ['diabète', 'cardio'] },
]);

const un = await patients.findOne({ nom: 'Awa Diop' });
const curseur = patients.find({ ville: 'Dakar', âge: { $gte: 30 } }, { projection: { nom: 1, _id: 0 } }).sort({ âge: -1 }).limit(10);
for await (const p of curseur) console.log(p.nom); // lus 1000 par 1000, au fil de la boucle
console.log(await patients.countDocuments({ tags: 'cardio' }), await patients.distinct('ville'));
console.log(un.âge);
  • Filtres : égalité, $eq $ne $gt $gte $lt $lte $in $nin $exists $type $all $size $elemMatch $regex $mod $not $and $or $nor $expr, la recherche plein texte $text (avec un index texte) ; une valeur cherchée dans une liste la trouve, comme dans MongoDB.
  • Projection : les champs gardés ({ nom: 1 }) ou retirés ({ notes: 0 }, { _id: 0 }), pas les deux.
  • Curseurs : .sort(), .limit(), .skip(), .project(), .toArray() (100 000 documents au plus : au-delà, parcourez le curseur), .explain() (le plan suivi : index utilisé ou lecture complète).
  • estimatedDocumentCount(), countDocuments(filtre), distinct(champ, filtre).

Écrire

const patients = client.db('Clinique').collection('patients');

await patients.updateOne({ nom: 'Awa Diop' }, { $set: { 'adresse.ville': 'Dakar' }, $inc: { visites: 1 }, $push: { tags: 'suivi' } });
await patients.updateOne({ nom: 'Khady Ba' }, { $set: { ville: 'Ziguinchor' } }, { upsert: true });
await patients.updateMany({ ville: 'Dakar' }, { $currentDate: { vuLe: true } });
await patients.replaceOne({ nom: 'Ibrahima Sow' }, { nom: 'Ibrahima Sow', ville: 'Thiès', âge: 61 });
await patients.deleteOne({ nom: 'Khady Ba' });
const lot = await patients.bulkWrite([
  { insertOne: { document: { nom: 'Ndeye Diouf', ville: 'Kaolack' } } },
  { updateOne: { filter: { nom: 'Ndeye Diouf' }, update: { $set: { âge: 45 } } } },
]);
console.log(lot.insertedCount, lot.modifiedCount); // 1 1

Opérateurs de modification : $set $unset $inc $mul $min $max $rename $currentDate $setOnInsert $push $addToSet $pull $pullAll $pop. insertMany envoie 1000 documents par requête. bulkWrite s'arrête au premier refus, sauf avec { ordered: false } ; un refus lève une erreur dont details.writeErrors dit lesquels.

Index

const patients = client.db('Clinique').collection('patients');
await patients.createIndex({ ville: 1, âge: -1 });
await patients.createIndex({ notes: 'text' }, { default_language: 'french' });
console.log((await patients.indexes()).map((i) => i.name)); // [ '_id_', 'ville_1_âge_-1', 'notes_text' ]

const comptes = client.db('Clinique').collection('comptes');
await comptes.createIndex({ email: 1 }, { unique: true, name: 'email_unique' });

Composés, uniques, partiels (partialFilterExpression), texte (15 langues, sans tenir compte des accents ni des majuscules), « wildcard » ({ "$**": 1 }) et d'expiration (une définition avec expireAt). dropIndex(nom), dropIndexes().

Agrégations

const patients = client.db('Clinique').collection('patients');
const parVille = await patients.aggregate([
  { $match: { âge: { $gte: 18 } } },
  { $group: { _id: '$ville', n: { $sum: 1 }, âgeMoyen: { $avg: '$âge' } } },
  { $sort: { n: -1 } },
]).toArray();
console.log(parVille);

Les étapes de MongoDB : $match $project $addFields $set $unset $group $sort $limit $skip $count $unwind $lookup $facet $bucket $bucketAuto $sortByCount $replaceRoot $replaceWith $sample, avec leurs expressions. Les résultats se lisent 1000 par 1000.

Vues et validation

const db = client.db('Clinique');
await db.createCollection('patients_dakar', { viewOn: 'patients', pipeline: [{ $match: { ville: 'Dakar' } }] });
console.log(await db.collection('patients_dakar').countDocuments());
await db.createCollection('ordonnances', {
  validator: { $jsonSchema: { required: ['patient', 'date'], properties: { patient: { bsonType: 'string' } } } },
  validationAction: 'error',
});

Une vue se lit comme une collection, sans s'écrire. Un document qui ne suit pas le validateur est refusé (ou écrit avec un avertissement au journal, avec validationAction: 'warn').

Transactions

const db = client.db('Clinique');
const session = client.startSession();
await session.withTransaction(async (s) => {
  await db.collection('stocks').updateOne({ _id: 'insuline' }, { $inc: { reste: -1 } }, { session: s, upsert: true });
  await db.collection('sorties').insertOne({ produit: 'insuline', quand: new Date() }, { session: s });
});
await session.endSession();

Tout ou rien, sur plusieurs collections et plusieurs bases d'un cluster ; withTransaction recommence en cas de conflit. startTransaction, commitTransaction et abortTransaction existent aussi.

Le temps réel

const flux = db.collection('rendezvous').watch([{ $match: { operationType: 'insert' } }]);
for await (const changement of flux) {
  console.log(changement.fullDocument);
}

Comme les change streams de MongoDB : watch() sur une collection, une base (db.watch()) ou le cluster (client.watch()), avec un pipeline, resumeAfter (le _id d'un changement reçu, pour reprendre après une coupure ; les changements sont gardés 24 heures), fullDocument: 'updateLookup', et les documents d'avant et d'après un changement pour une collection créée avec changeStreamPreAndPostImages. flux.close() l'arrête.

La syntaxe propre à IshedDB

En plus des méthodes de MongoDB, chaque collection offre des requêtes en chaîne, à la manière de Firestore :

import { connect, increment, remove, serverTime } from '@ished/client';

const client = connect({ url: process.env.ISHED_URL, key: process.env.ISHED_KEY });
const rdv = client.db('Clinique').collection('rendezvous');

const ref = await rdv.add({ patient: 'awa', service: 'cardiologie', jour: new Date('2026-12-01') });
await rdv.doc(ref.id).update({ rappels: increment(1), note: remove(), vuLe: serverTime() });
const prochains = await rdv.where('service', '==', 'cardiologie').orderBy('jour').limit(20).get();
console.log(prochains.length, await rdv.where('patient', '==', 'awa').exists());
console.log(await rdv.countBy('service'));

where(champ, op, valeur) avec == != < <= > >= in not-in array-contains array-contains-any exists missing matches, orderBy, limit, offset, select, get(), first(), count(), exists(), countBy(champ), pages(), updateAll, updateFirst, deleteFirst, delete() ; doc(id).get(), create, set, update, delete, onSnapshot ; les valeurs spéciales increment, multiply, atLeast, atMost, remove, rename, serverTime, arrayUnion, arrayRemove, arrayAppend, arrayPop. Un document lu ainsi est { id, data, createTime, updateTime }.

Les définitions

Avec une clé d'administration, une collection reçoit sa définition : champs et types, champs obligatoires, valeurs par défaut, champs uniques, index, champs cachés (jamais renvoyés sans être demandés, et chaque révélation va au journal), horodatage automatique, expiration.

await client.db('Clinique').collection('comptes').define({
  fields: { email: { type: 'string', required: true, lowercase: true }, motDePasse: { type: 'string', hidden: true } },
  timestamps: true,
  unique: ['email'],
});

Les erreurs

Un refus lève une IshedError : code (invalid-argument, unauthenticated, permission-denied, not-found, already-exists, failed-precondition, resource-exhausted…), message, status (le statut HTTP), requestId (à donner à l'assistance) et parfois details (les champs refusés et pourquoi).

import { connect, IshedError } from '@ished/client';

const client = connect({ url: process.env.ISHED_URL, key: process.env.ISHED_KEY });
const comptes = client.db('Clinique').collection('patients');
try {
  await comptes.insertOne({ _id: 'p-1', nom: 'Awa' });
  await comptes.insertOne({ _id: 'p-1', nom: 'Awa encore' });
} catch (e) {
  if (e instanceof IshedError && e.code === 'already-exists') console.log('déjà là');
  else throw e;
}