New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More

@dhis2/app-runtime

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dhis2/app-runtime - npm Package Compare versions

Comparing version 1.5.1 to 2.0.0

@@ -10,15 +10,16 @@ 'use strict';

const uninitializedFetch = async () => {
throw new Error('DHIS2 data context must be initialized, please ensure that you include a <DataProvider> in your application');
const useStaticInput = (staticValue, {
warn = false,
name = 'input'
} = {}) => {
const originalValue = React.useRef(staticValue);
const [value, setValue] = React.useState(() => originalValue.current);
React.useEffect(() => {
if (warn && originalValue.current !== staticValue) {
console.warn(`The ${name} should be static, don't create it within the render loop!`);
}
}, [warn, staticValue, originalValue, name]);
return [value, setValue];
};
const defaultContext = {
baseUrl: '',
apiVersion: 0,
apiUrl: '',
fetch: uninitializedFetch
};
const DataContext = React__default.createContext(defaultContext);
function ownKeys(object, enumerableOnly) {

@@ -72,3 +73,107 @@ var keys = Object.keys(object);

}
const useQueryExecutor = ({
execute,
variables: initialVariables,
singular,
immediate,
onComplete,
onError
}) => {
const [theExecute] = useStaticInput(execute);
const [state, setState] = React.useState({
called: !!immediate,
loading: !!immediate
});
const variables = React.useRef(initialVariables);
const abortControllersRef = React.useRef([]);
const abort = () => {
abortControllersRef.current.forEach(controller => controller.abort());
abortControllersRef.current = [];
};
const refetch = React.useCallback((newVariables = {}) => {
setState(state => !state.called || !state.loading ? {
called: true,
loading: true
} : state);
if (singular) {
abort(); // Cleanup any in-progress fetches
}
const controller = new AbortController();
abortControllersRef.current.push(controller);
variables.current = _objectSpread({}, variables.current, {}, newVariables);
const options = {
variables: variables.current,
signal: controller.signal,
onComplete,
onError
};
return theExecute(options).then(data => {
if (!controller.signal.aborted) {
setState({
called: true,
loading: false,
data
});
return data;
}
return new Promise(() => {});
}).catch(error => {
if (!controller.signal.aborted) {
setState({
called: true,
loading: false,
error
});
}
return new Promise(() => {}); // Don't throw errors in refetch promises, wait forever
});
}, [onComplete, onError, singular, theExecute]);
React.useEffect(() => {
if (immediate) {
refetch();
}
return abort;
}, [immediate, refetch]);
return _objectSpread({
refetch,
abort
}, state);
};
const resolveDynamicQuery = ({
resource,
id,
data,
params
}, variables) => ({
resource,
id: typeof id === 'function' ? id(variables) : id,
data: typeof data === 'function' ? data(variables) : data,
params: typeof params === 'function' ? params(variables) : params
});
const getMutationFetchType = mutation => mutation.type === 'update' ? mutation.partial ? 'update' : 'replace' : mutation.type;
function _defineProperty$1(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const reduceResponses = (responses, names) => responses.reduce((out, response, idx) => {

@@ -79,51 +184,195 @@ out[names[idx]] = response;

const fetchData = (context, query, signal) => {
const names = Object.keys(query);
const requests = names.map(name => query[name]);
const requestPromises = requests.map(q => context.fetch(q, {
signal: signal
}));
return Promise.all(requestPromises).then(responses => reduceResponses(responses, names));
};
class DataEngine {
constructor(link) {
_defineProperty$1(this, "link", void 0);
const useDataQuery = query => {
const context = React.useContext(DataContext);
const [state, setState] = React.useState({
loading: true
});
const [refetchCount, setRefetchCount] = React.useState(0);
const refetch = React.useCallback(() => setRefetchCount(count => count + 1), []);
React.useEffect(() => {
const controller = new AbortController();
this.link = link;
}
const abort = () => controller.abort();
fetchData(context, query, controller.signal).then(data => {
!controller.signal.aborted && setState({
loading: false,
data
query(query, {
variables = {},
signal,
onComplete,
onError
} = {}) {
const names = Object.keys(query);
const queries = names.map(name => query[name]);
return Promise.all(queries.map(q => {
const resolvedQuery = resolveDynamicQuery(q, variables);
return this.link.executeResourceQuery('read', resolvedQuery, {
signal
});
})).then(results => {
const data = reduceResponses(results, names);
onComplete && onComplete(data);
return data;
}).catch(error => {
!controller.signal.aborted && setState({
loading: false,
error
});
}); // Cleanup inflight requests
onError && onError(error);
throw error;
});
}
return abort;
}, [context, refetchCount]); // eslint-disable-line react-hooks/exhaustive-deps
mutate(mutation, {
variables = {},
signal,
onComplete,
onError
} = {}) {
const query = resolveDynamicQuery(mutation, variables);
const result = this.link.executeResourceQuery(getMutationFetchType(mutation), query, {
signal
});
return result.then(data => {
onComplete && onComplete(data);
return data;
}).catch(error => {
onError && onError(error);
throw error;
});
}
return _objectSpread({
refetch
}, state);
}
function _defineProperty$2(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class ErrorLink {
constructor(errorMessage) {
_defineProperty$2(this, "errorMessage", void 0);
this.errorMessage = errorMessage;
}
executeResourceQuery() {
console.error(this.errorMessage);
return Promise.reject(this.errorMessage);
}
}
const errorMessage = 'DHIS2 data context must be initialized, please ensure that you include a <DataProvider> in your application';
const link = new ErrorLink(errorMessage);
const engine = new DataEngine(link);
const defaultContext = {
engine
};
const DataContext = React__default.createContext(defaultContext);
const useDataEngine = () => {
const context = React.useContext(DataContext);
return context.engine;
};
const empty = {};
const useDataQuery = (query, {
onComplete,
onError,
variables = empty
} = {}) => {
const engine = useDataEngine();
const [theQuery] = useStaticInput(query, {
warn: true,
name: 'query'
});
const execute = React.useCallback(options => engine.query(theQuery, options), [engine, theQuery]);
const {
refetch,
loading,
error,
data
} = useQueryExecutor({
execute,
variables,
singular: true,
immediate: true,
onComplete,
onError
});
return {
engine,
refetch,
loading,
error,
data
};
};
const DataQuery = ({
query,
onComplete,
onError,
variables,
children
}) => {
const queryState = useDataQuery(query);
const queryState = useDataQuery(query, {
onComplete,
onError,
variables
});
return children(queryState);
};
const empty$1 = {};
const useDataMutation = (mutation, {
onComplete,
onError,
variables = empty$1
} = {}) => {
const engine = useDataEngine();
const [theMutation] = useStaticInput(mutation, {
warn: true,
name: 'mutation'
});
const execute = React.useCallback(options => engine.mutate(theMutation, options), [engine, theMutation]);
const {
refetch: mutate,
called,
loading,
error,
data
} = useQueryExecutor({
execute,
variables,
singular: false,
immediate: false,
onComplete,
onError
});
return [mutate, {
engine,
called,
loading,
error,
data
}];
};
const DataMutation = ({
mutation,
onComplete,
onError,
variables,
children
}) => {
const mutationState = useDataMutation(mutation, {
onComplete,
onError,
variables
});
return children(mutationState);
};
const ConfigContext = React__default.createContext({

@@ -145,3 +394,80 @@ baseUrl: '..',

function _defineProperty$1(obj, key, value) {
const joinPath = (...parts) => {
const realParts = parts.filter(part => !!part);
return realParts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
};
const encodeQueryParameter = param => {
if (Array.isArray(param)) {
return param.map(encodeQueryParameter).join(',');
}
if (typeof param === 'string') {
return encodeURIComponent(param);
}
if (typeof param === 'number') {
return String(param);
}
if (typeof param === 'object') {
throw new Error('Object parameter mappings not yet implemented');
}
throw new Error('Unknown parameter type');
};
const queryParametersToQueryString = params => Object.keys(params).filter(key => key && params[key]).map(key => `${encodeURIComponent(key)}=${encodeQueryParameter(params[key])}`).join('&');
const actionPrefix = 'action::';
const isAction = resource => resource.startsWith(actionPrefix);
const makeActionPath = resource => joinPath('dhis-web-commons', `${resource.substr(actionPrefix.length)}.action`);
const queryToResourcePath = (apiPath, {
resource,
id,
params = {}
}) => {
const base = isAction(resource) ? makeActionPath(resource) : joinPath(apiPath, resource, id);
if (Object.keys(params).length) {
return `${base}?${queryParametersToQueryString(params)}`;
}
return base;
};
const getMethod = type => {
switch (type) {
case 'create':
return 'POST';
case 'read':
return 'GET';
case 'update':
return 'PATCH';
case 'replace':
return 'PUT';
case 'delete':
return 'DELETE';
}
};
const queryToRequestOptions = (type, {
data
}, signal) => ({
method: getMethod(type),
body: data ? JSON.stringify(data) : undefined,
headers: data ? {
'Content-Type': 'application/json'
} : undefined,
signal
});
function _defineProperty$3(obj, key, value) {
if (key in obj) {

@@ -169,5 +495,5 @@ Object.defineProperty(obj, key, {

_defineProperty$1(this, "type", void 0);
_defineProperty$3(this, "type", void 0);
_defineProperty$1(this, "details", void 0);
_defineProperty$3(this, "details", void 0);

@@ -200,3 +526,3 @@ this.type = type;

ownKeys$1(source, true).forEach(function (key) {
_defineProperty$2(target, key, source[key]);
_defineProperty$4(target, key, source[key]);
});

@@ -215,3 +541,3 @@ } else if (Object.getOwnPropertyDescriptors) {

function _defineProperty$2(obj, key, value) {
function _defineProperty$4(obj, key, value) {
if (key in obj) {

@@ -230,3 +556,27 @@ Object.defineProperty(obj, key, {

}
function fetchData$1(url, options = {}) {
const parseStatus = async response => {
if (response.status === 401 || response.status === 403 || response.status === 409) {
const message = await response.json().then(body => {
return body.message;
}).catch(() => {
return response.status === 401 ? 'Unauthorized' : 'Forbidden';
});
throw new FetchError({
type: 'access',
message,
details: response
});
}
if (response.status < 200 || response.status >= 400) {
throw new FetchError({
type: 'unknown',
message: `An unknown error occurred - ${response.statusText} (${response.status})`,
details: response
});
}
return response;
};
function fetchData(url, options = {}) {
return fetch(url, _objectSpread$1({}, options, {

@@ -244,123 +594,53 @@ credentials: 'include',

});
}).then(response => {
if (response.status === 401 || response.status === 403 || response.status === 409) {
throw new FetchError({
type: 'access',
message: response.statusText,
details: response
});
}).then(parseStatus).then(async response => {
if (response.headers.get('Content-Type') === 'application/json') {
return await response.json(); // Will throw if invalid JSON!
}
if (response.status < 200 || response.status >= 400) {
throw new FetchError({
type: 'unknown',
message: `An unknown error occurred - ${response.statusText} (${response.status})`,
details: response
});
}
return response;
}).then(response => response.json());
return await response.text();
});
}
const joinPath = (...parts) => {
return parts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
};
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
function _defineProperty$5(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return target;
return obj;
}
class RestAPILink {
constructor({
baseUrl,
apiVersion
}) {
_defineProperty$5(this, "apiPath", void 0);
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
_defineProperty$5(this, "baseUrl", void 0);
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
_defineProperty$5(this, "apiVersion", void 0);
return target;
}
const encodeQueryParameter = param => {
if (Array.isArray(param)) {
return param.map(encodeQueryParameter).join(',');
this.baseUrl = baseUrl;
this.apiVersion = apiVersion;
this.apiPath = joinPath('api', String(apiVersion));
}
if (typeof param === 'string') {
return encodeURIComponent(param);
fetch(path, options) {
return fetchData(joinPath(this.baseUrl, path), options);
}
if (typeof param === 'number') {
return String(param);
executeResourceQuery(type, query, {
signal
}) {
return this.fetch(queryToResourcePath(this.apiPath, query), queryToRequestOptions(type, query, signal));
}
if (typeof param === 'object') {
throw new Error('Object parameter mappings not yet implemented');
}
}
throw new Error('Unknown parameter type');
};
const queryParametersToQueryString = params => Object.keys(params).filter(key => key && params[key]).map(key => `${encodeURIComponent(key)}=${encodeQueryParameter(params[key])}`).join('&');
const actionPrefix = 'action::';
const isAction = resource => resource.startsWith(actionPrefix);
const makeActionURL = (baseUrl, resource) => joinPath(baseUrl, 'dhis-web-commons', `${resource.substr(actionPrefix.length)}.action`);
const queryToResourceUrl = (_ref, {
baseUrl,
apiUrl
}) => {
let {
resource
} = _ref,
params = _objectWithoutProperties(_ref, ["resource"]);
const base = isAction(resource) ? makeActionURL(baseUrl, resource) : joinPath(apiUrl, resource);
if (Object.keys(params).length) {
return `${base}?${queryParametersToQueryString(params)}`;
}
return base;
};
const makeContext$1 = ({
baseUrl,
apiVersion
}) => {
const apiUrl = joinPath(baseUrl, 'api', String(apiVersion));
const context = {
baseUrl,
apiVersion,
apiUrl,
fetch: (query, options) => fetchData$1(joinPath(queryToResourceUrl(query, context)), options)
};
return context;
};
function ownKeys$2(object, enumerableOnly) {

@@ -386,3 +666,3 @@ var keys = Object.keys(object);

ownKeys$2(source, true).forEach(function (key) {
_defineProperty$3(target, key, source[key]);
_defineProperty$6(target, key, source[key]);
});

@@ -401,3 +681,3 @@ } else if (Object.getOwnPropertyDescriptors) {

function _defineProperty$3(obj, key, value) {
function _defineProperty$6(obj, key, value) {
if (key in obj) {

@@ -419,73 +699,78 @@ Object.defineProperty(obj, key, {

const link = new RestAPILink(config);
const engine = new DataEngine(link);
const context = {
engine
};
return React__default.createElement(DataContext.Provider, {
value: makeContext$1(config)
value: context
}, props.children);
};
const baseUrl = 'https://example.com';
const apiVersion = 42;
function _defineProperty$7(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
const resolveCustomResource = async (customResource, query, {
failOnMiss,
options
}) => {
switch (typeof customResource) {
case 'string':
case 'number':
case 'boolean':
case 'object':
return customResource;
return obj;
}
case 'function':
// function
const result = await customResource(query, options);
class CustomDataLink {
constructor(customData, {
failOnMiss = true,
loadForever = false
} = {}) {
_defineProperty$7(this, "failOnMiss", void 0);
if (!result && failOnMiss) {
throw new Error(`The custom function for resource ${query.resource} must always return a value but returned ${result}`);
}
_defineProperty$7(this, "loadForever", void 0);
return result || {};
_defineProperty$7(this, "data", void 0);
default:
// should be unreachable
throw new Error(`Unknown resource type ${typeof customResource}`);
this.data = customData;
this.failOnMiss = failOnMiss;
this.loadForever = loadForever;
}
};
const makeCustomContext = (customData, {
failOnMiss = true,
loadForever = false
} = {}) => {
const apiUrl = joinPath(baseUrl, 'api', String(apiVersion));
async executeResourceQuery(type, query, options) {
if (this.loadForever) {
return new Promise(() => {});
}
const customFetch = async (query, options) => {
const customResource = customData[query.resource];
const customResource = this.data[query.resource];
if (!customResource) {
if (failOnMiss) {
if (this.failOnMiss) {
throw new Error(`No data provided for resource type ${query.resource}!`);
}
return Promise.resolve({});
return Promise.resolve(null);
}
return await resolveCustomResource(customResource, query, {
failOnMiss,
options
});
};
switch (typeof customResource) {
case 'string':
case 'number':
case 'boolean':
case 'object':
return customResource;
const foreverLoadingFetch = async () => {
return new Promise(() => {}); // Load forever
};
case 'function':
const result = await customResource(type, query, options);
const context = {
baseUrl,
apiVersion,
apiUrl,
fetch: loadForever ? foreverLoadingFetch : customFetch
};
return context;
};
if (typeof result === 'undefined' && this.failOnMiss) {
throw new Error(`The custom function for resource ${query.resource} must always return a value but returned ${result}`);
}
return result || null;
}
}
}
const CustomDataProvider = ({

@@ -496,3 +781,7 @@ children,

}) => {
const context = makeCustomContext(data, options);
const link = new CustomDataLink(data, options);
const engine = new DataEngine(link);
const context = {
engine
};
return React__default.createElement(DataContext.Provider, {

@@ -513,2 +802,3 @@ value: context

exports.CustomDataProvider = CustomDataProvider;
exports.DataMutation = DataMutation;
exports.DataProvider = DataProvider;

@@ -518,2 +808,4 @@ exports.DataQuery = DataQuery;

exports.useConfig = useConfig;
exports.useDataEngine = useDataEngine;
exports.useDataMutation = useDataMutation;
exports.useDataQuery = useDataQuery;

@@ -1,16 +0,17 @@

import React, { useContext, useState, useCallback, useEffect } from 'react';
import React, { useRef, useState, useEffect, useCallback, useContext } from 'react';
const uninitializedFetch = async () => {
throw new Error('DHIS2 data context must be initialized, please ensure that you include a <DataProvider> in your application');
const useStaticInput = (staticValue, {
warn = false,
name = 'input'
} = {}) => {
const originalValue = useRef(staticValue);
const [value, setValue] = useState(() => originalValue.current);
useEffect(() => {
if (warn && originalValue.current !== staticValue) {
console.warn(`The ${name} should be static, don't create it within the render loop!`);
}
}, [warn, staticValue, originalValue, name]);
return [value, setValue];
};
const defaultContext = {
baseUrl: '',
apiVersion: 0,
apiUrl: '',
fetch: uninitializedFetch
};
const DataContext = React.createContext(defaultContext);
function ownKeys(object, enumerableOnly) {

@@ -64,3 +65,107 @@ var keys = Object.keys(object);

}
const useQueryExecutor = ({
execute,
variables: initialVariables,
singular,
immediate,
onComplete,
onError
}) => {
const [theExecute] = useStaticInput(execute);
const [state, setState] = useState({
called: !!immediate,
loading: !!immediate
});
const variables = useRef(initialVariables);
const abortControllersRef = useRef([]);
const abort = () => {
abortControllersRef.current.forEach(controller => controller.abort());
abortControllersRef.current = [];
};
const refetch = useCallback((newVariables = {}) => {
setState(state => !state.called || !state.loading ? {
called: true,
loading: true
} : state);
if (singular) {
abort(); // Cleanup any in-progress fetches
}
const controller = new AbortController();
abortControllersRef.current.push(controller);
variables.current = _objectSpread({}, variables.current, {}, newVariables);
const options = {
variables: variables.current,
signal: controller.signal,
onComplete,
onError
};
return theExecute(options).then(data => {
if (!controller.signal.aborted) {
setState({
called: true,
loading: false,
data
});
return data;
}
return new Promise(() => {});
}).catch(error => {
if (!controller.signal.aborted) {
setState({
called: true,
loading: false,
error
});
}
return new Promise(() => {}); // Don't throw errors in refetch promises, wait forever
});
}, [onComplete, onError, singular, theExecute]);
useEffect(() => {
if (immediate) {
refetch();
}
return abort;
}, [immediate, refetch]);
return _objectSpread({
refetch,
abort
}, state);
};
const resolveDynamicQuery = ({
resource,
id,
data,
params
}, variables) => ({
resource,
id: typeof id === 'function' ? id(variables) : id,
data: typeof data === 'function' ? data(variables) : data,
params: typeof params === 'function' ? params(variables) : params
});
const getMutationFetchType = mutation => mutation.type === 'update' ? mutation.partial ? 'update' : 'replace' : mutation.type;
function _defineProperty$1(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const reduceResponses = (responses, names) => responses.reduce((out, response, idx) => {

@@ -71,51 +176,195 @@ out[names[idx]] = response;

const fetchData = (context, query, signal) => {
const names = Object.keys(query);
const requests = names.map(name => query[name]);
const requestPromises = requests.map(q => context.fetch(q, {
signal: signal
}));
return Promise.all(requestPromises).then(responses => reduceResponses(responses, names));
};
class DataEngine {
constructor(link) {
_defineProperty$1(this, "link", void 0);
const useDataQuery = query => {
const context = useContext(DataContext);
const [state, setState] = useState({
loading: true
});
const [refetchCount, setRefetchCount] = useState(0);
const refetch = useCallback(() => setRefetchCount(count => count + 1), []);
useEffect(() => {
const controller = new AbortController();
this.link = link;
}
const abort = () => controller.abort();
fetchData(context, query, controller.signal).then(data => {
!controller.signal.aborted && setState({
loading: false,
data
query(query, {
variables = {},
signal,
onComplete,
onError
} = {}) {
const names = Object.keys(query);
const queries = names.map(name => query[name]);
return Promise.all(queries.map(q => {
const resolvedQuery = resolveDynamicQuery(q, variables);
return this.link.executeResourceQuery('read', resolvedQuery, {
signal
});
})).then(results => {
const data = reduceResponses(results, names);
onComplete && onComplete(data);
return data;
}).catch(error => {
!controller.signal.aborted && setState({
loading: false,
error
});
}); // Cleanup inflight requests
onError && onError(error);
throw error;
});
}
return abort;
}, [context, refetchCount]); // eslint-disable-line react-hooks/exhaustive-deps
mutate(mutation, {
variables = {},
signal,
onComplete,
onError
} = {}) {
const query = resolveDynamicQuery(mutation, variables);
const result = this.link.executeResourceQuery(getMutationFetchType(mutation), query, {
signal
});
return result.then(data => {
onComplete && onComplete(data);
return data;
}).catch(error => {
onError && onError(error);
throw error;
});
}
return _objectSpread({
refetch
}, state);
}
function _defineProperty$2(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class ErrorLink {
constructor(errorMessage) {
_defineProperty$2(this, "errorMessage", void 0);
this.errorMessage = errorMessage;
}
executeResourceQuery() {
console.error(this.errorMessage);
return Promise.reject(this.errorMessage);
}
}
const errorMessage = 'DHIS2 data context must be initialized, please ensure that you include a <DataProvider> in your application';
const link = new ErrorLink(errorMessage);
const engine = new DataEngine(link);
const defaultContext = {
engine
};
const DataContext = React.createContext(defaultContext);
const useDataEngine = () => {
const context = useContext(DataContext);
return context.engine;
};
const empty = {};
const useDataQuery = (query, {
onComplete,
onError,
variables = empty
} = {}) => {
const engine = useDataEngine();
const [theQuery] = useStaticInput(query, {
warn: true,
name: 'query'
});
const execute = useCallback(options => engine.query(theQuery, options), [engine, theQuery]);
const {
refetch,
loading,
error,
data
} = useQueryExecutor({
execute,
variables,
singular: true,
immediate: true,
onComplete,
onError
});
return {
engine,
refetch,
loading,
error,
data
};
};
const DataQuery = ({
query,
onComplete,
onError,
variables,
children
}) => {
const queryState = useDataQuery(query);
const queryState = useDataQuery(query, {
onComplete,
onError,
variables
});
return children(queryState);
};
const empty$1 = {};
const useDataMutation = (mutation, {
onComplete,
onError,
variables = empty$1
} = {}) => {
const engine = useDataEngine();
const [theMutation] = useStaticInput(mutation, {
warn: true,
name: 'mutation'
});
const execute = useCallback(options => engine.mutate(theMutation, options), [engine, theMutation]);
const {
refetch: mutate,
called,
loading,
error,
data
} = useQueryExecutor({
execute,
variables,
singular: false,
immediate: false,
onComplete,
onError
});
return [mutate, {
engine,
called,
loading,
error,
data
}];
};
const DataMutation = ({
mutation,
onComplete,
onError,
variables,
children
}) => {
const mutationState = useDataMutation(mutation, {
onComplete,
onError,
variables
});
return children(mutationState);
};
const ConfigContext = React.createContext({

@@ -137,3 +386,80 @@ baseUrl: '..',

function _defineProperty$1(obj, key, value) {
const joinPath = (...parts) => {
const realParts = parts.filter(part => !!part);
return realParts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
};
const encodeQueryParameter = param => {
if (Array.isArray(param)) {
return param.map(encodeQueryParameter).join(',');
}
if (typeof param === 'string') {
return encodeURIComponent(param);
}
if (typeof param === 'number') {
return String(param);
}
if (typeof param === 'object') {
throw new Error('Object parameter mappings not yet implemented');
}
throw new Error('Unknown parameter type');
};
const queryParametersToQueryString = params => Object.keys(params).filter(key => key && params[key]).map(key => `${encodeURIComponent(key)}=${encodeQueryParameter(params[key])}`).join('&');
const actionPrefix = 'action::';
const isAction = resource => resource.startsWith(actionPrefix);
const makeActionPath = resource => joinPath('dhis-web-commons', `${resource.substr(actionPrefix.length)}.action`);
const queryToResourcePath = (apiPath, {
resource,
id,
params = {}
}) => {
const base = isAction(resource) ? makeActionPath(resource) : joinPath(apiPath, resource, id);
if (Object.keys(params).length) {
return `${base}?${queryParametersToQueryString(params)}`;
}
return base;
};
const getMethod = type => {
switch (type) {
case 'create':
return 'POST';
case 'read':
return 'GET';
case 'update':
return 'PATCH';
case 'replace':
return 'PUT';
case 'delete':
return 'DELETE';
}
};
const queryToRequestOptions = (type, {
data
}, signal) => ({
method: getMethod(type),
body: data ? JSON.stringify(data) : undefined,
headers: data ? {
'Content-Type': 'application/json'
} : undefined,
signal
});
function _defineProperty$3(obj, key, value) {
if (key in obj) {

@@ -161,5 +487,5 @@ Object.defineProperty(obj, key, {

_defineProperty$1(this, "type", void 0);
_defineProperty$3(this, "type", void 0);
_defineProperty$1(this, "details", void 0);
_defineProperty$3(this, "details", void 0);

@@ -192,3 +518,3 @@ this.type = type;

ownKeys$1(source, true).forEach(function (key) {
_defineProperty$2(target, key, source[key]);
_defineProperty$4(target, key, source[key]);
});

@@ -207,3 +533,3 @@ } else if (Object.getOwnPropertyDescriptors) {

function _defineProperty$2(obj, key, value) {
function _defineProperty$4(obj, key, value) {
if (key in obj) {

@@ -222,3 +548,27 @@ Object.defineProperty(obj, key, {

}
function fetchData$1(url, options = {}) {
const parseStatus = async response => {
if (response.status === 401 || response.status === 403 || response.status === 409) {
const message = await response.json().then(body => {
return body.message;
}).catch(() => {
return response.status === 401 ? 'Unauthorized' : 'Forbidden';
});
throw new FetchError({
type: 'access',
message,
details: response
});
}
if (response.status < 200 || response.status >= 400) {
throw new FetchError({
type: 'unknown',
message: `An unknown error occurred - ${response.statusText} (${response.status})`,
details: response
});
}
return response;
};
function fetchData(url, options = {}) {
return fetch(url, _objectSpread$1({}, options, {

@@ -236,123 +586,53 @@ credentials: 'include',

});
}).then(response => {
if (response.status === 401 || response.status === 403 || response.status === 409) {
throw new FetchError({
type: 'access',
message: response.statusText,
details: response
});
}).then(parseStatus).then(async response => {
if (response.headers.get('Content-Type') === 'application/json') {
return await response.json(); // Will throw if invalid JSON!
}
if (response.status < 200 || response.status >= 400) {
throw new FetchError({
type: 'unknown',
message: `An unknown error occurred - ${response.statusText} (${response.status})`,
details: response
});
}
return response;
}).then(response => response.json());
return await response.text();
});
}
const joinPath = (...parts) => {
return parts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
};
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
function _defineProperty$5(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return target;
return obj;
}
class RestAPILink {
constructor({
baseUrl,
apiVersion
}) {
_defineProperty$5(this, "apiPath", void 0);
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
_defineProperty$5(this, "baseUrl", void 0);
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
_defineProperty$5(this, "apiVersion", void 0);
return target;
}
const encodeQueryParameter = param => {
if (Array.isArray(param)) {
return param.map(encodeQueryParameter).join(',');
this.baseUrl = baseUrl;
this.apiVersion = apiVersion;
this.apiPath = joinPath('api', String(apiVersion));
}
if (typeof param === 'string') {
return encodeURIComponent(param);
fetch(path, options) {
return fetchData(joinPath(this.baseUrl, path), options);
}
if (typeof param === 'number') {
return String(param);
executeResourceQuery(type, query, {
signal
}) {
return this.fetch(queryToResourcePath(this.apiPath, query), queryToRequestOptions(type, query, signal));
}
if (typeof param === 'object') {
throw new Error('Object parameter mappings not yet implemented');
}
}
throw new Error('Unknown parameter type');
};
const queryParametersToQueryString = params => Object.keys(params).filter(key => key && params[key]).map(key => `${encodeURIComponent(key)}=${encodeQueryParameter(params[key])}`).join('&');
const actionPrefix = 'action::';
const isAction = resource => resource.startsWith(actionPrefix);
const makeActionURL = (baseUrl, resource) => joinPath(baseUrl, 'dhis-web-commons', `${resource.substr(actionPrefix.length)}.action`);
const queryToResourceUrl = (_ref, {
baseUrl,
apiUrl
}) => {
let {
resource
} = _ref,
params = _objectWithoutProperties(_ref, ["resource"]);
const base = isAction(resource) ? makeActionURL(baseUrl, resource) : joinPath(apiUrl, resource);
if (Object.keys(params).length) {
return `${base}?${queryParametersToQueryString(params)}`;
}
return base;
};
const makeContext$1 = ({
baseUrl,
apiVersion
}) => {
const apiUrl = joinPath(baseUrl, 'api', String(apiVersion));
const context = {
baseUrl,
apiVersion,
apiUrl,
fetch: (query, options) => fetchData$1(joinPath(queryToResourceUrl(query, context)), options)
};
return context;
};
function ownKeys$2(object, enumerableOnly) {

@@ -378,3 +658,3 @@ var keys = Object.keys(object);

ownKeys$2(source, true).forEach(function (key) {
_defineProperty$3(target, key, source[key]);
_defineProperty$6(target, key, source[key]);
});

@@ -393,3 +673,3 @@ } else if (Object.getOwnPropertyDescriptors) {

function _defineProperty$3(obj, key, value) {
function _defineProperty$6(obj, key, value) {
if (key in obj) {

@@ -411,73 +691,78 @@ Object.defineProperty(obj, key, {

const link = new RestAPILink(config);
const engine = new DataEngine(link);
const context = {
engine
};
return React.createElement(DataContext.Provider, {
value: makeContext$1(config)
value: context
}, props.children);
};
const baseUrl = 'https://example.com';
const apiVersion = 42;
function _defineProperty$7(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
const resolveCustomResource = async (customResource, query, {
failOnMiss,
options
}) => {
switch (typeof customResource) {
case 'string':
case 'number':
case 'boolean':
case 'object':
return customResource;
return obj;
}
case 'function':
// function
const result = await customResource(query, options);
class CustomDataLink {
constructor(customData, {
failOnMiss = true,
loadForever = false
} = {}) {
_defineProperty$7(this, "failOnMiss", void 0);
if (!result && failOnMiss) {
throw new Error(`The custom function for resource ${query.resource} must always return a value but returned ${result}`);
}
_defineProperty$7(this, "loadForever", void 0);
return result || {};
_defineProperty$7(this, "data", void 0);
default:
// should be unreachable
throw new Error(`Unknown resource type ${typeof customResource}`);
this.data = customData;
this.failOnMiss = failOnMiss;
this.loadForever = loadForever;
}
};
const makeCustomContext = (customData, {
failOnMiss = true,
loadForever = false
} = {}) => {
const apiUrl = joinPath(baseUrl, 'api', String(apiVersion));
async executeResourceQuery(type, query, options) {
if (this.loadForever) {
return new Promise(() => {});
}
const customFetch = async (query, options) => {
const customResource = customData[query.resource];
const customResource = this.data[query.resource];
if (!customResource) {
if (failOnMiss) {
if (this.failOnMiss) {
throw new Error(`No data provided for resource type ${query.resource}!`);
}
return Promise.resolve({});
return Promise.resolve(null);
}
return await resolveCustomResource(customResource, query, {
failOnMiss,
options
});
};
switch (typeof customResource) {
case 'string':
case 'number':
case 'boolean':
case 'object':
return customResource;
const foreverLoadingFetch = async () => {
return new Promise(() => {}); // Load forever
};
case 'function':
const result = await customResource(type, query, options);
const context = {
baseUrl,
apiVersion,
apiUrl,
fetch: loadForever ? foreverLoadingFetch : customFetch
};
return context;
};
if (typeof result === 'undefined' && this.failOnMiss) {
throw new Error(`The custom function for resource ${query.resource} must always return a value but returned ${result}`);
}
return result || null;
}
}
}
const CustomDataProvider = ({

@@ -488,3 +773,7 @@ children,

}) => {
const context = makeCustomContext(data, options);
const link = new CustomDataLink(data, options);
const engine = new DataEngine(link);
const context = {
engine
};
return React.createElement(DataContext.Provider, {

@@ -504,2 +793,2 @@ value: context

export { CustomDataProvider, DataProvider, DataQuery, Provider, useConfig, useDataQuery };
export { CustomDataProvider, DataMutation, DataProvider, DataQuery, Provider, useConfig, useDataEngine, useDataMutation, useDataQuery };
{
"name": "@dhis2/app-runtime",
"description": "A singular runtime dependency for applications on the DHIS2 platform",
"version": "1.5.1",
"version": "2.0.0",
"main": "build/cjs/index.js",

@@ -22,4 +22,4 @@ "module": "build/es/index.js",

"devDependencies": {
"@dhis2/app-service-config": "1.5.1",
"@dhis2/app-service-data": "1.5.1"
"@dhis2/app-service-config": "2.0.0",
"@dhis2/app-service-data": "2.0.0"
},

@@ -32,5 +32,5 @@ "peerDependencies": {

"scripts": {
"build": "rollup -c",
"build": "rimraf build && rollup -c",
"test": "echo \"No tests yet!\""
}
}

@@ -26,5 +26,2 @@ # DHIS2 Application Runtime

The `@dhis2/app-runtime` library is a thin wrapper around application services. See each service's README for usage instructions. Currently, the included services are:
- [config](../services/config) - contextualized application configuration
- [data](../services/data) - declarative data fetching for DHIS2 api queries
See [the docs](https://runtime.dhis2.nu) for usage and examples