Browse Source

Refactroring CIF exporter

David Sehnal 6 years ago
parent
commit
3b8adde504

+ 5 - 13
src/apps/cif2bcif/converter.ts

@@ -4,9 +4,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Iterator from 'mol-data/iterator'
 import CIF, { CifCategory } from 'mol-io/reader/cif'
-import * as Encoder from 'mol-io/writer/cif'
+import { CIFCategory, createCIFEncoder } from 'mol-io/writer/cif'
 import * as fs from 'fs'
 import classify from './field-classifier'
 
@@ -19,19 +18,12 @@ async function getCIF(path: string) {
     return parsed.result;
 }
 
-function createDefinition(cat: CifCategory): Encoder.CategoryDefinition {
-    return {
-        name: cat.name,
-        fields: cat.fieldNames.map(n => classify(n, cat.getField(n)!))
-    }
-}
-
-function getCategoryInstanceProvider(cat: CifCategory): Encoder.CategoryProvider {
+function getCategoryInstanceProvider(cat: CifCategory): CIFCategory.Provider {
     return function (ctx: any) {
         return {
             data: cat,
-            definition: createDefinition(cat),
-            keys: () => Iterator.Range(0, cat.rowCount - 1),
+            name: cat.name,
+            fields: cat.fieldNames.map(n => classify(n, cat.getField(n)!)),
             rowCount: cat.rowCount
         };
     }
@@ -40,7 +32,7 @@ function getCategoryInstanceProvider(cat: CifCategory): Encoder.CategoryProvider
 export default async function convert(path: string, asText = false) {
     const cif = await getCIF(path);
 
-    const encoder = Encoder.create({ binary: !asText, encoderName: 'mol* cif2bcif' });
+    const encoder = createCIFEncoder({ binary: !asText, encoderName: 'mol* cif2bcif' });
     for (const b of cif.blocks) {
         encoder.startDataBlock(b.header);
         for (const c of b.categoryNames) {

+ 5 - 5
src/apps/cif2bcif/field-classifier.ts

@@ -6,14 +6,14 @@
 
 import { Column } from 'mol-data/db'
 import { CifField } from 'mol-io/reader/cif/data-model'
-import { FieldDefinition, FieldType } from 'mol-io/writer/cif/encoder'
+import { CIFField } from 'mol-io/writer/cif'
 
 const intRegex = /^-?\d+$/
 const floatRegex = /^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))([(][0-9]+[)])?([eE][+-]?[0-9]+)?/
 
 // Classify a cif field as str, int or float based the data it contains.
 // To classify a field as int or float all items are checked.
-function classify(name: string, field: CifField): FieldDefinition {
+function classify(name: string, field: CifField): CIFField {
     let floatCount = 0, hasString = false;
     for (let i = 0, _i = field.rowCount; i < _i; i++) {
         const k = field.valueKind(i);
@@ -24,9 +24,9 @@ function classify(name: string, field: CifField): FieldDefinition {
         else { hasString = true; break; }
     }
 
-    if (hasString) return { name, type: FieldType.Str, value: field.str, valueKind: field.valueKind };
-    if (floatCount > 0) return { name, type: FieldType.Float, value: field.float, valueKind: field.valueKind };
-    return { name, type: FieldType.Int, value: field.int, valueKind: field.valueKind };
+    if (hasString) return { name, type: CIFField.Type.Str, value: field.str, valueKind: field.valueKind };
+    if (floatCount > 0) return { name, type: CIFField.Type.Float, value: field.float, valueKind: field.valueKind };
+    return { name, type: CIFField.Type.Int, value: field.int, valueKind: field.valueKind };
 }
 
 export default classify;

+ 3 - 3
src/apps/combine-mmcif/index.ts

@@ -17,7 +17,7 @@ import { Progress } from 'mol-task'
 import { Database, Table, DatabaseCollection } from 'mol-data/db'
 import CIF from 'mol-io/reader/cif'
 // import { CCD_Schema } from 'mol-io/reader/cif/schema/ccd'
-import * as Encoder from 'mol-io/writer/cif'
+import { CIFEncoder, createCIFEncoder } from 'mol-io/writer/cif'
 import { mmCIF_Schema, mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import { CCD_Schema } from 'mol-io/reader/cif/schema/ccd';
 import { BIRD_Schema } from 'mol-io/reader/cif/schema/bird';
@@ -85,8 +85,8 @@ async function parseCif(data: string|Uint8Array) {
 }
 
 export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
-    const encoder = Encoder.create({ binary, encoderName: 'mol*' });
-    Encoder.writeDatabase(encoder, name, database)
+    const encoder = createCIFEncoder({ binary, encoderName: 'mol*' });
+    CIFEncoder.writeDatabase(encoder, name, database)
     return encoder.getData();
 }
 

+ 2 - 2
src/apps/domain-annotation-server/mapping.ts

@@ -5,7 +5,7 @@
  */
 
 import { Table } from 'mol-data/db'
-import { EncoderInstance, create as createEncoder } from 'mol-io/writer/cif'
+import { CIFEncoder, createCIFEncoder as createEncoder } from 'mol-io/writer/cif'
 import * as S from './schemas'
 import { getCategoryInstanceProvider } from './utils'
 
@@ -36,7 +36,7 @@ interface DomainAnnotation {
 }
 type MappingRow = Table.Row<S.mapping>;
 
-function writeDomain(enc: EncoderInstance, domain: DomainAnnotation | undefined) {
+function writeDomain(enc: CIFEncoder, domain: DomainAnnotation | undefined) {
     if (!domain) return;
     enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_annotation`, domain.domains));
     enc.writeCategory(getCategoryInstanceProvider(`pdbx_${domain.name}_domain_mapping`, domain.mappings));

+ 6 - 11
src/apps/domain-annotation-server/utils.ts

@@ -5,8 +5,7 @@
  */
 
 import { Table } from 'mol-data/db'
-import Iterator from 'mol-data/iterator'
-import * as Encoder from 'mol-io/writer/cif'
+import { CIFField, CIFCategory } from 'mol-io/writer/cif'
 
 function columnValue(k: string) {
     return (i: number, d: any) => d[k].value(i);
@@ -17,25 +16,21 @@ function columnValueKind(k: string) {
 }
 
 function ofSchema(schema: Table.Schema) {
-    const fields: Encoder.FieldDefinition[] = [];
+    const fields: CIFField[] = [];
     for (const k of Object.keys(schema)) {
         const t = schema[k];
-        const type: any = t.valueType === 'str' ? Encoder.FieldType.Str : t.valueType === 'int' ? Encoder.FieldType.Int : Encoder.FieldType.Float;
+        const type: any = t.valueType === 'str' ? CIFField.Type.Str : t.valueType === 'int' ? CIFField.Type.Int : CIFField.Type.Float;
         fields.push({ name: k, type, value: columnValue(k), valueKind: columnValueKind(k) })
     }
     return fields;
 }
 
-function ofTable<S extends Table.Schema>(name: string, table: Table<S>): Encoder.CategoryDefinition<number> {
-    return { name, fields: ofSchema(table._schema) }
-}
-
-export function getCategoryInstanceProvider(name: string, table: Table<any>): Encoder.CategoryProvider {
+export function getCategoryInstanceProvider(name: string, table: Table<any>): CIFCategory.Provider {
     return () => {
         return {
             data: table,
-            definition: ofTable(name, table),
-            keys: () => Iterator.Range(0, table._rowCount - 1),
+            name,
+            fields: ofSchema(table._schema),
             rowCount: table._rowCount
         };
     }

+ 2 - 2
src/mol-io/reader/cif/schema/mmcif.ts

@@ -80,11 +80,11 @@ export const mmCIF_Schema = {
         pdbx_aromatic_flag: Aliased<'Y' | 'N'>(str),
     },
     entity: {
-        details: str,
-        formula_weight: float,
         id: str,
         src_method: Aliased<'nat' | 'man' | 'syn'>(str),
         type: Aliased<'polymer' | 'non-polymer' | 'macrolide' | 'water'>(str),
+        details: str,
+        formula_weight: float,
         pdbx_description: str,
         pdbx_number_of_molecules: float,
         pdbx_mutation: str,

+ 2 - 21
src/mol-io/writer/cif.ts

@@ -5,32 +5,13 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Database, DatabaseCollection } from 'mol-data/db'
-
 import TextCIFEncoder from './cif/encoder/text'
 import BinaryCIFEncoder from './cif/encoder/binary'
-import { CategoryDefinition } from './cif/encoder'
+import { CIFEncoder } from './cif/encoder'
 
 export * from './cif/encoder'
 
-export type EncoderInstance = BinaryCIFEncoder<{}> | TextCIFEncoder<{}>
-
-export function create(params?: { binary?: boolean, encoderName?: string }): EncoderInstance {
+export function createCIFEncoder(params?: { binary?: boolean, encoderName?: string }): CIFEncoder {
     const { binary = false, encoderName = 'mol*' } = params || {};
     return binary ? new BinaryCIFEncoder(encoderName) : new TextCIFEncoder();
-}
-
-export function writeDatabase(encoder: EncoderInstance, name: string, database: Database<Database.Schema>) {
-    encoder.startDataBlock(name);
-    for (const table of database._tableNames) {
-        encoder.writeCategory(
-            CategoryDefinition.instanceProviderOfTable(table, database[table])
-        );
-    }
-}
-
-export function writeDatabaseCollection(encoder: EncoderInstance, collection: DatabaseCollection<Database.Schema>) {
-    for (const name of Object.keys(collection)) {
-        writeDatabase(encoder, name, collection[name])
-    }
 }

+ 90 - 80
src/mol-io/writer/cif/encoder.ts

@@ -6,7 +6,7 @@
  */
 
 import Iterator from 'mol-data/iterator'
-import { Column, Table } from 'mol-data/db'
+import { Column, Table, Database, DatabaseCollection } from 'mol-data/db'
 import { Tensor } from 'mol-math/linear-algebra'
 import Encoder from '../encoder'
 import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
@@ -17,65 +17,94 @@ import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
 // TODO: add "repeat encoding"? [[1, 2], [1, 2], [1, 2]] --- Repeat ---> [[1, 2], 3]
 // TODO: Add "higher level fields"? (i.e. generalization of repeat)
 // TODO: align "data blocks" to 8 byte offsets for fast typed array windows? (prolly needs some testing if this is actually the case too)
-// TODO: "parametric encoders"? Specify encoding as [{ param: 'value1', encoding1 }, { param: 'value2', encoding2 }]
-//       then the encoder can specify { param: 'value1' } and the correct encoding will be used.
-//       Use case: variable precision encoding for different fields.
-//       Perhaps implement this as parameter spaces...
 
-export const enum FieldType {
-    Str, Int, Float
-}
-
-export interface FieldDefinitionBase<Key, Data> {
+export interface CIFField<Key = any, Data = any> {
     name: string,
+    type: CIFField.Type,
     valueKind?: (key: Key, data: Data) => Column.ValueKind,
-    encoder?: ArrayEncoder
+    defaultFormat?: CIFField.Format,
+    value(key: Key, data: Data): string | number
 }
 
-export type FieldDefinition<Key = any, Data = any> =
-    | FieldDefinitionBase<Key, Data> & { type: FieldType.Str, value(key: Key, data: Data): string }
-    | FieldDefinitionBase<Key, Data> & { type: FieldType.Int, value(key: Key, data: Data): number, typedArray?: ArrayEncoding.TypedArrayCtor }
-    | FieldDefinitionBase<Key, Data> & { type: FieldType.Float, value(key: Key, data: Data): number, digitCount?: number, typedArray?: ArrayEncoding.TypedArrayCtor }
+export namespace CIFField {
+    export const enum Type { Str, Int, Float }
 
-export interface FieldFormat {
-    // TODO: do we actually need this?
-    // digitCount?: number,
-    // encoder?: ArrayEncoder,
-    // typedArray?: ArrayEncoding.TypedArrayCtor
-}
+    export interface Format {
+        digitCount?: number,
+        encoder?: ArrayEncoder,
+        typedArray?: ArrayEncoding.TypedArrayCtor
+    }
 
-export namespace FieldFormat {
-    export const Default: FieldFormat = {
-        // textDecimalPlaces: 3,
-        // stringEncoder: ArrayEncoder.by(E.stringArray),
-        // numericEncoder: ArrayEncoder.by(E.byteArray)
-    };
-}
+    export function getDigitCount(field: CIFField) {
+        if (field.defaultFormat && typeof field.defaultFormat.digitCount !== 'undefined') return field.defaultFormat.digitCount;
+        return 6;
+    }
 
-export interface CategoryDefinition<Key = any, Data = any> {
-    name: string,
-    fields: FieldDefinition<Key, Data>[],
-    format?: { [name: string]: FieldFormat }
+    export function str<K, D = any>(name: string, value: (k: K, d: D) => string, params?: { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder }): CIFField<K, D> {
+        return { name, type: Type.Str, value, valueKind: params && params.valueKind, defaultFormat: params && params.encoder ? { encoder: params.encoder } : void 0 };
+    }
+
+    export function int<K, D = any>(name: string, value: (k: K, d: D) => number, params?: { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, typedArray?: ArrayEncoding.TypedArrayCtor }): CIFField<K, D> {
+        return {
+            name,
+            type: Type.Int,
+            value,
+            valueKind: params && params.valueKind,
+            defaultFormat: params ? { encoder: params.encoder, typedArray: params.typedArray } : void 0 };
+    }
+
+    export function float<K, D = any>(name: string, value: (k: K, d: D) => number, params?: { valueKind?: (k: K, d: D) => Column.ValueKind, encoder?: ArrayEncoder, typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }): CIFField<K, D> {
+        return {
+            name,
+            type: Type.Float,
+            value,
+            valueKind: params && params.valueKind,
+            defaultFormat: params ? { encoder: params.encoder, typedArray: params.typedArray, digitCount: typeof params.digitCount !== 'undefined' ? params.digitCount : void 0 } : void 0
+        };
+    }
 }
 
-export interface CategoryInstance<Key = any, Data = any> {
-    data: Data,
-    definition: CategoryDefinition<Key, Data>,
-    formats?: { [name: string]: Partial<FieldFormat> },
+export interface CIFCategory<Key = any, Data = any> {
+    name: string,
+    fields: CIFField<Key, Data>[],
+    data?: Data,
     rowCount: number,
-    keys(): Iterator<Key>
+    keys?: () => Iterator<Key>
 }
 
-export interface CategoryProvider {
-    (ctx: any): CategoryInstance
+export namespace CIFCategory {
+    export interface Provider<Ctx = any> {
+        (ctx: Ctx): CIFCategory
+    }
+
+    export function ofTable(name: string, table: Table<Table.Schema>): CIFCategory<number, Table<Table.Schema>> {
+        return { name, fields: cifFieldsFromTableSchema(table._schema), data: table, rowCount: table._rowCount };
+    }
 }
 
-export interface CIFEncoder<T = string | Uint8Array, Context = any> extends Encoder {
+export interface CIFEncoder<T = string | Uint8Array> extends Encoder {
+    // setFormatter(): void,
     startDataBlock(header: string): void,
-    writeCategory(category: CategoryProvider, contexts?: Context[]): void,
+    writeCategory<Ctx>(category: CIFCategory.Provider<Ctx>, contexts?: Ctx[]): void,
     getData(): T
 }
 
+export namespace CIFEncoder {
+    export function writeDatabase(encoder: CIFEncoder, name: string, database: Database<Database.Schema>) {
+        encoder.startDataBlock(name);
+        for (const table of database._tableNames) {
+            encoder.writeCategory(() => CIFCategory.ofTable(table, database[table]));
+        }
+    }
+
+    export function writeDatabaseCollection(encoder: CIFEncoder, collection: DatabaseCollection<Database.Schema>) {
+        for (const name of Object.keys(collection)) {
+            writeDatabase(encoder, name, collection[name])
+        }
+    }
+}
+
+
 function columnValue(k: string) {
     return (i: number, d: any) => d[k].value(i);
 }
@@ -93,8 +122,8 @@ function columnValueKind(k: string) {
 }
 
 function getTensorDefinitions(field: string, space: Tensor.Space) {
-    const fieldDefinitions: FieldDefinition[] = []
-    const type = FieldType.Float
+    const fieldDefinitions: CIFField[] = []
+    const type = CIFField.Type.Float
     const valueKind = columnValueKind(field)
     if (space.rank === 1) {
         const rows = space.dimensions[0]
@@ -126,42 +155,23 @@ function getTensorDefinitions(field: string, space: Tensor.Space) {
     return fieldDefinitions
 }
 
-export namespace FieldDefinitions {
-    export function ofSchema(schema: Table.Schema) {
-        const fields: FieldDefinition[] = [];
-        for (const k of Object.keys(schema)) {
-            const t = schema[k];
-            if (t.valueType === 'int') {
-                fields.push({ name: k, type: FieldType.Int, value: columnValue(k), valueKind: columnValueKind(k) });
-            } else if (t.valueType === 'float') {
-                fields.push({ name: k, type: FieldType.Float, value: columnValue(k), valueKind: columnValueKind(k) });
-            } else if (t.valueType === 'str') {
-                fields.push({ name: k, type: FieldType.Str, value: columnValue(k), valueKind: columnValueKind(k) });
-            } else if (t.valueType === 'list') {
-                fields.push({ name: k, type: FieldType.Str, value: columnListValue(k), valueKind: columnValueKind(k) })
-            } else if (t.valueType === 'tensor') {
-                fields.push(...getTensorDefinitions(k, t.space))
-            } else {
-                throw new Error(`Unknown valueType ${t.valueType}`);
-            }
-        }
-        return fields;
-    }
-}
-
-export namespace CategoryDefinition {
-    export function ofTable<S extends Table.Schema>(name: string, table: Table<S>): CategoryDefinition<number> {
-        return { name, fields: FieldDefinitions.ofSchema(table._schema) }
-    }
-
-    export function instanceProviderOfTable(name: string, table: Table<Table.Schema>): CategoryProvider {
-        return function (ctx: any) {
-            return {
-                data: table,
-                definition: ofTable(name, table),
-                keys: () => Iterator.Range(0, table._rowCount - 1),
-                rowCount: table._rowCount
-            };
+function cifFieldsFromTableSchema(schema: Table.Schema) {
+    const fields: CIFField[] = [];
+    for (const k of Object.keys(schema)) {
+        const t = schema[k];
+        if (t.valueType === 'int') {
+            fields.push({ name: k, type: CIFField.Type.Int, value: columnValue(k), valueKind: columnValueKind(k) });
+        } else if (t.valueType === 'float') {
+            fields.push({ name: k, type: CIFField.Type.Float, value: columnValue(k), valueKind: columnValueKind(k) });
+        } else if (t.valueType === 'str') {
+            fields.push({ name: k, type: CIFField.Type.Str, value: columnValue(k), valueKind: columnValueKind(k) });
+        } else if (t.valueType === 'list') {
+            fields.push({ name: k, type: CIFField.Type.Str, value: columnListValue(k), valueKind: columnValueKind(k) })
+        } else if (t.valueType === 'tensor') {
+            fields.push(...getTensorDefinitions(k, t.space))
+        } else {
+            throw new Error(`Unknown valueType ${t.valueType}`);
         }
     }
-}
+    return fields;
+}

+ 16 - 16
src/mol-io/writer/cif/encoder/binary.ts

@@ -6,16 +6,16 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Iterator from 'mol-data/iterator'
+import { Iterator } from 'mol-data'
 import { Column } from 'mol-data/db'
 import encodeMsgPack from '../../../common/msgpack/encode'
 import {
     EncodedColumn, EncodedData, EncodedFile, EncodedDataBlock, EncodedCategory, ArrayEncoder, ArrayEncoding as E, VERSION
 } from '../../../common/binary-cif'
-import { FieldDefinition, FieldFormat, FieldType, CategoryProvider, CIFEncoder } from '../encoder'
+import { CIFField, CIFCategory, CIFEncoder } from '../encoder'
 import Writer from '../../writer'
 
-export default class BinaryCIFWriter<Context> implements CIFEncoder<Uint8Array, Context> {
+export default class BinaryCIFWriter implements CIFEncoder<Uint8Array> {
     private data: EncodedFile;
     private dataBlocks: EncodedDataBlock[] = [];
     private encodedData: Uint8Array;
@@ -27,7 +27,7 @@ export default class BinaryCIFWriter<Context> implements CIFEncoder<Uint8Array,
         });
     }
 
-    writeCategory(category: CategoryProvider, contexts?: Context[]) {
+    writeCategory<Ctx>(category: CIFCategory.Provider<Ctx>, contexts?: Ctx[]) {
         if (!this.data) {
             throw new Error('The writer contents have already been encoded, no more writing.');
         }
@@ -44,10 +44,10 @@ export default class BinaryCIFWriter<Context> implements CIFEncoder<Uint8Array,
         if (!count) return;
 
         const first = categories[0]!;
-        const cat: EncodedCategory = { name: '_' + first.definition.name, columns: [], rowCount: count };
-        const data = categories.map(c => ({ data: c.data, keys: () => c.keys() }));
-        for (const f of first.definition.fields) {
-            cat.columns.push(encodeField(f, data, count, FieldFormat.Default));
+        const cat: EncodedCategory = { name: '_' + first.name, columns: [], rowCount: count };
+        const data = categories.map(c => ({ data: c.data, keys: () => c.keys ? c.keys() : Iterator.Range(0, c.rowCount - 1) }));
+        for (const f of first.fields) {
+            cat.columns.push(encodeField(f, data, count, f.defaultFormat));
         }
         this.dataBlocks[this.dataBlocks.length - 1].categories.push(cat);
     }
@@ -77,19 +77,19 @@ export default class BinaryCIFWriter<Context> implements CIFEncoder<Uint8Array,
     }
 }
 
-function createArray(field: FieldDefinition, count: number) {
-    if (field.type === FieldType.Str) return new Array(count) as any;
-    else if (field.typedArray) return new field.typedArray(count) as any;
-    else return (field.type === FieldType.Int ? new Int32Array(count) : new Float32Array(count)) as any;
+function createArray(field: CIFField, count: number) {
+    if (field.type === CIFField.Type.Str) return new Array(count) as any;
+    else if (field.defaultFormat && field.defaultFormat.typedArray) return new field.defaultFormat.typedArray(count) as any;
+    else return (field.type === CIFField.Type.Int ? new Int32Array(count) : new Float32Array(count)) as any;
 }
 
-function encodeField(field: FieldDefinition, data: { data: any, keys: () => Iterator<any> }[], totalCount: number, format: FieldFormat): EncodedColumn {
-    const isStr = field.type === FieldType.Str;
+function encodeField(field: CIFField, data: { data: any, keys: () => Iterator<any> }[], totalCount: number, format?: CIFField.Format): EncodedColumn {
+    const isStr = field.type === CIFField.Type.Str;
     const array = createArray(field, totalCount);
     let encoder: ArrayEncoder;
 
-    if (field.encoder) {
-        encoder = field.encoder;
+    if (field.defaultFormat && field.defaultFormat.encoder) {
+        encoder = field.defaultFormat.encoder;
     } else if (isStr) {
         encoder = ArrayEncoder.by(E.stringArray);
     } else {

+ 19 - 18
src/mol-io/writer/cif/encoder/text.ts

@@ -6,12 +6,13 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { Iterator } from 'mol-data'
 import { Column } from 'mol-data/db'
 import StringBuilder from 'mol-util/string-builder'
-import * as Enc from '../encoder'
+import { CIFCategory, CIFField, CIFEncoder } from '../encoder'
 import Writer from '../../writer'
 
-export default class TextCIFEncoder<Context> implements Enc.CIFEncoder<string, Context> {
+export default class TextCIFEncoder implements CIFEncoder<string> {
     private builder = StringBuilder.create();
     private encoded = false;
     private dataBlockCreated = false;
@@ -21,7 +22,7 @@ export default class TextCIFEncoder<Context> implements Enc.CIFEncoder<string, C
         StringBuilder.write(this.builder, `data_${(header || '').replace(/[ \n\t]/g, '').toUpperCase()}\n#\n`);
     }
 
-    writeCategory(category: Enc.CategoryProvider, contexts?: Context[]) {
+    writeCategory<Ctx>(category: CIFCategory.Provider<Ctx>, contexts?: Ctx[]) {
         if (this.encoded) {
             throw new Error('The writer contents have already been encoded, no more writing.');
         }
@@ -60,7 +61,7 @@ export default class TextCIFEncoder<Context> implements Enc.CIFEncoder<string, C
     }
 }
 
-function writeValue(builder: StringBuilder, data: any, key: any, f: Enc.FieldDefinition<any, any>, floatPrecision: number): boolean {
+function writeValue(builder: StringBuilder, data: any, key: any, f: CIFField<any, any>, floatPrecision: number): boolean {
     const kind = f.valueKind;
     const p = kind ? kind(key, data) : Column.ValueKind.Present;
     if (p !== Column.ValueKind.Present) {
@@ -69,14 +70,14 @@ function writeValue(builder: StringBuilder, data: any, key: any, f: Enc.FieldDef
     } else {
         const val = f.value(key, data);
         const t = f.type;
-        if (t === Enc.FieldType.Str) {
+        if (t === CIFField.Type.Str) {
             if (isMultiline(val as string)) {
                 writeMultiline(builder, val as string);
                 return true;
             } else {
                 return writeChecked(builder, val as string);
             }
-        } else if (t === Enc.FieldType.Int) {
+        } else if (t === CIFField.Type.Int) {
             writeInteger(builder, val as number);
         } else {
             writeFloat(builder, val as number, floatPrecision);
@@ -85,42 +86,42 @@ function writeValue(builder: StringBuilder, data: any, key: any, f: Enc.FieldDef
     return false;
 }
 
-function getFloatPrecisions(cat: Enc.CategoryInstance) {
+function getFloatPrecisions(cat: CIFCategory) {
     const ret: number[] = [];
-    for (const f of cat.definition.fields) {
-        ret[ret.length] = f.type === Enc.FieldType.Float ? Math.pow(10, typeof f.digitCount === 'undefined' ? 6 : f.digitCount) : 0;
+    for (const f of cat.fields) {
+        ret[ret.length] = f.type === CIFField.Type.Float ? Math.pow(10, CIFField.getDigitCount(f)) : 0;
     }
     return ret;
 }
 
-function writeCifSingleRecord(category: Enc.CategoryInstance<any>, builder: StringBuilder) {
-    const fields = category.definition.fields;
+function writeCifSingleRecord(category: CIFCategory<any>, builder: StringBuilder) {
+    const fields = category.fields;
     const data = category.data;
-    const width = fields.reduce((w, s) => Math.max(w, s.name.length), 0) + category.definition.name.length + 6;
+    const width = fields.reduce((w, s) => Math.max(w, s.name.length), 0) + category.name.length + 6;
 
-    const it = category.keys();
+    const it = category.keys ? category.keys() : Iterator.Range(0, category.rowCount - 1);
     const key = it.move();
 
     const precisions = getFloatPrecisions(category);
 
     for (let _f = 0; _f < fields.length; _f++) {
         const f = fields[_f];
-        StringBuilder.writePadRight(builder, `_${category.definition.name}.${f.name}`, width);
+        StringBuilder.writePadRight(builder, `_${category.name}.${f.name}`, width);
         const multiline = writeValue(builder, data, key, f, precisions[_f]);
         if (!multiline) StringBuilder.newline(builder);
     }
     StringBuilder.write(builder, '#\n');
 }
 
-function writeCifLoop(categories: Enc.CategoryInstance[], builder: StringBuilder) {
+function writeCifLoop(categories: CIFCategory[], builder: StringBuilder) {
     const first = categories[0];
-    const fields = first.definition.fields;
+    const fields = first.fields;
     const fieldCount = fields.length;
     const precisions = getFloatPrecisions(first);
 
     writeLine(builder, 'loop_');
     for (let i = 0; i < fieldCount; i++) {
-        writeLine(builder, `_${first.definition.name}.${fields[i].name}`);
+        writeLine(builder, `_${first.name}.${fields[i].name}`);
     }
 
     for (let _c = 0; _c < categories.length; _c++) {
@@ -129,7 +130,7 @@ function writeCifLoop(categories: Enc.CategoryInstance[], builder: StringBuilder
 
         if (category.rowCount === 0) continue;
 
-        const it = category.keys();
+        const it = category.keys ? category.keys() : Iterator.Range(0, category.rowCount - 1);
         while (it.hasNext)  {
             const key = it.move();
 

+ 39 - 98
src/mol-model/structure/export/mmcif.ts

@@ -5,9 +5,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Column } from 'mol-data/db'
-import Iterator from 'mol-data/iterator'
-import * as Encoder from 'mol-io/writer/cif'
+import { CIFEncoder, createCIFEncoder, CIFCategory, CIFField } from 'mol-io/writer/cif'
 // import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
 import { Structure, Element } from '../structure'
 import { Model } from '../model'
@@ -18,108 +16,51 @@ interface Context {
     model: Model
 }
 
-function str<K, D>(name: string, value: (k: K, d: D) => string, valueKind?: (k: K) => Column.ValueKind): Encoder.FieldDefinition<K, D> {
-    return { name, type: Encoder.FieldType.Str, value, valueKind }
+const atom_site_fields: CIFField<Element.Location>[] = [
+    CIFField.str('group_PDB', P.residue.group_PDB),
+    CIFField.int('id', P.atom.id),
+    CIFField.str('type_symbol', P.atom.type_symbol as any),
+    CIFField.str('label_atom_id', P.atom.label_atom_id),
+    CIFField.str('label_alt_id', P.atom.label_alt_id),
+
+    CIFField.str('label_comp_id', P.residue.label_comp_id),
+    CIFField.int('label_seq_id', P.residue.label_seq_id),
+    CIFField.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code),
+
+    CIFField.str('label_asym_id', P.chain.label_asym_id),
+    CIFField.str('label_entity_id', P.chain.label_entity_id),
+
+    CIFField.float('Cartn_x', P.atom.x),
+    CIFField.float('Cartn_y', P.atom.y),
+    CIFField.float('Cartn_z', P.atom.z),
+    CIFField.float('occupancy', P.atom.occupancy),
+    CIFField.int('pdbx_formal_charge', P.atom.pdbx_formal_charge),
+
+    CIFField.str('auth_atom_id', P.atom.auth_atom_id),
+    CIFField.str('auth_comp_id', P.residue.auth_comp_id),
+    CIFField.int('auth_seq_id', P.residue.auth_seq_id),
+    CIFField.str('auth_asym_id', P.chain.auth_asym_id),
+
+    CIFField.int('pdbx_PDB_model_num', P.unit.model_num),
+    CIFField.str('operator_name', P.unit.operator_name)
+];
+
+function entityProvider({ model }: Context): CIFCategory {
+    return CIFCategory.ofTable('entity', model.entities.data);
 }
 
-function int<K, D = any>(name: string, value: (k: K, d: D) => number, valueKind?: (k: K) => Column.ValueKind): Encoder.FieldDefinition<K, D> {
-    return { name, type: Encoder.FieldType.Int, value, valueKind }
-}
-
-function float<K, D = any>(name: string, value: (k: K, d: D) => number, valueKind?: (k: K) => Column.ValueKind): Encoder.FieldDefinition<K, D> {
-    return { name, type: Encoder.FieldType.Float, value, valueKind, digitCount: 3 }
-}
-
-// function col<K, D>(name: string, c: (data: D) => Column<any>): Encoder.FieldDefinition<K, D> {
-//     const kind = c['@type'].kind;
-//     // TODO: matrix/vector/support
-//     const type = kind === 'str' ? Encoder.FieldType.Str : kind === 'int' ? Encoder.FieldType.Int : Encoder.FieldType.Float
-//     return { name, type, value, valueKind }
-// }
-
-// type Entity = Table.Columns<typeof mmCIF_Schema.entity>
-
-// const entity: Encoder.CategoryDefinition<number, Entity> = {
-//     name: 'entity',
-//     fields: ofSchema(mmCIF_Schema.entity)
-// }
-
-// [
-//     str('id', (i, e) => e.id.value(i)),
-//     str('type', (i, e) => e.type.value(i)),
-//     str('src_method', (i, e) => e.src_method.value(i)),
-//     str('pdbx_description', (i, e) => e.pdbx_description.value(i)),
-//     int('formula_weight', (i, e) => e.formula_weight.value(i)),
-//     float('pdbx_number_of_molecules', (i, e) => e.pdbx_number_of_molecules.value(i)),
-//     str('details', (i, e) => e.details.value(i)),
-//     str('pdbx_mutation', (i, e) => e.pdbx_mutation.value(i)),
-//     str('pdbx_fragment', (i, e) => e.pdbx_fragment.value(i)),
-//     str('pdbx_ec', (i, e) => e.pdbx_ec.value(i)),
-// ]
-
-// type AtomSite = typeof mmCIF_Schema.atom_site;
-// type DataSource<Key, Schema extends Table.Schema> = { [P in keyof Schema]: (key: Key) => Schema[P]['T'] }
-
-// export const atom_site1: Partial<DataSource<Atom.Location, AtomSite>> = {
-//     group_PDB: P.residue.group_PDB,
-//     id: P.atom.id,
-//     type_symbol: P.atom.type_symbol as any,
-//     label_atom_id: P.atom.label_atom_id,
-//     //...
-// }
-
-const atom_site: Encoder.CategoryDefinition<Element.Location> = {
-    name: 'atom_site',
-    fields: [
-        str('group_PDB', P.residue.group_PDB),
-        int('id', P.atom.id),
-        str('type_symbol', P.atom.type_symbol as any),
-        str('label_atom_id', P.atom.label_atom_id),
-        str('label_alt_id', P.atom.label_alt_id),
-
-        str('label_comp_id', P.residue.label_comp_id),
-        int('label_seq_id', P.residue.label_seq_id),
-        str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code),
-
-        str('label_asym_id', P.chain.label_asym_id),
-        str('label_entity_id', P.chain.label_entity_id),
-
-        float('Cartn_x', P.atom.x),
-        float('Cartn_y', P.atom.y),
-        float('Cartn_z', P.atom.z),
-        float('occupancy', P.atom.occupancy),
-        int('pdbx_formal_charge', P.atom.pdbx_formal_charge),
-
-        str('auth_atom_id', P.atom.auth_atom_id),
-        str('auth_comp_id', P.residue.auth_comp_id),
-        int('auth_seq_id', P.residue.auth_seq_id),
-        str('auth_asym_id', P.chain.auth_asym_id),
-
-        int('pdbx_PDB_model_num', P.unit.model_num),
-        str('operator_name', P.unit.operator_name)
-    ]
-};
-
-function entityProvider({ model }: Context): Encoder.CategoryInstance {
-    return {
-        data: model.entities.data,
-        definition: Encoder.CategoryDefinition.ofTable('entity', model.entities.data),
-        keys: () => Iterator.Range(0, model.entities.data._rowCount - 1),
-        rowCount: model.entities.data._rowCount
-    }
-}
-
-function atomSiteProvider({ structure }: Context): Encoder.CategoryInstance {
+function atomSiteProvider({ structure }: Context): CIFCategory {
     return {
         data: void 0,
-        definition: atom_site,
-        keys: () => structure.elementLocations(),
-        rowCount: structure.elementCount
+        name: 'atom_site',
+        fields: atom_site_fields,
+        rowCount: structure.elementCount,
+        keys: () => structure.elementLocations()
     }
 }
 
 /** Doesn't start a data block */
-export function encode_mmCIF_categories(encoder: Encoder.EncoderInstance, structure: Structure) {
+export function encode_mmCIF_categories(encoder: CIFEncoder, structure: Structure) {
     const models = Structure.getModels(structure);
     if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
     const model = models[0];
@@ -130,7 +71,7 @@ export function encode_mmCIF_categories(encoder: Encoder.EncoderInstance, struct
 }
 
 function to_mmCIF(name: string, structure: Structure, asBinary = false) {
-    const w = Encoder.create({ binary: asBinary });
+    const w = createCIFEncoder({ binary: asBinary });
     w.startDataBlock(name);
     encode_mmCIF_categories(w, structure);
     return w.getData();

+ 17 - 40
src/perf-tests/cif-encoder.ts

@@ -4,55 +4,32 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Iterator from 'mol-data/iterator'
-import * as Enc from 'mol-io/writer/cif'
+ import { CIFCategory, CIFField, createCIFEncoder } from 'mol-io/writer/cif'
 
-const category1: Enc.CategoryDefinition<number> = {
-    name: 'test',
-    fields: [{
-        name: 'f1',
-        type: Enc.FieldType.Str,
-        value: i => 'v' + i
-    }, {
-        name: 'f2',
-        type: Enc.FieldType.Int,
-        value: i => i * i
-    }, {
-        name: 'f3',
-        type: Enc.FieldType.Float,
-        value: i => Math.random()
-    }]
-}
+const category1fields: CIFField[] = [
+    CIFField.str('f1', i => 'v' + i),
+    CIFField.int('f2', i => i * i),
+    CIFField.float('f3', i => Math.random()),
+];
 
-const category2: Enc.CategoryDefinition<number> = {
-    name: 'test2',
-    fields: [{
-        name: 'e1',
-        type: Enc.FieldType.Str,
-        value: i => 'v\n' + i
-    }, {
-        name: 'e2',
-        type: Enc.FieldType.Int,
-        value: i => i * i
-    }, {
-        name: 'e3',
-        type: Enc.FieldType.Float,
-        value: i => Math.random()
-    }]
-}
+const category2fields: CIFField[] = [
+    CIFField.str('e1', i => 'v\n' + i),
+    CIFField.int('e2', i => i * i),
+    CIFField.float('e3', i => Math.random()),
+];
 
-function getInstance(ctx: { cat: Enc.CategoryDefinition<number>, rowCount: number }): Enc.CategoryInstance {
+function getInstance(ctx: { name: string, fields: CIFField[], rowCount: number }): CIFCategory {
     return {
         data: void 0,
-        definition: ctx.cat,
-        keys: () => Iterator.Range(0, ctx.rowCount - 1),
+        name: ctx.name,
+        fields: ctx.fields,
         rowCount: ctx.rowCount
     }
 }
 
-const w = Enc.create();
+const w = createCIFEncoder();
 
 w.startDataBlock('test');
-w.writeCategory(getInstance, [{ rowCount: 5, cat: category1 }]);
-w.writeCategory(getInstance, [{ rowCount: 1, cat: category2 }]);
+w.writeCategory(getInstance, [{ rowCount: 5, name: 'cat1', fields: category1fields }]);
+w.writeCategory(getInstance, [{ rowCount: 1, name: 'cat2', fields: category2fields  }]);
 console.log(w.getData());

+ 19 - 23
src/servers/model/server/query.ts

@@ -11,12 +11,11 @@ import Config from '../config';
 import { Progress, now } from 'mol-task';
 import { ConsoleLogger } from 'mol-util/console-logger';
 import Writer from 'mol-io/writer/writer';
-import * as Encoder from 'mol-io/writer/cif'
+import { CIFCategory, CIFField, createCIFEncoder } from 'mol-io/writer/cif'
 import { encode_mmCIF_categories } from 'mol-model/structure/export/mmcif';
 import { Selection } from 'mol-model/structure';
 import Version from '../version'
 import { Column } from 'mol-data/db';
-import { Iterator } from 'mol-data';
 import { PerformanceMonitor } from 'mol-util/performance-monitor';
 
 export interface ResponseFormat {
@@ -75,7 +74,7 @@ export async function resolveRequest(req: Request, writer: Writer) {
 
     ConsoleLogger.logId(req.id, 'Query', 'Query finished.');
 
-    const encoder = Encoder.create({ binary: req.responseFormat.isBinary, encoderName: `ModelServer ${Version}` });
+    const encoder = createCIFEncoder({ binary: req.responseFormat.isBinary, encoderName: `ModelServer ${Version}` });
 
     perf.start('encode');
     encoder.startDataBlock('result');
@@ -106,21 +105,18 @@ export function abortingObserver(p: Progress) {
     }
 }
 
-type FieldDesc<T> = Encoder.FieldDefinition<number, T>
-type CategoryInstance = Encoder.CategoryInstance
-
-function string<T>(name: string, str: (data: T, i: number) => string, isSpecified?: (data: T) => boolean): FieldDesc<T> {
+function string<T>(name: string, str: (data: T, i: number) => string, isSpecified?: (data: T) => boolean): CIFField<number, T> {
     if (isSpecified) {
-        return { name, type: Encoder.FieldType.Str, value: (i, d) => str(d, i), valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent };
+        return CIFField.str(name,  (i, d) => str(d, i), { valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent });
     }
-    return { name, type: Encoder.FieldType.Str, value: (i, d) => str(d, i) };
+    return CIFField.str(name,  (i, d) => str(d, i));
 }
 
-function int32<T>(name: string, value: (data: T) => number): FieldDesc<T> {
-    return { name, type: Encoder.FieldType.Int, value: (i, d) => value(d) };
+function int32<T>(name: string, value: (data: T) => number): CIFField<number, T> {
+    return CIFField.int(name, (i, d) => value(d));
 }
 
-const _model_server_result_fields: FieldDesc<Request>[] = [
+const _model_server_result_fields: CIFField<number, Request>[] = [
     string<Request>('request_id', ctx => '' + ctx.id),
     string<Request>('datetime_utc', ctx => ctx.datetime_utc),
     string<Request>('server_version', ctx => Version),
@@ -129,12 +125,12 @@ const _model_server_result_fields: FieldDesc<Request>[] = [
     string<Request>('entry_id', ctx => ctx.entryId),
 ];
 
-const _model_server_params_fields: FieldDesc<string[]>[] = [
+const _model_server_params_fields: CIFField<number, string[]>[] = [
     string<string[]>('name', (ctx, i) => ctx[i][0]),
     string<string[]>('value', (ctx, i) => ctx[i][1])
 ];
 
-const _model_server_stats_fields: FieldDesc<Stats>[] = [
+const _model_server_stats_fields: CIFField<number, Stats>[] = [
     int32<Stats>('io_time_ms', ctx => ctx.structure.info.readTime | 0),
     int32<Stats>('parse_time_ms', ctx => ctx.structure.info.parseTime | 0),
     int32<Stats>('create_model_time_ms', ctx => ctx.structure.info.createModelTime | 0),
@@ -143,33 +139,33 @@ const _model_server_stats_fields: FieldDesc<Stats>[] = [
 ];
 
 
-function _model_server_result(request: Request): CategoryInstance {
+function _model_server_result(request: Request): CIFCategory {
     return {
         data: request,
-        definition: { name: 'model_server_result', fields: _model_server_result_fields },
-        keys: () => Iterator.Value(0),
+        name: 'model_server_result',
+        fields: _model_server_result_fields,
         rowCount: 1
     };
 }
 
-function _model_server_params(request: Request): CategoryInstance {
+function _model_server_params(request: Request): CIFCategory {
     const params: string[][] = [];
     for (const k of Object.keys(request.normalizedParams)) {
         params.push([k, '' + request.normalizedParams[k]]);
     }
     return {
         data: params,
-        definition: { name: 'model_server_params', fields: _model_server_params_fields },
-        keys: () => Iterator.Range(0, params.length - 1),
+        name: 'model_server_params',
+        fields: _model_server_params_fields,
         rowCount: params.length
     };
 }
 
-function _model_server_stats(stats: Stats): CategoryInstance {
+function _model_server_stats(stats: Stats): CIFCategory {
     return {
         data: stats,
-        definition: { name: 'model_server_stats', fields: _model_server_stats_fields },
-        keys: () => Iterator.Value(0),
+        name: 'model_server_stats',
+        fields: _model_server_stats_fields,
         rowCount: 1
     };
 }

+ 18 - 29
src/servers/volume/server/query/encode.ts

@@ -6,17 +6,16 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import * as Encoder from 'mol-io/writer/cif'
+import { CIFEncoder, CIFCategory, CIFField, createCIFEncoder } from 'mol-io/writer/cif'
 import * as Data from './data-model'
 import * as Coords from '../algebra/coordinate'
 import VERSION from '../version'
 import * as DataFormat from '../../common/data-format'
 import { Column } from 'mol-data/db';
-import { Iterator } from 'mol-data';
 import { ArrayEncoding, ArrayEncoder } from 'mol-io/common/binary-cif';
 
 export default function encode(query: Data.QueryContext, output: Data.QueryOutputStream) {
-    let w = Encoder.create({ binary: query.params.asBinary, encoderName: `VolumeServer ${VERSION}` });
+    let w = createCIFEncoder({ binary: query.params.asBinary, encoderName: `VolumeServer ${VERSION}` });
     write(w, query);
     w.encode();
     w.writeTo(output);
@@ -27,26 +26,21 @@ interface ResultContext {
     channelIndex: number
 }
 
-//type Writer = CIF.Writer<ResultContext | Data.QueryContext>
+type FieldDesc<T> = CIFField<number, T>
 
-type FieldDesc<T> = Encoder.FieldDefinition<number, T>
-type CategoryInstance = Encoder.CategoryInstance
-
-//import E = CIF.Binary.Encoder
-
-function string<T>(name: string, str: (data: T) => string, isSpecified?: (data: T) => boolean): FieldDesc<T> {
+function string<T>(name: string, str: (data: T) => string, isSpecified?: (data: T) => boolean): CIFField<number, T> {
     if (isSpecified) {
-        return { name, type: Encoder.FieldType.Str, value: (i, d) => str(d), valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent };
+        return CIFField.str(name,  (i, d) => str(d), { valueKind: (i, d) => isSpecified(d) ? Column.ValueKind.Present : Column.ValueKind.NotPresent });
     }
-    return { name, type: Encoder.FieldType.Str, value: (i, d) => str(d) };
+    return CIFField.str(name,  (i, d) => str(d));
 }
 
-function int32<T>(name: string, value: (data: T) => number): FieldDesc<T> {
-    return { name, type: Encoder.FieldType.Int, value: (i, d) => value(d) };
+function int32<T>(name: string, value: (data: T) => number): CIFField<number, T> {
+    return CIFField.int(name, (i, d) => value(d));
 }
 
 function float64<T>(name: string, value: (data: T) => number, digitCount: number = 6): FieldDesc<T> {
-    return { name, type: Encoder.FieldType.Float, value: (i, d) => value(d), digitCount };
+    return CIFField.float(name, (i, d) => value(d), { digitCount: digitCount, typedArray: Float64Array });
 }
 
 interface _vd3d_Ctx {
@@ -98,7 +92,7 @@ const _volume_data_3d_info_fields: FieldDesc<_vd3d_Ctx>[] = [
     float64<_vd3d_Ctx>('max_sampled', ctx => ctx.sampledValuesInfo.max)
 ];
 
-function _volume_data_3d_info(result: ResultContext): CategoryInstance {
+function _volume_data_3d_info(result: ResultContext): CIFCategory {
     const ctx: _vd3d_Ctx = {
         header: result.query.data.header,
         channelIndex: result.channelIndex,
@@ -110,8 +104,8 @@ function _volume_data_3d_info(result: ResultContext): CategoryInstance {
 
     return {
         data: ctx,
-        definition: { name: 'volume_data_3d_info', fields: _volume_data_3d_info_fields },
-        keys: () => Iterator.Value(0),
+        name: 'volume_data_3d_info',
+        fields: _volume_data_3d_info_fields,
         rowCount: 1
     };
 }
@@ -143,14 +137,9 @@ function _volume_data_3d(ctx: ResultContext) {
         encoder = E.by(E.byteArray)
     }
 
-    let fields: FieldDesc<typeof data>[] = [{ name: 'values', type: Encoder.FieldType.Float, value: _volume_data_3d_number, encoder, typedArray, digitCount: 6 }];
+    const fields: FieldDesc<typeof data>[] = [CIFField.float('values', _volume_data_3d_number, { encoder, typedArray, digitCount: 6 })]
 
-    return {
-        data,
-        definition: { name: 'volume_data_3d', fields },
-        keys: () => Iterator.Range(0, data.length - 1),
-        rowCount: data.length
-    };
+    return { data, name: 'volume_data_3d', fields, rowCount: data.length };
 }
 
 function pickQueryBoxDimension(ctx: Data.QueryContext, e: 'a' | 'b', d: number) {
@@ -185,16 +174,16 @@ const _density_server_result_fields: FieldDesc<Data.QueryContext>[] = [
     queryBoxDimension('b', 2)
 ]
 
-function _density_server_result(ctx: Data.QueryContext) {
+function _density_server_result(ctx: Data.QueryContext): CIFCategory {
     return {
         data: ctx,
-        definition: { name: 'density_server_result', fields: _density_server_result_fields },
-        keys: () => Iterator.Value(0),
+        name: 'density_server_result',
+        fields: _density_server_result_fields,
         rowCount: 1
     };
 }
 
-function write(encoder: Encoder.EncoderInstance, query: Data.QueryContext) {
+function write(encoder: CIFEncoder, query: Data.QueryContext) {
     encoder.startDataBlock('SERVER');
     encoder.writeCategory(_density_server_result, [query]);