项目作者: sagiavinash

项目描述 :
A standard for Flux store objects and compatible action objects
高级语言:
项目地址: git://github.com/sagiavinash/flux-standard-data-storage.git
创建时间: 2016-08-21T10:23:58Z
项目社区:https://github.com/sagiavinash/flux-standard-data-storage

开源协议:

下载


Flux Standard Data Storage

Introduction

A standard for Flux store objects and compatible action objects

Problem

  1. When multiple actions(same action with different inputs) to data stored in flux stores is prone to query - data mismatch due to race conditions.
  2. Need for a data cache and making it navigable through history.
  3. Status of data stored in flux stores is often derived by the values(comparing current value with default values) or based on the events occured until now going against reactive programming.

Design goals

  1. Schema of data stored should be progressively enhanced so that all data reads/accessors are unaffected by changes in store.
  2. Should be compliant with existing standards and ecosystem (Do no re-invent the wheel).

Datapoint in a store

a dataPoint MUST

  • be a plain javascript object
  • have data property

a dataPoint MAY

  • have error property and must be an error object
  • have isLoading property
  • have query property
  • have cache property when query property is present
  • have prevQueries when query & cache properties are present
  • have nextQueries when query, cache& prevQueries properties are present

a dataPoint MUST NOT

  • have a property which is not listed above

data

The data property MAY be of any value

error

The error property MUST be an error object, data property should not remain same

isLoading

The isLoading property is a boolean, set to true by an action of LOADING type and set to false by the LOADED type.

query

The query property MAY be of any value.

The value of query should be sent in payload property of a LOADING action (FSA).

The value of query should be sent in meta property of a LOADED action (FSA).

cache

The cache property is a set (unique) of objects with query, data mandatory properties and optional error property.

prevQueries

The prevQueries property is stack (LIFO) of queries.

nextQueries

The nextQueries property is stack (LIFO) of queries.

Actions

an action should be flux-standard-action (FSA) compliant.

a datapoint MAY

  • be updated by a sync event and the action type should be prefixed with LOAD keyword
    • the LOAD event should have the query as a property of the meta object
    • the LOAD event should have the data as the value or propery of the payload
  • be updated by an async event and it should be done via two actions with action type prefixed by LOADING and LOADED keywords.
    • the LOADING event should have the query in the payload object
    • the LOADED event should have the query as a property of the meta object
    • the LOADED event should have the data as the value or propery of the payload

Examples

1. Simple data storage
  1. /// Flux Actions (FSA Compliant)
  2. // loaded data action
  3. dispatch({
  4. type: 'LOADED_DATAPOINT',
  5. payload: 'value',
  6. });
  7. /// dataPoint in a store
  8. datapoint: {
  9. data: 'value',
  10. },
2. Loading & error states
  1. /// Flux Actions (FSA Compliant)
  2. dispatch({
  3. type: actions.LOADING_DATAPOINT,
  4. });
  5. loadDataPoint().then((response) => {
  6. if (!response.error) {
  7. // loaded data action - success
  8. dispatch({
  9. type: actions.LOADED_DATAPOINT,
  10. payload: 'value',
  11. });
  12. } else {
  13. // loaded data action - failure
  14. dispatch({
  15. type: actions.LOADED_DATAPOINT,
  16. payload: new Error('message'),
  17. error: true,
  18. });
  19. }
  20. });
  21. /// dataPoint in a store (Flux Standard Data Storage Compliant)
  22. reducer(state, action) {
  23. switch (action.type) {
  24. case actions.LOADING_DATAPOINT: {
  25. return {
  26. ...state,
  27. isLoading: true,
  28. };
  29. }
  30. case actions.LOADED_DATAPOINT: {
  31. return !action.error ? {
  32. ...state,
  33. datapoint: {
  34. ...state.datapoint,
  35. data: action.payload,
  36. isLoading: false,
  37. }
  38. } : {
  39. ...state,
  40. dataPoint: {
  41. ...state.dataPoint,
  42. data: null,
  43. error: action.payload,
  44. isLoading: false,
  45. },
  46. };
  47. }
  48. }
  49. }
