npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@push.rocks/smartdata

v11.4.0

Published

An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.

Readme

@push.rocks/smartdata 🚀

npm version

The ultimate TypeScript-first MongoDB ODM — type-safe decorators, real-time change streams, Lucene-powered search, distributed leader election, and cursor streaming. Built for modern applications that demand performance, correctness, and developer experience.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

🌟 Why SmartData?

  • 🔒 100% Type-Safe — TC39 Stage 3 decorators, generic filters, and compile-time query validation
  • High Performance — Connection pooling, cursor streaming, and automatic indexing
  • 🔄 Real-time Ready — MongoDB Change Streams with RxJS for reactive applications
  • 🌍 Distributed Systems — Built-in leader election and task coordination via @push.rocks/taskbuffer
  • 🛡️ Security First$where injection prevention, operator allow-listing, and input sanitization
  • 🔎 Lucene Search — Full-text, wildcard, boolean, and range queries out of the box
  • 🎯 Great DX — Intuitive API, IntelliSense that just works, and lifecycle hooks

📦 Installation

pnpm add @push.rocks/smartdata

🚦 Requirements

  • Node.js >= 20.x
  • Deno >= 2.0 (for Deno projects)
  • MongoDB >= 5.0
  • TypeScript >= 5.2 (for TC39 decorator support)

Note: SmartData uses TC39 Stage 3 decorators (the standard). Make sure experimentalDecorators is not set in your tsconfig.json. Bun is not currently supported as it doesn't implement TC39 decorators yet.

🎯 Quick Start

1️⃣ Connect to Your Database

import { SmartdataDb } from '@push.rocks/smartdata';

const db = new SmartdataDb({
  mongoDbUrl: 'mongodb://localhost:27017/myapp',
  mongoDbName: 'myapp',
  mongoDbUser: 'username',
  mongoDbPass: 'password',
});

await db.init();
console.log(db.status); // 'connected'

2️⃣ Define Your Data Models

import {
  SmartDataDbDoc,
  SmartdataPersistenceError,
  type TSmartdataPersistenceErrorCode,
  Collection,
  compoundIndex,
  unI,
  svDb,
  index,
  searchable,
} from '@push.rocks/smartdata';

@compoundIndex({
  name: 'status_createdAt',
  key: { status: 1, createdAt: -1 },
})
@Collection(() => db, { collectionName: 'app_users' })
class User extends SmartDataDbDoc<User, User> {
  @unI()
  public id!: string;

  @svDb()
  @searchable()
  public username!: string;

  @svDb()
  @searchable()
  @index({ unique: false })
  public email!: string;

  @svDb()
  public status!: 'active' | 'inactive' | 'pending';

  @svDb()
  public tags!: string[];

  @svDb()
  public createdAt: Date = new Date();

  @svDb()
  public updatedAt: Date = new Date();

  @svDb()
  public revision = 0;

  @svDb()
  public pendingReason?: string;

  @svDb()
  public preferences!: Record<string, unknown>;

  constructor(username: string, email: string) {
    super();
    this.username = username;
    this.email = email;
  }
}

collectionName keeps a persisted collection stable when a model class is renamed. Within one SmartdataDb, different model constructors may bind the same collection only when their normalized persisted schemas are equivalent. Binding a divergent schema throws SmartdataPersistenceError with code === 'invalid_configuration'.

Model.getIndexInfo() returns a sanitized index inventory containing only the index name, ordered field paths and directions, uniqueness, sparsity, and optional TTL seconds. It does not expose the MongoDB collection or raw driver metadata.

3️⃣ CRUD Operations

// ✨ Create
const user = new User('johndoe', '[email protected]');
user.id = 'user-1';
user.status = 'active';
user.tags = ['developer', 'typescript'];
try {
  const insertResult = await User.insert(user);
  console.log(insertResult.insertedId);
} catch (error) {
  if (
    error instanceof SmartdataPersistenceError &&
    error.code === 'unique_conflict'
  ) {
    const code: TSmartdataPersistenceErrorCode = error.code;
    console.error(code, error.cause);
  } else {
    throw error;
  }
}

// 🔍 Read — fully type-safe filters
const foundUser = await User.getInstance({ username: 'johndoe' });
const activeUsers = await User.getInstances({ status: 'active' });
if (!foundUser) {
  throw new Error('User not found.');
}

// ✏️ Update
foundUser.email = '[email protected]';
await foundUser.save();

// 🗑️ Delete
await foundUser.delete();

Programmatic Collection Models

Use defineCollectionModel() when a model's persisted contract is assembled without field decorators. The collection name is mandatory, so renaming a constructor cannot silently move its data. Fields and stable named indexes are validated before any database work; index directions may be 1, -1, or 'text'.

import {
  SmartDataDbDoc,
  defineCollectionModel,
} from '@push.rocks/smartdata';

class Job extends SmartDataDbDoc<Job, Job> {
  public id!: string;
  public state!: 'pending' | 'active';
  public revision!: number;
}

defineCollectionModel(Job, () => db, {
  collectionName: 'jobs',
  persistedFields: ['id', 'state', 'revision'],
  identityFields: ['id'],
  indexes: [
    {
      name: 'job_id',
      key: { id: 1 },
      options: { unique: true },
    },
    {
      name: 'job_state_revision',
      key: { state: 1, revision: -1 },
    },
  ],
});

await Job.ensureInitialized();

Equivalent schemas may bind different constructors to the same (SmartdataDb, collectionName) pair. A divergent schema fails with SmartdataPersistenceError code invalid_configuration.

Every programmatic identityFields entry must also have an explicit, single-field ascending index with options.unique === true. SmartData rejects the model configuration synchronously when that matching index is absent. Programmatic identityFields and plain @unI() declarations accept only non-empty string values. A decorator model that owns an existing positive integer identity contract can opt in explicitly:

@unI({ valueType: 'positiveSafeInteger' })
public id!: number;

This opt-in accepts only finite integers from 1 through Number.MAX_SAFE_INTEGER. It rejects strings, zero, negative values, fractions, non-finite numbers, and unsafe integers before ordinary persistence, strict atomic identity filters, or instance-owned identity selectors reach MongoDB. SmartData keeps string and numeric identity domains distinct, and collection bindings with divergent identity value types are rejected.

Collection Initialization and Topology Inspection

Model.ensureInitialized() is the mutating path. It resolves the model's bound database, creates an absent collection, creates model-owned indexes, and verifies the installed index definitions. Use it when the application owns database bootstrap.

Topology inspection is read-only and does not resolve Model.collection, a manager or default manager, or the database resolver passed to @Collection(), @managed(), or defineCollectionModel(). It does not register a SmartdataCollection, create a namespace or index, or run collection initialization:

const expected = Job.getExpectedCollectionTopology();
const inspection = await Job.inspectCollectionTopology(db);

