UNPKG

@trap_stevo/ventry

Version:

The universal engine for creating, tracking, and evolving interactive content — from posts, comments, and likes to offers, auctions, events, and beyond. Define, extend, and analyze content objects in real time. Turn anything into user-driven content.

894 lines (893 loc) 29.2 kB
"use strict"; function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } const { ContentVaultInstanceManager } = require("./HUDManagers/ContentVaultInstanceManager.cjs"); const { ContalyticsInstanceManager } = require("./HUDManagers/ContalyticsInstanceManager.cjs"); const { CommentsManager } = require("./HUDManagers/CommentsManager.cjs"); const { AuctionsManager } = require("./HUDManagers/AuctionsManager.cjs"); const { OffersManager } = require("./HUDManagers/OffersManager.cjs"); const { EventsManager } = require("./HUDManagers/EventsManager.cjs"); const { geoIncludes } = require("./HUDComponents/Geo.cjs"); const { now } = require("./HUDComponents/Time.cjs"); const { newID } = require("./HUDComponents/ID.cjs"); var _vaultActionInfoDefault = /*#__PURE__*/new WeakMap(); var _vaultClientAuthDefault = /*#__PURE__*/new WeakMap(); var _metricPrefix = /*#__PURE__*/new WeakMap(); var _Ventry_brand = /*#__PURE__*/new WeakSet(); class Ventry { constructor(options = {}) { _classPrivateMethodInitSpec(this, _Ventry_brand); _classPrivateFieldInitSpec(this, _vaultActionInfoDefault, {}); _classPrivateFieldInitSpec(this, _vaultClientAuthDefault, null); _classPrivateFieldInitSpec(this, _metricPrefix, "ventry"); _classPrivateFieldSet(_vaultActionInfoDefault, this, options.vaultActionInfoDefault || {}); _classPrivateFieldSet(_vaultClientAuthDefault, this, options.vaultClientAuthDefault || null); _classPrivateFieldSet(_metricPrefix, this, options.metricPrefix || "ugc"); this.vaultManager = new ContentVaultInstanceManager(options.contentVault, options.vaultOptions || {}, _classPrivateFieldGet(_vaultActionInfoDefault, this), _classPrivateFieldGet(_vaultClientAuthDefault, this)); this.metricsManager = new ContalyticsInstanceManager(options.contentMetrics, Object.assign({}, options.metricOptions, { metricPrefix: _classPrivateFieldGet(_metricPrefix, this) })); this.eventsManager = new EventsManager(this.vaultManager, { eventPrefix: options.eventPrefix || _classPrivateFieldGet(_metricPrefix, this) || "ugc", actionInfo: _classPrivateFieldGet(_vaultActionInfoDefault, this) }); if (options.autoStartScheduler !== false) { this.eventsManager.startScheduler(); } this.commentsManager = new CommentsManager(this.vaultManager, this.metricsManager, this.eventsManager); this.offersManager = new OffersManager(this.vaultManager, this.metricsManager, this.eventsManager); this.auctionsManager = new AuctionsManager(this.vaultManager, this.metricsManager, this.eventsManager); this.visibilityPolicies = new Map(); } setActionInfo(actionInfo = {}) { _classPrivateFieldSet(_vaultActionInfoDefault, this, actionInfo); this.vaultManager.setDefaults(actionInfo, _classPrivateFieldGet(_vaultClientAuthDefault, this)); } setClientAuth(clientAuth = null) { _classPrivateFieldSet(_vaultClientAuthDefault, this, clientAuth); this.vaultManager.setDefaults(_classPrivateFieldGet(_vaultActionInfoDefault, this), clientAuth); } setMetricPrefix(prefix = "ugc") { _classPrivateFieldSet(_metricPrefix, this, prefix || "ugc"); this.metricsManager.setPrefix(_classPrivateFieldGet(_metricPrefix, this)); } setVaultPrefix(prefix = "ugc") { this.vaultManager.setPrefix(prefix); } on(ev, fn) { return this.eventsManager.on(ev, fn); } once(ev, fn) { return this.eventsManager.once(ev, fn); } off(ev, fn) { return this.eventsManager.off(ev, fn); } setEventPrefix(prefix) { this.eventsManager.setEventPrefix(prefix); } configureScheduler(cfg) { this.eventsManager.configureScheduler(cfg); } startExpirationScheduler() { this.eventsManager.startScheduler(); } stopExpirationScheduler() { this.eventsManager.stopScheduler(); } registerType(config = {}, actionInfo) { const { typeID } = config; if (!typeID) { console.warn("Type ID required."); return null; } this.visibilityPolicies.set(typeID, config.visibilityPolicy || null); return this.vaultManager.upsertType(config, actionInfo); } updateType(typeID, patch = {}, actionInfo) { if (!typeID) { console.warn("Type ID required."); return null; } if (patch.visibilityPolicy !== undefined) { this.visibilityPolicies.set(typeID, patch.visibilityPolicy || null); } return this.vaultManager.patchType(typeID, patch, actionInfo); } listTypes(actionInfo) { return this.vaultManager.listTypes(actionInfo); } async create(ownerID, contentPayload = {}, actionInfo = {}) { if (!ownerID) { console.warn("Owner ID required."); return null; } const id = newID(); const record = { typeID: contentPayload.typeID || "default", ownerID, id, version: 1, data: contentPayload.data || {}, attachments: contentPayload.attachments || [], tags: contentPayload.tags || [], labels: contentPayload.labels || [], status: contentPayload.status || "active", deployment: contentPayload.deployment || { scope: "global" }, expiration: contentPayload.expiration || null, boosts: Array.isArray(contentPayload.boosts) ? contentPayload.boosts : [], commentsEnabled: contentPayload.commentsEnabled === true }; const saved = this.vaultManager.createItem(record, actionInfo); this.eventsManager.emit("item.created", { id: saved.id, ownerID, typeID: record.typeID, item: saved }); return { id: saved.id }; } async getItems(actionInfo) { return this.vaultManager.getItems(actionInfo); } async get(id, actionInfo) { return this.vaultManager.getItem(id, actionInfo); } async update(id, actor, patch = {}, actionInfo) { const item = this.vaultManager.getItem(id, actionInfo); if (!item) { console.warn("Not found"); return null; } if (actor && actor.strictOwner && actor.id !== item.data.ownerID) { console.warn("Update unauthorized."); return null; } const updates = {}; if (patch.data !== undefined) { updates.data = patch.data; } if (patch.attachments !== undefined) { updates.attachments = patch.attachments; } if (patch.tags !== undefined) { updates.tags = patch.tags; } if (patch.labels !== undefined) { updates.labels = patch.labels; } if (patch.status !== undefined) { updates.status = patch.status; } if (patch.deployment !== undefined) { updates.deployment = patch.deployment; } if (patch.expiration !== undefined) { updates.expiration = patch.expiration; } if (patch.boosts !== undefined) { updates.boosts = patch.boosts; } if (patch.commentsEnabled !== undefined) { updates.commentsEnabled = !!patch.commentsEnabled; } updates.version = (item.data.version || 1) + 1; const updated = this.vaultManager.updateItem(id, updates, actionInfo); this.eventsManager.emit("item.updated", { id, updates, actor, item: updated }); return updated; } async remove(id, actor, opts = {}, actionInfo = {}) { const item = this.vaultManager.getItem(id, actionInfo); if (!item) { return { deleted: false }; } if (actor && actor.strictOwner && actor.id !== item.data.ownerID) { console.warn("Update unauthorized."); return null; } if (opts.cascadeSubvault === true) { const branches = Array.isArray(opts.subvaultBranch) ? opts.subvaultBranch : [opts.subvaultBranch || "subvault"]; for (const branch of branches) { try { this.vaultManager.deleteAllSubvaultForParent(id, { hard: !!opts.hard }, actionInfo, branch); } catch (e) { console.warn("Did not cascade delete subvault ~", e?.message || e, "branch ~", branch); } } } if (opts.hard === true) { const r = this.vaultManager.deleteRecord(id, actionInfo); this.eventsManager.emit("item.deleted_hard", { id, actor }); return r; } const r = this.vaultManager.softDelete(id, actionInfo); this.eventsManager.emit("item.deleted_soft", { id, actor }); return r; } getContentRecordHistory(collectionID = "items", id, options = {}, actionInfo, clientAuth) { return this.vaultManager.getContentRecordHistory(collectionID, id, options, actionInfo, clientAuth); } getContentRecordTimeline(collectionID = "items", id, options = {}, actionInfo, clientAuth) { return this.vaultManager.getRecordTimeline(collectionID, id, options, actionInfo, clientAuth); } getContentHistory(collectionID = "items", options = {}, actionInfo, clientAuth) { return this.vaultManager.getContentHistory(collectionID, options, actionInfo, clientAuth); } getContentTimeline(collectionID = "items", options = {}, actionInfo, clientAuth) { return this.vaultManager.getContentTimeline(collectionID, options, actionInfo, clientAuth); } getHistory(options = {}, actionInfo, clientAuth) { return this.vaultManager.getHistory(options, actionInfo, clientAuth); } async syncHistory(actionInfo, clientAuth) { return await this.vaultManager.syncHistory(actionInfo, clientAuth); } importContentRecord(collectionID = "items", record, options = {}, actionInfo, clientAuth) { return this.vaultManager.importContentRecord(collectionID, record, options, actionInfo, clientAuth); } importManyContentRecords(collectionID = "items", records, options = {}, actionInfo, clientAuth) { return this.vaultManager.importManyContentRecords(collectionID, records, options, actionInfo, clientAuth); } importContent(collectionID = "items", records, options = {}, actionInfo, clientAuth) { return this.vaultManager.importContent(collectionID, records, options, actionInfo, clientAuth); } importSnapshot(snapshot, options = {}, actionInfo, clientAuth) { return this.vaultManager.importSnapshot(snapshot, options, actionInfo, clientAuth); } addSubvault(itemID, ownerID, input = {}, actionInfo, branch = "subvault") { if (!itemID || !ownerID) { console.warn("itemID and ownerID required."); return { id: null }; } const parent = this.vaultManager.getItem(itemID, actionInfo); if (!parent) { console.warn("Parent not found."); return { id: null }; } const rec = { parentID: itemID, ownerID, key: input.key || null, payload: input.payload || {}, tags: Array.isArray(input.tags) ? input.tags : [], labels: Array.isArray(input.labels) ? input.labels : [], status: input.status || "active", createdAt: Date.now(), updatedAt: Date.now(), expiresAt: input.expiresAt || null }; const saved = this.vaultManager.createSubvault(rec, actionInfo, branch); this.eventsManager.emit("subvault.created", { id: saved.id, parentID: itemID, ownerID, branch }); return { id: saved.id }; } listSubvault(itemID, opts = {}, actionInfo, branch = "subvault") { if (!itemID) { return { items: [], nextCursor: undefined }; } const filter = { parentID: itemID }; if (Array.isArray(opts.keys) && opts.keys.length > 0) { filter.key = { $in: opts.keys }; } if (opts.status) { filter.status = opts.status; } const sort = opts.sort || { updatedAt: -1 }; const page = { limit: opts.limit || 100, offset: opts.offset || 0 }; const projection = opts.projection; const qb = this.vaultManager.querySubvault(filter, sort, page, projection, actionInfo, branch); const items = qb.execute(true); return { items, nextCursor: undefined }; } getSubvaultByKey(itemID, key, actionInfo, branch = "subvault") { if (!itemID || !key) { return null; } return this.vaultManager.getSubvaultByKey(itemID, key, actionInfo, branch); } upsertSubvaultByKey(itemID, ownerID, key, payload = {}, extras = {}, actionInfo, branch = "subvault") { if (!itemID || !ownerID || !key) { console.warn("itemID, ownerID and key required."); return null; } const parent = this.vaultManager.getItem(itemID, actionInfo); if (!parent) { console.warn("Parent not found."); return null; } const saved = this.vaultManager.upsertSubvaultByKey(itemID, ownerID, key, payload, extras, actionInfo, branch); const id = saved.id || saved.data && saved.data.id || saved; this.eventsManager.emit("subvault.upserted", { parentID: itemID, key, id, ownerID, branch }); return saved; } updateSubvault(subvaultID, actor, patch = {}, actionInfo, branch = "subvault") { const rec = this.vaultManager.getSubvault(subvaultID, actionInfo, branch); if (!rec) { return null; } if (actor && actor.strictOwner && actor.id !== (rec.data && rec.data.ownerID)) { console.warn("Update unauthorized."); return null; } const set = {}; if (patch.payload !== undefined) { set.payload = patch.payload; } if (patch.key !== undefined) { set.key = patch.key; } if (patch.tags !== undefined) { set.tags = patch.tags; } if (patch.labels !== undefined) { set.labels = patch.labels; } if (patch.status !== undefined) { set.status = patch.status; } if (patch.expiresAt !== undefined) { set.expiresAt = patch.expiresAt; } set.updatedAt = Date.now(); const updated = this.vaultManager.updateSubvault(subvaultID, set, actionInfo, branch); this.eventsManager.emit("subvault.updated", { id: subvaultID, parentID: rec.data ? rec.data.parentID : undefined, actor, branch }); return updated; } removeSubvault(subvaultID, actor, opts = {}, actionInfo, branch = "subvault") { const rec = this.vaultManager.getSubvault(subvaultID, actionInfo, branch); if (!rec) { return { deleted: false }; } if (actor && actor.strictOwner && actor.id !== (rec.data && rec.data.ownerID)) { console.warn("Delete unauthorized."); return { deleted: false }; } if (opts.hard === true) { const r = this.vaultManager.deleteSubvault(subvaultID, actionInfo, branch); this.eventsManager.emit("subvault.deleted_hard", { id: subvaultID, parentID: rec.data ? rec.data.parentID : undefined, actor, branch }); return r; } const r = this.vaultManager.softDeleteSubvault(subvaultID, actionInfo, branch); this.eventsManager.emit("subvault.deleted_soft", { id: subvaultID, parentID: rec.data ? rec.data.parentID : undefined, actor, branch }); return r; } existsSubvaultKey(itemID, key, actionInfo, branch = "subvault") { return this.vaultManager.existsSubvaultKey(itemID, key, actionInfo, branch); } countSubvault(itemID, filter = {}, actionInfo, branch = "subvault") { return this.vaultManager.countSubvault(itemID, filter, actionInfo, branch); } listSubvaultKeys(itemID, filter = {}, actionInfo, branch = "subvault") { return this.vaultManager.listSubvaultKeys(itemID, filter, actionInfo, branch); } moveSubvault(subvaultID, toItemID, actionInfo, branch = "subvault") { const updated = this.vaultManager.moveSubvault(subvaultID, toItemID, actionInfo, branch); if (updated) { this.eventsManager.emit("subvault.moved", { id: subvaultID, toParentID: toItemID, branch }); } return updated; } cloneSubvaultRecords(fromItemID, toItemID, options = {}, actionInfo, fromBranch = "subvault", toBranch = null) { const res = this.vaultManager.cloneSubvaultRecords(fromItemID, toItemID, options, actionInfo, fromBranch, toBranch); this.eventsManager.emit("subvault.cloned", { fromParentID: fromItemID, toParentID: toItemID, created: res.created, fromBranch, toBranch: toBranch || fromBranch }); return res; } touchSubvault(subvaultID, actionInfo, branch = "subvault") { return this.vaultManager.touchSubvault(subvaultID, actionInfo, branch); } setSubvaultStatus(subvaultID, status, actionInfo, branch = "subvault") { return this.vaultManager.setSubvaultStatus(subvaultID, status, actionInfo, branch); } purgeExpiredSubvault(itemID, actionInfo, branch = "subvault") { return this.vaultManager.purgeExpiredSubvault(itemID, actionInfo, branch); } trackItemMetric(itemID, metric, value = 1, actorID = null, extras = {}, actionInfo) { if (!itemID || !metric) { console.warn("Item ID and metric required."); return { ok: false, reason: "invalid_args" }; } const item = this.vaultManager.getItem(itemID, actionInfo); if (!item) { console.warn("Item not found."); return { ok: false, reason: "not_found" }; } const baseTags = { itemID: itemID, typeID: item.data?.typeID, ownerID: item.data?.ownerID }; if (actorID) { baseTags.actorID = actorID; } const extraTags = extras?.tags && typeof extras.tags === "object" ? extras.tags : {}; const safeTags = {}; for (const key of Object.keys(extraTags)) { const val = extraTags[key]; if (["string", "number", "boolean"].includes(typeof val)) { safeTags[key] = val; } else if (val != null) { safeTags[key] = String(val); } } const tags = Object.assign({}, safeTags, baseTags); const metadata = extras?.metadata && typeof extras.metadata === "object" ? extras.metadata : undefined; this.metricsManager.track(metric, value, tags, metadata); this.eventsManager.emit("item.metric", { id: itemID, metric: metric, value: value, tags: tags, metadata: metadata, actorID: actorID }); this.eventsManager.emit(`item.metric.${metric}`, { id: itemID, metric: metric, value: value, tags: tags, metadata: metadata, actorID: actorID }); return { ok: true }; } trackItemMetrics(batch = [], actionInfo) { if (!Array.isArray(batch) || batch.length === 0) { console.warn("Batch array required."); return { ok: false, reason: "invalid_args" }; } let success = 0; for (const rec of batch) { const r = this.trackItemMetric(rec.itemID, rec.metric, rec.value ?? 1, rec.actorID ?? null, rec.extras ?? {}, actionInfo); if (r && r.ok) { success += 1; } } return { ok: true, count: success }; } visit(id, actorID, context) { const item = this.vaultManager.getItem(id); if (!item) { return; } this.metricsManager.track("visit", 1, { itemID: id, typeID: item.data.typeID, ownerID: item.data.ownerID, actorID }, context); this.eventsManager.emit("item.visit", { id, actorID, context }); } like(id, actorID) { const it = this.vaultManager.getItem(id); if (!it) { return; } this.metricsManager.track("like", +1, { itemID: id, typeID: it.data.typeID, ownerID: it.data.ownerID, actorID }); this.eventsManager.emit("item.like", { id, actorID }); } unlike(id, actorID) { const it = this.vaultManager.getItem(id); if (!it) { return; } this.metricsManager.track("like", -1, { itemID: id, typeID: it.data.typeID, ownerID: it.data.ownerID, actorID }); this.eventsManager.emit("item.unlike", { id, actorID }); } favorite(id, actorID) { const it = this.vaultManager.getItem(id); if (!it) { return; } this.metricsManager.track("favorite", +1, { itemID: id, typeID: it.data.typeID, ownerID: it.data.ownerID, actorID }); this.eventsManager.emit("item.favorite", { id, actorID }); } unfavorite(id, actorID) { const it = this.vaultManager.getItem(id); if (!it) { return; } this.metricsManager.track("favorite", -1, { itemID: id, typeID: it.data.typeID, ownerID: it.data.ownerID, actorID }); this.eventsManager.emit("item.unfavorite", { id, actorID }); } reportItem(id, reporterID, reason, meta) { const it = this.vaultManager.getItem(id); if (!it) { return; } this.metricsManager.track("report", 1, { itemID: id, typeID: it.data.typeID, ownerID: it.data.ownerID, reporterID, reason }, meta); this.eventsManager.emit("item.report", { id, reporterID, reason, meta }); } addComment(itemID, ownerID, payload) { return this.commentsManager.addComment(itemID, ownerID, payload); } listComments(itemID, params) { return this.commentsManager.listComments(itemID, params); } reactToComment(commentID, actorID, rt) { return this.commentsManager.reactToComment(commentID, actorID, rt); } reportComment(commentID, reporterID, reason, meta) { return this.commentsManager.reportComment(commentID, reporterID, reason, meta); } proposeOffer(itemID, bidderID, input) { return this.offersManager.proposeOffer(itemID, bidderID, input); } acceptOffer(offerID, ownerID) { return this.offersManager.acceptOffer(offerID, ownerID); } rejectOffer(offerID, ownerID, reason) { return this.offersManager.rejectOffer(offerID, ownerID, reason); } withdrawOffer(offerID, bidderID) { return this.offersManager.withdrawOffer(offerID, bidderID); } listOffers(itemID, filter, page) { return this.offersManager.listOffers(itemID, filter, page); } markItemSold(itemID, ownerID, sale) { return this.offersManager.markItemSold(itemID, ownerID, sale); } toggleForSale(itemID, ownerID, forSale) { return this.offersManager.toggleForSale(itemID, ownerID, forSale); } updatePrice(itemID, ownerID, price) { return this.offersManager.updatePrice(itemID, ownerID, price); } startAuction(itemID, ownerID, cfg) { return this.auctionsManager.startAuction(itemID, ownerID, cfg); } cancelAuction(auctionID, ownerID, reason) { return this.auctionsManager.cancelAuction(auctionID, ownerID, reason); } getAuction(auctionID) { return this.auctionsManager.getAuction(auctionID); } listAuctions(filter, page) { return this.auctionsManager.listAuctions(filter, page); } watchAuction(auctionID, actorID) { return this.auctionsManager.watchAuction(auctionID, actorID); } unwatchAuction(auctionID, actorID) { return this.auctionsManager.unwatchAuction(auctionID, actorID); } placeBid(auctionID, bidderID, input) { return this.auctionsManager.placeBid(auctionID, bidderID, input); } retractBid(bidID, bidderID, reason) { return this.auctionsManager.retractBid(bidID, bidderID, reason); } listBids(auctionID, page) { return this.auctionsManager.listBids(auctionID, page); } getLeadingBid(auctionID) { return this.auctionsManager.getLeadingBid(auctionID); } endAuction(auctionID, systemActor) { return this.auctionsManager.endAuction(auctionID, systemActor); } settleAuction(auctionID, ownerID, settlement) { return this.auctionsManager.settleAuction(auctionID, ownerID, settlement); } query(params = {}) { const { typeID, filter, sort, page, projection, geoContext, actionInfo } = params; const qb = this.vaultManager.queryItems(typeID, filter, sort, page, projection, actionInfo); let items = qb.execute(false); const current = now(); items = items.filter(it => _assertClassBrand(_Ventry_brand, this, _filterByDeployment).call(this, it, geoContext)).filter(it => _assertClassBrand(_Ventry_brand, this, _applyExpiration).call(this, it, current)); return { items, nextCursor: undefined }; } feed(params = {}) { const { typeID, limit = 20, geoContext, visibilityContext, onRanking, actionInfo } = params; const qb = this.vaultManager.queryItems(typeID, params.filter, params.sort, params.page, params.projection, actionInfo); let items = qb.execute(false); const current = now(); items = items.filter(it => it.data.status !== "hidden" && it.data.status !== "banned"); items = items.filter(it => _assertClassBrand(_Ventry_brand, this, _filterByDeployment).call(this, it, geoContext)).filter(it => _assertClassBrand(_Ventry_brand, this, _applyExpiration).call(this, it, current)); const scored = []; for (const item of items) { const it = item.data; const metrics = this.metricsManager.readWindows(item.id); const boosts = Array.isArray(it.boosts) ? it.boosts.filter(b => (!b.startsAt || b.startsAt <= current) && (!b.endsAt || current < b.endsAt)) : []; const policy = this.visibilityPolicies.get(it.typeID); let score = 0; if (typeof onRanking === "function") { score = _assertClassBrand(_Ventry_brand, this, _coerceScore).call(this, onRanking({ item, metrics, boosts, geoContext, visibilityContext, now: current })); } else if (typeof policy === "function") { score = _assertClassBrand(_Ventry_brand, this, _coerceScore).call(this, policy({ item, metrics, boosts, geoContext, visibilityContext, now: current })); } else { const ageH = Math.max(1, (current - (item.timestamp || current)) / 3.6e6); const boostSum = boosts.reduce((s, b) => s + (b.weight || 0), 0); const visits24h = metrics.visits24h || 0; const fav7d = metrics.favorites7d || 0; const likes7d = metrics.likes7d || 0; score = (visits24h + likes7d * 4 + fav7d * 5) * (1 + boostSum) / Math.pow(ageH, 1.15); } scored.push({ item, score }); } scored.sort((a, b) => b.score - a.score); return { items: scored.slice(0, limit).map(s => s.item), nextCursor: undefined }; } getAllAnalytics(options = {}) { return this.metricsManager.getAllMetrics(options); } getItemAnalytics(itemID, options = {}) { return this.metricsManager.getItemMetrics(itemID, options); } getItemAnalyticsSummary(itemID, options = {}) { return this.metricsManager.getItemAnalyticsSummary(itemID, options); } getItemMetricTimeSeries(itemID, metric, options = {}) { return this.metricsManager.getItemMetricTimeSeries(itemID, metric, options); } getTopItemsBy(metric, options = {}) { return this.metricsManager.getTopItemsByMetric(metric, options); } } function _coerceScore(v) { if (v == null) { return 0; } if (typeof v === "number") { return v; } if (typeof v === "boolean") { return v ? 1 : 0; } if (typeof v === "object" && typeof v.score === "number") { return v.score; } return 0; } function _filterByDeployment(item, geoContext) { const deployment = item.data.deployment || { scope: "global" }; if (deployment.scope === "global") { return true; } if (!geoContext) { return true; } const { allow, deny } = deployment.geo || {}; if (Array.isArray(deny) && deny.length > 0 && geoIncludes(geoContext, deny)) { return false; } if (Array.isArray(allow) && allow.length > 0) { return geoIncludes(geoContext, allow); } return true; } function _applyExpiration(item, current) { const exp = item.data.expiration; if (!exp) { return true; } let expiresAt = exp.data.expiresAt; if (!expiresAt && exp.data.ttlMs) expiresAt = (item.timestamp || current) + exp.data.ttlMs; if (expiresAt && current >= expiresAt) { const action = exp.onExpire || "hide"; if (action === "hide") { this.vaultManager.updateItem(item.id, { status: "hidden" }); } else if (action === "archive") { this.vaultManager.updateItem(item.id, { status: "archived" }); } else if (action === "delete") { this.vaultManager.deleteRecord(item.id); } return false; } return true; } ; module.exports = { Ventry };