@just-in/core
Version:
A TypeScript-first framework for building adaptive digital health interventions.
190 lines • 9.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JustInLite = exports.JustInLiteWrapper = void 0;
const event_handler_manager_1 = require("./event/event-handler-manager");
const task_manager_1 = require("./handlers/task.manager");
const decision_rule_manager_1 = require("./handlers/decision-rule.manager");
const event_executor_1 = require("./event/event-executor");
const logger_manager_1 = require("./logger/logger-manager");
const result_recorder_1 = require("./handlers/result-recorder");
/**
* JustInLiteWrapper provides a minimal, serverless-oriented interface for 3rd-party apps:
* - configure logger & result writers
* - register tasks, decision rules, and event handlers
* - keep users in-memory for the current warm instance
* - run registered events immediately (no DB/queue)
*/
class JustInLiteWrapper {
constructor() {
/** In-memory idempotency (per warm instance only). */
this.processedKeys = new Set();
this.eventHandlerManager = event_handler_manager_1.EventHandlerManager.getInstance();
/** In-memory users for this warm instance (keyed by uniqueIdentifier). */
this.users = new Map();
// Lite/serverless: never touch DataManager from the recorder module.
(0, result_recorder_1.setResultRecorderPersistenceEnabled)(false);
}
/** Returns the singleton Lite instance. */
static getInstance() {
if (!JustInLiteWrapper.instance) {
JustInLiteWrapper.instance = new JustInLiteWrapper();
}
return JustInLiteWrapper.instance;
}
/**
* Reset the singleton (useful for tests).
* Includes a 1-tick drain to let any late recorder promises settle.
*/
async killInstance() {
try {
// allow any microtasks to flush
await new Promise((r) => setImmediate(r));
}
finally {
this.processedKeys.clear();
this.users.clear();
this.eventHandlerManager.clearEventHandlers();
JustInLiteWrapper.instance = null;
}
}
/**
* Static teardown helper for tests/tools.
* If an instance exists, delegate to it; otherwise do a best-effort drain+clear.
* Safe to call multiple times.
*/
static async killInstance() {
if (JustInLiteWrapper.instance) {
await JustInLiteWrapper.instance.killInstance();
return;
}
// No instance — still clear any registered handlers to avoid leaks across tests.
await new Promise((resolve) => setImmediate(resolve));
event_handler_manager_1.EventHandlerManager.getInstance().clearEventHandlers();
}
// ────────────────────────────────────────────────────────────────────────────
// Users (in-memory)
// ────────────────────────────────────────────────────────────────────────────
/**
* Loads users for the current invocation (serverless-safe).
* Accepts either `JUser[]` or `NewUserRecord[]`.
* Replaces the in-memory set each call (atomic), requires `uniqueIdentifier`,
* and throws on duplicates. Returns the normalized `JUser[]`.
*/
async loadUsers(users) {
if (!Array.isArray(users)) {
throw new Error('loadUsers expects an array.');
}
const next = new Map();
const normalized = [];
users.forEach((item, i) => {
var _a, _b;
const anyItem = item;
const uniqueIdentifier = typeof (anyItem === null || anyItem === void 0 ? void 0 : anyItem.uniqueIdentifier) === 'string' ? anyItem.uniqueIdentifier.trim() : '';
const idHint = typeof (anyItem === null || anyItem === void 0 ? void 0 : anyItem.id) === 'string' ? anyItem.id : undefined;
if (!uniqueIdentifier) {
const msg = `UniqueIdentifier is missing`;
logger_manager_1.Log.error(msg);
throw new Error(msg);
}
if (next.has(uniqueIdentifier)) {
const msg = `loadUsers: duplicate uniqueIdentifier "${uniqueIdentifier}".`;
logger_manager_1.Log.error(msg);
throw new Error(msg);
}
const attrs = 'attributes' in anyItem ? ((_a = anyItem.attributes) !== null && _a !== void 0 ? _a : {}) :
'initialAttributes' in anyItem ? ((_b = anyItem.initialAttributes) !== null && _b !== void 0 ? _b : {}) :
{};
const ju = {
id: idHint !== null && idHint !== void 0 ? idHint : uniqueIdentifier,
uniqueIdentifier,
attributes: { ...attrs },
};
next.set(uniqueIdentifier, ju);
normalized.push(ju);
});
this.users = next;
logger_manager_1.Log.info(`JustInLite: loaded ${next.size} users (in-memory, replacing previous set).`);
return normalized;
}
// ────────────────────────────────────────────────────────────────────────────
// Registration (match JustInWrapper vocabulary)
// ────────────────────────────────────────────────────────────────────────────
/** Register a Task. */
registerTask(task) {
(0, task_manager_1.registerTask)(task);
}
/** Register a Decision Rule. */
registerDecisionRule(decisionRule) {
(0, decision_rule_manager_1.registerDecisionRule)(decisionRule);
}
// TODO: Look at using EventHandlerManager instead and not from the Full Justin
/**
* Registers a new event type with ordered handler names.
* Also caches the definition locally for introspection.
* @param eventType - The type of the event.
* @param handlers - Ordered task/decision-rule names for the event.
*/
async registerEventHandlers(eventType, handlers) {
await this.eventHandlerManager.registerEventHandlers(eventType, handlers);
}
/** Unregister handlers for an event type. */
unregisterEventHandlers(eventType) {
this.eventHandlerManager.unregisterEventHandlers(eventType);
}
// ────────────────────────────────────────────────────────────────────────────
// Execution
// ────────────────────────────────────────────────────────────────────────────
/**
* Publish (execute) a registered event for the **currently loaded** users.
* Signature matches full JustIn; `idempotencyKey` is optional (in-memory only).
*
* @param eventType Registered event type.
* @param generatedTimestamp Event timestamp.
* @param eventDetails Optional event details payload.
* @param idempotencyKey Optional in-memory dedupe key (skips if seen).
*/
async publishEvent(eventType, generatedTimestamp, eventDetails, idempotencyKey) {
// Optional in-memory idempotency for cloud runs
if (idempotencyKey) {
if (this.processedKeys.has(idempotencyKey)) {
logger_manager_1.Log.warn(`[JustInLite] duplicate execution skipped for key: ${idempotencyKey}`);
return;
}
this.processedKeys.add(idempotencyKey);
}
if (!this.eventHandlerManager.hasHandlersForEventType(eventType)) {
throw new Error(`No handlers registered for event type "${eventType}".`);
}
// Ensure users are loaded
const users = Array.from(this.users.values());
if (users.length === 0) {
throw new Error('JustInLite.publishEvent called with no users loaded.');
}
const event = {
eventType,
generatedTimestamp,
eventDetails: eventDetails !== null && eventDetails !== void 0 ? eventDetails : {},
};
await (0, event_executor_1.executeEventForUsers)(event, users, this.eventHandlerManager);
}
// ────────────────────────────────────────────────────────────────────────────
// Logger & Writers (same names as full JustIn)
// ────────────────────────────────────────────────────────────────────────────
configureLogger(logger) {
(0, logger_manager_1.setLogger)(logger);
}
configureTaskResultWriter(taskWriter) {
(0, result_recorder_1.setTaskResultRecorder)(taskWriter);
}
configureDecisionRuleResultWriter(decisionRuleWriter) {
(0, result_recorder_1.setDecisionRuleResultRecorder)(decisionRuleWriter);
}
setLoggingLevels(levels) {
(0, logger_manager_1.setLogLevels)(levels);
}
}
exports.JustInLiteWrapper = JustInLiteWrapper;
JustInLiteWrapper.instance = null;
const JustInLite = () => JustInLiteWrapper.getInstance();
exports.JustInLite = JustInLite;
//# sourceMappingURL=JustInLite.js.map