console.log(expected.collectionName, expected.indexes);
console.log(inspection.status, inspection.reasonCodes);

getExpectedCollectionTopology() is synchronous and pure. Its deeply frozen result contains the collection name and canonical _id_ plus model-owned indexes. Each index exposes only its stable name, ordered keys, text weights, unique, sparse, and TTL seconds. Deprecated background declarations are normalized away. Extended expected index options that cannot be compared safely are rejected as invalid_configuration.

inspectCollectionTopology(db) requires an explicitly supplied, connected SmartdataDb. It snapshots the active client, database, and lifecycle generation before issuing listCollections and listIndexes; a read failure or connection change throws a sanitized SmartdataPersistenceError without a raw driver error. The bounded result contains canonical expected and actual indexes plus reason codes, never collection metadata or driver result objects.

The inspection statuses are:

  • absent: the namespace does not exist.
  • compatible: an ordinary option-free collection has the canonical _id_ index and its installed indexes are a valid subset of the expected indexes.
  • exact: every expected index is installed and there are no extras.
  • divergent: the namespace is a view or special collection, an index is changed or unexpected, or metadata uses unsupported options such as partial filters, collation, hidden, wildcard, or storage-engine options.

Model.insert() accepts only a newly constructed instance of that model and returns MongoDB's typed InsertOneResult. On success, SmartData marks the instance as database-backed; inserting it again is rejected. The optional { session } argument accepts either a raw MongoDB session or the opaque SmartdataSession returned by SmartdataDb.createSession(). While either session is in a transaction, the instance remains creationStatus: 'new' because SmartData cannot observe whether the caller later commits or aborts.

Model.insertMany() inserts a non-empty batch of newly constructed instances with the same per-document validation and returns MongoDB's typed InsertManyResult. Each document inserts atomically; the batch as a whole is not isolated unless the caller supplies a transaction session. With ordered semantics (the default) MongoDB stops at the first failure; with { ordered: false } it attempts every document. Outside a transaction SmartData marks exactly the documents MongoDB reports as inserted database-backed — even when the batch fails part-way with a unique_conflict — so callers can compensate precisely. While the supplied session is in a transaction every instance remains creationStatus: 'new', for the same reason insert() leaves it unchanged. Repeating the same instance in one batch is rejected.

Atomic Ordinary-Model Writes

Use the model-owned atomic API for optimistic concurrency, counters, targeted absence, and insert-only defaults. Atomic filters and update paths name declared top-level persisted fields, whether declared with decorators or defineCollectionModel(). Decorator models may opt exact nested paths into atomic selectors and updates with @svDb({ atomicPaths: [...] }); every other dotted path and nested object selector remains rejected. An explicit { $eq: wholeObject } condition supports safe exact whole-object compare-and-set without interpreting nested keys as query operators. SmartData captures an inert snapshot before any asynchronous work; the value must be safe BSON without accessors or custom encoders and match the stored document exactly. Whole declared objects may still be replaced through $set. SmartData validates supported field operators and declared identity-field values, requires at least one equality anchor, and rejects empty updates, direct undefined, non-finite increments, reserved fields, serialized roots, and conflicting paths before calling MongoDB. Fields declared with plain @unI() or programmatic identityFields use non-empty string selector values; @unI({ valueType: 'positiveSafeInteger' }) fields require positive safe integers. Identity fields remain immutable; create their value with insert() or an upsert selector rather than an update operator. Ordinary unique indexes enforce database uniqueness without adding identity-field immutability or identity-value selector semantics.

const outcome = await User.atomicUpdate(
  { id: 'user-1', revision: 7 },
  {
    $set: {
      status: 'active',
      updatedAt: new Date(),
    },
    $unset: {
      pendingReason: true,
    },
    $inc: {
      revision: 1,
    },
  },
);

if (outcome.matchedCount !== 1) {
  throw new Error('The user changed concurrently.');
}

const objectCas = await User.atomicUpdate(
  {
    id: 'user-1',
    preferences: {
      $eq: persistedPreferences,
    },
  },
  {
    $set: {
      preferences: nextPreferences,
    },
  },
);

Declare only the exact nested paths the model owns:

@Collection(() => db, { collectionName: 'services' })
class Service extends SmartDataDbDoc<Service, Service> {
  @unI()
  public id!: string;

  @svDb({
    atomicPaths: [
      'reconciliationStatus',
      'imageRolloutStatus',
    ],
  })
  public data!: {
    reconciliationStatus: {
      status: string;
      generation: number;
    };
    imageRolloutStatus: {
      rolloutId: string;
      status: string;
    };
  };
}

const reconciliation = await Service.atomicUpdate(
  {
    id: 'service-1',
    'data.reconciliationStatus': {
      $eq: {
        status: 'pending',
        generation: 4,
      },
    },
  },
  {
    $set: {
      'data.reconciliationStatus': {
        status: 'ready',
        generation: 4,
      },
    },
  },
);

The authoritative result contains acknowledged, matchedCount, modifiedCount, and upsertedId. A successful no-op can therefore report one match and zero modifications, while an upsert reports its new identifier. Options expose only upsert and a caller-owned MongoDB session. SmartData maintains _updatedAt on every atomic write and _createdAt on upsert.

Use atomicFindOneAndUpdate() when the caller needs an atomic before or after image rather than a separate reload. The required returnDocument option is explicit, and the result contains a sanitized matched, upserted, or not_matched status. Every non-null returned document is hydrated as the model; MongoDB command metadata and driver result objects are never exposed.

const transition = await User.atomicFindOneAndUpdate(
  { id: 'user-1', revision: 7 },
  {
    $set: { status: 'active' },
    $inc: { revision: 1 },
  },
  { returnDocument: 'after' },
);

if (transition.status !== 'matched') {
  throw new Error('The user changed concurrently.');
}

console.log(transition.document.revision); // 8

The options object accepts only returnDocument, optional upsert, an optional declared-top-level-field sort, and an optional raw or SmartData-owned session. It does not accept projection, hint, array filters, arbitrary operators, or bulk behavior. The sort option orders the candidate documents before MongoDB selects the single document to update — the claim-oldest-work pattern — and callers wanting deterministic ordering should include a unique tiebreaker field. When an upsert inserts a document, returnDocument: 'before' reports upserted with a null document, while returnDocument: 'after' returns the hydrated inserted model. An upsert that matches an existing document reports matched. SmartData fails closed when MongoDB does not provide a recognized authoritative result shape.

Use atomicDelete() when deletion itself must be compare-and-set fenced. In addition to the ordinary strict-filter rules, the selector must contain equality anchors that fully pin one globally constraining key set: a declared @unI() or programmatic identityFields field on its own, or every field of one declared unqualified unique index. An index qualifies only when it declares no option other than unique and background and every key direction is 1 or -1; sparse, partial, TTL, and text unique indexes never qualify because they do not guarantee at most one equality match. Anchors may be spread across the top level and $and branches; equalities that appear only inside alternative $or branches are rejected, so deleteOne never chooses an arbitrary match.

