Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/json-crdt/log/Log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ import type {JsonNode} from '../nodes/types';
*
* @todo Make this implement UILifecycle (start, stop) interface.
*/
export class Log<N extends JsonNode = JsonNode<any>> implements Printable {
export class Log<N extends JsonNode = JsonNode<any>, Metadata extends Record<string, unknown> = Record<string, unknown>>
implements Printable
{
/**
* Creates a `PatchLog` instance from a newly JSON CRDT model. Checks if
* the model API buffer has any initial operations applied, if yes, it
Expand All @@ -60,6 +62,13 @@ export class Log<N extends JsonNode = JsonNode<any>> implements Printable {
return new Log<N>(beginning, model);
}

/**
* Custom metadata associated with the log, it will be stored in the log's
* header when serialized with {@link LogEncoder} and can be used to store
* additional information about the log.
*/
public metadata: Metadata;

/**
* The collection of patches which are applied to the `start()` model to reach
* the `end` model. The patches in the log, stored in an AVL tree for
Expand Down Expand Up @@ -94,6 +103,8 @@ export class Log<N extends JsonNode = JsonNode<any>> implements Printable {
* @readonly
*/
public readonly end: Model<N> = start(),

metadata?: Metadata,
) {
const onPatch = (patch: Patch) => {
const id = patch.getId();
Expand All @@ -103,6 +114,7 @@ export class Log<N extends JsonNode = JsonNode<any>> implements Printable {
const api = end.api;
this.__onPatch = api.onPatch.listen(onPatch);
this.__onFlush = api.onFlush.listen(onPatch);
this.metadata = metadata ?? ({} as Metadata);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/json-crdt/log/codec/LogDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class LogDecoder {
}

public deserialize(components: types.LogComponentsWithFrontier, params: DeserializeParams = {}): DecodeResult {
const [view, metadata, model, , ...frontier] = components;
const [view, header, model, , ...frontier] = components;
const result: DecodeResult = {};
if (params.view) result.view = view;
if (params.history) result.history = this.deserializeHistory(components);
Expand All @@ -79,7 +79,7 @@ export class LogDecoder {
if (result.history) {
result.frontier = result.history;
} else if (model) {
const modelFormat = metadata[1];
const modelFormat = header[1];
const start = (): Model => {
const isSidecar = modelFormat === FileModelEncoding.SidecarBinary;
if (isSidecar) {
Expand All @@ -102,7 +102,7 @@ export class LogDecoder {
}

public deserializeHistory(components: types.LogComponentsWithFrontier): Log {
const [, , , history, ...frontier] = components;
const [, header, , history, ...frontier] = components;
const [startSerialized] = history;
const start = (): Model => {
if (!history || !startSerialized) {
Expand All @@ -112,7 +112,7 @@ export class LogDecoder {
}
return this.deserializeModel(startSerialized);
};
const log = new Log(start);
const log = new Log(start, void 0, header[0]);
const end = log.end;
if (history) {
const [, patches] = history;
Expand Down
6 changes: 3 additions & 3 deletions src/json-crdt/log/codec/LogEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export class LogEncoder {

public serialize(log: Log, params: SerializeParams = {}): types.LogComponents {
if (params.noView && params.model === 'sidecar') throw new Error('SIDECAR_MODEL_WITHOUT_VIEW');
const metadata: types.LogMetadata = [{}, FileModelEncoding.Auto];
const header: types.LogHeader = [log.metadata ?? {}, FileModelEncoding.Auto];
let model: Uint8Array | unknown | null = null;
const modelFormat = params.model ?? 'sidecar';
switch (modelFormat) {
case 'sidecar': {
metadata[1] = FileModelEncoding.SidecarBinary;
header[1] = FileModelEncoding.SidecarBinary;
const encoder = this.options.sidecarEncoder;
if (!encoder) throw new Error('NO_SIDECAR_ENCODER');
const [, uint8] = encoder.encode(log.end);
Expand Down Expand Up @@ -102,7 +102,7 @@ export class LogEncoder {
default:
throw new Error(`Invalid history format: ${patchFormat}`);
}
return [params.noView ? null : log.end.view(), metadata, model, history];
return [params.noView ? null : log.end.view(), header, model, history];
}

public encode(log: Log, params: EncodingParams): Uint8Array {
Expand Down
14 changes: 14 additions & 0 deletions src/json-crdt/log/codec/__tests__/LogDecoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ describe('can decode from blob', () => {
expect(history!.start().view()).toEqual(undefined);
expect(history!.end.view()).toEqual({foo: 'bar'});
});

test('can store custom metadata keys', () => {
const {log, encoder, decoder} = setup({foo: 'bar'});
const metadata = {
baz: 'qux',
time: 123,
active: true,
};
log.metadata = {...metadata};
const blob = encoder.encode(log, {format: 'seq.cbor'});
const decoded1 = decoder.decode(blob, {format: 'seq.cbor', frontier: true, history: true});
expect(decoded1.frontier?.metadata).toEqual(metadata);
expect(decoded1.history?.metadata).toEqual(metadata);
});
});

const assertEncoding = (log: Log, params: EncodingParams) => {
Expand Down
9 changes: 4 additions & 5 deletions src/json-crdt/log/codec/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type {FileModelEncoding} from './constants';

export type LogMetadata = [
// biome-ignore lint: allow brackets
map: {},
export type LogHeader<metadata extends Record<string, unknown> = Record<string, unknown>> = [
metadata: metadata,
modelFormat: FileModelEncoding,
];

export type LogComponents = [
export type LogComponents<Metadata extends Record<string, unknown> = Record<string, unknown>> = [
view: unknown | null,
metadata: LogMetadata,
header: LogHeader<Metadata>,
model: Uint8Array | unknown | null,
history: LogHistory,
];
Expand Down