integreat 0.8.0-beta.8 → 0.8.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +1292 -938
  2. package/ava.config.cjs +4 -3
  3. package/dist/adapters/index.d.ts +4 -0
  4. package/dist/adapters/index.js +5 -0
  5. package/dist/adapters/index.js.map +1 -0
  6. package/dist/adapters/json.d.ts +6 -0
  7. package/dist/adapters/json.js +69 -0
  8. package/dist/adapters/json.js.map +1 -0
  9. package/dist/authenticators/index.d.ts +2 -2
  10. package/dist/authenticators/index.js +5 -7
  11. package/dist/authenticators/index.js.map +1 -1
  12. package/dist/authenticators/options.d.ts +1 -1
  13. package/dist/authenticators/options.js +4 -6
  14. package/dist/authenticators/options.js.map +1 -1
  15. package/dist/authenticators/token.d.ts +1 -1
  16. package/dist/authenticators/token.js +1 -3
  17. package/dist/authenticators/token.js.map +1 -1
  18. package/dist/close.d.ts +2 -2
  19. package/dist/close.js +1 -4
  20. package/dist/close.js.map +1 -1
  21. package/dist/create.d.ts +13 -10
  22. package/dist/create.js +45 -33
  23. package/dist/create.js.map +1 -1
  24. package/dist/dispatch.d.ts +3 -3
  25. package/dist/dispatch.js +38 -47
  26. package/dist/dispatch.js.map +1 -1
  27. package/dist/dispatchScheduled.d.ts +2 -2
  28. package/dist/dispatchScheduled.js +1 -3
  29. package/dist/dispatchScheduled.js.map +1 -1
  30. package/dist/handlers/delete.d.ts +1 -1
  31. package/dist/handlers/delete.js +12 -18
  32. package/dist/handlers/delete.js.map +1 -1
  33. package/dist/handlers/expire.d.ts +1 -1
  34. package/dist/handlers/expire.js +18 -29
  35. package/dist/handlers/expire.js.map +1 -1
  36. package/dist/handlers/get.d.ts +1 -1
  37. package/dist/handlers/get.js +27 -29
  38. package/dist/handlers/get.js.map +1 -1
  39. package/dist/handlers/getAll.d.ts +2 -2
  40. package/dist/handlers/getAll.js +21 -24
  41. package/dist/handlers/getAll.js.map +1 -1
  42. package/dist/handlers/getIdent.d.ts +1 -1
  43. package/dist/handlers/getIdent.js +17 -20
  44. package/dist/handlers/getIdent.js.map +1 -1
  45. package/dist/handlers/getMeta.d.ts +1 -1
  46. package/dist/handlers/getMeta.js +18 -26
  47. package/dist/handlers/getMeta.js.map +1 -1
  48. package/dist/handlers/index.d.ts +3 -2
  49. package/dist/handlers/index.js +27 -28
  50. package/dist/handlers/index.js.map +1 -1
  51. package/dist/handlers/queue.d.ts +1 -1
  52. package/dist/handlers/queue.js +11 -16
  53. package/dist/handlers/queue.js.map +1 -1
  54. package/dist/handlers/run.d.ts +3 -3
  55. package/dist/handlers/run.js +164 -109
  56. package/dist/handlers/run.js.map +1 -1
  57. package/dist/handlers/service.d.ts +1 -1
  58. package/dist/handlers/service.js +7 -19
  59. package/dist/handlers/service.js.map +1 -1
  60. package/dist/handlers/set.d.ts +1 -1
  61. package/dist/handlers/set.js +13 -15
  62. package/dist/handlers/set.js.map +1 -1
  63. package/dist/handlers/setMeta.d.ts +1 -1
  64. package/dist/handlers/setMeta.js +10 -13
  65. package/dist/handlers/setMeta.js.map +1 -1
  66. package/dist/handlers/sync.d.ts +1 -1
  67. package/dist/handlers/sync.js +21 -24
  68. package/dist/handlers/sync.js.map +1 -1
  69. package/dist/index.d.ts +12 -12
  70. package/dist/index.js +13 -21
  71. package/dist/index.js.map +1 -1
  72. package/dist/listen.d.ts +2 -2
  73. package/dist/listen.js +2 -5
  74. package/dist/listen.js.map +1 -1
  75. package/dist/middleware/completeIdent.d.ts +1 -1
  76. package/dist/middleware/completeIdent.js +6 -8
  77. package/dist/middleware/completeIdent.js.map +1 -1
  78. package/dist/middleware/index.d.ts +1 -1
  79. package/dist/middleware/index.js +3 -5
  80. package/dist/middleware/index.js.map +1 -1
  81. package/dist/mutations/exchangeForm.js +1 -3
  82. package/dist/mutations/exchangeForm.js.map +1 -1
  83. package/dist/mutations/exchangeJson.js +1 -3
  84. package/dist/mutations/exchangeJson.js.map +1 -1
  85. package/dist/mutations/exchangeUri.js +2 -4
  86. package/dist/mutations/exchangeUri.js.map +1 -1
  87. package/dist/mutations/index.js +7 -9
  88. package/dist/mutations/index.js.map +1 -1
  89. package/dist/schema/accessForAction.d.ts +1 -1
  90. package/dist/schema/accessForAction.js +4 -7
  91. package/dist/schema/accessForAction.js.map +1 -1
  92. package/dist/schema/createCastMapping.d.ts +3 -3
  93. package/dist/schema/createCastMapping.js +82 -54
  94. package/dist/schema/createCastMapping.js.map +1 -1
  95. package/dist/schema/index.d.ts +4 -4
  96. package/dist/schema/index.js +29 -25
  97. package/dist/schema/index.js.map +1 -1
  98. package/dist/schema/types.d.ts +6 -6
  99. package/dist/schema/types.js +1 -2
  100. package/dist/service/Auth.d.ts +4 -3
  101. package/dist/service/Auth.js +32 -37
  102. package/dist/service/Auth.js.map +1 -1
  103. package/dist/service/Connection.d.ts +2 -2
  104. package/dist/service/Connection.js +19 -35
  105. package/dist/service/Connection.js.map +1 -1
  106. package/dist/service/authorize/action.d.ts +2 -2
  107. package/dist/service/authorize/action.js +5 -7
  108. package/dist/service/authorize/action.js.map +1 -1
  109. package/dist/service/authorize/data.d.ts +2 -2
  110. package/dist/service/authorize/data.js +10 -15
  111. package/dist/service/authorize/data.js.map +1 -1
  112. package/dist/service/endpoints/compare.d.ts +1 -1
  113. package/dist/service/endpoints/compare.js +1 -3
  114. package/dist/service/endpoints/compare.js.map +1 -1
  115. package/dist/service/endpoints/create.d.ts +5 -4
  116. package/dist/service/endpoints/create.js +41 -38
  117. package/dist/service/endpoints/create.js.map +1 -1
  118. package/dist/service/endpoints/index.d.ts +6 -6
  119. package/dist/service/endpoints/index.js +11 -8
  120. package/dist/service/endpoints/index.js.map +1 -1
  121. package/dist/service/endpoints/match.d.ts +2 -2
  122. package/dist/service/endpoints/match.js +5 -8
  123. package/dist/service/endpoints/match.js.map +1 -1
  124. package/dist/service/endpoints/types.d.ts +7 -8
  125. package/dist/service/endpoints/types.js +1 -2
  126. package/dist/service/index.d.ts +9 -5
  127. package/dist/service/index.js +152 -67
  128. package/dist/service/index.js.map +1 -1
  129. package/dist/service/types.d.ts +17 -19
  130. package/dist/service/types.js +1 -2
  131. package/dist/transformers/builtIns/boolean.d.ts +2 -2
  132. package/dist/transformers/builtIns/boolean.js +3 -5
  133. package/dist/transformers/builtIns/boolean.js.map +1 -1
  134. package/dist/transformers/builtIns/date.d.ts +2 -2
  135. package/dist/transformers/builtIns/date.js +6 -10
  136. package/dist/transformers/builtIns/date.js.map +1 -1
  137. package/dist/transformers/builtIns/index.d.ts +3 -11
  138. package/dist/transformers/builtIns/index.js +18 -19
  139. package/dist/transformers/builtIns/index.js.map +1 -1
  140. package/dist/transformers/builtIns/integer.d.ts +2 -2
  141. package/dist/transformers/builtIns/integer.js +5 -7
  142. package/dist/transformers/builtIns/integer.js.map +1 -1
  143. package/dist/transformers/builtIns/number.d.ts +2 -2
  144. package/dist/transformers/builtIns/number.js +6 -10
  145. package/dist/transformers/builtIns/number.js.map +1 -1
  146. package/dist/transformers/builtIns/object.d.ts +2 -2
  147. package/dist/transformers/builtIns/object.js +5 -7
  148. package/dist/transformers/builtIns/object.js.map +1 -1
  149. package/dist/transformers/builtIns/reference.d.ts +2 -2
  150. package/dist/transformers/builtIns/reference.js +11 -13
  151. package/dist/transformers/builtIns/reference.js.map +1 -1
  152. package/dist/transformers/builtIns/string.d.ts +2 -2
  153. package/dist/transformers/builtIns/string.js +5 -7
  154. package/dist/transformers/builtIns/string.js.map +1 -1
  155. package/dist/transformers/builtIns/unarray.d.ts +2 -2
  156. package/dist/transformers/builtIns/unarray.js +2 -4
  157. package/dist/transformers/builtIns/unarray.js.map +1 -1
  158. package/dist/transformers/form.d.ts +2 -2
  159. package/dist/transformers/form.js +6 -8
  160. package/dist/transformers/form.js.map +1 -1
  161. package/dist/transformers/generateUri.d.ts +7 -0
  162. package/dist/transformers/generateUri.js +69 -0
  163. package/dist/transformers/generateUri.js.map +1 -0
  164. package/dist/transformers/index.d.ts +3 -10
  165. package/dist/transformers/index.js +10 -13
  166. package/dist/transformers/index.js.map +1 -1
  167. package/dist/transformers/json.d.ts +2 -2
  168. package/dist/transformers/json.js +4 -6
  169. package/dist/transformers/json.js.map +1 -1
  170. package/dist/transformers/not.d.ts +2 -2
  171. package/dist/transformers/not.js +1 -3
  172. package/dist/transformers/not.js.map +1 -1
  173. package/dist/transformers/trim.d.ts +2 -2
  174. package/dist/transformers/trim.js +2 -4
  175. package/dist/transformers/trim.js.map +1 -1
  176. package/dist/types.d.ts +20 -15
  177. package/dist/types.js +1 -2
  178. package/dist/utils/action.d.ts +33 -0
  179. package/dist/utils/action.js +32 -0
  180. package/dist/utils/action.js.map +1 -0
  181. package/dist/utils/array.d.ts +1 -0
  182. package/dist/utils/array.js +6 -9
  183. package/dist/utils/array.js.map +1 -1
  184. package/dist/utils/createAction.d.ts +1 -1
  185. package/dist/utils/createAction.js +1 -4
  186. package/dist/utils/createAction.js.map +1 -1
  187. package/dist/utils/createError.d.ts +3 -3
  188. package/dist/utils/createError.js +7 -8
  189. package/dist/utils/createError.js.map +1 -1
  190. package/dist/utils/createMapOptions.d.ts +4 -4
  191. package/dist/utils/createMapOptions.js +3 -5
  192. package/dist/utils/createMapOptions.js.map +1 -1
  193. package/dist/utils/createSchedule.d.ts +2 -2
  194. package/dist/utils/createSchedule.js +4 -7
  195. package/dist/utils/createSchedule.js.map +1 -1
  196. package/dist/utils/createUnknownServiceError.d.ts +1 -1
  197. package/dist/utils/createUnknownServiceError.js +4 -7
  198. package/dist/utils/createUnknownServiceError.js.map +1 -1
  199. package/dist/utils/deepClone.js +2 -4
  200. package/dist/utils/deepClone.js.map +1 -1
  201. package/dist/utils/getField.js +5 -8
  202. package/dist/utils/getField.js.map +1 -1
  203. package/dist/utils/getService.d.ts +2 -2
  204. package/dist/utils/getService.js +1 -4
  205. package/dist/utils/getService.js.map +1 -1
  206. package/dist/utils/indexUtils.js +2 -7
  207. package/dist/utils/indexUtils.js.map +1 -1
  208. package/dist/utils/is.d.ts +5 -5
  209. package/dist/utils/is.js +19 -39
  210. package/dist/utils/is.js.map +1 -1
  211. package/dist/utils/mappingHelpers.d.ts +1 -1
  212. package/dist/utils/mappingHelpers.js +5 -12
  213. package/dist/utils/mappingHelpers.js.map +1 -1
  214. package/dist/utils/mergeDefinitions.d.ts +1 -1
  215. package/dist/utils/mergeDefinitions.js +1 -4
  216. package/dist/utils/mergeDefinitions.js.map +1 -1
  217. package/dist/utils/mergeResources.d.ts +1 -1
  218. package/dist/utils/mergeResources.js +1 -4
  219. package/dist/utils/mergeResources.js.map +1 -1
  220. package/dist/utils/mutationHelpers.d.ts +2 -0
  221. package/dist/utils/mutationHelpers.js +33 -0
  222. package/dist/utils/mutationHelpers.js.map +1 -0
  223. package/dist/utils/validateFilters.d.ts +1 -2
  224. package/dist/utils/validateFilters.js +31 -20
  225. package/dist/utils/validateFilters.js.map +1 -1
  226. package/dist/utils/xor.d.ts +1 -0
  227. package/dist/utils/xor.js +4 -0
  228. package/dist/utils/xor.js.map +1 -0
  229. package/package.json +23 -20
  230. package/dist/transformers/formatDate.d.ts +0 -7
  231. package/dist/transformers/formatDate.js +0 -50
  232. package/dist/transformers/formatDate.js.map +0 -1
