@player-ui/async-node-plugin 0.8.0--canary.307.9645 → 0.8.0--canary.410.15883

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/src/index.ts CHANGED
@@ -1,107 +1,192 @@
1
- import { NodeType, getNodeID } from '@player-ui/player';
1
+ import { NodeType, getNodeID } from "@player-ui/player";
2
2
  import type {
3
3
  Player,
4
4
  PlayerPlugin,
5
5
  Node,
6
6
  ParseObjectOptions,
7
- } from '@player-ui/player';
8
- import { AsyncParallelBailHook } from 'tapable-ts';
9
- import queueMicrotask from 'queue-microtask';
10
- import { omit } from 'timm';
7
+ ViewInstance,
8
+ Parser,
9
+ ViewPlugin,
10
+ Resolver,
11
+ Resolve,
12
+ } from "@player-ui/player";
13
+ import { AsyncParallelBailHook } from "tapable-ts";
14
+ import queueMicrotask from "queue-microtask";
15
+ import { omit } from "timm";
11
16
 
12
- export * from './types';
17
+ export * from "./types";
18
+
19
+ export interface AsyncNodePluginOptions {
20
+ /** A set of plugins to load */
21
+ plugins?: AsyncNodeViewPlugin[];
22
+ }
23
+
24
+ export interface AsyncNodeViewPlugin extends ViewPlugin {
25
+ /** Use this to tap into the async node plugin hooks */
26
+ applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;
27
+
28
+ asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;
29
+ }
13
30
 
14
31
  /**
15
32
  * Async node plugin used to resolve async nodes in the content
16
33
  * If an async node is present, allow users to provide a replacement node to be rendered when ready
17
34
  */
