The basic structure is like so:

The Data Model has a caching scheme for actual data, and a meta-data (fields) storage for schema. Both caching implementation and meta-data storage are intended to be pluggable.
Our implementation uses column-based schema, but it's understood we need to allow per-cell meta-data. This could be handled via a new meta-data handler or simply by extending the existing one.
The Data Source is a black-box for retrieving/committing data to persistent storage. The 'query protocol' for data transport and the format of the returned data tends to be application dependent. We can generate some standard implementations but it's also possible to match a Data Source and a Data Model by hand.
RPC/whatever becomes part of the Data Source implementation, and JSON/XML/whatever is part of the query protocol implementation. We leave that open on purpose.
For TurboDbAdmin, the implementation looks like this:

Data morphology is an issue. Data is generally 1-d (list) or 2-d (tabular), so we defined some basic methods for the Data Model interface (data protocol):
getDatum(col [, row])
setDatum(inDatum, col, [, row])
getDatum(fieldName [, row])
setDatum(inDatum, fieldName, [,row])
I'm not sure there is a use for n-dimensional data, but it wouldn't be hard to extend the model.
Note that 'fieldName' is part of meta-data and it's provision is part of the contract between the Data Model and the schema. Our schema implementation also includes pluggable sorting.
Our implementation has various other methods in the Data Model interface (e.g. row manipulation). It's an open question as to the scope of the basic interface (vs. some particular implementation). JavaScript's plasticity makes it easy to avoid these decsisions a-priori.
Another issue is the transaction scheme: when is data committed back to the Data Source by the model. Currently, our application maintains a transaction cache and commits modified rows back to the server when the user has applied them. This code is almost surely better abstracted somewhere; probably into the data model itself.
Our data-consumer (TurboGrid) maintains it's own set of meta-data. These include column indirection (for moving/hiding columns), formatting, and editing objects. The displayed (DOM) data is generally distinct from the stored data. Formatting objects are responsible for rendering data (i.e. displaying a check box for boolean data). Editing objects are reponsible for creating and managing UI data for a grid cell. In this way, grid is completely plastic.
We have a set of stock formatters and editors for common data types. Also, we have a Data Adapter object which takes our particular Data Model schema and creates compatible grid schema.