{"id":25833909,"url":"https://github.com/bigfootds/supercamo","last_synced_at":"2026-02-18T18:01:50.036Z","repository":{"id":240375468,"uuid":"799371105","full_name":"BigfootDS/supercamo","owner":"BigfootDS","description":"Camo-inspired ODM for NeDB, built specifically for BigfootDS' needs.","archived":false,"fork":false,"pushed_at":"2025-01-25T06:37:48.000Z","size":1262,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-02-26T06:32:50.388Z","etag":null,"topics":["bigfootds","camo","database","hacktoberfest","hacktoberfest2024","javascript","nedb","nodejs","nosql","odm","supercamo"],"latest_commit_sha":null,"homepage":"https://bigfootds.github.io/supercamo/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BigfootDS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["BigfootDS"]}},"created_at":"2024-05-11T23:48:23.000Z","updated_at":"2025-01-25T06:37:51.000Z","dependencies_parsed_at":"2024-07-26T07:30:25.159Z","dependency_job_id":"f21bc0af-9bc3-41d3-bf14-3bb7926c20d9","html_url":"https://github.com/BigfootDS/supercamo","commit_stats":null,"previous_names":["bigfootds/supercamo"],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigfootDS%2Fsupercamo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigfootDS%2Fsupercamo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigfootDS%2Fsupercamo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BigfootDS%2Fsupercamo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BigfootDS","download_url":"https://codeload.github.com/BigfootDS/supercamo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241275982,"owners_count":19937438,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bigfootds","camo","database","hacktoberfest","hacktoberfest2024","javascript","nedb","nodejs","nosql","odm","supercamo"],"created_at":"2025-02-28T23:33:20.624Z","updated_at":"2026-02-18T18:01:50.029Z","avatar_url":"https://github.com/BigfootDS.png","language":"TypeScript","funding_links":["https://github.com/sponsors/BigfootDS"],"categories":[],"sub_categories":[],"readme":"# @bigfootds/supercamo\n\nSuperCamo is a Camo-inspired object data modeller (ODM) for NeDB, built specifically for BigfootDS' needs.\n\nThis package was inspired by Scott Robinson's [Camo](https://github.com/scottwrobinson/camo) ODM - but BigfootDS had some specific needs and an urge to try out TypeScript. We greatly appreciate what Camo is and does!\n\n## The URLs\n\n- [NPM package](https://www.npmjs.com/package/@bigfootds/supercamo)\n- [GitHub repository](https://github.com/BigfootDS/supercamo)\n- [Documentation website](https://bigfootds.github.io/supercamo/)\n\nWe've dumped a bunch of info into this readme here for the sake of NPM and GitHub repo \"quickly glance at packages to see if they sound cool\"-type developers, but we strongly recommend digging into the documentation website. It's nicer!\n\n\n## The what\n\n- NeDB compatibility\n- Treat multiple NeDB datastores as a singular database\n- Allow concurrent connections to multiple databases\n- ODM-style wrapping around NeDB datastores\n- Modern JavaScript implementations\n- Leaning on standard NodeJS functions and APIs more than ever before to minimize production dependencies\n\n## The nitty-gritty\n\n- Built using NodeJS version 20, looking _forward_ only.\n- Built to depend on this particular flavour of NeDB:\n\t- [@seald-io/nedb](https://github.com/seald/nedb)\n\n## The installation\n\nRun this command:\n\n```bash\nnpm install @bigfootds/supercamo\n```\n\n## The usage\n\nThis package is intended for usage in back-end JavaScript systems - anything that is built with _and runs on_ NodeJS, not the browser. Might work in the browser - we haven't tested it in the browser ourselves - we only have a need for NodeJS compatibility at this time. We're specifically after ExpressJS server usage and ElectronJS \"main\" process usage.\n\n### Concepts\n\nSuperCamo is an object document manager (ODM) for NeDB. This means it's essentially a NoSQL database wrapper.\n\nSince SuperCamo facilitates concurrent connections of multiple databases, it's got some fundamental differences in how it works compared to ye olde Camo and other NeDB-compatible ODMs.\n\nEssentially:\n\n- This library lets you create custom NedbClients.\n- Each custom NedbClient that you create must include a list of Documents allowed to exist in that database client's database. \n\t- EmbeddedDocuments do not need specifying, as they can only be used within Documents anyway.\n- When performing queries on Documents, you must instead perform queries on a specific database client's reference to the Document. eg. no more `Document.findOne();`, but instead `SomeClient.findOneDocument(\"CollectionName\", query);`.\n\n### Declare Your Models\n\nFirst, you should create some models or documents.\n\n```js\nconst {NedbDocument} = require(\"@bigfootds/supercamo\");\n\nmodule.exports = class User extends NedbDocument {\n\tconstructor(data, databaseName, collectionName){\n\t\tsuper(data, databaseName, collectionName);\n\n\t\tthis.rules = {\n\t\t\tname: {\n\t\t\t\ttype: String,\n\t\t\t\trequired: true\n\t\t\t},\n\t\t\temail: {\n\t\t\t\ttype: String,\n\t\t\t\trequired: true,\n\t\t\t\tunique: true\n\t\t\t},\n\t\t\tcompany: {\n\t\t\t\ttype: String,\n\t\t\t\trequired: true,\n\t\t\t\tdefault: \"BigfootDS\"\n\t\t\t},\n\t\t\tluckyNumber: {\n\t\t\t\ttype: Number,\n\t\t\t\trequired: false,\n\t\t\t\tdefault: () =\u003e {\n\t\t\t\t\treturn Math.floor(Math.random() * 100) \n\t\t\t\t}\n\t\t\t},\n\t\t\tassignedPokemonOne: {\n\t\t\t\ttype: String,\n\t\t\t\trequired: true,\n\t\t\t\tdefault: async () =\u003e {\n\t\t\t\t\tlet pokemonNumber = Math.floor(Math.random() * 1025);\n\t\t\t\t\tlet response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonNumber}`);\n\t\t\t\t\tlet data = await response.json();\n\t\t\t\t\treturn data.name;\n\t\t\t\t}\n\t\t\t},\n\t\t\tassignedPokemonTwo: {\n\t\t\t\ttype: String,\n\t\t\t\trequired: false,\n\t\t\t\tdefault: new Promise((resolve, reject) =\u003e {\n\t\t\t\t\tlet pokemonNumber = Math.floor(Math.random() * 1025);\n\t\t\t\t\tfetch(`https://pokeapi.co/api/v2/pokemon/${pokemonNumber}`).then(response =\u003e response.json()).then((data) =\u003e {\n\t\t\t\t\t\tresolve(data.name);\n\t\t\t\t\t}).catch((error) =\u003e {\n\t\t\t\t\t\t// No reject() since we want this fallback data to be accepted if the fetch fails.\n\t\t\t\t\t\tresolve(\"pikachu\");\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t};\n\t}\n}\n```\n\nSome points of difference here, compared to the original Camo library:\n\n- We do allow a document property to be both required **and** have a default value. So if no value is provided for `company` when making instances of the model shown above, it will not throw an error - it will just set the value to `\"BigfootDS\"` and move on.\n- Whatever value `default` has must evaluate into a supported data type. This means that `default` can be a value, a function, an async function, or promise. \n\n\n\n### Instantiating a Database Client\n\nOnce you have a model created, you can create an instance of a database client.\n\nEssentially, we must tell each database client instance which models they're allowed to use. We do this by specifying a key-value pair list of collections and their models.\n\n```js\nconst {SuperCamo, CollectionListEntry} = require(\"@bigfootds/supercamo\");\n\nlet exampleDb = await SuperCamo.connect(\n\t\"SomeDatabaseName\", \n\tpath.join(process.cwd(), \"databases\", \"SomeDatabaseName\"),\n\t[\n\t\tnew CollectionListEntry(name: \"Users\", model: User), \n\t\tnew CollectionListEntry(name: \"Admins\", model: User), \n\t\tnew CollectionListEntry(name: \"Profiles\", model: Profile),\n\t\tnew CollectionListEntry(name: \"Config\", model: Settings)\n\t]\n);\n```\n\nAs you can see in the code above, we can use a model in multiple collections in a single database.\n\nSince these database client instances are entirely self contained, we can create more and more of them.\n\n```js\nconst {SuperCamo, CollectionListEntry} = require(\"@bigfootds/supercamo\");\n\nlet exampleDb = await SuperCamo.connect(\n\t\"SomeDatabaseName\", \n\tpath.join(process.cwd(), \"databases\"),\n\t[\n\t\tnew CollectionListEntry(name: \"Users\", model: User), \n\t\tnew CollectionListEntry(name: \"Admins\", model: User), \n\t\tnew CollectionListEntry(name: \"Profiles\", model: Profile),\n\t\tnew CollectionListEntry(name: \"Config\", model: Settings)\n\t]\n);\n\nlet anotherDb = await SuperCamo.connect(\n\t\"SomeOtherDatabaseName\", \n\tpath.join(process.cwd(), \"databases\"),\n\t[\n\t\tnew CollectionListEntry(name: \"Users\", model: User), \n\t\tnew CollectionListEntry(name: \"Admins\", model: User), \n\t\tnew CollectionListEntry(name: \"Profiles\", model: Profile),\n\t\tnew CollectionListEntry(name: \"Config\", model: Settings)\n\t]\n);\n```\n\n### Using a Database Client\n\nEach database client has its own set of data, so any queries run from that client instance will only find its data. No crossover between database clients!\n\n```js\nlet newUser = await exampleDb.insertOne(\"Users\", {name:\"Alex\", email:\"test@email.com\"})\nconsole.log(newUser.toString());\n\nlet newUsers = await anotherDb.insertMany(\"Users\", [\n\t{name:\"Not Alex\", email:\"test@email.com\"},\n\t{name:\"Definitely Not Alex\", email:\"test@email.com\"},\n\t{name:\"Could be Alex, but probably not\", email:\"test@email.com\"},\n]);\nconsole.log(newUsers.toString());\n\n```\n\nWe also provide a distinction between model instances and raw data objects. \nNeDB works in raw data objects.\n\n```js\nlet foundUsers = await exampleDb.findManyDocuments(\"Users\", {});\nconsole.log(foundUsers);\n\nlet foundUsers2 = await anotherDb.findManyObjects(\"Users\", {});\nconsole.log(foundUsers2);\n```\n\nRaw data objects are good for simplifying API logic (eg. retrieve data, send it in the response, nothing more), but document instances make it easier to do more things with the retrieved data such as update and validate the content.\n\n### SuperCamo static object\n\nThis package stores all database client instances on a static class. Clients exist during the code's runtime, and can be created via the `connect` static method on SuperCamo as well as disconnected or dropped too.\n\nBasically, you should be able to create your database clients in File A and continue using them in File B solely through the `SuperCamo` import variable.\n\n```js\n\nconst {SuperCamo} = require(\"@bigfootds/supercamo\");\n\nconsole.log(SuperCamo.clientList());\n\nlet dbInstance = SuperCamo.clientGet(\"SomeDatabaseName\");\n\nlet foundUsers = await dbInstance.findManyDocuments(\"Users\", {});\nconsole.log(foundUsers);\n\n```\n\n### Document References\n\nDocuments can reference other documents. You would do this by defining a property in one document using the other document as a data type, like so:\n\n```js\nconst {NedbDocument} = require(\"@bigfootds/supercamo\");\n\nclass Profile extends NedbDocument {\n\tconstructor(data, databaseName, collectionName){\n\t\tsuper(data, databaseName, collectionName);\n\n\t\tthis.rules = {\n\t\t\tusername: {\n\t\t\t\ttype: String,\n\t\t\t\trequired: true\n\t\t\t}\n\t\t\tuser: {\n\t\t\t\ttype: User,\n\t\t\t\trequired: true,\n\t\t\t\tcollection: \"Users\"\n\t\t\t}\n\t\t}\n\n\t}\n}\n```\n\nPlease note that you must specify the `collection` as well as the `type` when using a document reference.\n\nYou would then work with that data like so:\n\n```js\nlet existingUser = await exampleDb.findOneDocument(\"Users\", {email:\"alex@bigfootds.com\"});\n\nlet newProfile = await exampleDb.insertOne(\"Profiles\", {\n\tusername: \"thebestalex\", \n\tuser: existingUser._id,\n});\n```\n\nWhen retrieving data, the query will not populate any data. Instead, you must manually call `.toPopulatedObject()` on a document instance.\n\n```js\nlet existingProfile = await blogDb.findOneDocument(\"Profiles\", {username: \"thebestalex\"});\nlet populatedData = await existingProfile.toPopulatedObject();\nconsole.log(populatedData);\n```\n\n```js\n{\n  _id: 'UE5t6EzCDTB1f6Ss',\n  username: 'thebestalex',\n  user: {\n    _id: 'lX2uQosLgGPdHpbU',\n    name: 'Alex',\n    email: 'alex@bigfootds.com',\n    company: 'BigfootDS',\n    luckyNumber: 11,\n    assignedPokemonOne: 'magmortar',\n    assignedPokemonTwo: 'dragonite'\n  }\n}\n```\n\n\n### Embedded Documents\n\nEmbedded documents (a.k.a \"subdocuments\") are a way to give a schema and validation to a set of properties. They're like Documents, but cannot exist on their own.\n\nCreate them like so:\n\n```js\nconst {NedbEmbeddedDocument} = require(\"@bigfootds/supercamo\");\n\nclass ProfileFlags extends NedbEmbeddedDocument {\n\tconstructor(data, databaseName, collectionName){\n\t\tsuper(data, databaseName, collectionName);\n\n\t\tthis.banned = {\n\t\t\ttype: Boolean,\n\t\t\trequired: true,\n\t\t\tdefault: false\n\t\t}\n\n\t\tthis.premium = {\n\t\t\ttype: Boolean,\n\t\t\trequired: true,\n\t\t\tdefault: false\n\t\t}\n\t}\n}\n```\n\nYou would then use them in another document's schema as if they were a data type, like so:\n\n```js\nconst {NedbDocument} = require(\"@bigfootds/supercamo\");\n\nclass Profile extends NedbDocument {\n\tconstructor(data, databaseName, collectionName){\n\t\tsuper(data, databaseName, collectionName);\n\n\t\tthis.username = {\n\t\t\ttype: String,\n\t\t\trequired: true\n\t\t}\n\n\t\tthis.user = {\n\t\t\ttype: User,\n\t\t\tcollection: \"Users\",\n\t\t\trequired: true\n\t\t}\n\n\t\tthis.profileFlags = {\n\t\t\ttype: ProfileFlags,\n\t\t\trequired: false\n\t\t}\n\t}\n}\n```\n\nWhen creating data where the document uses an embedded document, you should use an object matching the schema of the embedded document. Consider the code below:\n\n```js\nlet newProfile = await blogDb.insertOne(\"Profiles\", {\n\tusername: \"thebestalex\", \n\tuser: existingUserId,\n\tprofileFlags: {banned: false, premium: true}\n});\n```\n\nEssentially, an embedded document is a validation tool. The data is still saved as an object in the database, and it's simplest to work with that data as an object in the current state of SuperCamo. But for things like `insertOne` as shown above, the validation of the ProfileFlags embedded document would run before saving the Profile document to the database.\n\n```js\n{\n  _id: 'UE5t6EzCDTB1f6Ss',\n  username: 'thebestalex',\n  user: 'lX2uQosLgGPdHpbU',\n  profileFlags: { banned: false, premium: true }\n}\n```\n\n\nDocument population is not tied to embedded document functionality at all - the above code snippet shows the embedded document data in full while the user ID is just an ID. The below code snippet shows the same profile flag data, as well as the populated user data. The embedded document data is fully present regardless of population settings.\n\n```js\n{\n  _id: 'UE5t6EzCDTB1f6Ss',\n  username: 'thebestalex',\n  user: {\n    _id: 'lX2uQosLgGPdHpbU',\n    name: 'Alex',\n    email: 'alex@bigfootds.com',\n    company: 'BigfootDS',\n    luckyNumber: 11,\n    assignedPokemonOne: 'magmortar',\n    assignedPokemonTwo: 'dragonite'\n  },\n  profileFlags: { banned: false, premium: true }\n}\n```\n\n## Example Projects\n\nDig through these to see what else can be done with this library, and learn about how it's used in specific types of projects:\n\n- [NodeJS terminal app](https://github.com/BigfootDS/supercamo-example-basic)\n- ExpressJS server - NOT YET IMPLEMENTED\n- ElectronJS app - NOT YET IMPLEMENTED\n\nThose repositories are also used to dogfood this library - features needed for those projects will be prioritised. Check their readme files to see what is mapped out on each repository.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigfootds%2Fsupercamo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbigfootds%2Fsupercamo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigfootds%2Fsupercamo/lists"}