3. Datapoint with a corresponding query
  1. /// Flux Actions (FSA Compliant)
  2. dispatch({
  3. type: actions.LOADING_DATAPOINT,
  4. payload: {
  5. query: query,
  6. },
  7. });
  8. loadDataPoint(query).then((response) => {
  9. if (!response.error) {
  10. dispatch({
  11. type: actions.LOADED_DATAPOINT,
  12. meta: {
  13. query: query,
  14. },
  15. payload: 'value',
  16. });
  17. } else {
  18. dispatch({
  19. type: actions.LOADED_DATAPOINT,
  20. meta: {
  21. query: query,
  22. },
  23. payload: new Error('message'),
  24. error: true,
  25. });
  26. }
  27. });
  28. /// dataPoint in a store (Flux Standard Data Storage Compliant)
  29. reducer(state, action) {
  30. switch (action.type) {
  31. case actions.LOADING_DATAPOINT: {
  32. return {
  33. ...state,
  34. datapoint: {
  35. ...state.dataPoint,
  36. query: action.payload.query,
  37. isLoading: true,
  38. },
  39. };
  40. }
  41. case actions.LOADED_DATAPOINT: {
  42. if (_.isEqual(state.query, action.payload.query) {
  43. // only updates if the LOADED action payload corresponds to the query of latest LOADING action.
  44. // no race condition for close simultaneous events
  45. return !action.error ? {
  46. ...state,
  47. datapoint: {
  48. ...state.dataPoint,
  49. data: action.payload,
  50. isLoading: false,
  51. },
  52. } : {
  53. ...state,
  54. datapoint: {
  55. ...state.dataPoint,
  56. data: null,
  57. error: action.payload,
  58. isLoading: false,
  59. },
  60. };
  61. }
  62. return state;
  63. }
  64. }
  65. }
4. Datapoint with cache
  1. /// Flux Actions (FSA Compliant)
  2. const cachedValue = _.find(store.getState().datapoint.cache, (entry) => isEqual(query, entry.query));
  3. if (!cachedValue) {
  4. dispatch({
  5. type: LOAD_DATAPOINT,
  6. meta: {
  7. query: query,
  8. },
  9. payload: cachedValue,
  10. });
  11. } else {
  12. dispatch({
  13. type: actions.LOADING_DATAPOINT,
  14. payload: {
  15. query: query,
  16. },
  17. });
  18. loadDataPoint(query).then((response) => {
  19. if (!response.error) {
  20. dispatch({
  21. type: actions.LOADED_DATAPOINT,
  22. meta: {
  23. query: query,
  24. },
  25. payload: 'value',
  26. });
  27. } else {
  28. dispatch({
  29. type: actions.LOADED_DATAPOINT,
  30. meta: {
  31. query: query,
  32. },
  33. payload: new Error('message'),
  34. error: true,
  35. });
  36. }
  37. });
  38. }
  39. /// dataPoint in a store (Flux Standard Data Storage Compliant)
  40. reducer(state, action) {
  41. switch (action.type) {
  42. case actions.LOAD_DATAPOINT: {
  43. // if data is cached then this action is triggered
  44. return {
  45. ...state,
  46. datapoint: {
  47. ...state.dataPoint,
  48. query: action.meta.query,
  49. data: action.payload,
  50. },
  51. };
  52. }
  53. case actions.LOADING_DATAPOINT: {
  54. return {
  55. ...state,
  56. datapoint: {
  57. ...state.dataPoint,
  58. query: action.payload.query,
  59. isLoading: true,
  60. },
  61. };
  62. }
  63. case actions.LOADED_DATAPOINT: {
  64. // if data is cached then this action is triggered and cache is updated
  65. if (_.isEqual(state.query, action.payload.query) {
  66. return !action.error ? {
  67. ...state,
  68. datapoint: {
  69. ...state.dataPoint,
  70. data: action.payload,
  71. cache: [...state.dataPoint.cache, { query: action.meta.query, data: action.payload }],
  72. isLoading: false,
  73. },
  74. } : {
  75. ...state,
  76. datapoint: {
  77. ...state.dataPoint,
  78. data: null,
  79. error: action.payload,
  80. cache: [...state.dataPoint.cache, { query: action.meta.query, error: action.payload }],
  81. isLoading: false,
  82. },
  83. };
  84. }
  85. return state;
  86. }
  87. }
  88. }
5. Datapoint with queryHistory
  1. /// Flux Actions (FSA Compliant)
  2. const cachedValue = _.find(store.getState().datapoint.cache, (entry) => isEqual(query, entry.query));
  3. if (!cachedValue) {
  4. dispatch({
  5. type: LOAD_DATAPOINT,
  6. meta: {
  7. query: query,
  8. },
  9. payload: cachedValue,
  10. });
  11. } else {
  12. dispatch({
  13. type: actions.LOADING_DATAPOINT,
  14. payload: {
  15. query: query,
  16. },
  17. });
  18. loadDataPoint(query).then((response) => {
  19. if (!response.error) {
  20. dispatch({
  21. type: actions.LOADED_DATAPOINT,
  22. meta: {
  23. query: query,
  24. },
  25. payload: 'value',
  26. });
  27. } else {
  28. dispatch({
  29. type: actions.LOADED_DATAPOINT,
  30. meta: {
  31. query: query,
  32. },
  33. payload: new Error('message'),
  34. error: true,
  35. });
  36. }
  37. });
  38. }
  39. /// dataPoint in a store (Flux Standard Data Storage Compliant)
  40. reducer(state, action) {
  41. switch (action.type) {
  42. case actions.LOAD_DATAPOINT: {
  43. return {
  44. ...state,
  45. datapoint: {
  46. ...state.dataPoint,
  47. query: action.meta.query,
  48. prevQueries: [...state.prevQueries, action.meta.query], // stack updated
  49. data: action.payload,
  50. },
  51. };
  52. }
  53. case actions.LOADING_DATAPOINT: {
  54. return {
  55. ...state,
  56. datapoint: {
  57. ...state.dataPoint,
  58. query: action.payload.query,
  59. prevQueries: [...state.prevQueries, action.meta.query], // stack updated
  60. isLoading: true,
  61. },
  62. };
  63. }
  64. case actions.LOADED_DATAPOINT: {
  65. if (_.isEqual(state.query, action.payload.query) {
  66. // only updates if the LOADED action payload corresponds to the query of latest LOADING action.
  67. // no race condition for close simultaneous events
  68. return !action.error ? {
  69. ...state,
  70. datapoint: {
  71. ...state.dataPoint,
  72. data: action.payload,
  73. cache: [...state.dataPoint.cache, { query: action.meta.query, error: action.payload }],
  74. isLoading: false,
  75. },
  76. } : {
  77. ...state,
  78. datapoint: {
  79. ...state.dataPoint,
  80. data: null,
  81. error: action.payload,
  82. isLoading: false,
  83. },
  84. };
  85. }
  86. return state;
  87. }
  88. }
  89. }
6. Datapoint with navigable queryHistory
  1. /// Flux Actions (FSA Compliant)
  2. const cachedValue = _.find(store.getState().datapoint.cache, (entry) => isEqual(query, entry.query));
  3. dispatch({
  4. type: LOAD_PREV_QUERY_OF_DATAPOINT,
  5. meta: {
  6. query: query,
  7. },
  8. payload: cachedValue,
  9. });
  10. /// dataPoint in a store (Flux Standard Data Storage Compliant)
  11. reducer(state, action) {
  12. switch (action.type) {
  13. case actions.LOAD_PREV_QUERY_OF_DATAPOINT: {
  14. // only updates if the LOADED action payload corresponds to the query of latest LOADING action.
  15. // no race condition for close simultaneous events
  16. return !action.error ? {
  17. ...state,
  18. datapoint: {
  19. ...state.dataPoint,
  20. query: _.last(state.dataPoint.prevQueries), // updated with last query
  21. prevQueries: _.slice(state.dataPoint.prevQueries, 0, -1)], // stack reduced
  22. nextQueries: [...state.dataPoint.nextQueries, state.dataPoint.query], // stack increased
  23. data: _.find(
  24. store.getState().datapoint.cache,
  25. (entry) => isEqual(_.last(state.dataPoint.prevQueries), entry.query)
  26. ), // data retrieved from cache
  27. cache: [...state.dataPoint.cache, { query: action.meta.query, error: action.payload }],
  28. isLoading: false,
  29. },
  30. } : {
  31. ...state,
  32. datapoint: {
  33. ...state.dataPoint,
  34. data: null,
  35. error: action.payload,
  36. isLoading: false,
  37. },
  38. };
  39. }
  40. }
  41. }