statedb.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. JSONObject, Token
  5. } from '@phosphor/coreutils';
  6. import {
  7. IDataConnector
  8. } from '.';
  9. /* tslint:disable */
  10. /**
  11. * The default state database token.
  12. */
  13. export
  14. const IStateDB = new Token<IStateDB>('jupyter.services.statedb');
  15. /* tslint:enable */
  16. /**
  17. */
  18. export
  19. interface IStateItem {
  20. /**
  21. * The identifier key for a state item.
  22. */
  23. id: string;
  24. /**
  25. * The data value for a state item.
  26. */
  27. value: JSONObject;
  28. }
  29. /**
  30. * The description of a state database.
  31. */
  32. export
  33. interface IStateDB extends IDataConnector<JSONObject, JSONObject> {
  34. /**
  35. * The maximum allowed length of the data after it has been serialized.
  36. */
  37. readonly maxLength: number;
  38. /**
  39. * The namespace prefix for all state database entries.
  40. *
  41. * #### Notes
  42. * This value should be set at instantiation and will only be used internally
  43. * by a state database. That means, for example, that an app could have
  44. * multiple, mutually exclusive state databases.
  45. */
  46. readonly namespace: string;
  47. /**
  48. * Retrieve a saved bundle from the database.
  49. *
  50. * @param id - The identifier used to retrieve a data bundle.
  51. *
  52. * @returns A promise that bears a data payload if available.
  53. *
  54. * #### Notes
  55. * The `id` values of stored items in the state database are formatted:
  56. * `'namespace:identifier'`, which is the same convention that command
  57. * identifiers in JupyterLab use as well. While this is not a technical
  58. * requirement for `fetch()`, `remove()`, and `save()`, it *is* necessary for
  59. * using the `fetchNamespace()` method.
  60. *
  61. * The promise returned by this method may be rejected if an error occurs in
  62. * retrieving the data. Non-existence of an `id` will succeed, however.
  63. */
  64. fetch(id: string): Promise<JSONObject | null>;
  65. /**
  66. * Retrieve all the saved bundles for a namespace.
  67. *
  68. * @param namespace - The namespace to retrieve.
  69. *
  70. * @returns A promise that bears a collection data payloads for a namespace.
  71. *
  72. * #### Notes
  73. * Namespaces are entirely conventional entities. The `id` values of stored
  74. * items in the state database are formatted: `'namespace:identifier'`, which
  75. * is the same convention that command identifiers in JupyterLab use as well.
  76. *
  77. * If there are any errors in retrieving the data, they will be logged to the
  78. * console in order to optimistically return any extant data without failing.
  79. * This promise will always succeed.
  80. */
  81. fetchNamespace(namespace: string): Promise<IStateItem[]>;
  82. /**
  83. * Remove a value from the database.
  84. *
  85. * @param id - The identifier for the data being removed.
  86. *
  87. * @returns A promise that is rejected if remove fails and succeeds otherwise.
  88. */
  89. remove(id: string): Promise<void>;
  90. /**
  91. * Save a value in the database.
  92. *
  93. * @param id - The identifier for the data being saved.
  94. *
  95. * @param value - The data being saved.
  96. *
  97. * @returns A promise that is rejected if saving fails and succeeds otherwise.
  98. *
  99. * #### Notes
  100. * The `id` values of stored items in the state database are formatted:
  101. * `'namespace:identifier'`, which is the same convention that command
  102. * identifiers in JupyterLab use as well. While this is not a technical
  103. * requirement for `fetch()`, `remove()`, and `save()`, it *is* necessary for
  104. * using the `fetchNamespace()` method.
  105. */
  106. save(id: string, value: JSONObject): Promise<void>;
  107. }
  108. /**
  109. * The default concrete implementation of a state database.
  110. */
  111. export
  112. class StateDB implements IStateDB {
  113. /**
  114. * Create a new state database.
  115. *
  116. * @param options - The instantiation options for a state database.
  117. */
  118. constructor(options: StateDB.IOptions) {
  119. this.namespace = options.namespace;
  120. }
  121. /**
  122. * The maximum allowed length of the data after it has been serialized.
  123. */
  124. readonly maxLength = 2000;
  125. /**
  126. * The namespace prefix for all state database entries.
  127. *
  128. * #### Notes
  129. * This value should be set at instantiation and will only be used internally
  130. * by a state database. That means, for example, that an app could have
  131. * multiple, mutually exclusive state databases.
  132. */
  133. readonly namespace: string;
  134. /**
  135. * Clear the entire database.
  136. */
  137. clear(): Promise<void> {
  138. const prefix = `${this.namespace}:`;
  139. let i = window.localStorage.length;
  140. while (i) {
  141. let key = window.localStorage.key(--i);
  142. if (key.indexOf(prefix) === 0) {
  143. window.localStorage.removeItem(key);
  144. }
  145. }
  146. return Promise.resolve(void 0);
  147. }
  148. /**
  149. * Retrieve a saved bundle from the database.
  150. *
  151. * @param id - The identifier used to retrieve a data bundle.
  152. *
  153. * @returns A promise that bears a data payload if available.
  154. *
  155. * #### Notes
  156. * The `id` values of stored items in the state database are formatted:
  157. * `'namespace:identifier'`, which is the same convention that command
  158. * identifiers in JupyterLab use as well. While this is not a technical
  159. * requirement for `fetch()`, `remove()`, and `save()`, it *is* necessary for
  160. * using the `fetchNamespace()` method.
  161. *
  162. * The promise returned by this method may be rejected if an error occurs in
  163. * retrieving the data. Non-existence of an `id` will succeed with `null`.
  164. */
  165. fetch(id: string): Promise<JSONObject | null> {
  166. const key = `${this.namespace}:${id}`;
  167. try {
  168. return Promise.resolve(JSON.parse(window.localStorage.getItem(key)));
  169. } catch (error) {
  170. return Promise.reject(error);
  171. }
  172. }
  173. /**
  174. * Retrieve all the saved bundles for a namespace.
  175. *
  176. * @param namespace - The namespace to retrieve.
  177. *
  178. * @returns A promise that bears a collection data payloads for a namespace.
  179. *
  180. * #### Notes
  181. * Namespaces are entirely conventional entities. The `id` values of stored
  182. * items in the state database are formatted: `'namespace:identifier'`, which
  183. * is the same convention that command identifiers in JupyterLab use as well.
  184. *
  185. * If there are any errors in retrieving the data, they will be logged to the
  186. * console in order to optimistically return any extant data without failing.
  187. * This promise will always succeed.
  188. */
  189. fetchNamespace(namespace: string): Promise<IStateItem[]> {
  190. const prefix = `${this.namespace}:${namespace}:`;
  191. const regex = new RegExp(`^${this.namespace}\:`);
  192. let items: IStateItem[] = [];
  193. let i = window.localStorage.length;
  194. while (i) {
  195. let key = window.localStorage.key(--i);
  196. if (key.indexOf(prefix) === 0) {
  197. try {
  198. items.push({
  199. id: key.replace(regex, ''),
  200. value: JSON.parse(window.localStorage.getItem(key))
  201. });
  202. } catch (error) {
  203. console.warn(error);
  204. window.localStorage.removeItem(key);
  205. }
  206. }
  207. }
  208. return Promise.resolve(items);
  209. }
  210. /**
  211. * Remove a value from the database.
  212. *
  213. * @param id - The identifier for the data being removed.
  214. *
  215. * @returns A promise that is rejected if remove fails and succeeds otherwise.
  216. */
  217. remove(id: string): Promise<void> {
  218. window.localStorage.removeItem(`${this.namespace}:${id}`);
  219. return Promise.resolve(void 0);
  220. }
  221. /**
  222. * Save a value in the database.
  223. *
  224. * @param id - The identifier for the data being saved.
  225. *
  226. * @param value - The data being saved.
  227. *
  228. * @returns A promise that is rejected if saving fails and succeeds otherwise.
  229. *
  230. * #### Notes
  231. * The `id` values of stored items in the state database are formatted:
  232. * `'namespace:identifier'`, which is the same convention that command
  233. * identifiers in JupyterLab use as well. While this is not a technical
  234. * requirement for `fetch()`, `remove()`, and `save()`, it *is* necessary for
  235. * using the `fetchNamespace()` method.
  236. */
  237. save(id: string, value: JSONObject): Promise<void> {
  238. try {
  239. const key = `${this.namespace}:${id}`;
  240. const serialized = JSON.stringify(value);
  241. const length = serialized.length;
  242. const max = this.maxLength;
  243. if (length > max) {
  244. throw new Error(`Data length (${length}) exceeds maximum (${max})`);
  245. }
  246. window.localStorage.setItem(key, serialized);
  247. return Promise.resolve(void 0);
  248. } catch (error) {
  249. return Promise.reject(error);
  250. }
  251. }
  252. }
  253. /**
  254. * A namespace for StateDB statics.
  255. */
  256. export
  257. namespace StateDB {
  258. /**
  259. * The instantiation options for a state database.
  260. */
  261. export
  262. interface IOptions {
  263. /**
  264. * The namespace prefix for all state database entries.
  265. */
  266. namespace: string;
  267. }
  268. }