lib0 1.0.0-rc.13 → 1.0.0-rc.14
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.
- package/dist/delta/delta.d.ts +24 -6
- package/dist/delta/transformer.d.ts +100 -6
- package/dist/hash/fnv1a.d.ts +11 -0
- package/package.json +5 -1
- package/src/bin/0serve.js +10 -2
- package/src/delta/delta.js +147 -21
- package/src/delta/transformer.js +93 -20
- package/src/hash/fnv1a.js +82 -0
package/dist/delta/delta.d.ts
CHANGED
|
@@ -42,6 +42,22 @@ export const $attribution: s.Schema<Attribution>;
|
|
|
42
42
|
* @type {s.Schema<DeltaAttrOpJSON>}
|
|
43
43
|
*/
|
|
44
44
|
export const $deltaMapChangeJson: s.Schema<DeltaAttrOpJSON>;
|
|
45
|
+
/**
|
|
46
|
+
* Invariants shared by all op classes below (TextOp, InsertOp, DeleteOp,
|
|
47
|
+
* RetainOp, ModifyOp, SetAttrOp, DeleteAttrOp, ModifyAttrOp):
|
|
48
|
+
*
|
|
49
|
+
* - **Only code inside `delta.js` may mutate op fields.** External consumers
|
|
50
|
+
* treat ops as immutable; structural fields are JSDoc-annotated `@readonly`
|
|
51
|
+
* to reinforce this. Mutation is permitted only while the owning Delta is
|
|
52
|
+
* not `done` — every builder entry point routes through `modDeltaCheck`
|
|
53
|
+
* to enforce this at runtime.
|
|
54
|
+
* - **Any mutation of a fingerprinted field MUST null `_fingerprint`.** The
|
|
55
|
+
* fingerprint is a lazy cache; if it has already been computed and the
|
|
56
|
+
* underlying data changes without invalidating it, every subsequent
|
|
57
|
+
* fingerprint read (and any `diff` / equality check that relies on it) is
|
|
58
|
+
* wrong. Fields covered: insert, delete, retain, format, attribution,
|
|
59
|
+
* value, key.
|
|
60
|
+
*/
|
|
45
61
|
export class TextOp extends list.ListNode {
|
|
46
62
|
/**
|
|
47
63
|
* @param {string} insert
|
|
@@ -125,9 +141,9 @@ export class InsertOp<ArrayContent extends unknown> extends list.ListNode {
|
|
|
125
141
|
*/
|
|
126
142
|
_fingerprint: string | null;
|
|
127
143
|
/**
|
|
128
|
-
* @param {ArrayContent}
|
|
144
|
+
* @param {ArrayContent} _newVal
|
|
129
145
|
*/
|
|
130
|
-
_updateInsert(
|
|
146
|
+
_updateInsert(_newVal: ArrayContent): void;
|
|
131
147
|
/**
|
|
132
148
|
* @return {'insert'}
|
|
133
149
|
*/
|
|
@@ -184,10 +200,10 @@ export class DeleteOp<Conf extends DeltaConf = {}> extends list.ListNode {
|
|
|
184
200
|
/**
|
|
185
201
|
* Remove a part of the operation (similar to Array.splice)
|
|
186
202
|
*
|
|
187
|
-
* @param {number}
|
|
203
|
+
* @param {number} offset
|
|
188
204
|
* @param {number} len
|
|
189
205
|
*/
|
|
190
|
-
_splice(
|
|
206
|
+
_splice(offset: number, len: number): this;
|
|
191
207
|
/**
|
|
192
208
|
* @return {DeltaListOpJSON}
|
|
193
209
|
*/
|
|
@@ -666,10 +682,12 @@ export class DeltaBuilder<Conf extends DeltaConf = {}, FixedConf extends boolean
|
|
|
666
682
|
*
|
|
667
683
|
* a.apply(b).apply(c)
|
|
668
684
|
*
|
|
669
|
-
*
|
|
685
|
+
* If `final = true`, we consider this delta the final state and drop deleteAttrOps from
|
|
686
|
+
* attributes. (E.g. if `otherOp` deletes an attribute, this op will simply not have the
|
|
687
|
+
* attribute). Any kind of `delete` op might be considered a bug. A final delta is not idempotent.
|
|
670
688
|
*
|
|
671
689
|
* @param {Delta<Conf>?} other
|
|
672
|
-
* @param {{ final?: boolean }} opts -- experimental
|
|
690
|
+
* @param {{ final?: boolean }} opts -- (experimental)
|
|
673
691
|
* @return {DeltaBuilder<Conf>}
|
|
674
692
|
*/
|
|
675
693
|
apply(other: Delta<Conf> | null, { final }?: {
|
|
@@ -215,10 +215,10 @@ export class ProjectionTransformer<A extends delta.DeltaConf, B extends delta.De
|
|
|
215
215
|
/**
|
|
216
216
|
* @template {delta.DeltaConf} A
|
|
217
217
|
* @template {delta.DeltaConf} B
|
|
218
|
-
* @template {Pipe<
|
|
218
|
+
* @template {Pipe<any>} PipeTemplate
|
|
219
219
|
* @extends {Transformer<A,B>}
|
|
220
220
|
*/
|
|
221
|
-
export class PipeTransformer<A extends delta.DeltaConf, B extends delta.DeltaConf, PipeTemplate extends Pipe<
|
|
221
|
+
export class PipeTransformer<A extends delta.DeltaConf, B extends delta.DeltaConf, PipeTemplate extends Pipe<any>> extends Transformer<A, B> {
|
|
222
222
|
/**
|
|
223
223
|
* @param {PipeTemplate} tpipe
|
|
224
224
|
*/
|
|
@@ -244,7 +244,102 @@ export type ApplyAttrRename<Renames extends {
|
|
|
244
244
|
attrs: import("../ts.js").PropsRename<delta.DeltaConfGetAttrs<IN>, Renames>;
|
|
245
245
|
}>;
|
|
246
246
|
export type ApplyExpectType<DConf extends delta.DeltaConf, IN extends delta.DeltaConf> = { [K in keyof DConf & keyof IN]: K extends "attrs" ? import("../ts.js").PropsPickShared<DConf[K], IN[K]> : (DConf[K] & IN[K]); } & {};
|
|
247
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Marker for absent props in a NormalizedDeltaConf.
|
|
249
|
+
*/
|
|
250
|
+
export type NotSet = {
|
|
251
|
+
"lib0:notset": true;
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* DeltaConf in normalized form: all props defined, absent props are set to NotSet.
|
|
255
|
+
*
|
|
256
|
+
* ApplyPipe iterates over this form (see ApplyPipeNorm).
|
|
257
|
+
*/
|
|
258
|
+
export type NormalizedDeltaConf<Name, Attrs, Children, Text, RecursiveChildren, RecursiveAttrs> = {
|
|
259
|
+
name: Name;
|
|
260
|
+
attrs: Attrs;
|
|
261
|
+
children: Children;
|
|
262
|
+
text: Text;
|
|
263
|
+
recursiveChildren: RecursiveChildren;
|
|
264
|
+
recursiveAttrs: RecursiveAttrs;
|
|
265
|
+
};
|
|
266
|
+
export type NormalizeDeltaConf<C extends delta.DeltaConf> = NormalizedDeltaConf<C extends {
|
|
267
|
+
name: infer Name extends string;
|
|
268
|
+
} ? Name : NotSet, C extends {
|
|
269
|
+
attrs: infer Attrs extends {
|
|
270
|
+
[K: string | number]: any;
|
|
271
|
+
};
|
|
272
|
+
} ? Attrs : NotSet, C extends {
|
|
273
|
+
children: infer Children;
|
|
274
|
+
} ? Children : NotSet, C extends {
|
|
275
|
+
text: infer Text extends boolean;
|
|
276
|
+
} ? Text : NotSet, C extends {
|
|
277
|
+
recursiveChildren: infer RecursiveChildren extends boolean;
|
|
278
|
+
} ? RecursiveChildren : NotSet, C extends {
|
|
279
|
+
recursiveAttrs: infer RecursiveAttrs extends boolean;
|
|
280
|
+
} ? RecursiveAttrs : NotSet>;
|
|
281
|
+
/**
|
|
282
|
+
* Strip NotSet props from a NormalizedDeltaConf, producing a regular DeltaConf again.
|
|
283
|
+
*/
|
|
284
|
+
export type DenormalizeDeltaConf<NC> = { [K in keyof NC as NC[K] extends NotSet ? never : K]: NC[K]; } & {};
|
|
285
|
+
/**
|
|
286
|
+
* Intersect a prop of a Filter conf with the corresponding pipe conf prop. The prop is only kept
|
|
287
|
+
* if it is defined on both sides (mirrors ApplyExpectType).
|
|
288
|
+
*/
|
|
289
|
+
export type FilterConfProp<FilterProp, PipeProp> = FilterProp extends NotSet ? NotSet : PipeProp extends NotSet ? NotSet : FilterProp & PipeProp;
|
|
290
|
+
/**
|
|
291
|
+
* Apply each Template to a NormalizedDeltaConf - must mirror the semantics of ApplyAttrRename /
|
|
292
|
+
* ApplyExpectType.
|
|
293
|
+
*
|
|
294
|
+
* This shape is tuned to stay below typescript's instantiation-depth limit (TS2589) for long
|
|
295
|
+
* pipes (~85 templates via pipe().init(), measured). What we learned:
|
|
296
|
+
*
|
|
297
|
+
* - The per-step destructure of NC is the load-bearing part: typescript resolves types lazily,
|
|
298
|
+
* and member inference out of the literal that was passed as a type argument in the previous
|
|
299
|
+
* step is what forces resolution of the accumulated conf. Without it (e.g. carrying the conf
|
|
300
|
+
* props as individual type params), the attrs accumulate as a deferred PropsRename chain and
|
|
301
|
+
* the limit hits at ~45 templates. Local annotations do NOT force resolution: `X & {}`,
|
|
302
|
+
* `X extends infer N ? ...`, and an inline `{ attrs: X } extends { attrs: infer A } ? ...`
|
|
303
|
+
* roundtrip were all measured to have no effect.
|
|
304
|
+
* - The recursion must carry a plain object literal. Wrapping the accumulator in a helper alias
|
|
305
|
+
* (even a trivial one like NormalizedDeltaConf) defers per step and rebuilds the chain.
|
|
306
|
+
* - The outer check must be on TS alone. Coupling NC into the check type (e.g.
|
|
307
|
+
* `[TS, NC] extends [[...], {...}]`) makes the conditional generic-deferred whenever the conf
|
|
308
|
+
* is generic, which sends constraint comparisons (e.g. Pipe<TS> vs Pipe<any>) into infinite
|
|
309
|
+
* recursion.
|
|
310
|
+
*/
|
|
311
|
+
export type ApplyPipeNorm<TS extends Array<Template>, NC> = TS extends [infer FirstT extends Template, ...infer RestT extends Template[]] ? (NC extends {
|
|
312
|
+
name: infer Name;
|
|
313
|
+
attrs: infer Attrs extends {
|
|
314
|
+
[K: string | number]: any;
|
|
315
|
+
};
|
|
316
|
+
children: infer Children;
|
|
317
|
+
text: infer Text;
|
|
318
|
+
recursiveChildren: infer RecursiveChildren;
|
|
319
|
+
recursiveAttrs: infer RecursiveAttrs;
|
|
320
|
+
} ? ApplyPipeNorm<RestT, FirstT extends AttrRename<infer Renames> ? {
|
|
321
|
+
name: Name;
|
|
322
|
+
attrs: import("../ts.js").PropsRename<Attrs extends NotSet ? {} : Attrs, Renames>;
|
|
323
|
+
children: Children;
|
|
324
|
+
text: Text;
|
|
325
|
+
recursiveChildren: RecursiveChildren;
|
|
326
|
+
recursiveAttrs: RecursiveAttrs;
|
|
327
|
+
} : FirstT extends Filter<infer DConf extends delta.DeltaConf> ? (NormalizeDeltaConf<DConf> extends {
|
|
328
|
+
name: infer FilterName;
|
|
329
|
+
attrs: infer FilterAttrs;
|
|
330
|
+
children: infer FilterChildren;
|
|
331
|
+
text: infer FilterText;
|
|
332
|
+
recursiveChildren: infer FilterRecursiveChildren;
|
|
333
|
+
recursiveAttrs: infer FilterRecursiveAttrs;
|
|
334
|
+
} ? {
|
|
335
|
+
name: FilterConfProp<FilterName, Name>;
|
|
336
|
+
attrs: FilterAttrs extends NotSet ? NotSet : Attrs extends NotSet ? NotSet : import("../ts.js").PropsPickShared<FilterAttrs, Attrs>;
|
|
337
|
+
children: FilterConfProp<FilterChildren, Children>;
|
|
338
|
+
text: FilterConfProp<FilterText, Text>;
|
|
339
|
+
recursiveChildren: FilterConfProp<FilterRecursiveChildren, RecursiveChildren>;
|
|
340
|
+
recursiveAttrs: FilterConfProp<FilterRecursiveAttrs, RecursiveAttrs>;
|
|
341
|
+
} : never) : NC> : NC) : NC;
|
|
342
|
+
export type ApplyPipe<TS extends Array<Template>, IN extends delta.DeltaConf> = DenormalizeDeltaConf<ApplyPipeNorm<TS, NormalizeDeltaConf<IN>>>;
|
|
248
343
|
export type ApplyQueryAttr<AttrName extends string, IN extends delta.DeltaConf> = {
|
|
249
344
|
name: "lib0:value";
|
|
250
345
|
attrs: {
|
|
@@ -253,14 +348,13 @@ export type ApplyQueryAttr<AttrName extends string, IN extends delta.DeltaConf>
|
|
|
253
348
|
} ? V : never;
|
|
254
349
|
};
|
|
255
350
|
};
|
|
256
|
-
export type EnsureDeltaConf<IN> = IN extends infer OUT extends delta.DeltaConf ? OUT : never;
|
|
257
|
-
export type ApplyTemplate<T extends Template, IN extends delta.DeltaConf> = EnsureDeltaConf<T extends AttrRename<infer Renames> ? ApplyAttrRename<Renames, IN> : T extends Filter<infer DConf extends delta.DeltaConf> ? ApplyExpectType<DConf, IN> : IN>;
|
|
258
351
|
/**
|
|
259
352
|
* Flattens nested Pipe instances into a single flat Template array.
|
|
260
353
|
* Since pipe() always produces flat Pipes, Inner is already flat and
|
|
261
354
|
* only one level of unwrapping is needed per Pipe element.
|
|
355
|
+
* Tail-recursive with an accumulator so the instantiation depth stays constant.
|
|
262
356
|
*/
|
|
263
|
-
export type FlattenTemplates<TS extends Array<Template
|
|
357
|
+
export type FlattenTemplates<TS extends Array<Template>, Acc extends Array<Template> = []> = TS extends [infer F extends Template, ...infer R extends Template[]] ? FlattenTemplates<R, F extends Pipe<infer Inner extends Template[]> ? [...Acc, ...Inner] : [...Acc, F]> : Acc;
|
|
264
358
|
import * as delta from './delta.js';
|
|
265
359
|
import * as s from '../schema.js';
|
|
266
360
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FNV-1a offset basis (32bit).
|
|
3
|
+
*/
|
|
4
|
+
export const offsetBasis: 2166136261;
|
|
5
|
+
/**
|
|
6
|
+
* FNV-1a prime (32bit).
|
|
7
|
+
*/
|
|
8
|
+
export const prime: 16777619;
|
|
9
|
+
export function digest(data: Uint8Array, hash?: number): number;
|
|
10
|
+
export function digestString(str: string, hash?: number): number;
|
|
11
|
+
//# sourceMappingURL=fnv1a.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lib0",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.14",
|
|
4
4
|
"description": "",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
"types": "./dist/crypto/rsa-oaep.d.ts",
|
|
70
70
|
"default": "./src/crypto/rsa-oaep.js"
|
|
71
71
|
},
|
|
72
|
+
"./hash/fnv1a": {
|
|
73
|
+
"types": "./dist/hash/fnv1a.d.ts",
|
|
74
|
+
"default": "./src/hash/fnv1a.js"
|
|
75
|
+
},
|
|
72
76
|
"./hash/rabin": {
|
|
73
77
|
"types": "./dist/hash/rabin.d.ts",
|
|
74
78
|
"default": "./src/hash/rabin.js"
|
package/src/bin/0serve.js
CHANGED
|
@@ -89,9 +89,17 @@ const server = http.createServer((req, res) => {
|
|
|
89
89
|
server.listen(port, host, () => {
|
|
90
90
|
logging.print(logging.BOLD, logging.ORANGE, `Server is running on http://${host}:${port}`)
|
|
91
91
|
if (paramOpenFile) {
|
|
92
|
-
const
|
|
92
|
+
const url = `http://${host}:${port}/${paramOpenFile}`
|
|
93
93
|
import('child_process').then(cp => {
|
|
94
|
-
|
|
94
|
+
if (debugBrowser) {
|
|
95
|
+
cp.execFile(debugBrowser, [url])
|
|
96
|
+
} else if (process.platform === 'darwin') {
|
|
97
|
+
cp.execFile('open', [url])
|
|
98
|
+
} else if (process.platform === 'win32') {
|
|
99
|
+
cp.execFile('cmd', ['/c', 'start', '', url])
|
|
100
|
+
} else {
|
|
101
|
+
cp.execFile('xdg-open', [url])
|
|
102
|
+
}
|
|
95
103
|
})
|
|
96
104
|
}
|
|
97
105
|
})
|
package/src/delta/delta.js
CHANGED
|
@@ -101,6 +101,22 @@ const _cloneAttrs = attrs => attrs == null ? attrs : { ...attrs }
|
|
|
101
101
|
*/
|
|
102
102
|
const _markMaybeDeltaAsDone = maybeDelta => $deltaAny.check(maybeDelta) ? /** @type {MaybeDelta} */ (maybeDelta.done()) : maybeDelta
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Invariants shared by all op classes below (TextOp, InsertOp, DeleteOp,
|
|
106
|
+
* RetainOp, ModifyOp, SetAttrOp, DeleteAttrOp, ModifyAttrOp):
|
|
107
|
+
*
|
|
108
|
+
* - **Only code inside `delta.js` may mutate op fields.** External consumers
|
|
109
|
+
* treat ops as immutable; structural fields are JSDoc-annotated `@readonly`
|
|
110
|
+
* to reinforce this. Mutation is permitted only while the owning Delta is
|
|
111
|
+
* not `done` — every builder entry point routes through `modDeltaCheck`
|
|
112
|
+
* to enforce this at runtime.
|
|
113
|
+
* - **Any mutation of a fingerprinted field MUST null `_fingerprint`.** The
|
|
114
|
+
* fingerprint is a lazy cache; if it has already been computed and the
|
|
115
|
+
* underlying data changes without invalidating it, every subsequent
|
|
116
|
+
* fingerprint read (and any `diff` / equality check that relies on it) is
|
|
117
|
+
* wrong. Fields covered: insert, delete, retain, format, attribution,
|
|
118
|
+
* value, key.
|
|
119
|
+
*/
|
|
104
120
|
export class TextOp extends list.ListNode {
|
|
105
121
|
/**
|
|
106
122
|
* @param {string} insert
|
|
@@ -228,14 +244,17 @@ export class InsertOp extends list.ListNode {
|
|
|
228
244
|
this._fingerprint = null
|
|
229
245
|
}
|
|
230
246
|
|
|
247
|
+
/* c8 ignore start */
|
|
231
248
|
/**
|
|
232
|
-
* @param {ArrayContent}
|
|
249
|
+
* @param {ArrayContent} _newVal
|
|
233
250
|
*/
|
|
234
|
-
_updateInsert (
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
251
|
+
_updateInsert (_newVal) {
|
|
252
|
+
// Mirror of TextOp._updateInsert; not currently called on InsertOp because
|
|
253
|
+
// adjacent inserts are merged in-place via `end.insert.push(...)`. Kept for
|
|
254
|
+
// parity with TextOp's API.
|
|
255
|
+
error.unexpectedCase() // throw if called
|
|
238
256
|
}
|
|
257
|
+
/* c8 ignore stop */
|
|
239
258
|
|
|
240
259
|
/**
|
|
241
260
|
* @return {'insert'}
|
|
@@ -357,11 +376,13 @@ export class DeleteOp extends list.ListNode {
|
|
|
357
376
|
/**
|
|
358
377
|
* Remove a part of the operation (similar to Array.splice)
|
|
359
378
|
*
|
|
360
|
-
* @param {number}
|
|
379
|
+
* @param {number} offset
|
|
361
380
|
* @param {number} len
|
|
362
381
|
*/
|
|
363
|
-
_splice (
|
|
364
|
-
|
|
382
|
+
_splice (offset, len) {
|
|
383
|
+
if (this.prevValue) {
|
|
384
|
+
/** @type {DeltaBuilder<any>} */ (this.prevValue).apply(create().retain(offset).delete(len))
|
|
385
|
+
}
|
|
365
386
|
this._fingerprint = null
|
|
366
387
|
this.delete -= len
|
|
367
388
|
return this
|
|
@@ -547,6 +568,9 @@ export class ModifyOp extends list.ListNode {
|
|
|
547
568
|
})))
|
|
548
569
|
}
|
|
549
570
|
|
|
571
|
+
/* c8 ignore start */
|
|
572
|
+
// ModifyOp has length 1, so callers never pass offset>0 or len>0 — splitHere
|
|
573
|
+
// is a no-op for length-1 ops. Kept for the structural _splice contract.
|
|
550
574
|
/**
|
|
551
575
|
* Remove a part of the operation (similar to Array.splice)
|
|
552
576
|
*
|
|
@@ -556,6 +580,7 @@ export class ModifyOp extends list.ListNode {
|
|
|
556
580
|
_splice (_offset, _len) {
|
|
557
581
|
return this
|
|
558
582
|
}
|
|
583
|
+
/* c8 ignore stop */
|
|
559
584
|
|
|
560
585
|
/**
|
|
561
586
|
* @return {DeltaListOpJSON}
|
|
@@ -851,7 +876,7 @@ export const $setAttrOpWith = $content => s.$custom(o => $setAttrOp.check(o) &&
|
|
|
851
876
|
* @param {s.Schema<Content>} $content
|
|
852
877
|
* @return {s.Schema<InsertOp<Content>>}
|
|
853
878
|
*/
|
|
854
|
-
export const $insertOpWith = $content => s.$custom(o => $insertOp.check(o) &&
|
|
879
|
+
export const $insertOpWith = $content => s.$custom(o => $insertOp.check(o) && o.insert.every(ins => $content.check(ins)))
|
|
855
880
|
|
|
856
881
|
/**
|
|
857
882
|
* @template {DeltaAny} Modify
|
|
@@ -1231,9 +1256,13 @@ const tryMergeWithPrev = (parent, op) => {
|
|
|
1231
1256
|
/** @type {DeleteOp<any>} */ (prevOp).delete += op.delete
|
|
1232
1257
|
} else if ($textOp.check(op)) {
|
|
1233
1258
|
/** @type {TextOp} */ (prevOp)._updateInsert(/** @type {TextOp} */ (prevOp).insert + op.insert)
|
|
1259
|
+
/* c8 ignore start */
|
|
1234
1260
|
} else {
|
|
1261
|
+
// unreachable: the constructor check at the top of the function already
|
|
1262
|
+
// limits `op` to one of the four kinds tested above
|
|
1235
1263
|
error.unexpectedCase()
|
|
1236
1264
|
}
|
|
1265
|
+
/* c8 ignore stop */
|
|
1237
1266
|
list.remove(parent, op)
|
|
1238
1267
|
}
|
|
1239
1268
|
|
|
@@ -1501,10 +1530,12 @@ export class DeltaBuilder extends Delta {
|
|
|
1501
1530
|
*
|
|
1502
1531
|
* a.apply(b).apply(c)
|
|
1503
1532
|
*
|
|
1504
|
-
*
|
|
1533
|
+
* If `final = true`, we consider this delta the final state and drop deleteAttrOps from
|
|
1534
|
+
* attributes. (E.g. if `otherOp` deletes an attribute, this op will simply not have the
|
|
1535
|
+
* attribute). Any kind of `delete` op might be considered a bug. A final delta is not idempotent.
|
|
1505
1536
|
*
|
|
1506
1537
|
* @param {Delta<Conf>?} other
|
|
1507
|
-
* @param {{ final?: boolean }} opts -- experimental
|
|
1538
|
+
* @param {{ final?: boolean }} opts -- (experimental)
|
|
1508
1539
|
* @return {DeltaBuilder<Conf>}
|
|
1509
1540
|
*/
|
|
1510
1541
|
apply (other, { final = this.isFinal } = {}) {
|
|
@@ -1517,7 +1548,7 @@ export class DeltaBuilder extends Delta {
|
|
|
1517
1548
|
const c = /** @type {SetAttrOp<any,any>|DeleteAttrOp<any>|ModifyAttrOp<any,any>} */ (this.attrs[op.key])
|
|
1518
1549
|
if ($modifyAttrOp.check(op)) {
|
|
1519
1550
|
if ($deltaAny.check(c?.value)) {
|
|
1520
|
-
c._modValue.apply(op.value)
|
|
1551
|
+
c._modValue.apply(op.value, { final })
|
|
1521
1552
|
} else {
|
|
1522
1553
|
// then this is a simple modify
|
|
1523
1554
|
// @ts-ignore
|
|
@@ -1588,10 +1619,14 @@ export class DeltaBuilder extends Delta {
|
|
|
1588
1619
|
this.childCnt += op.length
|
|
1589
1620
|
}
|
|
1590
1621
|
for (const op of other.children) {
|
|
1622
|
+
// defensive: the per-branch logic below resets opsI/offset whenever it
|
|
1623
|
+
// consumes an op exactly. This guard catches any path that forgets to.
|
|
1624
|
+
/* c8 ignore start */
|
|
1591
1625
|
if (opsI?.length === offset) {
|
|
1592
1626
|
opsI = opNextUndeleted(opsI)
|
|
1593
1627
|
offset = 0
|
|
1594
1628
|
}
|
|
1629
|
+
/* c8 ignore stop */
|
|
1595
1630
|
if ($textOp.check(op) || $insertOp.check(op)) {
|
|
1596
1631
|
insertClonedOp(op)
|
|
1597
1632
|
} else if ($retainOp.check(op)) {
|
|
@@ -1611,7 +1646,11 @@ export class DeltaBuilder extends Delta {
|
|
|
1611
1646
|
}
|
|
1612
1647
|
if (opsI != null) {
|
|
1613
1648
|
if (op.format != null && retainLen > 0) {
|
|
1614
|
-
offset
|
|
1649
|
+
// accumulate onto the existing offset — the else-branch below uses
|
|
1650
|
+
// `offset += retainLen`, and we must agree with it when prior
|
|
1651
|
+
// iterations have advanced offset into opsI without splitting (e.g.
|
|
1652
|
+
// a format-less retain followed by a same-format retain).
|
|
1653
|
+
offset += retainLen
|
|
1615
1654
|
splitHere()
|
|
1616
1655
|
updateOpFormat(/** @type {ChildrenOpAny} */ (opsI.prev), op.format)
|
|
1617
1656
|
scheduleForMerge(opsI.prev)
|
|
@@ -1670,9 +1709,12 @@ export class DeltaBuilder extends Delta {
|
|
|
1670
1709
|
opsI._splice(offset, delLen)
|
|
1671
1710
|
}
|
|
1672
1711
|
remainingLen -= delLen
|
|
1712
|
+
/* c8 ignore start */
|
|
1673
1713
|
} else {
|
|
1714
|
+
// unreachable: opsI was already typed as retain | non-delete-content | delete above
|
|
1674
1715
|
error.unexpectedCase()
|
|
1675
1716
|
}
|
|
1717
|
+
/* c8 ignore stop */
|
|
1676
1718
|
}
|
|
1677
1719
|
} else if ($modifyOp.check(op)) {
|
|
1678
1720
|
if (opsI != null && op.format != null && (!$deleteOp.check(opsI) && !$retainOp.check(opsI))) { // retain handles splitting seperately, without copying attrs
|
|
@@ -1709,14 +1751,20 @@ export class DeltaBuilder extends Delta {
|
|
|
1709
1751
|
opsI._splice(0, 1)
|
|
1710
1752
|
scheduleForMerge(opsI)
|
|
1711
1753
|
}
|
|
1712
|
-
|
|
1713
|
-
// nop
|
|
1754
|
+
/* c8 ignore start */
|
|
1714
1755
|
} else {
|
|
1756
|
+
// remaining branches: opsI is deleteOp or something unknown
|
|
1757
|
+
// both branches are unreachable today: opNextUndeleted skips
|
|
1758
|
+
// delete ops, so opsI is never a delete during iteration; and the four
|
|
1759
|
+
// branches above exhaust the other op kinds. The deleteOp branch is
|
|
1760
|
+
// kept as a defensive no-op (drops a modify that lands in a deleted
|
|
1761
|
+
// region) rather than a throw.
|
|
1715
1762
|
error.unexpectedCase()
|
|
1716
1763
|
}
|
|
1717
1764
|
} else {
|
|
1718
1765
|
error.unexpectedCase()
|
|
1719
1766
|
}
|
|
1767
|
+
/* c8 ignore stop */
|
|
1720
1768
|
}
|
|
1721
1769
|
// iterate backwards, to ensure that we merge all content
|
|
1722
1770
|
for (let i = maybeMergeable.length - 1; i >= 0; i--) {
|
|
@@ -1772,9 +1820,12 @@ export class DeltaBuilder extends Delta {
|
|
|
1772
1820
|
// @ts-ignore
|
|
1773
1821
|
delete this.attrs[otherOp.key]
|
|
1774
1822
|
}
|
|
1823
|
+
/* c8 ignore start */
|
|
1775
1824
|
} else {
|
|
1825
|
+
// unreachable: attr ops are exhaustively setAttr | deleteAttr | modifyAttr
|
|
1776
1826
|
error.unexpectedCase()
|
|
1777
1827
|
}
|
|
1828
|
+
/* c8 ignore stop */
|
|
1778
1829
|
}
|
|
1779
1830
|
/**
|
|
1780
1831
|
* Rebase children.
|
|
@@ -1831,7 +1882,10 @@ export class DeltaBuilder extends Delta {
|
|
|
1831
1882
|
otherOffset = otherChild.length
|
|
1832
1883
|
} else {
|
|
1833
1884
|
if ($modifyOp.check(otherChild)) {
|
|
1834
|
-
|
|
1885
|
+
// _modValue (not .value) — ModifyOp.clone() marks its inner delta
|
|
1886
|
+
// as `done`, so a cloned ModifyOp can only be rebased after the
|
|
1887
|
+
// _modValue getter lazy-clones it back to mutable.
|
|
1888
|
+
currChild._modValue.rebase(otherChild.value, priority)
|
|
1835
1889
|
} else if ($deleteOp.check(otherChild)) {
|
|
1836
1890
|
list.remove(this.children, currChild)
|
|
1837
1891
|
this.childCnt -= 1
|
|
@@ -1848,21 +1902,70 @@ export class DeltaBuilder extends Delta {
|
|
|
1848
1902
|
* - insert: split curr op and insert retain
|
|
1849
1903
|
*/
|
|
1850
1904
|
if ($retainOp.check(otherChild) || $modifyOp.check(otherChild)) {
|
|
1905
|
+
// Format reconciliation. priority=true is a no-op (currChild's format
|
|
1906
|
+
// wins). For !priority, currChild concedes any format key that
|
|
1907
|
+
// otherChild also writes — but only over the [currOffset..currOffset+
|
|
1908
|
+
// maxCommonLen] overlap. Split currChild around the overlap so the
|
|
1909
|
+
// prefix/suffix keep their original format and only the middle piece
|
|
1910
|
+
// carries the stripped format.
|
|
1911
|
+
if (
|
|
1912
|
+
!priority &&
|
|
1913
|
+
$retainOp.check(currChild) &&
|
|
1914
|
+
currChild.format != null &&
|
|
1915
|
+
otherChild.format != null
|
|
1916
|
+
) {
|
|
1917
|
+
/** @type {FormattingAttributes} */
|
|
1918
|
+
const stripped = {}
|
|
1919
|
+
let strippedAny = false
|
|
1920
|
+
for (const k in currChild.format) {
|
|
1921
|
+
if (k in otherChild.format) {
|
|
1922
|
+
strippedAny = true
|
|
1923
|
+
} else {
|
|
1924
|
+
stripped[k] = currChild.format[k]
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (strippedAny) {
|
|
1928
|
+
// split off the suffix [currOffset+maxCommonLen..length] if any
|
|
1929
|
+
if (currOffset + maxCommonLen < currChild.length) {
|
|
1930
|
+
const suffix = currChild.clone(currOffset + maxCommonLen, currChild.length)
|
|
1931
|
+
list.insertBetween(this.children, currChild, currChild.next, suffix)
|
|
1932
|
+
currChild._splice(currOffset + maxCommonLen, currChild.length - (currOffset + maxCommonLen))
|
|
1933
|
+
}
|
|
1934
|
+
// split off the prefix [0..currOffset] if any
|
|
1935
|
+
if (currOffset > 0) {
|
|
1936
|
+
const prefix = currChild.clone(0, currOffset)
|
|
1937
|
+
list.insertBetween(this.children, currChild.prev, currChild, prefix)
|
|
1938
|
+
currChild._splice(0, currOffset)
|
|
1939
|
+
currOffset = 0
|
|
1940
|
+
}
|
|
1941
|
+
// currChild now spans exactly the overlap. Replace its format.
|
|
1942
|
+
/** @type {any} */ (currChild).format = object.isEmpty(stripped) ? null : stripped
|
|
1943
|
+
currChild._fingerprint = null
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1851
1946
|
currOffset += maxCommonLen
|
|
1852
1947
|
otherOffset += maxCommonLen
|
|
1853
1948
|
} else if ($deleteOp.check(otherChild)) {
|
|
1854
1949
|
if ($retainOp.check(currChild)) {
|
|
1855
1950
|
// @ts-ignore
|
|
1856
1951
|
currChild.retain -= maxCommonLen
|
|
1952
|
+
currChild._fingerprint = null
|
|
1857
1953
|
} else if ($deleteOp.check(currChild)) {
|
|
1858
1954
|
currChild.delete -= maxCommonLen
|
|
1955
|
+
currChild._fingerprint = null
|
|
1859
1956
|
}
|
|
1860
1957
|
this.childCnt -= maxCommonLen
|
|
1861
|
-
|
|
1958
|
+
// advance other so subsequent currChild ops see what comes AFTER this
|
|
1959
|
+
// delete; without this we'd loop against the same delete forever and
|
|
1960
|
+
// never reach other's later inserts.
|
|
1961
|
+
otherOffset += maxCommonLen
|
|
1962
|
+
} else { // insert/text.check(otherChild)
|
|
1862
1963
|
if (currOffset > 0) {
|
|
1863
|
-
const leftPart = currChild.clone(currOffset)
|
|
1964
|
+
const leftPart = currChild.clone(0, currOffset)
|
|
1864
1965
|
list.insertBetween(this.children, currChild.prev, currChild, leftPart)
|
|
1865
|
-
currChild
|
|
1966
|
+
// leftPart is the prefix; currChild becomes the suffix. Remove the
|
|
1967
|
+
// prefix portion from currChild so it represents [currOffset..length].
|
|
1968
|
+
currChild._splice(0, currOffset)
|
|
1866
1969
|
currOffset = 0
|
|
1867
1970
|
}
|
|
1868
1971
|
list.insertBetween(this.children, currChild.prev, currChild, new RetainOp(otherChild.length, null, null))
|
|
@@ -2000,8 +2103,10 @@ export class $Delta extends s.Schema {
|
|
|
2000
2103
|
check (o, err = undefined) {
|
|
2001
2104
|
const { $name, $attrs, $children, hasText, $formats } = this.shape
|
|
2002
2105
|
if (!$deltaAny.check(o, err)) {
|
|
2106
|
+
/* c8 ignore next */
|
|
2003
2107
|
err?.extend(null, 'Delta', o?.constructor.name, 'Constructor match failed')
|
|
2004
2108
|
} else if (o.name != null && !$name.check(o.name, err)) {
|
|
2109
|
+
/* c8 ignore next */
|
|
2005
2110
|
err?.extend('Delta.name', $name.toString(), o.name, 'Unexpected node name')
|
|
2006
2111
|
} else if (list.toArray(o.children).some(c => (!hasText && $textOp.check(c)) || (hasText && $textOp.check(c) && c.format != null && !$formats.check(c.format)) || ($insertOp.check(c) && !c.insert.every(ins => $children.check(ins))))) {
|
|
2007
2112
|
err?.extend('Delta.children', '', '', 'Children don\'t match the schema')
|
|
@@ -2097,7 +2202,7 @@ export const mergeDeltas = (a, b) => {
|
|
|
2097
2202
|
c.apply(b)
|
|
2098
2203
|
return /** @type {any} */ (c)
|
|
2099
2204
|
}
|
|
2100
|
-
return
|
|
2205
|
+
return /** @type {D} */ (a || b || null)
|
|
2101
2206
|
}
|
|
2102
2207
|
|
|
2103
2208
|
/**
|
|
@@ -2325,6 +2430,16 @@ class _DiffStringWrapper {
|
|
|
2325
2430
|
*/
|
|
2326
2431
|
|
|
2327
2432
|
/**
|
|
2433
|
+
* Compute a delta that, when applied to `d1`, produces `d2`. Only the children and attributes of
|
|
2434
|
+
* `d1` and `d2` are compared; the top-level node names of `d1` and `d2` are *not*. Diffing
|
|
2435
|
+
* `<div>a</div>` against `<span>a</span>` is valid and yields an empty diff — they have the same
|
|
2436
|
+
* children and attributes, so as far as `diff` is concerned they are equal at the level it cares
|
|
2437
|
+
* about. The top-level name is treated as a document-type marker, not as diffable content.
|
|
2438
|
+
*
|
|
2439
|
+
* Names *are* compared on children: a child node whose name changes between `d1` and `d2` is
|
|
2440
|
+
* replaced wholesale (delete + insert), not converted into a `modify` op. Same-name child nodes
|
|
2441
|
+
* at aligned positions are paired and recursed into via `modify`.
|
|
2442
|
+
*
|
|
2328
2443
|
* @template {DeltaConf} Conf
|
|
2329
2444
|
* @param {Delta<Conf>} d1
|
|
2330
2445
|
* @param {NoInfer<Delta<Conf>>} d2
|
|
@@ -2395,9 +2510,14 @@ export const diff = (d1, d2) => {
|
|
|
2395
2510
|
cs2.push(left2.insert)
|
|
2396
2511
|
} else if ($insertOp.check(left2)) {
|
|
2397
2512
|
cs2.push(...left2.insert.map(ins => typeof ins === 'string' ? new _DiffStringWrapper(ins) : ins))
|
|
2513
|
+
/* c8 ignore start */
|
|
2398
2514
|
} else {
|
|
2515
|
+
// unreachable for valid diff inputs (delete on the rhs would already
|
|
2516
|
+
// have been rejected via the `[lib0/delta] diffing deletes unsupported`
|
|
2517
|
+
// path above)
|
|
2399
2518
|
error.unexpectedCase()
|
|
2400
2519
|
}
|
|
2520
|
+
/* c8 ignore stop */
|
|
2401
2521
|
formattingNeedsDiff ||= left2.format != null
|
|
2402
2522
|
left2 = left2.next
|
|
2403
2523
|
}
|
|
@@ -2459,9 +2579,14 @@ export const diff = (d1, d2) => {
|
|
|
2459
2579
|
a = a.next
|
|
2460
2580
|
aOffset = 0
|
|
2461
2581
|
}
|
|
2582
|
+
/* c8 ignore start */
|
|
2462
2583
|
} else {
|
|
2584
|
+
// unreachable: by this point both a and b are insert/text (deletes
|
|
2585
|
+
// were rejected upstream and `originalUpdated` is the result of an
|
|
2586
|
+
// apply, which keeps inserts only).
|
|
2463
2587
|
error.unexpectedCase()
|
|
2464
2588
|
}
|
|
2589
|
+
/* c8 ignore stop */
|
|
2465
2590
|
}
|
|
2466
2591
|
// @todo instead of applying, we want to first exec d, then formattingDiff - we need a merge
|
|
2467
2592
|
// function!
|
|
@@ -2481,10 +2606,11 @@ export const diff = (d1, d2) => {
|
|
|
2481
2606
|
} else {
|
|
2482
2607
|
d.setAttr(key, nextVal)
|
|
2483
2608
|
}
|
|
2609
|
+
/* c8 ignore start */
|
|
2484
2610
|
} else {
|
|
2485
|
-
/* c8 ignore next 2 */
|
|
2486
2611
|
error.unexpectedCase()
|
|
2487
2612
|
}
|
|
2613
|
+
/* c8 ignore stop */
|
|
2488
2614
|
}
|
|
2489
2615
|
}
|
|
2490
2616
|
for (const { key } of d1.attrs) {
|
package/src/delta/transformer.js
CHANGED
|
@@ -164,43 +164,116 @@ export const $transformer = transformerWith(s.$any, s.$any)
|
|
|
164
164
|
*/
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* @typedef {
|
|
167
|
+
* Marker for absent props in a NormalizedDeltaConf.
|
|
168
|
+
*
|
|
169
|
+
* @typedef {{ 'lib0:notset': true }} NotSet
|
|
170
170
|
*/
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
173
|
+
* DeltaConf in normalized form: all props defined, absent props are set to NotSet.
|
|
174
|
+
*
|
|
175
|
+
* ApplyPipe iterates over this form (see ApplyPipeNorm).
|
|
176
|
+
*
|
|
177
|
+
* @template Name
|
|
178
|
+
* @template Attrs
|
|
179
|
+
* @template Children
|
|
180
|
+
* @template Text
|
|
181
|
+
* @template RecursiveChildren
|
|
182
|
+
* @template RecursiveAttrs
|
|
183
|
+
* @typedef {{ name: Name, attrs: Attrs, children: Children, text: Text, recursiveChildren: RecursiveChildren, recursiveAttrs: RecursiveAttrs }} NormalizedDeltaConf
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @template {delta.DeltaConf} C
|
|
188
|
+
* @typedef {NormalizedDeltaConf<
|
|
189
|
+
* C extends { name: infer Name extends string } ? Name : NotSet,
|
|
190
|
+
* C extends { attrs: infer Attrs extends {[K:string|number]:any} } ? Attrs : NotSet,
|
|
191
|
+
* C extends { children: infer Children } ? Children : NotSet,
|
|
192
|
+
* C extends { text: infer Text extends boolean } ? Text : NotSet,
|
|
193
|
+
* C extends { recursiveChildren: infer RecursiveChildren extends boolean } ? RecursiveChildren : NotSet,
|
|
194
|
+
* C extends { recursiveAttrs: infer RecursiveAttrs extends boolean } ? RecursiveAttrs : NotSet
|
|
195
|
+
* >} NormalizeDeltaConf
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Strip NotSet props from a NormalizedDeltaConf, producing a regular DeltaConf again.
|
|
200
|
+
*
|
|
201
|
+
* @template NC
|
|
202
|
+
* @typedef {{ [K in keyof NC as NC[K] extends NotSet ? never : K]: NC[K] } & {}} DenormalizeDeltaConf
|
|
176
203
|
*/
|
|
177
204
|
|
|
178
205
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
206
|
+
* Intersect a prop of a Filter conf with the corresponding pipe conf prop. The prop is only kept
|
|
207
|
+
* if it is defined on both sides (mirrors ApplyExpectType).
|
|
208
|
+
*
|
|
209
|
+
* @template FilterProp
|
|
210
|
+
* @template PipeProp
|
|
211
|
+
* @typedef {FilterProp extends NotSet ? NotSet : PipeProp extends NotSet ? NotSet : FilterProp & PipeProp} FilterConfProp
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Apply each Template to a NormalizedDeltaConf - must mirror the semantics of ApplyAttrRename /
|
|
216
|
+
* ApplyExpectType.
|
|
217
|
+
*
|
|
218
|
+
* This shape is tuned to stay below typescript's instantiation-depth limit (TS2589) for long
|
|
219
|
+
* pipes (~85 templates via pipe().init(), measured). What we learned:
|
|
220
|
+
*
|
|
221
|
+
* - The per-step destructure of NC is the load-bearing part: typescript resolves types lazily,
|
|
222
|
+
* and member inference out of the literal that was passed as a type argument in the previous
|
|
223
|
+
* step is what forces resolution of the accumulated conf. Without it (e.g. carrying the conf
|
|
224
|
+
* props as individual type params), the attrs accumulate as a deferred PropsRename chain and
|
|
225
|
+
* the limit hits at ~45 templates. Local annotations do NOT force resolution: `X & {}`,
|
|
226
|
+
* `X extends infer N ? ...`, and an inline `{ attrs: X } extends { attrs: infer A } ? ...`
|
|
227
|
+
* roundtrip were all measured to have no effect.
|
|
228
|
+
* - The recursion must carry a plain object literal. Wrapping the accumulator in a helper alias
|
|
229
|
+
* (even a trivial one like NormalizedDeltaConf) defers per step and rebuilds the chain.
|
|
230
|
+
* - The outer check must be on TS alone. Coupling NC into the check type (e.g.
|
|
231
|
+
* `[TS, NC] extends [[...], {...}]`) makes the conditional generic-deferred whenever the conf
|
|
232
|
+
* is generic, which sends constraint comparisons (e.g. Pipe<TS> vs Pipe<any>) into infinite
|
|
233
|
+
* recursion.
|
|
234
|
+
*
|
|
235
|
+
* @template {Array<Template>} TS
|
|
236
|
+
* @template NC
|
|
237
|
+
* @typedef {TS extends [infer FirstT extends Template, ...infer RestT extends Template[]]
|
|
238
|
+
* ? (NC extends { name: infer Name, attrs: infer Attrs extends {[K:string|number]:any}, children: infer Children, text: infer Text, recursiveChildren: infer RecursiveChildren, recursiveAttrs: infer RecursiveAttrs }
|
|
239
|
+
* ? ApplyPipeNorm<RestT,
|
|
240
|
+
* FirstT extends AttrRename<infer Renames> ? { name: Name, attrs: import('../ts.js').PropsRename<Attrs extends NotSet ? {} : Attrs, Renames>, children: Children, text: Text, recursiveChildren: RecursiveChildren, recursiveAttrs: RecursiveAttrs } :
|
|
241
|
+
* FirstT extends Filter<infer DConf extends delta.DeltaConf> ? (NormalizeDeltaConf<DConf> extends { name: infer FilterName, attrs: infer FilterAttrs, children: infer FilterChildren, text: infer FilterText, recursiveChildren: infer FilterRecursiveChildren, recursiveAttrs: infer FilterRecursiveAttrs } ? {
|
|
242
|
+
* name: FilterConfProp<FilterName, Name>,
|
|
243
|
+
* attrs: FilterAttrs extends NotSet ? NotSet : Attrs extends NotSet ? NotSet : import('../ts.js').PropsPickShared<FilterAttrs, Attrs>,
|
|
244
|
+
* children: FilterConfProp<FilterChildren, Children>,
|
|
245
|
+
* text: FilterConfProp<FilterText, Text>,
|
|
246
|
+
* recursiveChildren: FilterConfProp<FilterRecursiveChildren, RecursiveChildren>,
|
|
247
|
+
* recursiveAttrs: FilterConfProp<FilterRecursiveAttrs, RecursiveAttrs>
|
|
248
|
+
* } : never) :
|
|
249
|
+
* NC>
|
|
250
|
+
* : NC)
|
|
251
|
+
* : NC} ApplyPipeNorm
|
|
181
252
|
*/
|
|
182
253
|
|
|
183
254
|
/**
|
|
184
|
-
* @template {Template}
|
|
255
|
+
* @template {Array<Template>} TS
|
|
185
256
|
* @template {delta.DeltaConf} IN
|
|
186
|
-
* @typedef {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
257
|
+
* @typedef {DenormalizeDeltaConf<ApplyPipeNorm<TS, NormalizeDeltaConf<IN>>>} ApplyPipe
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @template {string} AttrName
|
|
262
|
+
* @template {delta.DeltaConf} IN
|
|
263
|
+
* @typedef {{ name: 'lib0:value', attrs: { value: IN extends { attrs: { [K in AttrName]: infer V } } ? V : never }}} ApplyQueryAttr
|
|
191
264
|
*/
|
|
192
265
|
|
|
193
266
|
/**
|
|
194
267
|
* Flattens nested Pipe instances into a single flat Template array.
|
|
195
268
|
* Since pipe() always produces flat Pipes, Inner is already flat and
|
|
196
269
|
* only one level of unwrapping is needed per Pipe element.
|
|
270
|
+
* Tail-recursive with an accumulator so the instantiation depth stays constant.
|
|
197
271
|
*
|
|
198
272
|
* @template {Array<Template>} TS
|
|
273
|
+
* @template {Array<Template>} [Acc=[]]
|
|
199
274
|
* @typedef {TS extends [infer F extends Template, ...infer R extends Template[]]
|
|
200
|
-
* ? F extends Pipe<infer Inner extends Template[]>
|
|
201
|
-
*
|
|
202
|
-
* : [F, ...FlattenTemplates<R>]
|
|
203
|
-
* : []} FlattenTemplates
|
|
275
|
+
* ? FlattenTemplates<R, F extends Pipe<infer Inner extends Template[]> ? [...Acc, ...Inner] : [...Acc, F]>
|
|
276
|
+
* : Acc} FlattenTemplates
|
|
204
277
|
*/
|
|
205
278
|
|
|
206
279
|
/**
|
|
@@ -563,7 +636,7 @@ export class ProjectionTransformer extends Transformer {
|
|
|
563
636
|
/**
|
|
564
637
|
* @template {delta.DeltaConf} A
|
|
565
638
|
* @template {delta.DeltaConf} B
|
|
566
|
-
* @template {Pipe<
|
|
639
|
+
* @template {Pipe<any>} PipeTemplate
|
|
567
640
|
* @extends {Transformer<A,B>}
|
|
568
641
|
*/
|
|
569
642
|
export class PipeTransformer extends Transformer {
|
|
@@ -576,7 +649,7 @@ export class PipeTransformer extends Transformer {
|
|
|
576
649
|
/**
|
|
577
650
|
* @type {Transformer<any,any>[]}
|
|
578
651
|
*/
|
|
579
|
-
this.ts = tpipe.templates.map(t => t.init(delta.$deltaAny))
|
|
652
|
+
this.ts = tpipe.templates.map((/** @type {Template} */ t) => t.init(delta.$deltaAny))
|
|
580
653
|
}
|
|
581
654
|
|
|
582
655
|
/**
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module fnv1a
|
|
3
|
+
* FNV-1a (32bit) - a fast, non-cryptographic hash function.
|
|
4
|
+
* Spec: http://www.isthe.com/chongo/tech/comp/fnv/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as math from '../math.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* FNV-1a offset basis (32bit).
|
|
11
|
+
*/
|
|
12
|
+
export const offsetBasis = 0x811c9dc5
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* FNV-1a prime (32bit).
|
|
16
|
+
*/
|
|
17
|
+
export const prime = 0x01000193
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compute the FNV-1a 32bit hash of a byte sequence.
|
|
21
|
+
*
|
|
22
|
+
* Pass the result of a previous call as `hash` to digest a message in chunks:
|
|
23
|
+
* `digest(concat(a, b)) === digest(b, digest(a))`.
|
|
24
|
+
*
|
|
25
|
+
* @param {Uint8Array} data
|
|
26
|
+
* @param {number} hash - continue hashing from a previous result (defaults to the offset basis)
|
|
27
|
+
* @return {number} unsigned 32bit hash
|
|
28
|
+
*/
|
|
29
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
30
|
+
export const digest = (data, hash = offsetBasis) => {
|
|
31
|
+
for (let i = 0; i < data.length; i++) {
|
|
32
|
+
hash = math.imul(hash ^ data[i], prime)
|
|
33
|
+
}
|
|
34
|
+
return hash >>> 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compute the FNV-1a 32bit hash of a string - without allocating the utf8 encoding.
|
|
39
|
+
*
|
|
40
|
+
* Equivalent to `digest(string.encodeUtf8(str))`: the string is hashed as utf8 bytes; lone
|
|
41
|
+
* surrogates are hashed as the replacement character (like TextEncoder encodes them).
|
|
42
|
+
*
|
|
43
|
+
* Prefer this over `digest(string.encodeUtf8(str))` when hashing strings - it skips the
|
|
44
|
+
* TextEncoder call and the Uint8Array allocation, making it ~5x faster for small strings
|
|
45
|
+
* (<=20 chars, e.g. object keys) and ~2x faster for larger ones.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} str
|
|
48
|
+
* @param {number} hash - continue hashing from a previous result (defaults to the offset basis)
|
|
49
|
+
* @return {number} unsigned 32bit hash
|
|
50
|
+
*/
|
|
51
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
52
|
+
export const digestString = (str, hash = offsetBasis) => {
|
|
53
|
+
for (let i = 0; i < str.length; i++) {
|
|
54
|
+
let c = str.charCodeAt(i)
|
|
55
|
+
if (c < 0x80) {
|
|
56
|
+
hash = math.imul(hash ^ c, prime)
|
|
57
|
+
} else {
|
|
58
|
+
if (c >= 0xd800 && c < 0xe000) {
|
|
59
|
+
const lo = str.charCodeAt(i + 1) // NaN when out of bounds ⇒ NaN & x === 0
|
|
60
|
+
if (c < 0xdc00 && (lo & 0xfc00) === 0xdc00) {
|
|
61
|
+
c = 0x10000 + ((c & 0x3ff) << 10) + (lo & 0x3ff)
|
|
62
|
+
i++
|
|
63
|
+
} else {
|
|
64
|
+
c = 0xfffd // lone surrogate ⇒ replacement character
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (c < 0x800) {
|
|
68
|
+
hash = math.imul(hash ^ (0xc0 | (c >> 6)), prime)
|
|
69
|
+
} else {
|
|
70
|
+
if (c < 0x10000) {
|
|
71
|
+
hash = math.imul(hash ^ (0xe0 | (c >> 12)), prime)
|
|
72
|
+
} else {
|
|
73
|
+
hash = math.imul(hash ^ (0xf0 | (c >> 18)), prime)
|
|
74
|
+
hash = math.imul(hash ^ (0x80 | ((c >> 12) & 0x3f)), prime)
|
|
75
|
+
}
|
|
76
|
+
hash = math.imul(hash ^ (0x80 | ((c >> 6) & 0x3f)), prime)
|
|
77
|
+
}
|
|
78
|
+
hash = math.imul(hash ^ (0x80 | (c & 0x3f)), prime)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return hash >>> 0
|
|
82
|
+
}
|