@dhis2/app-service-datastore
Advanced tools
Comparing version 1.0.0-alpha.1 to 1.0.0-alpha.2
@@ -13,3 +13,2 @@ /* eslint-disable */ | ||
/* eslint-disable-line no-unused-vars */ | ||
const DataStoreContext = React.createContext(undefined); | ||
@@ -118,5 +117,4 @@ | ||
const joinPath = (...parts) => parts.map(part => part.replace(/^\/+/, '').replace(/\/+$/, '')).join('/'); // TODO: Proper engine typings | ||
const joinPath = (...parts) => parts.map(part => part.replace(/^\/+/, '').replace(/\/+$/, '')).join('/'); | ||
class SettingsStore { | ||
@@ -128,3 +126,4 @@ constructor({ | ||
item, | ||
defaults = {} | ||
defaults = {}, | ||
encrypt = false | ||
}) { | ||
@@ -139,2 +138,4 @@ _defineProperty(this, "engine", void 0); | ||
_defineProperty(this, "encrypt", void 0); | ||
_defineProperty(this, "eventEmitter", new EventEmitter()); | ||
@@ -146,2 +147,3 @@ | ||
this.settings = defaults; | ||
this.encrypt = encrypt; | ||
} | ||
@@ -157,2 +159,5 @@ | ||
type: 'create', | ||
params: { | ||
encrypt: this.encrypt | ||
}, | ||
data: this.settings | ||
@@ -215,2 +220,5 @@ }); | ||
id: this.dataStoreId, | ||
params: { | ||
encrypt: this.encrypt | ||
}, | ||
data: this.settings | ||
@@ -256,3 +264,4 @@ }); | ||
item, | ||
defaults: {} | ||
defaults: {}, | ||
encrypt: false | ||
}); | ||
@@ -302,3 +311,4 @@ } | ||
defaultGlobalSettings, | ||
defaultUserSettings | ||
defaultUserSettings, | ||
encryptSettings = false | ||
}) { | ||
@@ -318,3 +328,4 @@ _defineProperty(this, "globalSettings", void 0); | ||
item: 'settings', | ||
defaults: defaultGlobalSettings | ||
defaults: defaultGlobalSettings, | ||
encrypt: encryptSettings | ||
}); | ||
@@ -326,3 +337,4 @@ this.userSettings = new SettingsStore({ | ||
item: 'settings', | ||
defaults: defaultUserSettings | ||
defaults: defaultUserSettings, | ||
encrypt: encryptSettings | ||
}); | ||
@@ -370,3 +382,4 @@ this.userSavedObjects = new SavedObjectStore({ | ||
children, | ||
loadingComponent = null | ||
loadingComponent = null, | ||
encryptSettings = false | ||
}) => { | ||
@@ -379,6 +392,7 @@ const [loading, setLoading] = React.useState(true); | ||
defaultGlobalSettings, | ||
defaultUserSettings | ||
}), [] | ||
defaultUserSettings, | ||
encryptSettings | ||
}), []); | ||
/* eslint-disable-line react-hooks/exhaustive-deps */ | ||
); | ||
React.useEffect(() => { | ||
@@ -416,3 +430,3 @@ let cancelled = false; | ||
set: value => settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.set(id, value) | ||
}), [id, settingsStore]); | ||
}), [dataStore]); | ||
React.useEffect(() => { | ||
@@ -425,3 +439,3 @@ if (!ignoreUpdates) { | ||
} | ||
}, [settingsStore, ignoreUpdates, id]); | ||
}, [settingsStore, ignoreUpdates]); | ||
return [value, callbacks]; | ||
@@ -437,3 +451,5 @@ }; | ||
const [value, setValue] = React.useState(settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.settings); | ||
const set = React.useCallback((key, value) => settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.set(key, value), [settingsStore]); | ||
const callbacks = React.useMemo(() => ({ | ||
set: (key, value) => settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.set(key, value) | ||
}), [dataStore]); | ||
React.useEffect(() => { | ||
@@ -447,5 +463,3 @@ if (!ignoreUpdates) { | ||
}, [settingsStore, ignoreUpdates]); | ||
return [value, { | ||
set | ||
}]; | ||
return [value, callbacks]; | ||
}; | ||
@@ -466,3 +480,3 @@ | ||
unshare: () => dataStore === null || dataStore === void 0 ? void 0 : dataStore.unshareSavedObject(id) | ||
}), [dataStore, id, objectStore]); | ||
}), [dataStore]); | ||
React.useEffect(() => { | ||
@@ -475,3 +489,3 @@ if (!ignoreUpdates) { | ||
} | ||
}, [objectStore, ignoreUpdates, id]); | ||
}, [objectStore, ignoreUpdates]); | ||
return [value, callbacks]; | ||
@@ -496,3 +510,3 @@ }; | ||
remove: id => objectStore === null || objectStore === void 0 ? void 0 : objectStore.remove(id) | ||
}), [objectStore]); | ||
}), [dataStore]); | ||
React.useEffect(() => { | ||
@@ -499,0 +513,0 @@ if (!ignoreUpdates) { |
/* eslint-disable */ | ||
import React, { createContext, useState, useMemo, useEffect, useContext, useCallback } from 'react'; | ||
import React, { createContext, useState, useMemo, useEffect, useContext } from 'react'; | ||
import { useDataEngine } from '@dhis2/app-runtime'; | ||
import { v4 } from 'uuid'; | ||
/* eslint-disable-line no-unused-vars */ | ||
const DataStoreContext = createContext(undefined); | ||
@@ -110,5 +109,4 @@ | ||
const joinPath = (...parts) => parts.map(part => part.replace(/^\/+/, '').replace(/\/+$/, '')).join('/'); // TODO: Proper engine typings | ||
const joinPath = (...parts) => parts.map(part => part.replace(/^\/+/, '').replace(/\/+$/, '')).join('/'); | ||
class SettingsStore { | ||
@@ -120,3 +118,4 @@ constructor({ | ||
item, | ||
defaults = {} | ||
defaults = {}, | ||
encrypt = false | ||
}) { | ||
@@ -131,2 +130,4 @@ _defineProperty(this, "engine", void 0); | ||
_defineProperty(this, "encrypt", void 0); | ||
_defineProperty(this, "eventEmitter", new EventEmitter()); | ||
@@ -138,2 +139,3 @@ | ||
this.settings = defaults; | ||
this.encrypt = encrypt; | ||
} | ||
@@ -149,2 +151,5 @@ | ||
type: 'create', | ||
params: { | ||
encrypt: this.encrypt | ||
}, | ||
data: this.settings | ||
@@ -207,2 +212,5 @@ }); | ||
id: this.dataStoreId, | ||
params: { | ||
encrypt: this.encrypt | ||
}, | ||
data: this.settings | ||
@@ -248,3 +256,4 @@ }); | ||
item, | ||
defaults: {} | ||
defaults: {}, | ||
encrypt: false | ||
}); | ||
@@ -294,3 +303,4 @@ } | ||
defaultGlobalSettings, | ||
defaultUserSettings | ||
defaultUserSettings, | ||
encryptSettings = false | ||
}) { | ||
@@ -310,3 +320,4 @@ _defineProperty(this, "globalSettings", void 0); | ||
item: 'settings', | ||
defaults: defaultGlobalSettings | ||
defaults: defaultGlobalSettings, | ||
encrypt: encryptSettings | ||
}); | ||
@@ -318,3 +329,4 @@ this.userSettings = new SettingsStore({ | ||
item: 'settings', | ||
defaults: defaultUserSettings | ||
defaults: defaultUserSettings, | ||
encrypt: encryptSettings | ||
}); | ||
@@ -362,3 +374,4 @@ this.userSavedObjects = new SavedObjectStore({ | ||
children, | ||
loadingComponent = null | ||
loadingComponent = null, | ||
encryptSettings = false | ||
}) => { | ||
@@ -371,6 +384,7 @@ const [loading, setLoading] = useState(true); | ||
defaultGlobalSettings, | ||
defaultUserSettings | ||
}), [] | ||
defaultUserSettings, | ||
encryptSettings | ||
}), []); | ||
/* eslint-disable-line react-hooks/exhaustive-deps */ | ||
); | ||
useEffect(() => { | ||
@@ -408,3 +422,3 @@ let cancelled = false; | ||
set: value => settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.set(id, value) | ||
}), [id, settingsStore]); | ||
}), [dataStore]); | ||
useEffect(() => { | ||
@@ -417,3 +431,3 @@ if (!ignoreUpdates) { | ||
} | ||
}, [settingsStore, ignoreUpdates, id]); | ||
}, [settingsStore, ignoreUpdates]); | ||
return [value, callbacks]; | ||
@@ -429,3 +443,5 @@ }; | ||
const [value, setValue] = useState(settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.settings); | ||
const set = useCallback((key, value) => settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.set(key, value), [settingsStore]); | ||
const callbacks = useMemo(() => ({ | ||
set: (key, value) => settingsStore === null || settingsStore === void 0 ? void 0 : settingsStore.set(key, value) | ||
}), [dataStore]); | ||
useEffect(() => { | ||
@@ -439,5 +455,3 @@ if (!ignoreUpdates) { | ||
}, [settingsStore, ignoreUpdates]); | ||
return [value, { | ||
set | ||
}]; | ||
return [value, callbacks]; | ||
}; | ||
@@ -458,3 +472,3 @@ | ||
unshare: () => dataStore === null || dataStore === void 0 ? void 0 : dataStore.unshareSavedObject(id) | ||
}), [dataStore, id, objectStore]); | ||
}), [dataStore]); | ||
useEffect(() => { | ||
@@ -467,3 +481,3 @@ if (!ignoreUpdates) { | ||
} | ||
}, [objectStore, ignoreUpdates, id]); | ||
}, [objectStore, ignoreUpdates]); | ||
return [value, callbacks]; | ||
@@ -488,3 +502,3 @@ }; | ||
remove: id => objectStore === null || objectStore === void 0 ? void 0 : objectStore.remove(id) | ||
}), [objectStore]); | ||
}), [dataStore]); | ||
useEffect(() => { | ||
@@ -491,0 +505,0 @@ if (!ignoreUpdates) { |
import React from 'react'; | ||
interface DataStoreProviderInput { | ||
export declare const DataStoreProvider: ({ namespace, defaultGlobalSettings, defaultUserSettings, children, loadingComponent, encryptSettings, }: { | ||
namespace: string; | ||
children: React.ReactNode; | ||
loadingComponent: React.ReactNode; | ||
defaultGlobalSettings?: Record<string, any>; | ||
defaultUserSettings?: Record<string, any>; | ||
} | ||
export declare const DataStoreProvider: ({ namespace, defaultGlobalSettings, defaultUserSettings, children, loadingComponent, }: DataStoreProviderInput) => JSX.Element; | ||
export {}; | ||
defaultGlobalSettings?: Record<string, any> | undefined; | ||
defaultUserSettings?: Record<string, any> | undefined; | ||
encryptSettings?: boolean | undefined; | ||
}) => JSX.Element; |
@@ -1,3 +0,3 @@ | ||
import { SavedObjectStore } from './SavedObjectStore'; | ||
import { SettingsStore } from './SettingsStore'; | ||
import { SavedObjectStore } from "./SavedObjectStore"; | ||
import { SettingsStore } from "./SettingsStore"; | ||
declare type DataStoreInput = { | ||
@@ -8,2 +8,3 @@ engine: any; | ||
defaultUserSettings?: object; | ||
encryptSettings: boolean; | ||
}; | ||
@@ -15,3 +16,3 @@ export declare class DataStore { | ||
globalSavedObjects: SavedObjectStore; | ||
constructor({ engine, namespace, defaultGlobalSettings, defaultUserSettings, }: DataStoreInput); | ||
constructor({ engine, namespace, defaultGlobalSettings, defaultUserSettings, encryptSettings }: DataStoreInput); | ||
initialize(): Promise<void>; | ||
@@ -18,0 +19,0 @@ shareSavedObject(id: string): Promise<void>; |
@@ -1,2 +0,2 @@ | ||
import { SettingsStore, DataStoreResource } from './SettingsStore'; | ||
import { SettingsStore, DataStoreResource } from "./SettingsStore"; | ||
declare type SavedObjectStoreInput = { | ||
@@ -3,0 +3,0 @@ engine: any; |
@@ -1,2 +0,2 @@ | ||
import { EventEmitter } from '../utils/EventEmitter'; | ||
import { EventEmitter } from "../utils/EventEmitter"; | ||
export declare type DataStoreResource = 'dataStore' | 'userDataStore'; | ||
@@ -9,2 +9,3 @@ export declare type BaseSettingsStoreInput = { | ||
defaults: any; | ||
encrypt: boolean; | ||
}; | ||
@@ -16,4 +17,5 @@ export declare class SettingsStore { | ||
settings: Record<string, any>; | ||
encrypt: boolean; | ||
eventEmitter: EventEmitter; | ||
constructor({ engine, resource, namespace, item, defaults, }: BaseSettingsStoreInput); | ||
constructor({ engine, resource, namespace, item, defaults, encrypt, }: BaseSettingsStoreInput); | ||
initialize(): Promise<void>; | ||
@@ -20,0 +22,0 @@ create(): Promise<void>; |
@@ -1,4 +0,4 @@ | ||
export declare const useAllSettings: ({ global, ignoreUpdates, }?: { | ||
export declare const useAllSettings: ({ global, ignoreUpdates }?: { | ||
global?: boolean | undefined; | ||
ignoreUpdates?: boolean | undefined; | ||
}) => (Record<string, any> | undefined)[]; |
@@ -1,2 +0,2 @@ | ||
export declare const useSavedObjectList: ({ global, ignoreUpdates, }?: { | ||
export declare const useSavedObjectList: ({ global, ignoreUpdates }?: { | ||
global?: boolean | undefined; | ||
@@ -3,0 +3,0 @@ ignoreUpdates?: boolean | undefined; |
{ | ||
"name": "@dhis2/app-service-datastore", | ||
"version": "1.0.0-alpha.1", | ||
"description": "", | ||
"main": "build/cjs/lib.js", | ||
"module": "build/es/lib.js", | ||
"types": "build/types/index.d.ts", | ||
"license": "BSD-3-Clause", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"scripts": { | ||
"build:types": "tsc --emitDeclarationOnly --outDir ./build/types", | ||
"build:package": "d2-app-scripts build", | ||
"build": "concurrently -n build,types \"yarn build:package\" \"yarn build:types\"", | ||
"watch": "NODE_ENV=development concurrently -n build,types \"yarn build:package --watch\" \"yarn build:types --watch\"", | ||
"type-check": "tsc --noEmit --allowJs --checkJs", | ||
"type-check:watch": "yarn type-check --watch", | ||
"test": "echo 'No tests yet :-('", | ||
"coverage": "yarn test --coverage", | ||
"prepublishOnly": "yarn build", | ||
"lint": "d2-style js check && d2-style text check", | ||
"format": "d2-style js apply --all --no-stage && d2-style text apply --all --no-stage" | ||
}, | ||
"devDependencies": { | ||
"@dhis2/cli-app-scripts": "^4.0.8", | ||
"@dhis2/cli-style": "^7.0.0", | ||
"@types/jest": "^24.9.0", | ||
"@types/node": "^13.1.8", | ||
"@types/react": "^16.9.18", | ||
"@types/uuid": "^8.0.0", | ||
"@typescript-eslint/parser": "^3.3.0", | ||
"concurrently": "^5.2.0", | ||
"eslint-plugin-react-hooks": "^4.0.4", | ||
"typescript": "^3.9.5" | ||
}, | ||
"dependencies": { | ||
"uuid": "^8.1.0" | ||
}, | ||
"peerDependencies": { | ||
"@dhis2/app-service-data": "^2.1.1", | ||
"react": "^16.12.0", | ||
"react-dom": "^16.12.0" | ||
} | ||
"name": "@dhis2/app-service-datastore", | ||
"version": "1.0.0-alpha.2", | ||
"description": "", | ||
"main": "build/cjs/lib.js", | ||
"module": "build/es/lib.js", | ||
"types": "build/types/index.d.ts", | ||
"license": "BSD-3-Clause", | ||
"private": false, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"scripts": { | ||
"build:types": "tsc --emitDeclarationOnly --outDir ./build/types", | ||
"build:package": "d2-app-scripts build", | ||
"build": "concurrently -n build,types \"yarn build:package\" \"yarn build:types\"", | ||
"watch": "NODE_ENV=development concurrently -n build,types \"yarn build:package --watch\" \"yarn build:types --watch\"", | ||
"type-check": "tsc --noEmit --allowJs --checkJs", | ||
"type-check:watch": "yarn type-check --watch", | ||
"test": "d2-app-scripts test", | ||
"coverage": "yarn test --coverage", | ||
"prepublishOnly": "yarn build" | ||
}, | ||
"devDependencies": { | ||
"@dhis2/cli-app-scripts": "^4.0.0", | ||
"@types/jest": "^24.9.0", | ||
"@types/node": "^13.1.8", | ||
"@types/react": "^16.9.18", | ||
"@types/uuid": "^8.0.0", | ||
"concurrently": "^5.2.0" | ||
}, | ||
"dependencies": { | ||
"@dhis2/app-service-data": "^2.1.1", | ||
"uuid": "^8.1.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.12.0", | ||
"react-dom": "^16.12.0" | ||
} | ||
} |
195
README.md
# DataStore App Service | ||
> \*\*NOTE: THIS IS STILL AN UNPUBLISHED WORK-IN-PROGRESS! | ||
> **WARNING**: THIS SERVICE IS STILL A WORK-IN-PROGRESS, THE API WILL PROBABLY CHANGE! | ||
@@ -9,24 +9,193 @@ This DataStore app service support persistent user and global application settings as well as saved (and sharable) objects, such as visualization configurations. | ||
## Available Scripts | ||
## Installation | ||
In the project directory, you can run: | ||
```sh | ||
yarn add @dhis2/app-service-datastore | ||
``` | ||
### `yarn test` | ||
## Features | ||
Launches the test runner and runs all available tests found in `/src`.<br /> | ||
- Save and load application settings from a well-known dataStore (or userDataStore) key | ||
- Create, read, update, and delete saved objects (i.e. `visualizations`) from a managed key-value store in the dataStore or userDataStore | ||
- Client-side syncronized state - automatically re-render all components which use a setting or object when that setting or object is updated somewhere else in the application (no refetch required) | ||
- Optimistic updates - propagate "provisional" data to all consumers while mutation is in-transit, roll back changes if the mutation fails | ||
- Optionally encrypt settings data at rest | ||
See the section about [running tests](https://platform.dhis2.nu/#/scripts/test) for more information. | ||
## API | ||
### `yarn build` | ||
### DataStoreProvider props | ||
Builds the library for production to the `build` folder.<br /> | ||
| **Name** | **Type** | **Required** | **Default** | **Description** | | ||
| --------------------- | ----------------- | ------------ | ----------- | -------------------------------------------------------------------------------- | | ||
| namespace | _Boolean_ | **REQUIRED** | | The namespace to use | | ||
| loadingComponent | _React Component_ | | null | A component to render during initial load | | ||
| defaultGlobalSettings | _Object_ | | {} | Default settings to save in the dataStore | | ||
| defaultUserSettings | _Object_ | | {} | Default settings to save in the userDataStore | | ||
| encryptSettings | _boolean_ | | false | If true, encrypt all settings at rest (important if credentials could be stored) | | ||
See the section about [building](https://platform.dhis2.nu/#/scripts/build) for more information. | ||
### Hooks | ||
## Learn More | ||
This library provides four main hooks: | ||
You can learn more about the platform in the [DHIS2 Application Platform Documentation](https://platform.dhis2.nu/). | ||
```ts | ||
type useSetting = ( | ||
id: string, | ||
options?: HookOptions | ||
) => [value: any, { set: (newValue: any) => Promise<void> }]; | ||
You can learn more about the runtime in the [DHIS2 Application Runtime Documentation](https://runtime.dhis2.nu/). | ||
type useAllSettings = ( | ||
options?: HookOptions | ||
) => [ | ||
settings: Record<string, any>, | ||
{ set: (key: string, value: any) => Promise<void> } | ||
]; | ||
To learn React, check out the [React documentation](https://reactjs.org/). | ||
type useSavedObject = ( | ||
id: string, | ||
options?: HookOptions | ||
) => [ | ||
obj: object, | ||
{ | ||
update: (obj: object) => Promise<object>; | ||
replace: (obj: object) => Promise<void>; | ||
remove: () => Promise<void>; | ||
} | ||
]; | ||
type useSavedObjectList = ( | ||
options?: HookOptions | ||
) => [ | ||
list: object[], | ||
{ | ||
add: (obj: object) => Promise<void>; | ||
update: (id: string, obj: object) => Promise<object>; | ||
replace: (id: string, obj: object) => Promise<void>; | ||
remove: (id: string) => Promise<void>; | ||
} | ||
]; | ||
``` | ||
Each of the hooks accepts an optional options object: | ||
```ts | ||
type HookOptions = { | ||
// If true, store this setting or object in the dataStore instead of userDataStore | ||
global: boolean; | ||
// If true, do NOT rerender this component when the value is changed somewhere else in the application | ||
ignoreUpdates: boolean; | ||
}; | ||
``` | ||
There is one additional hook which exposes the DataStore controller for imperative access (advanced): | ||
```ts | ||
type useDataStore = () => DataStore; | ||
``` | ||
## Usage | ||
### Wrap the application in a DataStore provider | ||
```jsx | ||
import React from "react"; | ||
import { DataStoreProvider } from "@dhis2/app-service-datastore"; | ||
import AppRouter from "./AppRouter"; | ||
const App = () => ( | ||
<DataStoreProvider namespace="myAppName"> | ||
<AppRouter /> | ||
</DataStoreProvider> | ||
); | ||
export default App; | ||
``` | ||
### Reading settings | ||
```jsx | ||
import React from "react"; | ||
import { useSetting, useAllSettings } from "@dhis2/app-service-datastore"; | ||
const MyComponent = () => { | ||
// All data-store settings for the current user | ||
const [allUserSettings] = useAllSettings(); | ||
// All data-store settings within the namespace | ||
const [allGlobalSettings] = useAllSettings({ global: true }); | ||
// A specific setting for the current user | ||
const [aUserSetting] = useSetting("id-1"); | ||
// A specific global setting | ||
const [aGlobalSetting] = useSetting("id-1", { global: true }); | ||
return "Something"; | ||
}; | ||
export default MyComponent; | ||
``` | ||
### Reading saved objects | ||
```jsx | ||
import React from "react"; | ||
import { | ||
useSavedObject, | ||
useSavedObjectList, | ||
} from "@dhis2/app-service-datastore"; | ||
const MyComponent = () => { | ||
// All saved objects for the current user | ||
const [allUserSavedObjects] = useSavedObjectList(); | ||
// All saved objects within the namespace | ||
const [allGlobalSavedObjects] = useSavedObjectList({ global: true }); | ||
// A specific saved object for the current user | ||
const [aUserSavedObject] = useSavedObject("id-1"); | ||
// A specific global saved object | ||
const [aGlobalSavedObject] = useSavedObject("id-1", { global: true }); | ||
return "Something"; | ||
}; | ||
export default MyComponent; | ||
``` | ||
### Mutating settings and saved objects | ||
```jsx | ||
import React from "react"; | ||
import { | ||
useSavedObject, | ||
useSavedObjectList, | ||
useSetting, | ||
useAllSettings, | ||
} from "@dhis2/app-service-datastore"; | ||
const MyComponent = () => { | ||
// A setting for the current user | ||
const [userSetting, { set }] = useSetting("id-1"); | ||
// All settings for the current user | ||
const [userSettings, { set }] = useAllSettings(); | ||
// A saved object for the current user | ||
const [savedObject, { update, replace, remove }] = useSavedObject("id-1"); | ||
// All saved objects for the current user | ||
const [ | ||
allUserSavedObjects, | ||
{ add, update, replace, remove }, | ||
] = useSavedObjectList(); | ||
return "Something"; | ||
}; | ||
``` | ||
## Report an issue | ||
The issue tracker can be found in [DHIS2 JIRA](https://jira.dhis2.org) | ||
under the [LIBS](https://jira.dhis2.org/projects/LIBS) project. | ||
Deep links: | ||
- [Bug](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10700&issuetype=10006&components=11027) | ||
- [Feature](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10700&issuetype=10300&components=11027) | ||
- [Task](https://jira.dhis2.org/secure/CreateIssueDetails!init.jspa?pid=10700&issuetype=10003&components=11027) |
import { createContext } from 'react' | ||
import { DataStore } from './stores/DataStore' /* eslint-disable-line no-unused-vars */ | ||
import { DataStore } from './stores/DataStore' | ||
export const DataStoreContext = createContext<DataStore | undefined>(undefined) | ||
export const DataStoreContext = createContext<DataStore | undefined>(undefined) |
@@ -7,2 +7,2 @@ export { DataStoreProvider } from './DataStoreProvider' | ||
export { useSavedObject } from './useSavedObject' | ||
export { useSavedObjectList } from './useSavedObjectList' | ||
export { useSavedObjectList } from './useSavedObjectList' |
@@ -1,9 +0,10 @@ | ||
import { SavedObjectStore } from './SavedObjectStore' | ||
import { SettingsStore } from './SettingsStore' | ||
import { SavedObjectStore } from "./SavedObjectStore"; | ||
import { SettingsStore } from "./SettingsStore"; | ||
type DataStoreInput = { | ||
engine: any | ||
namespace: string | ||
engine: any, | ||
namespace: string, | ||
defaultGlobalSettings?: object | ||
defaultUserSettings?: object | ||
encryptSettings: boolean | ||
} | ||
@@ -17,8 +18,3 @@ | ||
constructor({ | ||
engine, | ||
namespace, | ||
defaultGlobalSettings, | ||
defaultUserSettings, | ||
}: DataStoreInput) { | ||
constructor({ engine, namespace, defaultGlobalSettings, defaultUserSettings, encryptSettings = false }: DataStoreInput) { | ||
this.globalSettings = new SettingsStore({ | ||
@@ -30,2 +26,3 @@ engine, | ||
defaults: defaultGlobalSettings, | ||
encrypt: encryptSettings | ||
}) | ||
@@ -38,4 +35,5 @@ this.userSettings = new SettingsStore({ | ||
defaults: defaultUserSettings, | ||
encrypt: encryptSettings | ||
}) | ||
this.userSavedObjects = new SavedObjectStore({ | ||
@@ -47,7 +45,7 @@ engine, | ||
}) | ||
this.globalSavedObjects = new SavedObjectStore({ | ||
this.globalSavedObjects = new SavedObjectStore({ | ||
engine, | ||
resource: 'dataStore', | ||
namespace, | ||
item: 'savedObjects', | ||
item: 'savedObjects' | ||
}) | ||
@@ -68,6 +66,3 @@ } | ||
// TODO: Handle errors | ||
await this.globalSavedObjects.update( | ||
id, | ||
this.userSavedObjects.get(id) | ||
) | ||
await this.globalSavedObjects.update(id, this.userSavedObjects.get(id)) | ||
await this.userSavedObjects.remove(id) | ||
@@ -80,6 +75,3 @@ } | ||
// TODO: Handle errors | ||
await this.userSavedObjects.update( | ||
id, | ||
this.globalSavedObjects.get(id) | ||
) | ||
await this.userSavedObjects.update(id, this.globalSavedObjects.get(id)) | ||
await this.globalSavedObjects.remove(id) | ||
@@ -86,0 +78,0 @@ } |
@@ -1,11 +0,8 @@ | ||
import { v4 as uuid } from 'uuid' | ||
import { | ||
SettingsStore, | ||
DataStoreResource /* eslint-disable-line no-unused-vars */, | ||
} from './SettingsStore' | ||
import { v4 as uuid } from "uuid"; | ||
import { SettingsStore, DataStoreResource } from "./SettingsStore"; | ||
type SavedObjectStoreInput = { | ||
engine: any | ||
resource: DataStoreResource | ||
namespace: string | ||
engine: any, | ||
resource: DataStoreResource, | ||
namespace: string, | ||
item: string | ||
@@ -15,3 +12,3 @@ } | ||
constructor({ engine, resource, namespace, item }: SavedObjectStoreInput) { | ||
super({ engine, resource, namespace, item, defaults: {} }) | ||
super({ engine, resource, namespace, item, defaults: {}, encrypt: false }) | ||
} | ||
@@ -25,5 +22,3 @@ | ||
if (typeof object !== 'object') { | ||
throw new Error( | ||
`Only objects are allowed in the SavedObjectStore, received ${object}` | ||
) | ||
throw new Error(`Only objects are allowed in the SavedObjectStore, received ${object}`) | ||
} | ||
@@ -34,3 +29,3 @@ const id = uuid() | ||
...object, | ||
id, | ||
id | ||
} | ||
@@ -45,3 +40,3 @@ | ||
...this.get(id), | ||
...object, | ||
...object | ||
} | ||
@@ -57,2 +52,2 @@ await this.set(id, objectToAdd) | ||
} | ||
} | ||
} |
@@ -1,15 +0,19 @@ | ||
import { EventEmitter } from '../utils/EventEmitter' | ||
import { EventEmitter } from "../utils/EventEmitter" | ||
export type DataStoreResource = 'dataStore' | 'userDataStore' | ||
const joinPath = (...parts: Array<string>) => | ||
parts.map(part => part.replace(/^\/+/, '').replace(/\/+$/, '')).join('/') | ||
const joinPath = (...parts: Array<string>) => | ||
parts.map(part => | ||
part | ||
.replace(/^\/+/, '') | ||
.replace(/\/+$/, '') | ||
).join('/') | ||
// TODO: Proper engine typings | ||
export type BaseSettingsStoreInput = { | ||
engine: any | ||
resource: DataStoreResource | ||
namespace: string | ||
item: string | ||
defaults: any | ||
engine: any, // TODO: Proper typings | ||
resource: DataStoreResource, | ||
namespace: string, | ||
item: string, | ||
defaults: any, | ||
encrypt: boolean | ||
} | ||
@@ -21,4 +25,4 @@ | ||
dataStoreId: string | ||
settings: Record<string, any> | ||
settings: Record<string,any> | ||
encrypt: boolean | ||
eventEmitter = new EventEmitter() | ||
@@ -32,2 +36,3 @@ | ||
defaults = {}, | ||
encrypt = false, | ||
}: BaseSettingsStoreInput) { | ||
@@ -38,2 +43,4 @@ this.engine = engine | ||
this.settings = defaults | ||
this.encrypt = encrypt | ||
} | ||
@@ -49,3 +56,6 @@ | ||
type: 'create', | ||
data: this.settings, | ||
params: { | ||
encrypt: this.encrypt | ||
}, | ||
data: this.settings | ||
}) | ||
@@ -55,3 +65,3 @@ } | ||
async refresh() { | ||
const prevSettings = this.settings | ||
const prevSettings = this.settings; | ||
try { | ||
@@ -62,5 +72,5 @@ const newSettings = await this.engine.query({ | ||
id: this.dataStoreId, | ||
}, | ||
} | ||
}) | ||
this.settings = newSettings.settings | ||
@@ -71,3 +81,3 @@ } catch (e) { | ||
} else { | ||
throw e | ||
throw e; | ||
} | ||
@@ -83,3 +93,3 @@ } | ||
} | ||
get(key: string) { | ||
@@ -92,3 +102,3 @@ return this.settings[key] | ||
...this.settings, | ||
[key]: value, | ||
[key]: value | ||
} | ||
@@ -108,3 +118,6 @@ if (typeof value === 'undefined') { | ||
id: this.dataStoreId, | ||
data: this.settings, | ||
params: { | ||
encrypt: this.encrypt | ||
}, | ||
data: this.settings | ||
}) | ||
@@ -111,0 +124,0 @@ } catch (e) { |
import { useDataStore } from './useDataStore' | ||
import { useEffect, useState, useCallback } from 'react' | ||
import { useMemo, useEffect, useState } from 'react' | ||
export const useAllSettings = ({ | ||
global = false, | ||
ignoreUpdates = false, | ||
} = {}) => { | ||
export const useAllSettings = ({ global = false, ignoreUpdates = false } = {}) => { | ||
const dataStore = useDataStore() | ||
const settingsStore = global | ||
? dataStore?.globalSettings | ||
: dataStore?.userSettings | ||
const settingsStore = global ? dataStore?.globalSettings : dataStore?.userSettings | ||
const [value, setValue] = useState(settingsStore?.settings) | ||
const set = useCallback( | ||
(key: string, value: any) => settingsStore?.set(key, value), | ||
[settingsStore] | ||
) | ||
const callbacks = useMemo(() => ({ | ||
set: (key: string, value: any) => settingsStore?.set(key, value), | ||
}), [dataStore]) | ||
useEffect(() => { | ||
if (!ignoreUpdates) { | ||
const callback = (newSettings: Record<string, any>) => | ||
setValue(newSettings) | ||
const callback = (newSettings: Record<string,any>) => setValue(newSettings) | ||
settingsStore?.subscribeAll(callback) | ||
@@ -28,9 +21,4 @@ return () => settingsStore?.unsubscribeAll(callback) | ||
}, [settingsStore, ignoreUpdates]) | ||
return [ | ||
value, | ||
{ | ||
set, | ||
}, | ||
] | ||
} | ||
return [value, callbacks] | ||
} |
import { useContext } from 'react' | ||
import { DataStoreContext } from './DataStoreContext' | ||
import { DataStoreContext} from './DataStoreContext' | ||
export const useDataStore = () => useContext(DataStoreContext) | ||
export const useDataStore = () => useContext(DataStoreContext) |
@@ -8,17 +8,12 @@ import { useDataStore } from './useDataStore' | ||
const global = !dataStore?.userSavedObjects.has(id) | ||
const objectStore = global | ||
? dataStore?.globalSavedObjects | ||
: dataStore?.userSavedObjects | ||
const objectStore = global ? dataStore?.globalSavedObjects : dataStore?.userSavedObjects | ||
const [value, setValue] = useState<object>(objectStore?.get(id)) | ||
const callbacks = useMemo( | ||
() => ({ | ||
update: (object: object) => objectStore?.update(id, object), | ||
replace: (object: object) => objectStore?.replace(id, object), | ||
remove: () => objectStore?.remove(id), | ||
share: () => dataStore?.shareSavedObject(id), | ||
unshare: () => dataStore?.unshareSavedObject(id), | ||
}), | ||
[dataStore, id, objectStore] | ||
) | ||
const callbacks = useMemo(() => ({ | ||
update: (object: object) => objectStore?.update(id, object), | ||
replace: (object: object) => objectStore?.replace(id, object), | ||
remove: () => objectStore?.remove(id), | ||
share: () => dataStore?.shareSavedObject(id), | ||
unshare: () => dataStore?.unshareSavedObject(id), | ||
}), [dataStore]) | ||
@@ -31,5 +26,5 @@ useEffect(() => { | ||
} | ||
}, [objectStore, ignoreUpdates, id]) | ||
}, [objectStore, ignoreUpdates]) | ||
return [value, callbacks] | ||
} | ||
} |
import { useDataStore } from './useDataStore' | ||
import { useMemo, useEffect, useState } from 'react' | ||
const flattenDictionary = (dict?: Record<string, object>) => | ||
dict && | ||
Object.entries(dict).map(([id, obj]) => ({ | ||
id, | ||
...obj, | ||
})) | ||
const flattenDictionary = (dict?: Record<string, object>) => dict && Object.entries(dict).map(([id, obj]) => ({ | ||
id, | ||
...obj | ||
})) | ||
export const useSavedObjectList = ({ | ||
global = false, | ||
ignoreUpdates = false, | ||
} = {}) => { | ||
export const useSavedObjectList = ({ global = false, ignoreUpdates = false } = {}) => { | ||
const dataStore = useDataStore() | ||
const objectStore = global | ||
? dataStore?.globalSavedObjects | ||
: dataStore?.userSavedObjects | ||
const objectStore = global ? dataStore?.globalSavedObjects : dataStore?.userSavedObjects | ||
const [list, setList] = useState(flattenDictionary(objectStore?.settings)) | ||
const callbacks = useMemo( | ||
() => ({ | ||
add: (object: object) => objectStore?.add(object), | ||
update: (id: string, object: object) => | ||
objectStore?.update(id, object), | ||
replace: (id: string, object: object) => | ||
objectStore?.replace(id, object), | ||
remove: (id: string) => objectStore?.remove(id), | ||
}), | ||
[objectStore] | ||
) | ||
const callbacks = useMemo(() => ({ | ||
add: (object: object) => objectStore?.add(object), | ||
update: (id: string, object: object) => objectStore?.update(id, object), | ||
replace: (id: string, object: object) => objectStore?.replace(id, object), | ||
remove: (id: string) => objectStore?.remove(id) | ||
}), [dataStore]) | ||
useEffect(() => { | ||
if (!ignoreUpdates) { | ||
const callback = (newSettings: Record<string, object>) => | ||
setList(flattenDictionary(newSettings)) | ||
const callback = (newSettings: Record<string, object>) => setList(flattenDictionary(newSettings)) | ||
objectStore?.subscribeAll(callback) | ||
@@ -42,4 +29,4 @@ return () => objectStore?.unsubscribeAll(callback) | ||
}, [objectStore, ignoreUpdates]) | ||
return [list, callbacks] | ||
} | ||
} |
import { useDataStore } from './useDataStore' | ||
import { useMemo, useEffect, useState } from 'react' | ||
export const useSetting = ( | ||
id: string, | ||
{ global = false, ignoreUpdates = false } = {} | ||
) => { | ||
export const useSetting = (id: string, { global = false, ignoreUpdates = false } = {}) => { | ||
const dataStore = useDataStore() | ||
const settingsStore = global | ||
? dataStore?.globalSettings | ||
: dataStore?.userSettings | ||
const settingsStore = global ? dataStore?.globalSettings : dataStore?.userSettings | ||
const [value, setValue] = useState(settingsStore?.get(id)) | ||
const callbacks = useMemo( | ||
() => ({ | ||
set: (value: any) => settingsStore?.set(id, value), | ||
}), | ||
[id, settingsStore] | ||
) | ||
const callbacks = useMemo(() => ({ | ||
set: (value: any) => settingsStore?.set(id, value) | ||
}), [dataStore]) | ||
@@ -28,5 +20,5 @@ useEffect(() => { | ||
} | ||
}, [settingsStore, ignoreUpdates, id]) | ||
}, [settingsStore, ignoreUpdates]) | ||
return [value, callbacks] | ||
} | ||
} |
@@ -11,16 +11,16 @@ /* Based on https://gist.github.com/mudge/5830382 */ | ||
if (!this.events[event]) { | ||
this.events[event] = [] | ||
this.events[event] = []; | ||
} | ||
this.events[event].push(listener) | ||
this.events[event].push(listener); | ||
} | ||
off(event: string, listener: Function) { | ||
var idx | ||
var idx; | ||
if (this.events[event]) { | ||
idx = this.events[event].indexOf(listener) | ||
idx = this.events[event].indexOf(listener); | ||
if (idx > -1) { | ||
this.events[event].splice(idx, 1) | ||
this.events[event].splice(idx, 1); | ||
} | ||
@@ -31,10 +31,10 @@ } | ||
emit(event: string, ...args: any[]) { | ||
var i, listeners, length | ||
var i, listeners, length; | ||
if (this.events[event]) { | ||
listeners = this.events[event].slice() | ||
length = listeners.length | ||
listeners = this.events[event].slice(); | ||
length = listeners.length; | ||
for (i = 0; i < length; i++) { | ||
listeners[i](...args) | ||
listeners[i](...args); | ||
} | ||
@@ -46,7 +46,7 @@ } | ||
const callback = () => { | ||
this.off(event, callback) | ||
listener.apply(this, arguments) | ||
this.off(event, callback); | ||
listener.apply(this, arguments); | ||
} | ||
this.on(event, callback) | ||
} | ||
} | ||
this.on(event, callback); | ||
}; | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
215461
6
2
201
1
36
1387
1