@player-ui/player 0.0.1-next.1

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/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@player-ui/player",
3
+ "version": "0.0.1-next.1",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org"
7
+ },
8
+ "peerDependencies": {},
9
+ "dependencies": {
10
+ "@player-ui/binding-grammar": "0.0.1-next.1",
11
+ "@player-ui/logger": "0.0.1-next.1",
12
+ "@player-ui/partial-match-registry": "0.0.1-next.1",
13
+ "@player-ui/types": "0.0.1-next.1",
14
+ "@player-ui/utils": "0.0.1-next.1",
15
+ "babel-plugin-preval": "^5.0.0",
16
+ "dequal": "^2.0.2",
17
+ "p-defer": "^3.0.0",
18
+ "queue-microtask": "^1.2.3",
19
+ "tapable": "1.1.3",
20
+ "timm": "^1.6.2",
21
+ "@babel/runtime": "7.15.4"
22
+ },
23
+ "main": "dist/index.cjs.js",
24
+ "module": "dist/index.esm.js",
25
+ "typings": "dist/index.d.ts"
26
+ }
package/src/data.ts ADDED
@@ -0,0 +1,247 @@
1
+ import { SyncHook, SyncWaterfallHook, SyncBailHook } from 'tapable';
2
+ import type { Logger } from '@player-ui/logger';
3
+ import { omit, removeAt } from 'timm';
4
+ import { dequal } from 'dequal';
5
+ import type { BindingParser, BindingLike } from '@player-ui/binding';
6
+ import { BindingInstance } from '@player-ui/binding';
7
+ import type {
8
+ BatchSetTransaction,
9
+ Updates,
10
+ DataModelOptions,
11
+ DataModelWithParser,
12
+ DataPipeline,
13
+ DataModelMiddleware,
14
+ } from '@player-ui/data';
15
+ import { PipelinedDataModel, LocalModel } from '@player-ui/data';
16
+ import type { RawSetTransaction } from './types';
17
+
18
+ /** The orchestrator for player data */
19
+ export class DataController implements DataModelWithParser<DataModelOptions> {
20
+ public hooks = {
21
+ resolve: new SyncWaterfallHook(['binding']),
22
+ resolveDataStages: new SyncWaterfallHook<DataPipeline>(['pipeline']),
23
+
24
+ // On any set or get of an undefined value, redirect the value to be the default
25
+ resolveDefaultValue: new SyncBailHook<BindingInstance, any>(['binding']),
26
+
27
+ onDelete: new SyncHook<any, void>(['binding']),
28
+ onSet: new SyncHook<BatchSetTransaction, void>(['transaction']),
29
+ onGet: new SyncHook<any, any, void>(['binding', 'result']),
30
+ onUpdate: new SyncHook<Updates>(['updates']),
31
+
32
+ format: new SyncWaterfallHook<any, BindingInstance>(['value', 'binding']),
33
+ deformat: new SyncWaterfallHook<any, BindingInstance>(['value', 'binding']),
34
+
35
+ serialize: new SyncWaterfallHook<any>(['data']),
36
+ };
37
+
38
+ private model?: PipelinedDataModel;
39
+ private trash: Set<BindingInstance>;
40
+ private pathResolver: BindingParser;
41
+ private baseMiddleware: Array<DataModelMiddleware>;
42
+ private logger?: Logger;
43
+
44
+ constructor(
45
+ model: Record<any, unknown> | undefined,
46
+ options: {
47
+ /** A means of parsing a raw binding to a Binding object */
48
+ pathResolver: BindingParser;
49
+
50
+ /** middleware to use. typically for validation */
51
+ middleware?: Array<DataModelMiddleware>;
52
+
53
+ /** A logger to use */
54
+ logger?: Logger;
55
+ }
56
+ ) {
57
+ this.logger = options.logger;
58
+ const middleware = options.middleware || [];
59
+ this.baseMiddleware = [new LocalModel(model), ...middleware];
60
+
61
+ this.trash = new Set();
62
+ this.pathResolver = options.pathResolver;
63
+ }
64
+
65
+ public getModel(): PipelinedDataModel {
66
+ if (!this.model) {
67
+ const stages = this.hooks.resolveDataStages.call(this.baseMiddleware);
68
+ const model = new PipelinedDataModel();
69
+ model.setMiddleware(stages);
70
+ this.model = model;
71
+ }
72
+
73
+ return this.model;
74
+ }
75
+
76
+ private resolveDataValue(
77
+ binding: BindingInstance,
78
+ value: any,
79
+ deformat: boolean
80
+ ) {
81
+ if (deformat) {
82
+ return this.hooks.deformat.call(value, binding);
83
+ }
84
+
85
+ return value;
86
+ }
87
+
88
+ public set(
89
+ transaction: RawSetTransaction,
90
+ options?: DataModelOptions
91
+ ): Updates {
92
+ let normalizedTransaction: BatchSetTransaction = [];
93
+
94
+ if (Array.isArray(transaction)) {
95
+ normalizedTransaction = transaction.map(([binding, value]) => {
96
+ const parsed = this.pathResolver.parse(binding);
97
+
98
+ return [
99
+ parsed,
100
+ this.resolveDataValue(parsed, value, Boolean(options?.formatted)),
101
+ ];
102
+ }) as BatchSetTransaction;
103
+ } else {
104
+ normalizedTransaction = Object.keys(transaction).map(
105
+ (binding: string) => {
106
+ const parsed = this.pathResolver.parse(binding);
107
+ const val = transaction[binding];
108
+
109
+ return [
110
+ parsed,
111
+ this.resolveDataValue(parsed, val, Boolean(options?.formatted)),
112
+ ];
113
+ }
114
+ ) as BatchSetTransaction;
115
+ }
116
+
117
+ // Figure out what the base changes being applied are
118
+ const setUpdates = normalizedTransaction.reduce<Updates>(
119
+ (updates, [binding, newVal]) => {
120
+ const oldVal = this.get(binding, { includeInvalid: true });
121
+
122
+ if (!dequal(oldVal, newVal)) {
123
+ updates.push({
124
+ binding,
125
+ newValue: newVal,
126
+ oldValue: oldVal,
127
+ });
128
+ }
129
+
130
+ this.logger?.debug(
131
+ `Setting path: ${binding.asString()} from: ${oldVal} to: ${newVal}`
132
+ );
133
+
134
+ return updates;
135
+ },
136
+ []
137
+ );
138
+
139
+ // Get the applied update
140
+ const result = this.getModel().set(normalizedTransaction, options);
141
+
142
+ // Add any extra bindings that were effected
143
+ const setUpdateBindings = new Set(setUpdates.map((su) => su.binding));
144
+ result.forEach((tr) => {
145
+ if (
146
+ !setUpdateBindings.has(tr.binding) &&
147
+ (tr.force === true || !dequal(tr.oldValue, tr.newValue))
148
+ ) {
149
+ this.logger?.debug(
150
+ `Path: ${tr.binding.asString()} was changed from: ${
151
+ tr.oldValue
152
+ } to: ${tr.newValue}`
153
+ );
154
+ setUpdates.push(tr);
155
+ }
156
+ });
157
+
158
+ this.hooks.onSet.call(normalizedTransaction);
159
+
160
+ if (setUpdates.length > 0) {
161
+ this.hooks.onUpdate.call(setUpdates);
162
+ }
163
+
164
+ return result;
165
+ }
166
+
167
+ private resolve(binding: BindingLike): BindingInstance {
168
+ return Array.isArray(binding) || typeof binding === 'string'
169
+ ? this.pathResolver.parse(binding)
170
+ : binding;
171
+ }
172
+
173
+ public get(binding: BindingLike, options?: DataModelOptions) {
174
+ const resolved =
175
+ binding instanceof BindingInstance ? binding : this.resolve(binding);
176
+ let result = this.getModel().get(resolved, options);
177
+
178
+ if (result === undefined && !options?.ignoreDefaultValue) {
179
+ const defaultVal = this.hooks.resolveDefaultValue.call(resolved);
180
+
181
+ if (defaultVal !== result) {
182
+ result = defaultVal;
183
+ }
184
+ }
185
+
186
+ if (options?.formatted) {
187
+ result = this.hooks.format.call(result, resolved);
188
+ }
189
+
190
+ this.hooks.onGet.call(binding, result);
191
+
192
+ return result;
193
+ }
194
+
195
+ public delete(binding: BindingLike) {
196
+ if (binding === undefined || binding === null) {
197
+ throw new Error(`Invalid arguments: delete expects a data path (string)`);
198
+ }
199
+
200
+ const resolved = this.resolve(binding);
201
+ this.hooks.onDelete.call(resolved);
202
+ this.deleteData(resolved);
203
+ }
204
+
205
+ public getTrash(): Set<BindingInstance> {
206
+ return this.trash;
207
+ }
208
+
209
+ private addToTrash(binding: BindingInstance) {
210
+ this.trash.add(binding);
211
+ }
212
+
213
+ private deleteData(binding: BindingInstance) {
214
+ const parentBinding = binding.parent();
215
+ const parentPath = parentBinding.asString();
216
+ const property = binding.key();
217
+
218
+ const existedBeforeDelete = Object.prototype.hasOwnProperty.call(
219
+ this.get(parentBinding),
220
+ property
221
+ );
222
+
223
+ if (property !== undefined) {
224
+ const parent = parentBinding ? this.get(parentBinding) : undefined;
225
+
226
+ // If we're deleting an item in an array, we just splice it out
227
+ // Don't add it to the trash
228
+ if (parentPath && Array.isArray(parent)) {
229
+ if (parent.length > property) {
230
+ this.set([[parentBinding, removeAt(parent, property as number)]]);
231
+ }
232
+ } else if (parentPath && parent[property]) {
233
+ this.set([[parentBinding, omit(parent, property as string)]]);
234
+ } else if (!parentPath) {
235
+ this.getModel().reset(omit(this.get(''), property as string));
236
+ }
237
+ }
238
+
239
+ if (existedBeforeDelete && !this.get(binding)) {
240
+ this.addToTrash(binding);
241
+ }
242
+ }
243
+
244
+ public serialize(): object {
245
+ return this.hooks.serialize.call(this.get(''));
246
+ }
247
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ // Add the types export first so it's naming takes precedence
2
+ export * from '@player-ui/types';
3
+ export * from '@player-ui/binding';
4
+ export * from '@player-ui/data';
5
+ export * from '@player-ui/expressions';
6
+ export * from '@player-ui/flow';
7
+ export * from '@player-ui/logger';
8
+ export * from '@player-ui/schema';
9
+ export * from '@player-ui/string-resolver';
10
+ export * from '@player-ui/validator';
11
+ export * from '@player-ui/view';
12
+
13
+ export * from './player';
14
+ export * from './validation';
15
+ export * from './view';
16
+ export * from './data';
17
+ export * from './types';
18
+ export * from './plugins/flow-exp-plugin';