Collection Components Developer Guide¶
This document explains the role, API surface and usage patterns for the Collection, CollectionMetadata, and DocumentOperations classes in JsonDbApp. These three classes work together to provide MongoDB-compatible collection operations with Google Drive persistence.
- Collection Components Developer Guide
- Overview
- Collection
- Purpose
- Constructor
- Public Methods
- insertOne(doc: Object): Object
- findOne(filter?: Object): Object|null
- find(filter?: Object): Array
- updateOne(filter: Object, update: Object): Object
- updateMany(filter: Object, update: Object): Object
- replaceOne(filter: Object, replacement: Object): Object
- deleteOne(filter: Object): Object
- countDocuments(filter?: Object): number
- getName(): string
- getMetadata(): Object
- isDirty(): boolean
- save(): void
- Lazy Loading
- CollectionMetadata
- Purpose
- Constructor
- Public Methods
updateLastModified(): voidtouch(): voidincrementDocumentCount(): voiddecrementDocumentCount(): voidsetDocumentCount(count: number): voidgetModificationToken(): string | nullsetModificationToken(token: string | null): voidgetLockStatus(): Object | nullsetLockStatus(lockStatus: Object | null): voidtoJSON(): Objectclone(): CollectionMetadata- Static Methods
static fromJSON(obj: Object): CollectionMetadatastatic create(name: string, fileId: string): CollectionMetadata
- DocumentOperations
- Purpose
- Constructor
- Public Methods
- insertDocument(doc: Object): Object
- findDocumentById(id: string): Object|null
- findAllDocuments(): Array\<Object>
- updateDocument(id: string, updateData: Object): Object
- deleteDocument(id: string): Object
- countDocuments(): number
- documentExists(id: string): boolean
- findByQuery(query: Object): Object|null
- findMultipleByQuery(query: Object): Array\<Object>
- countByQuery(query: Object): number
- updateDocumentWithOperators(id: string, updateOps: Object): Object
- updateDocumentByQuery(query: Object, updateOps: Object): number
- replaceDocument(id: string, doc: Object): Object
- replaceDocumentByQuery(query: Object, doc: Object): number
- Private Methods
Overview¶
The Collection Components form the core of JsonDbApp's document storage system:
- Collection: The main interface providing MongoDB-compatible CRUD operations
- CollectionMetadata: Manages collection metadata (timestamps, document counts)
- DocumentOperations: Handles low-level document manipulation
Architecture¶
graph TD
Collection[Collection (High-level MongoDB API)] --> CollectionMetadata[CollectionMetadata (Metadata management)]
Collection --> DocumentOperations[DocumentOperations (Document CRUD operations)]
DocumentOperations --> QueryEngine[QueryEngine (Query processing)]
DocumentOperations --> UpdateEngine[UpdateEngine (Update operation processing)]
Current Limitations (Section 5)¶
This section describes the capabilities as of Section 5. The Collection class has since been enhanced. Refer to the method descriptions below for current capabilities.
- findOne(): Only supports
{}(empty) and{_id: "id"}filters - find(): Only supports
{}(empty) filters - updateOne(): Only supports
{_id: "id"}filters, document replacement only (no operators) - deleteOne(): Only supports
{_id: "id"}filters - countDocuments(): Only supports
{}(empty) filters
Advanced query capabilities will be added in Section 6 (Query Engine) and Section 7 (Update Engine).
Collection¶
Purpose¶
Collection provides the primary interface for document operations, mimicking MongoDB's collection API whilst managing persistence to Google Drive.
Key responsibilities:
- MongoDB-compatible CRUD operations
- Lazy loading from Google Drive
- Dirty tracking and persistence
- Integration with CollectionMetadata and DocumentOperations
- Clear error messages for unsupported features
Constructor¶
- name: Collection name (non-empty string)
- driveFileId: Google Drive file ID for persistence
- database: Database instance reference
- fileService: FileService for Drive operations
Throws: InvalidArgumentError for invalid parameters
Example:
// Typically created by Database class, not directly
const collection = new Collection(
'users',
'1a2b3c4d5e6f7g8h9i0j',
databaseInstance,
fileServiceInstance
);
Public Methods¶
insertOne(doc: Object): Object¶
Insert a single document with automatic ID generation.
- Parameters
doc: Document object to insert- Returns
{insertedId: string, acknowledged: boolean}- Throws
InvalidArgumentErrorfor invalid documentsConflictErrorfor duplicate IDs
Example:
// Insert with auto-generated ID
const result1 = collection.insertOne({
name: 'Alice',
email: 'alice@example.com',
age: 30
});
console.log('Generated ID:', result1.insertedId);
// Insert with specific ID
const result2 = collection.insertOne({
_id: 'user123',
name: 'Bob',
email: 'bob@example.com'
});
console.log('Custom ID:', result2.insertedId); // 'user123'
// Error: duplicate ID
try {
collection.insertOne({ _id: 'user123', name: 'Charlie' });
} catch (error) {
console.error('Conflict:', error.message);
}
findOne(filter?: Object): Object|null¶
Find a single document matching the filter.
- Parameters
filter: Query filter (supports field-based queries,{_id: "id"}and empty{}filters)- Returns
- Document object or
nullif not found - Throws
InvalidArgumentErrorfor invalid filter structureOperationErrorfor unsupported filters
Example:
// Find first document (empty filter)
const firstDoc = collection.findOne({});
console.log('First document:', firstDoc);
// Find by ID
const userDoc = collection.findOne({ _id: 'user123' });
if (userDoc) {
console.log('Found user:', userDoc.name);
} else {
console.log('User not found');
}
// Find by field
const aliceDoc = collection.findOne({ name: 'Alice' });
if (aliceDoc) {
console.log('Found Alice by name:', aliceDoc.email);
}
// Unsupported filter (throws error)
try {
collection.findOne({ age: { $gt: 25 } }); // Complex operators may not be supported depending on QueryEngine
} catch (error) {
console.error('Unsupported:', error.message);
}
find(filter?: Object): Array¶
Find multiple documents matching the filter.
- Parameters
filter: Query filter (supports field-based queries and empty{}filter)- Returns
- Array of document objects
- Throws
InvalidArgumentErrorfor invalid filter structureOperationErrorfor unsupported filters
Example:
// Find all documents
const allDocs = collection.find({});
console.log('Total documents:', allDocs.length);
allDocs.forEach((doc) => {
console.log('Document ID:', doc._id);
});
// Find by field
const usersOver25 = collection.find({ age: { $gt: 25 } }); // Example, assuming QueryEngine supports $gt
console.log('Users over 25:', usersOver25.length);
// Empty collection returns empty array
const emptyResults = emptyCollection.find({});
console.log('Empty results:', emptyResults); // []
updateOne(filter: Object, update: Object): Object¶
Update a single document matching the filter.
- Parameters
filter: Query filter (supports field-based queries and{_id: "id"}filters)update: Document replacement or update operators (e.g.{$set: {field: value}})- Returns
{matchedCount: number, modifiedCount: number, acknowledged: boolean}- Throws
InvalidArgumentErrorfor invalid parameters (filter or update)OperationErrorfor unsupported filters or update operators
Example:
// Update by ID (document replacement)
const updateResult = collection.updateOne(
{ _id: 'user123' },
{
// _id: 'user123', // ID is preserved automatically if not specified in replacement
name: 'Robert',
email: 'robert@example.com',
age: 31,
lastUpdated: new Date()
}
);
console.log('Modified count (replacement):', updateResult.modifiedCount); // 1
// Update by field using $set operator
const setResult = collection.updateOne({ email: 'robert@example.com' }, { $set: { age: 32 } });
console.log('Modified count ($set):', setResult.modifiedCount); // 1
// Non-existent document
const noMatch = collection.updateOne({ _id: 'nonexistent' }, { name: 'Nobody' });
console.log('No match:', noMatch.modifiedCount); // 0
// Unsupported update operators (throws error)
try {
collection.updateOne(
{ _id: 'user123' },
{ $inc: { age: 1 } } // Assuming $inc is supported by UpdateEngine
);
} catch (error) {
console.error('Unsupported operator or error during update:', error.message);
}
updateMany(filter: Object, update: Object): Object¶
Update multiple documents matching a filter.
- Parameters
filter: Query filter criteria (supports field-based queries and empty{}filter)update: Update operators (e.g.{$set: {field: value}})- Returns
{matchedCount: number, modifiedCount: number, acknowledged: boolean}- Throws
InvalidArgumentErrorfor invalid parametersOperationErrorif update operators are invalid or an error occurs
Example:
// Add a 'status' field to all documents
const updateManyResult = collection.updateMany(
{}, // Empty filter matches all documents
{ $set: { status: 'active' } }
);
console.log('Matched:', updateManyResult.matchedCount, 'Modified:', updateManyResult.modifiedCount);
// Update status for users older than 30
const updateAdultsResult = collection.updateMany(
{ age: { $gt: 30 } }, // Assuming QueryEngine supports $gt
{ $set: { status: 'senior' } }
);
console.log('Adults updated:', updateAdultsResult.modifiedCount);
replaceOne(filter: Object, replacement: Object): Object¶
Replace a single document matching the filter.
- Parameters
filter: Query filter (supports field-based queries and{_id: "id"}filters)replacement: The new document. Cannot contain update operators._idif present must match original or be omitted.- Returns
{matchedCount: number, modifiedCount: number, acknowledged: boolean}- Throws
InvalidArgumentErrorfor invalid parameters or if replacement contains update operators.
Example:
// Replace document by ID
const replaceResult = collection.replaceOne(
{ _id: 'user123' },
{ name: 'Bobby Tables', email: 'bobby@example.com', age: 25 } // _id will be preserved
);
console.log('Replaced count:', replaceResult.modifiedCount); // 1
// Attempt to replace with update operators (will throw error)
try {
collection.replaceOne({ _id: 'user123' }, { $set: { name: 'Not Allowed' } });
} catch (error) {
console.error('Error:', error.message); // Should indicate $set is not allowed
}
deleteOne(filter: Object): Object¶
Delete a single document matching the filter.
- Parameters
filter: Query filter (supports field-based queries and{_id: "id"}filters)- Returns
{deletedCount: number, acknowledged: boolean}
- Throws
InvalidArgumentErrorfor invalid filter structureOperationErrorfor unsupported filters
Example:
// Delete by ID
const deleteResult = collection.deleteOne({ _id: 'user123' });
console.log('Deleted count by ID:', deleteResult.deletedCount); // 1
// Delete by field
const deleteByEmailResult = collection.deleteOne({ email: 'alice@example.com' });
console.log('Deleted count by email:', deleteByEmailResult.deletedCount); // 1
// Non-existent document
const noDelete = collection.deleteOne({ _id: 'nonexistent' });
console.log('Nothing deleted:', noDelete.deletedCount); // 0
// Document no longer exists
const gone = collection.findOne({ _id: 'user123' });
console.log('Document gone:', gone); // null
countDocuments(filter?: Object): number¶
Count documents matching the filter.
- Parameters
filter: Query filter (supports field-based queries and empty{}filter)- Returns
Number of matching documents
- Throws
InvalidArgumentErrorfor invalid filter structureOperationErrorfor unsupported filters
Example:
// Count all documents
const totalCount = collection.countDocuments({});
console.log('Total documents:', totalCount);
// Count documents matching a filter
const countOver30 = collection.countDocuments({ age: { $gt: 30 } }); // Assuming QueryEngine supports $gt
console.log('Documents with age > 30:', countOver30);
// Empty collection
const emptyCount = emptyCollection.countDocuments();
console.log('Empty count:', emptyCount); // 0
// Verify count matches find() results
const allDocs = collection.find({});
console.log('Count matches:', totalCount === allDocs.length); // true
getName(): string¶
Get the collection name.
getMetadata(): Object¶
Get collection metadata.
- Returns
{created: Date, lastUpdated: Date, documentCount: number}
const metadata = collection.getMetadata();
console.log('Created:', metadata.created);
console.log('Last updated:', metadata.lastUpdated);
console.log('Document count:', metadata.documentCount);
isDirty(): boolean¶
Check if collection has unsaved changes.
console.log('Has changes:', collection.isDirty()); // false
collection.insertOne({ name: 'Test' });
console.log('Has changes:', collection.isDirty()); // true
collection.save();
console.log('Has changes:', collection.isDirty()); // false
save(): void¶
Force save collection to Drive.
- Throws:
OperationErrorif save fails
// Manual save (usually automatic)
try {
collection.save();
console.log('Collection saved');
} catch (error) {
console.error('Save failed:', error.message);
}
Lazy Loading¶
Collections use lazy loading for performance:
const collection = new Collection(...); // No Drive access yet
const metadata = collection.getMetadata(); // Triggers loading
const docs = collection.find({}); // Already loaded, no Drive access
CollectionMetadata¶
Purpose¶
CollectionMetadata manages essential information about a collection. This includes its identity (name and file ID), timestamps for creation and updates, the number of documents it contains, a token for managing concurrent modifications, and its current lock status. It ensures that metadata is always in a valid state and provides methods for its manipulation and serialisation.
Constructor¶
or (legacy support)
- name (string): The name of the collection. This is a required field.
- fileId (string): The Google Drive file ID where the collection data is stored. This is a required field.
- initialMetadata (optional Object): An object containing initial values for metadata properties.
created: Creation timestamp (Date). Defaults to the current time.lastUpdated: Last update timestamp (Date). Defaults to the current time.documentCount: Document count (integer ≥ 0). Defaults to 0.modificationToken: A string token for optimistic locking, ornull. Defaults tonull.lockStatus: An object detailing the lock state, ornull. Defaults tonull.isLocked: (boolean) Whether the collection is locked.lockedBy: (string|null) Identifier of the process/user holding the lock.lockedAt: (number|null) Timestamp (epoch milliseconds) when the lock was acquired.lockTimeout: (number|null) Duration in milliseconds for which the lock is valid.
Throws: InvalidArgumentError for invalid or missing name, fileId, or other metadata values.
Example:
// Create with name, fileId, and defaults for other properties
const metadata1 = new CollectionMetadata('users', 'fileId123');
console.log('Collection Name:', metadata1.name); // 'users'
console.log('File ID:', metadata1.fileId); // 'fileId123'
console.log('Default count:', metadata1.documentCount); // 0
// Create with name, fileId, and specific initial values
const metadata2 = new CollectionMetadata('products', 'fileId456', {
created: new Date('2023-01-01'),
lastUpdated: new Date('2023-06-15'),
documentCount: 42,
modificationToken: 'initialToken123',
lockStatus: { isLocked: false, lockedBy: null, lockedAt: null, lockTimeout: null }
});
console.log('Product count:', metadata2.documentCount); // 42
console.log('Modification Token:', metadata2.getModificationToken()); // 'initialToken123'
// Invalid values throw errors
try {
new CollectionMetadata('', 'fileId789'); // Empty name
} catch (error) {
console.error('Invalid name:', error.message);
}
try {
new CollectionMetadata('orders', null); // Missing fileId
} catch (error) {
console.error('Invalid fileId:', error.message);
}
try {
new CollectionMetadata('inventory', 'fileIdABC', { documentCount: -1 });
} catch (error) {
console.error('Invalid count:', error.message);
}
Public Methods¶
updateLastModified(): void¶
Updates the lastUpdated timestamp to the current time.
const metadata = new CollectionMetadata('logs', 'logFileId');
const oldTime = metadata.lastUpdated;
// Wait a moment...
setTimeout(() => {
metadata.updateLastModified();
console.log('Updated:', metadata.lastUpdated > oldTime); // true
}, 10);
touch(): void¶
Alias for updateLastModified(). Updates the lastUpdated timestamp to the current time. This is typically used to signify that the collection metadata has been accessed or "touched" without necessarily changing other data like document count.
const metadata = new CollectionMetadata('cache', 'cacheFileId');
const oldTime = metadata.lastUpdated;
metadata.touch();
console.log('Touched, lastUpdated changed:', metadata.lastUpdated > oldTime); // true (assuming a slight delay)
incrementDocumentCount(): void¶
Increments the documentCount by 1 and updates the lastUpdated timestamp.
const metadata = new CollectionMetadata('tasks', 'tasksFileId');
console.log('Initial count:', metadata.documentCount); // 0
metadata.incrementDocumentCount();
console.log('After increment:', metadata.documentCount); // 1
decrementDocumentCount(): void¶
Decrements the documentCount by 1 and updates the lastUpdated timestamp.
- Throws:
InvalidArgumentErrorifdocumentCountwould go below zero.
const metadata = new CollectionMetadata('items', 'itemsFileId', { documentCount: 5 });
metadata.decrementDocumentCount();
console.log('After decrement:', metadata.documentCount); // 4
// Cannot go below zero
try {
const emptyMetadata = new CollectionMetadata('empty', 'emptyFileId', { documentCount: 0 });
emptyMetadata.decrementDocumentCount(); // Throws error
} catch (error) {
console.error('Cannot decrement:', error.message);
}
setDocumentCount(count: number): void¶
Sets the documentCount to a specific value and updates the lastUpdated timestamp.
- Parameters:
count: The new document count (must be an integer ≥ 0).- Throws:
InvalidArgumentErrorfor invalidcountvalues (e.g., negative, not an integer).
const metadata = new CollectionMetadata('notes', 'notesFileId');
metadata.setDocumentCount(100);
console.log('Set count:', metadata.documentCount); // 100
// Invalid values throw errors
try {
metadata.setDocumentCount(-5); // Throws error
} catch (error) {
console.error('Invalid count for setDocumentCount:', error.message);
}
getModificationToken(): string | null¶
Returns the current modificationToken. This token can be used for optimistic concurrency control.
const metadata = new CollectionMetadata('configs', 'configsFileId', { modificationToken: 'v1' });
console.log(metadata.getModificationToken()); // 'v1'
setModificationToken(token: string | null): void¶
Sets the modificationToken.
- Parameters:
token: The new modification token (string ornull). Must be a non-empty string if notnull.- Throws:
InvalidArgumentErrorfor invalid token values.
const metadata = new CollectionMetadata('settings', 'settingsFileId');
metadata.setModificationToken('v2-alpha');
console.log(metadata.getModificationToken()); // 'v2-alpha'
metadata.setModificationToken(null);
console.log(metadata.getModificationToken()); // null
try {
metadata.setModificationToken(''); // Empty string
} catch (error) {
console.error('Invalid token:', error.message);
}
getLockStatus(): Object | null¶
Returns the current lockStatus object or null if no lock information is set.
The lockStatus object has the shape: { isLocked: boolean, lockedBy: string|null, lockedAt: number|null, lockTimeout: number|null }.
const lockInfo = {
isLocked: true,
lockedBy: 'process-1',
lockedAt: Date.now(),
lockTimeout: 30000
};
const metadata = new CollectionMetadata('jobs', 'jobsFileId', { lockStatus: lockInfo });
console.log(metadata.getLockStatus()); // { isLocked: true, ... }
setLockStatus(lockStatus: Object | null): void¶
Sets the lockStatus.
- Parameters:
lockStatus: The new lock status object ornull. If an object, it must conform to the required structure and types.- Throws:
InvalidArgumentErrorfor invalidlockStatusobject structure or values.
const metadata = new CollectionMetadata('queue', 'queueFileId');
const newLock = {
isLocked: true,
lockedBy: 'worker-bee',
lockedAt: Date.now(),
lockTimeout: 60000
};
metadata.setLockStatus(newLock);
console.log(metadata.getLockStatus()); // { isLocked: true, lockedBy: 'worker-bee', ... }
metadata.setLockStatus(null);
console.log(metadata.getLockStatus()); // null
try {
metadata.setLockStatus({ isLocked: 'yes' }); // Invalid type for isLocked
} catch (error) {
console.error('Invalid lock status:', error.message);
}
toJSON(): Object¶
Returns the metadata as a plain JavaScript object, suitable for JSON.stringify(). This method includes a __type property for potential deserialisation and ensures all date properties are new Date instances.
- Returns: A plain object with all metadata properties:
{ __type: 'CollectionMetadata', name: string, fileId: string, created: Date, lastUpdated: Date, documentCount: number, modificationToken: string|null, lockStatus: Object|null }
const metadata = new CollectionMetadata('dataEntries', 'dataFileId1', {
documentCount: 10,
modificationToken: 'token-xyz'
});
const obj = metadata.toJSON();
console.log('Plain object:', obj);
/*
{
__type: 'CollectionMetadata',
name: 'dataEntries',
fileId: 'dataFileId1',
created: Date, // (a new Date instance)
lastUpdated: Date, // (a new Date instance)
documentCount: 10,
modificationToken: 'token-xyz',
lockStatus: null
}
*/
// Dates are independent copies
const originalCreationYear = metadata.created.getFullYear();
obj.created.setFullYear(2000);
console.log(
'Original created year unchanged:',
metadata.created.getFullYear() === originalCreationYear
); // true
clone(): CollectionMetadata¶
Creates and returns a new CollectionMetadata instance that is an independent, deep copy of the original.
const original = new CollectionMetadata('source', 'sourceFile', { documentCount: 5 });
const copy = original.clone();
copy.incrementDocumentCount();
console.log('Original unchanged:', original.documentCount); // 5
console.log('Copy changed:', copy.documentCount); // 6
// Ensure deep copy of nested objects like lockStatus if present
const lock = { isLocked: true, lockedBy: 'test', lockedAt: Date.now(), lockTimeout: 1000 };
const originalWithLock = new CollectionMetadata('lockedColl', 'lockFile', { lockStatus: lock });
const copyWithLock = originalWithLock.clone();
copyWithLock.getLockStatus().isLocked = false;
console.log('Original lock status unchanged:', originalWithLock.getLockStatus().isLocked); // true
console.log('Copy lock status changed:', copyWithLock.getLockStatus().isLocked); // false
Static Methods¶
static fromJSON(obj: Object): CollectionMetadata¶
Creates a CollectionMetadata instance from a plain JavaScript object (typically one produced by toJSON()).
- Parameters:
obj: A plain object containing metadata properties.- Returns: A new
CollectionMetadatainstance. - Throws:
InvalidArgumentErrorif the input object is invalid or missing required properties.
const plainObject = {
name: 'deserialisedCollection',
fileId: 'deserialisedFileId',
created: new Date('2022-01-01T10:00:00Z').toISOString(), // Dates can be ISO strings or Date objects
lastUpdated: new Date('2022-01-02T11:00:00Z'),
documentCount: 15,
modificationToken: 'tokenFromObject',
lockStatus: null
};
// Note: For fromJSON to correctly parse date strings, they should be in a format
// that the Date constructor can parse, or be actual Date objects.
// The constructor of CollectionMetadata handles new Date(value) for date fields.
const metadataInstance = CollectionMetadata.fromJSON(plainObject);
console.log('Instance name:', metadataInstance.name); // 'deserialisedCollection'
console.log('Instance document count:', metadataInstance.documentCount); // 15
console.log('Instance modification token:', metadataInstance.getModificationToken()); // 'tokenFromObject'
static create(name: string, fileId: string): CollectionMetadata¶
A static factory method to create a new CollectionMetadata instance with the specified name and fileId, and default values for other properties.
- Parameters:
name: The collection name.fileId: The Google Drive file ID.- Returns: A new
CollectionMetadatainstance. - Throws:
InvalidArgumentErrorifnameorfileIdare invalid.
const newMetadata = CollectionMetadata.create('newCollection', 'newFileIdXYZ');
console.log('Created name:', newMetadata.name); // 'newCollection'
console.log('Created fileId:', newMetadata.fileId); // 'newFileIdXYZ'
console.log('Created doc count:', newMetadata.documentCount); // 0
DocumentOperations¶
Purpose¶
DocumentOperations is a low-level component responsible for the direct manipulation of documents within a collection's in-memory representation. It provides the core Create, Read, Update, and Delete (CRUD) functionalities that are utilised by the higher-level Collection class. DocumentOperations itself does not interact with the file system or manage metadata; its focus is solely on operating on the JavaScript objects that represent documents.
It works in conjunction with:
- Collection: The
Collectionclass instantiates and usesDocumentOperationsto perform actions on its_documentsobject. - QueryEngine: For
findByQuery,findMultipleByQuery, andcountByQuerymethods,DocumentOperationsdelegates the filtering logic to theQueryEngine. - UpdateEngine: For
updateDocumentWithOperatorsandupdateDocumentByQuerymethods,DocumentOperationsuses theUpdateEngineto apply complex update operations.
Constructor¶
- collection: A reference to the
Collectioninstance. This providesDocumentOperationsaccess to the actual documents array (collection._documents) and methods to update metadata (collection._updateMetadata()) and mark the collection as dirty (collection._markDirty()).
Throws: InvalidArgumentError if the collection parameter is not a valid Collection instance (though this is typically ensured by the Collection class itself).
Example:
// Typically created by the Collection class, not directly
// Assuming 'this' is an instance of the Collection class
this._documentOperations = new DocumentOperations(this);
Public Methods¶
insertDocument(doc: Object): Object¶
Inserts a single document into the collection's internal _documents object. If the document does not have an _id field, one is generated.
- Parameters
doc: The document object to insert.- Returns
- The inserted document, including its
_id. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If the document is invalid (e.g.,null, not an object, or contains invalid field names).ErrorHandler.ErrorTypes.CONFLICT_ERROR: If a document with the same_idalready exists.
Example:
// Assuming docOps is an instance of DocumentOperations
const newDoc = docOps.insertDocument({ name: 'Alice', age: 30 });
console.log('Inserted document ID:', newDoc._id);
try {
docOps.insertDocument({ _id: newDoc._id, name: 'Bob' });
} catch (e) {
console.error(e.message); // Conflict error
}
findDocumentById(id: string): Object|null¶
Retrieves a document by its _id.
- Parameters
id: The_idof the document to find.- Returns
- The found document object, or
nullif no document with that_idexists. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If theidis invalid (e.g., not a string or empty).
Example:
const user = docOps.findDocumentById('some-unique-id');
if (user) {
console.log('Found user:', user.name);
} else {
console.log('User not found.');
}
findAllDocuments(): Array<Object>¶
Retrieves all documents currently held in the collection's _documents object.
- Returns
- An array of all document objects. Returns an empty array if the collection is empty.
Example:
updateDocument(id: string, updateData: Object): Object¶
Replaces the entire document identified by id with updateData. This is a full replacement, not a partial update. The _id in updateData must match the id parameter if provided, or updateData should not contain an _id (in which case the original _id is preserved).
- Parameters
id: The_idof the document to update.updateData: The new document data.- Returns
- An object
{ acknowledged: boolean, modifiedCount: number }.modifiedCountis 1 if the document was found and updated, 0 otherwise. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: IfidorupdateDatais invalid, or ifupdateData._idis present and does not matchid.ErrorHandler.ErrorTypes.DOCUMENT_NOT_FOUND: If no document with the givenidexists. (Note: current implementation returnsmodifiedCount: 0instead of throwing)
Example:
const result = docOps.updateDocument('some-unique-id', { name: 'Alice Smith', age: 31 });
if (result.modifiedCount > 0) {
console.log('Document updated.');
} else {
console.log('Document not found or not modified.');
}
deleteDocument(id: string): Object¶
Deletes a document by its _id.
- Parameters
id: The_idof the document to delete.- Returns
- An object
{ acknowledged: boolean, deletedCount: number }.deletedCountis 1 if the document was found and deleted, 0 otherwise. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If theidis invalid.
Example:
const result = docOps.deleteDocument('some-unique-id');
if (result.deletedCount > 0) {
console.log('Document deleted.');
} else {
console.log('Document not found.');
}
countDocuments(): number¶
Counts the total number of documents in the collection.
- Returns
- The total number of documents.
Example:
documentExists(id: string): boolean¶
Checks if a document with the given _id exists.
- Parameters
id: The_idto check.- Returns
trueif a document with the_idexists,falseotherwise.- Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If theidis invalid.
Example:
findByQuery(query: Object): Object|null¶
Finds the first document that matches the given query. Delegates to QueryEngine.
- Parameters
query: A MongoDB-compatible query object.- Returns
- The first matching document, or
nullif no documents match. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If the query is invalid.ErrorHandler.ErrorTypes.INVALID_QUERY: If the query contains invalid operators (as determined byQueryEngine).
Example:
const user = docOps.findByQuery({ age: { $gt: 30 } });
if (user) {
console.log('Found user older than 30:', user.name);
}
findMultipleByQuery(query: Object): Array<Object>¶
Finds all documents that match the given query. Delegates to QueryEngine.
- Parameters
query: A MongoDB-compatible query object.- Returns
- An array of matching documents. Returns an empty array if no documents match.
- Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If the query is invalid.ErrorHandler.ErrorTypes.INVALID_QUERY: If the query contains invalid operators.
Example:
const admins = docOps.findMultipleByQuery({ role: 'admin' });
admins.forEach((admin) => console.log(admin.name));
countByQuery(query: Object): number¶
Counts documents that match the given query. Delegates to QueryEngine.
- Parameters
query: A MongoDB-compatible query object.- Returns
- The number of matching documents.
- Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: If the query is invalid.ErrorHandler.ErrorTypes.INVALID_QUERY: If the query contains invalid operators.
Example:
const activeUserCount = docOps.countByQuery({ status: 'active' });
console.log(`Number of active users: ${activeUserCount}`);
updateDocumentWithOperators(id: string, updateOps: Object): Object¶
Updates a single document identified by id using MongoDB-style update operators (e.g., $set, $inc). Delegates to UpdateEngine.
- Parameters
id: The_idof the document to update.updateOps: An object containing update operator instructions.- Returns
- An object
{ acknowledged: boolean, modifiedCount: number }. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: IfidorupdateOpsare invalid.ErrorHandler.ErrorTypes.DOCUMENT_NOT_FOUND: If no document with the givenidexists.ErrorHandler.ErrorTypes.INVALID_QUERY: IfupdateOpsare invalid (as determined byUpdateEngine).
Example:
const result = docOps.updateDocumentWithOperators('some-unique-id', {
$set: { status: 'inactive' },
$inc: { loginAttempts: 1 }
});
if (result.modifiedCount > 0) {
console.log('Document updated with operators.');
}
updateDocumentByQuery(query: Object, updateOps: Object): number¶
Updates all documents matching the query using MongoDB-style update operators. Delegates to QueryEngine for finding documents and UpdateEngine for applying updates.
- Parameters
query: The filter criteria to select documents for update.updateOps: The update operator instructions.- Returns
- The number of documents modified.
- Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: IfqueryorupdateOpsare invalid.ErrorHandler.ErrorTypes.INVALID_QUERY: IfupdateOpsare invalid. (Note:DOCUMENT_NOT_FOUNDis listed in JSDoc but typically results in 0 modified documents rather than an error for "update many" type operations).
Example:
const updatedCount = docOps.updateDocumentByQuery(
{ department: 'Sales' },
{ $set: { onQuota: true } }
);
console.log(`${updatedCount} sales documents updated.`);
replaceDocument(id: string, doc: Object): Object¶
Replaces a single document identified by id with the new doc. The _id in doc must match id or be omitted.
- Parameters
id: The_idof the document to replace.doc: The replacement document.- Returns
- An object
{ acknowledged: boolean, modifiedCount: number }. - Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: Ifidordocis invalid, or ifdoc._idis present and mismatchesid.ErrorHandler.ErrorTypes.DOCUMENT_NOT_FOUND: If no document with the givenidexists.
Example:
const result = docOps.replaceDocument('some-unique-id', { name: 'New Name', value: 'New Value' });
if (result.modifiedCount > 0) {
console.log('Document replaced.');
}
replaceDocumentByQuery(query: Object, doc: Object): number¶
Replaces the first document matching the query with the new doc. Note: MongoDB's replaceOne is more aligned with replaceDocument. This method's name might be slightly misleading if it replaces multiple, but the current implementation seems to target one. If it's intended to replace multiple, the return type and logic would differ. Assuming it replaces one based on typical replace semantics.
- Parameters
query: The filter criteria to select the document for replacement.doc: The replacement document.- Returns
- The number of documents replaced (0 or 1).
- Throws
ErrorHandler.ErrorTypes.INVALID_ARGUMENT: Ifqueryordocis invalid.
Example:
const replacedCount = docOps.replaceDocumentByQuery(
{ status: 'pending' },
{ status: 'approved', approvedBy: 'admin' }
);
console.log(`${replacedCount} document(s) replaced.`);
Private Methods¶
DocumentOperations includes several private helper methods for validation, ID generation, and query orchestration:
Validation & ID Generation:
_generateDocumentId(): Generates a unique ID for new documents._validateDocument(doc): Validates the overall document structure and content._validateDocumentId(id): Validates a standalone document ID._validateDocumentIdInDocument(id, doc): Validates an_idfield within a document._validateDocumentFields(doc): Validates field names within a document._checkDuplicateId(id): Ensures an_idis not already in use before insertion._validateQuery(query): Basic validation for query objects._validateUpdateOperators(updateOps): Basic validation for update operator objects.
Query Execution (D1 Refactoring):
_executeQuery(query, operation): Consolidates query validation, document retrieval, QueryEngine execution, and logging. Used byfindByQuery(),findMultipleByQuery(), andcountByQuery().
Bulk Operations (D2 Refactoring):
_applyToMatchingDocuments(query, applyFn, throwIfNoMatches): Unifies match/apply pattern for query-based bulk operations. Finds matching documents, applies callback function, and accumulates affected count. Used byupdateDocumentByQuery()andreplaceDocumentByQuery().
These private methods ensure data integrity, consistent error handling, and DRY principles within the component.