modeldb.ts 14 KB

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