@y/y 14.0.0-rc.18 → 14.0.0-rc.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import * as error from 'lib0/error'
4
4
 
5
5
  import { Item, followRedone, ContentType } from '../structs/Item.js'
6
6
  import { writeID, readID, compareIDs, findRootTypeKey, createID } from './ID.js'
7
- import { noAttributionsManager } from './attribution-manager-helpers.js'
7
+ import { baseRenderer } from './renderer-helpers.js'
8
8
 
9
9
  /**
10
10
  * A relative position is based on the Yjs model and is not affected by document changes.
@@ -146,12 +146,12 @@ export const createRelativePosition = (type, item, assoc) => {
146
146
  * @param {YType} type The base type (e.g. YText or YArray).
147
147
  * @param {number} index The absolute position.
148
148
  * @param {number} [assoc]
149
- * @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
149
+ * @param {import('../utils/Renderer.js').AbstractRenderer} renderer
150
150
  * @return {RelativePosition}
151
151
  *
152
152
  * @function
153
153
  */
154
- export const createRelativePositionFromTypeIndex = (type, index, assoc = 0, attributionManager = noAttributionsManager) => {
154
+ export const createRelativePositionFromTypeIndex = (type, index, assoc = 0, renderer = baseRenderer) => {
155
155
  let t = type._start
156
156
  if (assoc < 0) {
157
157
  // associated to the left character or the beginning of a type, increment index if possible.
@@ -161,7 +161,7 @@ export const createRelativePositionFromTypeIndex = (type, index, assoc = 0, attr
161
161
  index--
162
162
  }
163
163
  while (t !== null) {
164
- const len = attributionManager.contentLength(t)
164
+ const len = renderer.contentLength(t)
165
165
  if (len > index) {
166
166
  // case 1: found position somewhere in the linked list
167
167
  return createRelativePosition(type, createID(t.id.client, t.id.clock + index), assoc)
@@ -272,12 +272,12 @@ const getItemWithOffset = (store, id) => {
272
272
  * @param {RelativePosition} rpos
273
273
  * @param {Doc} doc
274
274
  * @param {boolean} followUndoneDeletions - whether to follow undone deletions - see https://github.com/yjs/yjs/issues/638
275
- * @param {import('../utils/AttributionManager.js').AbstractAttributionManager} attributionManager
275
+ * @param {import('../utils/Renderer.js').AbstractRenderer} renderer
276
276
  * @return {AbsolutePosition|null}
277
277
  *
278
278
  * @function
279
279
  */
280
- export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true, attributionManager = noAttributionsManager) => {
280
+ export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true, renderer = baseRenderer) => {
281
281
  const store = doc.store
282
282
  const rightID = rpos.item
283
283
  const typeID = rpos.type
@@ -296,10 +296,10 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndo
296
296
  }
297
297
  type = /** @type {YType<any>} */ (right.parent)
298
298
  if (type._item === null || !type._item.deleted) {
299
- index = attributionManager.contentLength(right) === 0 ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary
299
+ index = renderer.contentLength(right) === 0 ? 0 : (res.diff + (assoc >= 0 ? 0 : 1)) // adjust position based on left association if necessary
300
300
  let n = right.left
301
301
  while (n !== null) {
302
- index += attributionManager.contentLength(n)
302
+ index += renderer.contentLength(n)
303
303
  n = n.left
304
304
  }
305
305
  }
@@ -11,16 +11,16 @@ import { UpdateEncoderV1 } from './UpdateEncoder.js'
11
11
  import { transact } from './Transaction.js'
12
12
  import { UndoManager, StackItem } from './UndoManager.js'
13
13
 
14
- import { $attributionManager, AttributedContent } from './attribution-manager-helpers.js'
14
+ import { $renderer, AttributedContent } from './renderer-helpers.js'
15
15
 
16
- export { noAttributionsManager, NoAttributionsManager, AbstractAttributionManager, $attributionManager } from './attribution-manager-helpers.js'
16
+ export { baseRenderer, BaseRenderer, AbstractRenderer, $renderer } from './renderer-helpers.js'
17
17
 
18
18
  /**
19
- * @implements AbstractAttributionManager
19
+ * @implements AbstractRenderer
20
20
  *
21
21
  * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
22
22
  */
23
- export class TwosetAttributionManager extends ObservableV2 {
23
+ export class TwosetRenderer extends ObservableV2 {
24
24
  /**
25
25
  * @param {IdMap<any>} inserts
26
26
  * @param {IdMap<any>} deletes
@@ -31,7 +31,7 @@ export class TwosetAttributionManager extends ObservableV2 {
31
31
  this.deletes = deletes
32
32
  }
33
33
 
34
- get $type () { return $attributionManager }
34
+ get $type () { return $renderer }
35
35
 
36
36
  /**
37
37
  * @param {Array<AttributedContent<any>>} contents - where to write the result
@@ -93,15 +93,15 @@ const getItemContent = (store, client, clock, len) => {
93
93
 
94
94
  /**
95
95
  * @param {Transaction?} tr - only specify this if you want to fill the content of deleted content
96
- * @param {DiffAttributionManager} am
96
+ * @param {DiffRenderer} renderer
97
97
  * @param {ID} start
98
98
  * @param {ID} end
99
99
  * @param {boolean} collectAll - collect as many items as possible. Accept adding redundant changes.
100
100
  */
101
- const collectSuggestedChanges = (tr, am, start, end, collectAll) => {
101
+ const collectSuggestedChanges = (tr, renderer, start, end, collectAll) => {
102
102
  const inserts = createIdSet()
103
103
  const deletes = createIdSet()
104
- const store = am._nextDoc.store
104
+ const store = renderer._nextDoc.store
105
105
  /**
106
106
  * make sure to collect suggestions until all formats are closed
107
107
  * @type {Set<string>}
@@ -121,7 +121,7 @@ const collectSuggestedChanges = (tr, am, start, end, collectAll) => {
121
121
  break
122
122
  }
123
123
  if (!item.deleted) {
124
- const slice = am.inserts.slice(item.id.client, item.id.clock, item.length)
124
+ const slice = renderer.inserts.slice(item.id.client, item.id.clock, item.length)
125
125
  if (slice.some(s => s.attrs === null)) {
126
126
  for (let i = slice.length - 1; i >= 0; i--) {
127
127
  const s = slice[i]
@@ -137,7 +137,7 @@ const collectSuggestedChanges = (tr, am, start, end, collectAll) => {
137
137
  // eslint-disable-next-line
138
138
  itemLoop: while (item != null) {
139
139
  const itemClient = item.id.client
140
- const slice = (item.deleted ? am.deletes : am.inserts).slice(itemClient, item.id.clock, item.length)
140
+ const slice = (item.deleted ? renderer.deletes : renderer.inserts).slice(itemClient, item.id.clock, item.length)
141
141
  foundEndItem ||= item === endItem
142
142
  if (item.deleted) {
143
143
  // item probably gc'd content. Need to split item and fill with content again
@@ -154,7 +154,7 @@ const collectSuggestedChanges = (tr, am, start, end, collectAll) => {
154
154
  if (tr != null) {
155
155
  const splicedItem = getItemCleanStart(tr, createID(itemClient, s.clock))
156
156
  if (s.attrs != null) {
157
- splicedItem.content = getItemContent(am._prevDocStore, itemClient, s.clock, s.len)
157
+ splicedItem.content = getItemContent(renderer._prevDocStore, itemClient, s.clock, s.len)
158
158
  }
159
159
  }
160
160
  }
@@ -197,15 +197,15 @@ export class Attributions {
197
197
  const extractAttributions = (attrs, slice) => attrs == null ? createIdMapFromIdSet(slice, []) : mergeIdMaps([intersectMaps(attrs, slice), createIdMapFromIdSet(slice, [])])
198
198
 
199
199
  /**
200
- * @implements AbstractAttributionManager
200
+ * @implements AbstractRenderer
201
201
  *
202
202
  * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
203
203
  */
204
- export class DiffAttributionManager extends ObservableV2 {
204
+ export class DiffRenderer extends ObservableV2 {
205
205
  /**
206
206
  * @param {Doc} prevDoc
207
207
  * @param {Doc} nextDoc
208
- * @param {Object} [options] - options for the attribution manager
208
+ * @param {Object} [options] - options for the renderer
209
209
  * @param {Attributions?} [options.attrs] - the attributes to apply to the diff
210
210
  */
211
211
  constructor (prevDoc, nextDoc, { attrs = null } = {}) {
@@ -264,7 +264,7 @@ export class DiffAttributionManager extends ObservableV2 {
264
264
  })
265
265
  this._afterTrListener = nextDoc.on('afterTransaction', (tr) => {
266
266
  // apply deletes on attributed deletes (content that is already deleted, but is rendered by
267
- // the attribution manager)
267
+ // the renderer)
268
268
  if (!this.suggestionMode && tr.local && (this.suggestionOrigins == null || this.suggestionOrigins.some(o => o === tr.origin))) {
269
269
  const attributedDeletes = tr.meta.get('attributedDeletes')
270
270
  if (attributedDeletes != null) {
@@ -290,7 +290,7 @@ export class DiffAttributionManager extends ObservableV2 {
290
290
  prevDoc.on('destroy', this._destroyHandler)
291
291
  }
292
292
 
293
- get $type () { return $attributionManager }
293
+ get $type () { return $renderer }
294
294
 
295
295
  destroy () {
296
296
  super.destroy()
@@ -411,24 +411,24 @@ export class DiffAttributionManager extends ObservableV2 {
411
411
  *
412
412
  * @param {Doc} prevDoc
413
413
  * @param {Doc} nextDoc
414
- * @param {Object} [options] - options for the attribution manager
414
+ * @param {Object} [options] - options for the renderer
415
415
  * @param {ContentMap?} [options.attrs] - the attributes to apply to the diff
416
416
  */
417
- export const createAttributionManagerFromDiff = (prevDoc, nextDoc, options) => new DiffAttributionManager(prevDoc, nextDoc, options)
417
+ export const createDiffRenderer = (prevDoc, nextDoc, options) => new DiffRenderer(prevDoc, nextDoc, options)
418
418
 
419
419
  /**
420
- * Intended for projects that used the v13 snapshot feature. With this AttributionManager you can
420
+ * Intended for projects that used the v13 snapshot feature. With this renderer you can
421
421
  * read content similar to the previous snapshot api. Requires that `ydoc.gc` is turned off.
422
422
  *
423
- * @implements AbstractAttributionManager
423
+ * @implements AbstractRenderer
424
424
  *
425
425
  * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
426
426
  */
427
- export class SnapshotAttributionManager extends ObservableV2 {
427
+ export class SnapshotRenderer extends ObservableV2 {
428
428
  /**
429
429
  * @param {Snapshot} prevSnapshot
430
430
  * @param {Snapshot} nextSnapshot
431
- * @param {Object} [options] - options for the attribution manager
431
+ * @param {Object} [options] - options for the renderer
432
432
  * @param {Array<ContentAttribute>} [options.attrs] - the attributes to apply to the diff
433
433
  */
434
434
  constructor (prevSnapshot, nextSnapshot) {
@@ -445,7 +445,7 @@ export class SnapshotAttributionManager extends ObservableV2 {
445
445
  this.attrs = mergeIdMaps([diffIdMap(inserts, prevSnapshot.ds), deletes])
446
446
  }
447
447
 
448
- get $type () { return $attributionManager }
448
+ get $type () { return $renderer }
449
449
 
450
450
  /**
451
451
  * @param {Array<AttributedContent<any>>} contents - where to write the result
@@ -495,4 +495,4 @@ export class SnapshotAttributionManager extends ObservableV2 {
495
495
  * @param {Snapshot} prevSnapshot
496
496
  * @param {Snapshot} nextSnapshot
497
497
  */
498
- export const createAttributionManagerFromSnapshots = (prevSnapshot, nextSnapshot = prevSnapshot) => new SnapshotAttributionManager(prevSnapshot, nextSnapshot)
498
+ export const createSnapshotRenderer = (prevSnapshot, nextSnapshot = prevSnapshot) => new SnapshotRenderer(prevSnapshot, nextSnapshot)
@@ -119,11 +119,11 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
119
119
  /**
120
120
  * @example
121
121
  * const ydoc = new Y.Doc({ gc: false })
122
- * ydoc.getText().insert(0, 'world!')
122
+ * ydoc.get().insert(0, 'world!')
123
123
  * const snapshot = Y.snapshot(ydoc)
124
- * ydoc.getText().insert(0, 'hello ')
124
+ * ydoc.get().insert(0, 'hello ')
125
125
  * const restored = Y.createDocFromSnapshot(ydoc, snapshot)
126
- * assert(restored.getText().toString() === 'world!')
126
+ * assert(restored.get().toString() === 'world!')
127
127
  *
128
128
  * @param {Doc} originDoc
129
129
  * @param {Snapshot} snapshot
@@ -26,18 +26,18 @@ export const generateNewClientId = random.uint53
26
26
  *
27
27
  * @example
28
28
  * const ydoc = new Y.Doc()
29
- * const map = ydoc.getMap('map')
29
+ * const map = ydoc.get('map')
30
30
  * // Log content when change is triggered
31
31
  * map.observe(() => {
32
32
  * console.log('change triggered')
33
33
  * })
34
34
  * // Each change on the map type triggers a log message:
35
- * map.set('a', 0) // => "change triggered"
36
- * map.set('b', 0) // => "change triggered"
35
+ * map.setAttr('a', 0) // => "change triggered"
36
+ * map.setAttr('b', 0) // => "change triggered"
37
37
  * // When put in a transaction, it will trigger the log after the transaction:
38
38
  * ydoc.transact(() => {
39
- * map.set('a', 1)
40
- * map.set('b', 1)
39
+ * map.setAttr('a', 1)
40
+ * map.setAttr('b', 1)
41
41
  * }) // => "change triggered"
42
42
  *
43
43
  * @public
@@ -2,7 +2,7 @@ import * as map from 'lib0/map'
2
2
  import * as set from 'lib0/set'
3
3
 
4
4
  import { diffIdSet, mergeIdSets } from './ids.js'
5
- import { noAttributionsManager } from './attribution-manager-helpers.js'
5
+ import { baseRenderer } from './renderer-helpers.js'
6
6
  import { createAbsolutePositionFromRelativePosition, createRelativePosition } from './RelativePosition.js'
7
7
 
8
8
  /**
@@ -85,14 +85,14 @@ export class YEvent {
85
85
 
86
86
  /**
87
87
  * @template {boolean} [Deep=false]
88
- * @param {AbstractAttributionManager} am
89
88
  * @param {object} [opts]
89
+ * @param {AbstractRenderer} [opts.renderer] - renders the content (with attributions); defaults to `baseRenderer`
90
90
  * @param {Deep} [opts.deep]
91
91
  * @return {Deep extends true ? Delta<DConf> : Delta<import('../ytype.js').DeltaConfDeltaToYType<DConf>>} The Delta representation of this type.
92
92
  *
93
93
  * @public
94
94
  */
95
- getDelta (am = noAttributionsManager, { deep } = {}) {
95
+ getDelta ({ renderer = baseRenderer, deep } = {}) {
96
96
  const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
97
97
  /**
98
98
  * @todo this should be done only one in the transaction step
@@ -120,7 +120,7 @@ export class YEvent {
120
120
  }
121
121
  modified = dchanged
122
122
  }
123
- return /** @type {any} */ (this.target.toDelta(am, { itemsToRender, retainDeletes: true, deletedItems: this.transaction.deleteSet, deep: !!deep, modified }))
123
+ return /** @type {any} */ (this.target.toDelta({ renderer, itemsToRender, retainDeletes: true, deletedItems: this.transaction.deleteSet, deep: !!deep, modified }))
124
124
  }
125
125
 
126
126
  /**
@@ -142,7 +142,7 @@ export class YEvent {
142
142
  * @public
143
143
  */
144
144
  get deltaDeep () {
145
- return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = /** @type {any} */ (this.getDelta(noAttributionsManager, { deep: true }))))
145
+ return /** @type {any} */ (this._deltaDeep ?? (this._deltaDeep = /** @type {any} */ (this.getDelta({ deep: true }))))
146
146
  }
147
147
  }
148
148
 
@@ -158,13 +158,13 @@ export class YEvent {
158
158
  *
159
159
  * @param {YType} parent
160
160
  * @param {YType} child target
161
- * @param {AbstractAttributionManager} am
161
+ * @param {AbstractRenderer} renderer
162
162
  * @return {Array<string|number>} Path to the target
163
163
  *
164
164
  * @private
165
165
  * @function
166
166
  */
167
- export const getPathTo = (parent, child, am = noAttributionsManager) => {
167
+ export const getPathTo = (parent, child, renderer = baseRenderer) => {
168
168
  const path = []
169
169
  const doc = /** @type {Doc} */ (parent.doc)
170
170
  while (child._item !== null && child !== parent) {
@@ -174,7 +174,7 @@ export const getPathTo = (parent, child, am = noAttributionsManager) => {
174
174
  } else {
175
175
  const parent = /** @type {import('../ytype.js').YType} */ (child._item.parent)
176
176
  // parent is array-ish
177
- const apos = /** @type {import('../utils/RelativePosition.js').AbsolutePosition} */ (createAbsolutePositionFromRelativePosition(createRelativePosition(parent, child._item.id), doc, false, am))
177
+ const apos = /** @type {import('../utils/RelativePosition.js').AbsolutePosition} */ (createAbsolutePositionFromRelativePosition(createRelativePosition(parent, child._item.id), doc, false, renderer))
178
178
  path.unshift(apos.index)
179
179
  }
180
180
  child = /** @type {YType} */ (child._item.parent)
@@ -1,6 +1,6 @@
1
1
  import * as delta from 'lib0/delta'
2
2
  import { createInsertSetFromStructStore, createDeleteSetFromStructStore, diffIdSet, mergeIdSets } from './ids.js'
3
- import { createAttributionManagerFromDiff } from './AttributionManager.js'
3
+ import { createDiffRenderer } from './Renderer.js'
4
4
  import { computeModifiedFromItems } from '../ytype.js'
5
5
 
6
6
  /**
@@ -8,7 +8,7 @@ import { computeModifiedFromItems } from '../ytype.js'
8
8
  * @param {Doc} v2
9
9
  * @return {delta.DeltaBuilderAny}
10
10
  */
11
- export const diffDocsToDelta = (v1, v2, { am = createAttributionManagerFromDiff(v1, v2) } = {}) => {
11
+ export const diffDocsToDelta = (v1, v2, { renderer = createDiffRenderer(v1, v2) } = {}) => {
12
12
  const insertDiff = diffIdSet(createInsertSetFromStructStore(v2.store, false), createInsertSetFromStructStore(v1.store, false))
13
13
  const deleteDiff = diffIdSet(createDeleteSetFromStructStore(v2.store), createDeleteSetFromStructStore(v1.store))
14
14
  // don't render items that have been inserted and then deleted
@@ -23,8 +23,8 @@ export const diffDocsToDelta = (v1, v2, { am = createAttributionManagerFromDiff(
23
23
  v2.share.forEach((type, typename) => {
24
24
  const typeConf = changedTypes.get(type)
25
25
  if (typeConf) {
26
- const shareDelta = type.toDelta(am, {
27
- itemsToRender, retainDeletes: true, deletedItems: deletesOnly, modified: changedTypes, deep: true
26
+ const shareDelta = type.toDelta({
27
+ renderer, itemsToRender, retainDeletes: true, deletedItems: deletesOnly, modified: changedTypes, deep: true
28
28
  })
29
29
  d.modifyAttr(typename, shareDelta)
30
30
  }
package/src/utils/ids.js CHANGED
@@ -7,7 +7,7 @@ import * as rabin from 'lib0/hash/rabin'
7
7
  import * as array from 'lib0/array'
8
8
  import * as map from 'lib0/map'
9
9
 
10
- import { iterateStructs, findIndexSS, iterateStructsWithoutSplits } from './transaction-helpers.js'
10
+ import { iterateStructs, findIndexSS, iterateStructsWithoutSplits, tryGc } from './transaction-helpers.js'
11
11
  import { UpdateEncoderV2, IdSetEncoderV2 } from './UpdateEncoder.js'
12
12
  import { IdSetDecoderV2 } from './UpdateDecoder.js'
13
13
 
@@ -369,6 +369,27 @@ export const iterateStructsByIdSet = (transaction, ds, f) =>
369
369
  }
370
370
  })
371
371
 
372
+ /**
373
+ * Garbage-collect the deleted content referenced by `ids` on `doc`.
374
+ *
375
+ * This is useful to retroactively reclaim memory on a document created with `gc: false`
376
+ * (e.g. once a snapshot, or an UndoManager StackItem, that referenced this content is no
377
+ * longer needed) - without enabling gc for the whole document. Ids that reference live
378
+ * (non-deleted) content, already-collected structs, items flagged with `keep`, or items
379
+ * rejected by `gcFilter` are skipped.
380
+ *
381
+ * @param {Doc} doc
382
+ * @param {IdSet} ids
383
+ * @param {function(Item):boolean} [gcFilter]
384
+ */
385
+ export const gcIdSet = (doc, ids, gcFilter = doc.gcFilter) =>
386
+ doc.transact(tr => {
387
+ // Split structs exactly at the IdSet boundaries so that only the referenced content is
388
+ // collected - an arbitrary IdSet need not align with struct boundaries.
389
+ iterateStructsByIdSet(tr, ids, () => {})
390
+ tryGc(tr, ids, gcFilter)
391
+ })
392
+
372
393
  /**
373
394
  * Iterate over all structs that are mentioned by the IdSet, without spliting the items.
374
395
  *
@@ -38,14 +38,14 @@ export class AttributedContent {
38
38
  }
39
39
 
40
40
  /**
41
- * Abstract class for associating Attributions to content / changes
41
+ * Abstract base class for renderers. A renderer renders Content (with Attributions) to a delta.
42
42
  *
43
43
  * Should fire an event when the attributions changed _after_ the original change happens. This
44
44
  * Event will be used to update the attribution on the current content.
45
45
  *
46
46
  * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
47
47
  */
48
- export class AbstractAttributionManager extends ObservableV2 {
48
+ export class AbstractRenderer extends ObservableV2 {
49
49
  /**
50
50
  * @param {Array<AttributedContent<any>>} _contents - where to write the result
51
51
  * @param {number} _client
@@ -72,17 +72,17 @@ export class AbstractAttributionManager extends ObservableV2 {
72
72
  }
73
73
  }
74
74
 
75
- export const $attributionManager = AbstractAttributionManager.prototype.$type = s.$type('y:am', AbstractAttributionManager)
75
+ export const $renderer = AbstractRenderer.prototype.$type = s.$type('y:r', AbstractRenderer)
76
76
 
77
77
  /**
78
- * Abstract class for associating Attributions to content / changes
78
+ * The default renderer. Renders content as-is, without looking up any attributions.
79
79
  *
80
- * @implements AbstractAttributionManager
80
+ * @implements AbstractRenderer
81
81
  *
82
82
  * @extends {ObservableV2<{change:(idset:IdSet,origin:any,local:boolean)=>void}>}
83
83
  */
84
- export class NoAttributionsManager extends ObservableV2 {
85
- get $type () { return $attributionManager }
84
+ export class BaseRenderer extends ObservableV2 {
85
+ get $type () { return $renderer }
86
86
 
87
87
  /**
88
88
  * @param {Array<AttributedContent<any>>} contents - where to write the result
@@ -107,4 +107,4 @@ export class NoAttributionsManager extends ObservableV2 {
107
107
  }
108
108
  }
109
109
 
110
- export const noAttributionsManager = new NoAttributionsManager()
110
+ export const baseRenderer = new BaseRenderer()