@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,149 @@
1
+ import { set } from 'timm';
2
+ import { resolveDataRefs } from '../../string-resolver';
3
+ import type { Options } from './options';
4
+ import type { View, ViewPlugin } from './plugin';
5
+ import type { Node } from '../parser';
6
+ import { NodeType } from '../parser';
7
+ import type { Resolver } from '../resolver';
8
+
9
+ /** Create a function that checks for a start/end sequence in a string */
10
+ const createPatternMatcher = (start: string, end: string) => {
11
+ return (testStr: string) => {
12
+ const startLocation = testStr.indexOf(start);
13
+
14
+ if (startLocation === -1) {
15
+ return false;
16
+ }
17
+
18
+ const endLocation = testStr.indexOf(end);
19
+
20
+ if (endLocation === -1) {
21
+ return false;
22
+ }
23
+
24
+ return startLocation < endLocation;
25
+ };
26
+ };
27
+
28
+ const bindingResolveLookup = createPatternMatcher('{{', '}}');
29
+ const expressionResolveLookup = createPatternMatcher('@[', ']@');
30
+
31
+ /** Check to see if a string contains a reference to dynamic content */
32
+ function hasSomethingToResolve(str: string) {
33
+ return bindingResolveLookup(str) || expressionResolveLookup(str);
34
+ }
35
+
36
+ /** Resolve data refs in a string if necessary. */
37
+ function resolveString(str: string, resolveOptions: Options) {
38
+ return hasSomethingToResolve(str)
39
+ ? resolveDataRefs(str, {
40
+ model: resolveOptions.data.model,
41
+ evaluate: resolveOptions.evaluate,
42
+ })
43
+ : str;
44
+ }
45
+
46
+ /** Recursively resolve all string references in an object or array */
47
+ export function resolveAllRefs(
48
+ node: any,
49
+ resolveOptions: Options,
50
+ propertiesToSkip: Set<string | number>
51
+ ): any {
52
+ if (
53
+ node === null ||
54
+ node === undefined ||
55
+ (typeof node !== 'object' && typeof node !== 'string')
56
+ ) {
57
+ return node;
58
+ }
59
+
60
+ if (typeof node === 'string') {
61
+ return resolveString(node, resolveOptions);
62
+ }
63
+
64
+ let newNode = node;
65
+
66
+ Object.keys(node).forEach((key: string | number) => {
67
+ if (propertiesToSkip.has(key)) {
68
+ return;
69
+ }
70
+
71
+ const val = node[key];
72
+
73
+ let newVal = val;
74
+
75
+ if (typeof val === 'object') {
76
+ newVal = resolveAllRefs(val, resolveOptions, propertiesToSkip);
77
+ } else if (typeof val === 'string') {
78
+ newVal = resolveString(val, resolveOptions);
79
+ }
80
+
81
+ if (newVal !== val) {
82
+ newNode = set(newNode, key as any, newVal);
83
+ }
84
+ });
85
+
86
+ return newNode;
87
+ }
88
+
89
+ /** Traverse up the node tree finding the first available 'path' */
90
+ const findBasePath = (node: Node.Node): Node.PathSegment[] => {
91
+ const parentNode = node.parent;
92
+ if (!parentNode) {
93
+ return [];
94
+ }
95
+
96
+ if ('children' in parentNode) {
97
+ return (
98
+ parentNode.children?.find((child) => child.value === node)?.path ?? []
99
+ );
100
+ }
101
+
102
+ if (parentNode.type !== NodeType.MultiNode) {
103
+ return [];
104
+ }
105
+
106
+ return findBasePath(parentNode);
107
+ };
108
+
109
+ /** A plugin that resolves all string references for each node */
110
+ export default class StringResolverPlugin implements ViewPlugin {
111
+ applyResolver(resolver: Resolver) {
112
+ resolver.hooks.resolve.tap('string-resolver', (value, node, options) => {
113
+ if (node.type === NodeType.Empty || node.type === NodeType.Unknown) {
114
+ return null;
115
+ }
116
+
117
+ if (
118
+ node.type === NodeType.Value ||
119
+ node.type === NodeType.Asset ||
120
+ node.type === NodeType.View
121
+ ) {
122
+ /** Use specified properties to skip during string resolution, or default */
123
+ const propsToSkip = new Set<string>(
124
+ node.plugins?.stringResolver?.propertiesToSkip
125
+ ? node.plugins?.stringResolver?.propertiesToSkip
126
+ : []
127
+ );
128
+
129
+ const nodePath = findBasePath(node);
130
+
131
+ /** If the path includes something that is supposed to be skipped, this node should be skipped too. */
132
+ if (
133
+ nodePath.length > 0 &&
134
+ nodePath.some((segment) => propsToSkip.has(segment.toString()))
135
+ ) {
136
+ return node.value;
137
+ }
138
+
139
+ return resolveAllRefs(node.value, options, propsToSkip);
140
+ }
141
+
142
+ return value;
143
+ });
144
+ }
145
+
146
+ apply(view: View) {
147
+ view.hooks.resolver.tap('string-resolver', this.applyResolver.bind(this));
148
+ }
149
+ }
@@ -0,0 +1,120 @@
1
+ import type { View, ViewPlugin } from './plugin';
2
+ import type { Options } from './options';
3
+ import type { Parser, Node, ParseObjectOptions } from '../parser';
4
+ import { EMPTY_NODE, NodeType } from '../parser';
5
+ import type { Resolver } from '../resolver';
6
+
7
+ /** A view plugin to resolve switches */
8
+ export default class SwitchPlugin implements ViewPlugin {
9
+ private readonly options: Options;
10
+
11
+ constructor(options: Options) {
12
+ this.options = options;
13
+ }
14
+
15
+ private resolveSwitch(node: Node.Switch, options: Options): Node.Node {
16
+ for (const switchCase of node.cases) {
17
+ const isApplicable = options.evaluate(switchCase.case);
18
+
19
+ if (isApplicable) {
20
+ return switchCase.value;
21
+ }
22
+ }
23
+
24
+ return EMPTY_NODE;
25
+ }
26
+
27
+ applyParser(parser: Parser) {
28
+ /** Switches resolved during the parsing phase are static */
29
+ parser.hooks.onCreateASTNode.tap('switch', (node) => {
30
+ if (node && node.type === NodeType.Switch && !node.dynamic) {
31
+ return this.resolveSwitch(node, this.options);
32
+ }
33
+
34
+ return node;
35
+ });
36
+
37
+ parser.hooks.determineNodeType.tap('switch', (obj) => {
38
+ if (
39
+ Object.prototype.hasOwnProperty.call(obj, 'dynamicSwitch') ||
40
+ Object.prototype.hasOwnProperty.call(obj, 'staticSwitch')
41
+ ) {
42
+ return NodeType.Switch;
43
+ }
44
+ });
45
+
46
+ parser.hooks.parseNode.tap(
47
+ 'switch',
48
+ (
49
+ obj: any,
50
+ nodeType: Node.ChildrenTypes,
51
+ options: ParseObjectOptions,
52
+ determinedNodeType: null | NodeType
53
+ ) => {
54
+ if (determinedNodeType === NodeType.Switch) {
55
+ const dynamic = 'dynamicSwitch' in obj;
56
+ const switchContent =
57
+ 'dynamicSwitch' in obj ? obj.dynamicSwitch : obj.staticSwitch;
58
+
59
+ const cases: Node.SwitchCase[] = [];
60
+
61
+ switchContent.forEach(
62
+ (switchCase: { [x: string]: any; case: any }) => {
63
+ const { case: switchCaseExpr, ...switchBody } = switchCase;
64
+ const value = parser.parseObject(
65
+ switchBody,
66
+ NodeType.Value,
67
+ options
68
+ );
69
+
70
+ if (value) {
71
+ cases.push({
72
+ case: switchCaseExpr,
73
+ value: value as Node.Value,
74
+ });
75
+ }
76
+ }
77
+ );
78
+
79
+ const switchAST = parser.hooks.onCreateASTNode.call(
80
+ {
81
+ type: NodeType.Switch,
82
+ dynamic,
83
+ cases,
84
+ },
85
+ obj
86
+ );
87
+
88
+ if (switchAST?.type === NodeType.Switch) {
89
+ switchAST.cases.forEach((sCase) => {
90
+ // eslint-disable-next-line no-param-reassign
91
+ sCase.value.parent = switchAST;
92
+ });
93
+ }
94
+
95
+ if (switchAST?.type === NodeType.Empty) {
96
+ return null;
97
+ }
98
+
99
+ return switchAST ?? null;
100
+ }
101
+ }
102
+ );
103
+ }
104
+
105
+ applyResolver(resolver: Resolver) {
106
+ /** Switches resolved during the parsing phase are dynamic */
107
+ resolver.hooks.beforeResolve.tap('switch', (node, options) => {
108
+ if (node && node.type === NodeType.Switch && node.dynamic) {
109
+ return this.resolveSwitch(node, options);
110
+ }
111
+
112
+ return node;
113
+ });
114
+ }
115
+
116
+ apply(view: View) {
117
+ view.hooks.parser.tap('switch', this.applyParser.bind(this));
118
+ view.hooks.resolver.tap('switch', this.applyResolver.bind(this));
119
+ }
120
+ }
@@ -0,0 +1,172 @@
1
+ import { SyncWaterfallHook } from 'tapable-ts';
2
+ import type { Node, ParseObjectOptions, Parser } from '../parser';
3
+ import { NodeType } from '../parser';
4
+ import type { ViewPlugin } from '.';
5
+ import type { View } from './plugin';
6
+ import type { Options } from './options';
7
+ import type { Resolver } from '../resolver';
8
+
9
+ export interface TemplateItemInfo {
10
+ /** The index of the data for the current iteration of the template */
11
+ index: number;
12
+ /** The data for the current iteration of the template */
13
+ data: any;
14
+ /** The depth of the template node */
15
+ depth: number;
16
+ }
17
+
18
+ export interface TemplateSubstitution {
19
+ /** Regular expression to find and replace. The global flag will be always be added to this expression. */
20
+ expression: string | RegExp;
21
+ /** The value to replace matches with. */
22
+ value: string;
23
+ }
24
+
25
+ export type TemplateSubstitutionsFunc = (
26
+ baseSubstitutions: TemplateSubstitution[],
27
+ templateItemInfo: TemplateItemInfo
28
+ ) => TemplateSubstitution[];
29
+
30
+ /** A view plugin to resolve/manage templates */
31
+ export default class TemplatePlugin implements ViewPlugin {
32
+ private readonly options: Options;
33
+
34
+ hooks = {
35
+ resolveTemplateSubstitutions: new SyncWaterfallHook<
36
+ [TemplateSubstitution[], TemplateItemInfo]
37
+ >(),
38
+ };
39
+
40
+ constructor(options: Options) {
41
+ this.options = options;
42
+ }
43
+
44
+ private parseTemplate(
45
+ parseObject: any,
46
+ node: Node.Template,
47
+ options: Options
48
+ ): Node.Node | null {
49
+ const { template, depth } = node;
50
+ const data = options.data.model.get(node.data);
51
+
52
+ if (!data) {
53
+ return null;
54
+ }
55
+
56
+ if (!Array.isArray(data)) {
57
+ throw new Error(`Template using '${node.data}' but is not an array`);
58
+ }
59
+
60
+ const values: Array<Node.Node> = [];
61
+
62
+ data.forEach((dataItem, index) => {
63
+ const templateSubstitutions =
64
+ this.hooks.resolveTemplateSubstitutions.call(
65
+ [
66
+ {
67
+ expression: new RegExp(`_index${depth || ''}_`),
68
+ value: String(index),
69
+ },
70
+ ],
71
+ {
72
+ depth,
73
+ data: dataItem,
74
+ index,
75
+ }
76
+ );
77
+ let templateStr = JSON.stringify(template);
78
+
79
+ for (const { expression, value } of templateSubstitutions) {
80
+ let flags = 'g';
81
+ if (typeof expression === 'object') {
82
+ flags = `${expression.flags}${expression.global ? '' : 'g'}`;
83
+ }
84
+
85
+ templateStr = templateStr.replace(new RegExp(expression, flags), value);
86
+ }
87
+
88
+ const parsed = parseObject(JSON.parse(templateStr), NodeType.Value, {
89
+ templateDepth: node.depth + 1,
90
+ });
91
+
92
+ if (parsed) {
93
+ values.push(parsed);
94
+ }
95
+ });
96
+
97
+ const result: Node.MultiNode = {
98
+ parent: node.parent,
99
+ type: NodeType.MultiNode,
100
+ values,
101
+ };
102
+
103
+ result.values.forEach((innerNode) => {
104
+ // eslint-disable-next-line no-param-reassign
105
+ innerNode.parent = result;
106
+ });
107
+
108
+ return result;
109
+ }
110
+
111
+ applyParser(parser: Parser) {
112
+ parser.hooks.onCreateASTNode.tap('template', (node) => {
113
+ if (node && node.type === NodeType.Template && !node.dynamic) {
114
+ return this.parseTemplate(
115
+ parser.parseObject.bind(parser),
116
+ node,
117
+ this.options
118
+ );
119
+ }
120
+
121
+ return node;
122
+ });
123
+
124
+ parser.hooks.determineNodeType.tap('template', (obj: any) => {
125
+ if (obj === 'template') {
126
+ return NodeType.Template;
127
+ }
128
+ });
129
+
130
+ parser.hooks.parseNode.tap(
131
+ 'template',
132
+ (
133
+ obj: any,
134
+ nodeType: Node.ChildrenTypes,
135
+ options: ParseObjectOptions,
136
+ determinedNodeType: null | NodeType
137
+ ) => {
138
+ if (determinedNodeType === NodeType.Template) {
139
+ const templateNode = parser.createASTNode(
140
+ {
141
+ type: NodeType.Template,
142
+ depth: options.templateDepth ?? 0,
143
+ data: obj.data,
144
+ template: obj.value,
145
+ dynamic: obj.dynamic ?? false,
146
+ },
147
+ obj
148
+ );
149
+
150
+ if (templateNode) {
151
+ return templateNode;
152
+ }
153
+ }
154
+ }
155
+ );
156
+ }
157
+
158
+ applyResolverHooks(resolver: Resolver) {
159
+ resolver.hooks.beforeResolve.tap('template', (node, options) => {
160
+ if (node && node.type === NodeType.Template && node.dynamic) {
161
+ return this.parseTemplate(options.parseNode, node, options);
162
+ }
163
+
164
+ return node;
165
+ });
166
+ }
167
+
168
+ apply(view: View) {
169
+ view.hooks.parser.tap('template', this.applyParser.bind(this));
170
+ view.hooks.resolver.tap('template', this.applyResolverHooks.bind(this));
171
+ }
172
+ }