package/README.md CHANGED
@@ -7,64 +7,66 @@ An integration layer for node.js.
7
7
  [![Coverage Status](https://coveralls.io/repos/github/integreat-io/integreat/badge.svg?branch=master)](https://coveralls.io/github/integreat-io/integreat?branch=master)
8
8
  [![Maintainability](https://api.codeclimate.com/v1/badges/a5bd9841a47ff9f74577/maintainability)](https://codeclimate.com/github/integreat-io/integreat/maintainability)
9
9
 
10
- **Note:** We're still changing the api rather drastically from release to
11
- release. We encourage trying it out and experimenting with Integreat, and we
12
- highly appreciate feedback, but know that anything might change.
13
-
14
- The basic idea of Integreat is to make it easy to define a set of data services
15
- and expose them through a well defined interface, to abstract away the specifics
16
- of each service, and map their data to defined schemas.
17
-
18
- This is done through:
19
-
20
- - adapters, that does all the hard work of communicating with the different
21
- services
22
- - a definition format, for setting up each service with the right adapter and
23
- parameters
24
- - a `dispatch()` function that sends actions to the right adapters via internal
25
- action handlers
26
-
27
- It is possible to set up Integreat to treat one service as a store/buffer for
28
- other services, and schedule syncs between the store and the other services.
29
-
30
- Finally, there will be different interface modules available, that will plug
31
- into the `dispatch()` function and offer other ways of reaching data from the
32
- services such as out of the box REST or GraphQL APSs.
10
+ **Note:** We're closing in on a more stable version, but there might still be
11
+ a few changes coming before v1.0. We encourage trying out and experimenting with
12
+ Integreat, and we highly appreciate feedback, but know that some things might
13
+ still change.
14
+
15
+ The basic idea of Integreat is to make it easy to define how to send data to and
16
+ receive data from a set of [**services**](#services), and expose them through a
17
+ well defined interface, abstracting away the specifics of each service.
18
+
19
+ There are a few concepts that makes this possible:
20
+
21
+ - [**Transporters**](#transporters) and [**adapters**](#adapters) speak the
22
+ language of different types of services and standards of data exchange, and
23
+ does the basic translation to and from the structures used by Integreat. You
24
+ deal with familiar JavasScript objects, arrays, and primitive data types,
25
+ regardless of what the service expects.
26
+ - [**Mutation pipelines**](#mutations) let you define how the data coming from
27
+ or going to a service should be transformed. This includes changing the overal
28
+ structure, renaming properties, transforming and filtering values with
29
+ transformer functions, etc. You may also provide your own transformer
30
+ functions.
31
+ - [**Schemas**](#schemas) serve as a common normalization of data between
32
+ services. You define your own schemas and mutate data to and from them,
33
+ enabling inter-service sharing of data. If you have data in one schema, you
34
+ may send it to any service where you have set up the right mutations for this
35
+ schema, again abstracting away all service details.
36
+
37
+ All configuration is done through basic JSON-friendly structures, and you define
38
+ your services with different endpoints, mutation pipelines, authentication
39
+ schemes, etc.
40
+
41
+ Your configuration is spun up as an Integreat instance. To send and retrieve
42
+ data, you dispatch [**actions**](#actions) to your instance and get
43
+ [**response**](#action-response) objects back. You may define [jobs](#jobs) to
44
+ run simple actions or longer "flows" consisting of several actions with
45
+ conditions and logic. You may also configure [queues](#queues) to have actions
46
+ run in sequence or on a later time.
33
47
 
34
48
  ```
35
- _________________
36
- | Integreat |
37
- | |
38
- | |-> Adapter <-> Service
39
- Action -> Dispatch -| |
40
- | |-> Adapter <-> Service
41
- | |
42
- |_________________|
49
+ ____________________________________________________
50
+ | |
51
+ | Integreat instance |
52
+ Action ----| | |
53
+ |-> Dispatch <-> Schema <-> Mutation <-> Adapter <-> Transporter <-> Service
54
+ Response <-| | |
55
+ |___________________________________________________|
43
56
  ```
44
57
 
45
- Data from the services is retrieved, normalized, and mapped by the adapter, and
46
- returned asynchronously back to the code that initiated the action. Actions for
47
- fetching data will be executed right away.
48
-
49
- Actions that update data on services will reversely map and serialize the data
50
- before it is sent to a service. These actions may be queued or scheduled, by
51
- setting up Integreat with the supplied queue middleware.
52
-
53
- Integreat comes with a [standard data format](#the-data-format), which is the
54
- only format that will be exposed to the code dispatching the actions. The
55
- mapping, normalizing, and serializing will happing to and from this format,
56
- according to the defined schemas and mapping rules.
57
-
58
- To deal with security and permissions, Integreat has a built-in concept of an
59
- ident. Other authentication schemes may be mapped to Integreat's ident scheme,
60
- to provide data security from a service to another service or to the dispatched
58
+ To deal with security and permissions, Integreat has a concept of an ident.
59
+ Other authentication schemes may be mapped to Integreat's ident scheme, to
60
+ provide data security from a service to another service or to the dispatched
61
61
  action. A ground principle is that nothing that enters Integreat from an
62
62
  authenticated service, will leave Integreat unauthenticated. What this means,
63
63
  though, depends on how you define your services.
64
64
 
65
+ # Usage
66
+
65
67
  ## Install
66
68
 
67
- Requires node v8.6.
69
+ Requires node v18.
68
70
 
69
71
  Install from npm:
70
72
 
@@ -72,802 +74,1066 @@ Install from npm:
72
74
  npm install integreat
73
75
  ```
74
76
 
75
- ## Hello world
77
+ You will probably also need some [transporters](#transporters) and
78
+ [adapters](#adapters), and the basic transformers in
79
+ [`integreat-transformers`](https://github.com/integreat-io/integreat-transformers).
76
80
 
77
- The hello world example of Integreat, would look something like this:
81
+ ## Basic example
82
+
83
+ The following is the "hello world" example of Integreat. As most hello world
84
+ examples, this is a bit too trivial a use case to demonstrate the real
85
+ usefulness of Integreat, but it shows you the simplest setup possible.
86
+
87
+ Here, we fetch cat facts from the API endpoint
88
+ 'https://cat-fact.herokuapp.com/facts', which returns data in JSON and requires
89
+ no authentication. The returned list of facts are mutated and cast to the `fact`
90
+ schema. We only fetch data _from_ the service, and no data is sent _to_ it.
78
91
 
79
92
  ```javascript
80
93
  import Integreat from 'integreat'
81
94
  import httpTransporter from 'integreat-transporter-http'
95
+ import jsonAdapter from 'integreat-adapter-json'
82
96
 
83
97
  const schemas = [
84
98
  {
85
- id: 'message',
86
- service: 'helloworld',
87
- shape: { text: 'string' },
99
+ id: 'fact', // The id of the schema
100
+ shape: {
101
+ // The fields of the type
102
+ id: 'string', // An id field will always be included, but we define it here for readability
103
+ text: 'string', // The text of the cat fact
104
+ createdAt: 'date', // The created date (`createdAt` and `updatedAt` will always be dates)
105
+ },
106
+ access: { allow: 'all' }, // No access restrictions
88
107
  },
89
108
  ]
90
109
 
91
110
  const services = [
92
111
  {
93
- id: 'helloworld',
94
- transporter: 'http',
95
- endpoints: [{ options: { uri: 'https://api.helloworld.io/json' } }],
96
- mappings: {
97
- message: { text: 'message' },
112
+ id: 'catfact', // The id of the service
113
+ transporter: 'http', // Use the http transporter
114
+ adapters: ['json'], // Run the request and the response through the json adapter
115
+ options: {
116
+ // Options for the transporter
117
+ uri: 'https://cat-fact.herokuapp.com/facts', // Only the uri is needed here
98
118
  },
119
+ endpoints: [
120
+ {
121
+ match: { action: 'GET', type: 'fact' }, // Match to a GET action for type 'fact'
122
+ mutation: {
123
+ $direction: 'from', // We're mutating data _from_ the service
124
+ // Here we're mutating `response.data` and "setting it back" where we found it ...
125
+ 'response.data': [
126
+ 'response.data[]',
127
+ {
128
+ $iterate: true, // Mutate each item in an array
129
+ id: '_id', // The id is called `_id` the data from the service
130
+ text: 'text', // text is called `text`
131
+ createdAt: 'createdAt', // Creation date is called `createdAt`
132
+ },
133
+ ],
134
+ },
135
+ },
136
+ ],
99
137
  },
100
138
  ]
101
139
 
102
- const transporters = { http: httpTransporter }
103
-
104
- const great = Integreat.create({ schemas, services }, { transporters })
105
- await great.listen() // Only needed for transporter listening to incoming requests
106
- const action = { type: 'GET', payload: { type: 'message' } }
140
+ // Create the Integreat instance from our definitions and provide the
141
+ // transporters and adapters we require.
142
+ const great = Integreat.create(
143
+ { schemas, services },
144
+ { transporters: { http: httpTransporter }, adapters: { json: jsonAdapter } }
145
+ )
107
146
 
108
- great.dispatch(action).then((data) => console.log(data.text))
109
- //--> Hello world
147
+ // Prepare an action to fetch all cat facts from the service `catfact`
148
+ const action = { type: 'GET', payload: { type: 'fact', service: 'catfact' } }
110
149
 
111
- await great.close()
150
+ // Dispatch the action and get the response
151
+ const response = await great.dispatch(action)
112
152
  ```
113
153
 
114
- As most hello world examples, this is a bit too trivial a use case to
115
- demonstrate the real usefulness of Integreat, but shows the simplest setup
116
- possible.
117
-
118
- The example requires an imagined api at 'https://api.helloworld.io/json',
119
- returning the following json data:
154
+ The `response` object will look like this:
120
155
 
121
- ```json
122
- {
123
- "message": "Hello world"
124
- }
125
- ```
126
-
127
- ## Schema definitions
128
-
129
- To do anything with Integreat, you need to define one or more schemas. They
130
- describe the data you expected to get out of Integreat. A type will be
131
- associated with a service, which is used to retrieve data for the type, unless
132
- another service is specified.
133
-
134
- ```
156
+ ```javascript
135
157
  {
136
- id: <string>,
137
- plural: <string>,
138
- service: <serviceId>,
139
- attributes: {
140
- <attrId>: {
141
- type: <string>,
142
- default: <object>
143
- }
144
- },
145
- relationships: {
146
- <relId>: {
147
- type: <string>,
148
- default: <object>,
149
- query: <query params>
150
- }
151
- },
152
- auth: <auth def>
158
+ status: 'ok',
159
+ data: [
160
+ {
161
+ id: '58e008780aac31001185ed05',
162
+ $type: 'fact',
163
+ text: 'Owning a cat can reduce the risk of stroke and heart attack by a third.',
164
+ createdAt: new Date('2018-03-29T20:20:03.844Z')
165
+ },
166
+ // ...
167
+ ]
153
168
  }
154
169
  ```
155
170
 
156
- The convention is to use singular mode for the `id`. The `plural` property is
157
- optional, but it's good practice to set it to the plural mode of the `id`, as
158
- some interfaces may use it. For instance,
159
- [`integreat-api-json`](https://github.com/integreat-io/integreat-api-json) uses
160
- it to build a RESTful endpoint structure, and will append an _s_ to `id` if
161
- `plural` is not set – which may be weird in some cases.
162
-
163
- ### Attributes
164
-
165
- Each attribute is defined with an id, which may contain only alphanumeric
166
- characters, and may not start with a digit. This id is used to reference the
167
- attribute.
168
-
169
- The `type` defaults to `string`. Other options are `integer`, `float`,
170
- `boolean`, and `date`. Data from Integreat will be cast to corresponding
171
- JavaScript types.
172
-
173
- The `default` value will be used when a data service does not provide this value.
174
- Default is `null`.
171
+ # Integreat concepts
175
172
 
176
- ### Relationships
177
-
178
- Relationship is defined in the same way as attributes, but with one important
179
- difference: The `type` property refers to other Integreat schemas. E.g. a
180
- schema for an article may have a relationship called `author`, with
181
- `type: 'user'`, referring to the schema with id `user`. `type` is required on
182
- relationships.
183
-
184
- The `default` property sets a default value for the relationship, in the same
185
- way as for attributes, but note that this value should be a valid id for an item
186
- of the type the relationship refers to.
187
-
188
- Finally, relationships have a `query` property, which is used to retrieve items
189
- for this relationship. In many cases, a service may not have data that maps to
190
- id(s) for a relationship directly, and this is the typical use case for this
191
- property.
192
-
193
- The `query` property is an object with key/value pairs, where the key is the id
194
- of a field (an attribute, a relationship, or `id`) on the schema the relationship
195
- refers to, and the value is the id of field on this schema.
196
-
197
- Example schema with a query definition:
198
-
199
- ```
200
- {
201
- id: 'user',
202
- ...
203
- relationships: {
204
- articles: {type: 'article', query: {author: 'id'}}
205
- }
206
- }
207
- ```
173
+ As mentioned in the introduction, the building blocks of Integreat are services,
174
+ transporters and adapters, mutation pipelines, and schemas.
208
175
 
209
- In this case, the `articles` relationship on the `user` schema may be fetched by
210
- querying for all items of type `article`, where the `author` field equals the
211
- `id` of the `user` item in question.
176
+ ## Services
212
177
 
213
- ### Authorization
178
+ A service is the API, database, FTP server, queue, etc. that you want to get
179
+ data from and/or set data to. We pass on a set of service definitions to
180
+ Integreat, specifying what transporter, adapters, authentication schemas it
181
+ requires, in adition to defining the different endpoints available on the
182
+ service, how they should be called, and how data should be mutated in each case.
214
183
 
215
- Set the `access` property to enforce permission checking on the schema. This
216
- applies to any service that provides this schema.
184
+ We'll get back to the details of all of this in turn, but first we want to
185
+ highlight how central the concept of a service is to Integreat. Basically, in
186
+ Integreat "everything is a service". A simple REST/JSON API is a service, a
187
+ database is a service, and everything external you want to communicate with are
188
+ services. Want to set up a queue to handle actions one by one? That's a service.
189
+ Want to cache data in a memory store? That's a service. Want to schedule actions
190
+ to run on intervals? That's a service to. By simply defining services and their
191
+ specifics, you may set up a variety of different types of configurations with
192
+ the same few building blocks. This is very powerful as soon as you get into the
193
+ right mindset.
217
194
 
218
- The simplest access type `auth`, which means that anyone can do anything with
219
- the data of this schema, as long as they are authenticated.
195
+ Services are configured by service definitions, and tells Integreat how to
196
+ fetch data from a service, how to mutate this data to schemas, and how to send
197
+ data back to the service.
220
198
 
221
- Example of a schema with an access rule:
199
+ The service definition object includes the transporter id, adapter ids, any
200
+ authentication method, the endpoints for fetching from and sending to the
201
+ service, mutations that data to all endpoints will pass through, and options
202
+ for transporters, adapters, etc.
222
203
 
223
204
  ```javascript
224
205
  {
225
- id: 'entry',
226
- attributes: {...},
227
- relationships: {...},
228
- access: 'auth'
229
- }
230
- ```
231
-
232
- To signal that the schema really has no need for authorization, use `all`.
233
- This is not the same as not setting the `auth` prop, as `all` will override
234
- Integreat's principle of not letting authorized data out of Integreat without
235
- an authorization rule. In a way, you can say that `all` is an authorization
236
- rule, but it allows anybody to access the data, even the unauthenticated.
237
-
238
- The last of the simpler access types, is `none`, which will simly give no one
239
- access, no matter who they are.
240
-
241
- For a more fine-grained rules, set `access` to an access definition.
242
-
243
- ## Service definitions
244
-
245
- Service definitions are at the core of Integreat, as they define the services to
246
- fetch data from, how to map this data to a set of items to make available
247
- through Integreat's data api, and how to send data back to the service.
248
-
249
- A service definition object defines the adapter, any authentication method, the
250
- endpoints for fetching from and sending to the service, and mappings to the
251
- supported schemas (attributes and relationships):
252
-
253
- ```
254
- {
255
- id: <string>,
256
- adapter: <string>,
206
+ id: <service id>,
207
+ transporter: <transporter id>,
208
+ adapters: [<adapter id>, <adapter id>, ...],
257
209
  auth: <auth id>,
258
210
  meta: <type id>,
259
211
  options: {...},
212
+ mutation: <mutation pipeline>,
260
213
  endpoints: [
261
214
  <endpoint definition>,
262
215
  ...
263
- ],
264
- mappings: {
265
- <schema id>: <mapping definition | mapping id>,
266
- ...
267
- }
216
+ ]
268
217
  }
269
218
  ```
270
219
 
271
220
  Service definitions are passed to Integreat on creation through the
272
- `Integreat.create()` function. There is no way to change services after
273
- creation.
221
+ `Integreat.create()` function. There is no way to change service defintions
222
+ after creation.
274
223
 
275
- See [mapping definition](#mapping-definition) for a description of the
276
- relationship between services and mappings, and the `mappings` property.
224
+ See [mutations](#mutations) for a description of how to define the mutation
225
+ pipeline for a service.
277
226
 
278
227
  The `auth` property should normally be set to the id of an
279
- [auth definition](#service-authentication) if the service requires authentication.
280
- In cases where the service is authenticated by other means, e.g. by including
281
- username and password in the uri, set the `auth` property to `true` to signal
282
- that this is an authenticated service.
283
-
284
- ### Endpoint definition
228
+ [auth definition](#service-authentication), if the service requires
229
+ authentication. In cases where the service is authenticated by other means, e.g.
230
+ by including username and password in the uri, set the `auth` property to `true`
231
+ to signal that this is an authenticated service.
232
+
233
+ ### Endpoints
234
+
235
+ A service will have at least one endpoint, but often there will be several. An
236
+ endpoint is a definition of one of the ways Integreat may interact with a
237
+ service. You decide how you want to set up the endpoints and what is the right
238
+ "endpoint design" for a service, but there might be one endpoint for each
239
+ operation that can be done with a type of data.
240
+
241
+ For example, let's say you have a simple REST API with blog articles and
242
+ authors. There will most likely be an endpoint to fetch all (or some) articles,
243
+ one endpoint for fetching one article by id, one endpoint for creating an
244
+ article, one for updating an article, and so on. And you'll have similar
245
+ endpoints for authors, one endpoint for fetching all, one for fetching one by
246
+ id, one endpoint for creating an author, etc. As this is REST, each endpoint
247
+ will address a different combination of urls and http verbs (through the
248
+ transporter).
249
+
250
+ As another example, you may be accessing a database of articles and authors
251
+ directly. The configuration details will be very different than for a REST API,
252
+ but you'll probably have the same endpoints, fetching all articles, fetching
253
+ one, creating, updating, and the same all over for users. Instead of urls and
254
+ http verbs, as for REST, these endpoints will address different databases and
255
+ different database operations (through the transporter).
256
+
257
+ > Note: This is not to say that Integreat requires you to set up endpoints
258
+ > exactly as described in these examples, it might be that you would like to set
259
+ > up an endpoint that handles many of these cases. The intention here is just to
260
+ > give you an understanding of what an endpoint is in Integreat.
261
+
262
+ When you dispatch an action, Integreat will figure out what service and what
263
+ endpoint to send the action to. The target service is often specified in the
264
+ action payload with the `service` property, but if not, the default service of
265
+ the schema specified with the payload `type` property, will be used.
266
+
267
+ The matching to an endpoint is done by finding the endpoint whose `match` object
268
+ matches the action with most accuracy. The rules of the endpoint matching is
269
+ describe in more details [below](#match-properties).
270
+
271
+ Here's the format of an endpoint definition:
285
272
 
286
- ```
273
+ ```javascript
287
274
  {
288
- id: <string>,
275
+ id: <endpoint id>,
289
276
  match: {
290
- type: <string>,
291
- scope: <'collection'|'member'>,
277
+ type: <schema id>,
278
+ scope: <'collection'|'member'|'members'|'all'>,
292
279
  action: <action type>,
293
280
  params: {...},
294
- filters: []
281
+ incoming: <boolean>,
282
+ filters: {...}
295
283
  },
296
- validate: [],
297
284
  mutation: <mutation pipeline>,
298
- sendNoDefaults: <boolean>,
299
- returnNoDefaults: <boolean>,
300
- mappings: {
301
- <schema id>: <mapping definition | mapping id>,
302
- },
303
- options: {...}
285
+ options: {...},
286
+ allowRawRequest: <boolean>,
287
+ allowRawResponse: <boolean>
304
288
  }
305
289
  ```
306
290
 
291
+ All of these properties are optional. An empty endpoint defintion object will
292
+ match anything, pass on the action to the transporter untouched, and relay any
293
+ response coming back. This might be what you need, but often you'll want to
294
+ specify a few things:
295
+
296
+ - `id`: The endpoint may have an id, which you may use to specify that you want
297
+ an action to go to this particular id. However, most of the time you'll set
298
+ up the `match` object so that Integreat will decide what endpoint to use for
299
+ the action you dispatch.
300
+ - `match`: The match object is used to decide the right endpoint for an action.
301
+ More one this in the [Match properties](#match-properties) section.
302
+ - `mutation`: A mutation pipeline for the endpoint. The pipeline is run for both
303
+ actions going to a service and the response coming back, so keep this in mind
304
+ when you set up this pipeline. See [Mutation pipelines](#mutation-pipelines)
305
+ for more on how to define the mutation.
306
+ - `options`: This object will be passed on to the transporter and may contain
307
+ any properties that are meaningful to the transporter. You may also add other
308
+ properties for use in your mutations, but keep in mind that they will be sent
309
+ to the transporter. The endpoint `options` object is merged with the service
310
+ `options` object. Endpoint properties will override equally named service
311
+ properties, but this is done through deep merging, so child objects will be
312
+ merged as well.
313
+ - `allowRawRequest`: When set to `true`, payload `data` sent to this endpoint
314
+ will not by cast automatically nor will an error be returned if the data is
315
+ not typed.
316
+ - `allowRawResponse`: When set to `true`, response `data` coming from this
317
+ endpoint will not by cast automatically nor will an error be returned if the
318
+ data is not typed.
319
+
307
320
  #### Match properties
308
321
 
309
322
  An endpoint may specify none or more of the following match properties:
310
323
 
311
- - `id`: An action payload may include an `endpoint` property, that will be
312
- matched against this `id`. For actions with an endpoint id, no other matching
313
- properties will be considered
314
324
  - `type`: When set, the endpoint will only be used for actions with the
315
- specified schema type (not to be confused with the action type)
316
- - `scope`: May be `member` or `collection`, to specify that the endpoint should
317
- be used to request one item (member) or an entire collection of items.
318
- Setting this to `member` will require an `id` property in the action payload.
319
- Not setting this property signals an endpoint that will work for both
325
+ specified schema type (the schema's id). `type` may also be an array of types,
326
+ matching any one of the schemas in the list.
327
+ - `scope`: May be `member`, `members`, `collection`, or `all`, to specify that
328
+ the endpoint should be used to request one item (member) by id, several items
329
+ by ids (members), or an entire collection of items. Setting this to `member`
330
+ or `members` will only match actions with a payload `id` property, and the
331
+ `id` should be an array of ids for `members`. Not setting this property, or
332
+ setting it to `all`, signals an endpoint that will work for all scopes.
320
333
  - `action`: May be set to the type string of an action. The endpoint will match
321
- only actions of this type
322
-
323
- Endpoints are matched to an action by picking the matching endpoint with highest
324
- level of specificity. E.g., for a GET action asking for resources of type
325
- `entry`, an endpoint with `action: 'GET'` and `type: 'entry'` is picked over an
326
- endpoint matching all GET actions.
327
-
328
- Properties are matched in the order they are listed above, so that when two
329
- endpoints matches e.g. one with a scope and the other with an action, the one
330
- matching with scope is picked. When two endpoints are equally specified with the
331
- same match properties specified, the first one is used.
332
-
333
- All match properties except `id` may be specified with an array of matching
334
- values, so that an endpoint may match more cases. However, when two endpoints
335
- match on a property specified as an array on one and as a single value on the
336
- other, the one with the single value is picked.
334
+ only actions of this type. When this is not specified, any action type will
335
+ match. `action` may also be a list of action types, matching any of these.
336
+ - `params`: This object should list all params that this endpoint supports. A
337
+ param in this context is any property on the action payload except `type`,
338
+ `id`, or `data`. Use the param name as key on this object and set the value to
339
+ `true` if it is required, and `false` if it is optional. When matching
340
+ endpoints, an action will only match if it has all the required params, and in
341
+ case several match, the endpoint with more specified params will be preferred.
342
+ - `incoming`: If this is `true`, it will only match incoming actions, if `false`
343
+ only outgoing, and if not set, it will match both.
344
+ - `filters`: The filter object specifies a set of tests that needs to match an
345
+ action. The key of the object is a full dot notation path for the action
346
+ object, e.g. `payload.onlyArchived` and the value is a
347
+ [JSON Schema Validation object](https://json-schema.org/draft/2020-12/json-schema-validation.html).
348
+
349
+ > Editor's note: Describe what incoming actions are, and give more details on
350
+ > filters.
351
+
352
+ There might be cases where several endpoints match an action, and in these cases
353
+ the endpoint with the highest level of specificity will be used. E.g., for a
354
+ `GET` action asking for resources of type `entry`, an endpoint with both
355
+ `action: 'GET'` and `type: 'entry'` is picked over an endpoint matching all
356
+ `GET` actions regardless of type. For `params` and `filters` this is decided by
357
+ the highest number of properties on these objects.
358
+
359
+ The order of the endpoints in the `endpoints` list matters only when two
360
+ endpoints are equally specified with the same match properties specified. Then
361
+ the first one is used.
337
362
 
338
363
  When no match properties are set, the endpoint will match any actions, as long
339
364
  as no other endpoints match.
340
365
 
341
- #### Params property
342
-
343
- An endpoint may accept properties, and indicate this by listing them on the
344
- `params` object, with the value set to `true` for required params. All
345
- properties are treated as strings.
366
+ Finally, if an action specifies the endpoint id with the `endpoint`
367
+ [payload property](#payload-properties), this overrides all else, and the
368
+ endpoint with the id is used regardless of how the match object would apply.
346
369
 
347
- An endpoint is only used for actions where all the required parameters are
348
- present.
370
+ Example service definition with endpoint match object:
349
371
 
350
- Example service definition with endpoint parameters:
351
-
352
- ```
372
+ ```javascript
353
373
  {
354
374
  id: 'entries',
355
- adapter: 'json',
375
+ transporter: 'http',
356
376
  endpoints: [
357
377
  {
358
- params: {
359
- author: true,
360
- archive: false
378
+ match: {
379
+ type: 'entry',
380
+ action: 'GET',
381
+ scope: 'collection',
382
+ params: {
383
+ author: true,
384
+ archive: false
385
+ }
361
386
  },
362
387
  options: {
363
- uri: 'https://example.api.com/1.0/{author}/{type}_log{?archive}'
388
+ uri: 'https://example.api.com/1.0/{author}/{type}_log?archive={archive}'
364
389
  }
365
390
  }
366
391
  ],
367
- ...
392
+ // ...
368
393
  }
369
394
  ```
370
395
 
371
- Some params are always available and does not need to be specified in `params`,
372
- unless to define them as required:
373
-
374
- - `id`: The item id from the action payload or from the data property (if it is
375
- an object and not an array). Required in endpoints with `scope: 'member'`, not
376
- included for `scope: 'collection'`, and optional when scope is not set.
377
- - `type`: The item type from the action payload or from the data property (if it
378
- is an object and not an array).
379
- - `typePlural`: The plural form of the type, gotten from the corresponding
380
- schema's `plural` prop – or by adding an 's' to the type is `plural` is not
381
- set.
382
-
383
- #### Options property
396
+ ### Service authentication
384
397
 
385
- Unlike the match properties, the `options` property is required. This should be
386
- an object with properties to be passed to the adapter as part of a request. The
387
- props are completely adapter specific, so that each adapter can dictate what
388
- kind of information it will need, but there are a set of recommended props to
389
- use when they are relevant:
390
-
391
- - `uri`: A uri template, where e.g. `{id}` will be placed with the value of the
392
- parameter `id` from the action payload. For a full specification of the template
393
- format, see
394
- [Integreat URI Template](https://github.com/integreat-io/great-uri-template).
395
-
396
- - `path`: A [path](#paths) into the data, specific for this endpoint. It will
397
- usually point to an array, in which the items can be found, but as mappings may
398
- have their own `path`, the endpoint path may point to an object from where the
399
- different mapping paths point to different arrays.
400
-
401
- - `method`: An adapter specific keyword, to tell the adapter which method of
402
- transportation to use. For adapters based on http, the options will typically
403
- be `PUT`, `POST`, etc. The method specified on the endpoint will override any
404
- method provided elsewhere. As an example, the `SET` action will use the `PUT`
405
- method as default, but only if no method is specified on the endpoint.
406
-
407
- ## Mapping definition
398
+ This definition format is used to authenticate with a service:
408
399
 
409
- ```
400
+ ```javascript
410
401
  {
411
- id: <string>,
412
- type: <typeId>,
413
- path: <string>,
414
- attributes: {
415
- <attrKey>: {
416
- path: <string>,
417
- transform: <transform pipeline>
418
- }
419
- },
420
- relationships: {
421
- <relKey>: {
422
- path: <string>,
423
- transform: <transform pipeline>
424
- }
425
- },
426
- toService: {
427
- path: <string>,
428
- transform: <transform pipeline>
402
+ id: <id>,
403
+ authenticator: <authenticator id>,
404
+ options: {
405
+ // ...
429
406
  }
430
- qualifier: <string>,
431
- transform: <transform pipeline>,
432
- filterFrom: <filter pipeline>,
433
- filterTo: <filter pipeline>
434
407
  }
435
408
  ```
436
409
 
437
- `id`, `createdAt`, or `updatedAt` should be defined as attributes on
438
- the `attributes` property, but will be moved to the item on mapping. If any of
439
- these are not defined, default values will be used; a UUID for `id` and the
440
- current timestamp for `createdAt` and `updatedAt`.
441
-
442
- Data from the service may come in a different format than what is
443
- [required by Integreat](<(#the-data-format)>), so specify a [`path`](#paths) to
444
- point to the right value for each attribute and relationship. These values will
445
- be cast to the right schema after all mapping, mutating, and transforming is
446
- done. The value of each attribute or relationship should be in a format that can
447
- be coerced to the type defined in the schema. The `transform` pipeline may be
448
- used to accomplish this, but it is sufficient to return something that can be
449
- cast to the right type. E.g. returning `'3'` for an integer is okay, as
450
- Integreat will cast it with `parseInt()`.
451
-
452
- The `param` property is an alternative to specifying a `path`, and refers to a
453
- param passed to the `retreive` method. Instead of retrieving a value from the
454
- service data, an attribute or relationship with `param` will get its value from
455
- the corresponding parameter. When sending data _to_ a service, this
456
- attribute/relationship will be disregarded.
457
-
458
- Most of the time, your `attributes` and `relationships` definitions will only
459
- have the `path` property, so providing the `path` string instead of an object
460
- is a useful shorthand for this. I.e. `{title: 'article.headline'}` translates to
461
- `{title: {path: 'article.headline'}}`.
462
-
463
- The `toService` section behaves just like `attributes` and `relationships`,
464
- except that it is only used when mapping _to_ a service. This is where you'll
465
- put mappings that don't have a corresponding field in the schema you map
466
- to/from, e.g. root paths like `^params.service`.
467
-
468
- Mappings relate to both services and schemas, as the thing that binds them
469
- together, but it is the service definition that "owns" them. The `mappings`
470
- object on a service definition contains the ids of all relevant schemas as keys,
471
- and the value for these keys are either a mapping definition object or a mapping
472
- id. The `type` property of a mapping definition is optional when defined
473
- directly in a service definition, but should match the key if it is set.
474
-
475
- In some cases you may be able to reuse a mapping for several services or several
476
- types, by referring to the mapping id from several service definitions.
477
-
478
- ### Paths
479
-
480
- Mappings, attributes, and relationships all have an optional `path` property,
481
- for specifying what part of the data from the service to return in each case.
482
- (Endpoints may also have a `path` property, but not all adapters support this.)
483
-
484
- The `path` properties use a dot notation with array brackets.
485
-
486
- For example, with this data returned from the service ...
410
+ - `id`: The id used to reference this authentication, especially from the
411
+ [service definition](#services).
412
+ - `authenticator`: The id of an authenticator used to authenticate requests.
413
+ Integreat comes with a few basic ones built in, and there are others
414
+ available.
415
+ - `options`: An object of values meaningful to the authenticator. See the
416
+ documentation of each authenticator to learn how it should be configured.
487
417
 
488
- ```javascript
489
- const data = {
490
- sections: [
491
- {
492
- title: 'First section',
493
- articles: {
494
- items: [
495
- {id: 'article1', title: 'The title', body: {content: 'The text'}}
496
- ],
497
- ...
498
- }
499
- }
500
- ]
501
- }
502
- ```
418
+ Available authenticators:
503
419
 
504
- ... a valid path to retrieve all `items` of the first instance of `sections`
505
- would be `'sections[0].articles.items[]'` and to get the content of each item
506
- `'body.content'`.
420
+ - `options`: Will pass on the options as authentication, so whatever you provide
421
+ here is the authentication. What options to provide, then, is depending on
422
+ what the relevant transporter requires. This is built into Integreat.
423
+ - `token`: A simple way of authenticating with a given token. For HTTP requests,
424
+ the token will be provided as a `Authorization` header, and a configurable
425
+ prefix like `Basic` or `Bearer`. This is built into Integreat.
426
+ - [`jwt`](https://github.com/integreat-io/integreat-authenticator-jwt): Will
427
+ generate and encode a JavaScript Web Token (JWT) based on the options.
428
+ - [`oauth2`](https://github.com/integreat-io/integreat-authenticator-oauth2):
429
+ Will run the balett of calling different OAuth2 endpoints and receive a token
430
+ based on the provided options.
507
431
 
508
- The bracket notation offers some possibilities for filtering arrays:
432
+ ### Configuring service metadata
509
433
 
510
- - `[]` - Matches _all_ items in an array
511
- - `[0]` - Matches the item at index 0
512
- - `[1:3]` - Matches all items from index 1 to 3, not including 3.
513
- - `[-1]` - Matches the last item in the array.
514
- - `[id="ent1"]` - Matches the item with an id equal to `'ent1'`
434
+ Integreat supports getting and setting metadata for a service. The most common
435
+ use of this is to keep track of when data of a certain type was last synced.
515
436
 
516
- The bracket notation also offers two options for objects:
437
+ Some services may have support for storing their own metadata, but usually you
438
+ set up a dedicated service for storing other services' metadata. A few different
439
+ pieces goes into setting up a meta store:
517
440
 
518
- - `[keys]` - Matches all keys on an object
519
- - `[values]` - Matches all values for the object's keys
441
+ - A meta schema with the fields available as metadata
442
+ - A service for storing metadata, with an endpoint suporting the metadata schema
443
+ - Possible a metadata mutation for the metadata service
520
444
 
521
- When mapping data _to_ the service, the paths are used to reconstruct the data
522
- format the service expects. Only properties included in the paths will be
523
- created, so any additional properties must be set by a transform function or the
524
- adapter.
445
+ When all of this is set up, you activate the metadata on the service the
446
+ metadata will be stored for, by setting the `meta` property to the id of the
447
+ schema defining the metadata fields. The `service` set on the schema will tell
448
+ Integreat what service to get and set the metadata from/to.
525
449
 
526
- Arrays are reconstructed with any object or value at the first index, unless a
527
- single, non-negative index is specified in the path.
450
+ The schema will look something like this:
528
451
 
529
- Prefix a path with `^` to map with values from the mapping object, e.g.
530
- `^params.service`.
452
+ ```javascript
453
+ {
454
+ id: 'meta', // You may give it any id you'd like and reference it on the `meta` prop on the service
455
+ service: <id of service handling the metadata>,
456
+ shape: {
457
+ <metadataKey>: <type string>,
458
+ // ...
459
+ }
460
+ }
461
+ ```
531
462
 
532
- You may optionally supply alternative paths by providing an array of paths. If
533
- the first one does not match any properties in the data, the next path is tried,
534
- and so on.
463
+ To get or set metadata, use [`GET_META`](#get_meta) and [`SET_META`](#set_meta)
464
+ with the service you are getting metadata from as the `service`. Integreat will
465
+ figure out the rest.
535
466
 
536
- ### Qualifiers
467
+ ## Transporters
537
468
 
538
- When a service returns data for several schemas, Integreat needs a way to
539
- recognize which schema to use for each item in the data. For some services,
540
- the different schemas may be find on different paths in the data, so
541
- specifying different paths on each mapping is sufficient. But when all items
542
- are returned in one array, for instance, you need to specify qualifiers for
543
- the mappings.
469
+ A transporter handles all the details of sending and receiving data to and from
470
+ a service. When dispatching an action to a service, the action will be handled
471
+ in a relevant manner for the type of service the transporter supports, e.g.
472
+ sending an http requrest for the HTTP transporter, or doing a query to a
473
+ database for the MongoDb transporter. Some transporters may also support
474
+ listening to a service, e.g. the HTTP transporter listing for incoming requests
475
+ or the MQTT transporter subscribing to events on a topic.
544
476
 
545
- A qualifier is simply a path with an expression that will evaluate to true or
546
- false. If a mapping has qualifiers, it will only be applied to data that
547
- satisfies all its qualifiers. Qualifiers are applied to the data at the
548
- mapping's path, before it is mapped and transformed.
477
+ Integreat has transporters for some common cases, and more may come:
549
478
 
550
- An example of two mappings with qualifiers:
479
+ - [Bull](https://github.com/integreat-io/integreat-transporter-bull)
480
+ - [FTP](https://github.com/integreat-io/integreat-transporter-ftp)
481
+ - [HTTP](https://github.com/integreat-io/integreat-transporter-http)
482
+ - [MongoDb](https://github.com/integreat-io/integreat-transporter-mongodb)
483
+ - [MQTT](https://github.com/integreat-io/integreat-transporter-mqtt)
484
+ - [Redis](https://github.com/integreat-io/integreat-transporter-redis)
551
485
 
552
- ```
553
- ...
554
- mappings: {
555
- entry: {
556
- attributes: {...},
557
- qualifier: 'type="entry"'
558
- },
559
- admin: {
560
- attributes: {...},
561
- qualifier: [
562
- 'type="account"',
563
- 'permissions.roles[]="admin"'
564
- ]
565
- }
566
- }
567
- ```
486
+ You may write your own transporters if your case is not covered by any of these.
487
+ Documentation on developing transporters are coming.
568
488
 
569
- When a qualifier points to an array, the qualifier returns true when at least
570
- one of the items in the array satisfies the condition.
489
+ Integreat will handle the transporters based on you configurations, but there
490
+ are some specifics to each transporter, like HTTP needing an `uri` option or
491
+ MongoDb needing a `collection` option. See the documentation of each transporter
492
+ for more.
571
493
 
572
- ### Configuring metadata
494
+ ## Adapters
573
495
 
574
- If a service may send and receive metadata, set the `meta` property to the id of
575
- a schema defining the metadata as attributes.
496
+ Adapters are working together with transporters to prepare the incoming and
497
+ outgoing data in accordance with the type of services they support.
498
+
499
+ As an example, the HTTP transporter will return data from a response as a
500
+ string, since there is no common way to treat the response body. So for a JSON
501
+ API, you will configure the JSON adapter to make sure the data from the
502
+ mutations are sent as a JSON string, and that the JSON comming back from the
503
+ service is parsed before mutation starts. For a service using XML, you would
504
+ instead set up the XML adapter, and perhaps also the SOAP adapter, to again
505
+ stringify and parse the data going back and forth.
506
+
507
+ The MongoDb transporter, on the other hand, does not require any adapters, as
508
+ documents from the database will always come as arrays and object, and may be
509
+ fed directly into the mutation pipelines.
510
+
511
+ Integreat currently have the following adapters:
512
+
513
+ - [CSV](https://github.com/integreat-io/integreat-adapter-csv)
514
+ - [JSON](https://github.com/integreat-io/integreat-adapter-json)
515
+ - [SOAP](https://github.com/integreat-io/integreat-adapter-soap)
516
+ - [Url encoded form data](https://github.com/integreat-io/integreat-adapter-form)
517
+ - [XML](https://github.com/integreat-io/integreat-adapter-xml)
518
+
519
+ You may write your own adapters as well, and documentation on this is coming.
520
+
521
+ ## Mutations
522
+
523
+ Both on the service and on endpoints, you define mutation pipelines. The service
524
+ mutation is run before the endpoint mutation for data coming from a service, and
525
+ in the oposite order when going to a service.
526
+
527
+ A nice, but sometimes complicated, thing about mutations, is that they are run
528
+ in both directions. They are by default defined for mutating data coming _from_
529
+ a service, and will be run in reverse for data going _to_ a service. In some
530
+ cases this reversing of the pipeline will work as expected without modifications
531
+ -- you define the mutation pipeline for data coming _from_ the service, and the
532
+ reversed pipeline works _to_ as well. But many times you need to make
533
+ adjustments and sometimes you'll have to have separate steps based on the
534
+ direction. We'll get into more details in the following.
535
+
536
+ A mutation pipeline consists of one or more steps that the data will go through,
537
+ before coming out on the other in the desired shape. It helps picturing this as
538
+ an actual pipeline. After each step, data will be in a different shape, and this
539
+ is the input to the next step.
540
+
541
+ You define a pipeline in Integreat with an array, although for a pipeline with
542
+ only one step, you may skip the array for simplicity.
543
+
544
+ Each step may be one of the following:
545
+
546
+ - [**A dot notation path**](#dot-notation-paths), e.g. `path.to.data`. The data
547
+ at that path will be extracted, and will be provided as the data to the next
548
+ step in the pipeline. When going in reverse, the data will be set on that path
549
+ instead.
550
+ - **A mutation object** is an object that basically describes the object you
551
+ want as a result, where the keys are dot notation paths and the values are
552
+ mutation pipelines. Each pipeline on the mutation object will be run on the
553
+ data, and then set on the path, resulting in an object that will be passed on
554
+ to the next step.
555
+ - **A transform object** letting you run a transformer function on the data,
556
+ e.g. `{ $transform: 'number' }` to transform the value into a number, or
557
+ `undefined` if not possible.
558
+ - **A filter object** that will run a transformer function on the data and
559
+ filter away any items not resulting in a truthy value. As an example,
560
+ `{ $filter: 'boolean' }` will filter away anything that is not convertable to
561
+ `true` in JS rules. When applied to an array, you'll get an array where items
562
+ are filtered away. For an object or a plain value, filtering away will means
563
+ `undefined` is passed on to the next step in the pipeline.
564
+ - **An if object** that runs a `then` pipeline if the provided pipeline returns
565
+ truthy, and the `else` pipeline if it returns falsy.
566
+ - **A cast object**, e.g. `{ $cast: 'author' }` that casts the data into a
567
+ schema, removing all properties that is not part of the shape of the schema,
568
+ and transforming all values to the expected types or `undefined` if not
569
+ possible.
570
+
571
+ ### Dot notation paths
572
+
573
+ At its most basic, a dot notation path is just a property key, like `content`.
574
+ You may dive into a data structure by adding a key from the next level,
575
+ separated by a dot, like `content.articles`. With an object like this:
576
576
 
577
- ```
577
+ ```javascript
578
578
  {
579
- id: 'meta',
580
- service: <id of service handling the metadata>,
581
- attributes: {
582
- <metadataKey>: {
583
- type: <string>
584
- }
579
+ content: {
580
+ articles: [{ id: '1' }, { id: '2' }],
581
+ authors: [{ id: 'john' }]
585
582
  }
586
583
  }
587
584
  ```
588
585
 
589
- The `service` property on the type defines the service that holds metadata for
590
- this type. In some cases the service you're defining metadata for and the service
591
- handling these metadata will be the same, but it is possible to let a service
592
- handle other services' metadata. If you're getting data from a read-only service,
593
- but need to, for instance, set the `lastSyncedAt` metadata for this store,
594
- you'll set up a service as a store for this (the store may also hold other types
595
- of data). Then the read-only store will be defined with `meta='meta'`, and the
596
- `meta` schema will have `service='store'`.
597
-
598
- It will usually make no sense to specify default values for metadata.
586
+ ... the path `content.articles` will give you the array
587
+ `[{ id: '1' }, { id: '2' }]`.
599
588
 
600
- As with other data received and sent to services, make sure to include endpoints
601
- for the service that will hold the metadata, matching the `GET_META` and
602
- `SET_META` actions, or the schema defining the metadata. The way you set up
603
- these endpoints will depend on your service.
589
+ You may add brackets to the path to traverse into arrays, e.g.
590
+ `content.articles[0]` will give you the object `{ id: '1' }`, and
591
+ `content.articles[0].id` will give you `'1'`.
604
592
 
605
- Also define a [mapping](#mapping-definition) between this schema and the
606
- service. You may leave out `attributes` and `relationships` definitions and the
607
- service will receive the metadata in Integreat's standard format:
593
+ Empty brackets, like `content.articles[]` will ensure that you get an array
594
+ back. If the data at the path is an array, this will return the same as
595
+ `content.articles`, but if the path returns an object or a plain value, it will
596
+ be returned in an array.
608
597
 
609
- ```
610
- {
611
- id: <serviceId>,
612
- type: <meta type>,
613
- createdAt: <date>,
614
- updatedAt: <date>,
615
- attributes: {
616
- <key>: <value>
617
- }
618
- }
619
- ```
598
+ When mapping data _to_ a service, the paths are used to reconstruct the data
599
+ format the service expects. Only properties included in the paths will be
600
+ created.
620
601
 
621
- Finally, if a service will not have metadata, simply set `meta` to null or skip
622
- it all together.
602
+ Arrays are reconstructed with any object or value at the first index, unless a
603
+ single, non-negative index is specified in the path.
623
604
 
624
- ## Idents and security rules
605
+ You may use a carret `^` to go one level up -- to the parent -- in the data
606
+ (after going down), so after `content.articles`, the path `^.authors` will
607
+ return `[{ id: 'john' }]`. Arrays count as one level, so after
608
+ `content.articles[0]` you will need to go up twice like so: `^.^.authors`.
625
609
 
626
- An ident in Integreat is basically an id unique to one participant in the
627
- security scheme. It is represented by an object, that may also have other
628
- properties to describe the ident's permissions, or to make it possible to map
629
- identities in other systems, to an Integreat ident.
610
+ A double carret `^^` takes you to the top -- the root -- so after
611
+ `content.articles[0].id`, `^^.content.authors` returns `[{ id: 'john' }]`.
630
612
 
631
- Example ident:
613
+ Carret notations -- parents and roots -- does not currently work in reverse, but
614
+ they might in a future version.
632
615
 
633
- ```javascript
634
- {
635
- id: 'ident1',
636
- tokens: ['facebook|12345', 'twitter|23456'],
637
- roles: ['admin']
638
- }
639
- ```
616
+ ### Jobs
640
617
 
641
- The actual value of the `id` is irrelevant to Integreat, as long as it is a
642
- string with A-Z, a-z, 0-9, \_, and -, and it's unique within one Integreat
643
- configuration. This means that mapped value from services may be used as ident
644
- ids, but be careful to set this up right.
618
+ > Editor's note: Write this.
645
619
 
646
- `tokens` are other values that may identify this ident. E.g., an api that uses
647
- Twitter OAuth to identify it's users, may provide the `'twitter|23456'` token in
648
- the example above, which will be replaced with this ident when it enters
649
- Integreat.
620
+ ## Schemas
650
621
 
651
- `roles` are an example of how idents are given permissions. The roles are
652
- custom defined per setup, and may be mapped to roles from other systems. When
653
- setting the auth rules for a service, roles may be used to require that
654
- the request to get data of this schema, an ident with the role `admin` must
655
- be provided.
622
+ A central idea to Integreat, is that any integration has two sides; the getting
623
+ of data from one service and the sending of data to another. Instead of setting
624
+ up an integration directly from A to B, you have a schema in middle, and
625
+ configure how data from A will be mutated to a schema, and then have data in
626
+ that schema will be mutated and sent to B.
656
627
 
657
- Idents may be supplied with an action on the `meta.ident` property. It's up to
658
- the code dispatching an action to get hold of the properties of an ident in a
659
- secure way. Once Integreat receives an ident, it will assume this is accurate
660
- information and uphold its part of the security agreement and only return data
661
- and execute actions that the ident have permissions for.
628
+ This is a useful abstraction, and if you ever need to change one side, you can
629
+ do so without involving the other side. If you need to switch out service B with
630
+ service C, you can do so without involving the configuration of service A, or
631
+ you can send data to both B and C, using the same setup for service A.
662
632
 
663
- ### Access rules
633
+ To be clear, you can setup flows without schemas in Integreat, but at the loss
634
+ of this flexibility and maintainability.
664
635
 
665
- Access rules are defined with properties telling Integreat which rights to
666
- require when performing different actions with a given schema. It may be set
667
- as a overall right to do anything with a schema, or it may be specified on the
668
- different action types available: GET, SET, and DELETE, including actions that
669
- start with these verbs.
636
+ A schema describe the data you expected to get out of Integreat, or send through
637
+ it. You basically define the fields and their types, and may then cast data to
638
+ that shape. Note that data on an action for a specified type, will be
639
+ automatically cast to that type.
670
640
 
671
- Note that this applies to the actual actions being sent to a service – some
672
- actions will never reach a service, but will trigger other actions, and access
673
- will be granted or refused to each of these actions as they reach the service
674
- interface, but not to the triggering action.
641
+ ```javascript
642
+ {
643
+ id: <schema id>,
644
+ plural: <the id in plural>,
645
+ service: <the default service for this schema>,
646
+ shape: {
647
+ <fieldId>: <field type>,
648
+ <fieldId>: {
649
+ $type: <field type>,
650
+ default: <default value>
651
+ const: <value that will override any other value>
652
+ },
653
+ },
654
+ access: <access def>
655
+ }
656
+ ```
675
657
 
676
- An access definition for letting all authorized idents to GET, but requiring
677
- the role `admin` for SETs:
658
+ - `id`: The id of the schema, used to reference it in actions (the payload
659
+ `type`), when casting to the schema with `{ $type: '<schema id>' }`, and to
660
+ signal what schema a data object is cast to (the `$type` prop on typed data
661
+ items). The convention is to use singular mode for the `id`, e.g. if your
662
+ defining a schema for articles, you would give it the id `'article'`.
663
+ - `plural`: When the plural of `id` is not simply a matter of adding an `'s'`,
664
+ you may specify the plural mode here. E.g. `id: 'entry'` would have
665
+ `plural: 'entries'`. This is not used by Integreat right now, but it may be
666
+ used in the future for error messages, generating APIs from schemas, etc.
667
+ - `service`: You may specify a default service for the schema when it makes
668
+ sense. This allows you to dispatch an action for a type without specifying the
669
+ target service, e.g. `{ type: 'GET', payload: { type: 'article' } }`, and have
670
+ Integreat use the default service. This is a way of hiding configuration
671
+ details from the code dispatching the actions, and you may also change the
672
+ default service without changing the dispatching code if need be. You may
673
+ always override this by specifying a `service` on the action payload.
674
+ - `shape`: This is where you define all the fields, see
675
+ [the section below](#the-shape-of-a-schema).
676
+ - `generateId`: Set this to `true` to generate a unique id for the `id` field
677
+ when the data being cast does not provide an `id`. Default is `false`, which
678
+ will just set `id: null`
679
+ - `access`: Integreat lets you define authorization schemes per schema. All use
680
+ of data cast to a schema will then be controlled by the rules you set here.
681
+ See [Access rules](#access-rules) below for details on these rules. Note that
682
+ `access` is optional, but when you get data from a service where any form of
683
+ authentication is used to access the data, you will not be able to do anything
684
+ with the data unless you cast it to a schema with `access` set up (or
685
+ specifically says that you allow raw data from that endpoint).
686
+
687
+ ### The shape of a schema
688
+
689
+ The shape is defined by an object where each key is the id of a field, which may
690
+ contain only alphanumeric characters, and may not start with a digit. A schema
691
+ cannot have the same id as a primitive type (see list below).
692
+
693
+ The values on this object define the types of the fields and a few other
694
+ optional features:
678
695
 
679
696
  ```javascript
680
697
  {
681
- id: 'access1',
682
- actions: {
683
- GET: {allow: 'auth'},
684
- SET: {role: 'admin'}
685
- }
698
+ $type: <field type>,
699
+ default: <default value>
700
+ const: <value that will override any other value>
686
701
  }
687
702
  ```
688
703
 
689
- To use these access rules, set the definition object directly on the `access`
690
- property, of an schema, or set `access: 'access1'` on the relevant schema(s).
691
- The `id` is only needed in the latter case.
704
+ The `$type` prop sets the type of the field. The available primitive types, are
705
+ `string`, `integer`, `float` (or `number`), `boolean`, and `date`. A field may
706
+ also have another schema as its type, in which case the id of the schema is set
707
+ in `$type`. An example can be an
708
+ `article` schema with an `author` field of type `user`, referring to a schema
709
+ with id `user`. When casting the `article`, data on the `author` prop will be
710
+ cast with the `user` schema.
711
+
712
+ The `default` value will be used when the field is `undefined`, `null`, or not
713
+ preset in data object being cast to this schema. If `default` is set to a
714
+ function, the function will be run with no argument, and the returned value is
715
+ used as the default value. When no `default` is given, `undefined` is used.
716
+
717
+ The `const` value override any value you provide to the field. It may be useful
718
+ if you want a field to always have a fixed value. Just as for `default`, you may
719
+ set it to a function, in which case the function will be run without arguments
720
+ and the returned value will be used.
692
721
 
693
- **Note:** Referring to access rules by id is not implemented yet.
722
+ If both `const` and `default` are set, `const` will be used.
694
723
 
695
- For rules that treat every action the same, set the props on the access object
696
- directly. This will also define a default for actions not defined specifically.
724
+ When only setting the field type, you don't need to provide the entire object,
725
+ you can just provide the type string.
697
726
 
698
- In the example above, no one will be allowed to DELETE. A better way to
699
- achieve what we aimed for above, could be:
727
+ Example schema:
700
728
 
701
729
  ```javascript
702
730
  {
703
- id: 'access2',
704
- role: 'admin',
705
- actions: {
706
- GET: {allow: 'auth'}
707
- }
731
+ id: 'article',
732
+ shape: {
733
+ id: 'string', // Not needed, as it is always provided, but it's good to include for clarity
734
+ title: { $type: 'string', default: 'Unnamed article' },
735
+ text: 'string',
736
+ readCount: 'integer',
737
+ archived: { $type: 'boolean', default: false },
738
+ rating: 'float',
739
+ createdAt: 'date',
740
+ updatedAt: 'date'
741
+ },
742
+ access: 'all'
708
743
  }
709
744
  ```
710
745
 
711
- In this example, all actions are allowed for admins, but anyone else that is
712
- authenticated may GET.
746
+ Note that if you provide the `id` field, it should be set to type `'string'` or
747
+ Integreat will throw. The same happens if you set `createdAt` or `updatedAt` to
748
+ anything else than the type `'date'`. If you don't include these fields,
749
+ Integreat will include the `id` for you, but not `createdAt` or `updatedAt`.
713
750
 
714
- Available rule props:
751
+ ### Typed data
715
752
 
716
- - `role` - Authorize only idents with this role. May be an array of strings
717
- (array is not implemented).
718
- - `ident` - Authorize only idents with this precise id. May be an array (array
719
- is not implemented).
720
- - `roleFromField` - Specify the field name (attribute or relationship) on
721
- the schema, that will hold the role value. When authorizing a data item with
722
- an ident, the field value on the item must match a role on the ident.
723
- - `identFromField` - The same as `roleFromField`, but for an ident id.
724
- - `allow` - Set to `all`, `auth`, or `none`, to give access to everybody, only
725
- the authenticated, or no one at all. This is also available in short form –
726
- use this string instead of a access rule object.
727
-
728
- Another example, intended for authorizing only the ident matching an account:
753
+ When data is cast to a schema, the data will be in the following format:
729
754
 
730
- ```javascript
755
+ ```
731
756
  {
732
- id: 'accountAccess',
733
- identFromField: 'id'
757
+ id: <string>,
758
+ $type: <schema>,
759
+ createdAt: <date>,
760
+ updatedAt: <date>,
761
+ <key>: <value>,
762
+ <key>: { id: <string>, $ref: <schema> },
763
+ <key: [{ id: <string>, $type: <schema>, ... }],
764
+ ...
734
765
  }
735
766
  ```
736
767
 
737
- When used with e.g. an `account` schema, given that the id of the account is
738
- used as ident id, only an ident with the same id as the account, will have
739
- access to it.
768
+ - `id`: The id is mandatory and created by Integreat when it is not included in
769
+ the schema. If you don't map anything to the id prop, it will be set to
770
+ `null`, unless the schema is set up with `generateId: true`, in which case a
771
+ universally unique id will be generated for you.
772
+ - `$type`: Set to the id of the schema by Integreat. This is a signal that the
773
+ data has been cast.
774
+ - `createdAt`: This is not mandatory, but has special meaning. When a schema has
775
+ a `createdAt` field, but the date is not set in the data, it will be set to
776
+ the same as `updatedAt` (if provided) or to the current date/time.
777
+ - `updatedAt`: Just as `createdAt`, this is not mandatory. When a schema has
778
+ an `updatedAt` field, and the date is not set in the data, it will be set to
779
+ the same as `createdAt` (if provided) or the current date/time.
780
+ - `<key>`: Then follows the values of all the fields specified in the schema.
781
+ Any value not provided in the data will be set to their default value or
782
+ `undefined`. Fields with other schemas as their type, will be an object. If
783
+ only the id is provided in the data, the `{ id: <string>, $ref: <schema id> }`
784
+ format will be used, with `$ref` being the id of the field type schema. When
785
+ more data is provided, Integreat will cast it to the target schema and provide
786
+ the entire data object, or array of objects, with the relevant `$type`.
740
787
 
741
- ### Persisting idents
788
+ ### Access rules
742
789
 
743
- A security scheme with no way of storing the permissions given to each ident,
744
- is of little value. (The only case where this would suffice, is when every
745
- relevant service provided the same ident id, and authorization where done on the
746
- ident id only.)
790
+ Set the `access` property on a schema to enforce permission checking. This
791
+ applies to any service that provides data in this schema.
747
792
 
748
- Unsurprisingly, Integreat uses schemas and services to store idents. In the
749
- definition object passed to `Integreat.create()`, set the id of the schema to
750
- use with idents, on `ident.schema`.
793
+ The simplest access rule is `auth`, which means that anyone can do anything with
794
+ the data of this schema, as long as they are authenticated. Being authenticated
795
+ in this context, means that the dispatched action has an `ident` in the `meta`
796
+ object. See [the section on idents](#idents) for more on this.
751
797
 
752
- In addition, you may define what fields (attributes or relationships) will
753
- match the different props on an ident:
798
+ Example of a schema with an access rule:
754
799
 
755
800
  ```javascript
756
801
  {
757
- ...,
758
- ident: {
759
- type: 'account',
760
- props: {
761
- id: 'id',
762
- roles: 'groups',
763
- tokens: 'tokens'
764
- }
765
- }
802
+ id: 'article',
803
+ shape: {
804
+ // ...
805
+ },
806
+ access: 'auth'
766
807
  }
767
808
  ```
768
809
 
769
- When the prop and the field has the same name, it may be omitted, though it
770
- doesn't hurt to specify it anyway for clarity. The service still have the
771
- final word, as any field that is not defined on the schema, will not survive
772
- casting.
773
-
774
- Note that in the example above, the `id` of the data will be used as the ident
775
- `id`. When the id is not suited for this, you will need another field on the
776
- schema that may act as the ident id. In cases where you need to transform the
777
- id from the data in some way, this must be set up as a separate field and the
778
- mapping definition will dictate how to transform it. In most cases, the `id`
779
- will do, though.
780
-
781
- The `service` specified on the schema, will be where the ident are stored,
782
- although that's not a precise way of putting it. The ident is never stored, but
783
- a data item of the specified schema is. The point is just that the ident
784
- system will get the relevant data item and get the relevant fields from it. In
785
- the same way, when storing an ident, a data item of the specified type is
786
- updated with props from the ident and then sent to the service.
810
+ To signal that the schema really has no need for authorization, use `all`.
811
+ This is not the same as not setting the `auth` prop, as `all` will override
812
+ Integreat's principle of not letting authorized data out of Integreat without
813
+ an access rule. `all` allows anybody to access the data, even the
814
+ unauthenticated.
815
+
816
+ On the other end of the spectrum, `none` will allow no one to access data cast
817
+ to this schema, no matter who they are.
818
+
819
+ For more fine-grained rules, set `access` to an access definition object with
820
+ rules telling Integreat which rights to require when performing different
821
+ actions with a given schema. These rules apply to the [idents](#idents) set on
822
+ the action `meta` object.
823
+
824
+ The following access rule props are available:
825
+
826
+ - `allow`: Set to `all`, `auth`, or `none`, to give access to everybody, only
827
+ the authenticated, or no one at all. This is what we describe in short form
828
+ above, where we provided this string instead of a access rule object.
829
+ - `role`: Authorize only idents with this `role`. May also be an array.
830
+ - `ident`: Authorize only idents with this precise `id`. May also be an array.
831
+ - `roleFromField`: Same as `role`, except the role is gotten from a field in the
832
+ schema. When authorizing data cast to this schema, the value of the role field
833
+ needs to be identical to (one of) the role(s) of the ident.
834
+ - `identFromField` - The same as `roleFromField`, but for an ident id.
787
835
 
788
- For some setups, this requires certain endpoints to be defined on the service.
789
- To match a token with an ident, the service must have an endpoint that matches
790
- actions like this:
836
+ In addition, you may override the general access rules of a schema with specific
837
+ rules for a type of action, by setting an `action` object with access rules for
838
+ action types. Here's an example of an access definition for allowing all
839
+ authorized idents to `GET` data in a certain shema, requiring the role `admin`
840
+ for `SET`s, and disallowing all other actions with the general rule
841
+ `allow: 'none'`:
791
842
 
792
843
  ```javascript
793
844
  {
794
- type: 'GET',
795
- payload: {
796
- type: 'account',
797
- params: {tokens: 'twitter|23456'}
845
+ id: 'article',
846
+ shape: {
847
+ // ...
848
+ },
849
+ access: {
850
+ allow: 'none',
851
+ actions: {
852
+ GET: { allow: 'auth' },
853
+ SET: { role: 'admin' }
854
+ }
798
855
  }
799
856
  }
800
857
  ```
801
858
 
802
- In this case, `account` is the schema mapped to idents, and the `tokens`
803
- property on the ident is mapped to the `tokens` field on the schema.
859
+ Note that these action specific rules only applies to actions being sent to a
860
+ service. Some actions will never reach a service, but will instead trigger other
861
+ actions, and access will be granted or rejected only for the actions that are
862
+ about to be sent to a service. E.g. when you dispatch a `SYNC` action, it starts
863
+ off by dispatching one or more `GET` actions. The `SYNC` action is not subjected
864
+ to any access rules, but the `GET` actions are, and so the `SYNC` will fail if
865
+ one of the `GET` is rejected.
804
866
 
805
- To make Integreat complete idents on actions with the persisted data, set it up
806
- with the `completeIdent` middleware:
867
+ Another example, intended for authorizing only the ident matching a user:
807
868
 
808
869
  ```javascript
809
- const great = Integreat.create(defs, resources, [
810
- integreat.middleware.completeIdent,
811
- ])
870
+ {
871
+ id: 'user',
872
+ shape: {
873
+ // ...
874
+ },
875
+ access: { identFromField: 'id' }
876
+ }
812
877
  ```
813
878
 
814
- This middleware will intercept any action with `meta.ident` and replace it with
815
- the ident item loaded from the designated schema. If the ident has an `id`,
816
- the ident with this id is loaded, otherwise a `withToken` is used to load the
817
- ident with the specified token. If no ident is found, the original ident is
818
- kept.
879
+ Here, only actions where the ident id is the same as the id of the user data,
880
+ will be allowed. This means that authenticated users (idents) may only
881
+ only access their own user data.
819
882
 
820
883
  ## Actions
821
884
 
822
- Actions are serializable objects that are dispatched to Integreat, and may be
823
- queued when appropriate. It is a key point that they are serializable, as they
824
- allows them to be put in a database persisted queue and be picked up of another
825
- Intergreat instance in another process.
885
+ Actions are serializable objects that are dispatched to Integreat. It is a
886
+ important that they are serializable, as this allows them to, for instance, be
887
+ put in a database persisted queue and be picked up of another Intergreat
888
+ instance in another process. Note that `Date` objects are considered
889
+ serializable, as they are converted to ISO date strings when needed.
826
890
 
827
891
  An action looks like this:
828
892
 
829
- ```
893
+ ```javascript
830
894
  {
831
- type: <actionType>,
832
- payload: <payload>,
833
- meta: <meta properties>
895
+ type: <action type>,
896
+ payload: <payload object>,
897
+ meta: <meta object>
834
898
  }
835
899
  ```
836
900
 
837
- `type` is one [of the action types](#available-actions) that comes with
838
- Integreat and `payload` are data for this action.
839
-
840
- The `meta` object is for properties that does not belong in the payload. You may
841
- add your own properties here, but be aware that some properties are already
842
- used by Integreat, and more may be added in the future.
901
+ - `type`: This is the id of one [of the action handlers](#available-action-handlers)
902
+ that comes with Integreat, e.g. `GET`. When you dispatch an action, it is
903
+ handed off to this handler (after some inital preperation). You may write
904
+ your own action handlers as well.
905
+ - `payload`: Holds parameters and [data](#typed-data) for this action. There are
906
+ some reserved [payload properties](#payload-properties), and the rest will be
907
+ made available to you in the mutation pipeline.
908
+ - `meta`: Holds information about the action that does not belong in the
909
+ payload, like the ident of the user dispatching, action id, etc. There are
910
+ some reserved [meta properties](#meta-properties), but you may add your own
911
+ here too.
912
+
913
+ When an action is dispatched, it returns a [response object](#action-response)
914
+ with status, data, error message, etc.
915
+
916
+ Note that in a mutation pipeline, action handler, or middleware, the
917
+ response object is provided as a fourth property on the action. You will most
918
+ likely meet this at least when setting up mutations.
919
+
920
+ ### Payload properties
921
+
922
+ The payload is, together with the action `type`, a description to Integreat and
923
+ the service of what to do. A design principle of Integreat has been to have as
924
+ little specifics in these payload, so actions may be discpatched to service
925
+ without knowing how the service works. This is not always possible, at least not
926
+ yet, but it's a good principle to follow, also when you configure services and
927
+ plan what props need to be sent in the action payload.
928
+
929
+ You may set any properties on the payload, and they will be be available to you
930
+ in the service endpoint match and in the service mutations. Some properties have
931
+ special meanings, though, and you should avoid using them for anything else:
932
+
933
+ - `type`: The type of the data the action sends and/or receives. This refers to
934
+ the `id` of a schema, and you will usually want to set this. Data provided
935
+ in the payload `data` and response `data` will be cast to this schema. If
936
+ you're dealing with several types in one action, you may set an array of
937
+ types, but will have to cast the data in the mutation yourself. Integreat will
938
+ validate that the data you send and receive is indeed of that type, and will
939
+ give you an auth error if not. (See
940
+ [`allowRawRequest` and `allowRawResponse` on endpoints](#endpoints) for an
941
+ exception.)
942
+ - `id`: You provide an id when you want to address a specific data item, usually
943
+ when you want to fetch one data item with an action like
944
+ `{ type: 'GET', payload: { type: 'article', id: '12345' } }`. You may also
945
+ supply an array of ids to fetch several data items by id. When setting data,
946
+ the id will instead be specified in the `data` when appropriate.
947
+ - `data`: The data to send to a service. This may be any data that makes sense
948
+ to the service, but will usually be a [typed data object](#typed-data) or an
949
+ array of typed data objects, where the adjustments for the service happens in
950
+ service mutations.
951
+ - `service`: The `id` of the service to send this action to. If not specified,
952
+ Integreat will try and find the right service from the `type`.
953
+ - `targetService`: An alias of `service`.
954
+ - `sourceService`: When data comes from a different service and has not been
955
+ mutated and cast yet, the `sourceService` property will tell Integreat to run
956
+ the data through the source service configuration before passing the action on
957
+ to an action handler. An example may be data coming in through an API, where
958
+ the API is configured as a service in Integreat. Note that this property is
959
+ usually set by transporters in their `listen()` methods, but you may also set
960
+ it directly on the action when it makes sense.
961
+ - `endpoint`: Set this to the `id` of a service endpoint when you want to
962
+ override the endpoint match rules of Integreat. This should only be used when
963
+ it is really necessary. Normally, you should instead design the match
964
+ properties to match the correct actions.
965
+
966
+ For services that support pagination, i.e. fetching data in several rounds, one
967
+ page at a time, the following properties may be supported:
968
+
969
+ - `pageSize`: The number of data items to fetch in one request to the service.
970
+ By specifying a page size, you signal that you would like to use pagination,
971
+ and without it all other pagination properties will be disregarded. You will
972
+ get the number of data items you specify (or less, if there are not that many
973
+ items), and may then go on to dispatch an action for the next page. See
974
+ [pagination](#pagination) for more
975
+ - `pageOffset`: The number of data items to "skip" before returning the number
976
+ of items specified in `pageSize`. If you ask for 500 items, the first action
977
+ should have `pageOffset: 0` (or not specified), the next action
978
+ `pageOffset: 500`, then `pageOffset: 1000`, and so on.
979
+ - `page`: The index of the page to fetch. Unlike most other indexes, this starts
980
+ with `1` being the first page. The effect is the same as `pageOffset`, it's
981
+ just a different way of specifying it. `page: 1` is the same as
982
+ `pageOffset: 0`, and `page: 2` is the same as `pageOffset: 500`, given a
983
+ `pageSize: 500`. Integreat will actually calculate both before sending it to
984
+ the transporter, as different types of services support different types of
985
+ pagination.
986
+ - `pageAfter`: As an alternative to specifying the number of items to skip, you
987
+ may ask for the items after the item with the id you provide as `pageAfter`.
988
+ If the last item of the first page is `'12345'`, you may set
989
+ `pageAfter: '12345'` to get the next page.
990
+ - `pageBefore`: This works the same as `pageAfter`, except it is intended for
991
+ when your going backward and fetching a number items _before_ the id you
992
+ provide.
993
+ - `pageId`: Some services and/or transporters will return an id for the next
994
+ page, as an alternative to the other properties mentioned above. You then
995
+ apply this id as `pageId` when dispatching the action for the next page. Note
996
+ that this id may hold internal logic from the transporter, but you should
997
+ never rely on this logic and simply use it as an id.
998
+
999
+ > **Important note:** Pagination has to be supported by the service and your
1000
+ > service configuration, and sometimes also the transporter. Integreat prepares
1001
+ > and passes on these pagination properties, but if the service disregards them,
1002
+ > there is little Integreat can do – except limiting the number of items
1003
+ > returned. It's up to you to figure out how to configure pagination for a
1004
+ > service, but youshould use these pagination properties to support it, to make
1005
+ > this predictable. It also lets you use actions such as `GET_ALL`, that support
1006
+ > pagination.
1007
+
1008
+ Finally, there are some properties that has no special meaning to Integreat
1009
+ itself, but that may be set on incoming actions from transporters. These should
1010
+ ideally be used in the same way or avoided:
1011
+
1012
+ - `contentType`: A keyword for the type of content in the `data` property. E.g.
1013
+ `application/json` or `text/plain`.
1014
+ - `headers`: An object of header information, given as key/value pairs. The
1015
+ value may be a string or an array of strings. This may be HTTP headers or any
1016
+ other type of header information that makes sense to a service.
1017
+ - `hostname`: The host name that incoming request was sent to. For HTTP, this
1018
+ will be the domain name the request was sent to.
1019
+ - `method`: The method of the incoming request. The HTTP transporter will set
1020
+ this to `GET`, `POST`, `PUT`, etc. from the incoming request.
1021
+ - `path`: The path from the incoming request. For the HTTP transporter, this
1022
+ will be the part of the url after the domain name, like
1023
+ `'/v1.0/articles/12345'`.
1024
+ - `port`: The port number of the incoming request.
1025
+ - `queryParams`: An object of query params from the incoming request, usually
1026
+ key/value pairs where the value is a string or an array of strings. For HTTP,
1027
+ this will be the part after the question mark.
1028
+
1029
+ ### Meta properties
1030
+
1031
+ The action meta object is for information about an action that does not directly
1032
+ define the action itself. The difference may be subtle in some cases, but the
1033
+ general rule is a piece of information affects how the action is run, it should
1034
+ be in the payload. E.g. the type of items to fetch is in the payload, while the
1035
+ time the action was dispatched would go in the meta.
1036
+
1037
+ This rule does not always hold, e.g. for information on the user dispatching the
1038
+ action in `ident` on the meta object. Different idents may result in different
1039
+ data being returned from the service, but still the action to perform is the
1040
+ same, so it makes sense to have the ident in the meta object.
1041
+
1042
+ You may set your own meta properties, but in most cases you'll probably rather
1043
+ set payload properties.
843
1044
 
844
1045
  Current meta properties reserved by Integreat:
845
1046
 
846
- - `id`: Assigning the action an id. Will be picked up when queueing.
847
- - `queue`: Signals that an action may be queued. May be `true` or a timestamp
848
- - `queuedAt`: Timestamp for when the action was pushed to the queue
849
- - `schedule`: A [schedule definition](#schedule-definition)
850
- - `ident`: The ident to authorize the action with
1047
+ - `ident`: The ident to authorize the action with. May hold an `id`, `roles`,
1048
+ `tokens`, and a few other options. See
1049
+ [the section on idents](#idents-and-security-rules).
1050
+ - `id`: The id of the action itself. You may set this yourself or let Integreat
1051
+ generate a universally unique id for you. Useful for logging and may be used
1052
+ by queues.
1053
+ - `cid`: A correlation id that may be used to group actions that belong
1054
+ together, primarily for logging purposes. You may set this yourself or
1055
+ Integreat will set it to the same as the `id`. Some Integreat action handlers
1056
+ will dispatch sub actions using the `cid` from the original action.
1057
+ - `dispatchedAt`: Timestamp for when the action was dispatched (set by
1058
+ Integreat).
1059
+ - `queue`: Signals to Integreat that an action may be queued. Set to `true` when
1060
+ you want the action to be queued, but executed as soon as possible. Set to a
1061
+ UNIX timestamp (number) to schedule for a later time. If no queue is set up,
1062
+ the action will be dispatched right away. More on this under
1063
+ [the section on queues](#queue).
1064
+ - `queuedAt`: Timestamp for when the action was pushed to the queue (set by
1065
+ Integreat).
1066
+ - `options`: Used for passing the processed service endpoint options object to
1067
+ a transporter. The `options` object is available through mutations, so that
1068
+ you may modify it futher before it goes to the transporter.
1069
+ - `authorized`: An internal flag signaling that the action has been authorized.
1070
+ Will be removed from any dispatched actions.
1071
+
1072
+ ### Action response
1073
+
1074
+ When you dispatch an action, you will get a response object back in this format:
851
1075
 
852
- ### Returned responses from actions
853
-
854
- Retrieving from a service will return an Intgreat response object of the
855
- following format:
856
-
857
- ```
1076
+ ```javascript
858
1077
  {
859
- status: <statusCode>,
860
- data: <object>
861
- error: <string>
1078
+ status: <status code>,
1079
+ data: <data from the service, usually mutated>,
1080
+ error: <error message>,
1081
+ warning: <warning message>,
1082
+ access: <ident of the user>,
1083
+ paging: <pagination objects>,
1084
+ params: <key/value pairs>,
1085
+ headers: <key/value pairs>,
1086
+ responses: <array of sub-responses when relevant>,
1087
+ meta: <meta object>
862
1088
  }
863
1089
  ```
864
1090
 
865
- The `status` will be one of the following status codes:
1091
+ - `status`: The status of the action. Will be `ok` when everything went well,
1092
+ see [list of status codes](#status-codes) below for more.
1093
+ - `data`: Any data returned from the service, after being modified by the
1094
+ mutation pipelines from your service and endpoint configuration. It will be
1095
+ cast to [typed data](#typed-data) through the schema specified by the payload
1096
+ `type`, if it is set to a single type and the endpoint `allowRawResponse` is
1097
+ not set to `true`.
1098
+ - `error`: All error statuses (i.e. not `ok` or `queued`) will return an error
1099
+ message, some may include error messages from the service.
1100
+ - `warning`: When the action was successful, but there still was something you
1101
+ should know, the warning message is where you'll get noticed. An example is
1102
+ when you get an array of data items, but some of them was removed due to the
1103
+ access of the ident on the action.
1104
+ - `paging`: For services and transporters that support
1105
+ [pagination](#pagination), this object will hold information about how to get
1106
+ the next or previous page, in a `next` or `prev` object. These objects are
1107
+ essentially the payloads you need to dispatch (with the same action `type` and
1108
+ meta), to get the next or previous page. If there is no next or previous page,
1109
+ the corresponding prop will not be set on the `paging` object. When pagination
1110
+ is not relevant or used, the `paging` object may be missing completely.
1111
+ - `params`: Integreat never sets this, but you may set it in your mutations to
1112
+ provide parameters from a service that does not belong in the `data`.
1113
+ - `headers`: Integreat never sets this, but you may set it in your mutations to
1114
+ provide header key/value pairs from a service. Typically used when this is a
1115
+ response to an incoming request that support headers, like HTTP do.
1116
+ - `responses`: In some cases, an action will run several sub-actions, like
1117
+ `SYNC` or `RUN`. The action handlers _may_ then provide an array of all the
1118
+ sub-response objects here.
1119
+
1120
+ > Editor's note: Do we return access in responses?
1121
+ > Editor's note: Do we return meta in responses?
1122
+ > Editor's note: Is it correct that queues return the id in the data?
1123
+
1124
+ When the status is `queued`, the id of the queued action may found in
1125
+ `response.data.id`. This is the id assigned by the queue, and not necessarily
1126
+ the same as `action.meta.id`.
1127
+
1128
+ ### Status codes
1129
+
1130
+ The `status` or the action response will be one of the following status codes:
866
1131
 
867
1132
  - `ok`: Everything is well, data is returned as expected
868
- - `queued`: The action has been queued
869
- - `notfound`: Tried to access a resource/endpoint that does not exist
870
- - `noaction`: The action did nothing
1133
+ - `queued`: The action has been queued. This is regarded as a success status
1134
+ - `noaction`: The action did nothing, e.g. when a `SYNC` action has no data to
1135
+ sync
1136
+ - `notfound`: Tried to get or modify a resource that does not exist
871
1137
  - `timeout`: The attempt to perform the action timed out
872
1138
  - `autherror`: An authentication request failed
873
1139
  - `noaccess`: Authentication is required or the provided auth is not enough
@@ -875,121 +1141,121 @@ The `status` will be one of the following status codes:
875
1141
  - `badresponse`: Response data is not as expected
876
1142
  - `error`: Any other error
877
1143
 
878
- On `ok` status, the retrieved data will be set on the `data` property. This will
879
- usually be mapped data in [Integreat's data format](#the-data-format), but
880
- essentially, the data format depends on which action it comes from.
1144
+ ### Idents
881
1145
 
882
- When the status is `queued`, the id of the queued action may found in
883
- `response.data.id`. This is the id assigned by the queue, but it is expected
884
- that queues will use `action.meta.id` when present.
885
-
886
- In case of any other status than `ok` or `queued`, there will be no `data`, and
887
- instead the `error` property will be set to an error message, usually returned
888
- from the adapter.
889
-
890
- `data` and `error` will never be set at the same time.
891
-
892
- The same principles applies when an action is sending data or performing an
893
- action other than receiving data. On success, the returned `status` will be
894
- `ok`, and the `data` property will hold whatever the adapter returns. There is
895
- no guaranty on the returned data format in these cases.
896
-
897
- ### The data format
1146
+ An ident in Integreat is basically an id unique to one participant in the
1147
+ security scheme. It is represented by an object that may also have other
1148
+ properties to describe the ident's permissions, or to make it possible to match
1149
+ to identities in other services.
898
1150
 
899
- Items will be in the following format:
1151
+ Example ident:
900
1152
 
901
- ```
1153
+ ```javascript
902
1154
  {
903
- id: <string>,
904
- type: <schema>,
905
- createdAt: <date>,
906
- updatedAt: <date>,
907
- attributes: {
908
- <attrKey>: <value>,
909
- ...
910
- },
911
- relationships: {
912
- <relKey>: {id: <string>, type: <schema>},
913
- <relKey: [{id: <string>, type: <schema>, ...],
914
- ...
915
- }
1155
+ id: 'ident1',
1156
+ tokens: ['facebook|12345', 'twitter|23456'],
1157
+ roles: ['admin']
916
1158
  }
917
1159
  ```
918
1160
 
919
- `id`, `type`, `createdAt`, and `updatedAt` are mandatory and created by
920
- Integreat even when there are no mappings to these fields. In the future,
921
- `createdAt` and `updatedAt` may become properties of `attributes`, but will
922
- still be treated in the same way as now.
923
-
924
- ### Available actions
1161
+ - `id`: A unique string identifying the ident. The actual value is irrelevant to
1162
+ Integreat, as long as it is a string with A-Z, a-z, 0-9, \_, and -, and it's
1163
+ unique within one Integreat configuration. This means that mapped values from
1164
+ services may be used as ident ids, as long as they are unique among these
1165
+ services.
1166
+ - `tokens`: A list of values that may identify this ident in other services. For
1167
+ example, an api that uses Twitter OAuth to identify its users, may provide
1168
+ the `'twitter|23456'` token in the example above, which will be replaced with
1169
+ this ident when it enters Integreat.
1170
+ - `roles`: A list of roles or permissions given to this ident. The roles are
1171
+ custom defined per setup, and may be mapped to roles from other systems. When
1172
+ setting the auth rules for a schema, you specify required rules so that to get
1173
+ data cast in this schema, an ident with e.g. the role `admin` must be
1174
+ provided.
1175
+
1176
+ Actions are authenticated by setting an ident on the `meta.ident` property. It's
1177
+ up to the code dispatching an action to get hold of the properties of an ident
1178
+ in a secure way. Once Integreat receives an ident through a dispatch, it will
1179
+ assume this is accurate information and uphold its part of the security
1180
+ agreement and only return data and execute actions that the ident have
1181
+ permissions for.
1182
+
1183
+ Note that it's possible to set up
1184
+ [the `completeIdent` middleware](#completeIdent-middleware) for combining
1185
+ information from the authenticator with user information held e.g. in a
1186
+ database.
1187
+
1188
+ ### Available action handlers
925
1189
 
926
1190
  #### `GET`
927
1191
 
928
- Get items from a service. Returned in the `data` property is an array of mapped
929
- object, in [Integreat's data format](#the-data-format).
1192
+ Get data from a service. You receive the data on the `data` property, after it
1193
+ has been run through your service and endpoint mutations.
930
1194
 
931
- Example GET action:
1195
+ Example GET action to a collection of data items:
932
1196
 
933
1197
  ```javascript
934
1198
  {
935
1199
  type: 'GET',
936
- payload: {
937
- type: 'entry'
938
- }
1200
+ payload: { type: 'article' }
939
1201
  }
940
1202
  ```
941
1203
 
942
- In the example above, the service is inferred from the payload `type` property.
943
- Override this by supplying the id of a service as a `service` property.
944
-
945
1204
  By providing an `id` property on `payload`, the item with the given id and type
946
- is fetched, if it exists.
1205
+ is fetched, if it exists:
947
1206
 
948
- The endpoint will be picked according to the matching properties, unless an
949
- endpoint id is supplied as an `endpoint` property of `payload`.
1207
+ ```javascript
1208
+ {
1209
+ type: 'GET',
1210
+ payload: { type: 'article', id: '12345' }
1211
+ }
1212
+ ```
950
1213
 
951
- By default, the returned data will be cast with default values, but set
952
- `returnNoDefaults: true` on the action payload to get only values mapped from
953
- the service data.
1214
+ See [the section on payload properties](#payload-properties) for more properties
1215
+ that may be used with the `GET` action.
954
1216
 
955
- #### `GET_UNMAPPED`
1217
+ #### `GET_ALL`
956
1218
 
957
- Get data from a service without applying the mapping rules. Returned in the
958
- `data` property is an array of normalized objects in the format retrieved from
959
- the service. The data is not mapped in any way, and the only thing guarantied, is
960
- that this is a JavaScript object.
1219
+ Will run as many `GET` actions as needed to the get all available pages of data.
961
1220
 
962
- This action does not require a `type`, unlike the `GET` action, as it won't
963
- lookup mappings for any given type. The only reason to include a `type` in the
964
- payload, would be if the endpoint uri requires a `type` parameter.
1221
+ The action ...
965
1222
 
966
- Furthermore, a `service` property is required, as there is no `type` to infer
967
- from.
1223
+ ```javascript
1224
+ {
1225
+ type: 'GET_ALL',
1226
+ payload: { type: 'article', pageSize: 500 }
1227
+ }
1228
+ ```
968
1229
 
969
- Example GET action:
1230
+ ... will dispatch the following action is sequence:
970
1231
 
971
1232
  ```javascript
972
1233
  {
973
- type: 'GET_UNMAPPED',
974
- payload: {
975
- service: 'store',
976
- endpoint: 'get'
977
- }
1234
+ type: 'GET',
1235
+ payload: { type: 'article', pageSize: 500 }
1236
+ }
1237
+ ```
1238
+
1239
+ ```javascript
1240
+ {
1241
+ type: 'GET',
1242
+ payload: { type: 'article', pageSize: 500, pageOffset: 500 }
978
1243
  }
979
1244
  ```
980
1245
 
981
- The endpoint will be picked according to the matching properties, unless an
982
- endpoint id is supplied as an `endpoint` property of `payload`.
1246
+ ... and so on, until we get no more data.
1247
+
1248
+ See [the section on pagination](#pagination) for more on the paging properties.
983
1249
 
984
1250
  #### `GET_META`
985
1251
 
986
- Get metadata for a service. Normal endpoint matching is applied, but it's
987
- common practice to define an endpoint matching the `GET_META` action.
1252
+ Get metadata for a service. See
1253
+ [the section on metadata](#configuring-service-metadata) for how to set this up.
988
1254
 
989
- The action returns an object with a `data` property, which contains the `service`
990
- (the service id) and `meta` object with the metadata set as properties.
1255
+ The `data` of the response from this aciton contains the `service` (the service
1256
+ id) and `meta` object with the metadata set as properties.
991
1257
 
992
- Example GET_META action:
1258
+ Example `GET_META` action:
993
1259
 
994
1260
  ```javascript
995
1261
  {
@@ -1020,55 +1286,43 @@ If the action has no `keys`, all metadata set on the service will be retrieved.
1020
1286
  The `keys` property may be an array of keys to retrieve several in one request,
1021
1287
  or a single key.
1022
1288
 
1023
- Note that the service must be set up to handle metadata. See
1024
- [Configuring metadata](#configuring-metadata) for more.
1025
-
1026
1289
  #### `SET`
1027
1290
 
1028
- Send data to a service. Returned in the `data` property is the data that was sent
1029
- to the service casted, but not mapped to the service.
1291
+ Send data to a service. The data to send is provided in the payload `data`
1292
+ property. Recomended practice is to provide the data as
1293
+ [typed data](#typed-data), i.e. data objects cast to a schema, and let
1294
+ mutations on the service endpoint modify it to the format the service expects.
1030
1295
 
1031
- The data to send is provided in the payload `data` property, and must given as
1032
- an array of objects in [Integreat's data format](#the-data-format).
1296
+ Any data coming back from the service, will be provided on `response.data` and
1297
+ may be mutated through service endpoint mutations, just as for [`GET`](#get)
1298
+ actions.
1033
1299
 
1034
- Example SET action:
1300
+ Example `SET` action:
1035
1301
 
1036
1302
  ```javascript
1037
1303
  {
1038
1304
  type: 'SET',
1039
1305
  payload: {
1040
- service: 'store',
1306
+ type: 'article',
1041
1307
  data: [
1042
- {id: 'ent1', type: 'entry'},
1043
- {id: 'ent5', type: 'entry'}
1308
+ { id: '12345', $type: 'article', title: 'First article' },
1309
+ { id: '12346', $type: 'article', title: 'Second article' }
1044
1310
  ]
1045
1311
  }
1046
1312
  }
1047
1313
  ```
1048
1314
 
1049
- In the example above, the `service` is specified in the payload. Specifying a
1050
- `type` to infer the service from is also possible, but not recommended, as it
1051
- may be removed in future versions of Integreat.
1052
-
1053
- The endpoint will be picked according to the matching properties, unless an
1054
- endpoint id is supplied as an `endpoint` property of `payload`.
1055
-
1056
- To send only fields mapped from the action data to the service, set
1057
- `sendNoDefaults: true` on the endpoint config to cast the data going to the
1058
- service without using default values. This will not affect the data coming back
1059
- from the action, but set `returnNoDefaults: true` to leave defaults out of the
1060
- response data.
1061
-
1062
1315
  #### `SET_META`
1063
1316
 
1064
- Set metadata on a service. Returned in the `data` property is whatever the
1065
- adapter returns. Normal endpoint matching is used, but it's common practice to
1066
- set up an endpoint matching the `SET_META` action.
1317
+ Set metadata on a service. The payload should contain the `service` to get
1318
+ metadata for (the service id), and a `meta` object, with all metadata to set as
1319
+ properties.
1067
1320
 
1068
- The payload should contain the `service` to get metadata for (the service id), and
1069
- a `meta` object, with all metadata to set as properties.
1321
+ Any data coming back from the service, will be provided on `response.data` and
1322
+ may be mutated through service endpoint mutations, just as for [`GET`](#get)
1323
+ actions.
1070
1324
 
1071
- Example SET_META action:
1325
+ Example `SET_META` action:
1072
1326
 
1073
1327
  ```javascript
1074
1328
  {
@@ -1083,37 +1337,35 @@ Example SET_META action:
1083
1337
  ```
1084
1338
 
1085
1339
  Note that the service must be set up to handle metadata. See
1086
- [Configuring metadata](#configuring-metadata) for more.
1340
+ [Configuring metadata](#configuring-service-metadata) for more.
1087
1341
 
1088
1342
  #### `DELETE` / `DEL`
1089
1343
 
1090
- Delete data for several items from a service. Returned in the `data` property is
1091
- whatever the adapter returns.
1344
+ Delete one or more items from a service. Set the data for the items to delete,
1345
+ in the payload `data` property as an array of [typed data](#typed-data).
1346
+ In most cases, you only need to provide the `id` and the `$type`, but the way
1347
+ you set up the service may require more properties.
1092
1348
 
1093
- The data for the items to delete, is provided in the payload `data` property,
1094
- and must given as an array of objects in
1095
- [Integreat's data format](#the-data-format), but note that the attributes and
1096
- relationships are not required.
1349
+ Any data coming back from the service, will be provided on `response.data` and
1350
+ may be mutated through service endpoint mutations, just as for [`GET`](#get)
1351
+ actions.
1097
1352
 
1098
- Example DELETE action:
1353
+ Example `DELETE` action:
1099
1354
 
1100
1355
  ```javascript
1101
1356
  {
1102
1357
  type: 'DELETE',
1103
1358
  payload: {
1104
- service: 'store',
1359
+ type: 'article',
1105
1360
  data: [
1106
- {id: 'ent1', type: 'entry'},
1107
- {id: 'ent5', type: 'entry'}
1361
+ { id: '12345', $type: 'article' },
1362
+ { id: '12346', $type: 'article' }
1108
1363
  ]
1109
1364
  }
1110
1365
  }
1111
1366
  ```
1112
1367
 
1113
- In the example above, the `service` is specified in the payload. Specifying a
1114
- `type` to infer the service from is also possible.
1115
-
1116
- Example DELETE action for one item:
1368
+ You may also `DELETE` one item like this:
1117
1369
 
1118
1370
  ```javascript
1119
1371
  {
@@ -1125,63 +1377,84 @@ Example DELETE action for one item:
1125
1377
  }
1126
1378
  ```
1127
1379
 
1128
- The endpoint will be picked according to the matching properties, unless an
1129
- endpoint id is supplied as an `endpoint` property of `payload`.
1380
+ `DEL` is a shorthand for `DELETE`.
1130
1381
 
1131
- The method used for the request defaults to `POST` when `data` is set, and
1132
- `DELETE` for the `id` and `type` option, but may be overridden on the endpoint.
1382
+ #### `RUN`
1133
1383
 
1134
- `DEL` is a shorthand for `DELETE`.
1384
+ The `RUN` action will run jobs provided to `Integreat.create()` in the jobs
1385
+ definitions. These jobs will then run other actions or series of action, also
1386
+ called "flows".
1387
+
1388
+ Only one payload property is required – the `jobId`, which refers to a job in
1389
+ the jobs definitions. Any other properties on the payload will be passed on as
1390
+ input to the job.
1391
+
1392
+ An action for running the `archiveOutdated` job:
1393
+
1394
+ ```javascript
1395
+ {
1396
+ type: 'RUN',
1397
+ payload: { jobId: 'archiveOutdated' }
1398
+ }
1399
+ ```
1400
+
1401
+ See [the section on jobs](#jobs) for more on how to configure jobs.
1135
1402
 
1136
1403
  #### `SYNC`
1137
1404
 
1138
- The `SYNC` action will retrieve items from one service and set them on another.
1405
+ The `SYNC` action will `GET` items from one service and `SET` them on another.
1139
1406
  There are different options for how to retrieve items, ranging from a crude
1140
1407
  retrieval of all items on every sync, to a more fine grained approach where only
1141
- items that have been updated since last sync, will be synced.
1408
+ items that have been updated or created since last sync, will be synced.
1142
1409
 
1143
1410
  The simplest action definition would look like this, where all items would be
1144
1411
  retrieved from the service and set on the target:
1145
1412
 
1146
- ```
1413
+ ```javascript
1147
1414
  {
1148
1415
  type: 'SYNC',
1149
1416
  payload: {
1150
- from: <serviceId | params>,
1151
- to: <serviceId | params>,
1152
- type: <itemType>,
1153
- retrieve: 'all'
1417
+ type: <item type>,
1418
+ retrieve: 'all',
1419
+ from: <service id | payload>,
1420
+ to: <service id | payload>
1154
1421
  }
1155
1422
  }
1156
1423
  ```
1157
1424
 
1158
- The action will dispatch a 'GET' action right away, and then immediately
1425
+ The action will dispatch a `GET` action right away, and then immediately
1159
1426
  dispatch a `SET_META` action to update the `lastSyncedAt` date on the service.
1160
- The actions to update the target is added to the queue.
1427
+ The `SET` actions to update the target service is added to the queue if one is
1428
+ configured.
1161
1429
 
1162
1430
  To retrieve only new items, change the `retrieve` property to `updated`. In
1163
- this case, the action will get the `lastSyncedAt` from the `from` service, and
1164
- get only newer items, by passing it the `updatedAfter` param. The action will
1165
- also filter out older items, in case the service does not support `updatedAfter`.
1431
+ this case, the action will dispatch `GET_META` to get the `lastSyncedAt` from
1432
+ the `from` service, and get only newer items, by passing it the `updatedAfter`
1433
+ param. The action will also filter out older items, in case the service does not
1434
+ support `updatedAfter`.
1435
+
1436
+ By setting `retrieve` to `created`, you accomplish the same, but with
1437
+ `createdAfter`.
1166
1438
 
1167
1439
  If you need to include more params in the actions to get from the `from` service
1168
1440
  or set to the `to` service, you may provide a params object for the `from` or
1169
- `to` props, with the service id set as a `service` param.
1441
+ `to` props, with the service id set as a `service` param. You may also provide
1442
+ different action types than `GET` and `SET`, by setting the `action` prop on
1443
+ the `from` or `to` objects respectively.
1444
+
1445
+ > There are more options than these, and the documentation will be updated to
1446
+ > include them later.
1170
1447
 
1171
1448
  #### `EXPIRE`
1172
1449
 
1173
- With an endpoint for getting expired items, the `EXPIRE` action will fetch
1174
- these and delete them from the service. The endpoint may include param for the
1175
- current time, either as microseconds since Januar 1, 1970 UTC with param
1176
- `{timestamp}` or as the current time in the extended ISO 8601 format
1177
- (`YYYY-MM-DDThh:mm:ss.sssZ`) with the `{isodate}` param. To get a time in the
1178
- future instead, set `msFromNow` to a positive number of milliseconds to add
1179
- to the current time, or set `msFromNow` to a negative number to a time in the
1180
- past.
1450
+ > Note: This action will change before we reach v1.0.
1181
1451
 
1182
- Here's a typical action definition:
1452
+ The `EXPIRE` action will `GET` expired data items from a service, and the then
1453
+ `DELETE` them.
1183
1454
 
1184
- ```
1455
+ Here's an example of an `EXPIRE` action:
1456
+
1457
+ ```javascript
1185
1458
  {
1186
1459
  type: 'EXPIRE',
1187
1460
  payload: {
@@ -1193,21 +1466,40 @@ Here's a typical action definition:
1193
1466
  }
1194
1467
  ```
1195
1468
 
1196
- This will get and map items of type `entry` from the `getExpired` endpoint on
1197
- the service `store`, and delete them from the same service. Only an endpoint
1198
- specified with an id is allowed, as the consequence of delete all items received
1199
- from the wrong endpoint could be quite severe.
1469
+ The `endpoint` property is required for this action, and needs to specify a
1470
+ service endpoint used to fetch expired items. The action dispatched to this
1471
+ endpoint will have a `timestamp` property with the current time as microseconds
1472
+ since epoc (Januar 1, 1970 UTC), and `isodate` as the current time in the
1473
+ extended ISO 8601 format(`YYYY-MM-DDThh:mm:ss.sssZ`).
1474
+
1475
+ To have `timestamp` and `isodate` be a time in the future instead, set
1476
+ `msFromNow` to a positive number of milliseconds. This will be added to the
1477
+ current time. To have a time in the past, use a negative number for `msFromNow`.
1200
1478
 
1201
- Example endpoint uri template for `getExpired` (from a CouchDB service):
1479
+ #### `SERVICE`
1480
+
1481
+ A `SERVICE` action will be sent directly to the specified service without any
1482
+ intervention by Integreat. This allows for running specialized actions on the
1483
+ service that goes beyond what Integreat supports. It's up to each transporter to
1484
+ support such actions, describe what they'll do, and define their payload
1485
+ properties.
1486
+
1487
+ An example of an action that will tell a
1488
+ [Bull](https://github.com/integreat-io/integreat-transporter-bull) queue to
1489
+ clean out all completed jobs more than a week old:
1202
1490
 
1203
1491
  ```javascript
1204
1492
  {
1205
- uri: '/_design/fns/_view/expired?include_docs=true{&endkey=timestamp}',
1206
- path: 'rows[].doc'
1493
+ type: 'SERVICE',
1494
+ payload: {
1495
+ type: 'cleanCompleted',
1496
+ targetService: 'bullService',
1497
+ olderThanMs: 604800000
1498
+ }
1207
1499
  }
1208
1500
  ```
1209
1501
 
1210
- ### Custom actions
1502
+ ### Write your own action handlers
1211
1503
 
1212
1504
  You may write your own action handlers to handle dispatched actions just like
1213
1505
  the built-in types.
@@ -1215,190 +1507,252 @@ the built-in types.
1215
1507
  Action handler signature:
1216
1508
 
1217
1509
  ```javascript
1218
- function (payload, {dispatch, services, schemas, getService}) { ... }
1510
+ async function (action, { dispatch, getService, setProgress, options }) { ... }
1219
1511
  ```
1220
1512
 
1221
- An action handler may dispatch new actions with the `dispatch()` method. These
1222
- will be passed through the middleware chain just like any other action, so it's
1223
- for instance possible to queue actions from an action handler by setting
1224
- `action.meta.queue = true`.
1225
-
1226
- The `services` and `schemas` arguments provide all services and schemas set on
1227
- objects with their ids as keys.
1228
-
1229
- Finally, `getService()` is a convenience method that will return the relevant
1230
- service object when you provide it with a type. An optional second argument may
1231
- be set to a service id, in which case the service object with this id will be
1232
- returned.
1233
-
1234
- Custom actions are supplied to an Integreat instance on setup, by providing an
1235
- object with the key set to the action type your handler will be responsible for,
1236
- and the handler function as the value.
1513
+ - `action`: This is the dispatched action after it has been modified a bit
1514
+ by the `dispatch()` method and possible after running an incoming mutation on
1515
+ it. The modifications include cleaning up alias fields (e.g. `service` will be
1516
+ set as `targetService`), removing sensitive or forbidden fields, and setting a
1517
+ few default or internal fields (like the `dispatchedAt` meta).
1518
+ - `dispatch`: From the handler, you may dispatch your own sub actions to the
1519
+ provided `dispatch()` method. Note that this is an "internal dispatch method",
1520
+ so it will return an action with the `response` object on it, instead of just
1521
+ the `response` object. It's good practice to set the `cid` meta prop for the
1522
+ actions you dispatch, to the `cid` meta prop on the `action` you're handling.
1523
+ You should also use the same `ident` unless you have very good reasons to do
1524
+ otherwise, to make sure you don't create security holes.
1525
+ - `getService`: This is a convenience method that will return the relevant
1526
+ service object when you provide it with a type and optional a service id. With
1527
+ a service id, you'll get the service with that id, with only the type, you'll
1528
+ get the default service for that type. E.g.: `getService('article')`.
1529
+ - `setProgress`: For long running tasks, you may want to set the progress along
1530
+ the way. Progress is specified as a number between `0` and `1`, e.g.
1531
+ `setProgress(.5)` to signal that you're halfway through. When the your handler
1532
+ is finished, the progress will automatically be set to `1`. This may be used
1533
+ by queue implementations etc., to give progress feedback to users and to know
1534
+ the action has not gone stale.
1535
+ - `options`: This is an object with a few settings: `queueService` is the id of
1536
+ the service set up as the default queue, and `identConfig` is the config
1537
+ object used for mapping ident schemas to ids, roles, and tokens (see
1538
+ [the `completeIdent` middleware](#completeIdent-middleware)).
1539
+
1540
+ Your action handler must return the `action` it received with a `response`
1541
+ object set on it. If your handler just relays to another action handler, it may
1542
+ reuse the `response` from that handler, but in many cases it will be more
1543
+ correct to generate your own response, possibly based on what the actions you
1544
+ dispatch returns.
1545
+
1546
+ You provide your custom actions to Integreat on setup, by providing an object
1547
+ with the key set to the action type your handler will be responsible for, and
1548
+ the handler function as the value:
1237
1549
 
1238
1550
  ```javascript
1239
1551
  const actions = {
1240
- `MYACTION`: function (payload, {dispatch}) { ... }
1552
+ `MY_ACTION`: async function myAction (action, { dispatch }) { ... }
1241
1553
  }
1242
- const great = Integreat.create(defs, {schemas, services, mappings, actions})
1554
+ const great = Integreat.create(defs, { schemas, services, mappings, actions })
1243
1555
  ```
1244
1556
 
1245
- Note that if a custom action handler is added with an action type that is
1246
- already use by one of Integreat's built-in action handlers, the custom handler
1557
+ Note that if you set up your custom action handler with an action type that is
1558
+ already used by one of Integreat's built-in action handlers, the custom handler
1247
1559
  will have precedence. So be careful when you choose an action type, if your
1248
1560
  intention is not to replace an existing action handler.
1249
1561
 
1250
- ## Adapters
1562
+ ## Queues
1251
1563
 
1252
- Interface:
1564
+ As everything else in Integreat, a queue is also a service. You configure a
1565
+ queue service, e.g.
1566
+ [`integreat-transporter-bull`](https://github.com/integreat-io/integreat-transporter-bull),
1567
+ and set its service id on the `queueService` property of the definition object
1568
+ you give to `Integreat.create()`:
1253
1569
 
1254
- - `prepareEndpoint(endpointOptions, [serviceOptions])`
1255
- - `async send(request)`
1256
- - `async normalize(data, request)`
1257
- - `async serialize(data, request)`
1570
+ ```javascript
1571
+ import bullQueue from `integreat-transporter-bull`
1572
+
1573
+ const services = [
1574
+ {
1575
+ id: 'queue',
1576
+ transporter: 'bull',
1577
+ // ...
1578
+ }
1579
+ ]
1580
+ const transporters = {
1581
+ bull: bullQueue
1582
+ }
1258
1583
 
1259
- Available adapters:
1584
+ const great = Integreat.create(
1585
+ { services, queueService: 'queue' },
1586
+ { transporters }
1587
+ )
1588
+ ```
1260
1589
 
1261
- - `json` (built in)
1262
- - [`couchdb`](https://github.com/integreat-io/integreat-adapter-couchdb)
1590
+ To queue an action instead of dispatching it right away, you set `queue: true`
1591
+ on the `meta` object. If everything is set up correctly, Integreat will push the
1592
+ action to the queue. When the action is later pulled from the queue, it will be
1593
+ dispatched again, but without the `queue` property.
1263
1594
 
1264
- ## Service authentication
1595
+ You may also set the meta `queue` property to a Unix timestamp, and if the queue
1596
+ transporter supports it, it will be run at this time instead of being processed
1597
+ as soon as it is next in line in the queue.
1265
1598
 
1266
- This definition format is used to authenticate with a service:
1599
+ When a queue is not set up, a dispatched action with `queue: true` will just be
1600
+ run right away as a normal action.
1267
1601
 
1268
- ```
1269
- {
1270
- id: <id>,
1271
- authenticator: <authenticator id>,
1272
- options: {
1273
- ...
1274
- }
1275
- }
1276
- ```
1602
+ You may also use queues directly, by dispatching to it as a server and getting
1603
+ incoming actions from its `listen()` method. In that case, it's just as any
1604
+ other service with no need for any special handling.
1277
1605
 
1278
- At runtime, the specified authenticator is used to authenticate requests. The
1279
- authenticator is given the `options` payload.
1606
+ > Queueing actions are actually done through an action handler, but this handler
1607
+ > is not available from outside Integreat.
1280
1608
 
1281
- ## Pipeline functions
1609
+ ## Middleware
1282
1610
 
1283
- - Item `transform(item)`
1284
- - Item `filter(item)`
1285
- - Attribute `transform(value)`
1611
+ Integreat supports middleware, and there are two different middleware
1612
+ "pipelines":
1286
1613
 
1287
- Built in transformers:
1614
+ - The first one is run on dispatched actions. The action goes through the
1615
+ middleware before the action handler takes over, but after the incoming
1616
+ mutations have been run. Because of this, given that you have set up the
1617
+ services with mutation and casting to schemas, you should always be dealing
1618
+ with [typed data](#typed-data) in the middleware.
1619
+ - The action then passes through a second middleware "pipeline" just before it
1620
+ is sent to the service. This happens _after_ all mutations have been run, so
1621
+ you will be dealing with the data as it is sent to the service. Incoming
1622
+ actions from a service also pass through this middleware on the way in,
1623
+ _before_ it is mutated, giving you access to the data as it comes from the
1624
+ service.
1288
1625
 
1289
- - `not` - inverts a boolean value going from or to a service
1290
- - `hash` - converts any string(ish) value to a SHA256 hash in base64 (with the
1291
- url-unfriendly characters +, /, and = replaced with -, \_, and ~)
1626
+ To set up a logger of what we recieve from and send to a service, you'll use the
1627
+ second middleware "pipeline", while a logger of dispatched actions would be
1628
+ placed in the first.
1292
1629
 
1293
- ### Schedule definition
1630
+ When actions pass through middleware, they may modifiy the actions as
1631
+ appropriate, and you will have middleware that modifies (e.g. the
1632
+ [`completeIdent` middleware](#completeident-middleware)), and others that just
1633
+ monitors what's coming through (e.g. a logger).
1294
1634
 
1295
- **Note:** This will likely be removed in version 0.8.
1635
+ Middelware is passed to Integreat like this:
1296
1636
 
1637
+ ```javascript
1638
+ const great = Integreat.create(
1639
+ defs,
1640
+ resources,
1641
+ [
1642
+ // Dispatch middleware
1643
+ ],
1644
+ [
1645
+ // Service middleware
1646
+ ]
1647
+ )
1297
1648
  ```
1298
- {
1299
- schedule: <schedule>,
1300
- action: <action definition>,
1301
- }
1649
+
1650
+ ### `completeIdent` middleware
1651
+
1652
+ If your access rules are based only on the information received from an
1653
+ authenticator, you don't need the following. You will always get an id and
1654
+ potentially some other fields, like roles.
1655
+
1656
+ But when you need to match the ident id from the authenticator with user
1657
+ information held somewhere else, e.g. in a database, you need to configure a
1658
+ user schema and set up a service to fetch this information.
1659
+
1660
+ Integreat uses schemas and services to store idents. In the definition object
1661
+ passed to `Integreat.create()`, you may provide an `identConfig` property with
1662
+ a definition object looking something like this:
1663
+
1664
+ ```javascript
1665
+ const great = Integreat.create(
1666
+ {
1667
+ // ...,
1668
+ identConfig: {
1669
+ type: 'user',
1670
+ props: {
1671
+ id: 'id',
1672
+ roles: 'groups',
1673
+ tokens: 'tokens',
1674
+ },
1675
+ },
1676
+ },
1677
+ {
1678
+ // ...
1679
+ }
1680
+ )
1302
1681
  ```
1303
1682
 
1304
- The `schedule` format is directly borrowed from
1305
- [Later](http://bunkat.github.io/later/schedules.html) (also accepts the basic or
1306
- composite schedule formats on the `schedule` property, as well as the [text
1307
- format](http://bunkat.github.io/later/parsers.html#text)).
1308
-
1309
- The following time periods are supported:
1310
-
1311
- - `s`: Seconds in a minute (0-59)
1312
- - `m`: Minutes in an hour (0-59)
1313
- - `h`: Hours in a day (0-23)
1314
- - `t`: Time of the day, as seconds since midnight (0-86399)
1315
- - `D`: Days of the month (1-maximum number of days in the month, 0 to specifies
1316
- last day of month)
1317
- - `d`: Days of the week (1-7, starting with Sunday)
1318
- - `dc`: Days of week count, (1-maximum weeks of the month, 0 specifies last in
1319
- the month). Use together with `d` to get first Wednesday every month, etc.
1320
- - `dy`: Days of year (1 to maximum number of days in the year, 0
1321
- specifies last day of year).
1322
- - `wm`: Weeks of the month (1-maximum number of weeks in the month, 0 for last
1323
- week of the month.). First week of the month is the week containing the 1st, and
1324
- weeks start on Sunday.
1325
- - `wy`: [ISO weeks of the year](http://en.wikipedia.org/wiki/ISO_week_date)
1326
- (1-maximum number of ISO weeks in the year, 0 is the last ISO week of the year).
1327
- - `M`: Months of the year (1-12)
1328
- - `Y`: Years (1970-2099)
1329
-
1330
- See Later's documentation on
1331
- [time periods](http://bunkat.github.io/later/time-periods.html) for more.
1332
-
1333
- Example schedule running an action at 2 am every weekday:
1683
+ - `type`: This is the id of the schema used for getting ident data. This schema
1684
+ needs to have a `service` specified.
1685
+ - `props`: You may provide alternative field names for the `id`, `roles`, and
1686
+ `tokens` for an ident in the schema specified on `type`. When the prop and the
1687
+ field has the same name, it may be omitted, though it doesn't hurt to specif
1688
+ it anyway for clarity.
1689
+
1690
+ Note that in the example above, the `id` of the data will be used as the ident
1691
+ `id`. When the id is not suited for this, you will need another field on the
1692
+ schema that may act as the ident id. In cases where you need to transform the
1693
+ id from the data in some way, this must be set up as a separate field and the
1694
+ mutation will dictate how to transform it. In most cases, the `id` will do,
1695
+ though.
1696
+
1697
+ For some setups, this requires certain endpoints to be defined on the service.
1698
+ To match a token with an ident, the service must have an endpoint that matches
1699
+ actions like this:
1334
1700
 
1335
1701
  ```javascript
1336
1702
  {
1337
- schedule: {d: [2,3,4,5,6], h: [2]},
1338
- action: {
1339
- type: 'SYNC',
1340
- payload: {
1341
- from: 'src1',
1342
- to: 'src2',
1343
- type: 'entry'
1344
- }
1703
+ type: 'GET',
1704
+ payload: {
1705
+ type: 'user',
1706
+ tokens: 'twitter|23456'
1345
1707
  }
1346
1708
  }
1347
1709
  ```
1348
1710
 
1349
- To run an action every hour, use `{m: [0]}` or simply `'every hour'`.
1711
+ In this case, `user` is the schema mapped to idents, and the `tokens`
1712
+ property on the ident is mapped to the `tokens` field on the schema.
1713
+
1714
+ To make Integreat complete idents on actions with the persisted data, set it up
1715
+ with the `completeIdent` middleware:
1716
+
1717
+ ```javascript
1718
+ const great = Integreat.create(defs, resources, [
1719
+ Integreat.middleware.completeIdent,
1720
+ ])
1721
+ ```
1722
+
1723
+ This middleware will intercept any action with `meta.ident` and replace it with
1724
+ the ident item loaded from the designated schema. If the ident has an `id`,
1725
+ the ident with this id is loaded, otherwise a `withToken` is used to load the
1726
+ ident with the specified token. If no ident is found, the original ident is
1727
+ kept.
1350
1728
 
1351
1729
  ## Writing middleware
1352
1730
 
1353
1731
  You may write middleware to intercept dispatched actions. This may be useful
1354
- for logging, debugging, and features like action replay. Also, Integreat's
1355
- queue feature is written as a middleware.
1732
+ for logging, debugging, and situations where you need to make adjustments to
1733
+ certain actions.
1356
1734
 
1357
1735
  A middleware is a function that accepts a `next()` function as only argument,
1358
1736
  and returns an async function that will be called with the action on dispatch.
1359
1737
  The returned function is expected to call `next()` with the action, and return
1360
1738
  the result from the `next()` function, but is not required to do so. The only
1361
- requirement is that the functions returns a valid Integreat response object.
1739
+ requirement is that the functions returns a valid Integreat action object,
1740
+ most likely with a `response` object on it.
1362
1741
 
1363
1742
  Example implementation of a very simple logger middleware:
1364
1743
 
1365
1744
  ```javascript
1366
1745
  const logger = (next) => async (action) => {
1367
1746
  console.log('Dispatch was called with action', action)
1368
- const response = await next(action)
1369
- console.log('Dispatch completed with response', response)
1370
- return response
1747
+ const responseAction = await next(action)
1748
+ console.log('Dispatch completed with response', responseAction.response)
1749
+ return responseAction
1371
1750
  }
1372
1751
  ```
1373
1752
 
1374
- ## Queue
1375
-
1376
- Integreat comes with a generic queue interface at `integreat.queue`, that must
1377
- be setup with a specific queue implementation, for instance
1378
- [`integreat-queue-redis`](https://github.com/integreat-io/integreat-queue-redis).
1379
-
1380
- The queue interface is a middleware, that will intercept any dispatched action
1381
- with `action.meta.queue` set to `true` or a timestamp, and direct it to the
1382
- queue. When the action is later pulled from the queue, it will be dispatched
1383
- again, but without the `action.meta.queue` property.
1384
-
1385
- If a dispatched action has a schedule definition at `action.meta.schedule`, it
1386
- will be queued for the next timestamp defined by the schedule.
1387
-
1388
- To setup Integreat with a queue:
1389
-
1390
- ```javascript
1391
- const queue = Integreat.queue(redisQueue(options))
1392
- const great = Integreat.create(defs, resources, [queue.middleware])
1393
- queue.setDispatch(great.dispatch)
1394
- ```
1395
-
1396
- `queue.middleware` is the middleware, while `queue.setDispatch` must be called
1397
- to tell the queue interface where to dispatch actions pulled from the queue.
1398
-
1399
1753
  ## Debugging
1400
1754
 
1401
1755
  Run Integreat with env variable `DEBUG=great`, to receive debug messages.
1402
1756
 
1403
- Some sub modules sends debug messages with the `great:` prefix, so use
1404
- `DEBUG=great,great:*` to catch these as well.
1757
+ Some sub modules sends debug messages with the `integreat:` prefix, so use
1758
+ `DEBUG=great,integreat:*` to catch these as well.