Procházet zdrojové kódy

Partial merge of ModelDB proposal.

Ian Rose před 8 roky
rodič
revize
6ea67c572f

+ 61 - 42
packages/cells/src/model.ts

@@ -18,7 +18,7 @@ import {
 } from '@jupyterlab/coreutils';
 
 import {
-  IObservableJSON, ObservableJSON
+  IObservableJSON, IModelDB, IObservableValue
 } from '@jupyterlab/coreutils';
 
 import {
@@ -121,14 +121,32 @@ class CellModel extends CodeEditor.Model implements ICellModel {
    * Construct a cell model from optional cell content.
    */
   constructor(options: CellModel.IOptions) {
-    super();
+    super({modelDB: options.modelDB});
+
     this.value.changed.connect(this.onGenericChange, this);
+
+    let cellType = this.modelDB.createValue('type');
+    cellType.set(this.type);
+
+    let observableMetadata = this.modelDB.createJSON('metadata');
+    observableMetadata.changed.connect(this.onGenericChange, this);
+
     let cell = options.cell;
+    let trusted = this.modelDB.createValue('trusted');
     if (!cell) {
+      trusted.set(false);
       return;
     }
-    this._trusted = !!cell.metadata['trusted'];
+    trusted.set(!!cell.metadata['trusted']);
     delete cell.metadata['trusted'];
+    trusted.changed.connect((value, args) => {
+      this.onTrustedChanged(args.newValue as boolean);
+      this.stateChanged.emit({
+        name: 'trusted',
+        oldValue: args.oldValue,
+        newValue: args.newValue
+      });
+    });
 
     if (Array.isArray(cell.source)) {
       this.value.text = (cell.source as string[]).join('\n');
@@ -143,10 +161,10 @@ class CellModel extends CodeEditor.Model implements ICellModel {
       delete metadata['collapsed'];
       delete metadata['scrolled'];
     }
+
     for (let key in metadata) {
-      this._metadata.set(key, metadata[key]);
+      observableMetadata.set(key, metadata[key]);
     }
-    this._metadata.changed.connect(this.onGenericChange, this);
   }
 
   /**
@@ -168,35 +186,25 @@ class CellModel extends CodeEditor.Model implements ICellModel {
    * The metadata associated with the cell.
    */
   get metadata(): IObservableJSON {
-    return this._metadata;
+    return this.modelDB.get('metadata') as IObservableJSON;
   }
 
   /**
    * Get the trusted state of the model.
    */
   get trusted(): boolean {
-    return this._trusted;
+    return (this.modelDB.get('trusted') as IObservableValue).get() as boolean;
   }
 
   /**
    * Set the trusted state of the model.
    */
   set trusted(newValue: boolean) {
-    if (this._trusted === newValue) {
+    let oldValue = this.trusted;
+    if (oldValue === newValue) {
       return;
     }
-    let oldValue = this._trusted;
-    this._trusted = newValue;
-    this.onTrustedChanged(newValue);
-    this.stateChanged.emit({ name: 'trusted', oldValue, newValue });
-  }
-
-  /**
-   * Dispose of the resources held by the model.
-   */
-  dispose(): void {
-    this._metadata.dispose();
-    super.dispose();
+    (this.modelDB.get('trusted') as IObservableValue).set(newValue);
   }
 
   /**
@@ -231,9 +239,6 @@ class CellModel extends CodeEditor.Model implements ICellModel {
   protected onGenericChange(): void {
     this.contentChanged.emit(void 0);
   }
-
-  private _metadata = new ObservableJSON();
-  private _trusted = false;
 }
 
 
@@ -250,6 +255,16 @@ namespace CellModel {
      * The source cell data.
      */
     cell?: nbformat.IBaseCell;
+
+    /**
+     * An IModelDB in which to store cell data.
+     */
+    modelDB?: IModelDB;
+
+    /**
+     * A unique identifier for this cell.
+     */
+    uuid?: string;
   }
 }
 
@@ -307,13 +322,27 @@ class CodeCellModel extends CellModel implements ICodeCellModel {
     let trusted = this.trusted;
     let cell = options.cell as nbformat.ICodeCell;
     let outputs: nbformat.IOutput[] = [];
-    if (cell && cell.cell_type === 'code') {
-      this.executionCount = cell.execution_count;
-      outputs = cell.outputs;
+    let executionCount = this.modelDB.createValue('executionCount');
+    if (!executionCount.get()) {
+      if (cell && cell.cell_type === 'code') {
+        executionCount.set(cell.execution_count || null);
+        outputs = cell.outputs;
+      } else {
+        executionCount.set(null);
+      }
     }
+    executionCount.changed.connect((value, args) => {
+      this.contentChanged.emit(void 0);
+      this.stateChanged.emit({
+        name: 'executionCount',
+        oldValue: args.oldValue,
+        newValue: args.newValue });
+    });
+
     this._outputs = factory.createOutputArea({
       trusted,
-      values: outputs
+      values: outputs,
+      modelDB: this.modelDB
     });
     this._outputs.stateChanged.connect(this.onGenericChange, this);
   }
@@ -329,16 +358,14 @@ class CodeCellModel extends CellModel implements ICodeCellModel {
    * The execution count of the cell.
    */
   get executionCount(): nbformat.ExecutionCount {
-    return this._executionCount || null;
+    return (this.modelDB.get('executionCount') as IObservableValue).get() as number || null;
   }
   set executionCount(newValue: nbformat.ExecutionCount) {
-    if (newValue === this._executionCount) {
+    let oldValue = this.executionCount;
+    if (newValue === oldValue) {
       return;
     }
-    let oldValue = this.executionCount;
-    this._executionCount = newValue || null;
-    this.contentChanged.emit(void 0);
-    this.stateChanged.emit({ name: 'executionCount', oldValue, newValue });
+    (this.modelDB.get('executionCount') as IObservableValue).set(newValue || null);
   }
 
   /**
@@ -372,15 +399,12 @@ class CodeCellModel extends CellModel implements ICodeCellModel {
 
   /**
    * Handle a change to the trusted state.
-   *
-   * The default implementation is a no-op.
    */
   onTrustedChanged(value: boolean): void {
     this._outputs.trusted = value;
   }
 
   private _outputs: IOutputAreaModel = null;
-  private _executionCount: nbformat.ExecutionCount = null;
 }
 
 
@@ -392,12 +416,7 @@ namespace CodeCellModel {
   /**
    * The options used to initialize a `CodeCellModel`.
    */
-  export interface IOptions {
-    /**
-     * The source cell data.
-     */
-    cell?: nbformat.IBaseCell;
-
+  export interface IOptions extends CellModel.IOptions {
     /**
      * The factory for output area model creation.
      */

+ 37 - 29
packages/codeeditor/src/editor.ts

@@ -14,15 +14,8 @@ import {
 } from '@phosphor/signaling';
 
 import {
-  IChangedArgs
-} from '@jupyterlab/coreutils';
-
-import {
-  IObservableString, ObservableString
-} from '@jupyterlab/coreutils';
-
-import {
-  IObservableMap, ObservableMap
+  IModelDB, ModelDB, IObservableValue,
+  IObservableMap, IObservableString, IChangedArgs
 } from '@jupyterlab/coreutils';
 
 
@@ -40,7 +33,7 @@ namespace CodeEditor {
    * A zero-based position in the editor.
    */
   export
-  interface IPosition {
+  interface IPosition extends JSONObject {
     /**
      * The cursor line number.
      */
@@ -78,7 +71,7 @@ namespace CodeEditor {
    * A range.
    */
   export
-  interface IRange {
+  interface IRange extends JSONObject {
     /**
      * The position of the first character in the current range.
      *
@@ -102,7 +95,7 @@ namespace CodeEditor {
    * A selection style.
    */
   export
-  interface ISelectionStyle {
+  interface ISelectionStyle extends JSONObject {
     /**
      * A class name added to a selection.
      */
@@ -186,8 +179,26 @@ namespace CodeEditor {
      */
     constructor(options?: Model.IOptions) {
       options = options || {};
-      this._value = new ObservableString(options.value);
-      this._mimetype = options.mimeType || 'text/plain';
+
+      if(options.modelDB) {
+        this.modelDB = options.modelDB;
+      } else {
+        this.modelDB = new ModelDB();
+      }
+
+      let value = this.modelDB.createString('value');
+      value.text = value.text || options.value || '';
+      let mimeType = this.modelDB.createValue('mimeType');
+      mimeType.set(options.mimeType || 'text/plain');
+      this.modelDB.createMap<ITextSelection[]>('selections');
+
+      mimeType.changed.connect((val, args)=>{
+        this._mimeTypeChanged.emit({
+          name: 'mimeType',
+          oldValue: args.oldValue as string,
+          newValue: args.newValue as string
+        });
+      });
     }
 
     /**
@@ -201,33 +212,28 @@ namespace CodeEditor {
      * Get the value of the model.
      */
     get value(): IObservableString {
-      return this._value;
+      return this.modelDB.get('value') as IObservableString;
     }
 
     /**
      * Get the selections for the model.
      */
     get selections(): IObservableMap<ITextSelection[]> {
-      return this._selections;
+      return this.modelDB.get('selections') as IObservableMap<ITextSelection[]>;
     }
 
     /**
      * A mime type of the model.
      */
     get mimeType(): string {
-      return this._mimetype;
+      return (this.modelDB.get('mimeType') as IObservableValue).get() as string;
     }
     set mimeType(newValue: string) {
-      const oldValue = this._mimetype;
+      const oldValue = this.mimeType;
       if (oldValue === newValue) {
         return;
       }
-      this._mimetype = newValue;
-      this._mimeTypeChanged.emit({
-        name: 'mimeType',
-        oldValue,
-        newValue
-      });
+      (this.modelDB.get('mimeType') as IObservableValue).set(newValue);
     }
 
     /**
@@ -246,13 +252,10 @@ namespace CodeEditor {
       }
       this._isDisposed = true;
       Signal.clearData(this);
-      this._selections.dispose();
-      this._value.dispose();
     }
 
-    private _value: ObservableString;
-    private _selections = new ObservableMap<ITextSelection[]>();
-    private _mimetype: string;
+
+    protected modelDB: IModelDB = null;
     private _isDisposed = false;
     private _mimeTypeChanged = new Signal<this, IChangedArgs<string>>(this);
   }
@@ -567,6 +570,11 @@ namespace CodeEditor {
        * The mimetype of the model.
        */
       mimeType?: string;
+
+      /**
+       * An optional modelDB for storing model state.
+       */
+      modelDB?: IModelDB;
     }
   }
 }

+ 1 - 0
packages/coreutils/src/index.ts

@@ -14,3 +14,4 @@ export * from './undoablevector';
 export * from './url';
 export * from './uuid';
 export * from './vector';
+export * from './modeldb';

+ 557 - 0
packages/coreutils/src/modeldb.ts

@@ -0,0 +1,557 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  IDisposable, DisposableSet
+} from '@phosphor/disposable';
+
+import {
+  ISignal, Signal
+} from '@phosphor/signaling';
+
+import {
+  JSONValue
+} from '@phosphor/coreutils';
+
+import {
+  PathExt
+} from './path';
+
+import {
+  IObservableMap, ObservableMap
+} from './observablemap';
+
+import {
+  IObservableJSON, ObservableJSON
+} from './observablejson';
+
+import {
+  IObservableString, ObservableString
+} from './observablestring';
+
+import {
+  IObservableVector, ObservableVector
+} from './observablevector';
+
+import {
+  IObservableUndoableVector, ObservableUndoableVector
+} from './undoablevector';
+
+
+/**
+ * String type annotations for Observable objects that can be
+ * created and placed in the IModelDB interface.
+ */
+export
+type ObservableType = 'Map' | 'Vector' | 'String' | 'Value';
+
+/**
+ * Base interface for Observable objects.
+ */
+export
+interface IObservable extends IDisposable {
+  /**
+   * The type of this object.
+   */
+  readonly type: ObservableType;
+}
+
+/**
+ * Interface for an Observable object that represents
+ * an opaque JSON value.
+ */
+export
+interface IObservableValue extends IObservable {
+  /**
+   * The type of this object.
+   */
+  type: 'Value';
+
+  /**
+   * The changed signal.
+   */
+  readonly changed: ISignal<IObservableValue, ObservableValue.IChangedArgs>;
+
+  /**
+   * Get the current value.
+   */
+  get(): JSONValue;
+
+  /**
+   * Set the value.
+   */
+  set(value: JSONValue): void;
+}
+
+/**
+ * An interface for a path based database for
+ * creating and storing values, which is agnostic
+ * to the particular type of store in the backend.
+ */
+export
+interface IModelDB extends IDisposable {
+  /**
+   * The base path for the `IModelDB`. This is prepended
+   * to all the paths that are passed in to the member
+   * functions of the object.
+   */
+  readonly basePath: string;
+
+  /**
+   * Whether the database has been disposed.
+   */
+  readonly isDisposed: boolean;
+
+  /**
+   * Whether the database has been populated
+   * with model values prior to connection.
+   */
+  readonly isPrepopulated: boolean;
+
+  /**
+   * A promise that resolves when the database
+   * has connected to its backend, if any.
+   */
+  readonly connected: Promise<void>;
+
+  /**
+   * Get a value for a path.
+   *
+   * @param path: the path for the object.
+   *
+   * @returns an `IObservable`.
+   */
+  get(path: string): IObservable;
+
+  /**
+   * Whether the `IModelDB` has an object at this path.
+   *
+   * @param path: the path for the object.
+   *
+   * @returns a boolean for whether an object is at `path`.
+   */
+  has(path: string): boolean;
+
+  /**
+   * Create a string and insert it in the database.
+   *
+   * @param path: the path for the string.
+   *
+   * @returns the string that was created.
+   */
+  createString(path: string): IObservableString;
+
+  /**
+   * Create a vector and insert it in the database.
+   *
+   * @param path: the path for the vector.
+   *
+   * @returns the vector that was created.
+   *
+   * #### Notes
+   * The vector can only store objects that are simple
+   * JSON Objects and primitives.
+   */
+  createVector<T extends JSONValue>(path: string): IObservableVector<T>;
+
+  /**
+   * Create an undoable vector and insert it in the database.
+   *
+   * @param path: the path for the vector.
+   *
+   * @returns the vector that was created.
+   *
+   * #### Notes
+   * The vector can only store objects that are simple
+   * JSON Objects and primitives.
+   */
+  createUndoableVector<T extends JSONValue>(path: string): IObservableUndoableVector<T>;
+
+  /**
+   * Create a map and insert it in the database.
+   *
+   * @param path: the path for the map.
+   *
+   * @returns the map that was created.
+   *
+   * #### Notes
+   * The map can only store objects that are simple
+   * JSON Objects and primitives.
+   */
+  createMap<T extends JSONValue>(path: string): IObservableMap<T>;
+
+  /**
+   * Create an `IObservableJSON` and insert it in the database.
+   *
+   * @param path: the path for the object.
+   *
+   * @returns the object that wsa created.
+   */
+  createJSON(path: string): IObservableJSON;
+
+  /**
+   * Create an opaque value and insert it in the database.
+   *
+   * @param path: the path for the value.
+   *
+   * @returns the value that was created.
+   */
+  createValue(path: string): IObservableValue;
+
+  /**
+   * Create a view onto a subtree of the model database.
+   *
+   * @param basePath: the path for the root of the subtree.
+   *
+   * @returns an `IModelDB` with a view onto the original
+   *   `IModelDB`, with `basePath` prepended to all paths.
+   */
+  view(basePath: string): IModelDB;
+
+  /**
+   * Dispose of the resources held by the database.
+   */
+  dispose(): void;
+}
+
+/**
+ * A concrete implementation of an `IObservableValue`.
+ */
+export
+class ObservableValue implements IObservableValue {
+  /**
+   * Constructor for the value.
+   *
+   * @param initialValue: the starting value for the `ObservableValue`.
+   */
+  constructor(initialValue?: JSONValue) {
+    this._value = initialValue;
+  }
+
+  /**
+   * The observable type.
+   */
+  readonly type: 'Value';
+
+  /**
+   * Whether the value has been disposed.
+   */
+  get isDisposed(): boolean {
+    return this._isDisposed;
+  }
+
+  /**
+   * The changed signal.
+   */
+  get changed(): ISignal<this, ObservableValue.IChangedArgs> {
+    return this._changed;
+  }
+
+  /**
+   * Get the current value.
+   */
+  get(): JSONValue {
+    return this._value;
+  }
+
+  /**
+   * Set the current value.
+   */
+  set(value: JSONValue): void {
+    let oldValue = this._value;
+    this._value = value;
+    this._changed.emit({
+      oldValue: oldValue,
+      newValue: value
+    });
+  }
+
+  /**
+   * Dispose of the resources held by the value.
+   */
+  dispose(): void {
+    if (this._isDisposed) {
+      return;
+    }
+    this._isDisposed = true;
+    Signal.clearData(this);
+    this._value = null;
+  }
+
+  private _value: JSONValue = null;
+  private _changed = new Signal<ObservableValue, ObservableValue.IChangedArgs>(this);
+  private _isDisposed = false;
+}
+
+/**
+ * The namespace for the `ObservableValue` class statics.
+ */
+export
+namespace ObservableValue {
+  /**
+   * The changed args object emitted by the `IObservableValue`.
+   */
+  export
+  class IChangedArgs {
+    /**
+     * The old value.
+     */
+    oldValue: JSONValue;
+
+    /**
+     * The new value.
+     */
+    newValue: JSONValue;
+  }
+}
+
+
+/**
+ * A concrete implementation of an `IModelDB`.
+ */
+export
+class ModelDB implements IModelDB {
+  /**
+   * Constructor for the `ModelDB`.
+   */
+  constructor(options: ModelDB.ICreateOptions = {}) {
+    this._basePath = options.basePath || '';
+    if(options.baseDB) {
+      this._db = options.baseDB;
+    } else {
+      this._db = new ObservableMap<IObservable>();
+      this._toDispose = true;
+    }
+  }
+
+  /**
+   * The base path for the `ModelDB`. This is prepended
+   * to all the paths that are passed in to the member
+   * functions of the object.
+   */
+  get basePath(): string {
+    return this._basePath;
+  }
+
+  /**
+   * Whether the database is disposed.
+   */
+  get isDisposed(): boolean {
+    return this._db === null;
+  }
+
+  /**
+   * Whether the model has been populated with
+   * any model values.
+   */
+  get isPrepopulated(): boolean {
+    return true;
+  }
+
+  /**
+   * A promise resolved when the model is connected
+   * to its backend. For the in-memory ModelDB it
+   * is immediately resolved.
+   */
+  get connected(): Promise<void> {
+    return Promise.resolve(void 0);
+  }
+
+  /**
+   * Get a value for a path.
+   *
+   * @param path: the path for the object.
+   *
+   * @returns an `IObservable`.
+   */
+  get(path: string): IObservable {
+    return this._db.get(this._resolvePath(path));
+  }
+
+  /**
+   * Whether the `IModelDB` has an object at this path.
+   *
+   * @param path: the path for the object.
+   *
+   * @returns a boolean for whether an object is at `path`.
+   */
+  has(path: string): boolean {
+    return this._db.has(this._resolvePath(path));
+  }
+
+  /**
+   * Create a string and insert it in the database.
+   *
+   * @param path: the path for the string.
+   *
+   * @returns the string that was created.
+   */
+  createString(path: string): IObservableString {
+    let str = new ObservableString();
+    this._disposables.add(str);
+    this.set(path, str);
+    return str;
+  }
+
+  /**
+   * Create a vector and insert it in the database.
+   *
+   * @param path: the path for the vector.
+   *
+   * @returns the vector that was created.
+   *
+   * #### Notes
+   * The vector can only store objects that are simple
+   * JSON Objects and primitives.
+   */
+  createVector(path: string): IObservableVector<JSONValue> {
+    let vec = new ObservableVector<JSONValue>();
+    this._disposables.add(vec);
+    this.set(path, vec);
+    return vec;
+  }
+
+  /**
+   * Create an undoable vector and insert it in the database.
+   *
+   * @param path: the path for the vector.
+   *
+   * @returns the vector that was created.
+   *
+   * #### Notes
+   * The vector can only store objects that are simple
+   * JSON Objects and primitives.
+   */
+  createUndoableVector(path: string): IObservableUndoableVector<JSONValue> {
+    let vec = new ObservableUndoableVector<JSONValue>(
+      new ObservableUndoableVector.IdentitySerializer());
+    this._disposables.add(vec);
+    this.set(path, vec);
+    return vec;
+  }
+
+  /**
+   * Create a map and insert it in the database.
+   *
+   * @param path: the path for the map.
+   *
+   * @returns the map that was created.
+   *
+   * #### Notes
+   * The map can only store objects that are simple
+   * JSON Objects and primitives.
+   */
+  createMap(path: string): IObservableMap<JSONValue> {
+    let map = new ObservableMap<JSONValue>();
+    this._disposables.add(map);
+    this.set(path, map);
+    return map;
+  }
+
+  /**
+   * Create an `IObservableJSON` and insert it in the database.
+   *
+   * @param path: the path for the object.
+   *
+   * @returns the object that wsa created.
+   */
+  createJSON(path: string): IObservableJSON {
+    let json = new ObservableJSON();
+    this._disposables.add(json);
+    this.set(path, json);
+    return json;
+  }
+
+  /**
+   * Create an opaque value and insert it in the database.
+   *
+   * @param path: the path for the value.
+   *
+   * @returns the value that was created.
+   */
+  createValue(path: string): IObservableValue {
+    let val = new ObservableValue();
+    this._disposables.add(val);
+    this.set(path, val);
+    return val;
+  }
+
+  /**
+   * Create a view onto a subtree of the model database.
+   *
+   * @param basePath: the path for the root of the subtree.
+   *
+   * @returns an `IModelDB` with a view onto the original
+   *   `IModelDB`, with `basePath` prepended to all paths.
+   */
+  view(basePath: string): ModelDB {
+    return new ModelDB({basePath, baseDB: this});
+  }
+
+  /**
+   * Set a value at a path. Not intended to
+   * be called by user code, instead use the
+   * `create*` factory methods.
+   *
+   * @param path: the path to set the value at.
+   *
+   * @param value: the value to set at the path.
+   */
+  set(path: string, value: IObservable): void {
+    this._db.set(this._resolvePath(path), value);
+  }
+
+  /**
+   * Dispose of the resources held by the database.
+   */
+  dispose(): void {
+    if (this.isDisposed) {
+      return;
+    }
+    let db = this._db;
+    this._db = null;
+
+    if (this._toDispose) {
+      db.dispose();
+    }
+    this._disposables.dispose();
+  }
+
+  /**
+   * Compute the fully resolved path for a path argument.
+   */
+  private _resolvePath(path: string): string {
+    if (this._basePath) {
+      path = this._basePath + '/' + path;
+    }
+    return PathExt.normalize(path)
+  }
+
+  private _basePath: string;
+  private _db: ModelDB | ObservableMap<IObservable> = null;
+  private _toDispose = true;
+  private _disposables = new DisposableSet();
+}
+
+/**
+ * A namespace for the `ModelDB` class statics.
+ */
+export
+namespace ModelDB {
+  /**
+   * Options for creating a `ModelDB` object.
+   */
+  export
+  interface ICreateOptions {
+    /**
+     * The base path to prepend to all the path arguments.
+     */
+    basePath?: string;
+
+    /**
+     * A ModelDB to use as the store for this
+     * ModelDB. If none is given, it uses its own store.
+     */
+    baseDB?: ModelDB;
+  }
+}

+ 16 - 1
packages/coreutils/src/observablemap.ts

@@ -9,12 +9,21 @@ import {
   ISignal, Signal
 } from '@phosphor/signaling';
 
+import {
+  IObservable
+} from './modeldb';
+
 
 /**
  * A map which can be observed for changes.
  */
 export
-interface IObservableMap<T> extends IDisposable {
+interface IObservableMap<T> extends IDisposable, IObservable {
+  /**
+   * The type of the Observable.
+   */
+  type: 'Map';
+
   /**
    * A signal emitted when the map has changed.
    */
@@ -108,6 +117,12 @@ class ObservableMap<T> implements IObservableMap<T> {
     }
   }
 
+  /**
+   * The type of the Observable.
+   */
+  type: 'Map';
+
+
   /**
    * A signal emitted when the map has changed.
    */

+ 15 - 1
packages/coreutils/src/observablestring.ts

@@ -9,12 +9,21 @@ import {
   ISignal, Signal
 } from '@phosphor/signaling';
 
+import {
+  IObservable
+} from './modeldb';
+
 
 /**
  * A string which can be observed for changes.
  */
 export
-interface IObservableString extends IDisposable {
+interface IObservableString extends IDisposable, IObservable {
+  /**
+   * The type of the Observable.
+   */
+  type: 'String';
+
   /**
    * A signal emitted when the string has changed.
    */
@@ -67,6 +76,11 @@ class ObservableString implements IObservableString {
     this._text = initialText;
   }
 
+  /**
+   * The type of the Observable.
+   */
+  type: 'String';
+
   /**
    * A signal emitted when the string has changed.
    */

+ 15 - 1
packages/coreutils/src/observablevector.ts

@@ -17,12 +17,21 @@ import {
   Vector
 } from './vector';
 
+import {
+  IObservable
+} from './modeldb';
+
 
 /**
  * A vector which can be observed for changes.
  */
 export
-interface IObservableVector<T> extends IDisposable {
+interface IObservableVector<T> extends IDisposable, IObservable {
+  /**
+   * The type of the Observable.
+   */
+  type: 'Vector';
+
   /**
    * A signal emitted when the vector has changed.
    */
@@ -310,6 +319,11 @@ class ObservableVector<T> extends Vector<T> implements IObservableVector<T> {
     this._itemCmp = options.itemCmp || Private.itemCmp;
   }
 
+  /**
+   * The type of the Observable.
+   */
+  type: 'Vector';
+
   /**
    * A signal emitted when the vector has changed.
    */

+ 26 - 0
packages/coreutils/src/undoablevector.ts

@@ -296,3 +296,29 @@ class ObservableUndoableVector<T> extends ObservableVector<T> implements IObserv
   private _stack: ObservableVector.IChangedArgs<JSONValue>[][] = [];
   private _serializer: ISerializer<T> = null;
 }
+
+/**
+ * Namespace for ObservableUndoableVector utilities.
+ */
+export
+namespace ObservableUndoableVector {
+  /**
+   * A default, identity serializer.
+   */
+  export
+  class IdentitySerializer implements ISerializer<JSONValue> {
+    /**
+     * Identity serialize.
+     */
+    toJSON(value: JSONValue): JSONValue {
+      return value;
+    }
+
+    /**
+     * Identity deserialize.
+     */
+    fromJSON(value: JSONValue): JSONValue {
+      return value;
+    }
+  }
+}

+ 5 - 5
packages/docregistry/src/default.ts

@@ -22,7 +22,7 @@ import {
 } from '@jupyterlab/codeeditor';
 
 import {
-  IChangedArgs
+  IChangedArgs, IModelDB
 } from '@jupyterlab/coreutils';
 
 import {
@@ -38,8 +38,8 @@ class DocumentModel extends CodeEditor.Model implements DocumentRegistry.ICodeMo
   /**
    * Construct a new document model.
    */
-  constructor(languagePreference?: string) {
-    super();
+  constructor(languagePreference?: string, modelDB?: IModelDB) {
+    super({modelDB});
     this._defaultLang = languagePreference || '';
     this.value.changed.connect(this.triggerContentChange, this);
   }
@@ -220,8 +220,8 @@ class TextModelFactory implements DocumentRegistry.CodeModelFactory {
    *
    * @returns A new document model.
    */
-  createNew(languagePreference?: string): DocumentRegistry.ICodeModel {
-    return new DocumentModel(languagePreference);
+  createNew(languagePreference?: string, modelDB?: IModelDB): DocumentRegistry.ICodeModel {
+    return new DocumentModel(languagePreference, modelDB);
   }
 
   /**

+ 2 - 3
packages/docregistry/src/registry.ts

@@ -34,10 +34,9 @@ import {
 } from '@jupyterlab/codeeditor';
 
 import {
-  IChangedArgs as IChangedArgsGeneric, PathExt
+  IChangedArgs as IChangedArgsGeneric, PathExt, IModelDB
 } from '@jupyterlab/coreutils';
 
-
 /* tslint:disable */
 /**
  * The document registry token.
@@ -873,7 +872,7 @@ namespace DocumentRegistry {
      *
      * @returns A new document model.
      */
-    createNew(languagePreference?: string): T;
+    createNew(languagePreference?: string, modelDB?: IModelDB): T;
 
     /**
      * Get the preferred kernel language given an extension.

+ 42 - 13
packages/notebook/src/celllist.ts

@@ -10,14 +10,18 @@ import {
 } from '@phosphor/signaling';
 
 import {
-  IObservableMap, ObservableMap, IObservableVector, ObservableVector,
-  IObservableUndoableVector, ObservableUndoableVector, uuid
+  IObservableMap, ObservableMap, ObservableVector,
+  IObservableUndoableVector, IModelDB
 } from '@jupyterlab/coreutils';
 
 import {
   ICellModel
 } from '@jupyterlab/cells';
 
+import {
+  NotebookModel
+} from './model';
+
 
 /**
  * A cell list object that supports undo/redo.
@@ -27,16 +31,17 @@ class CellList implements IObservableUndoableVector<ICellModel> {
   /**
    * Construct the cell list.
    */
-  constructor() {
-    this._cellOrder = new ObservableUndoableVector<string>({
-      toJSON: (val: string) => { return val; },
-      fromJSON: (val: string) => { return val; }
-    });
+  constructor(modelDB: IModelDB, factory: NotebookModel.IContentFactory) {
+    this._modelDB = modelDB;
+    this._factory = factory;
+    this._cellOrder = modelDB.createUndoableVector<string>('cellOrder');
     this._cellMap = new ObservableMap<ICellModel>();
 
     this._cellOrder.changed.connect(this._onOrderChanged, this);
   }
 
+  type: 'Vector';
+
   /**
    * A signal emitted when the cell list has changed.
    */
@@ -204,7 +209,7 @@ class CellList implements IObservableUndoableVector<ICellModel> {
    */
   set(index: number, cell: ICellModel): void {
     // Generate a new uuid for the cell.
-    let id = uuid();
+    let id = (cell as any).modelDB.basePath;
     // Set the internal data structures.
     this._cellMap.set(id, cell);
     this._cellOrder.set(index, id);
@@ -230,7 +235,7 @@ class CellList implements IObservableUndoableVector<ICellModel> {
    */
   pushBack(cell: ICellModel): number {
     // Generate a new uuid for the cell.
-    let id = uuid();
+    let id = (cell as any).modelDB.basePath;
     // Set the internal data structures.
     this._cellMap.set(id, cell);
     let num = this._cellOrder.pushBack(id);
@@ -284,7 +289,7 @@ class CellList implements IObservableUndoableVector<ICellModel> {
    */
   insert(index: number, cell: ICellModel): number {
     // Generate a new uuid for the cell.
-    let id = uuid();
+    let id = (cell as any).modelDB.basePath;
     // Set the internal data structures.
     this._cellMap.set(id, cell);
     let num = this._cellOrder.insert(index, id);
@@ -391,7 +396,7 @@ class CellList implements IObservableUndoableVector<ICellModel> {
     let newValues = toArray(cells);
     each(newValues, cell => {
       // Generate a new uuid for the cell.
-      let id = uuid();
+      let id = (cell as any).modelDB.basePath;
       // Set the internal data structures.
       this._cellMap.set(id, cell);
       this._cellOrder.pushBack(id);
@@ -429,7 +434,7 @@ class CellList implements IObservableUndoableVector<ICellModel> {
     let newValues = toArray(cells);
     each(newValues, cell => {
       // Generate a new uuid for the cell.
-      let id = uuid();
+      let id = (cell as any).modelDB.basePath;
       this._cellMap.set(id, cell);
       this._cellOrder.beginCompoundOperation();
       this._cellOrder.insert(index++, id);
@@ -523,7 +528,29 @@ class CellList implements IObservableUndoableVector<ICellModel> {
     this._cellOrder.clearUndo();
   }
 
-  private _onOrderChanged(order: IObservableVector<string>, change: ObservableVector.IChangedArgs<string>): void {
+  private _onOrderChanged(order: IObservableUndoableVector<string>, change: ObservableVector.IChangedArgs<string>): void {
+    if (change.type === 'add' || change.type === 'set') {
+      each(change.newValues, (id) => {
+        if (!this._cellMap.has(id)) {
+          let cellDB = this._factory.modelDB;
+          let cellType = (cellDB as any).getGoogleObject(id+'/type');
+          let cell: ICellModel;
+          switch (cellType) {
+            case 'code':
+              cell = this._factory.createCodeCell({ uuid: id});
+              break;
+            case 'markdown':
+              cell = this._factory.createMarkdownCell({ uuid: id});
+              break;
+            case 'raw':
+            default:
+              cell = this._factory.createRawCell({ uuid: id});
+              break;
+          }
+          this._cellMap.set(id, cell);
+        }
+      });
+    }
     let newValues: ICellModel[] = [];
     let oldValues: ICellModel[] = [];
     each(change.newValues, (id)=>{
@@ -545,4 +572,6 @@ class CellList implements IObservableUndoableVector<ICellModel> {
   private _cellOrder: IObservableUndoableVector<string> = null;
   private _cellMap: IObservableMap<ICellModel> = null;
   private _changed = new Signal<this, ObservableVector.IChangedArgs<ICellModel>>(this);
+  private _modelDB: IModelDB = null;
+  private _factory: NotebookModel.IContentFactory = null;
 }

+ 64 - 23
packages/notebook/src/model.ts

@@ -5,6 +5,10 @@ import {
   each
 } from '@phosphor/algorithm';
 
+import {
+  utils
+} from '@jupyterlab/services';
+
 import {
   DocumentModel, DocumentRegistry
 } from '@jupyterlab/docregistry';
@@ -15,8 +19,8 @@ import {
 } from '@jupyterlab/cells';
 
 import {
-  IObservableJSON, ObservableJSON, IObservableUndoableVector,
-  IObservableVector, ObservableVector, nbformat
+  IObservableJSON, IObservableUndoableVector,
+  IObservableVector, ObservableVector, nbformat, IModelDB
 } from '@jupyterlab/coreutils';
 
 import {
@@ -65,21 +69,28 @@ class NotebookModel extends DocumentModel implements INotebookModel {
    * Construct a new notebook model.
    */
   constructor(options: NotebookModel.IOptions = {}) {
-    super(options.languagePreference);
+    super(options.languagePreference, options.modelDB);
     let factory = (
       options.contentFactory || NotebookModel.defaultContentFactory
     );
+    let cellDB = this.modelDB.view('cells');
+    factory.modelDB = cellDB;
     this.contentFactory = factory;
-    this._cells = new CellList();
+    this._cells = new CellList(this.modelDB, this.contentFactory);
     // Add an initial code cell by default.
-    this._cells.pushBack(factory.createCodeCell({}));
+    if (!this._cells.length) {
+      this._cells.pushBack(factory.createCodeCell({}));
+    }
     this._cells.changed.connect(this._onCellsChanged, this);
 
     // Handle initial metadata.
-    let name = options.languagePreference || '';
-    this._metadata.set('language_info', { name });
+    let metadata = this.modelDB.createJSON('metadata');
+    if (!metadata.has('language_info')) {
+      let name = options.languagePreference || '';
+      metadata.set('language_info', { name });
+    }
     this._ensureMetadata();
-    this._metadata.changed.connect(this.triggerContentChange, this);
+    metadata.changed.connect(this.triggerContentChange, this);
   }
 
   /**
@@ -91,7 +102,7 @@ class NotebookModel extends DocumentModel implements INotebookModel {
    * The metadata associated with the notebook.
    */
   get metadata(): IObservableJSON {
-    return this._metadata;
+    return this.modelDB.get('metadata') as IObservableJSON;
   }
 
   /**
@@ -119,7 +130,7 @@ class NotebookModel extends DocumentModel implements INotebookModel {
    * The default kernel name of the document.
    */
   get defaultKernelName(): string {
-    let spec = this._metadata.get('kernelspec') as nbformat.IKernelspecMetadata;
+    let spec = this.metadata.get('kernelspec') as nbformat.IKernelspecMetadata;
     return spec ? spec.name : '';
   }
 
@@ -127,7 +138,7 @@ class NotebookModel extends DocumentModel implements INotebookModel {
    * The default kernel language of the document.
    */
   get defaultKernelLanguage(): string {
-    let info = this._metadata.get('language_info') as nbformat.ILanguageInfoMetadata;
+    let info = this.metadata.get('language_info') as nbformat.ILanguageInfoMetadata;
     return info ? info.name : '';
   }
 
@@ -136,12 +147,10 @@ class NotebookModel extends DocumentModel implements INotebookModel {
    */
   dispose(): void {
     // Do nothing if already disposed.
-    if (this._cells === null) {
+    if (this.cells === null) {
       return;
     }
-    let cells = this._cells;
-    this._cells = null;
-    this._metadata.dispose();
+    let cells = this.cells;
     cells.dispose();
     super.dispose();
   }
@@ -230,14 +239,14 @@ class NotebookModel extends DocumentModel implements INotebookModel {
       this.triggerStateChange({ name: 'nbformatMinor', oldValue, newValue });
     }
     // Update the metadata.
-    this._metadata.clear();
+    this.metadata.clear();
     let metadata = value.metadata;
     for (let key in metadata) {
       // orig_nbformat is not intended to be stored per spec.
       if (key === 'orig_nbformat') {
         continue;
       }
-      this._metadata.set(key, metadata[key]);
+      this.metadata.set(key, metadata[key]);
     }
     this._ensureMetadata();
     this.dirty = true;
@@ -269,12 +278,12 @@ class NotebookModel extends DocumentModel implements INotebookModel {
     }
     let factory = this.contentFactory;
     // Add code cell if there are no cells remaining.
-    if (!this._cells.length) {
+    if (!this.cells.length) {
       // Add the cell in a new context to avoid triggering another
       // cell changed event during the handling of this signal.
       requestAnimationFrame(() => {
-        if (!this.isDisposed && !this._cells.length) {
-          this._cells.pushBack(factory.createCodeCell({}));
+        if (!this.isDisposed && !this.cells.length) {
+          this.cells.pushBack(factory.createCodeCell({}));
         }
       });
     }
@@ -285,7 +294,7 @@ class NotebookModel extends DocumentModel implements INotebookModel {
    * Make sure we have the required metadata fields.
    */
   private _ensureMetadata(): void {
-    let metadata = this._metadata;
+    let metadata = this.metadata;
     if (!metadata.has('language_info')) {
       metadata.set('language_info', { name: '' });
     }
@@ -294,10 +303,9 @@ class NotebookModel extends DocumentModel implements INotebookModel {
     }
   }
 
-  private _cells: IObservableUndoableVector<ICellModel> = null;
+  private _cells: CellList;
   private _nbformat = nbformat.MAJOR_VERSION;
   private _nbformatMinor = nbformat.MINOR_VERSION;
-  private _metadata = new ObservableJSON();
 }
 
 
@@ -322,6 +330,12 @@ namespace NotebookModel {
      * The default is a shared factory instance.
      */
     contentFactory?: IContentFactory;
+
+
+    /**
+     * An optional modelDB for storing notebook data.
+     */
+    modelDB?: IModelDB;
   }
 
   /**
@@ -334,6 +348,8 @@ namespace NotebookModel {
      */
     readonly codeCellContentFactory: CodeCellModel.IContentFactory;
 
+    modelDB: IModelDB;
+
     /**
      * Create a new code cell.
      *
@@ -377,6 +393,7 @@ namespace NotebookModel {
       this.codeCellContentFactory = (options.codeCellContentFactory ||
         CodeCellModel.defaultContentFactory
       );
+      this._modelDB = options.modelDB || null;
     }
 
     /**
@@ -384,6 +401,14 @@ namespace NotebookModel {
      */
     readonly codeCellContentFactory: CodeCellModel.IContentFactory;
 
+    get modelDB(): IModelDB {
+      return this._modelDB;
+    }
+
+    set modelDB(db: IModelDB) {
+      this._modelDB = db;
+    }
+
     /**
      * Create a new code cell.
      *
@@ -398,6 +423,9 @@ namespace NotebookModel {
       if (options.contentFactory) {
         options.contentFactory = this.codeCellContentFactory;
       }
+      if(this._modelDB) {
+        options.modelDB = this._modelDB.view(options.uuid || utils.uuid());
+      }
       return new CodeCellModel(options);
     }
 
@@ -410,6 +438,9 @@ namespace NotebookModel {
      *   new cell will be intialized with the data from the source.
      */
     createMarkdownCell(options: CellModel.IOptions): IMarkdownCellModel {
+      if(this._modelDB) {
+        options.modelDB = this._modelDB.view(options.uuid || utils.uuid());
+      }
       return new MarkdownCellModel(options);
     }
 
@@ -422,8 +453,13 @@ namespace NotebookModel {
      *   new cell will be intialized with the data from the source.
      */
     createRawCell(options: CellModel.IOptions): IRawCellModel {
+      if(this._modelDB) {
+        options.modelDB = this._modelDB.view(options.uuid || utils.uuid());
+      }
      return new RawCellModel(options);
     }
+
+    private _modelDB: IModelDB;
   }
 
   /**
@@ -435,6 +471,11 @@ namespace NotebookModel {
      * The factory for code cell model content.
      */
     codeCellContentFactory?: CodeCellModel.IContentFactory;
+
+    /**
+     * The modelDB in which to place new content.
+     */
+    modelDB?: IModelDB;
   }
 
   /**

+ 6 - 2
packages/notebook/src/modelfactory.ts

@@ -5,6 +5,10 @@ import {
   Contents
 } from '@jupyterlab/services';
 
+import {
+  IModelDB
+} from '@jupyterlab/coreutils';
+
 import {
   DocumentRegistry
 } from '@jupyterlab/docregistry';
@@ -80,9 +84,9 @@ class NotebookModelFactory implements DocumentRegistry.IModelFactory<INotebookMo
    *
    * @returns A new document model.
    */
-  createNew(languagePreference?: string): INotebookModel {
+  createNew(languagePreference?: string, modelDB?: IModelDB): INotebookModel {
     let contentFactory = this.contentFactory;
-    return new NotebookModel({ languagePreference, contentFactory });
+    return new NotebookModel({ languagePreference, contentFactory, modelDB });
   }
 
   /**

+ 29 - 1
packages/outputarea/src/model.ts

@@ -10,7 +10,8 @@ import {
 } from '@phosphor/signaling';
 
 import {
-  IObservableVector, ObservableVector, nbformat
+  IObservableVector, ObservableVector, nbformat,
+  IObservableValue, IModelDB
 } from '@jupyterlab/coreutils';
 
 import {
@@ -40,6 +41,31 @@ class OutputAreaModel implements IOutputAreaModel {
       each(options.values, value => { this._add(value); });
     }
     this.list.changed.connect(this._onListChanged, this);
+
+    if(options.modelDB) {
+      let changeGuard = false;
+      this._modelDB = options.modelDB;
+      this._serialized = this._modelDB.createValue('outputs');
+      if (this._serialized.get()) {
+        this.fromJSON(this._serialized.get() as nbformat.IOutput[]);
+      } else {
+        this._serialized.set(this.toJSON());
+      }
+      this._serialized.changed.connect((obs, args) => {
+        if(!changeGuard) {
+          changeGuard = true;
+          this.fromJSON(args.newValue as nbformat.IOutput[]);
+          changeGuard = false;
+        }
+      });
+      this.list.changed.connect((list, args) => {
+        if(!changeGuard) {
+          changeGuard = true;
+          this._serialized.set(this.toJSON());
+          changeGuard = false;
+        }
+      });
+    }
   }
 
   /**
@@ -250,6 +276,8 @@ class OutputAreaModel implements IOutputAreaModel {
   private _isDisposed = false;
   private _stateChanged = new Signal<IOutputAreaModel, void>(this);
   private _changed = new Signal<this, IOutputAreaModel.ChangedArgs>(this);
+  private _modelDB: IModelDB = null;
+  private _serialized: IObservableValue = null;
 }
 
 

+ 6 - 1
packages/outputarea/src/widget.ts

@@ -38,7 +38,7 @@ import {
 } from '@jupyterlab/apputils';
 
 import {
-  ObservableVector, nbformat
+  ObservableVector, nbformat, IModelDB
 } from '@jupyterlab/coreutils';
 
 import {
@@ -872,6 +872,11 @@ namespace IOutputAreaModel {
      * If not given, a default factory will be used.
      */
     contentFactory?: IContentFactory;
+
+    /**
+     * An optional IModelDB to store the output area model.
+     */
+    modelDB?: IModelDB;
   }
 
   /**