@civet/core 0.6.9 → 1.0.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports["default"] = void 0;
7
+
8
+ var _react = _interopRequireDefault(require("react"));
9
+
10
+ var _AbortSignal = _interopRequireDefault(require("./AbortSignal"));
11
+
12
+ var _context2 = require("./context");
13
+
14
+ var _Meta = _interopRequireDefault(require("./Meta"));
15
+
16
+ var _uniqueIdentifier = _interopRequireDefault(require("./uniqueIdentifier"));
17
+
18
+ var _excluded = ["name", "query", "empty", "options", "provider", "persistent"],
19
+ _excluded2 = ["data"];
20
+
21
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
22
+
23
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
24
+
25
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
26
+
27
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
28
+
29
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
30
+
31
+ function _defineProperty(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; }
32
+
33
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
34
+
35
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
36
+
37
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
38
+
39
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
40
+
41
+ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
42
+
43
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
44
+
45
+ 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]; } } return target; }
46
+
47
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
48
+
49
+ /**
50
+ * Makes data from an DataProvider available.
51
+ * If not explicitly specified, necessary configuration is taken from the nearest <ConfigProvider>.
52
+ * The provided DataProvider must not be replaced.
53
+ */
54
+ function useResource(props) {
55
+ var name = props.name,
56
+ query = props.query,
57
+ empty = props.empty,
58
+ options = props.options,
59
+ providerProp = props.provider,
60
+ persistent = props.persistent,
61
+ rest = _objectWithoutProperties(props, _excluded);
62
+
63
+ var configContext = (0, _context2.useConfigContext)();
64
+ var currentProvider = providerProp || configContext.provider;
65
+
66
+ var provider = _react["default"].useMemo(function () {
67
+ return currentProvider;
68
+ }, []);
69
+
70
+ if (provider == null) {
71
+ throw new Error('Unmet requirement: The DataProvider for the useResource hook is missing - Check your ConfigContext and the provider property');
72
+ }
73
+
74
+ if (provider !== currentProvider) {
75
+ throw new Error('Constant violation: The DataProvider provided to the useResource hook must not be replaced - Check your ConfigContext and the provider property');
76
+ }
77
+
78
+ var comparator = {
79
+ name: name,
80
+ query: query,
81
+ empty: empty,
82
+ options: options
83
+ };
84
+
85
+ var _React$useState = _react["default"].useState(function () {
86
+ var request = (0, _uniqueIdentifier["default"])();
87
+ var revision = (0, _uniqueIdentifier["default"])();
88
+ return {
89
+ comparator: comparator,
90
+ request: request,
91
+ revision: revision,
92
+ isLoading: !empty,
93
+ value: {
94
+ name: name,
95
+ query: query,
96
+ options: options,
97
+ request: request,
98
+ revision: revision,
99
+ data: [],
100
+ meta: {},
101
+ error: undefined,
102
+ isEmpty: empty,
103
+ isIncomplete: !empty,
104
+ isInitial: !empty
105
+ },
106
+ persistent: persistent
107
+ };
108
+ }),
109
+ _React$useState2 = _slicedToArray(_React$useState, 2),
110
+ state = _React$useState2[0],
111
+ setState = _React$useState2[1];
112
+
113
+ var prevComparator = state.comparator,
114
+ request = state.request,
115
+ revision = state.revision,
116
+ isLoading = state.isLoading,
117
+ value = state.value,
118
+ prevPersistent = state.persistent;
119
+
120
+ if (prevComparator !== comparator && !provider.compareRequests(prevComparator, comparator)) {
121
+ setState(function (prevState) {
122
+ var nextRequest = (0, _uniqueIdentifier["default"])(prevState.request);
123
+ var nextRevision = (0, _uniqueIdentifier["default"])(prevState.revision);
124
+ var isPersistent;
125
+
126
+ if (prevState.value.meta.persistent === 'very' || persistent === 'very' && prevState.persistent === 'very') {
127
+ isPersistent = 'very';
128
+ } else if (prevState.value.meta.persistent || persistent && prevState.persistent) {
129
+ isPersistent = true;
130
+ }
131
+
132
+ var shouldValuePersist = !empty && isPersistent && (isPersistent === 'very' || prevState.comparator.name === comparator.name);
133
+ return {
134
+ comparator: comparator,
135
+ request: nextRequest,
136
+ revision: nextRevision,
137
+ isLoading: !empty,
138
+ value: shouldValuePersist ? prevState.value : {
139
+ name: name,
140
+ query: query,
141
+ options: options,
142
+ request: nextRequest,
143
+ revision: nextRevision,
144
+ data: [],
145
+ meta: {},
146
+ error: undefined,
147
+ isEmpty: empty,
148
+ isIncomplete: !empty,
149
+ isInitial: !empty
150
+ },
151
+ persistent: persistent
152
+ };
153
+ });
154
+ } else if (prevPersistent !== persistent) {
155
+ setState(function (prevState) {
156
+ return _objectSpread(_objectSpread({}, prevState), {}, {
157
+ persistent: persistent
158
+ });
159
+ });
160
+ }
161
+
162
+ var notify = _react["default"].useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
163
+ return regeneratorRuntime.wrap(function _callee$(_context) {
164
+ while (1) {
165
+ switch (_context.prev = _context.next) {
166
+ case 0:
167
+ return _context.abrupt("return", new Promise(function (resolve) {
168
+ setState(function (currentState) {
169
+ if (currentState.empty) return currentState;
170
+ var nextRevision = (0, _uniqueIdentifier["default"])(currentState.revision);
171
+ resolve({
172
+ request: currentState.request,
173
+ revision: nextRevision
174
+ });
175
+ return _objectSpread(_objectSpread({}, currentState), {}, {
176
+ isLoading: true,
177
+ revision: nextRevision
178
+ });
179
+ });
180
+ }));
181
+
182
+ case 1:
183
+ case "end":
184
+ return _context.stop();
185
+ }
186
+ }
187
+ }, _callee);
188
+ })), []); // DataProvider events
189
+
190
+
191
+ _react["default"].useEffect(function () {
192
+ if (empty) return undefined;
193
+ var unsubscribe = provider.subscribe(name, notify);
194
+ return unsubscribe;
195
+ }, [!empty, provider, name, notify]);
196
+
197
+ _react["default"].useEffect(function () {
198
+ if (empty) return undefined;
199
+ var abortSignal = new _AbortSignal["default"]();
200
+ var meta = new _Meta["default"](_objectSpread({}, value.meta));
201
+
202
+ var callback = function callback(error, done, data) {
203
+ setState(function (prevState) {
204
+ if (request !== prevState.request || revision !== prevState.revision) return prevState;
205
+
206
+ if (error != null) {
207
+ return _objectSpread(_objectSpread({}, prevState), {}, {
208
+ isLoading: false,
209
+ value: _objectSpread(_objectSpread({}, prevState.value), {}, {
210
+ error: error,
211
+ isIncomplete: false
212
+ })
213
+ });
214
+ }
215
+
216
+ var _prevState$value = prevState.value,
217
+ prevData = _prevState$value.data,
218
+ prevContext = _objectWithoutProperties(_prevState$value, _excluded2);
219
+
220
+ var context = {
221
+ name: name,
222
+ query: query,
223
+ options: options,
224
+ request: request,
225
+ revision: revision,
226
+ meta: meta.commit(prevContext.meta),
227
+ error: undefined,
228
+ isEmpty: false,
229
+ isIncomplete: !done,
230
+ isInitial: prevState.isInitial && !done
231
+ };
232
+ return {
233
+ isLoading: !done,
234
+ value: _objectSpread(_objectSpread({}, context), {}, {
235
+ data: provider.recycleItems(provider.transition(data, prevData, context, prevContext), prevData, context, prevContext)
236
+ })
237
+ };
238
+ });
239
+ };
240
+
241
+ provider.continuousGet(name, query, options, meta, callback, abortSignal);
242
+ return function () {
243
+ abortSignal.abort();
244
+ };
245
+ }, [request, revision]);
246
+
247
+ var isStale = request !== value.request;
248
+
249
+ var context = _react["default"].useMemo(function () {
250
+ return _objectSpread(_objectSpread({}, value), {}, {
251
+ provider: provider,
252
+ isLoading: isLoading,
253
+ isStale: isStale,
254
+ notify: notify
255
+ });
256
+ }, [value, provider, isLoading, isStale, notify]);
257
+
258
+ var contextPlugins = Array.isArray(provider.contextPlugins) ? provider.contextPlugins : [];
259
+ return contextPlugins.reduce(function (result, fn) {
260
+ return fn(result, rest);
261
+ }, context);
262
+ }
263
+
264
+ var _default = useResource;
265
+ exports["default"] = _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civet/core",
3
- "version": "0.6.9",
3
+ "version": "1.0.0-rc1",
4
4
  "description": "Civet",
