@player-ui/player 0.3.0-next.2 → 0.3.0-next.4

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.
Files changed (77) hide show
  1. package/dist/index.cjs.js +4128 -891
  2. package/dist/index.d.ts +1227 -50
  3. package/dist/index.esm.js +4065 -836
  4. package/package.json +9 -15
  5. package/src/binding/binding.ts +108 -0
  6. package/src/binding/index.ts +188 -0
  7. package/src/binding/resolver.ts +157 -0
  8. package/src/binding/utils.ts +51 -0
  9. package/src/binding-grammar/ast.ts +113 -0
  10. package/src/binding-grammar/custom/index.ts +304 -0
  11. package/src/binding-grammar/ebnf/binding.ebnf +22 -0
  12. package/src/binding-grammar/ebnf/index.ts +186 -0
  13. package/src/binding-grammar/ebnf/types.ts +104 -0
  14. package/src/binding-grammar/index.ts +4 -0
  15. package/src/binding-grammar/parsimmon/index.ts +78 -0
  16. package/src/controllers/constants/index.ts +85 -0
  17. package/src/controllers/constants/utils.ts +37 -0
  18. package/src/{data.ts → controllers/data.ts} +6 -6
  19. package/src/controllers/flow/controller.ts +95 -0
  20. package/src/controllers/flow/flow.ts +205 -0
  21. package/src/controllers/flow/index.ts +2 -0
  22. package/src/controllers/index.ts +5 -0
  23. package/src/{validation → controllers/validation}/binding-tracker.ts +5 -5
  24. package/src/{validation → controllers/validation}/controller.ts +15 -14
  25. package/src/{validation → controllers/validation}/index.ts +0 -0
  26. package/src/{view → controllers/view}/asset-transform.ts +2 -3
  27. package/src/{view → controllers/view}/controller.ts +9 -8
  28. package/src/controllers/view/index.ts +4 -0
  29. package/src/{view → controllers/view}/store.ts +0 -0
  30. package/src/{view → controllers/view}/types.ts +2 -1
  31. package/src/data/dependency-tracker.ts +187 -0
  32. package/src/data/index.ts +4 -0
  33. package/src/data/local-model.ts +41 -0
  34. package/src/data/model.ts +216 -0
  35. package/src/data/noop-model.ts +18 -0
  36. package/src/expressions/evaluator-functions.ts +29 -0
  37. package/src/expressions/evaluator.ts +405 -0
  38. package/src/expressions/index.ts +3 -0
  39. package/src/expressions/parser.ts +889 -0
  40. package/src/expressions/types.ts +200 -0
  41. package/src/expressions/utils.ts +8 -0
  42. package/src/index.ts +9 -12
  43. package/src/logger/consoleLogger.ts +49 -0
  44. package/src/logger/index.ts +5 -0
  45. package/src/logger/noopLogger.ts +13 -0
  46. package/src/logger/proxyLogger.ts +25 -0
  47. package/src/logger/tapableLogger.ts +38 -0
  48. package/src/logger/types.ts +6 -0
  49. package/src/player.ts +21 -18
  50. package/src/plugins/flow-exp-plugin.ts +2 -3
  51. package/src/schema/index.ts +2 -0
  52. package/src/schema/schema.ts +220 -0
  53. package/src/schema/types.ts +60 -0
  54. package/src/string-resolver/index.ts +188 -0
  55. package/src/types.ts +11 -13
  56. package/src/utils/index.ts +1 -0
  57. package/src/utils/replaceParams.ts +17 -0
  58. package/src/validator/index.ts +3 -0
  59. package/src/validator/registry.ts +20 -0
  60. package/src/validator/types.ts +75 -0
  61. package/src/validator/validation-middleware.ts +114 -0
  62. package/src/view/builder/index.ts +81 -0
  63. package/src/view/index.ts +5 -4
  64. package/src/view/parser/index.ts +318 -0
  65. package/src/view/parser/types.ts +141 -0
  66. package/src/view/plugins/applicability.ts +78 -0
  67. package/src/view/plugins/index.ts +5 -0
  68. package/src/view/plugins/options.ts +4 -0
  69. package/src/view/plugins/plugin.ts +21 -0
  70. package/src/view/plugins/string-resolver.ts +149 -0
  71. package/src/view/plugins/switch.ts +120 -0
  72. package/src/view/plugins/template-plugin.ts +172 -0
  73. package/src/view/resolver/index.ts +397 -0
  74. package/src/view/resolver/types.ts +161 -0
  75. package/src/view/resolver/utils.ts +57 -0
  76. package/src/view/view.ts +149 -0
  77. package/src/utils/desc.d.ts +0 -2
