ketting 7.0.1 → 7.3.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 (81) hide show
  1. package/LICENSE +1 -1
  2. package/browser/ketting.min.js +1 -1
  3. package/browser/ketting.min.js.map +1 -1
  4. package/dist/client.d.ts +24 -4
  5. package/dist/client.js +96 -29
  6. package/dist/client.js.map +1 -1
  7. package/dist/http/error.js +1 -1
  8. package/dist/http/error.js.map +1 -1
  9. package/dist/http/fetch-polyfill.js.map +1 -1
  10. package/dist/http/fetcher.js +3 -3
  11. package/dist/http/fetcher.js.map +1 -1
  12. package/dist/index.d.ts +3 -1
  13. package/dist/index.js +7 -5
  14. package/dist/index.js.map +1 -1
  15. package/dist/link.js +2 -2
  16. package/dist/link.js.map +1 -1
  17. package/dist/middlewares/accept-header.js +1 -1
  18. package/dist/middlewares/accept-header.js.map +1 -1
  19. package/dist/middlewares/cache.js +30 -25
  20. package/dist/middlewares/cache.js.map +1 -1
  21. package/dist/middlewares/warning.js +2 -1
  22. package/dist/middlewares/warning.js.map +1 -1
  23. package/dist/resource.d.ts +1 -1
  24. package/dist/resource.js +6 -6
  25. package/dist/resource.js.map +1 -1
  26. package/dist/src/client.d.ts +24 -4
  27. package/dist/src/index.d.ts +3 -1
  28. package/dist/src/resource.d.ts +1 -1
  29. package/dist/state/base-state.js +6 -4
  30. package/dist/state/base-state.js.map +1 -1
  31. package/dist/state/binary.js +1 -1
  32. package/dist/state/binary.js.map +1 -1
  33. package/dist/state/collection-json.js +1 -1
  34. package/dist/state/collection-json.js.map +1 -1
  35. package/dist/state/hal.js +13 -9
  36. package/dist/state/hal.js.map +1 -1
  37. package/dist/state/head.js +1 -1
  38. package/dist/state/head.js.map +1 -1
  39. package/dist/state/html.js +3 -3
  40. package/dist/state/html.js.map +1 -1
  41. package/dist/state/jsonapi.js +1 -1
  42. package/dist/state/jsonapi.js.map +1 -1
  43. package/dist/state/siren.js +3 -3
  44. package/dist/state/siren.js.map +1 -1
  45. package/dist/state/text.js +1 -1
  46. package/dist/state/text.js.map +1 -1
  47. package/dist/util/html.js +2 -2
  48. package/dist/util/html.js.map +1 -1
  49. package/dist/util/html.web.js +2 -2
  50. package/dist/util/html.web.js.map +1 -1
  51. package/dist/util/uri-template.js +1 -1
  52. package/dist/util/uri-template.js.map +1 -1
  53. package/dist/util/uri.js +3 -3
  54. package/dist/util/uri.js.map +1 -1
  55. package/dist/util/uri.web.js +1 -1
  56. package/dist/util/uri.web.js.map +1 -1
  57. package/package.json +11 -10
  58. package/src/action.ts +2 -2
  59. package/src/client.ts +119 -34
  60. package/src/field.ts +9 -9
  61. package/src/http/error.ts +7 -7
  62. package/src/http/fetch-polyfill.ts +4 -4
  63. package/src/http/fetcher.ts +3 -3
  64. package/src/index.ts +3 -5
  65. package/src/link.ts +10 -10
  66. package/src/middlewares/accept-header.ts +1 -1
  67. package/src/middlewares/cache.ts +31 -24
  68. package/src/middlewares/warning.ts +1 -0
  69. package/src/resource.ts +8 -8
  70. package/src/state/base-state.ts +5 -3
  71. package/src/state/collection-json.ts +28 -28
  72. package/src/state/hal.ts +12 -7
  73. package/src/state/interface.ts +2 -2
  74. package/src/state/jsonapi.ts +9 -9
  75. package/src/state/siren.ts +23 -23
  76. package/src/types.ts +1 -1
  77. package/src/util/html.ts +10 -10
  78. package/src/util/html.web.ts +7 -7
  79. package/src/util/uri.ts +1 -1
  80. package/src/util/uri.web.ts +2 -2
  81. package/changelog.md +0 -894