const deletion = await User.atomicDelete({
  id: 'user-1',
  revision: 8,
});

if (deletion.deletedCount !== 1) {
  throw new Error('The user changed concurrently.');
}

The static operation returns acknowledged and deletedCount. It does not hydrate a model or run instance deletion hooks; use instance.delete() when those hooks are required. The optional { session } participates in a caller-owned transaction.

atomicUpdate(), atomicFindOneAndUpdate(), and atomicUpdateMany() also support single-element array operators on declared top-level array fields: $push appends one element (duplicates allowed), $addToSet adds one element unless an equal element already exists, and $pull removes every element equal to a scalar operand. $push and $addToSet elements pass the same inert-value validation as $set, so plain object elements are allowed while modifier documents ($each, $position, $slice, $sort) are rejected. $pull accepts only scalar operands — strings, finite numbers, booleans, null, dates, binary values, and other trusted BSON scalars — and rejects regular expressions, operator documents, arrays, and plain objects. Nested paths, serialized fields, and identity fields are rejected for all three operators.

await CacheEntry.atomicUpdate(
  { org: 'foss', key: 'bundle-1' },
  {
    $addToSet: { leaseIds: leaseId },
    $set: { state: 'ready' },
  },
);

await CacheEntry.atomicUpdate(
  { org: 'foss', key: 'bundle-1' },
  { $pull: { leaseIds: leaseId } },
);

Use atomicDeleteMany() and atomicUpdateMany() for plural writes behind the same strict declared-field validation. Because plural intent is explicit in these APIs, the filter does not need to pin a unique index, but it still requires at least one equality anchor — at the top level, inside an $and branch, or in every $or branch — so range-only and empty filters are rejected. Each matched document is deleted or updated atomically; the batch as a whole is not isolated unless the caller supplies a transaction session. atomicUpdateMany() cannot upsert and rejects $setOnInsert; it maintains _updatedAt on every matched document and returns acknowledged, matchedCount, and modifiedCount.

const purged = await Delivery.atomicDeleteMany({
  org: 'foss',
  webhookId: 'hook-1',
  state: { $in: ['pending', 'delivering'] },
});

const abandoned = await MaintenanceRun.atomicUpdateMany(
  { state: 'running' },
  { $set: { state: 'failed' } },
);

Use getGroupedTotals() for bounded server-side grouped counts and numeric sums — the quota-accounting shape — without raw aggregation access. It groups by one or two declared, non-serialized top-level fields, always returns a count per group, and requires an explicit limit (1..10000). The group stage is not sorted, so when more groups exist than the limit the returned subset is arbitrary; detect overflow with the limit-plus-one idiom. Documents missing a group field merge with explicit-null values into one group reported as null. An optional filter uses the ordinary read-filter semantics, and the options also accept an optional maxTimeMS and a caller-owned session. sums names declared, non-serialized fields that TypeScript types as numeric; that numeric restriction is type-level, so an untyped caller naming a non-numeric declared field receives 0 for it instead of an error. A total that is not a finite JavaScript number — a Decimal128 field, for example — fails closed with SmartdataPersistenceError code unsupported_operation, and exact-persistence models reject the API with that same code; unsupported option keys and out-of-range bounds throw invalid_argument.

const retained = await PackageVersion.getGroupedTotals({
  groupBy: ['owner'],
  sums: ['sizeBytes'],
  limit: maximumInventoryOwners + 1,
});

if (retained.length > maximumInventoryOwners) {
  throw new Error('Owner inventory exceeds the supported limit.');
}

Instance-level save(), delete(), and updateFromDb() target the document through its declared identity fields. On a model that declares none, the update branch of save(), plus delete() and updateFromDb(), fail closed with SmartdataPersistenceError code invalid_configuration instead of issuing an empty filter that would address an arbitrary document. Such models still insert through Model.insert(), Model.insertMany(), or a first save(), and perform every later read-modify-write through the static atomic APIs.

$setOnInsert and { upsert: true } support race-safe registration without resetting existing fields. Duplicate insert and upsert conflicts throw SmartdataPersistenceError with code === 'unique_conflict' and retain the MongoDB error as cause. Exact-persistence models reject this ordinary API and continue to use Model.exact.

🔥 Features

Exact Persistence

Use exact persistence for immutable records or state machines where the stored BSON shape is part of the application contract. SmartData owns the storage envelope, canonical BSON projection, reconciliation, compare-and-swap transitions, and revision-fenced deletion. The application supplies one strict synchronous assertion for its document body.

import {
  Collection,
  SmartDataDbDoc,
  TExact,
  exactPersistence,
  svDb,
  unI,
} from '@push.rocks/smartdata';

interface IAuditDelivery {
  id: string;
  data: {
    state: 'pending' | 'delivered';
    eventId: string;
    sinkId: string;
    nextAttemptAt: number;
  };
}

const assertAuditDelivery = (
  value: unknown,
): asserts value is IAuditDelivery => {
  if (
    typeof value === 'object' &&
    value !== null &&
    !Array.isArray(value)
  ) {
    const document = value as Record<string, unknown>;
    const data = document.data as Record<string, unknown> | undefined;
    if (
      Object.keys(document).every((key) => ['id', 'data'].includes(key)) &&
      typeof document.id === 'string' &&
      typeof data === 'object' &&
      data !== null &&
      !Array.isArray(data) &&
      Object.keys(data).every((key) =>
        ['state', 'eventId', 'sinkId', 'nextAttemptAt'].includes(key),
      ) &&
      typeof data.eventId === 'string' &&
      typeof data.sinkId === 'string' &&
      Number.isSafeInteger(data.nextAttemptAt) &&
      (data.state === 'pending' || data.state === 'delivered')
    ) {
      return;
    }
  }
  throw new Error('Invalid audit delivery document.');
};

@Collection(() => db)
@exactPersistence({
  assertDocument: assertAuditDelivery,
})
class AuditDelivery extends SmartDataDbDoc<AuditDelivery, IAuditDelivery> {
  declare static exact: TExact<AuditDelivery>;

  @unI() public id!: string;
  @svDb() public data!: IAuditDelivery['data'];
}

const inserted = await AuditDelivery.exact.insert({
  id: 'delivery-123',
  data: {
    state: 'pending',
    eventId: 'event-123',
    sinkId: 'lossless-object-storage',
    nextAttemptAt: Date.now(),
  },
});

const transitioned = await AuditDelivery.exact.transition({
  current: inserted.document,
  change: (delivery) => {
    delivery.data = {
      ...delivery.data,
      state: 'delivered',
    };
  },
});
if (transitioned.status !== 'transitioned') {
  throw new Error('The audit delivery changed concurrently.');
}