5
5
  "main": "./lib/index.js",
6
6
  "scripts": {
@@ -52,8 +52,8 @@
52
52
  "eslint-plugin-react": "^7.21.5",
53
53
  "eslint-plugin-react-hooks": "^4.2.0",
54
54
  "prettier": "^2.3.2",
55
- "react": "*",
56
- "react-dom": "*",
55
+ "react": "^18.1.0",
56
+ "react-dom": "^18.1.0",
57
57
  "rimraf": "^3.0.2"
58
58
  },
59
59
  "peerDependencies": {
@@ -62,8 +62,8 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "fast-deep-equal": "^3.1.3",
65
- "object-hash": "^2.2.0",
66
- "prop-types": "^15.7.2",
65
+ "object-hash": "^3.0.0",
66
+ "prop-types": "^15.8.1",
67
67
  "uuid": "^8.3.2"
68
68
  }
69
69
  }
@@ -3,11 +3,24 @@ import Notifier from './Notifier';
3
3
  class AbortSignal {
4
4
  constructor() {
5
5
  this.notifier = new Notifier();
6
- this.locked = false;
7
- this.aborted = false;
6
+ Object.defineProperties(this, {
7
+ locked: {
8
+ value: false,
9
+ enumerable: true,
10
+ writable: false,
11
+ configurable: true,
12
+ },
13
+ aborted: {
14
+ value: false,
15
+ enumerable: true,
16
+ writable: false,
17
+ configurable: true,
18
+ },
19
+ });
8
20
  }
9
21
 
10
22
  listen(cb) {
23
+ if (this.locked) return () => {};
11
24
  const alreadySubscribed = this.notifier.isSubscribed(cb);
12
25
  const unsubscribe = this.notifier.subscribe(cb);
13
26
  if (this.aborted && !alreadySubscribed) cb();
@@ -16,13 +29,39 @@ class AbortSignal {
16
29
 
17
30
  abort() {
18
31
  if (this.locked) return;
19
- this.locked = true;
20
- this.aborted = true;
32
+ this.lock();
33
+ Object.defineProperty(this, 'aborted', {
34
+ value: true,
35
+ enumerable: true,
36
+ writable: false,
37
+ configurable: false,
38
+ });
21
39
  this.notifier.trigger();
22
40
  }
23
41
 
24
42
  lock() {
25
- this.locked = true;
43
+ if (this.locked) return;
44
+ Object.defineProperty(this, 'locked', {
45
+ value: true,
46
+ enumerable: true,
47
+ writable: false,
48
+ configurable: false,
49
+ });
50
+ }
51
+
52
+ proxy() {
53
+ const s = this;
54
+ return {
55
+ get notifier() {
56
+ return s.notifier;
57
+ },
58
+ get locked() {
59
+ return s.locked;
60
+ },
61
+ get aborted() {
62
+ return s.locked;
63
+ },
64
+ };
26
65
  }
27
66
  }
28
67
 
@@ -1,20 +1,20 @@
1
1
  import PropTypes from 'prop-types';
2
- import React, { useMemo } from 'react';
2
+ import React from 'react';
3
3
 
4
4
  import { ConfigContext } from './context';
5
- import { dataStorePropType } from './DataStore';
5
+ import { dataProviderPropType } from './DataProvider';
6
6
 
7
7
  /**
8
8
  * Provides general configuration to its descendants using React's context API.
9
9
  */
10
- function ConfigProvider({ dataStore, children }) {
11
- const context = useMemo(() => ({ dataStore }), [dataStore]);
10
+ function ConfigProvider({ provider, children }) {
11
+ const context = React.useMemo(() => ({ provider }), [provider]);
12
12
 
13
13
  return <ConfigContext.Provider value={context}>{children}</ConfigContext.Provider>;
14
14
  }
15
15
 
16
16
  ConfigProvider.propTypes = {
17
- dataStore: dataStorePropType,
17
+ provider: dataProviderPropType,
18
18
  children: PropTypes.node,
19
19
  };
20
20
 
@@ -0,0 +1,182 @@
1
+ import deepEquals from 'fast-deep-equal';
2
+ import objectHash from 'object-hash';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import AbortSignal from './AbortSignal';
6
+ import ChannelNotifier from './ChannelNotifier';
7
+ import Meta from './Meta';
8
+
9
+ const getMeta = (meta) => (meta instanceof Meta ? meta : new Meta(meta));
10
+
11
+ class DataProvider {
12
+ notifier = new ChannelNotifier();
13
+
14
+ constructor() {
15
+ const contextPlugins = [];
16
+ const uiPlugins = [];
17
+ this.extend({
18
+ context: (plugin) => {
19
+ const plugins = contextPlugins;
20
+ if (plugin != null && !plugins.includes(plugin)) plugins.push(plugin);
21
+ },
22
+ ui: (plugin) => {
23
+ const plugins = uiPlugins;
24
+ if (plugin != null && !plugins.includes(plugin)) plugins.push(plugin);
25
+ },
26
+ });
27
+ Object.defineProperties(this, {
28
+ contextPlugins: {
29
+ value: Object.freeze(contextPlugins.slice()),
30
+ enumerable: true,
31
+ writable: false,
32
+ configurable: false,
33
+ },
34
+ uiPlugins: {
35
+ value: Object.freeze(uiPlugins.slice()),
36
+ enumerable: true,
37
+ writable: false,
38
+ configurable: false,
39
+ },
40
+ });
41
+ }
42
+
43
+ extend() {}
44
+
45
+ subscribe(resource, handler) {
46
+ if (resource == null) throw new Error('No resource name specified');
47
+ return this.notifier.subscribe(resource, handler);
48
+ }
49
+
50
+ notify(resource) {
51
+ this.notifier.trigger(resource);
52
+ }
53
+
54
+ get(resource, query, options, meta) {
55
+ return new Promise((resolve, reject) =>
56
+ this.continuousGet(resource, query, options, meta, (error, done, result) => {
57
+ if (error != null) {
58
+ reject(error);
59
+ return;
60
+ }
61
+ if (done) resolve(result);
62
+ }),
63
+ );
64
+ }
65
+
66
+ continuousGet(resource, query, options, meta, callback, abortSignal) {
67
+ const signal = abortSignal == null ? new AbortSignal() : abortSignal;
68
+
69
+ new Promise((resolve) => {
70
+ if (resource == null) throw new Error('No resource name specified');
71
+
72
+ // result transformation
73
+ const cb = (error, done, result) => {
74
+ // prevent updates after completion
75
+ if (signal.locked) return;
76
+ if (error != null || done) {
77
+ signal.lock();
78
+ }
79
+ if (error != null) callback(error, true, []);
80
+ else if (result == null) callback(undefined, done, []);
81
+ else if (Array.isArray(result)) callback(undefined, done, result);
82
+ else callback(undefined, done, [result]);
83
+ };
84
+
85
+ resolve(
86
+ Promise.resolve(this.handleGet(resource, query, options, getMeta(meta))).then((result) => {
87
+ if (typeof result === 'function') {
88
+ result(cb, signal.proxy());
89
+ } else {
90
+ cb(undefined, true, result);
91
+ }
92
+ }),
93
+ );
94
+ }).catch((e) => {
95
+ if (!signal.locked) callback(e, true, []);
96
+ });
97
+ }
98
+
99
+ create(resource, data, options, meta) {
100
+ return new Promise((resolve) => {
101
+ if (resource == null) throw new Error('No resource name specified');
102
+ if (data == null) throw new Error('No data specified');
103
+ resolve(Promise.resolve(this.handleCreate(resource, data, options, getMeta(meta))));
104
+ });
105
+ }
106
+
107
+ update(resource, query, data, options, meta) {
108
+ return new Promise((resolve) => {
109
+ if (resource == null) throw new Error('No resource name specified');
110
+ if (data == null) throw new Error('No data specified');
111
+ resolve(Promise.resolve(this.handleUpdate(resource, query, data, options, getMeta(meta))));
112
+ });
113
+ }
114
+
115
+ patch(resource, query, data, options, meta) {
116
+ return new Promise((resolve) => {
117
+ if (resource == null) throw new Error('No resource name specified');
118
+ if (data == null) throw new Error('No data specified');
119
+ resolve(Promise.resolve(this.handlePatch(resource, query, data, options, getMeta(meta))));
120
+ });
121
+ }
122
+
123
+ remove(resource, query, options, meta) {
124
+ return new Promise((resolve) => {
125
+ if (resource == null) throw new Error('No resource name specified');
126
+ resolve(Promise.resolve(this.handleRemove(resource, query, options, getMeta(meta))));
127
+ });
128
+ }
129
+
130
+ compareRequests(prev, next) {
131
+ return deepEquals(prev, next);
132
+ }
133
+
134
+ compareItemVersions() {
135
+ return true;
136
+ }
137
+
138
+ getItemIdentifier(item) {
139
+ return objectHash(item);
140
+ }
141
+
142
+ transition(nextData) {
143
+ return nextData;
144
+ }
145
+
146
+ recycleItems(nextData, prevData) {
147
+ const prevMapping = {};
148
+ if (nextData.length > 0) {
149
+ prevData.forEach((item) => {
150
+ const id = this.getItemIdentifier(item);
151
+ if (id != null) prevMapping[id] = item;
152
+ });
153
+ }
154
+ let result;
155
+ if (prevData.length > 0) {
156
+ result = nextData.map((nextItem) => {
157
+ const id = this.getItemIdentifier(nextItem);
158
+ if (id != null && Object.prototype.hasOwnProperty.call(prevMapping, id)) {
159
+ const prevItem = prevMapping[id];
160
+ if (this.compareItemVersions(nextItem, prevItem)) return prevItem;
161
+ }
162
+ return nextItem;
163
+ });
164
+ } else {
165
+ result = nextData;
166
+ }
167
+ if (
168
+ prevData.length === result.length &&
169
+ result.reduce((sum, item, i) => sum && Object.is(prevData[i], item), true)
170
+ ) {
171
+ return prevData;
172
+ }
173
+ return result;
174
+ }
175
+ }
176
+
177
+ const isDataProvider = (provider) => provider instanceof DataProvider;
178
+
179
+ const dataProviderPropType = PropTypes.instanceOf(DataProvider);
180
+
181
+ export default DataProvider;
182
+ export { isDataProvider, dataProviderPropType };
package/src/Meta.js CHANGED
@@ -39,8 +39,11 @@ class Meta {
39
39
  return Object.values(this.data);
40
40
  }
41
41
 
42
- commit(prev) {
42
+ commit(prev, ignore) {
43
43
  const next = { ...this.data };
44
+ (ignore || []).forEach((item) => {
45
+ delete next[item];
46
+ });
44
47
  const keys = Object.keys(next);
45
48
  if (
46
49
  prev != null &&