{"id":20277516,"url":"https://github.com/udamir/patchpack","last_synced_at":"2025-04-11T05:50:48.562Z","repository":{"id":57320619,"uuid":"309821592","full_name":"udamir/patchpack","owner":"udamir","description":"Json patch serializer","archived":false,"fork":false,"pushed_at":"2020-12-12T13:10:11.000Z","size":576,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-29T09:35:07.748Z","etag":null,"topics":["delta-compression","encoder-decoder","javascript","json-schema","jsonpatch","schema","serializer","typescript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/udamir.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-03T22:25:09.000Z","updated_at":"2024-08-03T09:17:38.000Z","dependencies_parsed_at":"2022-08-26T01:10:56.384Z","dependency_job_id":null,"html_url":"https://github.com/udamir/patchpack","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udamir%2Fpatchpack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udamir%2Fpatchpack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udamir%2Fpatchpack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udamir%2Fpatchpack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/udamir","download_url":"https://codeload.github.com/udamir/patchpack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247974610,"owners_count":21026742,"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":["delta-compression","encoder-decoder","javascript","json-schema","jsonpatch","schema","serializer","typescript"],"created_at":"2024-11-14T13:18:46.181Z","updated_at":"2025-04-11T05:50:48.540Z","avatar_url":"https://github.com/udamir.png","language":"JavaScript","readme":"# PatchPack  \n\u003cimg alt=\"npm\" src=\"https://img.shields.io/npm/v/patchpack\"\u003e \u003cimg alt=\"npm\" src=\"https://img.shields.io/npm/dm/patchpack?label=npm\"\u003e \u003cimg alt=\"CircleCI\" src=\"https://img.shields.io/circleci/build/github/udamir/patchpack\"\u003e \u003cimg alt=\"npm type definitions\" src=\"https://img.shields.io/npm/types/patchpack\"\u003e \u003cimg alt=\"GitHub\" src=\"https://img.shields.io/npm/l/patchpack\"\u003e\n\n\nA binary JsonPatch serializer based on schema. Efficiently encode state object and JsonPatch in to compact byte buffers and then decode them back in to objects on the receiver. Integrates very well with observable state and WebSockets.\n\nOriginally it was part of [mosx](https://github.com/udamir/mosx) State Management engine, but then it moved to separate package.\n\n## Motivation\n\nI was working on an [magx](https://github.com/udamir/magx) game server framework that used WebSockets to syncronize state between server and clients. Syncronization principle is simple: first server sends full state to clients then on every change sends patches in JsonPatch format. I have found the problem that sending a lot of patches without serialization is a waste of bandwidth.\n\nAs state's schema is known on server side it can be sent to the clients, then state and each patch can be encoded based on that schema on server side and decoded back on client side. State schema is not static that means it must be also syncronized with clients. This sophisticated approach can significantly reduce patch size and bandwidth usage.\n\n## Concept\n\n![](https://github.com/udamir/patchpack/blob/master/.docs/patchpack.png?raw=true)\n\n## Installation\n\n```\nnpm install --save patchpack\n```\n\n## Browser\nA browser version of patchpack is also available:\n```\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/patchpack@0.4.2/browser/patchpack.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/patchpack@0.4.2/browser/patchpack.js\"\u003e\u003c/script\u003e\n```\n\n## Example\n\n### Server side\n```ts\nimport { PatchPack } from \"patchpack\"\n\nclass Client {\n  constructor(public name: string, public info = \"\") {}\n}\n\n// initial state\nconst state: any = {\n  clients: {\n    \"1\": new Client(\"Foo\"),\n    \"2\": new Client(\"Baz\", \"FooBaz\"),\n  },\n  objects: [\n    { id: 1, name: \"Foo\" },\n    { id: 2, name: \"Foo\", foo: \"Baz\" },\n  ],\n  foo: { baz: false }\n}\n\n// create patchpack instance and define schema types\nconst ppServer = new PatchPack({\n  State: [\"clients\", \"objects\", \"foo\"],\n  Client, // it is recommended to use class in schema (better performance)\n  Object: [\"id\", \"name\", \"foo\"],\n  Foo: [\"baz\"]\n})\n\n// encoded state can include types definition\nconst encodedStateWithTypes = ppServer.encodeState(state)\n\nconst encodedState = ppServer.encodeState(state, false)\n\n// add item\n\nconst client = new Client(\"FooBaz\", \"test\" )\nstate.clients[\"3\"] = client\n\nconst patch1 = { op: \"add\", path: \"/clients/3\", value: client }\nconst encodedPatch1 = ppServer.encodePatch(patch1)\n\n// update value\n\nstate.foo.baz = true\n\nconst patch2 = { op: \"replace\", path: \"/foo/baz\", value: true }\nconst encodedPatch2 = ppServer.encodePatch(patch2)\n\n```\n### Benchmark\n\nBenchmark for encoded object size (byte):\n|        | PatchPack | [MessagePack](https://msgpack.org/) | JSON.stringify |\n| ------ | --------- | ----------------------------------- | -------------- |\n| state  | 60        | 107  (+78%)                         | 165   (+175%)  |\n| patch1 | 22        | 53  (+140%)                         | 72    (+227%)  |\n| patch2 | 5         | 33  (+560%)                         | 47    (+840%)  |\n\nSend `encodedStateWithTypes`, `encodedPatch1` and `encodedPatch2` to Client and decode them:\n\n### Client side\n```ts\nconst ppClient = new PatchPack()\n\nconst decodedState = ppClient.decodeState(encodedStateWithTypes)\nconsole.log(decodedState)\n\n// {\n//   clients: {\n//     '1': { name: 'Foo', info: '' },\n//     '2': { name: 'Baz', info: 'FooBaz' }\n//   },\n//   objects: [\n//      { id: 1, name: 'Foo' },\n//      { id: 2, name: 'Foo', foo: 'Baz' } ],\n//   foo: { baz: false }\n// }\n\nconst decodedPatch1 = ppClient.decodePatch(encodedPatch1)\nconsole.log(decodedPatch1)\n\n// {\n//   op: 'add',\n//   path: '/clients/3',\n//   value: { name: 'FooBaz', info: 'test' }\n// }\n\nconst decodedPatch2 = ppClient.decodePatch(encodedPatch2)\nconsole.log(decodedPatch2)\n\n// { op: 'replace', path: '/foo/baz', value: true }\n```\n\n# Documentation\n\n## Patchpack\n\n### constructor \nReturn instance of PatchPack with defined schema types\n```ts\nconstructor (types?: { [type: string]: string[] | Type\u003cany\u003e })\n```\n\nTypes can be defined in 2 ways:\n- array of properties\n- Class name\n\nExample:\n```ts\nclass User {\n  constructor (public name: string) {}\n}\n\nclass Item {\n  constructor (public id: number) {}\n}\n\nconst state = {\n  users: [ new User(\"John\"), new User(\"Santa\") ]\n  item: new Item(123) \n}\n\nconst pp = new PatchPack({\n  State: [\"users\", \"item\"],\n  User,\n  Item,\n})\n```\n### encodeState\nEncode state and return in binary format\n```ts\nencodeState(state: any, includeTypes = true, updateSchema = true): Buffer\n```\n\nIf parameter `includeTypes = false` used, decoder instance of PatchPack must be created with the same types.\n\nFirst time state encoding must be with `updateSchema = true`. If you need to encode state with the same schema second time `updateSchema` can be set as `false`.\n\n### decodeState\nDecode state from binary format to object\n```ts\ndecodeState(buffer: Buffer): any\n```\n\n!Important. State can be decoded only once.\n\nExample:\n```ts\nconst pp = new PatchPack()\nconst state = pp.decodeState(encodedStatewWithTypes)\n```\n\n### encodePatch\nEncode JsonPatch and return in binary format\n```ts\nencodePatch(patch: IReversibleJsonPatch, updateSchema = true): Buffer\n```\n\nFirst time patch encoding must be with `updateSchema = true`. If you need to encode the same patch second time `updateSchema` must be set as `false`.\n\nThe following JsonPatch operation are supported:\n- add\n- replace\n- remove\n\nReversibleJsonPatch with `oldValue` is supported\n\nExample:\n```ts\n// JsonPatch\nconst p1 = pp.encodePatch({\n  op: \"replace\",\n  path: \"/a/b/c\",\n  value: \"100\",\n})\n\n// ReversibleJsonPatch\nconst p1 = pp.encodePatch({\n  op: \"replace\",\n  path: \"/a/b/c\",\n  value: \"100\",\n  oldValue: \"99\",\n})\n```\n\n### decodePatch\nDecode patch from binary format to JsonPatch (or ReversibleJsonPatch).\n```ts\ndecodePatch (buffer: Buffer): IReversibleJsonPatch\n```\n\n!Important. Patch can be decoded only once.\n\n### PatchPack.encode\nEncode object to binary with last MessagePack specification.\n```ts\nstatic encode(value: any): Buffer\n```\n\n### PatchPack.decode\nDecode binary to object with last MessagePack specification.\n```ts\nstatic decode(buffer: Buffer): any\n```\n\n## Specification \n\nwill be soon...\n\n## License\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudamir%2Fpatchpack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fudamir%2Fpatchpack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudamir%2Fpatchpack/lists"}