const querySignal = AbortSignal.timeout(5_000);
const pending = await AuditDelivery.exact.findStored({
  filter: { 'data.state': 'pending' },
  sort: { 'data.nextAttemptAt': 1 },
  signal: querySignal,
});

const sinkIds: string[] = await AuditDelivery.exact.distinct('data.sinkId');

const applicationBody = AuditDelivery.exact.toPersisted(transitioned.document);
const canonicalBodyBytes = AuditDelivery.exact.canonicalBytes(applicationBody);

const deleted = await AuditDelivery.exact.delete({
  current: transitioned.document,
});
if (deleted.status !== 'deleted') {
  throw new Error('The audit delivery changed concurrently.');
}

Exact-persistence models permit only default string-valued @unI() declarations; the presence of any @unI({ valueType: 'positiveSafeInteger' }) field is invalid configuration, even when another field is selected as reconcileBy. When the model has exactly one @unI() string field, SmartData infers it as the reconciliation identity. A model with no @unI() field is invalid. With multiple @unI() fields, pass reconcileBy explicitly; it must name one of those persisted string fields. Exact inserts report inserted, already_committed, or conflict. Transitions use SmartData-owned _smartdataRevision metadata and report transitioned or concurrent_change; transition callbacks must be synchronous. Initial and pre-existing revisionless documents receive their first revision on transition. Exact deletion accepts only { current } and synchronously validates and snapshots that stored document into inert BSON values before I/O. Accessors, proxy traps, and custom or inherited BSON encoders are rejected without being invoked. Malformed official BSON scalar internals fail with SmartdataExactPersistenceError code invalid_document before any write. The delete is atomically fenced by _id, reconciliation identity, and its current revision. Legacy revisionless documents use an explicit revision-absence fence. It reports { status: 'deleted', deletedCount: 1 } or { status: 'concurrent_change', deletedCount: 0 }.

Exact model instances do not support save() or delete(). Use Model.exact.insert(), findStoredOne(), findStored(), count(), distinct(), transition(), and the static Model.exact.delete() so validation, reconciliation, and concurrency guarantees stay intact. Exact delete never hydrates a model or invokes instance hooks, never upserts, and does not accept a caller-authored selector. SmartData rejects undeclared top-level fields and the reserved _id, _smartdataRevision, _createdAt, and _updatedAt fields. The assertion owns nested and cross-field domain rules and must be strict, synchronous, and free of mutations.

Every exact database operation accepts a SmartData-owned session through its options or a final { session } argument. Call Model.exact.prepare() for every participating model before any owned-session operation, including before opening a transaction; exact operations fail closed rather than running collection or index initialization under a session. Programmatic models reuse their verified stable named indexes, so exact initialization never adds a second unnamed identity index.

findStored() additionally accepts a caller-owned signal. SmartData checks an already-aborted signal before initialization or session acquisition and forwards the same signal to the MongoDB cursor, preserving signal.reason by identity. Use AbortSignal.timeout() as above, or compose caller deadlines and request cancellation with AbortSignal.any(). SmartData does not create or abort the controller. Cancellation applies only to the finite findStored() cursor query; it does not add cancellation to other exact APIs or interrupt collection/index initialization already in progress. Call prepare() first when initialization must be kept outside the query deadline. A query or canonicalization error takes precedence over a concurrent cursor-close error; after a successful query, a cursor-close error is returned. Abort-triggered and explicit cursor cleanup share one completion, so an explicit SmartData session is not reusable until the native cursor cleanup has settled.

Canonicalization sorts every plain-object level while preserving array positions and treating BSON values atomically. toPersisted() validates a stored envelope and returns an ordinary application object with only SmartData storage metadata removed; nested data properties named __proto__ and BSON scalar types remain intact. canonicalBytes() accepts only an application body, so signatures, hashes, and external sinks do not accidentally include storage metadata. Sort keys and distinct() fields accept type-checked dot paths through nested objects and array elements.

Contract violations and inconclusive writes throw SmartdataExactPersistenceError; inspect its exported TExactPersistenceErrorCode. In particular, code === 'ambiguous_write' means the write may have committed but SmartData could not prove the stored outcome during reconciliation. Do not retry it with a new reconciliation identity or treat it as a definite rollback. Keep the same identity and route the operation to explicit retry or operator reconciliation. For exact deletion, an unacknowledged or thrown write reports concurrent_change only when same-identity reconciliation still finds the same _id; absence or a replacement _id cannot prove which delete won and therefore remains ambiguous_write. code === 'unique_conflict' identifies a secondary unique-index conflict without leaking a raw MongoDB error.

🎯 Type-Safe Query Filters

SmartData provides a rich, type-safe filtering system supporting all MongoDB operators with full IntelliSense:

// Comparison operators
const adults = await User.getInstances({
  age: { $gte: 18, $lt: 65 },
});

// Array operators
const experts = await User.getInstances({
  tags: { $all: ['typescript', 'mongodb'] },
  skills: { $size: 5 },
});

// Logical operators
const complex = await Order.getInstances({
  $and: [
    { status: 'active' },
    { $or: [{ priority: 'high' }, { value: { $gte: 1000 } }] },
  ],
});

// Deep nested object queries
const users = await User.getInstances({
  profile: {
    settings: {
      notifications: { email: true },
    },
  },
});

// Dot notation
const sameUsers = await User.getInstances({
  'profile.settings.notifications.email': true,
});

// Regex patterns
const gmailUsers = await User.getInstances({
  email: { $regex: '@gmail\\.com$', $options: 'i' },
});

Security: The $where operator is automatically blocked to prevent NoSQL injection. Unknown operators trigger warnings.

🔎 Lucene-Powered Search

Mark fields with @searchable() to enable a built-in search engine with automatic compound text indexing:

@Collection(() => db)
class Product extends SmartDataDbDoc<Product, Product> {
  @unI() public id!: string;
  @svDb() @searchable() public name!: string;
  @svDb() @searchable() public description!: string;
  @svDb() @searchable() public category!: string;
  @svDb() public price!: number;
}

// Simple text search across all @searchable fields
const results = await Product.search('laptop');

// Field-specific search
const electronics = await Product.search('category:Electronics');

// Wildcard
const matches = await Product.search('Mac*');

// Boolean operators (AND, OR, NOT)
const query = await Product.search('laptop AND NOT gaming');

// Phrase search
const exact = await Product.search('"MacBook Pro"');

// Range queries
const midRange = await Product.search('price:[100 TO 500]');

// Combined with MongoDB filters and post-fetch validation
const affordable = await Product.search('laptop', {
  filter: { price: { $lte: 1500 } },
  validate: async (p) => p.price > 0,
});