package/src/client.ts CHANGED
@@ -38,16 +38,16 @@ export default class Client {
38
38
  * headers. Higher means higher priority.
39
39
  */
40
40
  contentTypeMap: {
41
- [mimeType: string]: [StateFactory<any>, string],
41
+ [mimeType: string]: [StateFactory<any>, string];
42
42
  } = {
43
- 'application/prs.hal-forms+json': [halStateFactory, '1.0'],
44
- 'application/hal+json': [halStateFactory, '0.9'],
45
- 'application/vnd.api+json': [jsonApiStateFactory, '0.8'],
46
- 'application/vnd.siren+json': [sirenStateFactory, '0.8'],
47
- 'application/vnd.collection+json': [cjStateFactory, '0.8'],
48
- 'application/json': [halStateFactory, '0.7'],
49
- 'text/html': [htmlStateFactory, '0.6'],
50
- }
43
+ 'application/prs.hal-forms+json': [halStateFactory, '1.0'],
44
+ 'application/hal+json': [halStateFactory, '0.9'],
45
+ 'application/vnd.api+json': [jsonApiStateFactory, '0.8'],
46
+ 'application/vnd.siren+json': [sirenStateFactory, '0.8'],
47
+ 'application/vnd.collection+json': [cjStateFactory, '0.8'],
48
+ 'application/json': [halStateFactory, '0.7'],
49
+ 'text/html': [htmlStateFactory, '0.6'],
50
+ };
51
51
 
52
52
  /**
53
53
  * The cache for 'State' objects
@@ -141,6 +141,83 @@ export default class Client {
141
141
  clearCache() {
142
142
 
143
143
  this.cache.clear();
144
+ this.cacheDependencies = new Map();
145
+
146
+ }
147
+
148
+ /**
149
+ * Caches a State object
150
+ *
151
+ * This function will also emit 'update' events to resources, and store all
152
+ * embedded states.
153
+ */
154
+ cacheState(state: State) {
155
+
156
+ this.cache.store(state);
157
+ const resource = this.resources.get(state.uri);
158
+ if (resource) {
159
+ // We have a resource for this object, notify it as well.
160
+ resource.emit('update', state);
161
+ }
162
+
163
+ for(const embeddedState of state.getEmbedded()) {
164
+ // Recursion. MADNESS
165
+ this.cacheState(embeddedState);
166
+ }
167
+
168
+ }
169
+
170
+ /**
171
+ * cacheDependencies contains all cache relationships between
172
+ * resources.
173
+ *
174
+ * This lets a user (for example) let a resource automatically
175
+ * expire, if another one expires.
176
+ *
177
+ * A server can populate this list using the `inv-by' link.
178
+ */
179
+ public cacheDependencies: Map<string, Set<string>> = new Map();
180
+
181
+ /**
182
+ * Helper function for clearing the cache for a resource.
183
+ *
184
+ * This function will also emit the 'stale' event for resources that have
185
+ * subscribers, and handle any dependent resource caches.
186
+ *
187
+ * If any resources are specified in deletedUris, those will not
188
+ * receive 'stale' events, but 'delete' events instead.
189
+ */
190
+ clearResourceCache(staleUris: string[], deletedUris: string[]) {
191
+
192
+ let stale = new Set<string>();
193
+ const deleted = new Set<string>();
194
+ for(const uri of staleUris) {
195
+ stale.add(resolve(this.bookmarkUri, uri));
196
+ }
197
+ for(const uri of deletedUris) {
198
+ stale.add(resolve(this.bookmarkUri, uri));
199
+ deleted.add(resolve(this.bookmarkUri, uri));
200
+ }
201
+
202
+ stale = expandCacheDependencies(
203
+ new Set([...stale, ...deleted]),
204
+ this.cacheDependencies
205
+ );
206
+
207
+ for(const uri of stale) {
208
+ this.cache.delete(uri);
209
+
210
+ const resource = this.resources.get(uri);
211
+ if (resource) {
212
+ if (deleted.has(uri)) {
213
+ resource.emit('delete');
214
+ } else {
215
+ resource.emit('stale');
216
+ }
217
+
218
+ }
219
+
220
+ }
144
221
 
145
222
  }
