{"id":13676607,"url":"https://github.com/0dataapp/0datawrap","last_synced_at":"2025-04-29T07:32:57.995Z","repository":{"id":60507409,"uuid":"339411911","full_name":"0dataapp/0datawrap","owner":"0dataapp","description":"Unified JavaScript API for Fission + remoteStorage.","archived":false,"fork":false,"pushed_at":"2024-06-16T21:57:36.000Z","size":146,"stargazers_count":20,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-11T18:43:09.491Z","etag":null,"topics":["0data","fission","ownyourdata","remotestorage","solid","unhosted"],"latest_commit_sha":null,"homepage":"https://0data.app","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0dataapp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"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":{"custom":"https://rosano.ca/fund"}},"created_at":"2021-02-16T13:44:22.000Z","updated_at":"2024-06-16T21:57:39.000Z","dependencies_parsed_at":"2024-11-11T18:44:57.523Z","dependency_job_id":null,"html_url":"https://github.com/0dataapp/0datawrap","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/0dataapp%2F0datawrap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0dataapp%2F0datawrap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0dataapp%2F0datawrap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0dataapp%2F0datawrap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0dataapp","download_url":"https://codeload.github.com/0dataapp/0datawrap/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251456053,"owners_count":21592285,"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":["0data","fission","ownyourdata","remotestorage","solid","unhosted"],"created_at":"2024-08-02T13:00:30.257Z","updated_at":"2025-04-29T07:32:52.986Z","avatar_url":"https://github.com/0dataapp.png","language":"JavaScript","readme":"\u003ca href=\"https://0data.app\"\u003e\u003cimg alt=\"Project logo\" src=\"https://static.rosano.ca/0data/identity-mono.svg\" width=\"64\" /\u003e\u003c/a\u003e\n\n# [Zero Data](https://0data.app) Wrap\n\n_Unified JavaScript API for [Fission](https://guide.fission.codes/developers/webnative) + [remoteStorage](https://github.com/remotestorage/remotestorage.js)._\n\n# Comparisons\n\n- [remotestorage.js](https://community.remotestorage.io/t/659)\n- [Webnative SDK](https://talk.fission.codes/t/1587)\n\n# Long names…\n\nYes, it's long and verbose and how I do things generally, but ideally your final code looks like this:\n\n```javascript\n// write `{\"id\":\"batmobile\",\"clean\":true,\"washed\":\"…\"}` to /bravo/cars/batmobile.json\nawait api.alfa.cars.wash({\n  id: 'batmobile',\n});\n```\n\n# API Guide\n\n## Setup\n\n```javascript\nconst api = await zerodatawrap.ZDRWrap({\n\n  // include then pass directly\n  ZDRParamLibrary: RemoteStorage, // or webnative\n\n  // read/write permissions\n  ZDRParamScopes: [{\n\n    // code-level identifier\n    ZDRScopeKey: 'alfa',\n\n    // top-level storage directory\n    ZDRScopeDirectory: 'bravo',\n\n  }],\n  \n});\n\n// write `{\"foo\":\"bar\"}` to /bravo/charlie.json\nawait api.alfa.ZDRStorageWriteObject('charlie.json', {\n  foo: 'bar',\n});\n```\n\n### ZDRWrap\n\n| param | type | notes |\n|-------|---------|-------|\n| ZDRParamLibrary \u003cbr\u003e **Required** | pass `RemoteStorage` or `webnative` or an object conforming to `ZDRClient` | |\n| ZDRParamScopes \u003cbr\u003e **Required** | array of `ZDRScope` objects | |\n| ZDRParamDispatchError | function | called on network or sync errors |\n| ZDRParamDispatchConnected | function | called if linked to a cloud account |\n| ZDRParamDispatchOnline | function | called when network is online |\n| ZDRParamDispatchOffline | function | called when network is offline |\n\n### ZDRScope\n\n| param | type | notes |\n|-------|---------|-------|\n| ZDRScopeKey \u003cbr\u003e **Required** | string, non-empty, trimmed | convenience accessor for code only |\n| ZDRScopeDirectory \u003cbr\u003e **Required** | string, non-empty, trimmed, no slashes | top-level directory for claiming read/write access |\n| ZDRScopeCreatorDirectory | string, non-empty, trimmed | if Fission, sets `permissions.app` instead of `permissions.fs` |\n| ZDRScopeSchemas | array of `ZDRSchema` objects | defines model helpers |\n| ZDRScopeIsPublic | boolean | use public directory on read/write |\n\n## Storage\n\n### ZDRStorageWriteObject(path, object)\n\nCall `JSON.stringify`; write to storage.\n\nReturns input object.\n\n### ZDRStorageWriteFile(path, data, mimetype)\n\n*`mimetype` used by remoteStorage.*\n\nWrite to storage.\n\nReturns input.\n\n### ZDRStorageReadObject(path)\n\nRead from storage; call `JSON.parse`.\n\nReturns object.\n\n### ZDRStorageReadFile(path)\n\nRead from storage.\n\nReturns data.\n\n### ZDRStorageListingObjects(path)\n\nRead from storage in path directory (non-recursive); group by path.\n\nReturns key/value object.\n\n### ZDRStoragePaths(path)\n\nFetch paths (non-recursive).\n\nReturns array of paths.\n\n### ZDRStoragePathsRecursive(path)\n\nFetch paths recursively.\n\nReturns flat array of paths.\n\n### ZDRStorageDeleteFile(path)\n\nDelete from storage.\n\nReturns null.\n\n### ZDRStorageDeleteFolderRecursive(path)\n\nDelete folder and subcontents recursively from storage.\n\nReturns input.\n\n## Cloud\n\n### ZDRCloudConnect(address)\n\nStart authorization process corresponding to `ZDRParamLibrary`.\n\nReturns undefined.\n\n### ZDRCloudReconnect()\n\nRetry authorization process in case of expiry or denied access.\n\nReturns undefined.\n\n### ZDRCloudDisconnect()\n\nLog out.\n\nReturns undefined.\n\n## Model (optional)\n\n```javascript\nconst api = zerodatawrap.ZDRWrap({\n\n  // …\n\n  ZDRParamScopes: [{\n\n    // …\n\n    // map schemas to paths\n    ZDRScopeSchemas: [{\n\n      // code-level identifier\n      ZDRSchemaKey: 'cars',\n\n      // path for a given object\n      ZDRSchemaPath (object) {\n        return `cars/${ object.id }.json`;\n      },\n\n      // object information for a given path\n      ZDRSchemaStub (path) {\n        return {\n          id: path.split('/').pop().split('.json').shift(),\n        };\n      },\n\n    }],\n\n    // …\n\n  }],\n  \n  // …\n\n});\n\n// write `{\"id\":\"batmobile\"}` to /bravo/cars/batmobile.json\nawait api.alfa.cars.ZDRModelWriteObject({\n  id: 'batmobile',\n});\n```\n\n### ZDRSchema\n\n| param | type | notes |\n|-------|---------|-------|\n| ZDRSchemaKey \u003cbr\u003e **Required** | string, non-empty, trimmed | convenience accessor for code only |\n| ZDRSchemaPath \u003cbr\u003e **Required** | function | constructs the path for a given object |\n| ZDRSchemaStub \u003cbr\u003e **Required** | function | constructs object information for a given path, used for filtering paths in recursion and routing sync events |\n| ZDRSchemaMethods | object | defines methods to be accessed from the api interface (bound to `this`) |\n| ZDRSchemaDispatchValidate | function | called before `ZDRModelWriteObject` |\n| ZDRSchemaDispatchSyncCreate | function | called on remote create *remoteStorage only* |\n| ZDRSchemaDispatchSyncUpdate | function | called on remote update *remoteStorage only* |\n| ZDRSchemaDispatchSyncDelete | function | called on remote delete *remoteStorage only* |\n| ZDRSchemaDispatchSyncConflict | function | called on remote conflict *remoteStorage only* \u003cbr\u003e **Note: this passes the remoteStorage change event directly** |\n\n### Specify a validation function\n\n```javascript\nconst api = zerodatawrap.ZDRWrap({\n\n  // …\n\n  ZDRParamScopes: [{\n\n    // …\n\n    ZDRScopeSchemas: [{\n\n      // …\n\n      // truthy values cause a promise rejection\n      ZDRSchemaDispatchValidate (object) {\n        if (typeof object.id !== 'string') {\n          return {\n            not: 'so fast',\n          };\n        }\n      },\n\n      // …\n\n    }],\n\n    // …\n\n  }],\n\n  // …\n\n});\n\n// rejects\ntry {\n  await api.alfa.cars.ZDRModelWriteObject({\n    id: 123,\n  });\n} catch (truthy) {\n  console.log(truthy.not); // so fast\n}\n```\n\n### Specify custom methods\n\n```javascript\nconst api = zerodatawrap.ZDRWrap({\n\n  // …\n\n  ZDRParamScopes: [{\n\n    // …\n\n    ZDRScopeSchemas: [{\n\n      // …\n\n      // logic to be done on save\n      ZDRSchemaMethods: {\n\n        clean (car) {\n          return this.alfa.cars.ZDRModelWriteObject(Object.assign(car, {\n            clean: true,\n            washed: new Date(),\n          }));\n        },\n\n      },\n\n      // …\n\n    }],\n\n    // …\n\n  }],\n\n  // …\n\n});\n\n// write `{\"id\":\"batmobile\",\"clean\":true,\"washed\":\"…\"}` to /bravo/cars/batmobile.json\nawait api.alfa.cars.clean({\n  id: 'batmobile',\n});\n```\n\n### Receive object sync notifications\n\n```javascript\nconst api = zerodatawrap.ZDRWrap({\n\n  // …\n\n  ZDRParamScopes: [{\n\n    // …\n\n    ZDRScopeSchemas: [{\n\n      // …\n\n      // update the interface for remote changes\n      ZDRSchemaDispatchSyncCreate (object) {\n        console.log('create', object);\n      },\n      ZDRSchemaDispatchSyncUpdate (object) {\n        console.log('update', object);\n      },\n      ZDRSchemaDispatchSyncDelete (object) {\n        console.log('delete', object);\n      },\n\n      // handle conflict\n      ZDRSchemaDispatchSyncConflict (event) {\n        console.log('conflict', event);\n      },\n\n      // …\n\n    }],\n\n    // …\n\n  }],\n\n  // …\n\n});\n```\n\n### ZDRModelPath(object)\n\nReturns object path via `ZDRSchemaPath`.\n\n### ZDRModelWriteObject(object)\n\nValidate with `ZDRSchemaDispatchValidate`, reject if truthy; write object to path from `ZDRModelPath`.\n\nReturns input.\n\n### ZDRModelListObjects()\n\nRead all objects recursively (where paths conform to logic in `ZDRSchema`).\n\nReturns array of objects.\n\n### ZDRModelDeleteObject(object)\n\nDelete object from storage.\n\nReturns input.\n\n## Multiple protocols\n\nWhen supporting multiple protocols, the library can help track which one was selected.\n\n```javascript\nconst api = await zerodatawrap.ZDRWrap({\n\n  ZDRParamLibrary: (function() {\n    // get the selected protocol, default to `ZDRProtocolCustom`\n    const selected = zerodatawrap.ZDRPreferenceProtocol(zerodatawrap.ZDRProtocolCustom());\n\n    if (selected === zerodatawrap.ZDRProtocolRemoteStorage()) {\n      return RemoteStorage;\n    }\n\n    if (selected === zerodatawrap.ZDRProtocolFission()) {\n      return webnative;\n    }\n\n    return {\n      ZDRClientWriteFile (path, data) {\n        console.log('custom protocol write', [...arguments]);\n      },\n      ZDRClientReadFile (path) {\n        console.log('custom protocol read', [...arguments]);\n      },\n      ZDRClientListObjects (path) {\n        console.log('custom protocol list objects', [...arguments]);\n      },\n      ZDRClientDelete (path) {\n        console.log('custom protocol delete', [...arguments]);\n      },\n    };\n  })(),\n\n  // …\n  \n});\n```\n\nMove from one protocol to another by generating APIs from preferences:\n\n```javascript\nif (zerodatawrap.ZDRPreferenceProtocolMigrate()) {\n\n  // generate apis from protocol\n  const wrap = function (protocol) {\n    return zerodatawrap.ZDRWrap({\n\n      // …\n      \n    });\n  };\n  const source = await wrap(zerodatawrap.ZDRPreferenceProtocolMigrate());\n  const destination = await wrap(zerodatawrap.ZDRPreferenceProtocol());\n  \n  // get all objects (this is simplified, should be recursive)\n  await Promise.all(Object.entries(await source.App.ZDRStorageListingObjects('')).map(async function ([key, value]) {\n    // write to destination\n    await destination.App.ZDRStorageWriteObject(key, value);\n    \n    // delete from source\n    await source.App.ZDRStorageDeleteFile(key);\n  }));\n\n  // clear migrate preference to avoid repeating\n  zerodatawrap.ZDRPreferenceProtocolMigrateClear();\n\n  // call disconnect to do any other cleanup\n  source.ZDRCloudDisconnect();\n};\n```\n\n### ZDRClient (for custom protocol only)\n\n| function | notes |\n|-------|---------|\n| ZDRClientWriteFile \u003cbr\u003e **Required** | called by `ZDRStorageWriteFile` |\n| ZDRClientReadFile \u003cbr\u003e **Required** | called by `ZDRStorageReadFile` |\n| ZDRClientListObjects \u003cbr\u003e **Required** | called by `ZDRStorageListingObjects` |\n| ZDRClientDelete \u003cbr\u003e **Required** | called by `ZDRStorageDeleteFile` |\n| ZDRClientPrepare | called before returning wrapper |\n| ZDRClientConnect | called by `ZDRCloudConnect` |\n| ZDRClientReconnect | called by `ZDRCloudReconnect` |\n| ZDRClientDisconnect | called by `ZDRCloudDisconnect` |\n\n### ZDRProtocolRemoteStorage()\n\nReturns string.\n\n### ZDRProtocolFission()\n\nReturns string.\n\n### ZDRProtocolCustom()\n\nReturns string.\n\n### ZDRPreferenceProtocol(protocol)\n\nStores input as the protocol preference if none is set.\n\nReturns the protocol preference.\n\n### ZDRPreferenceProtocolClear()\n\nClears the protocol preference.\n\nReturns null.\n\n### ZDRPreferenceProtocolConnect(protocol)\n\nSets the protocol preference as 'to be migrated' if connecting to a different protocol.\n\nReturns input.\n\n### ZDRPreferenceProtocolMigrate()\n\nReturns the 'to be migrated' protocol if set.\n\n### ZDRPreferenceProtocolMigrateClear()\n\nClears the 'to be migrated' protocol.\n\nReturns input object.\n\n## ❤️\n\nHelp me keep creating projects that are public, accessible for free, and open-source.\n\n\u003ca href=\"https://rosano.ca/back\"\u003e\u003cimg alt=\"Send a gift\" src=\"https://static.rosano.ca/_shared/_RCSBackButton.svg\" /\u003e\u003c/a\u003e\n\n## License\n\nThe code is released under a [Hippocratic License](https://firstdonoharm.dev), modified to exclude its use for surveillance capitalism and also to require large for-profit entities to purchase a paid license.\n\n## Questions\n\nFeel free to reach out on [Mastodon](https://rosano.ca/mastodon) or [Twitter](https://rosano.ca/twitter).\n","funding_links":["https://rosano.ca/fund"],"categories":["JavaScript","Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0dataapp%2F0datawrap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0dataapp%2F0datawrap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0dataapp%2F0datawrap/lists"}