📡 Real-Time Change Streams

Watch for database changes with RxJS subjects and EventEmitter support:

const watcher = await User.watch(
  { status: 'active' },
  {
    fullDocument: 'updateLookup',
    bufferTimeMs: 100, // optional: buffer changes via RxJS
  },
);

// RxJS subscription
watcher.changeSubject.subscribe((user) => {
  console.log('User changed:', user);
});

// Or EventEmitter style
watcher.on('change', (user) => {
  console.log('User changed:', user);
});

// Clean up
await watcher.close();

🔄 Cursor Streaming

Process large datasets without memory pressure:

const cursorOptions = {
  projection: { id: 1, username: 1, createdAt: 1 },
  sort: { createdAt: -1, id: -1 },
  batchSize: 100,
  limit: 1000,
  maxTimeMS: 10_000,
} as const;

// Iterate one-by-one
const callbackCursor = await User.getCursor(
  { status: 'active' },
  cursorOptions,
);
await callbackCursor.forEach(async (user) => {
  await processUser(user);
});

// Or use a separate cursor to collect into an array
const arrayCursor = await User.getCursor({ status: 'active' }, cursorOptions);
const users = await arrayCursor.toArray();

toArray() always closes its cursor. forEach() closes by default, including when hydration or the callback throws. next() returns null and closes by default at end-of-stream, and also closes before rethrowing a hydration error. A caller that stops a next() loop early must call close() in a finally block.

Structured batchSize and cursor/count limit values must be positive safe integers no greater than 10_000; maxTimeMS must be a positive safe integer no greater than 120_000. Invalid bounds, projections, or sort paths throw SmartdataPersistenceError with code === 'invalid_argument'. Projection and sort paths must name top-level declared persisted fields. Add a unique final sort field, such as id above, when equal preceding values require deterministic pagination. Cursor, count, existence, and single-document reads accept a caller-owned session where exposed by their options.

The raw cursor modifier option remains available for compatible callers, runs after the structured options, and is deprecated. New code should use the structured projection, sort, batch-size, limit, timeout, and session options. SmartData closes the original cursor if a legacy modifier throws or returns a different cursor.

🔐 Transactions

Use a SmartData-owned session when every operation in the transaction goes through finite SmartData model APIs. Initialize every participating model before opening the transaction:

await User.ensureInitialized();
const session = db.createSession();

try {
  await session.withTransaction(async (transaction) => {
    const sender = await User.getInstance(
      { id: 'user-1' },
      { session: transaction },
    );
    sender.balance -= 100;
    await User.atomicUpdate(
      { id: sender.id, revision: sender.revision },
      { $set: { balance: sender.balance }, $inc: { revision: 1 } },
      { session: transaction },
    );

    const receiver = await User.getInstance(
      { id: 'user-2' },
      { session: transaction },
    );
    receiver.balance += 100;
    await User.atomicUpdate(
      { id: receiver.id, revision: receiver.revision },
      { $set: { balance: receiver.balance }, $inc: { revision: 1 } },
      { session: transaction },
    );
  });
} finally {
  await session.close();
}

The driver retains control of TransientTransactionError callback retries and UnknownTransactionCommitResult commit retries. After those retries finish, an ordinary-model duplicate conflict rejects with SmartdataPersistenceError code unique_conflict and keeps the MongoDB error as cause. Callback-thrown SmartData and application errors preserve their identity.

Owned sessions support ordinary static inserts including insertMany(), single/multiple reads, counts, getGroupedTotals(), searches, atomic updates including find-one-and-update and atomicUpdateMany(), and atomic deletes including atomicDeleteMany(). They do not support cursor or paged APIs, change streams, or instance save()/delete() in this release. Use startSession() only when raw MongoDB operations or manual transaction control are required; that raw escape hatch keeps MongoDB's raw completion errors and uses endSession() for cleanup.

The same opaque session can coordinate exact persistence:

await Quota.exact.prepare();
await Receipt.exact.prepare();
const session = db.createSession();

try {
  await session.withTransaction(async (transaction) => {
    const quota = await Quota.exact.findStoredOne(
      { id: 'tenant-a:2026-07-30T14:00Z' },
      { session: transaction },
    );
    await Quota.exact.transition({
      current: quota!,
      change: (entry) => {
        entry.count += 1;
      },
    }, { session: transaction });
    await Receipt.exact.insert({
      id: 'request-123',
      quotaId: quota!.id,
    }, { session: transaction });
  });
} finally {
  await session.close();
}

The transaction callback may run more than once. Keep irreversible side effects outside it, do not run session operations in parallel, and treat exact result objects as transaction-local until commit. SmartData validates database ownership and lifecycle, uses the same session for reconciliation reads, and preserves transient MongoDB transaction errors for driver retry handling.

💾 EasyStore — Type-Safe Key-Value Storage

Built on top of SmartData collections, EasyStore provides simple key-value persistence:

interface AppConfig {
  apiKey: string;
  features: { darkMode: boolean; notifications: boolean };
  limits: { maxUsers: number };
}

const config = await db.createEasyStore<AppConfig>('app-config');

// Write
await config.writeKey('features', { darkMode: true, notifications: false });

// Read
const features = await config.readKey('features');
if (features) {
  // TypeScript knows: features.darkMode is boolean ✅
  console.log(features.darkMode);
}

// Read all
const all = await config.readAll();

// Write multiple keys
await config.writeAll({ apiKey: 'new-key', limits: { maxUsers: 500 } });

// Delete a key
await config.deleteKey('features');

// Wipe the store
await config.wipe();

🌐 Distributed Coordination

Built-in leader election using MongoDB for coordination, integrating with @push.rocks/taskbuffer:

import { SmartdataDistributedCoordinator } from '@push.rocks/smartdata';

const coordinator = new SmartdataDistributedCoordinator(db);

// Start coordination — automatic heartbeat and leader election
await coordinator.start();

// Fire distributed task requests
const result = await coordinator.fireDistributedTaskRequest({
  submitterId: 'instance-1',
  requestResponseId: 'unique-id',
  taskName: 'process-payments',
  taskVersion: '1.0.0',
  taskExecutionTime: Date.now(),
  taskExecutionTimeout: 30000,
  taskExecutionParallel: 1,
  status: 'requesting',
});

// Graceful shutdown with leadership handoff
await coordinator.stop();

🎨 Custom Serialization

Transform data on its way in and out of MongoDB:

@Collection(() => db)
class Doc extends SmartDataDbDoc<Doc, Doc> {
  @svDb({
    serialize: (set) => Array.from(set),
    deserialize: (arr) => new Set(arr),
  })
  public tags!: Set<string>;

  @svDb({
    serialize: (date) => date?.toISOString(),
    deserialize: (str) => (str ? new Date(str) : null),
  })
  public scheduledAt!: Date | null;
}

