npm.io
0.106.0 • Published 3d ago

@rpcbase/db

Licence
Version
0.106.0
Deps
2
Size
285 kB
Vulns
0
Weekly
364

@rpcbase/db

Zod extensions

This package provides a small Zod extension that adds .unique() and .sparse() to z.string(), z.number(), and z.date() (for chaining/compatibility).

import { z } from "@rpcbase/db"

const ZUser = z.object({
  email: z.string().email().unique().sparse(),
  createdAt: z.date().sparse(),
})
LocalizedString / zI18nString()

zI18nString() (alias of zLocalizedString()) describes a localized string as an object keyed by language codes (BCP47), where each locale stores the translated value and metadata:

import { z, zI18nString } from "@rpcbase/db"

const ZPost = z.object({
  title: zI18nString(),
})

const title = {
  en: { value: "Hello", updatedAt: new Date(), autoTranslated: false },
  fr: { value: "Bonjour", updatedAt: new Date() },
}
Mongoose re-export
import { Schema, model, mongoose } from "@rpcbase/db"
Tenant model context (models.get / models.getUnsafe)

Tenant-scoped model loading supports both context shapes:

import { models } from "@rpcbase/db"

const ItemA = await models.get("Item", { tenantId: "tenant-1" })
const ItemB = await models.get("Item", {
  req: { session: { user: { currentTenantId: "tenant-1" } } },
})

ctx.tenantId is checked first, then fallback is ctx.req.session.user.currentTenantId.

Transactions (MongoDB / Mongoose sessions)

@rpcbase/db supports MongoDB transactions via Mongoose sessions.

To make a write atomic across tenant DB + global DB + tenant filesystem DB, use withTransaction(...) and pass { session } to every operation (Mongoose and native driver):

import { models, withTransaction } from "@rpcbase/db"

await withTransaction({ tenantId }, async ({ session, ctx, db }) => {
  const Item = await models.get("Item", ctx.tenant)
  const item = await Item.create({ uid: "u1", name: "Hello", createdAt: Date.now() }, { session })

  const globalNative = db.global.db
  if (globalNative) {
    await globalNative.collection("rb_audit_log").insertOne({ itemId: item._id.toString() }, { session })
  }

  const filesystemNative = db.filesystem.db
  if (filesystemNative) {
    await filesystemNative.collection("rb_fs_meta").insertOne({ itemId: item._id.toString() }, { session })
  }
})

Notes:

  • MongoDB transactions require a replica set (or a sharded cluster). On a standalone MongoDB server, withTransaction will fail.
  • To include deletes in the same transaction, the RTS delete change-log plugin is session-aware (rtsChangeLogPlugin).
Mongoose localized fallback (optional)

When storing localized strings as plain objects, mongooseLocalizedStringField() adds a getter that returns a proxy with fallback behavior. Fallback access returns the full locale entry:

import { Schema, mongooseLocalizedStringField } from "@rpcbase/db"

const PostSchema = new Schema({
  title: mongooseLocalizedStringField(),
})

const entry = doc.title.get("fr-CA")
const text = resolveLocalizedString(doc.title, "fr-CA")
Search helpers

@rpcbase/db exposes helpers to run MongoDB search:

import { buildSearchTextStage, ensureSearchIndex, searchMetaProjection } from "@rpcbase/db"
  • buildSearchTextStage(...) builds a $search stage.
  • ensureSearchIndex(...) creates an index if missing (listSearchIndexes + createSearchIndexes) and returns { created: boolean }.
  • searchMetaProjection() adds score and highlights projection fields.

Sample usage (sample-app) in sample-app/src/api/search/items/handler.ts:

await ensureSearchIndex({ db: Item.db.db, collection: Item.collection.name, name: "item_name_text_v1", definition })
const results = await Item.aggregate([
  buildSearchTextStage({ index: "item_name_text_v1", query, path: "name", highlightPath: "name" }),
  { $limit: 20 },
  { $project: { name: 1, createdAt: 1, ...searchMetaProjection() } },
]).exec()

Dans sample-app, la config se trouve dans sample-app/infrastructure (compose.yml, mongot/config.yml, mongot/mongodb-init.js, ensure-mongot-*).