relaks-django-data-source
v2.0.3
Published
Django data source for Relaks
Downloads
37
Readme
Relaks Django Data Source
This module lets you access a Django server from a React app that use Relaks. It's designed to work with the Django REST framework.
Installation
npm --save-dev install relaks-django-data-source
Usage
import DjangoDataSource from 'relaks-django-data-source';
let options = {
abbreviatedFolderContents: false,
authorizationKeyword: 'Token',
baseURL: 'https://swapi.co/api',
refreshInterval: 60 * 1000,
};
let dataSource = new DjangoDataSource(options);
dataSource.activate();
/* Root-level React component */
class FrontEnd extends PureComponent {
constructor(props) {
super(props);
let { dataSource } = props;
this.state = {
database: new Database(dataSource);
}
}
componentDidMount() {
let { dataSource } = this.props;
dataSource.addEventListener('change', this.handleDataSourceChange);
}
/* ... */
handleDataSourceChange = (evt) => {
let { dataSource } = this.props;
let database = new Database(dataSource);
this.setState({ database });
}
}
Components are expected to access functionalities of the data source through a proxy object--Database
in the sample code above. See the documentation of Relaks for an explanation. A default implementation is provided for reference purpose. It's recommended that you create your own.
Options
abbreviatedFolderContents
A boolean value indicating whether objects in directory listings are always different from those fetched individually. Normally, when you ask for a single object, the data source will check to see if the object can be found in a cached directory query. For example, it'll check queries on /articles/
when you request /articles/5/
. This behavior would lead to erroneous results if objects in a directory listing only contains a subset of their properties. Setting abbreviatedFolderContents
to true
disables it completely.
You can also flag queries as abbreviated selectively.
The default value is false
.
authorizationKeyword
The keyword that precedes the token in the HTTP Authorization header:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
The default matches what the Django REST Framework's TokenAuthentication scheme uses. You don't need to supply this unless you have subclassed TokenAuthentication
and changed the keyword to something else.
baseURL
The base URL of the remote server. It'll be added to any URL that isn't absolute.
fetchFunc
An alternative function to be used in place of the browser's built-in fetch()
.
forceHTTPS
Ensures HTTPS is used if baseURL
starts with "https:". True by default.
refreshInterval
The amount of time, in milliseconds, to wait before rerunning data queries to ensure freshness. The data source caches all queries. When a query matches one that was performed before, the results obtained earlier will be returned immediately. If the amount of time elapsed since exceeds refreshInterval
, the data source will rerun the query. If the results differ in anyway, a change
event will occur.
You can also manually flag queries as out-of-date by calling invalidate().
Methods
Event listeners:
Activation
Data retrieval:
Cache invalidation:
Data modification:
Access control:
- authenticate()
- authorize()
- cancelAuthentication()
- cancelAuthorization()
- isAuthorized()
- revokeAuthorization()
HTTP request:
addEventListener()
function addEventListener(name: string, handler: function, beginning?:boolean): void
Attach an event listener to the data source. handler
will be called whenever events of type
occur. When beginning
is true, the listener will be place before any existing listeners. It's otherwise added at the end of the list.
Inherited from relaks-event-emitter.
removeEventListener()
function removeEventListener(name: string, handler: function): void
Detach an event listener from the data source. handler
and type
must match what were given to addEventListener()
.
Inherited from relaks-event-emitter.
waitForEvent()
async function waitForEvent(type: string): Event
Return a promise that is fulfilled when an event of the specified type occurs.
Inherited from relaks-event-emitter.
activate()
function activate(): void
Activate the data source, allowing it to fetch data from a remote server.
deactivate()
function deactivate(): void
Deactivate the data source, keeping it from performing data requests. Operations that require accessing a remote server will be on hold indefinitely, until activate()
is called.
The data source will continue to return cached data after its deactivation.
fetchList()
async function fetchList(url: string, options?: object): object[]
Conceptually, fetch all objects in a directory. In actuality, the method will only return the first page of results initially (when pagination is enabled). Attached to the returned array will be a method called more()
. When it's called, an additional page will be fetched and appended to the list.
This method is designed for handling large result sets with continuous scrolling (as opposed to traditional pagination).
In addition to more()
, the returned array will also have the property total
. It's the number of objects in the directory on the server. The standard property length
gives the number of objects already retrieved.
more()
and total
are always present, even when pagination is not available. A call to more()
does nothing when there are no more pages.
By default, fetchList()
will return as soon as it has one page of results. Specifying the option minimum
forces it to wait until a certain number of objects have become available. When minimum
is a negative number, that's interpreted as the difference from the total. When minimum
is a string, it's expected to hold a percentage of the total. For example, 100%
means the complete data set.
Options:
minimum
- the minimum number of objects to fetch (default: any)abbreviated
- indicates that the objects found aturl
do not have all their properties and they should not be used to fulfill calls tofetchOne()
afterInsert
- see afterInsert (default: "refresh")afterUpdate
- see afterUpdate (default: "refresh")afterDelete
- see afterDelete (default: "remove")
fetchMultiple()
async function fetchMultiple(urls: string[], options?: object): object[]
Fetch multiple objects in a single call. This is convenience method designed for handling one-to-many relations. It calls fetchOne()
internally.
By default, the promise returned by fetchMultiple()
is not fulfilled until every object is retrieved from the remote server. When the option minimum
is specified, the promise will fulfill immediately when the number of cached objects meets the requirement. null
will appear in place of an object in the array when it's uncached. When the uncached objects eventually arrive, the data source emits a change
event. Subsequent calls to fetchMultiple()
will then return all requested objects.
Options:
minimum
- the minimum number of objects to fetch (default: all)
fetchOne()
async function fetchOne(url: string, options?: object): object
Fetch an object from the server. This method will check the results of calls to fetchPage() and fetchList() to see if the object in question hasn't been fetched already.
Options:
afterUpdate
- see afterUpdate (default: "replace")afterDelete
- see afterDelete (default: "remove")
fetchPage()
async function fetchPage(url: string, page: number, options?: object): object[]
Fetch a single page of a directory listing. All objects will be returned if the server doesn't support pagination.
Options:
abbreviated
- indicates that the objects found aturl
do not have all their properties and they should not be used to fulfill calls tofetchOne()
afterInsert
- see afterInsert (default:"refresh"
)afterUpdate
- see afterUpdate (default:"refresh"
)afterDelete
- see afterDelete (default:"refresh"
)
invalidate()
function invalidate(time: string): boolean
Flag matching data queries performed before the specified time as out-of-date. time
should be an ISO timestamp (a Date
object is also acceptable). The methods returns true
when queries have been invalidated and false
otherwise.
The data source emits a change
event when queries are invalidated.
invalidateList()
function invalidateList(url:string, options?: object): boolean
Invalidate a query performed earlier using fetchList()
.
invalidateMultiple()
function invalidateMultiple(urls:string[], options?: object): boolean
Invalidate multiple queries performed earlier using fetchOne()
.
invalidatePage()
function invalidatePage(url:string, page: number, options?: object): boolean
Invalidate a query performed earlier using fetchPage()
.
invalidateOne()
function invalidateOne(url:string, options?: object): boolean
Invalidate a query performed earlier using fetchOne()
.
deleteOne()
async function deleteOne(folderURL: string, object: object): object
async function deleteOne(object: object): object
Delete an object on the remote server. The afterDelete
hooks of cached queries are invoked afterward.
When URL keys are used, folderURL
can be omitted (since the object contains its own URL).
deleteMultiple()
function deleteMultiple(folderURL: string, objects: object[]): object[]
function deleteMultiple(objects: object[]): object[]
Delete multiple objects on the remote server. The afterDelete
hooks of cached queries are invoked afterward.
When URL keys are used, folderURL
can be omitted (since the objects contain their own URLs).
insertOne()
async function insertOne(folderURL: string, object: object): object
Insert an object into a directory on the remote server. The afterInsert
hooks of cached queries are invoked afterward. The inserted object will be available through fetchOne()
immediately.
insertMultiple()
async function insertMultiple(folderURL: string, objects: object[]): object[]
Insert multiple objects into a directory on the remote server. The afterInsert
hooks of cached queries are invoked afterward. The inserted objects will be available through fetchOne()
immediately.
updateOne()
async function updateOne(folderURL: string, object: object): object
async function updateOne(object: object): object
Update an object on the remote server. The afterUpdate
hooks of cached queries are invoked afterward.
When URL keys are used, folderURL
can be omitted (since the object contains its own URL).
updateMultiple()
function updateMultiple(folderURL: string, objects: object[]): object[]
function updateMultiple(objects: object[]): object[]
Update multiple objects on the remote server. The afterUpdate
hooks of cached queries are invoked afterward.
When URL keys are used, folderURL
can be omitted (since the objects contain their own URLs).
authenticate()
async function authenticate(loginURL: string, credentials: object, allowURLs?: string[]): boolean
Gain elevated access to a Django server. loginURL
is the REST endpoint for logging in. credentials
is an object containng username
(or email
) and password
. allowURLs
is the list of URLs that will become accessible. This parameter is designed for apps that access multiple servers. It can typically be omitted. Its default value is [ "/" ]
, meaning all resources under baseURL
will become accessible.
The fulfillment value of this method is true
when the login process succeeds. If it fails, the promise will be rejected.
An authorization
event occurs when the data source acquire an authorization token. This give you a chance to save the token for use in a future session.
authorize()
async function authorize(token: string, allowURLs?: string[], fresh?: boolean): boolean
Provide a Django authorization token to the data source, likely one that was saved from an earlier authentication. allowURLs
indicates the scope of access. It can typically be omitted. fresh
indicates whether the token was just obtained from the remote server. If omitted, evt.fresh
will be false
in the subsequent authorization
event. Your code can then elect not to save the token again.
The method will return a promise that immediately resolves to false
if token
is empty or if the data source has previously encountered a 401 or 403 error using that token.
cancelAuthentication()
function cancelAuthentication(allowURLs?: string[]): void
Tell the data source to stop waiting for authentication. Operations that had encountered the HTTP status code 401 will then fail.
cancelAuthorization()
function cancelAuthorization(denyURLs?: string[]): void
Remove authorization to certain URLs. This can be used to force the user to authenticate again before a sensitive area of your app can be accessed.
This method does not trigger any event.
isAuthorized()
function isAuthorized(url?: string): boolean
Return true if the data source has an authorization token for the identicated location. url
can be omitted, in which case it defaults to "/"
.
revokeAuthorization()
function revokeAuthorization(logoutURL: string, denyURLs?: string[]): void
Log out from a Django server. logoutURL
is the REST endpoint for logging out. denyURLs
is the list of URLs from which authorization will be removed. It can typically be omitted.
An deauthorization
event will occur afterward. This give you a chance to remove a saved authorization token.
A change event will also occur after the data source removed cached queries.
delete()
async function delete(url: string): null
Low-level function that performs an HTTP DELETE operation.
get()
async function get(url: string): object
Low-level function that performs an HTTP GET operation.
post()
async function post(url: string, object: object): object
Low-level function that performs an HTTP POST operation.
put()
async function put(url: string, object: object): object
Low-level function that performs an HTTP PUT operation.
Hooks
afterDelete
When objects are deleted using deleteOne()
or deleteMultiple()
, a query's afterDelete
hook is invoked so that cached results are updated.
For queries performed through fetchOne()
, the hook function has the form:
function afterDeleteHook(object: object, deletedObject: object): object
object
is the cached object before, while deletedObject
is a copy of the same object. If the function returns an object, that'll be the result when the query is run again. If it returns true
, then the query is removed from the cache (the default behavior). If it returns false
, then the object will continue to be available despite its disappearance on the server.
For queries performed through fetchPage()
or fetchList
, the hook function has the form:
function afterDeleteHook(objects: objects[], deletedObjects: objects[]): objects[]
objects
are the currently cached objects, while deletedObjects
is a list of deleted objects. The function should return a new array not containing the deleted objects (the default behavior). If there's no change, it should return false
. If it returns true
, the query will be refreshed.
A string can be used to specify a predefined hook function. The possible values are "refresh"
, "ignore"
, "remove"
.
afterInsert
When new objects are inserted into a directory using insertOne()
or insertMultiple()
, a query's afterInsert
hook is invoked so that cached results are updated. Only queries performed through fetchPage()
or fetchList()
have this hook.
The hook function has the following form:
function afterInserHook(objects: object[], newObjects: object[]): objects[]
objects
are the currently cached objects, while newObjects
is a list of newly created objects. The function should return a new array with the new objects inserted at the correct positions. If there's no change (because none of the new objects actually matches the query), the function should return false
. If the function return true
, the query will be invalidated.
A string can be used to specify a predefined hook function. The possible values are "refresh"
, "ignore"
, "unshift"
, and "push"
.
afterUpdate
When existing objects are modified using updateOne()
or updateMultiple()
, a query's afterUpdate
hook is invoked so that cached results are updated. It's also invoked when the data source fetches a more recent copy of an object. Consider the following scenario:
fetchOne()
retrieves object A from the server- Object A is modified on the server side
fetchList()
retrieves object A, B, C, and D from the server
Query 1's afterUpdate
hook will be invoked because query 3 has obtained a newer copy of object A. Since the default hook in this case is "replace"
, query 1 will now also yield the newer copy.
For queries performed through fetchOne()
, the hook function has the form:
function afterUpdateHook(object: object, newObject: object): object
object
is the cached object before, while newObject
is the object returned by the server after the save operation. If the function returns an object, then that becomes the cached object. If it returns false
, that means no change occurred. If it returns true
, the query is invalidated.
For queries performed through fetchPage()
or fetchList
, the hook function has the form:
function afterUpdateHook(objects: object[], newObjects: object[]): object[]
objects
are the currently cached objects, while newObjects
is a list of modified objects. The function should return a new array with old objects replaced by new ones. If there's no change, the function should return false
. If the function return true
, the query will be invalidated.
The function might need to sort the objects if changes can impact their order.
A string can be used to specify a predefined hook function. The possible values are "refresh"
, "ignore"
, "replace"
.
Predefined hook functions
"ignore"
- return false to indicate there's no need to change the cached results"push"
- place new objects at the end of the list"refresh"
- return true to request refreshing of the query"remove"
- remove deleted objects from the list"replace"
- replace currently cached objects with the updated copy from the server"unshift"
- place new objects at the beginning of the list
Events
authentication
An authentication
event is emitted when the server responds to a request with the HTTP status code 401 ("Unauthorized").
Default action:
Wait for authentication to occur then try again.
Properties:
url
- the URL that triggered the 401 responsedefaultPrevented
- whetherpreventDefault()
was calledpropagationStopped
- whetherstopImmediatePropagation()
was calledtarget
- the data sourcetype
-"authentication"
Methods:
postponeDefault(promise: Promise)
- wait for given promise to be fulfilled and fail the operation if the fulfillment value is falsepreventDefault()
- fail the operation that trigger the authentication requeststopImmediatePropagation()
- stop other listeners from receiving the event
authorization
An authorization
event is emitted when the data source receives an authorization token, either from the server after a call to authenticate()
or from your app via authorize()
. A change
event will follow if the default action is not prevented.
Default action:
Allow operations waiting for authentication to proceed.
Properties:
allowURLs
- a list of URLs that have become accessiblefresh
- whether the token was freshly issued by the servertoken
- the authorization tokendefaultPrevented
- whetherpreventDefault()
was calledpropagationStopped
- whetherstopImmediatePropagation()
was calledtarget
- the data sourcetype
-"authorization"
Methods:
postponeDefault(promise: Promise)
- postpone the decision on whether to proceed until the given promise is fulfilledpreventDefault()
- stop pending operations from proceeding despite authorization having been grantedstopImmediatePropagation()
- stop other listeners from receiving the event
Generally, there's no reason to prevent the default behavior from happen. Doing so only makes sense if the app has failEd to gain some other necessary authorization.
change
A change
event is emitted whenever rerunning queries might yield new result sets. It's also emitted after the data source has gained or lost autorization.
Properties:
propagationStopped
- whetherstopImmediatePropagation()
was calledtarget
- the data sourcetype
-"change"
Methods:
stopImmediatePropagation()
- stop other listeners from receiving the event
deauthorization
A deauthorization
event is emitted after revokeAuthorization()
has logged out from a Django server. A change
event will follow even if the default action is prevented.
Default action:
Clear cache queries.
Properties:
denyURLs
- a list of URLs that might no longer be accessibledefaultPrevented
- whetherpreventDefault()
was calledpropagationStopped
- whetherstopImmediatePropagation()
was calledtarget
- the data sourcetype
-"deauthorization"
Methods:
postponeDefault(promise: Promise)
- postpone the clearing of cached queries until the given promise is fulfilledpreventDefault()
- prevent clearing of cached queriesstopImmediatePropagation()
- stop other listeners from receiving the event
Examples
- Starwars API: Episode V - sequel to the first Starwars API example
- Starwars API: Episode VI - The Server Strikes Back - demonstrates how to create an isomorphic app
- Django todo list - demonstrates authentication and data saving using relaks-django-data-source
License
This project is licensed under the MIT License - see the LICENSE file for details