🫙 undefined Means Absent, null Means Null

SmartData distinguishes the two, so an optional field genuinely has three states:

@Collection(() => db)
class Doc extends SmartDataDbDoc<Doc, Doc> {
  @unI() public id!: string;
  @svDb() public note?: string;      // may be absent
  @svDb() public archivedAt!: Date | null; // may be null
}

const doc = new Doc();
doc.id = 'a';
doc.archivedAt = null;
await doc.save();
// stored: { id: 'a', archivedAt: null }  -- `note` is not stored at all
  • A saveable property that is undefined is omitted from the stored document. It is never written as null.
  • A saveable property that is explicitly null is stored as null.
  • Nested undefined object properties are dropped too. Array positions are preserved: [1, undefined, 3] persists as [1, null, 3], matching JSON.stringify.

An omitted property is not part of the $set document, so on update it leaves whatever is already stored alone. To actively clear a stored field, issue an explicit $unset:

const clearResult = await Doc.atomicUpdate(
  { id: 'a' },
  { $unset: { note: true } },
);
if (clearResult.matchedCount !== 1) {
  throw new Error('The document no longer exists.');
}

SmartData deliberately does not unset automatically: overriding createSavableObject() to drop a key is an established way of saying "this field is managed elsewhere" — distributed lease ids, generation counters and similar fields updated through atomic operators would be destroyed by an implicit $unset on every save.

Query filters are unaffected — SmartData deliberately does not enable the MongoDB driver's ignoreUndefined option, because that would also strip undefined values out of filters and silently widen which documents a query matches. A filter of { field: null } continues to match both stored null and absent fields, which is standard MongoDB behaviour.

Migrating from < 8.0.0: earlier versions stored undefined as null. Existing nulls are left untouched; only new writes change. Code that tests field === null to detect an absent value should test for both, or preferably use field == null, which covers null and undefined alike.

🧹 Cached Documents With TTL

Use SmartdataCachedDocument for cache-like documents that need creation, last-access, and absolute-expiration timestamps:

import {
  Collection,
  SmartdataCachedDocument,
  smartdataTtlValues,
  svDb,
  unI,
} from '@push.rocks/smartdata';

@Collection(() => db)
class CachedLookup extends SmartdataCachedDocument<CachedLookup> {
  @unI()
  public cacheKey!: string;

  @svDb()
  public payload!: string;
}

const lookup = new CachedLookup();
lookup.cacheKey = 'example';
lookup.payload = 'cached value';
lookup.setTTL(smartdataTtlValues.HOURS_24);
await lookup.save();

expiresAt is indexed with MongoDB TTL semantics using expireAfterSeconds: 0, so the document expires after the absolute date stored in expiresAt. MongoDB TTL cleanup is asynchronous and may lag behind the exact expiration time.

🎣 Lifecycle Hooks

Add custom logic before and after save/delete:

@Collection(() => db)
class Order extends SmartDataDbDoc<Order, Order> {
  @unI() public id!: string;
  @svDb() public items!: Array<{ product: string; quantity: number; price: number }>;
  @svDb() public totalAmount!: number;

  async beforeSave() {
    this.totalAmount = this.items.reduce((s, i) => s + i.price * i.quantity, 0);
  }

  async afterSave() {
    await notificationService.orderUpdated(this.id);
  }

  async beforeDelete() {
    if (this.totalAmount > 0) throw new Error('Cannot delete non-zero orders');
  }

  async afterDelete() {
    await cache.delete(`order:${this.id}`);
  }
}

🏗️ Indexing

@Collection(() => db)
class HighPerformanceDoc extends SmartDataDbDoc<HighPerformanceDoc, HighPerformanceDoc> {
  @unI()
  public id!: string; // Unique index

  @index()
  public userId!: string; // Regular index

  @index({ sparse: true })
  public deletedAt?: Date; // Sparse index — only indexes docs where field exists

  @index({ expireAfterSeconds: 86400 })
  public expiresAt!: Date; // TTL index — auto-expires 24h after this Date
}

🔧 Connection Options

const db = new SmartdataDb({
  mongoDbUrl: 'mongodb://localhost:27017',
  mongoDbName: 'myapp',
  mongoDbUser: 'admin',
  mongoDbPass: 's3cret',

  // Connection pool tuning (all optional)
  maxPoolSize: 100,           // Max connections (default: 100)
  maxIdleTimeMS: 300000,      // Close idle connections after 5min (default)
  serverSelectionTimeoutMS: 30000, // Timeout for server selection
  socketTimeoutMS: 30000,     // Socket timeout to prevent hung operations
});

🩺 Bounded Readiness Checks

checkReadiness() is the public, read-only health probe for a SmartdataDb. It reports ready only after a MongoDB { ping: 1 } round trip; consumers do not need to access the raw driver.

const abortController = new AbortController();
const readiness = await db.checkReadiness({
  timeoutMs: 1_500,
  signal: abortController.signal,
});

if (!readiness.ready) {
  console.warn(
    `Database unavailable: ${readiness.reason} after ${readiness.durationMs}ms`,
  );
} else {
  console.log(`Database ready after ${readiness.durationMs}ms`);
}

The discriminated result is either { ready: true, status: 'ready', durationMs } or { ready: false, status: 'unavailable', reason, durationMs }. Failure reasons are not_initialized, connecting, connection_failed, closing, closed, close_failed, aborted, timeout, and unavailable. Lifecycle state takes precedence over an already-aborted signal while the database is not connected, and a probe already in flight becomes unavailable if the connection lifecycle or underlying driver references change.

timeoutMs defaults to 2_000 and must be a positive safe integer no greater than 30_000. Invalid values reject with SmartdataPersistenceError code invalid_argument. Both this deadline and the optional AbortSignal are passed to the MongoDB command so they cancel the underlying work rather than only racing its promise. Operational failures are returned as the typed unavailable result without exposing a raw MongoDB response, error, or cause.

Completed init()close()init() sequences are supported. Starting init() or close() while another lifecycle operation is still active rejects with SmartdataPersistenceError code unsupported_operation.

🛠️ Bounded MongoDB Administration

createMongoAdministration() exposes the small set of privileged MongoDB operations that infrastructure owners need without exposing the native client or database. The facade is bound to its owning SmartdataDb, validates the currently active connection for every operation, and fails closed if that connection changes while an operation is in flight.

const expectedTopology = {
  expectedSetName: 'onebox-rs',
  expectedMemberHost: 'onebox-mongodb:27017',
  timeoutMs: 5_000,
};

