
# 🌀 JSYN — a Next-Generation Streaming Data Format

JSYN is a universal streaming serialization format for JavaScript.
It’s designed not just to store data, but to work with it in real time — allowing read and write operations directly from a stream, at any nesting level.

## 💡 Key Features

🔥 True streaming on every level — each nested object or array behaves as an independent stream.
	There’s no need to load the entire structure into memory or know its total size in advance.

⚡ Partial read and write support — data can be processed as it arrives, before the full document is available.
	This makes JSYN a perfect foundation for network protocols and streaming formats.

🧩 Structural universality — there’s no “special” document type.
Every level of data behaves the same, whether it’s the root, an array, or a nested object.

♻️ Zero redundancy — JSYN doesn’t enforce its own built-in types.
All values are read as plain JavaScript types, and extended types can be added flexibly through user-defined hooks.

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Supported Types](#supported-types)
- [Extended Types](#extended-types)
- [BSON Integration](#bson-integration)
- [Binary Format](#binary-format)
- [Root Scope END Byte](#root-scope-end-byte)
- [Writing Data](#writing-data)
	- [Writer Lifecycle](#writer-lifecycle)
	- [Writer Scopes](#writer-scopes)
	- [Choosing a Sink](#choosing-a-sink)
	- [Hooks](#hooks)
	- [Writing Examples](#writing-examples)
- [Reading Data](#reading-data)
	- [Reader Lifecycle](#reader-lifecycle)
	- [Read Methods](#read-methods)
	- [Reader Scopes](#reader-scopes)
	- [Choosing a Source](#choosing-a-source)
	- [Reading Examples](#reading-examples)
- [Examples](#examples)
	- [File System Packing and Unpacking](#file-system-packing-and-unpacking)
	- [Database Serialization (MongoDB)](#database-serialization-mongodb)
	- [Simple Network Protocol](#simple-network-protocol)

## API Reference

- [JSYN (Namespace)](#jsyn-namespace)
	- [Writer](#writer)
	- [Reader](#reader)
	- [HookList](#hooklist)
	- [TypeMap](#typemap)
	- [Value](#value)
	- [types](#types)
	- [conf](#conf)
	- [JSYN](#jsyn)
	- [encode(...values)](#encodevalues)
	- [decode(source)](#decodesource)
	- [typeOf(value)](#typeofvalue)
	- [isSupported(value)](#issupportedvalue)
	- [isExt(value)](#isextvalue)
	- [ext(typeid, payload)](#exttypeid-payload)
- [JSYN.Writer](#jsynwriter)
	- [new Writer(sink, options)](#new-writersink-options)
	- **Properties**
	- [writer.state](#writerstate)
	- [writer.opened](#writeropened)
	- [writer.closed](#writerclosed)
	- [writer.failed](#writerfailed)
	- [writer.error](#writererror)
	- [writer.writable](#writerwritable)
	- [writer.storable](#writerstorable)
	- [writer.depth](#writerdepth)
	- [writer.path](#writerpath)
	- [writer.scope](#writerscope)
	- [writer.written](#writerwritten)
	- [writer.staged](#writerstaged)
	- [writer.hooks](#writerhooks)
	- **Writing Methods**
	- [writer.end(...values)](#writerendvalues)
	- [writer.key(key)](#writerkeykey)
	- [writer.entry(key, value)](#writerentrykey-value)
	- [writer.value(value)](#writervaluevalue)
	- [writer.push(...values)](#writerpushvalues)
	- [writer.add(value)](#writeraddvalue)
	- [writer.ext(typeid, payload)](#writerexttypeid-payload)
	- [writer.object()](#writerobject)
	- [writer.map()](#writermap)
	- [writer.array()](#writerarray)
	- [writer.set()](#writerset)
	- [writer.stream()](#writerstream)
	- [writer.text()](#writertext)
	- [writer.values(values)](#writervaluesvalues)
	- [writer.entries(entries)](#writerentriesentries)
	- [writer.assign(source)](#writerassignsource)
	- [writer.load(source)](#writerloadsource)
	- [writer.file(path)](#writerfilepath)
	- **Sink Methods**
	- [writer.to(sink, options)](#writertosink-options)
	- [writer.pipe(sink, options)](#writerpipesink-options)
	- [writer.wipe()](#writerwipe)
	- [writer.store()](#writerstore)
	- **Lifecycle Methods**
	- [writer.reset(force)](#writerresetforce)
	- [writer.abort(error)](#writeraborterror)
	- [writer.close(end)](#writercloseend)
	- [writer.sync()](#writersync)
	- [writer.flush()](#writerflush)
	- **Output Methods (for store sink)**
	- [writer.release(end)](#writerreleaseend)
	- [writer.buffer(end)](#writerbufferend)
	- [writer.blob(end)](#writerblobend)
	- **Hooks**
	- [writer.hook(hook)](#writerhookhook)
	- [writer.unhook(hook)](#writerunhookhook)
	- **Stats**
	- [writer.stat()](#writerstat)
- [JSYN.Reader](#jsynreader)
	- [new Reader(source)](#new-readersource)
	- **Properties**
	- [reader.state](#readerstate)
	- [reader.opened](#readeropened)
	- [reader.closed](#readerclosed)
	- [reader.failed](#readerfailed)
	- [reader.error](#readererror)
	- [reader.depth](#readerdepth)
	- [reader.path](#readerpath)
	- [reader.scope](#readerscope)
	- [reader.finished](#readerfinished)
	- [reader.paused](#readerpaused)
	- [reader.received](#readerreceived)
	- [reader.unread](#readerunread)
	- [reader.reserve](#readerreserve)
	- [reader.exts](#readerexts)
	- **Reading Methods**
	- [reader.each(fn)](#readereachfn)
	- [reader.walk(fn, depth)](#readerwalkfn-depth)
	- [reader.keys(end)](#readerkeysend)
	- [reader.values(end)](#readervaluesend)
	- [reader.entries(end)](#readerentriesend)
	- [reader.flat(depth)](#readerflatdepth)
	- [reader.flatKeys(depth)](#readerflatkeysdepth)
	- [reader.flatValues(depth)](#readerflatvaluesdepth)
	- [reader.flatEntries(depth)](#readerflatentriesdepth)
	- [reader.hasNext()](#readerhasnext)
	- [reader.raw()](#readerraw)
	- [reader.rawId()](#readerrawid)
	- [reader.type()](#readertype)
	- [reader.kind()](#readerkind)
	- [reader.key()](#readerkey)
	- [reader.value()](#readervalue)
	- [reader.entry()](#readerentry)
	- [reader.skip()](#readerskip)
	- [reader.open()](#readeropen)
	- [reader.dict()](#readerdict)
	- [reader.seq()](#readerseq)
	- [reader.flow()](#readerflow)
	- [reader.object()](#readerobject)
	- [reader.map()](#readermap)
	- [reader.array()](#readerarray)
	- [reader.set()](#readerset)
	- [reader.stream()](#readerstream)
	- [reader.text()](#readertext)
	- [reader.end()](#readerend)
	- [reader.rest()](#readerrest)
	- [reader.save(sink, options)](#readersavesink-options)
	- **Lifecycle Methods**
	- [reader.close()](#readerclose)
	- [reader.reset()](#readerreset)
	- [reader.abort(error)](#readeraborterror)
	- **Source Methods**
	- [reader.push(...chunks)](#readerpushchunks)
	- [reader.finish(...chunks)](#readerfinishchunks)
	- **Extended Types Methods**
	- [reader.register(typeid, handler)](#readerregistertypeid-handler)
	- [reader.registerAll(...args)](#readerregisterallargs)
	- [reader.unregister(typeid)](#readerunregistertypeid)
	- **Stats**
	- [reader.stat()](#readerstat)
- [JSYN.Value](#jsynvalue)
	- [Value.is(value)](#valueisvalue)
	- [value.type](#valuetype)
	- [value.payload](#valuepayload)
- [JSYN.HookList](#jsynhooklist)
	- [new HookList(...hooks)](#new-hooklisthooks)
	- [hooks.size](#hookssize)
	- [hooks.add(hook)](#hooksaddhook)
	- [hooks.remove(hook)](#hooksremovehook)
	- [hooks.clear()](#hooksclear)
	- [hooks.has(hook)](#hookshashook)
	- [hooks.resolve(key, value)](#hooksresolvekey-value)
- [JSYN.TypeMap](#jsyntypemap)
	- [typeMap.size](#typemapsize)
	- [typeMap.has(typeid)](#typemaphastypeid)
	- [typeMap.set(typeid, handler)](#typemapsettypeid-handler)
	- [typeMap.remove(typeid)](#typemapremovetypeid)
	- [typeMap.clear()](#typemapclear)
	- [typeMap.add(...args)](#typemapaddargs)
	- [typeMap.replace(...args)](#typemapreplaceargs)
	- [typeMap.resolve(typeid, payload)](#typemapresolvetypeid-payload)
- [JSYN.types](#jsyntypes)
	- [types.TYPE_OF(value)](#typestype_ofvalue)
	- [types.IS_SUPPORTED(value)](#typesis_supportedvalue)
	- [types.RAW_TYPE(typeid)](#typesraw_typetypeid)
	- [types.TYPE(typeid)](#typestypetypeid)
	- [types.KIND(typeid)](#typeskindtypeid)
	- [types.TYPE_ID(value)](#typestype_idvalue)
	- [Type Constants](#type-constants)
	- [types.IS_* (Type classifiers)](#typesis_-type-classifiers)
- [JSYN.conf](#jsynconf)
	- conf.MAX_BLOCK_SIZE
	- conf.MAX_EXTENDED_TYPE_ID
	- conf.READER_CACHE_SIZE
	- conf.WRITER_BUFFER_SIZE

## 👩🏽‍💻Installation
### Installing
Install JSyn via **npm**:
```bash
npm i jsyn
```

### Main Imports
Import both Writer and Reader from the main package:

```js
import { Writer, Reader } from 'jsyn';
// or
import JSYN from 'jsyn';
const writer = new JSYN.Writer();
const reader = new JSYN.Reader();
```

### Writer Only
If you only need writing capabilities:
```js
import { Writer } from 'jsyn/writer';
```

### Reader Only
If you only need reading capabilities:
```js
import { Reader } from 'jsyn/reader';
```
### Drivers (e.g., BSON)
For integration with BSON or other drivers:
```js
import { create } from 'jsyn/bson';
```

## 👩🏼‍🔬Quick Start

### Writing Data to Memory
```js
import {Writer} from 'jsyn';

const writer = new Writer();

// Write a simple object
writer.push({ name: "Alice", age: 30 });

// Finalize and get the buffer
const buffer = writer.buffer();

console.log(buffer); // Buffer
```

### Reading Data from Memory
```js
import { Reader } from 'jsyn';

const reader = new Reader(buffer);

// Read a single value
const data = await reader.value();
console.log(data); // { name: "Alice", age: 30 }

// Read all values (if multiple)
const allData = await reader.rest();
console.log(allData);
```

### Streaming Example
```js
import { Writer } from 'jsyn';
import fs from 'fs';

const fileWriter = new Writer('data.jsyn');
fileWriter.push({ event: 'start' });
fileWriter.push({ event: 'progress', value: 50 });
fileWriter.push({ event: 'end' });
await fileWriter.close();
```

```js
import { Reader } from 'jsyn';
import fs from 'fs';

const fileReader = new Reader('data.jsyn');
for await (const item of fileReader.values()) {
  console.log(item);
}
```

### JSYN provides simple one-liner methods for encoding and decoding values
```js
import JSYN from 'jsyn';

// Encode values into a single buffer
const buf = JSYN.encode(1, 2, "hello", true);

// Decode values back from the buffer
const values = await JSYN.decode(buf);

console.log(values); // [1, 2, "hello", true]
```

## 💎Supported Types
JSyn supports two main categories of types: **Primitives** and **Containers**.

---

### Primitive Types

_Primitives must be written and read atomically (completely)._


| Type           | Native Type | Description |
|----------------|-------------|-------------|
| **`NULL`**     | `NULL`      | Represents `null`. |
| **`BOOLEAN`**  | `TRUE`, `FALSE` | `true` or `false`. |
| **`NUMBER`**   | `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT32`, `FLOAT64` | Numeric values. JSyn automatically selects the optimal format when writing; always read as `number`. Supports: `int16`, `int32`, `int64`, `float32`, `float64`. |
| **`BIGINT`**   | `BIGINT_POS`, `BIGINT_NEG` | Arbitrary-size integer. Supports sizes up to `2^28-1` bytes. |
| **`STRING`**   | `STRING`    | UTF-8 encoded string. Maximum length: `0` to `2^28-1` bytes. |
| **`DATE`**     | `DATE`      | JavaScript [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object. |
| **`REGEXP`**   | `REGEXP`    | JavaScript [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) object. |
| **`BINARY`**   | `BINARY`    | Binary data. Supports [`Buffer`](https://nodejs.org/docs/latest/api/buffer.html), [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), and all `BufferView`s ([`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)). When read in Node.js, always returns `Buffer`. Maximum size: `0` to `2^28-1` bytes. |
| **`EXTENDED`** | `EXTENDED`  | Custom user-defined type. Written as [`JSYN.Value(typeid, payload)`](#jsynvalue); when read, handled by [`reader.exts`](#readerext). By default, returns `JSYN.Value()`. |

---

### Container Types

_Containers can be partially or fully read/written. They may represent potentially infinite sequences._

| Type           |   Description |
|----------------|---------------|
| **`OBJECT`**   | JavaScript object (`{}`). Keys are UTF-8 strings with maximum size `0` to `2^28-1` bytes. |
| **`ARRAY`**    | JavaScript array (`[]`). |
| **`MAP`**      | JavaScript [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). |
| **`SET`**      | JavaScript [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). |
| **`STREAM`**   | No direct JS equivalent. Container for binary data. Fully read → `BINARY`. Partially read → sequence of `Buffer` chunks. |
| **`TEXT`**     | No direct JS equivalent. Container for textual data. Fully read → `STRING`. Partially read → sequence of `STRING` chunks. |

---

### Unsupported Types

Some JavaScript values cannot be serialized in JSYN.
Attempting to write them will either **throw a non-fatal error** or result in a fallback (e.g. ignored value).

| Type            | Behavior                                              |
|-----------------|-------------------------------------------------------|
| **`undefined`** | Ignored or replaced with `null` depending on context. |
| **`symbol`**    | Ignored or replaced with `null` depending on context. |
| **`function`**  | Ignored or replaced with `null` depending on context. |

---

### JSYN Native Types

These are the low-level identifiers used internally by the JSYN protocol. They represent the exact encoding format and are used by readers and writers.

| Type             | Code (Hex) | Description / Value Range |
|------------------|------------|---------------------------|
| **`END`**        | `0x00`     | End of container marker. |
| **`NULL`**       | `0x01`     | Represents `null`. |
| **`FALSE`**      | `0x02`     | Boolean `false`. |
| **`TRUE`**       | `0x03`     | Boolean `true`. |
| **`INT8`**       | `0x04`     | Signed 8-bit integer. Range: `-128` to `127`. |
| **`INT16`**      | `0x05`     | Signed 16-bit integer. Range: `-32768` to `32767`. |
| **`INT32`**      | `0x06`     | Signed 32-bit integer. Range: `-2^31` to `2^31-1`. |
| **`INT64`**      | `0x07`     | Signed 64-bit integer. Range: `-2^63` to `2^63-1`. |
| **`FLOAT32`**    | `0x08`     | 32-bit floating point number. |
| **`FLOAT64`**    | `0x09`     | 64-bit floating point number. |
| **`BIGINT_POS`** | `0x0A`     | Positive arbitrary-size integer. Maximum size: `2^28-1` bytes. |
| **`BIGINT_NEG`** | `0x0B`     | Negative arbitrary-size integer. Maximum size: `2^28-1` bytes. |
| **`STRING`**     | `0x0C`     | UTF-8 encoded string. Maximum length: `0` to `2^28-1` bytes. |
| **`DATE`**       | `0x0D`     | JavaScript [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object. Signed 64-bit integer. |
| **`REGEXP`**     | `0x0E`     | JavaScript [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) object. |
| **`BINARY`**     | `0x0F`     | Binary data (`Buffer`, `ArrayBuffer`, TypedArray, DataView). Maximum size: `0` to `2^28-1` bytes. |
| **`EXTENDED`**   | `0x10`     | Custom user-defined type. Written as [`JSYN.Value(typeid, payload)`](#jsynvalue). |
| **`OBJECT`**     | `0x11`     | JavaScript object (`{}`). |
| **`MAP`**        | `0x12`     | JavaScript [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). |
| **`ARRAY`**      | `0x13`     | JavaScript array (`[]`). |
| **`SET`**        | `0x14`     | JavaScript [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). |
| **`STREAM`**     | `0x15`     | Sequence of binary chunks. Fully read → `BINARY`. |
| **`TEXT`**       | `0x16`     | Sequence of string chunks. Fully read → `STRING`. |

---

### Surrogate Type Categories

These are higher-level groupings of JSYN native types, used for classification and conditional handling in readers and writers.
Each category has a corresponding `IS_*` method that returns `true` if the type belongs to the group.

| Category / Method      | Includes Native Types                                           | Description |
|------------------------|-----------------------------------------------------------------|-------------|
| `IS_END(type)`         | `END`                                                           | Marks the end of a container. |
| `IS_TYPE(type)`        | All JSYN types                                                  | Any valid JSYN type. |
| `IS_PRIMITIVE(type)`   | `NULL`, `FALSE`, `TRUE`, `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT32`, `FLOAT64`, `BIGINT_POS`, `BIGINT_NEG`, `STRING`, `DATE`, `REGEXP`, `BINARY`, `EXTENDED` | Atomic values that are always read/written fully. |
| `IS_PRIMITIVE_OBJECT(type)` | `DATE`, `REGEXP`, `BINARY`, `EXTENDED`                     | Special primitive-like objects with internal structure. |
| `IS_NUMBER(type)`      | `INT8`, `INT16`, `INT32`, `INT64`, `FLOAT32`, `FLOAT64`         | Numeric types. |
| `IS_INT(type)`         | `INT8`, `INT16`, `INT32`, `INT64`                               | Signed integer types. |
| `IS_FLOAT(type)`       | `FLOAT32`, `FLOAT64`                                            | Floating point numbers. |
| `IS_BIGINT(type)`      | `BIGINT_POS`, `BIGINT_NEG`                                      | Arbitrary-size integers. |
| `IS_EXT(type)`         | `EXTENDED`                                                      | User-defined extension types. |
| `IS_CONTAINER(type)`   | `OBJECT`, `MAP`, `ARRAY`, `SET`, `STREAM`, `TEXT`               | All container types. |
| `IS_COLLECTION(type)`  | `ARRAY`, `SET`                                                  | Group of value-sequence containers. |
| `IS_DICTIONARY(type)`  | `OBJECT`, `MAP`                                                 | Key-value mapping containers. |
| `IS_FLOW(type)`        | `STREAM`, `TEXT`                                                | Sequential stream/text containers. |
| `IS_SEQUENCE(type)`    | `ARRAY`, `SET`, `STREAM`, `TEXT`                                | Any sequential container (values or chunks). |
| `IS_OBJECT(type)`      | `OBJECT`                                                        | Plain JavaScript object. |
| `IS_MAP(type)`         | `MAP`                                                           | Key-value map. |
| `IS_ARRAY(type)`       | `ARRAY`                                                         | Array of values. |
| `IS_SET(type)`         | `SET`                                                           | Set of values. |
| `IS_STREAM(type)`      | `STREAM`                                                        | Stream of binary chunks. |
| `IS_TEXT(type)`        | `TEXT`                                                          | Stream of text chunks. |

---

### Type Utility Methods

#### Value-based methods

| Method | Description |
|--------|-------------|
| [`JSYN.typeOf(value)`](#typeofvalue)      | Returns the internal numeric type code of any value. See [Supported Types](#supported-types). |
| [`JSYN.isSupported(value)`](#issupportedvalue) | Returns `true` if the value can be serialized by JSYN. |
| [`JSYN.isExt(value)`](#isextvalue)       | Returns `true` if the value is a user-defined [`JSYN.Value`](#jsynvalue) (extended type). |

#### Type-based methods

| Method | Description |
|--------|-------------|
| [`types.RAW_TYPE(typeId)`](#typesraw_typetypeid)   | Returns the internal protocol name of the given type identifier (string). |
| [`types.TYPE(typeId)`](#typestypetypeid)           | Returns a human-readable extended type name for the given type identifier. |
| [`types.KIND(typeId)`](#typeskindtypeid)           | Returns a generic type name compatible with JS or other languages. |
| [`types.TYPE_ID(value)`](#typestype_idvalue)       | Returns a internal protocol type id for the given type name |
| [`types.IS_*(typeId)`](#typesis_-type-classifiers) | Boolean classifiers for type groups or individual types. |

#### Reader methods

| Method | Description |
|--------|-------------|
| [`reader.rawId()`](#readerrawid) | Returns the numeric type ID of the next value in the current scope (or 0 if none). |
| [`reader.raw()`](#readerraw) | Returns the internal protocol name of the next value (or 'end' if none). |
| [`reader.type()`](#readertype) | Returns the human-readable extended type name of the next value (or `undefined` if none). |
| [`reader.kind()`](#readerkind) | Returns the JS/compatible type name of the next value (or `undefined` if none). |

### JSYN Names Type Reference

| typeId / rawId() | RAW_TYPE(t) / reader.raw() | TYPE(t) / reader.type() | KIND(t) / reader.kind() |
|-----------------|----------------------------|-------------------------|------------------------|
| 0x00            | 'end'                      | `undefined`             | `undefined`            |
| 0x01            | 'null'                     | 'null'                  | 'null'                 |
| 0x02            | 'false'                    | 'boolean'               | 'boolean'              |
| 0x03            | 'true'                     | 'boolean'               | 'boolean'              |
| 0x04            | 'int8'                     | 'number'                | 'number'               |
| 0x05            | 'int16'                    | 'number'                | 'number'               |
| 0x06            | 'int32'                    | 'number'                | 'number'               |
| 0x07            | 'int64'                    | 'number'                | 'number'               |
| 0x08            | 'float32'                  | 'number'                | 'number'               |
| 0x09            | 'float64'                  | 'number'                | 'number'               |
| 0x0A            | 'bigint_pos'               | 'bigint'                | 'bigint'               |
| 0x0B            | 'bigint_neg'               | 'bigint'                | 'bigint'               |
| 0x0C            | 'string'                   | 'string'                | 'string'               |
| 0x0D            | 'date'                     | 'date'                  | 'object'               |
| 0x0E            | 'regexp'                   | 'regexp'                | 'object'               |
| 0x0F            | 'binary'                   | 'binary'                | 'object'               |
| 0x10            | 'extended'                 | 'extended'              | 'object'               |
| 0x11            | 'object'                   | 'object'                | 'object'               |
| 0x12            | 'map'                      | 'map'                   | 'object'               |
| 0x13            | 'array'                    | 'array'                 | 'object'               |
| 0x14            | 'set'                      | 'set'                   | 'object'               |
| 0x15            | 'stream'                   | 'stream'                | 'object'               |
| 0x16            | 'text'                     | 'text'                  | 'string'               |
| `stream chunk`  | 'binary'                   | 'binary'                | 'object'               |
| `text chunk`    | 'string'                   | 'string'                | 'string'               |

## 🎁Extended Types

JSYN allows using **custom (extended) types** in addition to the built-in types.
Extended types let you assign a separate class or type for writing and reading atomic data.

User-defined data consists of two fields: `typeId` and `payload`.

### 1. typeId
- Integer identifier of the extended type.
- Values range from `1` to `JSYN.MAX_EXTENDED_TYPE_ID = 268435455 (2^28 - 1)`.
- Value `0` is reserved and cannot be used.
- All other values are available, but to avoid collisions it is recommended to follow the table:

| Range           | Usage                                         |
|-----------------|-----------------------------------------------|
| 1–63            | Reserved for user space                       |
| 64–79           | Reserved for BSON                             |
| 80–127          | Reserved for other standard extended types    |
| 128–268435455   | Grey zone, currently undefined                |

> Note: The typeId space does not conflict with system types, but following the table is recommended to avoid collisions.

### 2. payload
- Any JSYN-supported value (`<VALUE> ::= <TYPE> <DATA>`), e.g. `null`, `binary`, `string`, `array`, `object`, etc.
- Stored as a normal value, but with two differences:
  1. Hooks applied to system values are **not applied** to payload or its nested data.
  2. Cannot be written or read **partially** like containers — only as a whole:

```js
writer.ext(1, { key: 1 });
```
### Writing Extended Data

There are two ways to write extended data:

#### Explicit
```js
writer.ext(typeId, payload);
```
- Can be called wherever a value is expected (e.g., where writer.value() is allowed).

#### Implicit

- Any data wrapped with the [JSYN.Value](#jsynvalue) class is saved as an extended type.
- You can create a value using:
```js
import {ext, Value} from 'jsyn'

ext(typeId, payload);           // simple method
ext(typeId);                    // payload optional
new Value(typeId, payload);      // using class
```

### Implicit Interpretation of Data as Extended Type

To interpret a custom class (or other type) as an extended type during writing:

1. Implement toJSYN() for objects:
```js
class MyData {
  toJSYN() {
    return JSYN.ext(1234, "payload");
  }
}
writer.value(new MyData());
```

2. Use hooks:
```js
function MyHook(key, value) {
  if (value instanceof MyData)
    return JSYN.ext(1234, "payload");
  return value;
}
writer.hook(MyHook);
writer.value(new MyData());
```

### Reading Extended Types
- When an extended type is found, the parser uses the type registry reader.exts.
- If a handler is registered for the type, it is applied.
- If no handler is registered or an error occurs, the value is wrapped in the `JSYN.Value` class.

### Assigning a Handler for Extended Types
```js
function MyHandler(value) {
  return new MyData(value);
}

// Register handler
reader.exts.set(1234, MyHandler);

// Or using shorthand
reader.register(1234, MyHandler);
reader.register(1234, value => new MyData(value));
```

## 📚BSON Integration
JSYN provides optional integration with BSON (e.g., from the `mongodb` package) via a helper module.
This allows BSON types to be serialized as extended JSYN values and deserialized back to their original BSON representations.

### Usage Example
```js
// Import BSON from MongoDB driver
import { BSON } from 'mongodb';

// Import JSYN BSON helper
import { create } from 'jsyn/bson';

// Create BSON hooks and type registry
const bson = create(BSON);

// Access the hook for writer and type map for reader
bson.Hook  // Hook for Writer to wrap BSON types into JSYN.Value
bson.Types // TypeMap for Reader to convert JSYN.Value back to BSON

// Create Writer and Reader instances
import { Writer, Reader } from 'jsyn';
const writer = new Writer();
const reader = new Reader();

// Attach BSON hook to Writer
writer.hook(bson.Hook);

// Attach BSON type handlers to Reader
reader.registerAll(bson.Types);

// Write BSON value
writer.push(new BSON.ObjectId());

// Finish writer buffer and send to reader
reader.finish(writer.buffer());

// Read back the BSON value
const oid = await reader.value();
console.log(oid); // ObjectId
```
### Notes
- `writer.hook(bson.Hook)` automatically wraps supported BSON types in `JSYN.Value`.
- `reader.registerAll(bson.Types)` allows the reader to convert JSYN extended types back to BSON.
- Works seamlessly with all JSYN Writer/Reader methods.

---

## 🔬Binary Format

JSyn uses a **streamable tagged binary format**.
Each value is represented as `<VALUE> ::= <TYPE> <DATA>`.
The `<DATA>` can be either a primitive or a container.

### Root of the JSyn binary format
```bnf
<JSYN_ROOT> ::= (<VALUE>)* END
<JSYN_ROOT> ::= (<VALUE>)* EOF
```

### VALUE is a type byte followed by type-specific data
```bnf
<VALUE> ::= <TYPE> <DATA>
```

### EOF marker
```bnf
EOF ::= <end of data>   # represents the natural end of the data stream (no more bytes to read)
```

### End marker
```bnf
END ::= 0x00 # indicates end of any container/scope
```

### Type identifiers (1 byte)
```bnf
<TYPE> ::= 0x01..0xFF
| TYPE_NULL = 0x01
| TYPE_FALSE = 0x02
| TYPE_TRUE = 0x03
| TYPE_INT8 = 0x04
| TYPE_INT16 = 0x05
| TYPE_INT32 = 0x06
| TYPE_INT64 = 0x07
| TYPE_FLOAT32 = 0x08
| TYPE_FLOAT64 = 0x09
| TYPE_BIGINT_POS = 0x0A
| TYPE_BIGINT_NEG = 0x0B
| TYPE_STRING = 0x0C
| TYPE_DATE = 0x0D
| TYPE_REGEXP = 0x0E
| TYPE_BINARY = 0x0F
| TYPE_EXTENDED = 0x10
| TYPE_OBJECT = 0x11
| TYPE_MAP = 0x12
| TYPE_ARRAY = 0x13
| TYPE_SET = 0x14
| TYPE_STREAM = 0x15
| TYPE_TEXT = 0x16
```

### Variable-length integers (VARINT) for lengths and type IDs
```bnf
<VARINT> ::= 1-4 bytes
```
- 1 byte: 0xxxxxxx (0–127)
- 2 bytes: 1xxxxxxx 0xxxxxxx (0–16_383)
- 3 bytes: 1xxxxxxx 1xxxxxxx 0xxxxxxx (0–2_097_151)
- 4 bytes: 1xxxxxxx 1xxxxxxx 1xxxxxxx 0xxxxxxx (0–268_435_455)

```bnf
<VARSIZE> ::= <VARINT> # length of variable-size payload
<VARTYPE> ::= <VARINT> # user-defined type identifier
```

### DATA can be a primitive or a container
```bnf
<DATA> ::= <PRIMITIVE>
| <OBJECT>
| <ARRAY>
| <SET>
| <MAP>
| <STREAM>
| <TEXT>
```

### Primitive DATA formats
```bnf
<PRIMITIVE> ::=
INT8 ::= 1 byte signed
INT16 ::= 2 bytes BE signed
INT32 ::= 4 bytes BE signed
INT64 ::= 8 bytes BE signed
FLOAT32 ::= 4 bytes BE float
FLOAT64 ::= 8 bytes BE float
BIGINT_POS ::= <VARSIZE> + bytes BE unsigned
BIGINT_NEG ::= <VARSIZE> + bytes BE unsigned
STRING ::= <VARSIZE> + UTF-8 bytes
DATE ::= 8 bytes BE (INT64)
REGEXP ::= <STRING> pattern + <STRING> flags
BINARY ::= <VARSIZE> + bytes
EXTENDED ::= <VARTYPE> + <PAYLOAD>
```

### PAYLOAD is a complete value (can be primitive or container)
```bnf
<PAYLOAD> ::= <VALUE>
```

### Containers
```bnf
<OBJECT_KEY> ::= <STRING>
<OBJECT_ENTRY> ::= <TYPE> <OBJECT_KEY> <DATA>
<OBJECT> ::= (<OBJECT_ENTRY>)* END # 0x00 marks end

<MAP_KEY> ::= <VALUE>
<MAP_VALUE> ::= <VALUE>
<MAP_ENTRY> ::= <MAP_KEY> <MAP_VALUE>
<MAP> ::= (<MAP_ENTRY>)* END # 0x00 marks end

<ARRAY> ::= (<VALUE>)* END
<SET> ::= (<VALUE>)* END

<STREAM_CHUNK> ::= <BINARY> where <VARSIZE> > 0
<STREAM> ::= (<STREAM_CHUNK>)* END # 0x00 marks end

<TEXT_CHUNK> ::= <STRING> where <VARSIZE> > 0
<TEXT> ::= (<TEXT_CHUNK>)* END # 0x00 marks end
```

**Notes / Key Points:**

- `<VALUE>` = `<TYPE>` + `<DATA>`. `<DATA>` can be **either a primitive or a container**.
- Containers (`OBJECT`, `ARRAY`, `SET`, `MAP`, `STREAM`, `TEXT`) are **terminated by the END marker (0x00)**.
- Partial reading/writing is supported for streams and containers — enabling **potentially infinite sequences**.
- `VARINT` encodes lengths and type IDs efficiently (1–4 bytes).
- Every `<VALUE>` is **self-contained**, making JSyn suitable as a **streaming protocol**.

## 🔒Root Scope END Byte

Every scope in JSYN is terminated with an **END byte (`0x00`)**. In the **root scope**, this byte signals the **end of all data**, after which reading further values is meaningless.

- In the root scope, the END byte is **optional**. If it is absent, the end of data is considered to be **EOF** (end of the provided data).

### When to use END-ROOT
- When you want to **seal the root container** and guarantee that no further data will follow.
- Useful if you want to write a **complete, self-contained sequence** and ensure that the reader can detect its completion.
- Ideal for **network transmission**, where you do not want to rely on a connection close as the end-of-data signal.

### When NOT to use END-ROOT
- If you **cannot know whether more data will follow**, for example:
  - Logging to a file where additional entries will be appended later. Using END-ROOT would prevent further writes.
  - Writing an **infinite or continuous data stream**, such as ongoing message delivery or real-time telemetry, where a formal end is not expected.

### Default behavior
- By default, **END-ROOT is not emitted**.

### How to emit END-ROOT when writing
- Use the [`writer.close(end)`](#writercloseend) method with `end = true`. If writing is already finished, this has no effect.
- In **storable mode**, [`writer.release(end)`](#writerreleaseend), [`writer.blob(end)`](#writerblobend), and [`writer.buffer(end)`](#writerbufferend) accept the `end` argument.
- **Important:** [`writer.end(...values)`](#writerendvalues) **never emits END-ROOT**. To include END-ROOT with values:
```js
writer.push(v1, v2, v3);
await writer.close(true); // emits END-ROOT
```
### Reader behavior

- When reading, END-ROOT is treated as the end of data, equivalent to EOF.
- After encountering END-ROOT, the reader stops processing new data and waits for [reader.end()](#readerend) or [reader.close()](#readerclose) to formally finish and transition to the closed state.

## 📝Writing Data
### Writer Lifecycle
The **JSyn Writer** has **three main states** and a **writable flag**. These define what operations are allowed at any given moment.

### States Overview

1. **Opened** – default state after creation.
   - **Writable = true** – actively writing to a sink; all write methods available.
   - **Writable = false** – [`close()`](#writerclose) or `end()` (from root scope) has been called; all write methods throw errors except [`writer.end()`](#writerendvalues).

2. **Closed** – writing has successfully finished.
   - All write methods throw errors except [`writer.end()`](#writerendvalues).
   - Can be reset to `opened` via [`writer.reset()`](#writerresetforce) or writing methods that reopen the writer.

3. **Failed** – fatal error occurred; writing cannot continue.
   - Error details available in `writer.error`.
   - Can be reset to `opened` via [`writer.reset()`](#writerresetforce) or reopening methods.
   - Most operations throw errors except [`writer.end()`](#writerendvalues).

### Checking Writer State
The current state can be accessed via:
- `writer.state` → returns `'opened'`, `'closed'`, or `'failed'`.
- Convenience flags: `writer.opened`, `writer.closed`, `writer.failed`.
These indicate the same information and can be used to guard write operations or handle errors.

### Opened State Operations

From **opened**, the following actions are possible:

- [`writer.to(sink)`](#writertosink-options) – send data to a new sink and continue writing (storable mode).
- [`writer.reset(true)`](#writerresetforce) – clear written data if possible (storable mode).
- [`await writer.close()`](#writercloseend) – transitions to **closed**.
- [`writer.end()`](#writerendvalues) from root-scope – transitions to **closed** when the sink finishes writing remaining data.
- [`writer.abort()`](#writeraborterror) – transitions to **failed**.
- Any fatal error automatically moves writer to **failed**.

**Not allowed / will throw non-fatal error:**

- [`writer.reset(false)`](#writerresetforce)
- [`writer.pipe()`](#writerpipesink-options)
- [`writer.store()`](#writerstore)
- [`writer.wipe()`](#writerwipe)

### Closed State Operations

From **closed**, the following actions are possible:

- [`writer.reset()`](#writerresetforce) – transitions back to **opened**.
- [`writer.pipe(sink)`](#writerpipesink-options), [`writer.store()`](#writerstore), [`writer.wipe()`](#writerwipe) – transitions back to **opened**.
- [`writer.to(sink)`](#writertosink-options) – in storable mode, sends accumulated data to new sink but stays **closed**.
- [`writer.close()`](#writercloseend), [`writer.abort()`](#writeraborterror) – have no effect.
- All write methods throw errors except [`writer.end()`](#writerendvalues).

### Failed State Operations

From **failed**, the following actions are possible:

- [`writer.reset()`](#writerresetforce) – transitions back to **opened**.
- [`writer.pipe(sink)`](#writerpipesink-options), [`writer.store()`](#writerstore), [`writer.wipe()`](#writerwipe) – transitions back to **opened**.
- [`writer.to(sink)`](#writertosink-options) – will throw an error.
- [`await writer.sync()`](##writersync) – will throw an error.
- [`await writer.close()`](##writercloseend) – will throw an error.
- [`writer.abort()`](#writeraborterror) – have no effect.
- All write methods throw errors except [`writer.end()`](#writerendvalues).

### Notes

- The **writable flag** is automatically managed:
  - `true` when actively writing,
  - `false` after `close()` or `end()` in root scope.
- Proper handling of states ensures **stream safety** and allows **incremental or storable writing**.
- Use [`writer.reset()`](#writerresetforce) or compatible methods to **reuse a writer** after closed or failed states.

### Writer Scopes

Each **scope** represents a nested writing context — a container level on the internal stack.
Scopes are hierarchical; each new container (object, array, map, etc.) opens a new scope inside the current one.
The current scope can be accessed via [`writer.scope`](#writerscope), and the nesting level via [`writer.depth`](#writerdepth).

Each scope defines its own writing rules and available methods.

Scopes are opened by one of:
[`writer.object()`](#writerobject), [`writer.map()`](#writermap), [`writer.array()`](#writerarray), [`writer.set()`](#writerset), [`writer.stream()`](#writerstream), [`writer.text()`](#writertext)
and are closed by [`writer.end()`](#writerendvalues).

All nested scopes can be closed with [`writer.close()`](#writercloseend).

---

#### 1. Root Scope
- Always the top-level scope (`depth = 1`).
- Represents a sequence of values.
- Unsupported values are ignored.
- Cannot open another root scope.

**Allowed methods:**
- [`writer.value(value)`](#writervaluevalue), [`writer.values(values)`](#writervaluesvalues), [`writer.push(...values)`](#writerpushvalues), [`writer.ext(typeid, payload)`](#writerexttypeid-payload) — write values.
- [`writer.object()`](#writerobject), [`writer.map()`](#writermap), [`writer.array()`](#writerarray), [`writer.set()`](#writerset), [`writer.stream()`](#writerstream), [`writer.text()`](#writertext) — open nested scopes.
- [`writer.end(...values)`](#writerendvalues) — finishes the scope and completes the entire writing process (records values before ending).

---

#### 2. Array Scope
- Writes a sequence of values (array elements).
- Unsupported values are replaced with `null`.
- Opened via [`writer.array()`](#writerarray).

**Allowed methods:**
- [`writer.value(value)`](#writervaluevalue), [`writer.values(values)`](#writervaluesvalues), [`writer.push(...values)`](#writerpushvalues), [`writer.ext(typeid, payload)`](#writerexttypeid-payload) — write values.
- [`writer.object()`](#writerobject), [`writer.map()`](#writermap), [`writer.array()`](#writerarray), [`writer.set()`](#writerset), [`writer.stream()`](#writerstream), [`writer.text()`](#writertext) — open nested scopes.
- [`writer.end(...values)`](#writerendvalues) — closes the scope (records values before ending).

---

#### 3. Set Scope
- Writes a sequence of unique values.
- Unsupported values are ignored.
- Opened via [`writer.set()`](#writerset).

**Allowed methods:**
- [`writer.value(value)`](#writervaluevalue), [`writer.values(values)`](#writervaluesvalues), [`writer.push(...values)`](#writerpushvalues), [`writer.ext(typeid, payload)`](#writerexttypeid-payload), [`writer.add(value)`](#writeraddvalue) — write values.
- [`writer.object()`](#writerobject), [`writer.map()`](#writermap), [`writer.array()`](#writerarray), [`writer.set()`](#writerset), [`writer.stream()`](#writerstream), [`writer.text()`](#writertext) — open nested scopes.
- [`writer.end(...values)`](#writerendvalues) — closes the scope (records values before ending).

---

#### 4. Object Scope
- Writes key–value pairs (similar to a JavaScript object).
- Keys must be strings.
- Invalid keys cause a non-fatal error.
- Unsupported values are ignored.
- Opened via [`writer.object()`](#writerobject).

Has two sub-states: **expecting key** / **expecting value**.

**When expecting key:**
- [`writer.key(key)`](#writerkeykey) — write key.
- [`writer.entry(key, value)`](#writerentrykey-value) — writes the entry.
- [`writer.entries(entries)`](#writerentriesentries) — writes the entries.
- [`writer.end()`](#writerendvalues) — close the scope.

**When expecting value:**
- [`writer.key(key)`](#writerkeykey) — overwrites the last key.
- [`writer.entry(key, value)`](#writerentrykey-value) — writes the entry.
- [`writer.entries(entries)`](#writerentriesentries) — writes the entries.
- [`writer.assign(source)`](#writerassignsource) — writes the entries from object
- [`writer.value(value)`](#writervaluevalue), [`writer.ext(typeid, payload)`](#writerexttypeid-payload) — write value.
- [`writer.object()`](#writerobject), [`writer.map()`](#writermap), [`writer.array()`](#writerarray), [`writer.set()`](#writerset), [`writer.stream()`](#writerstream), [`writer.text()`](#writertext) — open nested scopes.
- [`writer.end()`](#writerendvalues) — closes the scope, discarding the last key.

[`writer.end(...values)`](#writerendvalues) — if values are passed → non-fatal error.

---

#### 5. Map Scope
- Writes key–value pairs with arbitrary key types.
- Invalid keys cause a non-fatal error.
- Unsupported values are ignored.
- Opened via [`writer.map()`](#writermap).

Has two sub-states: **expecting key** / **expecting value**.

**When expecting key:**
- [`writer.key(key)`](#writerkeykey) — write key.
- [`writer.entry(key, value)`](#writerentrykey-value) — writes the entry.
- [`writer.entries(entries)`](#writerentriesentries) — writes the entries.
- [`writer.end()`](#writerendvalues) — close the scope.

**When expecting value:**
- [`writer.key(key)`](#writerkeykey) — overwrites the last key.
- [`writer.entry(key, value)`](#writerentrykey-value) — writes the entry.
- [`writer.entries(entries)`](#writerentriesentries) — writes the entries.
- [`writer.assign(source)`](#writerassignsource) — writes the entries from object
- [`writer.value(value)`](#writervaluevalue), [`writer.ext(typeid, payload)`](#writerexttypeid-payload) — write value.
- [`writer.object()`](#writerobject), [`writer.map()`](#writermap), [`writer.array()`](#writerarray), [`writer.set()`](#writerset), [`writer.stream()`](#writerstream), [`writer.text()`](#writertext) — open nested scopes.
- [`writer.end()`](#writerendvalues) — closes the scope, discarding the last key.

[`writer.end(...values)`](#writerendvalues) — if values are passed → non-fatal error.

---

#### 6. Stream Scope
- Writes a sequence of binary chunks.
- Non-binary values cause a non-fatal error.
- Opened via [`writer.stream()`](#writerstream).

**Allowed methods:**
- [`writer.value(chunk)`](#writervaluevalue), [`writer.values(chunks)`](#writervaluesvalues), [`writer.push(...chunks)`](#writerpushvalues) — write chunks.
- [`writer.load(source)`](#writerloadsource) — write chunks from stream
- [`writer.file(path)`](#writerfilepath) — write chunks from file
- [`writer.end(...chunks)`](#writerendvalues) — closes the scope (records chunks before ending).

---

#### 7. Text Scope
- Writes a sequence of string chunks.
- Non-string values cause a non-fatal error.
- Opened via [`writer.text()`](#writertext).

**Allowed methods:**
- [`writer.value(chunk)`](#writervaluevalue), [`writer.values(chunks)`](#writervaluesvalues), [`writer.push(...chunks)`](#writerpushvalues) — write chunks.
- [`writer.load(source)`](#writerloadsource) — write chunks from stream
- [`writer.file(path)`](#writerfilepath) — write chunks from file
- [`writer.end(...chunks)`](#writerendvalues) — closes the scope (records chunks before ending).

#### Scope Method Compatibility Table

| Method / Scope     | Root | Array | Set | Stream/Text | Object/Map (key) | Object/Map (value) |
|--------------------|:----:|:-----:|:---:|:-----------:|:----------------:|:------------------:|
| [writer.value(v)](#writervaluevalue)             |✅|✅|✅|✅|❌|✅|
| [writer.values(vs)](#writervaluesvalues)         |✅|✅|✅|✅|❌|❌|
| [writer.push(...v)](#writerpushvalues)	         |✅|✅|✅|✅|❌|❌|
| [writer.add(v)](#writeraddvalue)	               |❌|❌|✅|❌|❌|❌|
| [writer.key(k)](#writerkeykey)	                 |❌|❌|❌|❌|✅|✅|
| [writer.entry(k,v)](#writerentrykey-value)       |❌|❌|❌|❌|✅|✅|
| [writer.entries(es)](#writerentriesentries)      |❌|❌|❌|❌|✅|✅|
| [writer.assign(s)](#writerassignsource)          |❌|❌|❌|❌|✅|✅|
| [writer.ext(id,p)](#writerexttypeid-payload)     |✅|✅|✅|❌|❌|✅|
| [writer.load(s)](#writerloadsource)              |❌|❌|❌|✅|❌|❌|
| [writer.file(p)](#writerfilepath)                |❌|❌|❌|✅|❌|❌|
| [writer.object()](#writerobject)	               |✅|✅|✅|❌|❌|✅|
| [writer.array()](#writerarray)	                 |✅|✅|✅|❌|❌|✅|
| [writer.set()](#writerset)	                     |✅|✅|✅|❌|❌|✅|
| [writer.map()](#writermap)	                     |✅|✅|✅|❌|❌|✅|
| [writer.stream()](#writerstream)	               |✅|✅|✅|❌|❌|✅|
| [writer.text()](#writertext)                     |✅|✅|✅|❌|❌|✅|
| [writer.end()](#writerendvalues)                 |✅|✅|✅|✅|✅|✅|
| [writer.end(...v)](#writerendvalues)             |✅|✅|✅|✅|❌|❌|

### Choosing a Sink

JSYN supports three types of **sinks** (receivers) for writing data.

#### Sink States Overview

| Sink Type | Stored Data | Notes |
|-----------|-------------|-----------------|
| **Store** (default) | ✅ Internal buffer | Data can be extracted via `release()`, `buffer()`, `blob()`. Can redirect to another sink with `to()`. |
| **Wipe** | ❌ Not stored | Silent mode, useful for testing. Data is discarded. |
| **Pipe** | ❌ Not stored internally | Data is streamed to an external sink. Switching sinks requires write completion, except with `to()`. |

#### 1. Store (default)

- **Description:** Data is saved in an internal buffer.
- **State:** Initial state or after calling [`writer.store()`](#writerstore).
- **Properties:** [`writer.storable`](#writerstorable) is `true`.
- **Reset:** [`writer.reset(true)`](#writerresetforce) can be used to clear stored data and restart writing.

**Data extraction methods:**

- [`writer.release()`](#writerreleaseend) — Returns an array of chunks.
- [`writer.buffer()`](#writerbufferend) — Returns a single concatenated buffer.
- [`writer.blob()`](#writerblobend) — Returns a `Blob`.
- [`writer.to(sink)`](#writertosink-options) — Redirects accumulated data to another sink and switches to **pipe** mode.

#### 2. Wipe

- **Description:** Data is not stored.
- **Use case:** Silent mode for testing or temporary operations.
- **Activation:** [`writer.wipe()`](#writerwipe)

#### 3. Pipe

- **Description:** Data is written to an external sink.
- **Activation:** [`writer.pipe(sink, options)`](#writerpipesink-options)
- **Supported external sinks:**
  - Function (callback)
  - Object (wrapped into a web stream)
  - Node.js stream
  - Web stream
  - File

**Notes:**

- Switching to a different sink is allowed **only after the current write is complete**.
- **Exception:** [`writer.to(sink, options)`](#writertosink-options) — redirects accumulated data to another sink **and continues writing** if the write is not finished.

### Hooks

JSYN supports hooks to customize how values are processed during writing. Hooks are functions that can transform, filter, or wrap data before it is serialized.

### Adding Hooks

You can add a hook to a `Writer` instance using the [`writer.hook()`](#writerhookhook) method:

```js
function myHook(key, value) {
  // Transform a specific class into an extended type
  if (value instanceof MyData) {
    return JSYN.ext(1234, value.toPayload());
  }
  // Transform a bigint values
  if(typeof value === 'bigint')
  	return value.toString();
  // Return value as-is for normal handling
  return value;
}

writer.hook(myHook);
```

- `key` – the current key in an object/map or index in an array/set/root scope
- `value` – the value to be written.
Hooks are applied before the value is serialized, allowing modification or replacement.

### Hooks Using

Hooks allow preprocessing of values **before they are serialized**. They are applied selectively depending on the scope:

- **Scopes where hooks are applied**:
  - `object` / `map` — applied to **values only**, **not keys**
  - `root`, `array`, `set` — applied to values
- **Scopes where hooks are NOT applied**:
  - `stream`, `text` — hooks do not apply
- Hooks are **not applied to payloads of extended types**
- Hooks are **not applied to containers created manually** via [`writer.object()`](#writerobject), [`writer.array()`](#writerarray), etc., but they **do apply to nested values** written using [`writer.value()`](#writervaluevalue), [`writer.push()`](#writerpushvalues), [`writer.add()`](#writeraddvalue), [`writer.entry()`](#writerentrykey-value)

### Hook Execution Algorithm

For each value being written:

1. **Check for `toJSYN`**:
   If the value is an object with a `toJSYN` property that is a function, it is called as:
   ```ts
   value.toJSYN(key)
   ```
   where key is the current key in scope.

2. **Apply registered hooks**:
If multiple hooks are registered, the output of the first hook is passed as input to the next, in sequence.

3. **Filter unsupported values**:
If after hooks the value is one of `undefined`, `symbol`, or `function` (unsupported types), it is handled as follows:
- `object/map` — the **key-value pair is ignored**
- `root/set` — the value is ignored
- `array` — replaced with `null`

This mechanism allows hooks to **filter or transform values** before serialization. For example, returning undefined from a hook will remove the value from the output.

```js
// Custom hook to filter out negative numbers
function filterNegative(key, value) {
  if (typeof value === 'number' && value < 0) return undefined;
  return value;
}

writer.hook(filterNegative);

writer.array();
writer.value(1);
writer.value(-5);  // filtered out, replaced with null in array
writer.value(3);
writer.end();  // result: [1, null, 3]
```


### Writing Examples
```js
import { Writer } from 'jsyn';

const writer = new Writer();

// -----------------------------
// 1. Atomic Writing (whole value)
// -----------------------------

writer.value("Simple string");        // atomic string
writer.value(123);                    // atomic number
writer.value(true);                   // atomic boolean
writer.value([1, 2, 3]);              // atomic array
writer.value({ name: "Alice" });      // atomic object
writer.value(new Set(["a","b"]));     // atomic set
writer.value(new Map([["k", 42]]));   // atomic map
writer.end();                         // closes root

// -----------------------------
// 2. Writing Containers Piece by Piece
// -----------------------------

// Array
writer.array();
writer.value(1);
writer.value(2);
writer.value(3);
writer.push(4,5,6);
writer.end();

// Object
writer.object();
writer.entry("name", "Bob");
writer.entry("age", 25);
writer.end();

// Set
writer.set();
writer.add("apple");
writer.add("banana");
writer.end();

// Map
writer.map();
writer.entry("key1", "value1");
writer.entry("key2", 42);
writer.end();

// -----------------------------
// 3. Nested Containers
// -----------------------------

writer.object();
writer.entry("user", { name: "Charlie", roles: ["admin", "editor"] }); // atomic object inside object

writer.entry("data", null); // atomic null
writer.entry("scores", [10, 20, 30]); // atomic array

writer.key("settings");     // now open nested map
writer.map();
writer.entry("theme", "dark");
writer.entry("notifications", true);
writer.end();               // closes nested map

writer.entry("tags", ["jsyn", "binary", "protocol"]); // atomic array
writer.end();               // closes root object

const buf = writer.buffer(); // release jsyn data as Buffer

// -----------------------------
// 4. Stream and Text Containers
// -----------------------------

// Stream (binary)
writer.stream();
writer.value(Buffer.from("hello "));
writer.value(Buffer.from([119,111,114,108,100]));
writer.end();

// Text (UTF-8 strings)
writer.text();
writer.value("line 1\n");
writer.value("line 2\n");
writer.end();

// -----------------------------
// 5. Loading External Sources
// -----------------------------

// Open Stream Scope
writer.stream();

// Load raw buffers / strings
await writer.load(Buffer.from("ABC"));
await writer.load("DEF");

// Load nested iterators
await writer.load([
  "chunk1",
  ["chunk2a", "chunk2b"],
  Buffer.from("chunk3")
]);

// Load readable streams
import fs from "node:fs";
await writer.load(fs.createReadStream("input.bin"));

// Load web streams
const webStream = new Response("xyz").body;
await writer.load(webStream);

// Load file directly from filesystem
await writer.file("data.bin");

// Close Stream
writer.end();

// -----------------------------
// 6. Bulk Values for Array / Root / Set
// -----------------------------

writer.array();
await writer.values([1, 2, 3, 4]);
await writer.values((async function* () {
  yield 5;
  yield 6;
})();
writer.end();

// -----------------------------
// 7. Entries and Assign for Object / Map
// -----------------------------

// object.entries(...)
writer.object();
await writer.entries([
  ["name", "Delta"],
  ["age", 30],
]);
writer.end();

// map.entries(...)
writer.map();
await writer.entries(new Map([
  [1, "one"],
  [2, "two"]
]));
writer.end();

// assign(object)
writer.object();
writer.assign({ a: 10, b: 20, c: 30 });
writer.end();
```

## 📚Reading Data
### Reader Lifecycle
The **JSyn Reader** has three main states (`opened`, `closed`, `failed`) and corresponding flags (`reader.opened`, `reader.closed`, `reader.failed`, `reader.state`, `reader.error`).

### States Overview

1. **Opened** – default state; reading is active.
   - All read methods are available.
   - [`reader.close()`](#readerclose) → transitions to **closed**.
   - [`reader.end()`](#readerend) or [`reader.rest()`](#readerrest) (in root-scope) → transitions to **closed**.
   - [`reader.abort()`](#readeraborterror) → transitions to **failed**.
   - Incorrect data termination or fatal error → transitions to **failed**.
   - [`reader.reset()`](#readerreset) → resets reading and terminates the source if possible, but does **not** change the state.
   - Proper data end (`END` marker or EOF) does **not** change the state; read methods simply return `undefined` or `false`.

2. **Closed** – client-initiated completion of reading.
   - Read methods return `undefined`.
   - [`reader.reset()`](#readerreset) → transitions to **opened**.
   - [`reader.close()`](#readerclose), [`reader.abort()`](#readeraborterror), [`reader.end()`](#readerend), [`reader.rest()`](#readerrest) → have no effect.

3. **Failed** – fatal error occurred; further reading is impossible.
   - All read methods throw errors.
   - Error can be retrieved via `reader.error`.
   - [`reader.reset()`](#readerreset) → transitions to **opened**.
   - [`reader.close()`](#readerclose), [`reader.abort()`](#readeraborterror) → have no effect.

### Checking Reader State
- `reader.state` → returns `'opened'`, `'closed'`, or `'failed'`.
- Flags: `reader.opened`, `reader.closed`, `reader.failed`.
- These reflect the same state and can be used to control flow or handle errors.

---

### Read Methods

<!-- BASICS -->
- **[`reader.hasNext()`](#readerhasnext)**  
  Checks if another value exists in the current scope.

- **[`reader.key()`](#readerkey)**  
  Reads the current key; repeated calls return the last read key.

- **[`reader.value()`](#readervalue)**  
  Reads the current value and moves to the next entry.

- **[`reader.entry()`](#readerentry)**  
  Reads the current key–value pair and moves to the next entry.

- **[`reader.skip()`](#readerskip)**  
  Skips the current entry without returning it.

---

### Type & Raw Access

- **[`reader.rawId()`](#readerrawid)**  
  Returns the numeric native ID of the current value’s type (or chunk type for streams/text).

- **[`reader.raw()`](#readerraw)**  
  Returns the native type name of the current value as a string (e.g., `'int16'`, `'date'`, `'string'`).

- **[`reader.type()`](#readertype)**  
  Returns the high-level type of the current value as a readable string (e.g., `'number'`, `'array'`, `'date'`).

- **[`reader.kind()`](#readerkind)**  
  Returns the most general, JavaScript-compatible type of the current value (e.g., `'number'`, `'string'`, `'object'`).

---

### Scope Navigation

- **[`reader.open()`](#readeropen)**  
  Opens a new nested scope if the current value is a container.
  Returns `number` if a new scope was opened, otherwise `0`.

- **[`reader.end()`](#readerend)**  
  Closes the current container and skips any unread values.
  In the root scope, ends all reading.

- **[`reader.rest()`](#readerrest)**  
  Closes the current container and returns any remaining unread values.
  In the root scope, ends all reading.

---

### Scope Shape Helpers

- **[`reader.dict()`](#readerdict)** — Opens a dictionary container (covers object/map).  
- **[`reader.seq()`](#readerseq)** — Opens a sequence container (covers array/set/stream/text).  
- **[`reader.flow()`](#readerflow)** — Opens a flow container for sequential reading (covers stream/text).  
- **[`reader.object()`](#readerobject)** — Opens an object container.  
- **[`reader.map()`](#readermap)** — Opens a map-like container.  
- **[`reader.array()`](#readerarray)** — Opens an array container.  
- **[`reader.set()`](#readerset)** — Opens a unique-value set container.  
- **[`reader.stream()`](#readerstream)** — Opens a binary stream container.  
- **[`reader.text()`](#readertext)** — Opens a text container.

---

### Bulk Reading

- **[`reader.save(sink, options)`](#readersavesink-options)**  
  Streams the current container’s remaining data to a sink, closing the scope when done.

- **[`reader.each(fn)`](#readereachfn)**  
  Applies `fn` to each value and closes the container when done.

- **[`reader.walk(fn, depth)`](#readerwalkfn-depth)**  
  Applies `fn` to each value recursively up to `depth` and closes all traversed scopes.

- **[`reader.keys(end)`](#readerkeysend)**  
  Returns an async iterator over keys in the current container.  
  If `end` is `true` (default), closes the scope after iteration.

- **[`reader.values(end)`](#readervaluesend)**  
  Returns an async iterator over values in the current container.  
  If `end` is `true` (default), closes the scope after iteration.

- **[`reader.entries(end)`](#readerentriesend)**  
  Returns an async iterator over key–value entries in the current container.  
  If `end` is `true` (default), closes the scope after iteration.

---

### **Flattening Utilities**

- **[`reader.flat(depth)`](#readerflatdepth)**  
  Returns an async iterator over all values recursively.  
  `depth` limits the nested depth to traverse.

- **[`reader.flatKeys(depth)`](#readerflatkeysdepth)**  
  Returns an async iterator over keys recursively.  
  `depth` limits the nested depth to traverse.

- **[`reader.flatValues(depth)`](#readerflatvaluesdepth)**  
  Returns an async iterator over values recursively.  
  `depth` limits the nested depth to traverse.

- **[`reader.flatEntries(depth)`](#readerflatentriesdepth)**  
  Returns an async iterator over key–value entries recursively.  
  `depth` limits the nested depth to traverse.

---

### Return Behavior

If there are no more values in the current scope:
- [`reader.open()`](#readeropen) and [`reader.hasNext()`](#readerhasnext) return `false`.
- [`reader.key()`](#readerkey), [`reader.value()`](#readervalue), [`reader.entry()`](#readerentry), [`reader.skip()`](#readerskip) return `undefined`.
If reading has fully finished (no active scopes left), all methods return `undefined`.

---

### Reader Scopes

Reading operates through hierarchical *scopes* — containers that define the current reading context.
The current scope is accessible via `reader.scope`, and the current nesting level (depth) via `reader.depth`.
Reader behavior depends on the active scope type: `root`, `array`, `set`, `map`, `stream`, or `text`.

#### 1. Root
- Created by default at the top level.
- Keys: numeric, sequential starting from `0`.
- Values: any supported values.
- `rest()` / `end()` — terminates all reading.

#### 2. Array
- Keys: numeric, sequential starting from `0`.
- Values: any supported values.
- `rest()` — returns an array.

#### 3. Set
- Keys: numeric, sequential starting from `0` (unlike JavaScript `Set`, which uses values as keys).
- Values: any supported values.
- `rest()` — returns a `Set`.

#### 4. Object
- Keys: strings.
- Values: any supported values.
- `rest()` — returns an object.

#### 5. Map
- Keys: any supported values.
- Values: any supported values.
- `rest()` — returns a `Map`.

#### 6. Stream
- Keys: numeric, sequential starting from `0`.
- Values: binary chunks (`Buffer`).
- `rest()` — returns a concatenated `Buffer`.

#### 7. Text
- Keys: numeric, sequential starting from `0`.
- Values: string chunks.
- `rest()` — returns a concatenated string.

### Choosing a Source

By default, a `Reader` is in the **reading** state.
If no data source is provided in the constructor, the reader waits for incoming data.

#### Providing Data

Data can be supplied using two methods:

- [`reader.push(...chunks)`](#readerpushchunks) — sends one or more data sources to the reader.
  Can be called multiple times to provide additional data.

- [`reader.finish(...chunks)`](#readerfinishchunks) — sends one or more data sources **and notifies the reader** that no more data will be provided.
  After calling `finish()`, further calls to `push()` or `finish()` have **no effect** until the reader closes the current session and starts a new one.

#### Supported Chunk Types

Each `chunk` can be:

- Binary data — [`Buffer`](https://nodejs.org/docs/latest/api/buffer.html), [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), `BufferView`, or anything convertible to a buffer.
- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
- [Node.js stream](https://nodejs.org/docs/latest/api/stream.html#class-streamreadable)
- [Web stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
- `String` — interpreted as a file path to read from.
- `Synchronous or asynchronous iterator` — each item can be any of the above, including nested iterators.

#### Data Processing

- Chunks are processed **lazily**, only when reading methods are called.
- They are queued internally if needed.
- Calling [`reader.finish()`](#readerfinishchunks) does **not** immediately finish reading, as unread data may remain in the queue or internal cache.
- Once the current reading session ends, **all unprocessed queued data is discarded**.

#### Conditions for Completing Data Processing

Processing stops when one of the following occurs:

- [`reader.close()`](#readerclose) is called.
- [`reader.end()`](#readerend) or [`reader.rest()`](#readerrest) is called in the **root scope**.
- [`reader.abort()`](#readeraborterror) is called.
- A fatal error occurs in one of the sources or due to invalid data.
- The end of all supplied data is reached (including all arguments passed to `finish()`).
- The parser encounters the **root-scope end marker** `END (0x00)`.

### Reading Examples
```js
import { Reader } from 'jsyn';

// Suppose we have a buffer with data produced by the Writer
const reader = new Reader(buffer);

// -----------------------------
// 1. Atomic Reading
// -----------------------------
console.log(await reader.value()); // "Simple string"
console.log(await reader.value()); // 123
console.log(await reader.value()); // true

// -----------------------------
// 2. Piecewise Reading with Async Iterators
// -----------------------------

// Array
if (await reader.open()) {
  for await (const value of reader.values()) {
    console.log(value); // 1, 2, 3
  }
}

// Object
if (await reader.open()) {
  for await (const [key, value] of reader.entries()) {
    console.log(key, value); // name: "Bob", age: 25
  }
}

// Set
if (await reader.open()) {
  for await (const value of reader.values()) {
    console.log(value); // "apple", "banana"
  }
}

// Map
if (await reader.open()) {
  for await (const key of reader.keys()) {
    console.log(key); // key1, key2
    console.log(await reader.value()); // "value1", 42
  }
}

// -----------------------------
// 3. Nested Containers
// -----------------------------

if (await reader.open()) {  // root object
  for await (const [key, value] of reader.entries()) {
    if (key === "user") {
      console.log(value); // atomic object
    } else if (key === "settings") {
      if (await reader.open()) {  // nested map
        for await (const [k, v] of reader.entries()) {
          console.log(k, v); // theme: "dark", notifications: true
        }
      }
    } else {
      console.log(key, value); // other atomic values
    }
  }
}

// -----------------------------
// 4. Saving Stream / Text to File or Stream
// -----------------------------

// Save to filesystem
await reader.save("output.txt");            // saves all remaining chunks to file

// Save to Node.js writable stream
import fs from 'node:fs';
const writable = fs.createWriteStream("output2.bin");
await reader.save(writable);                // writes remaining chunks to stream

// Save using a custom callback function
await reader.save(chunk => console.log("Chunk:", chunk));  // called for each chunk

// -----------------------------
// 5. Iterating Each Value (Array / Root / Set)
// -----------------------------

// Async iteration with callback for each value
await reader.each((value, key) => {
  console.log("Value:", value);
});

// Async function example
await reader.each(async value => {
  await processValue(value);               // handle each value asynchronously
});


// -----------------------------
// 6. Retrieve Remaining Unread Values
// -----------------------------

// returns all remaining unread values from current scope (or root) and close the scope
const remaining = reader.rest();
console.log("Remaining unread values:", remaining);
```

## 🛠Examples

### File System Packing and Unpacking
#### Example 1: Serialize a folder into a JSYN file
This example walks through a directory, serializes all files and subfolders into a JSYN writer, and saves the result to `fs.jsyn`. Each file is stored as a stream with metadata.
```js

import {stat, opendir} from 'fs/promises';
import {createReadStream} from 'fs';
import {Writer} from 'jsyn';

/**
 * Main function to serialize a part of the filesystem into JSYN format
 * @param {Writer} writer - JSYN Writer instance
 * @param {string} path - path to the root directory
 */
async function WriteFS(writer, path) {
    // Open root map scope
    writer.map();

    // Write the contents of the root directory
    await WriteDir(writer, path.replace(/\/$/, ''), '/');

    // Close the root map scope
    writer.end();
}

/**
 * Writes a single file into the writer
 * @param {Writer} writer
 * @param {string} realPath - actual filesystem path
 * @param {string} path - virtual path inside JSYN structure
 */
async function WriteFile(writer, realPath, path) {
    try {
        // Read file metadata
        const {
            mode,
            uid,
            gid,
            atimeMs: atime,
            mtimeMs: mtime,
            ctimeMs: ctime,
            birthtimeMs: birthtime,
        } = await stat(realPath);

        // Write file metadata as key
        writer.key({ path, mode, uid, gid, atime, mtime, ctime, birthtime });

        // Open a stream scope for file content
        writer.stream();

        // Read file and push to writer
        await writer.file(realPath);

        // Close stream scope
        writer.end();

        console.log(`Written file: ${path}`);
    } catch (error) {
        // Warn on errors but continue processing other files
        console.warn(`Failed to write file ${path}:`, error);
    }
}

/**
 * Recursively writes a directory into the writer
 * @param {Writer} writer
 * @param {string} realPath - filesystem directory path
 * @param {string} path - virtual path inside JSYN
 */
async function WriteDir(writer, realPath, path) {
    // Open the directory
    for await (const dirent of await opendir(realPath)) {
        // Construct real and virtual paths
        const direntRealPath = dirent.parentPath + '/' + dirent.name;
        const direntPath = path + dirent.name;

        if (dirent.isFile()) {
            // Write individual file
            await WriteFile(writer, direntRealPath, direntPath);
        } else if (dirent.isDirectory()) {
            // Recursively write subdirectory
            await WriteDir(writer, direntRealPath, direntPath + '/');
        }
    }
}

// Example usage
const path = '/home/user/JSYNTest'; // source dir
const file = '/home/user/JSYNTest.jsyn'; // target file
const writer = new Writer(file);

// Pack Files
await WriteFS(writer, path);

// End of Writing
await writer.close();

// Retrieve the buffer containing serialized data
export const buf = writer.buffer();
console.log('File Length:', writer.written);
```

#### Example 2: Restore a JSYN file to the filesystem
This example reads the `fs.jsyn` file, reconstructs all folders and files, and restores them to a target path.
```js

import { Reader } from 'jsyn';
import { mkdir } from 'fs/promises';
import { join } from 'path';

/**
 * Recursively restores filesystem from JSYN Reader.
 * @param {Reader} reader - Reader initialized with JSYN buffer
 * @param {string} targetPath - root folder to write files/folders
 */
async function ReadFS(reader, targetPath) {
  // Open the root map scope (returns true if successfully opened)
  if (await reader.open()) {

    // Iterate over all keys in the current scope
    for await (const key of reader.keys()) {
      // Build the full path for the file/directory
      const entryPath = join(targetPath, key.path);

      console.log(entryPath); // Logging for debug/inspection

      // Try to open the value associated with this key
      // If it succeeds, it is a file (stream) or nested container
      if (await reader.open()) {
        // Ensure parent directories exist
        await mkdir(join(entryPath, '..'), { recursive: true });

        // Save stream to file
        await reader.save(entryPath);
      }
    }

    // Close the root map scope
    await reader.end();
  }
}

// Usage
const file = '/home/user/JSYNTest.jsyn'; // source file
const path = '/home/user/JSYNTest-new'; // target folder
const reader = new Reader(file);
// Unpack Files
await ReadFS(reader, path);

```

#### Database Serialization (MongoDB)
```js
import { MongoClient, BSON } from "mongodb";
import { Writer, Reader } from "jsyn";
import { create as createBsonHelpers } from "jsyn/bson";
import fs from "node:fs";

// -----------------------------
// 1. Connect to MongoDB
// -----------------------------
const url = "mongodb://localhost:27017";
const client = new MongoClient(url);
await client.connect();

const db = client.db("mydb"); // database name
const collections = await db.listCollections().toArray()

// -----------------------------
// 2. Create a Writer for dump
// -----------------------------
const writer = new Writer();

// Add BSON types to Writer
const bsonHelpers = createBsonHelpers(BSON);
writer.hook(bsonHelpers.Hook);

writer.object(); // root object

// -----------------------------
// 4. Save dump to file
// -----------------------------

writer.to("mongodb_dump.jsyn");

// -----------------------------
// 5. Writing dump
// -----------------------------

for (const collInfo of collections) {
  const collName = collInfo.name;
  const collection = db.collection(collName);

  writer.key(collName);
  writer.array(); // each collection serialized as array

  // Stream documents using a cursor to avoid loading all into memory
  const cursor = collection.find();
  for await (const doc of cursor) {
    writer.value(doc); // atomic write per document
  }

  writer.end(); // closes the array for this collection
}

writer.end(); // closes root object

// ensure all buffered data is flushed and fully written, finalizing the current scope
await writer.close();

// -----------------------------
// 6. Reading dump back
// -----------------------------
const reader = new Reader("mongodb_dump.jsyn");
// Add BSON types to Reader
reader.exts.add(bsonHelpers.Types);


if (await reader.open()) { // root object
  for await (const [collection, docs] of reader.entries()) {
    console.log("Collection:", collection);
    for (const doc of docs) {
      console.log("  Document:", doc);
    }
  }
  await reader.end();
}

// -----------------------------
// 7. Close MongoDB connection
// -----------------------------
await client.close();
```

#### Simple Network Protocol
```js
import net from "node:net";
import { Writer, Reader } from "jsyn";
import fs from "fs";

// -----------------
// Server
// -----------------
const server = net.createServer(async socket => {
  const reader = new Reader(socket);
  const writer = new Writer(socket);
  // Open response root object
  writer.object();

  // Open request root object
  if (await reader.open()) {
    // Process commands as keys
    for await (const cmd of reader.keys()) {
      switch (cmd) {
        case "uploadFile": {
          // Read arguments
          if (await reader.open()) { // array of arguments
            const path = await reader.value(); // first argument
            // Write file to disk
            await reader.open();
            await reader.save(path); // second argument: file data
            await reader.end();
            writer.entry(cmd, "ok");
          }
          break;
        }
        case "deleteFile": {
          if (await reader.open()) {
            const path = await reader.value();
            await reader.end();
            try {
              fs.unlinkSync(path);
            	writer.entry(cmd, "ok");
            } catch(error){
            	writer.entry(cmd, "error");
            }
          }
          break;
        }
        default:
          writer.entry(cmd, "unknown command");
      }
    }
    await reader.end();
  }

  // Send response back to client
  await writer.close();
});

server.listen(9000, () => console.log("Server listening on port 9000"));

// -----------------
// Client
// -----------------
const clientWriter = new Writer();
clientWriter.object();

	// Upload
	clientWriter.key("uploadFile");
	clientWriter.array(); // open arguments
		clientWriter.value("test.txt"); // first arg: path
		clientWriter.stream(); // second arg: data
			clientWriter.push("Hello World");
		clientWriter.end(); // end data
	clientWriter.end(); // end arguments

	// Delete
	clientWriter.entry("deleteFile", ["old.txt"]);

clientWriter.end();

const clientSocket = net.connect(9000);
clientSocket.write(clientWriter.buffer());
clientSocket.on("data", data => {
  const clientReader = new Reader(data);
  (async () => {
    if (await clientReader.open()) {
      for await (const [cmd, result] of clientReader.entries()) {
        console.log(cmd, result); // uploadFile ok, deleteFile ok
      }
      await clientReader.end();
    }
  })();
});
```

# API Reference

## 🌀JSYN (Namespace)
The main namespace exposing all JSYN classes and utilities.

### [Writer](#jsynwriter)
Main class for writing JSYN data. Supports writing values, containers (object, array, map, set, text, stream), and extended types. Handles scopes, lifecycle states, and sinks.

### [Reader](#jsynreader)
Main class for reading JSYN data. Supports reading values, containers, async iteration, and managing reading scopes and lifecycle states.

### [HookList](#jsynhooklist)
Utility class to manage hooks. Hooks are functions applied to values during writing to transform, filter, or wrap them as extended types.

### [TypeMap](#jsyntypemap)
Registry of handlers for extended types. Used by `Reader` to interpret custom extended types when encountered during parsing.

### [Value](#jsynvalue)
Default wrapper class for extended types. Encapsulates a `typeid` and `payload` to store any value atomically as a custom type.

### [JSYN](#jsyn-namespace)
Cyclic reference to the JSYN namespace itself. Useful for utility methods, constants, and creating extended type wrappers.

### [types](#jsyntypes)
Utility for working with native JS types and their internal identifiers.  
Provides consistent mapping between JS types and JSYN type IDs.

### [conf](#jsynconf)
Contains JSYN constants and configuration values.  
Used internally and externally to ensure consistency across readers and writers.

### 🔥encode(...values)
Encodes one or multiple values into a single JSYN binary buffer.
**Parameters**
- `...values` — one or more values to encode. Can include any supported JSYN type (primitive, container, or extended type).

**Returns**
- `Buffer` — a JSYN-encoded binary representation of the passed values.

**Usage**
```js
import JSYN from 'jsyn';
// Encode multiple values into a single buffer
const buf = JSYN.encode(42, "hello", { key: "value" });
```
**Notes**
- This is a shortcut for creating a Writer, writing the values, and retrieving the buffer.

### 🔥decode(source)
Asynchronously decodes one or more values from a JSYN data source.
**Parameters**
- `source` — any valid data source accepted by [`reader.push()`](#readerpushchunks), including:
  - A binary buffer (`Buffer`, `ArrayBuffer`, `BufferView`)
  - A `Blob`
  - A Node.js stream
  - A Web stream
  - A string containing a file path
  - A synchronous or asynchronous iterator producing any of the above
**Returns**
- `Promise<Array>` — resolves with an array of all decoded values.
**Usage**
```js
import JSYN from 'jsyn';

// Encode some data
const buf = JSYN.encode(42, "hello", { key: "value" });

// Decode it back asynchronously
const values = await JSYN.decode(buf);

console.log(values); // [42, "hello", { key: "value" }]
```
**Notes**
- This is a shortcut for creating a Reader, pushing data to it, and reading until completion.
- Equivalent to manually using [reader.rest()](#readerrest) at the root scope.
- The returned array preserves the order of the encoded values.

### 🔥typeOf(value)
Returns the numeric **type code** corresponding to the given value according to the JSYN data type system.
Used internally to determine how a value should be encoded or decoded.
#### Parameters
- `value` — any value to check.
#### Returns
- `number` — unique integer identifier for each supported type; for unsupported types (`undefined`, `symbol`, `function`) returns `1`, same as for `null`.
- Does not perform any conversion — it only classifies the input value.

### 🔥isSupported(value)
Checks whether the given value can be represented in JSYN format.
#### Parameters
- `value` — any value to check.
#### Returns
- `boolean` — `true` if the value is supported; `false` for unsupported types (`undefined`, `symbol`, `function`).

### 🔥isExt(value)
Checks if the given value is an extended JSYN type.
#### Parameters
- `value` — any value to check.
#### Returns
- `boolean` — true if the value is an instance of [`JSYN.Value`](#jsynvalue), otherwise false.

### 🔥ext(typeid, payload)
Creates a JSYN extended value wrapper for the given type ID and payload.
#### Parameters
- `typeid` — integer, the user-defined type identifier (1–268435455). `0` is reserved.
- `payload` *(optional)* — any valid JSYN value to store as the extended value.
#### Returns
- [`JSYN.Value`](#jsynvalue) — a wrapper representing the extended type for use in writing or serialization.

### 🔒conf.MAX_BLOCK_SIZE
The maximum size of a single data block in JSYN format, in bytes (268435455 = 2^28-1).

### 🔒conf.MAX_EXTENDED_TYPE_ID
The maximum allowed identifier for extended/user-defined types (268435455 = 2^28-1).

### 🔒conf.WRITER_BUFFER_SIZE
Default internal buffer size used by [`JSYN.Writer`](#jsynwriter) when storing data before flushing to a sink.

### 🔒conf.READER_CACHE_SIZE
Default internal cache size used by [`JSYN.Reader`](#jsynreader) for temporarily storing incoming data chunks.

## 📝JSYN.Writer
`JSYN.Writer` is a class for writing data in JSYN format to a sink.
It supports atomic values, containers, and extended types, and can be reused to write multiple sequences of data.

### new Writer(sink, options)
Creates a new `JSYN.Writer` instance.
- `sink` — optional. If provided, behaves as if [`writer.to(sink, options)`](#writertosink-options) was called.
- `options` — optional object for configuration (e.g., `storable`, flags).
The writer is ready to accept values for JSYN serialization immediately after construction.

### 🫧writer.state
Returns the current state of the writer.
Possible values: `"opened"`, `"closed"`, `"failed"`.
See [Writer Lifecycle](#writer-lifecycle)

### 🫧writer.opened
Boolean flag indicating if the writer is currently in the `"opened"` state.

### 🫧writer.closed
Boolean flag indicating if the writer is currently in the `"closed"` state.

### 🫧writer.failed
Boolean flag indicating if the writer is currently in the `"failed"` state.

### 🫧writer.error
Holds the error object if the writer is in the `failed` state.
`null` if no error has occurred.

### 🫧writer.writable
Indicates whether the writer is currently writable.
`true` if data can be written, `false` if writing is blocked (e.g., after `close()`).

### 🫧writer.storable
Indicates whether the writer is in a storable mode (internal buffer).
`true` if data can be stored and later retrieved via `release()`, `buffer()`, or `blob()`.
See [Choosing a Sink](#choosing-a-sink)

### 🫧writer.depth
Returns the current depth of the writer stack.
- If `writable` is `true`:
  - Root scope has `depth = 1`
  - Nested scopes have `depth = parent.depth + 1`
- If `writable` is `false`, always returns `0`

### 🫧writer.path
Returns a string representing the current path in the writer stack, showing the hierarchy of nested scopes.
- For example: `"[0].key[2].subkey"`
- Reflects the current position in objects, arrays, maps, and sets within the stack.
- If `writable` is `false`, may return `null`

### 🫧writer.scope
Returns a string indicating the type of the current scope in the writer stack.
- Possible values correspond to the container types described in **Writer Scopes**:
  - `"root"` — the top-level scope
  - `"array"` — array container
  - `"set"` — set container
  - `"object"` — object container
  - `"map"` — map container
  - `"stream"` — binary stream
  - `"text"` — text stream
- Reflects the current active scope where values are being written.
See [Writer Scopes](#writer-scopes)

### 🫧writer.written
Returns the total number of bytes written to the sink over the entire lifetime of the writer.
- Does not include data currently held in the internal buffer (cache).

### 🫧writer.staged
Returns the amount of data currently in the internal buffer, awaiting dispatch either automatically or via [`writer.flush()`](#writerflush).

### 🫧writer.hooks
Gets or sets the [`HookList`](#jsynhooklist) instance used by this writer.
- Hooks are applied to values according to the rules described in the **Writer Hooks** section.
- For efficiency, it is recommended to create a `HookList` instance once and reuse it across multiple writers:
See [Hooks](#hooks), 
```js
const myHooks = new JSYN.HookList(hook1, hook2);
writer.hooks = myHooks;
```

### 🔥writer.end(...values)
Closes the current scope and returns to the parent scope.
#### Behavior by Scope
- **Root Scope**
  - Writes all passed values and ends the root scope.
  - Unsupported values are ignored.
- **Array Scope**
  - Writes all passed values and ends the array scope.
  - Unsupported values are written as `null`.
- **Set Scope**
  - Writes all passed values and ends the set scope.
  - Unsupported values are ignored.
- **Stream Scope**
  - Writes all values that can be converted to binary (`Buffer`, `ArrayBuffer`, `BufferView`, number arrays, UTF-8 strings).
  - Unsupported values throw an error.
- **Text Scope**
  - Writes all values converted to strings via `String(value)`.
  - Unsupported values throw an error.
- **Object/Map Scope**
  - Ends the object/map scope.
  - Passing any values throws an error.
  - Partially written key-value pairs with missing values are ignored.
#### Returns
- `true` if the writer is still writable, `false` if `writable=false`.

### 🔥writer.key(key)
Writes a key for the current container scope.
#### Behavior by Scope
- **Root / Array / Set / Stream / Text Scopes**
  - Calling `key()` in these scopes will throw an error.
- **Object Scope**
  - `key` can be any value convertible to a string (`String(key)`), otherwise throws an error.
  - If the key already exists, it is replaced with the new one.
- **Map Scope**
  - `key` can be any supported JSYN value.
  - Unsupported values (`undefined`, `symbol`, `function`) throw an error.
  - If the key already exists, it is replaced with the new one.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.entry(key, value)
Writes a key-value pair to the current container scope.
#### Behavior by Scope
- **Root / Array / Set / Stream / Text Scopes**
  - Calling `entry()` in these scopes will throw an error.
- **Object Scope**
  - `key` can be any value convertible to a string (`String(key)`), otherwise throws an error.
  - `value` can be any supported value.
  - If the key already exists, it is replaced with the new value.
  - If the value is unsupported, the pair is ignored.
- **Map Scope**
  - `key` can be any supported JSYN value.
  - Unsupported keys (`undefined`, `symbol`, `function`) throw an error.
  - If the key already exists, it is replaced with the new value.
  - If the value is unsupported, the pair is ignored.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.value(value)
Writes a single value to the current container scope.
#### Behavior by Scope
- **Root / Set Scope**
  - `value` can be any JSYN-supported value.
  - Unsupported values are ignored.
- **Array Scope**
  - `value` can be any JSYN-supported value.
  - Unsupported values are written as `null`.
- **Stream Scope**
  - `value` must be convertible to binary (`Buffer`, `ArrayBuffer`, `BufferView`, array of numbers, UTF-8 string), otherwise throws an error.
- **Text Scope**
  - `value` must be convertible to string (`String(value)`), otherwise throws an error.
- **Object / Map Scope**
  - If the scope is expecting a key (immediately after a value), throws an error.
  - `value` can be any supported value.
  - Unsupported values cause the current key-value pair to be ignored.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.push(...values)
Writes multiple values consecutively to the current container scope.
#### Behavior by Scope
- **Root / Set Scope**
  - `values` can be any JSYN-supported values.
  - Unsupported values are ignored.
- **Array Scope**
  - `values` can be any JSYN-supported values.
  - Unsupported values are written as `null`.
- **Stream Scope**
  - Each value must be convertible to binary (`Buffer`, `ArrayBuffer`, `BufferView`, array of numbers, UTF-8 string), otherwise throws an error.
- **Text Scope**
  - Each value must be convertible to string (`String(value)`), otherwise throws an error.
- **Object / Map Scope**
  - Error — writing multiple values at once is not allowed.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.add(value)
Adds a single value to a **Set** scope (alias for [`writer.value(value)`](#writervaluevalue) in set scope).
#### Behavior by Scope
- **Set Scope**
  - `value` can be any JSYN-supported value.
  - Unsupported values are ignored.
- **Root / Array / Stream / Text / Object / Map Scope**
  - Error — method not allowed.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.ext(typeid, payload)
Explicitly writes an **extended (custom) JSYN type**.
#### Parameters
- `typeid` — integer in the range `1..268_435_455`.
- `payload` — any value; unsupported values will be converted to `null`.
#### Behavior by Scope
- **Root / Set / Array Scope**
  Unsupported payload values are ignored.
- **Object / Map Scope**
  - If the scope expects a key (just after writing a value) — error.
  - Otherwise, writes a key-value pair using the payload as the value.
- **Stream / Text Scope**
  Always throws an error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.object()
Opens a nested **object scope**.
#### Behavior by Scope
- **Root / Set / Array Scope** — creates a nested object scope.
- **Object / Map Scope** — error if a key is expected, otherwise nested scope.
- **Stream / Text Scope** — always error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.map()
Opens a nested **map scope**.
#### Behavior by Scope
- **Root / Set / Array Scope** — creates a nested map scope.
- **Object / Map Scope** — error if a key is expected, otherwise nested scope.
- **Stream / Text Scope** — always error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.array()
Opens a nested **array scope**.
#### Behavior by Scope
- **Root / Set / Array Scope** — creates a nested array scope.
- **Object / Map Scope** — error if a key is expected, otherwise nested scope.
- **Stream / Text Scope** — always error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.set()
Opens a nested **set scope**.
#### Behavior by Scope
- **Root / Set / Array Scope** — creates a nested set scope.
- **Object / Map Scope** — error if a key is expected, otherwise nested scope.
- **Stream / Text Scope** — always error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.stream()
Opens a nested **stream scope**.
#### Behavior by Scope
- **Root / Set / Array Scope** — creates a nested stream scope.
- **Object / Map Scope** — error if a key is expected, otherwise nested scope.
- **Stream / Text Scope** — always error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.text()
Opens a nested **text scope**.
#### Behavior by Scope
- **Root / Set / Array Scope** — creates a nested text scope.
- **Object / Map Scope** — error if a key is expected, otherwise nested scope.
- **Stream / Text Scope** — always error.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.values(values)
Writes multiple values to the current container scope.
#### Behavior by Scope
- **Root / Set Scope**
  - Accepts a synchronous or asynchronous iterator.
  - Values can be any JSYN-supported value.
  - Unsupported values are ignored.
- **Array Scope**
  - Accepts a synchronous or asynchronous iterator.
  - Values can be any JSYN-supported value.
  - Unsupported values are replaced with `null`.
- **Stream Scope**
  - Accepts a synchronous or asynchronous iterator.
  - Values must be convertible to binary (`Buffer`, `ArrayBuffer`, `BufferView`, array of numbers, UTF-8 string).
- **Text Scope**
  - Accepts a synchronous or asynchronous iterator.
  - Values must be convertible to string (`String(value)`).
- **Object / Map Scope**
  - Not allowed. Passing this scope throws a fatal error.
#### Errors
- If an iterator yields an invalid value or throws, the iteration stops and a **non-fatal error** is raised.
#### Returns
- A **Promise** that resolves when iteration completes.
> ⚠️ Throws an error if `writer.writable = false`.

### 🔥writer.entries(entries)
Writes multiple key-value pairs to the current container scope.
#### Behavior by Scope
- **Object Scope**
  - Accepts a synchronous or asynchronous iterator of pairs `[key, value]`.
  - `key` is converted to a string.
  - `value` can be any JSYN-supported value.
  - Unsupported keys or values, or errors during iteration, cause the pair to be ignored.
- **Map Scope**
  - Accepts a synchronous or asynchronous iterator of pairs `[key, value]`.
  - Both `key` and `value` can be any JSYN-supported value.
  - Unsupported keys or values, or errors during iteration, cause the pair to be ignored.
- **Root / Array / Set / Stream / Text Scope**
  - Operation is not allowed. Passing these scopes throws a fatal error.
#### Errors
- If an iterator yields an invalid pair or throws, the iteration stops and a **non-fatal error** is raised.
#### Returns
- A **Promise** that resolves when iteration completes.

### 🔥writer.assign(source)
Writes the enumerable entries (key-value pairs) of an object into the current container scope.
#### Behavior by Scope
- **Object / Map Scope**
  - Accepts any object.
  - Writes each enumerable property as a key-value pair into the scope.
- **Root / Array / Set / Stream / Text Scope**
  - Operation is not allowed. Passing these scopes throws a fatal error.

### 🔥writer.load(source)
Writes binary or textual data into the current **stream** or **text** scope.
#### Accepted Sources
- **Buffer** or other binary types (`ArrayBuffer`, `BufferView`) – written entirely.
- **String** – written entirely.
- **Readable stream** – writes all chunks from the stream.
- **Web ReadableStream** – writes all chunks from the stream.
- **Blob** – writes all chunks.
- **Synchronous or asynchronous iterator** – writes all values; each value can be any of the types listed above, including nested iterators.
#### Behavior by Scope
- **Stream Scope**
  - All chunks are written as binary data.
- **Text Scope**
  - All chunks are converted to UTF-8 strings before writing.
- **Root / Array / Set / Object / Map Scope**
  - Operation is not allowed. Passing these scopes throws a fatal error.
#### Errors
- If reading from the source fails, or a value is of an invalid type, iteration stops and a **non-fatal error** is raised.
#### Returns
- A **Promise** that resolves when writing completes.

### 🔥writer.file(path)
Writes a file from the filesystem into the current container scope.
#### Parameters
- `path` – a string specifying the path to the file.
#### Behavior by Scope
- **Stream / Text Scope**
  - Reads the file and writes all chunks to the current scope.
- **Root / Array / Set / Object / Map Scope**
  - Operation is not allowed. Passing these scopes throws a fatal error.
#### Returns
- A **Promise** that resolves when the file has been fully written.

### 🔥writer.to(sink, options)
Transfers all currently stored data to a new external sink and optionally continues writing there.
- Available only when `writer.storable = true` (default mode).
#### Parameters
- `sink` — the new destination for the data. Can be one of:
  - **Node.js stream** — any writable stream.
  - **Web stream** — a `WritableStream`.
  - **Object** — will be wrapped as a `WritableStream` with a `write(chunk)` method.
  - **Function** — used as `{ write(chunk) { ... } }` and wrapped as a `WritableStream`.
  - **String** — interpreted as a file path to write to.
- `options` — optional object:
  - `options.autoclose` (boolean, default `true`) — for streams, automatically closes the sink when writing finishes.
  - `options.flags` (string/number) — file system flags when `sink` is a file path.
  - `options.mode` (number) — file mode (permissions) when `sink` is a file path.

#### Behavior
1. Opens a new external sink (like [`writer.pipe()`](#writerpipesink-options)).
2. Sends all accumulated data from the internal store to the new sink.
3. If the previous writing session was finished, closes the new sink; otherwise continues writing to it.
#### Returns
- `Promise` — resolves when all data has been successfully transferred and the session is completed, or rejects if an error occurs.

### 🔥writer.pipe(sink, options)
Selects an **external sink** and switches the writer to `pipe` mode.
- Can be called only when writer is in `closed` or `failed` state; calling in `opened` state throws an error.
#### Parameters
- **Sink** can be:
  - Node.js writable stream
  - Web `WritableStream`
  - Any object — will be wrapped into a `WritableStream`
  - Function — used as `{ write(chunk) {} }` and wrapped in `WritableStream`
  - String — interpreted as a file path
- **Options** (optional object):
  - `autoclose` — if `true` (default), the stream will be closed automatically when writing ends
  - `flags` — file opening flags
  - `mode` — file mode
#### Returns
- `Promise` — resolves when all data has been successfully transferred and the session is completed, or rejects if an error occurs.

### 🔥writer.wipe()
Selects a **null sink** (black hole) where data is discarded. Useful for testing or scenarios where output is not required.
- Can be called only when writer is in `closed` or `failed` state; calling in `opened` state throws an error.
- Behavior is otherwise identical to writing to a normal sink.
#### Returns
- `Promise` — resolves when all data has been successfully transferred and the session is completed, or rejects if an error occurs.

### 🔥writer.store()
Selects an **internal sink** for storing data in the writer’s buffer.
- Can be called only when writer is in `closed` or `failed` state; calling in `opened` state throws an error.
- Stored data can later be retrieved using:
  - [`writer.release(end)`](#writerreleaseend)
  - [`writer.buffer(end)`](#writerbufferend)
  - [`writer.blob(end)`](#writerblobend)
- Or redirected to another sink with:
  - [`writer.to(sink, options)`](#writertosink-options)
#### Returns
- `Promise` — resolves when all data has been successfully transferred and the session is completed, or rejects if an error occurs.

### 🔥writer.reset(force)
Restarts a new writing session.
- Applicable only for `store` or `wipe` sinks; using it on external sinks (`pipe`) throws an error.
- Can be called:
  - In `failed` or `closed` states — starts a new session for `store` or `wipe`.
  - In `opened` state if `force = true` — aborts the current session with an error and starts a new one.
- Calling in other situations results in an error.
#### Parameters
- `force` — optional boolean (default `false`), allows reset in `opened` state.

### 🔥writer.abort(error)
Immediately terminates the writing process with an error.
- Puts the writer into the `failed` state.
- Clears the internal buffer.
- Clears the scope stack.
- Clears all accumulated data if `writer.storable = true`.
- Terminates any external sinks (streams, sockets, etc.) with the error.
- Calling in `closed` or `failed` state has no effect.
#### Parameters
- `error` — any value representing the reason for the abort.

### 🔥writer.close(end)
Asynchronous method that finalizes the writing process.
- Closes all open containers.
- Sends the [`ROOT-END`](#root-scope-end-byte) marker if `end = true`.
- Flushes internal buffer to the sink.
- Clears the scope stack.
- Waits asynchronously for the sink to finish processing and then closes the source.
- Immediately sets `writer.writable = false`; the state changes to `closed` only after the promise resolves.
- In `closed` state, calling has no effect.
- In `failed` state, calling throws the last fatal error.
#### Parameters
- `end` — optional boolean, if `true` appends the [`ROOT-END`](#root-scope-end-byte) marker.

### 🔥writer.sync()
Asynchronous method that waits for the internal sink to process previously written data.
> **Purpose & Behavior:**
1. Ensures that data has been successfully handled and prevents sink overflow.
2. Confirms that data was correctly written and no errors occurred.
- In `closed` state, the method has no effect.
- In `failed` state, it throws the last encountered error.
- In `opened` state for `store` or `wipe` sinks, it executes synchronously and has no significant effect.
- For external sinks (e.g., sockets, files, streams), it waits for the completion of the write operation or throws an error if the write fails.
- Unlike [`writer.flush()`](#writerflush), this method **does not push data from the internal buffer**; it only waits for sink processing.
- Recommended to call periodically to match writing speed with the sink’s capacity.
#### Returns
- `Promise<void>`

### 🔥writer.flush()
Clears the internal buffer (cache) of the writer and immediately sends all accumulated data to the current sink.
> **Notes & Behavior:**
- Ensures that all written data is pushed to the sink without delay.
- Does **not** guarantee that the sink has fully processed the data.
- Typically, this method is called automatically when the buffer is full or when the writing session ends.
- Frequent manual calls may reduce performance due to extra overhead.
- Usually unnecessary unless you need to force immediate delivery of data.


### 🔥writer.release(end)
Returns all accumulated data as an array of buffers (chunks).
**Requirements & Behavior:**
- Works only if `writer.storable = true`; otherwise throws an error.
- Closes the current writing session, finishing all scopes (like [`writer.close()`](#writercloseend)).
- Accepts an optional `end` argument to append the [`ROOT-END`](#root-scope-end-byte) marker.
- Clears the accumulated data; subsequent calls return an empty array.
#### Parameters
- `end` — optional boolean, if `true` appends [`ROOT-END`](#root-scope-end-byte) marker.
#### Returns
- `Buffer[]` — array of binary chunks.

### 🔥writer.buffer(end)
Returns all accumulated data as a single `Buffer`.
 **Requirements & Behavior:**
 - Works only if `writer.storable = true`; otherwise throws an error.
 - Closes the current writing session, finishing all scopes (like [`writer.close()`](#writercloseend)).
 - Accepts an optional `end` argument to append the [`ROOT-END`](#root-scope-end-byte) marker.
 - Clears the accumulated data; subsequent calls return an empty buffer.
#### Parameters
- `end` — optional boolean, if `true` appends [`ROOT-END`](#root-scope-end-byte) marker.
#### Returns
- `Buffer` — combined binary data.

### 🔥writer.blob(end)
Returns all accumulated data as a `Blob`.
 **Requirements & Behavior:**
 - Works only if `writer.storable = true`; otherwise throws an error.
 - Closes the current writing session, finishing all scopes (like [`writer.close()`](#writercloseend)).
 - Accepts an optional `end` argument to append the [`ROOT-END`](#root-scope-end-byte) marker.
 - Clears the accumulated data; subsequent calls return an empty Blob.
#### Parameters
- `end` — optional boolean, if `true` appends [`ROOT-END`](#root-scope-end-byte) marker.
#### Returns
- `Blob` — combined data as Blob.

### 🔥writer.hook(hook)
Alias for `writer.hooks.add(hook)`.
Adds a hook function to the writer's hook list.
#### Parameters
- `hook` — function with signature `(key, value)` to process values before serialization.

### 🔥writer.unhook(hook)
Alias for `writer.hooks.remove(hook)`.
Removes a previously added hook from the writer's hook list.
#### Parameters
- `hook` — function previously added to the writer's hook list.

### 🔥writer.stat()
Returns a read-only snapshot of the current writer state.
This method is **undocumented** and may change in future implementations.

Current example output:

```json
{
  "state": "opened",
  "scope": "root",
  "depth": 3,
  "path": "[0].data[2]",
  "receiver": "store",
  "hooks": 1,
  "writable": true,
  "storable": true,
  "written": 28014,
  "staged": 102,
}
```

## 📚JSYN.Reader
`JSYN.Reader` is a class for reading data serialized in JSYN format.
It provides methods to access values, containers, and extended types, and can be reused for multiple reading sessions.

### new Reader(source)
Creates a new `JSYN.Writer` instance.
- `source` (optional) — initial data source, can be any type accepted by [`reader.push()`](#readerpushchunks).
- If `source` is provided, it behaves as if [`reader.finish(source)`](#readerfinishchunks) was called immediately after construction.

### 🫧reader.state
Returns the current state of the reader.
Possible values: `"opened"`, `"closed"`, `"failed"`.
See [Reader Lifecycle](#reader-lifecycle)

### 🫧reader.opened
Boolean flag indicating if the reader is currently in the `"opened"` state.

### 🫧reader.closed
Boolean flag indicating if the reader is currently in the `"closed"` state.

### 🫧reader.failed
Boolean flag indicating if the reader is currently in the `"failed"` state.

### 🫧reader.error
Holds the error object if the reader is in the `"failed"` state; otherwise `null`.

### 🫧reader.depth
Indicates the current nesting level of the reader's scopes.
- For `closed` or `failed` states, always `0`.
- For `opened` state:
  - Root scope: `1`
  - Each successful [`await reader.open()`](#readeropen) increments by `1`
  - Each [`await reader.end()`](#readerend) or [`await reader.rest()`](#readerrest) decrements by `1`

### 🫧reader.path
A string representing the current path of the reader within nested scopes.
It shows the sequence of container types and keys/indices leading to the current value.
See the **Reader Scopes** section for details on path composition.

### 🫧reader.scope
A string indicating the type of the current scope (e.g., `root`, `array`, `object`, `map`, `set`, `stream`, `text`).
For details on each scope and its behavior, see the **[Reader Scopes](#reader-scopes)** section.

### 🫧reader.finished
Boolean flag indicating that the data source has been finished or reading has been formally completed.
This becomes `true` if any of the following occurred:
- [`reader.finish()`](#readerfinishchunks) was called.
- Reading was completed with [`reader.end()`](#readerend) or [`reader.rest()`](#readerrest) in the root scope, or [`reader.close()`](#readerclose).
- A root scope END marker (`END-ROOT`) was encountered.
- A fatal error occurred during reading.

### 🫧reader.paused
Boolean flag indicating that the data source is currently paused.
This occurs when data is being supplied faster than the reader can process it.

### 🫧reader.received
Total number of bytes read by the reader across all sessions.
Does not include data still in the source queue.

### 🫧reader.unread
Number of bytes received by the parser but not yet read.

### 🫧reader.reserve
Byte balance between incoming data and parser consumption:
- Positive: more data is available than the parser has requested.
- Negative: not enough data is available for the parser to continue processing by this amount.

### 🫧reader.exts
A [`TypeMap`](#jsyntypemap) instance used to resolve extended JSYN types during reading.
- Maps `typeid` to handler functions.
- Handlers transform payloads of extended types into desired objects.
- Can be preconfigured or modified at any time.
See [Extended Types](#extended-types)

### 🔥reader.each(fn)
Asynchronous method to iterate over all remaining elements in the current scope.
Applies a function `fn` to each element without manually handling the iterator.
At the end of iteration, automatically closes the current scope and returns to the parent scope.
#### Parameters
- **fn** `function(value, key, reader)` — synchronous or asynchronous function called for each element:
  - `value` — current value
  - `key` — numeric index for root/array/set/text/stream, actual key for object/map
#### Behavior
- Advances the reader pointer for each value until the scope is exhausted.
- Automatically closes the current scope at the end and resumes iteration in the parent scope.
- In **closed** state — immediately resolves with `undefined`.
- In **failed** state — throws the current reader error.
#### Returns
- `Promise<void>` — resolves when all elements are processed and the current scope is closed.
#### Example
```javascript
const reader = new Reader(buffer);

await reader.open(); // open root scope

await reader.each(async (value, key, r) => {
  console.log('Index/key:', key, 'Value:', value);
});
// At this point, root scope is automatically closed
```

### 🔥reader.walk(fn, depth)
Asynchronous method to recursively iterate over all remaining elements in the current scope  
and its nested scopes up to the specified depth.
Automatically closes nested scopes during traversal, so the user does not need to manage iterator flow manually.
#### Parameters
- **fn** `function(value, key)` — synchronous or asynchronous function called for every value encountered:
  - `value` — current value  
  - `key` — numeric index for root/array/set/text/stream, actual key for object/map  
- **depth** `number` *(optional)* — maximum nesting depth to descend into:
  - Any integer ≥ 0, where `0` iterates only the current scope (equivalent to `each`)  
  - Larger integers descend correspondingly deeper into nested structures  
  - `Infinity` — traverse all nested levels without limitation  
  - Default: `Infinity`
#### Behavior
- Recursively advances the reader through the current scope and nested scopes up to the given depth.
- Automatically opens child scopes when a nested structure is encountered and closes them after processing.
- Applies the callback `fn` to each value before descending further.
- At the end of traversal, automatically closes the current scope and returns to the parent scope.
- In **closed** state — immediately resolves with `undefined`.
- In **failed** state — throws the current reader error.
#### Returns
- `Promise<void>` — resolves when all reachable elements are processed and the current scope is fully closed.

### 🔥reader.keys(end)
Asynchronously iterates over all remaining keys in the current object/map scope.
- For **object/map** scopes, these are the actual keys.
- For other scopes (root, array, set, stream, text), this behaves as a counter of remaining elements.
#### Parameters
- `end` — `boolean` (default `false`)
  If `true`, the scope will be automatically closed after all keys are iterated.
#### Returns
- `AsyncIterable<any>` — asynchronous iterator over the remaining keys in the current scope.
#### Example

```js
const reader = new JSYN.Reader(someSource);
await reader.open(); // enter object/map scope

for await (const key of reader.keys(true)) {
  console.log("Key:", key);
  console.log("Value:", await reader.value());
}
```

### 🔥reader.values(end)
Asynchronously iterates over the remaining values in the current scope.
- For **all scopes** (root, array, set, object, map, stream, text), values are returned according to the JSYN type semantics.
#### Parameters
- `end` — `boolean` (default `false`)
  If `true`, the current scope will be automatically closed after iteration.
#### Returns
- `AsyncIterable<any>` — asynchronous iterator over remaining values.
#### Example
```js
const reader = new JSYN.Reader(someSource);
await reader.open(); // enter any scope

for await (const value of reader.values(true)) {
  console.log("Value:", value);
}
```

### 🔥reader.entries(end)
Asynchronously iterates over key-value pairs in the current scope.
- For **object/map scopes**, returns `[key, value]` pairs.
- For other scopes (root, array, set, stream, text), returns `[index, value]` pairs where `index` is a numeric position or counter.
#### Parameters
- `end` — `boolean` (default `false`)
  If `true`, the current scope will be automatically closed after iteration.
#### Returns
- `AsyncIterable<[any, any]>` — asynchronous iterator over key-value pairs.
#### Example
```js
const reader = new JSYN.Reader(someSource);
await reader.open(); // enter a scope

for await (const [key, value] of reader.entries(true)) {
  console.log("Key:", key, "Value:", value);
}
```

### 🔥reader.flat(depth)
Alias for [`reader.flatValues(depth)`](#readerflatvaluesdepth).
#### Parameters
- `depth` — `number` (optional)  
  Maximum depth to iterate. If omitted, iterates all nested levels.
#### Returns
- `AsyncIterable<any>` — asynchronous iterator over values.

---

### 🔥reader.flatValues(depth)
Asynchronously iterates over all values in the current scope, including nested containers up to the specified depth.
#### Parameters
- `depth` — `number` (optional)  
  Maximum depth to iterate. If omitted, iterates all nested levels.
#### Returns
- `AsyncIterable<any>` — asynchronous iterator over values.

---

### 🔥reader.flatKeys(depth)
Asynchronously iterates over all keys in the current scope, including nested containers up to the specified depth.
#### Parameters
- `depth` — `number` (optional)  
  Maximum depth to iterate. If omitted, iterates all nested levels.
#### Returns
- `AsyncIterable<any>` — asynchronous iterator over keys.

---

### 🔥reader.flatEntries(depth)
Asynchronously iterates over all key-value pairs in the current scope, including nested containers up to the specified depth.
#### Parameters
- `depth` — `number` (optional)  
  Maximum depth to iterate. If omitted, iterates all nested levels.
#### Returns
- `AsyncIterable<[any, any]>` — asynchronous iterator over `[key, value]` pairs.

### 🔥reader.hasNext()
Checks whether there are remaining unread values in the current scope.
- Returns `false` if the reader has reached the end of the scope or the reader is in `closed`/`failed` state (in which case it will throw an error).
- For non-container scopes, checks if the current counter has more items.
- Returns `undefined` if the reader is in `closed` state.
#### Returns
- `Promise<boolean | undefined>` — resolves to `true` if there are unread values remaining, otherwise `false`.
- Throws an error if the reader is in `failed` state.

### 🔥reader.rawId()
Returns the numeric system type identifier of the next value in the current scope.
- See [Supported Types](#supported-types).
- Returns `0` if there are no more values in the current scope.
- In a stream scope: returns `BINARY (0x0F)` if a value exists, or `0`.
- In a text scope: returns `STRING (0x0C)` if a value exists, or `0`.
#### Returns
- `Promise<number>` — resolves to the system type identifier of the next value, or `0` if none.

### 🔥reader.raw()
Returns the name of the system type of the next value in the current scope.
- See [Supported Types](#supported-types).
- Returns `'end'` if there are no more values in the current scope.
- In a stream scope: returns `'binary'` if a value exists, or `'end'`.
- In a text scope: returns `'string'` if a value exists, or `'end'`.
#### Returns
- `Promise<string>` — resolves to the system type name of the next value, or `'end'` if none.

### 🔥reader.type()
Returns the human-readable type name of the next value in the current scope.
- See [Supported Types](#supported-types).
- Returns `undefined` if there are no more values in the current scope.
- In a stream scope: returns `'binary'` if a value exists, or `undefined`.
- In a text scope: returns `'string'` if a value exists, or `undefined`.
#### Returns
- `Promise<string | undefined>` — resolves to the human-readable type name, or `undefined` if none.

### 🔥reader.kind()
Returns the JS-compatible general type name of the next value in the current scope.
- See [Supported Types](#supported-types).
- Returns `undefined` if there are no more values in the current scope.
- In a stream scope: returns `'object'` if a value exists, or `undefined`.
- In a text scope: returns `'string'` if a value exists, or `undefined`.
- For `null`, returns `'null'`.
#### Returns
- `Promise<string | undefined>` — resolves to the JS-compatible type name, or `undefined` if none.

### 🔥reader.key()
Returns the current key in the current scope without advancing the reader.
- For `object` or `map` scopes: returns the last read key.
- For other scopes (`root`, `array`, `set`, `stream`, `text`): returns the index of the last read value.
- Returns `undefined` if there are no more keys or values in the current scope.
- Repeated calls return the same key until `reader.next()` or similar method advances the reader.
#### Returns
- `Promise<any | undefined>` — resolves to the current key or index, or `undefined`.
- Throws an error if the reader is in `failed` state.

### 🔥reader.value()
Returns the current value in the scope and advances the reader to the next position.
- If a key was not read yet in an `object`/`map` scope, the key is skipped.
- Repeated calls return the next value in the scope.
- Returns `undefined` if there are no more values in the current scope.
- Throws an error if the reader is in `failed` state.
- In `closed` state, always resolves to `undefined`.
#### Returns
- `Promise<any | undefined>` — resolves to the next value in the current scope or `undefined`.

### 🔥reader.entry()
Reads the next key-value pair in the current scope and advances the reader.
- Returns `[key, value]` for `object` and `map` scopes.
- Repeated calls advance to the next pair.
- Returns `undefined` if there are no more entries in the current scope.
- Throws an error if the reader is in `failed` state.
- In `closed` state, always resolves to `undefined`.
#### Returns
- `Promise<[any, any] | undefined>` — resolves to the next key-value pair or `undefined`.

### 🔥reader.skip()
Skips the next iteration in the current scope.
- Each call advances the reader by one value or key-value pair, regardless of the current read state.
- Repeated calls continue skipping subsequent values/pairs.
- Always returns a `Promise` resolving to `undefined`.
- Throws an error if the reader is in `failed` state.
#### Returns
- `Promise<undefined>` — resolves when the iteration has been skipped.

### 🔥reader.open()
Opens the current nested value if it represents a container and starts a new scope.
- If the current value is a primitive or there are no remaining values in the current scope, returns `0`.
- If the scope is `closed`, returns `undefined`.
- If the scope is `failed`, throws an error.
- If the current value has an unread key (for object/map), the key will be skipped automatically.
#### Returns
- `Promise<number | undefined>` —
  - Returns the numeric code of the container type (`OBJECT`, `ARRAY`, `SET`, `MAP`, `STREAM`, `TEXT`) if successfully opened.
  - Returns `0` if the current value cannot be opened **or if there are no remaining elements in the current scope**.
  - Returns `undefined` if the reader is closed.

### 🔥reader.dict()
Opens a new scope if the current value is an **object or map**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` —  
  - Numeric code of the container type (`OBJECT` or `MAP`) if successfully opened.  
  - `0` if the type does not match or no elements remain in the current scope.  
  - `undefined` if the reader is closed.
### 🔥reader.seq()
Opens a new scope if the current value is a **sequence container** (`ARRAY` or `SET`).  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` —  
  - Numeric code of the container type (`ARRAY` or `SET`) if successfully opened.  
  - `0` if the type does not match or no elements remain.  
  - `undefined` if the reader is closed.

### 🔥reader.flow()
Opens a new scope if the current value is a **flow container** (`STREAM` or `TEXT`).  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` —  
  - Numeric code of the container type (`STREAM` or `TEXT`) if successfully opened.  
  - `0` if the type does not match or no elements remain.  
  - `undefined` if the reader is closed.

### 🔥reader.object()
Opens a new scope if the current value is an **object**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` — `OBJECT` code if opened, `0` if type mismatch or empty, `undefined` if closed.

### 🔥reader.map()
Opens a new scope if the current value is a **map**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` — `MAP` code if opened, `0` if type mismatch or empty, `undefined` if closed.

### 🔥reader.array()
Opens a new scope if the current value is an **array**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` — `ARRAY` code if opened, `0` if type mismatch or empty, `undefined` if closed.

### 🔥reader.set()
Opens a new scope if the current value is a **set**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` — `SET` code if opened, `0` if type mismatch or empty, `undefined` if closed.

### 🔥reader.stream()
Opens a new scope if the current value is a **stream**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` — `STREAM` code if opened, `0` if type mismatch or empty, `undefined` if closed.

### 🔥reader.text()
Opens a new scope if the current value is a **text container**.  
Behaves like [`reader.open()`](#readeropen) otherwise.
#### Returns
- `Promise<number | undefined>` — `TEXT` code if opened, `0` if type mismatch or empty, `undefined` if closed.

### 🔥reader.end()
Closes the current scope and moves to the parent scope.
- For the **root scope**, immediately finalizes reading and releases resources, even if there are remaining values.
- For other scopes, skips all remaining unread values in the current scope and returns to reading the next value in the parent scope.
#### Returns
- `Promise<boolean | undefined>` —
  - `true` if the scope was closed and reading can continue.
  - `undefined` if the reader was already at the end (closed).

### 🔥reader.rest()
Reads all remaining values in the current scope, closes it, and moves to the parent scope.
Returns the collected values in the appropriate JS type depending on the scope.
- **root scope** — returns remaining values as an array, then closes reading and releases resources.
- **array scope** — returns remaining values as an array.
- **set scope** — returns remaining values as a `Set`.
- **map scope** — returns remaining values as a `Map`.
- **stream scope** — returns remaining values as a `Buffer`.
- **text scope** — returns remaining values as a `String`.
#### Returns
- `Promise<*>` — collected values depending on scope.
- `undefined` if the reader is `closed`.
- Throws an error if the reader is `failed`.

### 🔥reader.save(sink, options)
Writes all remaining chunks from the current **stream/text** scope to the specified `sink`, then finalizes the scope and returns to the parent.
#### Parameters
- **sink** — destination for the output data. Can be:
  - a function `write(chunk)`
  - a [writable stream](https://nodejs.org/docs/latest/api/stream.html#writable-streams)
  - a Web [WritableStream](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)
  - an object (wrapped into a Web WritableStream)
  - a string with a file path
- **options** *(optional)*
  - `autoclose` — if `true`, the target stream is automatically closed after completion; when a file path is used, the file is always closed.
  - `flags` — file open flags (only used when `sink` is a file path).
#### Returns
- `Promise<undefined>` — A resolved Promise after the data is fully written.

### 🔥reader.close()
Immediately closes the reading session.
**Opened state:**
- Clears the reader stack.
- Clears parser data (stack).
- Clears the internal buffer of accumulated data.
- Clears the data queue.
- Closes the source to prevent new data.
- Resolves all pending read promises with `undefined`.
- Sets `reader.closed = true`.
**Closed/Failed state:**
- No effect.

### 🔥reader.abort(error)
Immediately aborts the reading session with an error.
**Opened state:**
- Clears the reader stack.
- Clears parser data (stack).
- Clears the internal buffer of accumulated data.
- Clears the data queue.
- Closes the source to prevent new data.
- Resolves all pending read promises with the provided `error`.
- Sets `reader.failed = true` and `reader.error = error` (or `ABORT_ERROR()` if none provided).
**Closed/Failed state:**
- No effect.
#### Parameters
- `error` *(optional)* — any value representing the error. If not provided, a default `ABORT_ERROR()` is used.

### 🔥reader.reset()
Ends the current reading session and starts a new one from any state.
**Effect:**
- Clears the reader stack.
- Clears parser data (stack).
- Clears the internal buffer of accumulated data.
- Clears the data queue.
- Closes the source to prevent new data.
- Resolves all pending read promises with `undefined`.
- Sets `reader.state = 'opened'`.

### 🔥reader.push(...chunks)
Adds one or multiple data sources to the reader. Can be called multiple times.
Used when the reading session is open and data is not yet finished.
#### Parameters
- `...chunks` — one or more data sources. Each chunk can be:
  - [Buffer](https://nodejs.org/docs/latest/api/buffer.html) or any value convertible to a buffer (Buffer, [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), BufferView)
  - [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
  - [Node.js stream](https://nodejs.org/docs/latest/api/stream.html#class-streamreadable)
  - Web stream ([ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream))
  - String (interpreted as a file name)
  - Synchronous or asynchronous iterator (each item is any supported chunk, including nested iterators)
**Notes:**
- Data is queued and processed as needed.
- The reader remains unfinished (`reader.finished = false`).

### 🔥reader.finish(...chunks)
Adds one or multiple data sources and marks the reader as finished.
After calling `finish()`, further calls to `push()` or `finish()` have no effect until the next [`reader.reset()`](#readerreset).
#### Parameters
- `...chunks` — same as for [`reader.push()`](#readerpushchunks).
**Notes:**
- Data sent before or during `finish()` is still processed.
- Sets `reader.finished = true`.
- Useful to indicate that no more data will follow in this session.

### 🔥reader.register(typeid, handler)
Alias for [`reader.exts.set()`](#typemapsettypeid-handler).
Registers a handler function for the given extended type ID.
#### Parameters
- `typeid` — numeric identifier of the extended type (1..268_435_455).
- `handler` — function to process the payload of the extended type.

### 🔥reader.registerAll(...args)
Alias for [`reader.exts.add()`](#typemapaddargs).
Adds one or more `TypeMap` instances to this `TypeMap`.
Throws an error if any type ID conflicts with existing entries.
#### Parameters
- `...args` — one or more `TypeMap` instances to merge.

### 🔥reader.unregister(typeid)
Alias for [`reader.exts.remove()`](#typemapremovetypeid).
Removes a previously registered handler for the given extended type ID.
#### Parameters
- `typeid` — numeric identifier of the extended type.

### 🔥reader.stat()
Returns a read-only snapshot of the current reader state.
This method is **undocumented** and may change in future implementations.
Current example output:
```json
{
  "state": "opened",
  "scope": "root",
  "depth": 4,
  "stack": 1,
  "path": "[0].lines[12].row",
  "client": "idle",
  "queue": 0,
  "types": 10,
  "parser": "idle",
  "collector": "idle",
  "received": 43002,
  "unread": 28,
  "reserve": 28,
  "source": "closed",
  "finished": true,
  "paused": false
}
```

## 🎁JSYN.Value
`JSYN.Value` is a wrapper class for representing extended JSYN types.
Instances of this class indicate that the value should be treated as a user-defined or extended type during encoding and decoding.

### 🔥Value.is(value)
Alias for [`JSYN.isExt(value)`](#isextvalue).
Returns `true` if the given value is an instance of `JSYN.Value`, otherwise `false`.

### 🫧value.type
The numeric **type ID** of the extended JSYN value.

### 🫧value.payload
The actual **data payload** associated with the extended JSYN value.
Can be any valid JSYN value (`<VALUE>`), including `null`, binary, string, array, object, etc.

## 🔗JSYN.HookList
`JSYN.HookList` manages a list of hooks that can transform values before they are written by a [`JSYN.Writer`](#jsynwriter).
Hooks are applied only to values (not keys) in `object`, `map`, `array`, `set`, and `root` scopes, and are ignored for streams, text, and extended type payloads.

For performance, especially when using multiple writers, it is recommended to create a single `HookList` instance in advance and assign it to writers as needed:
```js
import {HookList} from 'jsyn'

// Add hooks to the list
function hook1(key, value) {
  // Transform or filter the value
  if (typeof value === "number" && value < 0) return 0; // convert negative numbers to 0
  return value;
}

function hook2(key, value) {
  // Example: prefix string values with key
  if (typeof value === "string") return `${key}:${value}`;
  return value;
}

// Example of creating a HookList with multiple hooks
const myHookList = new HookList(hook1, hook2);

// Assign the same HookList to multiple writers
writer1.hooks = myHookList;
writer2.hooks = myHookList;
```

### new HookList(...hooks)
Creates a new HookList containing one or more hook functions.
#### Parameters
- `...hooks` — functions with signature `(key, value)` that process values before serialization.
#### Returns
- `JSYN.HookList` — an instance that can be assigned to `writer.hooks` and reused across writers.

### 🫧hooks.size
The number of hook functions currently stored in the `HookList`.
Read-only property.

### 🔥hooks.add(hook)
Adds a new hook function to the `HookList`.
#### Parameters
- `hook` — a function of the form `(key, value) => any`.
#### Returns
- `this` — the `HookList` instance (allows chaining).

### 🔥hooks.remove(hook)
Removes a previously added hook function from the `HookList`.
#### Parameters
- `hook` — the hook function to remove.
#### Returns
- `boolean` — `true` if the hook was found and removed, `false` otherwise.

### 🔥hooks.clear()
Removes all hook functions from the `HookList`.

### 🔥hooks.has(hook)
Checks if a specific hook function is currently registered in the `HookList`.
#### Parameters
- `hook` — the hook function to check.
#### Returns
- `boolean` — `true` if the hook exists in the list, otherwise `false`.

### 🔥hooks.resolve(key, value)
Applies all registered hooks sequentially to a given value.
Each hook receives the current key and value; the result of one hook is passed to the next.
#### Parameters
- `key` — the current key associated with the value (can be `undefined` in some scopes).
- `value` — the value to process through the hooks.
#### Returns
- The final value after all hooks have been applied.

## 🧲JSYN.TypeMap
A utility class for managing handlers of extended JSYN types.
It maps numeric type IDs to functions that process the corresponding extended type payloads during reading.

- Can be reused across multiple readers.
- Supports adding, setting, and updating type handlers in bulk or individually.
- Provides an efficient way to register and manage extended type behavior in JSYN.

```js
import {TypeMap} from 'jsyn'

// Example of creating and reusing a TypeMap
const myTypes = new TypeMap();

// Add multiple type handlers at once
myTypes.add(types1);
myTypes.add(types2);

// Assign a handler for a specific extended type ID
myTypes.set(1234, value => new MyData(value));

// The same TypeMap can be reused for multiple readers
reader1.exts = myTypes;
reader2.exts = myTypes;
```

### 🫧typeMap.size
Returns the number of registered type handlers in the `TypeMap`.
Read-only property.

### 🔥typeMap.has(typeid)
Checks if a handler for the specified extended type ID is registered.
#### Parameters
- `typeid` — numeric identifier of the extended JSYN type.
#### Returns
- `boolean` — `true` if a handler exists for the given type ID, otherwise `false`.

### 🔥typeMap.set(typeid, handler)
Registers a handler for a specific extended JSYN type ID.
#### Parameters
- `typeid` — numeric identifier of the extended type (1..268_435_455).
- `handler` — a function that receives the payload and returns a value to be used when decoding.

### 🔥typeMap.remove(typeid)
Removes the handler associated with the specified extended JSYN type ID.
#### Parameters
- `typeid` — numeric identifier of the extended type to remove.
#### Returns
- `boolean` — `true` if the hook was found and removed, `false` otherwise.

### 🔥typeMap.clear()
Removes all registered type handlers, clearing the TypeMap.

### 🔥typeMap.add(...args)
Adds one or more `TypeMap` instances to this `TypeMap`.
Throws an error if any type ID conflicts with existing entries.
#### Parameters
- `...args` — one or more `TypeMap` instances to merge.

### 🔥typeMap.replace(...args)
Adds one or more `TypeMap` instances to this `TypeMap`.
If type IDs conflict, existing entries are overwritten by the last provided map.
#### Parameters
- `...args` — one or more `TypeMap` instances to merge.

### 🔥typeMap.resolve(typeid, payload)
Resolves the given `typeid` using the registered handler.
If a handler exists, it is called with the `payload` and its result is returned.
If no handler is registered, returns a new [`JSYN.Value(typeid, payload)`](#jsynvalue).
#### Parameters
- `typeid` — the numeric identifier of the extended type.
- `payload` — the data associated with this type.
#### Returns
- The processed value returned by the handler, or a `JSYN.Value` if no handler exists.

## 🫧JSYN.types
`JSYN.types` is an object with static methods for working with **raw JSYN type identifiers**.
It provides utilities for classification, naming, and checking types at the protocol level, independently of actual values.

### 🔥types.TYPE_OF(value)
Alias for [`JSYN.typeOf(value)`](#typeofvalue) — returns the JSYN type identifier of a value.

### 🔥types.IS_SUPPORTED(value)
Alias for [`JSYN.isSupported(value)`](#issupportedvalue) — checks if a value is supported by the JSYN format.

### 🔥types.RAW_TYPE(typeid)
Returns the raw, internal name of a JSYN type exactly as it is encoded in the format.
#### Parameters
- `type` — a JSYN type identifier (numeric code).
#### Returns
- `string` — the raw type name, representing the type precisely as it exists in the JSYN protocol, without mapping to JavaScript or user-facing types.
- Returns `'unknown'` if the provided value does not correspond to a valid JSYN type.

### 🔥types.TYPE(typeid)
Returns a user-friendly, meaningful name for a JSYN type or data class, representing the type that will be exposed to the user.
#### Parameters
- `type` — a JSYN type identifier (numeric code).
#### Returns
- `string` — one of the following: `'end', 'null', 'boolean', 'number', 'bigint', 'string', 'date', 'regexp', 'binary', 'extended', 'object', 'map', 'array', 'set', 'stream', 'text'`
- `undefined` (if the provided value does not correspond to a valid JSYN type)

### 🔥types.KIND(typeid)
Returns the general name of a type, compatible with the JavaScript `typeof` operator (except that `NULL` (0x01) is interpreted as `'null'`).
#### Parameters
- `type` — a JSYN type identifier (numeric code).
#### Returns
- `string` — one of: `'null', 'boolean', 'number', 'bigint', 'string', 'object'`
- `undefined` (if the provided value does not correspond to a valid JSYN type)

### 🔥types.TYPE_ID(value)
Converts a JSYN raw type name (string) to its corresponding internal numeric code.  
This is the inverse of [`reader.rawId()`](#readerrawid) for containers and primitive types.  
#### Parameters
- `value` — `string` — the JSYN raw type name.
  - Case-insensitive: `'Object'`, `'OBJECT'`, `'object'` are all valid.
  - Only raw type names are accepted; using a higher-level JSYN type (`boolean`, `number`, etc.) returns `-1`.
  - See [Supported Types](#supported-types) for full list.
#### Returns
- `number` — the internal numeric code of the given type.  
  - Returns `0` for `'end'` (no more values).  
  - Returns `-1` if the type name is unknown or unsupported.

### 🔒Type Constants
System constants representing all JSYN type identifiers.
#### Constants
`END, NULL, FALSE, TRUE, INT8, INT16, INT32, INT64, FLOAT32, FLOAT64, BIGINT_NEG, BIGINT_POS, STRING, DATE, REGEXP, BINARY, EXTENDED, OBJECT, MAP, ARRAY, SET, STREAM, TEXT`

### 🔥types.IS_* (Type classifiers)
Type classifier functions. Each function receives a JSYN type identifier and returns `true` if the type matches the classification.

| Function | Matches types |
|----------|---------------|
| IS_END(type) | END |
| IS_TYPE(type) | any valid type |
| IS_PRIMITIVE(type) | NULL, FALSE, TRUE, INT8..INT64, FLOAT32/64, BIGINT_POS/NEG, STRING |
| IS_PRIMITIVE_OBJECT(type) | DATE, REGEXP, BINARY, EXTENDED |
| IS_NUMBER(type) | INT8..INT64, FLOAT32/64 |
| IS_INT(type) | INT8, INT16, INT32, INT64 |
| IS_FLOAT(type) | FLOAT32, FLOAT64 |
| IS_BIGINT(type) | BIGINT_POS, BIGINT_NEG |
| IS_EXT(type) | EXTENDED |
| IS_CONTAINER(type) | OBJECT, MAP, ARRAY, SET, STREAM, TEXT |
| IS_COLLECTION(type) | ARRAY, SET |
| IS_DICTIONARY(type) | OBJECT, MAP |
| IS_FLOW(type) | STREAM, TEXT |
| IS_SEQUENCE(type) | ARRAY, SET, STREAM, TEXT |
| IS_OBJECT(type) | OBJECT |
| IS_MAP(type) | MAP |
| IS_ARRAY(type) | ARRAY |
| IS_SET(type) | SET |
| IS_STREAM(type) | STREAM |
| IS_TEXT(type) | TEXT |

### 🔒JSYN.conf
JSYN constants and configuration values.

| Constant               | Value       | Description                                  |
|------------------------|------------|----------------------------------------------|
| `MAX_BLOCK_SIZE`       | 268435455  | Maximum size of a single JSYN block.         |
| `MAX_EXTENDED_TYPE_ID` | 268435455  | Maximum allowed type ID for extended types.  |
| `READER_CACHE_SIZE`    | 65536      | Cache size used by Reader for efficiency.    |
| `WRITER_BUFFER_SIZE`   | 16384      | Internal buffer size used by Writer.        |
