@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.
- package/dist/index.cjs.js +4128 -891
- package/dist/index.d.ts +1227 -50
- package/dist/index.esm.js +4065 -836
- package/package.json +9 -15
- package/src/binding/binding.ts +108 -0
- package/src/binding/index.ts +188 -0
- package/src/binding/resolver.ts +157 -0
- package/src/binding/utils.ts +51 -0
- package/src/binding-grammar/ast.ts +113 -0
- package/src/binding-grammar/custom/index.ts +304 -0
- package/src/binding-grammar/ebnf/binding.ebnf +22 -0
- package/src/binding-grammar/ebnf/index.ts +186 -0
- package/src/binding-grammar/ebnf/types.ts +104 -0
- package/src/binding-grammar/index.ts +4 -0
- package/src/binding-grammar/parsimmon/index.ts +78 -0
- package/src/controllers/constants/index.ts +85 -0
- package/src/controllers/constants/utils.ts +37 -0
- package/src/{data.ts → controllers/data.ts} +6 -6
- package/src/controllers/flow/controller.ts +95 -0
- package/src/controllers/flow/flow.ts +205 -0
- package/src/controllers/flow/index.ts +2 -0
- package/src/controllers/index.ts +5 -0
- package/src/{validation → controllers/validation}/binding-tracker.ts +5 -5
- package/src/{validation → controllers/validation}/controller.ts +15 -14
- package/src/{validation → controllers/validation}/index.ts +0 -0
- package/src/{view → controllers/view}/asset-transform.ts +2 -3
- package/src/{view → controllers/view}/controller.ts +9 -8
- package/src/controllers/view/index.ts +4 -0
- package/src/{view → controllers/view}/store.ts +0 -0
- package/src/{view → controllers/view}/types.ts +2 -1
- package/src/data/dependency-tracker.ts +187 -0
- package/src/data/index.ts +4 -0
- package/src/data/local-model.ts +41 -0
- package/src/data/model.ts +216 -0
- package/src/data/noop-model.ts +18 -0
- package/src/expressions/evaluator-functions.ts +29 -0
- package/src/expressions/evaluator.ts +405 -0
- package/src/expressions/index.ts +3 -0
- package/src/expressions/parser.ts +889 -0
- package/src/expressions/types.ts +200 -0
- package/src/expressions/utils.ts +8 -0
- package/src/index.ts +9 -12
- package/src/logger/consoleLogger.ts +49 -0
- package/src/logger/index.ts +5 -0
- package/src/logger/noopLogger.ts +13 -0
- package/src/logger/proxyLogger.ts +25 -0
- package/src/logger/tapableLogger.ts +38 -0
- package/src/logger/types.ts +6 -0
- package/src/player.ts +21 -18
- package/src/plugins/flow-exp-plugin.ts +2 -3
- package/src/schema/index.ts +2 -0
- package/src/schema/schema.ts +220 -0
- package/src/schema/types.ts +60 -0
- package/src/string-resolver/index.ts +188 -0
- package/src/types.ts +11 -13
- package/src/utils/index.ts +1 -0
- package/src/utils/replaceParams.ts +17 -0
- package/src/validator/index.ts +3 -0
- package/src/validator/registry.ts +20 -0
- package/src/validator/types.ts +75 -0
- package/src/validator/validation-middleware.ts +114 -0
- package/src/view/builder/index.ts +81 -0
- package/src/view/index.ts +5 -4
- package/src/view/parser/index.ts +318 -0
- package/src/view/parser/types.ts +141 -0
- package/src/view/plugins/applicability.ts +78 -0
- package/src/view/plugins/index.ts +5 -0
- package/src/view/plugins/options.ts +4 -0
- package/src/view/plugins/plugin.ts +21 -0
- package/src/view/plugins/string-resolver.ts +149 -0
- package/src/view/plugins/switch.ts +120 -0
- package/src/view/plugins/template-plugin.ts +172 -0
- package/src/view/resolver/index.ts +397 -0
- package/src/view/resolver/types.ts +161 -0
- package/src/view/resolver/utils.ts +57 -0
- package/src/view/view.ts +149 -0
- package/src/utils/desc.d.ts +0 -2
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { DataModelWithParser } from '../data';
|
|
2
|
+
import type { Logger } from '../logger';
|
|
3
|
+
|
|
4
|
+
export type ExpressionLiteralType =
|
|
5
|
+
| string
|
|
6
|
+
| number
|
|
7
|
+
| boolean
|
|
8
|
+
| undefined
|
|
9
|
+
| null;
|
|
10
|
+
export type ExpressionType =
|
|
11
|
+
| object
|
|
12
|
+
| ExpressionLiteralType
|
|
13
|
+
| Array<ExpressionLiteralType>
|
|
14
|
+
| ExpressionNode;
|
|
15
|
+
|
|
16
|
+
export interface OperatorProcessingOptions {
|
|
17
|
+
/**
|
|
18
|
+
* When set to a falsy value, the arguments passed to the handler will be raw AST Nodes
|
|
19
|
+
* This enables lazy evaluation of arguments
|
|
20
|
+
*/
|
|
21
|
+
resolveParams: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type BinaryOperatorBasic = (left: any, right: any) => unknown;
|
|
25
|
+
export type BinaryOperatorAdvanced = OperatorProcessingOptions &
|
|
26
|
+
((ctx: ExpressionContext, left: any, right: any) => unknown);
|
|
27
|
+
|
|
28
|
+
export type BinaryOperator = BinaryOperatorAdvanced | BinaryOperatorBasic;
|
|
29
|
+
|
|
30
|
+
export type UnaryOperator =
|
|
31
|
+
| ((arg: any) => unknown)
|
|
32
|
+
| (((ctx: ExpressionContext, arg: any) => unknown) &
|
|
33
|
+
OperatorProcessingOptions);
|
|
34
|
+
|
|
35
|
+
export interface ExpressionContext {
|
|
36
|
+
/** A means of executing an expression */
|
|
37
|
+
evaluate: (expr: ExpressionType) => unknown;
|
|
38
|
+
|
|
39
|
+
/** The data model that expression handlers can use when fetching data */
|
|
40
|
+
model: DataModelWithParser;
|
|
41
|
+
|
|
42
|
+
/** A logger to use */
|
|
43
|
+
logger?: Logger;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ExpressionHandler<
|
|
47
|
+
T extends readonly unknown[] = unknown[],
|
|
48
|
+
R = void
|
|
49
|
+
> = ((context: ExpressionContext, ...args: T) => R) &
|
|
50
|
+
Partial<OperatorProcessingOptions>;
|
|
51
|
+
|
|
52
|
+
export const ExpNodeOpaqueIdentifier = Symbol('Expression Node ID');
|
|
53
|
+
|
|
54
|
+
/** Checks if the input is an already processed Expression node */
|
|
55
|
+
export function isExpressionNode(x: any): x is ExpressionNode {
|
|
56
|
+
return typeof x === 'object' && x.__id === ExpNodeOpaqueIdentifier;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface BaseNode<T> {
|
|
60
|
+
/** The thing to discriminate the AST type on */
|
|
61
|
+
type: T;
|
|
62
|
+
|
|
63
|
+
/** How to tell this apart from other objects */
|
|
64
|
+
__id: typeof ExpNodeOpaqueIdentifier;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A helper interface for nodes that container left and right children */
|
|
68
|
+
export interface DirectionalNode {
|
|
69
|
+
/** The left node. Often for the left hand side of an expression */
|
|
70
|
+
left: ExpressionNode;
|
|
71
|
+
|
|
72
|
+
/** The right child. Often for the right hand side of an expression */
|
|
73
|
+
right: ExpressionNode;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface LiteralNode extends BaseNode<'Literal'> {
|
|
77
|
+
/** A node that holds a literal value */
|
|
78
|
+
value: string | number;
|
|
79
|
+
|
|
80
|
+
/** The unprocessed value */
|
|
81
|
+
raw?: any;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface BinaryNode
|
|
85
|
+
extends BaseNode<'BinaryExpression'>,
|
|
86
|
+
DirectionalNode {
|
|
87
|
+
/** The operation to perform on the nodes */
|
|
88
|
+
operator: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface LogicalNode extends BaseNode<'LogicalExpression'> {
|
|
92
|
+
/** The left hand side of the equation */
|
|
93
|
+
left: any;
|
|
94
|
+
|
|
95
|
+
/** The right hand side of the equation */
|
|
96
|
+
right: any;
|
|
97
|
+
|
|
98
|
+
/** The logical operation to perform on the nodes */
|
|
99
|
+
operator: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface UnaryNode extends BaseNode<'UnaryExpression'> {
|
|
103
|
+
/** The operation to perform on the node */
|
|
104
|
+
operator: string;
|
|
105
|
+
|
|
106
|
+
/** The single argument that the operation should be performed on */
|
|
107
|
+
argument: any;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type ThisNode = BaseNode<'ThisExpression'>;
|
|
111
|
+
|
|
112
|
+
export interface ModelRefNode extends BaseNode<'ModelRef'> {
|
|
113
|
+
/** The binding that the model reference points to */
|
|
114
|
+
ref: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ObjectNode extends BaseNode<'Object'> {
|
|
118
|
+
/** */
|
|
119
|
+
attributes: Array<{
|
|
120
|
+
/** The property name of the object */
|
|
121
|
+
key: any;
|
|
122
|
+
|
|
123
|
+
/** the associated value */
|
|
124
|
+
value: any;
|
|
125
|
+
}>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface MemberExpressionNode extends BaseNode<'MemberExpression'> {
|
|
129
|
+
/** The object to be introspected */
|
|
130
|
+
object: ExpressionNode;
|
|
131
|
+
|
|
132
|
+
/** If the property uses . or open-bracket */
|
|
133
|
+
computed: boolean;
|
|
134
|
+
|
|
135
|
+
/** The property to access on the object */
|
|
136
|
+
property: ExpressionNode;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ConditionalExpressionNode
|
|
140
|
+
extends BaseNode<'ConditionalExpression'> {
|
|
141
|
+
/** The test for the ternary */
|
|
142
|
+
test: ExpressionNode;
|
|
143
|
+
|
|
144
|
+
/** The truthy case for the ternary */
|
|
145
|
+
consequent: ExpressionNode;
|
|
146
|
+
|
|
147
|
+
/** The falsy case for the ternary */
|
|
148
|
+
alternate: ExpressionNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface CompoundNode extends BaseNode<'Compound'> {
|
|
152
|
+
/** The contents of the compound expression */
|
|
153
|
+
body: ExpressionNode[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface CallExpressionNode extends BaseNode<'CallExpression'> {
|
|
157
|
+
/** The arguments to the function */
|
|
158
|
+
args: any[];
|
|
159
|
+
|
|
160
|
+
/** The function name */
|
|
161
|
+
callTarget: IdentifierNode;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface ArrayExpressionNode extends BaseNode<'ArrayExpression'> {
|
|
165
|
+
/** The items in an array */
|
|
166
|
+
elements: any[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface IdentifierNode extends BaseNode<'Identifier'> {
|
|
170
|
+
/** The variable name */
|
|
171
|
+
name: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type AssignmentNode = BaseNode<'Assignment'> & DirectionalNode;
|
|
175
|
+
|
|
176
|
+
export interface ModificationNode
|
|
177
|
+
extends BaseNode<'Modification'>,
|
|
178
|
+
DirectionalNode {
|
|
179
|
+
/** The operator for the modification */
|
|
180
|
+
operator: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type ExpressionNode =
|
|
184
|
+
| LiteralNode
|
|
185
|
+
| BinaryNode
|
|
186
|
+
| LogicalNode
|
|
187
|
+
| UnaryNode
|
|
188
|
+
| ThisNode
|
|
189
|
+
| ModelRefNode
|
|
190
|
+
| MemberExpressionNode
|
|
191
|
+
| ConditionalExpressionNode
|
|
192
|
+
| CompoundNode
|
|
193
|
+
| CallExpressionNode
|
|
194
|
+
| ArrayExpressionNode
|
|
195
|
+
| IdentifierNode
|
|
196
|
+
| AssignmentNode
|
|
197
|
+
| ModificationNode
|
|
198
|
+
| ObjectNode;
|
|
199
|
+
|
|
200
|
+
export type ExpressionNodeType = ExpressionNode['type'];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExpressionHandler } from './types';
|
|
2
|
+
|
|
3
|
+
/** Generates a function by removing the first context argument */
|
|
4
|
+
export function withoutContext<T extends unknown[], Return>(
|
|
5
|
+
fn: (...args: T) => Return
|
|
6
|
+
): ExpressionHandler<T, Return> {
|
|
7
|
+
return (_context, ...args) => fn(...args);
|
|
8
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
// Add the types export first so it's naming takes precedence
|
|
2
2
|
export * from '@player-ui/types';
|
|
3
|
-
export * from '
|
|
4
|
-
export * from '
|
|
5
|
-
export * from '
|
|
6
|
-
export * from '
|
|
7
|
-
export * from '
|
|
8
|
-
export * from '
|
|
9
|
-
export * from '
|
|
10
|
-
export * from '
|
|
11
|
-
export * from '@player-ui/view';
|
|
3
|
+
export * from './binding';
|
|
4
|
+
export * from './data';
|
|
5
|
+
export * from './expressions';
|
|
6
|
+
export * from './logger';
|
|
7
|
+
export * from './schema';
|
|
8
|
+
export * from './string-resolver';
|
|
9
|
+
export * from './validator';
|
|
10
|
+
export * from './view';
|
|
12
11
|
|
|
13
12
|
export * from './player';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './view';
|
|
16
|
-
export * from './data';
|
|
13
|
+
export * from './controllers';
|
|
17
14
|
export * from './types';
|
|
18
15
|
export * from './plugins/flow-exp-plugin';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Logger, Severity } from './types';
|
|
2
|
+
import { severities } from './types';
|
|
3
|
+
|
|
4
|
+
export type ConsoleHandler = Pick<typeof console, 'log' | 'warn' | 'error'>;
|
|
5
|
+
|
|
6
|
+
/** A Logger implementation that uses console */
|
|
7
|
+
export default class ConsoleLogger implements Logger {
|
|
8
|
+
private severity: Severity;
|
|
9
|
+
private _console: ConsoleHandler;
|
|
10
|
+
|
|
11
|
+
constructor(severity: Severity = 'warn', _console: ConsoleHandler = console) {
|
|
12
|
+
this.severity = severity;
|
|
13
|
+
this._console = _console;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public setSeverity(severity: Severity) {
|
|
17
|
+
this.severity = severity;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private getConsoleFn(severity: Severity) {
|
|
21
|
+
switch (severities.indexOf(severity)) {
|
|
22
|
+
case 0:
|
|
23
|
+
case 1:
|
|
24
|
+
case 2:
|
|
25
|
+
return this._console.log;
|
|
26
|
+
case 3:
|
|
27
|
+
return this._console.warn;
|
|
28
|
+
default:
|
|
29
|
+
return this._console.error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private createHandler(severity: Severity): (...args: any[]) => void {
|
|
34
|
+
return (...args: any[]) => {
|
|
35
|
+
const sevIndex = severities.indexOf(severity);
|
|
36
|
+
const sevConf = severities.indexOf(this.severity);
|
|
37
|
+
|
|
38
|
+
if (sevIndex >= sevConf) {
|
|
39
|
+
this.getConsoleFn(severity)(`player - ${severity} -`, ...args);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public readonly trace = this.createHandler('trace');
|
|
45
|
+
public readonly debug = this.createHandler('debug');
|
|
46
|
+
public readonly info = this.createHandler('info');
|
|
47
|
+
public readonly warn = this.createHandler('warn');
|
|
48
|
+
public readonly error = this.createHandler('error');
|
|
49
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Logger } from './types';
|
|
2
|
+
|
|
3
|
+
/** An empty function so the logger ignore everything */
|
|
4
|
+
const noop = () => {};
|
|
5
|
+
|
|
6
|
+
/** A logger implementation that goes nowhere */
|
|
7
|
+
export default class NoopLogger implements Logger {
|
|
8
|
+
public readonly trace = noop;
|
|
9
|
+
public readonly debug = noop;
|
|
10
|
+
public readonly info = noop;
|
|
11
|
+
public readonly warn = noop;
|
|
12
|
+
public readonly error = noop;
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Logger, Severity, LoggerProvider } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The ProxyLogger allows a user to log to another Logger instance that may not exist yet
|
|
5
|
+
*/
|
|
6
|
+
export default class ProxyLogger implements Logger {
|
|
7
|
+
private proxiedLoggerProvider: LoggerProvider;
|
|
8
|
+
|
|
9
|
+
constructor(loggerProvider: LoggerProvider) {
|
|
10
|
+
this.proxiedLoggerProvider = loggerProvider;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private createHandler(severity: Severity): (...args: any[]) => void {
|
|
14
|
+
return (...args: any[]) => {
|
|
15
|
+
const logger = this.proxiedLoggerProvider();
|
|
16
|
+
logger?.[severity](...args);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public readonly trace = this.createHandler('trace');
|
|
21
|
+
public readonly debug = this.createHandler('debug');
|
|
22
|
+
public readonly info = this.createHandler('info');
|
|
23
|
+
public readonly warn = this.createHandler('warn');
|
|
24
|
+
public readonly error = this.createHandler('error');
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SyncHook } from 'tapable-ts';
|
|
2
|
+
import type { Logger, Severity } from './types';
|
|
3
|
+
|
|
4
|
+
/** A logger that has a tapable subscriptions to callbacks */
|
|
5
|
+
export default class TapableLogger implements Logger {
|
|
6
|
+
public readonly hooks = {
|
|
7
|
+
trace: new SyncHook<[Array<any>]>(),
|
|
8
|
+
debug: new SyncHook<[Array<any>]>(),
|
|
9
|
+
info: new SyncHook<[Array<any>]>(),
|
|
10
|
+
warn: new SyncHook<[Array<any>]>(),
|
|
11
|
+
error: new SyncHook<[Array<any>]>(),
|
|
12
|
+
log: new SyncHook<[Severity, Array<any>]>(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
private logHandlers: Set<Logger> = new Set();
|
|
16
|
+
|
|
17
|
+
private createHandler(severity: Severity): (...args: any[]) => void {
|
|
18
|
+
return (...args: any[]) => {
|
|
19
|
+
this.hooks[severity].call(args);
|
|
20
|
+
this.hooks.log.call(severity, args);
|
|
21
|
+
this.logHandlers.forEach((logger) => logger[severity](...args));
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public addHandler(logHandler: Logger) {
|
|
26
|
+
this.logHandlers.add(logHandler);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public removeHandler(logHandler: Logger) {
|
|
30
|
+
this.logHandlers.delete(logHandler);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public readonly trace = this.createHandler('trace');
|
|
34
|
+
public readonly debug = this.createHandler('debug');
|
|
35
|
+
public readonly info = this.createHandler('info');
|
|
36
|
+
public readonly warn = this.createHandler('warn');
|
|
37
|
+
public readonly error = this.createHandler('error');
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type LogFn = (...args: Array<any>) => void;
|
|
2
|
+
|
|
3
|
+
export const severities = ['trace', 'debug', 'info', 'warn', 'error'] as const;
|
|
4
|
+
export type Severity = typeof severities[number];
|
|
5
|
+
export type Logger = Record<Severity, LogFn>;
|
|
6
|
+
export type LoggerProvider = () => Logger | undefined;
|
package/src/player.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { SyncHook, SyncWaterfallHook } from 'tapable-ts';
|
|
2
|
-
import type { FlowInstance } from '@player-ui/flow';
|
|
3
|
-
import { FlowController } from '@player-ui/flow';
|
|
4
|
-
import type { Logger } from '@player-ui/logger';
|
|
5
|
-
import { TapableLogger } from '@player-ui/logger';
|
|
6
|
-
import type { ExpressionHandler } from '@player-ui/expressions';
|
|
7
|
-
import { ExpressionEvaluator } from '@player-ui/expressions';
|
|
8
|
-
import { SchemaController } from '@player-ui/schema';
|
|
9
|
-
import { BindingParser } from '@player-ui/binding';
|
|
10
|
-
import type { ViewInstance } from '@player-ui/view';
|
|
11
1
|
import { setIn } from 'timm';
|
|
12
2
|
import deferred from 'p-defer';
|
|
13
|
-
import type { Flow as FlowType, FlowResult } from '@player-ui/types';
|
|
14
|
-
import { resolveDataRefs } from '@player-ui/string-resolver';
|
|
15
|
-
import { ConstantsController } from '@player-ui/constants';
|
|
16
3
|
import queueMicrotask from 'queue-microtask';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
import {
|
|
4
|
+
import type { Flow as FlowType, FlowResult } from '@player-ui/types';
|
|
5
|
+
|
|
6
|
+
import { SyncHook, SyncWaterfallHook } from 'tapable-ts';
|
|
7
|
+
import type { Logger } from './logger';
|
|
8
|
+
import { TapableLogger } from './logger';
|
|
9
|
+
import type { ExpressionHandler } from './expressions';
|
|
10
|
+
import { ExpressionEvaluator } from './expressions';
|
|
11
|
+
import { SchemaController } from './schema';
|
|
12
|
+
import { BindingParser } from './binding';
|
|
13
|
+
import type { ViewInstance } from './view';
|
|
14
|
+
import { resolveDataRefs } from './string-resolver';
|
|
15
|
+
import type { FlowInstance } from './controllers';
|
|
16
|
+
import {
|
|
17
|
+
ConstantsController,
|
|
18
|
+
ViewController,
|
|
19
|
+
DataController,
|
|
20
|
+
ValidationController,
|
|
21
|
+
FlowController,
|
|
22
|
+
} from './controllers';
|
|
20
23
|
import { FlowExpPlugin } from './plugins/flow-exp-plugin';
|
|
21
24
|
import type {
|
|
22
25
|
PlayerFlowState,
|
|
@@ -27,8 +30,8 @@ import type {
|
|
|
27
30
|
import { NOT_STARTED_STATE } from './types';
|
|
28
31
|
|
|
29
32
|
// Variables injected at build time
|
|
30
|
-
const PLAYER_VERSION = '0.3.0-next.
|
|
31
|
-
const COMMIT = '
|
|
33
|
+
const PLAYER_VERSION = '0.3.0-next.4';
|
|
34
|
+
const COMMIT = '774a3cf3b0765796901d03a311a06ee68e690a74';
|
|
32
35
|
|
|
33
36
|
export interface PlayerPlugin {
|
|
34
37
|
/**
|
|
@@ -3,10 +3,9 @@ import type {
|
|
|
3
3
|
ExpressionObject,
|
|
4
4
|
NavigationFlowState,
|
|
5
5
|
} from '@player-ui/types';
|
|
6
|
-
import type { ExpressionEvaluator } from '
|
|
7
|
-
import type { FlowInstance } from '
|
|
6
|
+
import type { ExpressionEvaluator } from '../expressions';
|
|
7
|
+
import type { FlowInstance } from '../controllers';
|
|
8
8
|
import type { Player, PlayerPlugin } from '../player';
|
|
9
|
-
import type { InProgressState } from '../types';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* A plugin that taps into the flow controller to evaluate available expressions
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { SyncWaterfallHook } from 'tapable-ts';
|
|
2
|
+
import type { Schema as SchemaType, Formatting } from '@player-ui/types';
|
|
3
|
+
|
|
4
|
+
import type { BindingInstance } from '../binding';
|
|
5
|
+
import type { ValidationProvider, ValidationObject } from '../validator';
|
|
6
|
+
import type { FormatDefinition, FormatOptions, FormatType } from './types';
|
|
7
|
+
|
|
8
|
+
/** A function that returns itself */
|
|
9
|
+
const identify = (val: any) => val;
|
|
10
|
+
|
|
11
|
+
/** Expand the authored schema into a set of paths -> DataTypes */
|
|
12
|
+
export function parse(
|
|
13
|
+
schema: SchemaType.Schema
|
|
14
|
+
): Map<string, SchemaType.DataType> {
|
|
15
|
+
const expandedPaths = new Map<string, SchemaType.DataType>();
|
|
16
|
+
|
|
17
|
+
if (!schema.ROOT) {
|
|
18
|
+
return expandedPaths;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parseQueue: Array<{
|
|
22
|
+
/** The node to process */
|
|
23
|
+
node: SchemaType.Node;
|
|
24
|
+
|
|
25
|
+
/** The path in the data-model this node represents */
|
|
26
|
+
path: Array<string>;
|
|
27
|
+
|
|
28
|
+
/** A set of visited DataTypes to prevent loops */
|
|
29
|
+
visited: Set<string>;
|
|
30
|
+
}> = [{ node: schema.ROOT, path: [], visited: new Set() }];
|
|
31
|
+
|
|
32
|
+
while (parseQueue.length > 0) {
|
|
33
|
+
const next = parseQueue.shift();
|
|
34
|
+
|
|
35
|
+
if (!next) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { node, path, visited } = next;
|
|
40
|
+
|
|
41
|
+
Object.entries(node).forEach(([prop, type]) => {
|
|
42
|
+
const nestedPath = [...path, prop];
|
|
43
|
+
|
|
44
|
+
const nestedPathStr = nestedPath.join('.');
|
|
45
|
+
|
|
46
|
+
if (expandedPaths.has(nestedPathStr)) {
|
|
47
|
+
// We've gone in a loop. Panic
|
|
48
|
+
throw new Error(
|
|
49
|
+
"Path has already been processed. There's either a loop somewhere or a bug"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (visited.has(type.type)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Path already contained type: ${type.type}. This likely indicates a loop in the schema`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
expandedPaths.set(nestedPathStr, type);
|
|
60
|
+
|
|
61
|
+
if (type.isArray) {
|
|
62
|
+
nestedPath.push('[]');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (type.type && schema[type.type]) {
|
|
66
|
+
parseQueue.push({
|
|
67
|
+
path: nestedPath,
|
|
68
|
+
node: schema[type.type],
|
|
69
|
+
visited: new Set([...visited, type.type]),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return expandedPaths;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The Schema is the central hub for all data invariants, and metaData associated with the data-model itself
|
|
80
|
+
* Outside of the types defined in the JSON payload, it doesn't manage or keep any state.
|
|
81
|
+
* It simply servers as an orchestrator for other modules to interface w/ the schema.
|
|
82
|
+
*/
|
|
83
|
+
export class SchemaController implements ValidationProvider {
|
|
84
|
+
private formatters: Map<string, FormatType<any, any, FormatOptions>> =
|
|
85
|
+
new Map();
|
|
86
|
+
|
|
87
|
+
private types: Map<string, SchemaType.DataType<any>> = new Map();
|
|
88
|
+
public readonly schema: Map<string, SchemaType.DataType> = new Map();
|
|
89
|
+
|
|
90
|
+
private bindingSchemaNormalizedCache: Map<BindingInstance, string> =
|
|
91
|
+
new Map();
|
|
92
|
+
|
|
93
|
+
public readonly hooks = {
|
|
94
|
+
resolveTypeForBinding: new SyncWaterfallHook<
|
|
95
|
+
[SchemaType.DataType | undefined, BindingInstance]
|
|
96
|
+
>(),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
constructor(schema?: SchemaType.Schema) {
|
|
100
|
+
this.schema = schema ? parse(schema) : new Map();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public addFormatters(fns: Array<FormatType<any, any, FormatOptions>>) {
|
|
104
|
+
fns.forEach((def) => {
|
|
105
|
+
this.formatters.set(def.name, def);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public addDataTypes(types: Array<SchemaType.DataType<any>>) {
|
|
110
|
+
types.forEach((t) => {
|
|
111
|
+
this.types.set(t.type, t);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getValidationsForBinding(
|
|
116
|
+
binding: BindingInstance
|
|
117
|
+
): Array<ValidationObject> | undefined {
|
|
118
|
+
const typeDef = this.getApparentType(binding);
|
|
119
|
+
|
|
120
|
+
if (!typeDef?.validation?.length) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Set the defaults for schema-level validations
|
|
125
|
+
return typeDef.validation.map((vRef) => ({
|
|
126
|
+
severity: 'error',
|
|
127
|
+
trigger: 'change',
|
|
128
|
+
...vRef,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private normalizeBinding(binding: BindingInstance): string {
|
|
133
|
+
const cached = this.bindingSchemaNormalizedCache.get(binding);
|
|
134
|
+
if (cached) {
|
|
135
|
+
return cached;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const normalized = binding
|
|
139
|
+
.asArray()
|
|
140
|
+
.map((p) => (typeof p === 'number' ? '[]' : p))
|
|
141
|
+
.join('.');
|
|
142
|
+
|
|
143
|
+
this.bindingSchemaNormalizedCache.set(binding, normalized);
|
|
144
|
+
|
|
145
|
+
return normalized;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public getType(binding: BindingInstance): SchemaType.DataType | undefined {
|
|
149
|
+
return this.hooks.resolveTypeForBinding.call(
|
|
150
|
+
this.schema.get(this.normalizeBinding(binding)),
|
|
151
|
+
binding
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public getApparentType(
|
|
156
|
+
binding: BindingInstance
|
|
157
|
+
): SchemaType.DataType | undefined {
|
|
158
|
+
const schemaType = this.getType(binding);
|
|
159
|
+
|
|
160
|
+
if (schemaType === undefined) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const baseType = this.getTypeDefinition(schemaType?.type);
|
|
165
|
+
|
|
166
|
+
if (baseType === undefined) {
|
|
167
|
+
return schemaType;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
...baseType,
|
|
172
|
+
...schemaType,
|
|
173
|
+
validation: [
|
|
174
|
+
...(schemaType.validation ?? []),
|
|
175
|
+
...(baseType.validation ?? []),
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public getTypeDefinition(dataType: string) {
|
|
181
|
+
return this.types.get(dataType);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public getFormatterForType(
|
|
185
|
+
formatReference: Formatting.Reference
|
|
186
|
+
): FormatDefinition<unknown, unknown> | undefined {
|
|
187
|
+
const { type: formatType, ...options } = formatReference;
|
|
188
|
+
|
|
189
|
+
const formatter = this.formatters.get(formatType);
|
|
190
|
+
|
|
191
|
+
if (!formatter) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
format: formatter.format
|
|
197
|
+
? (val) => formatter.format?.(val, options)
|
|
198
|
+
: identify,
|
|
199
|
+
deformat: formatter.deformat
|
|
200
|
+
? (val) => formatter.deformat?.(val, options)
|
|
201
|
+
: identify,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Given a binding, fetch a function that's responsible for formatting, and/or de-formatting the data
|
|
207
|
+
* If no formatter is registered, it will return undefined
|
|
208
|
+
*/
|
|
209
|
+
public getFormatter(
|
|
210
|
+
binding: BindingInstance
|
|
211
|
+
): FormatDefinition<unknown, unknown> | undefined {
|
|
212
|
+
const type = this.getApparentType(binding);
|
|
213
|
+
|
|
214
|
+
if (!type?.format) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return this.getFormatterForType(type.format);
|
|
219
|
+
}
|
|
220
|
+
}
|