From f63a2b6e93e7a744d3e14b0787842c7f9ae9bf13 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 5 Nov 2019 19:45:53 +0300
Subject: [PATCH 01/41] Some serialization fixes

---
 build.gradle.kts                              | 17 +++++++-----
 .../hep/dataforge/context/ContextBuilder.kt   |  2 ++
 .../kotlin/hep/dataforge/data/MapAction.kt    |  6 ++---
 .../io/serialization/serializationUtils.kt    | 26 ++++++++++++++-----
 .../hep/dataforge/output/html/HtmlOutput.kt   |  2 +-
 .../hep/dataforge/workspace/TaskBuilder.kt    |  3 ++-
 .../hep/dataforge/workspace/TaskModel.kt      |  3 ---
 7 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 04cce594..cd3e5d61 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,10 +1,12 @@
+import scientifik.ScientifikExtension
+
 plugins {
-    id("scientifik.mpp") version "0.2.1" apply false
-    id("scientifik.jvm") version "0.2.1" apply false
-    id("scientifik.publish") version "0.2.1" apply false
+    id("scientifik.mpp") version "0.2.2" apply false
+    id("scientifik.jvm") version "0.2.2" apply false
+    id("scientifik.publish") version "0.2.2" apply false
 }
 
-val dataforgeVersion by extra("0.1.4")
+val dataforgeVersion by extra("0.1.5-dev-1")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
@@ -15,7 +17,8 @@ allprojects {
 }
 
 subprojects {
-    if (name.startsWith("dataforge")) {
-        apply(plugin = "scientifik.publish")
-    } 
+    apply(plugin = "scientifik.publish")
+    afterEvaluate {
+        extensions.findByType<ScientifikExtension>()?.apply { withDokka() }
+    }
 }
\ No newline at end of file
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
index 58a03554..0aed9b7f 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
@@ -1,5 +1,6 @@
 package hep.dataforge.context
 
+import hep.dataforge.meta.DFBuilder
 import hep.dataforge.meta.MetaBuilder
 import hep.dataforge.meta.buildMeta
 import hep.dataforge.names.toName
@@ -7,6 +8,7 @@ import hep.dataforge.names.toName
 /**
  * A convenience builder for context
  */
+@DFBuilder
 class ContextBuilder(var name: String = "@anonymous", val parent: Context = Global) {
     private val plugins = ArrayList<Plugin>()
     private var meta = MetaBuilder()
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt
index 8c543927..89e887db 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt
@@ -1,9 +1,6 @@
 package hep.dataforge.data
 
-import hep.dataforge.meta.Meta
-import hep.dataforge.meta.MetaBuilder
-import hep.dataforge.meta.builder
-import hep.dataforge.meta.seal
+import hep.dataforge.meta.*
 import hep.dataforge.names.Name
 import kotlin.reflect.KClass
 
@@ -20,6 +17,7 @@ data class ActionEnv(
 /**
  * Action environment
  */
+@DFBuilder
 class MapActionBuilder<T, R>(var name: Name, var meta: MetaBuilder, val actionMeta: Meta) {
     lateinit var result: suspend ActionEnv.(T) -> R
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
index 09d17054..b32abb14 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
@@ -1,9 +1,7 @@
 package hep.dataforge.io.serialization
 
-import kotlinx.serialization.CompositeDecoder
-import kotlinx.serialization.Decoder
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.SerialDescriptor
+import hep.dataforge.meta.DFExperimental
+import kotlinx.serialization.*
 import kotlinx.serialization.internal.*
 
 /**
@@ -71,10 +69,24 @@ inline fun <reified T : Any> KSerializer<T>.descriptor(
 ): SerialDescriptor =
     SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
 
-fun Decoder.decodeStructure(
+@DFExperimental
+inline fun <R> Decoder.decodeStructure(
     desc: SerialDescriptor,
     vararg typeParams: KSerializer<*> = emptyArray(),
-    block: CompositeDecoder.() -> Unit
+    crossinline block:  CompositeDecoder.() -> R
+): R {
+    val decoder = beginStructure(desc, *typeParams)
+    val res = decoder.block()
+    decoder.endStructure(desc)
+    return res
+}
+
+inline fun Encoder.encodeStructure(
+    desc: SerialDescriptor,
+    vararg typeParams: KSerializer<*> = emptyArray(),
+    block: CompositeEncoder.() -> Unit
 ) {
-    beginStructure(desc, *typeParams).apply(block).endStructure(desc)
+    val encoder = beginStructure(desc, *typeParams)
+    encoder.block()
+    encoder.endStructure(desc)
 }
\ No newline at end of file
diff --git a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt
index b54b7eb7..3ff23403 100644
--- a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt
+++ b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt
@@ -47,7 +47,7 @@ class HtmlOutput<T : Any>(override val context: Context, private val consumer: T
 }
 
 /**
- * A text or binary renderer based on [kotlinx.io.core.Output]
+ * A text or binary renderer based on [Output]
  */
 @Type(HTML_CONVERTER_TYPE)
 interface HtmlBuilder<T : Any> {
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
index 80d89e24..3ff86325 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
@@ -3,6 +3,7 @@ package hep.dataforge.workspace
 import hep.dataforge.context.Context
 import hep.dataforge.data.*
 import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.DFBuilder
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.string
@@ -13,7 +14,7 @@ import hep.dataforge.names.toName
 import kotlin.jvm.JvmName
 import kotlin.reflect.KClass
 
-@TaskBuildScope
+@DFBuilder
 class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) {
     private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { allData() }
 //    private val additionalDependencies = HashSet<Dependency>()
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
index b4ccb7ae..71a5c006 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
@@ -58,9 +58,6 @@ fun TaskModel.buildInput(workspace: Workspace): DataTree<Any> {
     }.build()
 }
 
-@DslMarker
-annotation class TaskBuildScope
-
 interface TaskDependencyContainer {
     val defaultMeta: Meta
     fun add(dependency: Dependency)

From 5265c0e5abe2dd26a64fc90f91ff862eaa30b669 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 5 Nov 2019 21:36:17 +0300
Subject: [PATCH 02/41] Implements Envelope parts

An envelope consisting of other envelopes
---
 .../kotlin/hep/dataforge/io/Envelope.kt       | 37 ++----------
 .../hep/dataforge/io/EnvelopeBuilder.kt       | 36 +++++++++++
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt  | 60 +++++++++++++++++++
 .../kotlin/hep/dataforge/io/IOPlugin.kt       |  3 +
 .../hep/dataforge/io/EnvelopePartsTest.kt     | 32 ++++++++++
 5 files changed, 135 insertions(+), 33 deletions(-)
 create mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
 create mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
 create mode 100644 dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
index 80e07b56..61aeb4d2 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
@@ -1,11 +1,11 @@
 package hep.dataforge.io
 
-import hep.dataforge.meta.*
+import hep.dataforge.meta.Laminate
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.get
+import hep.dataforge.meta.string
 import hep.dataforge.names.asName
 import hep.dataforge.names.plus
-import kotlinx.io.core.Output
-import kotlinx.io.core.buildPacket
-import kotlinx.io.core.readBytes
 
 interface Envelope {
     val meta: Meta
@@ -83,32 +83,3 @@ fun Envelope.withMetaLayers(vararg layers: Meta): Envelope {
     }
 }
 
-class EnvelopeBuilder {
-    private val metaBuilder = MetaBuilder()
-    var data: Binary? = null
-
-    fun meta(block: MetaBuilder.() -> Unit) {
-        metaBuilder.apply(block)
-    }
-
-    fun meta(meta: Meta) {
-        metaBuilder.update(meta)
-    }
-
-    var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY)
-    var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY)
-    var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY)
-    var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY)
-
-    /**
-     * Construct a binary and transform it into byte-array based buffer
-     */
-    fun data(block: Output.() -> Unit) {
-        val bytes = buildPacket {
-            block()
-        }
-        data = ArrayBinary(bytes.readBytes())
-    }
-
-    internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
-}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
new file mode 100644
index 00000000..b8d0b660
--- /dev/null
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
@@ -0,0 +1,36 @@
+package hep.dataforge.io
+
+import hep.dataforge.meta.*
+import kotlinx.io.core.Output
+import kotlinx.io.core.buildPacket
+import kotlinx.io.core.readBytes
+
+class EnvelopeBuilder {
+    private val metaBuilder = MetaBuilder()
+    var data: Binary? = null
+
+    fun meta(block: MetaBuilder.() -> Unit) {
+        metaBuilder.apply(block)
+    }
+
+    fun meta(meta: Meta) {
+        metaBuilder.update(meta)
+    }
+
+    var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY)
+    var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY)
+    var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY)
+    var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY)
+
+    /**
+     * Construct a binary and transform it into byte-array based buffer
+     */
+    fun data(block: Output.() -> Unit) {
+        val bytes = buildPacket {
+            block()
+        }
+        data = ArrayBinary(bytes.readBytes())
+    }
+
+    internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
+}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
new file mode 100644
index 00000000..ca4efea4
--- /dev/null
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -0,0 +1,60 @@
+package hep.dataforge.io
+
+import hep.dataforge.context.Global
+import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY
+import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY
+import hep.dataforge.io.EnvelopeParts.PARTS_DATA_TYPE
+import hep.dataforge.io.EnvelopeParts.SIZE_KEY
+import hep.dataforge.meta.*
+import hep.dataforge.names.plus
+import hep.dataforge.names.toName
+
+object EnvelopeParts {
+    val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "size"
+    val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "format"
+    val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "meta"
+
+    const val PARTS_DATA_TYPE = "envelope.parts"
+}
+
+fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Collection<Envelope>) {
+    dataType = PARTS_DATA_TYPE
+    meta {
+        SIZE_KEY put envelopes.size
+        FORMAT_NAME_KEY put formatFactory.name.toString()
+    }
+    val format = formatFactory()
+    data {
+        format.run {
+            envelopes.forEach {
+                writeObject(it)
+            }
+        }
+    }
+}
+
+fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) =
+    parts(formatFactory, sequence(builder).toList())
+
+fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope> {
+    return when (dataType) {
+        PARTS_DATA_TYPE -> {
+            val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet")
+            val formatName = meta[FORMAT_NAME_KEY].string?.toName()
+                ?: error("Inferring parts format is not supported at the moment")
+            val formatMeta = meta[FORMAT_META_KEY].node ?: EmptyMeta
+            val format = io.envelopeFormat(formatName, formatMeta)
+                ?: error("Format $formatName is not resolved by $io")
+            return format.run {
+                data?.read {
+                    sequence {
+                        repeat(size) {
+                            yield(readObject())
+                        }
+                    }
+                } ?: emptySequence()
+            }
+        }
+        else -> emptySequence()
+    }
+}
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
index 7e61924f..ae88e4f4 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
@@ -26,6 +26,9 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
         context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values
     }
 
+    fun envelopeFormat(name: Name, meta: Meta = EmptyMeta) =
+        envelopeFormatFactories.find { it.name == name }?.invoke(meta, context)
+
     override fun provideTop(target: String): Map<Name, Any> {
         return when (target) {
             META_FORMAT_TYPE -> defaultMetaFormats.toMap()
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
new file mode 100644
index 00000000..5122680a
--- /dev/null
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
@@ -0,0 +1,32 @@
+package hep.dataforge.io
+
+import hep.dataforge.meta.get
+import hep.dataforge.meta.int
+import kotlinx.io.core.writeText
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class EnvelopePartsTest {
+    val envelopes = (0..5).map {
+        Envelope {
+            meta {
+                "value" put it
+            }
+            data {
+                writeText("Hello World $it")
+            }
+        }
+    }
+    val partsEnvelope = Envelope {
+        parts(TaggedEnvelopeFormat, envelopes)
+    }
+
+    @Test
+    fun testParts() {
+        val bytes = TaggedEnvelopeFormat.default.writeBytes(partsEnvelope)
+        val reconstructed = TaggedEnvelopeFormat.default.readBytes(bytes)
+        val parts = reconstructed.parts().toList()
+        assertEquals(2, parts[2].meta["value"].int)
+    }
+
+}
\ No newline at end of file

From ff59c14c173d575b4e193e42d06a73b82fff1ccb Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 5 Nov 2019 21:42:29 +0300
Subject: [PATCH 03/41] Minor fix to multipart envelope

---
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt           | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index ca4efea4..0a8a207d 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -17,6 +17,9 @@ object EnvelopeParts {
     const val PARTS_DATA_TYPE = "envelope.parts"
 }
 
+/**
+ * Append multiple serialized envelopes to the data block. Previous data is erased if it was present
+ */
 fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Collection<Envelope>) {
     dataType = PARTS_DATA_TYPE
     meta {
@@ -36,7 +39,10 @@ fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Colle
 fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) =
     parts(formatFactory, sequence(builder).toList())
 
-fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope> {
+/**
+ * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null.
+ */
+fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? {
     return when (dataType) {
         PARTS_DATA_TYPE -> {
             val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet")
@@ -55,6 +61,6 @@ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Enve
                 } ?: emptySequence()
             }
         }
-        else -> emptySequence()
+        else -> null
     }
 }

From 3e9cb3915cdd1e81bad42483fdb48d5af699f441 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 10 Nov 2019 19:08:32 +0300
Subject: [PATCH 04/41] Meta and Evelope format factories now implement Meta
 and Envelope formats (default representation).

---
 .../kotlin/hep/dataforge/data/DataNode.kt     |  20 ++--
 .../io/yaml/FrontMatterEnvelopeFormat.kt      |  17 ++-
 .../hep/dataforge/io/yaml/YamlMetaFormat.kt   |  10 +-
 .../kotlin/hep/dataforge/io/Binary.kt         |  21 ++--
 .../kotlin/hep/dataforge/io/EnvelopeFormat.kt |   2 +-
 .../kotlin/hep/dataforge/io/JsonMetaFormat.kt |  12 ++-
 .../kotlin/hep/dataforge/io/MetaFormat.kt     |   4 +-
 .../hep/dataforge/io/TaggedEnvelopeFormat.kt  |  18 +++-
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt |  23 +++-
 .../hep/dataforge/io/EnvelopeFormatTest.kt    |   4 +-
 .../hep/dataforge/io/EnvelopePartsTest.kt     |   6 +-
 .../kotlin/hep/dataforge/io/functionsJVM.kt   |  29 +++++
 .../kotlin/hep/dataforge/io/ioFormatsJVM.kt   | 100 +++++++++++++-----
 .../kotlin/hep/dataforge/io/FileBinaryTest.kt |   4 +-
 .../dataforge/workspace/WorkspaceBuilder.kt   |   2 +-
 .../hep/dataforge/workspace/fileData.kt       |  24 +----
 16 files changed, 197 insertions(+), 99 deletions(-)
 create mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt

diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
index 12bb06ab..589e875b 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
@@ -64,21 +64,13 @@ val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>
 val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.value
 
 /**
- * Start computation for all goals in data node
+ * Start computation for all goals in data node and return a job for the whole node
  */
-fun DataNode<*>.startAll(scope: CoroutineScope): Unit = items.values.forEach {
-    when (it) {
-        is DataItem.Node<*> -> it.value.startAll(scope)
-        is DataItem.Leaf<*> -> it.value.start(scope)
-    }
-}
-
-fun DataNode<*>.joinAll(scope: CoroutineScope): Job = scope.launch {
-    startAll(scope)
-    items.forEach {
-        when (val value = it.value) {
-            is DataItem.Node -> value.value.joinAll(this).join()
-            is DataItem.Leaf -> value.value.await(scope)
+fun DataNode<*>.launchAll(scope: CoroutineScope): Job = scope.launch {
+    items.values.forEach {
+        when (it) {
+            is DataItem.Node<*> -> it.value.launchAll(scope)
+            is DataItem.Leaf<*> -> it.value.start(scope)
         }
     }
 }
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
index db701625..023635e2 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
@@ -24,7 +24,7 @@ class FrontMatterEnvelopeFormat(
 
         val readMetaFormat =
             metaTypeRegex.matchEntire(line)?.groupValues?.first()
-                ?.let { io.metaFormat(it) } ?: YamlMetaFormat.default
+                ?.let { io.metaFormat(it) } ?: YamlMetaFormat
 
         val metaBlock = buildPacket {
             do {
@@ -45,7 +45,7 @@ class FrontMatterEnvelopeFormat(
 
         val readMetaFormat =
             metaTypeRegex.matchEntire(line)?.groupValues?.first()
-                ?.let { io.metaFormat(it) } ?: YamlMetaFormat.default
+                ?.let { io.metaFormat(it) } ?: YamlMetaFormat
 
         val metaBlock = buildPacket {
             do {
@@ -72,7 +72,7 @@ class FrontMatterEnvelopeFormat(
         private val metaTypeRegex = "---(\\w*)\\s*".toRegex()
 
         override fun invoke(meta: Meta, context: Context): EnvelopeFormat {
-            return FrontMatterEnvelopeFormat(context.io,  meta)
+            return FrontMatterEnvelopeFormat(context.io, meta)
         }
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
@@ -84,5 +84,16 @@ class FrontMatterEnvelopeFormat(
             }
         }
 
+        private val default by lazy { invoke() }
+
+        override fun Input.readPartial(): PartialEnvelope =
+            default.run { readPartial() }
+
+        override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
+            default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
+
+        override fun Input.readObject(): Envelope =
+            default.run { readObject() }
+
     }
 }
\ No newline at end of file
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
index 24ea44ec..7130518d 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
@@ -45,12 +45,18 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
     }
 
     companion object : MetaFormatFactory {
-        val default = YamlMetaFormat()
-
         override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta)
 
         override val name: Name = super.name + "yaml"
 
         override val key: Short = 0x594d //YM
+
+        private val default = YamlMetaFormat()
+
+        override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
+            default.run { writeMeta(meta, descriptor) }
+
+        override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
+            default.run { readMeta(descriptor) }
     }
 }
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
index ca05de4d..bd1f2249 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
@@ -8,7 +8,7 @@ import kotlin.math.min
  */
 interface Binary {
     /**
-     * The size of binary in bytes
+     * The size of binary in bytes. [ULong.MAX_VALUE] if size is not defined and input should be read until its end is reached
      */
     val size: ULong
 
@@ -18,6 +18,10 @@ interface Binary {
      * Some implementation may forbid this to be called twice. In this case second call will throw an exception.
      */
     fun <R> read(block: Input.() -> R): R
+
+    companion object {
+        val EMPTY = EmptyBinary
+    }
 }
 
 /**
@@ -48,12 +52,11 @@ fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read
 @ExperimentalUnsignedTypes
 object EmptyBinary : RandomAccessBinary {
 
-    override val size: ULong = 0.toULong()
+    override val size: ULong = 0u
 
     override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
         error("The binary is empty")
     }
-
 }
 
 @ExperimentalUnsignedTypes
@@ -79,9 +82,9 @@ fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
     }
 }
 
-fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary {
-    val packet = buildPacket {
-        writeObject(obj)
-    }
-    return ArrayBinary(packet.readBytes())
-}
\ No newline at end of file
+//fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary {
+//    val packet = buildPacket {
+//        writeObject(obj)
+//    }
+//    return ArrayBinary(packet.readBytes())
+//}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
index c52b9e1d..4f747ea2 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
@@ -31,7 +31,7 @@ interface EnvelopeFormat : IOFormat<Envelope> {
 }
 
 @Type(ENVELOPE_FORMAT_TYPE)
-interface EnvelopeFormatFactory : IOFormatFactory<Envelope> {
+interface EnvelopeFormatFactory : IOFormatFactory<Envelope>, EnvelopeFormat {
     override val name: Name get() = "envelope".asName()
     override val type: KClass<out Envelope> get() = Envelope::class
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
index a95cdec4..5c10505d 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
@@ -38,12 +38,18 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
     }
 
     companion object : MetaFormatFactory {
-        val default = JsonMetaFormat()
-
         override fun invoke(meta: Meta, context: Context): MetaFormat = default
 
         override val name: Name = super.name + "json"
         override val key: Short = 0x4a53//"JS"
+
+        private val default = JsonMetaFormat()
+
+        override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
+            default.run { writeMeta(meta,descriptor) }
+
+        override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
+            default.run { readMeta(descriptor) }
     }
 }
 
@@ -90,7 +96,7 @@ fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
 fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
     return when (val item = toMetaItem(descriptor)) {
         is MetaItem.NodeItem<*> -> item.node
-        is MetaItem.ValueItem ->item.value.toMeta()
+        is MetaItem.ValueItem -> item.value.toMeta()
     }
 }
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
index ca9a53a2..9d1af81a 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
@@ -27,7 +27,7 @@ interface MetaFormat : IOFormat<Meta> {
 }
 
 @Type(META_FORMAT_TYPE)
-interface MetaFormatFactory : IOFormatFactory<Meta> {
+interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat {
     override val name: Name get() = "meta".asName()
 
     override val type: KClass<out Meta> get() = Meta::class
@@ -47,7 +47,7 @@ fun Meta.toString(format: MetaFormat): String = buildPacket {
 
 fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory())
 
-fun Meta.toBytes(format: MetaFormat = JsonMetaFormat.default): ByteReadPacket = buildPacket {
+fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): ByteReadPacket = buildPacket {
     format.run { writeObject(this@toBytes) }
 }
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
index cce3eade..5f9164dc 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
@@ -39,7 +39,12 @@ class TaggedEnvelopeFormat(
     override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
         val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
         val metaBytes = metaFormat.writeBytes(envelope.meta)
-        val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, envelope.data?.size ?: 0.toULong())
+        val actualSize: ULong = if (envelope.data == null) {
+            0u
+        } else {
+            envelope.data?.size ?: ULong.MAX_VALUE
+        }
+        val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize)
         writePacket(tag.toBytes())
         writeFully(metaBytes)
         writeText("\r\n")
@@ -134,7 +139,16 @@ class TaggedEnvelopeFormat(
             }
         }
 
-        val default by lazy { invoke() }
+        private val default by lazy { invoke() }
+
+        override fun Input.readPartial(): PartialEnvelope =
+            default.run { readPartial() }
+
+        override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
+            default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
+
+        override fun Input.readObject(): Envelope =
+            default.run { readObject() }
     }
 
 }
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index 14d871db..d3953a0c 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -27,7 +27,13 @@ class TaglessEnvelopeFormat(
         //printing all properties
         writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type)
         //TODO add optional metaFormat properties
-        writeProperty(DATA_LENGTH_PROPERTY, envelope.data?.size ?: 0)
+        val actualSize: ULong = if (envelope.data == null) {
+            0u
+        } else {
+            envelope.data?.size ?: ULong.MAX_VALUE
+        }
+
+        writeProperty(DATA_LENGTH_PROPERTY, actualSize)
 
         //Printing meta
         if (!envelope.meta.isEmpty()) {
@@ -66,7 +72,7 @@ class TaglessEnvelopeFormat(
         var meta: Meta = EmptyMeta
 
         if (line.startsWith(metaStart)) {
-            val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
+            val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
             val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
             meta = if (metaSize != null) {
                 val metaPacket = buildPacket {
@@ -121,7 +127,7 @@ class TaglessEnvelopeFormat(
         var meta: Meta = EmptyMeta
 
         if (line.startsWith(metaStart)) {
-            val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
+            val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
 
             val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
             meta = if (metaSize != null) {
@@ -170,7 +176,16 @@ class TaglessEnvelopeFormat(
             return TaglessEnvelopeFormat(context.io, meta)
         }
 
-        val default by lazy { invoke() }
+        private val default by lazy { invoke() }
+
+        override fun Input.readPartial(): PartialEnvelope =
+            default.run { readPartial() }
+
+        override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
+            default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
+
+        override fun Input.readObject(): Envelope =
+            default.run { readObject() }
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
             return try {
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
index 29e60f2f..37ee827d 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
@@ -18,7 +18,7 @@ class EnvelopeFormatTest {
     @ExperimentalStdlibApi
     @Test
     fun testTaggedFormat(){
-        TaggedEnvelopeFormat.default.run {
+        TaggedEnvelopeFormat.run {
             val bytes = writeBytes(envelope)
             println(bytes.decodeToString())
             val res = readBytes(bytes)
@@ -32,7 +32,7 @@ class EnvelopeFormatTest {
 
     @Test
     fun testTaglessFormat(){
-        TaglessEnvelopeFormat.default.run {
+        TaglessEnvelopeFormat.run {
             val bytes = writeBytes(envelope)
             println(bytes.decodeToString())
             val res = readBytes(bytes)
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
index 5122680a..c33b3179 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
@@ -23,9 +23,9 @@ class EnvelopePartsTest {
 
     @Test
     fun testParts() {
-        val bytes = TaggedEnvelopeFormat.default.writeBytes(partsEnvelope)
-        val reconstructed = TaggedEnvelopeFormat.default.readBytes(bytes)
-        val parts = reconstructed.parts().toList()
+        val bytes = TaggedEnvelopeFormat.writeBytes(partsEnvelope)
+        val reconstructed = TaggedEnvelopeFormat.readBytes(bytes)
+        val parts = reconstructed.parts()?.toList() ?: emptyList()
         assertEquals(2, parts[2].meta["value"].int)
     }
 
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt
new file mode 100644
index 00000000..fae986d7
--- /dev/null
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt
@@ -0,0 +1,29 @@
+package hep.dataforge.io
+
+import hep.dataforge.io.functions.FunctionServer
+import hep.dataforge.io.functions.function
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.buildMeta
+import hep.dataforge.names.Name
+import kotlin.reflect.KClass
+import kotlin.reflect.full.isSuperclassOf
+
+
+fun IOPlugin.resolveIOFormatName(type: KClass<*>): Name {
+    return ioFormats.entries.find { it.value.type.isSuperclassOf(type) }?.key
+        ?: error("Can't resolve IOFormat for type $type")
+}
+
+inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = buildMeta {
+    FunctionServer.FUNCTION_NAME_KEY put functionName
+    FunctionServer.INPUT_FORMAT_KEY put resolveIOFormatName(T::class).toString()
+    FunctionServer.OUTPUT_FORMAT_KEY put resolveIOFormatName(R::class).toString()
+}
+
+inline fun <reified T : Any, reified R : Any> FunctionServer.function(
+    functionName: String
+): (suspend (T) -> R) {
+    val plugin = context.plugins.get<IOPlugin>() ?: error("IO plugin not loaded")
+    val meta = plugin.generateFunctionMeta<T, R>(functionName)
+    return function(meta)
+}
\ No newline at end of file
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt
index c926d07a..d0ce18b0 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt
@@ -1,51 +1,93 @@
 package hep.dataforge.io
 
 import hep.dataforge.descriptors.NodeDescriptor
-import hep.dataforge.io.functions.FunctionServer
-import hep.dataforge.io.functions.FunctionServer.Companion.FUNCTION_NAME_KEY
-import hep.dataforge.io.functions.FunctionServer.Companion.INPUT_FORMAT_KEY
-import hep.dataforge.io.functions.FunctionServer.Companion.OUTPUT_FORMAT_KEY
-import hep.dataforge.io.functions.function
+import hep.dataforge.meta.DFExperimental
+import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
-import hep.dataforge.meta.buildMeta
-import hep.dataforge.names.Name
+import kotlinx.io.nio.asInput
 import kotlinx.io.nio.asOutput
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardOpenOption
-import kotlin.reflect.KClass
 import kotlin.reflect.full.isSuperclassOf
+import kotlin.streams.asSequence
 
 inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
     return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>?
 }
 
-fun IOPlugin.resolveIOFormatName(type: KClass<*>): Name {
-    return ioFormats.entries.find { it.value.type.isSuperclassOf(type) }?.key
-        ?: error("Can't resolve IOFormat for type $type")
+/**
+ * Read file containing meta using given [formatOverride] or file extension to infer meta type.
+ */
+fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descriptor: NodeDescriptor? = null): Meta {
+    if (!Files.exists(path)) error("Meta file $path does not exist")
+    val extension = path.fileName.toString().substringAfterLast('.')
+
+    val metaFormat = formatOverride ?: metaFormat(extension) ?: error("Can't resolve meta format $extension")
+    return metaFormat.run {
+        Files.newByteChannel(path, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) }
+    }
 }
 
-inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = buildMeta {
-    FUNCTION_NAME_KEY put functionName
-    INPUT_FORMAT_KEY put resolveIOFormatName(T::class).toString()
-    OUTPUT_FORMAT_KEY put resolveIOFormatName(R::class).toString()
-}
-
-inline fun <reified T : Any, reified R : Any> FunctionServer.function(
-    functionName: String
-): (suspend (T) -> R) {
-    val plugin = context.plugins.get<IOPlugin>() ?: error("IO plugin not loaded")
-    val meta = plugin.generateFunctionMeta<T, R>(functionName)
-    return function(meta)
+fun IOPlugin.writeMetaFile(
+    path: Path,
+    metaFormat: MetaFormat = JsonMetaFormat,
+    descriptor: NodeDescriptor? = null
+) {
+    metaFormat.run {
+        Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use {
+            it.writeMeta(meta, descriptor)
+        }
+    }
 }
 
 /**
- * Write meta to file in a given [format]
+ * Read and envelope from file if the file exists, return null if file does not exist.
  */
-fun Meta.write(path: Path, format: MetaFormat, descriptor: NodeDescriptor? = null) {
-    format.run {
-        Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)
-            .asOutput()
-            .writeMeta(this@write, descriptor)
+@DFExperimental
+fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? {
+    if (!Files.exists(path)) return null
+
+    //read two-files directory
+    if (Files.isDirectory(path)) {
+        val metaFile = Files.list(path).asSequence()
+            .singleOrNull { it.fileName.toString().startsWith("meta") }
+
+        val meta = if (metaFile == null) {
+            EmptyMeta
+        } else {
+            readMetaFile(metaFile)
+        }
+
+        val dataFile = path.resolve("data")
+
+        val data: Binary? = if (Files.exists(dataFile)) {
+            dataFile.asBinary()
+        } else {
+            null
+        }
+
+        return SimpleEnvelope(meta, data)
+    }
+
+    val binary = path.asBinary()
+
+    val formats = envelopeFormatFactories.mapNotNull { factory ->
+        binary.read {
+            factory.peekFormat(this@readEnvelopeFromFile, this@read)
+        }
+    }
+    return when (formats.size) {
+        0 -> if (readNonEnvelopes) {
+            SimpleEnvelope(Meta.empty, binary)
+        } else {
+            null
+        }
+        1 -> formats.first().run {
+            binary.read {
+                readObject()
+            }
+        }
+        else -> error("Envelope format file recognition clash")
     }
 }
\ No newline at end of file
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
index 94403dcd..545601cd 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
@@ -39,7 +39,7 @@ class FileBinaryTest {
         }
         val binary = envelopeFromFile.data!!
         println(binary.toBytes().size)
-        assertEquals(binary.size.toInt(), binary.toBytes().size)
+        assertEquals(binary.size?.toInt(), binary.toBytes().size)
 
     }
 
@@ -50,7 +50,7 @@ class FileBinaryTest {
         Global.io.writeEnvelopeFile(tmpPath, envelope)
 
         val binary = Global.io.readEnvelopeFile(tmpPath).data!!
-        assertEquals(binary.size.toInt(), binary.toBytes().size)
+        assertEquals(binary.size?.toInt(), binary.toBytes().size)
     }
 
 }
\ No newline at end of file
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
index 2f717f78..b8f3ffa0 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
@@ -12,7 +12,7 @@ import hep.dataforge.names.toName
 import kotlin.jvm.JvmName
 import kotlin.reflect.KClass
 
-@TaskBuildScope
+@DFBuilder
 interface WorkspaceBuilder {
     val parentContext: Context
     var context: Context
diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
index a483c78b..4fbfa72a 100644
--- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
+++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
@@ -4,36 +4,16 @@ import hep.dataforge.data.Data
 import hep.dataforge.data.DataNode
 import hep.dataforge.data.DataTreeBuilder
 import hep.dataforge.data.datum
-import hep.dataforge.descriptors.NodeDescriptor
 import hep.dataforge.io.*
 import hep.dataforge.meta.EmptyMeta
-import hep.dataforge.meta.Meta
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import kotlinx.io.nio.asInput
-import kotlinx.io.nio.asOutput
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardOpenOption
 import kotlin.reflect.KClass
 
-/**
- * Read meta from file in a given [MetaFormat]
- */
-fun MetaFormat.readMetaFile(path: Path, descriptor: NodeDescriptor? = null): Meta {
-    return Files.newByteChannel(path, StandardOpenOption.READ)
-        .asInput()
-        .readMeta(descriptor)
-}
-
-/**
- * Write meta to file using given [MetaFormat]
- */
-fun MetaFormat.writeMetaFile(path: Path, meta: Meta, descriptor: NodeDescriptor? = null) {
-    return Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)
-        .asOutput()
-        .writeMeta(meta, descriptor)
-}
 
 /**
  * Read data with supported envelope format and binary format. If envelope format is null, then read binary directly from file.
@@ -50,10 +30,10 @@ fun <T : Any> IOPlugin.readData(
     dataFormat: IOFormat<T>,
     envelopeFormatFactory: EnvelopeFormatFactory? = null,
     metaFile: Path = path.resolveSibling("${path.fileName}.meta"),
-    metaFileFormat: MetaFormat = JsonMetaFormat.default
+    metaFileFormat: MetaFormat = JsonMetaFormat
 ): Data<T> {
     val externalMeta = if (Files.exists(metaFile)) {
-        metaFileFormat.readMetaFile(metaFile)
+        readMetaFile(metaFile)
     } else {
         null
     }

From 41d0cdb2b157b68ae9bfbdf7242e3ad22f0474f3 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 13 Nov 2019 18:08:48 +0300
Subject: [PATCH 05/41] Fix large buffers IO. A lot of refactoring.

---
 build.gradle.kts                              |  2 +-
 .../kotlin/hep/dataforge/data/DataNode.kt     | 27 ++++--
 .../kotlin/hep/dataforge/data/dataCast.kt     |  1 +
 .../dataforge/data/TypeFilteredDataNode.kt    |  2 +
 .../kotlin/hep/dataforge/data/dataJVM.kt      | 10 +--
 .../kotlin/hep/dataforge/io/Envelope.kt       |  1 +
 .../hep/dataforge/io/EnvelopeBuilder.kt       |  1 +
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt  | 25 +++---
 .../kotlin/hep/dataforge/io/IOFormat.kt       | 29 +-----
 .../hep/dataforge/io/TaggedEnvelopeFormat.kt  |  5 +-
 .../hep/dataforge/io/EnvelopePartsTest.kt     |  6 +-
 .../kotlin/hep/dataforge/io/FileEnvelope.kt   | 31 +------
 .../io/{ioFormatsJVM.kt => fileIO.kt}         | 56 ++++++++++--
 .../dataforge/io/tcp/InputStreamAsInput.kt    |  3 +-
 .../kotlin/hep/dataforge/io/FileBinaryTest.kt |  4 +-
 .../hep/dataforge/io/FileEnvelopeTest.kt      |  2 +-
 .../hep/dataforge/workspace/fileData.kt       | 90 ++++++++-----------
 17 files changed, 147 insertions(+), 148 deletions(-)
 rename dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/{ioFormatsJVM.kt => fileIO.kt} (56%)

diff --git a/build.gradle.kts b/build.gradle.kts
index cd3e5d61..7f190853 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version "0.2.2" apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-1")
+val dataforgeVersion by extra("0.1.5-dev-2")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
index 589e875b..65c07676 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
@@ -13,16 +13,22 @@ import kotlin.reflect.KClass
 sealed class DataItem<out T : Any> : MetaRepr {
     abstract val type: KClass<out T>
 
+    abstract val meta: Meta
+
     class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() {
         override val type: KClass<out T> get() = value.type
 
         override fun toMeta(): Meta = value.toMeta()
+
+        override val meta: Meta get() = value.meta
     }
 
     class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() {
         override val type: KClass<out T> get() = value.type
 
         override fun toMeta(): Meta = value.toMeta()
+
+        override val meta: Meta get() = value.meta
     }
 }
 
@@ -38,6 +44,8 @@ interface DataNode<out T : Any> : MetaRepr {
 
     val items: Map<NameToken, DataItem<T>>
 
+    val meta: Meta
+
     override fun toMeta(): Meta = buildMeta {
         "type" put (type.simpleName ?: "undefined")
         "items" put {
@@ -117,12 +125,9 @@ operator fun <T : Any> DataNode<T>.iterator(): Iterator<Pair<Name, DataItem<T>>>
 
 class DataTree<out T : Any> internal constructor(
     override val type: KClass<out T>,
-    override val items: Map<NameToken, DataItem<T>>
-) : DataNode<T> {
-    override fun toString(): String {
-        return super.toString()
-    }
-}
+    override val items: Map<NameToken, DataItem<T>>,
+    override val meta: Meta
+) : DataNode<T>
 
 private sealed class DataTreeBuilderItem<out T : Any> {
     class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
@@ -136,6 +141,8 @@ private sealed class DataTreeBuilderItem<out T : Any> {
 class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
     private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
 
+    private var meta = MetaBuilder()
+
     operator fun set(token: NameToken, node: DataTreeBuilder<out T>) {
         if (map.containsKey(token)) error("Tree entry with name $token is not empty")
         map[token] = DataTreeBuilderItem.Node(node)
@@ -203,13 +210,19 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
     infix fun String.put(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder(type).apply(block))
 
 
+    /**
+     * Update data with given node data and meta with node meta.
+     */
     fun update(node: DataNode<T>) {
         node.dataSequence().forEach {
             //TODO check if the place is occupied
             this[it.first] = it.second
         }
+        meta.update(node.meta)
     }
 
+    fun meta(block: MetaBuilder.() -> Unit) = meta.apply(block)
+
     fun build(): DataTree<T> {
         val resMap = map.mapValues { (_, value) ->
             when (value) {
@@ -217,7 +230,7 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
                 is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build())
             }
         }
-        return DataTree(type, resMap)
+        return DataTree(type, resMap, meta.seal())
     }
 }
 
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
index 556b77fc..0b9a4910 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
@@ -52,6 +52,7 @@ inline fun <reified R : Any> Data<*>.cast(): Data<R> = cast(R::class)
 @Suppress("UNCHECKED_CAST")
 fun <R : Any> DataNode<*>.cast(type: KClass<out R>): DataNode<R> {
     return object : DataNode<R> {
+        override val meta: Meta get() = this@cast.meta
         override val type: KClass<out R> = type
         override val items: Map<NameToken, DataItem<R>> get() = this@cast.items as Map<NameToken, DataItem<R>>
     }
diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt
index d24de964..331f3b0e 100644
--- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt
+++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt
@@ -1,5 +1,6 @@
 package hep.dataforge.data
 
+import hep.dataforge.meta.Meta
 import hep.dataforge.names.NameToken
 import kotlin.reflect.KClass
 
@@ -8,6 +9,7 @@ import kotlin.reflect.KClass
  * A zero-copy data node wrapper that returns only children with appropriate type.
  */
 class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val type: KClass<out T>) : DataNode<T> {
+    override val meta: Meta get() = origin.meta
     override val items: Map<NameToken, DataItem<T>> by lazy {
         origin.items.mapNotNull { (key, item) ->
             when (item) {
diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt
index 5b5507b2..29d048ed 100644
--- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt
+++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt
@@ -30,12 +30,10 @@ fun <R : Any> Data<*>.filterIsInstance(type: KClass<out R>): Data<R>? =
  * but could contain empty nodes
  */
 fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
-    return if (canCast(type)) {
-        cast(type)
-    } else if (this is TypeFilteredDataNode) {
-        origin.filterIsInstance(type)
-    } else {
-        TypeFilteredDataNode(this, type)
+    return when {
+        canCast(type) -> cast(type)
+        this is TypeFilteredDataNode -> origin.filterIsInstance(type)
+        else -> TypeFilteredDataNode(this, type)
     }
 }
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
index 61aeb4d2..7cb918df 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
@@ -21,6 +21,7 @@ interface Envelope {
         val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType"
         val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID"
         val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description"
+        val ENVELOPE_NAME_KEY = ENVELOPE_NODE_KEY + "name"
         //const val ENVELOPE_TIME_KEY = "@envelope.time"
 
         /**
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
index b8d0b660..354b4586 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
@@ -21,6 +21,7 @@ class EnvelopeBuilder {
     var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY)
     var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY)
     var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY)
+    var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY)
 
     /**
      * Construct a binary and transform it into byte-array based buffer
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index 0a8a207d..eb1dd696 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -3,30 +3,31 @@ package hep.dataforge.io
 import hep.dataforge.context.Global
 import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY
 import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY
-import hep.dataforge.io.EnvelopeParts.PARTS_DATA_TYPE
+import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE
 import hep.dataforge.io.EnvelopeParts.SIZE_KEY
 import hep.dataforge.meta.*
+import hep.dataforge.names.asName
 import hep.dataforge.names.plus
 import hep.dataforge.names.toName
 
 object EnvelopeParts {
-    val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "size"
-    val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "format"
-    val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "meta"
+    val MULTIPART_KEY = "multipart".asName()
+    val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "size"
+    val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format"
+    val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta"
 
-    const val PARTS_DATA_TYPE = "envelope.parts"
+    const val MULTIPART_DATA_TYPE = "envelope.multipart"
 }
 
 /**
  * Append multiple serialized envelopes to the data block. Previous data is erased if it was present
  */
-fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Collection<Envelope>) {
-    dataType = PARTS_DATA_TYPE
+fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collection<Envelope>) {
+    dataType = MULTIPART_DATA_TYPE
     meta {
         SIZE_KEY put envelopes.size
-        FORMAT_NAME_KEY put formatFactory.name.toString()
+        FORMAT_NAME_KEY put format.name.toString()
     }
-    val format = formatFactory()
     data {
         format.run {
             envelopes.forEach {
@@ -36,15 +37,15 @@ fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Colle
     }
 }
 
-fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) =
-    parts(formatFactory, sequence(builder).toList())
+fun EnvelopeBuilder.multipart(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) =
+    multipart(formatFactory, sequence(builder).toList())
 
 /**
  * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null.
  */
 fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? {
     return when (dataType) {
-        PARTS_DATA_TYPE -> {
+        MULTIPART_DATA_TYPE -> {
             val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet")
             val formatName = meta[FORMAT_NAME_KEY].string?.toName()
                 ?: error("Inferring parts format is not supported at the moment")
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
index 093ffbc8..bd1e54f4 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
@@ -16,7 +16,6 @@ import kotlinx.serialization.ImplicitReflectionSerializer
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.cbor.Cbor
 import kotlinx.serialization.serializer
-import kotlin.math.min
 import kotlin.reflect.KClass
 
 /**
@@ -80,33 +79,9 @@ inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuil
 }
 
 fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
+//TODO Double buffer copy. fix all that with IO-2
 fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
-fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T {
-    //= ByteReadPacket(array).readThis()
-    val byteArrayInput: Input = object : AbstractInput(
-        IoBuffer.Pool.borrow(),
-        remaining = array.size.toLong(),
-        pool = IoBuffer.Pool
-    ) {
-        var written = 0
-        override fun closeSource() {
-            // do nothing
-        }
-
-        override fun fill(): IoBuffer? {
-            if (array.size - written <= 0) return null
-
-            return IoBuffer.Pool.fill {
-                reserveEndGap(IoBuffer.ReservedSize)
-                val toWrite = min(capacity, array.size - written)
-                writeFully(array, written, toWrite)
-                written += toWrite
-            }
-        }
-
-    }
-    return byteArrayInput.readObject()
-}
+fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = buildPacket { writeFully(array) }.readObject()
 
 object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
     override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
index 5f9164dc..a95b7bfb 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
@@ -64,7 +64,10 @@ class TaggedEnvelopeFormat(
         val metaFormat = io.metaFormat(tag.metaFormatKey)
             ?: error("Meta format with key ${tag.metaFormatKey} not found")
 
-        val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
+        val metaBytes = readBytes(tag.metaSize.toInt())
+        val metaPacket = buildPacket {
+            writeFully(metaBytes)
+        }
         val dataBytes = readBytes(tag.dataSize.toInt())
 
         val meta = metaFormat.run { metaPacket.readObject() }
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
index c33b3179..d123d632 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
@@ -14,11 +14,14 @@ class EnvelopePartsTest {
             }
             data {
                 writeText("Hello World $it")
+                repeat(200){
+                    writeInt(it)
+                }
             }
         }
     }
     val partsEnvelope = Envelope {
-        parts(TaggedEnvelopeFormat, envelopes)
+        multipart(TaggedEnvelopeFormat, envelopes)
     }
 
     @Test
@@ -27,6 +30,7 @@ class EnvelopePartsTest {
         val reconstructed = TaggedEnvelopeFormat.readBytes(bytes)
         val parts = reconstructed.parts()?.toList() ?: emptyList()
         assertEquals(2, parts[2].meta["value"].int)
+        println(reconstructed.data!!.size)
     }
 
 }
\ No newline at end of file
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
index 3187cd54..5f21e6ae 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
@@ -1,9 +1,7 @@
 package hep.dataforge.io
 
-import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
 import kotlinx.io.nio.asInput
-import kotlinx.io.nio.asOutput
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardOpenOption
@@ -15,7 +13,7 @@ class FileEnvelope internal constructor(val path: Path, val format: EnvelopeForm
 
     init {
         val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
-        partialEnvelope = format.run { input.use { it.readPartial()} }
+        partialEnvelope = format.run { input.use { it.readPartial() } }
     }
 
     override val meta: Meta get() = partialEnvelope.meta
@@ -23,30 +21,3 @@ class FileEnvelope internal constructor(val path: Path, val format: EnvelopeForm
     override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize)
 }
 
-fun IOPlugin.readEnvelopeFile(
-    path: Path,
-    formatFactory: EnvelopeFormatFactory = TaggedEnvelopeFormat,
-    formatMeta: Meta = EmptyMeta
-): FileEnvelope {
-    val format = formatFactory(formatMeta, context)
-    return FileEnvelope(path, format)
-}
-
-fun IOPlugin.writeEnvelopeFile(
-    path: Path,
-    envelope: Envelope,
-    formatFactory: EnvelopeFormatFactory = TaggedEnvelopeFormat,
-    formatMeta: Meta = EmptyMeta
-) {
-    val output = Files.newByteChannel(
-        path,
-        StandardOpenOption.WRITE,
-        StandardOpenOption.CREATE,
-        StandardOpenOption.TRUNCATE_EXISTING
-    ).asOutput()
-
-    with(formatFactory(formatMeta, context)) {
-        output.writeObject(envelope)
-    }
-}
-
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
similarity index 56%
rename from dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt
rename to dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index d0ce18b0..14e4c077 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -18,24 +18,41 @@ inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
 
 /**
  * Read file containing meta using given [formatOverride] or file extension to infer meta type.
+ * If [path] is a directory search for file starting with `meta` in it
  */
 fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descriptor: NodeDescriptor? = null): Meta {
     if (!Files.exists(path)) error("Meta file $path does not exist")
-    val extension = path.fileName.toString().substringAfterLast('.')
+
+    val actualPath: Path = if (Files.isDirectory(path)) {
+        Files.list(path).asSequence().find { it.fileName.startsWith("meta") }
+            ?: error("The directory $path does not contain meta file")
+    } else {
+        path
+    }
+    val extension = actualPath.fileName.toString().substringAfterLast('.')
 
     val metaFormat = formatOverride ?: metaFormat(extension) ?: error("Can't resolve meta format $extension")
     return metaFormat.run {
-        Files.newByteChannel(path, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) }
+        Files.newByteChannel(actualPath, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) }
     }
 }
 
+/**
+ * Write meta to file using [metaFormat]. If [path] is a directory, write a file with name equals name of [metaFormat].
+ * Like "meta.json"
+ */
 fun IOPlugin.writeMetaFile(
     path: Path,
-    metaFormat: MetaFormat = JsonMetaFormat,
+    metaFormat: MetaFormatFactory = JsonMetaFormat,
     descriptor: NodeDescriptor? = null
 ) {
+    val actualPath = if (Files.isDirectory(path)) {
+        path.resolve(metaFormat.name.toString())
+    } else {
+        path
+    }
     metaFormat.run {
-        Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use {
+        Files.newByteChannel(actualPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use {
             it.writeMeta(meta, descriptor)
         }
     }
@@ -43,9 +60,19 @@ fun IOPlugin.writeMetaFile(
 
 /**
  * Read and envelope from file if the file exists, return null if file does not exist.
+ *
+ * If file is directory, then expect two files inside:
+ * * **meta.<format name>** for meta
+ * * **data** for data
+ *
+ * If the file is envelope read it using [EnvelopeFormatFactory.peekFormat] functionality to infer format.
+ *
+ * If the file is not an envelope and [readNonEnvelopes] is true, return an Envelope without meta, using file as binary.
+ *
+ * Return null otherwise.
  */
 @DFExperimental
-fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? {
+fun IOPlugin.readEnvelopeFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? {
     if (!Files.exists(path)) return null
 
     //read two-files directory
@@ -74,7 +101,7 @@ fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false)
 
     val formats = envelopeFormatFactories.mapNotNull { factory ->
         binary.read {
-            factory.peekFormat(this@readEnvelopeFromFile, this@read)
+            factory.peekFormat(this@readEnvelopeFile, this@read)
         }
     }
     return when (formats.size) {
@@ -90,4 +117,21 @@ fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false)
         }
         else -> error("Envelope format file recognition clash")
     }
+}
+
+fun IOPlugin.writeEnvelopeFile(
+    path: Path,
+    envelope: Envelope,
+    format: EnvelopeFormat = TaggedEnvelopeFormat
+) {
+    val output = Files.newByteChannel(
+        path,
+        StandardOpenOption.WRITE,
+        StandardOpenOption.CREATE,
+        StandardOpenOption.TRUNCATE_EXISTING
+    ).asOutput()
+
+    with(format) {
+        output.writeObject(envelope)
+    }
 }
\ No newline at end of file
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt
index 1c711be0..eb743625 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt
@@ -3,7 +3,6 @@ package hep.dataforge.io.tcp
 import kotlinx.io.core.AbstractInput
 import kotlinx.io.core.Input
 import kotlinx.io.core.IoBuffer
-import kotlinx.io.core.IoBuffer.Companion.NoPool
 import kotlinx.io.core.writePacket
 import kotlinx.io.streams.readPacketAtMost
 import java.io.InputStream
@@ -13,7 +12,7 @@ import java.io.InputStream
  */
 internal class InputStreamAsInput(
     private val stream: InputStream
-) : AbstractInput(pool = NoPool) {
+) : AbstractInput(pool = IoBuffer.Pool) {
 
 
     override fun fill(): IoBuffer? {
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
index 545601cd..d8c7c67a 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
@@ -49,8 +49,8 @@ class FileBinaryTest {
         val tmpPath = Files.createTempFile("dataforge_test", ".df")
         Global.io.writeEnvelopeFile(tmpPath, envelope)
 
-        val binary = Global.io.readEnvelopeFile(tmpPath).data!!
-        assertEquals(binary.size?.toInt(), binary.toBytes().size)
+        val binary = Global.io.readEnvelopeFile(tmpPath)?.data!!
+        assertEquals(binary.size.toInt(), binary.toBytes().size)
     }
 
 }
\ No newline at end of file
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
index ba7f7cc5..f4847cfd 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
@@ -25,7 +25,7 @@ class FileEnvelopeTest {
         val tmpPath = Files.createTempFile("dataforge_test", ".df")
         Global.io.writeEnvelopeFile(tmpPath,envelope)
         println(tmpPath.toUri())
-        val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)
+        val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)!!
         assertTrue { envelope.contentEquals(restored) }
     }
 }
\ No newline at end of file
diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
index 4fbfa72a..b5ecb519 100644
--- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
+++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
@@ -1,17 +1,15 @@
 package hep.dataforge.workspace
 
-import hep.dataforge.data.Data
-import hep.dataforge.data.DataNode
-import hep.dataforge.data.DataTreeBuilder
-import hep.dataforge.data.datum
-import hep.dataforge.io.*
-import hep.dataforge.meta.EmptyMeta
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import kotlinx.io.nio.asInput
+import hep.dataforge.data.*
+import hep.dataforge.io.Envelope
+import hep.dataforge.io.IOFormat
+import hep.dataforge.io.IOPlugin
+import hep.dataforge.io.readEnvelopeFile
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.get
+import hep.dataforge.meta.string
 import java.nio.file.Files
 import java.nio.file.Path
-import java.nio.file.StandardOpenOption
 import kotlin.reflect.KClass
 
 
@@ -20,54 +18,30 @@ import kotlin.reflect.KClass
  * The operation is blocking since it must read meta header. The reading of envelope body is lazy
  * @param type explicit type of data read
  * @param dataFormat binary format
- * @param envelopeFormatFactory the format of envelope. If null, file is read directly
+ * @param envelopeFormat the format of envelope. If null, file is read directly
  * @param metaFile the relative file for optional meta override
  * @param metaFileFormat the meta format for override
  */
-fun <T : Any> IOPlugin.readData(
+fun <T : Any> IOPlugin.readDataFile(
     path: Path,
     type: KClass<out T>,
-    dataFormat: IOFormat<T>,
-    envelopeFormatFactory: EnvelopeFormatFactory? = null,
-    metaFile: Path = path.resolveSibling("${path.fileName}.meta"),
-    metaFileFormat: MetaFormat = JsonMetaFormat
+    formatResolver: (Meta) -> IOFormat<T>
 ): Data<T> {
-    val externalMeta = if (Files.exists(metaFile)) {
-        readMetaFile(metaFile)
-    } else {
-        null
-    }
-    return if (envelopeFormatFactory == null) {
-        Data(type, externalMeta ?: EmptyMeta) {
-            withContext(Dispatchers.IO) {
-                dataFormat.run {
-                    Files.newByteChannel(path, StandardOpenOption.READ)
-                        .asInput()
-                        .readObject()
-                }
-            }
-        }
-    } else {
-        readEnvelopeFile(path, envelopeFormatFactory).let {
-            if (externalMeta == null) {
-                it
-            } else {
-                it.withMetaLayers(externalMeta)
-            }
-        }.toData(type, dataFormat)
-    }
+    val envelope = readEnvelopeFile(path, true) ?: error("Can't read data from $path")
+    val format = formatResolver(envelope.meta)
+    return envelope.toData(type, format)
 }
 
 //TODO wants multi-receiver
 fun <T : Any> DataTreeBuilder<T>.file(
     plugin: IOPlugin,
     path: Path,
-    dataFormat: IOFormat<T>,
-    envelopeFormatFactory: EnvelopeFormatFactory? = null
+    formatResolver: (Meta) -> IOFormat<T>
 ) {
     plugin.run {
-        val data = readData(path, type, dataFormat, envelopeFormatFactory)
-        val name = path.fileName.toString().replace(".df", "")
+        val data = readDataFile(path, type, formatResolver)
+        val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string
+            ?: path.fileName.toString().replace(".df", "")
         datum(name, data)
     }
 }
@@ -75,23 +49,35 @@ fun <T : Any> DataTreeBuilder<T>.file(
 /**
  * Read the directory as a data node
  */
-fun <T : Any> IOPlugin.readDataNode(
+fun <T : Any> IOPlugin.readDataDirectory(
     path: Path,
     type: KClass<out T>,
-    dataFormat: IOFormat<T>,
-    envelopeFormatFactory: EnvelopeFormatFactory? = null
+    formatResolver: (Meta) -> IOFormat<T>
 ): DataNode<T> {
     if (!Files.isDirectory(path)) error("Provided path $this is not a directory")
     return DataNode(type) {
         Files.list(path).forEach { path ->
             if (!path.fileName.toString().endsWith(".meta")) {
-                file(this@readDataNode,path, dataFormat, envelopeFormatFactory)
+                file(this@readDataDirectory, path, formatResolver)
             }
         }
     }
 }
 
-//suspend fun <T : Any> Path.writeData(
-//    data: Data<T>,
-//    format: IOFormat<T>,
-//    )
\ No newline at end of file
+fun <T : Any> DataTreeBuilder<T>.directory(
+    plugin: IOPlugin,
+    path: Path,
+    formatResolver: (Meta) -> IOFormat<T>
+) {
+    plugin.run {
+        val data = readDataDirectory(path, type, formatResolver)
+        val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string
+            ?: path.fileName.toString().replace(".df", "")
+        node(name, data)
+    }
+}
+
+
+
+
+

From d10bd40763605d733e94ac35234e2e4403e5a108 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 13 Nov 2019 18:24:33 +0300
Subject: [PATCH 06/41] Remove use of direct Input to ByteArray conversions due
 to bugs in kotlinx.io.

---
 build.gradle.kts                                            | 2 +-
 .../src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt      | 4 +++-
 .../kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt         | 6 +++---
 .../kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt        | 6 +++---
 4 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 7f190853..430fcda4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version "0.2.2" apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-2")
+val dataforgeVersion by extra("0.1.5-dev-3")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
index bd1e54f4..e5497365 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
@@ -79,8 +79,10 @@ inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuil
 }
 
 fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
-//TODO Double buffer copy. fix all that with IO-2
+
+@Deprecated("Not to be used outside tests due to double buffer write")
 fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
+@Deprecated("Not to be used outside tests due to double buffer write")
 fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = buildPacket { writeFully(array) }.readObject()
 
 object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
index a95b7bfb..a461d257 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
@@ -38,15 +38,15 @@ class TaggedEnvelopeFormat(
 
     override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
         val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
-        val metaBytes = metaFormat.writeBytes(envelope.meta)
+        val metaBytes = metaFormat.writePacket(envelope.meta)
         val actualSize: ULong = if (envelope.data == null) {
             0u
         } else {
             envelope.data?.size ?: ULong.MAX_VALUE
         }
-        val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize)
+        val tag = Tag(metaFormatFactory.key, metaBytes.remaining.toUInt() + 2u, actualSize)
         writePacket(tag.toBytes())
-        writeFully(metaBytes)
+        writePacket(metaBytes)
         writeText("\r\n")
         envelope.data?.read { copyTo(this@writeEnvelope) }
         flush()
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index d3953a0c..5c4b7dae 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -37,10 +37,10 @@ class TaglessEnvelopeFormat(
 
         //Printing meta
         if (!envelope.meta.isEmpty()) {
-            val metaBytes = metaFormat.writeBytes(envelope.meta)
-            writeProperty(META_LENGTH_PROPERTY, metaBytes.size)
+            val metaBytes = metaFormat.writePacket(envelope.meta)
+            writeProperty(META_LENGTH_PROPERTY, metaBytes.remaining)
             writeText(metaStart + "\r\n")
-            writeFully(metaBytes)
+            writePacket(metaBytes)
             writeText("\r\n")
         }
 

From 4370a66164d728a2293f2345afedf6bf304cd920 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 13 Nov 2019 21:12:19 +0300
Subject: [PATCH 07/41] Fixed TaglessEnvelopeFormat resolution

---
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt  | 35 ++++++++++++++-
 .../kotlin/hep/dataforge/io/IOPlugin.kt       |  2 +-
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt |  3 +-
 .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 45 ++++++++++++-------
 .../hep/dataforge/io/FileEnvelopeTest.kt      | 23 +++++++---
 5 files changed, 82 insertions(+), 26 deletions(-)

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index eb1dd696..d1b86195 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -3,6 +3,7 @@ package hep.dataforge.io
 import hep.dataforge.context.Global
 import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY
 import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY
+import hep.dataforge.io.EnvelopeParts.INDEX_KEY
 import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE
 import hep.dataforge.io.EnvelopeParts.SIZE_KEY
 import hep.dataforge.meta.*
@@ -13,6 +14,7 @@ import hep.dataforge.names.toName
 object EnvelopeParts {
     val MULTIPART_KEY = "multipart".asName()
     val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "size"
+    val INDEX_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "index"
     val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format"
     val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta"
 
@@ -37,8 +39,37 @@ fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collecti
     }
 }
 
-fun EnvelopeBuilder.multipart(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) =
-    multipart(formatFactory, sequence(builder).toList())
+/**
+ * Create a multipart partition in the envelope adding additional name-index mapping in meta
+ */
+@DFExperimental
+fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Map<String, Envelope>) {
+    dataType = MULTIPART_DATA_TYPE
+    meta {
+        SIZE_KEY put envelopes.size
+        FORMAT_NAME_KEY put format.name.toString()
+    }
+    data {
+        format.run {
+            var counter = 0
+            envelopes.forEach {(key, envelope)->
+                writeObject(envelope)
+                meta{
+                    append(INDEX_KEY, buildMeta {
+                        "key" put key
+                        "index" put counter
+                    })
+                }
+                counter++
+            }
+        }
+    }
+}
+
+fun EnvelopeBuilder.multipart(
+    formatFactory: EnvelopeFormatFactory,
+    builder: suspend SequenceScope<Envelope>.() -> Unit
+) = multipart(formatFactory, sequence(builder).toList())
 
 /**
  * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null.
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
index ae88e4f4..a463a053 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
@@ -52,7 +52,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
 
     companion object : PluginFactory<IOPlugin> {
         val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat)
-        val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat)
+        val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat)
 
         override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index 5c4b7dae..1cc62a2b 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -49,6 +49,7 @@ class TaglessEnvelopeFormat(
             writeText(dataStart + "\r\n")
             writeFully(data.toBytes())
         }
+        flush()
     }
 
     override fun Input.readObject(): Envelope {
@@ -191,7 +192,7 @@ class TaglessEnvelopeFormat(
             return try {
                 val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length)
                 input.readFully(buffer)
-                return if (buffer.toString() == TAGLESS_ENVELOPE_HEADER) {
+                return if (String(buffer) == TAGLESS_ENVELOPE_HEADER) {
                     TaglessEnvelopeFormat(io)
                 } else {
                     null
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index 14e4c077..eff9e705 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -58,6 +58,24 @@ fun IOPlugin.writeMetaFile(
     }
 }
 
+/**
+ * Return inferred [EnvelopeFormat] if only one format could read given file. If no format accepts file, return null. If
+ * multiple formats accepts file, throw an error.
+ */
+fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? {
+    val formats = envelopeFormatFactories.mapNotNull { factory ->
+        binary.read {
+            factory.peekFormat(this@peekBinaryFormat, this@read)
+        }
+    }
+
+    return when (formats.size) {
+        0 -> null
+        1 -> formats.first()
+        else -> error("Envelope format binary recognition clash")
+    }
+}
+
 /**
  * Read and envelope from file if the file exists, return null if file does not exist.
  *
@@ -72,7 +90,11 @@ fun IOPlugin.writeMetaFile(
  * Return null otherwise.
  */
 @DFExperimental
-fun IOPlugin.readEnvelopeFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? {
+fun IOPlugin.readEnvelopeFile(
+    path: Path,
+    readNonEnvelopes: Boolean = false,
+    formatPeeker: IOPlugin.(Binary) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat
+): Envelope? {
     if (!Files.exists(path)) return null
 
     //read two-files directory
@@ -99,24 +121,13 @@ fun IOPlugin.readEnvelopeFile(path: Path, readNonEnvelopes: Boolean = false): En
 
     val binary = path.asBinary()
 
-    val formats = envelopeFormatFactories.mapNotNull { factory ->
+    return formatPeeker(binary)?.run {
         binary.read {
-            factory.peekFormat(this@readEnvelopeFile, this@read)
+            readObject()
         }
-    }
-    return when (formats.size) {
-        0 -> if (readNonEnvelopes) {
-            SimpleEnvelope(Meta.empty, binary)
-        } else {
-            null
-        }
-        1 -> formats.first().run {
-            binary.read {
-                readObject()
-            }
-        }
-        else -> error("Envelope format file recognition clash")
-    }
+    } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary
+        SimpleEnvelope(Meta.empty, binary)
+    } else null
 }
 
 fun IOPlugin.writeEnvelopeFile(
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
index f4847cfd..b8bfafaa 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
@@ -22,10 +22,23 @@ class FileEnvelopeTest {
 
     @Test
     fun testFileWriteRead() {
-        val tmpPath = Files.createTempFile("dataforge_test", ".df")
-        Global.io.writeEnvelopeFile(tmpPath,envelope)
-        println(tmpPath.toUri())
-        val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)!!
-        assertTrue { envelope.contentEquals(restored) }
+        Global.io.run {
+            val tmpPath = Files.createTempFile("dataforge_test", ".df")
+            writeEnvelopeFile(tmpPath, envelope)
+            println(tmpPath.toUri())
+            val restored: Envelope = readEnvelopeFile(tmpPath)!!
+            assertTrue { envelope.contentEquals(restored) }
+        }
+    }
+
+    @Test
+    fun testFileWriteReadTagless() {
+        Global.io.run {
+            val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df")
+            writeEnvelopeFile(tmpPath, envelope, format = TaglessEnvelopeFormat)
+            println(tmpPath.toUri())
+            val restored: Envelope = readEnvelopeFile(tmpPath)!!
+            assertTrue { envelope.contentEquals(restored) }
+        }
     }
 }
\ No newline at end of file

From 45a5b6fe28318b951dfe598219d2750e27702c1e Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 17 Nov 2019 22:15:29 +0300
Subject: [PATCH 08/41] Working on zip and directory storage for data. Update
 to build 0.2.4

---
 .github/workflows/gradle.yml                  |   2 +-
 build.gradle.kts                              |   6 +-
 .../kotlin/hep/dataforge/data/DataNode.kt     |  38 ++--
 .../kotlin/hep/dataforge/data/dataCast.kt     |   4 +-
 .../dataforge/data/TypeFilteredDataNode.kt    |   4 +-
 .../kotlin/hep/dataforge/data/dataJVM.kt      |   4 +-
 .../dataforge-io-yaml/build.gradle.kts        |   2 -
 .../dataforge/io/yaml/YamlMetaFormatTest.kt   |   2 +-
 .../kotlin/hep/dataforge/io/Binary.kt         |   8 +-
 .../kotlin/hep/dataforge/io/Envelope.kt       |   1 -
 .../kotlin/hep/dataforge/io/EnvelopeFormat.kt |   8 +-
 .../kotlin/hep/dataforge/io/IOPlugin.kt       |   2 +-
 .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 101 ++++++++---
 .../hep/dataforge/io/FileEnvelopeTest.kt      |   2 +-
 .../dataforge/io/tcp/EnvelopeServerTest.kt    |  10 +-
 dataforge-scripting/build.gradle.kts          |   2 -
 .../hep/dataforge/scripting/BuildersKtTest.kt |   2 +-
 .../hep/dataforge/workspace/dataUtils.kt      |  14 --
 .../hep/dataforge/workspace/envelopeData.kt   |  33 ++++
 .../hep/dataforge/workspace/fileData.kt       | 162 ++++++++++++++----
 .../workspace/DataPropagationTest.kt          |   2 +-
 .../hep/dataforge/workspace/FileDataTest.kt   |  72 ++++++++
 .../workspace/SimpleWorkspaceTest.kt          |   2 +-
 gradle/wrapper/gradle-wrapper.jar             | Bin 55616 -> 58702 bytes
 gradle/wrapper/gradle-wrapper.properties      |   2 +-
 gradlew                                       |  29 ++--
 26 files changed, 383 insertions(+), 131 deletions(-)
 delete mode 100644 dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt
 create mode 100644 dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
 create mode 100644 dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
 mode change 100644 => 100755 gradlew

diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 85ea102d..adc74adf 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -1,4 +1,4 @@
-name: Java CI
+name: Gradle build
 
 on: [push]
 
diff --git a/build.gradle.kts b/build.gradle.kts
index 430fcda4..cf2f4387 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,9 +1,9 @@
 import scientifik.ScientifikExtension
 
 plugins {
-    id("scientifik.mpp") version "0.2.2" apply false
-    id("scientifik.jvm") version "0.2.2" apply false
-    id("scientifik.publish") version "0.2.2" apply false
+    id("scientifik.mpp") version "0.2.4" apply false
+    id("scientifik.jvm") version "0.2.4" apply false
+    id("scientifik.publish") version "0.2.4" apply false
 }
 
 val dataforgeVersion by extra("0.1.5-dev-3")
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
index 65c07676..a673f0b7 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
@@ -15,20 +15,20 @@ sealed class DataItem<out T : Any> : MetaRepr {
 
     abstract val meta: Meta
 
-    class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() {
-        override val type: KClass<out T> get() = value.type
+    class Node<out T : Any>(val node: DataNode<T>) : DataItem<T>() {
+        override val type: KClass<out T> get() = node.type
 
-        override fun toMeta(): Meta = value.toMeta()
+        override fun toMeta(): Meta = node.toMeta()
 
-        override val meta: Meta get() = value.meta
+        override val meta: Meta get() = node.meta
     }
 
-    class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() {
-        override val type: KClass<out T> get() = value.type
+    class Leaf<out T : Any>(val data: Data<T>) : DataItem<T>() {
+        override val type: KClass<out T> get() = data.type
 
-        override fun toMeta(): Meta = value.toMeta()
+        override fun toMeta(): Meta = data.toMeta()
 
-        override val meta: Meta get() = value.meta
+        override val meta: Meta get() = data.meta
     }
 }
 
@@ -68,8 +68,8 @@ interface DataNode<out T : Any> : MetaRepr {
     }
 }
 
-val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.value
-val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.value
+val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.node
+val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.data
 
 /**
  * Start computation for all goals in data node and return a job for the whole node
@@ -77,8 +77,8 @@ val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.v
 fun DataNode<*>.launchAll(scope: CoroutineScope): Job = scope.launch {
     items.values.forEach {
         when (it) {
-            is DataItem.Node<*> -> it.value.launchAll(scope)
-            is DataItem.Leaf<*> -> it.value.start(scope)
+            is DataItem.Node<*> -> it.node.launchAll(scope)
+            is DataItem.Leaf<*> -> it.data.start(scope)
         }
     }
 }
@@ -98,7 +98,7 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ
     items.forEach { (head, item) ->
         yield(head.asName() to item)
         if (item is DataItem.Node) {
-            val subSequence = item.value.asSequence()
+            val subSequence = item.node.asSequence()
                 .map { (name, data) -> (head.asName() + name) to data }
             yieldAll(subSequence)
         }
@@ -111,9 +111,9 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ
 fun <T : Any> DataNode<T>.dataSequence(): Sequence<Pair<Name, Data<T>>> = sequence {
     items.forEach { (head, item) ->
         when (item) {
-            is DataItem.Leaf -> yield(head.asName() to item.value)
+            is DataItem.Leaf -> yield(head.asName() to item.data)
             is DataItem.Node -> {
-                val subSequence = item.value.dataSequence()
+                val subSequence = item.node.dataSequence()
                     .map { (name, data) -> (head.asName() + name) to data }
                 yieldAll(subSequence)
             }
@@ -188,8 +188,8 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
     operator fun set(name: Name, node: DataNode<T>) = set(name, node.builder())
 
     operator fun set(name: Name, item: DataItem<T>) = when (item) {
-        is DataItem.Node<T> -> set(name, item.value.builder())
-        is DataItem.Leaf<T> -> set(name, item.value)
+        is DataItem.Node<T> -> set(name, item.node.builder())
+        is DataItem.Leaf<T> -> set(name, item.data)
     }
 
     /**
@@ -223,6 +223,10 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
 
     fun meta(block: MetaBuilder.() -> Unit) = meta.apply(block)
 
+    fun meta(meta: Meta) {
+        this.meta = meta.builder()
+    }
+
     fun build(): DataTree<T> {
         val resMap = map.mapValues { (_, value) ->
             when (value) {
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
index 0b9a4910..2bf8adde 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
@@ -28,8 +28,8 @@ expect fun <R : Any> DataNode<*>.canCast(type: KClass<out R>): Boolean
 expect fun <R : Any> Data<*>.canCast(type: KClass<out R>): Boolean
 
 fun <R : Any> DataItem<*>.canCast(type: KClass<out R>): Boolean = when (this) {
-    is DataItem.Node -> value.canCast(type)
-    is DataItem.Leaf -> value.canCast(type)
+    is DataItem.Node -> node.canCast(type)
+    is DataItem.Leaf -> data.canCast(type)
 }
 
 /**
diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt
index 331f3b0e..3590679c 100644
--- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt
+++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt
@@ -14,12 +14,12 @@ class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val ty
         origin.items.mapNotNull { (key, item) ->
             when (item) {
                 is DataItem.Leaf -> {
-                    (item.value.filterIsInstance(type))?.let {
+                    (item.data.filterIsInstance(type))?.let {
                         key to DataItem.Leaf(it)
                     }
                 }
                 is DataItem.Node -> {
-                    key to DataItem.Node(item.value.filterIsInstance(type))
+                    key to DataItem.Node(item.node.filterIsInstance(type))
                 }
             }
         }.associate { it }
diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt
index 29d048ed..f354c2f7 100644
--- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt
+++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt
@@ -42,8 +42,8 @@ fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
  */
 fun <R : Any> DataItem<*>?.filterIsInstance(type: KClass<out R>): DataItem<R>? = when (this) {
     null -> null
-    is DataItem.Node -> DataItem.Node(this.value.filterIsInstance(type))
-    is DataItem.Leaf -> this.value.filterIsInstance(type)?.let { DataItem.Leaf(it) }
+    is DataItem.Node -> DataItem.Node(this.node.filterIsInstance(type))
+    is DataItem.Leaf -> this.data.filterIsInstance(type)?.let { DataItem.Leaf(it) }
 }
 
 inline fun <reified R : Any> DataItem<*>?.filterIsInstance(): DataItem<R>? = this@filterIsInstance.filterIsInstance(R::class)
\ No newline at end of file
diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts
index 74ba43cf..d287d9ac 100644
--- a/dataforge-io/dataforge-io-yaml/build.gradle.kts
+++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts
@@ -7,6 +7,4 @@ description = "YAML meta IO"
 dependencies {
     api(project(":dataforge-io"))
     api("org.yaml:snakeyaml:1.25")
-    testImplementation(kotlin("test"))
-    testImplementation(kotlin("test-junit"))
 }
diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
index 414162f7..5c5b3c18 100644
--- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
+++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
@@ -6,7 +6,7 @@ import hep.dataforge.meta.Meta
 import hep.dataforge.meta.buildMeta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.seal
-import org.junit.Test
+import org.junit.jupiter.api.Test
 import kotlin.test.assertEquals
 
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
index bd1f2249..b671928b 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
@@ -10,7 +10,7 @@ interface Binary {
     /**
      * The size of binary in bytes. [ULong.MAX_VALUE] if size is not defined and input should be read until its end is reached
      */
-    val size: ULong
+    val size: ULong get() = ULong.MAX_VALUE
 
     /**
      * Read continuous [Input] from this binary stating from the beginning.
@@ -41,7 +41,11 @@ interface RandomAccessBinary : Binary {
 }
 
 fun Binary.toBytes(): ByteArray = read {
-    this.readBytes()
+    readBytes()
+}
+
+fun Binary.contentToString(): String = read {
+    readText()
 }
 
 @ExperimentalUnsignedTypes
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
index 7cb918df..abf8a504 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
@@ -83,4 +83,3 @@ fun Envelope.withMetaLayers(vararg layers: Meta): Envelope {
         else -> ProxyEnvelope(this, *layers)
     }
 }
-
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
index 4f747ea2..49a25919 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
@@ -23,11 +23,15 @@ interface EnvelopeFormat : IOFormat<Envelope> {
 
     fun Input.readPartial(): PartialEnvelope
 
-    fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta = EmptyMeta)
+    fun Output.writeEnvelope(
+        envelope: Envelope,
+        metaFormatFactory: MetaFormatFactory = defaultMetaFormat,
+        formatMeta: Meta = EmptyMeta
+    )
 
     override fun Input.readObject(): Envelope
 
-    override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj, defaultMetaFormat)
+    override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj)
 }
 
 @Type(ENVELOPE_FORMAT_TYPE)
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
index a463a053..eb975029 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
@@ -20,7 +20,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
         metaFormatFactories.find { it.key == key }?.invoke(meta)
 
     fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? =
-        metaFormatFactories.find { it.name.toString() == name }?.invoke(meta)
+        metaFormatFactories.find { it.name.last().toString() == name }?.invoke(meta)
 
     val envelopeFormatFactories by lazy {
         context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index eff9e705..9203d306 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -4,6 +4,9 @@ import hep.dataforge.descriptors.NodeDescriptor
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
+import hep.dataforge.meta.isEmpty
+import kotlinx.io.core.Output
+import kotlinx.io.core.copyTo
 import kotlinx.io.nio.asInput
 import kotlinx.io.nio.asOutput
 import java.nio.file.Files
@@ -12,10 +15,15 @@ import java.nio.file.StandardOpenOption
 import kotlin.reflect.full.isSuperclassOf
 import kotlin.streams.asSequence
 
+/**
+ * Resolve IOFormat based on type
+ */
+@DFExperimental
 inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
     return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>?
 }
 
+
 /**
  * Read file containing meta using given [formatOverride] or file extension to infer meta type.
  * If [path] is a directory search for file starting with `meta` in it
@@ -43,11 +51,12 @@ fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descri
  */
 fun IOPlugin.writeMetaFile(
     path: Path,
+    meta: Meta,
     metaFormat: MetaFormatFactory = JsonMetaFormat,
     descriptor: NodeDescriptor? = null
 ) {
     val actualPath = if (Files.isDirectory(path)) {
-        path.resolve(metaFormat.name.toString())
+        path.resolve("@" + metaFormat.name.toString())
     } else {
         path
     }
@@ -62,7 +71,8 @@ fun IOPlugin.writeMetaFile(
  * Return inferred [EnvelopeFormat] if only one format could read given file. If no format accepts file, return null. If
  * multiple formats accepts file, throw an error.
  */
-fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? {
+fun IOPlugin.peekBinaryFormat(path: Path): EnvelopeFormat? {
+    val binary = path.asBinary()
     val formats = envelopeFormatFactories.mapNotNull { factory ->
         binary.read {
             factory.peekFormat(this@peekBinaryFormat, this@read)
@@ -76,6 +86,9 @@ fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? {
     }
 }
 
+val IOPlugin.Companion.META_FILE_NAME: String get() = "@meta"
+val IOPlugin.Companion.DATA_FILE_NAME: String get() = "@data"
+
 /**
  * Read and envelope from file if the file exists, return null if file does not exist.
  *
@@ -93,14 +106,14 @@ fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? {
 fun IOPlugin.readEnvelopeFile(
     path: Path,
     readNonEnvelopes: Boolean = false,
-    formatPeeker: IOPlugin.(Binary) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat
+    formatPeeker: IOPlugin.(Path) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat
 ): Envelope? {
     if (!Files.exists(path)) return null
 
     //read two-files directory
     if (Files.isDirectory(path)) {
         val metaFile = Files.list(path).asSequence()
-            .singleOrNull { it.fileName.toString().startsWith("meta") }
+            .singleOrNull { it.fileName.toString().startsWith(IOPlugin.META_FILE_NAME) }
 
         val meta = if (metaFile == null) {
             EmptyMeta
@@ -108,7 +121,7 @@ fun IOPlugin.readEnvelopeFile(
             readMetaFile(metaFile)
         }
 
-        val dataFile = path.resolve("data")
+        val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME)
 
         val data: Binary? = if (Files.exists(dataFile)) {
             dataFile.asBinary()
@@ -119,30 +132,76 @@ fun IOPlugin.readEnvelopeFile(
         return SimpleEnvelope(meta, data)
     }
 
-    val binary = path.asBinary()
-
-    return formatPeeker(binary)?.run {
-        binary.read {
-            readObject()
-        }
+    return formatPeeker(path)?.let { format ->
+        FileEnvelope(path, format)
     } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary
-        SimpleEnvelope(Meta.empty, binary)
+        SimpleEnvelope(Meta.empty, path.asBinary())
     } else null
 }
 
+private fun Path.useOutput(consumer: Output.() -> Unit) {
+    //TODO forbid rewrite?
+    Files.newByteChannel(
+        this,
+        StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING
+    ).asOutput().use {
+        it.consumer()
+        it.flush()
+    }
+}
+
+/**
+ * Write a binary into file. Throws an error if file already exists
+ */
+fun <T : Any> IOFormat<T>.writeToFile(path: Path, obj: T) {
+    path.useOutput {
+        writeObject(obj)
+        flush()
+    }
+}
+
+/**
+ * Write envelope file to given [path] using [envelopeFormat] and optional [metaFormat]
+ */
+@DFExperimental
 fun IOPlugin.writeEnvelopeFile(
     path: Path,
     envelope: Envelope,
-    format: EnvelopeFormat = TaggedEnvelopeFormat
+    envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat,
+    metaFormat: MetaFormatFactory? = null
 ) {
-    val output = Files.newByteChannel(
-        path,
-        StandardOpenOption.WRITE,
-        StandardOpenOption.CREATE,
-        StandardOpenOption.TRUNCATE_EXISTING
-    ).asOutput()
+    path.useOutput {
+        with(envelopeFormat) {
+            writeEnvelope(envelope, metaFormat ?: envelopeFormat.defaultMetaFormat)
+        }
+    }
+}
 
-    with(format) {
-        output.writeObject(envelope)
+/**
+ * Write separate meta and data files to given directory [path]
+ */
+@DFExperimental
+fun IOPlugin.writeEnvelopeDirectory(
+    path: Path,
+    envelope: Envelope,
+    metaFormat: MetaFormatFactory = JsonMetaFormat
+) {
+    if (!Files.exists(path)) {
+        Files.createDirectories(path)
+    }
+    if (!Files.isDirectory(path)) {
+        error("Can't write envelope directory to file")
+    }
+    if (!envelope.meta.isEmpty()) {
+        writeMetaFile(path, envelope.meta, metaFormat)
+    }
+    val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME)
+    dataFile.useOutput {
+        envelope.data?.read {
+            val copied = copyTo(this@useOutput)
+            if (envelope.data?.size != ULong.MAX_VALUE && copied != envelope.data?.size?.toLong()) {
+                error("The number of copied bytes does not equal data size")
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
index b8bfafaa..585aced9 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
@@ -35,7 +35,7 @@ class FileEnvelopeTest {
     fun testFileWriteReadTagless() {
         Global.io.run {
             val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df")
-            writeEnvelopeFile(tmpPath, envelope, format = TaglessEnvelopeFormat)
+            writeEnvelopeFile(tmpPath, envelope, envelopeFormat = TaglessEnvelopeFormat)
             println(tmpPath.toUri())
             val restored: Envelope = readEnvelopeFile(tmpPath)!!
             assertTrue { envelope.contentEquals(restored) }
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt
index 37c35efc..64067dec 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt
@@ -7,9 +7,9 @@ import hep.dataforge.io.TaggedEnvelopeFormat
 import hep.dataforge.io.writeBytes
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.runBlocking
-import org.junit.AfterClass
-import org.junit.BeforeClass
-import org.junit.Test
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.BeforeAll
+import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.time.ExperimentalTime
 
@@ -30,13 +30,13 @@ class EnvelopeServerTest {
         @JvmStatic
         val echoEnvelopeServer = EnvelopeServer(Global, 7778, EchoResponder, GlobalScope)
 
-        @BeforeClass
+        @BeforeAll
         @JvmStatic
         fun start() {
             echoEnvelopeServer.start()
         }
 
-        @AfterClass
+        @AfterAll
         @JvmStatic
         fun close() {
             echoEnvelopeServer.stop()
diff --git a/dataforge-scripting/build.gradle.kts b/dataforge-scripting/build.gradle.kts
index 757f0c33..c848c1b1 100644
--- a/dataforge-scripting/build.gradle.kts
+++ b/dataforge-scripting/build.gradle.kts
@@ -19,8 +19,6 @@ kotlin {
         }
         val jvmTest by getting {
             dependencies {
-                implementation(kotlin("test"))
-                implementation(kotlin("test-junit"))
                 implementation("ch.qos.logback:logback-classic:1.2.3")
             }
         }
diff --git a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt
index 5a9ba56d..6dd61105 100644
--- a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt
+++ b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt
@@ -6,7 +6,7 @@ import hep.dataforge.meta.int
 import hep.dataforge.workspace.SimpleWorkspaceBuilder
 import hep.dataforge.workspace.context
 import hep.dataforge.workspace.target
-import org.junit.Test
+import kotlin.test.Test
 import kotlin.test.assertEquals
 
 
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt
deleted file mode 100644
index f6d27774..00000000
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package hep.dataforge.workspace
-
-import hep.dataforge.data.Data
-import hep.dataforge.io.Envelope
-import hep.dataforge.io.IOFormat
-import hep.dataforge.io.readWith
-import kotlin.reflect.KClass
-
-/**
- * Convert an [Envelope] to a data via given format. The actual parsing is done lazily.
- */
-fun <T : Any> Envelope.toData(type: KClass<out T>, format: IOFormat<T>): Data<T> = Data(type, meta) {
-    data?.readWith(format) ?: error("Can't convert envelope without data to Data")
-}
\ No newline at end of file
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
new file mode 100644
index 00000000..111bba76
--- /dev/null
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
@@ -0,0 +1,33 @@
+package hep.dataforge.workspace
+
+import hep.dataforge.data.Data
+import hep.dataforge.data.await
+import hep.dataforge.io.*
+import kotlinx.coroutines.coroutineScope
+import kotlinx.io.core.Input
+import kotlinx.io.core.buildPacket
+import kotlin.reflect.KClass
+
+/**
+ * Convert an [Envelope] to a data via given format. The actual parsing is done lazily.
+ */
+fun <T : Any> Envelope.toData(type: KClass<out T>, format: IOFormat<T>): Data<T> = Data(type, meta) {
+    data?.readWith(format) ?: error("Can't convert envelope without data to Data")
+}
+
+suspend fun <T : Any> Data<T>.toEnvelope(format: IOFormat<T>): Envelope {
+    val obj = coroutineScope {
+        await(this)
+    }
+    val binary = object : Binary {
+        override fun <R> read(block: Input.() -> R): R {
+            //TODO optimize away additional copy by creating inputs that reads directly from output
+            val packet = buildPacket {
+                format.run { writeObject(obj) }
+            }
+            return packet.block()
+        }
+
+    }
+    return SimpleEnvelope(meta, binary)
+}
\ No newline at end of file
diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
index b5ecb519..2b6d5454 100644
--- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
+++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt
@@ -1,17 +1,27 @@
 package hep.dataforge.workspace
 
+//import jdk.nio.zipfs.ZipFileSystemProvider
 import hep.dataforge.data.*
-import hep.dataforge.io.Envelope
-import hep.dataforge.io.IOFormat
-import hep.dataforge.io.IOPlugin
-import hep.dataforge.io.readEnvelopeFile
-import hep.dataforge.meta.Meta
-import hep.dataforge.meta.get
-import hep.dataforge.meta.string
+import hep.dataforge.io.*
+import hep.dataforge.meta.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.nio.file.FileSystem
 import java.nio.file.Files
 import java.nio.file.Path
+import java.nio.file.StandardCopyOption
+import java.nio.file.spi.FileSystemProvider
 import kotlin.reflect.KClass
 
+typealias FileFormatResolver<T> = (Path, Meta) -> IOFormat<T>
+
+//private val zipFSProvider = ZipFileSystemProvider()
+
+private fun newZFS(path: Path): FileSystem {
+    val fsProvider = FileSystemProvider.installedProviders().find { it.scheme == "jar" }
+        ?: error("Zip file system provider not found")
+    return fsProvider.newFileSystem(path, mapOf("create" to "true"))
+}
 
 /**
  * Read data with supported envelope format and binary format. If envelope format is null, then read binary directly from file.
@@ -22,62 +32,152 @@ import kotlin.reflect.KClass
  * @param metaFile the relative file for optional meta override
  * @param metaFileFormat the meta format for override
  */
+@DFExperimental
 fun <T : Any> IOPlugin.readDataFile(
     path: Path,
     type: KClass<out T>,
-    formatResolver: (Meta) -> IOFormat<T>
+    formatResolver: FileFormatResolver<T>
 ): Data<T> {
     val envelope = readEnvelopeFile(path, true) ?: error("Can't read data from $path")
-    val format = formatResolver(envelope.meta)
+    val format = formatResolver(path, envelope.meta)
     return envelope.toData(type, format)
 }
 
-//TODO wants multi-receiver
+@DFExperimental
+inline fun <reified T : Any> IOPlugin.readDataFile(path: Path): Data<T> =
+    readDataFile(path, T::class) { _, _ ->
+        resolveIOFormat<T>() ?: error("Can't resolve IO format for ${T::class}")
+    }
+
+/**
+ * Add file/directory-based data tree item
+ */
+@DFExperimental
 fun <T : Any> DataTreeBuilder<T>.file(
     plugin: IOPlugin,
     path: Path,
-    formatResolver: (Meta) -> IOFormat<T>
+    formatResolver: FileFormatResolver<T>
 ) {
-    plugin.run {
-        val data = readDataFile(path, type, formatResolver)
-        val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string
-            ?: path.fileName.toString().replace(".df", "")
-        datum(name, data)
+    //If path is a single file or a special directory, read it as single datum
+    if (!Files.isDirectory(path) || Files.list(path).allMatch { it.fileName.toString().startsWith("@") }) {
+        plugin.run {
+            val data = readDataFile(path, type, formatResolver)
+            val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string
+                ?: path.fileName.toString().replace(".df", "")
+            datum(name, data)
+        }
+    } else {
+        //otherwise, read as directory
+        plugin.run {
+            val data = readDataDirectory(path, type, formatResolver)
+            val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string
+                ?: path.fileName.toString().replace(".df", "")
+            node(name, data)
+        }
     }
 }
 
 /**
- * Read the directory as a data node
+ * Read the directory as a data node. If [path] is a zip archive, read it as directory
  */
+@DFExperimental
 fun <T : Any> IOPlugin.readDataDirectory(
     path: Path,
     type: KClass<out T>,
-    formatResolver: (Meta) -> IOFormat<T>
+    formatResolver: FileFormatResolver<T>
 ): DataNode<T> {
-    if (!Files.isDirectory(path)) error("Provided path $this is not a directory")
+    //read zipped data node
+    if (path.fileName != null && path.fileName.toString().endsWith(".zip")) {
+        //Using explicit Zip file system to avoid bizarre compatibility bugs
+        val fs = newZFS(path)
+        return readDataDirectory(fs.rootDirectories.first(), type, formatResolver)
+    }
+    if (!Files.isDirectory(path)) error("Provided path $path is not a directory")
     return DataNode(type) {
         Files.list(path).forEach { path ->
-            if (!path.fileName.toString().endsWith(".meta")) {
+            val fileName = path.fileName.toString()
+            if (fileName.startsWith(IOPlugin.META_FILE_NAME)) {
+                meta(readMetaFile(path))
+            } else if (!fileName.startsWith("@")) {
                 file(this@readDataDirectory, path, formatResolver)
             }
         }
     }
 }
 
-fun <T : Any> DataTreeBuilder<T>.directory(
-    plugin: IOPlugin,
+@DFExperimental
+inline fun <reified T : Any> IOPlugin.readDataDirectory(path: Path): DataNode<T> =
+    readDataDirectory(path, T::class) { _, _ ->
+        resolveIOFormat<T>() ?: error("Can't resolve IO format for ${T::class}")
+    }
+
+/**
+ * Write data tree to existing directory or create a new one using default [java.nio.file.FileSystem] provider
+ */
+@DFExperimental
+suspend fun <T : Any> IOPlugin.writeDataDirectory(
     path: Path,
-    formatResolver: (Meta) -> IOFormat<T>
+    node: DataNode<T>,
+    format: IOFormat<T>,
+    envelopeFormat: EnvelopeFormat? = null,
+    metaFormat: MetaFormatFactory? = null
 ) {
-    plugin.run {
-        val data = readDataDirectory(path, type, formatResolver)
-        val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string
-            ?: path.fileName.toString().replace(".df", "")
-        node(name, data)
+    withContext(Dispatchers.IO) {
+        if (!Files.exists(path)) {
+            Files.createDirectories(path)
+        } else if (!Files.isDirectory(path)) {
+            error("Can't write a node into file")
+        }
+        node.items.forEach { (token, item) ->
+            val childPath = path.resolve(token.toString())
+            when (item) {
+                is DataItem.Node -> {
+                    writeDataDirectory(childPath, item.node, format, envelopeFormat)
+                }
+                is DataItem.Leaf -> {
+                    val envelope = item.data.toEnvelope(format)
+                    if (envelopeFormat != null) {
+                        writeEnvelopeFile(childPath, envelope, envelopeFormat, metaFormat)
+                    } else {
+                        writeEnvelopeDirectory(childPath, envelope, metaFormat ?: JsonMetaFormat)
+                    }
+                }
+            }
+        }
+        if (!node.meta.isEmpty()) {
+            writeMetaFile(path, node.meta, metaFormat ?: JsonMetaFormat)
+        }
     }
 }
 
-
-
-
+suspend fun <T : Any> IOPlugin.writeZip(
+    path: Path,
+    node: DataNode<T>,
+    format: IOFormat<T>,
+    envelopeFormat: EnvelopeFormat? = null,
+    metaFormat: MetaFormatFactory? = null
+) {
+    withContext(Dispatchers.IO) {
+        val actualFile = if (path.toString().endsWith(".zip")) {
+            path
+        } else {
+            path.resolveSibling(path.fileName.toString() + ".zip")
+        }
+        if (Files.exists(actualFile) && Files.size(path) == 0.toLong()) {
+            Files.delete(path)
+        }
+        //Files.createFile(actualFile)
+        newZFS(actualFile).use { zipfs ->
+            val internalTargetPath = zipfs.getPath("/")
+            Files.createDirectories(internalTargetPath)
+            val tmp = Files.createTempDirectory("df_zip")
+            writeDataDirectory(tmp, node, format, envelopeFormat, metaFormat)
+            Files.list(tmp).forEach { sourcePath ->
+                val targetPath = sourcePath.fileName.toString()
+                val internalTargetPath = internalTargetPath.resolve(targetPath)
+                Files.copy(sourcePath, internalTargetPath, StandardCopyOption.REPLACE_EXISTING)
+            }
+        }
+    }
+}
 
diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt
index c449ffc3..083d3f57 100644
--- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt
+++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt
@@ -6,8 +6,8 @@ import hep.dataforge.context.PluginTag
 import hep.dataforge.data.*
 import hep.dataforge.meta.Meta
 import hep.dataforge.names.asName
-import org.junit.Test
 import kotlin.reflect.KClass
+import kotlin.test.Test
 import kotlin.test.assertEquals
 
 
diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
new file mode 100644
index 00000000..b73a4d59
--- /dev/null
+++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
@@ -0,0 +1,72 @@
+package hep.dataforge.workspace
+
+import hep.dataforge.context.Global
+import hep.dataforge.data.*
+import hep.dataforge.io.IOFormat
+import hep.dataforge.io.io
+import hep.dataforge.meta.DFExperimental
+import kotlinx.coroutines.runBlocking
+import kotlinx.io.core.Input
+import kotlinx.io.core.Output
+import kotlinx.io.core.readText
+import kotlinx.io.core.writeText
+import java.nio.file.Files
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class FileDataTest {
+    val dataNode = DataNode<String> {
+        node("dir") {
+            static("a", "Some string") {
+                "content" put "Some string"
+            }
+        }
+        static("b", "root data")
+        meta {
+            "content" put "This is root meta node"
+        }
+    }
+
+    object StringIOFormat : IOFormat<String> {
+        override fun Output.writeObject(obj: String) {
+            writeText(obj)
+        }
+
+        override fun Input.readObject(): String {
+            return readText()
+        }
+
+    }
+
+    @Test
+    @DFExperimental
+    fun testDataWriteRead() {
+        Global.io.run {
+            val dir = Files.createTempDirectory("df_data_node")
+            runBlocking {
+                writeDataDirectory(dir, dataNode, StringIOFormat)
+            }
+            println(dir.toUri().toString())
+            val reconstructed = readDataDirectory(dir, String::class) { _, _ -> StringIOFormat }
+            assertEquals(dataNode["dir.a"]?.meta, reconstructed["dir.a"]?.meta)
+            assertEquals(dataNode["b"]?.data?.get(), reconstructed["b"]?.data?.get())
+        }
+    }
+
+
+    @Test
+    @Ignore
+    fun testZipWriteRead() {
+        Global.io.run {
+            val zip = Files.createTempFile("df_data_node", ".zip")
+            runBlocking {
+                writeZip(zip, dataNode, StringIOFormat)
+            }
+            println(zip.toUri().toString())
+            val reconstructed = readDataDirectory(zip, String::class) { _, _ -> StringIOFormat }
+            assertEquals(dataNode["dir.a"]?.meta, reconstructed["dir.a"]?.meta)
+            assertEquals(dataNode["b"]?.data?.get(), reconstructed["b"]?.data?.get())
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt
index 3a40e783..a4df6a4b 100644
--- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt
+++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt
@@ -7,7 +7,7 @@ import hep.dataforge.meta.builder
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
 import hep.dataforge.names.plus
-import org.junit.Test
+import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
 
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b 100644
GIT binary patch
delta 16535
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
U#ULQK|Neget<zCj{4eSM0|LcRF8}}l

delta 13615
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
O=|=?tu^Rs0)%hQKQa~60

diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7c4388a9..6ce793f2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
index 83f2acfd..2fe81a7d
--- a/gradlew
+++ b/gradlew
@@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
         else
             eval `echo args$i`="\"$arg\""
         fi
-        i=$((i+1))
+        i=`expr $i + 1`
     done
     case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
     esac
 fi
 
@@ -175,14 +175,9 @@ save () {
     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
     echo " "
 }
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
 
 # Collect all arguments for the java command, following the shell quoting and substitution rules
 eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
 
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
-  cd "$(dirname "$0")"
-fi
-
 exec "$JAVACMD" "$@"

From 2e643287ef7c1f7d44ccf2b01b4348b8b7d25b43 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sat, 30 Nov 2019 23:36:06 +0300
Subject: [PATCH 09/41] Moving to io-2

---
 build.gradle.kts                              | 10 +-
 dataforge-io/build.gradle.kts                 | 19 ++--
 .../io/yaml/FrontMatterEnvelopeFormat.kt      | 13 +--
 .../hep/dataforge/io/yaml/YamlMetaFormat.kt   | 12 +--
 .../kotlin/hep/dataforge/io/Binary.kt         | 94 ------------------
 .../hep/dataforge/io/BinaryMetaFormat.kt      | 15 ++-
 .../kotlin/hep/dataforge/io/Envelope.kt       |  1 +
 .../hep/dataforge/io/EnvelopeBuilder.kt       |  8 +-
 .../kotlin/hep/dataforge/io/EnvelopeFormat.kt |  4 +-
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt  | 32 +++++--
 .../kotlin/hep/dataforge/io/IOFormat.kt       | 46 ++-------
 .../kotlin/hep/dataforge/io/IOPlugin.kt       |  2 +-
 .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 20 ++--
 .../kotlin/hep/dataforge/io/MetaFormat.kt     | 25 ++---
 .../hep/dataforge/io/TaggedEnvelopeFormat.kt  | 73 +++++++-------
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 96 +++++++++++--------
 .../io/serialization/MetaSerializer.kt        |  1 +
 .../io/serialization/serializationUtils.kt    |  1 +
 .../hep/dataforge/io/EnvelopeFormatTest.kt    | 18 ++--
 .../kotlin/hep/dataforge/io/MetaFormatTest.kt | 10 ++
 .../hep/dataforge/io/MetaSerializerTest.kt    |  4 +-
 ...{EnvelopePartsTest.kt => MultipartTest.kt} | 19 ++--
 .../kotlin/hep/dataforge/io/FileBinary.kt     | 31 ------
 .../kotlin/hep/dataforge/io/FileEnvelope.kt   | 15 ++-
 .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 37 +++----
 .../hep/dataforge/io/tcp/EnvelopeClient.kt    |  9 +-
 .../hep/dataforge/io/tcp/EnvelopeServer.kt    |  7 +-
 .../dataforge/io/tcp/InputStreamAsInput.kt    | 32 -------
 .../kotlin/hep/dataforge/io/tcp/streams.kt    | 62 ++++++++++++
 .../kotlin/hep/dataforge/io/FileBinaryTest.kt | 16 ++--
 .../hep/dataforge/io/FileEnvelopeTest.kt      |  1 +
 .../dataforge/io/tcp/EnvelopeServerTest.kt    | 15 +--
 .../kotlin/hep/dataforge/output/TextOutput.kt |  8 +-
 .../hep/dataforge/workspace/envelopeData.kt   |  9 +-
 .../hep/dataforge/workspace/FileDataTest.kt   |  8 +-
 settings.gradle.kts                           |  4 +-
 36 files changed, 351 insertions(+), 426 deletions(-)
 delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
 rename dataforge-io/src/commonTest/kotlin/hep/dataforge/io/{EnvelopePartsTest.kt => MultipartTest.kt} (54%)
 delete mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt
 delete mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt
 create mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index cf2f4387..9e432336 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,9 +1,9 @@
 import scientifik.ScientifikExtension
 
 plugins {
-    id("scientifik.mpp") version "0.2.4" apply false
-    id("scientifik.jvm") version "0.2.4" apply false
-    id("scientifik.publish") version "0.2.4" apply false
+    id("scientifik.mpp") version "0.2.5" apply false
+    id("scientifik.jvm") version "0.2.5" apply false
+    id("scientifik.publish") version "0.2.5" apply false
 }
 
 val dataforgeVersion by extra("0.1.5-dev-3")
@@ -14,6 +14,10 @@ val githubProject by extra("dataforge-core")
 allprojects {
     group = "hep.dataforge"
     version = dataforgeVersion
+
+    repositories {
+        mavenLocal()
+    }
 }
 
 subprojects {
diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts
index 083e9d53..bb27ceff 100644
--- a/dataforge-io/build.gradle.kts
+++ b/dataforge-io/build.gradle.kts
@@ -4,27 +4,30 @@ plugins {
 
 description = "IO module"
 
-scientifik{
+scientifik {
     withSerialization()
-    withIO()
+    //withIO()
 }
 
+val ioVersion by rootProject.extra("0.2.0-npm-dev-2")
 
 kotlin {
     sourceSets {
-        commonMain{
+        commonMain {
             dependencies {
                 api(project(":dataforge-context"))
+                api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion")
+                //api("org.jetbrains.kotlinx:kotlinx-io-metadata:$ioVersion")
             }
         }
-        jvmMain{
+        jvmMain {
             dependencies {
-
+                //api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion")
             }
         }
-        jsMain{
-            dependencies{
-                api(npm("text-encoding"))
+        jsMain {
+            dependencies {
+                //api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion")
             }
         }
     }
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
index 023635e2..be6e1d75 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
@@ -5,7 +5,8 @@ import hep.dataforge.io.*
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
-import kotlinx.io.core.*
+import kotlinx.io.Input
+import kotlinx.io.Output
 import kotlinx.serialization.toUtf8Bytes
 
 @DFExperimental
@@ -18,7 +19,7 @@ class FrontMatterEnvelopeFormat(
         var line: String = ""
         var offset = 0u
         do {
-            line = readUTF8Line() ?: error("Input does not contain front matter separator")
+            line = readUtf8Line() ?: error("Input does not contain front matter separator")
             offset += line.toUtf8Bytes().size.toUInt()
         } while (!line.startsWith(SEPARATOR))
 
@@ -28,7 +29,7 @@ class FrontMatterEnvelopeFormat(
 
         val metaBlock = buildPacket {
             do {
-                line = readUTF8Line() ?: error("Input does not contain closing front matter separator")
+                line = readUtf8Line() ?: error("Input does not contain closing front matter separator")
                 appendln(line)
                 offset += line.toUtf8Bytes().size.toUInt()
             } while (!line.startsWith(SEPARATOR))
@@ -40,7 +41,7 @@ class FrontMatterEnvelopeFormat(
     override fun Input.readObject(): Envelope {
         var line: String = ""
         do {
-            line = readUTF8Line() ?: error("Input does not contain front matter separator")
+            line = readUtf8Line() ?: error("Input does not contain front matter separator")
         } while (!line.startsWith(SEPARATOR))
 
         val readMetaFormat =
@@ -49,7 +50,7 @@ class FrontMatterEnvelopeFormat(
 
         val metaBlock = buildPacket {
             do {
-                appendln(readUTF8Line() ?: error("Input does not contain closing front matter separator"))
+                appendln(readUtf8Line() ?: error("Input does not contain closing front matter separator"))
             } while (!line.startsWith(SEPARATOR))
         }
         val meta = readMetaFormat.fromBytes(metaBlock)
@@ -76,7 +77,7 @@ class FrontMatterEnvelopeFormat(
         }
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
-            val line = input.readUTF8Line(3, 30)
+            val line = input.readUtf8Line(3, 30)
             return if (line != null && line.startsWith("---")) {
                 invoke()
             } else {
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
index 7130518d..5e31a69e 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
@@ -8,12 +8,10 @@ import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.toMap
 import hep.dataforge.meta.toMeta
-import hep.dataforge.names.Name
-import hep.dataforge.names.plus
-import kotlinx.io.core.Input
-import kotlinx.io.core.Output
-import kotlinx.io.core.readUByte
-import kotlinx.io.core.writeText
+import kotlinx.io.Input
+import kotlinx.io.Output
+import kotlinx.io.readUByte
+import kotlinx.io.writeText
 import org.yaml.snakeyaml.Yaml
 import java.io.InputStream
 
@@ -47,7 +45,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
     companion object : MetaFormatFactory {
         override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta)
 
-        override val name: Name = super.name + "yaml"
+        override val shortName = "yaml"
 
         override val key: Short = 0x594d //YM
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
deleted file mode 100644
index b671928b..00000000
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package hep.dataforge.io
-
-import kotlinx.io.core.*
-import kotlin.math.min
-
-/**
- * A source of binary data
- */
-interface Binary {
-    /**
-     * The size of binary in bytes. [ULong.MAX_VALUE] if size is not defined and input should be read until its end is reached
-     */
-    val size: ULong get() = ULong.MAX_VALUE
-
-    /**
-     * Read continuous [Input] from this binary stating from the beginning.
-     * The input is automatically closed on scope close.
-     * Some implementation may forbid this to be called twice. In this case second call will throw an exception.
-     */
-    fun <R> read(block: Input.() -> R): R
-
-    companion object {
-        val EMPTY = EmptyBinary
-    }
-}
-
-/**
- * A [Binary] with addition random access functionality. It by default allows multiple [read] operations.
- */
-@ExperimentalUnsignedTypes
-interface RandomAccessBinary : Binary {
-    /**
-     * Read at most [size] of bytes starting at [from] offset from the beginning of the binary.
-     * This method could be called multiple times simultaneously.
-     *
-     * If size
-     */
-    fun <R> read(from: UInt, size: UInt = UInt.MAX_VALUE, block: Input.() -> R): R
-
-    override fun <R> read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block)
-}
-
-fun Binary.toBytes(): ByteArray = read {
-    readBytes()
-}
-
-fun Binary.contentToString(): String = read {
-    readText()
-}
-
-@ExperimentalUnsignedTypes
-fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read(from, size) {
-    buildPacket { copyTo(this) }
-}
-
-@ExperimentalUnsignedTypes
-object EmptyBinary : RandomAccessBinary {
-
-    override val size: ULong = 0u
-
-    override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
-        error("The binary is empty")
-    }
-}
-
-@ExperimentalUnsignedTypes
-inline class ArrayBinary(val array: ByteArray) : RandomAccessBinary {
-    override val size: ULong get() = array.size.toULong()
-
-    override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
-        val theSize = min(size, array.size.toUInt() - from)
-        return buildPacket {
-            writeFully(array, from.toInt(), theSize.toInt())
-        }.block()
-    }
-}
-
-fun ByteArray.asBinary() = ArrayBinary(this)
-
-/**
- * Read given binary as object using given format
- */
-fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
-    read {
-        readObject()
-    }
-}
-
-//fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary {
-//    val packet = buildPacket {
-//        writeObject(obj)
-//    }
-//    return ArrayBinary(packet.readBytes())
-//}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
index ba9886d8..1182bfbc 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
@@ -3,16 +3,13 @@ package hep.dataforge.io
 import hep.dataforge.context.Context
 import hep.dataforge.descriptors.NodeDescriptor
 import hep.dataforge.meta.*
-import hep.dataforge.names.Name
-import hep.dataforge.names.plus
 import hep.dataforge.values.*
-import kotlinx.io.core.Input
-import kotlinx.io.core.Output
-import kotlinx.io.core.readText
-import kotlinx.io.core.writeText
+import kotlinx.io.*
+import kotlinx.io.text.readUtf8String
+import kotlinx.io.text.writeUtf8String
 
 object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
-    override val name: Name = super.name + "bin"
+    override val shortName: String = "bin"
     override val key: Short = 0x4249//BI
 
     override fun invoke(meta: Meta, context: Context): MetaFormat = this
@@ -25,7 +22,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
 
     private fun Output.writeString(str: String) {
         writeInt(str.length)
-        writeText(str)
+        writeUtf8String(str)
     }
 
     fun Output.writeValue(value: Value) {
@@ -93,7 +90,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
 
     private fun Input.readString(): String {
         val length = readInt()
-        return readText(max = length)
+        return readUtf8String(length)
     }
 
     @Suppress("UNCHECKED_CAST")
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
index abf8a504..adffdbf7 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
@@ -6,6 +6,7 @@ import hep.dataforge.meta.get
 import hep.dataforge.meta.string
 import hep.dataforge.names.asName
 import hep.dataforge.names.plus
+import kotlinx.io.Binary
 
 interface Envelope {
     val meta: Meta
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
index 354b4586..a0b21b64 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
@@ -1,9 +1,7 @@
 package hep.dataforge.io
 
 import hep.dataforge.meta.*
-import kotlinx.io.core.Output
-import kotlinx.io.core.buildPacket
-import kotlinx.io.core.readBytes
+import kotlinx.io.*
 
 class EnvelopeBuilder {
     private val metaBuilder = MetaBuilder()
@@ -27,10 +25,10 @@ class EnvelopeBuilder {
      * Construct a binary and transform it into byte-array based buffer
      */
     fun data(block: Output.() -> Unit) {
-        val bytes = buildPacket {
+        val bytes = buildBytes {
             block()
         }
-        data = ArrayBinary(bytes.readBytes())
+        data = ArrayBinary(bytes.toByteArray())
     }
 
     internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
index 49a25919..bf5b85f5 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt
@@ -7,8 +7,8 @@ import hep.dataforge.meta.Meta
 import hep.dataforge.names.Name
 import hep.dataforge.names.asName
 import hep.dataforge.provider.Type
-import kotlinx.io.core.Input
-import kotlinx.io.core.Output
+import kotlinx.io.Input
+import kotlinx.io.Output
 import kotlin.reflect.KClass
 
 /**
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index d1b86195..d7d981c4 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -24,16 +24,24 @@ object EnvelopeParts {
 /**
  * Append multiple serialized envelopes to the data block. Previous data is erased if it was present
  */
-fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collection<Envelope>) {
+@DFExperimental
+fun EnvelopeBuilder.multipart(
+    envelopes: Collection<Envelope>,
+    format: EnvelopeFormatFactory,
+    formatMeta: Meta = EmptyMeta
+) {
     dataType = MULTIPART_DATA_TYPE
     meta {
         SIZE_KEY put envelopes.size
         FORMAT_NAME_KEY put format.name.toString()
+        if (!formatMeta.isEmpty()) {
+            FORMAT_META_KEY put formatMeta
+        }
     }
     data {
-        format.run {
+        format(formatMeta).run {
             envelopes.forEach {
-                writeObject(it)
+                writeEnvelope(it)
             }
         }
     }
@@ -43,18 +51,25 @@ fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collecti
  * Create a multipart partition in the envelope adding additional name-index mapping in meta
  */
 @DFExperimental
-fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Map<String, Envelope>) {
+fun EnvelopeBuilder.multipart(
+    envelopes: Map<String, Envelope>,
+    format: EnvelopeFormatFactory,
+    formatMeta: Meta = EmptyMeta
+) {
     dataType = MULTIPART_DATA_TYPE
     meta {
         SIZE_KEY put envelopes.size
         FORMAT_NAME_KEY put format.name.toString()
+        if (!formatMeta.isEmpty()) {
+            FORMAT_META_KEY put formatMeta
+        }
     }
     data {
         format.run {
             var counter = 0
-            envelopes.forEach {(key, envelope)->
+            envelopes.forEach { (key, envelope) ->
                 writeObject(envelope)
-                meta{
+                meta {
                     append(INDEX_KEY, buildMeta {
                         "key" put key
                         "index" put counter
@@ -66,14 +81,17 @@ fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Map<Stri
     }
 }
 
+@DFExperimental
 fun EnvelopeBuilder.multipart(
     formatFactory: EnvelopeFormatFactory,
+    formatMeta: Meta = EmptyMeta,
     builder: suspend SequenceScope<Envelope>.() -> Unit
-) = multipart(formatFactory, sequence(builder).toList())
+) = multipart(sequence(builder).toList(), formatFactory, formatMeta)
 
 /**
  * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null.
  */
+@DFExperimental
 fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? {
     return when (dataType) {
         MULTIPART_DATA_TYPE -> {
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
index e5497365..44f68738 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt
@@ -10,12 +10,9 @@ import hep.dataforge.names.Name
 import hep.dataforge.names.asName
 import hep.dataforge.provider.Type
 import hep.dataforge.values.Value
-import kotlinx.io.core.*
+import kotlinx.io.*
+import kotlinx.io.buffer.Buffer
 import kotlinx.io.pool.ObjectPool
-import kotlinx.serialization.ImplicitReflectionSerializer
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.cbor.Cbor
-import kotlinx.serialization.serializer
 import kotlin.reflect.KClass
 
 /**
@@ -49,7 +46,7 @@ class ListIOFormat<T : Any>(val format: IOFormat<T>) : IOFormat<List<T>> {
 
 val <T : Any> IOFormat<T>.list get() = ListIOFormat(this)
 
-fun ObjectPool<IoBuffer>.fill(block: IoBuffer.() -> Unit): IoBuffer {
+fun ObjectPool<Buffer>.fill(block: Buffer.() -> Unit): Buffer {
     val buffer = borrow()
     return try {
         buffer.apply(block)
@@ -71,19 +68,11 @@ interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
     }
 }
 
-@Deprecated("To be removed in io-2")
-inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
-    val builder = BytePacketBuilder(headerSizeHint, IoBuffer.NoPool)
-    block(builder)
-    return builder.build()
-}
+fun <T : Any> IOFormat<T>.writeBytes(obj: T): Bytes = buildBytes { writeObject(obj) }
 
-fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
 
-@Deprecated("Not to be used outside tests due to double buffer write")
-fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
-@Deprecated("Not to be used outside tests due to double buffer write")
-fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = buildPacket { writeFully(array) }.readObject()
+fun <T : Any> IOFormat<T>.writeByteArray(obj: T): ByteArray = buildBytes { writeObject(obj) }.toByteArray()
+fun <T : Any> IOFormat<T>.readByteArray(array: ByteArray): T = array.asBinary().read { readObject() }
 
 object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
     override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this
@@ -117,25 +106,10 @@ object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
 }
 
 /**
- * Experimental
+ * Read given binary as object using given format
  */
-@ImplicitReflectionSerializer
-class SerializerIOFormat<T : Any>(
-    type: KClass<T>,
-    val serializer: KSerializer<T> = type.serializer()
-) : IOFormat<T> {
-
-    //override val name: Name = type.simpleName?.toName() ?: EmptyName
-
-
-    override fun Output.writeObject(obj: T) {
-        val bytes = Cbor.plain.dump(serializer, obj)
-        writeFully(bytes)
-    }
-
-    override fun Input.readObject(): T {
-        //FIXME reads the whole input
-        val bytes = readBytes()
-        return Cbor.plain.load(serializer, bytes)
+fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
+    read {
+        readObject()
     }
 }
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
index eb975029..6144a211 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt
@@ -20,7 +20,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
         metaFormatFactories.find { it.key == key }?.invoke(meta)
 
     fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? =
-        metaFormatFactories.find { it.name.last().toString() == name }?.invoke(meta)
+        metaFormatFactories.find { it.shortName == name }?.invoke(meta)
 
     val envelopeFormatFactories by lazy {
         context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
index 5c10505d..9b78cbb3 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
@@ -9,15 +9,15 @@ import hep.dataforge.descriptors.ValueDescriptor
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaBase
 import hep.dataforge.meta.MetaItem
-import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
-import hep.dataforge.names.plus
 import hep.dataforge.names.toName
 import hep.dataforge.values.*
-import kotlinx.io.core.Input
-import kotlinx.io.core.Output
-import kotlinx.io.core.readText
-import kotlinx.io.core.writeText
+import kotlinx.io.Input
+import kotlinx.io.Output
+import kotlinx.io.text.readUtf8String
+import kotlinx.io.text.writeUtf8String
+
+
 import kotlinx.serialization.json.*
 import kotlin.collections.component1
 import kotlin.collections.component2
@@ -28,11 +28,11 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
 
     override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
         val jsonObject = meta.toJson(descriptor)
-        writeText(json.stringify(JsonObjectSerializer, jsonObject))
+        writeUtf8String(json.stringify(JsonObjectSerializer, jsonObject))
     }
 
     override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
-        val str = readText()
+        val str = readUtf8String()
         val jsonElement = json.parseJson(str)
         return jsonElement.toMeta()
     }
@@ -40,13 +40,13 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
     companion object : MetaFormatFactory {
         override fun invoke(meta: Meta, context: Context): MetaFormat = default
 
-        override val name: Name = super.name + "json"
+        override val shortName = "json"
         override val key: Short = 0x4a53//"JS"
 
         private val default = JsonMetaFormat()
 
         override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
-            default.run { writeMeta(meta,descriptor) }
+            default.run { writeMeta(meta, descriptor) }
 
         override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
             default.run { readMeta(descriptor) }
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
index 9d1af81a..d22359c8 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
@@ -6,8 +6,9 @@ import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
 import hep.dataforge.meta.Meta
 import hep.dataforge.names.Name
 import hep.dataforge.names.asName
+import hep.dataforge.names.plus
 import hep.dataforge.provider.Type
-import kotlinx.io.core.*
+import kotlinx.io.*
 import kotlin.reflect.KClass
 
 /**
@@ -28,11 +29,13 @@ interface MetaFormat : IOFormat<Meta> {
 
 @Type(META_FORMAT_TYPE)
 interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat {
-    override val name: Name get() = "meta".asName()
+    val shortName: String
+
+    override val name: Name get() = "meta".asName() + shortName
 
     override val type: KClass<out Meta> get() = Meta::class
 
-    val key: Short
+    val key: Short get() = name.hashCode().toShort()
 
     override operator fun invoke(meta: Meta, context: Context): MetaFormat
 
@@ -41,24 +44,16 @@ interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat {
     }
 }
 
-fun Meta.toString(format: MetaFormat): String = buildPacket {
+fun Meta.toString(format: MetaFormat): String = buildBytes {
     format.run { writeObject(this@toString) }
-}.readText()
+}.toByteArray().decodeToString()
 
 fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory())
 
-fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): ByteReadPacket = buildPacket {
-    format.run { writeObject(this@toBytes) }
-}
-
 fun MetaFormat.parse(str: String): Meta {
-    return buildPacket { writeText(str) }.readObject()
+    return str.encodeToByteArray().read { readObject() }
 }
 
-fun MetaFormatFactory.parse(str: String): Meta = invoke().parse(str)
-
-fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta {
-    return packet.readObject()
-}
+fun MetaFormatFactory.parse(str: String, formatMeta: Meta): Meta = invoke(formatMeta).parse(str)
 
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
index a461d257..01888dac 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
@@ -7,48 +7,51 @@ import hep.dataforge.meta.string
 import hep.dataforge.names.Name
 import hep.dataforge.names.plus
 import hep.dataforge.names.toName
-import kotlinx.io.charsets.Charsets
-import kotlinx.io.core.*
+import kotlinx.io.*
+import kotlinx.io.text.readRawString
+import kotlinx.io.text.writeRawString
 
-@ExperimentalUnsignedTypes
+@ExperimentalIoApi
 class TaggedEnvelopeFormat(
     val io: IOPlugin,
-    val version: VERSION = TaggedEnvelopeFormat.VERSION.DF02
+    val version: VERSION = VERSION.DF02
 ) : EnvelopeFormat {
 
 //    private val metaFormat = io.metaFormat(metaFormatKey)
 //        ?: error("Meta format with key $metaFormatKey could not be resolved in $io")
 
 
-    private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) {
-        writeText(START_SEQUENCE)
-        writeText(version.name)
+    private fun Tag.toBytes() = buildBytes(24) {
+        writeRawString(START_SEQUENCE)
+        writeRawString(version.name)
         writeShort(metaFormatKey)
         writeUInt(metaSize)
         when (version) {
-            TaggedEnvelopeFormat.VERSION.DF02 -> {
+            VERSION.DF02 -> {
                 writeUInt(dataSize.toUInt())
             }
-            TaggedEnvelopeFormat.VERSION.DF03 -> {
+            VERSION.DF03 -> {
                 writeULong(dataSize)
             }
         }
-        writeText(END_SEQUENCE)
+        writeRawString(END_SEQUENCE)
     }
 
     override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
         val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
-        val metaBytes = metaFormat.writePacket(envelope.meta)
+        val metaBytes = metaFormat.writeBytes(envelope.meta)
         val actualSize: ULong = if (envelope.data == null) {
-            0u
+            0
         } else {
-            envelope.data?.size ?: ULong.MAX_VALUE
+            envelope.data?.size ?: Binary.INFINITE
+        }.toULong()
+        val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize)
+        writeBinary(tag.toBytes())
+        writeBinary(metaBytes)
+        writeRawString("\r\n")
+        envelope.data?.let {
+            writeBinary(it)
         }
-        val tag = Tag(metaFormatFactory.key, metaBytes.remaining.toUInt() + 2u, actualSize)
-        writePacket(tag.toBytes())
-        writePacket(metaBytes)
-        writeText("\r\n")
-        envelope.data?.read { copyTo(this@writeEnvelope) }
         flush()
     }
 
@@ -64,14 +67,17 @@ class TaggedEnvelopeFormat(
         val metaFormat = io.metaFormat(tag.metaFormatKey)
             ?: error("Meta format with key ${tag.metaFormatKey} not found")
 
-        val metaBytes = readBytes(tag.metaSize.toInt())
-        val metaPacket = buildPacket {
-            writeFully(metaBytes)
+        val meta: Meta = limit(tag.metaSize.toInt()).use {
+            metaFormat.run {
+                readObject()
+            }
         }
-        val dataBytes = readBytes(tag.dataSize.toInt())
 
-        val meta = metaFormat.run { metaPacket.readObject() }
-        return SimpleEnvelope(meta, ArrayBinary(dataBytes))
+        val data = buildBytes {
+            writeInput(this@readObject, tag.dataSize.toInt())
+        }
+
+        return SimpleEnvelope(meta, data)
     }
 
     override fun Input.readPartial(): PartialEnvelope {
@@ -80,8 +86,11 @@ class TaggedEnvelopeFormat(
         val metaFormat = io.metaFormat(tag.metaFormatKey)
             ?: error("Meta format with key ${tag.metaFormatKey} not found")
 
-        val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
-        val meta = metaFormat.run { metaPacket.readObject() }
+        val meta: Meta = limit(tag.metaSize.toInt()).run {
+            metaFormat.run {
+                readObject()
+            }
+        }
 
         return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize)
     }
@@ -107,16 +116,16 @@ class TaggedEnvelopeFormat(
             val io = context.io
 
             val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name
-            val metaFormatFactory = io.metaFormatFactories.find { it.name == metaFormatName }
-                ?: error("Meta format could not be resolved")
+            //Check if appropriate factory exists
+            io.metaFormatFactories.find { it.name == metaFormatName } ?: error("Meta format could not be resolved")
 
             return TaggedEnvelopeFormat(io)
         }
 
         private fun Input.readTag(version: VERSION): Tag {
-            val start = readTextExactBytes(2, charset = Charsets.ISO_8859_1)
+            val start = readRawString(2)
             if (start != START_SEQUENCE) error("The input is not an envelope")
-            val versionString = readTextExactBytes(4, charset = Charsets.ISO_8859_1)
+            val versionString = readRawString(4)
             if (version.name != versionString) error("Wrong version of DataForge: expected $version but found $versionString")
             val metaFormatKey = readShort()
             val metaLength = readUInt()
@@ -124,14 +133,14 @@ class TaggedEnvelopeFormat(
                 VERSION.DF02 -> readUInt().toULong()
                 VERSION.DF03 -> readULong()
             }
-            val end = readTextExactBytes(4, charset = Charsets.ISO_8859_1)
+            val end = readRawString(4)
             if (end != END_SEQUENCE) error("The input is not an envelope")
             return Tag(metaFormatKey, metaLength, dataLength)
         }
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
             return try {
-                val header = input.readTextExactBytes(6)
+                val header = input.readRawString(6)
                 when (header.substring(2..5)) {
                     VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02)
                     VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03)
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index 1cc62a2b..a8b72cda 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -3,9 +3,14 @@ package hep.dataforge.io
 import hep.dataforge.context.Context
 import hep.dataforge.meta.*
 import hep.dataforge.names.asName
-import kotlinx.io.core.*
+import kotlinx.io.*
+import kotlinx.io.text.readRawString
+import kotlinx.io.text.readUtf8Line
+import kotlinx.io.text.writeRawString
+import kotlinx.io.text.writeUtf8String
 import kotlinx.serialization.toUtf8Bytes
 
+@ExperimentalIoApi
 class TaglessEnvelopeFormat(
     val io: IOPlugin,
     meta: Meta = EmptyMeta
@@ -15,47 +20,46 @@ class TaglessEnvelopeFormat(
     private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START
 
     private fun Output.writeProperty(key: String, value: Any) {
-        writeText("#? $key: $value;\r\n")
+        writeUtf8String("#? $key: $value;\r\n")
     }
 
     override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
         val metaFormat = metaFormatFactory(formatMeta, io.context)
 
         //printing header
-        writeText(TAGLESS_ENVELOPE_HEADER + "\r\n")
+        writeRawString(TAGLESS_ENVELOPE_HEADER + "\r\n")
 
         //printing all properties
-        writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type)
+        writeProperty(META_TYPE_PROPERTY, metaFormatFactory.shortName)
         //TODO add optional metaFormat properties
-        val actualSize: ULong = if (envelope.data == null) {
-            0u
+        val actualSize: Int = if (envelope.data == null) {
+            0
         } else {
-            envelope.data?.size ?: ULong.MAX_VALUE
+            envelope.data?.size ?: Binary.INFINITE
         }
 
         writeProperty(DATA_LENGTH_PROPERTY, actualSize)
 
         //Printing meta
         if (!envelope.meta.isEmpty()) {
-            val metaBytes = metaFormat.writePacket(envelope.meta)
-            writeProperty(META_LENGTH_PROPERTY, metaBytes.remaining)
-            writeText(metaStart + "\r\n")
-            writePacket(metaBytes)
-            writeText("\r\n")
+            val metaBytes = metaFormat.writeBytes(envelope.meta)
+            writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2)
+            writeUtf8String(metaStart + "\r\n")
+            writeBinary(metaBytes)
+            writeUtf8String("\r\n")
         }
 
         //Printing data
         envelope.data?.let { data ->
-            writeText(dataStart + "\r\n")
-            writeFully(data.toBytes())
+            writeUtf8String(dataStart + "\r\n")
+            writeBinary(data)
         }
-        flush()
     }
 
     override fun Input.readObject(): Envelope {
-        var line: String = ""
+        var line: String
         do {
-            line = readUTF8Line() ?: error("Input does not contain tagless envelope header")
+            line = readUtf8Line() // ?: error("Input does not contain tagless envelope header")
         } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
         val properties = HashMap<String, String>()
 
@@ -67,19 +71,23 @@ class TaglessEnvelopeFormat(
                 val (key, value) = match.destructured
                 properties[key] = value
             }
-            line = readUTF8Line() ?: return SimpleEnvelope(Meta.empty, null)
+            try {
+                line = readUtf8Line()
+            } catch (ex: EOFException) {
+                //If can't read line, return envelope without data
+                return SimpleEnvelope(Meta.empty, null)
+            }
         }
 
         var meta: Meta = EmptyMeta
 
         if (line.startsWith(metaStart)) {
             val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
-            val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
+            val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
             meta = if (metaSize != null) {
-                val metaPacket = buildPacket {
-                    writeFully(readBytes(metaSize))
+                limit(metaSize).run {
+                    metaFormat.run { readObject() }
                 }
-                metaFormat.run { metaPacket.readObject() }
             } else {
                 metaFormat.run {
                     readObject()
@@ -88,17 +96,22 @@ class TaglessEnvelopeFormat(
         }
 
         do {
-            line = readUTF8Line() ?: return SimpleEnvelope(meta, null)
-            //returning an Envelope without data if end of input is reached
+            try {
+                line = readUtf8Line()
+            } catch (ex: EOFException) {
+                //returning an Envelope without data if end of input is reached
+                return SimpleEnvelope(meta, null)
+            }
         } while (!line.startsWith(dataStart))
 
         val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) {
             val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt())
-            readFully(bytes)
+            readArray(bytes)
             bytes.asBinary()
         } else {
-            val bytes = readBytes()
-            bytes.asBinary()
+            buildBytes {
+                writeInput(this@readObject)
+            }
         }
 
         return SimpleEnvelope(meta, data)
@@ -106,9 +119,9 @@ class TaglessEnvelopeFormat(
 
     override fun Input.readPartial(): PartialEnvelope {
         var offset = 0u
-        var line: String = ""
+        var line: String
         do {
-            line = readUTF8Line() ?: error("Input does not contain tagless envelope header")
+            line = readUtf8Line()// ?: error("Input does not contain tagless envelope header")
             offset += line.toUtf8Bytes().size.toUInt()
         } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
         val properties = HashMap<String, String>()
@@ -121,29 +134,31 @@ class TaglessEnvelopeFormat(
                 val (key, value) = match.destructured
                 properties[key] = value
             }
-            line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
-            offset += line.toUtf8Bytes().size.toUInt()
+            try {
+                line = readUtf8Line()
+                offset += line.toUtf8Bytes().size.toUInt()
+            } catch (ex: EOFException) {
+                return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
+            }
         }
 
         var meta: Meta = EmptyMeta
 
         if (line.startsWith(metaStart)) {
             val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
-
-            val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
+            val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
             meta = if (metaSize != null) {
-                val metaPacket = buildPacket {
-                    writeFully(readBytes(metaSize))
-                }
                 offset += metaSize.toUInt()
-                metaFormat.run { metaPacket.readObject() }
+                limit(metaSize).run {
+                    metaFormat.run { readObject() }
+                }
             } else {
                 error("Can't partially read an envelope with undefined meta size")
             }
         }
 
         do {
-            line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
+            line = readUtf8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
             offset += line.toUtf8Bytes().size.toUInt()
             //returning an Envelope without data if end of input is reached
         } while (!line.startsWith(dataStart))
@@ -190,9 +205,8 @@ class TaglessEnvelopeFormat(
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
             return try {
-                val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length)
-                input.readFully(buffer)
-                return if (String(buffer) == TAGLESS_ENVELOPE_HEADER) {
+                val string = input.readRawString(TAGLESS_ENVELOPE_HEADER.length)
+                return if (string == TAGLESS_ENVELOPE_HEADER) {
                     TaglessEnvelopeFormat(io)
                 } else {
                     null
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
index b22fed4a..31925c9f 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
@@ -13,6 +13,7 @@ import kotlinx.serialization.json.JsonOutput
 
 
 @Serializer(Value::class)
+@UseExperimental(InternalSerializationApi::class)
 object ValueSerializer : KSerializer<Value> {
     private val valueTypeSerializer = EnumSerializer(ValueType::class)
     private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) }
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
index b32abb14..18e28423 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
@@ -55,6 +55,7 @@ inline class SerialDescriptorBuilder(private val impl: SerialClassDescImpl) {
     fun doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
         element(name, DoubleArraySerializer.descriptor, isOptional, *annotations)
 
+    @UseExperimental(InternalSerializationApi::class)
     inline fun <reified E : Enum<E>> enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
         element(name, EnumSerializer(E::class).descriptor, isOptional, *annotations)
 
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
index 37ee827d..0d6782b1 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
@@ -1,5 +1,7 @@
 package hep.dataforge.io
 
+import kotlinx.io.readDouble
+import kotlinx.io.writeDouble
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
@@ -12,16 +14,18 @@ class EnvelopeFormatTest {
         }
         data{
             writeDouble(22.2)
+//            repeat(2000){
+//                writeInt(it)
+//            }
         }
     }
 
-    @ExperimentalStdlibApi
     @Test
     fun testTaggedFormat(){
         TaggedEnvelopeFormat.run {
-            val bytes = writeBytes(envelope)
-            println(bytes.decodeToString())
-            val res = readBytes(bytes)
+            val byteArray = this.writeByteArray(envelope)
+            println(byteArray.decodeToString())
+            val res = readByteArray(byteArray)
             assertEquals(envelope.meta,res.meta)
             val double = res.data?.read {
                 readDouble()
@@ -33,9 +37,9 @@ class EnvelopeFormatTest {
     @Test
     fun testTaglessFormat(){
         TaglessEnvelopeFormat.run {
-            val bytes = writeBytes(envelope)
-            println(bytes.decodeToString())
-            val res = readBytes(bytes)
+            val byteArray = writeByteArray(envelope)
+            println(byteArray.decodeToString())
+            val res = readByteArray(byteArray)
             assertEquals(envelope.meta,res.meta)
             val double = res.data?.read {
                 readDouble()
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt
index 02180dbc..9064a485 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt
@@ -1,12 +1,22 @@
 package hep.dataforge.io
 
 import hep.dataforge.meta.*
+import kotlinx.io.Bytes
+import kotlinx.io.buildBytes
 import kotlinx.serialization.json.JsonPrimitive
 import kotlinx.serialization.json.json
 import kotlinx.serialization.json.jsonArray
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
+fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): Bytes = buildBytes {
+    format.run { writeObject(this@toBytes) }
+}
+
+fun MetaFormat.fromBytes(packet: Bytes): Meta {
+    return packet.read { readObject() }
+}
+
 class MetaFormatTest {
     @Test
     fun testBinaryMetaFormat() {
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
index 7a8447c0..37db8833 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
@@ -5,8 +5,6 @@ import hep.dataforge.io.serialization.MetaSerializer
 import hep.dataforge.io.serialization.NameSerializer
 import hep.dataforge.meta.buildMeta
 import hep.dataforge.names.toName
-import kotlinx.io.charsets.Charsets
-import kotlinx.io.core.String
 import kotlinx.serialization.cbor.Cbor
 import kotlinx.serialization.json.Json
 import kotlin.test.Test
@@ -41,7 +39,7 @@ class MetaSerializerTest {
         }
 
         val bytes = Cbor.dump(MetaSerializer, meta)
-        println(String(bytes, charset = Charsets.ISO_8859_1))
+        println(bytes.contentToString())
         val restored = Cbor.load(MetaSerializer, bytes)
         assertEquals(restored, meta)
     }
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
similarity index 54%
rename from dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
rename to dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
index d123d632..34827c88 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
@@ -1,33 +1,38 @@
 package hep.dataforge.io
 
+import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
-import kotlinx.io.core.writeText
+import kotlinx.io.text.writeUtf8String
+
 import kotlin.test.Test
 import kotlin.test.assertEquals
+import kotlin.test.assertTrue
 
-class EnvelopePartsTest {
+@DFExperimental
+class MultipartTest {
     val envelopes = (0..5).map {
         Envelope {
             meta {
                 "value" put it
             }
             data {
-                writeText("Hello World $it")
-                repeat(200){
+                writeUtf8String("Hello World $it")
+                repeat(2000) {
                     writeInt(it)
                 }
             }
         }
     }
     val partsEnvelope = Envelope {
-        multipart(TaggedEnvelopeFormat, envelopes)
+        multipart(envelopes, TaggedEnvelopeFormat)
     }
 
     @Test
     fun testParts() {
-        val bytes = TaggedEnvelopeFormat.writeBytes(partsEnvelope)
-        val reconstructed = TaggedEnvelopeFormat.readBytes(bytes)
+        val bytes = TaggedEnvelopeFormat.writeByteArray(partsEnvelope)
+        assertTrue { bytes.size > envelopes.sumBy { it.data!!.size.toInt() } }
+        val reconstructed = TaggedEnvelopeFormat.readByteArray(bytes)
         val parts = reconstructed.parts()?.toList() ?: emptyList()
         assertEquals(2, parts[2].meta["value"].int)
         println(reconstructed.data!!.size)
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt
deleted file mode 100644
index aa90a638..00000000
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package hep.dataforge.io
-
-import kotlinx.io.core.Input
-import kotlinx.io.core.buildPacket
-import java.nio.channels.FileChannel
-import java.nio.file.Files
-import java.nio.file.Path
-import java.nio.file.StandardOpenOption
-import kotlin.math.min
-
-@ExperimentalUnsignedTypes
-class FileBinary(val path: Path, private val offset: UInt = 0u, size: ULong? = null) : RandomAccessBinary {
-
-    override val size: ULong = size ?: (Files.size(path).toULong() - offset).toULong()
-
-    init {
-        if( size != null && Files.size(path) < offset.toLong() + size.toLong()){
-            error("Can't read binary from file. File is to short.")
-        }
-    }
-
-    override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
-        FileChannel.open(path, StandardOpenOption.READ).use {
-            val theSize: UInt = min(size, Files.size(path).toUInt() - offset)
-            val buffer = it.map(FileChannel.MapMode.READ_ONLY, (from + offset).toLong(), theSize.toLong())
-            return buildPacket { writeFully(buffer) }.block()
-        }
-    }
-}
-
-fun Path.asBinary(offset: UInt = 0u, size: ULong? = null): FileBinary = FileBinary(this, offset, size)
\ No newline at end of file
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
index 5f21e6ae..3b34c26c 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
@@ -1,23 +1,20 @@
 package hep.dataforge.io
 
 import hep.dataforge.meta.Meta
-import kotlinx.io.nio.asInput
-import java.nio.file.Files
+import kotlinx.io.Binary
+import kotlinx.io.FileBinary
+import kotlinx.io.read
 import java.nio.file.Path
-import java.nio.file.StandardOpenOption
 
 class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope {
     //TODO do not like this constructor. Hope to replace it later
 
-    private val partialEnvelope: PartialEnvelope
-
-    init {
-        val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
-        partialEnvelope = format.run { input.use { it.readPartial() } }
+    private val partialEnvelope: PartialEnvelope = path.read {
+        format.run { readPartial() }
     }
 
     override val meta: Meta get() = partialEnvelope.meta
 
-    override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize)
+    override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset.toInt(), partialEnvelope.dataSize?.toInt())
 }
 
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index 9203d306..4e60707f 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -5,13 +5,9 @@ import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.isEmpty
-import kotlinx.io.core.Output
-import kotlinx.io.core.copyTo
-import kotlinx.io.nio.asInput
-import kotlinx.io.nio.asOutput
+import kotlinx.io.*
 import java.nio.file.Files
 import java.nio.file.Path
-import java.nio.file.StandardOpenOption
 import kotlin.reflect.full.isSuperclassOf
 import kotlin.streams.asSequence
 
@@ -23,7 +19,6 @@ inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
     return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>?
 }
 
-
 /**
  * Read file containing meta using given [formatOverride] or file extension to infer meta type.
  * If [path] is a directory search for file starting with `meta` in it
@@ -41,7 +36,9 @@ fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descri
 
     val metaFormat = formatOverride ?: metaFormat(extension) ?: error("Can't resolve meta format $extension")
     return metaFormat.run {
-        Files.newByteChannel(actualPath, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) }
+        actualPath.read{
+            readMeta(descriptor)
+        }
     }
 }
 
@@ -61,8 +58,8 @@ fun IOPlugin.writeMetaFile(
         path
     }
     metaFormat.run {
-        Files.newByteChannel(actualPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use {
-            it.writeMeta(meta, descriptor)
+        actualPath.write{
+            writeMeta(meta, descriptor)
         }
     }
 }
@@ -139,24 +136,12 @@ fun IOPlugin.readEnvelopeFile(
     } else null
 }
 
-private fun Path.useOutput(consumer: Output.() -> Unit) {
-    //TODO forbid rewrite?
-    Files.newByteChannel(
-        this,
-        StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING
-    ).asOutput().use {
-        it.consumer()
-        it.flush()
-    }
-}
-
 /**
  * Write a binary into file. Throws an error if file already exists
  */
 fun <T : Any> IOFormat<T>.writeToFile(path: Path, obj: T) {
-    path.useOutput {
+    path.write {
         writeObject(obj)
-        flush()
     }
 }
 
@@ -170,7 +155,7 @@ fun IOPlugin.writeEnvelopeFile(
     envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat,
     metaFormat: MetaFormatFactory? = null
 ) {
-    path.useOutput {
+    path.write {
         with(envelopeFormat) {
             writeEnvelope(envelope, metaFormat ?: envelopeFormat.defaultMetaFormat)
         }
@@ -196,10 +181,10 @@ fun IOPlugin.writeEnvelopeDirectory(
         writeMetaFile(path, envelope.meta, metaFormat)
     }
     val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME)
-    dataFile.useOutput {
+    dataFile.write {
         envelope.data?.read {
-            val copied = copyTo(this@useOutput)
-            if (envelope.data?.size != ULong.MAX_VALUE && copied != envelope.data?.size?.toLong()) {
+            val copied = writeInput(this)
+            if (envelope.data?.size != Binary.INFINITE && copied != envelope.data?.size) {
                 error("The number of copied bytes does not equal data size")
             }
         }
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt
index b6b85101..6c7e36c6 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt
@@ -7,7 +7,6 @@ import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
 import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.withContext
-import kotlinx.io.streams.writePacket
 import java.net.Socket
 import java.util.concurrent.Executors
 import kotlin.time.ExperimentalTime
@@ -52,14 +51,14 @@ class EnvelopeClient(
     override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) {
         //val address = InetSocketAddress(host,port)
         val socket = Socket(host, port)
-        val input = socket.getInputStream().asInput()
-        val output = socket.getOutputStream()
+        val inputStream = socket.getInputStream()
+        val outputStream = socket.getOutputStream()
         format.run {
-            output.writePacket {
+            outputStream.write {
                 writeObject(request)
             }
             logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" }
-            val res = input.readObject()
+            val res = inputStream.readBlocking { readObject() }
             logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" }
             return@withContext res
         }
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt
index b733aedd..778e563f 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt
@@ -9,7 +9,6 @@ import hep.dataforge.io.type
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
 import kotlinx.coroutines.*
-import kotlinx.io.streams.writePacket
 import java.net.ServerSocket
 import java.net.Socket
 import kotlin.concurrent.thread
@@ -71,11 +70,11 @@ class EnvelopeServer(
 
     private fun readSocket(socket: Socket) {
         thread {
-            val input = socket.getInputStream().asInput()
+            val inputStream = socket.getInputStream()
             val outputStream = socket.getOutputStream()
             format.run {
                 while (socket.isConnected) {
-                    val request = input.readObject()
+                    val request = inputStream.readBlocking { readObject() }
                     logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
                     if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
                         //Echo shutdown command
@@ -86,7 +85,7 @@ class EnvelopeServer(
                     }
                     runBlocking {
                         val response = responder.respond(request)
-                        outputStream.writePacket {
+                        outputStream.write {
                             writeObject(response)
                         }
                         logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" }
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt
deleted file mode 100644
index eb743625..00000000
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package hep.dataforge.io.tcp
-
-import kotlinx.io.core.AbstractInput
-import kotlinx.io.core.Input
-import kotlinx.io.core.IoBuffer
-import kotlinx.io.core.writePacket
-import kotlinx.io.streams.readPacketAtMost
-import java.io.InputStream
-
-/**
- * Modified version of InputStream to Input converter that supports waiting for input
- */
-internal class InputStreamAsInput(
-    private val stream: InputStream
-) : AbstractInput(pool = IoBuffer.Pool) {
-
-
-    override fun fill(): IoBuffer? {
-        val packet = stream.readPacketAtMost(4096)
-        return pool.borrow().apply {
-            resetForWrite(4096)
-            writePacket(packet)
-        }
-    }
-
-    override fun closeSource() {
-        stream.close()
-    }
-}
-
-fun InputStream.asInput(): Input =
-    InputStreamAsInput(this)
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt
new file mode 100644
index 00000000..2c240f77
--- /dev/null
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt
@@ -0,0 +1,62 @@
+package hep.dataforge.io.tcp
+
+import kotlinx.io.Input
+import kotlinx.io.Output
+import kotlinx.io.asBinary
+import kotlinx.io.buffer.Buffer
+import kotlinx.io.buffer.get
+import kotlinx.io.buffer.set
+import java.io.InputStream
+import java.io.OutputStream
+
+private class InputStreamInput(val source: InputStream, val waitForInput: Boolean = false) : Input() {
+    override fun closeSource() {
+        source.close()
+    }
+
+    override fun fill(buffer: Buffer): Int {
+        if (waitForInput) {
+            while (source.available() == 0) {
+                //block until input is available
+            }
+        }
+        var bufferPos = 0
+        do {
+            val byte = source.read()
+            buffer[bufferPos] = byte.toByte()
+            bufferPos++
+        } while (byte > 0 && bufferPos < buffer.size && source.available() > 0)
+        return bufferPos
+    }
+}
+
+private class OutputStreamOutput(val out: OutputStream) : Output() {
+    override fun flush(source: Buffer, length: Int) {
+        for (i in 0..length) {
+            out.write(source[i].toInt())
+        }
+        out.flush()
+    }
+
+    override fun closeSource() {
+        out.flush()
+        out.close()
+    }
+}
+
+
+fun <R> InputStream.read(size: Int, block: Input.() -> R): R {
+    val buffer = ByteArray(size)
+    read(buffer)
+    return buffer.asBinary().read(block)
+}
+
+fun <R> InputStream.read(block: Input.() -> R): R =
+    InputStreamInput(this, false).block()
+
+fun <R> InputStream.readBlocking(block: Input.() -> R): R =
+    InputStreamInput(this, true).block()
+
+fun OutputStream.write(block: Output.() -> Unit) {
+    OutputStreamOutput(this).block()
+}
\ No newline at end of file
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
index d8c7c67a..685342cf 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt
@@ -1,6 +1,9 @@
 package hep.dataforge.io
 
 import hep.dataforge.context.Global
+import kotlinx.io.asBinary
+import kotlinx.io.toByteArray
+import kotlinx.io.writeDouble
 import java.nio.file.Files
 import kotlin.test.Test
 import kotlin.test.assertEquals
@@ -21,11 +24,11 @@ class FileBinaryTest {
     @Test
     fun testSize() {
         val binary = envelope.data
-        assertEquals(binary?.size?.toInt(), binary?.toBytes()?.size)
+        assertEquals(binary?.size?.toInt(), binary?.toByteArray()?.size)
     }
 
     @Test
-    fun testFileData(){
+    fun testFileData() {
         val dataFile = Files.createTempFile("dataforge_test_bin", ".bin")
         dataFile.toFile().writeText("This is my binary")
         val envelopeFromFile = Envelope {
@@ -34,12 +37,12 @@ class FileBinaryTest {
                 "b" put 22.2
             }
             dataType = "hep.dataforge.satellite"
-            dataID = "cellDepositTest" // добавил только что
+            dataID = "cellDepositTest"
             data = dataFile.asBinary()
         }
         val binary = envelopeFromFile.data!!
-        println(binary.toBytes().size)
-        assertEquals(binary.size?.toInt(), binary.toBytes().size)
+        println(binary.toByteArray().size)
+        assertEquals(binary.size.toInt(), binary.toByteArray().size)
 
     }
 
@@ -50,7 +53,6 @@ class FileBinaryTest {
         Global.io.writeEnvelopeFile(tmpPath, envelope)
 
         val binary = Global.io.readEnvelopeFile(tmpPath)?.data!!
-        assertEquals(binary.size.toInt(), binary.toBytes().size)
+        assertEquals(binary.size.toInt(), binary.toByteArray().size)
     }
-
 }
\ No newline at end of file
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
index 585aced9..edee906b 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt
@@ -1,6 +1,7 @@
 package hep.dataforge.io
 
 import hep.dataforge.context.Global
+import kotlinx.io.writeDouble
 import java.nio.file.Files
 import kotlin.test.Test
 import kotlin.test.assertTrue
diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt
index 64067dec..de1d35ff 100644
--- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt
+++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt
@@ -4,11 +4,12 @@ import hep.dataforge.context.Global
 import hep.dataforge.io.Envelope
 import hep.dataforge.io.Responder
 import hep.dataforge.io.TaggedEnvelopeFormat
-import hep.dataforge.io.writeBytes
+import hep.dataforge.io.writeByteArray
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.runBlocking
-import org.junit.jupiter.api.AfterAll
-import org.junit.jupiter.api.BeforeAll
+import kotlinx.io.writeDouble
+import org.junit.AfterClass
+import org.junit.BeforeClass
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.time.ExperimentalTime
@@ -16,7 +17,7 @@ import kotlin.time.ExperimentalTime
 @ExperimentalStdlibApi
 object EchoResponder : Responder {
     override suspend fun respond(request: Envelope): Envelope {
-        val string = TaggedEnvelopeFormat().run { writeBytes(request).decodeToString() }
+        val string = TaggedEnvelopeFormat().run { writeByteArray(request).decodeToString() }
         println("ECHO:")
         println(string)
         return request
@@ -30,20 +31,20 @@ class EnvelopeServerTest {
         @JvmStatic
         val echoEnvelopeServer = EnvelopeServer(Global, 7778, EchoResponder, GlobalScope)
 
-        @BeforeAll
+        @BeforeClass
         @JvmStatic
         fun start() {
             echoEnvelopeServer.start()
         }
 
-        @AfterAll
+        @AfterClass
         @JvmStatic
         fun close() {
             echoEnvelopeServer.stop()
         }
     }
 
-    @Test
+    @Test(timeout = 1000)
     fun doEchoTest() {
         val request = Envelope.invoke {
             type = "test.echo"
diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt
index 91aa5024..77f589df 100644
--- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt
+++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt
@@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlin.reflect.KClass
 
-class TextOutput(override val context: Context, private val output: kotlinx.io.core.Output) : Output<Any> {
+class TextOutput(override val context: Context, private val output: kotlinx.io.Output) : Output<Any> {
     private val cache = HashMap<KClass<*>, TextRenderer>()
 
     /**
@@ -40,7 +40,7 @@ class TextOutput(override val context: Context, private val output: kotlinx.io.c
 }
 
 /**
- * A text or binary renderer based on [kotlinx.io.core.Output]
+ * A text or binary renderer based on [kotlinx.io.Output]
  */
 @Type(TEXT_RENDERER_TYPE)
 interface TextRenderer {
@@ -53,7 +53,7 @@ interface TextRenderer {
      */
     val type: KClass<*>
 
-    suspend fun kotlinx.io.core.Output.render(obj: Any)
+    suspend fun kotlinx.io.Output.render(obj: Any)
 
     companion object {
         const val TEXT_RENDERER_TYPE = "dataforge.textRenderer"
@@ -64,7 +64,7 @@ object DefaultTextRenderer : TextRenderer {
     override val priority: Int = Int.MAX_VALUE
     override val type: KClass<*> = Any::class
 
-    override suspend fun kotlinx.io.core.Output.render(obj: Any) {
+    override suspend fun kotlinx.io.Output.render(obj: Any) {
         append(obj.toString())
         append('\n')
     }
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
index 111bba76..1b3a27cd 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
@@ -2,10 +2,13 @@ package hep.dataforge.workspace
 
 import hep.dataforge.data.Data
 import hep.dataforge.data.await
-import hep.dataforge.io.*
+import hep.dataforge.io.Envelope
+import hep.dataforge.io.IOFormat
+import hep.dataforge.io.SimpleEnvelope
+import hep.dataforge.io.readWith
 import kotlinx.coroutines.coroutineScope
-import kotlinx.io.core.Input
-import kotlinx.io.core.buildPacket
+import kotlinx.io.Input
+import kotlinx.io.buildPacket
 import kotlin.reflect.KClass
 
 /**
diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
index b73a4d59..8d8bc385 100644
--- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
+++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
@@ -6,10 +6,10 @@ import hep.dataforge.io.IOFormat
 import hep.dataforge.io.io
 import hep.dataforge.meta.DFExperimental
 import kotlinx.coroutines.runBlocking
-import kotlinx.io.core.Input
-import kotlinx.io.core.Output
-import kotlinx.io.core.readText
-import kotlinx.io.core.writeText
+import kotlinx.io.Input
+import kotlinx.io.Output
+import kotlinx.io.readText
+import kotlinx.io.writeText
 import java.nio.file.Files
 import kotlin.test.Ignore
 import kotlin.test.Test
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b486c03f..42033ac1 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -31,4 +31,6 @@ include(
     ":dataforge-output-html",
     ":dataforge-workspace",
     ":dataforge-scripting"
-)
\ No newline at end of file
+)
+
+//includeBuild("../kotlinx-io")
\ No newline at end of file

From 76e7f47528db48460f66593e02fbf62921db2d12 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 1 Dec 2019 15:00:27 +0300
Subject: [PATCH 10/41] fix envelope formats

---
 build.gradle.kts                                      |  6 +++---
 dataforge-io/build.gradle.kts                         |  2 +-
 .../kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt   |  2 +-
 .../kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt  | 11 ++++-------
 .../kotlin/hep/dataforge/io/EnvelopeFormatTest.kt     |  4 ++--
 5 files changed, 11 insertions(+), 14 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 9e432336..969de524 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,9 +1,9 @@
 import scientifik.ScientifikExtension
 
 plugins {
-    id("scientifik.mpp") version "0.2.5" apply false
-    id("scientifik.jvm") version "0.2.5" apply false
-    id("scientifik.publish") version "0.2.5" apply false
+    id("scientifik.mpp") version "0.2.6" apply false
+    id("scientifik.jvm") version "0.2.6" apply false
+    id("scientifik.publish") version "0.2.6" apply false
 }
 
 val dataforgeVersion by extra("0.1.5-dev-3")
diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts
index bb27ceff..a8d30c2d 100644
--- a/dataforge-io/build.gradle.kts
+++ b/dataforge-io/build.gradle.kts
@@ -9,7 +9,7 @@ scientifik {
     //withIO()
 }
 
-val ioVersion by rootProject.extra("0.2.0-npm-dev-2")
+val ioVersion by rootProject.extra("0.2.0-npm-dev-3")
 
 kotlin {
     sourceSets {
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
index 01888dac..7cea659f 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
@@ -67,7 +67,7 @@ class TaggedEnvelopeFormat(
         val metaFormat = io.metaFormat(tag.metaFormatKey)
             ?: error("Meta format with key ${tag.metaFormatKey} not found")
 
-        val meta: Meta = limit(tag.metaSize.toInt()).use {
+        val meta: Meta = limit(tag.metaSize.toInt()).run {
             metaFormat.run {
                 readObject()
             }
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index a8b72cda..b92e4d44 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -46,7 +46,7 @@ class TaglessEnvelopeFormat(
             writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2)
             writeUtf8String(metaStart + "\r\n")
             writeBinary(metaBytes)
-            writeUtf8String("\r\n")
+            writeRawString("\r\n")
         }
 
         //Printing data
@@ -71,12 +71,9 @@ class TaglessEnvelopeFormat(
                 val (key, value) = match.destructured
                 properties[key] = value
             }
-            try {
-                line = readUtf8Line()
-            } catch (ex: EOFException) {
-                //If can't read line, return envelope without data
-                return SimpleEnvelope(Meta.empty, null)
-            }
+            //If can't read line, return envelope without data
+            if (eof()) return SimpleEnvelope(Meta.empty, null)
+            line = readUtf8Line()
         }
 
         var meta: Meta = EmptyMeta
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
index 0d6782b1..0851d6df 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt
@@ -24,7 +24,7 @@ class EnvelopeFormatTest {
     fun testTaggedFormat(){
         TaggedEnvelopeFormat.run {
             val byteArray = this.writeByteArray(envelope)
-            println(byteArray.decodeToString())
+            //println(byteArray.decodeToString())
             val res = readByteArray(byteArray)
             assertEquals(envelope.meta,res.meta)
             val double = res.data?.read {
@@ -38,7 +38,7 @@ class EnvelopeFormatTest {
     fun testTaglessFormat(){
         TaglessEnvelopeFormat.run {
             val byteArray = writeByteArray(envelope)
-            println(byteArray.decodeToString())
+            //println(byteArray.decodeToString())
             val res = readByteArray(byteArray)
             assertEquals(envelope.meta,res.meta)
             val double = res.data?.read {

From ad2f5681b609ce38159fd93144cf38afe221b6b0 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 1 Dec 2019 18:13:10 +0300
Subject: [PATCH 11/41] Fix everything bu function server

---
 .../hep/dataforge/io/EnvelopeBuilder.kt       | 10 ++++----
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt  | 11 ++++++++-
 .../hep/dataforge/io/TaggedEnvelopeFormat.kt  |  4 +---
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt |  2 +-
 .../kotlin/hep/dataforge/io/MultipartTest.kt  | 23 +++++++++++--------
 5 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
index a0b21b64..b5a32590 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
@@ -22,13 +22,13 @@ class EnvelopeBuilder {
     var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY)
 
     /**
-     * Construct a binary and transform it into byte-array based buffer
+     * Construct a data binary from given builder
      */
+    @ExperimentalIoApi
     fun data(block: Output.() -> Unit) {
-        val bytes = buildBytes {
-            block()
-        }
-        data = ArrayBinary(bytes.toByteArray())
+        val bytes = buildBytes(builder = block)
+        data = bytes.toByteArray().asBinary() //save data to byte array so
+        bytes.close()
     }
 
     internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index d7d981c4..471ee3be 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -4,12 +4,15 @@ import hep.dataforge.context.Global
 import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY
 import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY
 import hep.dataforge.io.EnvelopeParts.INDEX_KEY
+import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_SEPARATOR
 import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE
 import hep.dataforge.io.EnvelopeParts.SIZE_KEY
 import hep.dataforge.meta.*
 import hep.dataforge.names.asName
 import hep.dataforge.names.plus
 import hep.dataforge.names.toName
+import kotlinx.io.text.readRawString
+import kotlinx.io.text.writeRawString
 
 object EnvelopeParts {
     val MULTIPART_KEY = "multipart".asName()
@@ -18,6 +21,8 @@ object EnvelopeParts {
     val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format"
     val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta"
 
+    const val MULTIPART_DATA_SEPARATOR = "#~PART~#\r\n"
+
     const val MULTIPART_DATA_TYPE = "envelope.multipart"
 }
 
@@ -41,6 +46,7 @@ fun EnvelopeBuilder.multipart(
     data {
         format(formatMeta).run {
             envelopes.forEach {
+                writeRawString(MULTIPART_DATA_SEPARATOR)
                 writeEnvelope(it)
             }
         }
@@ -68,7 +74,8 @@ fun EnvelopeBuilder.multipart(
         format.run {
             var counter = 0
             envelopes.forEach { (key, envelope) ->
-                writeObject(envelope)
+                writeRawString(MULTIPART_DATA_SEPARATOR)
+                writeEnvelope(envelope)
                 meta {
                     append(INDEX_KEY, buildMeta {
                         "key" put key
@@ -105,6 +112,8 @@ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Enve
                 data?.read {
                     sequence {
                         repeat(size) {
+                            val separator = readRawString(MULTIPART_DATA_SEPARATOR.length)
+                            if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected")
                             yield(readObject())
                         }
                     }
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
index 7cea659f..0e18fa35 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt
@@ -73,9 +73,7 @@ class TaggedEnvelopeFormat(
             }
         }
 
-        val data = buildBytes {
-            writeInput(this@readObject, tag.dataSize.toInt())
-        }
+        val data = ByteArray(tag.dataSize.toInt()).also { readArray(it) }.asBinary()
 
         return SimpleEnvelope(meta, data)
     }
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index b92e4d44..32787cf7 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -108,7 +108,7 @@ class TaglessEnvelopeFormat(
         } else {
             buildBytes {
                 writeInput(this@readObject)
-            }
+            }.toByteArray().asBinary()
         }
 
         return SimpleEnvelope(meta, data)
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
index 34827c88..ae25df06 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
@@ -18,24 +18,29 @@ class MultipartTest {
             }
             data {
                 writeUtf8String("Hello World $it")
-                repeat(2000) {
-                    writeInt(it)
-                }
+//                repeat(2000) {
+//                    writeInt(it)
+//                }
             }
         }
     }
+
     val partsEnvelope = Envelope {
         multipart(envelopes, TaggedEnvelopeFormat)
     }
 
     @Test
     fun testParts() {
-        val bytes = TaggedEnvelopeFormat.writeByteArray(partsEnvelope)
-        assertTrue { bytes.size > envelopes.sumBy { it.data!!.size.toInt() } }
-        val reconstructed = TaggedEnvelopeFormat.readByteArray(bytes)
-        val parts = reconstructed.parts()?.toList() ?: emptyList()
-        assertEquals(2, parts[2].meta["value"].int)
-        println(reconstructed.data!!.size)
+        TaggedEnvelopeFormat.run {
+            val singleEnvelopeData = writeBytes(envelopes[0])
+            val singleEnvelopeSize = singleEnvelopeData.size
+            val bytes = writeBytes(partsEnvelope)
+            assertTrue(5*singleEnvelopeSize < bytes.size)
+            val reconstructed = bytes.readWith(this)
+            val parts = reconstructed.parts()?.toList() ?: emptyList()
+            assertEquals(2, parts[2].meta["value"].int)
+            println(reconstructed.data!!.size)
+        }
     }
 
 }
\ No newline at end of file

From ab47d7723e4930e6eb241b7e38840cdefe752aca Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 1 Dec 2019 19:46:53 +0300
Subject: [PATCH 12/41] All tests running

---
 .../kotlin/hep/dataforge/io/EnvelopeBuilder.kt           | 9 +++++----
 .../commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt  | 4 ++--
 .../kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt     | 4 ++--
 .../commonTest/kotlin/hep/dataforge/io/MultipartTest.kt  | 7 ++++---
 .../kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt        | 2 +-
 .../kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt        | 3 +++
 6 files changed, 17 insertions(+), 12 deletions(-)

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
index b5a32590..0f12c213 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
@@ -1,7 +1,10 @@
 package hep.dataforge.io
 
 import hep.dataforge.meta.*
-import kotlinx.io.*
+import kotlinx.io.ArrayBinary
+import kotlinx.io.Binary
+import kotlinx.io.ExperimentalIoApi
+import kotlinx.io.Output
 
 class EnvelopeBuilder {
     private val metaBuilder = MetaBuilder()
@@ -26,9 +29,7 @@ class EnvelopeBuilder {
      */
     @ExperimentalIoApi
     fun data(block: Output.() -> Unit) {
-        val bytes = buildBytes(builder = block)
-        data = bytes.toByteArray().asBinary() //save data to byte array so
-        bytes.close()
+        data = ArrayBinary.write(builder = block)
     }
 
     internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index 471ee3be..8e0b16de 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -21,7 +21,7 @@ object EnvelopeParts {
     val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format"
     val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta"
 
-    const val MULTIPART_DATA_SEPARATOR = "#~PART~#\r\n"
+    const val MULTIPART_DATA_SEPARATOR = "\r\n#~PART~#\r\n"
 
     const val MULTIPART_DATA_TYPE = "envelope.multipart"
 }
@@ -113,7 +113,7 @@ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Enve
                     sequence {
                         repeat(size) {
                             val separator = readRawString(MULTIPART_DATA_SEPARATOR.length)
-                            if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected")
+                            if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected, but $separator found")
                             yield(readObject())
                         }
                     }
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index 32787cf7..5b22ae8c 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -106,9 +106,9 @@ class TaglessEnvelopeFormat(
             readArray(bytes)
             bytes.asBinary()
         } else {
-            buildBytes {
+            ArrayBinary.write {
                 writeInput(this@readObject)
-            }.toByteArray().asBinary()
+            }
         }
 
         return SimpleEnvelope(meta, data)
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
index ae25df06..0f60c077 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
@@ -3,6 +3,7 @@ package hep.dataforge.io
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
+import kotlinx.io.text.writeRawString
 import kotlinx.io.text.writeUtf8String
 
 import kotlin.test.Test
@@ -18,9 +19,9 @@ class MultipartTest {
             }
             data {
                 writeUtf8String("Hello World $it")
-//                repeat(2000) {
-//                    writeInt(it)
-//                }
+                repeat(300) {
+                    writeRawString("$it ")
+                }
             }
         }
     }
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt
index 6c7e36c6..9562d146 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt
@@ -38,7 +38,7 @@ class EnvelopeClient(
     suspend fun close() {
         try {
             respond(
-                Envelope.invoke {
+                Envelope {
                     type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE
                 }
             )
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt
index 778e563f..10c5b712 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt
@@ -78,6 +78,9 @@ class EnvelopeServer(
                     logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
                     if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
                         //Echo shutdown command
+                        outputStream.write{
+                            writeObject(request)
+                        }
                         logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" }
                         socket.close()
                         return@thread

From c789dabdae3db1f0be9b4defcf61cbd6ec6d52e7 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 1 Dec 2019 20:53:32 +0300
Subject: [PATCH 13/41] Fix build with new IO

---
 .../io/yaml/FrontMatterEnvelopeFormat.kt      | 39 ++++++++++++-------
 .../hep/dataforge/io/yaml/YamlMetaFormat.kt   |  6 +--
 .../dataforge/io/yaml/YamlMetaFormatTest.kt   |  2 +-
 .../html/{HtmlOutput.kt => HtmlRenderer.kt}   |  9 +++--
 .../hep/dataforge/output/OutputManager.kt     | 24 +++++++-----
 .../output/{Output.kt => Renderer.kt}         |  4 +-
 .../output/{TextOutput.kt => TextRenderer.kt} | 33 ++++++++--------
 .../hep/dataforge/output/ConsoleOutput.kt     | 22 -----------
 .../kotlin/hep/dataforge/output/outputJS.kt   |  7 ++++
 .../hep/dataforge/output/ConsoleOutput.kt     | 13 -------
 .../kotlin/hep/dataforge/output/outputJVM.kt  |  5 +++
 .../hep/dataforge/workspace/envelopeData.kt   | 14 ++-----
 .../hep/dataforge/workspace/FileDataTest.kt   |  8 ++--
 13 files changed, 86 insertions(+), 100 deletions(-)
 rename dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/{HtmlOutput.kt => HtmlRenderer.kt} (86%)
 rename dataforge-output/src/commonMain/kotlin/hep/dataforge/output/{Output.kt => Renderer.kt} (86%)
 rename dataforge-output/src/commonMain/kotlin/hep/dataforge/output/{TextOutput.kt => TextRenderer.kt} (56%)
 delete mode 100644 dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt
 create mode 100644 dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt
 delete mode 100644 dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt
 create mode 100644 dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt

diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
index be6e1d75..be74d080 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
@@ -5,8 +5,10 @@ import hep.dataforge.io.*
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
-import kotlinx.io.Input
-import kotlinx.io.Output
+import kotlinx.io.*
+import kotlinx.io.text.readUtf8Line
+import kotlinx.io.text.writeRawString
+import kotlinx.io.text.writeUtf8String
 import kotlinx.serialization.toUtf8Bytes
 
 @DFExperimental
@@ -27,14 +29,17 @@ class FrontMatterEnvelopeFormat(
             metaTypeRegex.matchEntire(line)?.groupValues?.first()
                 ?.let { io.metaFormat(it) } ?: YamlMetaFormat
 
-        val metaBlock = buildPacket {
+        val meta = buildBytes {
             do {
-                line = readUtf8Line() ?: error("Input does not contain closing front matter separator")
-                appendln(line)
+                line = readUtf8Line()
+                writeUtf8String(line + "\r\n")
                 offset += line.toUtf8Bytes().size.toUInt()
             } while (!line.startsWith(SEPARATOR))
+        }.read {
+            readMetaFormat.run {
+                readMeta()
+            }
         }
-        val meta = readMetaFormat.fromBytes(metaBlock)
         return PartialEnvelope(meta, offset, null)
     }
 
@@ -48,23 +53,29 @@ class FrontMatterEnvelopeFormat(
             metaTypeRegex.matchEntire(line)?.groupValues?.first()
                 ?.let { io.metaFormat(it) } ?: YamlMetaFormat
 
-        val metaBlock = buildPacket {
+        val meta = buildBytes {
             do {
-                appendln(readUtf8Line() ?: error("Input does not contain closing front matter separator"))
+                writeUtf8String(readUtf8Line()  + "\r\n")
             } while (!line.startsWith(SEPARATOR))
+        }.read {
+            readMetaFormat.run {
+                readMeta()
+            }
         }
-        val meta = readMetaFormat.fromBytes(metaBlock)
-        val bytes = readBytes()
+        val bytes = readRemaining()
         val data = bytes.asBinary()
         return SimpleEnvelope(meta, data)
     }
 
     override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
         val metaFormat = metaFormatFactory(formatMeta, io.context)
-        writeText("$SEPARATOR\r\n")
+        writeRawString("$SEPARATOR\r\n")
         metaFormat.run { writeObject(envelope.meta) }
-        writeText("$SEPARATOR\r\n")
-        envelope.data?.read { copyTo(this@writeEnvelope) }
+        writeRawString("$SEPARATOR\r\n")
+        //Printing data
+        envelope.data?.let { data ->
+            writeBinary(data)
+        }
     }
 
     companion object : EnvelopeFormatFactory {
@@ -77,7 +88,7 @@ class FrontMatterEnvelopeFormat(
         }
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
-            val line = input.readUtf8Line(3, 30)
+            val line = input.readUtf8Line()
             return if (line != null && line.startsWith("---")) {
                 invoke()
             } else {
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
index 5e31a69e..acd19652 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
@@ -11,13 +11,13 @@ import hep.dataforge.meta.toMeta
 import kotlinx.io.Input
 import kotlinx.io.Output
 import kotlinx.io.readUByte
-import kotlinx.io.writeText
+import kotlinx.io.text.writeUtf8String
 import org.yaml.snakeyaml.Yaml
 import java.io.InputStream
 
 private class InputAsStream(val input: Input) : InputStream() {
     override fun read(): Int {
-        if (input.endOfInput) return -1
+        if (input.eof()) return -1
         return input.readUByte().toInt()
     }
 
@@ -34,7 +34,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
 
     override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
         val string = yaml.dump(meta.toMap(descriptor))
-        writeText(string)
+        writeUtf8String(string)
     }
 
     override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
index 5c5b3c18..2be83509 100644
--- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
+++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
@@ -6,7 +6,7 @@ import hep.dataforge.meta.Meta
 import hep.dataforge.meta.buildMeta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.seal
-import org.junit.jupiter.api.Test
+import kotlin.test.Test
 import kotlin.test.assertEquals
 
 
diff --git a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt
similarity index 86%
rename from dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt
rename to dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt
index 3ff23403..c0aeaaab 100644
--- a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt
+++ b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt
@@ -3,7 +3,8 @@ package hep.dataforge.output.html
 import hep.dataforge.context.Context
 import hep.dataforge.meta.Meta
 import hep.dataforge.output.Output
-import hep.dataforge.output.TextRenderer
+import hep.dataforge.output.Renderer
+import hep.dataforge.output.TextFormat
 import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
 import hep.dataforge.provider.Type
 import hep.dataforge.provider.top
@@ -14,11 +15,11 @@ import kotlinx.html.p
 import kotlin.reflect.KClass
 
 
-class HtmlOutput<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Output<T> {
+class HtmlRenderer<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Renderer<T> {
     private val cache = HashMap<KClass<*>, HtmlBuilder<*>>()
 
     /**
-     * Find the first [TextRenderer] matching the given object type.
+     * Find the first [TextFormat] matching the given object type.
      */
     override fun render(obj: T, meta: Meta) {
 
@@ -47,7 +48,7 @@ class HtmlOutput<T : Any>(override val context: Context, private val consumer: T
 }
 
 /**
- * A text or binary renderer based on [Output]
+ * A text or binary renderer based on [Renderer]
  */
 @Type(HTML_CONVERTER_TYPE)
 interface HtmlBuilder<T : Any> {
diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt
index d9f7c1b2..c05d5bb4 100644
--- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt
+++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt
@@ -1,9 +1,6 @@
 package hep.dataforge.output
 
-import hep.dataforge.context.AbstractPlugin
-import hep.dataforge.context.Context
-import hep.dataforge.context.PluginFactory
-import hep.dataforge.context.PluginTag
+import hep.dataforge.context.*
 import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
@@ -22,14 +19,14 @@ interface OutputManager {
      * Get an output specialized for given type, name and stage.
      * @param stage represents the node or directory for the output. Empty means root node.
      * @param name represents the name inside the node.
-     * @param meta configuration for [Output] (not for rendered object)
+     * @param meta configuration for [Renderer] (not for rendered object)
      */
     operator fun <T : Any> get(
         type: KClass<out T>,
         name: Name,
         stage: Name = EmptyName,
         meta: Meta = EmptyMeta
-    ): Output<T>
+    ): Renderer<T>
 }
 
 /**
@@ -44,7 +41,7 @@ inline operator fun <reified T : Any> OutputManager.get(
     name: Name,
     stage: Name = EmptyName,
     meta: Meta = EmptyMeta
-): Output<T> {
+): Renderer<T> {
     return get(T::class, name, stage, meta)
 }
 
@@ -56,14 +53,21 @@ fun OutputManager.render(obj: Any, name: Name, stage: Name = EmptyName, meta: Me
 
 /**
  * System console output.
- * The [ConsoleOutput] is used when no other [OutputManager] is provided.
+ * The [CONSOLE_RENDERER] is used when no other [OutputManager] is provided.
  */
-expect val ConsoleOutput: Output<Any>
+val CONSOLE_RENDERER: Renderer<Any> = object : Renderer<Any> {
+    override fun render(obj: Any, meta: Meta) {
+        println(obj)
+    }
+
+    override val context: Context get() = Global
+
+}
 
 class ConsoleOutputManager : AbstractPlugin(), OutputManager {
     override val tag: PluginTag get() = ConsoleOutputManager.tag
 
-    override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
+    override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> = CONSOLE_RENDERER
 
     companion object : PluginFactory<ConsoleOutputManager> {
         override val tag = PluginTag("output.console", group = DATAFORGE_GROUP)
diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Output.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Renderer.kt
similarity index 86%
rename from dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Output.kt
rename to dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Renderer.kt
index 091cb999..c1bcf6e5 100644
--- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Output.kt
+++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Renderer.kt
@@ -7,11 +7,11 @@ import hep.dataforge.meta.Meta
 /**
  * A generic way to render any object in the output.
  *
- * An object could be rendered either in append or overlay mode. The mode is decided by the [Output]
+ * An object could be rendered either in append or overlay mode. The mode is decided by the [Renderer]
  * based on its configuration and provided meta
  *
  */
-interface Output<in T : Any> : ContextAware {
+interface Renderer<in T : Any> : ContextAware {
     /**
      * Render specific object with configuration.
      *
diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextRenderer.kt
similarity index 56%
rename from dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt
rename to dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextRenderer.kt
index 77f589df..cc9d24f3 100644
--- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt
+++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextRenderer.kt
@@ -2,48 +2,50 @@ package hep.dataforge.output
 
 import hep.dataforge.context.Context
 import hep.dataforge.meta.Meta
-import hep.dataforge.output.TextRenderer.Companion.TEXT_RENDERER_TYPE
+import hep.dataforge.output.TextFormat.Companion.TEXT_RENDERER_TYPE
 import hep.dataforge.provider.Type
 import hep.dataforge.provider.top
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import kotlinx.io.Output
+import kotlinx.io.text.writeUtf8String
 import kotlin.reflect.KClass
 
-class TextOutput(override val context: Context, private val output: kotlinx.io.Output) : Output<Any> {
-    private val cache = HashMap<KClass<*>, TextRenderer>()
+class TextRenderer(override val context: Context, private val output: Output) : Renderer<Any> {
+    private val cache = HashMap<KClass<*>, TextFormat>()
 
     /**
-     * Find the first [TextRenderer] matching the given object type.
+     * Find the first [TextFormat] matching the given object type.
      */
     override fun render(obj: Any, meta: Meta) {
-        val renderer: TextRenderer = if (obj is CharSequence) {
-            DefaultTextRenderer
+        val format: TextFormat = if (obj is CharSequence) {
+            DefaultTextFormat
         } else {
             val value = cache[obj::class]
             if (value == null) {
                 val answer =
-                    context.top<TextRenderer>(TEXT_RENDERER_TYPE).values.firstOrNull { it.type.isInstance(obj) }
+                    context.top<TextFormat>(TEXT_RENDERER_TYPE).values.firstOrNull { it.type.isInstance(obj) }
                 if (answer != null) {
                     cache[obj::class] = answer
                     answer
                 } else {
-                    DefaultTextRenderer
+                    DefaultTextFormat
                 }
             } else {
                 value
             }
         }
         context.launch(Dispatchers.Output) {
-            renderer.run { output.render(obj) }
+            format.run { output.render(obj) }
         }
     }
 }
 
 /**
- * A text or binary renderer based on [kotlinx.io.Output]
+ * A text or binary renderer based on [Output]
  */
 @Type(TEXT_RENDERER_TYPE)
-interface TextRenderer {
+interface TextFormat {
     /**
      * The priority of this renderer compared to other renderers
      */
@@ -53,19 +55,18 @@ interface TextRenderer {
      */
     val type: KClass<*>
 
-    suspend fun kotlinx.io.Output.render(obj: Any)
+    suspend fun Output.render(obj: Any)
 
     companion object {
         const val TEXT_RENDERER_TYPE = "dataforge.textRenderer"
     }
 }
 
-object DefaultTextRenderer : TextRenderer {
+object DefaultTextFormat : TextFormat {
     override val priority: Int = Int.MAX_VALUE
     override val type: KClass<*> = Any::class
 
-    override suspend fun kotlinx.io.Output.render(obj: Any) {
-        append(obj.toString())
-        append('\n')
+    override suspend fun Output.render(obj: Any) {
+        writeUtf8String(obj.toString() + "\n")
     }
 }
\ No newline at end of file
diff --git a/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt b/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt
deleted file mode 100644
index b927a386..00000000
--- a/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package hep.dataforge.output
-
-import hep.dataforge.context.Context
-import hep.dataforge.context.Global
-import hep.dataforge.meta.Meta
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-
-/**
- * System console output.
- * The [ConsoleOutput] is used when no other [OutputManager] is provided.
- */
-actual val ConsoleOutput: Output<Any> = object : Output<Any> {
-    override fun render(obj: Any, meta: Meta) {
-        println(obj)
-    }
-
-    override val context: Context get() = Global
-
-}
-
-actual val Dispatchers.Output: CoroutineDispatcher get() = Dispatchers.Default
\ No newline at end of file
diff --git a/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt b/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt
new file mode 100644
index 00000000..18a71f07
--- /dev/null
+++ b/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt
@@ -0,0 +1,7 @@
+package hep.dataforge.output
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
+
+actual val Dispatchers.Output: CoroutineDispatcher get() = Dispatchers.Default
\ No newline at end of file
diff --git a/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt b/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt
deleted file mode 100644
index 57ae4294..00000000
--- a/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package hep.dataforge.output
-
-import hep.dataforge.context.Global
-import kotlinx.coroutines.Dispatchers
-import kotlinx.io.streams.asOutput
-
-/**
- * System console output.
- * The [ConsoleOutput] is used when no other [OutputManager] is provided.
- */
-actual val ConsoleOutput: Output<Any> = TextOutput(Global, System.out.asOutput())
-
-actual val Dispatchers.Output get() = Dispatchers.IO
\ No newline at end of file
diff --git a/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt b/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt
new file mode 100644
index 00000000..ea7c416c
--- /dev/null
+++ b/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt
@@ -0,0 +1,5 @@
+package hep.dataforge.output
+
+import kotlinx.coroutines.Dispatchers
+
+actual val Dispatchers.Output get() = Dispatchers.IO
\ No newline at end of file
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
index 1b3a27cd..1a713e37 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
@@ -7,8 +7,7 @@ import hep.dataforge.io.IOFormat
 import hep.dataforge.io.SimpleEnvelope
 import hep.dataforge.io.readWith
 import kotlinx.coroutines.coroutineScope
-import kotlinx.io.Input
-import kotlinx.io.buildPacket
+import kotlinx.io.ArrayBinary
 import kotlin.reflect.KClass
 
 /**
@@ -22,15 +21,8 @@ suspend fun <T : Any> Data<T>.toEnvelope(format: IOFormat<T>): Envelope {
     val obj = coroutineScope {
         await(this)
     }
-    val binary = object : Binary {
-        override fun <R> read(block: Input.() -> R): R {
-            //TODO optimize away additional copy by creating inputs that reads directly from output
-            val packet = buildPacket {
-                format.run { writeObject(obj) }
-            }
-            return packet.block()
-        }
-
+    val binary = ArrayBinary.write {
+        format.run { writeObject(obj) }
     }
     return SimpleEnvelope(meta, binary)
 }
\ No newline at end of file
diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
index 8d8bc385..4fc9e9a4 100644
--- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
+++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt
@@ -8,8 +8,8 @@ import hep.dataforge.meta.DFExperimental
 import kotlinx.coroutines.runBlocking
 import kotlinx.io.Input
 import kotlinx.io.Output
-import kotlinx.io.readText
-import kotlinx.io.writeText
+import kotlinx.io.text.readUtf8String
+import kotlinx.io.text.writeUtf8String
 import java.nio.file.Files
 import kotlin.test.Ignore
 import kotlin.test.Test
@@ -30,11 +30,11 @@ class FileDataTest {
 
     object StringIOFormat : IOFormat<String> {
         override fun Output.writeObject(obj: String) {
-            writeText(obj)
+            writeUtf8String(obj)
         }
 
         override fun Input.readObject(): String {
-            return readText()
+            return readUtf8String()
         }
 
     }

From 0ec42689d729b3fb9bbd99a81c3163d6f286650b Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 3 Dec 2019 17:01:56 +0300
Subject: [PATCH 14/41] Minor change to Goal API

---
 .../kotlin/hep/dataforge/data/Data.kt         | 10 ++---
 .../kotlin/hep/dataforge/data/DataNode.kt     | 28 ++++++++------
 .../kotlin/hep/dataforge/data/Goal.kt         | 37 +++++++++----------
 .../kotlin/hep/dataforge/data/dataCast.kt     |  2 +-
 .../dataforge/io/yaml/YamlMetaFormatTest.kt   |  4 +-
 .../hep/dataforge/workspace/envelopeData.kt   |  5 +--
 6 files changed, 42 insertions(+), 44 deletions(-)

diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
index 718fb46f..ea25e070 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
@@ -91,7 +91,7 @@ fun <T : Any, R : Any> Data<T>.map(
     meta: Meta = this.meta,
     block: suspend CoroutineScope.(T) -> R
 ): Data<R> = DynamicData(outputType, meta, coroutineContext, listOf(this)) {
-    block(await(this))
+    block(await())
 }
 
 
@@ -103,7 +103,7 @@ inline fun <T : Any, reified R : Any> Data<T>.map(
     meta: Meta = this.meta,
     noinline block: suspend CoroutineScope.(T) -> R
 ): Data<R> = DynamicData(R::class, meta, coroutineContext, listOf(this)) {
-    block(await(this))
+    block(await())
 }
 
 /**
@@ -119,7 +119,7 @@ inline fun <T : Any, reified R : Any> Collection<Data<T>>.reduce(
     coroutineContext,
     this
 ) {
-    block(map { run { it.await(this) } })
+    block(map { run { it.await() } })
 }
 
 fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce(
@@ -133,7 +133,7 @@ fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce(
     coroutineContext,
     this.values
 ) {
-    block(mapValues { it.value.await(this) })
+    block(mapValues { it.value.await() })
 }
 
 
@@ -153,7 +153,7 @@ inline fun <K, T : Any, reified R : Any> Map<K, Data<T>>.reduce(
     coroutineContext,
     this.values
 ) {
-    block(mapValues { it.value.await(this) })
+    block(mapValues { it.value.await() })
 }
 
 
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
index a673f0b7..764aea62 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
@@ -4,6 +4,7 @@ import hep.dataforge.meta.*
 import hep.dataforge.names.*
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 import kotlin.collections.component1
 import kotlin.collections.component2
@@ -55,6 +56,19 @@ interface DataNode<out T : Any> : MetaRepr {
         }
     }
 
+    /**
+     * Start computation for all goals in data node and return a job for the whole node
+     */
+    @Suppress("DeferredResultUnused")
+    fun CoroutineScope.startAll(): Job = launch {
+        items.values.forEach {
+            when (it) {
+                is DataItem.Node<*> -> it.node.run { startAll() }
+                is DataItem.Leaf<*> -> it.data.run { startAsync() }
+            }
+        }
+    }
+
     companion object {
         const val TYPE = "dataNode"
 
@@ -68,21 +82,11 @@ interface DataNode<out T : Any> : MetaRepr {
     }
 }
 
+suspend fun <T: Any> DataNode<T>.join(): Unit = coroutineScope { startAll().join() }
+
 val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.node
 val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.data
 
-/**
- * Start computation for all goals in data node and return a job for the whole node
- */
-fun DataNode<*>.launchAll(scope: CoroutineScope): Job = scope.launch {
-    items.values.forEach {
-        when (it) {
-            is DataItem.Node<*> -> it.node.launchAll(scope)
-            is DataItem.Leaf<*> -> it.data.start(scope)
-        }
-    }
-}
-
 operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) {
     0 -> error("Empty name")
     1 -> items[name.first()]
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt
index 8275d31e..8c0eeec7 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt
@@ -15,9 +15,7 @@ interface Goal<out T> {
      * Get ongoing computation or start a new one.
      * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
      */
-    fun startAsync(scope: CoroutineScope): Deferred<T>
-
-    suspend fun CoroutineScope.await(): T = startAsync(this).await()
+    fun CoroutineScope.startAsync(): Deferred<T>
 
     /**
      * Reset the computation
@@ -29,17 +27,15 @@ interface Goal<out T> {
     }
 }
 
-fun Goal<*>.start(scope: CoroutineScope): Job = startAsync(scope)
+suspend fun <T> Goal<T>.await(): T = coroutineScope { startAsync().await() }
 
 val Goal<*>.isComplete get() = result?.isCompleted ?: false
 
-suspend fun <T> Goal<T>.await(scope: CoroutineScope): T = scope.await()
-
 open class StaticGoal<T>(val value: T) : Goal<T> {
     override val dependencies: Collection<Goal<*>> get() = emptyList()
     override val result: Deferred<T> = CompletableDeferred(value)
 
-    override fun startAsync(scope: CoroutineScope): Deferred<T> = result
+    override fun CoroutineScope.startAsync(): Deferred<T> = result
 
     override fun reset() {
         //doNothing
@@ -59,18 +55,19 @@ open class DynamicGoal<T>(
      * Get ongoing computation or start a new one.
      * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
      */
-    override fun startAsync(scope: CoroutineScope): Deferred<T> {
-        val startedDependencies = this.dependencies.map { goal ->
-            goal.startAsync(scope)
+    override fun CoroutineScope.startAsync(): Deferred<T> {
+        val startedDependencies = this@DynamicGoal.dependencies.map { goal ->
+            goal.run { startAsync() }
         }
-        return result ?: scope.async(coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) {
-            startedDependencies.forEach { deferred ->
-                deferred.invokeOnCompletion { error ->
-                    if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}"))
+        return result
+            ?: async(this@DynamicGoal.coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) {
+                startedDependencies.forEach { deferred ->
+                    deferred.invokeOnCompletion { error ->
+                        if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}"))
+                    }
                 }
-            }
-            block()
-        }.also { result = it }
+                block()
+            }.also { result = it }
     }
 
     /**
@@ -89,7 +86,7 @@ fun <T, R> Goal<T>.map(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     block: suspend CoroutineScope.(T) -> R
 ): Goal<R> = DynamicGoal(coroutineContext, listOf(this)) {
-    block(await(this))
+    block(await())
 }
 
 /**
@@ -99,7 +96,7 @@ fun <T, R> Collection<Goal<T>>.reduce(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     block: suspend CoroutineScope.(Collection<T>) -> R
 ): Goal<R> = DynamicGoal(coroutineContext, this) {
-    block(map { run { it.await(this) } })
+    block(map { run { it.await() } })
 }
 
 /**
@@ -112,6 +109,6 @@ fun <K, T, R> Map<K, Goal<T>>.reduce(
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     block: suspend CoroutineScope.(Map<K, T>) -> R
 ): Goal<R> = DynamicGoal(coroutineContext, this.values) {
-    block(mapValues { it.value.await(this) })
+    block(mapValues { it.value.await() })
 }
 
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
index 2bf8adde..21301dd4 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt
@@ -41,7 +41,7 @@ fun <R : Any> Data<*>.cast(type: KClass<out R>): Data<R> {
         override val meta: Meta get() = this@cast.meta
         override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies
         override val result: Deferred<R>? get() = this@cast.result as Deferred<R>
-        override fun startAsync(scope: CoroutineScope): Deferred<R> = this@cast.startAsync(scope) as Deferred<R>
+        override fun CoroutineScope.startAsync(): Deferred<R> = this@cast.run { startAsync() as Deferred<R> }
         override fun reset() = this@cast.reset()
         override val type: KClass<out R> = type
     }
diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
index 2be83509..b330e080 100644
--- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
+++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
@@ -10,9 +10,9 @@ import kotlin.test.Test
 import kotlin.test.assertEquals
 
 
-class YamlMetaFormatTest{
+class YamlMetaFormatTest {
     @Test
-    fun testYamlMetaFormat(){
+    fun testYamlMetaFormat() {
         val meta = buildMeta {
             "a" put 22
             "node" put {
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
index 1a713e37..d378726f 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt
@@ -6,7 +6,6 @@ import hep.dataforge.io.Envelope
 import hep.dataforge.io.IOFormat
 import hep.dataforge.io.SimpleEnvelope
 import hep.dataforge.io.readWith
-import kotlinx.coroutines.coroutineScope
 import kotlinx.io.ArrayBinary
 import kotlin.reflect.KClass
 
@@ -18,9 +17,7 @@ fun <T : Any> Envelope.toData(type: KClass<out T>, format: IOFormat<T>): Data<T>
 }
 
 suspend fun <T : Any> Data<T>.toEnvelope(format: IOFormat<T>): Envelope {
-    val obj = coroutineScope {
-        await(this)
-    }
+    val obj = await()
     val binary = ArrayBinary.write {
         format.run { writeObject(obj) }
     }

From 4eb07949b41f6495259266de6995c39746a6d85a Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 10 Dec 2019 17:15:20 +0300
Subject: [PATCH 15/41] Bump build and io versions

---
 build.gradle.kts              | 8 ++++----
 dataforge-io/build.gradle.kts | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 969de524..ccc46351 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,12 +1,12 @@
 import scientifik.ScientifikExtension
 
 plugins {
-    id("scientifik.mpp") version "0.2.6" apply false
-    id("scientifik.jvm") version "0.2.6" apply false
-    id("scientifik.publish") version "0.2.6" apply false
+    id("scientifik.mpp") version "0.2.7" apply false
+    id("scientifik.jvm") version "0.2.7" apply false
+    id("scientifik.publish") version "0.2.7" apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-3")
+val dataforgeVersion by extra("0.1.5-dev-4")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts
index a8d30c2d..fa4b336d 100644
--- a/dataforge-io/build.gradle.kts
+++ b/dataforge-io/build.gradle.kts
@@ -9,7 +9,7 @@ scientifik {
     //withIO()
 }
 
-val ioVersion by rootProject.extra("0.2.0-npm-dev-3")
+val ioVersion by rootProject.extra("0.2.0-npm-dev-4")
 
 kotlin {
     sourceSets {

From be2bca24b125be629b120759441089c65491652d Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 22 Dec 2019 20:29:32 +0300
Subject: [PATCH 16/41] Build migration

---
 README.md                                       |  3 +++
 build.gradle.kts                                | 13 +++++--------
 dataforge-io/build.gradle.kts                   |  7 +++----
 dataforge-io/dataforge-io-yaml/build.gradle.kts |  4 ++++
 4 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md
index b712501c..626ee576 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
+[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+
+[ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion)
 
 [![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
 
diff --git a/build.gradle.kts b/build.gradle.kts
index ccc46351..0dbaef48 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,12 +1,12 @@
-import scientifik.ScientifikExtension
 
 plugins {
-    id("scientifik.mpp") version "0.2.7" apply false
-    id("scientifik.jvm") version "0.2.7" apply false
-    id("scientifik.publish") version "0.2.7" apply false
+    val toolsVersion = "0.3.1"
+    id("scientifik.mpp") version toolsVersion apply false
+    id("scientifik.jvm") version toolsVersion apply false
+    id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-4")
+val dataforgeVersion by extra("0.1.5-dev-5")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
@@ -22,7 +22,4 @@ allprojects {
 
 subprojects {
     apply(plugin = "scientifik.publish")
-    afterEvaluate {
-        extensions.findByType<ScientifikExtension>()?.apply { withDokka() }
-    }
 }
\ No newline at end of file
diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts
index fa4b336d..2606f5ea 100644
--- a/dataforge-io/build.gradle.kts
+++ b/dataforge-io/build.gradle.kts
@@ -1,13 +1,12 @@
+import scientifik.useSerialization
+
 plugins {
     id("scientifik.mpp")
 }
 
 description = "IO module"
 
-scientifik {
-    withSerialization()
-    //withIO()
-}
+useSerialization()
 
 val ioVersion by rootProject.extra("0.2.0-npm-dev-4")
 
diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts
index d287d9ac..bad9b42e 100644
--- a/dataforge-io/dataforge-io-yaml/build.gradle.kts
+++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts
@@ -1,9 +1,13 @@
+import scientifik.useSerialization
+
 plugins {
     id("scientifik.jvm")
 }
 
 description = "YAML meta IO"
 
+useSerialization()
+
 dependencies {
     api(project(":dataforge-io"))
     api("org.yaml:snakeyaml:1.25")

From effac131de001925fcf5cf49f8511f41fafe2557 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 22 Dec 2019 20:29:58 +0300
Subject: [PATCH 17/41] Fix name comparison

---
 .../src/commonMain/kotlin/hep/dataforge/names/Name.kt       | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
index 41680eb0..084f27df 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
@@ -182,6 +182,8 @@ fun Name.startsWith(token: NameToken): Boolean = first() == token
 
 fun Name.endsWith(token: NameToken): Boolean = last() == token
 
-fun Name.startsWith(name: Name): Boolean = tokens.subList(0, name.length) == name.tokens
+fun Name.startsWith(name: Name): Boolean =
+    this.length >= name.length && tokens.subList(0, name.length) == name.tokens
 
-fun Name.endsWith(name: Name): Boolean = tokens.subList(length - name.length, length) == name.tokens
\ No newline at end of file
+fun Name.endsWith(name: Name): Boolean =
+    this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens
\ No newline at end of file

From f9ae9348e20f20c0bbccffae63de85640a325b88 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 22 Dec 2019 20:30:47 +0300
Subject: [PATCH 18/41] Meta subtyping and get by empty name.

---
 .../kotlin/hep/dataforge/meta/Meta.kt         | 29 +++++++++++--------
 .../kotlin/hep/dataforge/meta/MutableMeta.kt  | 10 ++++---
 2 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index e5aa0c45..57364593 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -66,8 +66,14 @@ interface Meta : MetaRepr {
 
 /* Get operations*/
 
+/**
+ * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
+ *
+ * If [name] is empty reture current [Meta] as a [NodeItem]
+ */
 operator fun Meta?.get(name: Name): MetaItem<*>? {
     if (this == null) return null
+    if (name.isEmpty()) return NodeItem(this)
     return name.first()?.let { token ->
         val tail = name.cutFirst()
         when (tail.length) {
@@ -78,6 +84,9 @@ operator fun Meta?.get(name: Name): MetaItem<*>? {
 }
 
 operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token)
+/**
+ * Parse [Name] from [key] using full name notation and pass it to [Meta.get]
+ */
 operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName())
 
 /**
@@ -113,12 +122,16 @@ operator fun Meta.iterator(): Iterator<Pair<Name, MetaItem<*>>> = sequence().ite
 /**
  * A meta node that ensures that all of its descendants has at least the same type
  */
-interface MetaNode<M : MetaNode<M>> : Meta {
+interface MetaNode<out M : MetaNode<M>> : Meta {
     override val items: Map<NameToken, MetaItem<M>>
 }
 
-operator fun <M : MetaNode<M>> MetaNode<M>?.get(name: Name): MetaItem<M>? {
+/**
+ * The same as [Meta.get], but with specific node type
+ */
+operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? {
     if (this == null) return null
+    if (name.isEmpty()) return NodeItem(this)
     return name.first()?.let { token ->
         val tail = name.cutFirst()
         when (tail.length) {
@@ -128,17 +141,9 @@ operator fun <M : MetaNode<M>> MetaNode<M>?.get(name: Name): MetaItem<M>? {
     }
 }
 
-operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = if (this == null) {
-    null
-} else {
-    this[key.toName()]
-}
+operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()]
 
-operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: NameToken): MetaItem<M>? = if (this == null) {
-    null
-} else {
-    this[key.asName()]
-}
+operator fun <M : MetaNode<M>> M?.get(key: NameToken): MetaItem<M>? = this[key.asName()]
 
 /**
  * Equals, hashcode and to string for any meta
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
index 4da08ce4..7a379f24 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
@@ -3,7 +3,7 @@ package hep.dataforge.meta
 import hep.dataforge.names.*
 import hep.dataforge.values.Value
 
-interface MutableMeta<M : MutableMeta<M>> : MetaNode<M> {
+interface MutableMeta<out M : MutableMeta<M>> : MetaNode<M> {
     override val items: Map<NameToken, MetaItem<M>>
     operator fun set(name: Name, item: MetaItem<*>?)
 //    fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
@@ -54,13 +54,14 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
             0 -> error("Can't setValue meta item for empty name")
             1 -> {
                 val token = name.first()!!
-                replaceItem(token, get(name), wrapItem(item))
+                @Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M>
+                replaceItem(token, oldItem, wrapItem(item))
             }
             else -> {
                 val token = name.first()!!
                 //get existing or create new node. Query is ignored for new node
-                if(items[token] == null){
-                    replaceItem(token,null, MetaItem.NodeItem(empty()))
+                if (items[token] == null) {
+                    replaceItem(token, null, MetaItem.NodeItem(empty()))
                 }
                 items[token]?.node!![name.cutFirst()] = item
             }
@@ -71,6 +72,7 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
 
 @Suppress("NOTHING_TO_INLINE")
 inline fun MutableMeta<*>.remove(name: Name) = set(name, null)
+
 @Suppress("NOTHING_TO_INLINE")
 inline fun MutableMeta<*>.remove(name: String) = remove(name.toName())
 

From 659fded3a5695dc784bbe1db132563828a5829ef Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 24 Dec 2019 20:06:16 +0300
Subject: [PATCH 19/41] Replace scecification builders by invokations

---
 .../kotlin/hep/dataforge/data/DataFilter.kt       |  2 +-
 .../hep/dataforge/descriptors/ItemDescriptor.kt   | 15 +++++++--------
 .../kotlin/hep/dataforge/meta/Specific.kt         |  8 ++++----
 .../hep/dataforge/descriptors/DescriptorTest.kt   |  2 +-
 .../kotlin/hep/dataforge/meta/MetaDelegateTest.kt |  2 +-
 .../hep/dataforge/meta/SpecificationTest.kt       |  2 +-
 .../kotlin/hep/dataforge/workspace/TaskBuilder.kt |  2 +-
 .../kotlin/hep/dataforge/workspace/TaskModel.kt   |  2 +-
 8 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
index a55aac9d..3770c9a8 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
@@ -54,4 +54,4 @@ fun <T : Any> DataNode<T>.filter(filter: Meta): DataNode<T> = filter(DataFilter.
  * Filter data using [DataFilter] builder
  */
 fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> =
-    filter(DataFilter.build(filterBuilder))
\ No newline at end of file
+    filter(DataFilter.invoke(filterBuilder))
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
index 0658dfd8..79c17e47 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
@@ -86,7 +86,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
      * Add a value descriptor using block for
      */
     fun value(name: String, block: ValueDescriptor.() -> Unit) {
-        value(name, ValueDescriptor.build { this.name = name }.apply(block))
+        value(name, ValueDescriptor { this.name = name }.apply(block))
     }
 
     /**
@@ -105,7 +105,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
     }
 
     fun node(name: String, block: NodeDescriptor.() -> Unit) {
-        node(name, build { this.name = name }.apply(block))
+        node(name, invoke { this.name = name }.apply(block))
     }
 
     val items: Map<String, ItemDescriptor> get() = nodes + values
@@ -205,12 +205,11 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
 
         override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
 
-        inline fun <reified E : Enum<E>> enum(name: String) =
-            build {
-                this.name = name
-                type(ValueType.STRING)
-                this.allowedValues = enumValues<E>().map { Value.of(it.name) }
-            }
+        inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
+            this.name = name
+            type(ValueType.STRING)
+            this.allowedValues = enumValues<E>().map { Value.of(it.name) }
+        }
 
 //        /**
 //         * Build a value descriptor from annotation
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
index d3ec418a..a4ebffe6 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
@@ -25,9 +25,9 @@ interface Specification<T : Specific> {
         return wrap(config).apply(action)
     }
 
-    fun build(action: T.() -> Unit) = update(Config(), action)
+    operator fun invoke(action: T.() -> Unit) = update(Config(), action)
 
-    fun empty() = build { }
+    fun empty() = wrap(Config())
 
     /**
      * Wrap generic configuration producing instance of desired type
@@ -67,7 +67,7 @@ fun <C : Specific> Specific.spec(
     key: Name? = null
 ): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) }
 
-fun <T: Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)}
+fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
 
 @JvmName("configSpec")
-fun <T: Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)}
\ No newline at end of file
+fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt
index 79800ec5..81d25285 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt
@@ -6,7 +6,7 @@ import kotlin.test.assertEquals
 
 class DescriptorTest {
 
-    val descriptor = NodeDescriptor.build {
+    val descriptor = NodeDescriptor {
         node("aNode") {
             info = "A root demo node"
             value("b") {
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
index 997a13e3..162a3852 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
@@ -29,7 +29,7 @@ class MetaDelegateTest {
         testObject.config["myValue"] = "theString"
         testObject.enumValue = TestEnum.NO
 
-        testObject.inner = innerSpec.build { innerValue = "ddd"}
+        testObject.inner = innerSpec { innerValue = "ddd" }
 
         assertEquals("theString", testObject.myValue)
         assertEquals(TestEnum.NO, testObject.enumValue)
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
index 9098cf18..c11c137b 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
@@ -15,7 +15,7 @@ class SpecificationTest {
 
     @Test
     fun testSpecific(){
-        val testObject = TestSpecific.build {
+        val testObject = TestSpecific {
             list = emptyList()
         }
         assertEquals(emptyList(), testObject.list)
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
index 3ff86325..6ae15fa3 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
@@ -201,7 +201,7 @@ class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) {
      * Use DSL to create a descriptor for this task
      */
     fun description(transform: NodeDescriptor.() -> Unit) {
-        this.descriptor = NodeDescriptor.build(transform)
+        this.descriptor = NodeDescriptor(transform)
     }
 
     internal fun build(): GenericTask<R> {
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
index 71a5c006..45b07fca 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
@@ -105,7 +105,7 @@ fun <T : Any> TaskDependencyContainer.dependsOn(
  * Add custom data dependency
  */
 fun TaskDependencyContainer.data(action: DataFilter.() -> Unit): DataDependency =
-    DataDependency(DataFilter.build(action)).also { add(it) }
+    DataDependency(DataFilter(action)).also { add(it) }
 
 /**
  * User-friendly way to add data dependency

From ce8be7854901190b0d2396078f47f93fb4719326 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 24 Dec 2019 20:59:14 +0300
Subject: [PATCH 20/41] Replace EmptyName by Name.Empty Composite builders for
 node descriptor

---
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt |  6 +-
 .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt |  2 +-
 .../dataforge/descriptors/ItemDescriptor.kt   | 70 +++++++++++++------
 .../kotlin/hep/dataforge/meta/Meta.kt         |  2 +-
 .../hep/dataforge/meta/configDelegates.kt     |  4 +-
 .../hep/dataforge/meta/metaDelegates.kt       |  4 +-
 .../kotlin/hep/dataforge/names/Name.kt        |  8 +--
 .../hep/dataforge/output/OutputManager.kt     |  7 +-
 .../hep/dataforge/workspace/Dependency.kt     | 11 +--
 .../hep/dataforge/workspace/TaskBuilder.kt    |  5 +-
 .../hep/dataforge/workspace/TaskModel.kt      | 11 ++-
 .../dataforge/workspace/WorkspaceBuilder.kt   |  5 +-
 12 files changed, 81 insertions(+), 54 deletions(-)

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index 5b22ae8c..0ed5214b 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -72,7 +72,7 @@ class TaglessEnvelopeFormat(
                 properties[key] = value
             }
             //If can't read line, return envelope without data
-            if (eof()) return SimpleEnvelope(Meta.empty, null)
+            if (eof()) return SimpleEnvelope(Meta.EMPTY, null)
             line = readUtf8Line()
         }
 
@@ -135,7 +135,7 @@ class TaglessEnvelopeFormat(
                 line = readUtf8Line()
                 offset += line.toUtf8Bytes().size.toUInt()
             } catch (ex: EOFException) {
-                return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
+                return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
             }
         }
 
@@ -155,7 +155,7 @@ class TaglessEnvelopeFormat(
         }
 
         do {
-            line = readUtf8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
+            line = readUtf8Line() ?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
             offset += line.toUtf8Bytes().size.toUInt()
             //returning an Envelope without data if end of input is reached
         } while (!line.startsWith(dataStart))
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index 4e60707f..fb693057 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -132,7 +132,7 @@ fun IOPlugin.readEnvelopeFile(
     return formatPeeker(path)?.let { format ->
         FileEnvelope(path, format)
     } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary
-        SimpleEnvelope(Meta.empty, path.asBinary())
+        SimpleEnvelope(Meta.EMPTY, path.asBinary())
     } else null
 }
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
index 79c17e47..3d6e7744 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
@@ -1,8 +1,9 @@
 package hep.dataforge.descriptors
 
 import hep.dataforge.meta.*
+import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
-import hep.dataforge.names.toName
+import hep.dataforge.names.asName
 import hep.dataforge.values.False
 import hep.dataforge.values.True
 import hep.dataforge.values.Value
@@ -36,7 +37,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
      *
      * @return
      */
-    var attributes by node()
+    var attributes by child()
 
     /**
      * True if the item is required
@@ -66,13 +67,54 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
      *
      * @return
      */
-    var default: Config? by node()
+    var default: Config? by child()
+
+    /**
+     * The map of children node descriptors
+     */
+    val nodes: Map<String, NodeDescriptor>
+        get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) ->
+            name to wrap(node.node ?: error("Node descriptor must be a node"))
+        }
+
+
+    fun node(name: String, descriptor: NodeDescriptor) {
+        if (items.keys.contains(name)) error("The key $name already exists in descriptor")
+        val token = NameToken(NODE_KEY, name)
+        config[token] = descriptor.config
+    }
+
+
+    fun node(name: String, block: NodeDescriptor.() -> Unit) {
+        val token = NameToken(NODE_KEY, name)
+        if (config[token] == null) {
+            config[token] = NodeDescriptor(block)
+        } else {
+            NodeDescriptor.update(config[token].node ?: error("Node expected"), block)
+        }
+    }
+
+    private fun buildNode(name: Name): NodeDescriptor {
+        return when (name.length) {
+            0 -> this
+            1 -> {
+                val token = NameToken(NODE_KEY, name.toString())
+                val config: Config = config[token].node ?: Config().also { config[token] = it }
+                wrap(config)
+            }
+            else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst())
+        }
+    }
+
+    fun node(name: Name, block: NodeDescriptor.() -> Unit) {
+        buildNode(name).apply(block)
+    }
 
     /**
      * The list of value descriptors
      */
     val values: Map<String, ValueDescriptor>
-        get() = config.getIndexed(VALUE_KEY.toName()).entries.associate { (name, node) ->
+        get() = config.getIndexed(VALUE_KEY.asName()).entries.associate { (name, node) ->
             name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
         }
 
@@ -89,23 +131,9 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
         value(name, ValueDescriptor { this.name = name }.apply(block))
     }
 
-    /**
-     * The map of children node descriptors
-     */
-    val nodes: Map<String, NodeDescriptor>
-        get() = config.getIndexed(NODE_KEY.toName()).entries.associate { (name, node) ->
-            name to wrap(node.node ?: error("Node descriptor must be a node"))
-        }
-
-
-    fun node(name: String, descriptor: NodeDescriptor) {
-        if (items.keys.contains(name)) error("The key $name already exists in descriptor")
-        val token = NameToken(NODE_KEY, name)
-        config[token] = descriptor.config
-    }
-
-    fun node(name: String, block: NodeDescriptor.() -> Unit) {
-        node(name, invoke { this.name = name }.apply(block))
+    fun value(name: Name, block: ValueDescriptor.() -> Unit) {
+        require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
+        buildNode(name.cutLast()).value(name.last().toString())
     }
 
     val items: Map<String, ItemDescriptor> get() = nodes + values
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index 57364593..7b980137 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -60,7 +60,7 @@ interface Meta : MetaRepr {
          */
         const val VALUE_KEY = "@value"
 
-        val empty: EmptyMeta = EmptyMeta
+        val EMPTY: EmptyMeta = EmptyMeta
     }
 }
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt
index 34fa0e71..e3dbc9aa 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt
@@ -105,7 +105,7 @@ inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null
 
 /* Node delegates */
 
-fun Configurable.node(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key)
+fun Configurable.child(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key)
 
 fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) =
     MutableMorphDelegate(config, key) { spec.wrap(it) }
@@ -133,5 +133,5 @@ fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value?
             ?: doubleArrayOf()
     }
 
-fun <T : Configurable> Configurable.node(key: Name? = null, converter: (Meta) -> T) =
+fun <T : Configurable> Configurable.child(key: Name? = null, converter: (Meta) -> T) =
     MutableMorphDelegate(config, key, converter)
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
index 65e49da2..0921eae8 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
@@ -144,7 +144,7 @@ fun Meta.boolean(default: Boolean? = null, key: String? = null) = BooleanDelegat
 
 fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(this, key, default)
 
-fun Meta.node(key: String? = null) = ChildDelegate(this, key) { it }
+fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it }
 
 @JvmName("safeString")
 fun Meta.string(default: String, key: String? = null) =
@@ -400,7 +400,7 @@ fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null)
 fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null) =
     MutableNumberDelegate(this, key, default)
 
-fun <M : MutableMeta<M>> M.node(key: Name? = null) =
+fun <M : MutableMeta<M>> M.child(key: Name? = null) =
     MutableNodeDelegate(this, key)
 
 @JvmName("safeString")
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
index 084f27df..3c8d93d5 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
@@ -53,6 +53,8 @@ class Name(val tokens: List<NameToken>) {
 
     companion object {
         const val NAME_SEPARATOR = "."
+
+        val EMPTY = Name(emptyList())
     }
 }
 
@@ -87,7 +89,7 @@ data class NameToken(val body: String, val index: String = "") {
  * This operation is rather heavy so it should be used with care in high performance code.
  */
 fun String.toName(): Name {
-    if (isBlank()) return EmptyName
+    if (isBlank()) return Name.EMPTY
     val tokens = sequence {
         var bodyBuilder = StringBuilder()
         var queryBuilder = StringBuilder()
@@ -139,7 +141,7 @@ fun String.toName(): Name {
  * Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
  * The input string could contain dots and braces, but they are just escaped, not parsed.
  */
-fun String.asName(): Name = if (isBlank()) EmptyName else NameToken(this).asName()
+fun String.asName(): Name = if (isBlank()) Name.EMPTY else NameToken(this).asName()
 
 operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
 
@@ -153,8 +155,6 @@ fun Name.appendLeft(other: String): Name = NameToken(other) + this
 
 fun NameToken.asName() = Name(listOf(this))
 
-val EmptyName = Name(emptyList())
-
 fun Name.isEmpty(): Boolean = this.length == 0
 
 /**
diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt
index c05d5bb4..e88b29a5 100644
--- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt
+++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt
@@ -4,7 +4,6 @@ import hep.dataforge.context.*
 import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
-import hep.dataforge.names.EmptyName
 import hep.dataforge.names.Name
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
@@ -24,7 +23,7 @@ interface OutputManager {
     operator fun <T : Any> get(
         type: KClass<out T>,
         name: Name,
-        stage: Name = EmptyName,
+        stage: Name = Name.EMPTY,
         meta: Meta = EmptyMeta
     ): Renderer<T>
 }
@@ -39,7 +38,7 @@ val Context.output: OutputManager get() = plugins.get() ?: ConsoleOutputManager(
  */
 inline operator fun <reified T : Any> OutputManager.get(
     name: Name,
-    stage: Name = EmptyName,
+    stage: Name = Name.EMPTY,
     meta: Meta = EmptyMeta
 ): Renderer<T> {
     return get(T::class, name, stage, meta)
@@ -48,7 +47,7 @@ inline operator fun <reified T : Any> OutputManager.get(
 /**
  * Directly render an object using the most suitable renderer
  */
-fun OutputManager.render(obj: Any, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta) =
+fun OutputManager.render(obj: Any, name: Name, stage: Name = Name.EMPTY, meta: Meta = EmptyMeta) =
     get(obj::class, name, stage).render(obj, meta)
 
 /**
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt
index ddb53d5d..72402a17 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt
@@ -6,7 +6,10 @@ import hep.dataforge.data.filter
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaRepr
 import hep.dataforge.meta.buildMeta
-import hep.dataforge.names.*
+import hep.dataforge.names.Name
+import hep.dataforge.names.asName
+import hep.dataforge.names.isEmpty
+import hep.dataforge.names.plus
 
 /**
  * A dependency of the task which allows to lazily create a data tree for single dependency
@@ -15,7 +18,7 @@ sealed class Dependency : MetaRepr {
     abstract fun apply(workspace: Workspace): DataNode<Any>
 }
 
-class DataDependency(val filter: DataFilter, val placement: Name = EmptyName) : Dependency() {
+class DataDependency(val filter: DataFilter, val placement: Name = Name.EMPTY) : Dependency() {
     override fun apply(workspace: Workspace): DataNode<Any> {
         val result = workspace.data.filter(filter)
         return if (placement.isEmpty()) {
@@ -31,7 +34,7 @@ class DataDependency(val filter: DataFilter, val placement: Name = EmptyName) :
     }
 }
 
-class AllDataDependency(val placement: Name = EmptyName) : Dependency() {
+class AllDataDependency(val placement: Name = Name.EMPTY) : Dependency() {
     override fun apply(workspace: Workspace): DataNode<Any> = if (placement.isEmpty()) {
         workspace.data
     } else {
@@ -46,7 +49,7 @@ class AllDataDependency(val placement: Name = EmptyName) : Dependency() {
 
 abstract class TaskDependency<out T : Any>(
     val meta: Meta,
-    val placement: Name = EmptyName
+    val placement: Name = Name.EMPTY
 ) : Dependency() {
     abstract fun resolveTask(workspace: Workspace): Task<T>
 
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
index 6ae15fa3..87736c01 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
@@ -7,7 +7,6 @@ import hep.dataforge.meta.DFBuilder
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.string
-import hep.dataforge.names.EmptyName
 import hep.dataforge.names.Name
 import hep.dataforge.names.isEmpty
 import hep.dataforge.names.toName
@@ -57,7 +56,7 @@ class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) {
         block: TaskEnv.(DataNode<*>) -> DataNode<R>
     ) {
         dataTransforms += DataTransformation(from, to) { context, model, data ->
-            val env = TaskEnv(EmptyName, model.meta, context, data)
+            val env = TaskEnv(Name.EMPTY, model.meta, context, data)
             env.block(data)
         }
     }
@@ -70,7 +69,7 @@ class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) {
     ) {
         dataTransforms += DataTransformation(from, to) { context, model, data ->
             data.ensureType(inputType)
-            val env = TaskEnv(EmptyName, model.meta, context, data)
+            val env = TaskEnv(Name.EMPTY, model.meta, context, data)
             env.block(data.cast(inputType))
         }
     }
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
index 45b07fca..ede9efaa 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
@@ -9,7 +9,6 @@ import hep.dataforge.data.DataFilter
 import hep.dataforge.data.DataTree
 import hep.dataforge.data.DataTreeBuilder
 import hep.dataforge.meta.*
-import hep.dataforge.names.EmptyName
 import hep.dataforge.names.Name
 import hep.dataforge.names.asName
 import hep.dataforge.names.toName
@@ -68,21 +67,21 @@ interface TaskDependencyContainer {
  */
 fun TaskDependencyContainer.dependsOn(
     name: Name,
-    placement: Name = EmptyName,
+    placement: Name = Name.EMPTY,
     meta: Meta = defaultMeta
 ): WorkspaceTaskDependency =
     WorkspaceTaskDependency(name, meta, placement).also { add(it) }
 
 fun TaskDependencyContainer.dependsOn(
     name: String,
-    placement: Name = EmptyName,
+    placement: Name = Name.EMPTY,
     meta: Meta = defaultMeta
 ): WorkspaceTaskDependency =
     dependsOn(name.toName(), placement, meta)
 
 fun <T : Any> TaskDependencyContainer.dependsOn(
     task: Task<T>,
-    placement: Name = EmptyName,
+    placement: Name = Name.EMPTY,
     meta: Meta = defaultMeta
 ): DirectTaskDependency<T> =
     DirectTaskDependency(task, meta, placement).also { add(it) }
@@ -96,7 +95,7 @@ fun <T : Any> TaskDependencyContainer.dependsOn(
 
 fun <T : Any> TaskDependencyContainer.dependsOn(
     task: Task<T>,
-    placement: Name = EmptyName,
+    placement: Name = Name.EMPTY,
     metaBuilder: MetaBuilder.() -> Unit
 ): DirectTaskDependency<T> =
     dependsOn(task, placement, buildMeta(metaBuilder))
@@ -120,7 +119,7 @@ fun TaskDependencyContainer.data(pattern: String? = null, from: String? = null,
 /**
  * Add all data as root node
  */
-fun TaskDependencyContainer.allData(to: Name = EmptyName) = AllDataDependency(to).also { add(it) }
+fun TaskDependencyContainer.allData(to: Name = Name.EMPTY) = AllDataDependency(to).also { add(it) }
 
 /**
  * A builder for [TaskModel]
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
index b8f3ffa0..7b831a50 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
@@ -5,7 +5,6 @@ import hep.dataforge.context.ContextBuilder
 import hep.dataforge.data.DataNode
 import hep.dataforge.data.DataTreeBuilder
 import hep.dataforge.meta.*
-import hep.dataforge.names.EmptyName
 import hep.dataforge.names.Name
 import hep.dataforge.names.isEmpty
 import hep.dataforge.names.toName
@@ -32,7 +31,7 @@ fun WorkspaceBuilder.context(name: String = "WORKSPACE", block: ContextBuilder.(
 }
 
 inline fun <reified T : Any> WorkspaceBuilder.data(
-    name: Name = EmptyName,
+    name: Name = Name.EMPTY,
     noinline block: DataTreeBuilder<T>.() -> Unit
 ): DataNode<T> {
     val node = DataTreeBuilder(T::class).apply(block)
@@ -47,7 +46,7 @@ inline fun <reified T : Any> WorkspaceBuilder.data(
 
 @JvmName("rawData")
 fun WorkspaceBuilder.data(
-    name: Name = EmptyName,
+    name: Name = Name.EMPTY,
     block: DataTreeBuilder<Any>.() -> Unit
 ): DataNode<Any> = data<Any>(name, block)
 

From e532e8358ed9c2dc892839e6ed24a37c44de5af0 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Tue, 24 Dec 2019 22:10:48 +0300
Subject: [PATCH 21/41] Remove name from descriptors. It is never used

---
 .../kotlin/hep/dataforge/io/JsonMetaFormat.kt      | 14 ++++++--------
 .../hep/dataforge/descriptors/ItemDescriptor.kt    | 12 ++----------
 2 files changed, 8 insertions(+), 18 deletions(-)

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
index 9b78cbb3..d27b0a97 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
@@ -72,7 +72,7 @@ fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
 //Use these methods to customize JSON key mapping
 private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
 
-private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
+//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
 
 fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
 
@@ -141,15 +141,13 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M
 
     @Suppress("UNCHECKED_CAST")
     private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit {
-        val itemDescriptor = descriptor.getDescriptor(key)
-        //use name from descriptor in case descriptor name differs from json key
-        val name = itemDescriptor?.name ?: key
+        val itemDescriptor = descriptor?.items?.get(key)
         return when (value) {
             is JsonPrimitive -> {
-                this[name] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
+                this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
             }
             is JsonObject -> {
-                this[name] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
+                this[key] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
             }
             is JsonArray -> {
                 when {
@@ -160,10 +158,10 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M
                                 (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
                             }
                         )
-                        this[name] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
+                        this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
                     }
                     else -> value.forEachIndexed { index, jsonElement ->
-                        this["$name[$index]"] = jsonElement.toMetaItem(itemDescriptor)
+                        this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor)
                     }
                 }
             }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
index 3d6e7744..93765dd4 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
@@ -11,13 +11,6 @@ import hep.dataforge.values.ValueType
 
 sealed class ItemDescriptor(override val config: Config) : Specific {
 
-    /**
-     * The name of this item
-     *
-     * @return
-     */
-    var name: String by string { error("Anonymous descriptors are not allowed") }
-
     /**
      * True if same name siblings with this name are allowed
      *
@@ -128,12 +121,12 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
      * Add a value descriptor using block for
      */
     fun value(name: String, block: ValueDescriptor.() -> Unit) {
-        value(name, ValueDescriptor { this.name = name }.apply(block))
+        value(name, ValueDescriptor(block))
     }
 
     fun value(name: Name, block: ValueDescriptor.() -> Unit) {
         require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
-        buildNode(name.cutLast()).value(name.last().toString())
+        buildNode(name.cutLast()).value(name.last().toString(), block)
     }
 
     val items: Map<String, ItemDescriptor> get() = nodes + values
@@ -234,7 +227,6 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
         override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
 
         inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
-            this.name = name
             type(ValueType.STRING)
             this.allowedValues = enumValues<E>().map { Value.of(it.name) }
         }

From 1b879eccc7f7afd2b5edc6551e895b33c4a32771 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sat, 28 Dec 2019 16:06:32 +0300
Subject: [PATCH 22/41] Specialized non-null delegates for spec

---
 build.gradle.kts                              |  2 +-
 .../hep/dataforge/descriptors/Described.kt    |  6 ++---
 .../kotlin/hep/dataforge/meta/Specific.kt     | 25 ++++++++++++++++---
 .../hep/dataforge/meta/metaDelegates.kt       |  4 +--
 4 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 0dbaef48..cfa22253 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-5")
+val dataforgeVersion by extra("0.1.5-dev-6")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
index f355c828..f2ab0c00 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
@@ -1,7 +1,7 @@
 package hep.dataforge.descriptors
 
 import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE
-import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaRepr
 import hep.dataforge.meta.get
 import hep.dataforge.meta.node
 
@@ -19,11 +19,11 @@ interface Described {
 /**
  * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
  */
-val Meta.descriptor: NodeDescriptor?
+val MetaRepr.descriptor: NodeDescriptor?
     get() {
         return if (this is Described) {
             descriptor
         } else {
-            get(DESCRIPTOR_NODE).node?.let { NodeDescriptor.wrap(it) }
+            toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
         }
     }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
index a4ebffe6..850365a5 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
@@ -1,7 +1,10 @@
 package hep.dataforge.meta
 
 import hep.dataforge.names.Name
+import hep.dataforge.names.asName
 import kotlin.jvm.JvmName
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
 
 /**
  * Marker interface for classes with specifications
@@ -61,13 +64,27 @@ fun <C : Specific, S : Specification<C>> Specific.update(spec: S, action: C.() -
 fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
     Config().also { update(it, action) }
 
+class SpecDelegate<T : Specific, S : Specification<T>>(
+    val target: Specific,
+    val spec: S,
+    val key: Name? = null
+) : ReadWriteProperty<Any?, T> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+        return target.config[key ?: property.name.asName()]?.node?.let { spec.wrap(it) } ?: spec.empty()
+    }
 
-fun <C : Specific> Specific.spec(
-    spec: Specification<C>,
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+        target.config[key ?: property.name.asName()] = value.config
+    }
+}
+
+fun <T : Specific, S : Specification<T>> Specific.spec(
+    spec: S,
     key: Name? = null
-): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) }
+): SpecDelegate<T, S> = SpecDelegate(this, spec, key)
 
 fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
 
 @JvmName("configSpec")
-fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
\ No newline at end of file
+fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
+
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
index 0921eae8..02caa9fa 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
@@ -350,8 +350,8 @@ class MutableNodeDelegate<M : MutableMeta<M>>(
     }
 }
 
-class MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>(
-    val meta: M,
+class MutableMorphDelegate<T : Configurable>(
+    val meta: MutableMeta<*>,
     private val key: Name? = null,
     private val converter: (Meta) -> T
 ) : ReadWriteProperty<Any?, T?> {

From 736ec621b0982ba30ee5a215a74002c3ae91c0d6 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sat, 28 Dec 2019 21:24:16 +0300
Subject: [PATCH 23/41] Invoke mechanism for defining specific and fix to
 attach config child

---
 .../kotlin/hep/dataforge/meta/Specific.kt           | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
index 850365a5..865114e5 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
@@ -15,6 +15,13 @@ interface Specific : Configurable
 
 operator fun Specific.get(name: String): MetaItem<*>? = config[name]
 
+/**
+ * Editor for specific objects
+ */
+inline operator fun <S : Specific> S.invoke(block: S.() -> Unit): Unit {
+    run(block)
+}
+
 /**
  * Allows to apply custom configuration in a type safe way to simple untyped configuration.
  * By convention [Specific] companion should inherit this class
@@ -69,8 +76,12 @@ class SpecDelegate<T : Specific, S : Specification<T>>(
     val spec: S,
     val key: Name? = null
 ) : ReadWriteProperty<Any?, T> {
+
     override fun getValue(thisRef: Any?, property: KProperty<*>): T {
-        return target.config[key ?: property.name.asName()]?.node?.let { spec.wrap(it) } ?: spec.empty()
+        val name = key ?: property.name.asName()
+        return target.config[name]?.node?.let { spec.wrap(it) } ?: (spec.empty().also {
+            target.config[name] = it.config
+        })
     }
 
     override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {

From b83821af51e5b66557787e365cd6853abd6d30f6 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 8 Jan 2020 21:41:27 +0300
Subject: [PATCH 24/41] Configurable and Scheme revision

---
 README.md                                     |   5 +-
 build.gradle.kts                              |   2 +-
 .../kotlin/hep/dataforge/data/DataFilter.kt   |   6 +-
 .../io/serialization/MetaSerializer.kt        |   2 +-
 .../hep/dataforge/descriptors/Described.kt    |  29 +--
 .../dataforge/descriptors/DescriptorMeta.kt   |  28 +++
 .../dataforge/descriptors/ItemDescriptor.kt   |  36 +--
 .../kotlin/hep/dataforge/meta/Config.kt       |  20 +-
 .../kotlin/hep/dataforge/meta/Configurable.kt |  44 ++++
 .../kotlin/hep/dataforge/meta/Laminate.kt     |  18 +-
 .../kotlin/hep/dataforge/meta/Meta.kt         |  29 ++-
 .../kotlin/hep/dataforge/meta/MutableMeta.kt  |   2 +-
 .../kotlin/hep/dataforge/meta/Scheme.kt       |  78 ++++++
 .../kotlin/hep/dataforge/meta/Specific.kt     | 101 --------
 .../hep/dataforge/meta/Specification.kt       |  60 +++++
 .../kotlin/hep/dataforge/meta/Styled.kt       |  72 ------
 .../hep/dataforge/meta/configDelegates.kt     | 137 ----------
 .../dataforge/meta/configurableDelegates.kt   | 236 ++++++++++++++++++
 .../hep/dataforge/meta/metaDelegates.kt       |  18 --
 .../hep/dataforge/values/valueExtensions.kt   |   1 +
 .../hep/dataforge/meta/MetaDelegateTest.kt    |   7 +-
 .../hep/dataforge/meta/MutableMetaTest.kt     |   2 +-
 .../meta/{StyledTest.kt => SchemeTest.kt}     |  13 +-
 .../hep/dataforge/meta/SpecificationTest.kt   |  15 +-
 24 files changed, 548 insertions(+), 413 deletions(-)
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
 delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt
 delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt
 delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt
 rename dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/{StyledTest.kt => SchemeTest.kt} (71%)

diff --git a/README.md b/README.md
index 626ee576..a6e6ed99 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,11 @@
 [![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
+
+![Gradle build](https://github.com/mipt-npm/dataforge-core/workflows/Gradle%20build/badge.svg)
 
 [ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion)
 
-[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
+
 
 # Questions and Answers #
 
diff --git a/build.gradle.kts b/build.gradle.kts
index cfa22253..53a45364 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-6")
+val dataforgeVersion by extra("0.1.5-dev-7")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
index 3770c9a8..08a0ea87 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
@@ -4,7 +4,7 @@ import hep.dataforge.meta.*
 import hep.dataforge.names.toName
 
 
-class DataFilter(override val config: Config) : Specific {
+class DataFilter : Scheme() {
     /**
      * A source node for the filter
      */
@@ -22,9 +22,7 @@ class DataFilter(override val config: Config) : Specific {
 
     fun isEmpty(): Boolean = config.isEmpty()
 
-    companion object : Specification<DataFilter> {
-        override fun wrap(config: Config): DataFilter = DataFilter(config)
-    }
+    companion object : SchemeSpec<DataFilter>(::DataFilter)
 }
 
 /**
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
index 31925c9f..a1ac33a5 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
@@ -133,7 +133,7 @@ object ConfigSerializer : KSerializer<Config> {
     override val descriptor: SerialDescriptor = MetaSerializer.descriptor
 
     override fun deserialize(decoder: Decoder): Config {
-        return MetaSerializer.deserialize(decoder).toConfig()
+        return MetaSerializer.deserialize(decoder).asConfig()
     }
 
     override fun serialize(encoder: Encoder, obj: Config) {
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
index f2ab0c00..4bf6c9dd 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
@@ -1,29 +1,24 @@
 package hep.dataforge.descriptors
 
-import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE
-import hep.dataforge.meta.MetaRepr
-import hep.dataforge.meta.get
-import hep.dataforge.meta.node
-
 /**
  * An object which provides its descriptor
  */
 interface Described {
-    val descriptor: NodeDescriptor
+    val descriptor: NodeDescriptor?
 
     companion object {
         const val DESCRIPTOR_NODE = "@descriptor"
     }
 }
 
-/**
- * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
- */
-val MetaRepr.descriptor: NodeDescriptor?
-    get() {
-        return if (this is Described) {
-            descriptor
-        } else {
-            toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
-        }
-    }
\ No newline at end of file
+///**
+// * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
+// */
+//val MetaRepr.descriptor: NodeDescriptor?
+//    get() {
+//        return if (this is Described) {
+//            descriptor
+//        } else {
+//            toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
+//        }
+//    }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt
new file mode 100644
index 00000000..49aef28e
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt
@@ -0,0 +1,28 @@
+package hep.dataforge.descriptors
+
+import hep.dataforge.meta.MetaBase
+import hep.dataforge.meta.MetaItem
+import hep.dataforge.names.NameToken
+import hep.dataforge.values.Null
+
+class DescriptorMeta(val descriptor: NodeDescriptor) : MetaBase() {
+    override val items: Map<NameToken, MetaItem<*>>
+        get() = descriptor.items.entries.associate { entry ->
+            NameToken(entry.key) to entry.value.defaultItem()
+        }
+}
+
+fun NodeDescriptor.defaultItem(): MetaItem.NodeItem<*> =
+    MetaItem.NodeItem(default ?: DescriptorMeta(this))
+
+fun ValueDescriptor.defaultItem(): MetaItem.ValueItem = MetaItem.ValueItem(default ?: Null)
+
+/**
+ * Build a default [MetaItem] from descriptor.
+ */
+fun ItemDescriptor.defaultItem(): MetaItem<*> {
+    return when (this) {
+        is ValueDescriptor -> defaultItem()
+        is NodeDescriptor -> defaultItem()
+    }
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
index 93765dd4..99b3e850 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
@@ -4,12 +4,13 @@ import hep.dataforge.meta.*
 import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
 import hep.dataforge.names.asName
+import hep.dataforge.names.isEmpty
 import hep.dataforge.values.False
 import hep.dataforge.values.True
 import hep.dataforge.values.Value
 import hep.dataforge.values.ValueType
 
-sealed class ItemDescriptor(override val config: Config) : Specific {
+sealed class ItemDescriptor : Scheme() {
 
     /**
      * True if same name siblings with this name are allowed
@@ -30,7 +31,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
      *
      * @return
      */
-    var attributes by child()
+    var attributes by config()
 
     /**
      * True if the item is required
@@ -46,7 +47,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
  *
  * @author Alexander Nozik
  */
-class NodeDescriptor(config: Config) : ItemDescriptor(config) {
+class NodeDescriptor : ItemDescriptor() {
 
     /**
      * True if the node is required
@@ -60,7 +61,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
      *
      * @return
      */
-    var default: Config? by child()
+    var default: Config? by nullableConfig()
 
     /**
      * The map of children node descriptors
@@ -134,18 +135,28 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
 
     //override val descriptor: NodeDescriptor =  empty("descriptor")
 
-    companion object : Specification<NodeDescriptor> {
+    companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
 
         //        const val ITEM_KEY = "item"
         const val NODE_KEY = "node"
         const val VALUE_KEY = "value"
 
-        override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
+        //override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
 
         //TODO infer descriptor from spec
     }
 }
 
+/**
+ * Get a descriptor item associated with given name or null if item for given name not provided
+ */
+operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
+    if (name.isEmpty()) return this
+    return when (this) {
+        is ValueDescriptor -> null // empty name already checked
+        is NodeDescriptor -> items[name.first()!!.toString()]?.get(name.cutFirst())
+    }
+}
 
 /**
  * A descriptor for meta value
@@ -154,7 +165,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
  *
  * @author Alexander Nozik
  */
-class ValueDescriptor(config: Config) : ItemDescriptor(config) {
+class ValueDescriptor : ItemDescriptor() {
 
 
     /**
@@ -180,8 +191,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
      *
      * @return
      */
-    var type: List<ValueType> by value {
-        it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
+    var type: List<ValueType> by item {
+        it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
     }
 
     fun type(vararg t: ValueType) {
@@ -222,10 +233,7 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
         this.allowedValues = v.map { Value.of(it) }
     }
 
-    companion object : Specification<ValueDescriptor> {
-
-        override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
-
+    companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
         inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
             type(ValueType.STRING)
             this.allowedValues = enumValues<E>().map { Value.of(it.name) }
@@ -289,4 +297,4 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
 //            return ValueDescriptor(Laminate(primary.meta, secondary.meta))
 //        }
     }
-}
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
index f47d3bcd..087cb077 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
@@ -40,7 +40,7 @@ class Config : AbstractMutableMeta<Config>() {
     override fun replaceItem(key: NameToken, oldItem: MetaItem<Config>?, newItem: MetaItem<Config>?) {
         if (newItem == null) {
             _items.remove(key)
-            if(oldItem!= null && oldItem is MetaItem.NodeItem<Config>) {
+            if (oldItem != null && oldItem is MetaItem.NodeItem<Config>) {
                 oldItem.node.removeListener(this)
             }
         } else {
@@ -57,7 +57,7 @@ class Config : AbstractMutableMeta<Config>() {
     /**
      * Attach configuration node instead of creating one
      */
-    override fun wrapNode(meta: Meta): Config = meta.toConfig()
+    override fun wrapNode(meta: Meta): Config = meta.asConfig()
 
     override fun empty(): Config = Config()
 
@@ -68,22 +68,12 @@ class Config : AbstractMutableMeta<Config>() {
 
 operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
 
-fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
+fun Meta.asConfig(): Config = this as? Config ?: Config().also { builder ->
     this.items.mapValues { entry ->
         val item = entry.value
         builder[entry.key.asName()] = when (item) {
             is MetaItem.ValueItem -> item.value
-            is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
+            is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.asConfig())
         }
     }
-}
-
-interface Configurable {
-    val config: Config
-}
-
-fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
-
-fun <T : Configurable> T.configure(action: MetaBuilder.() -> Unit): T = configure(buildMeta(action))
-
-open class SimpleConfigurable(override val config: Config) : Configurable
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
new file mode 100644
index 00000000..c13a6214
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
@@ -0,0 +1,44 @@
+package hep.dataforge.meta
+
+import hep.dataforge.names.Name
+import hep.dataforge.names.toName
+
+/**
+ * A container that holds a [Config] and a default item provider.
+ * Default item provider could be use for example to reference parent configuration.
+ * It is not possible to know if some property is declared by provider just by looking on [Configurable],
+ * this information should be provided externally.
+ */
+interface Configurable {
+    /**
+     * Backing config
+     */
+    val config: Config
+
+    /**
+     * Default meta item provider
+     */
+    fun getDefaultItem(name: Name): MetaItem<*>? = null
+}
+
+/**
+ * Get a property with default
+ */
+fun Configurable.getProperty(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name)
+
+fun Configurable.getProperty(key: String) = getProperty(key.toName())
+
+/**
+ * Set a configurable property
+ */
+fun Configurable.setProperty(name: Name, item: MetaItem<*>?) {
+    config[name] = item
+}
+
+fun Configurable.setProperty(key: String, item: MetaItem<*>?) {
+    setProperty(key.toName(), item)
+}
+
+fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
+
+fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
index b403544c..c6d69967 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
@@ -1,9 +1,10 @@
 package hep.dataforge.meta
 
+import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
 
 /**
- * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Styled].
+ * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
  */
 class Laminate(layers: List<Meta>) : MetaBase() {
 
@@ -17,10 +18,11 @@ class Laminate(layers: List<Meta>) : MetaBase() {
 
     constructor(vararg layers: Meta?) : this(layers.filterNotNull())
 
-    override val items: Map<NameToken, MetaItem<Meta>>
-        get() = layers.map { it.items.keys }.flatten().associateWith { key ->
+    override val items: Map<NameToken, MetaItem<Meta>> by lazy {
+        layers.map { it.items.keys }.flatten().associateWith { key ->
             layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
         }
+    }
 
     /**
      * Generate sealed meta using [mergeRule]
@@ -77,6 +79,16 @@ class Laminate(layers: List<Meta>) : MetaBase() {
     }
 }
 
+/**
+ * Performance optimized version of get method
+ */
+fun Laminate.getFirst(name: Name): MetaItem<*>? {
+    layers.forEach { layer ->
+        layer[name]?.let { return it }
+    }
+    return null
+}
+
 /**
  * Create a new [Laminate] adding given layer to the top
  */
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index 7b980137..d917559d 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -5,6 +5,7 @@ import hep.dataforge.meta.MetaItem.NodeItem
 import hep.dataforge.meta.MetaItem.ValueItem
 import hep.dataforge.names.*
 import hep.dataforge.values.EnumValue
+import hep.dataforge.values.Null
 import hep.dataforge.values.Value
 import hep.dataforge.values.boolean
 
@@ -22,6 +23,17 @@ sealed class MetaItem<out M : Meta> {
     data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
         override fun toString(): String = node.toString()
     }
+
+    companion object {
+        fun of(arg: Any?): MetaItem<*> {
+            return when (arg) {
+                null -> ValueItem(Null)
+                is MetaItem<*> -> arg
+                is Meta -> NodeItem(arg)
+                else -> ValueItem(Value.of(arg))
+            }
+        }
+    }
 }
 
 /**
@@ -45,7 +57,7 @@ interface Meta : MetaRepr {
      */
     val items: Map<NameToken, MetaItem<*>>
 
-    override fun toMeta(): Meta = this
+    override fun toMeta(): Meta = seal()
 
     override fun equals(other: Any?): Boolean
 
@@ -69,7 +81,7 @@ interface Meta : MetaRepr {
 /**
  * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
  *
- * If [name] is empty reture current [Meta] as a [NodeItem]
+ * If [name] is empty return current [Meta] as a [NodeItem]
  */
 operator fun Meta?.get(name: Name): MetaItem<*>? {
     if (this == null) return null
@@ -129,17 +141,8 @@ interface MetaNode<out M : MetaNode<M>> : Meta {
 /**
  * The same as [Meta.get], but with specific node type
  */
-operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? {
-    if (this == null) return null
-    if (name.isEmpty()) return NodeItem(this)
-    return name.first()?.let { token ->
-        val tail = name.cutFirst()
-        when (tail.length) {
-            0 -> items[token]
-            else -> items[token]?.node?.get(tail)
-        }
-    }
-}
+@Suppress("UNCHECKED_CAST")
+operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? = (this as Meta)[name] as MetaItem<M>?
 
 operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()]
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
index 7a379f24..950df7d6 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
@@ -105,7 +105,7 @@ operator fun MutableMeta<*>.set(name: Name, value: Any?) {
         null -> remove(name)
         is MetaItem<*> -> setItem(name, value)
         is Meta -> setNode(name, value)
-        is Specific -> setNode(name, value.config)
+        is Configurable -> setNode(name, value.config)
         else -> setValue(name, Value.of(value))
     }
 }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
new file mode 100644
index 00000000..2de46dc0
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
@@ -0,0 +1,78 @@
+package hep.dataforge.meta
+
+import hep.dataforge.descriptors.*
+import hep.dataforge.names.Name
+import hep.dataforge.names.NameToken
+import hep.dataforge.names.plus
+
+open class Scheme() : Configurable, Described {
+    constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() {
+        this.config = config
+        this.defaultProvider = defaultProvider
+    }
+
+    //constructor(config: Config, default: Meta) : this(config, { default[it] })
+    constructor(config: Config) : this(config, { null })
+
+    final override lateinit var config: Config
+        internal set
+
+    lateinit var defaultProvider: (Name) -> MetaItem<*>?
+        internal set
+
+    override val descriptor: NodeDescriptor? = null
+
+    override fun getDefaultItem(name: Name): MetaItem<*>? {
+        return defaultProvider(name) ?: descriptor?.get(name)?.defaultItem()
+    }
+
+    /**
+     * Provide a default layer which returns items from [defaultProvider] and falls back to descriptor
+     * values if default value is unavailable.
+     * Values from [defaultProvider] completely replace
+     */
+    open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY)
+
+    private inner class DefaultLayer(val path: Name) : MetaBase() {
+        override val items: Map<NameToken, MetaItem<*>> =
+            (descriptor?.get(path) as? NodeDescriptor)?.items?.entries?.associate { (key, descriptor) ->
+                val token = NameToken(key)
+                val fullName = path + token
+                val item: MetaItem<*> = when (descriptor) {
+                    is ValueDescriptor -> getDefaultItem(fullName) ?: descriptor.defaultItem()
+                    is NodeDescriptor -> MetaItem.NodeItem(DefaultLayer(fullName))
+                }
+                token to item
+            } ?: emptyMap()
+    }
+
+}
+
+/**
+ * A specification for simplified generation of wrappers
+ */
+open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
+    override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T {
+        return builder().apply {
+            this.config = config
+            this.defaultProvider = defaultProvider
+        }
+    }
+}
+
+open class MetaScheme(
+    val meta: Meta,
+    override val descriptor: NodeDescriptor? = null,
+    config: Config = Config()
+) : Scheme(config, meta::get) {
+    override val defaultLayer: Meta get() = meta
+}
+
+fun Meta.toScheme() = MetaScheme(this)
+
+fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)
+
+/**
+ * Create a snapshot laminate
+ */
+fun Scheme.toMeta(): Laminate = Laminate(config, defaultLayer)
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
deleted file mode 100644
index 865114e5..00000000
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-package hep.dataforge.meta
-
-import hep.dataforge.names.Name
-import hep.dataforge.names.asName
-import kotlin.jvm.JvmName
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
-
-/**
- * Marker interface for classes with specifications
- */
-interface Specific : Configurable
-
-//TODO separate mutable config from immutable meta to allow free wrapping of meta
-
-operator fun Specific.get(name: String): MetaItem<*>? = config[name]
-
-/**
- * Editor for specific objects
- */
-inline operator fun <S : Specific> S.invoke(block: S.() -> Unit): Unit {
-    run(block)
-}
-
-/**
- * Allows to apply custom configuration in a type safe way to simple untyped configuration.
- * By convention [Specific] companion should inherit this class
- *
- */
-interface Specification<T : Specific> {
-    /**
-     * Update given configuration using given type as a builder
-     */
-    fun update(config: Config, action: T.() -> Unit): T {
-        return wrap(config).apply(action)
-    }
-
-    operator fun invoke(action: T.() -> Unit) = update(Config(), action)
-
-    fun empty() = wrap(Config())
-
-    /**
-     * Wrap generic configuration producing instance of desired type
-     */
-    fun wrap(config: Config): T
-
-    //TODO replace by free wrapper
-    fun wrap(meta: Meta): T = wrap(meta.toConfig())
-}
-
-fun <T : Specific> specification(wrapper: (Config) -> T): Specification<T> =
-    object : Specification<T> {
-        override fun wrap(config: Config): T = wrapper(config)
-    }
-
-/**
- * Apply specified configuration to configurable
- */
-fun <T : Configurable, C : Specific, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) =
-    apply { spec.update(config, action) }
-
-/**
- * Update configuration using given specification
- */
-fun <C : Specific, S : Specification<C>> Specific.update(spec: S, action: C.() -> Unit) =
-    apply { spec.update(config, action) }
-
-/**
- * Create a style based on given specification
- */
-fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
-    Config().also { update(it, action) }
-
-class SpecDelegate<T : Specific, S : Specification<T>>(
-    val target: Specific,
-    val spec: S,
-    val key: Name? = null
-) : ReadWriteProperty<Any?, T> {
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
-        val name = key ?: property.name.asName()
-        return target.config[name]?.node?.let { spec.wrap(it) } ?: (spec.empty().also {
-            target.config[name] = it.config
-        })
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
-        target.config[key ?: property.name.asName()] = value.config
-    }
-}
-
-fun <T : Specific, S : Specification<T>> Specific.spec(
-    spec: S,
-    key: Name? = null
-): SpecDelegate<T, S> = SpecDelegate(this, spec, key)
-
-fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
-
-@JvmName("configSpec")
-fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
-
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt
new file mode 100644
index 00000000..9c8b740a
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt
@@ -0,0 +1,60 @@
+package hep.dataforge.meta
+
+import hep.dataforge.names.Name
+import kotlin.jvm.JvmName
+
+/**
+ * Allows to apply custom configuration in a type safe way to simple untyped configuration.
+ * By convention [Scheme] companion should inherit this class
+ *
+ */
+interface Specification<T : Configurable> {
+    /**
+     * Update given configuration using given type as a builder
+     */
+    fun update(config: Config, action: T.() -> Unit): T {
+        return wrap(config).apply(action)
+    }
+
+    operator fun invoke(action: T.() -> Unit) = update(Config(), action)
+
+    fun empty() = wrap()
+
+    /**
+     * Wrap generic configuration producing instance of desired type
+     */
+    fun wrap(config: Config = Config(), defaultProvider: (Name) -> MetaItem<*>? = { null }): T
+
+    /**
+     * Wrap a configuration using static meta as default
+     */
+    fun wrap(config: Config = Config(), default: Meta): T = wrap(config){default[it]}
+
+    /**
+     * Wrap a configuration using static meta as default
+     */
+    fun wrap(default: Meta): T = wrap(Config()){default[it]}
+}
+
+/**
+ * Apply specified configuration to configurable
+ */
+fun <T : Configurable, C : Configurable, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) =
+    apply { spec.update(config, action) }
+
+/**
+ * Update configuration using given specification
+ */
+fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action: C.() -> Unit) =
+    apply { spec.update(config, action) }
+
+/**
+ * Create a style based on given specification
+ */
+fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
+    Config().also { update(it, action) }
+
+fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(Config(), it) }
+
+@JvmName("configSpec")
+fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt
deleted file mode 100644
index 55d652aa..00000000
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package hep.dataforge.meta
-
-import hep.dataforge.names.Name
-import hep.dataforge.names.NameToken
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
-
-
-/**
- * A meta object with read-only meta base and changeable configuration on top of it
- * @param base - unchangeable base
- * @param style - the style
- */
-class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMutableMeta<Styled>() {
-    override fun wrapNode(meta: Meta): Styled  = Styled(meta)
-
-    override fun empty(): Styled  = Styled(EmptyMeta)
-
-    override val items: Map<NameToken, MetaItem<Styled>>
-        get() = (base.items.keys + style.items.keys).associate { key ->
-            val value = base.items[key]
-            val styleValue = style[key]
-            val item: MetaItem<Styled> = when (value) {
-                null -> when (styleValue) {
-                    null -> error("Should be unreachable")
-                    is MetaItem.NodeItem -> MetaItem.NodeItem(Styled(style.empty(), styleValue.node))
-                    is MetaItem.ValueItem -> styleValue
-                }
-                is MetaItem.ValueItem -> value
-                is MetaItem.NodeItem -> MetaItem.NodeItem(
-                    Styled(value.node, styleValue?.node ?: Config.empty())
-                )
-            }
-            key to item
-        }
-
-    override fun set(name: Name, item: MetaItem<*>?) {
-        if (item == null) {
-            style.remove(name)
-        } else {
-            style[name] = item
-        }
-    }
-
-    fun onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
-        //TODO test correct behavior
-        style.onChange(owner) { name, before, after -> action(name, before ?: base[name], after ?: base[name]) }
-    }
-
-    fun removeListener(owner: Any?) {
-        style.removeListener(owner)
-    }
-}
-
-fun Styled.configure(meta: Meta) = apply { style.update(meta) }
-
-fun Meta.withStyle(style: Meta = EmptyMeta) = if (this is Styled) {
-    this.apply { this.configure(style) }
-} else {
-    Styled(this, style.toConfig())
-}
-
-class StyledNodeDelegate(val owner: Styled, val key: String?) : ReadWriteProperty<Any?, Meta> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Meta {
-        return owner[key ?: property.name]?.node ?: EmptyMeta
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta) {
-        owner.style[key ?: property.name] = value
-    }
-
-}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt
deleted file mode 100644
index e3dbc9aa..00000000
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-package hep.dataforge.meta
-
-import hep.dataforge.names.Name
-import hep.dataforge.values.DoubleArrayValue
-import hep.dataforge.values.Null
-import hep.dataforge.values.Value
-import kotlin.jvm.JvmName
-
-
-//Configurable delegates
-
-/**
- * A property delegate that uses custom key
- */
-fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate<Config> =
-    MutableValueDelegate(config, key, Value.of(default))
-
-fun <T> Configurable.value(
-    default: T? = null,
-    key: Name? = null,
-    writer: (T) -> Value = { Value.of(it) },
-    reader: (Value?) -> T
-): ReadWriteDelegateWrapper<Value?, T> =
-    MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer)
-
-fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate<Config> =
-    MutableStringDelegate(config, key, default)
-
-fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate<Config> =
-    MutableBooleanDelegate(config, key, default)
-
-fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate<Config> =
-    MutableNumberDelegate(config, key, default)
-
-/* Number delegates*/
-
-fun Configurable.int(default: Int? = null, key: Name? = null) =
-    number(default, key).int
-
-fun Configurable.double(default: Double? = null, key: Name? = null) =
-    number(default, key).double
-
-fun Configurable.long(default: Long? = null, key: Name? = null) =
-    number(default, key).long
-
-fun Configurable.short(default: Short? = null, key: Name? = null) =
-    number(default, key).short
-
-fun Configurable.float(default: Float? = null, key: Name? = null) =
-    number(default, key).float
-
-
-@JvmName("safeString")
-fun Configurable.string(default: String, key: Name? = null) =
-    MutableSafeStringDelegate(config, key) { default }
-
-@JvmName("safeBoolean")
-fun Configurable.boolean(default: Boolean, key: Name? = null) =
-    MutableSafeBooleanDelegate(config, key) { default }
-
-@JvmName("safeNumber")
-fun Configurable.number(default: Number, key: Name? = null) =
-    MutableSafeNumberDelegate(config, key) { default }
-
-@JvmName("safeString")
-fun Configurable.string(key: Name? = null, default: () -> String) =
-    MutableSafeStringDelegate(config, key, default)
-
-@JvmName("safeBoolean")
-fun Configurable.boolean(key: Name? = null, default: () -> Boolean) =
-    MutableSafeBooleanDelegate(config, key, default)
-
-@JvmName("safeNumber")
-fun Configurable.number(key: Name? = null, default: () -> Number) =
-    MutableSafeNumberDelegate(config, key, default)
-
-
-/* Safe number delegates*/
-
-@JvmName("safeInt")
-fun Configurable.int(default: Int, key: Name? = null) =
-    number(default, key).int
-
-@JvmName("safeDouble")
-fun Configurable.double(default: Double, key: Name? = null) =
-    number(default, key).double
-
-@JvmName("safeLong")
-fun Configurable.long(default: Long, key: Name? = null) =
-    number(default, key).long
-
-@JvmName("safeShort")
-fun Configurable.short(default: Short, key: Name? = null) =
-    number(default, key).short
-
-@JvmName("safeFloat")
-fun Configurable.float(default: Float, key: Name? = null) =
-    number(default, key).float
-
-/**
- * Enum delegate
- */
-inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null) =
-    MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) }
-
-/* Node delegates */
-
-fun Configurable.child(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key)
-
-fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) =
-    MutableMorphDelegate(config, key) { spec.wrap(it) }
-
-fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: Name? = null) =
-    MutableMorphDelegate(config, key) { specification(builder).wrap(it) }
-
-/*
- * Extra delegates for special cases
- */
-
-fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<String>> =
-    value(strings.asList(), key) { it?.list?.map { value -> value.string } ?: emptyList() }
-
-fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<Number>> =
-    value(numbers.asList(), key) { it?.list?.map { value -> value.number } ?: emptyList() }
-
-/**
- * A special delegate for double arrays
- */
-fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value?, DoubleArray> =
-    value(doubleArrayOf(), key) {
-        (it as? DoubleArrayValue)?.value
-            ?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
-            ?: doubleArrayOf()
-    }
-
-fun <T : Configurable> Configurable.child(key: Name? = null, converter: (Meta) -> T) =
-    MutableMorphDelegate(config, key, converter)
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt
new file mode 100644
index 00000000..313ddd0b
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt
@@ -0,0 +1,236 @@
+package hep.dataforge.meta
+
+import hep.dataforge.names.Name
+import hep.dataforge.names.asName
+import hep.dataforge.values.*
+import kotlin.jvm.JvmName
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+
+//delegates
+
+/**
+ * A delegate that uses a [Configurable] object and delegate read and write operations to its properties
+ */
+open class ConfigurableDelegate(
+    val owner: Configurable,
+    val key: Name? = null,
+    open val default: MetaItem<*>? = null
+) : ReadWriteProperty<Any?, MetaItem<*>?> {
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
+        val name = key ?: property.name.asName()
+        return owner.getProperty(name) ?: default
+    }
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
+        val name = key ?: property.name.asName()
+        owner.setProperty(name, value)
+    }
+
+    fun <T> transform(
+        writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
+        reader: (MetaItem<*>?) -> T
+    ): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
+        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+            return reader(this@ConfigurableDelegate.getValue(thisRef, property))
+        }
+
+        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+            this@ConfigurableDelegate.setValue(thisRef, property, writer(value))
+        }
+    }
+}
+
+class LazyConfigurableDelegate(
+    configurable: Configurable,
+    key: Name? = null,
+    defaultProvider: () -> MetaItem<*>? = { null }
+) : ConfigurableDelegate(configurable, key) {
+    override val default by lazy(defaultProvider)
+}
+
+/**
+ * A property delegate that uses custom key
+ */
+fun Configurable.item(default: Any?, key: Name? = null): ConfigurableDelegate =
+    ConfigurableDelegate(this, key, MetaItem.of(default))
+
+/**
+ * Generation of item delegate with lazy default.
+ * Lazy default could be used also for validation
+ */
+fun Configurable.lazyItem(key: Name? = null, default: () -> Any?): ConfigurableDelegate =
+    LazyConfigurableDelegate(this, key) { default()?.let { MetaItem.of(it) } }
+
+fun <T> Configurable.item(
+    default: T? = null,
+    key: Name? = null,
+    writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
+    reader: (MetaItem<*>?) -> T
+): ReadWriteProperty<Any?, T> =
+    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(reader = reader, writer = writer)
+
+fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> =
+    item(default, key).transform { it.value }
+
+fun <T> Configurable.value(
+    default: T? = null,
+    key: Name? = null,
+    writer: (T) -> Value? = { Value.of(it) },
+    reader: (Value?) -> T
+): ReadWriteProperty<Any?, T> =
+    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(
+        reader = { reader(it.value) },
+        writer = { writer(it)?.let { MetaItem.ValueItem(it) } }
+    )
+
+fun Configurable.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> =
+    item(default, key).transform { it.value?.string }
+
+fun Configurable.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
+    item(default, key).transform { it.value?.boolean }
+
+fun Configurable.number(default: Number? = null, key: Name? = null): ReadWriteProperty<Any?, Number?> =
+    item(default, key).transform { it.value?.number }
+
+/* Number delegates*/
+
+fun Configurable.int(default: Int? = null, key: Name? = null): ReadWriteProperty<Any?, Int?> =
+    item(default, key).transform { it.value?.int }
+
+fun Configurable.double(default: Double? = null, key: Name? = null): ReadWriteProperty<Any?, Double?> =
+    item(default, key).transform { it.value?.double }
+
+fun Configurable.long(default: Long? = null, key: Name? = null): ReadWriteProperty<Any?, Long?> =
+    item(default, key).transform { it.value?.long }
+
+fun Configurable.short(default: Short? = null, key: Name? = null): ReadWriteProperty<Any?, Short?> =
+    item(default, key).transform { it.value?.short }
+
+fun Configurable.float(default: Float? = null, key: Name? = null): ReadWriteProperty<Any?, Float?> =
+    item(default, key).transform { it.value?.float }
+
+
+@JvmName("safeString")
+fun Configurable.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
+    item(default, key).transform { it.value!!.string }
+
+@JvmName("safeBoolean")
+fun Configurable.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
+    item(default, key).transform { it.value!!.boolean }
+
+@JvmName("safeNumber")
+fun Configurable.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
+    item(default, key).transform { it.value!!.number }
+
+/* Lazy initializers for values */
+
+@JvmName("lazyString")
+fun Configurable.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
+    lazyItem(key, default).transform { it.value!!.string }
+
+@JvmName("lazyBoolean")
+fun Configurable.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
+    lazyItem(key, default).transform { it.value!!.boolean }
+
+@JvmName("lazyNumber")
+fun Configurable.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
+    lazyItem(key, default).transform { it.value!!.number }
+
+/* Safe number delegates*/
+
+@JvmName("safeInt")
+fun Configurable.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
+    item(default, key).transform { it.value!!.int }
+
+@JvmName("safeDouble")
+fun Configurable.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
+    item(default, key).transform { it.value!!.double }
+
+@JvmName("safeLong")
+fun Configurable.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
+    item(default, key).transform { it.value!!.long }
+
+@JvmName("safeShort")
+fun Configurable.short(default: Short, key: Name? = null): ReadWriteProperty<Any?, Short> =
+    item(default, key).transform { it.value!!.short }
+
+@JvmName("safeFloat")
+fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
+    item(default, key).transform { it.value!!.float }
+
+/**
+ * Enum delegate
+ */
+inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E?> =
+    item(default, key).transform { it.enum<E>() }
+
+/*
+ * Extra delegates for special cases
+ */
+
+fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty<Any?, List<String>> =
+    item(listOf(*strings), key) {
+        it?.value?.stringList ?: emptyList()
+    }
+
+fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty<Any?, List<Number>> =
+    item(listOf(*numbers), key) { item ->
+        item?.value?.list?.map { it.number } ?: emptyList()
+    }
+
+/**
+ * A special delegate for double arrays
+ */
+fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWriteProperty<Any?, DoubleArray> =
+    item(doubleArrayOf(*doubles), key) {
+        (it.value as? DoubleArrayValue)?.value
+            ?: it?.value?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
+            ?: doubleArrayOf()
+    }
+
+
+/* Node delegates */
+
+fun Configurable.nullableConfig(key: Name? = null): ReadWriteProperty<Any?, Config?> =
+    object : ReadWriteProperty<Any?, Config?> {
+        override fun getValue(thisRef: Any?, property: KProperty<*>): Config? {
+            val name = key ?: property.name.asName()
+            return config[name].node
+        }
+
+        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config?) {
+            val name = key ?: property.name.asName()
+            config[name] = value
+        }
+    }
+
+fun Configurable.config(key: Name? = null, default: Config.() -> Unit = {}): ReadWriteProperty<Any?, Config> =
+    object : ReadWriteProperty<Any?, Config> {
+        override fun getValue(thisRef: Any?, property: KProperty<*>): Config {
+            val name = key ?: property.name.asName()
+            return config[name].node ?: Config().apply(default).also { config[name] = it }
+        }
+
+        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config) {
+            val name = key ?: property.name.asName()
+            config[name] = value
+        }
+    }
+
+fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> =
+    object : ReadWriteProperty<Any?, T?> {
+        override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
+            val name = key ?: property.name.asName()
+            return config[name].node?.let { spec.wrap(it) }
+        }
+
+        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
+            val name = key ?: property.name.asName()
+            config[name] = value?.config
+        }
+
+    }
+
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
index 02caa9fa..7c8d2026 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
@@ -350,24 +350,6 @@ class MutableNodeDelegate<M : MutableMeta<M>>(
     }
 }
 
-class MutableMorphDelegate<T : Configurable>(
-    val meta: MutableMeta<*>,
-    private val key: Name? = null,
-    private val converter: (Meta) -> T
-) : ReadWriteProperty<Any?, T?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
-        return meta[key ?: property.name.asName()]?.node?.let(converter)
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
-        if (value == null) {
-            meta.remove(key ?: property.name.asName())
-        } else {
-            meta[key ?: property.name.asName()] = value.config
-        }
-    }
-}
-
 class ReadWriteDelegateWrapper<T, R>(
     val delegate: ReadWriteProperty<Any?, T>,
     val reader: (T) -> R,
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt
index a63d5ec1..f9137dca 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt
@@ -22,6 +22,7 @@ val Value.boolean
 val Value.int get() = number.toInt()
 val Value.double get() = number.toDouble()
 val Value.float get() = number.toFloat()
+val Value.short get() = number.toShort()
 val Value.long get() = number.toLong()
 
 val Value.stringList: List<String> get() = list.map { it.string }
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
index 162a3852..93aad8e5 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
@@ -13,14 +13,13 @@ class MetaDelegateTest {
     @Test
     fun delegateTest() {
 
-        class InnerSpec(override val config: Config) : Specific {
+        class InnerSpec : Scheme() {
             var innerValue by string()
         }
 
-        val innerSpec = specification(::InnerSpec)
+        val innerSpec = object : SchemeSpec<InnerSpec>(::InnerSpec){}
 
-        val testObject = object : Specific {
-            override val config: Config = Config()
+        val testObject = object : Scheme(Config()) {
             var myValue by string()
             var safeValue by double(2.2)
             var enumValue by enum(TestEnum.YES)
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt
index 9057782f..194c77e3 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt
@@ -14,7 +14,7 @@ class MutableMetaTest{
                 "b" put 22
                 "c" put "StringValue"
             }
-        }.toConfig()
+        }.asConfig()
 
         meta.remove("aNode.c")
         assertEquals(meta["aNode.c"], null)
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
similarity index 71%
rename from dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt
rename to dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
index a4cbe18e..c7703a47 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
@@ -4,19 +4,22 @@ import kotlin.test.Test
 import kotlin.test.assertEquals
 
 
-class StyledTest{
+class SchemeTest{
     @Test
-    fun testSNS(){
-        val meta = buildMeta {
+    fun testMetaScheme(){
+        val styled = buildMeta {
             repeat(10){
                 "b.a[$it]" put {
                     "d" put it
                 }
             }
-        }.seal().withStyle()
+        }.toScheme()
+
+        val meta = styled.toMeta()
+
         assertEquals(10, meta.values().count())
 
-        val bNode = meta["b"].node
+        val bNode = styled.getProperty("b").node
 
         val aNodes = bNode?.getIndexed("a")
 
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
index c11c137b..f21c5b2c 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
@@ -1,21 +1,26 @@
 package hep.dataforge.meta
 
+import hep.dataforge.names.Name
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
 class SpecificationTest {
-    class TestSpecific(override val config: Config) : Specific {
+    class TestStyled(config: Config, defaultProvider: (Name) -> MetaItem<*>?) :
+        Scheme(config, defaultProvider) {
         var list by numberList(1, 2, 3)
 
-        companion object : Specification<TestSpecific> {
-            override fun wrap(config: Config): TestSpecific = TestSpecific(config)
+        companion object : Specification<TestStyled> {
+            override fun wrap(
+                config: Config,
+                defaultProvider: (Name) -> MetaItem<*>?
+            ): TestStyled = TestStyled(config, defaultProvider)
         }
     }
 
 
     @Test
-    fun testSpecific(){
-        val testObject = TestSpecific {
+    fun testSpecific() {
+        val testObject = TestStyled {
             list = emptyList()
         }
         assertEquals(emptyList(), testObject.list)

From fe6760eee62a0d7733f54a60d84cac3a707ccf5d Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Fri, 10 Jan 2020 11:14:18 +0300
Subject: [PATCH 25/41] Refactor Meta delegates

---
 .../dataforge/descriptors/ItemDescriptor.kt   |   2 +-
 .../kotlin/hep/dataforge/meta/Configurable.kt |  11 +-
 ...leDelegates.kt => ConfigurableDelegate.kt} |  45 +-
 .../kotlin/hep/dataforge/meta/Laminate.kt     |   1 +
 .../kotlin/hep/dataforge/meta/MetaDelegate.kt |  98 +++++
 .../hep/dataforge/meta/MutableMetaDelegate.kt | 108 +++++
 .../kotlin/hep/dataforge/meta/Scheme.kt       |  20 +-
 .../kotlin/hep/dataforge/meta/mapMeta.kt      |   8 -
 .../hep/dataforge/meta/metaDelegates.kt       | 414 ------------------
 .../kotlin/hep/dataforge/meta/metaMatcher.kt  |  19 +-
 .../kotlin/hep/dataforge/meta/SchemeTest.kt   |   2 +-
 11 files changed, 241 insertions(+), 487 deletions(-)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{configurableDelegates.kt => ConfigurableDelegate.kt} (80%)
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt
 delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt

diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
index 99b3e850..514349fe 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
@@ -61,7 +61,7 @@ class NodeDescriptor : ItemDescriptor() {
      *
      * @return
      */
-    var default: Config? by nullableConfig()
+    var default: Config? by config()
 
     /**
      * The map of children node descriptors
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
index c13a6214..e95b5564 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
@@ -1,5 +1,9 @@
 package hep.dataforge.meta
 
+import hep.dataforge.descriptors.Described
+import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.descriptors.defaultItem
+import hep.dataforge.descriptors.get
 import hep.dataforge.names.Name
 import hep.dataforge.names.toName
 
@@ -9,7 +13,7 @@ import hep.dataforge.names.toName
  * It is not possible to know if some property is declared by provider just by looking on [Configurable],
  * this information should be provided externally.
  */
-interface Configurable {
+interface Configurable : Described {
     /**
      * Backing config
      */
@@ -19,12 +23,15 @@ interface Configurable {
      * Default meta item provider
      */
     fun getDefaultItem(name: Name): MetaItem<*>? = null
+
+    override val descriptor: NodeDescriptor? get() = null
 }
 
 /**
  * Get a property with default
  */
-fun Configurable.getProperty(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name)
+fun Configurable.getProperty(name: Name): MetaItem<*>? =
+    config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem()
 
 fun Configurable.getProperty(key: String) = getProperty(key.toName())
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt
similarity index 80%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt
index 313ddd0b..31cc2c9c 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt
@@ -28,19 +28,6 @@ open class ConfigurableDelegate(
         val name = key ?: property.name.asName()
         owner.setProperty(name, value)
     }
-
-    fun <T> transform(
-        writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
-        reader: (MetaItem<*>?) -> T
-    ): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
-        override fun getValue(thisRef: Any?, property: KProperty<*>): T {
-            return reader(this@ConfigurableDelegate.getValue(thisRef, property))
-        }
-
-        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
-            this@ConfigurableDelegate.setValue(thisRef, property, writer(value))
-        }
-    }
 }
 
 class LazyConfigurableDelegate(
@@ -55,7 +42,7 @@ class LazyConfigurableDelegate(
  * A property delegate that uses custom key
  */
 fun Configurable.item(default: Any?, key: Name? = null): ConfigurableDelegate =
-    ConfigurableDelegate(this, key, MetaItem.of(default))
+    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) })
 
 /**
  * Generation of item delegate with lazy default.
@@ -70,7 +57,7 @@ fun <T> Configurable.item(
     writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
     reader: (MetaItem<*>?) -> T
 ): ReadWriteProperty<Any?, T> =
-    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(reader = reader, writer = writer)
+    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer)
 
 fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> =
     item(default, key).transform { it.value }
@@ -81,7 +68,7 @@ fun <T> Configurable.value(
     writer: (T) -> Value? = { Value.of(it) },
     reader: (Value?) -> T
 ): ReadWriteProperty<Any?, T> =
-    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(
+    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map(
         reader = { reader(it.value) },
         writer = { writer(it)?.let { MetaItem.ValueItem(it) } }
     )
@@ -194,31 +181,9 @@ fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWri
 
 /* Node delegates */
 
-fun Configurable.nullableConfig(key: Name? = null): ReadWriteProperty<Any?, Config?> =
-    object : ReadWriteProperty<Any?, Config?> {
-        override fun getValue(thisRef: Any?, property: KProperty<*>): Config? {
-            val name = key ?: property.name.asName()
-            return config[name].node
-        }
+fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> =
+    config.node(key)
 
-        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config?) {
-            val name = key ?: property.name.asName()
-            config[name] = value
-        }
-    }
-
-fun Configurable.config(key: Name? = null, default: Config.() -> Unit = {}): ReadWriteProperty<Any?, Config> =
-    object : ReadWriteProperty<Any?, Config> {
-        override fun getValue(thisRef: Any?, property: KProperty<*>): Config {
-            val name = key ?: property.name.asName()
-            return config[name].node ?: Config().apply(default).also { config[name] = it }
-        }
-
-        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config) {
-            val name = key ?: property.name.asName()
-            config[name] = value
-        }
-    }
 
 fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> =
     object : ReadWriteProperty<Any?, T?> {
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
index c6d69967..beda1732 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
@@ -5,6 +5,7 @@ import hep.dataforge.names.NameToken
 
 /**
  * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
+ * If [layers] list contains a [Laminate] it is flat-mapped.
  */
 class Laminate(layers: List<Meta>) : MetaBase() {
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt
new file mode 100644
index 00000000..50764a7c
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt
@@ -0,0 +1,98 @@
+package hep.dataforge.meta
+
+import hep.dataforge.names.Name
+import hep.dataforge.names.asName
+import hep.dataforge.values.Value
+import kotlin.jvm.JvmName
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/* Meta delegates */
+
+open class MetaDelegate(
+    open val owner: Meta,
+    val key: Name? = null,
+    open val default: MetaItem<*>? = null
+) : ReadOnlyProperty<Any?, MetaItem<*>?> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
+        return owner[key ?: property.name.asName()] ?: default
+    }
+}
+
+class LazyMetaDelegate(
+    owner: Meta,
+    key: Name? = null,
+    defaultProvider: () -> MetaItem<*>? = { null }
+) : MetaDelegate(owner, key) {
+    override val default by lazy(defaultProvider)
+}
+
+class DelegateWrapper<T, R>(
+    val delegate: ReadOnlyProperty<Any?, T>,
+    val reader: (T) -> R
+) : ReadOnlyProperty<Any?, R> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): R {
+        return reader(delegate.getValue(thisRef, property))
+    }
+}
+
+fun <T, R> ReadOnlyProperty<Any?, T>.map(reader: (T) -> R): DelegateWrapper<T, R> =
+    DelegateWrapper(this, reader)
+
+
+fun Meta.item(default: Any? = null, key: Name? = null): MetaDelegate =
+    MetaDelegate(this, key, default?.let { MetaItem.of(it) })
+
+fun Meta.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMetaDelegate =
+    LazyMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } }
+
+//TODO add caching for sealed nodes
+
+
+//Read-only delegates for Metas
+
+/**
+ * A property delegate that uses custom key
+ */
+fun Meta.value(default: Value? = null, key: Name? = null) =
+    item(default, key).map { it.value }
+
+fun Meta.string(default: String? = null, key: Name? = null) =
+    item(default, key).map { it.string }
+
+fun Meta.boolean(default: Boolean? = null, key: Name? = null) =
+    item(default, key).map { it.boolean }
+
+fun Meta.number(default: Number? = null, key: Name? = null) =
+    item(default, key).map { it.number }
+
+fun Meta.node(key: Name? = null) =
+    item(key).map { it.node }
+
+@JvmName("safeString")
+fun Meta.string(default: String, key: Name? = null) =
+    item(default, key).map { it.string!! }
+
+@JvmName("safeBoolean")
+fun Meta.boolean(default: Boolean, key: Name? = null) =
+    item(default, key).map { it.boolean!! }
+
+@JvmName("safeNumber")
+fun Meta.number(default: Number, key: Name? = null) =
+    item(default, key).map { it.number!! }
+
+@JvmName("lazyString")
+fun Meta.string(key: Name? = null, default: () -> String) =
+    lazyItem(key, default).map { it.string!! }
+
+@JvmName("lazyBoolean")
+fun Meta.boolean(key: Name? = null, default: () -> Boolean) =
+    lazyItem(key, default).map { it.boolean!! }
+
+@JvmName("lazyNumber")
+fun Meta.number(key: Name? = null, default: () -> Number) =
+    lazyItem(key, default).map { it.number!! }
+
+
+inline fun <reified E : Enum<E>> Meta.enum(default: E, key: Name? = null) =
+    item(default, key).map { it.enum<E>()!! }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt
new file mode 100644
index 00000000..5ab6ba0c
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt
@@ -0,0 +1,108 @@
+package hep.dataforge.meta
+
+import hep.dataforge.names.Name
+import hep.dataforge.names.asName
+import hep.dataforge.values.Value
+import kotlin.jvm.JvmName
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+/* Read-write delegates */
+
+open class MutableMetaDelegate<M : MutableMeta<M>>(
+    override val owner: M,
+    key: Name? = null,
+    default: MetaItem<*>? = null
+) : MetaDelegate(owner, key, default), ReadWriteProperty<Any?, MetaItem<*>?> {
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
+        val name = key ?: property.name.asName()
+        owner.setItem(name, value)
+    }
+}
+
+class LazyMutableMetaDelegate<M : MutableMeta<M>>(
+    owner: M,
+    key: Name? = null,
+    defaultProvider: () -> MetaItem<*>? = { null }
+) : MutableMetaDelegate<M>(owner, key) {
+    override val default by lazy(defaultProvider)
+}
+
+class ReadWriteDelegateWrapper<T, R>(
+    val delegate: ReadWriteProperty<Any?, T>,
+    val reader: (T) -> R,
+    val writer: (R) -> T
+) : ReadWriteProperty<Any?, R> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): R {
+        return reader(delegate.getValue(thisRef, property))
+    }
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
+        delegate.setValue(thisRef, property, writer(value))
+    }
+}
+
+fun <T, R> ReadWriteProperty<Any?, T>.map(reader: (T) -> R, writer: (R) -> T): ReadWriteDelegateWrapper<T, R> =
+    ReadWriteDelegateWrapper(this, reader, writer)
+
+fun <R> ReadWriteProperty<Any?, MetaItem<*>?>.transform(reader: (MetaItem<*>?) -> R): ReadWriteProperty<Any?, R> =
+    map(reader = reader, writer = { MetaItem.of(it) })
+
+fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R) =
+    map(reader = reader, writer = { Value.of(it) })
+
+
+fun <M : MutableMeta<M>> M.item(default: Any? = null, key: Name? = null): MutableMetaDelegate<M> =
+    MutableMetaDelegate(this, key, default?.let { MetaItem.of(it) })
+
+fun <M : MutableMeta<M>> M.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMutableMetaDelegate<M> =
+    LazyMutableMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } }
+
+//Read-write delegates
+
+/**
+ * A property delegate that uses custom key
+ */
+fun <M : MutableMeta<M>> M.value(default: Value? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> =
+    item(default, key).transform { it.value }
+
+fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> =
+    item(default, key).transform { it.string }
+
+fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
+    item(default, key).transform { it.boolean }
+
+fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null): ReadWriteProperty<Any?, Number?> =
+    item(default, key).transform { it.number }
+
+inline fun <reified M : MutableMeta<M>> M.node(key: Name? = null) =
+    item(this, key).transform { it.node as? M }
+
+@JvmName("safeString")
+fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) =
+    item(default, key).transform { it.string!! }
+
+@JvmName("safeBoolean")
+fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) =
+    item(default, key).transform { it.boolean!! }
+
+@JvmName("safeNumber")
+fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) =
+    item(default, key).transform { it.number!! }
+
+@JvmName("lazyString")
+fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) =
+    lazyItem(key, default).transform { it.string!! }
+
+@JvmName("safeBoolean")
+fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) =
+    lazyItem(key, default).transform { it.boolean!! }
+
+@JvmName("safeNumber")
+fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) =
+    lazyItem(key, default).transform { it.number!! }
+
+
+inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) =
+    item(default, key).transform { it.enum<E>()!! }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
index 2de46dc0..84b350f1 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
@@ -5,6 +5,9 @@ import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
 import hep.dataforge.names.plus
 
+/**
+ * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
+ */
 open class Scheme() : Configurable, Described {
     constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() {
         this.config = config
@@ -14,13 +17,14 @@ open class Scheme() : Configurable, Described {
     //constructor(config: Config, default: Meta) : this(config, { default[it] })
     constructor(config: Config) : this(config, { null })
 
-    final override lateinit var config: Config
+    final override var config: Config = Config()
         internal set
 
     lateinit var defaultProvider: (Name) -> MetaItem<*>?
         internal set
 
-    override val descriptor: NodeDescriptor? = null
+    final override var descriptor: NodeDescriptor? = null
+        internal set
 
     override fun getDefaultItem(name: Name): MetaItem<*>? {
         return defaultProvider(name) ?: descriptor?.get(name)?.defaultItem()
@@ -60,15 +64,21 @@ open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
     }
 }
 
+/**
+ * A scheme that uses [Meta] as a default layer
+ */
 open class MetaScheme(
     val meta: Meta,
-    override val descriptor: NodeDescriptor? = null,
+    descriptor: NodeDescriptor? = null,
     config: Config = Config()
 ) : Scheme(config, meta::get) {
-    override val defaultLayer: Meta get() = meta
+    init {
+        this.descriptor = descriptor
+    }
+    override val defaultLayer: Meta get() = Laminate(meta, descriptor?.defaultItem().node)
 }
 
-fun Meta.toScheme() = MetaScheme(this)
+fun Meta.asScheme() = MetaScheme(this)
 
 fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
index b0d8162c..ab68212c 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
@@ -3,14 +3,6 @@ package hep.dataforge.meta
 import hep.dataforge.descriptors.NodeDescriptor
 import hep.dataforge.values.Value
 
-///**
-// * Find all elements with given body
-// */
-//private fun Meta.byBody(body: String): Map<String, MetaItem<*>> =
-//    items.filter { it.key.body == body }.mapKeys { it.key.index }
-//
-//private fun Meta.distinctNames() = items.keys.map { it.body }.distinct()
-
 /**
  * Convert meta to map of maps
  */
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
deleted file mode 100644
index 7c8d2026..00000000
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt
+++ /dev/null
@@ -1,414 +0,0 @@
-package hep.dataforge.meta
-
-import hep.dataforge.names.Name
-import hep.dataforge.names.asName
-import hep.dataforge.values.Null
-import hep.dataforge.values.Value
-import hep.dataforge.values.asValue
-import kotlin.jvm.JvmName
-import kotlin.properties.ReadOnlyProperty
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
-
-/* Meta delegates */
-
-//TODO add caching for sealed nodes
-
-class ValueDelegate(val meta: Meta, private val key: String? = null, private val default: Value? = null) :
-    ReadOnlyProperty<Any?, Value?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Value? {
-        return meta[key ?: property.name]?.value ?: default
-    }
-}
-
-class StringDelegate(val meta: Meta, private val key: String? = null, private val default: String? = null) :
-    ReadOnlyProperty<Any?, String?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
-        return meta[key ?: property.name]?.string ?: default
-    }
-}
-
-class BooleanDelegate(
-    val meta: Meta,
-    private val key: String? = null,
-    private val default: Boolean? = null
-) : ReadOnlyProperty<Any?, Boolean?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? {
-        return meta[key ?: property.name]?.boolean ?: default
-    }
-}
-
-class NumberDelegate(
-    val meta: Meta,
-    private val key: String? = null,
-    private val default: Number? = null
-) : ReadOnlyProperty<Any?, Number?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Number? {
-        return meta[key ?: property.name]?.number ?: default
-    }
-
-    //delegates for number transformation
-
-    val double get() = DelegateWrapper(this) { it?.toDouble() }
-    val int get() = DelegateWrapper(this) { it?.toInt() }
-    val short get() = DelegateWrapper(this) { it?.toShort() }
-    val long get() = DelegateWrapper(this) { it?.toLong() }
-}
-
-class DelegateWrapper<T, R>(val delegate: ReadOnlyProperty<Any?, T>, val reader: (T) -> R) :
-    ReadOnlyProperty<Any?, R> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): R {
-        return reader(delegate.getValue(thisRef, property))
-    }
-}
-
-//Delegates with non-null values
-
-class SafeStringDelegate(
-    val meta: Meta,
-    private val key: String? = null,
-    default: () -> String
-) : ReadOnlyProperty<Any?, String> {
-
-    private val default: String by lazy(default)
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
-        return meta[key ?: property.name]?.string ?: default
-    }
-}
-
-class SafeBooleanDelegate(
-    val meta: Meta,
-    private val key: String? = null,
-    default: () -> Boolean
-) : ReadOnlyProperty<Any?, Boolean> {
-
-    private val default: Boolean by lazy(default)
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
-        return meta[key ?: property.name]?.boolean ?: default
-    }
-}
-
-class SafeNumberDelegate(
-    val meta: Meta,
-    private val key: String? = null,
-    default: () -> Number
-) : ReadOnlyProperty<Any?, Number> {
-
-    private val default: Number by lazy(default)
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
-        return meta[key ?: property.name]?.number ?: default
-    }
-
-    val double get() = DelegateWrapper(this) { it.toDouble() }
-    val int get() = DelegateWrapper(this) { it.toInt() }
-    val short get() = DelegateWrapper(this) { it.toShort() }
-    val long get() = DelegateWrapper(this) { it.toLong() }
-}
-
-class SafeEnumDelegate<E : Enum<E>>(
-    val meta: Meta,
-    private val key: String? = null,
-    private val default: E,
-    private val resolver: (String) -> E
-) : ReadOnlyProperty<Any?, E> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): E {
-        return (meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default
-    }
-}
-
-//Child node delegate
-
-class ChildDelegate<T>(
-    val meta: Meta,
-    private val key: String? = null,
-    private val converter: (Meta) -> T
-) : ReadOnlyProperty<Any?, T?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
-        return meta[key ?: property.name]?.node?.let { converter(it) }
-    }
-}
-
-//Read-only delegates for Metas
-
-/**
- * A property delegate that uses custom key
- */
-fun Meta.value(default: Value = Null, key: String? = null) = ValueDelegate(this, key, default)
-
-fun Meta.string(default: String? = null, key: String? = null) = StringDelegate(this, key, default)
-
-fun Meta.boolean(default: Boolean? = null, key: String? = null) = BooleanDelegate(this, key, default)
-
-fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(this, key, default)
-
-fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it }
-
-@JvmName("safeString")
-fun Meta.string(default: String, key: String? = null) =
-    SafeStringDelegate(this, key) { default }
-
-@JvmName("safeBoolean")
-fun Meta.boolean(default: Boolean, key: String? = null) =
-    SafeBooleanDelegate(this, key) { default }
-
-@JvmName("safeNumber")
-fun Meta.number(default: Number, key: String? = null) =
-    SafeNumberDelegate(this, key) { default }
-
-@JvmName("safeString")
-fun Meta.string(key: String? = null, default: () -> String) =
-    SafeStringDelegate(this, key, default)
-
-@JvmName("safeBoolean")
-fun Meta.boolean(key: String? = null, default: () -> Boolean) =
-    SafeBooleanDelegate(this, key, default)
-
-@JvmName("safeNumber")
-fun Meta.number(key: String? = null, default: () -> Number) =
-    SafeNumberDelegate(this, key, default)
-
-
-inline fun <reified E : Enum<E>> Meta.enum(default: E, key: String? = null) =
-    SafeEnumDelegate(this, key, default) { enumValueOf(it) }
-
-/* Read-write delegates */
-
-class MutableValueDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    private val default: Value? = null
-) : ReadWriteProperty<Any?, Value?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Value? {
-        return meta[key ?: property.name.asName()]?.value ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
-        val name = key ?: property.name.asName()
-        if (value == null) {
-            meta.remove(name)
-        } else {
-            meta.setValue(name, value)
-        }
-    }
-
-    fun <T> transform(writer: (T) -> Value? = { Value.of(it) }, reader: (Value?) -> T) =
-        ReadWriteDelegateWrapper(this, reader, writer)
-}
-
-class MutableStringDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    private val default: String? = null
-) : ReadWriteProperty<Any?, String?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
-        return meta[key ?: property.name.asName()]?.string ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
-        val name = key ?: property.name.asName()
-        if (value == null) {
-            meta.remove(name)
-        } else {
-            meta.setValue(name, value.asValue())
-        }
-    }
-}
-
-class MutableBooleanDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    private val default: Boolean? = null
-) : ReadWriteProperty<Any?, Boolean?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? {
-        return meta[key ?: property.name.asName()]?.boolean ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) {
-        val name = key ?: property.name.asName()
-        if (value == null) {
-            meta.remove(name)
-        } else {
-            meta.setValue(name, value.asValue())
-        }
-    }
-}
-
-class MutableNumberDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    private val default: Number? = null
-) : ReadWriteProperty<Any?, Number?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Number? {
-        return meta[key ?: property.name.asName()]?.number ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) {
-        val name = key ?: property.name.asName()
-        if (value == null) {
-            meta.remove(name)
-        } else {
-            meta.setValue(name, value.asValue())
-        }
-    }
-
-    val double get() = ReadWriteDelegateWrapper(this, reader = { it?.toDouble() }, writer = { it })
-    val float get() = ReadWriteDelegateWrapper(this, reader = { it?.toFloat() }, writer = { it })
-    val int get() = ReadWriteDelegateWrapper(this, reader = { it?.toInt() }, writer = { it })
-    val short get() = ReadWriteDelegateWrapper(this, reader = { it?.toShort() }, writer = { it })
-    val long get() = ReadWriteDelegateWrapper(this, reader = { it?.toLong() }, writer = { it })
-}
-
-//Delegates with non-null values
-
-class MutableSafeStringDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    default: () -> String
-) : ReadWriteProperty<Any?, String> {
-
-    private val default: String by lazy(default)
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
-        return meta[key ?: property.name.asName()]?.string ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
-        meta.setValue(key ?: property.name.asName(), value.asValue())
-    }
-}
-
-class MutableSafeBooleanDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    default: () -> Boolean
-) : ReadWriteProperty<Any?, Boolean> {
-
-    private val default: Boolean by lazy(default)
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
-        return meta[key ?: property.name.asName()]?.boolean ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
-        meta.setValue(key ?: property.name.asName(), value.asValue())
-    }
-}
-
-class MutableSafeNumberDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null,
-    default: () -> Number
-) : ReadWriteProperty<Any?, Number> {
-
-    private val default: Number by lazy(default)
-
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
-        return meta[key ?: property.name.asName()]?.number ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) {
-        meta.setValue(key ?: property.name.asName(), value.asValue())
-    }
-
-    val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it })
-    val float get() = ReadWriteDelegateWrapper(this, reader = { it.toFloat() }, writer = { it })
-    val int get() = ReadWriteDelegateWrapper(this, reader = { it.toInt() }, writer = { it })
-    val short get() = ReadWriteDelegateWrapper(this, reader = { it.toShort() }, writer = { it })
-    val long get() = ReadWriteDelegateWrapper(this, reader = { it.toLong() }, writer = { it })
-}
-
-class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>(
-    val meta: M,
-    private val key: Name? = null,
-    private val default: E,
-    private val resolver: (String) -> E
-) : ReadWriteProperty<Any?, E> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): E {
-        return (meta[key ?: property.name.asName()]?.string)?.let { resolver(it) } ?: default
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) {
-        meta.setValue(key ?: property.name.asName(), value.name.asValue())
-    }
-}
-
-//Child node delegate
-
-class MutableNodeDelegate<M : MutableMeta<M>>(
-    val meta: M,
-    private val key: Name? = null
-) : ReadWriteProperty<Any?, M?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): M? {
-        return meta[key ?: property.name.asName()]?.node
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: M?) {
-        meta[key ?: property.name.asName()] = value
-    }
-}
-
-class ReadWriteDelegateWrapper<T, R>(
-    val delegate: ReadWriteProperty<Any?, T>,
-    val reader: (T) -> R,
-    val writer: (R) -> T
-) : ReadWriteProperty<Any?, R> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): R {
-        return reader(delegate.getValue(thisRef, property))
-    }
-
-    override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
-        delegate.setValue(thisRef, property, writer(value))
-    }
-}
-
-
-//Read-write delegates
-
-/**
- * A property delegate that uses custom key
- */
-fun <M : MutableMeta<M>> M.value(default: Value = Null, key: Name? = null) =
-    MutableValueDelegate(this, key, default)
-
-fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null) =
-    MutableStringDelegate(this, key, default)
-
-fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null) =
-    MutableBooleanDelegate(this, key, default)
-
-fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null) =
-    MutableNumberDelegate(this, key, default)
-
-fun <M : MutableMeta<M>> M.child(key: Name? = null) =
-    MutableNodeDelegate(this, key)
-
-@JvmName("safeString")
-fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) =
-    MutableSafeStringDelegate(this, key) { default }
-
-@JvmName("safeBoolean")
-fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) =
-    MutableSafeBooleanDelegate(this, key) { default }
-
-@JvmName("safeNumber")
-fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) =
-    MutableSafeNumberDelegate(this, key) { default }
-
-@JvmName("safeString")
-fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) =
-    MutableSafeStringDelegate(this, key, default)
-
-@JvmName("safeBoolean")
-fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) =
-    MutableSafeBooleanDelegate(this, key, default)
-
-@JvmName("safeNumber")
-fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) =
-    MutableSafeNumberDelegate(this, key, default)
-
-
-inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) =
-    MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt
index 6f16f537..263483a2 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt
@@ -26,26 +26,13 @@ fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
 @DFExperimental
 fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
 
-
 /**
  * Get all items matching given name.
  */
+@Suppress("UNCHECKED_CAST")
 @DFExperimental
-fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> {
-    val root: MetaNode<M>? = when (name.length) {
-        0 -> error("Can't use empty name for that")
-        1 -> this
-        else -> (this[name.cutLast()] as? MetaItem.NodeItem<M>)?.node
-    }
-
-    val (body, index) = name.last()!!
-    val regex = index.toRegex()
-
-    return root?.items
-        ?.filter { it.key.body == body && (index.isEmpty() || regex.matches(it.key.index)) }
-        ?.mapKeys { it.key.index }
-        ?: emptyMap()
-}
+fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> =
+    (this as Meta).getIndexed(name) as Map<String, MetaItem<M>>
 
 @DFExperimental
 fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName())
\ No newline at end of file
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
index c7703a47..0246fb10 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
@@ -13,7 +13,7 @@ class SchemeTest{
                     "d" put it
                 }
             }
-        }.toScheme()
+        }.asScheme()
 
         val meta = styled.toMeta()
 

From f906fbdb0a07e9ccbb09ad4fc8bdc6af09493615 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 29 Jan 2020 21:34:51 +0300
Subject: [PATCH 26/41] Tables basics A lot of refactoring

---
 build.gradle.kts                              |   2 +-
 .../hep/dataforge/descriptors/annotations.kt  | 130 +++++++++++++++
 .../descriptors/reflectiveDescriptors.kt      |  65 ++++++++
 .../kotlin/hep/dataforge/data/DataFilter.kt   |   3 +
 .../hep/dataforge/io/yaml/YamlMetaFormat.kt   |   3 +-
 .../hep/dataforge/io/BinaryMetaFormat.kt      |   2 +-
 .../kotlin/hep/dataforge/io/JsonMetaFormat.kt |   6 +-
 .../kotlin/hep/dataforge/io/MetaFormat.kt     |   2 +-
 .../io/functions/RemoteFunctionClient.kt      |   1 +
 .../io/functions/RemoteFunctionServer.kt      |   1 +
 .../io/serialization/MetaSerializer.kt        |   2 +-
 .../kotlin/hep/dataforge/io/MultipartTest.kt  |   1 +
 .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt |   2 +-
 .../hep/dataforge/descriptors/annotations.kt  | 149 ------------------
 .../kotlin/hep/dataforge/meta/Config.kt       |  11 +-
 .../kotlin/hep/dataforge/meta/MutableMeta.kt  |   5 +-
 .../{ => meta}/descriptors/Described.kt       |   2 +-
 .../{ => meta}/descriptors/DescriptorMeta.kt  |   2 +-
 .../{ => meta}/descriptors/ItemDescriptor.kt  |  55 +++++--
 .../kotlin/hep/dataforge/meta/mapMeta.kt      |   2 +-
 .../meta/{ => scheme}/Configurable.kt         |  22 ++-
 .../meta/{ => scheme}/ConfigurableDelegate.kt |  37 ++++-
 .../hep/dataforge/meta/{ => scheme}/Scheme.kt |  25 ++-
 .../meta/{ => scheme}/Specification.kt        |  10 +-
 .../MetaTransformation.kt                     |  39 +++--
 .../hep/dataforge/meta/MetaDelegateTest.kt    |   1 +
 .../kotlin/hep/dataforge/meta/SchemeTest.kt   |   3 +
 .../hep/dataforge/meta/SpecificationTest.kt   |   3 +
 .../{ => meta}/descriptors/DescriptorTest.kt  |  10 +-
 .../dataforge-output-html}/build.gradle.kts   |   0
 .../hep/dataforge/output/html/HtmlRenderer.kt |   0
 .../hep/dataforge/scripting/BuildersKtTest.kt |   1 +
 dataforge-tables/build.gradle                 |  25 ---
 dataforge-tables/build.gradle.kts             |  14 ++
 .../hep/dataforge/tables/ColumnScheme.kt      |   8 +
 .../hep/dataforge/tables/ColumnTable.kt       |  35 ++++
 .../kotlin/hep/dataforge/tables/ListColumn.kt |  28 ++++
 .../kotlin/hep/dataforge/tables/MapRow.kt     |  19 +++
 .../hep/dataforge/tables/NumberColumn.kt      |  94 +++++++++++
 .../kotlin/hep/dataforge/tables/RowTable.kt   |  26 +++
 .../kotlin/hep/dataforge/tables/Table.kt      |  30 ++++
 .../hep/dataforge/tables/TableAccessor.kt     |  37 +++++
 .../hep/dataforge/workspace/GenericTask.kt    |   2 +-
 .../kotlin/hep/dataforge/workspace/Task.kt    |   2 +-
 .../hep/dataforge/workspace/TaskBuilder.kt    |   2 +-
 .../workspace/SimpleWorkspaceTest.kt          |   1 +
 settings.gradle.kts                           |   3 +-
 47 files changed, 664 insertions(+), 259 deletions(-)
 create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt
 create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt
 delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{ => meta}/descriptors/Described.kt (92%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{ => meta}/descriptors/DescriptorMeta.kt (95%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{ => meta}/descriptors/ItemDescriptor.kt (85%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/Configurable.kt (71%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/ConfigurableDelegate.kt (88%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/Scheme.kt (81%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/Specification.kt (90%)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => transformations}/MetaTransformation.kt (83%)
 rename dataforge-meta/src/commonTest/kotlin/hep/dataforge/{ => meta}/descriptors/DescriptorTest.kt (77%)
 rename {dataforge-output-html => dataforge-output/dataforge-output-html}/build.gradle.kts (100%)
 rename {dataforge-output-html => dataforge-output/dataforge-output-html}/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt (100%)
 delete mode 100644 dataforge-tables/build.gradle
 create mode 100644 dataforge-tables/build.gradle.kts
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
 create mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index 53a45364..14ccd3e9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-7")
+val dataforgeVersion by extra("0.1.5-dev-8")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt
new file mode 100644
index 00000000..cadd4231
--- /dev/null
+++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright  2018 Alexander Nozik.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package hep.dataforge.descriptors
+
+import hep.dataforge.meta.DFExperimental
+import hep.dataforge.values.ValueType
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+annotation class Attribute(
+    val key: String,
+    val value: String
+)
+
+@MustBeDocumented
+annotation class Attributes(
+    val attrs: Array<Attribute>
+)
+
+@MustBeDocumented
+annotation class ItemDef(
+    val info: String = "",
+    val multiple: Boolean = false,
+    val required: Boolean = false
+)
+
+@Target(AnnotationTarget.PROPERTY)
+@MustBeDocumented
+annotation class ValueDef(
+    val type: Array<ValueType> = [ValueType.STRING],
+    val def: String = "",
+    val allowed: Array<String> = [],
+    val enumeration: KClass<*> = Any::class
+)
+
+///**
+// * Description text for meta property, node or whole object
+// */
+//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class Description(val value: String)
+//
+///**
+// * Annotation for value property which states that lists are expected
+// */
+//@Target(AnnotationTarget.PROPERTY)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class Multiple
+//
+///**
+// * Descriptor target
+// * The DataForge path to the resource containing the description. Following targets are supported:
+// *  1. resource
+// *  1. file
+// *  1. class
+// *  1. method
+// *  1. property
+// *
+// *
+// * Does not work if [type] is provided
+// */
+//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class Descriptor(val value: String)
+//
+//
+///**
+// * Aggregator class for descriptor nodes
+// */
+//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class DescriptorNodes(vararg val nodes: NodeDef)
+//
+///**
+// * Aggregator class for descriptor values
+// */
+//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class DescriptorValues(vararg val nodes: ValueDef)
+//
+///**
+// * Alternative name for property descriptor declaration
+// */
+//@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class DescriptorName(val name: String)
+//
+//@Target(AnnotationTarget.PROPERTY)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class DescriptorValue(val def: ValueDef)
+////TODO enter fields directly?
+//
+//@Target(AnnotationTarget.PROPERTY)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class ValueProperty(
+//    val name: String = "",
+//    val type: Array<ValueType> = arrayOf(ValueType.STRING),
+//    val multiple: Boolean = false,
+//    val def: String = "",
+//    val enumeration: KClass<*> = Any::class,
+//    val tags: Array<String> = emptyArray()
+//)
+//
+//
+//@Target(AnnotationTarget.PROPERTY)
+//@Retention(AnnotationRetention.RUNTIME)
+//@MustBeDocumented
+//annotation class NodeProperty(val name: String = "")
diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt
new file mode 100644
index 00000000..0015436c
--- /dev/null
+++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt
@@ -0,0 +1,65 @@
+package hep.dataforge.descriptors
+
+import hep.dataforge.meta.*
+import hep.dataforge.meta.descriptors.ItemDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.attributes
+import hep.dataforge.meta.scheme.ConfigurableDelegate
+import hep.dataforge.meta.scheme.Scheme
+import hep.dataforge.values.parseValue
+import kotlin.reflect.KProperty1
+import kotlin.reflect.full.findAnnotation
+import kotlin.reflect.full.isSubclassOf
+import kotlin.reflect.full.memberProperties
+
+
+//inline fun <reified T : Scheme> T.buildDescriptor(): NodeDescriptor = NodeDescriptor {
+//    T::class.apply {
+//        findAnnotation<ItemDef>()?.let { def ->
+//            info = def.info
+//            required = def.required
+//            multiple = def.multiple
+//        }
+//        findAnnotation<Attribute>()?.let { attr ->
+//            attributes {
+//                this[attr.key] = attr.value.parseValue()
+//            }
+//        }
+//        findAnnotation<Attributes>()?.attrs?.forEach { attr ->
+//            attributes {
+//                this[attr.key] = attr.value.parseValue()
+//            }
+//        }
+//    }
+//    T::class.memberProperties.forEach { property ->
+//        val delegate = property.getDelegate(this@buildDescriptor)
+//
+//        val descriptor: ItemDescriptor = when (delegate) {
+//            is ConfigurableDelegate -> buildPropertyDescriptor(property, delegate)
+//            is ReadWriteDelegateWrapper<*, *> -> {
+//                if (delegate.delegate is ConfigurableDelegate) {
+//                    buildPropertyDescriptor(property, delegate.delegate as ConfigurableDelegate)
+//                } else {
+//                    return@forEach
+//                }
+//            }
+//            else -> return@forEach
+//        }
+//        defineItem(property.name, descriptor)
+//    }
+//}
+
+//inline fun <T : Scheme, reified V : Any?> buildPropertyDescriptor(
+//    property: KProperty1<T, V>,
+//    delegate: ConfigurableDelegate
+//): ItemDescriptor {
+//    when {
+//        V::class.isSubclassOf(Scheme::class) -> NodeDescriptor {
+//            default = delegate.default.node
+//        }
+//        V::class.isSubclassOf(Meta::class) -> NodeDescriptor {
+//            default = delegate.default.node
+//        }
+//
+//    }
+//}
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
index 08a0ea87..bc9caaec 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt
@@ -1,6 +1,9 @@
 package hep.dataforge.data
 
 import hep.dataforge.meta.*
+import hep.dataforge.meta.scheme.Scheme
+import hep.dataforge.meta.scheme.SchemeSpec
+import hep.dataforge.meta.scheme.string
 import hep.dataforge.names.toName
 
 
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
index acd19652..d0543fd0 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
@@ -1,12 +1,13 @@
 package hep.dataforge.io.yaml
 
 import hep.dataforge.context.Context
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.io.MetaFormat
 import hep.dataforge.io.MetaFormatFactory
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.toMap
+import hep.dataforge.meta.scheme.toMeta
 import hep.dataforge.meta.toMeta
 import kotlinx.io.Input
 import kotlinx.io.Output
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
index 1182bfbc..c86fe883 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
@@ -1,7 +1,7 @@
 package hep.dataforge.io
 
 import hep.dataforge.context.Context
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.*
 import hep.dataforge.values.*
 import kotlinx.io.*
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
index d27b0a97..8ec244a5 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
@@ -3,9 +3,9 @@
 package hep.dataforge.io
 
 import hep.dataforge.context.Context
-import hep.dataforge.descriptors.ItemDescriptor
-import hep.dataforge.descriptors.NodeDescriptor
-import hep.dataforge.descriptors.ValueDescriptor
+import hep.dataforge.meta.descriptors.ItemDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.ValueDescriptor
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaBase
 import hep.dataforge.meta.MetaItem
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
index d22359c8..4776e015 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt
@@ -1,7 +1,7 @@
 package hep.dataforge.io
 
 import hep.dataforge.context.Context
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
 import hep.dataforge.meta.Meta
 import hep.dataforge.names.Name
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt
index 7c294891..1df4b42d 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt
@@ -6,6 +6,7 @@ import hep.dataforge.io.*
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
+import hep.dataforge.meta.scheme.int
 import kotlin.reflect.KClass
 
 class RemoteFunctionClient(override val context: Context, val responder: Responder) : FunctionServer, ContextAware {
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt
index 8252b1d3..efd4351e 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt
@@ -8,6 +8,7 @@ import hep.dataforge.io.Responder
 import hep.dataforge.io.type
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
+import hep.dataforge.meta.scheme.int
 
 class RemoteFunctionServer(
     override val context: Context,
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
index a1ac33a5..ef989869 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
@@ -18,7 +18,7 @@ object ValueSerializer : KSerializer<Value> {
     private val valueTypeSerializer = EnumSerializer(ValueType::class)
     private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) }
 
-    override val descriptor: SerialDescriptor = descriptor("hep.dataforge.values.Value") {
+    override val descriptor: SerialDescriptor = descriptor("Value") {
         boolean("isList")
         enum<ValueType>("valueType")
         element("value", null)
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
index 0f60c077..04d097aa 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt
@@ -3,6 +3,7 @@ package hep.dataforge.io
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
+import hep.dataforge.meta.scheme.int
 import kotlinx.io.text.writeRawString
 import kotlinx.io.text.writeUtf8String
 
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index fb693057..1fe9d59d 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -1,6 +1,6 @@
 package hep.dataforge.io
 
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt
deleted file mode 100644
index d65a0e79..00000000
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright  2018 Alexander Nozik.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package hep.dataforge.descriptors
-
-import hep.dataforge.values.ValueType
-import kotlin.reflect.KClass
-
-@Target(AnnotationTarget.PROPERTY)
-@MustBeDocumented
-annotation class ValueDef(
-        val key: String,
-        val type: Array<ValueType> = arrayOf(ValueType.STRING),
-        val multiple: Boolean = false,
-        val def: String = "",
-        val info: String = "",
-        val required: Boolean = true,
-        val allowed: Array<String> = emptyArray(),
-        val enumeration: KClass<*> = Any::class,
-        val tags: Array<String> = emptyArray()
-)
-
-@MustBeDocumented
-annotation class NodeDef(
-        val key: String,
-        val info: String = "",
-        val multiple: Boolean = false,
-        val required: Boolean = false,
-        val tags: Array<String> = emptyArray(),
-        /**
-         * A list of child value descriptors
-         */
-        val values: Array<ValueDef> = emptyArray(),
-        /**
-         * A target class for this node to describe
-         * @return
-         */
-        val type: KClass<*> = Any::class,
-        /**
-         * The DataForge path to the resource containing the description. Following targets are supported:
-         *
-         *  1. resource
-         *  1. file
-         *  1. class
-         *  1. method
-         *  1. property
-         *
-         *
-         * Does not work if [type] is provided
-         *
-         * @return
-         */
-        val descriptor: String = ""
-)
-
-/**
- * Description text for meta property, node or whole object
- */
-@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class Description(val value: String)
-
-/**
- * Annotation for value property which states that lists are expected
- */
-@Target(AnnotationTarget.PROPERTY)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class Multiple
-
-/**
- * Descriptor target
- * The DataForge path to the resource containing the description. Following targets are supported:
- *  1. resource
- *  1. file
- *  1. class
- *  1. method
- *  1. property
- *
- *
- * Does not work if [type] is provided
- */
-@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class Descriptor(val value: String)
-
-
-/**
- * Aggregator class for descriptor nodes
- */
-@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class DescriptorNodes(vararg val nodes: NodeDef)
-
-/**
- * Aggregator class for descriptor values
- */
-@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class DescriptorValues(vararg val nodes: ValueDef)
-
-/**
- * Alternative name for property descriptor declaration
- */
-@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class DescriptorName(val name: String)
-
-@Target(AnnotationTarget.PROPERTY)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class DescriptorValue(val def: ValueDef)
-//TODO enter fields directly?
-
-@Target(AnnotationTarget.PROPERTY)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class ValueProperty(
-        val name: String = "",
-        val type: Array<ValueType> = arrayOf(ValueType.STRING),
-        val multiple: Boolean = false,
-        val def: String = "",
-        val enumeration: KClass<*> = Any::class,
-        val tags: Array<String> = emptyArray()
-)
-
-
-@Target(AnnotationTarget.PROPERTY)
-@Retention(AnnotationRetention.RUNTIME)
-@MustBeDocumented
-annotation class NodeProperty(val name: String = "")
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
index 087cb077..833117b5 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
@@ -12,10 +12,15 @@ data class MetaListener(
     val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
 )
 
+interface ObservableMeta : Meta {
+    fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
+    fun removeListener(owner: Any?)
+}
+
 /**
  * Mutable meta representing object state
  */
-class Config : AbstractMutableMeta<Config>() {
+class Config : AbstractMutableMeta<Config>(), ObservableMeta {
 
     private val listeners = HashSet<MetaListener>()
 
@@ -26,14 +31,14 @@ class Config : AbstractMutableMeta<Config>() {
     /**
      * Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
      */
-    fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) {
+    override fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) {
         listeners.add(MetaListener(owner, action))
     }
 
     /**
      * Remove all listeners belonging to given owner
      */
-    fun removeListener(owner: Any?) {
+    override fun removeListener(owner: Any?) {
         listeners.removeAll { it.owner === owner }
     }
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
index 950df7d6..88819f2c 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
@@ -1,5 +1,6 @@
 package hep.dataforge.meta
 
+import hep.dataforge.meta.scheme.Configurable
 import hep.dataforge.names.*
 import hep.dataforge.values.Value
 
@@ -160,7 +161,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = set
 /**
  * Append the node with a same-name-sibling, automatically generating numerical index
  */
-fun MutableMeta<*>.append(name: Name, value: Any?) {
+fun <M: MutableMeta<M>> M.append(name: Name, value: Any?) {
     require(!name.isEmpty()) { "Name could not be empty for append operation" }
     val newIndex = name.last()!!.index
     if (newIndex.isNotEmpty()) {
@@ -171,4 +172,4 @@ fun MutableMeta<*>.append(name: Name, value: Any?) {
     }
 }
 
-fun MutableMeta<*>.append(name: String, value: Any?) = append(name.toName(), value)
\ No newline at end of file
+fun <M: MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value)
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/Described.kt
similarity index 92%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/Described.kt
index 4bf6c9dd..0a09a0fb 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/Described.kt
@@ -1,4 +1,4 @@
-package hep.dataforge.descriptors
+package hep.dataforge.meta.descriptors
 
 /**
  * An object which provides its descriptor
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt
similarity index 95%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt
index 49aef28e..f95069d6 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt
@@ -1,4 +1,4 @@
-package hep.dataforge.descriptors
+package hep.dataforge.meta.descriptors
 
 import hep.dataforge.meta.MetaBase
 import hep.dataforge.meta.MetaItem
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
similarity index 85%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
index 514349fe..9c5cf15f 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
@@ -1,6 +1,7 @@
-package hep.dataforge.descriptors
+package hep.dataforge.meta.descriptors
 
 import hep.dataforge.meta.*
+import hep.dataforge.meta.scheme.*
 import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
 import hep.dataforge.names.asName
@@ -41,6 +42,25 @@ sealed class ItemDescriptor : Scheme() {
     abstract var required: Boolean
 }
 
+/**
+ * Configure attributes of the descriptor
+ */
+fun ItemDescriptor.attributes(block: Config.() -> Unit) {
+    (attributes ?: Config().also { this.config = it }).apply(block)
+}
+
+/**
+ * Check if given item suits the descriptor
+ */
+fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean {
+    return when (this) {
+        is ValueDescriptor -> isAllowedValue(item.value ?: return false)
+        is NodeDescriptor -> items.all { (key, d) ->
+            d.validateItem(item.node[key])
+        }
+    }
+}
+
 /**
  * Descriptor for meta node. Could contain additional information for viewing
  * and editing.
@@ -61,7 +81,7 @@ class NodeDescriptor : ItemDescriptor() {
      *
      * @return
      */
-    var default: Config? by config()
+    var default by node()
 
     /**
      * The map of children node descriptors
@@ -71,15 +91,21 @@ class NodeDescriptor : ItemDescriptor() {
             name to wrap(node.node ?: error("Node descriptor must be a node"))
         }
 
-
-    fun node(name: String, descriptor: NodeDescriptor) {
+    /**
+     * Define a child item descriptor for this node
+     */
+    fun defineItem(name: String, descriptor: ItemDescriptor) {
         if (items.keys.contains(name)) error("The key $name already exists in descriptor")
-        val token = NameToken(NODE_KEY, name)
+        val token = when (descriptor) {
+            is NodeDescriptor -> NameToken(NODE_KEY, name)
+            is ValueDescriptor -> NameToken(VALUE_KEY, name)
+        }
         config[token] = descriptor.config
+
     }
 
 
-    fun node(name: String, block: NodeDescriptor.() -> Unit) {
+    fun defineNode(name: String, block: NodeDescriptor.() -> Unit) {
         val token = NameToken(NODE_KEY, name)
         if (config[token] == null) {
             config[token] = NodeDescriptor(block)
@@ -100,7 +126,7 @@ class NodeDescriptor : ItemDescriptor() {
         }
     }
 
-    fun node(name: Name, block: NodeDescriptor.() -> Unit) {
+    fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
         buildNode(name).apply(block)
     }
 
@@ -112,28 +138,23 @@ class NodeDescriptor : ItemDescriptor() {
             name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
         }
 
-    fun value(name: String, descriptor: ValueDescriptor) {
-        if (items.keys.contains(name)) error("The key $name already exists in descriptor")
-        val token = NameToken(VALUE_KEY, name)
-        config[token] = descriptor.config
-    }
 
     /**
      * Add a value descriptor using block for
      */
-    fun value(name: String, block: ValueDescriptor.() -> Unit) {
-        value(name, ValueDescriptor(block))
+    fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
+        defineItem(name, ValueDescriptor(block))
     }
 
-    fun value(name: Name, block: ValueDescriptor.() -> Unit) {
+    fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) {
         require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
-        buildNode(name.cutLast()).value(name.last().toString(), block)
+        buildNode(name.cutLast()).defineValue(name.last().toString(), block)
     }
 
     val items: Map<String, ItemDescriptor> get() = nodes + values
 
 
-    //override val descriptor: NodeDescriptor =  empty("descriptor")
+//override val descriptor: NodeDescriptor =  empty("descriptor")
 
     companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
index ab68212c..ddf049e3 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
@@ -1,6 +1,6 @@
 package hep.dataforge.meta
 
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.values.Value
 
 /**
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
similarity index 71%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
index e95b5564..42aaeb07 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
@@ -1,9 +1,7 @@
-package hep.dataforge.meta
+package hep.dataforge.meta.scheme
 
-import hep.dataforge.descriptors.Described
-import hep.dataforge.descriptors.NodeDescriptor
-import hep.dataforge.descriptors.defaultItem
-import hep.dataforge.descriptors.get
+import hep.dataforge.meta.*
+import hep.dataforge.meta.descriptors.*
 import hep.dataforge.names.Name
 import hep.dataforge.names.toName
 
@@ -24,6 +22,14 @@ interface Configurable : Described {
      */
     fun getDefaultItem(name: Name): MetaItem<*>? = null
 
+    /**
+     * Check if property with given [name] could be assigned to [value]
+     */
+    fun validateItem(name: Name, item: MetaItem<*>?): Boolean {
+        val descriptor = descriptor?.get(name)
+        return descriptor?.validateItem(item) ?: true
+    }
+
     override val descriptor: NodeDescriptor? get() = null
 }
 
@@ -39,7 +45,11 @@ fun Configurable.getProperty(key: String) = getProperty(key.toName())
  * Set a configurable property
  */
 fun Configurable.setProperty(name: Name, item: MetaItem<*>?) {
-    config[name] = item
+    if(validateItem(name,item)) {
+        config[name] = item
+    } else {
+        error("Validation failed for property $name with value $item")
+    }
 }
 
 fun Configurable.setProperty(key: String, item: MetaItem<*>?) {
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
similarity index 88%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
index 31cc2c9c..9b86abba 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
@@ -1,5 +1,6 @@
-package hep.dataforge.meta
+package hep.dataforge.meta.scheme
 
+import hep.dataforge.meta.*
 import hep.dataforge.names.Name
 import hep.dataforge.names.asName
 import hep.dataforge.values.*
@@ -41,23 +42,35 @@ class LazyConfigurableDelegate(
 /**
  * A property delegate that uses custom key
  */
-fun Configurable.item(default: Any?, key: Name? = null): ConfigurableDelegate =
-    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) })
+fun Configurable.item(default: Any? = null, key: Name? = null): ConfigurableDelegate =
+    ConfigurableDelegate(
+        this,
+        key,
+        default?.let { MetaItem.of(it) })
 
 /**
  * Generation of item delegate with lazy default.
  * Lazy default could be used also for validation
  */
 fun Configurable.lazyItem(key: Name? = null, default: () -> Any?): ConfigurableDelegate =
-    LazyConfigurableDelegate(this, key) { default()?.let { MetaItem.of(it) } }
+    LazyConfigurableDelegate(this, key) {
+        default()?.let {
+            MetaItem.of(it)
+        }
+    }
 
 fun <T> Configurable.item(
     default: T? = null,
     key: Name? = null,
-    writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
+    writer: (T) -> MetaItem<*>? = {
+        MetaItem.of(it)
+    },
     reader: (MetaItem<*>?) -> T
 ): ReadWriteProperty<Any?, T> =
-    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer)
+    ConfigurableDelegate(
+        this,
+        key,
+        default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer)
 
 fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> =
     item(default, key).transform { it.value }
@@ -68,9 +81,13 @@ fun <T> Configurable.value(
     writer: (T) -> Value? = { Value.of(it) },
     reader: (Value?) -> T
 ): ReadWriteProperty<Any?, T> =
-    ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map(
+    ConfigurableDelegate(
+        this,
+        key,
+        default?.let { MetaItem.of(it) }
+    ).map(
         reader = { reader(it.value) },
-        writer = { writer(it)?.let { MetaItem.ValueItem(it) } }
+        writer = { value -> writer(value)?.let { MetaItem.ValueItem(it) } }
     )
 
 fun Configurable.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> =
@@ -184,6 +201,10 @@ fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWri
 fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> =
     config.node(key)
 
+fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item().map(
+    reader = { it.node },
+    writer = { it?.let { MetaItem.NodeItem(it) } }
+)
 
 fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> =
     object : ReadWriteProperty<Any?, T?> {
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
similarity index 81%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
index 84b350f1..c730eed2 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
@@ -1,6 +1,7 @@
-package hep.dataforge.meta
+package hep.dataforge.meta.scheme
 
-import hep.dataforge.descriptors.*
+import hep.dataforge.meta.*
+import hep.dataforge.meta.descriptors.*
 import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
 import hep.dataforge.names.plus
@@ -8,7 +9,7 @@ import hep.dataforge.names.plus
 /**
  * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
  */
-open class Scheme() : Configurable, Described {
+open class Scheme() : Configurable, Described, MetaRepr {
     constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() {
         this.config = config
         this.defaultProvider = defaultProvider
@@ -17,7 +18,8 @@ open class Scheme() : Configurable, Described {
     //constructor(config: Config, default: Meta) : this(config, { default[it] })
     constructor(config: Config) : this(config, { null })
 
-    final override var config: Config = Config()
+    final override var config: Config =
+        Config()
         internal set
 
     lateinit var defaultProvider: (Name) -> MetaItem<*>?
@@ -37,6 +39,8 @@ open class Scheme() : Configurable, Described {
      */
     open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY)
 
+    override fun toMeta(): Meta = config.seal()
+
     private inner class DefaultLayer(val path: Name) : MetaBase() {
         override val items: Map<NameToken, MetaItem<*>> =
             (descriptor?.get(path) as? NodeDescriptor)?.items?.entries?.associate { (key, descriptor) ->
@@ -55,7 +59,8 @@ open class Scheme() : Configurable, Described {
 /**
  * A specification for simplified generation of wrappers
  */
-open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
+open class SchemeSpec<T : Scheme>(val builder: () -> T) :
+    Specification<T> {
     override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T {
         return builder().apply {
             this.config = config
@@ -75,14 +80,18 @@ open class MetaScheme(
     init {
         this.descriptor = descriptor
     }
-    override val defaultLayer: Meta get() = Laminate(meta, descriptor?.defaultItem().node)
+
+    override val defaultLayer: Meta
+        get() = Laminate(meta, descriptor?.defaultItem().node)
 }
 
-fun Meta.asScheme() = MetaScheme(this)
+fun Meta.asScheme() =
+    MetaScheme(this)
 
 fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)
 
 /**
  * Create a snapshot laminate
  */
-fun Scheme.toMeta(): Laminate = Laminate(config, defaultLayer)
\ No newline at end of file
+fun Scheme.toMeta(): Laminate =
+    Laminate(config, defaultLayer)
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt
similarity index 90%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt
index 9c8b740a..4aa7bf02 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt
@@ -1,5 +1,6 @@
-package hep.dataforge.meta
+package hep.dataforge.meta.scheme
 
+import hep.dataforge.meta.*
 import hep.dataforge.names.Name
 import kotlin.jvm.JvmName
 
@@ -33,7 +34,9 @@ interface Specification<T : Configurable> {
     /**
      * Wrap a configuration using static meta as default
      */
-    fun wrap(default: Meta): T = wrap(Config()){default[it]}
+    fun wrap(default: Meta): T = wrap(
+        Config()
+    ){default[it]}
 }
 
 /**
@@ -54,7 +57,8 @@ fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action
 fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
     Config().also { update(it, action) }
 
-fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(Config(), it) }
+fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(
+    Config(), it) }
 
 @JvmName("configSpec")
 fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaTransformation.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt
similarity index 83%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaTransformation.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt
index 8deada19..4dad4466 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaTransformation.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt
@@ -1,5 +1,6 @@
-package hep.dataforge.meta
+package hep.dataforge.meta.transformations
 
+import hep.dataforge.meta.*
 import hep.dataforge.names.Name
 
 /**
@@ -8,7 +9,7 @@ import hep.dataforge.names.Name
 interface TransformationRule {
 
     /**
-     * Check if this transformation
+     * Check if this transformation should be applied to a node with given name and value
      */
     fun matches(name: Name, item: MetaItem<*>?): Boolean
 
@@ -29,7 +30,8 @@ interface TransformationRule {
 /**
  * A transformation which keeps all elements, matching [selector] unchanged.
  */
-data class KeepTransformationRule(val selector: (Name) -> Boolean) : TransformationRule {
+data class KeepTransformationRule(val selector: (Name) -> Boolean) :
+    TransformationRule {
     override fun matches(name: Name, item: MetaItem<*>?): Boolean {
         return selector(name)
     }
@@ -87,25 +89,27 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
     /**
      * Produce new meta using only those items that match transformation rules
      */
-    fun transform(source: Meta): Meta = buildMeta {
-        transformations.forEach { rule ->
-            rule.selectItems(source).forEach { name ->
-                rule.transformItem(name, source[name], this)
+    fun transform(source: Meta): Meta =
+        buildMeta {
+            transformations.forEach { rule ->
+                rule.selectItems(source).forEach { name ->
+                    rule.transformItem(name, source[name], this)
+                }
             }
         }
-    }
 
     /**
      * Transform a meta, replacing all elements found in rules with transformed entries
      */
-    fun apply(source: Meta): Meta = buildMeta(source) {
-        transformations.forEach { rule ->
-            rule.selectItems(source).forEach { name ->
-                remove(name)
-                rule.transformItem(name, source[name], this)
+    fun apply(source: Meta): Meta =
+        buildMeta(source) {
+            transformations.forEach { rule ->
+                rule.selectItems(source).forEach { name ->
+                    remove(name)
+                    rule.transformItem(name, source[name], this)
+                }
             }
         }
-    }
 
     /**
      * Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
@@ -150,9 +154,10 @@ class MetaTransformationBuilder {
      * Keep nodes by regex
      */
     fun keep(regex: String) {
-        transformations.add(RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
-            setItem(name, metaItem)
-        })
+        transformations.add(
+            RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
+                    setItem(name, metaItem)
+                })
     }
 
     /**
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
index 93aad8e5..2461c363 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
@@ -1,5 +1,6 @@
 package hep.dataforge.meta
 
+import hep.dataforge.meta.scheme.*
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
index 0246fb10..09a3e03c 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
@@ -1,5 +1,8 @@
 package hep.dataforge.meta
 
+import hep.dataforge.meta.scheme.asScheme
+import hep.dataforge.meta.scheme.getProperty
+import hep.dataforge.meta.scheme.toMeta
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
index f21c5b2c..6d99101a 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt
@@ -1,5 +1,8 @@
 package hep.dataforge.meta
 
+import hep.dataforge.meta.scheme.Scheme
+import hep.dataforge.meta.scheme.Specification
+import hep.dataforge.meta.scheme.numberList
 import hep.dataforge.names.Name
 import kotlin.test.Test
 import kotlin.test.assertEquals
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt
similarity index 77%
rename from dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt
rename to dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt
index 81d25285..1fa07382 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt
@@ -1,4 +1,4 @@
-package hep.dataforge.descriptors
+package hep.dataforge.meta.descriptors
 
 import hep.dataforge.values.ValueType
 import kotlin.test.Test
@@ -7,14 +7,14 @@ import kotlin.test.assertEquals
 class DescriptorTest {
 
     val descriptor = NodeDescriptor {
-        node("aNode") {
+        defineNode("aNode") {
             info = "A root demo node"
-            value("b") {
+            defineValue("b") {
                 info = "b number value"
                 type(ValueType.NUMBER)
             }
-            node("otherNode") {
-                value("otherValue") {
+            defineNode("otherNode") {
+                defineValue("otherValue") {
                     type(ValueType.BOOLEAN)
                     default(false)
                     info = "default value"
diff --git a/dataforge-output-html/build.gradle.kts b/dataforge-output/dataforge-output-html/build.gradle.kts
similarity index 100%
rename from dataforge-output-html/build.gradle.kts
rename to dataforge-output/dataforge-output-html/build.gradle.kts
diff --git a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt b/dataforge-output/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt
similarity index 100%
rename from dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt
rename to dataforge-output/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt
diff --git a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt
index 6dd61105..9fb1c919 100644
--- a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt
+++ b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt
@@ -3,6 +3,7 @@ package hep.dataforge.scripting
 import hep.dataforge.context.Global
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
+import hep.dataforge.meta.scheme.int
 import hep.dataforge.workspace.SimpleWorkspaceBuilder
 import hep.dataforge.workspace.context
 import hep.dataforge.workspace.target
diff --git a/dataforge-tables/build.gradle b/dataforge-tables/build.gradle
deleted file mode 100644
index a0011494..00000000
--- a/dataforge-tables/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-plugins {
-    id "org.jetbrains.kotlin.multiplatform"
-}
-
-repositories {
-    jcenter()
-}
-
-kotlin {
-    targets {
-        fromPreset(presets.jvm, 'jvm')
-        //fromPreset(presets.js, 'js')
-        // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
-        // For Linux, preset should be changed to e.g. presets.linuxX64
-        // For MacOS, preset should be changed to e.g. presets.macosX64
-        //fromPreset(presets.iosX64, 'ios')
-    }
-    sourceSets {
-        commonMain {
-            dependencies {
-                api project(":dataforge-context")
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/dataforge-tables/build.gradle.kts b/dataforge-tables/build.gradle.kts
new file mode 100644
index 00000000..6c0a1e53
--- /dev/null
+++ b/dataforge-tables/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+    id("scientifik.mpp")
+}
+
+kotlin {
+    sourceSets {
+        val commonMain by getting{
+            dependencies {
+                api(project(":dataforge-context"))
+                api(project(":dataforge-io"))
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
new file mode 100644
index 00000000..aa0d2aec
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
@@ -0,0 +1,8 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.scheme.Scheme
+import hep.dataforge.meta.scheme.SchemeSpec
+
+class ColumnScheme : Scheme() {
+    companion object : SchemeSpec<ColumnScheme>(::ColumnScheme)
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
new file mode 100644
index 00000000..ce7b7dae
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
@@ -0,0 +1,35 @@
+package hep.dataforge.tables
+
+import kotlin.reflect.KClass
+
+class ColumnTable(override val columns: Map<String, Column<*>>) :
+    Table {
+    private val rowsNum = columns.values.first().size
+
+    init {
+        require(columns.values.all { it.size == rowsNum }) { "All columns must be of the same size" }
+    }
+
+    override val rows: List<Row>
+        get() = (0 until rowsNum).map { VirtualRow(it) }
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
+        val value = columns[column]?.get(row)
+        return when {
+            value == null -> null
+            type.isInstance(value) -> value as T
+            else -> error("Expected type is $type, but found ${value::class}")
+        }
+    }
+
+    private inner class VirtualRow(val index: Int) : Row {
+        override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = getValue(index, column, type)
+    }
+}
+
+class ColumnTableBuilder {
+    private val columns = ArrayList<Column<*>>()
+
+    fun build() = ColumnTable(columns.associateBy { it.name })
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt
new file mode 100644
index 00000000..a926a2ad
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt
@@ -0,0 +1,28 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+class ListColumn<T : Any>(
+    override val name: String,
+    private val data: List<T?>,
+    override val type: KClass<out T>,
+    override val meta: Meta
+) : Column<T> {
+    override val size: Int get() = data.size
+
+    override fun get(index: Int): T? = data[index]
+
+    companion object {
+        inline operator fun <reified T : Any> invoke(
+            name: String,
+            data: List<T>,
+            noinline metaBuilder: ColumnScheme.() -> Unit
+        ): ListColumn<T> = ListColumn(name, data, T::class, ColumnScheme(metaBuilder).toMeta())
+    }
+}
+
+inline fun <T : Any, reified R : Any> Column<T>.map(meta: Meta = this.meta, noinline block: (T?) -> R): Column<R> {
+    val data = List(size) { block(get(it)) }
+    return ListColumn(name, data, R::class, meta)
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
new file mode 100644
index 00000000..0a78ca9e
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
@@ -0,0 +1,19 @@
+package hep.dataforge.tables
+
+import kotlin.reflect.KClass
+
+class MapRow(val values: Map<String, Any>) : Row {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : Any> getValue(column: String, type: KClass<out T>): T? {
+        val value = values[column]
+        return when {
+            value == null -> null
+            type.isInstance(value) -> {
+                value as T?
+            }
+            else -> {
+                error("Expected type is $type, but found ${value::class}")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt
new file mode 100644
index 00000000..1bd8d6a3
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt
@@ -0,0 +1,94 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+//interface NumberColumn<N : Number> : Column<N>
+
+data class RealColumn(
+    override val name: String,
+    val data: DoubleArray,
+    override val meta: Meta = Meta.EMPTY
+) : Column<Double> {
+    override val type: KClass<out Double> get() = Double::class
+
+    override val size: Int get() = data.size
+
+    @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
+    override inline fun get(index: Int): Double = data[index]
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RealColumn) return false
+
+        if (name != other.name) return false
+        if (!data.contentEquals(other.data)) return false
+        if (meta != other.meta) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = name.hashCode()
+        result = 31 * result + data.contentHashCode()
+        result = 31 * result + meta.hashCode()
+        return result
+    }
+
+    companion object {
+        inline operator fun <reified T : Any> invoke(
+            name: String,
+            data: DoubleArray,
+            noinline metaBuilder: ColumnScheme.() -> Unit
+        ): RealColumn = RealColumn(name, data, ColumnScheme(metaBuilder).toMeta())
+    }
+}
+
+fun <T : Any> Column<T>.map(meta: Meta = this.meta, block: (T?) -> Double): RealColumn {
+    val data = DoubleArray(size) { block(get(it)) }
+    return RealColumn(name, data, meta)
+}
+
+data class IntColumn(
+    override val name: String,
+    val data: IntArray,
+    override val meta: Meta = Meta.EMPTY
+) : Column<Int> {
+    override val type: KClass<out Int> get() = Int::class
+
+    override val size: Int get() = data.size
+
+    @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE")
+    override inline fun get(index: Int): Int = data[index]
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is IntColumn) return false
+
+        if (name != other.name) return false
+        if (!data.contentEquals(other.data)) return false
+        if (meta != other.meta) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = name.hashCode()
+        result = 31 * result + data.contentHashCode()
+        result = 31 * result + meta.hashCode()
+        return result
+    }
+
+    companion object {
+        inline operator fun <reified T : Any> invoke(
+            name: String,
+            data: IntArray,
+            noinline metaBuilder: ColumnScheme.() -> Unit
+        ): IntColumn = IntColumn(name, data, ColumnScheme(metaBuilder).toMeta())
+    }
+}
+
+fun <T : Any> Column<T>.map(meta: Meta = this.meta, block: (T?) -> Int): IntColumn {
+    val data = IntArray(size) { block(get(it)) }
+    return IntColumn(name, data, meta)
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
new file mode 100644
index 00000000..fbcec0b7
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
@@ -0,0 +1,26 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+data class ColumnDef<T : Any>(val name: String, val type: KClass<T>, val meta: Meta)
+
+class RowTable<R : Row>(override val rows: List<R>, private val columnDefs: List<ColumnDef<*>>) : Table {
+    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? =
+        rows[row].getValue(column, type)
+
+    override val columns: Map<String, Column<*>>
+        get() = columnDefs.associate { it.name to VirtualColumn(it) }
+
+    private inner class VirtualColumn<T : Any>(val def: ColumnDef<T>) :
+        Column<T> {
+
+        override val name: String get() = def.name
+        override val type: KClass<out T> get() = def.type
+        override val meta: Meta get() = def.meta
+        override val size: Int get() = rows.size
+
+        override fun get(index: Int): T? = rows[index].getValue(name, type)
+    }
+}
+
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
new file mode 100644
index 00000000..52171625
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
@@ -0,0 +1,30 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+interface Table {
+    fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
+    val columns: Map<String, Column<*>>
+    val rows: List<Row>
+}
+
+interface Column<out T : Any> {
+    val name: String
+    val type: KClass<out T>
+    val meta: Meta
+    val size: Int
+    operator fun get(index: Int): T?
+}
+
+val Column<*>.indices get() = (0 until size)
+
+operator fun <T : Any> Column<T>.iterator() = iterator {
+    for (i in indices){
+        yield(get(i))
+    }
+}
+
+interface Row {
+    fun <T : Any> getValue(column: String, type: KClass<out T>): T?
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt
new file mode 100644
index 00000000..21453426
--- /dev/null
+++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt
@@ -0,0 +1,37 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KClass
+import kotlin.reflect.KProperty
+import kotlin.reflect.full.cast
+import kotlin.reflect.full.isSubclassOf
+
+class TableAccessor(val table: Table) : Table by table {
+    inline fun <reified T : Any> column() = ColumnProperty(table, T::class)
+}
+
+@Suppress("UNCHECKED_CAST")
+fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
+    return if (type.isSubclassOf(this.type)) {
+        this as Column<T>
+    } else {
+        ColumnWrapper(this, type)
+    }
+}
+
+class ColumnWrapper<T : Any>(val column: Column<*>, override val type: KClass<T>) : Column<T> {
+    override val name: String get() = column.name
+    override val meta: Meta get() = column.meta
+    override val size: Int get() = column.size
+
+
+    override fun get(index: Int): T? = type.cast(column[index])
+}
+
+class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>?> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T>? {
+        val name = property.name
+        return table.columns[name]?.cast(type)
+    }
+}
\ No newline at end of file
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt
index ff499888..4e0ca715 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt
@@ -1,7 +1,7 @@
 package hep.dataforge.workspace
 
 import hep.dataforge.data.DataNode
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.node
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt
index 0b11a7d5..7511bda9 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt
@@ -2,7 +2,7 @@ package hep.dataforge.workspace
 
 import hep.dataforge.context.Named
 import hep.dataforge.data.DataNode
-import hep.dataforge.descriptors.Described
+import hep.dataforge.meta.descriptors.Described
 import hep.dataforge.meta.Meta
 import hep.dataforge.provider.Type
 import hep.dataforge.workspace.Task.Companion.TYPE
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
index 87736c01..7f359914 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt
@@ -2,7 +2,7 @@ package hep.dataforge.workspace
 
 import hep.dataforge.context.Context
 import hep.dataforge.data.*
-import hep.dataforge.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.DFBuilder
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.get
diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt
index a4df6a4b..8bd02c35 100644
--- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt
+++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt
@@ -6,6 +6,7 @@ import hep.dataforge.meta.boolean
 import hep.dataforge.meta.builder
 import hep.dataforge.meta.get
 import hep.dataforge.meta.int
+import hep.dataforge.meta.scheme.int
 import hep.dataforge.names.plus
 import kotlin.test.Test
 import kotlin.test.assertEquals
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 42033ac1..e0d250bf 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,7 +28,8 @@ include(
     ":dataforge-context",
     ":dataforge-data",
     ":dataforge-output",
-    ":dataforge-output-html",
+    ":dataforge-output:dataforge-output-html",
+    ":dataforge-tables",
     ":dataforge-workspace",
     ":dataforge-scripting"
 )

From 819ab3ab20a8bd9fd8658b1ba92878416abac6e6 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 29 Jan 2020 21:50:49 +0300
Subject: [PATCH 27/41] Fix scheme to meta transformation

---
 .../src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
index c730eed2..7ac49de0 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
@@ -39,7 +39,7 @@ open class Scheme() : Configurable, Described, MetaRepr {
      */
     open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY)
 
-    override fun toMeta(): Meta = config.seal()
+    override fun toMeta(): Meta = Laminate(config, defaultLayer)
 
     private inner class DefaultLayer(val path: Name) : MetaBase() {
         override val items: Map<NameToken, MetaItem<*>> =

From 061c570407ad8dd8c8b4dd8eca5618d896a71386 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sat, 8 Feb 2020 21:36:27 +0300
Subject: [PATCH 28/41] API for tables

---
 .../kotlin/hep/dataforge/tables/ColumnDef.kt  | 10 ++++
 .../hep/dataforge/tables/ColumnTable.kt       | 30 ++++------
 .../dataforge/tables/ColumnTableBuilder.kt    | 58 +++++++++++++++++++
 .../kotlin/hep/dataforge/tables/ListColumn.kt | 13 ++++-
 .../kotlin/hep/dataforge/tables/MapRow.kt     | 11 +---
 .../kotlin/hep/dataforge/tables/MetaQuery.kt  | 11 ++++
 .../kotlin/hep/dataforge/tables/RowTable.kt   | 17 +++---
 .../kotlin/hep/dataforge/tables/Table.kt      | 45 +++++++++++---
 .../kotlin/hep/dataforge/tables/CastColumn.kt | 36 ++++++++++++
 .../hep/dataforge/tables/TableAccessor.kt     | 37 ------------
 10 files changed, 182 insertions(+), 86 deletions(-)
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt
 create mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
 delete mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt

diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt
new file mode 100644
index 00000000..2df51d41
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt
@@ -0,0 +1,10 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+data class ColumnDef<out T : Any>(
+    override val name: String,
+    override val type: KClass<out T>,
+    override val meta: Meta
+): ColumnHeader<T>
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
index ce7b7dae..07ea5d94 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
@@ -2,34 +2,26 @@ package hep.dataforge.tables
 
 import kotlin.reflect.KClass
 
-class ColumnTable(override val columns: Map<String, Column<*>>) :
-    Table {
-    private val rowsNum = columns.values.first().size
+/**
+ * @param C bottom type for all columns in the table
+ */
+class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table {
+    private val rowsNum = columns.first().size
 
     init {
-        require(columns.values.all { it.size == rowsNum }) { "All columns must be of the same size" }
+        require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" }
     }
 
     override val rows: List<Row>
-        get() = (0 until rowsNum).map { VirtualRow(it) }
+        get() = (0 until rowsNum).map { VirtualRow(this, it) }
 
-    @Suppress("UNCHECKED_CAST")
     override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
         val value = columns[column]?.get(row)
-        return when {
-            value == null -> null
-            type.isInstance(value) -> value as T
-            else -> error("Expected type is $type, but found ${value::class}")
-        }
-    }
-
-    private inner class VirtualRow(val index: Int) : Row {
-        override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = getValue(index, column, type)
+        return type.cast(value)
     }
 }
 
-class ColumnTableBuilder {
-    private val columns = ArrayList<Column<*>>()
+internal class VirtualRow(val table: Table, val index: Int) : Row {
+    override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type)
+}
 
-    fun build() = ColumnTable(columns.associateBy { it.name })
-}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
new file mode 100644
index 00000000..b453c957
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
@@ -0,0 +1,58 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+class ColumnTableBuilder(val size: Int) : Table {
+    private val _columns = ArrayList<Column<*>>()
+
+    override val columns: List<Column<*>> get() = _columns
+    override val rows: List<Row> get() = (0 until size).map {
+        VirtualRow(this, it)
+    }
+
+    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
+        val value = columns[column]?.get(row)
+        return type.cast(value)
+    }
+
+    /**
+     * Add a fixed column to the end of the table
+     */
+    fun add(column: Column<*>) {
+        require(column.size == this.size) { "Required column size $size, but found ${column.size}" }
+        _columns.add(column)
+    }
+
+    /**
+     * Insert a column at [index]
+     */
+    fun insert(index: Int, column: Column<*>) {
+        require(column.size == this.size) { "Required column size $size, but found ${column.size}" }
+        _columns.add(index, column)
+    }
+}
+
+class MapColumn<T : Any, R : Any>(
+    val source: Column<T>,
+    override val type: KClass<out R>,
+    override val name: String,
+    override val meta: Meta = source.meta,
+    val mapper: (T?) -> R?
+) : Column<R> {
+    override val size: Int get() = source.size
+
+    override fun get(index: Int): R? = mapper(source[index])
+}
+
+class CachedMapColumn<T : Any, R : Any>(
+    val source: Column<T>,
+    override val type: KClass<out R>,
+    override val name: String,
+    override val meta: Meta = source.meta,
+    val mapper: (T?) -> R?
+) : Column<R> {
+    override val size: Int get() = source.size
+    private val values: HashMap<Int, R?> = HashMap()
+    override fun get(index: Int): R? = values.getOrPut(index) { mapper(source[index]) }
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt
index a926a2ad..fc7f03ea 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt
@@ -16,9 +16,16 @@ class ListColumn<T : Any>(
     companion object {
         inline operator fun <reified T : Any> invoke(
             name: String,
-            data: List<T>,
-            noinline metaBuilder: ColumnScheme.() -> Unit
-        ): ListColumn<T> = ListColumn(name, data, T::class, ColumnScheme(metaBuilder).toMeta())
+            def: ColumnScheme,
+            data: List<T?>
+        ): ListColumn<T> = ListColumn(name, data, T::class, def.toMeta())
+
+        inline operator fun <reified T : Any> invoke(
+            name: String,
+            def: ColumnScheme,
+            size: Int,
+            dataBuilder: (Int) -> T?
+        ): ListColumn<T> = invoke(name, def, List(size, dataBuilder))
     }
 }
 
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
index 0a78ca9e..76002e9d 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
@@ -3,17 +3,8 @@ package hep.dataforge.tables
 import kotlin.reflect.KClass
 
 class MapRow(val values: Map<String, Any>) : Row {
-    @Suppress("UNCHECKED_CAST")
     override fun <T : Any> getValue(column: String, type: KClass<out T>): T? {
         val value = values[column]
-        return when {
-            value == null -> null
-            type.isInstance(value) -> {
-                value as T?
-            }
-            else -> {
-                error("Expected type is $type, but found ${value::class}")
-            }
-        }
+        return type.cast(value)
     }
 }
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt
new file mode 100644
index 00000000..cf07a5c8
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt
@@ -0,0 +1,11 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.scheme.Scheme
+import hep.dataforge.meta.scheme.SchemeSpec
+import hep.dataforge.meta.scheme.string
+
+class MetaQuery : Scheme() {
+    var field by string()
+
+    companion object : SchemeSpec<MetaQuery>(::MetaQuery)
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
index fbcec0b7..c640aade 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
@@ -3,24 +3,21 @@ package hep.dataforge.tables
 import hep.dataforge.meta.Meta
 import kotlin.reflect.KClass
 
-data class ColumnDef<T : Any>(val name: String, val type: KClass<T>, val meta: Meta)
 
-class RowTable<R : Row>(override val rows: List<R>, private val columnDefs: List<ColumnDef<*>>) : Table {
+class RowTable<R : Row>(override val rows: List<R>, override val header: TableHeader) : Table {
     override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? =
         rows[row].getValue(column, type)
 
-    override val columns: Map<String, Column<*>>
-        get() = columnDefs.associate { it.name to VirtualColumn(it) }
+    override val columns: List<Column<*>> get() = header.map { RotTableColumn(it) }
 
-    private inner class VirtualColumn<T : Any>(val def: ColumnDef<T>) :
-        Column<T> {
-
-        override val name: String get() = def.name
-        override val type: KClass<out T> get() = def.type
-        override val meta: Meta get() = def.meta
+    private inner class RotTableColumn<T : Any>(val header: ColumnHeader<T>) : Column<T> {
+        override val name: String get() = header.name
+        override val type: KClass<out T> get() = header.type
+        override val meta: Meta get() = header.meta
         override val size: Int get() = rows.size
 
         override fun get(index: Int): T? = rows[index].getValue(name, type)
     }
+
 }
 
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
index 52171625..4961f9bb 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
@@ -3,16 +3,44 @@ package hep.dataforge.tables
 import hep.dataforge.meta.Meta
 import kotlin.reflect.KClass
 
-interface Table {
-    fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
-    val columns: Map<String, Column<*>>
-    val rows: List<Row>
+//TODO to be removed in 1.3.70
+@Suppress("UNCHECKED_CAST")
+internal fun <T : Any> KClass<T>.cast(value: Any?): T? {
+    return when {
+        value == null -> null
+        !isInstance(value) -> error("Expected type is $this, but found ${value::class}")
+        else -> value as T
+    }
 }
 
-interface Column<out T : Any> {
+typealias TableHeader = List<ColumnHeader<*>>
+
+
+interface Table {
+    fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
+    val columns: Collection<Column<*>>
+    val header: TableHeader get() = columns.toList()
+    val rows: List<Row>
+
+    /**
+     * Apply query to a table and return lazy Flow
+     */
+    //fun find(query: Any): Flow<Row>
+}
+
+operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name }
+
+inline operator fun <reified T : Any> Table.get(row: Int, column: String): T? = getValue(row, column, T::class)
+
+interface ColumnHeader<out T : Any> {
     val name: String
     val type: KClass<out T>
     val meta: Meta
+}
+
+operator fun <T : Any> Table.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
+
+interface Column<out T : Any> : ColumnHeader<T> {
     val size: Int
     operator fun get(index: Int): T?
 }
@@ -20,11 +48,14 @@ interface Column<out T : Any> {
 val Column<*>.indices get() = (0 until size)
 
 operator fun <T : Any> Column<T>.iterator() = iterator {
-    for (i in indices){
+    for (i in indices) {
         yield(get(i))
     }
 }
 
 interface Row {
     fun <T : Any> getValue(column: String, type: KClass<out T>): T?
-}
\ No newline at end of file
+}
+
+inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class)
+operator fun <T : Any> Row.get(column: Column<T>): T? = getValue(column.name, column.type)
\ No newline at end of file
diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
new file mode 100644
index 00000000..9b9801c8
--- /dev/null
+++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
@@ -0,0 +1,36 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KClass
+import kotlin.reflect.KProperty
+import kotlin.reflect.full.cast
+import kotlin.reflect.full.isSubclassOf
+
+@Suppress("UNCHECKED_CAST")
+fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
+    return if (type.isSubclassOf(this.type)) {
+        this as Column<T>
+    } else {
+        CastColumn(this, type)
+    }
+}
+
+class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) : Column<T> {
+    override val name: String get() = origin.name
+    override val meta: Meta get() = origin.meta
+    override val size: Int get() = origin.size
+
+
+    override fun get(index: Int): T? = type.cast(origin[index])
+}
+
+class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T> {
+        val name = property.name
+        return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type)
+    }
+}
+
+operator fun <T : Any> Collection<Column<*>>.get(header: ColumnHeader<T>): Column<T>? =
+    find { it.name == header.name }?.cast(header.type)
diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt
deleted file mode 100644
index 21453426..00000000
--- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package hep.dataforge.tables
-
-import hep.dataforge.meta.Meta
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KClass
-import kotlin.reflect.KProperty
-import kotlin.reflect.full.cast
-import kotlin.reflect.full.isSubclassOf
-
-class TableAccessor(val table: Table) : Table by table {
-    inline fun <reified T : Any> column() = ColumnProperty(table, T::class)
-}
-
-@Suppress("UNCHECKED_CAST")
-fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
-    return if (type.isSubclassOf(this.type)) {
-        this as Column<T>
-    } else {
-        ColumnWrapper(this, type)
-    }
-}
-
-class ColumnWrapper<T : Any>(val column: Column<*>, override val type: KClass<T>) : Column<T> {
-    override val name: String get() = column.name
-    override val meta: Meta get() = column.meta
-    override val size: Int get() = column.size
-
-
-    override fun get(index: Int): T? = type.cast(column[index])
-}
-
-class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>?> {
-    override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T>? {
-        val name = property.name
-        return table.columns[name]?.cast(type)
-    }
-}
\ No newline at end of file

From 126e59f81a134723c8717393c45e384a968ceef9 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sat, 8 Feb 2020 22:01:56 +0300
Subject: [PATCH 29/41] Bottom type check for tables

---
 .../kotlin/hep/dataforge/tables/ColumnTable.kt         |  4 ++--
 .../kotlin/hep/dataforge/tables/ColumnTableBuilder.kt  | 10 +++++-----
 .../commonMain/kotlin/hep/dataforge/tables/RowTable.kt |  6 +++---
 .../commonMain/kotlin/hep/dataforge/tables/Table.kt    |  8 ++++----
 .../jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt  |  2 +-
 5 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
index 07ea5d94..c48b30f8 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
@@ -5,7 +5,7 @@ import kotlin.reflect.KClass
 /**
  * @param C bottom type for all columns in the table
  */
-class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table {
+class ColumnTable<C : Any>(override val columns: Collection<Column<C>>) : Table<C> {
     private val rowsNum = columns.first().size
 
     init {
@@ -21,7 +21,7 @@ class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table {
     }
 }
 
-internal class VirtualRow(val table: Table, val index: Int) : Row {
+internal class VirtualRow<C : Any>(val table: Table<C>, val index: Int) : Row {
     override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type)
 }
 
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
index b453c957..78a32366 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
@@ -3,10 +3,10 @@ package hep.dataforge.tables
 import hep.dataforge.meta.Meta
 import kotlin.reflect.KClass
 
-class ColumnTableBuilder(val size: Int) : Table {
-    private val _columns = ArrayList<Column<*>>()
+class ColumnTableBuilder<C: Any>(val size: Int) : Table<C> {
+    private val _columns = ArrayList<Column<C>>()
 
-    override val columns: List<Column<*>> get() = _columns
+    override val columns: List<Column<C>> get() = _columns
     override val rows: List<Row> get() = (0 until size).map {
         VirtualRow(this, it)
     }
@@ -19,7 +19,7 @@ class ColumnTableBuilder(val size: Int) : Table {
     /**
      * Add a fixed column to the end of the table
      */
-    fun add(column: Column<*>) {
+    fun add(column: Column<C>) {
         require(column.size == this.size) { "Required column size $size, but found ${column.size}" }
         _columns.add(column)
     }
@@ -27,7 +27,7 @@ class ColumnTableBuilder(val size: Int) : Table {
     /**
      * Insert a column at [index]
      */
-    fun insert(index: Int, column: Column<*>) {
+    fun insert(index: Int, column: Column<C>) {
         require(column.size == this.size) { "Required column size $size, but found ${column.size}" }
         _columns.add(index, column)
     }
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
index c640aade..bc2ca808 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
@@ -4,13 +4,13 @@ import hep.dataforge.meta.Meta
 import kotlin.reflect.KClass
 
 
-class RowTable<R : Row>(override val rows: List<R>, override val header: TableHeader) : Table {
+class RowTable<C: Any, R : Row>(override val rows: List<R>, override val header:  List<ColumnHeader<C>>) : Table<C> {
     override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? =
         rows[row].getValue(column, type)
 
-    override val columns: List<Column<*>> get() = header.map { RotTableColumn(it) }
+    override val columns: List<Column<C>> get() = header.map { RotTableColumn(it) }
 
-    private inner class RotTableColumn<T : Any>(val header: ColumnHeader<T>) : Column<T> {
+    private inner class RotTableColumn<T : C>(val header: ColumnHeader<T>) : Column<T> {
         override val name: String get() = header.name
         override val type: KClass<out T> get() = header.type
         override val meta: Meta get() = header.meta
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
index 4961f9bb..96d7964d 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
@@ -16,9 +16,9 @@ internal fun <T : Any> KClass<T>.cast(value: Any?): T? {
 typealias TableHeader = List<ColumnHeader<*>>
 
 
-interface Table {
+interface Table<out C: Any> {
     fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
-    val columns: Collection<Column<*>>
+    val columns: Collection<Column<C>>
     val header: TableHeader get() = columns.toList()
     val rows: List<Row>
 
@@ -30,7 +30,7 @@ interface Table {
 
 operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name }
 
-inline operator fun <reified T : Any> Table.get(row: Int, column: String): T? = getValue(row, column, T::class)
+inline operator fun <C: Any, reified T : C> Table<C>.get(row: Int, column: String): T? = getValue(row, column, T::class)
 
 interface ColumnHeader<out T : Any> {
     val name: String
@@ -38,7 +38,7 @@ interface ColumnHeader<out T : Any> {
     val meta: Meta
 }
 
-operator fun <T : Any> Table.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
+operator fun <C: Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
 
 interface Column<out T : Any> : ColumnHeader<T> {
     val size: Int
diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
index 9b9801c8..1ac7e138 100644
--- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
+++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
@@ -25,7 +25,7 @@ class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) :
     override fun get(index: Int): T? = type.cast(origin[index])
 }
 
-class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> {
+class ColumnProperty<C: Any, T : C>(val table: Table<C>, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> {
     override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T> {
         val name = property.name
         return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type)

From cd4f07267b20868f0ff6b0bcb2eb7895a271eb3a Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Mon, 10 Feb 2020 21:49:53 +0300
Subject: [PATCH 30/41] Mutable tables

---
 .../kotlin/hep/dataforge/io/FileEnvelope.kt   |   2 +
 .../kotlin/hep/dataforge/meta/Meta.kt         |   2 +-
 .../meta/scheme/ConfigurableDelegate.kt       |   4 +-
 .../hep/dataforge/tables/ColumnScheme.kt      |  11 +-
 .../kotlin/hep/dataforge/tables/MapRow.kt     |   2 +-
 .../kotlin/hep/dataforge/tables/MetaQuery.kt  |  11 --
 ...nTableBuilder.kt => MutableColumnTable.kt} |   5 +-
 .../hep/dataforge/tables/MutableTable.kt      |  43 +++++++
 .../kotlin/hep/dataforge/tables/RowTable.kt   |  25 ++--
 .../kotlin/hep/dataforge/tables/Table.kt      |  28 ++--
 .../hep/dataforge/tables/io/TextRows.kt       | 120 ++++++++++++++++++
 .../hep/dataforge/tables/io/TextRowsTest.kt   |   6 +
 12 files changed, 222 insertions(+), 37 deletions(-)
 delete mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt
 rename dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/{ColumnTableBuilder.kt => MutableColumnTable.kt} (93%)
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
 create mode 100644 dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt

diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
index 3b34c26c..21cca102 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt
@@ -2,10 +2,12 @@ package hep.dataforge.io
 
 import hep.dataforge.meta.Meta
 import kotlinx.io.Binary
+import kotlinx.io.ExperimentalIoApi
 import kotlinx.io.FileBinary
 import kotlinx.io.read
 import java.nio.file.Path
 
+@ExperimentalIoApi
 class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope {
     //TODO do not like this constructor. Hope to replace it later
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index d917559d..93e416de 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -208,7 +208,7 @@ val MetaItem<*>?.int get() = number?.toInt()
 val MetaItem<*>?.long get() = number?.toLong()
 val MetaItem<*>?.short get() = number?.toShort()
 
-inline fun <reified E : Enum<E>> MetaItem<*>?.enum() = if (this is ValueItem && this.value is EnumValue<*>) {
+inline fun <reified E : Enum<E>> MetaItem<*>?.enum(): E? = if (this is ValueItem && this.value is EnumValue<*>) {
     this.value.value as E
 } else {
     string?.let { enumValueOf<E>(it) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
index 9b86abba..c6e9a44a 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
@@ -168,8 +168,8 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any
 /**
  * Enum delegate
  */
-inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E?> =
-    item(default, key).transform { it.enum<E>() }
+inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> =
+    item(default, key).transform { it.enum<E>() ?: default }
 
 /*
  * Extra delegates for special cases
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
index aa0d2aec..2b65b234 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
@@ -2,7 +2,16 @@ package hep.dataforge.tables
 
 import hep.dataforge.meta.scheme.Scheme
 import hep.dataforge.meta.scheme.SchemeSpec
+import hep.dataforge.meta.scheme.enum
+import hep.dataforge.meta.scheme.string
+import hep.dataforge.values.ValueType
+
+open class ColumnScheme : Scheme() {
+    var title by string()
 
-class ColumnScheme : Scheme() {
     companion object : SchemeSpec<ColumnScheme>(::ColumnScheme)
+}
+
+class ValueColumnScheme : ColumnScheme() {
+    var valueType by enum(ValueType.STRING)
 }
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
index 76002e9d..421241d4 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
@@ -2,7 +2,7 @@ package hep.dataforge.tables
 
 import kotlin.reflect.KClass
 
-class MapRow(val values: Map<String, Any>) : Row {
+inline class MapRow(val values: Map<String, Any?>) : Row {
     override fun <T : Any> getValue(column: String, type: KClass<out T>): T? {
         val value = values[column]
         return type.cast(value)
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt
deleted file mode 100644
index cf07a5c8..00000000
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package hep.dataforge.tables
-
-import hep.dataforge.meta.scheme.Scheme
-import hep.dataforge.meta.scheme.SchemeSpec
-import hep.dataforge.meta.scheme.string
-
-class MetaQuery : Scheme() {
-    var field by string()
-
-    companion object : SchemeSpec<MetaQuery>(::MetaQuery)
-}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt
similarity index 93%
rename from dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
rename to dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt
index 78a32366..d7a360c9 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt
@@ -3,7 +3,10 @@ package hep.dataforge.tables
 import hep.dataforge.meta.Meta
 import kotlin.reflect.KClass
 
-class ColumnTableBuilder<C: Any>(val size: Int) : Table<C> {
+/**
+ * Mutable table with a fixed size, but dynamic columns
+ */
+class MutableColumnTable<C: Any>(val size: Int) : Table<C> {
     private val _columns = ArrayList<Column<C>>()
 
     override val columns: List<Column<C>> get() = _columns
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt
new file mode 100644
index 00000000..686e3c34
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt
@@ -0,0 +1,43 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import kotlin.reflect.KClass
+
+class SimpleColumnHeader<T : Any>(
+    override val name: String,
+    override val type: KClass<out T>,
+    override val meta: Meta
+) : ColumnHeader<T>
+
+class MutableTable<C : Any>(
+    override val rows: MutableList<Row>,
+    override val header: MutableList<ColumnHeader<C>>
+) : RowTable<C, Row>(rows, header) {
+    fun <T : C> column(name: String, type: KClass<out T>, meta: Meta): ColumnHeader<T> {
+        val column = SimpleColumnHeader(name, type, meta)
+        header.add(column)
+        return column
+    }
+
+    inline fun <reified T : C> column(
+        name: String,
+        noinline columnMetaBuilder: ColumnScheme.() -> Unit
+    ): ColumnHeader<T> {
+        return column(name, T::class, ColumnScheme(columnMetaBuilder).toMeta())
+    }
+
+    fun row(block: MutableMap<String, Any?>.() -> Unit): Row {
+        val map = HashMap<String, Any?>().apply(block)
+        val row = MapRow(map)
+        rows.add(row)
+        return row
+    }
+
+    operator fun <T : Any> MutableMap<String, Any?>.set(header: ColumnHeader<T>, value: T?) {
+        set(header.name, value)
+    }
+}
+
+fun <C : Any> Table<C>.edit(block: MutableTable<C>.() -> Unit): Table<C> {
+    return MutableTable(rows.toMutableList(), header.toMutableList()).apply(block)
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
index bc2ca808..98d7d4e1 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
@@ -1,23 +1,24 @@
 package hep.dataforge.tables
 
 import hep.dataforge.meta.Meta
+import kotlinx.coroutines.flow.toList
 import kotlin.reflect.KClass
 
+internal class RowTableColumn<C : Any, T : C>(val table: Table<C>, val header: ColumnHeader<T>) : Column<T> {
+    override val name: String get() = header.name
+    override val type: KClass<out T> get() = header.type
+    override val meta: Meta get() = header.meta
+    override val size: Int get() = table.rows.size
 
-class RowTable<C: Any, R : Row>(override val rows: List<R>, override val header:  List<ColumnHeader<C>>) : Table<C> {
+    override fun get(index: Int): T? = table.rows[index].getValue(name, type)
+}
+
+open class RowTable<C : Any, R : Row>(override val rows: List<R>, override val header: List<ColumnHeader<C>>) :
+    Table<C> {
     override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? =
         rows[row].getValue(column, type)
 
-    override val columns: List<Column<C>> get() = header.map { RotTableColumn(it) }
-
-    private inner class RotTableColumn<T : C>(val header: ColumnHeader<T>) : Column<T> {
-        override val name: String get() = header.name
-        override val type: KClass<out T> get() = header.type
-        override val meta: Meta get() = header.meta
-        override val size: Int get() = rows.size
-
-        override fun get(index: Int): T? = rows[index].getValue(name, type)
-    }
-
+    override val columns: List<Column<C>> get() = header.map { RowTableColumn(this, it) }
 }
 
+suspend fun Rows.collect(): Table<*> = this as? Table<*> ?: RowTable(rowFlow().toList(), header)
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
index 96d7964d..4e8215e0 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
@@ -1,6 +1,8 @@
 package hep.dataforge.tables
 
 import hep.dataforge.meta.Meta
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
 import kotlin.reflect.KClass
 
 //TODO to be removed in 1.3.70
@@ -13,24 +15,34 @@ internal fun <T : Any> KClass<T>.cast(value: Any?): T? {
     }
 }
 
-typealias TableHeader = List<ColumnHeader<*>>
+typealias TableHeader<C> = List<ColumnHeader<C>>
 
+/**
+ * Finite or infinite row set. Rows are produced in a lazy suspendable [Flow].
+ * Each row must contain at least all the fields mentioned in [header].
+ */
+interface Rows {
+    val header: TableHeader<*>
+    fun rowFlow(): Flow<Row>
+}
 
-interface Table<out C: Any> {
+interface Table<out C : Any> : Rows {
     fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
     val columns: Collection<Column<C>>
-    val header: TableHeader get() = columns.toList()
+    override val header: TableHeader<C> get() = columns.toList()
     val rows: List<Row>
+    override fun rowFlow(): Flow<Row> = rows.asFlow()
 
     /**
-     * Apply query to a table and return lazy Flow
+     * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty.
      */
-    //fun find(query: Any): Flow<Row>
+    //fun select(query: Any): Flow<Row> = error("Query of type ${query::class} is not supported by this table")
 }
 
 operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name }
 
-inline operator fun <C: Any, reified T : C> Table<C>.get(row: Int, column: String): T? = getValue(row, column, T::class)
+inline operator fun <C : Any, reified T : C> Table<C>.get(row: Int, column: String): T? =
+    getValue(row, column, T::class)
 
 interface ColumnHeader<out T : Any> {
     val name: String
@@ -38,7 +50,7 @@ interface ColumnHeader<out T : Any> {
     val meta: Meta
 }
 
-operator fun <C: Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
+operator fun <C : Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
 
 interface Column<out T : Any> : ColumnHeader<T> {
     val size: Int
@@ -58,4 +70,4 @@ interface Row {
 }
 
 inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class)
-operator fun <T : Any> Row.get(column: Column<T>): T? = getValue(column.name, column.type)
\ No newline at end of file
+operator fun <T : Any> Row.get(column: ColumnHeader<T>): T? = getValue(column.name, column.type)
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
new file mode 100644
index 00000000..f4e73702
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
@@ -0,0 +1,120 @@
+package hep.dataforge.tables.io
+
+import hep.dataforge.meta.get
+import hep.dataforge.meta.int
+import hep.dataforge.meta.string
+import hep.dataforge.tables.*
+import hep.dataforge.values.*
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flow
+import kotlinx.io.Binary
+import kotlinx.io.ExperimentalIoApi
+import kotlinx.io.Output
+import kotlinx.io.RandomAccessBinary
+import kotlinx.io.text.forEachUtf8Line
+import kotlinx.io.text.readUtf8Line
+import kotlinx.io.text.writeUtf8String
+import kotlin.reflect.KClass
+
+private fun readLine(header: List<ColumnHeader<Value>>, line: String): Row {
+    val values = line.split("\\s+".toRegex()).map { it.parseValue() }
+
+    if (values.size == header.size) {
+        val map = header.map { it.name }.zip(values).toMap()
+        return MapRow(map)
+    } else {
+        error("Can't read line $line. Expected ${header.size} values in a line, but found ${values.size}")
+    }
+}
+
+
+@ExperimentalIoApi
+class TextRows(override val header: List<ColumnHeader<Value>>, val binary: Binary) : Rows {
+
+    override fun rowFlow(): Flow<Row> = binary.read {
+        flow {
+            forEachUtf8Line { line ->
+                if (line.isNotBlank()) {
+                    val row = readLine(header, line)
+                    emit(row)
+                }
+            }
+        }
+    }
+}
+
+@ExperimentalIoApi
+class TextTable(
+    override val header: List<ColumnHeader<Value>>,
+    val binary: RandomAccessBinary,
+    val index: List<Int>
+) : Table<Value> {
+
+    override val columns: Collection<Column<Value>> get() = header.map { RowTableColumn(this, it) }
+
+    override val rows: List<Row> get() = index.map { readAt(it) }
+
+    override fun rowFlow(): Flow<Row> = TextRows(header, binary).rowFlow()
+
+    private fun readAt(offset: Int): Row {
+        return binary.read(offset) {
+            val line = readUtf8Line()
+            return@read readLine(header, line)
+        }
+    }
+
+    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
+        val offset = index[row]
+        return type.cast(readAt(offset)[column])
+    }
+}
+
+fun Output.writeValue(value: Value, width: Int, left: Boolean = true) {
+    require(width > 5) { "Width could not be less than 5" }
+    val str: String = when (value.type) {
+        ValueType.NUMBER -> value.number.toString() //TODO apply decimal format
+        ValueType.STRING -> value.string.take(width)
+        ValueType.BOOLEAN -> if (value.boolean) {
+            "true"
+        } else {
+            "false"
+        }
+        ValueType.NULL -> "@null"
+    }
+    val padded = if (left) {
+        str.padEnd(width)
+    } else {
+        str.padStart(width)
+    }
+    writeUtf8String(padded)
+}
+
+val ColumnHeader<Value>.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) }
+
+private val ColumnHeader<Value>.width: Int
+    get() = meta["columnWidth"].int ?: when (valueType) {
+        ValueType.NUMBER -> 8
+        ValueType.STRING -> 16
+        ValueType.BOOLEAN -> 5
+        ValueType.NULL -> 5
+        null -> 16
+    }
+
+
+/**
+ * Write rows without header to the output
+ */
+suspend fun Output.writeRows(rows: Rows) {
+    @Suppress("UNCHECKED_CAST") val header = rows.header.map {
+        if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader<Value>)
+    }
+    val widths: List<Int> = header.map {
+        it.width
+    }
+    rows.rowFlow().collect { row ->
+        header.forEachIndexed { index, columnHeader ->
+            writeValue(row[columnHeader] ?: Null, widths[index])
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
new file mode 100644
index 00000000..010f903f
--- /dev/null
+++ b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
@@ -0,0 +1,6 @@
+package hep.dataforge.tables.io
+
+
+class TextRowsTest{
+
+}
\ No newline at end of file

From dfea68b65c9b94f4e53002b71fb2eea144302ac9 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 16 Feb 2020 15:18:09 +0300
Subject: [PATCH 31/41] Text tables test working

---
 .../kotlin/hep/dataforge/io/Envelope.kt       |  2 +-
 .../hep/dataforge/io/EnvelopeBuilder.kt       | 20 ++++-
 .../kotlin/hep/dataforge/meta/MutableMeta.kt  |  6 +-
 .../hep/dataforge/tables/ColumnHeader.kt      | 36 ++++++++
 .../hep/dataforge/tables/ColumnTable.kt       | 12 ++-
 .../kotlin/hep/dataforge/tables/MapRow.kt     |  4 +-
 .../dataforge/tables/MutableColumnTable.kt    |  4 +-
 .../hep/dataforge/tables/MutableTable.kt      | 25 +++---
 .../kotlin/hep/dataforge/tables/RowTable.kt   |  7 +-
 .../kotlin/hep/dataforge/tables/Table.kt      | 37 ++++----
 .../hep/dataforge/tables/io/TextRows.kt       | 87 ++++++++++++-------
 .../dataforge/tables/io/textTableEnvelope.kt  | 42 +++++++++
 .../hep/dataforge/tables/io/TextRowsTest.kt   |  6 --
 .../kotlin/hep/dataforge/tables/CastColumn.kt |  6 +-
 .../hep/dataforge/tables/io/TextRowsTest.kt   | 39 +++++++++
 15 files changed, 243 insertions(+), 90 deletions(-)
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt
 create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt
 delete mode 100644 dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
 create mode 100644 dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt

diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
index adffdbf7..d7c60116 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt
@@ -28,7 +28,7 @@ interface Envelope {
         /**
          * Build a static envelope using provided builder
          */
-        operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build()
+        inline operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build()
     }
 }
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
index 0f12c213..94f4e59d 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt
@@ -18,8 +18,15 @@ class EnvelopeBuilder {
         metaBuilder.update(meta)
     }
 
+    /**
+     * The general purpose of the envelope
+     */
     var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY)
     var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY)
+
+    /**
+     * Data unique identifier to bypass identity checks
+     */
     var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY)
     var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY)
     var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY)
@@ -32,5 +39,14 @@ class EnvelopeBuilder {
         data = ArrayBinary.write(builder = block)
     }
 
-    internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
-}
\ No newline at end of file
+    fun build() = SimpleEnvelope(metaBuilder.seal(), data)
+
+}
+
+//@ExperimentalContracts
+//suspend fun EnvelopeBuilder.buildData(block: suspend Output.() -> Unit): Binary{
+//    contract {
+//        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
+//    }
+//    val scope = CoroutineScope(coroutineContext)
+//}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
index 88819f2c..043283e3 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt
@@ -115,6 +115,8 @@ operator fun MutableMeta<*>.set(name: NameToken, value: Any?) = set(name.asName(
 
 operator fun MutableMeta<*>.set(key: String, value: Any?) = set(key.toName(), value)
 
+operator fun MutableMeta<*>.set(key: String, index: String, value: Any?) = set(key.toName().withIndex(index), value)
+
 /**
  * Update existing mutable node with another node. The rules are following:
  *  * value replaces anything
@@ -161,7 +163,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = set
 /**
  * Append the node with a same-name-sibling, automatically generating numerical index
  */
-fun <M: MutableMeta<M>> M.append(name: Name, value: Any?) {
+fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
     require(!name.isEmpty()) { "Name could not be empty for append operation" }
     val newIndex = name.last()!!.index
     if (newIndex.isNotEmpty()) {
@@ -172,4 +174,4 @@ fun <M: MutableMeta<M>> M.append(name: Name, value: Any?) {
     }
 }
 
-fun <M: MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value)
\ No newline at end of file
+fun <M : MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value)
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt
new file mode 100644
index 00000000..2023d11b
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt
@@ -0,0 +1,36 @@
+package hep.dataforge.tables
+
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.get
+import hep.dataforge.meta.int
+import hep.dataforge.meta.string
+import hep.dataforge.values.Value
+import hep.dataforge.values.ValueType
+import kotlin.reflect.KClass
+
+typealias TableHeader<C> = List<ColumnHeader<C>>
+
+typealias ValueTableHeader = List<ColumnHeader<Value>>
+
+interface ColumnHeader<out T : Any> {
+    val name: String
+    val type: KClass<out T>
+    val meta: Meta
+}
+
+data class SimpleColumnHeader<T : Any>(
+    override val name: String,
+    override val type: KClass<out T>,
+    override val meta: Meta
+) : ColumnHeader<T>
+
+val ColumnHeader<Value>.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) }
+
+val ColumnHeader<Value>.textWidth: Int
+    get() = meta["columnWidth"].int ?: when (valueType) {
+        ValueType.NUMBER -> 8
+        ValueType.STRING -> 16
+        ValueType.BOOLEAN -> 5
+        ValueType.NULL -> 5
+        null -> 16
+    }
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
index c48b30f8..dbb90cf2 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt
@@ -12,16 +12,20 @@ class ColumnTable<C : Any>(override val columns: Collection<Column<C>>) : Table<
         require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" }
     }
 
-    override val rows: List<Row>
+    override val rows: List<Row<C>>
         get() = (0 until rowsNum).map { VirtualRow(this, it) }
 
-    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
+    override fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? {
         val value = columns[column]?.get(row)
         return type.cast(value)
     }
 }
 
-internal class VirtualRow<C : Any>(val table: Table<C>, val index: Int) : Row {
-    override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type)
+internal class VirtualRow<C : Any>(val table: Table<C>, val index: Int) : Row<C> {
+    override fun <T : C> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type)
+
+//    override fun <T : C> get(columnHeader: ColumnHeader<T>): T? {
+//        return table.co[columnHeader][index]
+//    }
 }
 
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
index 421241d4..04c7d3a4 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt
@@ -2,8 +2,8 @@ package hep.dataforge.tables
 
 import kotlin.reflect.KClass
 
-inline class MapRow(val values: Map<String, Any?>) : Row {
-    override fun <T : Any> getValue(column: String, type: KClass<out T>): T? {
+inline class MapRow<C: Any>(val values: Map<String, C?>) : Row<C> {
+    override fun <T : C> getValue(column: String, type: KClass<out T>): T? {
         val value = values[column]
         return type.cast(value)
     }
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt
index d7a360c9..8a9b06bc 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt
@@ -10,11 +10,11 @@ class MutableColumnTable<C: Any>(val size: Int) : Table<C> {
     private val _columns = ArrayList<Column<C>>()
 
     override val columns: List<Column<C>> get() = _columns
-    override val rows: List<Row> get() = (0 until size).map {
+    override val rows: List<Row<C>> get() = (0 until size).map {
         VirtualRow(this, it)
     }
 
-    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
+    override fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? {
         val value = columns[column]?.get(row)
         return type.cast(value)
     }
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt
index 686e3c34..aa2d9abf 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt
@@ -1,18 +1,14 @@
 package hep.dataforge.tables
 
 import hep.dataforge.meta.Meta
+import hep.dataforge.values.Value
 import kotlin.reflect.KClass
 
-class SimpleColumnHeader<T : Any>(
-    override val name: String,
-    override val type: KClass<out T>,
-    override val meta: Meta
-) : ColumnHeader<T>
-
 class MutableTable<C : Any>(
-    override val rows: MutableList<Row>,
+    override val rows: MutableList<Row<C>>,
     override val header: MutableList<ColumnHeader<C>>
-) : RowTable<C, Row>(rows, header) {
+) : RowTable<C>(rows, header) {
+
     fun <T : C> column(name: String, type: KClass<out T>, meta: Meta): ColumnHeader<T> {
         val column = SimpleColumnHeader(name, type, meta)
         header.add(column)
@@ -21,23 +17,24 @@ class MutableTable<C : Any>(
 
     inline fun <reified T : C> column(
         name: String,
-        noinline columnMetaBuilder: ColumnScheme.() -> Unit
+        noinline columnMetaBuilder: ColumnScheme.() -> Unit = {}
     ): ColumnHeader<T> {
         return column(name, T::class, ColumnScheme(columnMetaBuilder).toMeta())
     }
 
-    fun row(block: MutableMap<String, Any?>.() -> Unit): Row {
-        val map = HashMap<String, Any?>().apply(block)
+    fun row(map: Map<String, C?>): Row<C> {
         val row = MapRow(map)
         rows.add(row)
         return row
     }
 
-    operator fun <T : Any> MutableMap<String, Any?>.set(header: ColumnHeader<T>, value: T?) {
-        set(header.name, value)
-    }
+    fun <T : C> row(vararg pairs: Pair<ColumnHeader<T>, T>): Row<C> =
+        row(pairs.associate { it.first.name to it.second })
 }
 
+fun MutableTable<Value>.row(vararg pairs: Pair<ColumnHeader<Value>, Any?>): Row<Value> =
+    row(pairs.associate { it.first.name to Value.of(it.second) })
+
 fun <C : Any> Table<C>.edit(block: MutableTable<C>.() -> Unit): Table<C> {
     return MutableTable(rows.toMutableList(), header.toMutableList()).apply(block)
 }
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
index 98d7d4e1..c565d9cd 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt
@@ -13,12 +13,11 @@ internal class RowTableColumn<C : Any, T : C>(val table: Table<C>, val header: C
     override fun get(index: Int): T? = table.rows[index].getValue(name, type)
 }
 
-open class RowTable<C : Any, R : Row>(override val rows: List<R>, override val header: List<ColumnHeader<C>>) :
-    Table<C> {
-    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? =
+open class RowTable<C : Any>(override val rows: List<Row<C>>, override val header: List<ColumnHeader<C>>) : Table<C> {
+    override fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? =
         rows[row].getValue(column, type)
 
     override val columns: List<Column<C>> get() = header.map { RowTableColumn(this, it) }
 }
 
-suspend fun Rows.collect(): Table<*> = this as? Table<*> ?: RowTable(rowFlow().toList(), header)
\ No newline at end of file
+suspend fun <C : Any> Rows<C>.collect(): Table<C> = this as? Table<C> ?: RowTable(rowFlow().toList(), header)
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
index 4e8215e0..71469984 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
@@ -1,6 +1,5 @@
 package hep.dataforge.tables
 
-import hep.dataforge.meta.Meta
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.asFlow
 import kotlin.reflect.KClass
@@ -15,28 +14,30 @@ internal fun <T : Any> KClass<T>.cast(value: Any?): T? {
     }
 }
 
-typealias TableHeader<C> = List<ColumnHeader<C>>
-
 /**
  * Finite or infinite row set. Rows are produced in a lazy suspendable [Flow].
  * Each row must contain at least all the fields mentioned in [header].
  */
-interface Rows {
-    val header: TableHeader<*>
-    fun rowFlow(): Flow<Row>
+interface Rows<C : Any> {
+    val header: TableHeader<C>
+    fun rowFlow(): Flow<Row<C>>
 }
 
-interface Table<out C : Any> : Rows {
-    fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
+interface Table<C : Any> : Rows<C> {
+    fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T?
     val columns: Collection<Column<C>>
     override val header: TableHeader<C> get() = columns.toList()
-    val rows: List<Row>
-    override fun rowFlow(): Flow<Row> = rows.asFlow()
+    val rows: List<Row<C>>
+    override fun rowFlow(): Flow<Row<C>> = rows.asFlow()
 
     /**
      * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty.
      */
     //fun select(query: Any): Flow<Row> = error("Query of type ${query::class} is not supported by this table")
+    companion object {
+        inline operator fun <T : Any> invoke(block: MutableTable<T>.() -> Unit): Table<T> =
+            MutableTable<T>(arrayListOf(), arrayListOf()).apply(block)
+    }
 }
 
 operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name }
@@ -44,15 +45,9 @@ operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.nam
 inline operator fun <C : Any, reified T : C> Table<C>.get(row: Int, column: String): T? =
     getValue(row, column, T::class)
 
-interface ColumnHeader<out T : Any> {
-    val name: String
-    val type: KClass<out T>
-    val meta: Meta
-}
-
 operator fun <C : Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
 
-interface Column<out T : Any> : ColumnHeader<T> {
+interface Column<T : Any> : ColumnHeader<T> {
     val size: Int
     operator fun get(index: Int): T?
 }
@@ -65,9 +60,9 @@ operator fun <T : Any> Column<T>.iterator() = iterator {
     }
 }
 
-interface Row {
-    fun <T : Any> getValue(column: String, type: KClass<out T>): T?
+interface Row<C: Any> {
+    fun <T : C> getValue(column: String, type: KClass<out T>): T?
 }
 
-inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class)
-operator fun <T : Any> Row.get(column: ColumnHeader<T>): T? = getValue(column.name, column.type)
\ No newline at end of file
+inline operator fun <C : Any, reified T : C> Row<C>.get(column: String): T? = getValue(column, T::class)
+operator fun <C : Any, T : C> Row<C>.get(column: ColumnHeader<T>): T? = getValue(column.name, column.type)
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
index f4e73702..2675b483 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
@@ -1,38 +1,56 @@
 package hep.dataforge.tables.io
 
-import hep.dataforge.meta.get
-import hep.dataforge.meta.int
-import hep.dataforge.meta.string
 import hep.dataforge.tables.*
 import hep.dataforge.values.*
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.toList
 import kotlinx.io.Binary
 import kotlinx.io.ExperimentalIoApi
 import kotlinx.io.Output
 import kotlinx.io.RandomAccessBinary
 import kotlinx.io.text.forEachUtf8Line
 import kotlinx.io.text.readUtf8Line
+import kotlinx.io.text.readUtf8StringUntilDelimiter
 import kotlinx.io.text.writeUtf8String
 import kotlin.reflect.KClass
 
-private fun readLine(header: List<ColumnHeader<Value>>, line: String): Row {
-    val values = line.split("\\s+".toRegex()).map { it.parseValue() }
+/**
+ * Read a lin as a fixed width [Row]
+ */
+private fun readLine(header: ValueTableHeader, line: String): Row<Value> {
+    val values = line.trim().split("\\s+".toRegex()).map { it.lazyParseValue() }
 
     if (values.size == header.size) {
         val map = header.map { it.name }.zip(values).toMap()
         return MapRow(map)
     } else {
-        error("Can't read line $line. Expected ${header.size} values in a line, but found ${values.size}")
+        error("Can't read line \"$line\". Expected ${header.size} values in a line, but found ${values.size}")
     }
 }
 
-
+/**
+ * Finite or infinite [Rows] created from a fixed width text binary
+ */
 @ExperimentalIoApi
-class TextRows(override val header: List<ColumnHeader<Value>>, val binary: Binary) : Rows {
+class TextRows(override val header: ValueTableHeader, val binary: Binary) : Rows<Value> {
 
-    override fun rowFlow(): Flow<Row> = binary.read {
+    /**
+     * A flow of indexes of string start offsets ignoring empty strings
+     */
+    fun indexFlow(): Flow<Int> = binary.read {
+        var counter: Int = 0
+        flow {
+            val string = readUtf8StringUntilDelimiter('\n')
+            counter += string.length
+            if (!string.isBlank()) {
+                emit(counter)
+            }
+        }
+    }
+
+    override fun rowFlow(): Flow<Row<Value>> = binary.read {
         flow {
             forEachUtf8Line { line ->
                 if (line.isNotBlank()) {
@@ -42,35 +60,57 @@ class TextRows(override val header: List<ColumnHeader<Value>>, val binary: Binar
             }
         }
     }
+
+    companion object
 }
 
+/**
+ * Create a row offset index for [TextRows]
+ */
+@ExperimentalIoApi
+suspend fun TextRows.buildRowIndex(): List<Int> = indexFlow().toList()
+
+/**
+ * Finite table created from [RandomAccessBinary] with fixed width text table
+ */
 @ExperimentalIoApi
 class TextTable(
-    override val header: List<ColumnHeader<Value>>,
+    override val header: ValueTableHeader,
     val binary: RandomAccessBinary,
     val index: List<Int>
 ) : Table<Value> {
 
     override val columns: Collection<Column<Value>> get() = header.map { RowTableColumn(this, it) }
 
-    override val rows: List<Row> get() = index.map { readAt(it) }
+    override val rows: List<Row<Value>> get() = index.map { readAt(it) }
 
-    override fun rowFlow(): Flow<Row> = TextRows(header, binary).rowFlow()
+    override fun rowFlow(): Flow<Row<Value>> = TextRows(header, binary).rowFlow()
 
-    private fun readAt(offset: Int): Row {
+    private fun readAt(offset: Int): Row<Value> {
         return binary.read(offset) {
             val line = readUtf8Line()
             return@read readLine(header, line)
         }
     }
 
-    override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
+    override fun <T : Value> getValue(row: Int, column: String, type: KClass<out T>): T? {
         val offset = index[row]
         return type.cast(readAt(offset)[column])
     }
+
+    companion object {
+        suspend operator fun invoke(header: ValueTableHeader, binary: RandomAccessBinary): TextTable {
+            val index = TextRows(header, binary).buildRowIndex()
+            return TextTable(header, binary, index)
+        }
+    }
 }
 
-fun Output.writeValue(value: Value, width: Int, left: Boolean = true) {
+
+/**
+ * Write a fixed width value to the output
+ */
+private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) {
     require(width > 5) { "Width could not be less than 5" }
     val str: String = when (value.type) {
         ValueType.NUMBER -> value.number.toString() //TODO apply decimal format
@@ -90,31 +130,20 @@ fun Output.writeValue(value: Value, width: Int, left: Boolean = true) {
     writeUtf8String(padded)
 }
 
-val ColumnHeader<Value>.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) }
-
-private val ColumnHeader<Value>.width: Int
-    get() = meta["columnWidth"].int ?: when (valueType) {
-        ValueType.NUMBER -> 8
-        ValueType.STRING -> 16
-        ValueType.BOOLEAN -> 5
-        ValueType.NULL -> 5
-        null -> 16
-    }
-
-
 /**
  * Write rows without header to the output
  */
-suspend fun Output.writeRows(rows: Rows) {
+suspend fun Output.writeRows(rows: Rows<Value>) {
     @Suppress("UNCHECKED_CAST") val header = rows.header.map {
         if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader<Value>)
     }
     val widths: List<Int> = header.map {
-        it.width
+        it.textWidth
     }
     rows.rowFlow().collect { row ->
         header.forEachIndexed { index, columnHeader ->
             writeValue(row[columnHeader] ?: Null, widths[index])
         }
+        writeUtf8String("\r\n")
     }
 }
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt
new file mode 100644
index 00000000..29ce27b2
--- /dev/null
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt
@@ -0,0 +1,42 @@
+package hep.dataforge.tables.io
+
+import hep.dataforge.io.Envelope
+import hep.dataforge.meta.*
+import hep.dataforge.tables.SimpleColumnHeader
+import hep.dataforge.tables.Table
+import hep.dataforge.values.Value
+import kotlinx.io.ByteArrayOutput
+import kotlinx.io.EmptyBinary
+import kotlinx.io.ExperimentalIoApi
+import kotlinx.io.asBinary
+
+
+@ExperimentalIoApi
+suspend fun Table<Value>.wrap(): Envelope = Envelope {
+    meta {
+        header.forEachIndexed { index, columnHeader ->
+            set("column", index.toString(), buildMeta {
+                "name" put columnHeader.name
+                if (!columnHeader.meta.isEmpty()) {
+                    "meta" put columnHeader.meta
+                }
+            })
+        }
+    }
+
+    type = "table.value"
+    dataID = "valueTable[${this@wrap.hashCode()}]"
+
+    data = ByteArrayOutput().apply { writeRows(this@wrap) }.toByteArray().asBinary()
+}
+
+@DFExperimental
+@ExperimentalIoApi
+fun TextRows.Companion.readEnvelope(envelope: Envelope): TextRows {
+    val header = envelope.meta.getIndexed("column")
+        .entries.sortedBy { it.key.toInt() }
+        .map { (_, item) ->
+            SimpleColumnHeader(item.node["name"].string!!, Value::class, item.node["meta"].node ?: Meta.EMPTY)
+        }
+    return TextRows(header, envelope.data ?: EmptyBinary)
+}
\ No newline at end of file
diff --git a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
deleted file mode 100644
index 010f903f..00000000
--- a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package hep.dataforge.tables.io
-
-
-class TextRowsTest{
-
-}
\ No newline at end of file
diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
index 1ac7e138..a0bcb75e 100644
--- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
+++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt
@@ -8,7 +8,7 @@ import kotlin.reflect.full.cast
 import kotlin.reflect.full.isSubclassOf
 
 @Suppress("UNCHECKED_CAST")
-fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
+fun <T : Any> Column<*>.cast(type: KClass<out T>): Column<T> {
     return if (type.isSubclassOf(this.type)) {
         this as Column<T>
     } else {
@@ -16,7 +16,7 @@ fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
     }
 }
 
-class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) : Column<T> {
+class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<out T>) : Column<T> {
     override val name: String get() = origin.name
     override val meta: Meta get() = origin.meta
     override val size: Int get() = origin.size
@@ -32,5 +32,5 @@ class ColumnProperty<C: Any, T : C>(val table: Table<C>, val type: KClass<T>) :
     }
 }
 
-operator fun <T : Any> Collection<Column<*>>.get(header: ColumnHeader<T>): Column<T>? =
+operator fun <C: Any, T : C> Collection<Column<C>>.get(header: ColumnHeader<T>): Column<T>? =
     find { it.name == header.name }?.cast(header.type)
diff --git a/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
new file mode 100644
index 00000000..02d97caf
--- /dev/null
+++ b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt
@@ -0,0 +1,39 @@
+package hep.dataforge.tables.io
+
+import hep.dataforge.meta.DFExperimental
+import hep.dataforge.tables.Table
+import hep.dataforge.tables.get
+import hep.dataforge.tables.row
+import hep.dataforge.values.Value
+import hep.dataforge.values.int
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.runBlocking
+import kotlinx.io.ExperimentalIoApi
+import kotlinx.io.toByteArray
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+@DFExperimental
+@ExperimentalIoApi
+class TextRowsTest {
+    val table = Table<Value> {
+        val a = column<Value>("a")
+        val b = column<Value>("b")
+        row(a to 1, b to "b1")
+        row(a to 2, b to "b2")
+    }
+
+    @Test
+    fun testTableWriteRead() {
+        runBlocking {
+            val envelope = table.wrap()
+            val string = envelope.data!!.toByteArray().decodeToString()
+            println(string)
+            val table = TextRows.readEnvelope(envelope)
+            val rows = table.rowFlow().toList()
+            assertEquals(1, rows[0]["a"]?.int)
+            assertEquals("b2", rows[1]["b"]?.string)
+        }
+    }
+}
\ No newline at end of file

From 8dfd56f02ec2b3cc4527d33af9753a08c6a27feb Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Mon, 17 Feb 2020 14:46:03 +0300
Subject: [PATCH 32/41] Initial API

---
 build.gradle.kts                              |  2 +-
 .../hep/dataforge/meta/MetaDelegateTest.kt    | 28 ++++++++++---------
 2 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 14ccd3e9..82d402e2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-8")
+val dataforgeVersion by extra("0.1.5-dev-9")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
index 2461c363..4cf2002f 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
@@ -11,25 +11,27 @@ class MetaDelegateTest {
         NO
     }
 
+    class InnerSpec : Scheme() {
+        var innerValue by string()
+        companion object: SchemeSpec<InnerSpec>(::InnerSpec)
+    }
+
+    class TestScheme : Scheme() {
+        var myValue by string()
+        var safeValue by double(2.2)
+        var enumValue by enum(TestEnum.YES)
+        var inner by spec(InnerSpec)
+        companion object: SchemeSpec<TestScheme>(::TestScheme)
+    }
+
     @Test
     fun delegateTest() {
 
-        class InnerSpec : Scheme() {
-            var innerValue by string()
-        }
-
-        val innerSpec = object : SchemeSpec<InnerSpec>(::InnerSpec){}
-
-        val testObject = object : Scheme(Config()) {
-            var myValue by string()
-            var safeValue by double(2.2)
-            var enumValue by enum(TestEnum.YES)
-            var inner by spec(innerSpec)
-        }
+        val testObject = TestScheme.empty()
         testObject.config["myValue"] = "theString"
         testObject.enumValue = TestEnum.NO
 
-        testObject.inner = innerSpec { innerValue = "ddd" }
+        testObject.inner = InnerSpec { innerValue = "ddd" }
 
         assertEquals("theString", testObject.myValue)
         assertEquals(TestEnum.NO, testObject.enumValue)

From a2e8d4f018de3b847df7fb6cd1489816eb4bf24d Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sat, 22 Feb 2020 22:02:58 +0300
Subject: [PATCH 33/41] Basic design for property builders

---
 .../kotlin/hep/dataforge/meta/MetaBuilder.kt  |  7 ++
 .../meta/transformations/MetaCaster.kt        | 75 +++++++++++++++++++
 .../hep/dataforge/values/valueExtensions.kt   |  3 +-
 .../kotlin/hep/dataforge/tables/Table.kt      |  2 +-
 4 files changed, 84 insertions(+), 3 deletions(-)
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt

diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
index c5ad3831..243d40bf 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
@@ -141,8 +141,15 @@ fun Meta.edit(builder: MetaBuilder.() -> Unit): MetaBuilder = builder().apply(bu
 /**
  * Build a [MetaBuilder] using given transformation
  */
+@Deprecated("To be replaced with fake constructor", ReplaceWith("Meta"))
 fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
 
+/**
+ * Build a [MetaBuilder] using given transformation
+ */
+@Suppress("FunctionName")
+fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
+
 /**
  * Build meta using given source meta as a base
  */
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt
new file mode 100644
index 00000000..21785980
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt
@@ -0,0 +1,75 @@
+package hep.dataforge.meta.transformations
+
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaItem
+import hep.dataforge.meta.get
+import hep.dataforge.meta.value
+import hep.dataforge.values.*
+
+/**
+ * A converter of generic object to and from [MetaItem]
+ */
+interface MetaCaster<T : Any> {
+    fun itemToObject(item: MetaItem<*>): T
+    fun objectToMetaItem(obj: T): MetaItem<*>
+
+    companion object {
+
+        val meta = object : MetaCaster<Meta> {
+            override fun itemToObject(item: MetaItem<*>): Meta = when (item) {
+                is MetaItem.NodeItem -> item.node
+                is MetaItem.ValueItem -> item.value.toMeta()
+            }
+
+            override fun objectToMetaItem(obj: Meta): MetaItem<*> = MetaItem.NodeItem(obj)
+        }
+
+        val value = object : MetaCaster<Value> {
+            override fun itemToObject(item: MetaItem<*>): Value = when (item) {
+                is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
+                is MetaItem.ValueItem -> item.value
+            }
+
+            override fun objectToMetaItem(obj: Value): MetaItem<*> = MetaItem.ValueItem(obj)
+        }
+
+        val string = object : MetaCaster<String> {
+            override fun itemToObject(item: MetaItem<*>): String = when (item) {
+                is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
+                is MetaItem.ValueItem -> item.value
+            }.string
+
+            override fun objectToMetaItem(obj: String): MetaItem<*> = MetaItem.ValueItem(obj.asValue())
+        }
+
+        val boolean = object : MetaCaster<Boolean> {
+            override fun itemToObject(item: MetaItem<*>): Boolean = when (item) {
+                is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
+                is MetaItem.ValueItem -> item.value
+            }.boolean
+
+            override fun objectToMetaItem(obj: Boolean): MetaItem<*> = MetaItem.ValueItem(obj.asValue())
+        }
+
+        val double = object : MetaCaster<Double> {
+            override fun itemToObject(item: MetaItem<*>): Double = when (item) {
+                is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
+                is MetaItem.ValueItem -> item.value
+            }.double
+
+            override fun objectToMetaItem(obj: Double): MetaItem<*> = MetaItem.ValueItem(obj.asValue())
+        }
+
+        val int = object : MetaCaster<Int> {
+            override fun itemToObject(item: MetaItem<*>): Int = when (item) {
+                is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
+                is MetaItem.ValueItem -> item.value
+            }.int
+
+            override fun objectToMetaItem(obj: Int): MetaItem<*> = MetaItem.ValueItem(obj.asValue())
+        }
+    }
+}
+
+fun <T : Any> MetaCaster<T>.metaToObject(meta: Meta): T = itemToObject(MetaItem.NodeItem(meta))
+fun <T : Any> MetaCaster<T>.valueToObject(value: Value): T = itemToObject(MetaItem.ValueItem(value))
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt
index f9137dca..3767e2fb 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt
@@ -1,7 +1,6 @@
 package hep.dataforge.values
 
 import hep.dataforge.meta.Meta
-import hep.dataforge.meta.buildMeta
 
 /**
  * Check if value is null
@@ -35,4 +34,4 @@ val Value.doubleArray: DoubleArray
     }
 
 
-fun Value.toMeta() = buildMeta { Meta.VALUE_KEY put this }
\ No newline at end of file
+fun Value.toMeta() = Meta { Meta.VALUE_KEY put this }
\ No newline at end of file
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
index 71469984..bba90d38 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt
@@ -45,7 +45,7 @@ operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.nam
 inline operator fun <C : Any, reified T : C> Table<C>.get(row: Int, column: String): T? =
     getValue(row, column, T::class)
 
-operator fun <C : Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
+operator fun <C : Any, T : C> Table<C>.get(row: Int, column: ColumnHeader<T>): T? = getValue(row, column.name, column.type)
 
 interface Column<T : Any> : ColumnHeader<T> {
     val size: Int

From 93c806c3bfe5a9d7a9a43d6c8af92502161f4045 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Mon, 2 Mar 2020 19:17:36 +0300
Subject: [PATCH 34/41] Basic design for external connectors

---
 .../kotlin/hep/dataforge/context/PluginManager.kt    |  2 +-
 .../kotlin/hep/dataforge/meta/MetaBuilder.kt         |  4 ++++
 .../kotlin/hep/dataforge/meta/MutableMetaDelegate.kt | 12 +++++++++++-
 .../kotlin/hep/dataforge/meta/scheme/Scheme.kt       |  3 +--
 4 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
index dd114f79..afac0392 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
@@ -62,7 +62,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
      * @return
      */
     @Suppress("UNCHECKED_CAST")
-    operator fun <T : Any> get(type: KClass<T>, tag: PluginTag? = null, recursive: Boolean = true): T? =
+    operator fun <T : Any> get(type: KClass<out T>, tag: PluginTag? = null, recursive: Boolean = true): T? =
         find(recursive) { type.isInstance(it) && (tag == null || tag.matches(it.tag)) } as T?
 
     inline operator fun <reified T : Any> get(tag: PluginTag? = null, recursive: Boolean = true): T? =
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
index 243d40bf..b36b3316 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
@@ -15,6 +15,10 @@ class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
     override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder()
     override fun empty(): MetaBuilder = MetaBuilder()
 
+    infix fun String.put(item: MetaItem<*>?) {
+        set(this, item)
+    }
+
     infix fun String.put(value: Value?) {
         set(this, value)
     }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt
index 5ab6ba0c..3fcdc9da 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt
@@ -49,9 +49,19 @@ fun <T, R> ReadWriteProperty<Any?, T>.map(reader: (T) -> R, writer: (R) -> T): R
 fun <R> ReadWriteProperty<Any?, MetaItem<*>?>.transform(reader: (MetaItem<*>?) -> R): ReadWriteProperty<Any?, R> =
     map(reader = reader, writer = { MetaItem.of(it) })
 
-fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R) =
+fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R): ReadWriteDelegateWrapper<Value?, R> =
     map(reader = reader, writer = { Value.of(it) })
 
+/**
+ * A delegate that throws
+ */
+fun <R : Any> ReadWriteProperty<Any?, R?>.notNull(default: () -> R): ReadWriteProperty<Any?, R> {
+    return ReadWriteDelegateWrapper(this,
+        reader = { it ?: default() },
+        writer = { it }
+    )
+}
+
 
 fun <M : MutableMeta<M>> M.item(default: Any? = null, key: Name? = null): MutableMetaDelegate<M> =
     MutableMetaDelegate(this, key, default?.let { MetaItem.of(it) })
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
index 7ac49de0..78d0a511 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
@@ -59,8 +59,7 @@ open class Scheme() : Configurable, Described, MetaRepr {
 /**
  * A specification for simplified generation of wrappers
  */
-open class SchemeSpec<T : Scheme>(val builder: () -> T) :
-    Specification<T> {
+open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
     override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T {
         return builder().apply {
             this.config = config

From e2845f4efc1217bc7d50c97ad35ac8755e1e14be Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Wed, 11 Mar 2020 22:30:29 +0300
Subject: [PATCH 35/41] Move Meta serialization to dataforge-meta

---
 build.gradle.kts                              |   4 +-
 dataforge-context/build.gradle.kts            |   6 +-
 .../kotlin/hep/dataforge/context/Context.kt   |   2 +-
 .../hep/dataforge/context/ContextBuilder.kt   |   5 +-
 .../kotlin/hep/dataforge/context/Plugin.kt    |   2 +-
 .../hep/dataforge/context/PluginManager.kt    |   4 +-
 .../kotlin/hep/dataforge/context/PluginTag.kt |   2 +-
 .../kotlin/hep/dataforge/data/Data.kt         |   2 +-
 .../kotlin/hep/dataforge/data/DataNode.kt     |   6 +-
 .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 131 +---------------
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt |   6 +-
 .../io/serialization/MetaSerializer.kt        | 142 ------------------
 .../io/serialization/nameSerializers.kt       |  33 ----
 .../io/serialization/serializationUtils.kt    |  93 ------------
 dataforge-meta/build.gradle.kts               |   4 +
 .../kotlin/hep/dataforge/meta/Laminate.kt     |   2 +-
 .../kotlin/hep/dataforge/meta/Meta.kt         |   7 +-
 .../kotlin/hep/dataforge/meta/MetaBuilder.kt  |   7 +-
 .../kotlin/hep/dataforge/meta/annotations.kt  |   2 +-
 .../meta/descriptors/ItemDescriptor.kt        |   8 +-
 .../dataforge/meta/serialization/JsonMeta.kt  | 135 +++++++++++++++++
 .../meta/serialization/MetaSerializer.kt      | 110 ++++++++++++++
 .../meta/serialization/serializationUtils.kt  |  67 +++++++++
 .../transformations/MetaTransformation.kt     |   8 +-
 .../kotlin/hep/dataforge/names/Name.kt        |  30 +++-
 .../kotlin/hep/dataforge/values/Value.kt      |   3 +
 26 files changed, 392 insertions(+), 429 deletions(-)
 delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
 delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt
 delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index 82d402e2..0d4cc1fb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,12 +1,12 @@
 
 plugins {
-    val toolsVersion = "0.3.1"
+    val toolsVersion = "0.4.0"
     id("scientifik.mpp") version toolsVersion apply false
     id("scientifik.jvm") version toolsVersion apply false
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-9")
+val dataforgeVersion by extra("0.1.5-dev-10")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts
index 896e7b89..104f4037 100644
--- a/dataforge-context/build.gradle.kts
+++ b/dataforge-context/build.gradle.kts
@@ -12,20 +12,20 @@ kotlin {
             dependencies {
                 api(project(":dataforge-meta"))
                 api(kotlin("reflect"))
-                api("io.github.microutils:kotlin-logging-common:1.7.2")
+                api("io.github.microutils:kotlin-logging-common:1.7.8")
                 api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
             }
         }
         val jvmMain by getting {
             dependencies {
-                api("io.github.microutils:kotlin-logging:1.7.2")
+                api("io.github.microutils:kotlin-logging:1.7.8")
                 api("ch.qos.logback:logback-classic:1.2.3")
                 api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
             }
         }
         val jsMain by getting {
             dependencies {
-                api("io.github.microutils:kotlin-logging-js:1.7.2")
+                api("io.github.microutils:kotlin-logging-js:1.7.8")
                 api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
             }
         }
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt
index ffc5b197..b3adcbfd 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt
@@ -103,7 +103,7 @@ open class Context(
         plugins.forEach { it.detach() }
     }
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "parent" to parent?.name
         "properties" put properties.seal()
         "plugins" put plugins.map { it.toMeta() }
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
index 0aed9b7f..1f267c37 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt
@@ -1,6 +1,7 @@
 package hep.dataforge.context
 
 import hep.dataforge.meta.DFBuilder
+import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaBuilder
 import hep.dataforge.meta.buildMeta
 import hep.dataforge.names.toName
@@ -22,11 +23,11 @@ class ContextBuilder(var name: String = "@anonymous", val parent: Context = Glob
     }
 
     fun plugin(tag: PluginTag, action: MetaBuilder.() -> Unit = {}) {
-        plugins.add(PluginRepository.fetch(tag, buildMeta(action)))
+        plugins.add(PluginRepository.fetch(tag, Meta(action)))
     }
 
     fun plugin(builder: PluginFactory<*>, action: MetaBuilder.() -> Unit = {}) {
-        plugins.add(builder.invoke(buildMeta(action)))
+        plugins.add(builder.invoke(Meta(action)))
     }
 
     fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt
index 90d669e0..6a937962 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt
@@ -65,7 +65,7 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr {
      */
     fun detach()
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "context" put context.name.toString()
         "type" to this::class.simpleName
         "tag" put tag
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
index afac0392..5f35d876 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt
@@ -102,7 +102,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
         load(factory(meta, context))
 
     fun <T : Plugin> load(factory: PluginFactory<T>, metaBuilder: MetaBuilder.() -> Unit): T =
-        load(factory, buildMeta(metaBuilder))
+        load(factory, Meta(metaBuilder))
 
     /**
      * Remove a plugin from [PluginManager]
@@ -134,7 +134,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
         factory: PluginFactory<T>,
         recursive: Boolean = true,
         metaBuilder: MetaBuilder.() -> Unit
-    ): T = fetch(factory, recursive, buildMeta(metaBuilder))
+    ): T = fetch(factory, recursive, Meta(metaBuilder))
 
     override fun iterator(): Iterator<Plugin> = plugins.iterator()
 
diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt
index 390de7bc..f02308fd 100644
--- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt
+++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt
@@ -36,7 +36,7 @@ data class PluginTag(
 
     override fun toString(): String = listOf(group, name, version).joinToString(separator = ":")
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "name" put name
         "group" put group
         "version" put version
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
index ea25e070..4d787b69 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt
@@ -19,7 +19,7 @@ interface Data<out T : Any> : Goal<T>, MetaRepr{
      */
     val meta: Meta
 
-    override fun toMeta(): Meta  = buildMeta {
+    override fun toMeta(): Meta  = Meta {
         "type" put (type.simpleName?:"undefined")
         if(!meta.isEmpty()) {
             "meta" put meta
diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
index 764aea62..ee8d9c4d 100644
--- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
+++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt
@@ -47,7 +47,7 @@ interface DataNode<out T : Any> : MetaRepr {
 
     val meta: Meta
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "type" put (type.simpleName ?: "undefined")
         "items" put {
             this@DataNode.items.forEach {
@@ -255,11 +255,11 @@ fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, meta: Meta = EmptyM
 }
 
 fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, block: MetaBuilder.() -> Unit = {}) {
-    this[name] = Data.static(data, buildMeta(block))
+    this[name] = Data.static(data, Meta(block))
 }
 
 fun <T : Any> DataTreeBuilder<T>.static(name: String, data: T, block: MetaBuilder.() -> Unit = {}) {
-    this[name.toName()] = Data.static(data, buildMeta(block))
+    this[name.toName()] = Data.static(data, Meta(block))
 }
 
 fun <T : Any> DataTreeBuilder<T>.node(name: Name, node: DataNode<T>) {
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
index 8ec244a5..1101b35f 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
@@ -9,6 +9,8 @@ import hep.dataforge.meta.descriptors.ValueDescriptor
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaBase
 import hep.dataforge.meta.MetaItem
+import hep.dataforge.meta.serialization.toJson
+import hep.dataforge.meta.serialization.toMeta
 import hep.dataforge.names.NameToken
 import hep.dataforge.names.toName
 import hep.dataforge.values.*
@@ -16,6 +18,7 @@ import kotlinx.io.Input
 import kotlinx.io.Output
 import kotlinx.io.text.readUtf8String
 import kotlinx.io.text.writeUtf8String
+import kotlinx.serialization.UnstableDefault
 
 
 import kotlinx.serialization.json.*
@@ -23,8 +26,8 @@ import kotlin.collections.component1
 import kotlin.collections.component2
 import kotlin.collections.set
 
-
-class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
+@OptIn(UnstableDefault::class)
+class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat {
 
     override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
         val jsonObject = meta.toJson(descriptor)
@@ -38,6 +41,8 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
     }
 
     companion object : MetaFormatFactory {
+        val DEFAULT_JSON = Json{prettyPrint = true}
+
         override fun invoke(meta: Meta, context: Context): MetaFormat = default
 
         override val shortName = "json"
@@ -52,125 +57,3 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
             default.run { readMeta(descriptor) }
     }
 }
-
-/**
- * @param descriptor reserved for custom serialization in future
- */
-fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
-    return if (isList()) {
-        JsonArray(list.map { it.toJson() })
-    } else {
-        when (type) {
-            ValueType.NUMBER -> JsonPrimitive(number)
-            ValueType.STRING -> JsonPrimitive(string)
-            ValueType.BOOLEAN -> JsonPrimitive(boolean)
-            ValueType.NULL -> JsonNull
-        }
-    }
-}
-
-//Use these methods to customize JSON key mapping
-private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
-
-//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
-
-fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
-
-    //TODO search for same name siblings and arrange them into arrays
-    val map = this.items.entries.associate { (name, item) ->
-        val itemDescriptor = descriptor?.items?.get(name.body)
-        val key = name.toJsonKey(itemDescriptor)
-        val value = when (item) {
-            is MetaItem.ValueItem -> {
-                item.value.toJson(itemDescriptor as? ValueDescriptor)
-            }
-            is MetaItem.NodeItem -> {
-                item.node.toJson(itemDescriptor as? NodeDescriptor)
-            }
-        }
-        key to value
-    }
-    return JsonObject(map)
-}
-
-fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
-    return when (val item = toMetaItem(descriptor)) {
-        is MetaItem.NodeItem<*> -> item.node
-        is MetaItem.ValueItem -> item.value.toMeta()
-    }
-}
-
-fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
-    return when (this) {
-        JsonNull -> Null
-        else -> this.content.parseValue() // Optimize number and boolean parsing
-    }
-}
-
-fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) {
-    is JsonPrimitive -> {
-        val value = this.toValue(descriptor as? ValueDescriptor)
-        MetaItem.ValueItem(value)
-    }
-    is JsonObject -> {
-        val meta = JsonMeta(this, descriptor as? NodeDescriptor)
-        MetaItem.NodeItem(meta)
-    }
-    is JsonArray -> {
-        if (this.all { it is JsonPrimitive }) {
-            val value = if (isEmpty()) {
-                Null
-            } else {
-                ListValue(
-                    map<JsonElement, Value> {
-                        //We already checked that all values are primitives
-                        (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
-                    }
-                )
-            }
-            MetaItem.ValueItem(value)
-        } else {
-            json {
-                "@value" to this@toMetaItem
-            }.toMetaItem(descriptor)
-        }
-    }
-}
-
-class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() {
-
-    @Suppress("UNCHECKED_CAST")
-    private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit {
-        val itemDescriptor = descriptor?.items?.get(key)
-        return when (value) {
-            is JsonPrimitive -> {
-                this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
-            }
-            is JsonObject -> {
-                this[key] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
-            }
-            is JsonArray -> {
-                when {
-                    value.all { it is JsonPrimitive } -> {
-                        val listValue = ListValue(
-                            value.map {
-                                //We already checked that all values are primitives
-                                (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
-                            }
-                        )
-                        this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
-                    }
-                    else -> value.forEachIndexed { index, jsonElement ->
-                        this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor)
-                    }
-                }
-            }
-        }
-    }
-
-    override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy {
-        val map = HashMap<String, MetaItem<JsonMeta>>()
-        json.forEach { (key, value) -> map[key] = value }
-        map.mapKeys { it.key.toName().first()!! }
-    }
-}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index 0ed5214b..ebeedd6d 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -119,7 +119,7 @@ class TaglessEnvelopeFormat(
         var line: String
         do {
             line = readUtf8Line()// ?: error("Input does not contain tagless envelope header")
-            offset += line.toUtf8Bytes().size.toUInt()
+            offset += line.encodeToByteArray().size.toUInt()
         } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
         val properties = HashMap<String, String>()
 
@@ -133,7 +133,7 @@ class TaglessEnvelopeFormat(
             }
             try {
                 line = readUtf8Line()
-                offset += line.toUtf8Bytes().size.toUInt()
+                offset += line.encodeToByteArray().size.toUInt()
             } catch (ex: EOFException) {
                 return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
             }
@@ -156,7 +156,7 @@ class TaglessEnvelopeFormat(
 
         do {
             line = readUtf8Line() ?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
-            offset += line.toUtf8Bytes().size.toUInt()
+            offset += line.encodeToByteArray().size.toUInt()
             //returning an Envelope without data if end of input is reached
         } while (!line.startsWith(dataStart))
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
deleted file mode 100644
index ef989869..00000000
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-package hep.dataforge.io.serialization
-
-import hep.dataforge.io.toJson
-import hep.dataforge.io.toMeta
-import hep.dataforge.meta.*
-import hep.dataforge.names.NameToken
-import hep.dataforge.values.*
-import kotlinx.serialization.*
-import kotlinx.serialization.internal.*
-import kotlinx.serialization.json.JsonInput
-import kotlinx.serialization.json.JsonObjectSerializer
-import kotlinx.serialization.json.JsonOutput
-
-
-@Serializer(Value::class)
-@UseExperimental(InternalSerializationApi::class)
-object ValueSerializer : KSerializer<Value> {
-    private val valueTypeSerializer = EnumSerializer(ValueType::class)
-    private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) }
-
-    override val descriptor: SerialDescriptor = descriptor("Value") {
-        boolean("isList")
-        enum<ValueType>("valueType")
-        element("value", null)
-    }
-
-    private fun Decoder.decodeValue(): Value {
-        return when (decode(valueTypeSerializer)) {
-            ValueType.NULL -> Null
-            ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
-            ValueType.BOOLEAN -> decodeBoolean().asValue()
-            ValueType.STRING -> decodeString().asValue()
-            else -> decodeString().parseValue()
-        }
-    }
-
-
-    override fun deserialize(decoder: Decoder): Value {
-        val isList = decoder.decodeBoolean()
-        return if (isList) {
-            listSerializer.deserialize(decoder).asValue()
-        } else {
-            decoder.decodeValue()
-        }
-    }
-
-    private fun Encoder.encodeValue(value: Value) {
-        encode(valueTypeSerializer, value.type)
-        when (value.type) {
-            ValueType.NULL -> {
-                // do nothing
-            }
-            ValueType.NUMBER -> encodeDouble(value.double)
-            ValueType.BOOLEAN -> encodeBoolean(value.boolean)
-            ValueType.STRING -> encodeString(value.string)
-            else -> encodeString(value.string)
-        }
-    }
-
-    override fun serialize(encoder: Encoder, obj: Value) {
-        encoder.encodeBoolean(obj.isList())
-        if (obj.isList()) {
-            listSerializer.serialize(encoder, obj.list)
-        } else {
-            encoder.encodeValue(obj)
-        }
-    }
-}
-
-@Serializer(MetaItem::class)
-object MetaItemSerializer : KSerializer<MetaItem<*>> {
-    override val descriptor: SerialDescriptor = descriptor("MetaItem") {
-        boolean("isNode")
-        element("value", null)
-    }
-
-
-    override fun deserialize(decoder: Decoder): MetaItem<*> {
-        val isNode = decoder.decodeBoolean()
-        return if (isNode) {
-            MetaItem.NodeItem(decoder.decode(MetaSerializer))
-        } else {
-            MetaItem.ValueItem(decoder.decode(ValueSerializer))
-        }
-    }
-
-    override fun serialize(encoder: Encoder, obj: MetaItem<*>) {
-        encoder.encodeBoolean(obj is MetaItem.NodeItem)
-        when (obj) {
-            is MetaItem.NodeItem -> MetaSerializer.serialize(encoder, obj.node)
-            is MetaItem.ValueItem -> ValueSerializer.serialize(encoder, obj.value)
-        }
-    }
-}
-
-private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase()
-
-/**
- * Serialized for meta
- */
-@Serializer(Meta::class)
-object MetaSerializer : KSerializer<Meta> {
-    private val mapSerializer = HashMapSerializer(
-        StringSerializer,
-        MetaItemSerializer
-    )
-
-    override val descriptor: SerialDescriptor = NamedMapClassDescriptor(
-        "hep.dataforge.meta.Meta",
-        StringSerializer.descriptor,
-        MetaItemSerializer.descriptor
-    )
-
-    override fun deserialize(decoder: Decoder): Meta {
-        return if (decoder is JsonInput) {
-            JsonObjectSerializer.deserialize(decoder).toMeta()
-        } else {
-            DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) })
-        }
-    }
-
-    override fun serialize(encoder: Encoder, obj: Meta) {
-        if (encoder is JsonOutput) {
-            JsonObjectSerializer.serialize(encoder, obj.toJson())
-        } else {
-            mapSerializer.serialize(encoder, obj.items.mapKeys { it.key.toString() })
-        }
-    }
-}
-
-@Serializer(Config::class)
-object ConfigSerializer : KSerializer<Config> {
-    override val descriptor: SerialDescriptor = MetaSerializer.descriptor
-
-    override fun deserialize(decoder: Decoder): Config {
-        return MetaSerializer.deserialize(decoder).asConfig()
-    }
-
-    override fun serialize(encoder: Encoder, obj: Config) {
-        MetaSerializer.serialize(encoder, obj)
-    }
-}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt
deleted file mode 100644
index c12a6c19..00000000
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package hep.dataforge.io.serialization
-
-import hep.dataforge.names.Name
-import hep.dataforge.names.NameToken
-import hep.dataforge.names.toName
-import kotlinx.serialization.*
-import kotlinx.serialization.internal.StringDescriptor
-
-@Serializer(Name::class)
-object NameSerializer : KSerializer<Name> {
-    override val descriptor: SerialDescriptor = StringDescriptor.withName("Name")
-
-    override fun deserialize(decoder: Decoder): Name {
-        return decoder.decodeString().toName()
-    }
-
-    override fun serialize(encoder: Encoder, obj: Name) {
-        encoder.encodeString(obj.toString())
-    }
-}
-
-@Serializer(NameToken::class)
-object NameTokenSerializer : KSerializer<NameToken> {
-    override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
-
-    override fun deserialize(decoder: Decoder): NameToken {
-        return decoder.decodeString().toName().first()!!
-    }
-
-    override fun serialize(encoder: Encoder, obj: NameToken) {
-        encoder.encodeString(obj.toString())
-    }
-}
\ No newline at end of file
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
deleted file mode 100644
index 18e28423..00000000
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package hep.dataforge.io.serialization
-
-import hep.dataforge.meta.DFExperimental
-import kotlinx.serialization.*
-import kotlinx.serialization.internal.*
-
-/**
- * A convenience builder for serial descriptors
- */
-inline class SerialDescriptorBuilder(private val impl: SerialClassDescImpl) {
-    fun element(
-        name: String,
-        descriptor: SerialDescriptor?,
-        isOptional: Boolean = false,
-        vararg annotations: Annotation
-    ) {
-        impl.addElement(name, isOptional)
-        descriptor?.let { impl.pushDescriptor(descriptor) }
-        annotations.forEach {
-            impl.pushAnnotation(it)
-        }
-    }
-
-    fun element(
-        name: String,
-        isOptional: Boolean = false,
-        vararg annotations: Annotation,
-        block: SerialDescriptorBuilder.() -> Unit
-    ) {
-        impl.addElement(name, isOptional)
-        impl.pushDescriptor(SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build())
-        annotations.forEach {
-            impl.pushAnnotation(it)
-        }
-    }
-
-    fun boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, BooleanDescriptor, isOptional, *annotations)
-
-    fun string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, StringDescriptor, isOptional, *annotations)
-
-    fun int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, IntDescriptor, isOptional, *annotations)
-
-    fun double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, DoubleDescriptor, isOptional, *annotations)
-
-    fun float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, FloatDescriptor, isOptional, *annotations)
-
-    fun long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, LongDescriptor, isOptional, *annotations)
-
-    fun doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, DoubleArraySerializer.descriptor, isOptional, *annotations)
-
-    @UseExperimental(InternalSerializationApi::class)
-    inline fun <reified E : Enum<E>> enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-        element(name, EnumSerializer(E::class).descriptor, isOptional, *annotations)
-
-    fun classAnnotation(a: Annotation) = impl.pushClassAnnotation(a)
-
-    fun build(): SerialDescriptor = impl
-}
-
-inline fun <reified T : Any> KSerializer<T>.descriptor(
-    name: String,
-    block: SerialDescriptorBuilder.() -> Unit
-): SerialDescriptor =
-    SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
-
-@DFExperimental
-inline fun <R> Decoder.decodeStructure(
-    desc: SerialDescriptor,
-    vararg typeParams: KSerializer<*> = emptyArray(),
-    crossinline block:  CompositeDecoder.() -> R
-): R {
-    val decoder = beginStructure(desc, *typeParams)
-    val res = decoder.block()
-    decoder.endStructure(desc)
-    return res
-}
-
-inline fun Encoder.encodeStructure(
-    desc: SerialDescriptor,
-    vararg typeParams: KSerializer<*> = emptyArray(),
-    block: CompositeEncoder.() -> Unit
-) {
-    val encoder = beginStructure(desc, *typeParams)
-    encoder.block()
-    encoder.endStructure(desc)
-}
\ No newline at end of file
diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts
index 6f2a5160..f19a39fc 100644
--- a/dataforge-meta/build.gradle.kts
+++ b/dataforge-meta/build.gradle.kts
@@ -1,5 +1,9 @@
+import scientifik.useSerialization
+
 plugins {
     id("scientifik.mpp")
 }
 
+useSerialization()
+
 description = "Meta definition and basic operations on meta"
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
index beda1732..7dec0790 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt
@@ -64,7 +64,7 @@ class Laminate(layers: List<Meta>) : MetaBase() {
                 }
                 else -> map {
                     when (it) {
-                        is MetaItem.ValueItem -> MetaItem.NodeItem(buildMeta { Meta.VALUE_KEY put it.value })
+                        is MetaItem.ValueItem -> MetaItem.NodeItem(Meta { Meta.VALUE_KEY put it.value })
                         is MetaItem.NodeItem -> it
                     }
                 }.merge()
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index 93e416de..b37188b4 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -3,11 +3,13 @@ package hep.dataforge.meta
 import hep.dataforge.meta.Meta.Companion.VALUE_KEY
 import hep.dataforge.meta.MetaItem.NodeItem
 import hep.dataforge.meta.MetaItem.ValueItem
+import hep.dataforge.meta.serialization.toJson
 import hep.dataforge.names.*
 import hep.dataforge.values.EnumValue
 import hep.dataforge.values.Null
 import hep.dataforge.values.Value
 import hep.dataforge.values.boolean
+import kotlinx.serialization.Serializable
 
 
 /**
@@ -15,11 +17,14 @@ import hep.dataforge.values.boolean
  * * a [ValueItem] (leaf)
  * * a [NodeItem] (node)
  */
+@Serializable
 sealed class MetaItem<out M : Meta> {
+    @Serializable
     data class ValueItem(val value: Value) : MetaItem<Nothing>() {
         override fun toString(): String = value.toString()
     }
 
+    @Serializable
     data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
         override fun toString(): String = node.toString()
     }
@@ -161,7 +166,7 @@ abstract class MetaBase : Meta {
 
     override fun hashCode(): Int = items.hashCode()
 
-    override fun toString(): String = items.toString()
+    override fun toString(): String = toJson().toString()
 }
 
 /**
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
index b36b3316..da1bafad 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt
@@ -152,9 +152,4 @@ fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().appl
  * Build a [MetaBuilder] using given transformation
  */
 @Suppress("FunctionName")
-fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
-
-/**
- * Build meta using given source meta as a base
- */
-fun buildMeta(source: Meta, builder: MetaBuilder.() -> Unit): MetaBuilder = source.builder().apply(builder)
\ No newline at end of file
+fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt
index 865ef800..2085bc5d 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt
@@ -6,6 +6,6 @@ package hep.dataforge.meta
 @DslMarker
 annotation class DFBuilder
 
-@Experimental(level = Experimental.Level.WARNING)
+@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
 @Retention(AnnotationRetention.BINARY)
 annotation class DFExperimental
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
index 9c5cf15f..4ff5de11 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
@@ -255,10 +255,10 @@ class ValueDescriptor : ItemDescriptor() {
     }
 
     companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
-        inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
-            type(ValueType.STRING)
-            this.allowedValues = enumValues<E>().map { Value.of(it.name) }
-        }
+//        inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
+//            type(ValueType.STRING)
+//            this.allowedValues = enumValues<E>().map { Value.of(it.name) }
+//        }
 
 //        /**
 //         * Build a value descriptor from annotation
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt
new file mode 100644
index 00000000..78357c6d
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt
@@ -0,0 +1,135 @@
+package hep.dataforge.meta.serialization
+
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaBase
+import hep.dataforge.meta.MetaItem
+import hep.dataforge.meta.descriptors.ItemDescriptor
+import hep.dataforge.meta.descriptors.NodeDescriptor
+import hep.dataforge.meta.descriptors.ValueDescriptor
+import hep.dataforge.names.NameToken
+import hep.dataforge.names.toName
+import hep.dataforge.values.*
+import kotlinx.serialization.json.*
+
+
+/**
+ * @param descriptor reserved for custom serialization in future
+ */
+fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
+    return if (isList()) {
+        JsonArray(list.map { it.toJson() })
+    } else {
+        when (type) {
+            ValueType.NUMBER -> JsonPrimitive(number)
+            ValueType.STRING -> JsonPrimitive(string)
+            ValueType.BOOLEAN -> JsonPrimitive(boolean)
+            ValueType.NULL -> JsonNull
+        }
+    }
+}
+
+//Use these methods to customize JSON key mapping
+private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
+
+//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
+
+fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
+
+    //TODO search for same name siblings and arrange them into arrays
+    val map = this.items.entries.associate { (name, item) ->
+        val itemDescriptor = descriptor?.items?.get(name.body)
+        val key = name.toJsonKey(itemDescriptor)
+        val value = when (item) {
+            is MetaItem.ValueItem -> {
+                item.value.toJson(itemDescriptor as? ValueDescriptor)
+            }
+            is MetaItem.NodeItem -> {
+                item.node.toJson(itemDescriptor as? NodeDescriptor)
+            }
+        }
+        key to value
+    }
+    return JsonObject(map)
+}
+
+fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
+    return when (val item = toMetaItem(descriptor)) {
+        is MetaItem.NodeItem<*> -> item.node
+        is MetaItem.ValueItem -> item.value.toMeta()
+    }
+}
+
+fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
+    return when (this) {
+        JsonNull -> Null
+        else -> this.content.parseValue() // Optimize number and boolean parsing
+    }
+}
+
+fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) {
+    is JsonPrimitive -> {
+        val value = this.toValue(descriptor as? ValueDescriptor)
+        MetaItem.ValueItem(value)
+    }
+    is JsonObject -> {
+        val meta = JsonMeta(this, descriptor as? NodeDescriptor)
+        MetaItem.NodeItem(meta)
+    }
+    is JsonArray -> {
+        if (this.all { it is JsonPrimitive }) {
+            val value = if (isEmpty()) {
+                Null
+            } else {
+                ListValue(
+                    map<JsonElement, Value> {
+                        //We already checked that all values are primitives
+                        (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
+                    }
+                )
+            }
+            MetaItem.ValueItem(value)
+        } else {
+            json {
+                "@value" to this@toMetaItem
+            }.toMetaItem(descriptor)
+        }
+    }
+}
+
+class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() {
+
+    @Suppress("UNCHECKED_CAST")
+    private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit {
+        val itemDescriptor = descriptor?.items?.get(key)
+        return when (value) {
+            is JsonPrimitive -> {
+                this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
+            }
+            is JsonObject -> {
+                this[key] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
+            }
+            is JsonArray -> {
+                when {
+                    value.all { it is JsonPrimitive } -> {
+                        val listValue = ListValue(
+                            value.map {
+                                //We already checked that all values are primitives
+                                (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
+                            }
+                        )
+                        this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
+                    }
+                    else -> value.forEachIndexed { index, jsonElement ->
+                        this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor)
+                    }
+                }
+            }
+        }
+    }
+
+    override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy {
+        val map = HashMap<String, MetaItem<JsonMeta>>()
+        json.forEach { (key, value) -> map[key] = value }
+        map.mapKeys { it.key.toName().first()!! }
+    }
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt
new file mode 100644
index 00000000..a98aa4c0
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt
@@ -0,0 +1,110 @@
+package hep.dataforge.meta.serialization
+
+import hep.dataforge.meta.*
+import hep.dataforge.names.NameToken
+import hep.dataforge.values.*
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.builtins.list
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.json.JsonInput
+import kotlinx.serialization.json.JsonObjectSerializer
+import kotlinx.serialization.json.JsonOutput
+
+
+@Serializer(Value::class)
+@OptIn(InternalSerializationApi::class)
+object ValueSerializer : KSerializer<Value> {
+    //    private val valueTypeSerializer = EnumSerializer(ValueType::class)
+    private val listSerializer by lazy { ValueSerializer.list }
+
+    override val descriptor: SerialDescriptor = SerialDescriptor("Value") {
+        boolean("isList")
+        enum<ValueType>("valueType")
+        string("value")
+    }
+
+    private fun Decoder.decodeValue(): Value {
+        return when (decode(ValueType.serializer())) {
+            ValueType.NULL -> Null
+            ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
+            ValueType.BOOLEAN -> decodeBoolean().asValue()
+            ValueType.STRING -> decodeString().asValue()
+        }
+    }
+
+
+    override fun deserialize(decoder: Decoder): Value {
+        val isList = decoder.decodeBoolean()
+        return if (isList) {
+            listSerializer.deserialize(decoder).asValue()
+        } else {
+            decoder.decodeValue()
+        }
+    }
+
+    private fun Encoder.encodeValue(value: Value) {
+        encode(ValueType.serializer(), value.type)
+        when (value.type) {
+            ValueType.NULL -> {
+                // do nothing
+            }
+            ValueType.NUMBER -> encodeDouble(value.double)
+            ValueType.BOOLEAN -> encodeBoolean(value.boolean)
+            ValueType.STRING -> encodeString(value.string)
+        }
+    }
+
+    override fun serialize(encoder: Encoder, value: Value) {
+        encoder.encodeBoolean(value.isList())
+        if (value.isList()) {
+            listSerializer.serialize(encoder, value.list)
+        } else {
+            encoder.encodeValue(value)
+        }
+    }
+}
+
+private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase()
+
+/**
+ * Serialized for meta
+ */
+@Serializer(Meta::class)
+object MetaSerializer : KSerializer<Meta> {
+    private val mapSerializer = MapSerializer(
+        String.serializer(),
+        MetaItem.serializer(MetaSerializer)
+    )
+
+    override val descriptor: SerialDescriptor get() = mapSerializer.descriptor
+
+    override fun deserialize(decoder: Decoder): Meta {
+        return if (decoder is JsonInput) {
+            JsonObjectSerializer.deserialize(decoder).toMeta()
+        } else {
+            DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) })
+        }
+    }
+
+    override fun serialize(encoder: Encoder, value: Meta) {
+        if (encoder is JsonOutput) {
+            JsonObjectSerializer.serialize(encoder, value.toJson())
+        } else {
+            mapSerializer.serialize(encoder, value.items.mapKeys { it.key.toString() })
+        }
+    }
+}
+
+@Serializer(Config::class)
+object ConfigSerializer : KSerializer<Config> {
+    override val descriptor: SerialDescriptor = MetaSerializer.descriptor
+
+    override fun deserialize(decoder: Decoder): Config {
+        return MetaSerializer.deserialize(decoder).asConfig()
+    }
+
+    override fun serialize(encoder: Encoder, value: Config) {
+        MetaSerializer.serialize(encoder, value)
+    }
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt
new file mode 100644
index 00000000..a92609e9
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt
@@ -0,0 +1,67 @@
+package hep.dataforge.meta.serialization
+
+import hep.dataforge.meta.DFExperimental
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.DoubleArraySerializer
+import kotlinx.serialization.internal.*
+
+fun SerialDescriptorBuilder.boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name, PrimitiveDescriptor(name, PrimitiveKind.BOOLEAN), isOptional = isOptional, annotations = annotations.toList())
+
+fun SerialDescriptorBuilder.string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name, PrimitiveDescriptor(name, PrimitiveKind.STRING), isOptional = isOptional, annotations = annotations.toList())
+
+fun SerialDescriptorBuilder.int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name, PrimitiveDescriptor(name, PrimitiveKind.INT), isOptional = isOptional, annotations = annotations.toList())
+
+fun SerialDescriptorBuilder.double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name,PrimitiveDescriptor(name, PrimitiveKind.DOUBLE), isOptional = isOptional, annotations = annotations.toList())
+
+fun SerialDescriptorBuilder.float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name, PrimitiveDescriptor(name, PrimitiveKind.FLOAT), isOptional = isOptional, annotations = annotations.toList())
+
+fun SerialDescriptorBuilder.long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name, PrimitiveDescriptor(name, PrimitiveKind.LONG), isOptional = isOptional, annotations = annotations.toList())
+
+fun SerialDescriptorBuilder.doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
+    element(name, DoubleArraySerializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
+
+@OptIn(InternalSerializationApi::class)
+inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) {
+    val enumDescriptor = SerialDescriptor(serialName, UnionKind.ENUM_KIND) {
+        enumValues<E>().forEach {
+            val fqn = "$serialName.${it.name}"
+            val enumMemberDescriptor = SerialDescriptor(fqn, StructureKind.OBJECT)
+            element(it.name, enumMemberDescriptor)
+        }
+    }
+    element(name, enumDescriptor, isOptional = isOptional, annotations = annotations.toList())
+}
+
+//inline fun <reified T : Any> KSerializer<T>.descriptor(
+//    name: String,
+//    block: SerialDescriptorBuilder.() -> Unit
+//): SerialDescriptor =
+//    SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
+
+@DFExperimental
+inline fun <R> Decoder.decodeStructure(
+    desc: SerialDescriptor,
+    vararg typeParams: KSerializer<*> = emptyArray(),
+    crossinline block:  CompositeDecoder.() -> R
+): R {
+    val decoder = beginStructure(desc, *typeParams)
+    val res = decoder.block()
+    decoder.endStructure(desc)
+    return res
+}
+
+inline fun Encoder.encodeStructure(
+    desc: SerialDescriptor,
+    vararg typeParams: KSerializer<*> = emptyArray(),
+    block: CompositeEncoder.() -> Unit
+) {
+    val encoder = beginStructure(desc, *typeParams)
+    encoder.block()
+    encoder.endStructure(desc)
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt
index 4dad4466..d6f3bedf 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt
@@ -90,7 +90,7 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
      * Produce new meta using only those items that match transformation rules
      */
     fun transform(source: Meta): Meta =
-        buildMeta {
+        Meta {
             transformations.forEach { rule ->
                 rule.selectItems(source).forEach { name ->
                     rule.transformItem(name, source[name], this)
@@ -102,7 +102,7 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
      * Transform a meta, replacing all elements found in rules with transformed entries
      */
     fun apply(source: Meta): Meta =
-        buildMeta(source) {
+        source.edit {
             transformations.forEach { rule ->
                 rule.selectItems(source).forEach { name ->
                     remove(name)
@@ -156,8 +156,8 @@ class MetaTransformationBuilder {
     fun keep(regex: String) {
         transformations.add(
             RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
-                    setItem(name, metaItem)
-                })
+                setItem(name, metaItem)
+            })
     }
 
     /**
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
index 3c8d93d5..255a2ffd 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
@@ -1,11 +1,14 @@
 package hep.dataforge.names
 
+import kotlinx.serialization.*
+
 
 /**
  * The general interface for working with names.
  * The name is a dot separated list of strings like `token1.token2.token3`.
  * Each token could contain additional index in square brackets.
  */
+@Serializable
 class Name(val tokens: List<NameToken>) {
 
     val length get() = tokens.size
@@ -51,10 +54,21 @@ class Name(val tokens: List<NameToken>) {
     }
 
 
-    companion object {
+    @Serializer(Name::class)
+    companion object: KSerializer<Name> {
         const val NAME_SEPARATOR = "."
 
         val EMPTY = Name(emptyList())
+
+        override val descriptor: SerialDescriptor = PrimitiveDescriptor("Name", PrimitiveKind.STRING)
+
+        override fun deserialize(decoder: Decoder): Name {
+            return decoder.decodeString().toName()
+        }
+
+        override fun serialize(encoder: Encoder, value: Name) {
+            encoder.encodeString(value.toString())
+        }
     }
 }
 
@@ -63,6 +77,7 @@ class Name(val tokens: List<NameToken>) {
  * Following symbols are prohibited in name tokens: `{}.:\`.
  * A name token could have appendix in square brackets called *index*
  */
+@Serializable
 data class NameToken(val body: String, val index: String = "") {
 
     init {
@@ -82,6 +97,19 @@ data class NameToken(val body: String, val index: String = "") {
     }
 
     fun hasIndex() = index.isNotEmpty()
+
+    @Serializer(NameToken::class)
+    companion object :KSerializer<NameToken>{
+        override val descriptor: SerialDescriptor = PrimitiveDescriptor("NameToken", PrimitiveKind.STRING)
+
+        override fun deserialize(decoder: Decoder): NameToken {
+            return decoder.decodeString().toName().first()!!
+        }
+
+        override fun serialize(encoder: Encoder, value: NameToken) {
+            encoder.encodeString(value.toString())
+        }
+    }
 }
 
 /**
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt
index f044b018..ccc92d50 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt
@@ -1,5 +1,7 @@
 package hep.dataforge.values
 
+import kotlinx.serialization.Serializable
+
 
 /**
  * The list of supported Value types.
@@ -7,6 +9,7 @@ package hep.dataforge.values
  * Time value and binary value are represented by string
  *
  */
+@Serializable
 enum class ValueType {
     NUMBER, STRING, BOOLEAN, NULL
 }

From db03dfaae9996f53f8a3cf1ec77f90e01c09f024 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Fri, 13 Mar 2020 19:15:37 +0300
Subject: [PATCH 36/41] Finish migrating to 0.4.0 plugin

---
 dataforge-context/build.gradle.kts            |   7 +-
 dataforge-io/build.gradle.kts                 |  18 +--
 .../dataforge-io-yaml/build.gradle.kts        |   8 +-
 .../io/yaml/FrontMatterEnvelopeFormat.kt      |   6 +-
 .../hep/dataforge/io/yaml/YamlMetaFormat.kt   |   3 +-
 .../dataforge/io/yaml/YamlMetaFormatTest.kt   |   3 +-
 .../hep/dataforge/io/BinaryMetaFormat.kt      |   7 +-
 .../kotlin/hep/dataforge/io/EnvelopeParts.kt  |   2 +-
 .../kotlin/hep/dataforge/io/JsonMetaFormat.kt |  28 ++---
 .../hep/dataforge/io/TaglessEnvelopeFormat.kt |   3 +-
 .../kotlin/hep/dataforge/io/MetaFormatTest.kt |   4 +-
 .../hep/dataforge/io/MetaSerializerTest.kt    |  20 ++--
 dataforge-meta/build.gradle.kts               |   4 +-
 .../meta/{serialization => }/JsonMeta.kt      |  19 ++-
 .../kotlin/hep/dataforge/meta/Meta.kt         |  32 ++++-
 .../hep/dataforge/meta/MetaSerializer.kt      |  53 +++++++++
 .../kotlin/hep/dataforge/meta/mapMeta.kt      |   2 +-
 .../meta/scheme/ConfigurableDelegate.kt       |   2 +-
 .../hep/dataforge/meta/scheme/Scheme.kt       |  10 +-
 .../meta/serialization/MetaSerializer.kt      | 110 ------------------
 .../{serialization => }/serializationUtils.kt |  32 +++--
 .../kotlin/hep/dataforge/names/Name.kt        |   9 +-
 .../hep/dataforge/values/ValueSerializer.kt   |  59 ++++++++++
 .../hep/dataforge/meta/MetaExtensionTest.kt   |   4 +-
 .../kotlin/hep/dataforge/meta/MetaTest.kt     |   8 +-
 .../hep/dataforge/meta/MutableMetaTest.kt     |   2 +-
 .../kotlin/hep/dataforge/meta/SchemeTest.kt   |   3 +-
 .../dataforge/tables/io/textTableEnvelope.kt  |   2 +-
 .../hep/dataforge/workspace/Dependency.kt     |   7 +-
 .../hep/dataforge/workspace/TaskModel.kt      |   4 +-
 .../hep/dataforge/workspace/Workspace.kt      |   5 +-
 .../dataforge/workspace/WorkspaceBuilder.kt   |   2 +-
 32 files changed, 235 insertions(+), 243 deletions(-)
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{serialization => }/JsonMeta.kt (90%)
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt
 delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt
 rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{serialization => }/serializationUtils.kt (60%)
 create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt

diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts
index 104f4037..dd581254 100644
--- a/dataforge-context/build.gradle.kts
+++ b/dataforge-context/build.gradle.kts
@@ -1,10 +1,12 @@
+import scientifik.coroutines
+
 plugins {
     id("scientifik.mpp")
 }
 
 description = "Context and provider definitions"
 
-val coroutinesVersion: String  = Scientifik.coroutinesVersion
+coroutines()
 
 kotlin {
     sourceSets {
@@ -13,20 +15,17 @@ kotlin {
                 api(project(":dataforge-meta"))
                 api(kotlin("reflect"))
                 api("io.github.microutils:kotlin-logging-common:1.7.8")
-                api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
             }
         }
         val jvmMain by getting {
             dependencies {
                 api("io.github.microutils:kotlin-logging:1.7.8")
                 api("ch.qos.logback:logback-classic:1.2.3")
-                api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
             }
         }
         val jsMain by getting {
             dependencies {
                 api("io.github.microutils:kotlin-logging-js:1.7.8")
-                api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
             }
         }
     }
diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts
index 2606f5ea..1760f613 100644
--- a/dataforge-io/build.gradle.kts
+++ b/dataforge-io/build.gradle.kts
@@ -1,4 +1,5 @@
-import scientifik.useSerialization
+import scientifik.DependencySourceSet.TEST
+import scientifik.serialization
 
 plugins {
     id("scientifik.mpp")
@@ -6,7 +7,9 @@ plugins {
 
 description = "IO module"
 
-useSerialization()
+serialization(sourceSet = TEST){
+    cbor()
+}
 
 val ioVersion by rootProject.extra("0.2.0-npm-dev-4")
 
@@ -16,17 +19,6 @@ kotlin {
             dependencies {
                 api(project(":dataforge-context"))
                 api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion")
-                //api("org.jetbrains.kotlinx:kotlinx-io-metadata:$ioVersion")
-            }
-        }
-        jvmMain {
-            dependencies {
-                //api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion")
-            }
-        }
-        jsMain {
-            dependencies {
-                //api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion")
             }
         }
     }
diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts
index bad9b42e..14ea2c19 100644
--- a/dataforge-io/dataforge-io-yaml/build.gradle.kts
+++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts
@@ -1,4 +1,4 @@
-import scientifik.useSerialization
+import scientifik.serialization
 
 plugins {
     id("scientifik.jvm")
@@ -6,9 +6,11 @@ plugins {
 
 description = "YAML meta IO"
 
-useSerialization()
+serialization{
+    yaml()
+}
 
 dependencies {
     api(project(":dataforge-io"))
-    api("org.yaml:snakeyaml:1.25")
+    api("org.yaml:snakeyaml:1.26")
 }
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
index be74d080..6361c5dd 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
@@ -21,7 +21,7 @@ class FrontMatterEnvelopeFormat(
         var line: String = ""
         var offset = 0u
         do {
-            line = readUtf8Line() ?: error("Input does not contain front matter separator")
+            line = readUtf8Line() //?: error("Input does not contain front matter separator")
             offset += line.toUtf8Bytes().size.toUInt()
         } while (!line.startsWith(SEPARATOR))
 
@@ -46,7 +46,7 @@ class FrontMatterEnvelopeFormat(
     override fun Input.readObject(): Envelope {
         var line: String = ""
         do {
-            line = readUtf8Line() ?: error("Input does not contain front matter separator")
+            line = readUtf8Line() //?: error("Input does not contain front matter separator")
         } while (!line.startsWith(SEPARATOR))
 
         val readMetaFormat =
@@ -89,7 +89,7 @@ class FrontMatterEnvelopeFormat(
 
         override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
             val line = input.readUtf8Line()
-            return if (line != null && line.startsWith("---")) {
+            return if (line.startsWith("---")) {
                 invoke()
             } else {
                 null
diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
index d0543fd0..d1ab09e4 100644
--- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
+++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt
@@ -1,13 +1,12 @@
 package hep.dataforge.io.yaml
 
 import hep.dataforge.context.Context
-import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.io.MetaFormat
 import hep.dataforge.io.MetaFormatFactory
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.Meta
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.toMap
-import hep.dataforge.meta.scheme.toMeta
 import hep.dataforge.meta.toMeta
 import kotlinx.io.Input
 import kotlinx.io.Output
diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
index b330e080..24fb6593 100644
--- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
+++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt
@@ -3,7 +3,6 @@ package hep.dataforge.io.yaml
 import hep.dataforge.io.parse
 import hep.dataforge.io.toString
 import hep.dataforge.meta.Meta
-import hep.dataforge.meta.buildMeta
 import hep.dataforge.meta.get
 import hep.dataforge.meta.seal
 import kotlin.test.Test
@@ -13,7 +12,7 @@ import kotlin.test.assertEquals
 class YamlMetaFormatTest {
     @Test
     fun testYamlMetaFormat() {
-        val meta = buildMeta {
+        val meta = Meta {
             "a" put 22
             "node" put {
                 "b" put "DDD"
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
index c86fe883..e753b56c 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt
@@ -1,8 +1,11 @@
 package hep.dataforge.io
 
 import hep.dataforge.context.Context
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaBuilder
+import hep.dataforge.meta.MetaItem
 import hep.dataforge.meta.descriptors.NodeDescriptor
-import hep.dataforge.meta.*
+import hep.dataforge.meta.setItem
 import hep.dataforge.values.*
 import kotlinx.io.*
 import kotlinx.io.text.readUtf8String
@@ -112,7 +115,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
             }
             'M' -> {
                 val length = readInt()
-                val meta = buildMeta {
+                val meta = Meta {
                     (1..length).forEach { _ ->
                         val name = readString()
                         val item = readMetaItem()
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
index 8e0b16de..9541c8fb 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt
@@ -77,7 +77,7 @@ fun EnvelopeBuilder.multipart(
                 writeRawString(MULTIPART_DATA_SEPARATOR)
                 writeEnvelope(envelope)
                 meta {
-                    append(INDEX_KEY, buildMeta {
+                    append(INDEX_KEY, Meta {
                         "key" put key
                         "index" put counter
                     })
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
index 1101b35f..01606e6d 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt
@@ -2,29 +2,20 @@
 
 package hep.dataforge.io
 
+
 import hep.dataforge.context.Context
-import hep.dataforge.meta.descriptors.ItemDescriptor
-import hep.dataforge.meta.descriptors.NodeDescriptor
-import hep.dataforge.meta.descriptors.ValueDescriptor
 import hep.dataforge.meta.Meta
-import hep.dataforge.meta.MetaBase
-import hep.dataforge.meta.MetaItem
-import hep.dataforge.meta.serialization.toJson
-import hep.dataforge.meta.serialization.toMeta
-import hep.dataforge.names.NameToken
-import hep.dataforge.names.toName
-import hep.dataforge.values.*
+import hep.dataforge.meta.descriptors.NodeDescriptor
+import hep.dataforge.meta.node
+import hep.dataforge.meta.toJson
+import hep.dataforge.meta.toMetaItem
 import kotlinx.io.Input
 import kotlinx.io.Output
 import kotlinx.io.text.readUtf8String
 import kotlinx.io.text.writeUtf8String
 import kotlinx.serialization.UnstableDefault
-
-
-import kotlinx.serialization.json.*
-import kotlin.collections.component1
-import kotlin.collections.component2
-import kotlin.collections.set
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonObjectSerializer
 
 @OptIn(UnstableDefault::class)
 class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat {
@@ -37,11 +28,12 @@ class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat {
     override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
         val str = readUtf8String()
         val jsonElement = json.parseJson(str)
-        return jsonElement.toMeta()
+        val item = jsonElement.toMetaItem(descriptor)
+        return item.node ?: Meta.EMPTY
     }
 
     companion object : MetaFormatFactory {
-        val DEFAULT_JSON = Json{prettyPrint = true}
+        val DEFAULT_JSON = Json { prettyPrint = true }
 
         override fun invoke(meta: Meta, context: Context): MetaFormat = default
 
diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
index ebeedd6d..7023ce1a 100644
--- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
+++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt
@@ -8,7 +8,6 @@ import kotlinx.io.text.readRawString
 import kotlinx.io.text.readUtf8Line
 import kotlinx.io.text.writeRawString
 import kotlinx.io.text.writeUtf8String
-import kotlinx.serialization.toUtf8Bytes
 
 @ExperimentalIoApi
 class TaglessEnvelopeFormat(
@@ -155,7 +154,7 @@ class TaglessEnvelopeFormat(
         }
 
         do {
-            line = readUtf8Line() ?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
+            line = readUtf8Line() //?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
             offset += line.encodeToByteArray().size.toUInt()
             //returning an Envelope without data if end of input is reached
         } while (!line.startsWith(dataStart))
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt
index 9064a485..6fc801ec 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt
@@ -20,7 +20,7 @@ fun MetaFormat.fromBytes(packet: Bytes): Meta {
 class MetaFormatTest {
     @Test
     fun testBinaryMetaFormat() {
-        val meta = buildMeta {
+        val meta = Meta {
             "a" put 22
             "node" put {
                 "b" put "DDD"
@@ -35,7 +35,7 @@ class MetaFormatTest {
 
     @Test
     fun testJsonMetaFormat() {
-        val meta = buildMeta {
+        val meta = Meta {
             "a" put 22
             "node" put {
                 "b" put "DDD"
diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
index 37db8833..69b0427b 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
@@ -1,9 +1,9 @@
 package hep.dataforge.io
 
-import hep.dataforge.io.serialization.MetaItemSerializer
-import hep.dataforge.io.serialization.MetaSerializer
-import hep.dataforge.io.serialization.NameSerializer
-import hep.dataforge.meta.buildMeta
+import hep.dataforge.meta.Meta
+import hep.dataforge.meta.MetaItem
+import hep.dataforge.meta.MetaSerializer
+import hep.dataforge.names.Name
 import hep.dataforge.names.toName
 import kotlinx.serialization.cbor.Cbor
 import kotlinx.serialization.json.Json
@@ -13,7 +13,7 @@ import kotlin.test.assertEquals
 class MetaSerializerTest {
     @Test
     fun testMetaSerialization() {
-        val meta = buildMeta {
+        val meta = Meta {
             "a" put 22
             "node" put {
                 "b" put "DDD"
@@ -29,7 +29,7 @@ class MetaSerializerTest {
 
     @Test
     fun testCborSerialization() {
-        val meta = buildMeta {
+        val meta = Meta {
             "a" put 22
             "node" put {
                 "b" put "DDD"
@@ -47,13 +47,13 @@ class MetaSerializerTest {
     @Test
     fun testNameSerialization() {
         val name = "a.b.c".toName()
-        val string = Json.indented.stringify(NameSerializer, name)
-        val restored = Json.plain.parse(NameSerializer, string)
+        val string = Json.indented.stringify(Name.serializer(), name)
+        val restored = Json.plain.parse(Name.serializer(), string)
         assertEquals(restored, name)
     }
 
     @Test
-    fun testMetaItemDescriptor(){
-        val descriptor = MetaItemSerializer.descriptor.getElementDescriptor(0)
+    fun testMetaItemDescriptor() {
+        val descriptor = MetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0)
     }
 }
\ No newline at end of file
diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts
index f19a39fc..fea7ecd7 100644
--- a/dataforge-meta/build.gradle.kts
+++ b/dataforge-meta/build.gradle.kts
@@ -1,9 +1,9 @@
-import scientifik.useSerialization
+import scientifik.serialization
 
 plugins {
     id("scientifik.mpp")
 }
 
-useSerialization()
+serialization()
 
 description = "Meta definition and basic operations on meta"
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt
similarity index 90%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt
index 78357c6d..ee906008 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt
@@ -1,8 +1,5 @@
-package hep.dataforge.meta.serialization
+package hep.dataforge.meta
 
-import hep.dataforge.meta.Meta
-import hep.dataforge.meta.MetaBase
-import hep.dataforge.meta.MetaItem
 import hep.dataforge.meta.descriptors.ItemDescriptor
 import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.descriptors.ValueDescriptor
@@ -52,12 +49,7 @@ fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
     return JsonObject(map)
 }
 
-fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
-    return when (val item = toMetaItem(descriptor)) {
-        is MetaItem.NodeItem<*> -> item.node
-        is MetaItem.ValueItem -> item.value.toMeta()
-    }
-}
+fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): Meta = JsonMeta(this, descriptor)
 
 fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
     return when (this) {
@@ -106,7 +98,12 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M
                 this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
             }
             is JsonObject -> {
-                this[key] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
+                this[key] = MetaItem.NodeItem(
+                    JsonMeta(
+                        value,
+                        itemDescriptor as? NodeDescriptor
+                    )
+                )
             }
             is JsonArray -> {
                 when {
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index b37188b4..e5cda8f6 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -3,13 +3,9 @@ package hep.dataforge.meta
 import hep.dataforge.meta.Meta.Companion.VALUE_KEY
 import hep.dataforge.meta.MetaItem.NodeItem
 import hep.dataforge.meta.MetaItem.ValueItem
-import hep.dataforge.meta.serialization.toJson
 import hep.dataforge.names.*
-import hep.dataforge.values.EnumValue
-import hep.dataforge.values.Null
-import hep.dataforge.values.Value
-import hep.dataforge.values.boolean
-import kotlinx.serialization.Serializable
+import hep.dataforge.values.*
+import kotlinx.serialization.*
 
 
 /**
@@ -22,11 +18,33 @@ sealed class MetaItem<out M : Meta> {
     @Serializable
     data class ValueItem(val value: Value) : MetaItem<Nothing>() {
         override fun toString(): String = value.toString()
+
+        @Serializer(ValueItem::class)
+        companion object : KSerializer<ValueItem> {
+            override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor
+
+            override fun deserialize(decoder: Decoder): ValueItem = ValueItem(ValueSerializer.deserialize(decoder))
+
+            override fun serialize(encoder: Encoder, value: ValueItem) {
+                ValueSerializer.serialize(encoder, value.value)
+            }
+        }
     }
 
     @Serializable
     data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
         override fun toString(): String = node.toString()
+
+        @Serializer(NodeItem::class)
+        companion object : KSerializer<NodeItem<*>> {
+            override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor
+
+            override fun deserialize(decoder: Decoder): NodeItem<*> = NodeItem(MetaSerializer.deserialize(decoder))
+
+            override fun serialize(encoder: Encoder, value: NodeItem<*>) {
+                MetaSerializer.serialize(encoder, value.node)
+            }
+        }
     }
 
     companion object {
@@ -72,6 +90,7 @@ interface Meta : MetaRepr {
 
     companion object {
         const val TYPE = "meta"
+
         /**
          * A key for single value node
          */
@@ -101,6 +120,7 @@ operator fun Meta?.get(name: Name): MetaItem<*>? {
 }
 
 operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token)
+
 /**
  * Parse [Name] from [key] using full name notation and pass it to [Meta.get]
  */
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt
new file mode 100644
index 00000000..63a45664
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt
@@ -0,0 +1,53 @@
+package hep.dataforge.meta
+
+import hep.dataforge.names.NameToken
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.json.JsonInput
+import kotlinx.serialization.json.JsonObjectSerializer
+import kotlinx.serialization.json.JsonOutput
+
+
+private class DeserializedMeta(override val items: Map<NameToken, MetaItem<Meta>>) : MetaBase()
+
+/**
+ * Serialized for meta
+ */
+@Serializer(Meta::class)
+object MetaSerializer : KSerializer<Meta> {
+    private val mapSerializer = MapSerializer(
+        NameToken.serializer(),
+        MetaItem.serializer(MetaSerializer)
+    )
+
+    override val descriptor: SerialDescriptor get() = mapSerializer.descriptor
+
+    override fun deserialize(decoder: Decoder): Meta {
+        return if (decoder is JsonInput) {
+            JsonObjectSerializer.deserialize(decoder).toMeta()
+        } else {
+            DeserializedMeta(mapSerializer.deserialize(decoder))
+        }
+    }
+
+    override fun serialize(encoder: Encoder, value: Meta) {
+        if (encoder is JsonOutput) {
+            JsonObjectSerializer.serialize(encoder, value.toJson())
+        } else {
+            mapSerializer.serialize(encoder, value.items)
+        }
+    }
+}
+
+@Serializer(Config::class)
+object ConfigSerializer : KSerializer<Config> {
+    override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
+
+    override fun deserialize(decoder: Decoder): Config {
+        return MetaSerializer.deserialize(decoder).asConfig()
+    }
+
+    override fun serialize(encoder: Encoder, value: Config) {
+        MetaSerializer.serialize(encoder, value)
+    }
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
index ddf049e3..df3e97a5 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt
@@ -20,7 +20,7 @@ fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
  * Convert map of maps to meta
  */
 @DFExperimental
-fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = buildMeta {
+fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta {
     entries.forEach { (key, value) ->
         @Suppress("UNCHECKED_CAST")
         when (value) {
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
index c6e9a44a..da02c30e 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
@@ -201,7 +201,7 @@ fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWri
 fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> =
     config.node(key)
 
-fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item().map(
+fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(key).map(
     reader = { it.node },
     writer = { it?.let { MetaItem.NodeItem(it) } }
 )
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
index 78d0a511..8e74ba7b 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
@@ -39,7 +39,7 @@ open class Scheme() : Configurable, Described, MetaRepr {
      */
     open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY)
 
-    override fun toMeta(): Meta = Laminate(config, defaultLayer)
+    override fun toMeta(): Laminate = Laminate(config, defaultLayer)
 
     private inner class DefaultLayer(val path: Name) : MetaBase() {
         override val items: Map<NameToken, MetaItem<*>> =
@@ -87,10 +87,4 @@ open class MetaScheme(
 fun Meta.asScheme() =
     MetaScheme(this)
 
-fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)
-
-/**
- * Create a snapshot laminate
- */
-fun Scheme.toMeta(): Laminate =
-    Laminate(config, defaultLayer)
\ No newline at end of file
+fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt
deleted file mode 100644
index a98aa4c0..00000000
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-package hep.dataforge.meta.serialization
-
-import hep.dataforge.meta.*
-import hep.dataforge.names.NameToken
-import hep.dataforge.values.*
-import kotlinx.serialization.*
-import kotlinx.serialization.builtins.MapSerializer
-import kotlinx.serialization.builtins.list
-import kotlinx.serialization.builtins.serializer
-import kotlinx.serialization.json.JsonInput
-import kotlinx.serialization.json.JsonObjectSerializer
-import kotlinx.serialization.json.JsonOutput
-
-
-@Serializer(Value::class)
-@OptIn(InternalSerializationApi::class)
-object ValueSerializer : KSerializer<Value> {
-    //    private val valueTypeSerializer = EnumSerializer(ValueType::class)
-    private val listSerializer by lazy { ValueSerializer.list }
-
-    override val descriptor: SerialDescriptor = SerialDescriptor("Value") {
-        boolean("isList")
-        enum<ValueType>("valueType")
-        string("value")
-    }
-
-    private fun Decoder.decodeValue(): Value {
-        return when (decode(ValueType.serializer())) {
-            ValueType.NULL -> Null
-            ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
-            ValueType.BOOLEAN -> decodeBoolean().asValue()
-            ValueType.STRING -> decodeString().asValue()
-        }
-    }
-
-
-    override fun deserialize(decoder: Decoder): Value {
-        val isList = decoder.decodeBoolean()
-        return if (isList) {
-            listSerializer.deserialize(decoder).asValue()
-        } else {
-            decoder.decodeValue()
-        }
-    }
-
-    private fun Encoder.encodeValue(value: Value) {
-        encode(ValueType.serializer(), value.type)
-        when (value.type) {
-            ValueType.NULL -> {
-                // do nothing
-            }
-            ValueType.NUMBER -> encodeDouble(value.double)
-            ValueType.BOOLEAN -> encodeBoolean(value.boolean)
-            ValueType.STRING -> encodeString(value.string)
-        }
-    }
-
-    override fun serialize(encoder: Encoder, value: Value) {
-        encoder.encodeBoolean(value.isList())
-        if (value.isList()) {
-            listSerializer.serialize(encoder, value.list)
-        } else {
-            encoder.encodeValue(value)
-        }
-    }
-}
-
-private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase()
-
-/**
- * Serialized for meta
- */
-@Serializer(Meta::class)
-object MetaSerializer : KSerializer<Meta> {
-    private val mapSerializer = MapSerializer(
-        String.serializer(),
-        MetaItem.serializer(MetaSerializer)
-    )
-
-    override val descriptor: SerialDescriptor get() = mapSerializer.descriptor
-
-    override fun deserialize(decoder: Decoder): Meta {
-        return if (decoder is JsonInput) {
-            JsonObjectSerializer.deserialize(decoder).toMeta()
-        } else {
-            DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) })
-        }
-    }
-
-    override fun serialize(encoder: Encoder, value: Meta) {
-        if (encoder is JsonOutput) {
-            JsonObjectSerializer.serialize(encoder, value.toJson())
-        } else {
-            mapSerializer.serialize(encoder, value.items.mapKeys { it.key.toString() })
-        }
-    }
-}
-
-@Serializer(Config::class)
-object ConfigSerializer : KSerializer<Config> {
-    override val descriptor: SerialDescriptor = MetaSerializer.descriptor
-
-    override fun deserialize(decoder: Decoder): Config {
-        return MetaSerializer.deserialize(decoder).asConfig()
-    }
-
-    override fun serialize(encoder: Encoder, value: Config) {
-        MetaSerializer.serialize(encoder, value)
-    }
-}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serializationUtils.kt
similarity index 60%
rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt
rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serializationUtils.kt
index a92609e9..16c58bdc 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serializationUtils.kt
@@ -1,33 +1,36 @@
-package hep.dataforge.meta.serialization
+package hep.dataforge.meta
 
-import hep.dataforge.meta.DFExperimental
 import kotlinx.serialization.*
 import kotlinx.serialization.builtins.DoubleArraySerializer
-import kotlinx.serialization.internal.*
+import kotlinx.serialization.builtins.serializer
 
 fun SerialDescriptorBuilder.boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-    element(name, PrimitiveDescriptor(name, PrimitiveKind.BOOLEAN), isOptional = isOptional, annotations = annotations.toList())
+    element(name, Boolean.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 fun SerialDescriptorBuilder.string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-    element(name, PrimitiveDescriptor(name, PrimitiveKind.STRING), isOptional = isOptional, annotations = annotations.toList())
+    element(name, String.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 fun SerialDescriptorBuilder.int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-    element(name, PrimitiveDescriptor(name, PrimitiveKind.INT), isOptional = isOptional, annotations = annotations.toList())
+    element(name, Int.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 fun SerialDescriptorBuilder.double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-    element(name,PrimitiveDescriptor(name, PrimitiveKind.DOUBLE), isOptional = isOptional, annotations = annotations.toList())
+    element(name, Double.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 fun SerialDescriptorBuilder.float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-    element(name, PrimitiveDescriptor(name, PrimitiveKind.FLOAT), isOptional = isOptional, annotations = annotations.toList())
+    element(name, Float.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 fun SerialDescriptorBuilder.long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
-    element(name, PrimitiveDescriptor(name, PrimitiveKind.LONG), isOptional = isOptional, annotations = annotations.toList())
+    element(name, Long.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 fun SerialDescriptorBuilder.doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
     element(name, DoubleArraySerializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
 
 @OptIn(InternalSerializationApi::class)
-inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) {
+inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(
+    name: String,
+    isOptional: Boolean = false,
+    vararg annotations: Annotation
+) {
     val enumDescriptor = SerialDescriptor(serialName, UnionKind.ENUM_KIND) {
         enumValues<E>().forEach {
             val fqn = "$serialName.${it.name}"
@@ -38,17 +41,11 @@ inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(name: String, isOp
     element(name, enumDescriptor, isOptional = isOptional, annotations = annotations.toList())
 }
 
-//inline fun <reified T : Any> KSerializer<T>.descriptor(
-//    name: String,
-//    block: SerialDescriptorBuilder.() -> Unit
-//): SerialDescriptor =
-//    SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
-
 @DFExperimental
 inline fun <R> Decoder.decodeStructure(
     desc: SerialDescriptor,
     vararg typeParams: KSerializer<*> = emptyArray(),
-    crossinline block:  CompositeDecoder.() -> R
+    crossinline block: CompositeDecoder.() -> R
 ): R {
     val decoder = beginStructure(desc, *typeParams)
     val res = decoder.block()
@@ -56,6 +53,7 @@ inline fun <R> Decoder.decodeStructure(
     return res
 }
 
+@DFExperimental
 inline fun Encoder.encodeStructure(
     desc: SerialDescriptor,
     vararg typeParams: KSerializer<*> = emptyArray(),
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
index 255a2ffd..2b8908ed 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt
@@ -53,14 +53,13 @@ class Name(val tokens: List<NameToken>) {
         }
     }
 
-
     @Serializer(Name::class)
-    companion object: KSerializer<Name> {
+    companion object : KSerializer<Name> {
         const val NAME_SEPARATOR = "."
 
         val EMPTY = Name(emptyList())
 
-        override val descriptor: SerialDescriptor = PrimitiveDescriptor("Name", PrimitiveKind.STRING)
+        override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
 
         override fun deserialize(decoder: Decoder): Name {
             return decoder.decodeString().toName()
@@ -99,8 +98,8 @@ data class NameToken(val body: String, val index: String = "") {
     fun hasIndex() = index.isNotEmpty()
 
     @Serializer(NameToken::class)
-    companion object :KSerializer<NameToken>{
-        override val descriptor: SerialDescriptor = PrimitiveDescriptor("NameToken", PrimitiveKind.STRING)
+    companion object : KSerializer<NameToken> {
+        override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
 
         override fun deserialize(decoder: Decoder): NameToken {
             return decoder.decodeString().toName().first()!!
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt
new file mode 100644
index 00000000..8055b554
--- /dev/null
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt
@@ -0,0 +1,59 @@
+package hep.dataforge.values
+
+import hep.dataforge.meta.boolean
+import hep.dataforge.meta.enum
+import hep.dataforge.meta.string
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.list
+
+@Serializer(Value::class)
+object ValueSerializer : KSerializer<Value> {
+    private val listSerializer by lazy { ValueSerializer.list }
+
+    override val descriptor: SerialDescriptor =
+        SerialDescriptor("hep.dataforge.values.Value") {
+            boolean("isList")
+            enum<ValueType>("valueType")
+            string("value")
+        }
+
+    private fun Decoder.decodeValue(): Value {
+        return when (decode(ValueType.serializer())) {
+            ValueType.NULL -> Null
+            ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
+            ValueType.BOOLEAN -> decodeBoolean().asValue()
+            ValueType.STRING -> decodeString().asValue()
+        }
+    }
+
+
+    override fun deserialize(decoder: Decoder): Value {
+        val isList = decoder.decodeBoolean()
+        return if (isList) {
+            listSerializer.deserialize(decoder).asValue()
+        } else {
+            decoder.decodeValue()
+        }
+    }
+
+    private fun Encoder.encodeValue(value: Value) {
+        encode(ValueType.serializer(), value.type)
+        when (value.type) {
+            ValueType.NULL -> {
+                // do nothing
+            }
+            ValueType.NUMBER -> encodeDouble(value.double)
+            ValueType.BOOLEAN -> encodeBoolean(value.boolean)
+            ValueType.STRING -> encodeString(value.string)
+        }
+    }
+
+    override fun serialize(encoder: Encoder, value: Value) {
+        encoder.encodeBoolean(value.isList())
+        if (value.isList()) {
+            listSerializer.serialize(encoder, value.list)
+        } else {
+            encoder.encodeValue(value)
+        }
+    }
+}
\ No newline at end of file
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt
index f2fffd19..0f4c19be 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt
@@ -11,12 +11,12 @@ class MetaExtensionTest {
 
     @Test
     fun testEnum(){
-        val meta = buildMeta{"enum" put TestEnum.test}
+        val meta = Meta{"enum" put TestEnum.test}
         meta["enum"].enum<TestEnum>()
     }
     @Test
     fun testEnumByString(){
-        val meta = buildMeta{"enum" put TestEnum.test.name}
+        val meta = Meta{"enum" put TestEnum.test.name}
         println(meta["enum"].enum<TestEnum>())
     }
 
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt
index fb424116..c55cd4ad 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt
@@ -16,13 +16,13 @@ class MetaTest {
 
     @Test
     fun metaEqualityTest() {
-        val meta1 = buildMeta {
+        val meta1 = Meta {
             "a" put 22
             "b" put {
                 "c" put "ddd"
             }
         }
-        val meta2 = buildMeta {
+        val meta2 = Meta {
             "b" put {
                 "c" put "ddd"
             }
@@ -33,13 +33,13 @@ class MetaTest {
 
     @Test
     fun metaToMap(){
-        val meta = buildMeta {
+        val meta = Meta {
             "a" put 22
             "b" put {
                 "c" put "ddd"
             }
             "list" put (0..4).map {
-                buildMeta {
+                Meta {
                     "value" put it
                 }
             }
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt
index 194c77e3..ba44edec 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt
@@ -6,7 +6,7 @@ import kotlin.test.assertEquals
 class MutableMetaTest{
     @Test
     fun testRemove(){
-        val meta = buildMeta {
+        val meta = Meta {
             "aNode" put {
                 "innerNode" put {
                     "innerValue" put true
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
index 09a3e03c..bcebedc6 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt
@@ -2,7 +2,6 @@ package hep.dataforge.meta
 
 import hep.dataforge.meta.scheme.asScheme
 import hep.dataforge.meta.scheme.getProperty
-import hep.dataforge.meta.scheme.toMeta
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
@@ -10,7 +9,7 @@ import kotlin.test.assertEquals
 class SchemeTest{
     @Test
     fun testMetaScheme(){
-        val styled = buildMeta {
+        val styled = Meta {
             repeat(10){
                 "b.a[$it]" put {
                     "d" put it
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt
index 29ce27b2..1180ca23 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt
@@ -15,7 +15,7 @@ import kotlinx.io.asBinary
 suspend fun Table<Value>.wrap(): Envelope = Envelope {
     meta {
         header.forEachIndexed { index, columnHeader ->
-            set("column", index.toString(), buildMeta {
+            set("column", index.toString(), Meta {
                 "name" put columnHeader.name
                 if (!columnHeader.meta.isEmpty()) {
                     "meta" put columnHeader.meta
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt
index 72402a17..ed5ab7f0 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt
@@ -5,7 +5,6 @@ import hep.dataforge.data.DataNode
 import hep.dataforge.data.filter
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaRepr
-import hep.dataforge.meta.buildMeta
 import hep.dataforge.names.Name
 import hep.dataforge.names.asName
 import hep.dataforge.names.isEmpty
@@ -28,7 +27,7 @@ class DataDependency(val filter: DataFilter, val placement: Name = Name.EMPTY) :
         }
     }
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "data" put filter.config
         "to" put placement.toString()
     }
@@ -41,7 +40,7 @@ class AllDataDependency(val placement: Name = Name.EMPTY) : Dependency() {
         DataNode.invoke(Any::class) { this[placement] = workspace.data }
     }
 
-    override fun toMeta() = buildMeta {
+    override fun toMeta() = Meta {
         "data" put "@all"
         "to" put placement.toString()
     }
@@ -69,7 +68,7 @@ abstract class TaskDependency<out T : Any>(
         }
     }
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "task" put name.toString()
         "meta" put meta
         "to" put placement.toString()
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
index ede9efaa..a811f428 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt
@@ -29,7 +29,7 @@ data class TaskModel(
     //TODO provide a way to get task descriptor
     //TODO add pre-run check of task result type?
 
-    override fun toMeta(): Meta = buildMeta {
+    override fun toMeta(): Meta = Meta {
         "name" put name.toString()
         "meta" put meta
         "dependsOn" put {
@@ -98,7 +98,7 @@ fun <T : Any> TaskDependencyContainer.dependsOn(
     placement: Name = Name.EMPTY,
     metaBuilder: MetaBuilder.() -> Unit
 ): DirectTaskDependency<T> =
-    dependsOn(task, placement, buildMeta(metaBuilder))
+    dependsOn(task, placement, Meta(metaBuilder))
 
 /**
  * Add custom data dependency
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt
index 31da6c56..ac2b1131 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt
@@ -8,7 +8,6 @@ import hep.dataforge.data.DataNode
 import hep.dataforge.data.dataSequence
 import hep.dataforge.meta.Meta
 import hep.dataforge.meta.MetaBuilder
-import hep.dataforge.meta.buildMeta
 import hep.dataforge.names.Name
 import hep.dataforge.names.toName
 import hep.dataforge.provider.Provider
@@ -76,7 +75,7 @@ fun Workspace.run(task: String, meta: Meta) =
     tasks[task.toName()]?.let { run(it, meta) } ?: error("Task with name $task not found")
 
 fun Workspace.run(task: String, block: MetaBuilder.() -> Unit = {}) =
-    run(task, buildMeta(block))
+    run(task, Meta(block))
 
 fun <T: Any> Workspace.run(task: Task<T>, metaBuilder: MetaBuilder.() -> Unit = {}): DataNode<T> =
-    run(task, buildMeta(metaBuilder))
\ No newline at end of file
+    run(task, Meta(metaBuilder))
\ No newline at end of file
diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
index 7b831a50..3bc1ffcf 100644
--- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
+++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt
@@ -52,7 +52,7 @@ fun WorkspaceBuilder.data(
 
 
 fun WorkspaceBuilder.target(name: String, block: MetaBuilder.() -> Unit) {
-    targets[name] = buildMeta(block).seal()
+    targets[name] = Meta(block).seal()
 }
 
 /**

From 4ab71a79db75e3fd65760dd2a86926886c8caa4d Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 15 Mar 2020 09:22:08 +0300
Subject: [PATCH 37/41] Fix meta serialization

---
 .../hep/dataforge/io/MetaSerializerTest.kt    |  27 ++++++------------
 .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt |   3 +-
 .../kotlin/hep/dataforge/io/functionsJVM.kt   |   3 +-
 .../kotlin/hep/dataforge/meta/Config.kt       |  15 +++++++++-
 .../kotlin/hep/dataforge/meta/Meta.kt         |  13 +++++----
 .../hep/dataforge/meta/MetaSerializer.kt      |  19 ++----------
 .../meta/scheme/ConfigurableDelegate.kt       |   5 ++--
 gradle/wrapper/gradle-wrapper.jar             | Bin 58702 -> 58695 bytes
 gradle/wrapper/gradle-wrapper.properties      |   2 +-
 gradlew.bat                                   |   3 ++
 10 files changed, 44 insertions(+), 46 deletions(-)

diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
index 69b0427b..86a569a6 100644
--- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
+++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt
@@ -11,17 +11,17 @@ import kotlin.test.Test
 import kotlin.test.assertEquals
 
 class MetaSerializerTest {
+    val meta = Meta {
+        "a" put 22
+        "node" put {
+            "b" put "DDD"
+            "c" put 11.1
+            "array" put doubleArrayOf(1.0, 2.0, 3.0)
+        }
+    }
+
     @Test
     fun testMetaSerialization() {
-        val meta = Meta {
-            "a" put 22
-            "node" put {
-                "b" put "DDD"
-                "c" put 11.1
-                "array" put doubleArrayOf(1.0, 2.0, 3.0)
-            }
-        }
-
         val string = Json.indented.stringify(MetaSerializer, meta)
         val restored = Json.plain.parse(MetaSerializer, string)
         assertEquals(restored, meta)
@@ -29,15 +29,6 @@ class MetaSerializerTest {
 
     @Test
     fun testCborSerialization() {
-        val meta = Meta {
-            "a" put 22
-            "node" put {
-                "b" put "DDD"
-                "c" put 11.1
-                "array" put doubleArrayOf(1.0, 2.0, 3.0)
-            }
-        }
-
         val bytes = Cbor.dump(MetaSerializer, meta)
         println(bytes.contentToString())
         val restored = Cbor.load(MetaSerializer, bytes)
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
index 1fe9d59d..c9b4f1bf 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
@@ -1,9 +1,9 @@
 package hep.dataforge.io
 
-import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.DFExperimental
 import hep.dataforge.meta.EmptyMeta
 import hep.dataforge.meta.Meta
+import hep.dataforge.meta.descriptors.NodeDescriptor
 import hep.dataforge.meta.isEmpty
 import kotlinx.io.*
 import java.nio.file.Files
@@ -14,6 +14,7 @@ import kotlin.streams.asSequence
 /**
  * Resolve IOFormat based on type
  */
+@Suppress("UNCHECKED_CAST")
 @DFExperimental
 inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
     return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>?
diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt
index fae986d7..ffb924ef 100644
--- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt
+++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt
@@ -3,7 +3,6 @@ package hep.dataforge.io
 import hep.dataforge.io.functions.FunctionServer
 import hep.dataforge.io.functions.function
 import hep.dataforge.meta.Meta
-import hep.dataforge.meta.buildMeta
 import hep.dataforge.names.Name
 import kotlin.reflect.KClass
 import kotlin.reflect.full.isSuperclassOf
@@ -14,7 +13,7 @@ fun IOPlugin.resolveIOFormatName(type: KClass<*>): Name {
         ?: error("Can't resolve IOFormat for type $type")
 }
 
-inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = buildMeta {
+inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = Meta {
     FunctionServer.FUNCTION_NAME_KEY put functionName
     FunctionServer.INPUT_FORMAT_KEY put resolveIOFormatName(T::class).toString()
     FunctionServer.OUTPUT_FORMAT_KEY put resolveIOFormatName(R::class).toString()
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
index 833117b5..53a2e184 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt
@@ -4,6 +4,7 @@ import hep.dataforge.names.Name
 import hep.dataforge.names.NameToken
 import hep.dataforge.names.asName
 import hep.dataforge.names.plus
+import kotlinx.serialization.*
 
 //TODO add validator to configuration
 
@@ -20,6 +21,7 @@ interface ObservableMeta : Meta {
 /**
  * Mutable meta representing object state
  */
+@Serializable
 class Config : AbstractMutableMeta<Config>(), ObservableMeta {
 
     private val listeners = HashSet<MetaListener>()
@@ -66,8 +68,19 @@ class Config : AbstractMutableMeta<Config>(), ObservableMeta {
 
     override fun empty(): Config = Config()
 
-    companion object {
+    @Serializer(Config::class)
+    companion object ConfigSerializer : KSerializer<Config> {
+
         fun empty(): Config = Config()
+        override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
+
+        override fun deserialize(decoder: Decoder): Config {
+            return MetaSerializer.deserialize(decoder).asConfig()
+        }
+
+        override fun serialize(encoder: Encoder, value: Config) {
+            MetaSerializer.serialize(encoder, value)
+        }
     }
 }
 
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
index e5cda8f6..672a1922 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt
@@ -15,6 +15,7 @@ import kotlinx.serialization.*
  */
 @Serializable
 sealed class MetaItem<out M : Meta> {
+
     @Serializable
     data class ValueItem(val value: Value) : MetaItem<Nothing>() {
         override fun toString(): String = value.toString()
@@ -32,12 +33,13 @@ sealed class MetaItem<out M : Meta> {
     }
 
     @Serializable
-    data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
+    data class NodeItem<M : Meta>(@Serializable(MetaSerializer::class) val node: M) : MetaItem<M>() {
+        //Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializeable
         override fun toString(): String = node.toString()
 
         @Serializer(NodeItem::class)
-        companion object : KSerializer<NodeItem<*>> {
-            override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor
+        companion object : KSerializer<NodeItem<out Meta>> {
+            override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
 
             override fun deserialize(decoder: Decoder): NodeItem<*> = NodeItem(MetaSerializer.deserialize(decoder))
 
@@ -199,8 +201,9 @@ abstract class AbstractMetaNode<M : MetaNode<M>> : MetaNode<M>, MetaBase()
  *
  * If the argument is possibly mutable node, it is copied on creation
  */
-class SealedMeta internal constructor(override val items: Map<NameToken, MetaItem<SealedMeta>>) :
-    AbstractMetaNode<SealedMeta>()
+class SealedMeta internal constructor(
+    override val items: Map<NameToken, MetaItem<SealedMeta>>
+) : AbstractMetaNode<SealedMeta>()
 
 /**
  * Generate sealed node from [this]. If it is already sealed return it as is
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt
index 63a45664..6f0db59e 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt
@@ -8,8 +8,6 @@ import kotlinx.serialization.json.JsonObjectSerializer
 import kotlinx.serialization.json.JsonOutput
 
 
-private class DeserializedMeta(override val items: Map<NameToken, MetaItem<Meta>>) : MetaBase()
-
 /**
  * Serialized for meta
  */
@@ -26,7 +24,9 @@ object MetaSerializer : KSerializer<Meta> {
         return if (decoder is JsonInput) {
             JsonObjectSerializer.deserialize(decoder).toMeta()
         } else {
-            DeserializedMeta(mapSerializer.deserialize(decoder))
+            object : MetaBase() {
+                override val items: Map<NameToken, MetaItem<*>> = mapSerializer.deserialize(decoder)
+            }
         }
     }
 
@@ -37,17 +37,4 @@ object MetaSerializer : KSerializer<Meta> {
             mapSerializer.serialize(encoder, value.items)
         }
     }
-}
-
-@Serializer(Config::class)
-object ConfigSerializer : KSerializer<Config> {
-    override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
-
-    override fun deserialize(decoder: Decoder): Config {
-        return MetaSerializer.deserialize(decoder).asConfig()
-    }
-
-    override fun serialize(encoder: Encoder, value: Config) {
-        MetaSerializer.serialize(encoder, value)
-    }
 }
\ No newline at end of file
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
index da02c30e..da6d92a8 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
@@ -168,8 +168,9 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any
 /**
  * Enum delegate
  */
-inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> =
-    item(default, key).transform { it.enum<E>() ?: default }
+inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> {
+    return item(default, key).transform { it.enum<E>() ?: default }
+}
 
 /*
  * Extra delegates for special cases
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644
GIT binary patch
delta 12333
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

delta 12451
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

diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6ce793f2..a2bf1313 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew.bat b/gradlew.bat
index 9618d8d9..62bd9b9c 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
 set APP_BASE_NAME=%~n0
 set APP_HOME=%DIRNAME%
 
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
 

From 2c52e9aaa34e160f69c8f9c57fbc3937900016d4 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 15 Mar 2020 13:33:20 +0300
Subject: [PATCH 38/41] Workaround for enum delegate

---
 build.gradle.kts                                      |  2 +-
 .../kotlin/hep/dataforge/meta/scheme/Configurable.kt  |  8 ++++++--
 .../hep/dataforge/meta/scheme/ConfigurableDelegate.kt |  7 +++----
 .../kotlin/hep/dataforge/meta/scheme/Specification.kt | 11 +++++++----
 .../commonMain/kotlin/hep/dataforge/values/Value.kt   |  4 +++-
 .../kotlin/hep/dataforge/meta/MetaDelegateTest.kt     |  8 +++++---
 .../kotlin/hep/dataforge/tables/ColumnScheme.kt       |  3 ++-
 7 files changed, 27 insertions(+), 16 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 0d4cc1fb..fca616de 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,6 @@
 
 plugins {
-    val toolsVersion = "0.4.0"
+    val toolsVersion = "0.4.0-dev"
     id("scientifik.mpp") version toolsVersion apply false
     id("scientifik.jvm") version toolsVersion apply false
     id("scientifik.publish") version toolsVersion apply false
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
index 42aaeb07..df8c8e93 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
@@ -4,6 +4,7 @@ import hep.dataforge.meta.*
 import hep.dataforge.meta.descriptors.*
 import hep.dataforge.names.Name
 import hep.dataforge.names.toName
+import hep.dataforge.values.Value
 
 /**
  * A container that holds a [Config] and a default item provider.
@@ -45,17 +46,20 @@ fun Configurable.getProperty(key: String) = getProperty(key.toName())
  * Set a configurable property
  */
 fun Configurable.setProperty(name: Name, item: MetaItem<*>?) {
-    if(validateItem(name,item)) {
+    if (validateItem(name, item)) {
         config[name] = item
     } else {
         error("Validation failed for property $name with value $item")
     }
 }
 
+fun Configurable.setProperty(name: Name, value: Value) = setProperty(name, MetaItem.ValueItem(value))
+fun Configurable.setProperty(name: Name, meta: Meta) = setProperty(name, MetaItem.NodeItem(meta))
+
 fun Configurable.setProperty(key: String, item: MetaItem<*>?) {
     setProperty(key.toName(), item)
 }
 
 fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
 
-fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
+inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
index da6d92a8..a83bc62c 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
@@ -168,14 +168,13 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any
 /**
  * Enum delegate
  */
-inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> {
-    return item(default, key).transform { it.enum<E>() ?: default }
-}
+fun <E : Enum<E>> Configurable.enum(
+    default: E, key: Name? = null, resolve: MetaItem<*>.() -> E?
+): ReadWriteProperty<Any?, E> = item(default, key).transform {it?.resolve() ?: default }
 
 /*
  * Extra delegates for special cases
  */
-
 fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty<Any?, List<String>> =
     item(listOf(*strings), key) {
         it?.value?.stringList ?: emptyList()
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt
index 4aa7bf02..1783e841 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt
@@ -29,14 +29,14 @@ interface Specification<T : Configurable> {
     /**
      * Wrap a configuration using static meta as default
      */
-    fun wrap(config: Config = Config(), default: Meta): T = wrap(config){default[it]}
+    fun wrap(config: Config = Config(), default: Meta): T = wrap(config) { default[it] }
 
     /**
      * Wrap a configuration using static meta as default
      */
     fun wrap(default: Meta): T = wrap(
         Config()
-    ){default[it]}
+    ) { default[it] }
 }
 
 /**
@@ -57,8 +57,11 @@ fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action
 fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
     Config().also { update(it, action) }
 
-fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(
-    Config(), it) }
+fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let {
+    spec.wrap(
+        Config(), it
+    )
+}
 
 @JvmName("configSpec")
 fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt
index ccc92d50..28a5838c 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt
@@ -44,7 +44,7 @@ interface Value {
      * get this value represented as List
      */
     val list: List<Value>
-        get() = if(this == Null) emptyList() else listOf(this)
+        get() = if (this == Null) emptyList() else listOf(this)
 
     override fun equals(other: Any?): Boolean
 
@@ -231,6 +231,8 @@ fun FloatArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { Numbe
 
 fun ByteArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { NumberValue(it) })
 
+fun <E : Enum<E>> E.asValue(): Value = EnumValue(this)
+
 
 /**
  * Create Value from String using closest match conversion
diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
index 4cf2002f..277c2a6c 100644
--- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
+++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
@@ -13,15 +13,17 @@ class MetaDelegateTest {
 
     class InnerSpec : Scheme() {
         var innerValue by string()
-        companion object: SchemeSpec<InnerSpec>(::InnerSpec)
+
+        companion object : SchemeSpec<InnerSpec>(::InnerSpec)
     }
 
     class TestScheme : Scheme() {
         var myValue by string()
         var safeValue by double(2.2)
-        var enumValue by enum(TestEnum.YES)
+        var enumValue by enum(TestEnum.YES) { enum<TestEnum>() }
         var inner by spec(InnerSpec)
-        companion object: SchemeSpec<TestScheme>(::TestScheme)
+
+        companion object : SchemeSpec<TestScheme>(::TestScheme)
     }
 
     @Test
diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
index 2b65b234..7d364784 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt
@@ -1,5 +1,6 @@
 package hep.dataforge.tables
 
+import hep.dataforge.meta.enum
 import hep.dataforge.meta.scheme.Scheme
 import hep.dataforge.meta.scheme.SchemeSpec
 import hep.dataforge.meta.scheme.enum
@@ -13,5 +14,5 @@ open class ColumnScheme : Scheme() {
 }
 
 class ValueColumnScheme : ColumnScheme() {
-    var valueType by enum(ValueType.STRING)
+    var valueType by enum(ValueType.STRING){enum<ValueType>()}
 }
\ No newline at end of file

From b78fc2e7d7fe6ec675c42fbe3e5c44f4da1e6b7a Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 15 Mar 2020 13:34:13 +0300
Subject: [PATCH 39/41] bump version

---
 build.gradle.kts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index fca616de..7e41aa3b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ plugins {
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-10")
+val dataforgeVersion by extra("0.1.5-dev-11")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")

From bc8878da4822d9fefb7f8219b814d03701f79189 Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Sun, 15 Mar 2020 13:45:24 +0300
Subject: [PATCH 40/41] Remove redundant cast in tables

---
 .../commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt  | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
index 2675b483..ae3c386e 100644
--- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
+++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt
@@ -134,14 +134,11 @@ private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) {
  * Write rows without header to the output
  */
 suspend fun Output.writeRows(rows: Rows<Value>) {
-    @Suppress("UNCHECKED_CAST") val header = rows.header.map {
-        if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader<Value>)
-    }
-    val widths: List<Int> = header.map {
+    val widths: List<Int> = rows.header.map {
         it.textWidth
     }
     rows.rowFlow().collect { row ->
-        header.forEachIndexed { index, columnHeader ->
+        rows.header.forEachIndexed { index, columnHeader ->
             writeValue(row[columnHeader] ?: Null, widths[index])
         }
         writeUtf8String("\r\n")

From e835d811833a19ed7a94f6559f2143f602c66e4a Mon Sep 17 00:00:00 2001
From: Alexander Nozik <altavir@gmail.com>
Date: Mon, 16 Mar 2020 22:52:54 +0300
Subject: [PATCH 41/41] Utility methods to access Configurable properties

---
 build.gradle.kts                              |  4 +-
 .../meta/descriptors/ItemDescriptor.kt        |  1 +
 .../hep/dataforge/meta/scheme/Configurable.kt | 39 +++++++++---------
 .../meta/scheme/ConfigurableDelegate.kt       | 40 +++++++++++++------
 .../hep/dataforge/meta/scheme/Scheme.kt       |  8 ++--
 5 files changed, 55 insertions(+), 37 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 7e41aa3b..1fe59eff 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,12 +1,12 @@
 
 plugins {
-    val toolsVersion = "0.4.0-dev"
+    val toolsVersion = "0.4.0"
     id("scientifik.mpp") version toolsVersion apply false
     id("scientifik.jvm") version toolsVersion apply false
     id("scientifik.publish") version toolsVersion apply false
 }
 
-val dataforgeVersion by extra("0.1.5-dev-11")
+val dataforgeVersion by extra("0.1.5")
 
 val bintrayRepo by extra("dataforge")
 val githubProject by extra("dataforge-core")
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
index 4ff5de11..84d0d5c0 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt
@@ -67,6 +67,7 @@ fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean {
  *
  * @author Alexander Nozik
  */
+@DFBuilder
 class NodeDescriptor : ItemDescriptor() {
 
     /**
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
index df8c8e93..4ae5e180 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt
@@ -32,34 +32,37 @@ interface Configurable : Described {
     }
 
     override val descriptor: NodeDescriptor? get() = null
-}
 
-/**
- * Get a property with default
- */
-fun Configurable.getProperty(name: Name): MetaItem<*>? =
-    config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem()
+    /**
+     * Get a property with default
+     */
+    fun getProperty(name: Name): MetaItem<*>? =
+        config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem()
 
-fun Configurable.getProperty(key: String) = getProperty(key.toName())
-
-/**
- * Set a configurable property
- */
-fun Configurable.setProperty(name: Name, item: MetaItem<*>?) {
-    if (validateItem(name, item)) {
-        config[name] = item
-    } else {
-        error("Validation failed for property $name with value $item")
+    /**
+     * Set a configurable property
+     */
+    fun setProperty(name: Name, item: MetaItem<*>?) {
+        if (validateItem(name, item)) {
+            config[name] = item
+        } else {
+            error("Validation failed for property $name with value $item")
+        }
     }
 }
 
-fun Configurable.setProperty(name: Name, value: Value) = setProperty(name, MetaItem.ValueItem(value))
-fun Configurable.setProperty(name: Name, meta: Meta) = setProperty(name, MetaItem.NodeItem(meta))
+fun Configurable.getProperty(key: String) = getProperty(key.toName())
+
+fun Configurable.setProperty(name: Name, value: Value?) = setProperty(name, value?.let { MetaItem.ValueItem(value) })
+fun Configurable.setProperty(name: Name, meta: Meta?) = setProperty(name, meta?.let { MetaItem.NodeItem(meta) })
 
 fun Configurable.setProperty(key: String, item: MetaItem<*>?) {
     setProperty(key.toName(), item)
 }
 
+fun Configurable.setProperty(key: String, value: Value?) = setProperty(key, value?.let { MetaItem.ValueItem(value) })
+fun Configurable.setProperty(key: String, meta: Meta?) = setProperty(key, meta?.let { MetaItem.NodeItem(meta) })
+
 fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
 
 inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
index a83bc62c..c582282b 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
@@ -170,7 +170,7 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any
  */
 fun <E : Enum<E>> Configurable.enum(
     default: E, key: Name? = null, resolve: MetaItem<*>.() -> E?
-): ReadWriteProperty<Any?, E> = item(default, key).transform {it?.resolve() ?: default }
+): ReadWriteProperty<Any?, E> = item(default, key).transform { it?.resolve() ?: default }
 
 /*
  * Extra delegates for special cases
@@ -206,17 +206,31 @@ fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(
     writer = { it?.let { MetaItem.NodeItem(it) } }
 )
 
-fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> =
-    object : ReadWriteProperty<Any?, T?> {
-        override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
-            val name = key ?: property.name.asName()
-            return config[name].node?.let { spec.wrap(it) }
-        }
-
-        override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
-            val name = key ?: property.name.asName()
-            config[name] = value?.config
-        }
-
+fun <T : Configurable> Configurable.spec(
+    spec: Specification<T>, key: Name? = null
+): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
+        val name = key ?: property.name.asName()
+        return config[name].node?.let { spec.wrap(it) }
     }
 
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
+        val name = key ?: property.name.asName()
+        config[name] = value?.config
+    }
+}
+
+fun <T : Configurable> Configurable.spec(
+    spec: Specification<T>, default: T, key: Name? = null
+): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+        val name = key ?: property.name.asName()
+        return config[name].node?.let { spec.wrap(it) }?:default
+    }
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+        val name = key ?: property.name.asName()
+        config[name] = value.config
+    }
+}
+
diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
index 8e74ba7b..b8d6257f 100644
--- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
+++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt
@@ -18,11 +18,10 @@ open class Scheme() : Configurable, Described, MetaRepr {
     //constructor(config: Config, default: Meta) : this(config, { default[it] })
     constructor(config: Config) : this(config, { null })
 
-    final override var config: Config =
-        Config()
+    final override var config: Config = Config()
         internal set
 
-    lateinit var defaultProvider: (Name) -> MetaItem<*>?
+    var defaultProvider: (Name) -> MetaItem<*>? = { null }
         internal set
 
     final override var descriptor: NodeDescriptor? = null
@@ -53,9 +52,10 @@ open class Scheme() : Configurable, Described, MetaRepr {
                 token to item
             } ?: emptyMap()
     }
-
 }
 
+inline operator fun <T : Scheme> T.invoke(block: T.() -> Unit) = apply(block)
+
 /**
  * A specification for simplified generation of wrappers
  */