modeldb.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. IDisposable, DisposableSet
  5. } from '@phosphor/disposable';
  6. import {
  7. ISignal, Signal
  8. } from '@phosphor/signaling';
  9. import {
  10. JSONValue, JSONObject
  11. } from '@phosphor/coreutils';
  12. import {
  13. ObservableMap
  14. } from './observablemap';
  15. import {
  16. IObservableJSON, ObservableJSON
  17. } from './observablejson';
  18. import {
  19. IObservableString, ObservableString
  20. } from './observablestring';
  21. import {
  22. IObservableUndoableList, ObservableUndoableList
  23. } from './undoablelist';
  24. import {
  25. IObservableMap
  26. } from './observablemap';
  27. /**
  28. * String type annotations for Observable objects that can be
  29. * created and placed in the IModelDB interface.
  30. */
  31. export
  32. type ObservableType = 'Map' | 'List' | 'String' | 'Value';
  33. /**
  34. * Base interface for Observable objects.
  35. */
  36. export
  37. interface IObservable extends IDisposable {
  38. /**
  39. * The type of this object.
  40. */
  41. readonly type: ObservableType;
  42. }
  43. /**
  44. * Interface for an Observable object that represents
  45. * an opaque JSON value.
  46. */
  47. export
  48. interface IObservableValue extends IObservable {
  49. /**
  50. * The type of this object.
  51. */
  52. readonly type: 'Value';
  53. /**
  54. * The changed signal.
  55. */
  56. readonly changed: ISignal<IObservableValue, ObservableValue.IChangedArgs>;
  57. /**
  58. * Get the current value, or `undefined` if it has not been set.
  59. */
  60. get(): JSONValue | undefined;
  61. /**
  62. * Set the value.
  63. */
  64. set(value: JSONValue): void;
  65. }
  66. /**
  67. * Interface for an object representing a single collaborator
  68. * on a realtime model.
  69. */
  70. export
  71. interface ICollaborator extends JSONObject {
  72. /**
  73. * A user id for the collaborator.
  74. * This might not be unique, if the user has more than
  75. * one editing session at a time.
  76. */
  77. readonly userId: string;
  78. /**
  79. * A session id, which should be unique to a
  80. * particular view on a collaborative model.
  81. */
  82. readonly sessionId: string;
  83. /**
  84. * A human-readable display name for a collaborator.
  85. */
  86. readonly displayName: string;
  87. /**
  88. * A color to be used to identify the collaborator in
  89. * UI elements.
  90. */
  91. readonly color: string;
  92. /**
  93. * A human-readable short name for a collaborator, for
  94. * use in places where the full `displayName` would take
  95. * too much space.
  96. */
  97. readonly shortName: string;
  98. }
  99. /**
  100. * Interface for an IObservableMap that tracks collaborators.
  101. */
  102. export
  103. interface ICollaboratorMap extends IObservableMap<ICollaborator> {
  104. /**
  105. * The local collaborator on a model.
  106. */
  107. readonly localCollaborator: ICollaborator;
  108. }
  109. /**
  110. * An interface for a path based database for
  111. * creating and storing values, which is agnostic
  112. * to the particular type of store in the backend.
  113. */
  114. export
  115. interface IModelDB extends IDisposable {
  116. /**
  117. * The base path for the `IModelDB`. This is prepended
  118. * to all the paths that are passed in to the member
  119. * functions of the object.
  120. */
  121. readonly basePath: string;
  122. /**
  123. * Whether the database has been disposed.
  124. */
  125. readonly isDisposed: boolean;
  126. /**
  127. * Whether the database has been populated
  128. * with model values prior to connection.
  129. */
  130. readonly isPrepopulated: boolean;
  131. /**
  132. * Whether the database is collaborative.
  133. */
  134. readonly isCollaborative: boolean;
  135. /**
  136. * A promise that resolves when the database
  137. * has connected to its backend, if any.
  138. */
  139. readonly connected: Promise<void>;
  140. /**
  141. * A map of the currently active collaborators
  142. * for the database, including the local user.
  143. */
  144. readonly collaborators?: ICollaboratorMap;
  145. /**
  146. * Get a value for a path.
  147. *
  148. * @param path: the path for the object.
  149. *
  150. * @returns an `IObservable`.
  151. */
  152. get(path: string): IObservable | undefined;
  153. /**
  154. * Whether the `IModelDB` has an object at this path.
  155. *
  156. * @param path: the path for the object.
  157. *
  158. * @returns a boolean for whether an object is at `path`.
  159. */
  160. has(path: string): boolean;
  161. /**
  162. * Create a string and insert it in the database.
  163. *
  164. * @param path: the path for the string.
  165. *
  166. * @returns the string that was created.
  167. */
  168. createString(path: string): IObservableString;
  169. /**
  170. * Create an undoable list and insert it in the database.
  171. *
  172. * @param path: the path for the list.
  173. *
  174. * @returns the list that was created.
  175. *
  176. * #### Notes
  177. * The list can only store objects that are simple
  178. * JSON Objects and primitives.
  179. */
  180. createList<T extends JSONValue>(path: string): IObservableUndoableList<T>;
  181. /**
  182. * Create a map and insert it in the database.
  183. *
  184. * @param path: the path for the map.
  185. *
  186. * @returns the map that was created.
  187. *
  188. * #### Notes
  189. * The map can only store objects that are simple
  190. * JSON Objects and primitives.
  191. */
  192. createMap(path: string): IObservableJSON;
  193. /**
  194. * Create an opaque value and insert it in the database.
  195. *
  196. * @param path: the path for the value.
  197. *
  198. * @returns the value that was created.
  199. */
  200. createValue(path: string): IObservableValue;
  201. /**
  202. * Get a value at a path, or `undefined if it has not been set
  203. * That value must already have been created using `createValue`.
  204. *
  205. * @param path: the path for the value.
  206. */
  207. getValue(path: string): JSONValue | undefined;
  208. /**
  209. * Set a value at a path. That value must already have
  210. * been created using `createValue`.
  211. *
  212. * @param path: the path for the value.
  213. *
  214. * @param value: the new value.
  215. */
  216. setValue(path: string, value: JSONValue): void;
  217. /**
  218. * Create a view onto a subtree of the model database.
  219. *
  220. * @param basePath: the path for the root of the subtree.
  221. *
  222. * @returns an `IModelDB` with a view onto the original
  223. * `IModelDB`, with `basePath` prepended to all paths.
  224. */
  225. view(basePath: string): IModelDB;
  226. /**
  227. * Dispose of the resources held by the database.
  228. */
  229. dispose(): void;
  230. }
  231. /**
  232. * A concrete implementation of an `IObservableValue`.
  233. */
  234. export
  235. class ObservableValue implements IObservableValue {
  236. /**
  237. * Constructor for the value.
  238. *
  239. * @param initialValue: the starting value for the `ObservableValue`.
  240. */
  241. constructor(initialValue: JSONValue = null) {
  242. this._value = initialValue;
  243. }
  244. /**
  245. * The observable type.
  246. */
  247. get type(): 'Value' {
  248. return 'Value';
  249. }
  250. /**
  251. * Whether the value has been disposed.
  252. */
  253. get isDisposed(): boolean {
  254. return this._isDisposed;
  255. }
  256. /**
  257. * The changed signal.
  258. */
  259. get changed(): ISignal<this, ObservableValue.IChangedArgs> {
  260. return this._changed;
  261. }
  262. /**
  263. * Get the current value, or `undefined` if it has not been set.
  264. */
  265. get(): JSONValue {
  266. return this._value;
  267. }
  268. /**
  269. * Set the current value.
  270. */
  271. set(value: JSONValue): void {
  272. let oldValue = this._value;
  273. this._value = value;
  274. this._changed.emit({
  275. oldValue: oldValue,
  276. newValue: value
  277. });
  278. }
  279. /**
  280. * Dispose of the resources held by the value.
  281. */
  282. dispose(): void {
  283. if (this._isDisposed) {
  284. return;
  285. }
  286. this._isDisposed = true;
  287. Signal.clearData(this);
  288. this._value = null;
  289. }
  290. private _value: JSONValue = null;
  291. private _changed = new Signal<this, ObservableValue.IChangedArgs>(this);
  292. private _isDisposed = false;
  293. }
  294. /**
  295. * The namespace for the `ObservableValue` class statics.
  296. */
  297. export
  298. namespace ObservableValue {
  299. /**
  300. * The changed args object emitted by the `IObservableValue`.
  301. */
  302. export
  303. class IChangedArgs {
  304. /**
  305. * The old value.
  306. */
  307. oldValue: JSONValue | undefined;
  308. /**
  309. * The new value.
  310. */
  311. newValue: JSONValue | undefined;
  312. }
  313. }
  314. /**
  315. * A concrete implementation of an `IModelDB`.
  316. */
  317. export
  318. class ModelDB implements IModelDB {
  319. /**
  320. * Constructor for the `ModelDB`.
  321. */
  322. constructor(options: ModelDB.ICreateOptions = {}) {
  323. this._basePath = options.basePath || '';
  324. if (options.baseDB) {
  325. this._db = options.baseDB;
  326. } else {
  327. this._db = new ObservableMap<IObservable>();
  328. this._toDispose = true;
  329. }
  330. }
  331. /**
  332. * The base path for the `ModelDB`. This is prepended
  333. * to all the paths that are passed in to the member
  334. * functions of the object.
  335. */
  336. get basePath(): string {
  337. return this._basePath;
  338. }
  339. /**
  340. * Whether the database is disposed.
  341. */
  342. get isDisposed(): boolean {
  343. return this._isDisposed;
  344. }
  345. /**
  346. * Whether the model has been populated with
  347. * any model values.
  348. */
  349. readonly isPrepopulated: boolean = false;
  350. /**
  351. * Whether the model is collaborative.
  352. */
  353. readonly isCollaborative: boolean = false;
  354. /**
  355. * A promise resolved when the model is connected
  356. * to its backend. For the in-memory ModelDB it
  357. * is immediately resolved.
  358. */
  359. readonly connected: Promise<void> = Promise.resolve(void 0);
  360. /**
  361. * Get a value for a path.
  362. *
  363. * @param path: the path for the object.
  364. *
  365. * @returns an `IObservable`.
  366. */
  367. get(path: string): IObservable | undefined {
  368. return this._db.get(this._resolvePath(path));
  369. }
  370. /**
  371. * Whether the `IModelDB` has an object at this path.
  372. *
  373. * @param path: the path for the object.
  374. *
  375. * @returns a boolean for whether an object is at `path`.
  376. */
  377. has(path: string): boolean {
  378. return this._db.has(this._resolvePath(path));
  379. }
  380. /**
  381. * Create a string and insert it in the database.
  382. *
  383. * @param path: the path for the string.
  384. *
  385. * @returns the string that was created.
  386. */
  387. createString(path: string): IObservableString {
  388. let str = new ObservableString();
  389. this._disposables.add(str);
  390. this.set(path, str);
  391. return str;
  392. }
  393. /**
  394. * Create an undoable list and insert it in the database.
  395. *
  396. * @param path: the path for the list.
  397. *
  398. * @returns the list that was created.
  399. *
  400. * #### Notes
  401. * The list can only store objects that are simple
  402. * JSON Objects and primitives.
  403. */
  404. createList<T extends JSONValue>(path: string): IObservableUndoableList<T> {
  405. let vec = new ObservableUndoableList<T>(
  406. new ObservableUndoableList.IdentitySerializer<T>());
  407. this._disposables.add(vec);
  408. this.set(path, vec);
  409. return vec;
  410. }
  411. /**
  412. * Create a map and insert it in the database.
  413. *
  414. * @param path: the path for the map.
  415. *
  416. * @returns the map that was created.
  417. *
  418. * #### Notes
  419. * The map can only store objects that are simple
  420. * JSON Objects and primitives.
  421. */
  422. createMap(path: string): IObservableJSON {
  423. let map = new ObservableJSON();
  424. this._disposables.add(map);
  425. this.set(path, map);
  426. return map;
  427. }
  428. /**
  429. * Create an opaque value and insert it in the database.
  430. *
  431. * @param path: the path for the value.
  432. *
  433. * @returns the value that was created.
  434. */
  435. createValue(path: string): IObservableValue {
  436. let val = new ObservableValue();
  437. this._disposables.add(val);
  438. this.set(path, val);
  439. return val;
  440. }
  441. /**
  442. * Get a value at a path, or `undefined if it has not been set
  443. * That value must already have been created using `createValue`.
  444. *
  445. * @param path: the path for the value.
  446. */
  447. getValue(path: string): JSONValue | undefined {
  448. let val = this.get(path);
  449. if (!val || val.type !== 'Value') {
  450. throw Error('Can only call getValue for an ObservableValue');
  451. }
  452. return (val as ObservableValue).get();
  453. }
  454. /**
  455. * Set a value at a path. That value must already have
  456. * been created using `createValue`.
  457. *
  458. * @param path: the path for the value.
  459. *
  460. * @param value: the new value.
  461. */
  462. setValue(path: string, value: JSONValue): void {
  463. let val = this.get(path);
  464. if (!val || val.type !== 'Value') {
  465. throw Error('Can only call setValue on an ObservableValue');
  466. }
  467. (val as ObservableValue).set(value);
  468. }
  469. /**
  470. * Create a view onto a subtree of the model database.
  471. *
  472. * @param basePath: the path for the root of the subtree.
  473. *
  474. * @returns an `IModelDB` with a view onto the original
  475. * `IModelDB`, with `basePath` prepended to all paths.
  476. */
  477. view(basePath: string): ModelDB {
  478. let view = new ModelDB({basePath, baseDB: this});
  479. this._disposables.add(view);
  480. return view;
  481. }
  482. /**
  483. * Set a value at a path. Not intended to
  484. * be called by user code, instead use the
  485. * `create*` factory methods.
  486. *
  487. * @param path: the path to set the value at.
  488. *
  489. * @param value: the value to set at the path.
  490. */
  491. set(path: string, value: IObservable): void {
  492. this._db.set(this._resolvePath(path), value);
  493. }
  494. /**
  495. * Dispose of the resources held by the database.
  496. */
  497. dispose(): void {
  498. if (this.isDisposed) {
  499. return;
  500. }
  501. this._isDisposed = true;
  502. if (this._toDispose) {
  503. this._db.dispose();
  504. }
  505. this._disposables.dispose();
  506. }
  507. /**
  508. * Compute the fully resolved path for a path argument.
  509. */
  510. private _resolvePath(path: string): string {
  511. if (this._basePath) {
  512. path = this._basePath + '.' + path;
  513. }
  514. return path;
  515. }
  516. private _basePath: string;
  517. private _db: ModelDB | ObservableMap<IObservable>;
  518. private _toDispose = false;
  519. private _isDisposed = false;
  520. private _disposables = new DisposableSet();
  521. }
  522. /**
  523. * A namespace for the `ModelDB` class statics.
  524. */
  525. export
  526. namespace ModelDB {
  527. /**
  528. * Options for creating a `ModelDB` object.
  529. */
  530. export
  531. interface ICreateOptions {
  532. /**
  533. * The base path to prepend to all the path arguments.
  534. */
  535. basePath?: string;
  536. /**
  537. * A ModelDB to use as the store for this
  538. * ModelDB. If none is given, it uses its own store.
  539. */
  540. baseDB?: ModelDB;
  541. }
  542. /**
  543. * A factory interface for creating `IModelDB` objects.
  544. */
  545. export
  546. interface IFactory {
  547. /**
  548. * Create a new `IModelDB` instance.
  549. */
  550. createNew(path: string): IModelDB;
  551. }
  552. }