1- import { Model } from " ../model" ;
2- import { PatchLog } from " ./PatchLog" ;
3- import { FileModelEncoding } from " ./constants" ;
1+ import { Model } from ' ../model' ;
2+ import { PatchLog } from ' ./PatchLog' ;
3+ import { FileModelEncoding } from ' ./constants' ;
44import { Encoder as SidecarEncoder } from '../codec/sidecar/binary/Encoder' ;
5+ import { Decoder as SidecarDecoder } from '../codec/sidecar/binary/Decoder' ;
56import { Encoder as StructuralEncoderCompact } from '../codec/structural/compact/Encoder' ;
67import { Encoder as StructuralEncoderVerbose } from '../codec/structural/verbose/Encoder' ;
78import { encode as encodeCompact } from '../../json-crdt-patch/codec/compact/encode' ;
89import { encode as encodeVerbose } from '../../json-crdt-patch/codec/verbose/encode' ;
9- import type * as types from "./types" ;
10+ import { Writer } from '../../util/buffers/Writer' ;
11+ import { CborEncoder } from '../../json-pack/cbor/CborEncoder' ;
12+ import { JsonEncoder } from '../../json-pack/json/JsonEncoder' ;
13+ import { printTree } from '../../util/print/printTree' ;
14+ import { decodeModel , decodeNdjsonComponents , decodePatch , decodeSeqCborComponents } from './util' ;
15+ import { Patch } from '../../json-crdt-patch' ;
16+ import type * as types from './types' ;
17+ import type { Printable } from '../../util/print/types' ;
1018
11- export class File {
12- public static fromModel ( model : Model ) : File {
19+ export class File implements Printable {
20+ public static unserialize ( components : types . FileReadSequence ) : File {
21+ const [ view , metadata , model , history , ...frontier ] = components ;
22+ const modelFormat = metadata [ 1 ] ;
23+ let decodedModel : Model < any > | null = null ;
24+ if ( model ) {
25+ const isSidecar = modelFormat === FileModelEncoding . SidecarBinary ;
26+ if ( isSidecar ) {
27+ const decoder = new SidecarDecoder ( ) ;
28+ if ( ! ( model instanceof Uint8Array ) ) throw new Error ( 'NOT_BLOB' ) ;
29+ decodedModel = decoder . decode ( view , model ) ;
30+ } else {
31+ decodedModel = decodeModel ( model ) ;
32+ }
33+ }
34+ let log : PatchLog | null = null ;
35+ if ( history ) {
36+ const [ start , patches ] = history ;
37+ if ( start ) {
38+ const startModel = decodeModel ( start ) ;
39+ log = new PatchLog ( startModel ) ;
40+ for ( const patch of patches ) log . push ( decodePatch ( patch ) ) ;
41+ }
42+ }
43+ if ( ! log ) throw new Error ( 'NO_HISTORY' ) ;
44+ if ( ! decodedModel ) decodedModel = log . replayToEnd ( ) ;
45+ if ( frontier . length ) {
46+ for ( const patch of frontier ) {
47+ const patchDecoded = decodePatch ( patch ) ;
48+ decodedModel . applyPatch ( patchDecoded ) ;
49+ log . push ( patchDecoded ) ;
50+ }
51+ }
52+ const file = new File ( decodedModel , log ) ;
53+ return file ;
54+ }
55+
56+ public static fromNdjson ( blob : Uint8Array ) : File {
57+ const components = decodeNdjsonComponents ( blob ) ;
58+ return File . unserialize ( components as types . FileReadSequence ) ;
59+ }
60+
61+ public static fromSeqCbor ( blob : Uint8Array ) : File {
62+ const components = decodeSeqCborComponents ( blob ) ;
63+ return File . unserialize ( components as types . FileReadSequence ) ;
64+ }
65+
66+ public static fromModel ( model : Model < any > ) : File {
1367 return new File ( model , PatchLog . fromModel ( model ) ) ;
1468 }
1569
16- constructor (
17- public readonly model : Model ,
18- public readonly history : PatchLog ,
19- ) { }
70+ constructor ( public readonly model : Model , public readonly log : PatchLog ) { }
71+
72+ public apply ( patch : Patch ) : void {
73+ const id = patch . getId ( ) ;
74+ if ( ! id ) return ;
75+ this . model . applyPatch ( patch ) ;
76+ this . log . push ( patch ) ;
77+ }
78+
79+ public sync ( ) : ( ) => void {
80+ const { model, log} = this ;
81+ const api = model . api ;
82+ const autoflushUnsubscribe = api . autoFlush ( ) ;
83+ const onPatchUnsubscribe = api . onPatch . listen ( ( patch ) => {
84+ log . push ( patch ) ;
85+ } ) ;
86+ const onFlushUnsubscribe = api . onFlush . listen ( ( patch ) => {
87+ log . push ( patch ) ;
88+ } ) ;
89+ return ( ) => {
90+ autoflushUnsubscribe ( ) ;
91+ onPatchUnsubscribe ( ) ;
92+ onFlushUnsubscribe ( ) ;
93+ } ;
94+ }
2095
2196 public serialize ( params : types . FileSerializeParams = { } ) : types . FileWriteSequence {
22- const view = this . model . view ( ) ;
23- const metadata : types . FileMetadata = [
24- { } ,
25- FileModelEncoding . SidecarBinary ,
26- ] ;
97+ if ( params . noView && params . model === 'sidecar' ) throw new Error ( 'SIDECAR_MODEL_WITHOUT_VIEW' ) ;
98+ const metadata : types . FileMetadata = [ { } , FileModelEncoding . Auto ] ;
2799 let model : Uint8Array | unknown | null = null ;
28100 const modelFormat = params . model ?? 'sidecar' ;
29101 switch ( modelFormat ) {
@@ -35,58 +107,80 @@ export class File {
35107 break ;
36108 }
37109 case 'binary' : {
38- metadata [ 1 ] = FileModelEncoding . StructuralBinary ;
39110 model = this . model . toBinary ( ) ;
40111 break ;
41112 }
42113 case 'compact' : {
43- metadata [ 1 ] = FileModelEncoding . StructuralCompact ;
44114 model = new StructuralEncoderCompact ( ) . encode ( this . model ) ;
45115 break ;
46116 }
47117 case 'verbose' : {
48- metadata [ 1 ] = FileModelEncoding . StructuralVerbose ;
49118 model = new StructuralEncoderVerbose ( ) . encode ( this . model ) ;
50119 break ;
51120 }
121+ case 'none' : {
122+ model = null ;
123+ break ;
124+ }
52125 default :
53126 throw new Error ( `Invalid model format: ${ modelFormat } ` ) ;
54127 }
55- const history : types . FileWriteSequenceHistory = [
56- null ,
57- [ ] ,
58- ] ;
128+ const history : types . FileWriteSequenceHistory = [ null , [ ] ] ;
59129 const patchFormat = params . history ?? 'binary' ;
60130 switch ( patchFormat ) {
61131 case 'binary' : {
62- history [ 0 ] = this . history . start . toBinary ( ) ;
63- this . history . patches . forEach ( ( { v} ) => {
132+ history [ 0 ] = this . log . start . toBinary ( ) ;
133+ this . log . patches . forEach ( ( { v} ) => {
64134 history [ 1 ] . push ( v . toBinary ( ) ) ;
65135 } ) ;
66136 break ;
67137 }
68138 case 'compact' : {
69- history [ 0 ] = new StructuralEncoderCompact ( ) . encode ( this . history . start ) ;
70- this . history . patches . forEach ( ( { v} ) => {
139+ history [ 0 ] = new StructuralEncoderCompact ( ) . encode ( this . log . start ) ;
140+ this . log . patches . forEach ( ( { v} ) => {
71141 history [ 1 ] . push ( encodeCompact ( v ) ) ;
72142 } ) ;
73143 break ;
74144 }
75145 case 'verbose' : {
76- history [ 0 ] = new StructuralEncoderVerbose ( ) . encode ( this . history . start ) ;
77- this . history . patches . forEach ( ( { v} ) => {
146+ history [ 0 ] = new StructuralEncoderVerbose ( ) . encode ( this . log . start ) ;
147+ this . log . patches . forEach ( ( { v} ) => {
78148 history [ 1 ] . push ( encodeVerbose ( v ) ) ;
79149 } ) ;
80150 break ;
81151 }
152+ case 'none' : {
153+ break ;
154+ }
82155 default :
83156 throw new Error ( `Invalid history format: ${ patchFormat } ` ) ;
84157 }
85- return [
86- view ,
87- metadata ,
88- model ,
89- history ,
90- ] ;
158+ return [ params . noView ? null : this . model . view ( ) , metadata , model , history ] ;
159+ }
160+
161+ public toBinary ( params : types . FileEncodingParams ) : Uint8Array {
162+ const sequence = this . serialize ( params ) ;
163+ const writer = new Writer ( 16 * 1024 ) ;
164+ switch ( params . format ) {
165+ case 'ndjson' : {
166+ const json = new JsonEncoder ( writer ) ;
167+ for ( const component of sequence ) {
168+ json . writeAny ( component ) ;
169+ json . writer . u8 ( '\n' . charCodeAt ( 0 ) ) ;
170+ }
171+ return json . writer . flush ( ) ;
172+ }
173+ case 'seq.cbor' : {
174+ const cbor = new CborEncoder ( writer ) ;
175+ for ( const component of sequence ) cbor . writeAny ( component ) ;
176+ return cbor . writer . flush ( ) ;
177+ }
178+ }
179+ }
180+
181+ // ---------------------------------------------------------------- Printable
182+
183+ public toString ( tab ?: string ) {
184+ return `file` + printTree ( tab , [ ( tab ) => this . model . toString ( tab ) , ( ) => '' , ( tab ) => this . log . toString ( tab ) ] ) ;
91185 }
92186}
0 commit comments