package lix:plugin@2.0.0;
interface host {
type bytes = list<u8>;
variant host-error {
invalid-range,
limit-exceeded(string),
rejected(string),
}
record record-chunk {
total-len: u64,
bytes: bytes,
}
/// One immutable host-owned file and projection-state version.
resource snapshot {
file-len: func() -> u64;
read-file: func(offset: u64, length: u32) -> result<bytes, host-error>;
read-state: func(
key: bytes,
offset: u64,
max-bytes: u32,
) -> result<option<record-chunk>, host-error>;
}
/// One bounded, self-describing page of row mutations.
record row-page {
payload: bytes,
/// Large typed values are carried out-of-band from the page descriptor.
/// Payload records reference these page-local attachments by ordinal.
attachments: list<bytes>,
}
resource row-source {
next-page: func(max-bytes: u32) -> result<option<row-page>, host-error>;
}
record file-edit {
offset: u64,
delete-len: u64,
insert: bytes,
}
/// Host-owned atomic output shared by the four projection operations.
/// The SDK exposes only the methods appropriate for each operation.
resource transition {
max-batch-bytes: func() -> u32;
put-state: func(key: bytes, value: bytes) -> result<_, host-error>;
delete-state: func(key: bytes) -> result<_, host-error>;
emit-rows: func(page: row-page) -> result<_, host-error>;
replace-all-rows: func() -> result<_, host-error>;
emit-file-edit: func(edit: file-edit) -> result<_, host-error>;
begin-file-replacement: func(total-length: u64) -> result<_, host-error>;
write-file-replacement: func(chunk: bytes) -> result<_, host-error>;
finish-file-replacement: func() -> result<_, host-error>;
}
enum merge-side {
base,
a,
b,
}
record column-merge-meta {
ordinal: u32,
schema-key: string,
/// Each primary-key component is one Schema v1 value encoded by the
/// canonical typed-value codec, never a lossy textual identity.
primary-key: list<bytes>,
schema-fingerprint: list<u8>,
file-id: option<string>,
column: string,
base-len: option<u64>,
a-len: option<u64>,
b-len: option<u64>,
base-row-len: u64,
a-row-len: u64,
b-row-len: u64,
}
/// Lazy access to overlapping column changes and their complete row context.
resource column-merge-source {
len: func() -> u32;
get: func(index: u32) -> result<column-merge-meta, host-error>;
read-value: func(
index: u32,
side: merge-side,
offset: u64,
length: u32,
) -> result<option<bytes>, host-error>;
/// The range contains canonical typed-value bytes.
read-row: func(
index: u32,
side: merge-side,
offset: u64,
length: u32,
) -> result<bytes, host-error>;
/// The range contains one canonical typed-row encoding.
}
/// Ordered column merge results. Omitted results are invalid; `use-lww`
/// explicitly accepts the host's higher-ranked `b` value.
resource column-merge-sink {
max-batch-bytes: func() -> u32;
use-lww: func(ordinal: u32) -> result<_, host-error>;
begin-replace: func(
ordinal: u32,
total-length: option<u64>,
) -> result<_, host-error>;
write-replacement: func(chunk: bytes) -> result<_, host-error>;
finish-replace: func() -> result<_, host-error>;
}
}
interface types {
use host.{bytes, file-edit, row-source, snapshot};
variant plugin-error {
invalid-input(string),
limit-exceeded(string),
internal(string),
}
record create-context {
high: u64,
low: u32,
}
record parse-request {
file-id: string,
path: string,
file: own<snapshot>,
creates: create-context,
}
record parse-changes-request {
file-id: string,
before-path: string,
after-path: string,
before: own<snapshot>,
file-edits: list<file-edit>,
/// Cold reconciliation receives current durable typed rows to preserve
/// semantic identities. Warm incremental execution omits them.
rows: option<own<row-source>>,
creates: create-context,
}
record serialize-request {
file-id: string,
path: string,
rows: own<row-source>,
before: option<own<snapshot>>,
}
record serialize-changes-request {
file-id: string,
path: string,
before: own<snapshot>,
row-changes: own<row-source>,
}
}
interface column-merger {
use host.{column-merge-sink, column-merge-source};
use types.{plugin-error};
/// Called only for columns changed differently on both concurrent sides.
merge: func(
input: own<column-merge-source>,
output: borrow<column-merge-sink>,
) -> result<_, plugin-error>;
}
interface file-projection {
use host.{transition};
use types.{parse-changes-request, parse-request, plugin-error,
serialize-changes-request, serialize-request};
parse: func(
input: parse-request,
output: borrow<transition>,
) -> result<_, plugin-error>;
parse-changes: func(
input: parse-changes-request,
output: borrow<transition>,
) -> result<_, plugin-error>;
serialize: func(
input: serialize-request,
output: borrow<transition>,
) -> result<_, plugin-error>;
serialize-changes: func(
input: serialize-changes-request,
output: borrow<transition>,
) -> result<_, plugin-error>;
}
world column-merger-plugin {
import host;
export column-merger;
}
world file-projection-plugin {
import host;
export file-projection;
}
world plugin {
import host;
export column-merger;
export file-projection;
}