UNPKG

@just-in/core

Version:

A TypeScript-first framework for building adaptive digital health interventions.

316 lines 12.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestingUserManager = exports.UserManager = exports.addUsers = exports.addUser = exports._checkInitialization = exports._users = void 0; const data_manager_1 = __importDefault(require("../data-manager/data-manager")); const change_listener_manager_1 = require("../data-manager/change-listener.manager"); const data_manager_constants_1 = require("../data-manager/data-manager.constants"); const data_manager_helpers_1 = require("../data-manager/data-manager.helpers"); const data_manager_type_1 = require("../data-manager/data-manager.type"); const logger_manager_1 = require("../logger/logger-manager"); /** * @type {Map<string, JUser>} _users - In-memory cache for user data. * This Map enables quick lookups, insertions, and deletions by `id`. * @private */ exports._users = new Map(); const dm = data_manager_1.default.getInstance(); const clm = change_listener_manager_1.ChangeListenerManager.getInstance(); /** * Initializes the UserManager by initializing the DataManager, * loading users into the cache, and setting up listeners for * user-related database changes. * * @returns {Promise<void>} Resolves when initialization is complete. */ const init = async () => { await dm.init(); await refreshCache(); setupChangeListeners(); }; /** * Shuts down the UserManager by removing all change listeners * * @returns {void} */ const shutdown = () => { clm.removeChangeListener(data_manager_constants_1.USERS, data_manager_type_1.CollectionChangeType.INSERT); clm.removeChangeListener(data_manager_constants_1.USERS, data_manager_type_1.CollectionChangeType.UPDATE); clm.removeChangeListener(data_manager_constants_1.USERS, data_manager_type_1.CollectionChangeType.DELETE); }; /** * Loads all users from the database into the in-memory cache. * * @returns {Promise<void>} Resolves when users are loaded into the cache. */ const refreshCache = async () => { (0, exports._checkInitialization)(); exports._users.clear(); const userDocs = (await dm.getAllInCollection(data_manager_constants_1.USERS)) || []; userDocs.forEach((user) => { const jUser = transformUserDocument(user); exports._users.set(jUser.id, jUser); }); }; /** * Transforms a document to use `id` instead of `_id`. * @param {any} doc - The raw document from the database. * @returns {any} The transformed document. */ const transformUserDocument = (doc) => { const { _id, ...rest } = doc; return { id: _id === null || _id === void 0 ? void 0 : _id.toString(), ...rest }; }; /** * Sets up change listeners for user-related database changes. * @private */ const setupChangeListeners = () => { clm.addChangeListener(data_manager_constants_1.USERS, data_manager_type_1.CollectionChangeType.INSERT, (user) => { const jUser = transformUserDocument(user); exports._users.set(jUser.id, jUser); }); clm.addChangeListener(data_manager_constants_1.USERS, data_manager_type_1.CollectionChangeType.UPDATE, (user) => { const jUser = transformUserDocument(user); exports._users.set(jUser.id, jUser); }); clm.addChangeListener(data_manager_constants_1.USERS, data_manager_type_1.CollectionChangeType.DELETE, (userId) => { exports._users.delete(userId); }); }; /** * Ensures that the DataManager has been initialized before any user * management operation can proceed. * * @throws Error if DataManager is not initialized. * @private */ const _checkInitialization = () => { if (!dm.getInitializationStatus()) { throw new Error("UserManager has not been initialized"); } }; exports._checkInitialization = _checkInitialization; /** * Adds one user to the Users collection in a single operation. * @param {object} user - The user object to add. * @returns {Promise<JUser | null>} Resolves with the added user or null if the operation fails. * @throws {Error} If no user is provided or if the user fails validation. */ const addUser = async (user) => { (0, exports._checkInitialization)(); if (!user || typeof user !== "object" || Array.isArray(user)) { const msg = `Invalid user data: ${JSON.stringify(user)}. It must be a non-null object and should not be an array.`; logger_manager_1.Log.warn(msg); return null; } if (!user.uniqueIdentifier) { const msg = `UniqueIdentifier is missing`; logger_manager_1.Log.warn(msg); return null; } const userDataCheck = await isIdentifierUnique(user["uniqueIdentifier"]); if (!userDataCheck) { logger_manager_1.Log.warn(`User's unique identifier already exists. Skipping insertion: ${user.uniqueIdentifier}. `); return null; } try { const { uniqueIdentifier, initialAttributes } = user; const convertedUser = { uniqueIdentifier, attributes: initialAttributes }; const addedUser = (await dm.addItemToCollection(data_manager_constants_1.USERS, convertedUser)); exports._users.set(addedUser.id, addedUser); logger_manager_1.Log.info(`Added user: ${user.uniqueIdentifier}. `); return addedUser; } catch (error) { return (0, data_manager_helpers_1.handleDbError)("Failed to add users:", error); } }; exports.addUser = addUser; /** * Adds multiple users to the Users collection in a single operation. * @param {NewUserRecord[]} users - An array of user objects to add. * @returns {Promise<(JUser | null)[]>} Resolves with the added users or null if the operation fails. * @throws {Error} If no users are provided or if any user fails validation. */ const addUsers = async (users) => { if (!Array.isArray(users) || users.length === 0) { throw new Error("No users provided for insertion."); } try { let addedUsers = []; for (const user of users) { const addedUser = await (0, exports.addUser)(user); if (addedUser) { addedUsers.push(addedUser); } } if (addedUsers.length > 0) { logger_manager_1.Log.info(`${addedUsers.length} user(s) added successfully.`); } else { logger_manager_1.Log.info("No new users were added."); } return addedUsers; } catch (error) { return (0, data_manager_helpers_1.handleDbError)("Failed to add users:", error); } }; exports.addUsers = addUsers; /** * Retrieves all cached users. * * @returns {JUser[]} An array of all cached users. */ const getAllUsers = () => { (0, exports._checkInitialization)(); return Array.from(exports._users.values()); }; /** * Retrieves a user by their unique identifier from the cache. * @returns {JUser | null} The user with the specified unique identifier, or null if not found. */ const getUserByUniqueIdentifier = (uniqueIdentifier) => { (0, exports._checkInitialization)(); return Array.from(exports._users.values()).find(user => user.uniqueIdentifier === uniqueIdentifier) || null; }; /** * Update the properties of a user by uniqueIdentifier * @param {string} userUniqueIdentifier - the uniqueIdentifier value. * @param {object} attributesToUpdate - the data to update. * @returns {Promise<JUser | null>} Resolves with the updated JUser or `null` on error. * @throws {Error} If trying to update the uniqueIdentifier field directly. * If the user with the given uniqueIdentifier does not exist. * If the update operation fails. */ const updateUserByUniqueIdentifier = async (userUniqueIdentifier, attributesToUpdate) => { if (!userUniqueIdentifier || typeof userUniqueIdentifier !== "string") { const msg = `Invalid uniqueIdentifier: ${userUniqueIdentifier}`; throw new Error(msg); } if ("uniqueIdentifier" in attributesToUpdate) { const msg = `Cannot update uniqueIdentifier field using updateUserByUniqueIdentifier. Use modifyUserUniqueIdentifier instead.`; throw new Error(msg); } if (!attributesToUpdate || typeof attributesToUpdate !== "object" || Object.keys(attributesToUpdate).length === 0 || Array.isArray(attributesToUpdate)) { const msg = `Invalid updateData: ${JSON.stringify(attributesToUpdate)}. It must be a non-null and non-empty object and should not be an array.`; throw new Error(msg); } const theUser = await getUserByUniqueIdentifier(userUniqueIdentifier); if (!theUser) { const msg = `User with uniqueIdentifier (${userUniqueIdentifier}) not found.`; throw new Error(msg); } const { id, uniqueIdentifier, ...dataToUpdate } = attributesToUpdate; const updatedUser = await updateUserById(theUser.id, dataToUpdate); return updatedUser; }; /** * Updates a user's data in both the database and the in-memory cache. * * @param {string} userId - The user's ID. * @param {object} attributesToUpdate - New data to update. * @returns {Promise<JUser>} Resolves to the updated user. */ const updateUserById = async (userId, attributesToUpdate) => { (0, exports._checkInitialization)(); const existingUser = exports._users.get(userId); const mergedAttributes = { ...existingUser.attributes, ...attributesToUpdate }; const updatedUser = (await dm.updateItemByIdInCollection(data_manager_constants_1.USERS, userId, { attributes: mergedAttributes })); if (!updatedUser) { throw new Error(`Failed to update user: ${userId}`); } exports._users.set(updatedUser.id, updatedUser); return updatedUser; }; /** * Deletes a user by ID from both the database and the in-memory cache. * * @param {string} userId - The user's ID. * @returns {Promise<void>} Resolves when deletion is complete. */ const deleteUserById = async (userId) => { (0, exports._checkInitialization)(); await dm.removeItemFromCollection(data_manager_constants_1.USERS, userId); exports._users.delete(userId); }; /** * Deletes a user by ID from both the database and the in-memory cache. * * @param {string} userId - The user's ID. * @returns {Promise<void>} Resolves when deletion is complete. */ const deleteUserByUniqueIdentifier = async (uniqueIdentifier) => { const theUser = await getUserByUniqueIdentifier(uniqueIdentifier); await deleteUserById(theUser.id); exports._users.delete(theUser.id); }; /** * Deletes all users from the database and clears the in-memory cache. * * @returns {Promise<void>} Resolves when all users are deleted. */ const deleteAllUsers = async () => { (0, exports._checkInitialization)(); await dm.clearCollection(data_manager_constants_1.USERS); exports._users.clear(); }; /** * Check for unique identifier duplication. * @param {string} userUniqueIdentifier - the unique identifier. * @returns {Promise<boolean>} Resolves with a boolean indicating if the identifier is unique. * If the unique identifier is new, it returns true; otherwise, it returns false. */ const isIdentifierUnique = async (userUniqueIdentifier) => { if (!userUniqueIdentifier || typeof userUniqueIdentifier !== "string" || userUniqueIdentifier.trim() === "") { const msg = `Invalid unique identifier: ${userUniqueIdentifier}`; throw new Error(msg); } const existingUser = await getUserByUniqueIdentifier(userUniqueIdentifier); if (existingUser) { const msg = `User with unique identifier (${userUniqueIdentifier}) already exists.`; logger_manager_1.Log.dev(msg); return false; } return true; }; /** * UserManager provides methods for managing users. * * Includes user creation, deletion, retrieval, and updates. * @namespace UserManager */ exports.UserManager = { init, addUser: exports.addUser, addUsers: exports.addUsers, getAllUsers, getUserByUniqueIdentifier, updateUserByUniqueIdentifier, deleteUserByUniqueIdentifier, deleteAllUsers, shutdown, }; /** * TestingUserManager provides additional utilities for testing. * * @namespace TestingUserManager * @private */ exports.TestingUserManager = { ...exports.UserManager, updateUserById, deleteUserById, transformUserDocument, _checkInitialization: exports._checkInitialization, refreshCache, isIdentifierUnique, setupChangeListeners, _users: exports._users, // Exposes the in-memory cache for testing purposes }; //# sourceMappingURL=user-manager.js.map