@player-ui/player 0.4.0-next.4 → 0.4.0-next.6
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 +110 -39
- package/dist/index.d.ts +33 -10
- package/dist/index.esm.js +110 -40
- package/dist/player.dev.js +4108 -4037
- package/dist/player.prod.js +1 -1
- package/package.json +3 -3
- package/src/controllers/data.ts +0 -2
- package/src/expressions/evaluator.ts +2 -2
- package/src/expressions/index.ts +1 -0
- package/src/expressions/parser.ts +53 -27
- package/src/expressions/types.ts +6 -0
- package/src/player.ts +20 -21
- package/src/plugins/default-exp-plugin.ts +57 -0
- package/src/schema/schema.ts +28 -9
- package/src/string-resolver/index.ts +18 -7
- package/src/validator/validation-middleware.ts +4 -1
- package/src/view/parser/index.ts +7 -1
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@player-ui/player",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org"
|
|
7
7
|
},
|
|
8
8
|
"peerDependencies": {},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@player-ui/partial-match-registry": "0.4.0-next.
|
|
11
|
-
"@player-ui/types": "0.4.0-next.
|
|
10
|
+
"@player-ui/partial-match-registry": "0.4.0-next.6",
|
|
11
|
+
"@player-ui/types": "0.4.0-next.6",
|
|
12
12
|
"dequal": "^2.0.2",
|
|
13
13
|
"p-defer": "^3.0.0",
|
|
14
14
|
"queue-microtask": "^1.2.3",
|
package/src/controllers/data.ts
CHANGED
|
@@ -157,8 +157,6 @@ export class DataController implements DataModelWithParser<DataModelOptions> {
|
|
|
157
157
|
|
|
158
158
|
this.hooks.onSet.call(normalizedTransaction);
|
|
159
159
|
|
|
160
|
-
this.hooks.onSet.call(normalizedTransaction);
|
|
161
|
-
|
|
162
160
|
if (setUpdates.length > 0) {
|
|
163
161
|
this.hooks.onUpdate.call(setUpdates, options);
|
|
164
162
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SyncWaterfallHook, SyncBailHook } from 'tapable-ts';
|
|
2
|
-
import
|
|
2
|
+
import { parseExpression } from './parser';
|
|
3
3
|
import * as DEFAULT_EXPRESSION_HANDLERS from './evaluator-functions';
|
|
4
4
|
import { isExpressionNode } from './types';
|
|
5
5
|
import type {
|
|
@@ -212,7 +212,7 @@ export class ExpressionEvaluator {
|
|
|
212
212
|
return this._execAST(storedAST, options);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
const expAST =
|
|
215
|
+
const expAST = parseExpression(matchedExp);
|
|
216
216
|
this.expressionsCache.set(matchedExp, expAST);
|
|
217
217
|
|
|
218
218
|
return this._execAST(expAST, options);
|
package/src/expressions/index.ts
CHANGED
|
@@ -195,7 +195,15 @@ function isModelRefStart(ch0: number, ch1: number) {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/** Parse out an expression from the string */
|
|
198
|
-
export
|
|
198
|
+
export function parseExpression(
|
|
199
|
+
expr: string,
|
|
200
|
+
options?: {
|
|
201
|
+
/** If true (the default), will throw on invalid expressions */
|
|
202
|
+
strict?: boolean;
|
|
203
|
+
}
|
|
204
|
+
): ExpressionNode {
|
|
205
|
+
const strictMode = options?.strict ?? true;
|
|
206
|
+
|
|
199
207
|
// `index` stores the character number we are currently at while `length` is a constant
|
|
200
208
|
// All of the gobbles below will modify `index` as we move along
|
|
201
209
|
const charAtFunc = expr.charAt;
|
|
@@ -789,6 +797,10 @@ export default function parseExpression(expr: string): ExpressionNode {
|
|
|
789
797
|
args.push(node);
|
|
790
798
|
}
|
|
791
799
|
|
|
800
|
+
if (charIndex !== termination) {
|
|
801
|
+
throwError(`Expected ${String.fromCharCode(termination)}`, index);
|
|
802
|
+
}
|
|
803
|
+
|
|
792
804
|
return args;
|
|
793
805
|
}
|
|
794
806
|
|
|
@@ -899,37 +911,51 @@ export default function parseExpression(expr: string): ExpressionNode {
|
|
|
899
911
|
|
|
900
912
|
const nodes = [];
|
|
901
913
|
|
|
902
|
-
|
|
903
|
-
|
|
914
|
+
try {
|
|
915
|
+
while (index < length) {
|
|
916
|
+
const chIndex = exprICode(index);
|
|
917
|
+
|
|
918
|
+
// Expressions can be separated by semicolons, commas, or just inferred without any
|
|
919
|
+
// separators
|
|
920
|
+
if (chIndex === SEMCOL_CODE || chIndex === COMMA_CODE) {
|
|
921
|
+
index++; // ignore separators
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const node = gobbleExpression();
|
|
904
926
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
927
|
+
// Try to gobble each expression individually
|
|
928
|
+
if (node) {
|
|
929
|
+
nodes.push(node);
|
|
930
|
+
// If we weren't able to find a binary expression and are out of room, then
|
|
931
|
+
// the expression passed in probably has too much
|
|
932
|
+
} else if (index < length) {
|
|
933
|
+
throwError(`Unexpected "${exprI(index)}"`, index);
|
|
934
|
+
}
|
|
910
935
|
}
|
|
911
936
|
|
|
912
|
-
|
|
937
|
+
// If there's only one expression just try returning the expression
|
|
938
|
+
if (nodes.length === 1) {
|
|
939
|
+
return nodes[0];
|
|
940
|
+
}
|
|
913
941
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
}
|
|
920
|
-
|
|
942
|
+
return {
|
|
943
|
+
__id: ExpNodeOpaqueIdentifier,
|
|
944
|
+
type: 'Compound',
|
|
945
|
+
body: nodes,
|
|
946
|
+
location: getLocation(0),
|
|
947
|
+
};
|
|
948
|
+
} catch (e) {
|
|
949
|
+
if (strictMode || !(e instanceof Error)) {
|
|
950
|
+
throw e;
|
|
921
951
|
}
|
|
922
|
-
}
|
|
923
952
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
953
|
+
return {
|
|
954
|
+
__id: ExpNodeOpaqueIdentifier,
|
|
955
|
+
type: 'Compound',
|
|
956
|
+
body: nodes,
|
|
957
|
+
location: getLocation(0),
|
|
958
|
+
error: e,
|
|
959
|
+
};
|
|
927
960
|
}
|
|
928
|
-
|
|
929
|
-
return {
|
|
930
|
-
__id: ExpNodeOpaqueIdentifier,
|
|
931
|
-
type: 'Compound',
|
|
932
|
-
body: nodes,
|
|
933
|
-
location: getLocation(0),
|
|
934
|
-
};
|
|
935
961
|
}
|
package/src/expressions/types.ts
CHANGED
|
@@ -81,6 +81,12 @@ export interface BaseNode<T> {
|
|
|
81
81
|
|
|
82
82
|
/** The location of the node in the source expression string */
|
|
83
83
|
location?: NodeLocation;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The error that occurred while parsing this node
|
|
87
|
+
* This is only set if the parsing mode is set to non-strict
|
|
88
|
+
*/
|
|
89
|
+
error?: Error;
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
/** A helper interface for nodes that container left and right children */
|
package/src/player.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
FlowController,
|
|
22
22
|
} from './controllers';
|
|
23
23
|
import { FlowExpPlugin } from './plugins/flow-exp-plugin';
|
|
24
|
+
import { DefaultExpPlugin } from './plugins/default-exp-plugin';
|
|
24
25
|
import type {
|
|
25
26
|
PlayerFlowState,
|
|
26
27
|
InProgressState,
|
|
@@ -30,8 +31,8 @@ import type {
|
|
|
30
31
|
import { NOT_STARTED_STATE } from './types';
|
|
31
32
|
|
|
32
33
|
// Variables injected at build time
|
|
33
|
-
const PLAYER_VERSION = '0.4.0-next.
|
|
34
|
-
const COMMIT = '
|
|
34
|
+
const PLAYER_VERSION = '0.4.0-next.6';
|
|
35
|
+
const COMMIT = '380eb6be2e6d61675cf721c4c3e64821099e5fbe';
|
|
35
36
|
|
|
36
37
|
export interface PlayerPlugin {
|
|
37
38
|
/**
|
|
@@ -49,6 +50,18 @@ export interface PlayerPlugin {
|
|
|
49
50
|
apply: (player: Player) => void;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
54
|
+
export interface ExtendedPlayerPlugin<
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
56
|
+
Assets = void,
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
58
|
+
Views = void,
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
60
|
+
Expressions = void,
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
62
|
+
DataTypes = void
|
|
63
|
+
> {}
|
|
64
|
+
|
|
52
65
|
export interface PlayerConfigOptions {
|
|
53
66
|
/** A set of plugins to load */
|
|
54
67
|
plugins?: PlayerPlugin[];
|
|
@@ -117,17 +130,16 @@ export class Player {
|
|
|
117
130
|
};
|
|
118
131
|
|
|
119
132
|
constructor(config?: PlayerConfigOptions) {
|
|
120
|
-
const initialPlugins: PlayerPlugin[] = [];
|
|
121
|
-
const flowExpPlugin = new FlowExpPlugin();
|
|
122
|
-
|
|
123
|
-
initialPlugins.push(flowExpPlugin);
|
|
124
|
-
|
|
125
133
|
if (config?.logger) {
|
|
126
134
|
this.logger.addHandler(config.logger);
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
this.config = config || {};
|
|
130
|
-
this.config.plugins = [
|
|
138
|
+
this.config.plugins = [
|
|
139
|
+
new DefaultExpPlugin(),
|
|
140
|
+
...(this.config.plugins || []),
|
|
141
|
+
new FlowExpPlugin(),
|
|
142
|
+
];
|
|
131
143
|
this.config.plugins?.forEach((plugin) => {
|
|
132
144
|
plugin.apply(this);
|
|
133
145
|
});
|
|
@@ -414,19 +426,6 @@ export class Player {
|
|
|
414
426
|
});
|
|
415
427
|
this.hooks.viewController.call(viewController);
|
|
416
428
|
|
|
417
|
-
/** Gets formatter for given formatName and formats value if found, returns value otherwise */
|
|
418
|
-
const formatFunction: ExpressionHandler<[unknown, string], any> = (
|
|
419
|
-
ctx,
|
|
420
|
-
value,
|
|
421
|
-
formatName
|
|
422
|
-
) => {
|
|
423
|
-
return (
|
|
424
|
-
schema.getFormatterForType({ type: formatName })?.format(value) ?? value
|
|
425
|
-
);
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
expressionEvaluator.addExpressionFunction('format', formatFunction);
|
|
429
|
-
|
|
430
429
|
return {
|
|
431
430
|
start: () => {
|
|
432
431
|
flowController
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ExpressionHandler, ExpressionType } from '../expressions';
|
|
2
|
+
import type { SchemaController } from '../schema';
|
|
3
|
+
import type { Player, PlayerPlugin } from '../player';
|
|
4
|
+
|
|
5
|
+
/** Gets formatter for given formatName and formats value if found, returns value otherwise */
|
|
6
|
+
const createFormatFunction = (schema: SchemaController) => {
|
|
7
|
+
/**
|
|
8
|
+
* The generated handler for the given schema
|
|
9
|
+
*/
|
|
10
|
+
const handler: ExpressionHandler<[unknown, string], any> = (
|
|
11
|
+
ctx,
|
|
12
|
+
value,
|
|
13
|
+
formatName
|
|
14
|
+
) => {
|
|
15
|
+
return (
|
|
16
|
+
schema.getFormatterForType({ type: formatName })?.format(value) ?? value
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return handler;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A plugin that provides the out-of-the-box expressions for player
|
|
25
|
+
*/
|
|
26
|
+
export class DefaultExpPlugin implements PlayerPlugin {
|
|
27
|
+
name = 'flow-exp-plugin';
|
|
28
|
+
|
|
29
|
+
apply(player: Player) {
|
|
30
|
+
let formatFunction: ExpressionHandler<[unknown, string]> | undefined;
|
|
31
|
+
|
|
32
|
+
player.hooks.schema.tap(this.name, (schemaController) => {
|
|
33
|
+
formatFunction = createFormatFunction(schemaController);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
player.hooks.expressionEvaluator.tap(this.name, (expEvaluator) => {
|
|
37
|
+
if (formatFunction) {
|
|
38
|
+
expEvaluator.addExpressionFunction('format', formatFunction);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expEvaluator.addExpressionFunction('log', (ctx, ...args) => {
|
|
42
|
+
player.logger.info(...args);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expEvaluator.addExpressionFunction('debug', (ctx, ...args) => {
|
|
46
|
+
player.logger.debug(...args);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expEvaluator.addExpressionFunction(
|
|
50
|
+
'eval',
|
|
51
|
+
(ctx, ...args: [ExpressionType]) => {
|
|
52
|
+
return ctx.evaluate(...args);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/schema/schema.ts
CHANGED
|
@@ -11,8 +11,8 @@ const identify = (val: any) => val;
|
|
|
11
11
|
/** Expand the authored schema into a set of paths -> DataTypes */
|
|
12
12
|
export function parse(
|
|
13
13
|
schema: SchemaType.Schema
|
|
14
|
-
): Map<string, SchemaType.
|
|
15
|
-
const expandedPaths = new Map<string, SchemaType.
|
|
14
|
+
): Map<string, SchemaType.DataTypes> {
|
|
15
|
+
const expandedPaths = new Map<string, SchemaType.DataTypes>();
|
|
16
16
|
|
|
17
17
|
if (!schema.ROOT) {
|
|
18
18
|
return expandedPaths;
|
|
@@ -62,6 +62,10 @@ export function parse(
|
|
|
62
62
|
nestedPath.push('[]');
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
if (type.isRecord) {
|
|
66
|
+
nestedPath.push('{}');
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
if (type.type && schema[type.type]) {
|
|
66
70
|
parseQueue.push({
|
|
67
71
|
path: nestedPath,
|
|
@@ -85,14 +89,14 @@ export class SchemaController implements ValidationProvider {
|
|
|
85
89
|
new Map();
|
|
86
90
|
|
|
87
91
|
private types: Map<string, SchemaType.DataType<any>> = new Map();
|
|
88
|
-
public readonly schema: Map<string, SchemaType.
|
|
92
|
+
public readonly schema: Map<string, SchemaType.DataTypes> = new Map();
|
|
89
93
|
|
|
90
94
|
private bindingSchemaNormalizedCache: Map<BindingInstance, string> =
|
|
91
95
|
new Map();
|
|
92
96
|
|
|
93
97
|
public readonly hooks = {
|
|
94
98
|
resolveTypeForBinding: new SyncWaterfallHook<
|
|
95
|
-
[SchemaType.
|
|
99
|
+
[SchemaType.DataTypes | undefined, BindingInstance]
|
|
96
100
|
>(),
|
|
97
101
|
};
|
|
98
102
|
|
|
@@ -135,17 +139,32 @@ export class SchemaController implements ValidationProvider {
|
|
|
135
139
|
return cached;
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
let bindingArray = binding.asArray();
|
|
143
|
+
let normalized = bindingArray
|
|
140
144
|
.map((p) => (typeof p === 'number' ? '[]' : p))
|
|
141
145
|
.join('.');
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
if (normalized) {
|
|
148
|
+
this.bindingSchemaNormalizedCache.set(binding, normalized);
|
|
149
|
+
bindingArray = normalized.split('.');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
bindingArray.forEach((item) => {
|
|
153
|
+
const recordBinding = bindingArray
|
|
154
|
+
.map((p) => (p === item ? '{}' : p))
|
|
155
|
+
.join('.');
|
|
156
|
+
|
|
157
|
+
if (this.schema.get(recordBinding)) {
|
|
158
|
+
this.bindingSchemaNormalizedCache.set(binding, recordBinding);
|
|
159
|
+
bindingArray = recordBinding.split('.');
|
|
160
|
+
normalized = recordBinding;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
144
163
|
|
|
145
164
|
return normalized;
|
|
146
165
|
}
|
|
147
166
|
|
|
148
|
-
public getType(binding: BindingInstance): SchemaType.
|
|
167
|
+
public getType(binding: BindingInstance): SchemaType.DataTypes | undefined {
|
|
149
168
|
return this.hooks.resolveTypeForBinding.call(
|
|
150
169
|
this.schema.get(this.normalizeBinding(binding)),
|
|
151
170
|
binding
|
|
@@ -154,7 +173,7 @@ export class SchemaController implements ValidationProvider {
|
|
|
154
173
|
|
|
155
174
|
public getApparentType(
|
|
156
175
|
binding: BindingInstance
|
|
157
|
-
): SchemaType.
|
|
176
|
+
): SchemaType.DataTypes | undefined {
|
|
158
177
|
const schemaType = this.getType(binding);
|
|
159
178
|
|
|
160
179
|
if (schemaType === undefined) {
|
|
@@ -6,11 +6,17 @@ const DOUBLE_OPEN_CURLY = '{{';
|
|
|
6
6
|
const DOUBLE_CLOSE_CURLY = '}}';
|
|
7
7
|
|
|
8
8
|
export interface Options {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The model to use when resolving refs
|
|
11
|
+
* Passing `false` will skip trying to resolve any direct model refs ({{foo}})
|
|
12
|
+
*/
|
|
13
|
+
model: false | DataModelWithParser;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A function to evaluate an expression
|
|
17
|
+
* Passing `false` will skip trying to evaluate any expressions (@[ foo() ]@)
|
|
18
|
+
*/
|
|
19
|
+
evaluate: false | ((exp: Expression) => any);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/** Search the given string for the coordinates of the next expression to resolve */
|
|
@@ -70,6 +76,10 @@ export function resolveExpressionsInString(
|
|
|
70
76
|
val: string,
|
|
71
77
|
{ evaluate }: Options
|
|
72
78
|
): string {
|
|
79
|
+
if (!evaluate) {
|
|
80
|
+
return val;
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
const expMatch = /@\[.*?\]@/;
|
|
74
84
|
let newVal = val;
|
|
75
85
|
let match = newVal.match(expMatch);
|
|
@@ -110,6 +120,7 @@ export function resolveDataRefsInString(val: string, options: Options): string {
|
|
|
110
120
|
let workingString = resolveExpressionsInString(val, options);
|
|
111
121
|
|
|
112
122
|
if (
|
|
123
|
+
!model ||
|
|
113
124
|
typeof workingString !== 'string' ||
|
|
114
125
|
workingString.indexOf(DOUBLE_OPEN_CURLY) === -1
|
|
115
126
|
) {
|
|
@@ -166,13 +177,13 @@ function traverseObject<T>(val: T, options: Options): T {
|
|
|
166
177
|
let newVal = val;
|
|
167
178
|
|
|
168
179
|
if (keys.length > 0) {
|
|
169
|
-
|
|
180
|
+
keys.forEach((key) => {
|
|
170
181
|
newVal = setIn(
|
|
171
182
|
newVal as any,
|
|
172
183
|
[key],
|
|
173
184
|
traverseObject((val as any)[key], options)
|
|
174
185
|
) as any;
|
|
175
|
-
}
|
|
186
|
+
});
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
return newVal;
|
|
@@ -72,7 +72,10 @@ export class ValidationMiddleware implements DataModelMiddleware {
|
|
|
72
72
|
} else if (validations instanceof Set) {
|
|
73
73
|
validations.forEach((validation) => {
|
|
74
74
|
invalidBindings.push(validation.binding);
|
|
75
|
-
if (
|
|
75
|
+
if (
|
|
76
|
+
!validation.isStrong &&
|
|
77
|
+
validation.binding.asString() === binding.asString()
|
|
78
|
+
) {
|
|
76
79
|
nextTransaction.push([validation.binding, value]);
|
|
77
80
|
}
|
|
78
81
|
});
|
package/src/view/parser/index.ts
CHANGED
|
@@ -120,7 +120,13 @@ export class Parser {
|
|
|
120
120
|
|
|
121
121
|
const objEntries = Array.isArray(localObj)
|
|
122
122
|
? localObj.map((v, i) => [i, v])
|
|
123
|
-
:
|
|
123
|
+
: [
|
|
124
|
+
...Object.entries(localObj),
|
|
125
|
+
...Object.getOwnPropertySymbols(localObj).map((s) => [
|
|
126
|
+
s,
|
|
127
|
+
(localObj as any)[s],
|
|
128
|
+
]),
|
|
129
|
+
];
|
|
124
130
|
|
|
125
131
|
const defaultValue: NestedObj = {
|
|
126
132
|
children: [],
|