146
223
 
@@ -151,48 +228,56 @@ export default class Client {
151
228
 
152
229
  const contentType = parseContentType(response.headers.get('Content-Type')!);
153
230
 
154
- let state: State;
155
-
156
- if (!contentType) {
231
+ if (!contentType || response.status === 204) {
157
232
  return binaryStateFactory(this, uri, response);
158
233
  }
159
234
 
160
235
  if (contentType in this.contentTypeMap) {
161
- state = await this.contentTypeMap[contentType][0](this, uri, response);
236
+ return this.contentTypeMap[contentType][0](this, uri, response);
162
237
  } else if (contentType.startsWith('text/')) {
163
238
  // Default to TextState for any format starting with text/
164
- state = await textStateFactory(this, uri, response);
239
+ return textStateFactory(this, uri, response);
165
240
  } else if (contentType.match(/^application\/[A-Za-z-.]+\+json/)) {
166
241
  // Default to HalState for any format containing a pattern like application/*+json
167
- state = await halStateFactory(this, uri, response);
242
+ return halStateFactory(this, uri, response);
168
243
  } else {
169
- state = await binaryStateFactory(this, uri, response);
244
+ return binaryStateFactory(this, uri, response);
170
245
  }
171
246
 
172
- return state;
173
-
174
247
  }
175
248
 
176
- /**
177
- * Caches a State object
178
- *
179
- * This function will also emit 'update' events to resources, and store all
180
- * embedded states.
181
- */
182
- cacheState(state: State) {
183
249
 
184
- this.cache.store(state);
185
- const resource = this.resources.get(state.uri);
186
- if (resource) {
187
- // We have a resource for this object, notify it as well.
188
- resource.emit('update', state);
189
- }
250
+ }
190
251
 
191
- for(const embeddedState of state.getEmbedded()) {
192
- // Recursion. MADNESS
193
- this.cacheState(embeddedState);
252
+
253
+
254
+ /**
255
+ * Find all dependencies for a given resource.
256
+ *
257
+ * For example, if
258
+ * * if resourceA depends on resourceB
259
+ * * and resourceB depends on resourceC
260
+ *
261
+ * Then if 'resourceC' expires, so should 'resourceA' and 'resourceB'.
262
+ *
263
+ * This function helps us find these dependencies recursively and guarding
264
+ * against recursive loops.
265
+ */
266
+ function expandCacheDependencies(uris: Set<string>, dependencies: Map<string, Set<string>>, output?: Set<string>): Set<string> {
267
+
268
+ if (!output) output = new Set();
269
+
270
+ for(const uri of uris) {
271
+
272
+ if (!output.has(uri)) {
273
+ output.add(uri);
274
+ if (dependencies.has(uri)) {
275
+ expandCacheDependencies(dependencies.get(uri)!, dependencies, output);
276
+ }
194
277
  }
195
278
 
196
279
  }
197
280
 
281
+ return output;
282
+
198
283
  }
package/src/field.ts CHANGED
@@ -30,7 +30,7 @@ interface BaseField<T> {
30
30
  *
31
31
  * This is similar to the HTML5 "type" attribute on forms.
32
32
  */
33
- type: string,
33
+ type: string;
34
34
 
35
35
  /**
36
36
  * The current (pre-filed) value on the form.
@@ -81,15 +81,15 @@ interface BooleanField extends BaseField<boolean> {
81
81
  * special features.
82
82
  */
83
83
  interface BasicStringField extends BaseField<string> {
84
- type: 'color' | 'email' | 'password' | 'search' | 'tel' | 'url',
84
+ type: 'color' | 'email' | 'password' | 'search' | 'tel' | 'url';
85
85
  }
86
86
 
87
87
  interface RangeStringField extends RangeField<string> {
88
- type: 'date' | 'month' | 'time' | 'week',
88
+ type: 'date' | 'month' | 'time' | 'week';
89
89
  }
90
90
 
91
91
  interface DateTimeField extends RangeField<Date> {
92
- type: 'datetime' | 'datetime-local',
92
+ type: 'datetime' | 'datetime-local';
93
93
  }
94
94
 
95
95
  interface HiddenField extends BaseField<string | number | null | boolean> {
@@ -115,7 +115,7 @@ export type OptionsDataSource = {
115
115
  *
116
116
  * If specified as a plain array, use array values as labels and values.
117
117
  */
118
- options: Record<string, string> | string[]
118
+ options: Record<string, string> | string[];
119
119
  } | {
120
120
  /**
121
121
  * If dataSource is specified, we'll grab the list of options from a
@@ -147,7 +147,7 @@ export type OptionsDataSource = {
147
147
  * This is ignored if it doesn't apply for the media type.
148
148
  */
149
149
  valueField?: string;
150
- }
150
+ };
151
151
  } | {
152
152
  /**
153
153
  * If 'linkSource' is specified, we assume that the value will be a URI.
@@ -158,7 +158,7 @@ export type OptionsDataSource = {
158
158
  linkSource: {
159
159
  href: string;
160
160
  rel: string;
161
- }
161
+ };
162
162
  }
163
163
 
164
164
  /**
@@ -181,10 +181,10 @@ type SelectFieldMulti = BaseField<string> & {
181
181
 
182
182
 
183
183
  interface TextField extends BaseField<string> {
184
- type: 'text',
184
+ type: 'text';
185
185
  minLength?: number;
186
186
  maxLength?: number;
187
- pattern?: RegExp,
187
+ pattern?: RegExp;
188
188
  }
189
189
 
190
190
  interface TextAreaField extends BaseField<string> {
package/src/http/error.ts CHANGED
@@ -28,12 +28,12 @@ export class HttpError extends Error {
28
28
  export class Problem extends HttpError {
29
29
 
30
30
  body: {
31
- type: string
32
- title?: string
33
- status: number
34
- detail?: string
35
- instance?: string
36
- [x: string]: any
31
+ type: string;
32
+ title?: string;
33
+ status: number;
34
+ detail?: string;
35
+ instance?: string;
36
+ [x: string]: any;
37
37
  };
38
38
 
39
39
  constructor(response: Response, problemBody: Record<string, any>) {
@@ -65,7 +65,7 @@ export class Problem extends HttpError {
65
65
  export default async function problemFactory(response: Response): Promise<HttpError | Problem> {
66
66
 
67
67
  const contentType = response.headers.get('Content-Type');
68
- if (contentType && contentType.match(/^application\/problem\+json/i)) {
68
+ if (contentType?.match(/^application\/problem\+json/i)) {
69
69
  const problemBody = await response.json();
70
70
  return new Problem(response, problemBody);
71
71
  } else {
@@ -6,7 +6,7 @@ import {
6
6
  } from 'node-fetch';
7
7
 
8
8
  // Registering Fetch as a glboal polyfill
9
- (<any> global).fetch = nodeFetch;
10
- (<any> global).Request = Request;
11
- (<any> global).Headers = Headers;
12
- (<any> global).Response = Response;
9
+ (global as any).fetch = nodeFetch;
10
+ (global as any).Request = Request;
11
+ (global as any).Headers = Headers;
12
+ (global as any).Response = Response;
@@ -15,7 +15,7 @@ export class Fetcher {
15
15
 
16
16
  middlewares: [RegExp, FetchMiddleware][] = [];
17
17
 
18
- advertiseKetting: boolean = true
18
+ advertiseKetting: boolean = true;
19
19
 
20
20
  /**
21
21
  * A wrapper for MDN fetch()
@@ -49,10 +49,10 @@ export class Fetcher {
49
49
  */
50
50
  getMiddlewaresByOrigin(origin: string): FetchMiddleware[] {
51
51
 
52
- return this.middlewares.filter( ([regex, middleware]) => {
52
+ return this.middlewares.filter( ([regex]) => {
53
53
  return regex.test(origin);
54
54
 
55
- }).map( ([regex, middleware]) => {
55
+ }).map( ([, middleware]) => {
56
56
  return middleware;
57
57
  });
58
58
 
package/src/index.ts CHANGED
@@ -15,11 +15,9 @@ export {
15
15
  isState,
16
16
  } from './state';
17
17
 
18
- export {
19
- ForeverCache,
20
- NeverCache,
21
- ShortCache,
22
- } from './cache';
18
+ export { ForeverCache } from './cache/forever';
19
+ export { ShortCache } from './cache/short';
20
+ export { NeverCache } from './cache/never';
23
21
 
24
22
  export { default as basicAuth } from './http/basic-auth';
25
23
  export { default as bearerAuth } from './http/bearer-auth';
package/src/link.ts CHANGED
@@ -5,7 +5,7 @@ export type Link = {
5
5
  /**
6
6
  * Target URI
7
7
  */
8
- href: string,
8
+ href: string;
9
9
 
10
10
  /**
11
11
  * Context URI.
@@ -17,17 +17,17 @@ export type Link = {
17
17
  /**
18
18
  * Relation type
19
19
  */
20
- rel: string,
20
+ rel: string;
21
21
 
22
22
  /**
23
23
  * Link title
24
24
  */
25
- title?: string,
25
+ title?: string;
26
26
 
27
27
  /**
28
28
  * Content type hint of the target resource
29
29
  */
30
- type?: string,
30
+ type?: string;
31
31
 
32
32
  /**
33
33
  * Anchor.
@@ -35,22 +35,22 @@ export type Link = {
35
35
  * This describes where the link is linked from, from for example
36
36
  * a fragment in the current document
37
37
  */
38
- anchor?: string,
38
+ anchor?: string;
39
39
 
40
40
  /**
41
41
  * Language of the target resource
42
42
  */
43
- hreflang?: string,
43
+ hreflang?: string;
44
44
 
45
45
  /**
46
46
  * HTML5 media attribute
47
47
  */
48
- media?: string,
48
+ media?: string;
49
49
 
50
50
  /**
51
51
  * If templated is set to true, the href is a templated URI.
52
52
  */
53
- templated?: boolean,
53
+ templated?: boolean;
54
54
 
55
55
  /**
56
56
  * Link hints, as defined in draft-nottingham-link-hint
@@ -67,7 +67,7 @@ type NewLink = Omit<Link, 'context'>;
67
67
  */
68
68
  export class Links {
69
69
 
70
- private store: Map<string, Link[]>
70
+ private store: Map<string, Link[]>;
71
71
 
72
72
  constructor(public defaultContext: string, links?: Link[] | Links) {
73
73
 
@@ -221,5 +221,5 @@ export class LinkNotFound extends Error {}
221
221
  * A key->value map of variables to place in a templated link
222
222
  */
223
223
  export type LinkVariables = {
224
- [key: string]: string | number
224
+ [key: string]: string | number;
225
225
  };
@@ -13,7 +13,7 @@ export default function(client: Client): FetchMiddleware {
13
13
 
14
14
  if (!request.headers.has('Accept')) {
15
15
  const acceptHeader = Object.entries(client.contentTypeMap).map(
16
- ([contentType, [stateFactory, q]]) => contentType + ';q=' + q
16
+ ([contentType, [, q]]) => contentType + ';q=' + q
17
17
  ).join(', ');
18
18
  request.headers.set('Accept', acceptHeader);
19
19
  }
@@ -29,6 +29,22 @@ export default function(client: Client): FetchMiddleware {
29
29
  }
30
30
 
31
31
  const response = await next(request);
32
+
33
+ // If the response had a Link: rel=inv-by header, it means that when the
34
+ // target uri's cache expires, the uri of this resource should also
35
+ // expire.
36
+ if (response.headers.has('Link')) {
37
+ for (const httpLink of LinkHeader.parse(response.headers.get('Link')!).rel('inv-by')) {
38
+ const uri = resolve(request.url, httpLink.uri);
39
+ if (client.cacheDependencies.has(uri)) {
40
+ client.cacheDependencies.get(uri)!.add(request.url);
41
+ } else {
42
+ client.cacheDependencies.set(uri, new Set([request.url]));
43
+ }
44
+ }
45
+ }
46
+
47
+
32
48
  if (isSafeMethod(request.method)) {
33
49
  return response;
34
50
  }
@@ -39,10 +55,13 @@ export default function(client: Client): FetchMiddleware {
39
55
  }
40
56
 
41
57
  // We just processed an unsafe method, lets notify all subsystems.
42
- const expireUris = [];
43
- if (!noStaleEvent && request.method !== 'DELETE') {
44
- // Sorry for the double negative
45
- expireUris.push(request.url);
58
+ const stale = [];
59
+ const deleted = [];
60
+
61
+ if (request.method === 'DELETE') {
62
+ deleted.push(request.url);
63
+ } else if (!noStaleEvent) {
64
+ stale.push(request.url);
46
65
  }
47
66
 
48
67
  // If the response had a Link: rel=invalidate header, we want to
@@ -50,17 +69,22 @@ export default function(client: Client): FetchMiddleware {
50
69
  if (response.headers.has('Link')) {
51
70
  for (const httpLink of LinkHeader.parse(response.headers.get('Link')!).rel('invalidates')) {
52
71
  const uri = resolve(request.url, httpLink.uri);
53
- expireUris.push(uri);
72
+ stale.push(uri);
54
73
  }
55
74
  }
56
75
 
57
76
  // Location headers should also expire
58
77
  if (response.headers.has('Location')) {
59
- expireUris.push(
78
+ stale.push(
60
79
  resolve(request.url, response.headers.get('Location')!)
61
80
  );
62
81
  }
63
- // Content-Location headers should also expire
82
+
83
+ client.clearResourceCache(stale, deleted);
84
+
85
+ // If the response had a 'Content-Location' header, it means that the
86
+ // response body is the _new_ state for the url in the content-location
87
+ // header, so we store it!
64
88
  if (response.headers.has('Content-Location')) {
65
89
  const cl = resolve(request.url, response.headers.get('Content-Location')!);
66
90
  const clState = await client.getStateForResponse(
@@ -70,23 +94,6 @@ export default function(client: Client): FetchMiddleware {
70
94
  client.cacheState(clState);
71
95
  }
72
96
 
73
- for (const uri of expireUris) {
74
- client.cache.delete(request.url);
75
-
76
- const resource = client.resources.get(uri);
77
- if (resource) {
78
- // We have a resource for this object, notify it as well.
79
- resource.emit('stale');
80
- }
81
- }
82
- if (request.method === 'DELETE') {
83
- client.cache.delete(request.url);
84
- const resource = client.resources.get(request.url);
85
- if (resource) {
86
- resource.emit('delete');
87
- }
88
- }
89
-
90
97
  return response;
91
98
 
92
99
  };
@@ -27,6 +27,7 @@ export default function(): FetchMiddleware {
27
27
  }
28
28
  }
29
29
 
30
+ /* eslint-disable-next-line no-console */
30
31
  console.warn(msg);
31
32
 
32
33
  }
package/src/resource.ts CHANGED
@@ -197,7 +197,7 @@ export class Resource<T = any> extends EventEmitter {
197
197
  switch (response.status) {
198
198
  case 201:
199
199
  if (response.headers.has('location')) {
200
- return this.go(<string> response.headers.get('location'));
200
+ return this.go(response.headers.get('location')!);
201
201
  }
202
202
  throw new Error('Could not follow after a 201 request, because the server did not reply with a Location header. If you sent a Location header, check if your service is returning "Access-Control-Expose-Headers: Location".');
203
203
  case 204 :
@@ -216,7 +216,7 @@ export class Resource<T = any> extends EventEmitter {
216
216
  *
217
217
  * If the server responds with 200 Status code this will return a State object
218
218
  */
219
- async patch(options: PatchRequestOptions): Promise<void | State<T>> {
219
+ async patch(options: PatchRequestOptions): Promise<undefined | State<T>> {
220
220
 
221
221
  const response = await this.fetchOrThrow(
222
222
  optionsToRequestInit('PATCH', options)
@@ -307,7 +307,7 @@ export class Resource<T = any> extends EventEmitter {
307
307
  */
308
308
  clearCache(): void {
309
309
 
310
- this.client.cache.delete(this.uri);
310
+ this.client.clearResourceCache([this.uri],[]);
311
311
 
312
312
  }
313
313
 
@@ -385,7 +385,7 @@ export declare interface Resource<T = any> {
385
385
  * It will also trigger when calling 'PUT' with a full state object,
386
386
  * and when updateCache() was used.
387
387
  */
388
- on(event: 'update', listener: (state: State) => void) : this
388
+ on(event: 'update', listener: (state: State) => void) : this;
389
389
 
390
390
  /**
391
391
  * Subscribe to the 'stale' event.
@@ -408,7 +408,7 @@ export declare interface Resource<T = any> {
408
408
  * Subscribe to the 'update' event and unsubscribe after it was
409
409
  * emitted the first time.
410
410
  */
411
- once(event: 'update', listener: (state: State) => void) : this
411
+ once(event: 'update', listener: (state: State) => void) : this;
412
412
 
413
413
  /**
414
414
  * Subscribe to the 'stale' event and unsubscribe after it was
@@ -425,7 +425,7 @@ export declare interface Resource<T = any> {
425
425
  /**
426
426
  * Unsubscribe from the 'update' event
427
427
  */
428
- off(event: 'update', listener: (state: State) => void) : this
428
+ off(event: 'update', listener: (state: State) => void) : this;
429
429
 
430
430
  /**
431
431
  * Unsubscribe from the 'stale' event
@@ -440,7 +440,7 @@ export declare interface Resource<T = any> {
440
440
  /**
441
441
  * Emit an 'update' event.
442
442
  */
443
- emit(event: 'update', state: State) : boolean
443
+ emit(event: 'update', state: State) : boolean;
444
444
 
445
445
  /**
446
446
  * Emit a 'stale' event.
@@ -457,7 +457,7 @@ export declare interface Resource<T = any> {
457
457
  export default Resource;
458
458
 
459
459
  type StrictRequestInit = RequestInit & {
460
- headers: Headers,
460
+ headers: Headers;
461
461
  };
462
462
 
463
463
  /**
@@ -9,7 +9,7 @@ import { entityHeaderNames } from '../http/util';
9
9
 
10
10
  type HeadStateInit = {
11
11
 
12
- client: Client,
12
+ client: Client;
13
13
  uri: string;
14
14
  links: Links;
15
15
 
@@ -20,7 +20,7 @@ type HeadStateInit = {
20
20
  }
21
21
 
22
22
  type StateInit<T> = {
23
- client: Client,
23
+ client: Client;
24
24
  uri: string;
25
25
  data: T;
26
26
  headers: Headers;
@@ -84,6 +84,7 @@ export class BaseHeadState implements HeadState {
84
84
  href = resolve(link);
85
85
  }
86
86
  if (link.hints?.status === 'deprecated') {
87
+ /* eslint-disable-next-line no-console */
87
88
  console.warn(`[ketting] The ${link.rel} link on ${this.uri} is marked deprecated.`, link);
88
89
  }
89
90
 
@@ -102,6 +103,7 @@ export class BaseHeadState implements HeadState {
102
103
  return this.links.getMany(rel).map( link => {
103
104
 
104
105
  if (link.hints?.status === 'deprecated') {
106
+ /* eslint-disable-next-line no-console */
105
107
  console.warn(`[ketting] The ${link.rel} link on ${this.uri} is marked deprecated.`, link);
106
108
  }
107
109
  const href = resolve(link);
@@ -171,7 +173,7 @@ export class BaseState<T> extends BaseHeadState implements State<T> {
171
173
  }
172
174
  for(const action of this.actionInfo) {
173
175
  if (action.name === name) {
174
- return new SimpleAction(this.client, this.actionInfo[0]);
176
+ return new SimpleAction(this.client, action);
175
177
  }
176
178
  }
177
179
  throw new ActionNotFound('This State defines no action');