From 04e720cb4dd288ffe1338bf9da9be61202302208 Mon Sep 17 00:00:00 2001 From: Jamie Gaehring Date: Thu, 9 Dec 2021 22:36:23 -0500 Subject: [PATCH] Change h1 to h2, h2 to h3, etc, in docs. --- docs/entities.md | 14 ++++++++------ docs/index.md | 20 +++++++++++--------- docs/metadata.md | 6 ++++-- docs/remotes.md | 12 +++++++----- docs/schemata.md | 12 +++++++----- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/docs/entities.md b/docs/entities.md index 5ec5311..13c3bae 100644 --- a/docs/entities.md +++ b/docs/entities.md @@ -1,11 +1,13 @@ # Entities + +## farmOS's core data structures As outlined in the [farmOS Data Model](https://docs.farmos.org/model/), there are a few high-level types of records, known as entities, which share some common attributes and can have their own subtype, also referred to as a bundle. These entities comprise the main data structures in farmOS domain model. The two primary entities are [Assets](https://docs.farmos.org/model/type/asset/) and [Logs](https://docs.farmos.org/model/type/log/). A few examples of asset bundles would be equipment, plants, animals, land or water. A few examples of log bundles would be activities, harvests, inputs or observations. In farmOS.js, there are broadly two types of methods for handling entities: write methods, which return a new entity or modified copy of a previously existing entity; and remote methods, which transport entities between the local and a remote system, using an asynchronous request/response pattern. -# Write methods +## Write methods When you wish to generate new entities or modify existing ones, you should always use a write method, rather than mutating it in place. Write methods will provide a degree of immutability by returning a deep clone of the original entity. Although it will still be possible to reassign properties of that clone, it is not recommended. Largely this is because write methods will also track and update the entity's [metadata](/docs/metadata.md), which will significantly simplify how remote operations can be performed. Each entity has corresponding write methods on its corresponding namespace of the `farm` instance. For example, `farm.log.create` can be used to generate a new farmOS log: @@ -34,10 +36,10 @@ const merged = farm.log.merge(updated, remoteLog); Corresponding methods exist for all entities, so `farm.asset.create`, `farm.asset.update` and `farm.asset.merge` methods can be used for writing to assets, as well as users, terms, etc. -# Remote methods +## Remote methods If you've already [configured a remote host and authorized a user](remotes.md#authenticating), you can exchange entities with that host via AJAX. There are three remote methods for each entity, on the same namespace as the write methods: `fetch`, `send` and `delete`. All remote methods are asynchronous, and use the [axios](https://axios-http.com/) HTTP client internally, so they will work the same in both browser and Node.js environments. -## Fetching entities +### Fetching entities The fetch method can be called without parameters, although it is not recommended for reasons that will be outlined below: ```js @@ -53,7 +55,7 @@ For this reason, it's recommended to use some combination of options as an objec - `filter` - `limit` -### Filtering fetch requests +#### Filtering fetch requests A `filter` option can be provided to the `fetch` method, which is a [MongoDB-style query selector](https://docs.mongodb.com/manual/reference/operator/query/), supporting the following operators: - Logical operators @@ -159,7 +161,7 @@ const log = { This is a good place to note, too, that query fields should not be nested within the `attributes` or `relationships` objects, even though the corresponding entity field may be so nested. -### Limiting fetch requests +#### Limiting fetch requests All the above filters, however, may not be sufficient to retrieve _all_ of activity logs that match the provided query. More likely than not, they will only retrieve the first 50 logs that match, assuming there are as many logs on the server. That's because the default configuration for farmOS servers, at the time of writing this, sets a hard limit of 50 results per page, which can only be changed at the server; clients cannot override it remotely. It's also important to note that this limit will apply separately for each entity bundle (aka, `type`) being requested. So the example query from above, @@ -188,7 +190,7 @@ const request = farm.log.fetch({ filter, limit: Infinity }) It's especially important, when using a `limit` of `Infinity`, to combine it with a reasonable filter query, to keep the duration of the request cycle as short as possible, or to otherwise be prepared to accommodate long cycles without degrading performance or user experience. -## Sending and deleting entities +### Sending and deleting entities Sending entities to a remote server is much more straightforward in comparison: ```js diff --git a/docs/index.md b/docs/index.md index ffc7be2..f94c795 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,25 +1,27 @@ -# Overview +# Introduction + +## Motivation farmOS.js is a JavaScript library for working with farmOS data structures and interacting with farmOS servers. The need for farmOS.js arose out of development of the farmOS Field Kit app, which required the ability to work offline and to work with farmOS servers in varying configurations that could only be determined at runtime. To solve this, farmOS.js retrieves and stores [JSON Schema](https://json-schema.org/) documents from the servers it connects to. It then uses these schemata to generate farmOS data structures according to each server's particular configuration to ensure they will be validated by that server. To resolve the conflicts that inevitably arise from storing and modifying concurrent data while offline, farmOS.js implements a ["last-write-wins" merging strategy](metadata.md#last-write-wins-lww), so synchronization can be handled more fluidly and intuitively. Altogether, this has led farmOS.js to develop into a system with robust support for high degrees of both __modularity__ and __concurrency__, opening up the potential for the development of a wider array of interoperable applications. -# Requirements & browser support +## Requirements & browser support farmOS.js supports the [farmOS 2.x Data Model](https://docs.farmos.org/model/), compatible with farmOS 2.x servers. Previous versions will no longer be supported. farmOS.js can run in Node.js (versions 12.9.0 or higher), and in most modern browsers. IE 11 is not supported. -# Quick Start +## Quick Start Learn farmOS.js by example. -## 1. Install farmOS.js via npm +### 1. Install farmOS.js via npm ```bash $ npm install farmos ``` -## 2. Create a farm instance +### 2. Create a farm instance ```js import farmOS from 'farmos'; @@ -36,7 +38,7 @@ const options = { const farm = farmOS(options); ``` -## 3. Connect to a remote farmOS server +### 3. Connect to a remote farmOS server ```js const username = 'Farmer Sam'; @@ -44,7 +46,7 @@ const password = '123_you_cant_guess_me'; farm.remote.authorize(username, password); ``` -## 4. Retrieve JSON schema for farmOS record types from the server +### 4. Retrieve JSON schema for farmOS record types from the server ```js farm.schema.fetch() @@ -54,7 +56,7 @@ farm.schema.fetch() }); ``` -## 5. CRUD operations with a farmOS log +### 5. CRUD operations with a farmOS log ```js const initProps = { type: 'activity', name: 'did some stuff' }; @@ -71,7 +73,7 @@ farm.log.send(activity) }); ``` -# Next steps +## Next steps Now that you know the basics, dive deeper into following topics: - [Using farmOS JSON Schema documents](schemata.md) diff --git a/docs/metadata.md b/docs/metadata.md index 5cd7003..6e238d9 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -1,4 +1,6 @@ # Metadata + +## The `meta` property An important feature of farmOS.js is the way it manages metadata for farmOS data structures. This is of primary importance for easing synchronization and mitigating conflicts when farmOS data may reside on two or more independent devices that can make concurrent changes to that data. The basic structure of any farmOS entity will look something like the following object, which would be accessed on the `.meta` property: @@ -34,7 +36,7 @@ If a conflict cannot be resolved automatically during a merge, the remote data w Finally, the `remote` metadata contains information specific to a particular remote connection to another source of farmOS data. -## Last-Write-Wins (LWW) +### Last-Write-Wins (LWW) The strategy employed for resolving conflicts between a local and remote copy of the same entity (ie, two entities of the same entity type and identical `id`'s), is what's called "last write wins" or LWW. This is a fairly simple algorithm to implement, which essentially uses what metadata is available to determine which value changed most recently, and selects the most recent. There are limitations to this approach, however, since it can only detect changes down to the field level, which is sufficient for primitive value types like booleans or integers, but fails to capture subtler changes to complex data types. For instance, if an original string of `"Hi my namw is Sam"` was changed locally to `"Hi my name is Joe"`, but remotely changed to `"Hi my name is Sam."`, a plain LWW on the field-level cannot merge those changes, as obvious as they may be to human eyes, so it will generate a conflict which will require some other intervention. @@ -45,7 +47,7 @@ Finally, there may be occasions when "first write wins" would be more appropriat Other options exist that might refine our merging strategy, such as [CRDT's](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type), but so far such an approach has yet to be implemented. -# Sync status +## Sync status The `farm.isUnsynced` method is a quick way of determining whether or not an entity has been synced to a remote system: ```js diff --git a/docs/remotes.md b/docs/remotes.md index fa85a17..7fc6535 100644 --- a/docs/remotes.md +++ b/docs/remotes.md @@ -1,7 +1,9 @@ -# Working with remote farmOS instances +# Remotes + +## Working with remote farmOS instances A farmOS server based on Drupal JSON:API authenticates users using [OAuth2](https://oauth.net/2/). Most details of this exchange are abstracted by the farmOS.js client, although it can be helpful to have some understanding of the OAuth protocol. For more specific details on the type of OAuth configurations that are possible with farmOS servers, see the [farmOS OAuth docs](https://docs.farmos.org/development/api/authentication/#oauth2-details). -# Configuring the host +## Configuring the host At the very least, you will need to provide the host address of the server you are trying to reach, such as https://farm.example.com/. This host can be provided as part of the `remote` options when you create your farm instance: ```js @@ -28,7 +30,7 @@ farm.remote.setHost('https://farm.example.com'); This can be useful when you may still be awaiting user input to provide the host at the time you create the farm instance. -# Authorizing a user +## Authorizing a user Once a farm instance has been created and the host has been set, you can use a [Password Grant](https://docs.farmos.org/development/api/authentication/#password-credentials-grant): ```js @@ -37,10 +39,10 @@ const password = '123_you_cant_guess_me'; farm.remote.authorize(username, password); ``` -### ⚠️ __WARNING__ ⚠️ +#### ⚠️ __WARNING__ ⚠️ At this time, Password Grant is the only available method for authorization, but it is only recommended for trusted clients (called _1st party_), such as Field Kit. -# General information and other requests +## General information and other requests In addition to providing methods for configuring the host and authorizing, the `farm.remote` namespace other general methods for interacting with a farmOS server. The `request` method is a pre-configured axios client that only provides access and refresh tokens to authorized farm instances, but otherwise just accepts an endpoint (with or without URL search parameters) as its first parameter, and an optional [request config] object as the second parameter (defaults to `GET` method): diff --git a/docs/schemata.md b/docs/schemata.md index d022907..af4659e 100644 --- a/docs/schemata.md +++ b/docs/schemata.md @@ -1,11 +1,13 @@ # Schemata + +## Schema publication enables interoperability In order to generate and modify farmOS data structures, your farm instance must have the corresponding schema for each entity type (aka, the "bundle") that you wish to work with. For instance, if you wish to create an activity log and send it to a server, you will need the schema for logs with the type `'activity'`. These schemata are formatted using the [JSON Schema](https://json-schema.org) specification. Although this requires an extra degree of complexity, to retrieve and load schemata at runtime, it ensures that independent devices using separate configurations can still share data, so long as they adhere to the farmOS Data Model. -# Setting schemata +## Setting schemata To set a schema for a particular entity type, call `farm.schema.set` with the entity and type as the first two parameters, and the JSON Schema itself as the third parameter: ```js @@ -62,7 +64,7 @@ const equipment = farm.asset.create({ type: 'equipment' }); // works In this last example, only the activity log schema would have been overwritten, preserving the original schemata for harvest logs and equipment assets. -## Instantiate your farm with the `schemata` option +### Instantiate your farm with the `schemata` option If schemata are available to your application when you instantiate `farmOS`, you can also provide a `schemata` option to the constructor. This option should have as its value an object, containing each entity as a key (eg, `'log'`), whose value is an object containing each entity type as a key (eg, `'activity'`), whose value is the corresponding schema for that entity type. ```js @@ -75,7 +77,7 @@ const myFarm = farmOS({ }); ``` -# Retrieving schemata with the `get` method +## Retrieving schemata with the `get` method It is also possible to retrieve schemata after they've been set, which can be useful for checking supported types before attempting other operations, or generating a list of available entity types that can be displayed to the user. As with the `set` method, `farm.schema.get` will accept 0, 1 or 2 parameters: @@ -131,7 +133,7 @@ Activity schema: } ``` -# Fetching remote schemata +## Fetching remote schemata If your ultimate goal is to send data to a farmOS server, the best way to retrieve a schema is to fetch it from that server. This is facilitated by the `farm.schema.fetch` method, which accepts up to 2 arguments and returns a promise for the request: ```js @@ -153,7 +155,7 @@ farm.schema.fetch().then((allSchemata) => { }); ``` -# Using core schemata +## Using core schemata The "core" farmOS schemata, those that are included in a standard installation of a farmOS server, can be imported as JSON files from the farmOS.js Node package, if your bundler supports that JSON loading: ```js