@just-in/core
Version:
A TypeScript-first framework for building adaptive digital health interventions.
316 lines • 12.6 kB
JavaScript
;
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