UNPKG

@nteract/core

Version:

core modules and components for nteract apps

289 lines (255 loc) 8.17 kB
This document attempts to specify the goal for an nteract core package that can provide generic/core state management to all notebook-y applications. Right now this is focused on these apps: - Desktop - nteract on jupyter - `@nteract/play` - notebook on next This describes a pretty far and wide overhaul of our core logic, including the notebook itself. ## Core concepts - `ref` - an internal _reference_ to an entity upon _recognition_, e.g. kernels, hosts, kernelspec collections, etc. - `id` - likely an external identifier, e.g. with /api/kernels/9092, 9092 is the id We use the term _recognition_ over _creation_ because we want to have a way to reference an entity _before_ we get a response from some api. A good example is having a _ref_ for an active kernel before the kernel has been launched with a jupyter notebook server. Since there will be a proliferation of id-strings, the internal ones are called \_ref_s and they are only meant for use inside the application--i.e., they have no meaning externally. The external id-string that will typically be found is called `id`. --- Side note -- In flow, this would be typed as: ```js opaque type Id = string; opaque type Ref = string; ``` Which can also help us ensure we are using the right ids and refs amongst disparate entities: ```js opaque type KernelId = Id; ``` --- ## Flattened Structure, Database Like Feeling Stemming from the Redux docs' [Normalizing State Shape](https://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html), we setup our application to be collections of entries built in a relational fashion. We were doing something similar with `cellMap` (map of cell id to cell) and `cellOrder` (list of cell ids). We're taking it to the next level here. ## The Proposed Structure ```js type Id = string; type Ref = string; type core = { // The core state is meant to be document-centric. So, we basically use the // currently selected document to set the context for the rest of the app. // This model may need to change when twe have things like split panes and // support objects in core that are not really considered *documents*. selectedContentRef: Ref // The piece of state that allows the ui to show loading/error indicators. // This is split apart from the entities definitions because the two parts of // state serve very different purposes. communication: { preferences: { loading: boolean, saving: boolean, error: ?Object }, // TODO: what's this doing again? hostSpec: { loading: boolean, error: ?Object }, hosts: { byRef: { [ref: Ref]: { loading: boolean, error: ?Object } } }, sessions: { byRef: { [ref: Ref]: { loading: boolean, error: ?Object } } }, kernels: { byRef: { [ref: Ref]: { loading: boolean, error: ?Object } } }, kernelspecs: { byRef: { [ref: Ref]: { loading: boolean, error: ?Object } } }, contents: { byRef: { [ref: Ref]: { loading: boolean, saving: boolean, error: ?Object } } } }, // These are the actual data that we get back from // * API Calls // * User input // * Kernel output entities: { preferences: { lastSaved: Date, theme: "light" | "dark" }, outputs: { byRef: { [ref: Ref]: { data: Object, metadata: Object, transient: Object, type: string // TODO: should be enummed } }, // For capturing the display ID mappings (aliases) displayIdToOutputRefs: { [id: Id]: Array<Ref> } }, cells: { byRef: { [ref: Ref]: { type: "code" | "markdown" | "raw", source: string, metadata: Object, // NOTE: the following fields are only on code cells outputRefs: Array<Ref>, executionState: "finished" | "executing" | "queued" | "dirty", executionCount: ?number, lastExecuteMessage: JupyterMessage } }, refs: Array<Ref> }, hostSpec: { // TODO: is this something that's going to be hard-coded into an app? Or, // is it something that we'll indeed need to request from some api? See // related hostSpec in the `communication` state hunk. // Else, should this be sorta top-level alongside the `notebook` hunk // of state? }, // Each host implementation has a set of kernels which may be activated. kernelspecs: { byRef: { [ref: Ref]: { defaultKernelName: string, hostRef: Ref, byName: { [name: string]: { argv: Array<string>, displayName: string, env: Object, language: string, interruptMode: string, resources: Object } }, } }, refs: List<KernelspecsRef> }, hosts: { // On desktop we'll have the one built-in local host that connects to // zeromq directly. On jupyterhub backed apps, you'll be able to switch to // different hosts. byRef: { [ref: Ref]: { id: string, type: ("local" | "jupyter"), kernelIds: Array<Id>, // the list of *active* kernels for a host. token: string, serverUrl: string, crossDomain: boolean, // TODO: I think we're attempting to split apart host and document. // 1. what was the purpose of this? // 2. can it be provided in some other way? rootContentRef: Ref, messages: Array<string> // binder only } } refs: Array<Ref> }, // A host may have one active kernel (but we allow multiple to allow smooth // transitions between switching kernels). kernels: { byRef: { [ref: Ref]: { type: ("local" | "jupyter"), // same as server, literal, unchanging hostRef: HostRef, name: string, lastActivity: Date, channels: rxjs$Subject, status: string, id: Id, // jupyter only spawn: ChildProcess, // local only connectionFile: string, // local only cwd: string, // current working directory, absolute on local, relative to server on jupyter } } }, sessions: { byRef: { [ref: Ref]: { id: Id, name: string, // This is just a display name. type: string, // TODO: this should be an enum. kernelRef: Ref } }, refs: Array<Ref> }, contents: { byRef: { [ref: Ref]: { type: "directory" | "notebook" | "file", mimetype: ?string, // file-type only. path: string, name: string, created: Date, lastSaved: Date, modified: boolean, writable: bool, format: null | "json" | "text" | "base64", // "json" for dir / nb // The model is a little confusing. Think of it as the in-memory, app // version of the content string that you get back from the contents // api. So, for a plain file, which we don't necessarily know how to // handle, the model will just be a string still. However, for a // notebook, we basically flesh out all the references to cells in // here. model: ?Object, // null | DirectoryModel | NotebookModel | FileModel // The sessionRef is nullable here because we don't necessarily need // the session to be running to display the document. This allows us // to render a document and start up a session in parallel. sessionRef: ?SessionRef } } }, notifications: { byRef: { [ref: Ref]: { message: string, // TODO: Figure out our structure here }, refs: Array<Ref> } } } } ```