18
35
  export class AsyncNodePlugin implements PlayerPlugin {
19
- public readonly hooks = {
20
- onAsyncNode: new AsyncParallelBailHook<[Node.Node], Node.Node>(),
21
- };
36
+ private plugins: AsyncNodeViewPlugin[] | undefined;
22
37
 
23
- name = 'AsyncNode';
38
+ constructor(options: AsyncNodePluginOptions) {
39
+ if (options?.plugins) {
40
+ this.plugins = options.plugins;
41
+ options.plugins.forEach((plugin) => {
42
+ plugin.applyPlugin(this);
43
+ });
44
+ }
45
+ }
24
46
 
25
- private resolvedMapping = new Map<string, Node.Node>();
47
+ public readonly hooks = {
48
+ onAsyncNode: new AsyncParallelBailHook<
49
+ [Node.Async, (result: any) => void],
50
+ any
51
+ >(),
52
+ };
26
53
 
27
- private isAsync(node: Node.Node | null): node is Node.Async {
28
- return node?.type === NodeType.Async;
29
- }
54
+ name = "AsyncNode";
30
55
 
31
56
  apply(player: Player) {
32
57
  player.hooks.viewController.tap(this.name, (viewController) => {
33
58
  viewController.hooks.view.tap(this.name, (view) => {
34
- view.hooks.parser.tap(this.name, (parser) => {
35
- parser.hooks.determineNodeType.tap(this.name, (obj) => {
36
- if (Object.prototype.hasOwnProperty.call(obj, 'async')) {
37
- return NodeType.Async;
38
- }
39
- });
40
- parser.hooks.parseNode.tap(
41
- this.name,
42
- (
43
- obj: any,
44
- nodeType: Node.ChildrenTypes,
45
- options: ParseObjectOptions,
46
- determinedNodeType: null | NodeType
47
- ) => {
48
- if (determinedNodeType === NodeType.Async) {
49
- const parsedAsync = parser.parseObject(
50
- omit(obj, 'async'),
51
- nodeType,
52
- options
53
- );
54
- const parsedNodeId = getNodeID(parsedAsync);
55
- if (parsedAsync !== null && parsedNodeId) {
56
- return parser.createASTNode(
57
- {
58
- id: parsedNodeId,
59
- type: NodeType.Async,
60
- value: parsedAsync,
61
- },
62
- obj
63
- );
64
- }
65
-
66
- return null;
67
- }
68
- }
69
- );
59
+ this.plugins?.forEach((plugin) => {
60
+ plugin.apply(view);
70
61
  });
62
+ });
63
+ });
64
+ }
65
+ }
66
+
67
+ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
68
+ public asyncNode = new AsyncParallelBailHook<
69
+ [Node.Async, (result: any) => void],
70
+ any
71
+ >();
72
+ private basePlugin: AsyncNodePlugin | undefined;
73
+
74
+ name = "AsyncNode";
75
+
76
+ private resolvedMapping = new Map<string, any>();
71
77
 
72
- view.hooks.resolver.tap(this.name, (resolver) => {
73
- resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
74
- let resolvedNode;
75
- if (this.isAsync(node)) {
76
- const mappedValue = this.resolvedMapping.get(node.id);
77
- if (mappedValue) {
78
- resolvedNode = mappedValue;
79
- }
80
- } else {
81
- resolvedNode = null;
82
- }
83
-
84
- const newNode = resolvedNode || node;
85
- if (!resolvedNode && node?.type === NodeType.Async) {
86
- queueMicrotask(async () => {
87
- const result = await this.hooks.onAsyncNode.call(node);
88
- const parsedNode = options.parseNode
89
- ? options.parseNode(result)
90
- : undefined;
91
-
92
- if (parsedNode) {
93
- this.resolvedMapping.set(node.id, parsedNode);
94
- viewController.currentView?.updateAsync();
95
- }
96
- });
97
-
98
- return node;
99
- }
100
-
101
- return newNode;
102
- });
78
+ private currentView: ViewInstance | undefined;
79
+
80
+ /**
81
+ * Updates the node asynchronously based on the result provided.
82
+ * This method is responsible for handling the update logic of asynchronous nodes.
83
+ * It checks if the node needs to be updated based on the new result and updates the mapping accordingly.
84
+ * If an update is necessary, it triggers an asynchronous update on the view.
85
+ * @param node The asynchronous node that might be updated.
86
+ * @param result The result obtained from resolving the async node. This could be any data structure or value.
87
+ * @param options Options provided for node resolution, including a potential parseNode function to process the result.
88
+ * @param view The view instance where the node resides. This can be undefined if the view is not currently active.
89
+ */
90
+ private handleAsyncUpdate(
91
+ node: Node.Async,
92
+ result: any,
93
+ options: Resolve.NodeResolveOptions,
94
+ view: ViewInstance | undefined,
95
+ ) {
96
+ const parsedNode =
97
+ options.parseNode && result ? options.parseNode(result) : undefined;
98
+
99
+ if (this.resolvedMapping.get(node.id) !== parsedNode) {
100
+ this.resolvedMapping.set(node.id, parsedNode ? parsedNode : node);
101
+ view?.updateAsync();
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Handles the asynchronous API integration for resolving nodes.
107
+ * This method sets up a hook on the resolver's `beforeResolve` event to process async nodes.
108
+ * @param resolver The resolver instance to attach the hook to.
109
+ * @param view
110
+ */
111
+ applyResolver(resolver: Resolver) {
112
+ resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
113
+ let resolvedNode;
114
+ if (this.isAsync(node)) {
115
+ const mappedValue = this.resolvedMapping.get(node.id);
116
+ if (mappedValue) {
117
+ resolvedNode = mappedValue;
118
+ }
119
+ } else {
120
+ resolvedNode = null;
121
+ }
122
+
123
+ const newNode = resolvedNode || node;
124
+ if (!resolvedNode && node?.type === NodeType.Async) {
125
+ queueMicrotask(async () => {
126
+ const result = await this.basePlugin?.hooks.onAsyncNode.call(
127
+ node,
128
+ (result) => {
129
+ this.handleAsyncUpdate(node, result, options, this.currentView);
130
+ },
131
+ );
132
+ this.handleAsyncUpdate(node, result, options, this.currentView);
103
133
  });
104
- });
134
+
135
+ return node;
136
+ }
137
+ return newNode;
105
138
  });
106
139
  }
