@classytic/review
v0.3.1
Published
Framework-agnostic polymorphic review + rating engine — one engine for ecom product reviews, food/restaurant reviews, ride/driver reviews, traveler/sender reviews. Host-configurable subjects, facets, moderation, eligibility. Powered by MongoDB via @clas
Readme
@classytic/review
Framework-agnostic polymorphic review + rating engine. One engine for every
review shape — ecom product reviews, food/restaurant reviews, ride/driver
reviews, traveler↔sender reviews — all host-configurable. MongoKit repositories
are the primitive; arc auto-CRUD works directly; Zod schemas (at the /schemas
subpath) feed auto-OpenAPI.
Install
npm i @classytic/review
# peers
npm i @classytic/mongokit @classytic/repo-core @classytic/primitives mongoose zodQuick start
import mongoose from 'mongoose';
import { createReview } from '@classytic/review';
const reviews = await createReview({
connection: mongoose.connection,
ratingScale: { min: 1, max: 5 }, // default
subjects: ['product', 'driver', 'restaurant'], // optional allowlist (undefined = any)
facets: { driver: ['punctuality', 'cleanliness', 'driving'] },
moderation: { defaultStatus: 'published' }, // or 'pending'
modules: { summary: true, helpfulness: true, response: true },
eligibility: myVerifiedPurchaseBridge, // optional gate
});
// Repositories ARE the domain layer
await reviews.repositories.review.submit(
{ subjectType: 'product', subjectId: 'SKU-1', raterId: 'u1', rating: 5, body: 'Great' },
ctx,
);
const summary = await reviews.getSummary('product', 'SKU-1', ctx);
// { count, avg, ratingSum, distribution: { '5': 1 }, ... }Engine surface
| Member | Description |
|---|---|
| reviews.models.{Review,ReviewSummary} | Connection-scoped Mongoose models |
| reviews.repositories.review | submit / edit / moderate / respond / voteHelpful / flag / remove |
| reviews.repositories.summary | applyDelta / recompute / getSummary (present when modules.summary) |
| reviews.events | arc-compatible EventTransport — subscribe to review:* |
| reviews.getSummary(subjectType, subjectId, ctx) | Read the rollup |
| reviews.canReview(input, ctx) | Probe the eligibility bridge |
| reviews.syncIndexes() | Idempotent index sync |
| reviews.disconnect() | Graceful shutdown |
Polymorphic subject
(subjectType, subjectId) are plain strings + an optional eligibility
bridge — never a Mongoose refPath/ObjectId. The subject collection varies per
host (a product, a driver, a restaurant, a traveler) and is never coupled into
the kernel. Verified-purchase / verified-ride linkage flows through the
EligibilityBridge decision ({ allowed, verified?, sourceType?, sourceId? })
and is stamped onto the review.
Summary rollup
ReviewSummary is maintained incrementally. Whenever a review enters / leaves
the counted set (published | flagged) or changes its rating while
counted, ReviewSummaryRepository.applyDelta(...) runs under the same
ctx.session and applies a single atomic update: all $inc deltas plus an
aggregation-pipeline recompute of avg = ratingSum / count in one round-trip,
so avg can never drift. recompute(...) re-aggregates from source reviews as
a repair path.
Events
review:review.submitted | .edited | .moderated | .responded | .voted |
.flagged | .removed and review:summary.changed. Each ships a Zod-backed
definition in reviewEventDefinitions (importable from . or
@classytic/review/events) — register them with arc's EventRegistry.
License
MIT
