{"id":23740099,"url":"https://github.com/cobbzilla/mobiletto-orm","last_synced_at":"2026-03-03T21:30:21.323Z","repository":{"id":174581475,"uuid":"652436659","full_name":"cobbzilla/mobiletto-orm","owner":"cobbzilla","description":"Object-relational mapper (ORM) built atop mobiletto","archived":false,"fork":false,"pushed_at":"2024-01-06T14:35:36.000Z","size":688,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-04T23:35:54.528Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cobbzilla.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-06-12T04:35:31.000Z","updated_at":"2023-06-12T04:35:55.000Z","dependencies_parsed_at":"2024-01-13T10:40:51.085Z","dependency_job_id":"f8d3c255-a4f1-4c2f-8969-7b203207f778","html_url":"https://github.com/cobbzilla/mobiletto-orm","commit_stats":{"total_commits":232,"total_committers":1,"mean_commits":232.0,"dds":0.0,"last_synced_commit":"8afbfdf1f53a92169046e90321ed72c75f148529"},"previous_names":["cobbzilla/mobiletto-orm"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cobbzilla%2Fmobiletto-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cobbzilla%2Fmobiletto-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cobbzilla%2Fmobiletto-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cobbzilla%2Fmobiletto-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cobbzilla","download_url":"https://codeload.github.com/cobbzilla/mobiletto-orm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239036952,"owners_count":19571600,"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":[],"created_at":"2024-12-31T09:47:04.428Z","updated_at":"2026-03-03T21:30:21.281Z","avatar_url":"https://github.com/cobbzilla.png","language":"JavaScript","funding_links":["https://www.patreon.com/cobbzilla"],"categories":[],"sub_categories":[],"readme":"mobiletto-orm\n=============\nA simple object-relational mapper (ORM) for [mobiletto](https://www.npmjs.com/package/mobiletto)\nstorage.\n\nMobiletto supports connections to Amazon S3, Backblaze B2, and local filesystems.\n\n# Contents\n* [Source](#Source)\n* [Support and Funding](#Support-and-Funding)\n* [Installation and usage](#Installation-and-usage)\n    * [npm package](#npm-package)\n    * [From source](#From-source)\n* [Mobiletto Storage Drivers](#Mobiletto-Storage-Drivers)\n* [Quick Start](#Quick-Start)\n* [repositoryFactory](#repositoryFactory)\n* [Type Definitions](#Type-Definitions)\n  * [Type Name](#Type-Name) \n  * [Fields](#Fields)\n    * [Field Types](#Field-Types)\n    * [Field Controls](#Field-Controls)\n  * [Optional Type Parameters](#Optional-Type-Parameters)\n    * [Base Path](#Base-Path)\n    * [Max Versions](#Max-Versions)\n    * [Min Writes](#Min-Writes)\n    * [Alternate IDs](#Alternate-IDs)\n* [Caveats](#Caveats)\n  * [id Field](#id-field)\n\n\n### Source\n* [mobiletto-orm on GitHub](https://github.com/cobbzilla/mobiletto-orm)\n* [mobiletto-orm on npm](https://www.npmjs.com/package/mobiletto-orm)\n\n## Support and Funding\nI would be sincerely grateful for any [contribution via Patreon](https://www.patreon.com/cobbzilla)\n\n## Installation and usage\nYou can install `mobiletto-orm` via npm or yarn\n\n### npm package\n\n    # install with npm\n    npm i mobiletto-orm\n\n    # install with yarn\n    yarn add mobiletto-orm\n\n### From source\nTo access the mobiletto-orm source:\n\n    # Clone source and install dependencies\n    git clone https://github.com/cobbzilla/mobiletto-orm.git\n    cd mobiletto-orm\n    yarn install\n\n## Mobiletto Storage Drivers\nmobiletto-orm depends on [mobiletto-base](https://github.com/cobbzilla/mobiletto-base),\nwhich does not include any storage drivers.\n\nTo enable a particular storage driver, first add the dependency to your project:\n\n    # Use npm to install the storage driver(s) that you will need \n    npm i mobiletto-driver-s3\n    npm i mobiletto-driver-b2\n    npm i mobiletto-driver-local\n    npm i mobiletto-driver-indexeddb\n\n    # Or, use yarn to install the storage driver(s) that you will need\n    yarn add mobiletto-driver-s3\n    yarn add mobiletto-driver-b2\n    yarn add mobiletto-driver-local\n    yarn add mobiletto-driver-indexeddb\n\nIn your code, before using mobiletto to connect to storage, register the driver:\n\n    const { registerDriver } = require('mobiletto-base')\n    registerDriver('s3', require('mobiletto-driver-s3'))\n    registerDriver('b2', require('mobiletto-driver-b2'))\n    registerDriver('local', require('mobiletto-driver-local'))\n    registerDriver('indexeddb', require('mobiletto-driver-indexeddb'))\n\n## Quick Start\n\n    const orm = require('mobiletto-orm')\n\n    // Register mobiletto storage drivers (described above)\n\n    // How to create mobiletto connections: https://github.com/cobbzilla/mobiletto/blob/master/README.md#Basic-usage\n    const conns = [ ...array of connections... ]\n\n    // Objects and indexes will be replicated across all mobiletto connections\n    // The 'conns' parameter below could also be an async function that returns an array of connections\n    const factory = orm.repositoryFactory(conns)\n\n    // Objects are stored in type-specific repositories\n    // A repository is backed by a directory on each mobiletto connection\n    const repository = factory.repository({\n        typeName: 'Account',\n        fields: {\n            username: {\n                required: true,        // field is required\n                min: 5,                // min 5 chars\n                max: 100,              // max 100 chars\n                regex: /[A-Z\\d+]+/gi,  // validate against a regex\n                index: true,           // enable findBy('username', someUsername)\n                updatable: false       // updates will be silently ignored\n            },\n            email: {\n                required: true,        // field is required\n                min: 8,                // min 8 chars\n                max: 100,              // max 100 chars\n                // a reasonable email regex\n                regex: /^[A-Z\\d][A-Z\\d._%+-]*@[A-Z\\d.-]+\\.[A-Z]{2,6}$/gi,\n                index: true            // enable findBy('email', someEmailAddress)\n            },\n            bio: {\n                max: 1000              // max 1000 chars (field is optional)\n            },\n            yearJoined: {\n                minValue: 2023         // minimum numeric value\n                maxValue: 2123         // maxmimum numeric value\n            }\n        }\n    })\n\n    const username = 'some_username'\n    const email = 'jimmy@example.com'\n\n    // Every object has a unique 'id' field that is always required and must be unique\n    // However, if typeDef supports alternateID (default enables) you can use 'username' or 'email' as the 'id'\n    // See Alternate IDs below for more info\n    // If an object with the same id already exists, a MobilettoOrmValidationError will be thrown\n    // If a race condition is detected (simultaneous create), a MobilettoOrmSyncError will be throw\n    const newUser = repository.create({\n        username: username,\n        email: email,\n        password: 'some_hashed_password'\n    })\n\n    // Find by username. This works because the field has 'index: true'\n    const foundByUsername = repository.findBy('username', username)\n\n    // Find by email. This works because the field has 'index: true'\n    const foundByEmail = repository.findBy('email', email)\n\n    // Find all accounts\n    const everyone = repository.findAll()\n\n    // Find all accounts, even removed ones\n    const everyone = repository.findAllIncludingRemoved()\n\n    // Find by arbitrary predicate\n    const matches = repository.find(obj =\u003e functionThatReturnsTrueIfObjectMatches(obj))\n\n    // Find by arbitrary predicate, including removed objects\n    const matchesIncludingRemoved = repository.find(obj =\u003e predicate(obj), { removed: true })\n\n    // When creating changes, you must always specify the 'id' of the object to update\n    // But alternate IDs (see below) will be used if present\n    // Any other changes are optional\n    const changes = {\n      username,\n      bio: 'this is my biography'\n    }\n\n    // When calling 'update' you must supply the previous version, this helps avoid race conditions\n    // If a race condition is detected (simultaneous changes), a MobilettoOrmSyncError will be throw\n    const updatedUser = repository.update(changes, newUser.version)\n\n    // When calling 'remove' you must supply the previous version, this helps avoid race conditions\n    // If a race condition is detected (simultaneous changes), a MobilettoOrmSyncError will be throw\n    // The tombstone retains the object ID, ctime\n    const tombstone = repository.remove(username, updatedUser.version)\n\n    // Call 'purge' to clean up all the files. You must call 'remove' before calling 'purge'\n    // The following are all equivalent statements. Note that in our example, username was the\n    // object ID, and is thus also the tombstone id\n    const purged1 = repository.purge(tombstone)\n    const purged2 = repository.purge(tombstone.id)\n    const purged3 = repository.purge(username)\n\n## repositoryFactory\nThe `repositoryFactory` function is the way to start working with mobiletto-orm\n\nIf you're unfamiliar with [how to create mobiletto connections](https://github.com/cobbzilla/mobiletto/blob/master/README.md#Basic-usage),\nnow is a great time to read up. It's fairly simple.\n\nWhen you create a `repositoryFactory`, you pass an array of mobiletto connections, or an async function that\nreturns a Promise that resolves to an array of mobiletto connections.\n\n## Type Definitions\n\n### Type Name\nThe `typeName` property is a string that designates the name of the type.\n\nType names must be globally unique within your app.\n\nType names cannot contain the `%` or `~` characters.\n\n### Fields\nEvery type has some built-in fields:\n* id: the primary key, a unique identifier for each instance of the type\n* ctime: the creation time: initialized when the object is created, never updated thereafter\n* mtime: the modification time: initialized when the object is created, updated upon every change (update or remove)\n* version: a unique string that identifies the particular version of the object represented by the 'id'\n* type: the data type of the field; if not set explicitly, it will be implied (see [Field Types](#Field-Types)) \n\nWithin a type definition object that you might pass to the repository function, the `fields` property\nis a JSON object, where the keys are the field names, and the values are objects that describes that\nfield's configuration.\n\nThe simplest field declaration is\n\n    myAnythingField: {}\n\nThis allows anything to be stored in the field. The field can also be omitted or set to null.\n\nThe next simplest field declaration is:\n\n    myRequiredField: { required: true } \n\nThis creates a field that is required. Calls to `create` or `update` where the object passed in\ndoes not define this field (or where the field's value is null or the empty string), then a validation\nerror (of type MobilettoOrmValidationError) will be thrown back to the caller.\n\nOther field configuration properties are outlined below:\n\n    myExampleField: {\n        # this field can only be set upon creation\n        # updates to this field will be silently ignored\n        updatable: false,\n\n        # the type of the field\n        # valid values are: 'string', 'number', 'boolean', 'array', 'object'\n        # incorrectly-typed values result in a validation errors\n        type: 'string',\n\n        # restrict to a specific set of values\n        # caveat: because this field doesn't define `required: true`, a null value is also valid\n        values: ['Some-Default-Value', 'foo', 'bar'],\n\n        # a separate set of labels to use, when presenting the above values in a user interface\n        # if not defined, the `value` array will be used\n        labels: ['the default thing', 'the foo thing', 'the bar thing'],\n\n        # Instead of the above separate `values` and `labels` arrays, use a single `items` array\n        items: [\n          { value: 'Some-Default-Value', label: 'the default thing'},\n          { value: 'foo',                label: 'the foo thing'},\n          { value: 'bar',                label: 'the bar thing'}\n        ]\n\n        # when creating a new object, use this default value if myExampleField is empty\n        default: 'Some-Default-Value'\n    }\n\n    myExampleStringField: {\n        control: 'password', # in a user interface, use a password field (do not show the value)\n        min: 10,             # minimum string length of 10 characters\n        max: 200,            # maximum string length of 200 characters\n        regex: /^[A-Z]+$/gi  # values must match this regex\n    }\n\n    myExampleNumberField: {\n        minValue: 100,       # value must be greater than or equal to this minimum numeric value\n        max: 1000,           # value must be less than or equal to this maximum numeric value\n        regex: /^[\\d]+$/gi   # values must match this regex\n    }\n\n    myMultivaluedField: {\n        # value must be an array of these values\n        # note: if required is false/undefined, then an empty or null array is also valid\n        multi: ['apple', 'banana', 'peach', 'plum', 'eggplant', 'squash', 'durian', 'pear']\n    }\n\n#### Field Types\nThe `type` property of a field definition determines what values are allowed when calling `create` or `update`.\n\nThe `type` can be `string`, `number`, `boolean`, `array`, or `object`\n\nThe `id` property always has a `type` of `string`\n\nYou usually don't have to set the `type` on a field, because it can be implied:\n\n * If the field has a `min`, `max` or `regex` property, the field's implied `type` is `string`\n * If the field has a `minValue` or `maxValue` property, the field's implied `type` is `number`\n * If the field has a `default` value, the field's implied `type` will be the type of the `default` value\n * If the field has a `values` array of valid values, the field's implied `type` will be the type of the first element in the array\n * If the field doesn't have an explicit `type` and none of the above applies, the field's type will be `string`\n\n#### Field Controls\nThe `control` field is a suggestion to other code about what kind of user-interface control would be best\nto set the value for this field.\n\nThe `control` can be:\n * `text`: a text box. the default value if nothing more specific can be determined\n * `password`: a text box that does not show its contents to the user\n * `label`: a read-only display view of the value\n * `textarea`: a larger text editing area\n * `select`: select one item from a list\n * `multi`: multi-select 1+ items from a list\n * `flag`: a yes/no value\n * `hidden`: do not show this field at all in a user interface\n * `system`: do not show this field at all in a user interface, even to admins/superusers\n\nIf no `control` is set on a field, the default `control` is:\n\n  * If the field's type is `boolean`, then the `control` is `flag`\n  * If the field has a `multi` array, then the `control` is `multi`\n  * If the field has a `values` array, then the `control` is `select` (for example a single-selection drop-down)\n  * If the field's name is `password`, then the `control` is `password`\n  * If nothing else matches, then the `control` is `text`\n\n### Optional Type Parameters\nThese type definition properties are optional.\n\n#### Base Path\nThe `basePath` property specifies a directory prefix when writing to the mobiletto connections.\n\nThe default `basePath` is `''` (no prefix).\n\n#### Max Versions\nThe `maxVersions` property specifies how many (most recent) versions of an object will be retained.\n\nOlder versions are deleted. The default `maxVersions` is 5.\n\n#### Min Writes\nThe `minWrites` property specifies how many of the underlying storage must have a successful write\nto consider a create/update operation a success.\n\nIf fewer than this many writes succeed, the entire operation fails and any successful writes are deleted.\n\nThe default value is 0, which means that *all* writes must succeed. Set to 1 and only a single write must succeed.\n\n#### Alternate IDs\nThe `alternateIdFields` property is an array of strings. If an object is passed to `create` or `update` and\ndoes not have an `id` field, but does have one of these fields, then the first field that has a non-empty\nstring value will be used as the `id`.\n\nThe default set of `alternateIdFields` is: `['name', 'username', 'email']`\n\nIf you prefer that a particular TypeDef should always require an explicitly set `id`, then\nset `alternateIdFields` to `[]` or `null` on your type definition object.\n\n## Caveats\n\n### typeName and id Field\nThe name of the type, given by `typeName`, and whatever value the `id` field holds will become part of the\nunderlying filename to the JSON representation of the object.\n\nThis means that the `typeName` and the `id` field must be coerced into a filesystem-friendly names.\n\nmobiletto-orm coerces these values using: `encodeURIComponent(id).replaceAll('%', '~')`\n\nThis invocation ensures that repeated invocations yield the same result.\n\n**Because of a subtle collision risk if `typeName` or `id` value contains a literal `%` or `~` character,\nthese characters are not allowed in `typeName` or `id` values**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcobbzilla%2Fmobiletto-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcobbzilla%2Fmobiletto-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcobbzilla%2Fmobiletto-orm/lists"}