├── .github └── .gitkeep ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── aftman.toml ├── default.project.json ├── dev.project.json ├── src └── init.luau ├── tests └── .gitkeep └── wally.toml /.github/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typedwaves/Rongo/413a61dd802e2ca3b6b0e90e6775aeb0efb94b8c/.github/.gitkeep -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "luau-lsp.sourcemap.rojoProjectFile": "dev.project.json", 3 | "cSpell.words": [ 4 | "Rongo" 5 | ], 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Untitled Games 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 | # Rongo 2 | A wrapper to connect MongoDB to your Roblox games. 3 | 4 | Rongo is MongoDB Atlas Data API Wrapper for Roblox however also has support for self-hosted or non-Atlas MongoDB instances through use of a 1:1 REST API, relea 5 | 6 | [**DevForum Post & Documentation**](https://devforum.roblox.com/t/rongo/1755615) 7 | -------------------------------------------------------------------------------- /aftman.toml: -------------------------------------------------------------------------------- 1 | # This file lists tools managed by Aftman, a cross-platform toolchain manager. 2 | # For more information, see https://github.com/LPGhatguy/aftman 3 | 4 | # To add a new tool, add an entry to this table. 5 | [tools] 6 | rojo = "rojo-rbx/rojo@7.4.0-rc3" 7 | # rojo = "rojo-rbx/rojo@6.2.0" -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rongo", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /dev.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RongoDev", 3 | "tree": { 4 | "$className": "DataModel", 5 | 6 | "ReplicatedStorage": { 7 | "Rongo": { 8 | "$path": "src" 9 | } 10 | }, 11 | 12 | "ServerScriptService": { 13 | "Tests": { 14 | "$className": "Folder", 15 | 16 | // TODO: Add tests for all the different tables in init 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/init.luau: -------------------------------------------------------------------------------- 1 | --!nonstrict 2 | 3 | --[[ 4 | 5 | ______ 6 | | ___ \ 7 | | |_/ /___ _ __ __ _ ___ 8 | | // _ \| '_ \ / _` |/ _ \ 9 | | |\ \ (_) | | | | (_| | (_) | 10 | \_| \_\___/|_| |_|\__, |\___/ 11 | __/ | 12 | |___/ 13 | 14 | Rongo - MongoDB API Wrapper for Roblox 15 | 16 | Rongo gives developers easy access to MongoDBs web APIs 17 | so they're able to use MongoDB directly in their games 18 | 19 | Rongo uses MongoDB's Data API, which is disabled by 20 | default so you must enable it in your Atlas settings 21 | 22 | Version: 2.1.0 23 | License: MIT License 24 | Documentation: https://devforum.roblox.com/t/rongo/1755615 25 | 26 | Copyright (c) 2024 Untitled Games 27 | 28 | --]] 29 | 30 | --// Services 31 | 32 | local HttpService = game:GetService("HttpService") 33 | 34 | --// Constants 35 | 36 | local ENDPOINTS = { 37 | POST = { 38 | ["FindOne"] = "/action/findOne", 39 | ["FindMany"] = "/action/find", 40 | ["InsertOne"] = "/action/insertOne", 41 | ["InsertMany"] = "/action/insertMany", 42 | ["UpdateOne"] = "/action/updateOne", 43 | ["UpdateMany"] = "/action/updateMany", 44 | ["DeleteOne"] = "/action/deleteOne", 45 | ["DeleteMany"] = "/action/deleteMany", 46 | ["Aggregate"] = "/action/aggregate", 47 | }, 48 | } 49 | 50 | --// Variables 51 | 52 | local Cache = {} 53 | 54 | --// Private Functions 55 | 56 | local function RongoError(message: string) 57 | error("[RONGO] " .. message .. "\n", 2) 58 | end 59 | 60 | local function RongoWarn(message: string, ...) 61 | warn("[RONGO] " .. message, ...) 62 | end 63 | 64 | local function ValidateStringArguments(Arguments: { [string]: any }, Function: string) 65 | local err = 0 66 | for arg, value in pairs(Arguments) do 67 | if type(value) ~= "string" then 68 | err = 1 69 | continue 70 | elseif #value < 1 then 71 | err = 2 72 | continue 73 | end 74 | end 75 | if err == 0 then 76 | return 77 | end 78 | local keys = {} 79 | for k in pairs(Arguments) do 80 | table.insert(keys, k) 81 | end 82 | if err == 1 then 83 | return RongoError(`{table.concat(keys, ", ")} argument(s) in '{Function}' were not valid (must be a string)`) 84 | elseif err == 2 then 85 | return RongoError( 86 | `{table.concat(keys, ", ")} argument(s) in '{Function}' were empty strings, please ensure they have content` 87 | ) 88 | end 89 | end 90 | 91 | local function CreateAuthHeaders(Auth: { [string]: string }) 92 | if not Auth or not Auth.Type then 93 | return 94 | end 95 | if Auth.Type == "key" then 96 | return { 97 | ["apiKey"] = Auth.Key, 98 | } 99 | elseif Auth.Type == "emailpassword" then 100 | return { 101 | ["email"] = Auth.Email, 102 | ["password"] = Auth.Password, 103 | } 104 | elseif Auth.Type == "bearer" then 105 | return { 106 | ["Authorization"] = "Bearer " .. string.gsub(Auth.Token, "Bearer ", ""), 107 | } 108 | elseif Auth.Type == "uri" then 109 | return { 110 | ["Authorization"] = Auth.Uri, 111 | } 112 | end 113 | 114 | return {} 115 | end 116 | 117 | local function RongoRequest(Url: string, Auth: any, Data: any) 118 | local Success, Response = pcall(function() 119 | return HttpService:RequestAsync({ 120 | Url = Url, 121 | Method = "POST", 122 | Headers = { 123 | ["apiKey"] = Auth and Auth["apiKey"], 124 | ["email"] = Auth and Auth["email"], 125 | ["password"] = Auth and Auth["password"], 126 | ["Authorization"] = Auth and Auth["Authorization"], 127 | ["Content-Type"] = "application/json", 128 | ["Accept"] = "application/json", 129 | ["Access-Control-Request-Headers"] = "*", 130 | }, 131 | Body = HttpService:JSONEncode(Data), 132 | }) 133 | end) 134 | 135 | if not Success or not Response or not Response.Body then 136 | RongoWarn("Response data:", Response) 137 | RongoError(`Failed to send request to {Url}`) 138 | return 139 | end 140 | 141 | local Body = HttpService:JSONDecode(Response.Body) 142 | if Body and Body["error"] and Body["error_code"] then 143 | RongoWarn( 144 | `Request returned an error with the following details:\nError message: {Body["error"]}\nError code: {Body["error_code"]}\nLink: {Body["link"]}\n` 145 | ) 146 | return Body or Response.Body 147 | end 148 | if Body and Body["error"] and Body["error"]["name"] == "ZodError" then 149 | RongoWarn(`Request returned an error with the following details:`, Body["error"]) 150 | return Body or Response.Body 151 | end 152 | if Response["Success"] == false then 153 | RongoWarn( 154 | `Request returned an error with the following details:\nError message: {Body}\nStatus code: {Response["StatusCode"]}\nStatus Message: {Response["StatusMessage"]}\n` 155 | ) 156 | end 157 | 158 | return Body or Response.Body 159 | end 160 | 161 | --// Main Module 162 | 163 | local Rongo = {} 164 | 165 | --// Classes 166 | 167 | local RongoClient = {} 168 | RongoClient.__index = RongoClient 169 | 170 | local RongoCluster = {} 171 | RongoCluster.__index = RongoCluster 172 | 173 | local RongoDatabase = {} 174 | RongoDatabase.__index = RongoDatabase 175 | 176 | local RongoCollection = {} 177 | RongoCollection.__index = RongoCollection 178 | 179 | --// Types 180 | 181 | export type RongoClient = typeof(RongoClient) 182 | export type RongoCluster = typeof(RongoCluster) 183 | export type RongoDatabase = typeof(RongoDatabase) 184 | export type RongoCollection = typeof(RongoCollection) 185 | 186 | --// Functions 187 | 188 | function Rongo.new( 189 | Url: string, 190 | Key: string | { Type: "emailpassword" | "key" | "bearer" | "uri", [string]: string } 191 | ): RongoClient 192 | ValidateStringArguments({ Url = Url }, "Rongo.new()") 193 | return setmetatable({ 194 | _settings = { 195 | Url = Url, 196 | Auth = (type(Key) == "table") and Key or { 197 | Key = Key, 198 | Type = "key", 199 | }, 200 | }, 201 | _type = "RongoClient", 202 | }, RongoClient) 203 | end 204 | 205 | function Rongo.auth(Type: "emailpassword" | "key" | "bearer" | "uri", Value: string, Value2: string?) 206 | local AuthDict = {} 207 | if Type == "emailpassword" then 208 | AuthDict = { 209 | Type = "emailpassword", 210 | Email = Value, 211 | Password = Value2, 212 | } 213 | elseif Type == "key" then 214 | AuthDict = { 215 | Type = "key", 216 | Key = Value, 217 | } 218 | elseif Type == "bearer" then 219 | AuthDict = { 220 | Type = "bearer", 221 | Token = Value, 222 | } 223 | elseif Type == "uri" then 224 | AuthDict = { 225 | Type = "uri", 226 | Uri = Value, 227 | } 228 | end 229 | 230 | return AuthDict 231 | end 232 | 233 | --// Rongo Client 234 | 235 | function RongoClient:SetEmailPasswordAuth(Email: string, Password: string) 236 | if not self then 237 | return RongoWarn( 238 | "Attempted to call 'RongoClient:SetEmailPasswordAuth()' without initializing a new client; you can do this with 'Rongo.new()'" 239 | ) 240 | end 241 | ValidateStringArguments({ Email = Email, Password = Password }, "RongoClient:SetPasswordAuth()") 242 | self._settings.Auth = { 243 | Type = "emailpassword", 244 | Email = Email, 245 | Password = Password, 246 | } 247 | end 248 | 249 | function RongoClient:SetApiKeyAuth(Key: string) 250 | if not self then 251 | return RongoWarn( 252 | "Attempted to call 'RongoClient:SetApiKeyAuth()' without initializing a new client; you can do this with 'Rongo.new()'" 253 | ) 254 | end 255 | ValidateStringArguments({ Key = Key }, "RongoClient:SetApiKeyAuth()") 256 | self._settings.Auth = { 257 | Type = "key", 258 | Key = Key, 259 | } 260 | end 261 | 262 | function RongoClient:SetBearerAuth(Token: string) 263 | if not self then 264 | return RongoWarn( 265 | "Attempted to call 'RongoClient:SetBearerAuth()' without initializing a new client; you can do this with 'Rongo.new()'" 266 | ) 267 | end 268 | ValidateStringArguments({ Token = Token }, "RongoClient:SetBearer()") 269 | self._settings.Auth = { 270 | Type = "bearer", 271 | Token = Token, 272 | } 273 | end 274 | 275 | function RongoClient:SetConnectionUri(ConnectionUri: string) 276 | if not self then 277 | return RongoWarn( 278 | "Attempted to call 'RongoClient:SetConnectionUri()' without initializing a new client; you can do this with 'Rongo.new()'" 279 | ) 280 | end 281 | ValidateStringArguments({ ConnectionUri = ConnectionUri }, "RongoClient:SetConnectionUri()") 282 | self._settings.Auth = { 283 | Type = "uri", 284 | Uri = ConnectionUri, 285 | } 286 | end 287 | 288 | function RongoClient:GetCluster(Cluster: string): RongoCluster 289 | if not self then 290 | return RongoWarn( 291 | "Attempted to call 'RongoClient:GetCluster()' without initializing a new client; you can do this with 'Rongo.new()'" 292 | ) 293 | end 294 | ValidateStringArguments({ Cluster = Cluster }, "RongoClient:GetCluster()") 295 | return setmetatable({ 296 | _cluster = Cluster, 297 | _client = self, 298 | _type = "RongoCluster", 299 | }, RongoCluster) 300 | end 301 | 302 | --// Rongo Cluster 303 | 304 | function RongoCluster:GetDatabase(Database: string): RongoDatabase 305 | if not self then 306 | return RongoWarn( 307 | "Attempted to call 'RongoCluster:GetDatabase()' without initializing a cluster; you can do this with 'RongoClient:GetCluster()'" 308 | ) 309 | end 310 | if self._type ~= "RongoCluster" or not self._client or not self._cluster then 311 | return RongoError( 312 | "Missing required values on cluster object, please ensure you have correctly setup a new cluster" 313 | ) 314 | end 315 | ValidateStringArguments({ Database = Database }, "RongoCluster:GetCluster()") 316 | return setmetatable({ 317 | _cluster = self._cluster, 318 | _client = self._client, 319 | _database = Database, 320 | _type = "RongoDatabase", 321 | }, RongoDatabase) 322 | end 323 | 324 | --// Rongo Database 325 | 326 | function RongoDatabase:GetCollection(Collection: string): RongoCollection 327 | if not self then 328 | return RongoWarn( 329 | "Attempted to call 'RongoDatabase:GetCollection()' without initializing a database; you can do this with 'RongoCluster:GetDatabase()'" 330 | ) 331 | end 332 | if self._type ~= "RongoDatabase" or not self._client or not self._cluster or not self._database then 333 | return RongoError( 334 | "Missing required values on database object, please ensure you have correctly setup a new database" 335 | ) 336 | end 337 | ValidateStringArguments({ Collection = Collection }, "RongoDatabase:GetCollection()") 338 | return setmetatable({ 339 | _cluster = self._cluster, 340 | _client = self._client, 341 | _database = self._database, 342 | _collection = Collection, 343 | _type = "RongoCollection", 344 | }, RongoCollection) 345 | end 346 | 347 | --// Rongo Collection 348 | 349 | function RongoCollection:FindOne(Filter: { [string]: string | { [string]: string } }?): { [string]: any }? 350 | if not self then 351 | return RongoWarn( 352 | "Attempted to call 'RongoCollection:FindOne()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 353 | ) 354 | end 355 | if 356 | self._type ~= "RongoCollection" 357 | or not self._client 358 | or not self._cluster 359 | or not self._database 360 | or not self._collection 361 | then 362 | return RongoError( 363 | "Missing required values on database object, please ensure you have correctly setup a new database" 364 | ) 365 | end 366 | local url = self._client._settings.Url or nil 367 | local auth = self._client._settings.Auth or nil 368 | if not url or not auth then 369 | return RongoError( 370 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 371 | ) 372 | end 373 | local authHeaders = CreateAuthHeaders(auth) 374 | if not authHeaders then 375 | return RongoError( 376 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 377 | ) 378 | end 379 | 380 | local bodyData = { 381 | ["dataSource"] = self._cluster, 382 | ["database"] = self._database, 383 | ["collection"] = self._collection, 384 | ["filter"] = Filter or {}, 385 | } 386 | 387 | local response = RongoRequest(url .. ENDPOINTS.POST.FindOne, authHeaders, bodyData) 388 | if not response then 389 | return 390 | end 391 | 392 | if response["document"] then 393 | return response["document"] 394 | end 395 | return response 396 | end 397 | 398 | function RongoCollection:FindMany( 399 | Filter: { [string]: string | { [string]: string } }?, 400 | Projection: { [string]: number }?, 401 | Sort: { [string]: number }?, 402 | Limit: number?, 403 | Skip: number? 404 | ): { { [string]: any } }? 405 | if not self then 406 | return RongoWarn( 407 | "Attempted to call 'RongoCollection:FindMany()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 408 | ) 409 | end 410 | if 411 | self._type ~= "RongoCollection" 412 | or not self._client 413 | or not self._cluster 414 | or not self._database 415 | or not self._collection 416 | then 417 | return RongoError( 418 | "Missing required values on database object, please ensure you have correctly setup a new database" 419 | ) 420 | end 421 | local url = self._client._settings.Url or nil 422 | local auth = self._client._settings.Auth or nil 423 | if not url or not auth then 424 | return RongoError( 425 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 426 | ) 427 | end 428 | local authHeaders = CreateAuthHeaders(auth) 429 | if not authHeaders then 430 | return RongoError( 431 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 432 | ) 433 | end 434 | 435 | local bodyData = { 436 | ["dataSource"] = self._cluster, 437 | ["database"] = self._database, 438 | ["collection"] = self._collection, 439 | ["filter"] = Filter or "{}", 440 | ["projection"] = Projection, 441 | ["sort"] = Sort, 442 | ["limit"] = Limit, 443 | ["skip"] = Skip, 444 | } 445 | 446 | local response = RongoRequest(url .. ENDPOINTS.POST.FindMany, authHeaders, bodyData) 447 | if not response then 448 | return 449 | end 450 | 451 | if response["documents"] then 452 | return response["documents"] 453 | end 454 | 455 | return response 456 | end 457 | 458 | function RongoCollection:InsertOne(Document: { [string]: any }): string? 459 | if not self then 460 | return RongoWarn( 461 | "Attempted to call 'RongoCollection:InsertOne()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 462 | ) 463 | end 464 | if 465 | self._type ~= "RongoCollection" 466 | or not self._client 467 | or not self._cluster 468 | or not self._database 469 | or not self._collection 470 | then 471 | return RongoError( 472 | "Missing required values on database object, please ensure you have correctly setup a new database" 473 | ) 474 | end 475 | local url = self._client._settings.Url or nil 476 | local auth = self._client._settings.Auth or nil 477 | if not url or not auth then 478 | return RongoError( 479 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 480 | ) 481 | end 482 | local authHeaders = CreateAuthHeaders(auth) 483 | if not authHeaders then 484 | return RongoError( 485 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 486 | ) 487 | end 488 | 489 | local bodyData = { 490 | ["dataSource"] = self._cluster, 491 | ["database"] = self._database, 492 | ["collection"] = self._collection, 493 | ["document"] = Document, 494 | } 495 | 496 | local response = RongoRequest(url .. ENDPOINTS.POST.InsertOne, authHeaders, bodyData) 497 | if not response then 498 | return 499 | end 500 | 501 | if response["insertedId"] then 502 | return response["insertedId"] 503 | end 504 | return response 505 | end 506 | 507 | function RongoCollection:InsertMany(Documents: { { [string]: any } }): { string }? 508 | if not self then 509 | return RongoWarn( 510 | "Attempted to call 'RongoCollection:InsertMany()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 511 | ) 512 | end 513 | if 514 | self._type ~= "RongoCollection" 515 | or not self._client 516 | or not self._cluster 517 | or not self._database 518 | or not self._collection 519 | then 520 | return RongoError( 521 | "Missing required values on database object, please ensure you have correctly setup a new database" 522 | ) 523 | end 524 | local url = self._client._settings.Url or nil 525 | local auth = self._client._settings.Auth or nil 526 | if not url or not auth then 527 | return RongoError( 528 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 529 | ) 530 | end 531 | local authHeaders = CreateAuthHeaders(auth) 532 | if not authHeaders then 533 | return RongoError( 534 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 535 | ) 536 | end 537 | 538 | local bodyData = { 539 | ["dataSource"] = self._cluster, 540 | ["database"] = self._database, 541 | ["collection"] = self._collection, 542 | ["documents"] = Documents, 543 | } 544 | 545 | local response = RongoRequest(url .. ENDPOINTS.POST.InsertMany, authHeaders, bodyData) 546 | if not response then 547 | return 548 | end 549 | 550 | if response["insertedIds"] then 551 | return response["insertedIds"] 552 | end 553 | 554 | return response 555 | end 556 | 557 | function RongoCollection:UpdateOne( 558 | Filter: { [string]: string | { [string]: string } }?, 559 | Update: { [string]: { [string]: any } }?, 560 | Upsert: boolean 561 | ): { matchedCount: number, modifiedCount: number }? 562 | if not self then 563 | return RongoWarn( 564 | "Attempted to call 'RongoCollection:UpdateOne()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 565 | ) 566 | end 567 | if 568 | self._type ~= "RongoCollection" 569 | or not self._client 570 | or not self._cluster 571 | or not self._database 572 | or not self._collection 573 | then 574 | return RongoError( 575 | "Missing required values on database object, please ensure you have correctly setup a new database" 576 | ) 577 | end 578 | local url = self._client._settings.Url or nil 579 | local auth = self._client._settings.Auth or nil 580 | if not url or not auth then 581 | return RongoError( 582 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 583 | ) 584 | end 585 | local authHeaders = CreateAuthHeaders(auth) 586 | if not authHeaders then 587 | return RongoError( 588 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 589 | ) 590 | end 591 | 592 | local bodyData = { 593 | ["dataSource"] = self._cluster, 594 | ["database"] = self._database, 595 | ["collection"] = self._collection, 596 | ["filter"] = Filter or {}, 597 | ["update"] = Update, 598 | ["upsert"] = Upsert, 599 | } 600 | 601 | local response = RongoRequest(url .. ENDPOINTS.POST.UpdateOne, authHeaders, bodyData) 602 | if not response then 603 | return 604 | end 605 | 606 | return response 607 | end 608 | 609 | function RongoCollection:UpdateMany( 610 | Filter: { [string]: string | { [string]: string } }?, 611 | Update: { [string]: { [string]: any } }?, 612 | Upsert: boolean 613 | ): { matchedCount: number, modifiedCount: number }? 614 | if not self then 615 | return RongoWarn( 616 | "Attempted to call 'RongoCollection:UpdateMany()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 617 | ) 618 | end 619 | if 620 | self._type ~= "RongoCollection" 621 | or not self._client 622 | or not self._cluster 623 | or not self._database 624 | or not self._collection 625 | then 626 | return RongoError( 627 | "Missing required values on database object, please ensure you have correctly setup a new database" 628 | ) 629 | end 630 | local url = self._client._settings.Url or nil 631 | local auth = self._client._settings.Auth or nil 632 | if not url or not auth then 633 | return RongoError( 634 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 635 | ) 636 | end 637 | local authHeaders = CreateAuthHeaders(auth) 638 | if not authHeaders then 639 | return RongoError( 640 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 641 | ) 642 | end 643 | 644 | local bodyData = { 645 | ["dataSource"] = self._cluster, 646 | ["database"] = self._database, 647 | ["collection"] = self._collection, 648 | ["filter"] = Filter or {}, 649 | ["update"] = Update, 650 | ["upsert"] = Upsert, 651 | } 652 | 653 | local response = RongoRequest(url .. ENDPOINTS.POST.UpdateMany, authHeaders, bodyData) 654 | if not response then 655 | return 656 | end 657 | 658 | return response 659 | end 660 | 661 | function RongoCollection:DeleteOne(Filter: { [string]: string | { [string]: string } }?): number? 662 | if not self then 663 | return RongoWarn( 664 | "Attempted to call 'RongoCollection:DeleteOne()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 665 | ) 666 | end 667 | if 668 | self._type ~= "RongoCollection" 669 | or not self._client 670 | or not self._cluster 671 | or not self._database 672 | or not self._collection 673 | then 674 | return RongoError( 675 | "Missing required values on database object, please ensure you have correctly setup a new database" 676 | ) 677 | end 678 | local url = self._client._settings.Url or nil 679 | local auth = self._client._settings.Auth or nil 680 | if not url or not auth then 681 | return RongoError( 682 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 683 | ) 684 | end 685 | local authHeaders = CreateAuthHeaders(auth) 686 | if not authHeaders then 687 | return RongoError( 688 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 689 | ) 690 | end 691 | 692 | local bodyData = { 693 | ["dataSource"] = self._cluster, 694 | ["database"] = self._database, 695 | ["collection"] = self._collection, 696 | ["filter"] = Filter or {}, 697 | } 698 | 699 | local response = RongoRequest(url .. ENDPOINTS.POST.DeleteOne, authHeaders, bodyData) 700 | if not response then 701 | return 702 | end 703 | 704 | if response["deletedCount"] then 705 | return response["deletedCount"] 706 | end 707 | return response 708 | end 709 | 710 | function RongoCollection:DeleteMany(Filter: { [string]: string | { [string]: string } }?): number? 711 | if not self then 712 | return RongoWarn( 713 | "Attempted to call 'RongoCollection:DeleteMany()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 714 | ) 715 | end 716 | if 717 | self._type ~= "RongoCollection" 718 | or not self._client 719 | or not self._cluster 720 | or not self._database 721 | or not self._collection 722 | then 723 | return RongoError( 724 | "Missing required values on database object, please ensure you have correctly setup a new database" 725 | ) 726 | end 727 | local url = self._client._settings.Url or nil 728 | local auth = self._client._settings.Auth or nil 729 | if not url or not auth then 730 | return RongoError( 731 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 732 | ) 733 | end 734 | local authHeaders = CreateAuthHeaders(auth) 735 | if not authHeaders then 736 | return RongoError( 737 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 738 | ) 739 | end 740 | 741 | local bodyData = { 742 | ["dataSource"] = self._cluster, 743 | ["database"] = self._database, 744 | ["collection"] = self._collection, 745 | ["filter"] = Filter or {}, 746 | } 747 | 748 | local response = RongoRequest(url .. ENDPOINTS.POST.DeleteMany, authHeaders, bodyData) 749 | if not response then 750 | return 751 | end 752 | 753 | if response["deletedCount"] then 754 | return response["deletedCount"] 755 | end 756 | return response 757 | end 758 | 759 | function RongoCollection:Aggregate(Pipeline: { { [string]: any } }): { { [string]: any } }? 760 | if not self then 761 | return RongoWarn( 762 | "Attempted to call 'RongoCollection:Aggregate()' without initializing a collection; you can do this with 'RongoDatabase:GetCollection()'" 763 | ) 764 | end 765 | if 766 | self._type ~= "RongoCollection" 767 | or not self._client 768 | or not self._cluster 769 | or not self._database 770 | or not self._collection 771 | then 772 | return RongoError( 773 | "Missing required values on database object, please ensure you have correctly setup a new database" 774 | ) 775 | end 776 | local url = self._client._settings.Url or nil 777 | local auth = self._client._settings.Auth or nil 778 | if not url or not auth then 779 | return RongoError( 780 | "RongoClient contains invalid Url or Auth values, please ensure you have correctly initialized the client" 781 | ) 782 | end 783 | local authHeaders = CreateAuthHeaders(auth) 784 | if not authHeaders then 785 | return RongoError( 786 | "RongoClient authorization was setup incorrectly, please ensure you have correctly initialized the client" 787 | ) 788 | end 789 | 790 | local bodyData = { 791 | ["dataSource"] = self._cluster, 792 | ["database"] = self._database, 793 | ["collection"] = self._collection, 794 | ["pipeline"] = Pipeline, 795 | } 796 | 797 | local response = RongoRequest(url .. ENDPOINTS.POST.Aggregate, authHeaders, bodyData) 798 | if not response then 799 | return 800 | end 801 | 802 | return response 803 | end 804 | 805 | return Rongo 806 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typedwaves/Rongo/413a61dd802e2ca3b6b0e90e6775aeb0efb94b8c/tests/.gitkeep -------------------------------------------------------------------------------- /wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "starnamics/rongo" 3 | description = "MongoDB Atlas Data API wrapper for connecting Roblox to MongoDB" 4 | version = "2.1.2" 5 | license = "MIT" 6 | realm = "server" 7 | registry = "https://github.com/UpliftGames/wally-index" 8 | exclude = ["**"] 9 | include = ["src", "src/**", "default.project.json", "wally.toml"] --------------------------------------------------------------------------------