@player-ui/player 0.13.0 → 0.14.0-next.0
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/Player.native.js +117 -115
- package/dist/Player.native.js.map +1 -1
- package/dist/cjs/index.cjs +75 -116
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +75 -116
- package/dist/index.mjs +75 -116
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/binding/__tests__/resolver.test.ts +5 -0
- package/src/binding/resolver.ts +5 -1
- package/src/binding-grammar/__tests__/parser.test.ts +0 -32
- package/src/binding-grammar/__tests__/test-utils/ast-cases.ts +31 -0
- package/src/binding-grammar/ast.ts +2 -2
- package/src/binding-grammar/custom/index.ts +17 -9
- package/src/view/__tests__/view.test.ts +61 -1
- package/src/view/builder/index.ts +6 -1
- package/src/view/parser/index.ts +45 -33
- package/src/view/parser/types.ts +5 -0
- package/src/view/plugins/__tests__/multi-node.test.ts +36 -0
- package/src/view/plugins/multi-node.ts +14 -14
- package/src/view/resolver/__tests__/index.test.ts +153 -0
- package/src/view/resolver/index.ts +109 -157
- package/src/view/view.ts +2 -2
- package/types/binding-grammar/ast.d.ts +2 -2
- package/types/view/builder/index.d.ts +1 -1
- package/types/view/parser/index.d.ts +38 -22
- package/types/view/parser/types.d.ts +4 -0
- package/types/view/resolver/index.d.ts +37 -25
- package/types/view/view.d.ts +1 -1
|
@@ -52,59 +52,66 @@ const withContext = (model: DataModelWithParser): DataModelWithParser => {
|
|
|
52
52
|
};
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
export type ResolverHooks = {
|
|
56
|
+
/** A hook to allow skipping of the resolution tree for a specific node */
|
|
57
|
+
skipResolve: SyncWaterfallHook<
|
|
58
|
+
[boolean, Node.Node, Resolve.NodeResolveOptions]
|
|
59
|
+
>;
|
|
60
|
+
|
|
61
|
+
/** An event emitted before calculating the next update */
|
|
62
|
+
beforeUpdate: SyncHook<[Set<BindingInstance> | undefined]>;
|
|
63
|
+
|
|
64
|
+
/** An event emitted after calculating the next update */
|
|
65
|
+
afterUpdate: SyncHook<[any]>;
|
|
66
|
+
|
|
67
|
+
/** The options passed to a node to resolve it to an object */
|
|
68
|
+
resolveOptions: SyncWaterfallHook<[Resolve.NodeResolveOptions, Node.Node]>;
|
|
69
|
+
|
|
70
|
+
/** A hook to transform the AST node into a new AST node before resolving it */
|
|
71
|
+
beforeResolve: SyncWaterfallHook<
|
|
72
|
+
[Node.Node | null, Resolve.NodeResolveOptions]
|
|
73
|
+
>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A hook to transform an AST node into it's resolved value.
|
|
77
|
+
* This runs _before_ any children are resolved
|
|
78
|
+
*/
|
|
79
|
+
resolve: SyncWaterfallHook<[any, Node.Node, Resolve.NodeResolveOptions]>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A hook to transform the resolved value of an AST node.
|
|
83
|
+
* This runs _after_ all children nodes are resolved
|
|
84
|
+
*/
|
|
85
|
+
afterResolve: SyncWaterfallHook<[any, Node.Node, Resolve.NodeResolveOptions]>;
|
|
86
|
+
|
|
87
|
+
/** Called at the very end of a node's tree being updated */
|
|
88
|
+
afterNodeUpdate: SyncHook<[Node.Node, Node.Node | undefined, NodeUpdate]>;
|
|
89
|
+
};
|
|
90
|
+
|
|
55
91
|
/**
|
|
56
92
|
* The Resolver is the way to take a parsed AST graph of a view and resolve it to a concrete representation of the current user state
|
|
57
93
|
* It combines the ability to mutate ast nodes before resolving, as well as the mutating the resolved objects while parsing
|
|
58
94
|
*/
|
|
59
95
|
export class Resolver {
|
|
60
|
-
public readonly hooks = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/** An event emitted after calculating the next update */
|
|
70
|
-
afterUpdate: new SyncHook<[any]>(),
|
|
71
|
-
|
|
72
|
-
/** The options passed to a node to resolve it to an object */
|
|
73
|
-
resolveOptions: new SyncWaterfallHook<
|
|
74
|
-
[Resolve.NodeResolveOptions, Node.Node]
|
|
75
|
-
>(),
|
|
76
|
-
|
|
77
|
-
/** A hook to transform the AST node into a new AST node before resolving it */
|
|
78
|
-
beforeResolve: new SyncWaterfallHook<
|
|
79
|
-
[Node.Node | null, Resolve.NodeResolveOptions]
|
|
80
|
-
>(),
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* A hook to transform an AST node into it's resolved value.
|
|
84
|
-
* This runs _before_ any children are resolved
|
|
85
|
-
*/
|
|
86
|
-
resolve: new SyncWaterfallHook<
|
|
87
|
-
[any, Node.Node, Resolve.NodeResolveOptions]
|
|
88
|
-
>(),
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* A hook to transform the resolved value of an AST node.
|
|
92
|
-
* This runs _after_ all children nodes are resolved
|
|
93
|
-
*/
|
|
94
|
-
afterResolve: new SyncWaterfallHook<
|
|
95
|
-
[any, Node.Node, Resolve.NodeResolveOptions]
|
|
96
|
-
>(),
|
|
97
|
-
|
|
98
|
-
/** Called at the very end of a node's tree being updated */
|
|
99
|
-
afterNodeUpdate: new SyncHook<
|
|
100
|
-
[Node.Node, Node.Node | undefined, NodeUpdate]
|
|
101
|
-
>(),
|
|
96
|
+
public readonly hooks: ResolverHooks = {
|
|
97
|
+
skipResolve: new SyncWaterfallHook(),
|
|
98
|
+
beforeUpdate: new SyncHook(),
|
|
99
|
+
afterUpdate: new SyncHook(),
|
|
100
|
+
resolveOptions: new SyncWaterfallHook(),
|
|
101
|
+
beforeResolve: new SyncWaterfallHook(),
|
|
102
|
+
resolve: new SyncWaterfallHook(),
|
|
103
|
+
afterResolve: new SyncWaterfallHook(),
|
|
104
|
+
afterNodeUpdate: new SyncHook(),
|
|
102
105
|
};
|
|
103
106
|
|
|
104
107
|
/**
|
|
105
108
|
* The AST tree after beforeResolve is ran mapped to the AST before beforeResolve is ran
|
|
106
109
|
*/
|
|
107
110
|
private readonly ASTMap: Map<Node.Node, Node.Node>;
|
|
111
|
+
/**
|
|
112
|
+
* The AST tree after beforeResolve is ran mapped to the AST before beforeResolve is ran
|
|
113
|
+
*/
|
|
114
|
+
private AsyncIdMap: Map<string, Node.Node>;
|
|
108
115
|
/**
|
|
109
116
|
* The root node in the AST tree we want to resolve
|
|
110
117
|
*/
|
|
@@ -138,19 +145,36 @@ export class Resolver {
|
|
|
138
145
|
this.ASTMap = new Map();
|
|
139
146
|
this.logger = options.logger;
|
|
140
147
|
this.idCache = new Set();
|
|
148
|
+
this.AsyncIdMap = new Map();
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
public getSourceNode(convertedAST: Node.Node) {
|
|
151
|
+
public getSourceNode(convertedAST: Node.Node): Node.Node | undefined {
|
|
144
152
|
return this.ASTMap.get(convertedAST);
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
public update(
|
|
155
|
+
public update(
|
|
156
|
+
changes?: Set<BindingInstance>,
|
|
157
|
+
asyncChanges?: Set<string>,
|
|
158
|
+
): any {
|
|
148
159
|
this.hooks.beforeUpdate.call(changes);
|
|
149
160
|
const resolveCache = new Map<Node.Node, Resolve.ResolvedNode>();
|
|
150
161
|
this.idCache.clear();
|
|
151
162
|
const prevASTMap = new Map(this.ASTMap);
|
|
152
163
|
this.ASTMap.clear();
|
|
153
164
|
|
|
165
|
+
const prevAsyncIdMap = new Map(this.AsyncIdMap);
|
|
166
|
+
const nextAsyncIdMap = new Map<string, Node.Node>();
|
|
167
|
+
asyncChanges?.forEach((id) => {
|
|
168
|
+
let current: Node.Node | undefined = prevAsyncIdMap.get(id);
|
|
169
|
+
while (current && prevASTMap.has(current)) {
|
|
170
|
+
const next = prevASTMap.get(current);
|
|
171
|
+
if (next && this.resolveCache.has(next)) {
|
|
172
|
+
this.resolveCache.delete(next);
|
|
173
|
+
}
|
|
174
|
+
current = current.parent;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
154
178
|
const updated = this.computeTree(
|
|
155
179
|
this.root,
|
|
156
180
|
undefined,
|
|
@@ -159,13 +183,15 @@ export class Resolver {
|
|
|
159
183
|
toNodeResolveOptions(this.options),
|
|
160
184
|
undefined,
|
|
161
185
|
prevASTMap,
|
|
186
|
+
nextAsyncIdMap,
|
|
162
187
|
);
|
|
188
|
+
this.AsyncIdMap = nextAsyncIdMap;
|
|
163
189
|
this.resolveCache = resolveCache;
|
|
164
190
|
this.hooks.afterUpdate.call(updated.value);
|
|
165
191
|
return updated.value;
|
|
166
192
|
}
|
|
167
193
|
|
|
168
|
-
public getResolveCache() {
|
|
194
|
+
public getResolveCache(): Map<Node.Node, Resolve.ResolvedNode> {
|
|
169
195
|
return new Map(this.resolveCache);
|
|
170
196
|
}
|
|
171
197
|
|
|
@@ -226,6 +252,7 @@ export class Resolver {
|
|
|
226
252
|
options: Resolve.NodeResolveOptions,
|
|
227
253
|
partiallyResolvedParent: Node.Node | undefined,
|
|
228
254
|
prevASTMap: Map<Node.Node, Node.Node>,
|
|
255
|
+
nextAsyncIdMap: Map<string, Node.Node>,
|
|
229
256
|
): NodeUpdate {
|
|
230
257
|
const dependencyModel = new DependencyModel(options.data.model);
|
|
231
258
|
|
|
@@ -258,31 +285,6 @@ export class Resolver {
|
|
|
258
285
|
resolveOptions,
|
|
259
286
|
);
|
|
260
287
|
|
|
261
|
-
// Shallow clone the node so that changes to it during the resolve steps don't impact the original.
|
|
262
|
-
// We are trusting that this becomes a deep clone once the whole node tree has been traversed.
|
|
263
|
-
const clonedNode = {
|
|
264
|
-
...this.cloneNode(node),
|
|
265
|
-
parent: partiallyResolvedParent,
|
|
266
|
-
};
|
|
267
|
-
const resolvedAST = this.hooks.beforeResolve.call(
|
|
268
|
-
clonedNode,
|
|
269
|
-
resolveOptions,
|
|
270
|
-
) ?? {
|
|
271
|
-
type: NodeType.Empty,
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
const isNestedMultiNodeWithAsync =
|
|
275
|
-
resolvedAST.type === NodeType.MultiNode &&
|
|
276
|
-
partiallyResolvedParent?.parent?.parent?.type === NodeType.MultiNode &&
|
|
277
|
-
partiallyResolvedParent.parent.type === NodeType.Value &&
|
|
278
|
-
resolvedAST.parent?.type === NodeType.Asset &&
|
|
279
|
-
resolvedAST.parent.value.id.includes("async");
|
|
280
|
-
|
|
281
|
-
const isNestedMultiNode =
|
|
282
|
-
resolvedAST.type === NodeType.MultiNode &&
|
|
283
|
-
partiallyResolvedParent?.parent?.type === NodeType.MultiNode &&
|
|
284
|
-
partiallyResolvedParent.type === NodeType.Value;
|
|
285
|
-
|
|
286
288
|
if (previousResult && shouldUseLastValue) {
|
|
287
289
|
const update = {
|
|
288
290
|
...previousResult,
|
|
@@ -302,6 +304,12 @@ export class Resolver {
|
|
|
302
304
|
updated: false,
|
|
303
305
|
};
|
|
304
306
|
cacheUpdate.set(AST, resolvedUpdate);
|
|
307
|
+
if (resolvedUpdate.node.type === NodeType.Async) {
|
|
308
|
+
nextAsyncIdMap.set(resolvedUpdate.node.id, resolvedUpdate.node);
|
|
309
|
+
}
|
|
310
|
+
for (const key of resolvedUpdate.node.asyncNodesResolved ?? []) {
|
|
311
|
+
nextAsyncIdMap.set(key, resolvedUpdate.node);
|
|
312
|
+
}
|
|
305
313
|
|
|
306
314
|
/** Helper function for recursing over child node */
|
|
307
315
|
const handleChildNode = (childNode: Node.Node) => {
|
|
@@ -336,10 +344,26 @@ export class Resolver {
|
|
|
336
344
|
return update;
|
|
337
345
|
}
|
|
338
346
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
347
|
+
// Shallow clone the node so that changes to it during the resolve steps don't impact the original.
|
|
348
|
+
// We are trusting that this becomes a deep clone once the whole node tree has been traversed.
|
|
349
|
+
const clonedNode: Node.Node = {
|
|
350
|
+
...this.cloneNode(node),
|
|
351
|
+
parent: partiallyResolvedParent,
|
|
352
|
+
};
|
|
353
|
+
const resolvedAST = this.hooks.beforeResolve.call(
|
|
354
|
+
clonedNode,
|
|
355
|
+
resolveOptions,
|
|
356
|
+
) ?? {
|
|
357
|
+
type: NodeType.Empty,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
resolvedAST.parent = partiallyResolvedParent;
|
|
361
|
+
|
|
362
|
+
if (resolvedAST.type === NodeType.Async) {
|
|
363
|
+
nextAsyncIdMap.set(resolvedAST.id, resolvedAST);
|
|
364
|
+
}
|
|
365
|
+
for (const id of resolvedAST.asyncNodesResolved ?? []) {
|
|
366
|
+
nextAsyncIdMap.set(id, resolvedAST);
|
|
343
367
|
}
|
|
344
368
|
|
|
345
369
|
resolveOptions.node = resolvedAST;
|
|
@@ -371,6 +395,7 @@ export class Resolver {
|
|
|
371
395
|
resolveOptions,
|
|
372
396
|
resolvedAST,
|
|
373
397
|
prevASTMap,
|
|
398
|
+
nextAsyncIdMap,
|
|
374
399
|
);
|
|
375
400
|
const {
|
|
376
401
|
dependencies: childTreeDeps,
|
|
@@ -401,15 +426,9 @@ export class Resolver {
|
|
|
401
426
|
resolvedAST.children = newChildren;
|
|
402
427
|
} else if (resolvedAST.type === NodeType.MultiNode) {
|
|
403
428
|
const childValue: any = [];
|
|
404
|
-
const rawParentToPassIn =
|
|
405
|
-
? partiallyResolvedParent?.parent
|
|
406
|
-
: node;
|
|
429
|
+
const rawParentToPassIn = node;
|
|
407
430
|
|
|
408
|
-
|
|
409
|
-
.map((value, index) => (value.type === NodeType.Async ? index : -1))
|
|
410
|
-
.filter((index) => index !== -1);
|
|
411
|
-
|
|
412
|
-
const newValues = resolvedAST.values.map((mValue) => {
|
|
431
|
+
resolvedAST.values = resolvedAST.values.map((mValue) => {
|
|
413
432
|
const mTree = this.computeTree(
|
|
414
433
|
mValue,
|
|
415
434
|
rawParentToPassIn,
|
|
@@ -418,47 +437,21 @@ export class Resolver {
|
|
|
418
437
|
resolveOptions,
|
|
419
438
|
resolvedAST,
|
|
420
439
|
prevASTMap,
|
|
440
|
+
nextAsyncIdMap,
|
|
421
441
|
);
|
|
422
442
|
|
|
423
443
|
if (mTree.value !== undefined && mTree.value !== null) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
* Add the content streamed in to the childValue of parent multi-node
|
|
428
|
-
* Array.isArray(mTree.value.asset.values) is the case when the content is an async asset
|
|
429
|
-
*/
|
|
430
|
-
if (
|
|
431
|
-
mValue.type === NodeType.Async &&
|
|
432
|
-
mValue.flatten &&
|
|
433
|
-
mTree.value.asset &&
|
|
434
|
-
Array.isArray(mTree.value.asset.values)
|
|
435
|
-
) {
|
|
436
|
-
// This flatten function only changed the values not node structure
|
|
437
|
-
unpackAndPush(mTree.value, childValue);
|
|
438
|
-
} else {
|
|
439
|
-
childValue.push(mTree.value);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
mTree.dependencies.forEach((bindingDep) =>
|
|
444
|
-
childDependencies.add(bindingDep),
|
|
445
|
-
);
|
|
444
|
+
mTree.dependencies.forEach((bindingDep) =>
|
|
445
|
+
childDependencies.add(bindingDep),
|
|
446
|
+
);
|
|
446
447
|
|
|
447
|
-
|
|
448
|
+
updated = updated || mTree.updated;
|
|
449
|
+
childValue.push(mTree.value);
|
|
450
|
+
}
|
|
448
451
|
|
|
449
452
|
return mTree.node;
|
|
450
453
|
});
|
|
451
454
|
|
|
452
|
-
if (hasAsync.length > 0) {
|
|
453
|
-
// this likely turned into a nested multinode, attempt to flatten in node structure
|
|
454
|
-
const copy = newValues;
|
|
455
|
-
hasAsync.forEach((index) => {
|
|
456
|
-
if (copy[index]) copy.splice(index, 1, ...unpackNode(copy[index]));
|
|
457
|
-
});
|
|
458
|
-
resolvedAST.values = copy;
|
|
459
|
-
} else {
|
|
460
|
-
resolvedAST.values = newValues;
|
|
461
|
-
}
|
|
462
455
|
resolved = childValue;
|
|
463
456
|
}
|
|
464
457
|
|
|
@@ -487,50 +480,9 @@ export class Resolver {
|
|
|
487
480
|
]),
|
|
488
481
|
};
|
|
489
482
|
|
|
490
|
-
this.hooks.afterNodeUpdate.call(
|
|
491
|
-
node,
|
|
492
|
-
isNestedMultiNode ? partiallyResolvedParent?.parent : rawParent,
|
|
493
|
-
update,
|
|
494
|
-
);
|
|
483
|
+
this.hooks.afterNodeUpdate.call(node, rawParent, update);
|
|
495
484
|
cacheUpdate.set(node, update);
|
|
496
485
|
|
|
497
486
|
return update;
|
|
498
487
|
}
|
|
499
488
|
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* helper function to flatten a potential nested array and combine with initial array
|
|
503
|
-
*/
|
|
504
|
-
function unpackAndPush(item: any | any[], initial: any[]): void {
|
|
505
|
-
if (item.asset.values && Array.isArray(item.asset.values)) {
|
|
506
|
-
item.asset.values.forEach((i: any) => {
|
|
507
|
-
unpackAndPush(i, initial);
|
|
508
|
-
});
|
|
509
|
-
} else {
|
|
510
|
-
initial.push(item);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function unpackNode(item: Node.Node) {
|
|
515
|
-
const unpacked: Node.Node[] = [];
|
|
516
|
-
if (
|
|
517
|
-
"children" in item &&
|
|
518
|
-
item.children?.[0]?.value.type === NodeType.Asset &&
|
|
519
|
-
(item.children?.[0]?.value as Node.Asset).children
|
|
520
|
-
) {
|
|
521
|
-
if (
|
|
522
|
-
(item.children?.[0]?.value as Node.Asset).children?.[0]?.value.type ===
|
|
523
|
-
NodeType.MultiNode
|
|
524
|
-
) {
|
|
525
|
-
(
|
|
526
|
-
(item.children?.[0]?.value as Node.Asset).children?.[0]
|
|
527
|
-
?.value as Node.MultiNode
|
|
528
|
-
).values.forEach((value) => {
|
|
529
|
-
unpacked.push(value);
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
} else {
|
|
533
|
-
unpacked.push(item);
|
|
534
|
-
}
|
|
535
|
-
return unpacked;
|
|
536
|
-
}
|
package/src/view/view.ts
CHANGED
|
@@ -109,8 +109,8 @@ export class ViewInstance implements ValidationProvider {
|
|
|
109
109
|
this.resolverOptions = resolverOptions;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
public updateAsync(): void {
|
|
113
|
-
const update = this.resolver?.update();
|
|
112
|
+
public updateAsync(asyncNode: string): void {
|
|
113
|
+
const update = this.resolver?.update(new Set(), new Set([asyncNode]));
|
|
114
114
|
this.lastUpdate = update;
|
|
115
115
|
this.hooks.onUpdate.call(update);
|
|
116
116
|
}
|
|
@@ -23,7 +23,7 @@ export interface QueryNode extends Node<"Query"> {
|
|
|
23
23
|
/** A simple segment */
|
|
24
24
|
export interface ValueNode extends Node<"Value"> {
|
|
25
25
|
/** The segment value */
|
|
26
|
-
value: string | number;
|
|
26
|
+
value: string | number | boolean;
|
|
27
27
|
}
|
|
28
28
|
/** A nested expression */
|
|
29
29
|
export interface ExpressionNode extends Node<"Expression"> {
|
|
@@ -31,7 +31,7 @@ export interface ExpressionNode extends Node<"Expression"> {
|
|
|
31
31
|
value: string;
|
|
32
32
|
}
|
|
33
33
|
/** Helper to create a value node */
|
|
34
|
-
export declare const toValue: (value: string | number) => ValueNode;
|
|
34
|
+
export declare const toValue: (value: string | number | boolean) => ValueNode;
|
|
35
35
|
/** Helper to create an expression node */
|
|
36
36
|
export declare const toExpression: (value: string) => ExpressionNode;
|
|
37
37
|
/** Helper to create a nested path node */
|
|
@@ -29,7 +29,7 @@ export declare class Builder {
|
|
|
29
29
|
*
|
|
30
30
|
* @param id - the id of async node. It should be identical for each async node
|
|
31
31
|
*/
|
|
32
|
-
static asyncNode(id: string, flatten?: boolean): Node.Async;
|
|
32
|
+
static asyncNode(id: string, flatten?: boolean, onValueReceived?: (node: Node.Node) => Node.Node): Node.Async;
|
|
33
33
|
/**
|
|
34
34
|
* Adds a child node to a node
|
|
35
35
|
*
|
|
@@ -13,33 +13,49 @@ export interface ParseObjectChildOptions {
|
|
|
13
13
|
path: Node.PathSegment[];
|
|
14
14
|
parentObj: object;
|
|
15
15
|
}
|
|
16
|
+
export type ParserHooks = {
|
|
17
|
+
/**
|
|
18
|
+
* A hook to interact with an object _before_ parsing it into an AST
|
|
19
|
+
*
|
|
20
|
+
* @param value - The object we're are about to parse
|
|
21
|
+
* @returns - A new value to parse.
|
|
22
|
+
* If undefined, the original value is used.
|
|
23
|
+
* If null, we stop parsing this node.
|
|
24
|
+
*/
|
|
25
|
+
onParseObject: SyncWaterfallHook<[object, NodeType]>;
|
|
26
|
+
/**
|
|
27
|
+
* A callback to interact with an AST _after_ we parse it into the AST
|
|
28
|
+
*
|
|
29
|
+
* @param value - The object we parsed
|
|
30
|
+
* @param node - The AST node we generated
|
|
31
|
+
* @returns - A new AST node to use
|
|
32
|
+
* If undefined, the original value is used.
|
|
33
|
+
* If null, we ignore this node all together
|
|
34
|
+
*/
|
|
35
|
+
onCreateASTNode: SyncWaterfallHook<[Node.Node | undefined | null, object]>;
|
|
36
|
+
/** A hook to call when parsing an object into an AST node
|
|
37
|
+
*
|
|
38
|
+
* @param obj - The object we're are about to parse
|
|
39
|
+
* @param nodeType - The type of node we're parsing
|
|
40
|
+
* @param parseOptions - Additional options when parsing
|
|
41
|
+
* @param childOptions - Additional options that are populated when the node being parsed is a child of another node
|
|
42
|
+
* @returns - A new AST node to use
|
|
43
|
+
* If undefined, the original value is used.
|
|
44
|
+
* If null, we ignore this node all together
|
|
45
|
+
*/
|
|
46
|
+
parseNode: SyncBailHook<[
|
|
47
|
+
obj: object,
|
|
48
|
+
nodeType: Node.ChildrenTypes,
|
|
49
|
+
parseOptions: ParseObjectOptions,
|
|
50
|
+
childOptions?: ParseObjectChildOptions
|
|
51
|
+
], Node.Node | Node.Child[]>;
|
|
52
|
+
};
|
|
16
53
|
/**
|
|
17
54
|
* The Parser is the way to take an incoming view from the user and parse it into an AST.
|
|
18
55
|
* It provides a few ways to interact with the parsing, including mutating an object before and after creation of an AST node
|
|
19
56
|
*/
|
|
20
57
|
export declare class Parser {
|
|
21
|
-
readonly hooks:
|
|
22
|
-
/**
|
|
23
|
-
* A hook to interact with an object _before_ parsing it into an AST
|
|
24
|
-
*
|
|
25
|
-
* @param value - The object we're are about to parse
|
|
26
|
-
* @returns - A new value to parse.
|
|
27
|
-
* If undefined, the original value is used.
|
|
28
|
-
* If null, we stop parsing this node.
|
|
29
|
-
*/
|
|
30
|
-
onParseObject: SyncWaterfallHook<[object, NodeType], Record<string, any>>;
|
|
31
|
-
/**
|
|
32
|
-
* A callback to interact with an AST _after_ we parse it into the AST
|
|
33
|
-
*
|
|
34
|
-
* @param value - The object we parsed
|
|
35
|
-
* @param node - The AST node we generated
|
|
36
|
-
* @returns - A new AST node to use
|
|
37
|
-
* If undefined, the original value is used.
|
|
38
|
-
* If null, we ignore this node all together
|
|
39
|
-
*/
|
|
40
|
-
onCreateASTNode: SyncWaterfallHook<[Node.Node | null | undefined, object], Record<string, any>>;
|
|
41
|
-
parseNode: SyncBailHook<[obj: object, nodeType: Node.ChildrenTypes, parseOptions: ParseObjectOptions, childOptions?: ParseObjectChildOptions | undefined], Node.Node | Node.Child[], Record<string, any>>;
|
|
42
|
-
};
|
|
58
|
+
readonly hooks: ParserHooks;
|
|
43
59
|
parseView(value: AnyAssetType): Node.View;
|
|
44
60
|
createASTNode(node: Node.Node | null, value: any): Node.Node | null;
|
|
45
61
|
parseObject(obj: object, type?: Node.ChildrenTypes, options?: ParseObjectOptions): Node.Node | null;
|
|
@@ -19,6 +19,8 @@ export declare namespace Node {
|
|
|
19
19
|
type: T;
|
|
20
20
|
/** Every node (outside of the root) contains a reference to it's parent */
|
|
21
21
|
parent?: Node;
|
|
22
|
+
/** The ids of async nodes resolved within this node */
|
|
23
|
+
asyncNodesResolved?: string[];
|
|
22
24
|
}
|
|
23
25
|
type PathSegment = string | number;
|
|
24
26
|
interface Child {
|
|
@@ -93,6 +95,8 @@ export declare namespace Node {
|
|
|
93
95
|
* Should the content streamed in be flattened during resolving
|
|
94
96
|
*/
|
|
95
97
|
flatten?: boolean;
|
|
98
|
+
/** Function to run against parsed content from the node to manipulate the content before resolving it. */
|
|
99
|
+
onValueReceived?: (node: Node.Node) => Node.Node;
|
|
96
100
|
}
|
|
97
101
|
interface PluginOptions {
|
|
98
102
|
/** A list of plugins */
|
|
@@ -8,39 +8,51 @@ interface NodeUpdate extends Resolve.ResolvedNode {
|
|
|
8
8
|
/** A flag to track if a node has changed since the last resolution */
|
|
9
9
|
updated: boolean;
|
|
10
10
|
}
|
|
11
|
+
export type ResolverHooks = {
|
|
12
|
+
/** A hook to allow skipping of the resolution tree for a specific node */
|
|
13
|
+
skipResolve: SyncWaterfallHook<[
|
|
14
|
+
boolean,
|
|
15
|
+
Node.Node,
|
|
16
|
+
Resolve.NodeResolveOptions
|
|
17
|
+
]>;
|
|
18
|
+
/** An event emitted before calculating the next update */
|
|
19
|
+
beforeUpdate: SyncHook<[Set<BindingInstance> | undefined]>;
|
|
20
|
+
/** An event emitted after calculating the next update */
|
|
21
|
+
afterUpdate: SyncHook<[any]>;
|
|
22
|
+
/** The options passed to a node to resolve it to an object */
|
|
23
|
+
resolveOptions: SyncWaterfallHook<[Resolve.NodeResolveOptions, Node.Node]>;
|
|
24
|
+
/** A hook to transform the AST node into a new AST node before resolving it */
|
|
25
|
+
beforeResolve: SyncWaterfallHook<[
|
|
26
|
+
Node.Node | null,
|
|
27
|
+
Resolve.NodeResolveOptions
|
|
28
|
+
]>;
|
|
29
|
+
/**
|
|
30
|
+
* A hook to transform an AST node into it's resolved value.
|
|
31
|
+
* This runs _before_ any children are resolved
|
|
32
|
+
*/
|
|
33
|
+
resolve: SyncWaterfallHook<[any, Node.Node, Resolve.NodeResolveOptions]>;
|
|
34
|
+
/**
|
|
35
|
+
* A hook to transform the resolved value of an AST node.
|
|
36
|
+
* This runs _after_ all children nodes are resolved
|
|
37
|
+
*/
|
|
38
|
+
afterResolve: SyncWaterfallHook<[any, Node.Node, Resolve.NodeResolveOptions]>;
|
|
39
|
+
/** Called at the very end of a node's tree being updated */
|
|
40
|
+
afterNodeUpdate: SyncHook<[Node.Node, Node.Node | undefined, NodeUpdate]>;
|
|
41
|
+
};
|
|
11
42
|
/**
|
|
12
43
|
* The Resolver is the way to take a parsed AST graph of a view and resolve it to a concrete representation of the current user state
|
|
13
44
|
* It combines the ability to mutate ast nodes before resolving, as well as the mutating the resolved objects while parsing
|
|
14
45
|
*/
|
|
15
46
|
export declare class Resolver {
|
|
16
|
-
readonly hooks:
|
|
17
|
-
/** A hook to allow skipping of the resolution tree for a specific node */
|
|
18
|
-
skipResolve: SyncWaterfallHook<[boolean, Node.Node, Resolve.NodeResolveOptions], Record<string, any>>;
|
|
19
|
-
/** An event emitted before calculating the next update */
|
|
20
|
-
beforeUpdate: SyncHook<[Set<BindingInstance> | undefined], Record<string, any>>;
|
|
21
|
-
/** An event emitted after calculating the next update */
|
|
22
|
-
afterUpdate: SyncHook<[any], Record<string, any>>;
|
|
23
|
-
/** The options passed to a node to resolve it to an object */
|
|
24
|
-
resolveOptions: SyncWaterfallHook<[Resolve.NodeResolveOptions, Node.Node], Record<string, any>>;
|
|
25
|
-
/** A hook to transform the AST node into a new AST node before resolving it */
|
|
26
|
-
beforeResolve: SyncWaterfallHook<[Node.Node | null, Resolve.NodeResolveOptions], Record<string, any>>;
|
|
27
|
-
/**
|
|
28
|
-
* A hook to transform an AST node into it's resolved value.
|
|
29
|
-
* This runs _before_ any children are resolved
|
|
30
|
-
*/
|
|
31
|
-
resolve: SyncWaterfallHook<[any, Node.Node, Resolve.NodeResolveOptions], Record<string, any>>;
|
|
32
|
-
/**
|
|
33
|
-
* A hook to transform the resolved value of an AST node.
|
|
34
|
-
* This runs _after_ all children nodes are resolved
|
|
35
|
-
*/
|
|
36
|
-
afterResolve: SyncWaterfallHook<[any, Node.Node, Resolve.NodeResolveOptions], Record<string, any>>;
|
|
37
|
-
/** Called at the very end of a node's tree being updated */
|
|
38
|
-
afterNodeUpdate: SyncHook<[Node.Node, Node.Node | undefined, NodeUpdate], Record<string, any>>;
|
|
39
|
-
};
|
|
47
|
+
readonly hooks: ResolverHooks;
|
|
40
48
|
/**
|
|
41
49
|
* The AST tree after beforeResolve is ran mapped to the AST before beforeResolve is ran
|
|
42
50
|
*/
|
|
43
51
|
private readonly ASTMap;
|
|
52
|
+
/**
|
|
53
|
+
* The AST tree after beforeResolve is ran mapped to the AST before beforeResolve is ran
|
|
54
|
+
*/
|
|
55
|
+
private AsyncIdMap;
|
|
44
56
|
/**
|
|
45
57
|
* The root node in the AST tree we want to resolve
|
|
46
58
|
*/
|
|
@@ -64,7 +76,7 @@ export declare class Resolver {
|
|
|
64
76
|
private logger?;
|
|
65
77
|
constructor(root: Node.Node, options: Resolve.ResolverOptions);
|
|
66
78
|
getSourceNode(convertedAST: Node.Node): Node.Node | undefined;
|
|
67
|
-
update(changes?: Set<BindingInstance>): any;
|
|
79
|
+
update(changes?: Set<BindingInstance>, asyncChanges?: Set<string>): any;
|
|
68
80
|
getResolveCache(): Map<Node.Node, Resolve.ResolvedNode>;
|
|
69
81
|
private getPreviousResult;
|
|
70
82
|
private cloneNode;
|
package/types/view/view.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare class ViewInstance implements ValidationProvider {
|
|
|
27
27
|
private templatePlugin;
|
|
28
28
|
lastUpdate: Record<string, any> | undefined;
|
|
29
29
|
constructor(initialView: ViewType, resolverOptions: Resolve.ResolverOptions);
|
|
30
|
-
updateAsync(): void;
|
|
30
|
+
updateAsync(asyncNode: string): void;
|
|
31
31
|
update(changes?: Set<BindingInstance>): any;
|
|
32
32
|
getValidationsForBinding(binding: BindingInstance): Array<ValidationObject> | undefined;
|
|
33
33
|
setTemplatePlugin(plugin: TemplatePlugin): void;
|