140
+
141
+ private isAsync(node: Node.Node | null): node is Node.Async {
142
+ return node?.type === NodeType.Async;
143
+ }
144
+
145
+ applyParser(parser: Parser) {
146
+ parser.hooks.determineNodeType.tap(this.name, (obj) => {
147
+ if (Object.prototype.hasOwnProperty.call(obj, "async")) {
148
+ return NodeType.Async;
149
+ }
150
+ });
151
+ parser.hooks.parseNode.tap(
152
+ this.name,
153
+ (
154
+ obj: any,
155
+ nodeType: Node.ChildrenTypes,
156
+ options: ParseObjectOptions,
157
+ determinedNodeType: null | NodeType,
158
+ ) => {
159
+ if (determinedNodeType === NodeType.Async) {
160
+ const parsedAsync = parser.parseObject(
161
+ omit(obj, "async"),
162
+ nodeType,
163
+ options,
164
+ );
165
+ const parsedNodeId = getNodeID(parsedAsync);
166
+ if (parsedAsync !== null && parsedNodeId) {
167
+ return parser.createASTNode(
168
+ {
169
+ id: parsedNodeId,
170
+ type: NodeType.Async,
171
+ value: parsedAsync,
172
+ },
173
+ obj,
174
+ );
175
+ }
176
+
177
+ return null;
178
+ }
179
+ },
180
+ );
181
+ }
182
+
183
+ apply(view: ViewInstance): void {
184
+ this.currentView = view;
185
+ view.hooks.parser.tap("async", this.applyParser.bind(this));
186
+ view.hooks.resolver.tap("async", this.applyResolver.bind(this));
187
+ }
188
+
189
+ applyPlugin(asyncNodePlugin: AsyncNodePlugin): void {
190
+ this.basePlugin = asyncNodePlugin;
191
+ }
107
192
  }
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { Node } from '@player-ui/player';
1
+ import type { Node } from "@player-ui/player";
2
2
 
3
3
  export type AsyncNodeHandler = (
4
4
  node: Node.Node,
5
- update: (object: any) => void
5
+ update: (object: any) => void,
6
6
  ) => void;
@@ -0,0 +1,55 @@
1
+ import type { Player, PlayerPlugin, Node, ViewInstance, Parser, ViewPlugin, Resolver } from "@player-ui/player";
2
+ import { AsyncParallelBailHook } from "tapable-ts";
3
+ export * from "./types";
4
+ export interface AsyncNodePluginOptions {
5
+ /** A set of plugins to load */
6
+ plugins?: AsyncNodeViewPlugin[];
7
+ }
8
+ export interface AsyncNodeViewPlugin extends ViewPlugin {
9
+ /** Use this to tap into the async node plugin hooks */
10
+ applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;
11
+ asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;
12
+ }
13
+ /**
14
+ * Async node plugin used to resolve async nodes in the content
15
+ * If an async node is present, allow users to provide a replacement node to be rendered when ready
16
+ */
17
+ export declare class AsyncNodePlugin implements PlayerPlugin {
18
+ private plugins;
19
+ constructor(options: AsyncNodePluginOptions);
20
+ readonly hooks: {
21
+ onAsyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any, Record<string, any>>;
22
+ };
23
+ name: string;
24
+ apply(player: Player): void;
25
+ }
26
+ export declare class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
27
+ asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any, Record<string, any>>;
28
+ private basePlugin;
29
+ name: string;
30
+ private resolvedMapping;
31
+ private currentView;
32
+ /**
33
+ * Updates the node asynchronously based on the result provided.
34
+ * This method is responsible for handling the update logic of asynchronous nodes.
35
+ * It checks if the node needs to be updated based on the new result and updates the mapping accordingly.
36
+ * If an update is necessary, it triggers an asynchronous update on the view.
37
+ * @param node The asynchronous node that might be updated.
38
+ * @param result The result obtained from resolving the async node. This could be any data structure or value.
39
+ * @param options Options provided for node resolution, including a potential parseNode function to process the result.
40
+ * @param view The view instance where the node resides. This can be undefined if the view is not currently active.
41
+ */
42
+ private handleAsyncUpdate;
43
+ /**
44
+ * Handles the asynchronous API integration for resolving nodes.
45
+ * This method sets up a hook on the resolver's `beforeResolve` event to process async nodes.
46
+ * @param resolver The resolver instance to attach the hook to.
47
+ * @param view
48
+ */
49
+ applyResolver(resolver: Resolver): void;
50
+ private isAsync;
51
+ applyParser(parser: Parser): void;
52
+ apply(view: ViewInstance): void;
53
+ applyPlugin(asyncNodePlugin: AsyncNodePlugin): void;
54
+ }
55
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ import type { Node } from "@player-ui/player";
2
+ export type AsyncNodeHandler = (node: Node.Node, update: (object: any) => void) => void;
3
+ //# sourceMappingURL=types.d.ts.map