-
-
Notifications
You must be signed in to change notification settings - Fork 32
Upgrading
Ketting version 1 to 5 was relatively stable, but Ketting 6 introduces larger changes. Many of these changes were made to fix long-standing architecture issues, and open the door to new features and strong integration with Front- end frameworks such as React.
A non-exhaustive list of new features:
- A react-ketting package with deep React integration via Hooks.
- Multiple cache strategies, such as
forever
,short
andnever
. - Support for fetch-middlewares. OAuth2 is reimplemented as such a plugin. these plugins can be added globally, or per-origin.
-
get()
now returns aState
object, and functions such asput()
will require one as an argument. -
put()
now automatically updates the state cache. - Support for
HEAD
requests and following links fromHEAD
response headers. - It's now possible to set
Content-Type
and other headers on every resource method such asResource.post()
,Resource.patch()
, etc. -
update
,stale
anddelete
events on Resources. - PKCE support for OAuth2.
- Links can now be mutated and sent back to the server.
- Support for submitting HTML forms, Siren and HAL Forms (experimental).
- Nested transcluded items/embeds.
- A separate
post()
andpostFollow()
method. - Better support for binary responses and
text/*
responses.
By far the largest change is the use of State
objects. In Ketting 5 and
below, you would get a parsed HTTP response when calling get()
.
// Ketting 5
const body = await resource.get();
The result of .get()
is now a State
object. To get the response body,
you need to get it from the .data
property.
// Ketting 6
const state = await resource.get();
const body = state.data;
Similarly, when doing a PUT
request, you need also wrap your body:
// Ketting 5
await resource.put({
title: 'Hello world'
});
// Ketting 6
await resource.put({
data: {
title: 'Hello world'
}
});
You might ask why. We wanted the ability to get more information from responses, and pass more information when doing requests. Implementing this in a BC way would have been a pain and make for an ugly API.
The State object adds a ton of data that would otherwise have been difficult to get to:
-
State.data
- The response data. -
State.uri
- URI of the resource -
State.links
- List of all links related to the response. -
State.contentType
- Content type string -
State.contentHeaders
- HTTP Headers related to the content. -
State.action()
- Execute actions such as submitting HTML forms or submit Siren actions. -
State.serializeBody()
- Return a serialized version of the body.
Another design goal was that the State
object should entirely encapsulate
state synchronous, so without the use of promises or async/await. It's all
there. This makes it much easier to use in functional programming paradigms,
and modern frontend frameworks.
It also allows users to for example: Get state, add a (hal-) link and submit the result again:
// Ketting 6
const state = await resource.get();
state.links.add('author', 'Dr. Pidgin');
await resource.put(state);
Lastly, there's a subscription framework related to states. When Ketting
'knows' of a new state, it will emit an update
event.
// Ketting 6
resource.on('update', (state: State) => {
// We got a new State
// Either caused by a `PUT` request, a `GET` response, or maybe the
// resource was embedded in another resource's response.
});
The following functions are now deprecated:
Resource.hasLink()
Resource.links()
Resource.link()
They will be removed in a future major Ketting release.
This is how you migrate:
// Ketting 5
const link = await resource.link('rel');
const links = await resource.links('rel');
const hasLink = await resource.hasLink('rel');
// Ketting 6
const state = await resource.get();
const link = state.links.get('rel');
const links = state.links.getMany('rel');
const hasLink = state.links.has('rel');
Once you have a State
object, you get access to all the link information
syncronously.
In Ketting 6 you can also do the same with a .head()
request. If links
are in the HTTP headers of the response to HEAD
, they're all there.
The Client.getResource(uri)
and Resource.getResource(uri)
were used to get
a resource based on a (potentially relative) uri.
This function was renamed to .go()
some time ago:
Just search and replace getResource()
to go()
and you're good to go.
In REST apis, POST
typically has 2 major functions:
- To submit an action or a form (RPC style).
- To create a new resource.
It was hard to make a easy-to-use function to cover both cases, especially since the return types of each may be different.
In Ketting 6, the old post()
function is now split in 2 new functions:
-
.post()
for RPC style submission. This returns aState
object, which has the response body and other information. -
.postFollow()
for 'create' actions.
I suspect that most uses of .post()
need to be migrated to .postFollow()
.
Here's how it works:
// Ketting 6
const newResource = await parentResource.postFollow({
data: {
title: "New blog post!",
}
});
newResource
in the above example will be the brand new resource that you
just created. It works because if the server returns a 201 Created
status,
and a Location
header, it knows that a new resource was created and where
it lives.
If the server returned a 204 No Content
or 205 Reset Content
response,
.postFollow()
will simply return the current resource again.
In the past, to set up Ketting with OAuth2 or Basic auth, this was accomplished via a complex settings object in the Ketting constructor:
// Ketting 5
import { Client } from 'ketting';
const client = new Client(
'api.example.org',
{
auth: {
type: 'basic',
userName: 'foo',
password: 'bar'
}
}
);
const client = new Client(
'api.example.org',
{
auth: {
type: 'oauth2',
grant_type: 'authorization_code',
clientId: 'fooClient',
code: '...',
tokenEndpoint: 'https://api.example.org/oauth/token',
}
}
});
In Ketting 6, auhtentication is handled via plugins, or Fetch middlewares.
// Ketting 6
import { Client, basicAuth, oauth2 } from 'ketting';
const client = new Client('api.example.org');
client.use(basicAuth({
userName: 'foo',
password: 'bar'
});
client.use(oauth2({
grant_type: 'authorization_code',
clientId: 'fooClient',
code: '...',
tokenEndpoint: 'https://api.example.org/oauth/token',
});
To only set up authentication for a single domain (origin), use the second
argument of use()
// Ketting 6
client.use(oauth2({
grant_type: 'authorization_code',
clientId: 'fooClient',
code: '...',
tokenEndpoint: 'https://api.example.org/oauth/token',
}, 'api.example.org');
Wildcards are supported here for domain-wide authentication:
// Ketting 6
client.use(oauth2({
grant_type: 'authorization_code',
clientId: 'fooClient',
code: '...',
tokenEndpoint: 'https://api.example.org/oauth/token',
}, '*.example.org');
If you run into issues, we would love to hear from you. We want the docs to be as good as possible, so please open a ticket.
Want to submit changes to the Wiki? Submit a PR here