const bootstrapDb = new SmartdataDb({
  mongoDbUrl: 'mongodb://<USERNAME>:<PASSWORD>@onebox-mongodb:27017/admin?authSource=admin&directConnection=true',
  mongoDbName: 'admin',
  mongoDbUser: adminUsername,
  mongoDbPass: adminPassword,
});
await bootstrapDb.init();
try {
  const bootstrapAdministration = bootstrapDb.createMongoAdministration();
  await bootstrapAdministration.initializeSingleMemberReplicaSet(expectedTopology);
} finally {
  await bootstrapDb.close();
}

After initialization, replace the direct bootstrap connection with a new SmartdataDb that names the replica set before proving transactions or managing database users:

const replicaSetDb = new SmartdataDb({
  mongoDbUrl: 'mongodb://<USERNAME>:<PASSWORD>@onebox-mongodb:27017/myapp?authSource=admin&replicaSet=onebox-rs',
  mongoDbName: 'myapp',
  mongoDbUser: adminUsername,
  mongoDbPass: adminPassword,
});
await replicaSetDb.init();
const replicaSetAdministration = replicaSetDb.createMongoAdministration();

const readiness = await replicaSetAdministration.checkTransactionReadiness({
  timeoutMs: 5_000,
});
if (!readiness.ready) {
  throw new Error(`MongoDB transactions are unavailable: ${readiness.reason}`);
}

await replicaSetAdministration.ensureReadWriteDatabaseUser({
  expectedDatabaseName: 'myapp',
  username: 'myapp-runtime',
  password: runtimeDatabasePassword,
  timeoutMs: 5_000,
});

const removal = await replicaSetAdministration.removeDatabaseUser({
  expectedDatabaseName: 'myapp',
  username: 'myapp-runtime',
  timeoutMs: 5_000,
});
console.log(removal.removed);

await replicaSetAdministration.dropDatabase({
  expectedDatabaseName: 'myapp',
  confirmationDatabaseName: 'myapp',
  timeoutMs: 5_000,
});

Replica-set inspection returns exact_primary or exact_non_primary only for the expected set name and one-member host; any other configured topology is a sanitized divergent result. Initialization mutates only an uninitialized replica set, returns already_configured for an already exact topology, and never reconfigures an existing topology. Transaction readiness creates a reserved capability collection, writes two uniquely identified probe documents in one transaction, aborts it, and reports ready only after proving that neither probe document remains.

Database-user operations require the expected database name to match the connected SmartData database exactly. ensureReadWriteDatabaseUser() creates or updates the named user and replaces its roles with the single database-scoped readWrite role. removeDatabaseUser() returns { removed: false } when the user is already absent. dropDatabase() requires expectedDatabaseName to match both the connected driver database and current SmartData descriptor, a separately supplied exact name confirmation, and a non-system target; it rejects the admin, config, and local databases. Every operation has a bounded deadline and optional AbortSignal, and operational failures do not expose raw MongoDB responses or errors.

These methods require a MongoDB credential with the corresponding administrative privileges. They are intended for infrastructure controllers and versioned migrations, not ordinary application persistence.

For isolated, disposable databases such as per-test namespaces, close() can remove the connected database before closing its client. The caller must retain the intended database name independently and supply it as an exact destructive fence:

const expectedDatabaseName = testDescriptor.mongoDbName;
const db = new SmartdataDb(testDescriptor);
await db.init();

// Run the isolated test workload.

await db.close({
  dropDatabase: {
    expectedDatabaseName,
  },
});

The expected name, the connected MongoDB database, and the current SmartData descriptor must all match exactly. Malformed options, lifecycle overlap, stale bindings, or name mismatches reject before any session, database, connection, status, or readiness state is changed. This option is intentionally destructive and must not be used for shared or production databases. Ordinary close() continues to preserve data.

📚 Decorators Reference

| Decorator | Target | Description | |-----------|--------|-------------| | @Collection(dbGetter, options?) | Class | Binds a document class to MongoDB with an optional explicit persisted collectionName | | @compoundIndex({ name, key, options? }) | Class | Declares an inherited named compound index with ordered 1 / -1 directions | | @exactPersistence(options) | Class | Enables exact persistence using one synchronous document assertion and an inferred or explicit reconciliation identity | | @managed(managerGetterOrOptions?, options?) | Class | Like @Collection but controlled by a manager instance and optionally bound to an explicit persisted collectionName | | @unI(options?) | Field | Marks as unique index + saveable; defaults to string identities and supports explicit valueType: 'positiveSafeInteger' for ordinary decorator models | | @svDb(options?) | Field | Marks a field as saveable, with optional serialize, deserialize, and exact nested atomicPaths | | @index(options?) | Field | Creates a regular MongoDB index | | @searchable() | Field | Enables Lucene-style text search on this field | | @globalSvDb() | Field | Marks field as globally saveable across all doc types |

📚 API Reference

Core Classes

| Class | Description | |-------|-------------| | SmartdataDb | Database connection, bounded readiness checks, session management, and EasyStore factory | | SmartdataMongoAdministration | Lifecycle-bound replica-set inspection/initialization, transaction capability proof, database-user reconciliation, and exactly confirmed database removal | | SmartdataSession | Opaque SmartData-owned ordinary/exact transaction session with withTransaction() and idempotent close() | | SmartDataDbDoc<T, TImpl> | Base class for all document models | | SmartdataCollection<T> | Underlying collection manager (usually accessed indirectly) | | SmartdataExactCollection<T, TPersisted> | Validated exact insert, query, reconciliation, CAS transition, and revision-fenced delete API exposed as Model.exact | | SmartdataExactPersistenceError | Exact-persistence contract error with typed codes including ambiguous_write and unique_conflict | | SmartdataPersistenceError | Ordinary-model persistence error with stable codes including unique_conflict | | SmartdataDbCursor<T> | Cursor for streaming large result sets | | SmartdataDbWatcher<T> | Change stream watcher with RxJS + EventEmitter | | SmartdataDistributedCoordinator | Leader election and distributed task coordination | | SmartdataCachedDocument<T> | Base class for cache documents with creation, access, and expiration timestamps | | EasyStore<T> | Type-safe key-value store backed by a collection |

Utilities