@@ -0,0 +1,318 @@
1
+ import { setIn } from 'timm';
2
+ import { SyncBailHook, SyncWaterfallHook } from 'tapable-ts';
3
+ import type { Template } from '@player-ui/types';
4
+ import type { Node, AnyAssetType } from './types';
5
+ import { NodeType } from './types';
6
+
7
+ export * from './types';
8
+
9
+ export const EMPTY_NODE: Node.Empty = {
10
+ type: NodeType.Empty,
11
+ };
12
+
13
+ export interface ParseObjectOptions {
14
+ /** how nested the templated is */
15
+ templateDepth?: number;
16
+ }
17
+
18
+ interface NestedObj {
19
+ /** The values of a nested local object */
20
+ children: Node.Child[];
21
+
22
+ value: any;
23
+ }
24
+ /**
25
+ * The Parser is the way to take an incoming view from the user and parse it into an AST.
26
+ * It provides a few ways to interact with the parsing, including mutating an object before and after creation of an AST node
27
+ */
28
+ export class Parser {
29
+ public readonly hooks = {
30
+ /**
31
+ * A hook to interact with an object _before_ parsing it into an AST
32
+ *
33
+ * @param value - The object we're are about to parse
34
+ * @returns - A new value to parse.
35
+ * If undefined, the original value is used.
36
+ * If null, we stop parsing this node.
37
+ */
38
+ onParseObject: new SyncWaterfallHook<[object, NodeType]>(),
39
+
40
+ /**
41
+ * A callback to interact with an AST _after_ we parse it into the AST
42
+ *
43
+ * @param value - The object we parsed
44
+ * @param node - The AST node we generated
45
+ * @returns - A new AST node to use
46
+ * If undefined, the original value is used.
47
+ * If null, we ignore this node all together
48
+ */
49
+ onCreateASTNode: new SyncWaterfallHook<
50
+ [Node.Node | undefined | null, object]
51
+ >(),
52
+
53
+ determineNodeType: new SyncBailHook<[object | string], NodeType>(),
54
+
55
+ parseNode: new SyncBailHook<
56
+ [
57
+ obj: object,
58
+ nodeType: Node.ChildrenTypes,
59
+ parseOptions: ParseObjectOptions,
60
+ determinedNodeType: NodeType | null
61
+ ],
62
+ Node.Node
63
+ >(),
64
+ };
65
+
66
+ public parseView(value: AnyAssetType): Node.View {
67
+ const viewNode = this.parseObject(value, NodeType.View);
68
+
69
+ if (!viewNode) {
70
+ throw new Error('Unable to parse object into a view');
71
+ }
72
+
73
+ return viewNode as Node.View;
74
+ }
75
+
76
+ public createASTNode(node: Node.Node | null, value: any): Node.Node | null {
77
+ const tapped = this.hooks.onCreateASTNode.call(node, value);
78
+
79
+ if (tapped === undefined) {
80
+ return node;
81
+ }
82
+
83
+ return tapped;
84
+ }
85
+
86
+ public parseObject(
87
+ obj: object,
88
+ type: Node.ChildrenTypes = NodeType.Value,
89
+ options: ParseObjectOptions = { templateDepth: 0 }
90
+ ): Node.Node | null {
91
+ const nodeType = this.hooks.determineNodeType.call(obj);
92
+
93
+ if (nodeType !== undefined) {
94
+ const parsedNode = this.hooks.parseNode.call(
95
+ obj,
96
+ type,
97
+ options,
98
+ nodeType
99
+ );
100
+ if (parsedNode) {
101
+ return parsedNode;
102
+ }
103
+ }
104
+
105
+ const parseLocalObject = (
106
+ currentValue: any,
107
+ objToParse: unknown,
108
+ path: string[] = []
109
+ ): NestedObj => {
110
+ if (typeof objToParse !== 'object' || objToParse === null) {
111
+ // value = objToParse;
112
+ return { value: objToParse, children: [] };
113
+ }
114
+
115
+ const localObj = this.hooks.onParseObject.call(objToParse, type);
116
+
117
+ if (!localObj) {
118
+ return currentValue;
119
+ }
120
+
121
+ const objEntries = Array.isArray(localObj)
122
+ ? localObj.map((v, i) => [i, v])
123
+ : Object.entries(localObj);
124
+
125
+ const defaultValue: NestedObj = {
126
+ children: [],
127
+ value: currentValue,
128
+ };
129
+
130
+ const newValue = objEntries.reduce((accumulation, current): NestedObj => {
131
+ const { children, ...rest } = accumulation;
132
+ const [localKey, localValue] = current;
133
+ if (localKey === 'asset' && typeof localValue === 'object') {
134
+ const assetAST = this.parseObject(
135
+ localValue,
136
+ NodeType.Asset,
137
+ options
138
+ );
139
+
140
+ if (assetAST) {
141
+ return {
142
+ ...rest,
143
+ children: [
144
+ ...children,
145
+ {
146
+ path: [...path, 'asset'],
147
+ value: assetAST,
148
+ },
149
+ ],
150
+ };
151
+ }
152
+ } else if (
153
+ this.hooks.determineNodeType.call(localKey) === NodeType.Template &&
154
+ Array.isArray(localValue)
155
+ ) {
156
+ const templateChildren = localValue
157
+ .map((template: Template) => {
158
+ const templateAST = this.hooks.onCreateASTNode.call(
159
+ {
160
+ type: NodeType.Template,
161
+ depth: options.templateDepth ?? 0,
162
+ data: template.data,
163
+ template: template.value,
164
+ dynamic: template.dynamic ?? false,
165
+ },
166
+ template
167
+ );
168
+
169
+ if (templateAST) {
170
+ return {
171
+ path: [...path, template.output],
172
+ value: templateAST,
173
+ };
174
+ }
175
+
176
+ // eslint-disable-next-line no-useless-return
177
+ return;
178
+ })
179
+ .filter((element) => !!element);
180
+
181
+ return {
182
+ ...rest,
183
+ children: [...children, ...templateChildren],
184
+ } as NestedObj;
185
+ } else if (
186
+ localValue &&
187
+ this.hooks.determineNodeType.call(localValue) === NodeType.Switch
188
+ ) {
189
+ const localSwitch = this.hooks.parseNode.call(
190
+ localValue,
191
+ NodeType.Value,
192
+ options,
193
+ NodeType.Switch
194
+ );
195
+
196
+ if (localSwitch) {
197
+ return {
198
+ ...rest,
199
+ children: [
200
+ ...children,
201
+ {
202
+ path: [...path, localKey],
203
+ value: localSwitch,
204
+ },
205
+ ],
206
+ };
207
+ }
208
+ } else if (localValue && Array.isArray(localValue)) {
209
+ const childValues = localValue
210
+ .map((childVal) =>
211
+ this.parseObject(childVal, NodeType.Value, options)
212
+ )
213
+ .filter((child): child is Node.Node => !!child);
214
+
215
+ if (childValues.length > 0) {
216
+ const multiNode = this.hooks.onCreateASTNode.call(
217
+ {
218
+ type: NodeType.MultiNode,
219
+ override: true,
220
+ values: childValues,
221
+ },
222
+ localValue
223
+ );
224
+
225
+ if (multiNode?.type === NodeType.MultiNode) {
226
+ multiNode.values.forEach((v) => {
227
+ // eslint-disable-next-line no-param-reassign
228
+ v.parent = multiNode;
229
+ });
230
+ }
231
+
232
+ if (multiNode) {
233
+ return {
234
+ ...rest,
235
+ children: [
236
+ ...children,
237
+ {
238
+ path: [...path, localKey],
239
+ value: multiNode,
240
+ },
241
+ ],
242
+ };
243
+ }
244
+ }
245
+ } else if (localValue && typeof localValue === 'object') {
246
+ const determineNodeType =
247
+ this.hooks.determineNodeType.call(localValue);
248
+
249
+ if (determineNodeType === NodeType.Applicability) {
250
+ const parsedNode = this.hooks.parseNode.call(
251
+ localValue,
252
+ type,
253
+ options,
254
+ determineNodeType
255
+ );
256
+ if (parsedNode) {
257
+ return {
258
+ ...rest,
259
+ children: [
260
+ ...children,
261
+ {
262
+ path: [...path, localKey],
263
+ value: parsedNode,
264
+ },
265
+ ],
266
+ };
267
+ }
268
+ } else {
269
+ const result = parseLocalObject(accumulation.value, localValue, [
270
+ ...path,
271
+ localKey,
272
+ ]);
273
+ return {
274
+ value: result.value,
275
+ children: [...children, ...result.children],
276
+ };
277
+ }
278
+ } else {
279
+ const value = setIn(
280
+ accumulation.value,
281
+ [...path, localKey],
282
+ localValue
283
+ );
284
+
285
+ return {
286
+ children,
287
+ value,
288
+ };
289
+ }
290
+
291
+ return accumulation;
292
+ }, defaultValue);
293
+
294
+ return newValue;
295
+ };
296
+
297
+ const { value, children } = parseLocalObject(undefined, obj);
298
+
299
+ const baseAst =
300
+ value === undefined && children.length === 0
301
+ ? undefined
302
+ : {
303
+ type,
304
+ value,
305
+ };
306
+
307
+ if (baseAst !== undefined && children.length > 0) {
308
+ const parent = baseAst as Node.BaseWithChildren<any>;
309
+ parent.children = children;
310
+ children.forEach((child) => {
311
+ // eslint-disable-next-line no-param-reassign
312
+ child.value.parent = parent;
313
+ });
314
+ }
315
+
316
+ return this.hooks.onCreateASTNode.call(baseAst, obj) ?? null;
317
+ }
318
+ }
@@ -0,0 +1,141 @@
1
+ import type { Asset as AssetType, Expression, Binding } from '@player-ui/types';
2
+
3
+ export type AnyAssetType = AssetType<string>;
4
+ export enum NodeType {
5
+ Asset = 'asset',
6
+ View = 'view',
7
+ Applicability = 'applicability',
8
+ Template = 'template',
9
+ Value = 'value',
10
+ MultiNode = 'multi-node',
11
+ Switch = 'switch',
12
+ Unknown = 'unknown',
13
+ Empty = 'empty',
14
+ }
15
+ export declare namespace Node {
16
+ export type ChildrenTypes = NodeType.Asset | NodeType.Value | NodeType.View;
17
+
18
+ export interface Base<T extends NodeType> {
19
+ /** Every node contains a type to distinguish it from other nodes */
20
+ type: T;
21
+
22
+ /** Every node (outside of the root) contains a reference to it's parent */
23
+ parent?: Node;
24
+ }
25
+
26
+ export type PathSegment = string | number;
27
+
28
+ export interface Child {
29
+ /** The path of the child relative to the parent */
30
+ path: PathSegment[];
31
+
32
+ /** If true, the path points to an array, and the value will be appended to it result */
33
+ array?: boolean;
34
+
35
+ /** The child node */
36
+ value: Node;
37
+ }
38
+
39
+ export interface BaseWithChildren<T extends NodeType> extends Base<T> {
40
+ /** Any node that contains a list of children underneath it */
41
+ children?: Child[];
42
+ }
43
+
44
+ export interface Asset<T extends AnyAssetType = AnyAssetType>
45
+ extends BaseWithChildren<NodeType.Asset>,
46
+ PluginOptions {
47
+ /** Any asset nested within a view */
48
+ value: T;
49
+ }
50
+
51
+ export interface View<T extends AnyAssetType = AnyAssetType>
52
+ extends BaseWithChildren<NodeType.View>,
53
+ PluginOptions {
54
+ /** The root of the parsed view */
55
+ value: T;
56
+ }
57
+
58
+ export interface Applicability extends Base<NodeType.Applicability> {
59
+ /** The expression to execute that determines applicability of the target node */
60
+ expression: Expression;
61
+
62
+ /** The node to use if the expression is truthy */
63
+ value: Node;
64
+ }
65
+
66
+ export interface Template extends Base<NodeType.Template> {
67
+ /** The location of an array in the model */
68
+ data: Binding;
69
+
70
+ /** The template to use when mapping over the data */
71
+ template: unknown;
72
+
73
+ /** The number of nested templates so far */
74
+ depth: number;
75
+
76
+ /** should the template recomputed when data changes */
77
+ dynamic?: boolean;
78
+ }
79
+
80
+ export interface Value
81
+ extends BaseWithChildren<NodeType.Value>,
82
+ PluginOptions {
83
+ /** A simple node representing a value */
84
+ value: any;
85
+ }
86
+
87
+ export interface MultiNode extends Base<NodeType.MultiNode> {
88
+ /**
89
+ * Should this list override the target node if they overlap?
90
+ * If not amend the existing list
91
+ */
92
+ override?: boolean;
93
+
94
+ /** A list of values that comprise this node */
95
+ values: Array<Node>;
96
+ }
97
+
98
+ export interface Switch extends Base<NodeType.Switch> {
99
+ /** Should this list be re-computed when data changes */
100
+ dynamic?: boolean;
101
+
102
+ /** A list of cases to evaluate in order */
103
+ cases: SwitchCase[];
104
+ }
105
+
106
+ export interface SwitchCase {
107
+ /** The expression to evaluate for a single case statement */
108
+ case: Expression | true;
109
+ /** The value to use if this case is true */
110
+ value: Value;
111
+ }
112
+
113
+ export interface PluginOptions {
114
+ /** A list of plugins */
115
+ plugins?: {
116
+ /** StringResolverPlugin options */
117
+ stringResolver?: {
118
+ /**
119
+ * An optional array of node properties to skip during string resolution
120
+ * Specified in the AssetTransformPlugin
121
+ */
122
+ propertiesToSkip?: string[];
123
+ };
124
+ };
125
+ }
126
+
127
+ export type Unknown = Base<NodeType.Unknown>;
128
+ export type Empty = Base<NodeType.Empty>;
129
+ export type ViewOrAsset = View | Asset;
130
+
131
+ export type Node =
132
+ | Asset
133
+ | Applicability
134
+ | Template
135
+ | Value
136
+ | View
137
+ | MultiNode
138
+ | Switch
139
+ | Unknown
140
+ | Empty;
141
+ }
@@ -0,0 +1,78 @@
1
+ import { omit } from 'timm';
2
+ import type { ViewPlugin, View } from './plugin';
3
+ import type { Options } from './options';
4
+ import type { Resolver } from '../resolver';
5
+ import type { Node, ParseObjectOptions, Parser } from '../parser';
6
+ import { NodeType } from '../parser';
7
+
8
+ /** A view plugin to remove inapplicable assets from the tree */
9
+ export default class ApplicabilityPlugin implements ViewPlugin {
10
+ applyResolver(resolver: Resolver) {
11
+ resolver.hooks.beforeResolve.tap(
12
+ 'applicability',
13
+ (node: Node.Node | null, options: Options) => {
14
+ let newNode = node;
15
+
16
+ if (node?.type === NodeType.Applicability) {
17
+ const isApplicable = options.evaluate(node.expression);
18
+
19
+ if (!isApplicable) {
20
+ return null;
21
+ }
22
+
23
+ newNode = node.value;
24
+ }
25
+
26
+ return newNode;
27
+ }
28
+ );
29
+ }
30
+
31
+ applyParser(parser: Parser) {
32
+ /** Switches resolved during the parsing phase are static */
33
+ parser.hooks.determineNodeType.tap('applicability', (obj: any) => {
34
+ if (Object.prototype.hasOwnProperty.call(obj, 'applicability')) {
35
+ return NodeType.Applicability;
36
+ }
37
+ });
38
+
39
+ parser.hooks.parseNode.tap(
40
+ 'applicability',
41
+ (
42
+ obj: any,
43
+ nodeType: Node.ChildrenTypes,
44
+ options: ParseObjectOptions,
45
+ determinedNodeType: null | NodeType
46
+ ) => {
47
+ if (determinedNodeType === NodeType.Applicability) {
48
+ const parsedApplicability = parser.parseObject(
49
+ omit(obj, 'applicability'),
50
+ nodeType,
51
+ options
52
+ );
53
+ if (parsedApplicability !== null) {
54
+ const applicabilityNode = parser.createASTNode(
55
+ {
56
+ type: NodeType.Applicability,
57
+ expression: (obj as any).applicability,
58
+ value: parsedApplicability,
59
+ },
60
+ obj
61
+ );
62
+
63
+ if (applicabilityNode?.type === NodeType.Applicability) {
64
+ applicabilityNode.value.parent = applicabilityNode;
65
+ }
66
+
67
+ return applicabilityNode;
68
+ }
69
+ }
70
+ }
71
+ );
72
+ }
73
+
74
+ apply(view: View) {
75
+ view.hooks.resolver.tap('applicability', this.applyResolver.bind(this));
76
+ view.hooks.parser.tap('applicability', this.applyParser.bind(this));
77
+ }
78
+ }
@@ -0,0 +1,5 @@
1
+ export { default as TemplatePlugin } from './template-plugin';
2
+ export { default as StringResolverPlugin } from './string-resolver';
3
+ export { default as ApplicabilityPlugin } from './applicability';
4
+ export { default as SwitchPlugin } from './switch';
5
+ export type { ViewPlugin } from './plugin';
@@ -0,0 +1,4 @@
1
+ import type { Resolve } from '../resolver';
2
+
3
+ /** Config options that are required to resolve/update a view */
4
+ export type Options = Resolve.NodeResolveOptions;
@@ -0,0 +1,21 @@
1
+ import type { SyncHook } from 'tapable-ts';
2
+ import type { Resolver } from '../resolver';
3
+ import type { Parser } from '../parser';
4
+
5
+ /** The basic view api */
6
+ export interface View {
7
+ /** The hooks for a view */
8
+ hooks: {
9
+ /** A hook when a parser is created for this view */
10
+ parser: SyncHook<[Parser]>;
11
+
12
+ /** A hook when a resolver is created for this view */
13
+ resolver: SyncHook<[Resolver]>;
14
+ };
15
+ }
16
+
17
+ /** A plugin for a view */
18
+ export interface ViewPlugin {
19
+ /** Called with a view instance */
20
+ apply(view: View): void;
21
+ }