{"id":25837235,"url":"https://github.com/devtin/duck-storage","last_synced_at":"2025-10-15T22:42:51.925Z","repository":{"id":40282084,"uuid":"284732527","full_name":"devtin/duck-storage","owner":"devtin","description":null,"archived":false,"fork":false,"pushed_at":"2023-01-07T04:52:52.000Z","size":3035,"stargazers_count":0,"open_issues_count":9,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-12T10:57:12.205Z","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devtin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-03T15:10:36.000Z","updated_at":"2021-11-06T14:54:51.000Z","dependencies_parsed_at":"2023-02-06T12:30:59.548Z","dependency_job_id":null,"html_url":"https://github.com/devtin/duck-storage","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtin%2Fduck-storage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtin%2Fduck-storage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtin%2Fduck-storage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtin%2Fduck-storage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devtin","download_url":"https://codeload.github.com/devtin/duck-storage/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241309091,"owners_count":19941726,"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":"2025-03-01T02:48:00.694Z","updated_at":"2025-10-15T22:42:46.873Z","avatar_url":"https://github.com/devtin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp\u003e\u003cimg width=\"480\" src=\"https://repository-images.githubusercontent.com/284732527/2b28a880-d57b-11ea-9b43-283e2cdd605c\"/\u003e\u003c/p\u003e\n\n\u003cdiv\u003e\u003ch1\u003educk-storage\u003c/h1\u003e\u003c/div\u003e\n\n\u003cp\u003e\n    \u003ca href=\"https://www.npmjs.com/package/duck-storage\" target=\"_blank\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/duck-storage.svg\" alt=\"Version\"\u003e\u003c/a\u003e\n\u003ca href=\"http://opensource.org/licenses\" target=\"_blank\"\u003e\u003cimg src=\"http://img.shields.io/badge/License-MIT-brightgreen.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n    storage for schematized data objects\n\u003c/p\u003e\n\n## Installation\n\n```sh\n$ npm i duck-storage --save\n# or\n$ yarn add duck-storage\n```\n\n## Features\n\n- [stores schematized ducks](#stores-schematized-ducks)\n- [finds a duck](#finds-a-duck)\n- [updates information of a duck](#updates-information-of-a-duck)\n- [updates information of multiple ducks at a time](#updates-information-of-multiple-ducks-at-a-time)\n- [removes ducks from the rack](#removes-ducks-from-the-rack)\n- [lists ducks in the rack](#lists-ducks-in-the-rack)\n- [loads references of ducks in other racks](#loads-references-of-ducks-in-other-racks)\n- [defines duck rack methods](#defines-duck-rack-methods)\n- [validates properties in realtime](#validates-properties-in-realtime)\n- [registers duck racks from given dir](#registers-duck-racks-from-given-dir)\n- [checks all events emitted by a duck](#checks-all-events-emitted-by-a-duck)\n\n\n\u003ca name=\"stores-schematized-ducks\"\u003e\u003c/a\u003e\n\n## stores schematized ducks\n\n\n```js\nconst createEvent = forEvent(Rack, 'create')\nconst entry = await Rack.create({\n  firstName: 'Martin',\n  lastName: 'Rafael'\n})\n\nt.truthy(entry)\nt.true(Object.prototype.hasOwnProperty.call(entry, '_id'))\nt.true(Object.prototype.hasOwnProperty.call(entry, '_v'))\nt.is(entry.fullName, 'Martin Rafael')\n\nconst createEventPayload = (await createEvent)[0]\n\nt.truthy(createEvent)\nt.deepEqual(createEventPayload, entry)\n\nentry.fullName = 'Pedro Perez'\n\nt.is(entry.firstName, 'Pedro')\nt.is(entry.lastName, 'Perez')\n```\n\n\u003ca name=\"finds-a-duck\"\u003e\u003c/a\u003e\n\n## finds a duck\n\n\n```js\nconst entry = await Rack.create({\n  firstName: 'Martin'\n})\n\nconst found = await Rack.read(entry._id)\nt.deepEqual(entry.toObject(), found.toObject())\nt.not(entry, found)\n```\n\n\u003ca name=\"updates-information-of-a-duck\"\u003e\u003c/a\u003e\n\n## updates information of a duck\n\n\n```js\nconst updateEvent = forEvent(Rack, 'update')\nconst created = (await Rack.create({\n  firstName: 'Martin',\n  lastName: 'Gonzalez'\n})).consolidate()\n\nconst toUpdate = {\n  firstName: 'Olivia'\n}\nconst updated = await Rack.update({ lastName: { $eq: 'Gonzalez' } }, toUpdate)\n\nconst updatePayload = (await updateEvent)[0]\n\nt.truthy(updatePayload)\nt.deepEqual(updatePayload.oldEntry, created)\nt.deepEqual(updatePayload.newEntry, toUpdate)\nt.deepEqual(updatePayload.entry, updated[0])\n\nt.true(Array.isArray(updated))\nt.is(updated.length, 1)\nt.is(updated[0].firstName, 'Olivia')\nt.is(updated[0].lastName, 'Gonzalez')\nt.is(updated[0]._v, 2)\n\nconst updatedEntry = await Rack.read(created._id)\nt.is(updatedEntry.firstName, 'Olivia')\n```\n\n\u003ca name=\"updates-information-of-multiple-ducks-at-a-time\"\u003e\u003c/a\u003e\n\n## updates information of multiple ducks at a time\n\n\n```js\nconst updateEvent = forEvent(Rack, 'update', { trap: 2 })\n\nconst martin = (await Rack.create({\n  firstName: 'Martin',\n  lastName: 'Gonzalez'\n})).toObject()\n\nconst ana = (await Rack.create({\n  firstName: 'Ana',\n  lastName: 'Sosa'\n})).toObject()\n\nconst toUpdate = {\n  firstName: 'Olivia'\n}\n\nconst updated = await Rack.update({}, toUpdate)\n\nt.true(Array.isArray(updated))\n\nt.is(updated.length, 2)\nt.is(updated[0]._id, martin._id)\nt.is(updated[0].firstName, 'Olivia')\nt.is(updated[0].lastName, 'Gonzalez')\nt.is(updated[0]._v, 2)\n\nt.is(updated[1]._id, ana._id)\nt.is(updated[0].firstName, 'Olivia')\nt.is(updated[0].lastName, 'Gonzalez')\nt.is(updated[0]._v, 2)\n\nconst updatePayload = await updateEvent\n\nt.truthy(updatePayload)\n\nt.deepEqual(updatePayload[0].oldEntry, martin)\nt.deepEqual(updatePayload[0].newEntry, toUpdate)\nt.deepEqual(updatePayload[0].entry, updated[0])\n\nt.deepEqual(updatePayload[1].oldEntry, ana)\nt.deepEqual(updatePayload[1].newEntry, toUpdate)\nt.deepEqual(updatePayload[1].entry, updated[1])\n```\n\n\u003ca name=\"removes-ducks-from-the-rack\"\u003e\u003c/a\u003e\n\n## removes ducks from the rack\n\n\n```js\nconst deleteEvent = forEvent(Rack, 'delete')\nconst entry = await Rack.create({\n  firstName: 'Martin',\n  lastName: 'Gonzalez'\n})\nconst deleted = await Rack.delete({\n  _id: {\n    $eq: entry._id\n  }\n})\nconst deletedPayload = (await deleteEvent)[0]\n\nt.deepEqual(deletedPayload, deleted[0])\n\nt.true(Array.isArray(deleted))\nt.is(deleted.length, 1)\nt.is(deleted[0]._id, entry._id)\nt.is(deleted[0].firstName, 'Martin')\nt.is(deleted[0].lastName, 'Gonzalez')\n\nconst notFound = await Rack.read({ _id: { $eq: entry._id } })\nt.is(notFound, undefined)\nt.is(Object.keys(Rack.storeKey).length, 0)\n```\n\n\u003ca name=\"lists-ducks-in-the-rack\"\u003e\u003c/a\u003e\n\n## lists ducks in the rack\n\n\n```js\nconst entry1 = (await Rack.create({\n  firstName: 'Martin',\n  lastName: 'Gonzalez'\n})).toObject()\n\nconst entry2 = (await Rack.create({\n  firstName: 'Olivia',\n  lastName: 'Gonzalez'\n})).toObject()\n\nconst res = await Rack.list()\nt.true(Array.isArray(res))\nt.is(res.length, 2)\nt.deepEqual(res.map(entry =\u003e entry.consolidate()), [entry1, entry2])\n```\n\n\u003ca name=\"loads-references-of-ducks-in-other-racks\"\u003e\u003c/a\u003e\n\n## loads references of ducks in other racks\n\n\n```js\nconst orderSchema = new Schema({\n  customer: {\n    type: 'ObjectId',\n    duckRack: 'customer'\n  },\n  amount: {\n    type: Number,\n    integer: true\n  },\n  createdAt: {\n    type: Date,\n    default: Date.now\n  }\n})\nconst customerSchema = new Schema({\n  firstName: String,\n  lastName: String,\n  email: String\n})\n\nconst OrderModel = new Duck({ schema: orderSchema })\nconst CustomerModel = new Duck({ schema: customerSchema })\n\nconst OrderBucket = new DuckRack('order', {\n  duckModel: OrderModel\n})\n\nOrderBucket.hook('before', 'create', function (entry) {\n  return entry\n})\n\nasync function loadReferences (entry) {\n  const entriesToLoad = this.duckModel\n    .schema\n    .paths\n    .filter((path) =\u003e {\n      return this.duckModel.schema.schemaAtPath(path).settings.duckRack \u0026\u0026 Utils.find(entry, path)\n    })\n    .map(path =\u003e {\n      const Rack = DuckStorage.getRackByName(this.duckModel.schema.schemaAtPath(path).settings.duckRack)\n      const _idPayload = Utils.find(entry, path)\n      const _id = Rack.duckModel.schema.isValid(_idPayload) ? _idPayload._id : _idPayload\n      return { duckRack: this.duckModel.schema.schemaAtPath(path).settings.duckRack, _id, path }\n    })\n\n  for (const entryToLoad of entriesToLoad) {\n    set(entry, entryToLoad.path, await DuckStorage.getRackByName(entryToLoad.duckRack).findOneById(entryToLoad._id))\n  }\n\n  return entry\n}\n\nOrderBucket.hook('after', 'read', loadReferences)\nOrderBucket.hook('after', 'create', loadReferences)\n\nconst CustomerBucket = new DuckRack('customer', {\n  duckModel: CustomerModel\n})\n\nconst customer = await CustomerBucket.create({\n  firstName: 'Martin',\n  lastName: 'Rafael',\n  email: 'tin@devtin.io'\n})\n\nt.truthy(customer._id)\n\n// console.log({ customer })\n// console.log(CustomerModel.schema.parse(customer))\n\ntry {\n  await OrderModel.schema.parse({\n    customer,\n    amount: 100\n  })\n} catch (err) {\n  console.log('\\n\\nERROR\\n\\n', err)\n  throw err\n}\n\nconst order = await OrderBucket.create({\n  customer,\n  amount: 100\n})\n\nt.deepEqual(order.customer, customer.consolidate())\n\nconst readOrder = await OrderBucket.read(order._id)\nt.deepEqual(readOrder.customer, customer.consolidate())\n```\n\n\u003ca name=\"defines-duck-rack-methods\"\u003e\u003c/a\u003e\n\n## defines duck rack methods\n\n\n```js\nconst userSchema = new Schema({\n  name: String,\n  level: String\n})\nconst userDuckModel = new Duck({ schema: userSchema })\nconst UserRack = new DuckRack('some-user', {\n  duckModel: userDuckModel,\n  methods: {\n    changeLevel: {\n      input: {\n        userId: 'ObjectId',\n        newLevel: String\n      },\n      async handler ({ userId, newLevel }) {\n        const user = await this.findOneById(userId)\n        user.level = newLevel\n        return this.update(userId, user)\n      }\n    },\n    getAdmins () {\n      return this.list({\n        level: {\n          $eq: 'admin'\n        }\n      })\n    },\n    getUsers () {\n      return this.list({\n        level: {\n          $eq: 'user'\n        }\n      })\n    }\n  }\n})\n\nawait UserRack.create({\n  name: 'Martin',\n  level: 'admin'\n})\n\nawait UserRack.create({\n  name: 'Rafael',\n  level: 'admin'\n})\n\nawait UserRack.create({\n  name: 'Pedro',\n  level: 'user'\n})\n\nconst admins = await UserRack.getAdmins()\nt.truthy(admins)\nt.is(admins.length, 2)\n\nconst users = await UserRack.getUsers()\nt.truthy(users)\nt.is(users.length, 1)\n\nUserRack.changeLevel({\n  userId: admins[0]._id,\n  newLevel: 'user'\n})\n```\n\n\u003ca name=\"validates-properties-in-realtime\"\u003e\u003c/a\u003e\n\n## validates properties in realtime\n\n\n```js\nconst duckModel = Duck.create({ schema: AdvancedSchema })\nlet err\n\nerr = t.throws(() =\u003e { return duckModel.dont.do.this.to.me })\nt.is(err.message, 'Unknown property dont')\n\nerr = t.throws(() =\u003e { duckModel.firstName = 123 })\nt.is(err.message, 'Invalid string')\n\nerr = t.throws(() =\u003e { duckModel.address.zip = '33q29' })\nt.is(err.message, 'Invalid number')\n\nerr = t.throws(() =\u003e { duckModel.email = 'martin' })\nt.is(err.message, 'Invalid e-mail address')\n\nt.notThrows(() =\u003e { duckModel.firstName = 'Martin' })\nt.notThrows(() =\u003e { duckModel.lastName = 'Rafael' })\nt.notThrows(() =\u003e { duckModel.email = 'tin@devtin.io' })\nt.notThrows(() =\u003e { duckModel.address.line1 = 'Brickell' })\nt.notThrows(() =\u003e { duckModel.address.zip = 305 })\n\nt.is(duckModel.firstName, 'Martin')\nt.is(duckModel.lastName, 'Rafael')\nt.is(duckModel.email, 'tin@devtin.io')\nt.is(duckModel.address.line1, 'Brickell')\nt.is(duckModel.address.zip, 305)\n\nerr = t.throws(() =\u003e duckModel.getEmailDomain())\nt.is(err.message, 'consolidate the model prior invoking method getEmailDomain')\n\nduckModel.consolidate()\n\nt.truthy(duckModel._id)\nt.is(duckModel.getEmailDomain(), 'devtin.io')\n\nt.deepEqual(duckModel.toObject(), {\n  _id: duckModel._id,\n  _v: 1,\n  firstName: 'Martin',\n  lastName: 'Rafael',\n  email: 'tin@devtin.io',\n  address: {\n    line1: 'Brickell',\n    zip: 305\n  }\n})\n```\n\n\u003ca name=\"registers-duck-racks-from-given-dir\"\u003e\u003c/a\u003e\n\n## registers duck racks from given dir\n\n\n\n\n\u003ca name=\"checks-all-events-emitted-by-a-duck\"\u003e\u003c/a\u003e\n\n## checks all events emitted by a duck\n\n\n```js\nconst User = new Schema({\n  name: String,\n  emails: {\n    type: Array,\n    default () {\n      return []\n    }\n  }\n}, {\n  methods: {\n    addEmail: {\n      events: {\n        emailAdded: String\n      },\n      input: String,\n      handler (email) {\n        this.$field.emails.push(email)\n        this.$emit('emailAdded', email)\n      }\n    }\n  }\n})\n\nconst userPayload = User.parse({\n  name: 'Martin'\n})\n\nconst eventsFired = schemaDuckMonitor(User, userPayload)\n\nt.is(eventsFired.length, 0)\nuserPayload.addEmail('martin')\nt.is(eventsFired.length, 1)\n```\n\n\n\u003cbr\u003e\u003ca name=\"DuckRack\"\u003e\u003c/a\u003e\n\n### DuckRack\n**Description:**\n\nStores only ducks specified by the `duckModel`\n\n\n\u003cbr\u003e\u003ca name=\"DuckRack+read\"\u003e\u003c/a\u003e\n\n#### duckRack.read(_id) ⇒ \u003ccode\u003ePromise.\u0026lt;\\*\u0026gt;\u003c/code\u003e\n\n| Param |\n| --- |\n| _id | \n\n**Description:**\n\nSugar for `find(entityName, { _id: { $eq: _id } })`\n\n\n\u003cbr\u003e\u003ca name=\"Duck\"\u003e\u003c/a\u003e\n\n### Duck\n**Description:**\n\nA duck model\n\n\n* [Duck](#Duck)\n    * _instance_\n        * [.getModel([defaultValues], [state])](#Duck+getModel) ⇒ \u003ccode\u003eObject\u003c/code\u003e\n    * _static_\n        * [.create(duckPayload, [...modelPayload])](#Duck.create) ⇒ \u003ccode\u003eObject\u003c/code\u003e\n\n\n\u003cbr\u003e\u003ca name=\"Duck+getModel\"\u003e\u003c/a\u003e\n\n#### duck.getModel([defaultValues], [state]) ⇒ \u003ccode\u003eObject\u003c/code\u003e\n\n| Param | Type |\n| --- | --- |\n| [defaultValues] | \u003ccode\u003eObject\u003c/code\u003e | \n| [state] | \u003ccode\u003eObject\u003c/code\u003e | \n\n**Returns**: \u003ccode\u003eObject\u003c/code\u003e - the duck proxy model  \n**Description:**\n\nPrepares a duck proxy model to be used with the defined schema\n\n\n\u003cbr\u003e\u003ca name=\"Duck.create\"\u003e\u003c/a\u003e\n\n#### Duck.create(duckPayload, [...modelPayload]) ⇒ \u003ccode\u003eObject\u003c/code\u003e\n\n| Param | Type | Description |\n| --- | --- | --- |\n| duckPayload | \u003ccode\u003eObject\u003c/code\u003e | the duck constructor payload |\n| [...modelPayload] |  | the model payload |\n\n**Returns**: \u003ccode\u003eObject\u003c/code\u003e - the duck proxy model  \n**Description:**\n\nSugar for calling `new Duck({...}).getModel()`\n\n\n\u003cbr\u003e\u003ca name=\"schemaDuckMonitor\"\u003e\u003c/a\u003e\n\n### schemaDuckMonitor ⇒ \u003ccode\u003eArray\u003c/code\u003e\n\n| Param | Type |\n| --- | --- |\n| schema | \u003ccode\u003eObject\u003c/code\u003e | \n| payload | \u003ccode\u003eObject\u003c/code\u003e | \n\n**Returns**: \u003ccode\u003eArray\u003c/code\u003e - an array with all of the events fired  \n**Description:**\n\nLogs all events emitted by a duck\n\n\n* * *\n\n### License\n\n[MIT](https://opensource.org/licenses/MIT)\n\n\u0026copy; 2020-present Martin Rafael Gonzalez \u003ctin@devtin.io\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevtin%2Fduck-storage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevtin%2Fduck-storage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevtin%2Fduck-storage/lists"}