| Utility | Description | |---------|-------------| | defineCollectionModel(Model, dbOrGetter, config) | Bind a programmatically declared ordinary model with explicit fields and stable named indexes | | ICollectionModelConfig<T> | Programmatic collection name, persisted/searchable fields, identity fields with matching explicit ascending unique indexes, and index contract | | smartdataTtlValues | Common TTL durations in milliseconds for cache documents | | isMongoObjectId(value) | Runtime guard for exact stored-document _id validation | | TExactPersistedDocument<T> | Application-owned BSON document shape accepted by exact inserts | | TStoredDocument<T> | Exact stored shape including SmartData-owned _id and optional revision metadata | | TExact<Model> | Exact collection type inferred from a SmartData model's persisted document type | | TPersistedOf<Model> | Persisted document type extracted from a SmartData model | | TExactDocumentAssertion<T> | Synchronous assertion contract accepted by @exactPersistence() | | TExactIdentityKey<T> | Required non-nullable string keys eligible for reconcileBy | | TExactDocumentPath<T> | Bounded, type-checked dot paths through nested objects and array elements | | TExactDocumentPathValue<T, TPath> | Value type resolved from an exact-document dot path | | TExactDistinctValue<T> | Element type returned when distinct() targets an array field | | TExactDeleteResult | Exact CAS-delete result: { status: 'deleted', deletedCount: 1 } or { status: 'concurrent_change', deletedCount: 0 } | | IExactSessionOptions | Optional SmartData-owned session accepted by exact operations | | TSmartdataOrdinarySession | Compatibility type accepted by finite ordinary-model APIs: raw MongoDB ClientSession or owned SmartdataSession | | TExactPersistenceErrorCode | Typed exact-persistence error codes for explicit failure handling | | TSmartdataPersistenceErrorCode | Typed ordinary-persistence codes: invalid_argument, invalid_configuration, unique_conflict, and unsupported_operation | | TSmartdataAtomicFilter<T> | Strict ordinary-model selector for declared top-level and explicitly allowed nested paths | | ISmartdataAtomicUpdate<T> | Typed $set, $unset, numeric $inc, $setOnInsert, and single-element array $push/$addToSet/$pull operations | | ISmartdataAtomicUpdateOptions | Optional upsert and caller-owned session controls | | ISmartdataAtomicUpdateResult | Authoritative acknowledged, matched, modified, and upserted outcome | | TSmartdataAtomicReturnDocument | Required before or after return mode for atomic find-and-update operations | | ISmartdataAtomicFindOneAndUpdateOptions<TReturnDocument, TModel> | Required return mode plus optional upsert, declared-field sort, and caller-owned session controls | | TSmartdataAtomicFindOneAndUpdateResult<T, TReturnDocument> | Sanitized matched, upserted, or not-matched outcome with a hydrated non-null model document | | ISmartdataAtomicDeleteOptions | Optional caller-owned session control for globally constrained and plural deletes | | ISmartdataAtomicDeleteResult | Authoritative acknowledged and deleted outcome | | ISmartdataAtomicUpdateManyOptions | Optional caller-owned session control for plural updates | | ISmartdataAtomicUpdateManyResult | Authoritative acknowledged, matched, and modified plural outcome | | ISmartdataGroupedTotalsOptions<T> | Declared groupBy/sum fields, ordinary read filter, required group bound, timeout, and optional session | | ISmartdataGroupedTotalsRow<T> | One group's key values, count, and numeric sums | | ISmartdataCursorOptions<T> | Structured projection, sort, batch, limit, timeout, session, and deprecated modifier controls | | ISmartdataCountOptions | Bounded count limit, timeout, and optional session | | ISmartdataFindOneOptions<T> | Structured projection, timeout, and optional session for one-document reads | | ISmartdataReadinessOptions | Optional readiness timeoutMs deadline and AbortSignal | | TSmartdataReadinessResult | Discriminated ready/unavailable result returned by SmartdataDb.checkReadiness() | | TSmartdataReadinessFailureReason | Typed readiness failure reasons for lifecycle, cancellation, timeout, and availability failures | | ISmartdataMongoOperationOptions | Optional bounded deadline and AbortSignal shared by MongoDB administration operations | | ISmartdataMongoReplicaSetExpectation | Exact expected single-member replica-set topology with bounded operation controls | | ISmartdataMongoReplicaSetMember | Sanitized observed member host, health, and state | | TSmartdataMongoReplicaSetState | Exact-primary, exact-non-primary, divergent, disabled, uninitialized, or unavailable topology state | | ISmartdataMongoReplicaSetInspection | Sanitized replica-set topology inspection result | | ISmartdataMongoReplicaSetInitializationResult | Initiated or already-configured result with the latest sanitized topology | | TSmartdataMongoTransactionReadinessFailureReason | Typed cancellation, timeout, topology, availability, or cleanup failure reason | | TSmartdataMongoTransactionReadinessResult | Sanitized result from the two-write transaction-abort capability proof | | ISmartdataMongoDatabaseUserOptions | Fenced read/write database-user reconciliation options | | ISmartdataMongoDatabaseUserRemovalOptions | Fenced database-user removal options | | ISmartdataMongoDatabaseDropOptions | Exact connected-database and repeated-name confirmation fence for non-system database removal |

Key Static Methods on SmartDataDbDoc

| Method | Description | |--------|-------------| | getInstances(filter, opts?) | Find multiple documents | | getInstance(filter, opts?) | Find one document or null, with optional projection, timeout, and session | | exists(filter, opts?) | Check existence using an _id-only read | | getCursor(filter, opts?) | Get a bounded streaming cursor with projection, compound sort, batch size, limit, timeout, and session | | getCount(filter?, opts?) | Count matching documents with optional limit, timeout, and session | | getGroupedTotals(options) | Bounded server-side grouped counts and declared-numeric-field sums without raw aggregation access | | init() / ensureInitialized() | Retry-safe model collection and index initialization | | getIndexInfo() | Return the model's sanitized installed-index inventory | | insert(document, opts?) | Insert a newly constructed ordinary model with typed unique-conflict errors | | insertMany(documents, opts?) | Insert a validated batch with ordered or unordered semantics and precise inserted-document marking | | atomicUpdate(filter, update, opts?) | Conditionally apply $set, $unset, $inc, $setOnInsert, and single-element $push/$addToSet/$pull with optional upsert/session | | atomicFindOneAndUpdate(filter, update, opts) | Conditionally apply an update with optional declared-field sort and return a sanitized atomic before or after image without a separate reload | | atomicDelete(filter, opts?) | Delete one ordinary document behind a filter that fully pins an identity field or a declared unqualified unique index | | atomicDeleteMany(filter, opts?) | Delete every match behind strict declared-field validation with an explicit equality anchor | | atomicUpdateMany(filter, update, opts?) | Update every match without upsert while maintaining atomic write timestamps | | watch(filter, opts?) | Watch for real-time changes | | search(query, opts?) | Lucene-style full-text search | | forEach(filter, fn) | Iterate all matches with a callback | | getNewId(length?) | Generate a class-prefixed unique ID | | createSearchFilter(luceneQuery) | Convert Lucene query to MongoDB filter | | getSearchableFields() | List all @searchable() fields |

Key Instance Methods on SmartDataDbDoc

| Method | Description | |--------|-------------| | save(opts?) | Insert or update the document | | delete(opts?) | Delete the document | | updateFromDb() | Refresh fields from the database | | saveDeep(savedMap?) | Recursively save referenced documents | | createSavableObject() | Serialize to a plain object for persistence | | createIdentifiableObject() | Extract unique index fields for filtering |

License and Legal Information

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at [email protected].

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.