├── .github └── pull_request_template.md ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── assets ├── dangoDB_logo_long_midboi.png └── dango_deno.png ├── deps.ts ├── lib ├── connections.ts ├── dango.ts ├── datatypes.ts ├── model.ts ├── query.ts └── schema.ts ├── mod.ts └── tests ├── test_connections.ts ├── test_datatypes.ts ├── test_query.ts └── test_schema.ts /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Checklist 2 | 3 | - [ ] Bugfix 4 | - [ ] New feature 5 | - [ ] Refactor 6 | 7 | # Related Issue 8 | 9 | - the problem you are solving goes here. 10 | 11 | # Solution 12 | 13 | - solution to the problem goes here here. Why did you solve this problem the way you did? 14 | 15 | # Additional Info 16 | 17 | - Any additional information or context -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | internal/* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | dangoDB logo 3 |
4 | 5 |

dangoDB

6 | 7 |
A MongoDB ODM for Deno
8 |
9 |
Visit our Website
10 |
11 |
Read our Medium Launch Article
12 |
13 | 14 | ## Table of Contents 15 | 16 | 1. [Description](#description) 17 | 2. [Getting Started](#get-started) 18 | 3. [Query](#query) 19 | 4. [Schema](#schema) 20 | 5. [Authors](#authors) 21 | 6. [License](#license) 22 | 23 | ## Description 24 | 25 | dangoDB is a light-weight MongoDB Object Document Mapper (ODM) library built for the Deno runtime. It provides the core functionality and familiar look and feel of established Node-based libraries. With dangoDB, developers can construct schemas and models, and they can enforce strict type-casting and schema validation to structure their databases. The query functions available from the deno_mongo driver can all be accessed with ease. 26 | 27 | In addition, we built a user-friendly web-based [GUI](https://dangodb.land/#/schema) that auto-generates schema for users to copy and paste directly into their code. 28 | 29 | 30 | ## Getting Started 31 | 32 | First, be sure that you have [Deno](https://deno.land) runtime installed and configured. 33 | 34 | ### Quick Start 35 | 36 | In your application, import the dangoDB module from the deno.land [module](https://deno.land/x/dangodb). 37 | 38 | ```javascript 39 | import { dango } from "https://deno.land/x/dangodb@v1.0.2/mod.ts"; 40 | ``` 41 | 42 | ### Connect to your Database 43 | 44 | Next, open a connection to your MongoDB database using your URI string. 45 | 46 | ```javascript 47 | await dango.connect('URI_STRING'); 48 | 49 | await dango.disconnect(); 50 | ``` 51 | 52 | ### Define Your Schema 53 | 54 | Now, you can define a schema and create a reference to it as illustrated below. 55 | 56 | ```javascript 57 | const dinosaurSchema = dango.schema( 58 | { 59 | name: // A property accepts the below options. Only type is required and must be a valid selection. 60 | { 61 | type: 'string', // Valid data types listed below. 62 | required: true, // A boolean to indicate if the inserted property must have a value specified. Defaults to false. 63 | unique: true, // A boolean to indicate if the inserted property much be unique in its value in the collection. Defaults to false. 64 | default: 'T-Rex', // A value to default to if none specified and required = false. Defaults to null. 65 | validator: null // A user provided validation function that must return true with the casted value as input for the data to pass schema validation. Defaults to null. 66 | }, 67 | age: 'number', // A property can also accept a schema value with only a type indicated. 68 | } 69 | ); 70 | 71 | /** 72 | * Valid datatypes: 73 | * - 'number' 74 | * - 'decimal128' 75 | * - 'string' 76 | * - 'boolean' 77 | * - 'objectid' 78 | * - 'uuid' 79 | * - 'date' 80 | * - userSchema // User defined schema. 81 | * - array ** // In progress - Not yet implemented. 82 | * 83 | */ 84 | 85 | ``` 86 | 87 | ### Create Your Model 88 | 89 | Great! Now you have a schema with one property, name, which will be a 'string.' The next step is compiling our schema into a Model. 90 | 91 | ```javascript 92 | const Dinosaur = dango.model('Dinosaur', dinosaurSchema); 93 | ``` 94 | 95 | ### Make a Query 96 | 97 | Now, let's insert a document into the Dinosaur model. 98 | 99 | ```javascript 100 | await Dinosaur.insertOne({ name: 'Stegosaurus' }); 101 | ``` 102 | 103 | Now, let's say you wanted to display all the dinosaurs in our collection. You can access all of the dinosaur documents through our Dinosaur model. 104 | 105 | ```javascript 106 | const dinosaurs = await Dinosaur.find({ }); 107 | console.log(dinosaurs); 108 | // [ { name: 'Triceratops', age: 70,000,000 }, { name: 'Brontosaurus', age: 150,000,000 }, { name: 'Stegosaurus', age: null }]; 109 | ``` 110 | 111 | Now you've successfully inserted a document into the Dinosaur collection at your MongoDB database. 112 | 113 | Congratulations! That's the end of the quick start. You've successfully imported dangoDB, opened up a connection, created a schema, inserted a document for Stegosaurus, and 114 | queried all the dinosaurs in your Dinosaur model using dangoDB. Explore the rest of the readme.MD for more detailed instructions on how to use dangoDB. 115 | 116 | 117 | 118 | ## Query 119 | 120 | All queries in dangoDB are performed using models. The queries are built on the [deno_mongo](https://deno.land/x/mongo@v0.29.4) drivers. The documentation there may help guide query options. Listed below are all the query functions available in the dangoDB library. 121 | 122 | #### Create, Read, Update, Delete Operations 123 | 124 | - Model.deleteMany() 125 | ```javascript 126 | Model.deleteMany() 127 | /** 128 | * @description Deletes all of the documents that match the conditions from the DB collection. It returns an object with 129 | * the property deletedCount, indicating how many documents were deleted. 130 | * @param queryObject - Query to specify which documents to delete. 131 | * @param options - [optional] 132 | * @param callback - [callback] 133 | * @returns object with property deletedCount, value number. 134 | */ 135 | ``` 136 | 137 | - Model.deleteOne() 138 | ```javascript 139 | Model.deleteOne() 140 | /** 141 | * @description Deletes the first document that matches the conditions from the DB collection. It returns an object 142 | * with the property deletedCount, indicating how many documents were deleted. 143 | * @param queryObject - The query used to find matching document. 144 | * @param options [optional] 145 | * @param callback [optional] 146 | * @returns object with property deletedCount, value number. 147 | */ 148 | ``` 149 | - Model.find() 150 | ```javascript 151 | Model.find() 152 | /** 153 | * @ description Returns all documents that satisfy the specified query criteria on the collection or view. 154 | * @param queryObject - The query used to find matching documents. 155 | * @param options - [optional] Additional options for the operation (e.g. lean, populate, projection) 156 | * @param callback - [optional] 157 | * @returns All matching documents in an array. 158 | */ 159 | ``` 160 | 161 | - Model.findById() 162 | ```javascript 163 | Model.findById() 164 | /** 165 | * @description Returns document that matches user provided ObjectId. 166 | * @param queryObject - The query used to find matching document, using id. 167 | * @param options - [optional] - Additional options for the operation (e.g. lean, populate, projection) 168 | * @param callback - [optional] 169 | * @returns Matching document. 170 | */ 171 | ``` 172 | 173 | - Model.findByIdAndDelete() 174 | ```javascript 175 | Model.findByIdAndDelete() 176 | /** 177 | * @description Deletes the first document that matches the id from the DB collection. It returns the document 178 | * with the matched property. 179 | * @param queryObject - The query used to find matching document, using id. 180 | * @param options [optional] 181 | * @param callback [optional] 182 | * @returns the deleted document. 183 | */ 184 | ``` 185 | 186 | - Model.findByIdAndRemove() 187 | ```javascript 188 | Model.findByIdAndRemove() 189 | /** 190 | * @description Deletes the first document that matches the id from the DB collection. It returns the document 191 | * with the matched property. 192 | * @param queryObject - The query used to find matching document, using id. 193 | * @param options [optional] 194 | * @param callback [optional] 195 | * @returns the document matched and removed. 196 | */ 197 | ``` 198 | 199 | - Model.findByIdAndUpdate() 200 | ```javascript 201 | Model.findByIdAndUpdate() 202 | /** 203 | * @description Updates the first document that matches the id from the DB collection. It returns the document 204 | * with the matched property. 205 | * @param filter - id used to find matching document. 206 | * @param replace - User document to replace matching document at database. 207 | * @param callback [optional] 208 | * @returns the document matched. 209 | */ 210 | ``` 211 | 212 | - Model.findOne() 213 | ```javascript 214 | Model.findOne() 215 | /** 216 | * @description Returns first document that matches query. 217 | * @param queryObject - Query used to find matching document. 218 | * @param options - [optional] 219 | * @param callback - [optional] 220 | * @returns Matching document. 221 | */ 222 | ``` 223 | 224 | - Model.findOneAndDelete() 225 | ```javascript 226 | Model.findOneAndDelete() 227 | /** 228 | * @description Deletes the first document that matches the filter from the DB collection. It returns the document 229 | * with the matched property. 230 | * @param queryObject - The query used to find matching document. 231 | * @param options [optional] 232 | * @param callback [optional] 233 | * @returns the deleted document. 234 | */ 235 | ``` 236 | 237 | - Model.findOneAndRemove() 238 | ```javascript 239 | Model.findOneAndRemove() 240 | /** 241 | * @description Deletes the first document that matches the filter from the DB collection. It returns the document 242 | * with the matched property. 243 | * @param queryObject - The query used to find matching document. 244 | * @param options [optional] 245 | * @param callback [optional] 246 | * @returns the deleted document. 247 | */ 248 | ``` 249 | 250 | - Model.findOneAndReplace() 251 | ```javascript 252 | Model.findOneAndReplace() 253 | /** 254 | * @description Finds a matching document, removes it, and passes in user's document. Replacement document retains same ObjectId as original document. 255 | * @param filter - Query used to find matching document. 256 | * @param replace - User document to replace matching document at database. 257 | * @param options - [optional] 258 | * @param callback - [optional] 259 | * @returns object displaying count for how many documents were upserted, matching, modified. 260 | */ 261 | ``` 262 | 263 | - Model.findOneAndUpdate() 264 | ```javascript 265 | Model.findOneAndUpdate() 266 | /** 267 | * @description Updates the first document that matches filter from the DB collection. It returns the document 268 | * with the matched property. 269 | * @param filter - id used to find matching document. 270 | * @param replace - User document to replace matching document at database. 271 | * @param callback [optional] 272 | * @returns the document matched. 273 | */ 274 | ``` 275 | 276 | - Model.insertOne() 277 | ```javascript 278 | Model.insertOne() 279 | /** 280 | * @description Inserts one document into database collection. 281 | * @param document - User provided object to be inserted into the database. 282 | * @returns ObjectId of inserted document. 283 | */ 284 | ``` 285 | 286 | - Model.insertMany() 287 | ```javascript 288 | Model.insertMany() 289 | /** 290 | * @description Insert multiple documents into database. 291 | * @param document - Array of document(s) to be inserted into database. 292 | * @param options - [optional] 293 | * @param callback - [optional] 294 | * @returns documents that passed validation. 295 | */ 296 | ``` 297 | 298 | - Model.replaceOne() 299 | ```javascript 300 | Model.replaceOne() 301 | /** 302 | * @description Finds a matching document, removes it, and passes in user's document. Replacement document retains same ObjectId as original document. 303 | * @param filter - Query used to find matching document. 304 | * @param replace - User document to replace matching document at database. 305 | * @param options - [optional] 306 | * @param callback - [optional] 307 | * @returns The updated object. 308 | */ 309 | ``` 310 | 311 | - Model.updateMany() 312 | ```javascript 313 | Model.updateMany() 314 | /**Parameters: 315 | * @description Updates all documents that match queryObject. The matching fields in the DB collection will be set to the values in the updateObject. 316 | * @param document - query used to find document(s) to update. 317 | * @param update - object containing field(s) and values to set them to. 318 | * @param options - [optional] 319 | * @param callback - [optional] 320 | * @returns object with properties upsertedId, upsertedCount, matchedCount, modifiedCount. 321 | */ 322 | ``` 323 | 324 | - Model.updateOne() 325 | ```javascript 326 | Model.updateOne() 327 | /** 328 | * @description Updates one document. The fields in the updateObject will be set to their respective values. 329 | * @param document - query used to find document to update. 330 | * @param update - object containing field(s) and values to set them to. 331 | * @param options - [optional] 332 | * @param callback - [optional] 333 | * @returns object with properties upsertedId, upsertedCount, matchedCount, modifiedCount. 334 | */ 335 | ``` 336 | 337 | #### Other Operations 338 | 339 | - Model.aggregate() 340 | ```javascript 341 | Model.aggregate() 342 | /** 343 | * @ description Aggregation operations process multiple documents and return computed results. You can use aggregation operations to: 344 | * Group values from multiple documents together. 345 | * Perform operations on the grouped data to return a single result. 346 | * Analyze data changes over time. 347 | * @param Aggregation pipeline as an array of objects. 348 | * @returns Documents returned are plain javascript documents; 349 | */ 350 | ``` 351 | 352 | - Model.countDocuments() 353 | ```javascript 354 | /** 355 | Model.countDocuments() 356 | * @description Counts number of documents matching filter in a database collection. 357 | * @param document - query used to find document to update. 358 | * @param options - [optional] 359 | * @param callback - [optional] 360 | * @returns a count (number); 361 | * example: query.countDocuments({ username: 'test' }); 362 | */ 363 | ``` 364 | 365 | - Model.dropCollection() 366 | ```javascript 367 | Model.dropCollection() 368 | /** 369 | * @description DropCollection drops current model/collection that user is connected to. 370 | * @returns undefined 371 | */ 372 | ``` 373 | 374 | - Model.estimatedDocumentCount() 375 | ```javascript 376 | Model.estimatedDocumentCount() 377 | /** 378 | * @description Returns the count of all documents in a collection or view. The method wraps the count command. 379 | * @param options - [optional] 380 | * @param callback - [optional] 381 | * @returns a count (number); 382 | */ 383 | ``` 384 | 385 | ## Schema 386 | 387 | When creating a schema, either a type can be assigned to each property in string format, or an object with schema options properties. 388 | 389 | #### Schema Options 390 | - type - Number, decimal128, string, boolean, objectid, UUID, date, object. Specified as a lowercase string. 391 | - required - Boolean, specifies whether a value is required in an inserted document or replacement document. 392 | - unique - Boolean, specifies whether a value is designated as unique for the property. 393 | - default - Default value if no value is provided by user. Defaults to null. 394 | - validator - User can provide a function test which the values to be inserted/updated need to pass before being inserted. Defaults to null. 395 | 396 | To set the type for an embedded object, create a schema for that object, and assign that schema to the property that corresponds with the object. 397 | 398 | ```javascript 399 | addressSchema = dango.schema( 400 | { 401 | house_number: 'number', 402 | unit: 'string', 403 | street: 'string', 404 | city: 'string' 405 | } 406 | ); 407 | 408 | personSchema = dango.schema( 409 | { 410 | name: 'string', 411 | address: addressSchema 412 | } 413 | ); 414 | ``` 415 | 416 | 417 | ## Authors 418 | 419 | - [Bill Greco](https://github.com/wgreco13) 420 | - [Steve Jue](https://github.com/kaizenjoo) 421 | - [Celeste Knopf](https://github.com/DHolliday1881) 422 | - [Emilia Yoffie](https://github.com/emiliayoffie) 423 | 424 | ## License 425 | 426 | This product is licensed under the MIT License - see the LICENSE file for details. 427 | 428 | This is an open source product. 429 | 430 | This product is accelerated by OS Labs. 431 | 432 | -------------------------------------------------------------------------------- /assets/dangoDB_logo_long_midboi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/dangoDB/90528aa435b17243dc1402acc3d247452571df0d/assets/dangoDB_logo_long_midboi.png -------------------------------------------------------------------------------- /assets/dango_deno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/dangoDB/90528aa435b17243dc1402acc3d247452571df0d/assets/dango_deno.png -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Dependency file imports dependencies for use by local modules. 3 | * To add additional dependencies, add an export statement. 4 | */ 5 | 6 | export { 7 | MongoClient, 8 | Database, 9 | Collection, 10 | Bson 11 | } from "https://deno.land/x/mongo@v0.29.4/mod.ts"; 12 | 13 | export type { 14 | CountOptions, 15 | InsertOptions, 16 | UpdateOptions, 17 | FindAndModifyOptions, 18 | DropOptions, 19 | AggregateOptions, 20 | FindOptions, 21 | DeleteOptions, 22 | } from 'https://deno.land/x/mongo@v0.29.4/src/types.ts'; 23 | 24 | export { 25 | afterAll, 26 | afterEach, 27 | beforeAll, 28 | beforeEach, 29 | describe, 30 | it, 31 | } from 'https://deno.land/std@0.138.0/testing/bdd.ts'; 32 | 33 | export { 34 | assert, 35 | assertAlmostEquals, 36 | assertArrayIncludes, 37 | assertEquals, 38 | assertExists, 39 | assertFalse, 40 | assertInstanceOf, 41 | assertIsError, 42 | assertMatch, 43 | assertNotEquals, 44 | assertNotMatch, 45 | assertNotStrictEquals, 46 | assertObjectMatch, 47 | assertRejects, 48 | assertStrictEquals, 49 | assertStringIncludes, 50 | assertThrows, 51 | equal, 52 | fail, 53 | unimplemented, 54 | unreachable, 55 | } from 'https://deno.land/std@0.138.0/testing/asserts.ts'; 56 | 57 | import * as dotenv from "https://deno.land/x/dotenv@v3.2.0/mod.ts"; 58 | export { dotenv } 59 | -------------------------------------------------------------------------------- /lib/connections.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, Database } from '../deps.ts'; 2 | 3 | /** 4 | * Counts number of documents matching filter in a database collection. 5 | * @param connectionString A URI string from the user. 6 | * @returns A connection object. 7 | * example: new Connection(''mongodb+srv://example-uri'); 8 | */ 9 | class Connection { 10 | private client!: MongoClient; 11 | public connected: boolean; 12 | public db: Database | boolean; 13 | 14 | constructor(private connectionString: string) { 15 | if (!connectionString) 16 | throw new Error('Connect method requires at least one argument'); 17 | this.connected = false; 18 | this.connectionString = connectionString; 19 | this.db = false; 20 | } 21 | 22 | /** 23 | * Establishes connection to the database. 24 | * Reassigns Connection class properties of connected to true and db to the connected database. 25 | */ 26 | public async connect() { 27 | try { 28 | this.client = new MongoClient(); 29 | const db = await this.client.connect(this.connectionString); 30 | this.connected = true; 31 | console.log('Connected to Database.'); 32 | this.db = db; 33 | return this.db; 34 | } catch (error) { 35 | throw new Error(`Could not connect to database. ${error}`); 36 | } 37 | } 38 | 39 | /** 40 | * Closes connection to the database. 41 | * Reassigns Connection class properties of connected to false and db to false. 42 | */ 43 | public disconnect() { 44 | if (this.connected) { 45 | this.client.close(); 46 | this.connected = false; 47 | this.db = false; 48 | console.log('Disconnected from Database.'); 49 | } else { 50 | throw new Error(`No connection established to disconnect.`); 51 | } 52 | } 53 | } 54 | 55 | export { Connection }; 56 | -------------------------------------------------------------------------------- /lib/dango.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | /** 4 | * 5 | * @description This file exports the main dango object. 6 | * 7 | */ 8 | 9 | import { Connection } from './connections.ts'; 10 | import { model } from './model.ts'; 11 | import { Schema } from './schema.ts'; 12 | import { 13 | SchemaNumber, 14 | SchemaDecimal128, 15 | SchemaString, 16 | SchemaBoolean, 17 | SchemaObjectId, 18 | SchemaUUID, 19 | SchemaDate, 20 | SchemaObject, 21 | } from './datatypes.ts'; 22 | 23 | /** 24 | * Class definition of Dango.. 25 | * @returns An object of class SchemaOptions. 26 | */ 27 | class Dango { 28 | currentConnection: boolean | Connection; 29 | model: typeof model; 30 | types: Record; 31 | 32 | constructor() { 33 | this.currentConnection = false; 34 | this.model = model; 35 | this.types = { 36 | number: SchemaNumber, 37 | decimal128: SchemaDecimal128, 38 | string: SchemaString, 39 | boolean: SchemaBoolean, 40 | objectid: SchemaObjectId, 41 | UUID: SchemaUUID, 42 | date: SchemaDate, 43 | object: SchemaObject, 44 | }; 45 | } 46 | 47 | /** 48 | * Establishes a new connection to a database. Invokes the connect method of a Connection object. 49 | * @param connectionString A database URI 50 | * 51 | * @returns The connection object. 52 | */ 53 | async connect(connectionString: string) { 54 | this.currentConnection = new Connection(connectionString); 55 | await this.currentConnection.connect(); 56 | return this.currentConnection; 57 | } 58 | 59 | /** 60 | * Disconnects an existing connection to a database. Invokes the disconnect method of a Connection object. 61 | * 62 | * @returns undefined 63 | */ 64 | async disconnect() { 65 | if (typeof this.currentConnection === 'boolean') { 66 | if (this.currentConnection === false) { 67 | throw new Error('No database connection exists to disconnect.'); 68 | } 69 | } else { 70 | await this.currentConnection.disconnect(); 71 | this.currentConnection = false; 72 | return; 73 | } 74 | } 75 | 76 | /** 77 | * Creates a new instance of a Schema object. 78 | * @param schemaObj A user-defined schema. 79 | * 80 | * @returns A schema object 81 | */ 82 | schema(schemaObj: Record) { 83 | return new Schema(schemaObj); 84 | } 85 | } 86 | 87 | const dango: Dango = new Dango(); 88 | 89 | export { dango }; 90 | -------------------------------------------------------------------------------- /lib/datatypes.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | /** 4 | * 5 | * @description This file defines valid data types and associated methods 6 | * 7 | */ 8 | 9 | import { Bson } from '../deps.ts'; 10 | 11 | /** 12 | * Class definition of Number Schema datatype 13 | * @param value Raw value input from the user. 14 | * @returns An object of class SchemaNumber. 15 | */ 16 | export class SchemaNumber { 17 | public value: any; 18 | public valid: boolean | undefined; 19 | public convertedValue: number | Bson.Double | null | undefined; 20 | 21 | constructor(value: any) { 22 | if (value === undefined) { 23 | throw new Error('A value is required.'); 24 | } 25 | this.value = value; 26 | this.convertedValue; 27 | this.valid; 28 | } 29 | 30 | /** 31 | * Attempts to convert raw value to the given data type. 32 | * Sets convertedValue property to the casted input if possible, or undefined if not. 33 | */ 34 | convertType() { 35 | if (this.value === null) { 36 | this.convertedValue = this.value; 37 | } else if (typeof this.value === 'object') { 38 | if (this.value instanceof Bson.Double) { 39 | this.convertedValue = this.value; 40 | } 41 | } else if ( 42 | typeof this.value !== 'number' && 43 | typeof this.value !== 'string' 44 | ) { 45 | return; 46 | } else if (typeof this.value === 'string') { 47 | const stringNum: number = parseFloat(this.value); 48 | if (isNaN(stringNum)) { 49 | return; 50 | } 51 | const bsonNumber = new Bson.Double(stringNum); 52 | this.convertedValue = bsonNumber; 53 | } else if (typeof this.value === 'number') { 54 | const bsonNumber = new Bson.Double(this.value); 55 | this.convertedValue = bsonNumber; 56 | } 57 | 58 | return this.convertedValue; 59 | } 60 | 61 | /** 62 | * Checks whether the value was casted to the correct data type. 63 | * Sets valid property to true or false. 64 | */ 65 | validateType() { 66 | this.valid = this.convertedValue === undefined ? false : true; 67 | return this.valid; 68 | } 69 | } 70 | 71 | /** 72 | * Class definition of Decimal128 Schema datatype 73 | * @param value Raw value input from the user. 74 | * @returns An object of class SchemaDecimal128. 75 | */ 76 | export class SchemaDecimal128 { 77 | public value: any; 78 | public valid: boolean | undefined; 79 | public convertedValue: Bson.Decimal128 | null | undefined; 80 | 81 | constructor(value: any) { 82 | if (value === undefined) { 83 | throw new Error('A value is required.'); 84 | } 85 | this.value = value; 86 | this.convertedValue; 87 | this.valid; 88 | } 89 | 90 | /** 91 | * Attempts to convert raw value to the given data type. 92 | * Sets convertedValue property to the casted input if possible, or undefined if not. 93 | */ 94 | convertType() { 95 | if (this.value === null) { 96 | this.convertedValue = this.value; 97 | } else if (typeof this.value === 'object') { 98 | if (this.value instanceof Bson.Decimal128) { 99 | this.convertedValue = this.value; 100 | } 101 | } else if ( 102 | typeof this.value !== 'number' && 103 | typeof this.value !== 'string' 104 | ) { 105 | return; 106 | } else if (typeof this.value === 'string') { 107 | const stringNum: number = parseFloat(this.value); 108 | if (isNaN(stringNum)) { 109 | return; 110 | } 111 | const decimal128Number = new Bson.Decimal128(this.value); 112 | this.convertedValue = decimal128Number; 113 | } else if (typeof this.value === 'number') { 114 | const decimal128Number = new Bson.Decimal128(this.value.toString()); 115 | this.convertedValue = decimal128Number; 116 | } 117 | 118 | return this.convertedValue; 119 | } 120 | 121 | /** 122 | * Checks whether the value was casted to the correct data type. 123 | * Sets valid property to true or false. 124 | */ 125 | validateType() { 126 | this.valid = this.convertedValue === undefined ? false : true; 127 | return this.valid; 128 | } 129 | } 130 | 131 | /** 132 | * Class definition of String Schema datatype 133 | * @param value Raw value input from the user. 134 | * @returns An object of class SchemaString. 135 | */ 136 | export class SchemaString { 137 | public value: any; 138 | public valid: boolean | undefined; 139 | public convertedValue: string | String | null | undefined; 140 | 141 | constructor(value: any) { 142 | if (value === undefined) { 143 | throw new Error('A value is required.'); 144 | } 145 | this.value = value; 146 | this.convertedValue; 147 | this.valid; 148 | } 149 | 150 | /** 151 | * Attempts to convert raw value to the given data type. 152 | * Sets convertedValue property to the casted input if possible, or undefined if not. 153 | */ 154 | convertType() { 155 | if (this.value === null) { 156 | this.convertedValue = this.value; 157 | } else if (typeof this.value === 'object') { 158 | if (this.value instanceof String) { 159 | this.convertedValue = this.value; 160 | } 161 | } else if ( 162 | typeof this.value !== 'number' && 163 | typeof this.value !== 'string' && 164 | typeof this.value !== 'boolean' 165 | ) { 166 | return; 167 | } else if (typeof this.value === 'string') { 168 | this.convertedValue = this.value; 169 | } else if (typeof this.value === 'number') { 170 | this.convertedValue = this.value.toString(); 171 | } else if (typeof this.value === 'boolean') { 172 | this.convertedValue = this.value ? 'true' : 'false'; 173 | } 174 | 175 | return this.convertedValue; 176 | } 177 | 178 | /** 179 | * Checks whether the value was casted to the correct data type. 180 | * Sets valid property to true or false. 181 | */ 182 | validateType() { 183 | this.valid = this.convertedValue === undefined ? false : true; 184 | return this.valid; 185 | } 186 | } 187 | 188 | /** 189 | * Class definition of Boolean Schema datatype 190 | * @param value Raw value input from the user. 191 | * @returns An object of class SchemaBoolean. 192 | */ 193 | export class SchemaBoolean { 194 | public value: any; 195 | public valid: boolean | undefined; 196 | public convertedValue: boolean | Boolean | null | undefined; 197 | 198 | constructor(value: any) { 199 | if (value === undefined) { 200 | throw new Error('A value is required.'); 201 | } 202 | this.value = value; 203 | this.convertedValue; 204 | this.valid; 205 | } 206 | 207 | /** 208 | * Attempts to convert raw value to the given data type. 209 | * Sets convertedValue property to the casted input if possible, or undefined if not. 210 | */ 211 | convertType() { 212 | if (this.value === null) { 213 | this.convertedValue = this.value; 214 | } else if (typeof this.value === 'object') { 215 | if (this.value instanceof Boolean) { 216 | this.convertedValue = this.value; 217 | } 218 | } else if ( 219 | typeof this.value !== 'number' && 220 | typeof this.value !== 'string' && 221 | typeof this.value !== 'boolean' 222 | ) { 223 | return; 224 | } else if (typeof this.value === 'string') { 225 | if (this.value.toLowerCase() === 'true') this.convertedValue = true; 226 | else if (this.value.toLowerCase() === 'false') 227 | this.convertedValue = false; 228 | } else if (typeof this.value === 'number') { 229 | if (this.value === 1) this.convertedValue = true; 230 | else if (this.value === 0) this.convertedValue = false; 231 | } else if (typeof this.value === 'boolean') { 232 | this.convertedValue = this.value; 233 | } 234 | 235 | return this.convertedValue; 236 | } 237 | 238 | /** 239 | * Checks whether the value was casted to the correct data type.. 240 | * Sets valid property to true or false. 241 | */ 242 | validateType() { 243 | this.valid = this.convertedValue === undefined ? false : true; 244 | return this.valid; 245 | } 246 | } 247 | 248 | /** 249 | * Class definition of ObjectId Schema datatype 250 | * @param value Raw value input from the user. 251 | * @returns An object of class SchemaObjectId. 252 | */ 253 | export class SchemaObjectId { 254 | public value: any; 255 | public valid: boolean | undefined; 256 | public convertedValue: Bson.ObjectId | null | undefined; 257 | 258 | constructor(value: any) { 259 | if (value === undefined) { 260 | throw new Error('A value is required.'); 261 | } 262 | this.value = value; 263 | this.convertedValue; 264 | this.valid; 265 | } 266 | 267 | /** 268 | * Attempts to convert raw value to the given data type. 269 | * Sets convertedValue property to the casted input if possible, or undefined if not. 270 | */ 271 | convertType() { 272 | if (this.value === null) { 273 | this.convertedValue = this.value; 274 | } else if (typeof this.value === 'object') { 275 | if (this.value instanceof Bson.ObjectId) { 276 | this.convertedValue = this.value; 277 | } 278 | } else if (Bson.ObjectId.isValid(this.value)) { 279 | this.convertedValue = new Bson.ObjectId(this.value); 280 | } 281 | 282 | return this.convertedValue; 283 | } 284 | 285 | /** 286 | * Checks whether the value was casted to the correct data type.. 287 | * Sets valid property to true or false. 288 | */ 289 | validateType() { 290 | this.valid = this.convertedValue === undefined ? false : true; 291 | return this.valid; 292 | } 293 | } 294 | 295 | /** 296 | * Class definition of UUID Schema datatype 297 | * @param value Raw value input from the user. 298 | * @returns An object of class SchemaUUID. 299 | */ 300 | export class SchemaUUID { 301 | public value: any; 302 | public valid: boolean | undefined; 303 | public convertedValue: Bson.UUID | null | undefined; 304 | 305 | constructor(value: any) { 306 | if (value === undefined) { 307 | throw new Error('A value is required.'); 308 | } 309 | this.value = value; 310 | this.convertedValue; 311 | this.valid; 312 | } 313 | 314 | /** 315 | * Attempts to convert raw value to the given data type. 316 | * Sets convertedValue property to the casted input if possible, or undefined if not. 317 | */ 318 | convertType() { 319 | if (this.value === null) { 320 | this.convertedValue = this.value; 321 | } else if (typeof this.value === 'object') { 322 | if (this.value instanceof Bson.UUID) { 323 | this.convertedValue = this.value; 324 | } 325 | } else if (Bson.UUID.isValid(this.value)) { 326 | this.convertedValue = new Bson.UUID(this.value); 327 | } 328 | 329 | return this.convertedValue; 330 | } 331 | 332 | /** 333 | * Checks whether the value was casted to the correct data type.. 334 | * Sets valid property to true or false. 335 | */ 336 | validateType() { 337 | this.valid = this.convertedValue === undefined ? false : true; 338 | return this.valid; 339 | } 340 | } 341 | 342 | /** 343 | * Class definition of Date Schema datatype 344 | * @param value Raw value input from the user. 345 | * @returns An object of class SchemaDate. 346 | */ 347 | export class SchemaDate { 348 | public value: any; 349 | public valid: boolean | undefined; 350 | public convertedValue: Date | null | undefined; 351 | 352 | constructor(value: any) { 353 | if (value === undefined) { 354 | throw new Error('A value is required.'); 355 | } 356 | this.value = value; 357 | this.convertedValue; 358 | this.valid; 359 | } 360 | 361 | /** 362 | * Attempts to convert raw value to the given data type. 363 | * Sets convertedValue property to the casted input if possible, or undefined if not. 364 | */ 365 | convertType() { 366 | if (this.value === null) { 367 | this.convertedValue = this.value; 368 | } else if (typeof this.value === 'object') { 369 | if (this.value instanceof Date) { 370 | this.convertedValue = this.value; 371 | } 372 | } else if ( 373 | typeof this.value !== 'number' && 374 | typeof this.value !== 'string' 375 | ) { 376 | return; 377 | } else if (typeof this.value === 'string') { 378 | const convertedDate: number | Date = Date.parse(this.value); 379 | if (typeof convertedDate === 'number') { 380 | if (isNaN(convertedDate)) { 381 | return; 382 | } else { 383 | this.convertedValue = new Date(convertedDate); 384 | } 385 | } 386 | } else if (typeof this.value === 'number') { 387 | this.convertedValue = new Date(this.value); 388 | } 389 | 390 | return this.convertedValue; 391 | } 392 | 393 | /** 394 | * Checks whether the value was casted to the correct data type.. 395 | * Sets valid property to true or false. 396 | */ 397 | validateType() { 398 | this.valid = this.convertedValue === undefined ? false : true; 399 | return this.valid; 400 | } 401 | } 402 | 403 | // SCHEMA OBJ 404 | 405 | export class SchemaObject { 406 | public value: any; 407 | public valid: boolean | undefined; 408 | public convertedValue: Record | null | undefined; 409 | 410 | constructor(value: any) { 411 | if (value === undefined) { 412 | throw new Error('A value is required.'); 413 | } 414 | this.value = value; 415 | this.convertedValue; 416 | this.valid; 417 | } 418 | 419 | /** 420 | * Attempts to convert raw value to the given data type. 421 | * Sets convertedValue property to the casted input if possible, or undefined if not. 422 | */ 423 | convertType() { 424 | if (this.value === null) { 425 | this.convertedValue = this.value; 426 | } else if (typeof this.value === 'object') { 427 | this.convertedValue = this.value; 428 | // } 429 | } else if (typeof this.value !== 'object') { 430 | return; 431 | } 432 | 433 | return this.convertedValue; 434 | } 435 | 436 | /** 437 | * Checks whether the value was casted to the correct data type.. 438 | * Sets valid property to true or false. 439 | */ 440 | validateType() { 441 | this.valid = this.convertedValue === undefined ? false : true; 442 | return this.valid; 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /lib/model.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import { Query } from './query.ts'; 4 | import { Schema } from './schema.ts'; 5 | 6 | /** 7 | * @description Exports a function model that returns a new Model object. 8 | * @param collectionName The collection name to apply the schema to 9 | * @param schema The created instance of the Schema class 10 | * 11 | * @returns A new model object 12 | */ 13 | export function model(collectionName: string, schema: Schema) { 14 | return new Model(collectionName, schema); 15 | } 16 | 17 | /** 18 | * Class definition of a Model. 19 | * Extends the Query class. 20 | * @param collectionName The collection name to apply the schema to 21 | * @param schema The created instance of the Schema class 22 | * 23 | * @returns An object of class SchemaOptions. 24 | */ 25 | class Model extends Query { 26 | collectionName: string; 27 | schema: Schema; 28 | 29 | constructor(collectionName: string, schema: Schema) { 30 | super(collectionName, schema); 31 | //TODO: Can we delete these 32 | this.collectionName = collectionName; 33 | this.schema = schema; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/query.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @description This file defines the query class and its methods. 4 | * 5 | */ 6 | import { Connection } from './connections.ts'; 7 | import { Bson } from '../deps.ts'; 8 | import { 9 | CountOptions, 10 | InsertOptions, 11 | UpdateOptions, 12 | FindAndModifyOptions, 13 | DropOptions, 14 | AggregateOptions, 15 | FindOptions, 16 | DeleteOptions, 17 | } from '../deps.ts'; 18 | import { dango } from './dango.ts'; 19 | import { Schema, optionsObject } from './schema.ts'; 20 | 21 | interface MatchInterface { 22 | $match: { [unknownKeyName: string]: string }; 23 | } 24 | 25 | interface GroupInterface { 26 | $group: { 27 | [unknownKeyName: string]: string | { $sum: number }; 28 | }; 29 | } 30 | 31 | class Query { 32 | public collectionName: string; 33 | public connection: Connection | boolean; 34 | public schema: Schema; 35 | public updatedQueryObject: { [key: string]: unknown }; 36 | 37 | constructor(collectionName: string, schema: Schema) { 38 | this.collectionName = collectionName; 39 | this.connection = dango.currentConnection; 40 | this.schema = schema; 41 | this.updatedQueryObject = {}; 42 | } 43 | /** 44 | * Returns one document that satisfies the specified query criteria on the collection or view. 45 | * 46 | * @param query - Selects documents in a collection or view and returns a cursor to the selected documents. 47 | * @param options Additional options for the operation (e.g. lean, populate, projection) 48 | * @param callback 49 | * @returns A count of the documents in the database. 50 | * example: query.find(); 51 | */ 52 | public async find( 53 | allQueryObjects?: Record, 54 | options?: FindOptions, 55 | callback?: (input: unknown) => unknown 56 | ) { 57 | try { 58 | if ( 59 | typeof this.connection === 'boolean' || 60 | typeof this.connection.db === 'boolean' 61 | ) { 62 | if (this.connection === false) { 63 | throw new Error('No connection established before query.'); 64 | } 65 | } else { 66 | const collection = this.connection.db.collection(this.collectionName); 67 | const data = await collection.find(allQueryObjects, options); 68 | const dataRes = await data.toArray(); 69 | 70 | if (callback) return callback(data); 71 | 72 | return dataRes; 73 | } 74 | } catch (error) { 75 | throw new Error(`Error in find function. ${error}`); 76 | } 77 | } 78 | /** 79 | * Returns one document that satisfies the specified query criteria on the collection or view. 80 | * 81 | * @param query - The query used to match documents 82 | * @param options Additional options for the operation (e.g. lean, populate, projection) 83 | * @param callback 84 | * @returns The document matched and modified 85 | * example: query.findOne({ username: 'newtest2' }); 86 | */ 87 | 88 | public async findOne( 89 | queryObject: Record, 90 | options?: FindOptions, 91 | callback?: (input: unknown) => unknown 92 | ) { 93 | try { 94 | if ( 95 | typeof this.connection === 'boolean' || 96 | typeof this.connection.db === 'boolean' 97 | ) { 98 | if (this.connection === false) { 99 | throw new Error('No connection established before query.'); 100 | } 101 | } else { 102 | const collection = this.connection.db.collection(this.collectionName); 103 | const data = await collection.findOne(queryObject, options); 104 | 105 | if (callback) return callback(data); 106 | 107 | return data; 108 | } 109 | } catch (error) { 110 | throw new Error(`Error in findOne function. ${error}`); 111 | } 112 | } 113 | /** 114 | Counts number of documents matching filter in a database collection. 115 | * @param Filter. 116 | * @param Additional options for the operation. 117 | * @param Optional callback such as (err, count); 118 | * @returns a count (number); 119 | * example: query.countDocuments({ username: 'test' }); 120 | */ 121 | public async countDocuments( 122 | queryObject: Record, 123 | callback?: (input: unknown) => unknown 124 | ) { 125 | try { 126 | if ( 127 | typeof this.connection === 'boolean' || 128 | typeof this.connection.db === 'boolean' 129 | ) { 130 | if (this.connection === false) { 131 | throw new Error('No connection established before query.'); 132 | } 133 | } else { 134 | const collection = this.connection.db.collection(this.collectionName); 135 | const data = await collection.countDocuments(queryObject); 136 | 137 | if (callback) return callback(data); 138 | console.log(data); 139 | 140 | return data; 141 | } 142 | } catch (error) { 143 | throw new Error(`Error in countDocuments function. ${error}`); 144 | } 145 | } 146 | /** 147 | Returns the count of all documents in a collection or view. The method wraps the count command. 148 | * @param Additional options for the operation. 149 | * @param Optional callback such as (err, count); 150 | * @returns a count (number); 151 | * example: query.estimatedDocumentCount(); 152 | */ 153 | public async estimatedDocumentCount() { 154 | try { 155 | if ( 156 | typeof this.connection === 'boolean' || 157 | typeof this.connection.db === 'boolean' 158 | ) { 159 | if (this.connection === false) { 160 | throw new Error('No connection established before query.'); 161 | } 162 | } else { 163 | const collection = this.connection.db.collection(this.collectionName); 164 | const data = await collection.estimatedDocumentCount(); 165 | 166 | return data; 167 | } 168 | } catch (error) { 169 | throw new Error(`Error in estimatedDocumentCount function. ${error}`); 170 | } 171 | } 172 | /** 173 | Aggregation operations process multiple documents and return computed results. You can use aggregation operations to: 174 | Group values from multiple documents together. 175 | Perform operations on the grouped data to return a single result. 176 | Analyze data changes over time. 177 | * @param Aggregation pipeline as an array of objects. 178 | * @returns Documents returned are plain javascript documents; 179 | * example: query.aggregate([ 180 | { $match: { username: 'test' } }, 181 | { $group: { _id: '$username', total: { $sum: 1 } } }, 182 | ]); 183 | */ 184 | public async aggregate(arg1: [MatchInterface, GroupInterface]) { 185 | try { 186 | if ( 187 | typeof this.connection === 'boolean' || 188 | typeof this.connection.db === 'boolean' 189 | ) { 190 | if (this.connection === false) { 191 | throw new Error('No connection established before query.'); 192 | } 193 | } else { 194 | const collection = this.connection.db.collection(this.collectionName); 195 | const data = await collection.aggregate(arg1); 196 | const dataRes = await data.toArray(); 197 | 198 | return dataRes; 199 | } 200 | } catch (error) { 201 | throw new Error(`Error in aggregate function. ${error}`); 202 | } 203 | } 204 | /** 205 | * Find and modify a document in one, returning the matching document. 206 | * 207 | * @param The query used to match documents. 208 | * @param Additional options for the operation (e.g. sort, limit, skip) 209 | * @returns The document matched and modified 210 | * example:query.findAndModify({ username: 'emilia' }, 211 | { 212 | sort: { _id: 1 }, 213 | update: { $inc: { newField: +2 } }, 214 | new: true, 215 | }); 216 | */ 217 | public async findAndModify( 218 | filter: Record, 219 | options?: FindAndModifyOptions 220 | ) { 221 | try { 222 | if ( 223 | typeof this.connection === 'boolean' || 224 | typeof this.connection.db === 'boolean' 225 | ) { 226 | if (this.connection === false) { 227 | throw new Error('No connection established before query.'); 228 | } 229 | } else { 230 | const collection = this.connection.db.collection(this.collectionName); 231 | const data = await collection.findAndModify(filter, options); 232 | 233 | console.log('findByIdAndModify Successful', data); 234 | return data; 235 | } 236 | } catch (error) { 237 | throw new Error(`Error in findandModify function. ${error}`); 238 | } 239 | } 240 | /** 241 | * Issue a MongoDB findOneAndDelete() command by a document's _id field. 242 | * 243 | * @param The id used to match documents. 244 | * @param Callback function. 245 | * @returns The document matched and deleted. 246 | * example: query.findByIdAndDelete("62642ee21bcc7078ae1dba3d") 247 | */ 248 | public async findByIdAndDelete( 249 | id: string, 250 | options?: FindOptions | DeleteOptions | ((input: unknown) => unknown), 251 | callback?: (input: unknown) => unknown 252 | ) { 253 | try { 254 | const stringId = new Bson.ObjectId(id); 255 | if ( 256 | typeof this.connection === 'boolean' || 257 | typeof this.connection.db === 'boolean' 258 | ) { 259 | if (this.connection === false) { 260 | throw new Error('No connection established before query.'); 261 | } 262 | } else { 263 | const collection = this.connection.db.collection(this.collectionName); 264 | 265 | if (typeof options === 'function') { 266 | callback = options; 267 | options = {}; 268 | } 269 | 270 | const data = await collection.deleteOne({ _id: stringId }, options); 271 | console.log('findByIdAndDelete Successful', data); 272 | 273 | if (callback) { 274 | return callback(data); 275 | } else { 276 | return data; 277 | } 278 | } 279 | } catch (error) { 280 | throw new Error(`Error in findByIdAndDelete function. ${error}`); 281 | } 282 | } 283 | /** 284 | * Issue a mongodb findAndModify remove command. Finds a matching document, removes it, passing the found document (if any) to the callback. Executes the query if callback is passed. 285 | * 286 | * @param Conditions. 287 | * @param Additional options for the operation. 288 | * @param Callback function. 289 | * @returns The document matched and removed. 290 | * example: query.findOneAndRemove({username: "Bob"}, (input) => {console.log('callback executed', input)}); 291 | */ 292 | public async findOneAndRemove( 293 | queryObject: Record, 294 | callback?: (input: unknown) => unknown 295 | ) { 296 | try { 297 | if ( 298 | typeof this.connection === 'boolean' || 299 | typeof this.connection.db === 'boolean' 300 | ) { 301 | if (this.connection === false) { 302 | throw new Error('No connection established before query.'); 303 | } 304 | } else { 305 | const collection = this.connection.db.collection(this.collectionName); 306 | const data = await collection.findAndModify(queryObject, { 307 | remove: true, 308 | }); 309 | 310 | console.log('findOneAndRemove Successful', data); 311 | 312 | if (callback) { 313 | return callback(data); 314 | } else { 315 | return data; 316 | } 317 | } 318 | } catch (error) { 319 | throw new Error(`Error in findOneAndRemove function. ${error}`); 320 | } 321 | } 322 | /** 323 | * Issue a mongodb findAndModify remove command by a document's _id field. 324 | * 325 | * @param The id used to match documents. 326 | * @param Additional options for the operation. 327 | * @param Callback function. 328 | * @returns The document matched and removed. 329 | * example: query.findByIdAndRemove("626d8508c522d90bacb1c843", (input) => {console.log('callback executed', input)}); 330 | */ 331 | public async findByIdAndRemove( 332 | id?: string, 333 | callback?: (input: unknown) => unknown 334 | ) { 335 | try { 336 | const stringId = new Bson.ObjectId(id); 337 | 338 | if ( 339 | typeof this.connection === 'boolean' || 340 | typeof this.connection.db === 'boolean' 341 | ) { 342 | if (this.connection === false) { 343 | throw new Error('No connection established before query.'); 344 | } 345 | } else { 346 | const collection = this.connection.db.collection(this.collectionName); 347 | const data = await collection.findAndModify( 348 | { _id: stringId }, 349 | { remove: true } 350 | ); 351 | 352 | console.log('findByIdAndRemove Successful', data); 353 | 354 | if (callback) return callback(data); 355 | 356 | return data; 357 | } 358 | } catch (error) { 359 | throw new Error(`Error in findByIdAndRemove function. ${error}`); 360 | } 361 | } 362 | /** 363 | * ReplaceOne replaces the filters existing document with the input doc - same as update() in the mongoDB Driver. Returns a query. 364 | * 365 | * @param Filter. 366 | * @param Document which is the client input for what will replace the filter. 367 | * @param Additional options for the operation. 368 | * @param Callback function. 369 | * @returns The updated document. 370 | * example: await query.replaceOne("626d8508c522d90bacb1c843", (input) => {console.log('callback executed', input)}); 371 | */ 372 | public async replaceOne( 373 | filter: Record, 374 | document: Record, 375 | options?: UpdateOptions | ((input: unknown) => unknown), 376 | callback?: (input: unknown) => unknown 377 | ) { 378 | try { 379 | if ( 380 | typeof this.connection === 'boolean' || 381 | typeof this.connection.db === 'boolean' 382 | ) { 383 | if (this.connection === false) { 384 | throw new Error('No connection established before query.'); 385 | } 386 | } else { 387 | const collection = this.connection.db.collection(this.collectionName); 388 | if (typeof options === 'function') callback = options; 389 | options = {}; 390 | 391 | const data = await collection.replaceOne(filter, document, options); 392 | 393 | console.log('Successfully executed replaceOne', data); 394 | 395 | if (callback) return callback(data); 396 | 397 | return data; 398 | } 399 | } catch (error) { 400 | throw new Error(`Error in replaceOne function. ${error}`); 401 | } 402 | } 403 | /** 404 | * Insert One takes one parameter, a document, which inserts a document into the database 405 | * 406 | * @param Document which is the client input for what will replace the filter. 407 | * @param WriteConcern. Optional. A document that expresses the write concern of the insert command. Omit to use the default write concern. 408 | * Do not explicitly set the write concern for the operation if run in a transaction. To use write concern with transactions, see Transactions and Write Concern. 409 | * @returns The inserted document. 410 | * example: await query.insertOne({username: 'Celeste'}); 411 | */ 412 | public async insertOne( 413 | document: Record, 414 | writeConcern?: InsertOptions 415 | ) { 416 | try { 417 | if ( 418 | typeof this.connection === 'boolean' || 419 | typeof this.connection.db === 'boolean' 420 | ) { 421 | if (this.connection === false) { 422 | throw new Error('No connection established before query.'); 423 | } 424 | } else { 425 | const collection = this.connection.db.collection(this.collectionName); 426 | await this.validateInsertAgainstSchema( 427 | document, 428 | this.schema, 429 | this.updatedQueryObject 430 | ); 431 | const id = await collection.insertOne( 432 | this.updatedQueryObject, 433 | writeConcern 434 | ); 435 | this.resetQueryObject(); 436 | console.log('Successfully insertedOne'); 437 | return id; 438 | } 439 | } catch (error) { 440 | throw new Error(`Error in insertOne function. ${error}`); 441 | } 442 | } 443 | 444 | /** 445 | * Insert Many inserts an array or object into the database. 446 | * 447 | * @param Document which is the client input for what will replace the filter. 448 | * @param options Optional for different inderting options 449 | * @param callback function 450 | * @returns a promise resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise 451 | * example: await query.insertMany([ { username: 'anotherOne'}, { username: 'Tulips' }], (input) => {console.log('callback executed', input)}); 452 | */ 453 | public async insertMany( 454 | document: Record[], 455 | options?: InsertOptions | ((input: unknown) => unknown), 456 | callback?: (input: unknown) => unknown 457 | ) { 458 | try { 459 | if ( 460 | typeof this.connection === 'boolean' || 461 | typeof this.connection.db === 'boolean' 462 | ) { 463 | if (this.connection === false) { 464 | throw new Error('No connection established before query.'); 465 | } 466 | } else { 467 | const collection = this.connection.db.collection(this.collectionName); 468 | 469 | if (typeof options === 'function') callback = options; 470 | options = {}; 471 | 472 | const validatedDocuments = []; 473 | for (const doc of document) { 474 | await this.validateInsertAgainstSchema( 475 | doc, 476 | this.schema, 477 | this.updatedQueryObject 478 | ); 479 | validatedDocuments.push(this.updatedQueryObject); 480 | this.resetQueryObject(); 481 | } 482 | const ids = await collection.insertMany(validatedDocuments, options); 483 | if (callback) return callback(ids); 484 | 485 | return ids; 486 | } 487 | } catch (error) { 488 | throw new Error(`Error in insertMany function. ${error}`); 489 | } 490 | } 491 | 492 | /** 493 | * Find One And Update finds a matching document, updates it according to the update arg, passing any options, and returns the found document (if any) to the callbacks. 494 | * 495 | * @param Filter which is the client input for the document to find 496 | * @param Update which is the clients input for what portion will be updated within the document 497 | * @param Additonal options UpdateOptions 498 | * @param callback function 499 | * @returns the found document 500 | * example: await query.findByIdAndUpdate('626aa9c8b1d75dd60462cf15', { username: "omgThisWorksAgain"}, (input) => {console.log('callback executed', input)}) 501 | */ 502 | public async findOneAndUpdate( 503 | filter: Record, 504 | update: Record, 505 | options?: UpdateOptions | ((input: unknown) => unknown), 506 | callback?: (input: unknown) => unknown 507 | ) { 508 | try { 509 | if ( 510 | typeof this.connection === 'boolean' || 511 | typeof this.connection.db === 'boolean' 512 | ) { 513 | if (this.connection === false) { 514 | throw new Error('No connection established before query.'); 515 | } 516 | } else { 517 | const collection = this.connection.db.collection(this.collectionName); 518 | await this.validateUpdateAgainstSchema( 519 | update, 520 | this.schema, 521 | this.updatedQueryObject 522 | ); 523 | const newUpdate = { $set: this.updatedQueryObject }; 524 | if (typeof options === 'function') callback = options; 525 | options = {}; 526 | const data = await collection.updateOne(filter, newUpdate, options); 527 | this.resetQueryObject(); 528 | if (callback) return callback(data); 529 | 530 | return data; 531 | } 532 | } catch (error) { 533 | throw new Error(`Error in findOneAndUpdate function. ${error}`); 534 | } 535 | } 536 | 537 | /** 538 | * Find One And Replace finds a matching document, removes the contents of the document, and passes the input document (if any) into the document as a replacement. 539 | * @param Filter which is the client input for which document will be found 540 | * @param Replacement is the client input for which document will be replacing the filter 541 | * @param Additional options 542 | * @param Callback function 543 | * @returns 544 | * example: await query.findOneAndReplace({ username: "OneandUpdating" }, { username: "Iron_Man"}, (input) => {console.log('callback executed', input)}) 545 | */ 546 | public async findOneAndReplace( 547 | filter: Record, 548 | replacement: Record, 549 | options?: FindAndModifyOptions | ((input: unknown) => unknown), 550 | callback?: (input: unknown) => unknown 551 | ) { 552 | try { 553 | if ( 554 | typeof this.connection === 'boolean' || 555 | typeof this.connection.db === 'boolean' 556 | ) { 557 | if (this.connection === false) { 558 | throw new Error('No connection established before query.'); 559 | } 560 | } else { 561 | const collection = this.connection.db.collection(this.collectionName); 562 | if (typeof options === 'function') callback = options; 563 | options = {}; 564 | await this.validateReplaceAgainstSchema( 565 | filter, 566 | replacement, 567 | this.schema, 568 | this.updatedQueryObject 569 | ); 570 | const data = await collection.replaceOne( 571 | filter, 572 | this.updatedQueryObject, 573 | options 574 | ); 575 | this.resetQueryObject(); 576 | if (callback) return callback(data); 577 | 578 | return data; 579 | } 580 | } catch (error) { 581 | throw new Error(`Error in findOneAndReplace function. ${error}`); 582 | } 583 | } 584 | 585 | /** 586 | * Find By Id finds a single document by its _id field 587 | * @param id which is the client input for which document will be found 588 | * @param Additional options such as projection, sort etc 589 | * @param callback function 590 | * @returns the document 591 | * example: await query.findById('626aaa96500d65b1228e6940'); 592 | */ 593 | public async findById( 594 | id: string, 595 | options?: FindOptions | ((input: unknown) => unknown), 596 | callback?: (input: unknown) => unknown 597 | ) { 598 | try { 599 | const stringId = new Bson.ObjectId(id); 600 | 601 | if ( 602 | typeof this.connection === 'boolean' || 603 | typeof this.connection.db === 'boolean' 604 | ) { 605 | if (this.connection === false) { 606 | throw new Error('No connection established before query.'); 607 | } 608 | } else { 609 | const collection = this.connection.db.collection(this.collectionName); 610 | 611 | if (typeof options === 'function') callback = options; 612 | options = {}; 613 | 614 | const data = await collection.findOne({ _id: stringId }, options); 615 | if (callback) { 616 | return callback(data); 617 | } else { 618 | return data; 619 | } 620 | } 621 | } catch (error) { 622 | throw new Error(`Error in findById function. ${error}`); 623 | } 624 | } 625 | 626 | /** 627 | * Find By Id and Update finds a matching document by _id, updates it according to the update arg, passing any options, and returns the found document (if any) to the callbacks. 628 | * @param id which is the client input for which document will be found 629 | * @param update which is the clients input for what portion will be updated within the document 630 | * @param additional options updateoptions 631 | * @param callback function 632 | * @returns the document 633 | * example: await query.findByIdAndUpdate(626aa9c8b1d75dd60462cf15', { username: "omgThisWorksAgain"}, (input) => {console.log('callback executed', input)}); 634 | */ 635 | public async findByIdAndUpdate( 636 | id: string, 637 | update: Record, 638 | options?: UpdateOptions | ((input: unknown) => unknown), 639 | callback?: (input: unknown) => unknown 640 | ) { 641 | try { 642 | const filter = { _id: new Bson.ObjectId(id) }; 643 | console.log(filter); 644 | 645 | if ( 646 | typeof this.connection === 'boolean' || 647 | typeof this.connection.db === 'boolean' 648 | ) { 649 | if (this.connection === false) { 650 | throw new Error('No connection established before query.'); 651 | } 652 | } else { 653 | const collection = this.connection.db.collection(this.collectionName); 654 | 655 | await this.validateUpdateAgainstSchema( 656 | update, 657 | this.schema, 658 | this.updatedQueryObject 659 | ); 660 | const newUpdate = { $set: this.updatedQueryObject }; 661 | 662 | if (typeof options === 'function') callback = options; 663 | options = {}; 664 | 665 | const data = await collection.updateOne(filter, newUpdate, options); 666 | this.resetQueryObject(); 667 | if (callback) { 668 | return callback(data); 669 | } else { 670 | return data; 671 | } 672 | } 673 | } catch (error) { 674 | throw new Error(`Error in findByIdAndUpdate function. ${error}`); 675 | } 676 | } 677 | 678 | /** 679 | * DropCollection drops current model/collection that user is connected to. 680 | * @returns undefined 681 | * example: Model.dropCollection() 682 | */ 683 | public async dropCollection() { 684 | try { 685 | if ( 686 | typeof this.connection === 'boolean' || 687 | typeof this.connection.db === 'boolean' 688 | ) { 689 | if (this.connection === false) { 690 | throw new Error('No connection established before query.'); 691 | } 692 | } else { 693 | const collection = this.connection.db.collection(this.collectionName); 694 | const data = await collection.drop(); 695 | 696 | console.log('Collection successfully dropped.'); 697 | 698 | return data; 699 | } 700 | } catch (error) { 701 | throw new Error(`Error in dropCollection function. ${error}`); 702 | } 703 | } 704 | 705 | /** 706 | * Delete One deletes the first document that matches conditions from the collection. 707 | * @param document which is the client input for which document will be found 708 | * @param additional options deleteOptions 709 | * @param callback function 710 | * @returns an object with the property deletedCount indicating how many documents were deleted. 711 | * example: await query.deleteOne({ username: "test"}, (input) => {console.log('callback executed', input)}) 712 | */ 713 | public async deleteOne( 714 | document: Record, 715 | options?: DeleteOptions | ((input: unknown) => unknown), 716 | callback?: (input: unknown) => unknown 717 | ) { 718 | try { 719 | if (typeof options === 'function') { 720 | callback = options; 721 | options = {}; 722 | } 723 | if ( 724 | typeof this.connection === 'boolean' || 725 | typeof this.connection.db === 'boolean' 726 | ) { 727 | if (this.connection === false) { 728 | throw new Error('No connection established before query.'); 729 | } 730 | } else { 731 | const collection = this.connection.db.collection(this.collectionName); 732 | 733 | const data = await collection.deleteOne(document, options); 734 | if (callback) { 735 | return callback(data); 736 | } else { 737 | const formattedReturnObj = { deletedCount: data }; 738 | 739 | return formattedReturnObj; 740 | } 741 | } 742 | } catch (error) { 743 | throw new Error(`Error in deleteOne function. ${error}`); 744 | } 745 | } 746 | 747 | /** 748 | * Delete Many deletes all of the documents that match conditions from the collection 749 | * @param document which is the client input for which document will be found 750 | * @param additional options deleteOptions 751 | * @param callback function 752 | * @returns an object with the property deletedCount containing the number of documents deleted 753 | * example: await query.deleteMany({ username: 'newtest1' }, { limit: 1 } ,(data) => { console.log(data); }); 754 | */ 755 | public async deleteMany( 756 | document: Record, 757 | options?: DeleteOptions | ((input: unknown) => unknown), 758 | callback?: (input: unknown) => unknown 759 | ) { 760 | try { 761 | if ( 762 | typeof this.connection === 'boolean' || 763 | typeof this.connection.db === 'boolean' 764 | ) { 765 | if (this.connection === false) { 766 | throw new Error('No connection established before query.'); 767 | } 768 | } else { 769 | const collection = this.connection.db.collection(this.collectionName); 770 | if (typeof options === 'function') { 771 | callback = options; 772 | options = {}; 773 | } 774 | 775 | const data = await collection.deleteMany(document, options); 776 | const formattedReturnObj = { deletedCount: data }; 777 | console.log(formattedReturnObj); 778 | if (callback) return callback(data); 779 | 780 | return formattedReturnObj; 781 | 782 | } 783 | } catch (error) { 784 | throw new Error(`Error in deleteMany function. ${error}`); 785 | } 786 | } 787 | /** 788 | * Update One finds a matching document, updates it according to the update arg, passing any options. Will update only the first document that matches filter regardless of the value of the multi option. 789 | * 790 | * @param document which is the client input for the document to find 791 | * @param Update which is the clients input for what portion will be updated within the document 792 | * @param Additonal options UpdateOptions 793 | * @param callback function params are (error, writeOpResult) 794 | * @returns the found document 795 | * example: await query.updateOne({ username: 'rob ott'}, { username: 'ROBO OTT' }); 796 | */ 797 | public async updateOne( 798 | document: Record, 799 | update: Record, 800 | options?: UpdateOptions | ((input: unknown) => unknown), 801 | callback?: (input: unknown) => unknown 802 | ) { 803 | try { 804 | if ( 805 | typeof this.connection === 'boolean' || 806 | typeof this.connection.db === 'boolean' 807 | ) { 808 | if (this.connection === false) { 809 | throw new Error('No connection established before query.'); 810 | } 811 | } else { 812 | const collection = this.connection.db.collection(this.collectionName); 813 | if (typeof options === 'function') { 814 | callback = options; 815 | options = {}; 816 | } 817 | 818 | await this.validateUpdateAgainstSchema( 819 | update, 820 | this.schema, 821 | this.updatedQueryObject 822 | ); 823 | const setUpdateObject = { $set: this.updatedQueryObject }; 824 | 825 | const data = await collection.updateOne( 826 | document, 827 | setUpdateObject, 828 | options 829 | ); 830 | this.resetQueryObject(); 831 | 832 | if (callback) return callback(data); 833 | 834 | return data; 835 | } 836 | } catch (error) { 837 | throw new Error(`Error in updateOne function. ${error}`); 838 | } 839 | } 840 | 841 | /** 842 | * Update Many updates many documents matching search criteria in the database. 843 | * 844 | * @param document which is the client input for the document to find 845 | * @param update which is the clients input for what portion will be updated within the document 846 | * @param options options UpdateOptions 847 | * @param callback function 848 | * @returns the found document 849 | * example: await query.updateMany({ name: 'Mireille' }, { favoriteFood: 'pizza' }, (input) => {console.log('callback executed', input)}) 850 | */ 851 | public async updateMany( 852 | document: Record, 853 | update: Record, 854 | options?: UpdateOptions | ((input: unknown) => unknown), 855 | callback?: (input: unknown) => unknown 856 | ) { 857 | // if upsert is true, and no matching documents are found, updateObject( regardless of how complete it is) will be inserted. 858 | try { 859 | if ( 860 | typeof this.connection === 'boolean' || 861 | typeof this.connection.db === 'boolean' 862 | ) { 863 | if (this.connection === false) { 864 | throw new Error('No connection established before query.'); 865 | } 866 | } else { 867 | const collection = this.connection.db.collection(this.collectionName); 868 | if (typeof options === 'function') { 869 | callback = options; 870 | options = {}; 871 | } 872 | 873 | await this.validateUpdateAgainstSchema( 874 | update, 875 | this.schema, 876 | this.updatedQueryObject 877 | ); 878 | const setUpdateObject = { $set: this.updatedQueryObject }; 879 | const data = await collection.updateMany( 880 | document, 881 | setUpdateObject, 882 | options 883 | ); 884 | this.resetQueryObject(); 885 | 886 | if (callback) return callback(data); 887 | 888 | return data; 889 | } 890 | } catch (error) { 891 | throw new Error(`Error in updateMany function. ${error}`); 892 | } 893 | } 894 | 895 | /** 896 | * Method validates schema for insert queries by calling each schema option. Validation steps will throw an error if validation fails. 897 | * 898 | * @param queryObject which is the client document to insert into the database 899 | * @param schema which is the current schema, either at the outer level of a document or is an embedded schema 900 | * @param updatedQueryObject which is the converted version of the user's queryObject with properly coverted types for each value 901 | * @param embeddedUniqueProperty When checking a property in an embedded document with the schema option 'unique', set to true, this array will 902 | * contain property keys from outer levels 903 | * @returns true or undefined. 904 | */ 905 | async validateInsertAgainstSchema( 906 | queryObject: Record, 907 | schema: Schema, 908 | updatedQueryObject: Record, 909 | embeddedUniqueProperty: string[] = [] 910 | ) { 911 | const currentSchemaMap = schema.schemaMap; 912 | 913 | this.checkDataFields(queryObject, currentSchemaMap); 914 | for (const property in currentSchemaMap) { 915 | // current SchemaMap's current property value is either an instance of a Schema or a SchemaOption 916 | // If Schema is stored, validate embedded object. 917 | if (currentSchemaMap[property] instanceof Schema) { 918 | updatedQueryObject[property] = {}; 919 | embeddedUniqueProperty.push(property); 920 | await this.validateInsertAgainstSchema( 921 | queryObject[property] as Record, 922 | currentSchemaMap[property] as Schema, 923 | updatedQueryObject[property] as Record, 924 | embeddedUniqueProperty 925 | ); 926 | embeddedUniqueProperty.pop(); 927 | } else { 928 | this.checkRequired( 929 | queryObject, 930 | property, 931 | currentSchemaMap[property] as optionsObject 932 | ); 933 | this.setDefault( 934 | queryObject, 935 | property, 936 | currentSchemaMap[property] as optionsObject 937 | ); 938 | this.populateQuery( 939 | queryObject, 940 | property, 941 | currentSchemaMap[property] as optionsObject, 942 | updatedQueryObject 943 | ); 944 | await this.checkUnique( 945 | property, 946 | currentSchemaMap[property] as optionsObject, 947 | updatedQueryObject, 948 | embeddedUniqueProperty 949 | ); 950 | this.checkConstraints( 951 | property, 952 | currentSchemaMap[property] as optionsObject 953 | ); 954 | } 955 | } 956 | return true; 957 | } 958 | 959 | /** 960 | * Method validates schema for replace queries by calling each schema option. Validation steps will throw an error if validation fails. 961 | * checkUnique step is different from validateInsertAgainstSchema for edge case where document property flagged as unique replaces itself. 962 | * 963 | * @param findObject The query used to match documents 964 | * @param queryObject which is the client document to insert into the database 965 | * @param schema which is the current schema, either at the outer level of a document or is an embedded schema 966 | * @param updatedQueryObject which is the converted version of the user's queryObject with properly coverted types for each value 967 | * @param embeddedUniqueProperty When checking a property in an embedded document with the schema option 'unique', set to true, this array will 968 | * contain property keys from outer levels 969 | * @returns true or undefined. 970 | */ 971 | async validateReplaceAgainstSchema( 972 | findObject: Record, 973 | queryObject: Record, 974 | schema: Schema, 975 | updatedQueryObject: Record, 976 | embeddedUniqueProperty: string[] = [] 977 | ) { 978 | const currentSchemaMap = schema.schemaMap; 979 | this.checkDataFields(queryObject, currentSchemaMap); 980 | for (const property in currentSchemaMap) { 981 | // current SchemaMap's current property value is either an instance of a Schema or a SchemaOption 982 | // If Schema is stored, validate embedded object. 983 | if (currentSchemaMap[property] instanceof Schema) { 984 | updatedQueryObject[property] = {}; 985 | embeddedUniqueProperty.push(property); 986 | await this.validateReplaceAgainstSchema( 987 | findObject, 988 | queryObject[property] as Record, 989 | currentSchemaMap[property] as Schema, 990 | updatedQueryObject[property] as Record, 991 | embeddedUniqueProperty 992 | ); 993 | embeddedUniqueProperty.pop(); 994 | } else { 995 | this.checkRequired(queryObject, property, currentSchemaMap[property]); 996 | this.setDefault(queryObject, property, currentSchemaMap[property]); 997 | this.populateQuery( 998 | queryObject, 999 | property, 1000 | currentSchemaMap[property], 1001 | updatedQueryObject 1002 | ); 1003 | await this.checkUniqueForReplace( 1004 | property, 1005 | currentSchemaMap[property], 1006 | findObject, 1007 | updatedQueryObject, 1008 | embeddedUniqueProperty 1009 | ); 1010 | this.checkConstraints(property, currentSchemaMap[property]); 1011 | } 1012 | } 1013 | return true; 1014 | } 1015 | 1016 | /** 1017 | * Method validates schema for update queries by calling each schema option. Validation steps will throw an error if validation fails. 1018 | * checkRequired and setDefault steps are not needed in validateInsertAgainstSchema method. 1019 | * 1020 | * @param queryObject which is the client document field to update in the database 1021 | * @param schema which is the current schema, either at the outer level of a document or is an embedded schema 1022 | * @param updatedQueryObject which is the converted version of the user's queryObject with properly coverted types for each value 1023 | * @param embeddedUniqueProperty When checking a property in an embedded document with the schema option 'unique', set to true, this array will 1024 | * contain property keys from outer levels 1025 | * @returns true or undefined. 1026 | */ 1027 | async validateUpdateAgainstSchema( 1028 | queryObject: Record, 1029 | schema: Schema, 1030 | updatedQueryObject: Record, 1031 | embeddedUniqueProperty: string[] = [] 1032 | ) { 1033 | const currentSchemaMap = schema.schemaMap; 1034 | this.checkDataFields(queryObject, currentSchemaMap); 1035 | for (const property in queryObject) { 1036 | // current SchemaMap's current property value is either an instance of a Schema or a SchemaOption 1037 | // If Schema is stored, validate embedded object. 1038 | if (currentSchemaMap[property] instanceof Schema) { 1039 | updatedQueryObject[property] = {}; 1040 | embeddedUniqueProperty.push(property); 1041 | await this.validateUpdateAgainstSchema( 1042 | queryObject[property] as Record, 1043 | currentSchemaMap[property], 1044 | updatedQueryObject[property] as Record, 1045 | embeddedUniqueProperty 1046 | ); 1047 | embeddedUniqueProperty.pop(); 1048 | } else { 1049 | this.populateQuery( 1050 | queryObject, 1051 | property, 1052 | currentSchemaMap[property], 1053 | updatedQueryObject 1054 | ); 1055 | await this.checkUnique( 1056 | property, 1057 | currentSchemaMap[property], 1058 | updatedQueryObject, 1059 | embeddedUniqueProperty 1060 | ); 1061 | this.checkConstraints(property, currentSchemaMap[property]); 1062 | } 1063 | } 1064 | return true; 1065 | } 1066 | 1067 | /** 1068 | * Method loops through all fields in query objects and throws an error if any properties exist which are not present in the schema. 1069 | * 1070 | * @param queryObject which is the client document field to update or insert into the database 1071 | * @param schemaMap which contains the schemaMap for the current level in the user's document 1072 | * @returns true or undefined. 1073 | */ 1074 | checkDataFields( 1075 | queryObject: Record, 1076 | schemaMap: Record 1077 | ) { 1078 | for (const property in queryObject) { 1079 | if (!Object.prototype.hasOwnProperty.call(schemaMap, property)) { 1080 | throw new Error( 1081 | 'Requested query object contains properties not present in the Schema.' 1082 | ); 1083 | } 1084 | } 1085 | return true; 1086 | } 1087 | 1088 | /** 1089 | * Method validates a property with the option of 'required' has a given value and throws an error if any property does not meet schema criteria. 1090 | * 1091 | * @param queryObject which is the client document field to update or insert into the database 1092 | * @param propertyName which is the property key to check 1093 | * @param propertyOptions which is the propertyOptions object for the given property from the schema 1094 | * @returns true or undefined. 1095 | */ 1096 | checkRequired( 1097 | queryObject: Record, 1098 | propertyName: string, 1099 | propertyOptions: optionsObject 1100 | ) { 1101 | if (propertyOptions.required === true) { 1102 | if (!Object.prototype.hasOwnProperty.call(queryObject, propertyName)) { 1103 | throw new Error(`${propertyName} is Required by the Schema.`); 1104 | } 1105 | } 1106 | return true; 1107 | } 1108 | 1109 | /** 1110 | * Method populates original queryObject with properties not present in the query but present in the schema with their specified default values. 1111 | * 1112 | * @param queryObject which is the client document field to update or insert into the database 1113 | * @param propertyName which is the property key to check 1114 | * @param propertyOptions which is the propertyOptions object for the given property from the schema 1115 | * @returns true or undefined. 1116 | */ 1117 | setDefault( 1118 | queryObject: Record, 1119 | propertyName: string, 1120 | propertyOptions: optionsObject 1121 | ) { 1122 | if (!Object.prototype.hasOwnProperty.call(queryObject, propertyName)) { 1123 | queryObject[propertyName] = propertyOptions.default; 1124 | } 1125 | return true; 1126 | } 1127 | 1128 | /** 1129 | * Method populates the property updatedQueryObject object to be used in the actual query. 1130 | * Creates an instance of the given schema datatype with the user value from their query. 1131 | * Casts the value to the datatype and returns an error if impossible. 1132 | * 1133 | * @param queryObject which is the client document field to update or insert into the database 1134 | * @param propertyName which is the property key to check 1135 | * @param propertyOptions which is the propertyOptions object for the given property from the schema 1136 | * @param updatedQueryObject which is the formatted version of the user's queryObject with properly coverted types for each value. 1137 | * This may refer to the outer object or embedded objects within. 1138 | * @returns true or undefined. 1139 | */ 1140 | populateQuery( 1141 | queryObject: Record, 1142 | propertyName: string, 1143 | propertyOptions: optionsObject, 1144 | updatedQueryObject: Record 1145 | ) { 1146 | const valueAsDatatype = new propertyOptions.type(queryObject[propertyName]); 1147 | valueAsDatatype.convertType(); 1148 | valueAsDatatype.validateType(); 1149 | if (valueAsDatatype.valid === false) { 1150 | throw new Error( 1151 | 'Data was not able to be translated to given specified schema data type.' 1152 | ); 1153 | } 1154 | updatedQueryObject[propertyName] = valueAsDatatype.convertedValue; 1155 | } 1156 | 1157 | /** 1158 | * Method queries the database to see if a document already exists with a duplicate value for the given property. 1159 | * 1160 | * @param propertyName which is the property key to check 1161 | * @param propertyOptions which is the propertyOptions object for the given property from the schema 1162 | * @param updatedQueryObject which is the formatted version of the user's queryObject with properly coverted types for each value 1163 | * This may refer to the outer object or embedded objects within. 1164 | * @param embeddedUniqueProperty When checking a property in an embedded document with the schema option unique, set to true, this array will 1165 | * contain property keys from outer levels 1166 | * @returns true or undefined. 1167 | */ 1168 | async checkUnique( 1169 | propertyName: string, 1170 | propertyOptions: optionsObject, 1171 | updatedQueryObject: Record, 1172 | embeddedUniqueProperty: string[] 1173 | ) { 1174 | if (propertyOptions.unique === true) { 1175 | // query object to check if unique value already exists in database collection 1176 | const queryObjectForUnique: Record = {}; 1177 | // constructs correctly formatted string for property key of queryObjectForUnique 1178 | const formattedPropertyName = this.formatQueryField( 1179 | propertyName, 1180 | embeddedUniqueProperty 1181 | ); 1182 | queryObjectForUnique[formattedPropertyName] = 1183 | updatedQueryObject[propertyName]; 1184 | const propertyExists = await this.findOne(queryObjectForUnique); 1185 | if (propertyExists !== undefined) { 1186 | throw new Error( 1187 | 'Property designated as unique in Schema already exists.' 1188 | ); 1189 | } 1190 | } 1191 | return true; 1192 | } 1193 | 1194 | /** 1195 | * Method queries the database to see if a document already exists with a duplicate value for the given property. 1196 | * This method is different from checkUnique due to edge case where document property flagged as unique replaces itself. 1197 | * 1198 | * @param propertyName which is the property key to check 1199 | * @param findObject The query used to match documents 1200 | * @param updatedQueryObject which is the formatted version of the user's queryObject with properly coverted types for each value 1201 | * This may refer to the outer object or embedded objects within. 1202 | * @param embeddedUniqueProperty When checking a property in an embedded document with the schema option unique, set to true, this array will 1203 | * contain property keys from outer levels 1204 | * @returns true or undefined. 1205 | */ 1206 | async checkUniqueForReplace( 1207 | propertyName: string, 1208 | propertyOptions: optionsObject, 1209 | findObject: Record, 1210 | updatedQueryObject: Record, 1211 | embeddedUniqueProperty: string[] 1212 | ) { 1213 | if (propertyOptions.unique === true) { 1214 | let originalPropertyValue = await this.findOne(findObject); 1215 | if (originalPropertyValue === undefined) { 1216 | throw new Error('No database entry found on query.'); 1217 | } else if (originalPropertyValue === null) 1218 | throw new Error('No database entry found on query.'); 1219 | else if (typeof originalPropertyValue === 'object') { 1220 | // iterate through embeddedUniqueProperty array to access embedded objects that directly contains current propertyName 1221 | if (embeddedUniqueProperty.length) { 1222 | embeddedUniqueProperty.forEach((prop) => { 1223 | //@ts-ignore Fix later. 1224 | originalPropertyValue = originalPropertyValue[prop]; 1225 | }); 1226 | } 1227 | // convert originalPropertyValue's type to match matching property's type in the updatedQueryObject 1228 | const valueAsDatatype = new propertyOptions.type( 1229 | //@ts-ignore Fix later. 1230 | originalPropertyValue[propertyName] 1231 | ); 1232 | valueAsDatatype.convertType(); 1233 | valueAsDatatype.validateType(); 1234 | if (valueAsDatatype.valid === false) { 1235 | throw new Error( 1236 | 'Data was not able to be translated to given specified schema data type.' 1237 | ); 1238 | } 1239 | const formattedOriginalPropertyValue = JSON.stringify( 1240 | valueAsDatatype.convertedValue 1241 | ); 1242 | const formattedUpdatedQueryObjectValue = JSON.stringify( 1243 | updatedQueryObject[propertyName] 1244 | ); 1245 | 1246 | // query object to check if unique value already exists in database collection 1247 | const queryObjectForUnique: Record = {}; 1248 | // constructs correctly formatted string for property key of queryObjectForUnique 1249 | const formattedPropertyName = this.formatQueryField( 1250 | propertyName, 1251 | embeddedUniqueProperty 1252 | ); 1253 | queryObjectForUnique[formattedPropertyName] = 1254 | updatedQueryObject[propertyName]; 1255 | const propertyExists = await this.findOne(queryObjectForUnique); 1256 | 1257 | // If property value exists in database collection, AND the property value from the found document (ln 1096) doesn't match 1258 | // the property value in the updatedQueryObject, throw an error. 1259 | if ( 1260 | propertyExists !== undefined && 1261 | formattedOriginalPropertyValue !== formattedUpdatedQueryObjectValue 1262 | ) { 1263 | throw new Error( 1264 | `Property designated as unique in Schema already exists. ${formattedPropertyName}: ${formattedUpdatedQueryObjectValue}` 1265 | ); 1266 | } 1267 | } 1268 | } 1269 | return true; 1270 | } 1271 | 1272 | /** 1273 | * Method validates that the casted datatype satisfies the user defined callback function. 1274 | * 1275 | * @param propertyName which is the property key to check 1276 | * @param propertyOptions which is the propertyOptions object for the given property from the schema 1277 | * @returns true or undefined. 1278 | */ 1279 | checkConstraints(propertyName: string, propertyOptions: optionsObject) { 1280 | if (propertyOptions.validator === null) return true; 1281 | if (typeof propertyOptions.validator !== 'function') { 1282 | throw new Error( 1283 | 'Callback given as validator in Schema is not a function.' 1284 | ); 1285 | } 1286 | const isConstraintMet = propertyOptions.validator( 1287 | this.updatedQueryObject[propertyName] 1288 | ); 1289 | if (isConstraintMet !== true) { 1290 | throw new Error('Callback given as validator in Schema is violated.'); 1291 | } 1292 | return true; 1293 | } 1294 | 1295 | /** 1296 | * Method resets the updatedQueryObject to an empty object after an insert, replace, or update query. 1297 | * 1298 | * @returns undefined. 1299 | */ 1300 | resetQueryObject() { 1301 | this.updatedQueryObject = {}; 1302 | return; 1303 | } 1304 | 1305 | /** 1306 | * Method 1307 | * 1308 | * @param propertyName which is the property key to check, will be the last property in the returned string 1309 | * @param embeddedUniqueProperty When checking a property in an embedded document with the schema option 'unique', set to true, this array will 1310 | * contain property keys from outer levels. 1311 | * @returns a string that is either equal to propertyName or a concatenated string containing properties from parent levels with dot notation 1312 | * in order to format a proper string key to execute a database query for a value inside an embedded document. 1313 | */ 1314 | formatQueryField(propertyName: string, embeddedUniqueProperty: string[]) { 1315 | let string = ''; 1316 | if (embeddedUniqueProperty.length === 0) { 1317 | return propertyName; 1318 | } else { 1319 | while (embeddedUniqueProperty.length > 0) { 1320 | if (string === '') string += embeddedUniqueProperty.shift(); 1321 | else string += `.${embeddedUniqueProperty.shift()}`; 1322 | } 1323 | string += `.${propertyName}`; 1324 | } 1325 | return string; 1326 | } 1327 | } 1328 | 1329 | export { Query }; 1330 | -------------------------------------------------------------------------------- /lib/schema.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | /** 4 | * 5 | * @description This file defines the schema class. 6 | * 7 | */ 8 | 9 | import { dango } from './dango.ts'; 10 | 11 | /** 12 | * Class definition of a Schema. 13 | * @param schemaObj Object passed in by users containing key value pairs of properties allowed in the document with a value of an object containing property options including: 14 | * type - Required. A lowercase string of expected datatype 15 | * required - If set to true, the property must include a value in insertion/replace queries 16 | * unique - If set to true, the database can only have one instance with the specified insertion/update value. Ignores null. 17 | * default - The default value of the property if not specified. 18 | * validator - A user-defined function that will take the converted value and return a boolean for validation 19 | * Sets the schemaMap property to objects populated with all options. 20 | * 21 | * @returns An object of class Schema. 22 | */ 23 | export class Schema { 24 | schemaMap: Record; 25 | 26 | constructor(schemaObj: Record) { 27 | if (schemaObj === undefined) { 28 | throw new Error('Schema requires a valid argument.'); 29 | } 30 | this.schemaMap = {}; 31 | for (const property in schemaObj) { 32 | // SJ: check if Schema assigned as value 33 | if (schemaObj[property] instanceof Schema) { 34 | // SJ: assign Schema 35 | this.schemaMap[property] = schemaObj[property]; 36 | } else if (typeof schemaObj[property] === 'object') { 37 | this.schemaMap[property] = new SchemaOptions(schemaObj[property]); 38 | } else if ( 39 | typeof schemaObj[property] !== 'object' && 40 | Object.prototype.hasOwnProperty.call(dango.types, schemaObj[property]) 41 | ) { 42 | this.schemaMap[property] = new SchemaOptions({ 43 | type: schemaObj[property], 44 | }); 45 | } else { 46 | throw new Error( 47 | 'Argument for schema definition incorrectly formatted.' 48 | ); 49 | } 50 | } 51 | } 52 | } 53 | 54 | export interface optionsObject { 55 | type: any; 56 | required?: boolean; 57 | unique?: boolean; 58 | default?: any; 59 | validator?: Function | null; 60 | } 61 | 62 | /** 63 | * Class definition of a SchemaOptions. 64 | * @param options Options object passed in by the user. Default values are set for each property and overriden by user input. 65 | * 66 | * @returns An object of class SchemaOptions. 67 | */ 68 | 69 | export class SchemaOptions { 70 | type: any; 71 | required?: boolean; 72 | unique?: boolean; 73 | default?: any; 74 | validator?: Function | null; 75 | 76 | constructor(options: optionsObject) { 77 | if (!Object.prototype.hasOwnProperty.call(options, 'type')) { 78 | throw new Error('Type must be specified'); 79 | } 80 | this.type = undefined; 81 | this.required = false; 82 | this.unique = false; 83 | this.default = null; 84 | this.validator = null; 85 | for (const key in options) { 86 | if (key === 'type') { 87 | if (Object.prototype.hasOwnProperty.call(dango.types, options[key])) { 88 | this.type = dango.types[options[key]]; 89 | } else { 90 | throw new Error('Specified type is invalid'); 91 | } 92 | } else if (Object.prototype.hasOwnProperty.call(this, key)) { 93 | this[key as keyof optionsObject] = options[key as keyof optionsObject]; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export { dango } from './lib/dango.ts'; -------------------------------------------------------------------------------- /tests/test_connections.ts: -------------------------------------------------------------------------------- 1 | // Required Flags for Test: 2 | // - --allow-read 3 | // - --allow-net 4 | 5 | import { 6 | assertInstanceOf, 7 | assertRejects, 8 | assertStrictEquals, 9 | assertThrows, 10 | afterEach, 11 | beforeEach, 12 | describe, 13 | it, 14 | } from '../deps.ts'; 15 | 16 | import { dotenv } from '../deps.ts'; 17 | 18 | import { MongoClient, Database } from '../deps.ts'; 19 | 20 | import { Connection } from '../lib/connections.ts'; 21 | 22 | const ENV = dotenv.config({ path: '../.env' }); 23 | 24 | describe('Connection constructor', () => { 25 | let newObject: unknown; 26 | describe('creating an instance of the class with no input', () => { 27 | it('will throw an error', () => { 28 | assertThrows(() => { 29 | //@ts-ignore Ignore TS warning to run test 30 | newObject = new Connection(); 31 | }); 32 | }); 33 | }); 34 | describe('creating an instance of the class with a valid URI string', () => { 35 | beforeEach(() => { 36 | newObject = new Connection(ENV.URI_STRING); 37 | }); 38 | it('will create an instance of the class', () => { 39 | assertInstanceOf(newObject, Connection); 40 | }); 41 | it('will have a connected property assigned the value of false', () => { 42 | if (newObject instanceof Connection) { 43 | assertStrictEquals(newObject.connected, false); 44 | } 45 | }); 46 | it('will have a connectionString property assigned the value of the URI connection string', () => { 47 | if (newObject instanceof Connection) { 48 | //@ts-ignore Ignore TS warning to run test 49 | assertStrictEquals(newObject.connectionString, ENV.URI_STRING); 50 | } 51 | }); 52 | it('will have a db property assigned the value of false', () => { 53 | if (newObject instanceof Connection) { 54 | assertStrictEquals(newObject.db, false); 55 | } 56 | }); 57 | it('will have a client property assigned the value of undefined', () => { 58 | if (newObject instanceof Connection) { 59 | //@ts-ignore Ignore TS warning to run test 60 | assertStrictEquals(newObject.client, undefined); 61 | } 62 | }); 63 | it('will have a connect method', () => { 64 | if (newObject instanceof Connection) { 65 | assertInstanceOf(newObject.connect, Function); 66 | } 67 | }); 68 | it('will have a disconnect method', () => { 69 | if (newObject instanceof Connection) { 70 | assertInstanceOf(newObject.disconnect, Function); 71 | } 72 | }); 73 | }); 74 | }); 75 | describe('Connection methods', () => { 76 | let newObject: unknown; 77 | describe('using the connect method', () => { 78 | describe('creating a Connection object with a valid URI', () => { 79 | beforeEach(() => { 80 | newObject = new Connection(ENV.URI_STRING); 81 | }); 82 | afterEach(async () => { 83 | if (newObject instanceof Connection) { 84 | await newObject.disconnect(); 85 | } 86 | }); 87 | it('will set connected property to true', async () => { 88 | if (newObject instanceof Connection) { 89 | await newObject.connect(); 90 | assertStrictEquals(newObject.connected, true); 91 | } 92 | }); 93 | it('will connect to the MongoClient and set client property to the connection', async () => { 94 | if (newObject instanceof Connection) { 95 | await newObject.connect(); 96 | //@ts-ignore Ignore TS warning to run test 97 | assertInstanceOf(newObject.client, MongoClient); 98 | } 99 | }); 100 | it('will connect to the database and set db property to the connection', async () => { 101 | if (newObject instanceof Connection) { 102 | await newObject.connect(); 103 | assertInstanceOf(newObject.db, Database); 104 | } 105 | }); 106 | }); 107 | describe('creating a Connection object with an invalid URI', () => { 108 | beforeEach(() => { 109 | newObject = new Connection('BAD_URI_STRING'); 110 | }); 111 | it('will throw an error', async () => { 112 | await assertRejects(async () => { 113 | if (newObject instanceof Connection) { 114 | await newObject.connect(); 115 | } 116 | }); 117 | }); 118 | it('will not change the value of the connected property', async () => { 119 | if (newObject instanceof Connection) { 120 | try { 121 | await newObject.connect(); 122 | } catch (err) { 123 | assertStrictEquals(newObject.connected, false); 124 | } 125 | } 126 | }); 127 | it('will not change the value of the db property', async () => { 128 | if (newObject instanceof Connection) { 129 | try { 130 | await newObject.connect(); 131 | } catch (err) { 132 | assertStrictEquals(newObject.db, false); 133 | } 134 | } 135 | }); 136 | }); 137 | }); 138 | describe('using the disconnect method', () => { 139 | describe('invoking disconnect after a connection is established', () => { 140 | beforeEach(() => { 141 | newObject = new Connection(ENV.URI_STRING); 142 | }); 143 | it('should reset the value of the connected property', async () => { 144 | if (newObject instanceof Connection) { 145 | await newObject.connect(); 146 | await newObject.disconnect(); 147 | assertStrictEquals(newObject.connected, false); 148 | } 149 | }); 150 | it('should reset the value of the db property', async () => { 151 | if (newObject instanceof Connection) { 152 | await newObject.connect(); 153 | await newObject.disconnect(); 154 | assertStrictEquals(newObject.db, false); 155 | } 156 | }); 157 | }); 158 | describe('invoking disconnect before a connection is established', () => { 159 | it('will throw an error', () => { 160 | if (newObject instanceof Connection) { 161 | assertRejects(async () => { 162 | if (newObject instanceof Connection) { 163 | await newObject.disconnect(); 164 | } 165 | }); 166 | } 167 | }); 168 | it('will not change the value of the connected property', async () => { 169 | if (newObject instanceof Connection) { 170 | try { 171 | await newObject.disconnect(); 172 | } catch (err) { 173 | assertStrictEquals(newObject.connected, false); 174 | } 175 | } 176 | }); 177 | it('will not change the value of the db property', async () => { 178 | if (newObject instanceof Connection) { 179 | try { 180 | await newObject.disconnect(); 181 | } catch (err) { 182 | assertStrictEquals(newObject.db, false); 183 | } 184 | } 185 | }); 186 | }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /tests/test_datatypes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertInstanceOf, 3 | assertStrictEquals, 4 | assertThrows, 5 | beforeEach, 6 | describe, 7 | it, 8 | } from '../deps.ts'; 9 | 10 | import { 11 | SchemaNumber, 12 | SchemaDecimal128, 13 | SchemaString, 14 | SchemaBoolean, 15 | SchemaObjectId, 16 | SchemaUUID, 17 | SchemaDate 18 | } from '../lib/datatypes.ts' 19 | 20 | import { 21 | Bson 22 | } from '../deps.ts' 23 | 24 | describe('Schema Number', () => { 25 | let newObject: unknown 26 | describe('creating an instance of the class with no input', () => { 27 | it('will throw an error', () => { 28 | assertThrows(() => { 29 | //@ts-ignore Ignore TS warning to run test 30 | newObject = new SchemaNumber(); 31 | }); 32 | }); 33 | }); 34 | describe('creating an instance of the class with a null input', () => { 35 | beforeEach(() => { 36 | newObject = new SchemaNumber(null) 37 | }); 38 | it('will create an instance of the class', () => { 39 | assertInstanceOf(newObject, SchemaNumber) 40 | }); 41 | it('will have a convertType method', () => { 42 | if (newObject instanceof SchemaNumber) { 43 | assertInstanceOf(newObject.convertType, Function); 44 | } 45 | }); 46 | it('will cast the input and set property convertedValue to null', () => { 47 | if (newObject instanceof SchemaNumber) { 48 | newObject.convertType(); 49 | assertStrictEquals(newObject.convertedValue, null); 50 | } 51 | }); 52 | it('will set property valid to true after invoking validateType', () => { 53 | if (newObject instanceof SchemaNumber) { 54 | newObject.validateType(); 55 | assertStrictEquals(newObject.valid, false); 56 | newObject.convertType() 57 | newObject.validateType(); 58 | assertStrictEquals(newObject.valid, true); 59 | } 60 | }); 61 | }); 62 | describe('creating an instance of the class with a Bson Double input', () => { 63 | beforeEach(() => { 64 | newObject = new SchemaNumber(new Bson.Double(3.1415)) 65 | }); 66 | it('will create an instance of the class', () => { 67 | assertInstanceOf(newObject, SchemaNumber) 68 | }); 69 | it('will have a convertType method', () => { 70 | if (newObject instanceof SchemaNumber) { 71 | assertInstanceOf(newObject.convertType, Function); 72 | } 73 | }); 74 | it('will cast the input and set property convertedValue to a Double', () => { 75 | if (newObject instanceof SchemaNumber) { 76 | newObject.convertType(); 77 | assertInstanceOf(newObject.convertedValue, Bson.Double); 78 | } 79 | }); 80 | it('will set property valid to true after invoking validateType', () => { 81 | if (newObject instanceof SchemaNumber) { 82 | newObject.validateType(); 83 | assertStrictEquals(newObject.valid, false); 84 | newObject.convertType() 85 | newObject.validateType(); 86 | assertStrictEquals(newObject.valid, true); 87 | } 88 | }); 89 | }); 90 | describe('creating an instance of the class with a non-string or number input', () => { 91 | beforeEach(() => { 92 | newObject = new SchemaNumber([5]) 93 | }); 94 | it('will create an instance of the class', () => { 95 | assertInstanceOf(newObject, SchemaNumber) 96 | }); 97 | it('will have a convertType method', () => { 98 | if (newObject instanceof SchemaNumber) { 99 | assertInstanceOf(newObject.convertType, Function); 100 | } 101 | }); 102 | it('will be unable cast the input and set property convertedValue to a Double. Value will be undefined', () => { 103 | if (newObject instanceof SchemaNumber) { 104 | newObject.convertType(); 105 | assertStrictEquals(newObject.convertedValue, undefined); 106 | } 107 | }); 108 | it('will set property valid to false after invoking validateType', () => { 109 | if (newObject instanceof SchemaNumber) { 110 | newObject.validateType(); 111 | assertStrictEquals(newObject.valid, false); 112 | newObject.convertType() 113 | newObject.validateType(); 114 | assertStrictEquals(newObject.valid, false); 115 | } 116 | }); 117 | }); 118 | describe('creating an instance of the class with string input that cannot be cast to a number', () => { 119 | beforeEach(() => { 120 | newObject = new SchemaNumber('dangoDB') 121 | }); 122 | it('will create an instance of the class', () => { 123 | assertInstanceOf(newObject, SchemaNumber) 124 | }); 125 | it('will have a convertType method', () => { 126 | if (newObject instanceof SchemaNumber) { 127 | assertInstanceOf(newObject.convertType, Function); 128 | } 129 | }); 130 | it('will be unable cast the input and set property convertedValue to a Double. Value will be undefined', () => { 131 | if (newObject instanceof SchemaNumber) { 132 | newObject.convertType(); 133 | assertStrictEquals(newObject.convertedValue, undefined); 134 | } 135 | }); 136 | it('will set property valid to false after invoking validateType', () => { 137 | if (newObject instanceof SchemaNumber) { 138 | newObject.validateType(); 139 | assertStrictEquals(newObject.valid, false); 140 | newObject.convertType() 141 | newObject.validateType(); 142 | assertStrictEquals(newObject.valid, false); 143 | } 144 | }); 145 | }); 146 | describe('creating an instance of the class with string input that can be cast to a number', () => { 147 | beforeEach(() => { 148 | newObject = new SchemaNumber('5') 149 | }); 150 | it('will create an instance of the class', () => { 151 | assertInstanceOf(newObject, SchemaNumber) 152 | }); 153 | it('will have a convertType method', () => { 154 | if (newObject instanceof SchemaNumber) { 155 | assertInstanceOf(newObject.convertType, Function); 156 | } 157 | }); 158 | it('will cast the input and set property convertedValue to a Double', () => { 159 | if (newObject instanceof SchemaNumber) { 160 | newObject.convertType(); 161 | assertInstanceOf(newObject.convertedValue, Bson.Double); 162 | } 163 | }); 164 | it('will set property valid to true after invoking validateType', () => { 165 | if (newObject instanceof SchemaNumber) { 166 | newObject.validateType(); 167 | assertStrictEquals(newObject.valid, false); 168 | newObject.convertType() 169 | newObject.validateType(); 170 | assertStrictEquals(newObject.valid, true); 171 | } 172 | }); 173 | }); 174 | describe('creating an instance of the class with a number input', () => { 175 | beforeEach(() => { 176 | newObject = new SchemaNumber(5) 177 | }); 178 | it('will create an instance of the class', () => { 179 | assertInstanceOf(newObject, SchemaNumber) 180 | }); 181 | it('will have a convertType method', () => { 182 | if (newObject instanceof SchemaNumber) { 183 | assertInstanceOf(newObject.convertType, Function); 184 | } 185 | }); 186 | it('will cast the input and set property convertedValue to a Double', () => { 187 | if (newObject instanceof SchemaNumber) { 188 | newObject.convertType(); 189 | assertInstanceOf(newObject.convertedValue, Bson.Double); 190 | } 191 | }); 192 | it('will set property valid to true after invoking validateType', () => { 193 | if (newObject instanceof SchemaNumber) { 194 | newObject.validateType(); 195 | assertStrictEquals(newObject.valid, false); 196 | newObject.convertType() 197 | newObject.validateType(); 198 | assertStrictEquals(newObject.valid, true); 199 | } 200 | }); 201 | }); 202 | }); 203 | 204 | describe('Schema Decimal128', () => { 205 | let newObject: unknown 206 | describe('creating an instance of the class with no input', () => { 207 | it('will throw an error', () => { 208 | assertThrows(() => { 209 | //@ts-ignore Ignore TS warning to run test 210 | newObject = new SchemaDecimal128(); 211 | }); 212 | }); 213 | }); 214 | describe('creating an instance of the class with a null input', () => { 215 | beforeEach(() => { 216 | newObject = new SchemaDecimal128(null) 217 | }); 218 | it('will create an instance of the class', () => { 219 | assertInstanceOf(newObject, SchemaDecimal128) 220 | }); 221 | it('will have a convertType method', () => { 222 | if (newObject instanceof SchemaDecimal128) { 223 | assertInstanceOf(newObject.convertType, Function); 224 | } 225 | }); 226 | it('will cast the input and set property convertedValue to null', () => { 227 | if (newObject instanceof SchemaDecimal128) { 228 | newObject.convertType(); 229 | assertStrictEquals(newObject.convertedValue, null); 230 | } 231 | }); 232 | it('will set property valid to true after invoking validateType', () => { 233 | if (newObject instanceof SchemaDecimal128) { 234 | newObject.validateType(); 235 | assertStrictEquals(newObject.valid, false); 236 | newObject.convertType() 237 | newObject.validateType(); 238 | assertStrictEquals(newObject.valid, true); 239 | } 240 | }); 241 | }); 242 | describe('creating an instance of the class with a Decimal128 input', () => { 243 | beforeEach(() => { 244 | newObject = new SchemaDecimal128(new Bson.Decimal128('3.1415')) 245 | }); 246 | it('will create an instance of the class', () => { 247 | assertInstanceOf(newObject, SchemaDecimal128) 248 | }); 249 | it('will have a convertType method', () => { 250 | if (newObject instanceof SchemaDecimal128) { 251 | assertInstanceOf(newObject.convertType, Function); 252 | } 253 | }); 254 | it('will cast the input and set property convertedValue to a Double', () => { 255 | if (newObject instanceof SchemaDecimal128) { 256 | newObject.convertType(); 257 | assertInstanceOf(newObject.convertedValue, Bson.Decimal128); 258 | } 259 | }); 260 | it('will set property valid to true after invoking validateType', () => { 261 | if (newObject instanceof SchemaDecimal128) { 262 | newObject.validateType(); 263 | assertStrictEquals(newObject.valid, false); 264 | newObject.convertType() 265 | newObject.validateType(); 266 | assertStrictEquals(newObject.valid, true); 267 | } 268 | }); 269 | }); 270 | describe('creating an instance of the class with a non-string or number input', () => { 271 | beforeEach(() => { 272 | newObject = new SchemaDecimal128([5]) 273 | }); 274 | it('will create an instance of the class', () => { 275 | assertInstanceOf(newObject, SchemaDecimal128) 276 | }); 277 | it('will have a convertType method', () => { 278 | if (newObject instanceof SchemaDecimal128) { 279 | assertInstanceOf(newObject.convertType, Function); 280 | } 281 | }); 282 | it('will be unable cast the input and set property convertedValue to a Decimal128. Value will be undefined', () => { 283 | if (newObject instanceof SchemaDecimal128) { 284 | newObject.convertType(); 285 | assertStrictEquals(newObject.convertedValue, undefined); 286 | } 287 | }); 288 | it('will set property valid to false after invoking validateType', () => { 289 | if (newObject instanceof SchemaDecimal128) { 290 | newObject.validateType(); 291 | assertStrictEquals(newObject.valid, false); 292 | newObject.convertType() 293 | newObject.validateType(); 294 | assertStrictEquals(newObject.valid, false); 295 | } 296 | }); 297 | }); 298 | describe('creating an instance of the class with string input that cannot be cast to a Decimal128', () => { 299 | beforeEach(() => { 300 | newObject = new SchemaDecimal128('dangoDB') 301 | }); 302 | it('will create an instance of the class', () => { 303 | assertInstanceOf(newObject, SchemaDecimal128) 304 | }); 305 | it('will have a convertType method', () => { 306 | if (newObject instanceof SchemaDecimal128) { 307 | assertInstanceOf(newObject.convertType, Function); 308 | } 309 | }); 310 | it('will be unable cast the input and set property convertedValue to a Decimal128. Value will be undefined', () => { 311 | if (newObject instanceof SchemaDecimal128) { 312 | newObject.convertType(); 313 | assertStrictEquals(newObject.convertedValue, undefined); 314 | } 315 | }); 316 | it('will set property valid to false after invoking validateType', () => { 317 | if (newObject instanceof SchemaDecimal128) { 318 | newObject.validateType(); 319 | assertStrictEquals(newObject.valid, false); 320 | newObject.convertType() 321 | newObject.validateType(); 322 | assertStrictEquals(newObject.valid, false); 323 | } 324 | }); 325 | }); 326 | describe('creating an instance of the class with string input that can be cast to a Decimal128', () => { 327 | beforeEach(() => { 328 | newObject = new SchemaDecimal128('3.1415') 329 | }); 330 | it('will create an instance of the class', () => { 331 | assertInstanceOf(newObject, SchemaDecimal128) 332 | }); 333 | it('will have a convertType method', () => { 334 | if (newObject instanceof SchemaDecimal128) { 335 | assertInstanceOf(newObject.convertType, Function); 336 | } 337 | }); 338 | it('will cast the input and set property convertedValue to a Decimal128', () => { 339 | if (newObject instanceof SchemaDecimal128) { 340 | newObject.convertType(); 341 | assertInstanceOf(newObject.convertedValue, Bson.Decimal128); 342 | } 343 | }); 344 | it('will set property valid to true after invoking validateType', () => { 345 | if (newObject instanceof SchemaDecimal128) { 346 | newObject.validateType(); 347 | assertStrictEquals(newObject.valid, false); 348 | newObject.convertType() 349 | newObject.validateType(); 350 | assertStrictEquals(newObject.valid, true); 351 | } 352 | }); 353 | }); 354 | describe('creating an instance of the class with a number input', () => { 355 | beforeEach(() => { 356 | newObject = new SchemaDecimal128(3.1415) 357 | }); 358 | it('will create an instance of the class', () => { 359 | assertInstanceOf(newObject, SchemaDecimal128) 360 | }); 361 | it('will have a convertType method', () => { 362 | if (newObject instanceof SchemaDecimal128) { 363 | assertInstanceOf(newObject.convertType, Function); 364 | } 365 | }); 366 | it('will cast the input and set property convertedValue to a Decimal128', () => { 367 | if (newObject instanceof SchemaDecimal128) { 368 | newObject.convertType(); 369 | assertInstanceOf(newObject.convertedValue, Bson.Decimal128); 370 | } 371 | }); 372 | it('will set property valid to true after invoking validateType', () => { 373 | if (newObject instanceof SchemaDecimal128) { 374 | newObject.validateType(); 375 | assertStrictEquals(newObject.valid, false); 376 | newObject.convertType() 377 | newObject.validateType(); 378 | assertStrictEquals(newObject.valid, true); 379 | } 380 | }); 381 | }); 382 | }); 383 | 384 | describe('Schema String', () => { 385 | let newObject: unknown 386 | describe('creating an instance of the class with no input', () => { 387 | it('will throw an error', () => { 388 | assertThrows(() => { 389 | //@ts-ignore Ignore TS warning to run test 390 | newObject = new SchemaString(); 391 | }); 392 | }); 393 | }); 394 | describe('creating an instance of the class with a null input', () => { 395 | beforeEach(() => { 396 | newObject = new SchemaString(null) 397 | }); 398 | it('will create an instance of the class', () => { 399 | assertInstanceOf(newObject, SchemaString) 400 | }); 401 | it('will have a convertType method', () => { 402 | if (newObject instanceof SchemaString) { 403 | assertInstanceOf(newObject.convertType, Function); 404 | } 405 | }); 406 | it('will cast the input and set property convertedValue to null', () => { 407 | if (newObject instanceof SchemaString) { 408 | newObject.convertType(); 409 | assertStrictEquals(newObject.convertedValue, null); 410 | } 411 | }); 412 | it('will set property valid to true after invoking validateType', () => { 413 | if (newObject instanceof SchemaString) { 414 | newObject.validateType(); 415 | assertStrictEquals(newObject.valid, false); 416 | newObject.convertType() 417 | newObject.validateType(); 418 | assertStrictEquals(newObject.valid, true); 419 | } 420 | }); 421 | }); 422 | describe('creating an instance of the class with a String object input', () => { 423 | beforeEach(() => { 424 | newObject = new SchemaString(new String('dangoDB')); 425 | }); 426 | it('will create an instance of the class', () => { 427 | assertInstanceOf(newObject, SchemaString) 428 | }); 429 | it('will have a convertType method', () => { 430 | if (newObject instanceof SchemaString) { 431 | assertInstanceOf(newObject.convertType, Function); 432 | } 433 | }); 434 | it('will cast the input and set property convertedValue to a string', () => { 435 | if (newObject instanceof SchemaString) { 436 | newObject.convertType(); 437 | assertInstanceOf(newObject.convertedValue, String); 438 | } 439 | }); 440 | it('will set property valid to true after invoking validateType', () => { 441 | if (newObject instanceof SchemaString) { 442 | newObject.validateType(); 443 | assertStrictEquals(newObject.valid, false); 444 | newObject.convertType() 445 | newObject.validateType(); 446 | assertStrictEquals(newObject.valid, true); 447 | } 448 | }); 449 | }); 450 | describe('creating an instance of the class with a non-string or number or boolean input', () => { 451 | beforeEach(() => { 452 | newObject = new SchemaString(['dangoDB']) 453 | }); 454 | it('will create an instance of the class', () => { 455 | assertInstanceOf(newObject, SchemaString) 456 | }); 457 | it('will have a convertType method', () => { 458 | if (newObject instanceof SchemaString) { 459 | assertInstanceOf(newObject.convertType, Function); 460 | } 461 | }); 462 | it('will be unable cast the input and set property convertedValue to a string. Value will be undefined', () => { 463 | if (newObject instanceof SchemaString) { 464 | newObject.convertType(); 465 | assertStrictEquals(newObject.convertedValue, undefined); 466 | } 467 | }); 468 | it('will set property valid to false after invoking validateType', () => { 469 | if (newObject instanceof SchemaString) { 470 | newObject.validateType(); 471 | assertStrictEquals(newObject.valid, false); 472 | newObject.convertType() 473 | newObject.validateType(); 474 | assertStrictEquals(newObject.valid, false); 475 | } 476 | }); 477 | }); 478 | describe('creating an instance of the class with string input', () => { 479 | beforeEach(() => { 480 | newObject = new SchemaString('dangoDB') 481 | }); 482 | it('will create an instance of the class', () => { 483 | assertInstanceOf(newObject, SchemaString) 484 | }); 485 | it('will have a convertType method', () => { 486 | if (newObject instanceof SchemaString) { 487 | assertInstanceOf(newObject.convertType, Function); 488 | } 489 | }); 490 | it('will cast the input and set property convertedValue to a string', () => { 491 | if (newObject instanceof SchemaString) { 492 | newObject.convertType(); 493 | assertStrictEquals(typeof newObject.convertedValue, 'string'); 494 | } 495 | }); 496 | it('will set property valid to true after invoking validateType', () => { 497 | if (newObject instanceof SchemaString) { 498 | newObject.validateType(); 499 | assertStrictEquals(newObject.valid, false); 500 | newObject.convertType() 501 | newObject.validateType(); 502 | assertStrictEquals(newObject.valid, true); 503 | } 504 | }); 505 | }); 506 | describe('creating an instance of the class with a number input', () => { 507 | beforeEach(() => { 508 | newObject = new SchemaString(3.1415) 509 | }); 510 | it('will create an instance of the class', () => { 511 | assertInstanceOf(newObject, SchemaString) 512 | }); 513 | it('will have a convertType method', () => { 514 | if (newObject instanceof SchemaString) { 515 | assertInstanceOf(newObject.convertType, Function); 516 | } 517 | }); 518 | it('will cast the input and set property convertedValue to a string', () => { 519 | if (newObject instanceof SchemaString) { 520 | newObject.convertType(); 521 | assertStrictEquals(typeof newObject.convertedValue, 'string'); 522 | } 523 | }); 524 | it('will set property valid to true after invoking validateType', () => { 525 | if (newObject instanceof SchemaString) { 526 | newObject.validateType(); 527 | assertStrictEquals(newObject.valid, false); 528 | newObject.convertType() 529 | newObject.validateType(); 530 | assertStrictEquals(newObject.valid, true); 531 | } 532 | }); 533 | }); 534 | describe('creating an instance of the class with a boolean input', () => { 535 | beforeEach(() => { 536 | newObject = new SchemaString(true) 537 | }); 538 | it('will create an instance of the class', () => { 539 | assertInstanceOf(newObject, SchemaString) 540 | }); 541 | it('will have a convertType method', () => { 542 | if (newObject instanceof SchemaString) { 543 | assertInstanceOf(newObject.convertType, Function); 544 | } 545 | }); 546 | it('will cast the input and set property convertedValue to a string', () => { 547 | if (newObject instanceof SchemaString) { 548 | newObject.convertType(); 549 | assertStrictEquals(typeof newObject.convertedValue, 'string'); 550 | } 551 | }); 552 | it('will cast the input and set property convertedValue to a value of "true"', () => { 553 | if (newObject instanceof SchemaString) { 554 | newObject.convertType(); 555 | assertStrictEquals(newObject.convertedValue, 'true'); 556 | } 557 | }); 558 | it('will set property valid to true after invoking validateType', () => { 559 | if (newObject instanceof SchemaString) { 560 | newObject.validateType(); 561 | assertStrictEquals(newObject.valid, false); 562 | newObject.convertType() 563 | newObject.validateType(); 564 | assertStrictEquals(newObject.valid, true); 565 | } 566 | }); 567 | }); 568 | }); 569 | 570 | describe('Schema Boolean', () => { 571 | let newObject: unknown 572 | describe('creating an instance of the class with no input', () => { 573 | it('will throw an error', () => { 574 | assertThrows(() => { 575 | //@ts-ignore Ignore TS warning to run test 576 | newObject = new SchemaBoolean(); 577 | }); 578 | }); 579 | }); 580 | describe('creating an instance of the class with a null input', () => { 581 | beforeEach(() => { 582 | newObject = new SchemaBoolean(null) 583 | }); 584 | it('will create an instance of the class', () => { 585 | assertInstanceOf(newObject, SchemaBoolean) 586 | }); 587 | it('will have a convertType method', () => { 588 | if (newObject instanceof SchemaBoolean) { 589 | assertInstanceOf(newObject.convertType, Function); 590 | } 591 | }); 592 | it('will cast the input and set property convertedValue to null', () => { 593 | if (newObject instanceof SchemaBoolean) { 594 | newObject.convertType(); 595 | assertStrictEquals(newObject.convertedValue, null); 596 | } 597 | }); 598 | it('will set property valid to true after invoking validateType', () => { 599 | if (newObject instanceof SchemaBoolean) { 600 | newObject.validateType(); 601 | assertStrictEquals(newObject.valid, false); 602 | newObject.convertType() 603 | newObject.validateType(); 604 | assertStrictEquals(newObject.valid, true); 605 | } 606 | }); 607 | }); 608 | describe('creating an instance of the class with a Boolean object input', () => { 609 | beforeEach(() => { 610 | newObject = new SchemaBoolean(new Boolean(false)); 611 | }); 612 | it('will create an instance of the class', () => { 613 | assertInstanceOf(newObject, SchemaBoolean) 614 | }); 615 | it('will have a convertType method', () => { 616 | if (newObject instanceof SchemaBoolean) { 617 | assertInstanceOf(newObject.convertType, Function); 618 | } 619 | }); 620 | it('will cast the input and set property convertedValue to a boolean', () => { 621 | if (newObject instanceof SchemaBoolean) { 622 | newObject.convertType(); 623 | assertInstanceOf(newObject.convertedValue, Boolean); 624 | } 625 | }); 626 | it('will set property valid to true after invoking validateType', () => { 627 | if (newObject instanceof SchemaBoolean) { 628 | newObject.validateType(); 629 | assertStrictEquals(newObject.valid, false); 630 | newObject.convertType() 631 | newObject.validateType(); 632 | assertStrictEquals(newObject.valid, true); 633 | } 634 | }); 635 | }); 636 | describe('creating an instance of the class with a non-string or number or boolean input', () => { 637 | beforeEach(() => { 638 | newObject = new SchemaBoolean([true]) 639 | }); 640 | it('will create an instance of the class', () => { 641 | assertInstanceOf(newObject, SchemaBoolean) 642 | }); 643 | it('will have a convertType method', () => { 644 | if (newObject instanceof SchemaBoolean) { 645 | assertInstanceOf(newObject.convertType, Function); 646 | } 647 | }); 648 | it('will be unable cast the input and set property convertedValue to a boolean. Value will be undefined', () => { 649 | if (newObject instanceof SchemaBoolean) { 650 | newObject.convertType(); 651 | assertStrictEquals(newObject.convertedValue, undefined); 652 | } 653 | }); 654 | it('will set property valid to false after invoking validateType', () => { 655 | if (newObject instanceof SchemaBoolean) { 656 | newObject.validateType(); 657 | assertStrictEquals(newObject.valid, false); 658 | newObject.convertType() 659 | newObject.validateType(); 660 | assertStrictEquals(newObject.valid, false); 661 | } 662 | }); 663 | }); 664 | describe('creating an instance of the class with string input', () => { 665 | let newObject1: unknown; 666 | let newObject2: unknown; 667 | beforeEach(() => { 668 | newObject1 = new SchemaBoolean('true'); 669 | newObject2 = new SchemaBoolean('false'); 670 | }); 671 | it('will create an instance of the class', () => { 672 | assertInstanceOf(newObject1, SchemaBoolean) 673 | }); 674 | it('will have a convertType method', () => { 675 | if (newObject1 instanceof SchemaBoolean) { 676 | assertInstanceOf(newObject1.convertType, Function); 677 | } 678 | }); 679 | it('will cast the input and set property convertedValue to a boolean of string value', () => { 680 | if (newObject1 instanceof SchemaBoolean) { 681 | newObject1.convertType(); 682 | assertStrictEquals(typeof newObject1.convertedValue, 'boolean'); 683 | assertStrictEquals(newObject1.convertedValue, true); 684 | } 685 | if (newObject2 instanceof SchemaBoolean) { 686 | newObject2.convertType(); 687 | assertStrictEquals(typeof newObject2.convertedValue, 'boolean'); 688 | assertStrictEquals(newObject2.convertedValue, false); 689 | } 690 | }); 691 | it('will set property valid to true after invoking validateType', () => { 692 | if (newObject1 instanceof SchemaBoolean) { 693 | newObject1.validateType(); 694 | assertStrictEquals(newObject1.valid, false); 695 | newObject1.convertType() 696 | newObject1.validateType(); 697 | assertStrictEquals(newObject1.valid, true); 698 | } 699 | }); 700 | }); 701 | describe('creating an instance of the class with number input', () => { 702 | let newObject1: unknown; 703 | let newObject2: unknown; 704 | beforeEach(() => { 705 | newObject1 = new SchemaBoolean(1); 706 | newObject2 = new SchemaBoolean(0); 707 | }); 708 | it('will create an instance of the class', () => { 709 | assertInstanceOf(newObject1, SchemaBoolean) 710 | }); 711 | it('will have a convertType method', () => { 712 | if (newObject1 instanceof SchemaBoolean) { 713 | assertInstanceOf(newObject1.convertType, Function); 714 | } 715 | }); 716 | it('will cast the input and set property convertedValue to a boolean of number value (1 = true, 0 = false)', () => { 717 | if (newObject1 instanceof SchemaBoolean) { 718 | newObject1.convertType(); 719 | assertStrictEquals(typeof newObject1.convertedValue, 'boolean'); 720 | assertStrictEquals(newObject1.convertedValue, true); 721 | } 722 | if (newObject2 instanceof SchemaBoolean) { 723 | newObject2.convertType(); 724 | assertStrictEquals(typeof newObject2.convertedValue, 'boolean'); 725 | assertStrictEquals(newObject2.convertedValue, false); 726 | } 727 | }); 728 | it('will set property valid to true after invoking validateType', () => { 729 | if (newObject1 instanceof SchemaBoolean) { 730 | newObject1.validateType(); 731 | assertStrictEquals(newObject1.valid, false); 732 | newObject1.convertType() 733 | newObject1.validateType(); 734 | assertStrictEquals(newObject1.valid, true); 735 | } 736 | }); 737 | }); 738 | describe('creating an instance of the class with a boolean input', () => { 739 | beforeEach(() => { 740 | newObject = new SchemaBoolean(true) 741 | }); 742 | it('will create an instance of the class', () => { 743 | assertInstanceOf(newObject, SchemaBoolean) 744 | }); 745 | it('will have a convertType method', () => { 746 | if (newObject instanceof SchemaBoolean) { 747 | assertInstanceOf(newObject.convertType, Function); 748 | } 749 | }); 750 | it('will cast the input and set property convertedValue to a boolean', () => { 751 | if (newObject instanceof SchemaBoolean) { 752 | newObject.convertType(); 753 | assertStrictEquals(typeof newObject.convertedValue, 'boolean'); 754 | assertStrictEquals(newObject.convertedValue, true); 755 | } 756 | }); 757 | it('will set property valid to true after invoking validateType', () => { 758 | if (newObject instanceof SchemaBoolean) { 759 | newObject.validateType(); 760 | assertStrictEquals(newObject.valid, false); 761 | newObject.convertType() 762 | newObject.validateType(); 763 | assertStrictEquals(newObject.valid, true); 764 | } 765 | }); 766 | }); 767 | }); 768 | 769 | describe('Schema ObjectId', () => { 770 | let newObject: unknown 771 | describe('creating an instance of the class with no input', () => { 772 | it('will throw an error', () => { 773 | assertThrows(() => { 774 | //@ts-ignore Ignore TS warning to run test 775 | newObject = new SchemaObjectId(); 776 | }); 777 | }); 778 | }); 779 | describe('creating an instance of the class with a null input', () => { 780 | beforeEach(() => { 781 | newObject = new SchemaObjectId(null) 782 | }); 783 | it('will create an instance of the class', () => { 784 | assertInstanceOf(newObject, SchemaObjectId) 785 | }); 786 | it('will have a convertType method', () => { 787 | if (newObject instanceof SchemaObjectId) { 788 | assertInstanceOf(newObject.convertType, Function); 789 | } 790 | }); 791 | it('will cast the input and set property convertedValue to null', () => { 792 | if (newObject instanceof SchemaObjectId) { 793 | newObject.convertType(); 794 | assertStrictEquals(newObject.convertedValue, null); 795 | } 796 | }); 797 | it('will set property valid to true after invoking validateType', () => { 798 | if (newObject instanceof SchemaObjectId) { 799 | newObject.validateType(); 800 | assertStrictEquals(newObject.valid, false); 801 | newObject.convertType() 802 | newObject.validateType(); 803 | assertStrictEquals(newObject.valid, true); 804 | } 805 | }); 806 | }); 807 | describe('creating an instance of the class with a ObjectId input', () => { 808 | beforeEach(() => { 809 | newObject = new SchemaObjectId(new Bson.ObjectId('62743a2d6e85141671a45ea7')); 810 | }); 811 | it('will create an instance of the class', () => { 812 | assertInstanceOf(newObject, SchemaObjectId) 813 | }); 814 | it('will have a convertType method', () => { 815 | if (newObject instanceof SchemaObjectId) { 816 | assertInstanceOf(newObject.convertType, Function); 817 | } 818 | }); 819 | it('will cast the input and set property convertedValue to an ObjectId', () => { 820 | if (newObject instanceof SchemaObjectId) { 821 | newObject.convertType(); 822 | assertInstanceOf(newObject.convertedValue, Bson.ObjectId); 823 | } 824 | }); 825 | it('will set property valid to true after invoking validateType', () => { 826 | if (newObject instanceof SchemaObjectId) { 827 | newObject.validateType(); 828 | assertStrictEquals(newObject.valid, false); 829 | newObject.convertType() 830 | newObject.validateType(); 831 | assertStrictEquals(newObject.valid, true); 832 | } 833 | }); 834 | }); 835 | describe('creating an invalid ObjectId', () => { 836 | beforeEach(() => { 837 | newObject = new SchemaObjectId('dangoDB') 838 | }); 839 | it('will create an instance of the class', () => { 840 | assertInstanceOf(newObject, SchemaObjectId) 841 | }); 842 | it('will have a convertType method', () => { 843 | if (newObject instanceof SchemaObjectId) { 844 | assertInstanceOf(newObject.convertType, Function); 845 | } 846 | }); 847 | it('will be unable cast the input and set property convertedValue to an ObjectId. Value will be undefined', () => { 848 | if (newObject instanceof SchemaObjectId) { 849 | newObject.convertType(); 850 | assertStrictEquals(newObject.convertedValue, undefined); 851 | } 852 | }); 853 | it('will set property valid to false after invoking validateType', () => { 854 | if (newObject instanceof SchemaObjectId) { 855 | newObject.validateType(); 856 | assertStrictEquals(newObject.valid, false); 857 | newObject.convertType() 858 | newObject.validateType(); 859 | assertStrictEquals(newObject.valid, false); 860 | } 861 | }); 862 | }); 863 | describe('creating an instance of the class with string input that can be cast to an ObjectId', () => { 864 | beforeEach(() => { 865 | newObject = new SchemaObjectId('62743a2d6e85141671a45ea7') 866 | }); 867 | it('will create an instance of the class', () => { 868 | assertInstanceOf(newObject, SchemaObjectId) 869 | }); 870 | it('will have a convertType method', () => { 871 | if (newObject instanceof SchemaObjectId) { 872 | assertInstanceOf(newObject.convertType, Function); 873 | } 874 | }); 875 | it('will be able to cast the input and set property convertedValue to an ObjectId', () => { 876 | if (newObject instanceof SchemaObjectId) { 877 | newObject.convertType(); 878 | assertInstanceOf(newObject.convertedValue, Bson.ObjectId); 879 | } 880 | }); 881 | it('will set property valid to true after invoking validateType', () => { 882 | if (newObject instanceof SchemaObjectId) { 883 | newObject.validateType(); 884 | assertStrictEquals(newObject.valid, false); 885 | newObject.convertType() 886 | newObject.validateType(); 887 | assertStrictEquals(newObject.valid, true); 888 | } 889 | }); 890 | }); 891 | }); 892 | 893 | describe('Schema UUID', () => { 894 | let newObject: unknown 895 | describe('creating an instance of the class with no input', () => { 896 | it('will throw an error', () => { 897 | assertThrows(() => { 898 | //@ts-ignore Ignore TS warning to run test 899 | newObject = new SchemaUUID(); 900 | }); 901 | }); 902 | }); 903 | describe('creating an instance of the class with a null input', () => { 904 | beforeEach(() => { 905 | newObject = new SchemaUUID(null) 906 | }); 907 | it('will create an instance of the class', () => { 908 | assertInstanceOf(newObject, SchemaUUID) 909 | }); 910 | it('will have a convertType method', () => { 911 | if (newObject instanceof SchemaUUID) { 912 | assertInstanceOf(newObject.convertType, Function); 913 | } 914 | }); 915 | it('will cast the input and set property convertedValue to null', () => { 916 | if (newObject instanceof SchemaUUID) { 917 | newObject.convertType(); 918 | assertStrictEquals(newObject.convertedValue, null); 919 | } 920 | }); 921 | it('will set property valid to true after invoking validateType', () => { 922 | if (newObject instanceof SchemaUUID) { 923 | newObject.validateType(); 924 | assertStrictEquals(newObject.valid, false); 925 | newObject.convertType() 926 | newObject.validateType(); 927 | assertStrictEquals(newObject.valid, true); 928 | } 929 | }); 930 | }); 931 | describe('creating an instance of the class with a UUID input', () => { 932 | beforeEach(() => { 933 | newObject = new SchemaUUID(new Bson.UUID()); 934 | }); 935 | it('will create an instance of the class', () => { 936 | assertInstanceOf(newObject, SchemaUUID) 937 | }); 938 | it('will have a convertType method', () => { 939 | if (newObject instanceof SchemaUUID) { 940 | assertInstanceOf(newObject.convertType, Function); 941 | } 942 | }); 943 | it('will cast the input and set property convertedValue to an UUID', () => { 944 | if (newObject instanceof SchemaUUID) { 945 | newObject.convertType(); 946 | assertInstanceOf(newObject.convertedValue, Bson.UUID); 947 | } 948 | }); 949 | it('will set property valid to true after invoking validateType', () => { 950 | if (newObject instanceof SchemaUUID) { 951 | newObject.validateType(); 952 | assertStrictEquals(newObject.valid, false); 953 | newObject.convertType() 954 | newObject.validateType(); 955 | assertStrictEquals(newObject.valid, true); 956 | } 957 | }); 958 | }); 959 | describe('creating an invalid UUID', () => { 960 | beforeEach(() => { 961 | newObject = new SchemaUUID('dangoDB') 962 | }); 963 | it('will create an instance of the class', () => { 964 | assertInstanceOf(newObject, SchemaUUID) 965 | }); 966 | it('will have a convertType method', () => { 967 | if (newObject instanceof SchemaUUID) { 968 | assertInstanceOf(newObject.convertType, Function); 969 | } 970 | }); 971 | it('will be unable cast the input and set property convertedValue to an UUID. Value will be undefined', () => { 972 | if (newObject instanceof SchemaUUID) { 973 | newObject.convertType(); 974 | assertStrictEquals(newObject.convertedValue, undefined); 975 | } 976 | }); 977 | it('will set property valid to false after invoking validateType', () => { 978 | if (newObject instanceof SchemaUUID) { 979 | newObject.validateType(); 980 | assertStrictEquals(newObject.valid, false); 981 | newObject.convertType() 982 | newObject.validateType(); 983 | assertStrictEquals(newObject.valid, false); 984 | } 985 | }); 986 | }); 987 | describe('creating an instance of the class with string input that can be cast to a UUID', () => { 988 | let validUUID!: string 989 | beforeEach(() => { 990 | validUUID = new Bson.UUID().toString(); 991 | newObject = new SchemaUUID(validUUID) 992 | }); 993 | it('will create an instance of the class', () => { 994 | assertInstanceOf(newObject, SchemaUUID) 995 | }); 996 | it('will have a convertType method', () => { 997 | if (newObject instanceof SchemaUUID) { 998 | assertInstanceOf(newObject.convertType, Function); 999 | } 1000 | }); 1001 | it('will be able to cast the input and set property convertedValue to an UUID', () => { 1002 | if (newObject instanceof SchemaUUID) { 1003 | newObject.convertType(); 1004 | assertInstanceOf(newObject.convertedValue, Bson.UUID); 1005 | } 1006 | }); 1007 | it('will set property valid to true after invoking validateType', () => { 1008 | if (newObject instanceof SchemaUUID) { 1009 | newObject.validateType(); 1010 | assertStrictEquals(newObject.valid, false); 1011 | newObject.convertType() 1012 | newObject.validateType(); 1013 | assertStrictEquals(newObject.valid, true); 1014 | } 1015 | }); 1016 | }); 1017 | }); 1018 | 1019 | describe('Schema Date', () => { 1020 | let newObject: unknown 1021 | describe('creating an instance of the class with no input', () => { 1022 | it('will throw an error', () => { 1023 | assertThrows(() => { 1024 | //@ts-ignore Ignore TS warning to run test 1025 | newObject = new SchemaDate(); 1026 | }); 1027 | }); 1028 | }); 1029 | describe('creating an instance of the class with a null input', () => { 1030 | beforeEach(() => { 1031 | newObject = new SchemaDate(null) 1032 | }); 1033 | it('will create an instance of the class', () => { 1034 | assertInstanceOf(newObject, SchemaDate) 1035 | }); 1036 | it('will have a convertType method', () => { 1037 | if (newObject instanceof SchemaDate) { 1038 | assertInstanceOf(newObject.convertType, Function); 1039 | } 1040 | }); 1041 | it('will cast the input and set property convertedValue to null', () => { 1042 | if (newObject instanceof SchemaDate) { 1043 | newObject.convertType(); 1044 | assertStrictEquals(newObject.convertedValue, null); 1045 | } 1046 | }); 1047 | it('will set property valid to true after invoking validateType', () => { 1048 | if (newObject instanceof SchemaNumber) { 1049 | newObject.validateType(); 1050 | assertStrictEquals(newObject.valid, false); 1051 | newObject.convertType() 1052 | newObject.validateType(); 1053 | assertStrictEquals(newObject.valid, true); 1054 | } 1055 | }); 1056 | }); 1057 | describe('creating an instance of the class with a Date object input', () => { 1058 | beforeEach(() => { 1059 | newObject = new SchemaDate(new Date()) 1060 | }); 1061 | it('will create an instance of the class', () => { 1062 | assertInstanceOf(newObject, SchemaDate) 1063 | }); 1064 | it('will have a convertType method', () => { 1065 | if (newObject instanceof SchemaDate) { 1066 | assertInstanceOf(newObject.convertType, Function); 1067 | } 1068 | }); 1069 | it('will cast the input and set property convertedValue to a Date', () => { 1070 | if (newObject instanceof SchemaDate) { 1071 | newObject.convertType(); 1072 | assertInstanceOf(newObject.convertedValue, Date); 1073 | } 1074 | }); 1075 | it('will set property valid to true after invoking validateType', () => { 1076 | if (newObject instanceof SchemaDate) { 1077 | newObject.validateType(); 1078 | assertStrictEquals(newObject.valid, false); 1079 | newObject.convertType() 1080 | newObject.validateType(); 1081 | assertStrictEquals(newObject.valid, true); 1082 | } 1083 | }); 1084 | }); 1085 | describe('creating an instance of the class with a non-string or number input', () => { 1086 | beforeEach(() => { 1087 | newObject = new SchemaDate([5]) 1088 | }); 1089 | it('will create an instance of the class', () => { 1090 | assertInstanceOf(newObject, SchemaDate) 1091 | }); 1092 | it('will have a convertType method', () => { 1093 | if (newObject instanceof SchemaDate) { 1094 | assertInstanceOf(newObject.convertType, Function); 1095 | } 1096 | }); 1097 | it('will be unable cast the input and set property convertedValue to a Date. Value will be undefined', () => { 1098 | if (newObject instanceof SchemaDate) { 1099 | newObject.convertType(); 1100 | assertStrictEquals(newObject.convertedValue, undefined); 1101 | } 1102 | }); 1103 | it('will set property valid to false after invoking validateType', () => { 1104 | if (newObject instanceof SchemaDate) { 1105 | newObject.validateType(); 1106 | assertStrictEquals(newObject.valid, false); 1107 | newObject.convertType() 1108 | newObject.validateType(); 1109 | assertStrictEquals(newObject.valid, false); 1110 | } 1111 | }); 1112 | }); 1113 | describe('creating an instance of the class with string input that cannot be cast to a date', () => { 1114 | beforeEach(() => { 1115 | newObject = new SchemaDate('dangoDB') 1116 | }); 1117 | it('will create an instance of the class', () => { 1118 | assertInstanceOf(newObject, SchemaDate) 1119 | }); 1120 | it('will have a convertType method', () => { 1121 | if (newObject instanceof SchemaDate) { 1122 | assertInstanceOf(newObject.convertType, Function); 1123 | } 1124 | }); 1125 | it('will be unable cast the input and set property convertedValue to a Date. Value will be undefined', () => { 1126 | if (newObject instanceof SchemaDate) { 1127 | newObject.convertType(); 1128 | assertStrictEquals(newObject.convertedValue, undefined); 1129 | } 1130 | }); 1131 | it('will set property valid to false after invoking validateType', () => { 1132 | if (newObject instanceof SchemaDate) { 1133 | newObject.validateType(); 1134 | assertStrictEquals(newObject.valid, false); 1135 | newObject.convertType() 1136 | newObject.validateType(); 1137 | assertStrictEquals(newObject.valid, false); 1138 | } 1139 | }); 1140 | }); 1141 | describe('creating an instance of the class with string input that can be cast to a date', () => { 1142 | beforeEach(() => { 1143 | newObject = new SchemaDate('1991-03-15') 1144 | }); 1145 | it('will create an instance of the class', () => { 1146 | assertInstanceOf(newObject, SchemaDate) 1147 | }); 1148 | it('will have a convertType method', () => { 1149 | if (newObject instanceof SchemaDate) { 1150 | assertInstanceOf(newObject.convertType, Function); 1151 | } 1152 | }); 1153 | it('will cast the input and set property convertedValue to a Dated', () => { 1154 | if (newObject instanceof SchemaDate) { 1155 | newObject.convertType(); 1156 | assertInstanceOf(newObject.convertedValue, Date); 1157 | } 1158 | }); 1159 | it('will set property valid to true after invoking validateType', () => { 1160 | if (newObject instanceof SchemaDate) { 1161 | newObject.validateType(); 1162 | assertStrictEquals(newObject.valid, false); 1163 | newObject.convertType() 1164 | newObject.validateType(); 1165 | assertStrictEquals(newObject.valid, true); 1166 | } 1167 | }); 1168 | }); 1169 | describe('creating an instance of the class with a number input', () => { 1170 | beforeEach(() => { 1171 | newObject = new SchemaDate(668995200000) 1172 | }); 1173 | it('will create an instance of the class', () => { 1174 | assertInstanceOf(newObject, SchemaDate) 1175 | }); 1176 | it('will have a convertType method', () => { 1177 | if (newObject instanceof SchemaDate) { 1178 | assertInstanceOf(newObject.convertType, Function); 1179 | } 1180 | }); 1181 | it('will cast the input and set property convertedValue to a Date', () => { 1182 | if (newObject instanceof SchemaDate) { 1183 | newObject.convertType(); 1184 | assertInstanceOf(newObject.convertedValue, Date); 1185 | } 1186 | }); 1187 | it('will set property valid to true after invoking validateType', () => { 1188 | if (newObject instanceof SchemaDate) { 1189 | newObject.validateType(); 1190 | assertStrictEquals(newObject.valid, false); 1191 | newObject.convertType() 1192 | newObject.validateType(); 1193 | assertStrictEquals(newObject.valid, true); 1194 | } 1195 | }); 1196 | }); 1197 | }); -------------------------------------------------------------------------------- /tests/test_query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertInstanceOf, 3 | assertStrictEquals, 4 | assertThrows, 5 | beforeEach, 6 | describe, 7 | it, 8 | } from '../deps.ts'; 9 | 10 | // import { 11 | // Query 12 | // } from '../lib/query.ts'; 13 | 14 | import { model } from '../lib/model.ts'; 15 | 16 | import { Bson } from '../deps.ts'; 17 | 18 | import { Schema, SchemaOptions } from '../lib/schema.ts'; 19 | 20 | import { dango } from '../lib/dango.ts'; 21 | 22 | describe('test Query methods', () => { 23 | const UserSchema = { 24 | name: { 25 | type: 'string', 26 | required: true, 27 | }, 28 | occupation: { 29 | type: 'string', 30 | required: false, 31 | default: null, 32 | }, 33 | age: { 34 | type: 'number', 35 | required: false, 36 | default: null, 37 | }, 38 | }; 39 | 40 | const collectionName = 'testCollection'; 41 | const testSchema = dango.schema(UserSchema); 42 | // console.log('testSchema: ', testSchema); 43 | let queryObject: Record; 44 | 45 | const query = dango.model(collectionName, testSchema); 46 | 47 | describe('checkDataFields', () => { 48 | // let queryObject: Record; 49 | let schemaMap: Record; 50 | 51 | schemaMap = testSchema.schemaMap; 52 | queryObject = { name: 'Mr. A', age: 25 }; 53 | // console.log('queryObject: ', queryObject); 54 | it('it will throw an error if extra properties are present in the queryObject', () => { 55 | queryObject = { name: 'Mr. A', school: 'Furinkan HS' }; 56 | assertThrows(() => query.checkDataFields(queryObject, schemaMap)); 57 | }); 58 | 59 | it('it will return true if only fields specified in the schema are present in the queryObject', () => { 60 | queryObject = { name: 'Mr. A' }; 61 | assertStrictEquals(query.checkDataFields(queryObject, schemaMap), true); 62 | }); 63 | }); 64 | 65 | describe('checkRequired', () => { 66 | let propertyName: string; 67 | let propertyOptions: SchemaOptions; 68 | 69 | beforeEach(() => { 70 | propertyName = 'name'; 71 | propertyOptions = testSchema.schemaMap[propertyName]; 72 | }); 73 | 74 | it('will throw an error when a required property is missing from queryObject', () => { 75 | queryObject = { age: 25 }; 76 | assertThrows(() => { 77 | query.checkRequired(queryObject, propertyName, propertyOptions); 78 | }); 79 | }); 80 | it('will return true when required properties are in queryObject', () => { 81 | queryObject = { name: 'Mr. C' }; 82 | assertStrictEquals( 83 | query.checkRequired(queryObject, propertyName, propertyOptions), 84 | true 85 | ); 86 | }); 87 | }); 88 | 89 | describe('setDefault', () => { 90 | let propertyName: string; 91 | let propertyOptions: SchemaOptions; 92 | beforeEach(() => { 93 | propertyName = 'occupation'; 94 | propertyOptions = testSchema.schemaMap[propertyName]; 95 | // queryObject = { name: 'Mr. D' }; 96 | }); 97 | 98 | it('will set property value in queryObject to default if value not assigned', () => { 99 | queryObject = { name: 'Mr. D' }; 100 | query.setDefault(queryObject, propertyName, propertyOptions); 101 | assertStrictEquals(queryObject[propertyName], null); 102 | }); 103 | 104 | it('will not assign a default value to a property in queryObject if it already has an assigned value', () => { 105 | queryObject = { name: 'Mr. D', occupation: 'baker' }; 106 | query.setDefault(queryObject, propertyName, propertyOptions); 107 | assertStrictEquals(queryObject[propertyName], 'baker'); 108 | }); 109 | }); 110 | 111 | describe('populateQuery', () => { 112 | let propertyName: string; 113 | let propertyOptions: SchemaOptions; 114 | let updatedQueryObject: Record = {}; 115 | 116 | it('will assign converted value (STRING) to updatedQueryObject', () => { 117 | queryObject = { name: 'Mick Jagger' }; 118 | propertyName = 'name'; 119 | propertyOptions = testSchema.schemaMap[propertyName]; 120 | query.populateQuery( 121 | queryObject, 122 | propertyName, 123 | propertyOptions, 124 | updatedQueryObject 125 | ); 126 | assertStrictEquals( 127 | updatedQueryObject[propertyName], 128 | queryObject[propertyName] 129 | ); 130 | }); 131 | 132 | it('will convert a number to a Bson.Double and assign it to updatedQueryObject', () => { 133 | queryObject = { age: 25 }; 134 | propertyName = 'age'; 135 | propertyOptions = testSchema.schemaMap[propertyName]; 136 | query.populateQuery( 137 | queryObject, 138 | propertyName, 139 | propertyOptions, 140 | updatedQueryObject 141 | ); 142 | assertInstanceOf(updatedQueryObject[propertyName], Bson.Double); 143 | }); 144 | 145 | it('will throw an error if assigned value in queryObject cannot be converted to specified type in schema', () => { 146 | queryObject = { age: true }; 147 | propertyName = 'age'; 148 | propertyOptions = testSchema.schemaMap[propertyName]; 149 | assertThrows(() => { 150 | query.populateQuery( 151 | queryObject, 152 | propertyName, 153 | propertyOptions, 154 | updatedQueryObject 155 | ); 156 | }); 157 | }); 158 | }); 159 | 160 | // need to figure out how to mock or craete a fake evalauted result from invoking findOne query 161 | // describe('checkUnique', () => { 162 | // let propertyName: string; 163 | // let propertyOptions: SchemaOptions; 164 | // let updatedQueryObject: Record = {}; 165 | // let embeddedUniqueProperty: string[]; 166 | // }) 167 | 168 | // checkUniqueForReplace 169 | 170 | describe('checkConstraints', () => { 171 | let propertyName: string; 172 | let propertyOptions: SchemaOptions; 173 | 174 | it('will return true if validator property is set to null', () => { 175 | propertyName = 'name'; 176 | propertyOptions = testSchema.schemaMap[propertyName]; 177 | assertStrictEquals( 178 | query.checkConstraints(propertyName, propertyOptions), 179 | true 180 | ); 181 | }); 182 | }); 183 | 184 | describe('resetQueryObject', () => { 185 | query.updatedQueryObject = { test: 'a test' }; 186 | query.resetQueryObject(); 187 | const updatedQueryObjectKeys = Object.keys(query.updatedQueryObject); 188 | it('will reset updatedQuery object', () => { 189 | assertStrictEquals(updatedQueryObjectKeys.length, 0); 190 | }); 191 | }); 192 | 193 | describe('formatQueryString result', () => { 194 | let propertyName: string; 195 | let propArray: string[]; 196 | 197 | beforeEach(() => { 198 | propertyName = 'test'; 199 | propArray = ['address', 'property_id']; 200 | }); 201 | 202 | it('will return propertyName when embeddedUnique is empty', () => { 203 | assertStrictEquals( 204 | query.formatQueryField(propertyName, []), 205 | propertyName 206 | ); 207 | }); 208 | it('will return concatenated string using elements from embeddedUniqeProperty and propertyName', () => { 209 | assertStrictEquals( 210 | query.formatQueryField(propertyName, propArray), 211 | `address.property_id.${propertyName}` 212 | ); 213 | }); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /tests/test_schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertInstanceOf, 3 | assertStrictEquals, 4 | assertThrows, 5 | beforeEach, 6 | describe, 7 | it, 8 | } from '../deps.ts'; 9 | 10 | import { Schema, SchemaOptions } from '../lib/schema.ts'; 11 | 12 | import { 13 | SchemaNumber, 14 | SchemaDecimal128, 15 | SchemaString, 16 | SchemaBoolean, 17 | SchemaObjectId, 18 | SchemaUUID, 19 | SchemaDate, 20 | } from '../lib/datatypes.ts'; 21 | 22 | import { 23 | spy, 24 | assertSpyCall, 25 | assertSpyCalls, 26 | } from 'https://deno.land/std@0.139.0/testing/mock.ts'; 27 | 28 | describe('SchemaOptions Class', () => { 29 | let newObject: unknown; 30 | describe('creating an instance of the class with no input', () => { 31 | it('will throw an error', () => { 32 | assertThrows(() => { 33 | //@ts-ignore Ignore TS warning to run test 34 | newObject = new SchemaOptions(); 35 | }); 36 | }); 37 | }); 38 | describe('creating an instance of the class with no type property', () => { 39 | it('will throw an error', () => { 40 | assertThrows(() => { 41 | //@ts-ignore Ignore TS warning to run test 42 | newObject = new SchemaOptions({ 43 | required: true, 44 | unique: true, 45 | }); 46 | }); 47 | }); 48 | }); 49 | describe('creating an instance of the class with an invalid type property', () => { 50 | it('will throw an error', () => { 51 | assertThrows(() => { 52 | //@ts-ignore Ignore TS warning to run test 53 | newObject = new SchemaOptions({ 54 | type: 'dangoDB', 55 | }); 56 | }); 57 | }); 58 | }); 59 | describe('creating an instance of the class with a valid options object', () => { 60 | describe('the only property is type', () => { 61 | describe('the type is number', () => { 62 | beforeEach(() => { 63 | newObject = new SchemaOptions({ 64 | type: 'number', 65 | }); 66 | }); 67 | it('will create an instance of the class', () => { 68 | assertInstanceOf(newObject, SchemaOptions); 69 | }); 70 | it('will have assign the type property the class definition of the data type', () => { 71 | if (newObject instanceof SchemaOptions) { 72 | assertStrictEquals(newObject.type, SchemaNumber); 73 | } 74 | }); 75 | it('will have a default required property with the value of false', () => { 76 | if (newObject instanceof SchemaOptions) { 77 | assertStrictEquals(newObject.required, false); 78 | } 79 | }); 80 | it('will have a default unique property with the value of false', () => { 81 | if (newObject instanceof SchemaOptions) { 82 | assertStrictEquals(newObject.unique, false); 83 | } 84 | }); 85 | it('will have a default default property with the value of null', () => { 86 | if (newObject instanceof SchemaOptions) { 87 | assertStrictEquals(newObject.default, null); 88 | } 89 | }); 90 | it('will have a default validator property with the value of null', () => { 91 | if (newObject instanceof SchemaOptions) { 92 | assertStrictEquals(newObject.validator, null); 93 | } 94 | }); 95 | }); 96 | describe('the type is decimal128', () => { 97 | beforeEach(() => { 98 | newObject = new SchemaOptions({ 99 | type: 'decimal128', 100 | }); 101 | }); 102 | it('will create an instance of the class', () => { 103 | assertInstanceOf(newObject, SchemaOptions); 104 | }); 105 | it('will have assign the type property the class definition of the data type', () => { 106 | if (newObject instanceof SchemaOptions) { 107 | assertStrictEquals(newObject.type, SchemaDecimal128); 108 | } 109 | }); 110 | it('will have a default required property with the value of false', () => { 111 | if (newObject instanceof SchemaOptions) { 112 | assertStrictEquals(newObject.required, false); 113 | } 114 | }); 115 | it('will have a default unique property with the value of false', () => { 116 | if (newObject instanceof SchemaOptions) { 117 | assertStrictEquals(newObject.unique, false); 118 | } 119 | }); 120 | it('will have a default default property with the value of null', () => { 121 | if (newObject instanceof SchemaOptions) { 122 | assertStrictEquals(newObject.default, null); 123 | } 124 | }); 125 | it('will have a default validator property with the value of null', () => { 126 | if (newObject instanceof SchemaOptions) { 127 | assertStrictEquals(newObject.validator, null); 128 | } 129 | }); 130 | }); 131 | describe('the type is string', () => { 132 | beforeEach(() => { 133 | newObject = new SchemaOptions({ 134 | type: 'string', 135 | }); 136 | }); 137 | it('will create an instance of the class', () => { 138 | assertInstanceOf(newObject, SchemaOptions); 139 | }); 140 | it('will have assign the type property the class definition of the data type', () => { 141 | if (newObject instanceof SchemaOptions) { 142 | assertStrictEquals(newObject.type, SchemaString); 143 | } 144 | }); 145 | it('will have a default required property with the value of false', () => { 146 | if (newObject instanceof SchemaOptions) { 147 | assertStrictEquals(newObject.required, false); 148 | } 149 | }); 150 | it('will have a default unique property with the value of false', () => { 151 | if (newObject instanceof SchemaOptions) { 152 | assertStrictEquals(newObject.unique, false); 153 | } 154 | }); 155 | it('will have a default default property with the value of null', () => { 156 | if (newObject instanceof SchemaOptions) { 157 | assertStrictEquals(newObject.default, null); 158 | } 159 | }); 160 | it('will have a default validator property with the value of null', () => { 161 | if (newObject instanceof SchemaOptions) { 162 | assertStrictEquals(newObject.validator, null); 163 | } 164 | }); 165 | }); 166 | describe('the type is boolean', () => { 167 | beforeEach(() => { 168 | newObject = new SchemaOptions({ 169 | type: 'boolean', 170 | }); 171 | }); 172 | it('will create an instance of the class', () => { 173 | assertInstanceOf(newObject, SchemaOptions); 174 | }); 175 | it('will have assign the type property the class definition of the data type', () => { 176 | if (newObject instanceof SchemaOptions) { 177 | assertStrictEquals(newObject.type, SchemaBoolean); 178 | } 179 | }); 180 | it('will have a default required property with the value of false', () => { 181 | if (newObject instanceof SchemaOptions) { 182 | assertStrictEquals(newObject.required, false); 183 | } 184 | }); 185 | it('will have a default unique property with the value of false', () => { 186 | if (newObject instanceof SchemaOptions) { 187 | assertStrictEquals(newObject.unique, false); 188 | } 189 | }); 190 | it('will have a default default property with the value of null', () => { 191 | if (newObject instanceof SchemaOptions) { 192 | assertStrictEquals(newObject.default, null); 193 | } 194 | }); 195 | it('will have a default validator property with the value of null', () => { 196 | if (newObject instanceof SchemaOptions) { 197 | assertStrictEquals(newObject.validator, null); 198 | } 199 | }); 200 | }); 201 | describe('the type is objectid', () => { 202 | beforeEach(() => { 203 | newObject = new SchemaOptions({ 204 | type: 'objectid', 205 | }); 206 | }); 207 | it('will create an instance of the class', () => { 208 | assertInstanceOf(newObject, SchemaOptions); 209 | }); 210 | it('will have assign the type property the class definition of the data type', () => { 211 | if (newObject instanceof SchemaOptions) { 212 | assertStrictEquals(newObject.type, SchemaObjectId); 213 | } 214 | }); 215 | it('will have a default required property with the value of false', () => { 216 | if (newObject instanceof SchemaOptions) { 217 | assertStrictEquals(newObject.required, false); 218 | } 219 | }); 220 | it('will have a default unique property with the value of false', () => { 221 | if (newObject instanceof SchemaOptions) { 222 | assertStrictEquals(newObject.unique, false); 223 | } 224 | }); 225 | it('will have a default default property with the value of null', () => { 226 | if (newObject instanceof SchemaOptions) { 227 | assertStrictEquals(newObject.default, null); 228 | } 229 | }); 230 | it('will have a default validator property with the value of null', () => { 231 | if (newObject instanceof SchemaOptions) { 232 | assertStrictEquals(newObject.validator, null); 233 | } 234 | }); 235 | }); 236 | describe('the type is objectid', () => { 237 | beforeEach(() => { 238 | newObject = new SchemaOptions({ 239 | type: 'UUID', 240 | }); 241 | }); 242 | it('will create an instance of the class', () => { 243 | assertInstanceOf(newObject, SchemaOptions); 244 | }); 245 | it('will have assign the type property the class definition of the data type', () => { 246 | if (newObject instanceof SchemaOptions) { 247 | assertStrictEquals(newObject.type, SchemaUUID); 248 | } 249 | }); 250 | it('will have a default required property with the value of false', () => { 251 | if (newObject instanceof SchemaOptions) { 252 | assertStrictEquals(newObject.required, false); 253 | } 254 | }); 255 | it('will have a default unique property with the value of false', () => { 256 | if (newObject instanceof SchemaOptions) { 257 | assertStrictEquals(newObject.unique, false); 258 | } 259 | }); 260 | it('will have a default default property with the value of null', () => { 261 | if (newObject instanceof SchemaOptions) { 262 | assertStrictEquals(newObject.default, null); 263 | } 264 | }); 265 | it('will have a default validator property with the value of null', () => { 266 | if (newObject instanceof SchemaOptions) { 267 | assertStrictEquals(newObject.validator, null); 268 | } 269 | }); 270 | }); 271 | describe('the type is date', () => { 272 | beforeEach(() => { 273 | newObject = new SchemaOptions({ 274 | type: 'date', 275 | }); 276 | }); 277 | it('will create an instance of the class', () => { 278 | assertInstanceOf(newObject, SchemaOptions); 279 | }); 280 | it('will have assign the type property the class definition of the data type', () => { 281 | if (newObject instanceof SchemaOptions) { 282 | assertStrictEquals(newObject.type, SchemaDate); 283 | } 284 | }); 285 | it('will have a default required property with the value of false', () => { 286 | if (newObject instanceof SchemaOptions) { 287 | assertStrictEquals(newObject.required, false); 288 | } 289 | }); 290 | it('will have a default unique property with the value of false', () => { 291 | if (newObject instanceof SchemaOptions) { 292 | assertStrictEquals(newObject.unique, false); 293 | } 294 | }); 295 | it('will have a default default property with the value of null', () => { 296 | if (newObject instanceof SchemaOptions) { 297 | assertStrictEquals(newObject.default, null); 298 | } 299 | }); 300 | it('will have a default validator property with the value of null', () => { 301 | if (newObject instanceof SchemaOptions) { 302 | assertStrictEquals(newObject.validator, null); 303 | } 304 | }); 305 | }); 306 | }); 307 | describe('the only properties are type and required', () => { 308 | beforeEach(() => { 309 | newObject = new SchemaOptions({ 310 | type: 'number', 311 | required: true, 312 | }); 313 | }); 314 | it('will create an instance of the class', () => { 315 | assertInstanceOf(newObject, SchemaOptions); 316 | }); 317 | it('will have assign the type property the class definition of the data type', () => { 318 | if (newObject instanceof SchemaOptions) { 319 | assertStrictEquals(newObject.type, SchemaNumber); 320 | } 321 | }); 322 | it('will have a required property with the value of true', () => { 323 | if (newObject instanceof SchemaOptions) { 324 | assertStrictEquals(newObject.required, true); 325 | } 326 | }); 327 | it('will have a default unique property with the value of false', () => { 328 | if (newObject instanceof SchemaOptions) { 329 | assertStrictEquals(newObject.unique, false); 330 | } 331 | }); 332 | it('will have a default default property with the value of null', () => { 333 | if (newObject instanceof SchemaOptions) { 334 | assertStrictEquals(newObject.default, null); 335 | } 336 | }); 337 | it('will have a default validator property with the value of null', () => { 338 | if (newObject instanceof SchemaOptions) { 339 | assertStrictEquals(newObject.validator, null); 340 | } 341 | }); 342 | }); 343 | describe('the only properties are type and unique', () => { 344 | beforeEach(() => { 345 | newObject = new SchemaOptions({ 346 | type: 'number', 347 | unique: true, 348 | }); 349 | }); 350 | it('will create an instance of the class', () => { 351 | assertInstanceOf(newObject, SchemaOptions); 352 | }); 353 | it('will have assign the type property the class definition of the data type', () => { 354 | if (newObject instanceof SchemaOptions) { 355 | assertStrictEquals(newObject.type, SchemaNumber); 356 | } 357 | }); 358 | it('will have a default required property with the value of false', () => { 359 | if (newObject instanceof SchemaOptions) { 360 | assertStrictEquals(newObject.required, false); 361 | } 362 | }); 363 | it('will have a unique property with the value of true', () => { 364 | if (newObject instanceof SchemaOptions) { 365 | assertStrictEquals(newObject.unique, true); 366 | } 367 | }); 368 | it('will have a default default property with the value of null', () => { 369 | if (newObject instanceof SchemaOptions) { 370 | assertStrictEquals(newObject.default, null); 371 | } 372 | }); 373 | it('will have a default validator property with the value of null', () => { 374 | if (newObject instanceof SchemaOptions) { 375 | assertStrictEquals(newObject.validator, null); 376 | } 377 | }); 378 | }); 379 | describe('the only properties are type and default', () => { 380 | beforeEach(() => { 381 | newObject = new SchemaOptions({ 382 | type: 'number', 383 | default: 54, 384 | }); 385 | }); 386 | it('will create an instance of the class', () => { 387 | assertInstanceOf(newObject, SchemaOptions); 388 | }); 389 | it('will have assign the type property the class definition of the data type', () => { 390 | if (newObject instanceof SchemaOptions) { 391 | assertStrictEquals(newObject.type, SchemaNumber); 392 | } 393 | }); 394 | it('will have a default required property with the value of false', () => { 395 | if (newObject instanceof SchemaOptions) { 396 | assertStrictEquals(newObject.required, false); 397 | } 398 | }); 399 | it('will have a default unique property with the value of false', () => { 400 | if (newObject instanceof SchemaOptions) { 401 | assertStrictEquals(newObject.unique, false); 402 | } 403 | }); 404 | it('will have a default property with the value of 54', () => { 405 | if (newObject instanceof SchemaOptions) { 406 | assertStrictEquals(newObject.default, 54); 407 | } 408 | }); 409 | it('will have a default validator property with the value of null', () => { 410 | if (newObject instanceof SchemaOptions) { 411 | assertStrictEquals(newObject.validator, null); 412 | } 413 | }); 414 | }); 415 | describe('the only properties are type and validator', () => { 416 | let testFunc!: Function; 417 | beforeEach(() => { 418 | testFunc = (num: number): boolean => num > 5; 419 | newObject = new SchemaOptions({ 420 | type: 'number', 421 | validator: testFunc, 422 | }); 423 | }); 424 | it('will create an instance of the class', () => { 425 | assertInstanceOf(newObject, SchemaOptions); 426 | }); 427 | it('will have assign the type property the class definition of the data type', () => { 428 | if (newObject instanceof SchemaOptions) { 429 | assertStrictEquals(newObject.type, SchemaNumber); 430 | } 431 | }); 432 | it('will have a default required property with the value of false', () => { 433 | if (newObject instanceof SchemaOptions) { 434 | assertStrictEquals(newObject.required, false); 435 | } 436 | }); 437 | it('will have a default unique property with the value of false', () => { 438 | if (newObject instanceof SchemaOptions) { 439 | assertStrictEquals(newObject.unique, false); 440 | } 441 | }); 442 | it('will have a default default property with the value of null', () => { 443 | if (newObject instanceof SchemaOptions) { 444 | assertStrictEquals(newObject.default, null); 445 | } 446 | }); 447 | it('will have a validator property with the value of testFunc', () => { 448 | if (newObject instanceof SchemaOptions) { 449 | assertStrictEquals(newObject.validator, testFunc); 450 | } 451 | }); 452 | }); 453 | describe('all options properties are provided to SchemaOptions', () => { 454 | let testFunc!: Function; 455 | beforeEach(() => { 456 | testFunc = (num: number): boolean => num > 5; 457 | newObject = new SchemaOptions({ 458 | type: 'number', 459 | required: true, 460 | unique: true, 461 | default: 315, 462 | validator: testFunc, 463 | }); 464 | }); 465 | it('will create an instance of the class', () => { 466 | assertInstanceOf(newObject, SchemaOptions); 467 | }); 468 | it('will have assign the type property the class definition of the data type', () => { 469 | if (newObject instanceof SchemaOptions) { 470 | assertStrictEquals(newObject.type, SchemaNumber); 471 | } 472 | }); 473 | it('will have a required property with the value of true', () => { 474 | if (newObject instanceof SchemaOptions) { 475 | assertStrictEquals(newObject.required, true); 476 | } 477 | }); 478 | it('will have a unique property with the value of true', () => { 479 | if (newObject instanceof SchemaOptions) { 480 | assertStrictEquals(newObject.unique, true); 481 | } 482 | }); 483 | it('will have a default property with the value of 315', () => { 484 | if (newObject instanceof SchemaOptions) { 485 | assertStrictEquals(newObject.default, 315); 486 | } 487 | }); 488 | it('will have a validator property with the value of testFunc', () => { 489 | if (newObject instanceof SchemaOptions) { 490 | assertStrictEquals(newObject.validator, testFunc); 491 | } 492 | }); 493 | }); 494 | describe('an unknown options property was provided to SchemaOptions', () => { 495 | let testFunc!: Function; 496 | beforeEach(() => { 497 | testFunc = (num: number): boolean => num > 5; 498 | newObject = new SchemaOptions({ 499 | type: 'number', 500 | required: true, 501 | unique: true, 502 | default: 315, 503 | validator: testFunc, 504 | //@ts-ignore Ignore TS warning to run test 505 | unknownProp: false, 506 | }); 507 | }); 508 | it('will create an instance of the class', () => { 509 | assertInstanceOf(newObject, SchemaOptions); 510 | }); 511 | it('will have assign the type property the class definition of the data type', () => { 512 | if (newObject instanceof SchemaOptions) { 513 | assertStrictEquals(newObject.type, SchemaNumber); 514 | } 515 | }); 516 | it('will have a required property with the value of true', () => { 517 | if (newObject instanceof SchemaOptions) { 518 | assertStrictEquals(newObject.required, true); 519 | } 520 | }); 521 | it('will have a unique property with the value of true', () => { 522 | if (newObject instanceof SchemaOptions) { 523 | assertStrictEquals(newObject.unique, true); 524 | } 525 | }); 526 | it('will have a default property with the value of 315', () => { 527 | if (newObject instanceof SchemaOptions) { 528 | assertStrictEquals(newObject.default, 315); 529 | } 530 | }); 531 | it('will have a validator property with the value of testFunc', () => { 532 | if (newObject instanceof SchemaOptions) { 533 | assertStrictEquals(newObject.validator, testFunc); 534 | } 535 | }); 536 | it('will not have unknownProp as a property', () => { 537 | if (newObject instanceof SchemaOptions) { 538 | assertStrictEquals( 539 | Object.prototype.hasOwnProperty.call(newObject, 'unknownProp'), 540 | false 541 | ); 542 | } 543 | }); 544 | }); 545 | }); 546 | }); 547 | 548 | describe('Schema Class', () => { 549 | let newObject: unknown; 550 | describe('creating an instance of the class with no input', () => { 551 | it('will throw an error', () => { 552 | assertThrows(() => { 553 | //@ts-ignore Ignore TS warning to run test 554 | newObject = new Schema(); 555 | }); 556 | }); 557 | }); 558 | describe('creating an instance of the class with a schema object', () => { 559 | describe('and the schema object only has one property with only type defined with an invalid type', () => { 560 | it('will throw an error', () => { 561 | assertThrows(() => { 562 | newObject = new Schema({ 563 | name: 'dangoDB', 564 | }); 565 | }); 566 | }); 567 | }); 568 | describe('and the schema object only has one property with only type defined with a valid type', () => { 569 | beforeEach(() => { 570 | newObject = new Schema({ 571 | name: 'string', 572 | }); 573 | }); 574 | it('will create an instance of the class', () => { 575 | assertInstanceOf(newObject, Schema); 576 | }); 577 | it('will have a property schemaMap', () => { 578 | if (newObject instanceof Schema) { 579 | assertStrictEquals( 580 | Object.prototype.hasOwnProperty.call(newObject, 'schemaMap'), 581 | true 582 | ); 583 | } 584 | }); 585 | it('will have a property schemaMap with the property name', () => { 586 | if (newObject instanceof Schema) { 587 | assertStrictEquals( 588 | Object.prototype.hasOwnProperty.call(newObject.schemaMap, 'name'), 589 | true 590 | ); 591 | } 592 | }); 593 | it('will have a property schemaMap with the property name which is an instance of SchemaOptions', () => { 594 | if (newObject instanceof Schema) { 595 | assertInstanceOf(newObject.schemaMap.name, SchemaOptions); 596 | } 597 | }); 598 | }); 599 | describe('and the schema object only has one property with a value of an options object', () => { 600 | let testFunc!: (param: string) => boolean; 601 | beforeEach(() => { 602 | newObject = new Schema({ 603 | name: { 604 | type: 'string', 605 | required: true, 606 | unique: true, 607 | default: 'dangoDB', 608 | validator: (testFunc = (str: string): boolean => str[0] === 'b'), 609 | }, 610 | }); 611 | }); 612 | it('will create an instance of the class', () => { 613 | assertInstanceOf(newObject, Schema); 614 | }); 615 | it('will have a property schemaMap', () => { 616 | if (newObject instanceof Schema) { 617 | assertStrictEquals( 618 | Object.prototype.hasOwnProperty.call(newObject, 'schemaMap'), 619 | true 620 | ); 621 | } 622 | }); 623 | it('will have a property schemaMap with the property name', () => { 624 | if (newObject instanceof Schema) { 625 | assertStrictEquals( 626 | Object.prototype.hasOwnProperty.call(newObject.schemaMap, 'name'), 627 | true 628 | ); 629 | } 630 | }); 631 | it('will have a property schemaMap with the property name which is an instance of SchemaOptions', () => { 632 | if (newObject instanceof Schema) { 633 | assertInstanceOf(newObject.schemaMap.name, SchemaOptions); 634 | } 635 | }); 636 | }); 637 | }); 638 | }); 639 | --------------------------------------------------------------------------------