@player-ui/async-node-plugin 0.13.0-next.2 → 0.13.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/AsyncNodePlugin.native.js +114 -48
- package/dist/AsyncNodePlugin.native.js.map +1 -1
- package/dist/cjs/index.cjs +72 -32
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +73 -33
- package/dist/index.mjs +73 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/index.test.ts +274 -9
- package/src/index.ts +125 -46
- package/types/index.d.ts +37 -10
package/dist/cjs/index.cjs
CHANGED
|
@@ -65,7 +65,8 @@ var asyncTransform = (assetId, wrapperAssetType, asset, flatten) => {
|
|
|
65
65
|
var AsyncNodePlugin = class {
|
|
66
66
|
constructor(options, asyncHandler) {
|
|
67
67
|
this.hooks = {
|
|
68
|
-
onAsyncNode: new import_tapable_ts.AsyncParallelBailHook()
|
|
68
|
+
onAsyncNode: new import_tapable_ts.AsyncParallelBailHook(),
|
|
69
|
+
onAsyncNodeError: new import_tapable_ts.SyncBailHook()
|
|
69
70
|
};
|
|
70
71
|
this.name = "AsyncNode";
|
|
71
72
|
if (options?.plugins) {
|
|
@@ -83,7 +84,11 @@ var AsyncNodePlugin = class {
|
|
|
83
84
|
);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
87
|
+
getPlayerInstance() {
|
|
88
|
+
return this.playerInstance;
|
|
89
|
+
}
|
|
86
90
|
apply(player) {
|
|
91
|
+
this.playerInstance = player;
|
|
87
92
|
player.hooks.viewController.tap(this.name, (viewController) => {
|
|
88
93
|
viewController.hooks.view.tap(this.name, (view) => {
|
|
89
94
|
this.plugins?.forEach((plugin) => {
|
|
@@ -97,7 +102,17 @@ var AsyncNodePluginPlugin = class {
|
|
|
97
102
|
constructor() {
|
|
98
103
|
this.asyncNode = new import_tapable_ts.AsyncParallelBailHook();
|
|
99
104
|
this.name = "AsyncNode";
|
|
100
|
-
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parses the node from the result and triggers an asynchronous view update if necessary.
|
|
108
|
+
* @param node The asynchronous node that might be updated.
|
|
109
|
+
* @param result The result obtained from resolving the async node. This could be any data structure or value.
|
|
110
|
+
* @param options Options provided for node resolution, including a potential parseNode function to process the result.
|
|
111
|
+
* @param view The view instance where the node resides. This can be undefined if the view is not currently active.
|
|
112
|
+
*/
|
|
113
|
+
parseNodeAndUpdate(node, context, result, options) {
|
|
114
|
+
const parsedNode = options.parseNode && result ? options.parseNode(result) : void 0;
|
|
115
|
+
this.handleAsyncUpdate(node, context, parsedNode);
|
|
101
116
|
}
|
|
102
117
|
/**
|
|
103
118
|
* Updates the node asynchronously based on the result provided.
|
|
@@ -105,15 +120,14 @@ var AsyncNodePluginPlugin = class {
|
|
|
105
120
|
* It checks if the node needs to be updated based on the new result and updates the mapping accordingly.
|
|
106
121
|
* If an update is necessary, it triggers an asynchronous update on the view.
|
|
107
122
|
* @param node The asynchronous node that might be updated.
|
|
108
|
-
* @param
|
|
109
|
-
* @param options Options provided for node resolution, including a potential parseNode function to process the result.
|
|
123
|
+
* @param newNode The new node to replace the async node.
|
|
110
124
|
* @param view The view instance where the node resides. This can be undefined if the view is not currently active.
|
|
111
125
|
*/
|
|
112
|
-
handleAsyncUpdate(node,
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
view
|
|
126
|
+
handleAsyncUpdate(node, context, newNode) {
|
|
127
|
+
const { nodeResolveCache, view } = context;
|
|
128
|
+
if (nodeResolveCache.get(node.id) !== newNode) {
|
|
129
|
+
nodeResolveCache.set(node.id, newNode ? newNode : node);
|
|
130
|
+
view.updateAsync();
|
|
117
131
|
}
|
|
118
132
|
}
|
|
119
133
|
/**
|
|
@@ -122,33 +136,53 @@ var AsyncNodePluginPlugin = class {
|
|
|
122
136
|
* @param resolver The resolver instance to attach the hook to.
|
|
123
137
|
* @param view
|
|
124
138
|
*/
|
|
125
|
-
applyResolver(resolver) {
|
|
139
|
+
applyResolver(resolver, context) {
|
|
126
140
|
resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const mappedValue = this.resolvedMapping.get(node.id);
|
|
130
|
-
if (mappedValue) {
|
|
131
|
-
resolvedNode = mappedValue;
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
resolvedNode = null;
|
|
141
|
+
if (!this.isAsync(node)) {
|
|
142
|
+
return node;
|
|
135
143
|
}
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
(result2) => {
|
|
142
|
-
this.handleAsyncUpdate(node, result2, options, this.currentView);
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
this.handleAsyncUpdate(node, result, options, this.currentView);
|
|
146
|
-
});
|
|
144
|
+
const resolvedNode = context.nodeResolveCache.get(node.id);
|
|
145
|
+
if (resolvedNode !== void 0) {
|
|
146
|
+
return resolvedNode;
|
|
147
|
+
}
|
|
148
|
+
if (context.inProgressNodes.has(node.id)) {
|
|
147
149
|
return node;
|
|
148
150
|
}
|
|
149
|
-
|
|
151
|
+
context.inProgressNodes.add(node.id);
|
|
152
|
+
(0, import_queue_microtask.default)(() => {
|
|
153
|
+
this.runAsyncNode(node, context, options).finally();
|
|
154
|
+
});
|
|
155
|
+
return node;
|
|
150
156
|
});
|
|
151
157
|
}
|
|
158
|
+
async runAsyncNode(node, context, options) {
|
|
159
|
+
try {
|
|
160
|
+
const result = await this.basePlugin?.hooks.onAsyncNode.call(
|
|
161
|
+
node,
|
|
162
|
+
(result2) => {
|
|
163
|
+
this.parseNodeAndUpdate(node, context, result2, options);
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
context.inProgressNodes.delete(node.id);
|
|
167
|
+
this.parseNodeAndUpdate(node, context, result, options);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
170
|
+
const result = this.basePlugin?.hooks.onAsyncNodeError.call(error, node);
|
|
171
|
+
if (result === void 0) {
|
|
172
|
+
const playerState = this.basePlugin?.getPlayerInstance()?.getState();
|
|
173
|
+
if (playerState?.status === "in-progress") {
|
|
174
|
+
playerState.fail(error);
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
options.logger?.error(
|
|
179
|
+
"Async node handling failed and resolved with a fallback. Error:",
|
|
180
|
+
error
|
|
181
|
+
);
|
|
182
|
+
context.inProgressNodes.delete(node.id);
|
|
183
|
+
this.parseNodeAndUpdate(node, context, result, options);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
152
186
|
isAsync(node) {
|
|
153
187
|
return node?.type === import_player2.NodeType.Async;
|
|
154
188
|
}
|
|
@@ -191,9 +225,15 @@ var AsyncNodePluginPlugin = class {
|
|
|
191
225
|
);
|
|
192
226
|
}
|
|
193
227
|
apply(view) {
|
|
194
|
-
|
|
228
|
+
const context = {
|
|
229
|
+
nodeResolveCache: /* @__PURE__ */ new Map(),
|
|
230
|
+
inProgressNodes: /* @__PURE__ */ new Set(),
|
|
231
|
+
view
|
|
232
|
+
};
|
|
195
233
|
view.hooks.parser.tap("async", this.applyParser.bind(this));
|
|
196
|
-
view.hooks.resolver.tap("async",
|
|
234
|
+
view.hooks.resolver.tap("async", (resolver) => {
|
|
235
|
+
this.applyResolver(resolver, context);
|
|
236
|
+
});
|
|
197
237
|
}
|
|
198
238
|
applyPlugin(asyncNodePlugin) {
|
|
199
239
|
this.basePlugin = asyncNodePlugin;
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/index.ts","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/transform.ts"],"sourcesContent":["import { NodeType, getNodeID } from \"@player-ui/player\";\nimport type {\n Player,\n PlayerPlugin,\n Node,\n ParseObjectOptions,\n ParseObjectChildOptions,\n ViewInstance,\n Parser,\n ViewPlugin,\n Resolver,\n Resolve,\n} from \"@player-ui/player\";\nimport { AsyncParallelBailHook } from \"tapable-ts\";\nimport queueMicrotask from \"queue-microtask\";\nimport { omit } from \"timm\";\n\nexport * from \"./types\";\nexport * from \"./transform\";\n\nexport interface AsyncNodePluginOptions {\n /** A set of plugins to load */\n plugins?: AsyncNodeViewPlugin[];\n}\n\nexport interface AsyncNodeViewPlugin extends ViewPlugin {\n /** Use this to tap into the async node plugin hooks */\n applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;\n\n asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;\n}\nexport type AsyncHandler = (\n node: Node.Async,\n callback?: (result: any) => void,\n) => Promise<any>;\n\n/**\n * Async node plugin used to resolve async nodes in the content\n * If an async node is present, allow users to provide a replacement node to be rendered when ready\n */\nexport class AsyncNodePlugin implements PlayerPlugin {\n private plugins: AsyncNodeViewPlugin[] | undefined;\n\n constructor(options: AsyncNodePluginOptions, asyncHandler?: AsyncHandler) {\n if (options?.plugins) {\n this.plugins = options.plugins;\n options.plugins.forEach((plugin) => {\n plugin.applyPlugin(this);\n });\n }\n\n if (asyncHandler) {\n this.hooks.onAsyncNode.tap(\n \"async\",\n async (node: Node.Async, callback) => {\n return await asyncHandler(node, callback);\n },\n );\n }\n }\n\n public readonly hooks = {\n onAsyncNode: new AsyncParallelBailHook<\n [Node.Async, (result: any) => void],\n any\n >(),\n };\n\n name = \"AsyncNode\";\n\n apply(player: Player) {\n player.hooks.viewController.tap(this.name, (viewController) => {\n viewController.hooks.view.tap(this.name, (view) => {\n this.plugins?.forEach((plugin) => {\n plugin.apply(view);\n });\n });\n });\n }\n}\n\nexport class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {\n public asyncNode = new AsyncParallelBailHook<\n [Node.Async, (result: any) => void],\n any\n >();\n private basePlugin: AsyncNodePlugin | undefined;\n\n name = \"AsyncNode\";\n\n private resolvedMapping = new Map<string, any>();\n\n private currentView: ViewInstance | undefined;\n\n /**\n * Updates the node asynchronously based on the result provided.\n * This method is responsible for handling the update logic of asynchronous nodes.\n * It checks if the node needs to be updated based on the new result and updates the mapping accordingly.\n * If an update is necessary, it triggers an asynchronous update on the view.\n * @param node The asynchronous node that might be updated.\n * @param result The result obtained from resolving the async node. This could be any data structure or value.\n * @param options Options provided for node resolution, including a potential parseNode function to process the result.\n * @param view The view instance where the node resides. This can be undefined if the view is not currently active.\n */\n private handleAsyncUpdate(\n node: Node.Async,\n result: any,\n options: Resolve.NodeResolveOptions,\n view: ViewInstance | undefined,\n ) {\n const parsedNode =\n options.parseNode && result ? options.parseNode(result) : undefined;\n\n if (this.resolvedMapping.get(node.id) !== parsedNode) {\n this.resolvedMapping.set(node.id, parsedNode ? parsedNode : node);\n view?.updateAsync();\n }\n }\n\n /**\n * Handles the asynchronous API integration for resolving nodes.\n * This method sets up a hook on the resolver's `beforeResolve` event to process async nodes.\n * @param resolver The resolver instance to attach the hook to.\n * @param view\n */\n applyResolver(resolver: Resolver) {\n resolver.hooks.beforeResolve.tap(this.name, (node, options) => {\n let resolvedNode;\n if (this.isAsync(node)) {\n const mappedValue = this.resolvedMapping.get(node.id);\n if (mappedValue) {\n resolvedNode = mappedValue;\n }\n } else {\n resolvedNode = null;\n }\n\n const newNode = resolvedNode || node;\n if (!resolvedNode && node?.type === NodeType.Async) {\n queueMicrotask(async () => {\n const result = await this.basePlugin?.hooks.onAsyncNode.call(\n node,\n (result) => {\n this.handleAsyncUpdate(node, result, options, this.currentView);\n },\n );\n this.handleAsyncUpdate(node, result, options, this.currentView);\n });\n\n return node;\n }\n return newNode;\n });\n }\n\n private isAsync(node: Node.Node | null): node is Node.Async {\n return node?.type === NodeType.Async;\n }\n\n private isDeterminedAsync(obj: any) {\n return obj && Object.prototype.hasOwnProperty.call(obj, \"async\");\n }\n\n applyParser(parser: Parser) {\n parser.hooks.parseNode.tap(\n this.name,\n (\n obj: any,\n nodeType: Node.ChildrenTypes,\n options: ParseObjectOptions,\n childOptions?: ParseObjectChildOptions,\n ) => {\n if (this.isDeterminedAsync(obj)) {\n const parsedAsync = parser.parseObject(\n omit(obj, \"async\"),\n nodeType,\n options,\n );\n const parsedNodeId = getNodeID(parsedAsync);\n\n if (parsedAsync === null || !parsedNodeId) {\n return childOptions ? [] : null;\n }\n\n const asyncAST = parser.createASTNode(\n {\n id: parsedNodeId,\n type: NodeType.Async,\n value: parsedAsync,\n },\n obj,\n );\n\n if (childOptions) {\n return asyncAST\n ? [\n {\n path: [...childOptions.path, childOptions.key],\n value: asyncAST,\n },\n ]\n : [];\n }\n\n return asyncAST;\n }\n },\n );\n }\n\n apply(view: ViewInstance): void {\n this.currentView = view;\n view.hooks.parser.tap(\"async\", this.applyParser.bind(this));\n view.hooks.resolver.tap(\"async\", this.applyResolver.bind(this));\n }\n\n applyPlugin(asyncNodePlugin: AsyncNodePlugin): void {\n this.basePlugin = asyncNodePlugin;\n }\n}\n","import { Builder } from \"@player-ui/player\";\nimport type { AsyncTransformFunc } from \"./types\";\n\n/**\n * Util function to generate transform function for async asset\n * @param asset - async asset to apply beforeResolve transform\n * @param transformedAssetType: transformed asset type for rendering\n * @param wrapperAssetType: container asset type\n * @param flatten: flatten the streamed in content\n * @returns - wrapper asset with children of transformed asset and async node\n */\n\nexport const asyncTransform: AsyncTransformFunc = (\n assetId,\n wrapperAssetType,\n asset,\n flatten,\n) => {\n const id = \"async-\" + assetId;\n\n const asyncNode = Builder.asyncNode(id, flatten);\n let multiNode;\n let assetNode;\n\n if (asset) {\n assetNode = Builder.assetWrapper(asset);\n multiNode = Builder.multiNode(assetNode, asyncNode);\n } else {\n multiNode = Builder.multiNode(asyncNode);\n }\n\n const wrapperAsset = Builder.asset({\n id: wrapperAssetType + \"-\" + id,\n type: wrapperAssetType,\n });\n\n Builder.addChild(wrapperAsset, [\"values\"], multiNode);\n\n return wrapperAsset;\n};\n"],"mappings":"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","names":["import_player","queueMicrotask","result"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/index.ts","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/transform.ts"],"sourcesContent":["import { NodeType, getNodeID } from \"@player-ui/player\";\nimport type {\n Player,\n PlayerPlugin,\n Node,\n ParseObjectOptions,\n ParseObjectChildOptions,\n ViewInstance,\n Parser,\n ViewPlugin,\n Resolver,\n Resolve,\n} from \"@player-ui/player\";\nimport { AsyncParallelBailHook, SyncBailHook } from \"tapable-ts\";\nimport queueMicrotask from \"queue-microtask\";\nimport { omit } from \"timm\";\n\nexport * from \"./types\";\nexport * from \"./transform\";\n\n/** Object type for storing data related to a single `apply` of the `AsyncNodePluginPlugin`\n * This object should be setup once per ViewInstance to keep any cached info just for that view to avoid conflicts of shared async node ids across different view states.\n */\ntype AsyncPluginContext = {\n /** Map of async node id to resolved content */\n nodeResolveCache: Map<string, any>;\n /** The view instance this context is attached to. */\n view: ViewInstance;\n /** Map of async node id to promises being used to resolve them */\n inProgressNodes: Set<string>;\n};\n\nexport interface AsyncNodePluginOptions {\n /** A set of plugins to load */\n plugins?: AsyncNodeViewPlugin[];\n}\n\nexport interface AsyncNodeViewPlugin extends ViewPlugin {\n /** Use this to tap into the async node plugin hooks */\n applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;\n\n asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;\n}\nexport type AsyncHandler = (\n node: Node.Async,\n callback?: (result: any) => void,\n) => Promise<any>;\n\n/** Hook declaration for the AsyncNodePlugin */\ntype AsyncNodeHooks = {\n /** Async hook to get content for an async node */\n onAsyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;\n /** Sync hook to manage errors coming from the onAsyncNode hook. Return a fallback node or null to render a fallback. The first argument of passed in the call is the error thrown. */\n onAsyncNodeError: SyncBailHook<[Error, Node.Async], any>;\n};\n\n/**\n * Async node plugin used to resolve async nodes in the content\n * If an async node is present, allow users to provide a replacement node to be rendered when ready\n */\nexport class AsyncNodePlugin implements PlayerPlugin {\n private plugins: AsyncNodeViewPlugin[] | undefined;\n private playerInstance: Player | undefined;\n\n constructor(options: AsyncNodePluginOptions, asyncHandler?: AsyncHandler) {\n if (options?.plugins) {\n this.plugins = options.plugins;\n options.plugins.forEach((plugin) => {\n plugin.applyPlugin(this);\n });\n }\n\n if (asyncHandler) {\n this.hooks.onAsyncNode.tap(\n \"async\",\n async (node: Node.Async, callback) => {\n return await asyncHandler(node, callback);\n },\n );\n }\n }\n\n public readonly hooks: AsyncNodeHooks = {\n onAsyncNode: new AsyncParallelBailHook(),\n onAsyncNodeError: new SyncBailHook(),\n };\n\n getPlayerInstance(): Player | undefined {\n return this.playerInstance;\n }\n\n name = \"AsyncNode\";\n\n apply(player: Player): void {\n this.playerInstance = player;\n\n player.hooks.viewController.tap(this.name, (viewController) => {\n viewController.hooks.view.tap(this.name, (view) => {\n this.plugins?.forEach((plugin) => {\n plugin.apply(view);\n });\n });\n });\n }\n}\n\nexport class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {\n public asyncNode: AsyncParallelBailHook<\n [Node.Async, (result: any) => void],\n any\n > = new AsyncParallelBailHook();\n private basePlugin: AsyncNodePlugin | undefined;\n\n name = \"AsyncNode\";\n\n /**\n * Parses the node from the result and triggers an asynchronous view update if necessary.\n * @param node The asynchronous node that might be updated.\n * @param result The result obtained from resolving the async node. This could be any data structure or value.\n * @param options Options provided for node resolution, including a potential parseNode function to process the result.\n * @param view The view instance where the node resides. This can be undefined if the view is not currently active.\n */\n private parseNodeAndUpdate(\n node: Node.Async,\n context: AsyncPluginContext,\n result: any,\n options: Resolve.NodeResolveOptions,\n ) {\n const parsedNode =\n options.parseNode && result ? options.parseNode(result) : undefined;\n\n this.handleAsyncUpdate(node, context, parsedNode);\n }\n\n /**\n * Updates the node asynchronously based on the result provided.\n * This method is responsible for handling the update logic of asynchronous nodes.\n * It checks if the node needs to be updated based on the new result and updates the mapping accordingly.\n * If an update is necessary, it triggers an asynchronous update on the view.\n * @param node The asynchronous node that might be updated.\n * @param newNode The new node to replace the async node.\n * @param view The view instance where the node resides. This can be undefined if the view is not currently active.\n */\n private handleAsyncUpdate(\n node: Node.Async,\n context: AsyncPluginContext,\n newNode?: Node.Node | null,\n ) {\n const { nodeResolveCache, view } = context;\n if (nodeResolveCache.get(node.id) !== newNode) {\n nodeResolveCache.set(node.id, newNode ? newNode : node);\n view.updateAsync();\n }\n }\n\n /**\n * Handles the asynchronous API integration for resolving nodes.\n * This method sets up a hook on the resolver's `beforeResolve` event to process async nodes.\n * @param resolver The resolver instance to attach the hook to.\n * @param view\n */\n applyResolver(resolver: Resolver, context: AsyncPluginContext): void {\n resolver.hooks.beforeResolve.tap(this.name, (node, options) => {\n if (!this.isAsync(node)) {\n return node;\n }\n\n const resolvedNode = context.nodeResolveCache.get(node.id);\n if (resolvedNode !== undefined) {\n return resolvedNode;\n }\n\n if (context.inProgressNodes.has(node.id)) {\n return node;\n }\n\n // Track that the node is in progress.\n context.inProgressNodes.add(node.id);\n queueMicrotask(() => {\n this.runAsyncNode(node, context, options).finally();\n });\n\n return node;\n });\n }\n\n private async runAsyncNode(\n node: Node.Async,\n context: AsyncPluginContext,\n options: Resolve.NodeResolveOptions,\n ) {\n try {\n const result = await this.basePlugin?.hooks.onAsyncNode.call(\n node,\n (result) => {\n this.parseNodeAndUpdate(node, context, result, options);\n },\n );\n\n // Stop tracking before the next update is triggered\n context.inProgressNodes.delete(node.id);\n this.parseNodeAndUpdate(node, context, result, options);\n } catch (e: unknown) {\n const error = e instanceof Error ? e : new Error(String(e));\n const result = this.basePlugin?.hooks.onAsyncNodeError.call(error, node);\n\n if (result === undefined) {\n const playerState = this.basePlugin?.getPlayerInstance()?.getState();\n\n if (playerState?.status === \"in-progress\") {\n playerState.fail(error);\n }\n\n return;\n }\n\n options.logger?.error(\n \"Async node handling failed and resolved with a fallback. Error:\",\n error,\n );\n\n // Stop tracking before the next update is triggered\n context.inProgressNodes.delete(node.id);\n this.parseNodeAndUpdate(node, context, result, options);\n }\n }\n\n private isAsync(node: Node.Node | null): node is Node.Async {\n return node?.type === NodeType.Async;\n }\n\n private isDeterminedAsync(obj: any) {\n return obj && Object.prototype.hasOwnProperty.call(obj, \"async\");\n }\n\n applyParser(parser: Parser): void {\n parser.hooks.parseNode.tap(\n this.name,\n (\n obj: any,\n nodeType: Node.ChildrenTypes,\n options: ParseObjectOptions,\n childOptions?: ParseObjectChildOptions,\n ) => {\n if (this.isDeterminedAsync(obj)) {\n const parsedAsync = parser.parseObject(\n omit(obj, \"async\"),\n nodeType,\n options,\n );\n const parsedNodeId = getNodeID(parsedAsync);\n\n if (parsedAsync === null || !parsedNodeId) {\n return childOptions ? [] : null;\n }\n\n const asyncAST = parser.createASTNode(\n {\n id: parsedNodeId,\n type: NodeType.Async,\n value: parsedAsync,\n },\n obj,\n );\n\n if (childOptions) {\n return asyncAST\n ? [\n {\n path: [...childOptions.path, childOptions.key],\n value: asyncAST,\n },\n ]\n : [];\n }\n\n return asyncAST;\n }\n },\n );\n }\n\n apply(view: ViewInstance): void {\n const context: AsyncPluginContext = {\n nodeResolveCache: new Map(),\n inProgressNodes: new Set(),\n view,\n };\n\n view.hooks.parser.tap(\"async\", this.applyParser.bind(this));\n view.hooks.resolver.tap(\"async\", (resolver) => {\n this.applyResolver(resolver, context);\n });\n }\n\n applyPlugin(asyncNodePlugin: AsyncNodePlugin): void {\n this.basePlugin = asyncNodePlugin;\n }\n}\n","import { Builder } from \"@player-ui/player\";\nimport type { AsyncTransformFunc } from \"./types\";\n\n/**\n * Util function to generate transform function for async asset\n * @param asset - async asset to apply beforeResolve transform\n * @param transformedAssetType: transformed asset type for rendering\n * @param wrapperAssetType: container asset type\n * @param flatten: flatten the streamed in content\n * @returns - wrapper asset with children of transformed asset and async node\n */\n\nexport const asyncTransform: AsyncTransformFunc = (\n assetId,\n wrapperAssetType,\n asset,\n flatten,\n) => {\n const id = \"async-\" + assetId;\n\n const asyncNode = Builder.asyncNode(id, flatten);\n let multiNode;\n let assetNode;\n\n if (asset) {\n assetNode = Builder.assetWrapper(asset);\n multiNode = Builder.multiNode(assetNode, asyncNode);\n } else {\n multiNode = Builder.multiNode(asyncNode);\n }\n\n const wrapperAsset = Builder.asset({\n id: wrapperAssetType + \"-\" + id,\n type: wrapperAssetType,\n });\n\n Builder.addChild(wrapperAsset, [\"values\"], multiNode);\n\n return wrapperAsset;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,iBAAoC;AAapC,wBAAoD;AACpD,6BAA2B;AAC3B,kBAAqB;;;ACfrB,oBAAwB;AAYjB,IAAM,iBAAqC,CAChD,SACA,kBACA,OACA,YACG;AACH,QAAM,KAAK,WAAW;AAEtB,QAAM,YAAY,sBAAQ,UAAU,IAAI,OAAO;AAC/C,MAAI;AACJ,MAAI;AAEJ,MAAI,OAAO;AACT,gBAAY,sBAAQ,aAAa,KAAK;AACtC,gBAAY,sBAAQ,UAAU,WAAW,SAAS;AAAA,EACpD,OAAO;AACL,gBAAY,sBAAQ,UAAU,SAAS;AAAA,EACzC;AAEA,QAAM,eAAe,sBAAQ,MAAM;AAAA,IACjC,IAAI,mBAAmB,MAAM;AAAA,IAC7B,MAAM;AAAA,EACR,CAAC;AAED,wBAAQ,SAAS,cAAc,CAAC,QAAQ,GAAG,SAAS;AAEpD,SAAO;AACT;;;ADqBO,IAAM,kBAAN,MAA8C;AAAA,EAInD,YAAY,SAAiC,cAA6B;AAkB1E,SAAgB,QAAwB;AAAA,MACtC,aAAa,IAAI,wCAAsB;AAAA,MACvC,kBAAkB,IAAI,+BAAa;AAAA,IACrC;AAMA,gBAAO;AA1BL,QAAI,SAAS,SAAS;AACpB,WAAK,UAAU,QAAQ;AACvB,cAAQ,QAAQ,QAAQ,CAAC,WAAW;AAClC,eAAO,YAAY,IAAI;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,QAAI,cAAc;AAChB,WAAK,MAAM,YAAY;AAAA,QACrB;AAAA,QACA,OAAO,MAAkB,aAAa;AACpC,iBAAO,MAAM,aAAa,MAAM,QAAQ;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAOA,oBAAwC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA,EAIA,MAAM,QAAsB;AAC1B,SAAK,iBAAiB;AAEtB,WAAO,MAAM,eAAe,IAAI,KAAK,MAAM,CAAC,mBAAmB;AAC7D,qBAAe,MAAM,KAAK,IAAI,KAAK,MAAM,CAAC,SAAS;AACjD,aAAK,SAAS,QAAQ,CAAC,WAAW;AAChC,iBAAO,MAAM,IAAI;AAAA,QACnB,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAEO,IAAM,wBAAN,MAA2D;AAAA,EAA3D;AACL,SAAO,YAGH,IAAI,wCAAsB;AAG9B,gBAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASC,mBACN,MACA,SACA,QACA,SACA;AACA,UAAM,aACJ,QAAQ,aAAa,SAAS,QAAQ,UAAU,MAAM,IAAI;AAE5D,SAAK,kBAAkB,MAAM,SAAS,UAAU;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,kBACN,MACA,SACA,SACA;AACA,UAAM,EAAE,kBAAkB,KAAK,IAAI;AACnC,QAAI,iBAAiB,IAAI,KAAK,EAAE,MAAM,SAAS;AAC7C,uBAAiB,IAAI,KAAK,IAAI,UAAU,UAAU,IAAI;AACtD,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,UAAoB,SAAmC;AACnE,aAAS,MAAM,cAAc,IAAI,KAAK,MAAM,CAAC,MAAM,YAAY;AAC7D,UAAI,CAAC,KAAK,QAAQ,IAAI,GAAG;AACvB,eAAO;AAAA,MACT;AAEA,YAAM,eAAe,QAAQ,iBAAiB,IAAI,KAAK,EAAE;AACzD,UAAI,iBAAiB,QAAW;AAC9B,eAAO;AAAA,MACT;AAEA,UAAI,QAAQ,gBAAgB,IAAI,KAAK,EAAE,GAAG;AACxC,eAAO;AAAA,MACT;AAGA,cAAQ,gBAAgB,IAAI,KAAK,EAAE;AACnC,iCAAAC,SAAe,MAAM;AACnB,aAAK,aAAa,MAAM,SAAS,OAAO,EAAE,QAAQ;AAAA,MACpD,CAAC;AAED,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,aACZ,MACA,SACA,SACA;AACA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,YAAY,MAAM,YAAY;AAAA,QACtD;AAAA,QACA,CAACC,YAAW;AACV,eAAK,mBAAmB,MAAM,SAASA,SAAQ,OAAO;AAAA,QACxD;AAAA,MACF;AAGA,cAAQ,gBAAgB,OAAO,KAAK,EAAE;AACtC,WAAK,mBAAmB,MAAM,SAAS,QAAQ,OAAO;AAAA,IACxD,SAAS,GAAY;AACnB,YAAM,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAC1D,YAAM,SAAS,KAAK,YAAY,MAAM,iBAAiB,KAAK,OAAO,IAAI;AAEvE,UAAI,WAAW,QAAW;AACxB,cAAM,cAAc,KAAK,YAAY,kBAAkB,GAAG,SAAS;AAEnE,YAAI,aAAa,WAAW,eAAe;AACzC,sBAAY,KAAK,KAAK;AAAA,QACxB;AAEA;AAAA,MACF;AAEA,cAAQ,QAAQ;AAAA,QACd;AAAA,QACA;AAAA,MACF;AAGA,cAAQ,gBAAgB,OAAO,KAAK,EAAE;AACtC,WAAK,mBAAmB,MAAM,SAAS,QAAQ,OAAO;AAAA,IACxD;AAAA,EACF;AAAA,EAEQ,QAAQ,MAA4C;AAC1D,WAAO,MAAM,SAAS,wBAAS;AAAA,EACjC;AAAA,EAEQ,kBAAkB,KAAU;AAClC,WAAO,OAAO,OAAO,UAAU,eAAe,KAAK,KAAK,OAAO;AAAA,EACjE;AAAA,EAEA,YAAY,QAAsB;AAChC,WAAO,MAAM,UAAU;AAAA,MACrB,KAAK;AAAA,MACL,CACE,KACA,UACA,SACA,iBACG;AACH,YAAI,KAAK,kBAAkB,GAAG,GAAG;AAC/B,gBAAM,cAAc,OAAO;AAAA,gBACzB,kBAAK,KAAK,OAAO;AAAA,YACjB;AAAA,YACA;AAAA,UACF;AACA,gBAAM,mBAAe,0BAAU,WAAW;AAE1C,cAAI,gBAAgB,QAAQ,CAAC,cAAc;AACzC,mBAAO,eAAe,CAAC,IAAI;AAAA,UAC7B;AAEA,gBAAM,WAAW,OAAO;AAAA,YACtB;AAAA,cACE,IAAI;AAAA,cACJ,MAAM,wBAAS;AAAA,cACf,OAAO;AAAA,YACT;AAAA,YACA;AAAA,UACF;AAEA,cAAI,cAAc;AAChB,mBAAO,WACH;AAAA,cACE;AAAA,gBACE,MAAM,CAAC,GAAG,aAAa,MAAM,aAAa,GAAG;AAAA,gBAC7C,OAAO;AAAA,cACT;AAAA,YACF,IACA,CAAC;AAAA,UACP;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,MAA0B;AAC9B,UAAM,UAA8B;AAAA,MAClC,kBAAkB,oBAAI,IAAI;AAAA,MAC1B,iBAAiB,oBAAI,IAAI;AAAA,MACzB;AAAA,IACF;AAEA,SAAK,MAAM,OAAO,IAAI,SAAS,KAAK,YAAY,KAAK,IAAI,CAAC;AAC1D,SAAK,MAAM,SAAS,IAAI,SAAS,CAAC,aAAa;AAC7C,WAAK,cAAc,UAAU,OAAO;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,iBAAwC;AAClD,SAAK,aAAa;AAAA,EACpB;AACF;","names":["import_player","queueMicrotask","result"]}
|
package/dist/index.legacy-esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/index.ts
|
|
2
2
|
import { NodeType, getNodeID } from "@player-ui/player";
|
|
3
|
-
import { AsyncParallelBailHook } from "tapable-ts";
|
|
3
|
+
import { AsyncParallelBailHook, SyncBailHook } from "tapable-ts";
|
|
4
4
|
import queueMicrotask from "queue-microtask";
|
|
5
5
|
import { omit } from "timm";
|
|
6
6
|
|
|
@@ -29,7 +29,8 @@ var asyncTransform = (assetId, wrapperAssetType, asset, flatten) => {
|
|
|
29
29
|
var AsyncNodePlugin = class {
|
|
30
30
|
constructor(options, asyncHandler) {
|
|
31
31
|
this.hooks = {
|
|
32
|
-
onAsyncNode: new AsyncParallelBailHook()
|
|
32
|
+
onAsyncNode: new AsyncParallelBailHook(),
|
|
33
|
+
onAsyncNodeError: new SyncBailHook()
|
|
33
34
|
};
|
|
34
35
|
this.name = "AsyncNode";
|
|
35
36
|
if (options?.plugins) {
|
|
@@ -47,7 +48,11 @@ var AsyncNodePlugin = class {
|
|
|
47
48
|
);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
getPlayerInstance() {
|
|
52
|
+
return this.playerInstance;
|
|
53
|
+
}
|
|
50
54
|
apply(player) {
|
|
55
|
+
this.playerInstance = player;
|
|
51
56
|
player.hooks.viewController.tap(this.name, (viewController) => {
|
|
52
57
|
viewController.hooks.view.tap(this.name, (view) => {
|
|
53
58
|
this.plugins?.forEach((plugin) => {
|
|
@@ -61,7 +66,17 @@ var AsyncNodePluginPlugin = class {
|
|
|
61
66
|
constructor() {
|
|
62
67
|
this.asyncNode = new AsyncParallelBailHook();
|
|
63
68
|
this.name = "AsyncNode";
|
|
64
|
-
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Parses the node from the result and triggers an asynchronous view update if necessary.
|
|
72
|
+
* @param node The asynchronous node that might be updated.
|
|
73
|
+
* @param result The result obtained from resolving the async node. This could be any data structure or value.
|
|
74
|
+
* @param options Options provided for node resolution, including a potential parseNode function to process the result.
|
|
75
|
+
* @param view The view instance where the node resides. This can be undefined if the view is not currently active.
|
|
76
|
+
*/
|
|
77
|
+
parseNodeAndUpdate(node, context, result, options) {
|
|
78
|
+
const parsedNode = options.parseNode && result ? options.parseNode(result) : void 0;
|
|
79
|
+
this.handleAsyncUpdate(node, context, parsedNode);
|
|
65
80
|
}
|
|
66
81
|
/**
|
|
67
82
|
* Updates the node asynchronously based on the result provided.
|
|
@@ -69,15 +84,14 @@ var AsyncNodePluginPlugin = class {
|
|
|
69
84
|
* It checks if the node needs to be updated based on the new result and updates the mapping accordingly.
|
|
70
85
|
* If an update is necessary, it triggers an asynchronous update on the view.
|
|
71
86
|
* @param node The asynchronous node that might be updated.
|
|
72
|
-
* @param
|
|
73
|
-
* @param options Options provided for node resolution, including a potential parseNode function to process the result.
|
|
87
|
+
* @param newNode The new node to replace the async node.
|
|
74
88
|
* @param view The view instance where the node resides. This can be undefined if the view is not currently active.
|
|
75
89
|
*/
|
|
76
|
-
handleAsyncUpdate(node,
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
view
|
|
90
|
+
handleAsyncUpdate(node, context, newNode) {
|
|
91
|
+
const { nodeResolveCache, view } = context;
|
|
92
|
+
if (nodeResolveCache.get(node.id) !== newNode) {
|
|
93
|
+
nodeResolveCache.set(node.id, newNode ? newNode : node);
|
|
94
|
+
view.updateAsync();
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
97
|
/**
|
|
@@ -86,33 +100,53 @@ var AsyncNodePluginPlugin = class {
|
|
|
86
100
|
* @param resolver The resolver instance to attach the hook to.
|
|
87
101
|
* @param view
|
|
88
102
|
*/
|
|
89
|
-
applyResolver(resolver) {
|
|
103
|
+
applyResolver(resolver, context) {
|
|
90
104
|
resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const mappedValue = this.resolvedMapping.get(node.id);
|
|
94
|
-
if (mappedValue) {
|
|
95
|
-
resolvedNode = mappedValue;
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
resolvedNode = null;
|
|
105
|
+
if (!this.isAsync(node)) {
|
|
106
|
+
return node;
|
|
99
107
|
}
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
(result2) => {
|
|
106
|
-
this.handleAsyncUpdate(node, result2, options, this.currentView);
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
this.handleAsyncUpdate(node, result, options, this.currentView);
|
|
110
|
-
});
|
|
108
|
+
const resolvedNode = context.nodeResolveCache.get(node.id);
|
|
109
|
+
if (resolvedNode !== void 0) {
|
|
110
|
+
return resolvedNode;
|
|
111
|
+
}
|
|
112
|
+
if (context.inProgressNodes.has(node.id)) {
|
|
111
113
|
return node;
|
|
112
114
|
}
|
|
113
|
-
|
|
115
|
+
context.inProgressNodes.add(node.id);
|
|
116
|
+
queueMicrotask(() => {
|
|
117
|
+
this.runAsyncNode(node, context, options).finally();
|
|
118
|
+
});
|
|
119
|
+
return node;
|
|
114
120
|
});
|
|
115
121
|
}
|
|
122
|
+
async runAsyncNode(node, context, options) {
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.basePlugin?.hooks.onAsyncNode.call(
|
|
125
|
+
node,
|
|
126
|
+
(result2) => {
|
|
127
|
+
this.parseNodeAndUpdate(node, context, result2, options);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
context.inProgressNodes.delete(node.id);
|
|
131
|
+
this.parseNodeAndUpdate(node, context, result, options);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
134
|
+
const result = this.basePlugin?.hooks.onAsyncNodeError.call(error, node);
|
|
135
|
+
if (result === void 0) {
|
|
136
|
+
const playerState = this.basePlugin?.getPlayerInstance()?.getState();
|
|
137
|
+
if (playerState?.status === "in-progress") {
|
|
138
|
+
playerState.fail(error);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
options.logger?.error(
|
|
143
|
+
"Async node handling failed and resolved with a fallback. Error:",
|
|
144
|
+
error
|
|
145
|
+
);
|
|
146
|
+
context.inProgressNodes.delete(node.id);
|
|
147
|
+
this.parseNodeAndUpdate(node, context, result, options);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
116
150
|
isAsync(node) {
|
|
117
151
|
return node?.type === NodeType.Async;
|
|
118
152
|
}
|
|
@@ -155,9 +189,15 @@ var AsyncNodePluginPlugin = class {
|
|
|
155
189
|
);
|
|
156
190
|
}
|
|
157
191
|
apply(view) {
|
|
158
|
-
|
|
192
|
+
const context = {
|
|
193
|
+
nodeResolveCache: /* @__PURE__ */ new Map(),
|
|
194
|
+
inProgressNodes: /* @__PURE__ */ new Set(),
|
|
195
|
+
view
|
|
196
|
+
};
|
|
159
197
|
view.hooks.parser.tap("async", this.applyParser.bind(this));
|
|
160
|
-
view.hooks.resolver.tap("async",
|
|
198
|
+
view.hooks.resolver.tap("async", (resolver) => {
|
|
199
|
+
this.applyResolver(resolver, context);
|
|
200
|
+
});
|
|
161
201
|
}
|
|
162
202
|
applyPlugin(asyncNodePlugin) {
|
|
163
203
|
this.basePlugin = asyncNodePlugin;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/index.ts
|
|
2
2
|
import { NodeType, getNodeID } from "@player-ui/player";
|
|
3
|
-
import { AsyncParallelBailHook } from "tapable-ts";
|
|
3
|
+
import { AsyncParallelBailHook, SyncBailHook } from "tapable-ts";
|
|
4
4
|
import queueMicrotask from "queue-microtask";
|
|
5
5
|
import { omit } from "timm";
|
|
6
6
|
|
|
@@ -29,7 +29,8 @@ var asyncTransform = (assetId, wrapperAssetType, asset, flatten) => {
|
|
|
29
29
|
var AsyncNodePlugin = class {
|
|
30
30
|
constructor(options, asyncHandler) {
|
|
31
31
|
this.hooks = {
|
|
32
|
-
onAsyncNode: new AsyncParallelBailHook()
|
|
32
|
+
onAsyncNode: new AsyncParallelBailHook(),
|
|
33
|
+
onAsyncNodeError: new SyncBailHook()
|
|
33
34
|
};
|
|
34
35
|
this.name = "AsyncNode";
|
|
35
36
|
if (options?.plugins) {
|
|
@@ -47,7 +48,11 @@ var AsyncNodePlugin = class {
|
|
|
47
48
|
);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
getPlayerInstance() {
|
|
52
|
+
return this.playerInstance;
|
|
53
|
+
}
|
|
50
54
|
apply(player) {
|
|
55
|
+
this.playerInstance = player;
|
|
51
56
|
player.hooks.viewController.tap(this.name, (viewController) => {
|
|
52
57
|
viewController.hooks.view.tap(this.name, (view) => {
|
|
53
58
|
this.plugins?.forEach((plugin) => {
|
|
@@ -61,7 +66,17 @@ var AsyncNodePluginPlugin = class {
|
|
|
61
66
|
constructor() {
|
|
62
67
|
this.asyncNode = new AsyncParallelBailHook();
|
|
63
68
|
this.name = "AsyncNode";
|
|
64
|
-
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Parses the node from the result and triggers an asynchronous view update if necessary.
|
|
72
|
+
* @param node The asynchronous node that might be updated.
|
|
73
|
+
* @param result The result obtained from resolving the async node. This could be any data structure or value.
|
|
74
|
+
* @param options Options provided for node resolution, including a potential parseNode function to process the result.
|
|
75
|
+
* @param view The view instance where the node resides. This can be undefined if the view is not currently active.
|
|
76
|
+
*/
|
|
77
|
+
parseNodeAndUpdate(node, context, result, options) {
|
|
78
|
+
const parsedNode = options.parseNode && result ? options.parseNode(result) : void 0;
|
|
79
|
+
this.handleAsyncUpdate(node, context, parsedNode);
|
|
65
80
|
}
|
|
66
81
|
/**
|
|
67
82
|
* Updates the node asynchronously based on the result provided.
|
|
@@ -69,15 +84,14 @@ var AsyncNodePluginPlugin = class {
|
|
|
69
84
|
* It checks if the node needs to be updated based on the new result and updates the mapping accordingly.
|
|
70
85
|
* If an update is necessary, it triggers an asynchronous update on the view.
|
|
71
86
|
* @param node The asynchronous node that might be updated.
|
|
72
|
-
* @param
|
|
73
|
-
* @param options Options provided for node resolution, including a potential parseNode function to process the result.
|
|
87
|
+
* @param newNode The new node to replace the async node.
|
|
74
88
|
* @param view The view instance where the node resides. This can be undefined if the view is not currently active.
|
|
75
89
|
*/
|
|
76
|
-
handleAsyncUpdate(node,
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
view
|
|
90
|
+
handleAsyncUpdate(node, context, newNode) {
|
|
91
|
+
const { nodeResolveCache, view } = context;
|
|
92
|
+
if (nodeResolveCache.get(node.id) !== newNode) {
|
|
93
|
+
nodeResolveCache.set(node.id, newNode ? newNode : node);
|
|
94
|
+
view.updateAsync();
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
97
|
/**
|
|
@@ -86,33 +100,53 @@ var AsyncNodePluginPlugin = class {
|
|
|
86
100
|
* @param resolver The resolver instance to attach the hook to.
|
|
87
101
|
* @param view
|
|
88
102
|
*/
|
|
89
|
-
applyResolver(resolver) {
|
|
103
|
+
applyResolver(resolver, context) {
|
|
90
104
|
resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const mappedValue = this.resolvedMapping.get(node.id);
|
|
94
|
-
if (mappedValue) {
|
|
95
|
-
resolvedNode = mappedValue;
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
resolvedNode = null;
|
|
105
|
+
if (!this.isAsync(node)) {
|
|
106
|
+
return node;
|
|
99
107
|
}
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
(result2) => {
|
|
106
|
-
this.handleAsyncUpdate(node, result2, options, this.currentView);
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
this.handleAsyncUpdate(node, result, options, this.currentView);
|
|
110
|
-
});
|
|
108
|
+
const resolvedNode = context.nodeResolveCache.get(node.id);
|
|
109
|
+
if (resolvedNode !== void 0) {
|
|
110
|
+
return resolvedNode;
|
|
111
|
+
}
|
|
112
|
+
if (context.inProgressNodes.has(node.id)) {
|
|
111
113
|
return node;
|
|
112
114
|
}
|
|
113
|
-
|
|
115
|
+
context.inProgressNodes.add(node.id);
|
|
116
|
+
queueMicrotask(() => {
|
|
117
|
+
this.runAsyncNode(node, context, options).finally();
|
|
118
|
+
});
|
|
119
|
+
return node;
|
|
114
120
|
});
|
|
115
121
|
}
|
|
122
|
+
async runAsyncNode(node, context, options) {
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.basePlugin?.hooks.onAsyncNode.call(
|
|
125
|
+
node,
|
|
126
|
+
(result2) => {
|
|
127
|
+
this.parseNodeAndUpdate(node, context, result2, options);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
context.inProgressNodes.delete(node.id);
|
|
131
|
+
this.parseNodeAndUpdate(node, context, result, options);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
134
|
+
const result = this.basePlugin?.hooks.onAsyncNodeError.call(error, node);
|
|
135
|
+
if (result === void 0) {
|
|
136
|
+
const playerState = this.basePlugin?.getPlayerInstance()?.getState();
|
|
137
|
+
if (playerState?.status === "in-progress") {
|
|
138
|
+
playerState.fail(error);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
options.logger?.error(
|
|
143
|
+
"Async node handling failed and resolved with a fallback. Error:",
|
|
144
|
+
error
|
|
145
|
+
);
|
|
146
|
+
context.inProgressNodes.delete(node.id);
|
|
147
|
+
this.parseNodeAndUpdate(node, context, result, options);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
116
150
|
isAsync(node) {
|
|
117
151
|
return node?.type === NodeType.Async;
|
|
118
152
|
}
|
|
@@ -155,9 +189,15 @@ var AsyncNodePluginPlugin = class {
|
|
|
155
189
|
);
|
|
156
190
|
}
|
|
157
191
|
apply(view) {
|
|
158
|
-
|
|
192
|
+
const context = {
|
|
193
|
+
nodeResolveCache: /* @__PURE__ */ new Map(),
|
|
194
|
+
inProgressNodes: /* @__PURE__ */ new Set(),
|
|
195
|
+
view
|
|
196
|
+
};
|
|
159
197
|
view.hooks.parser.tap("async", this.applyParser.bind(this));
|
|
160
|
-
view.hooks.resolver.tap("async",
|
|
198
|
+
view.hooks.resolver.tap("async", (resolver) => {
|
|
199
|
+
this.applyResolver(resolver, context);
|
|
200
|
+
});
|
|
161
201
|
}
|
|
162
202
|
applyPlugin(asyncNodePlugin) {
|
|
163
203
|
this.basePlugin = asyncNodePlugin;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/index.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/transform.ts"],"sourcesContent":["import { NodeType, getNodeID } from \"@player-ui/player\";\nimport type {\n Player,\n PlayerPlugin,\n Node,\n ParseObjectOptions,\n ParseObjectChildOptions,\n ViewInstance,\n Parser,\n ViewPlugin,\n Resolver,\n Resolve,\n} from \"@player-ui/player\";\nimport { AsyncParallelBailHook } from \"tapable-ts\";\nimport queueMicrotask from \"queue-microtask\";\nimport { omit } from \"timm\";\n\nexport * from \"./types\";\nexport * from \"./transform\";\n\nexport interface AsyncNodePluginOptions {\n /** A set of plugins to load */\n plugins?: AsyncNodeViewPlugin[];\n}\n\nexport interface AsyncNodeViewPlugin extends ViewPlugin {\n /** Use this to tap into the async node plugin hooks */\n applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;\n\n asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;\n}\nexport type AsyncHandler = (\n node: Node.Async,\n callback?: (result: any) => void,\n) => Promise<any>;\n\n/**\n * Async node plugin used to resolve async nodes in the content\n * If an async node is present, allow users to provide a replacement node to be rendered when ready\n */\nexport class AsyncNodePlugin implements PlayerPlugin {\n private plugins: AsyncNodeViewPlugin[] | undefined;\n\n constructor(options: AsyncNodePluginOptions, asyncHandler?: AsyncHandler) {\n if (options?.plugins) {\n this.plugins = options.plugins;\n options.plugins.forEach((plugin) => {\n plugin.applyPlugin(this);\n });\n }\n\n if (asyncHandler) {\n this.hooks.onAsyncNode.tap(\n \"async\",\n async (node: Node.Async, callback) => {\n return await asyncHandler(node, callback);\n },\n );\n }\n }\n\n public readonly hooks = {\n onAsyncNode: new AsyncParallelBailHook<\n [Node.Async, (result: any) => void],\n any\n >(),\n };\n\n name = \"AsyncNode\";\n\n apply(player: Player) {\n player.hooks.viewController.tap(this.name, (viewController) => {\n viewController.hooks.view.tap(this.name, (view) => {\n this.plugins?.forEach((plugin) => {\n plugin.apply(view);\n });\n });\n });\n }\n}\n\nexport class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {\n public asyncNode = new AsyncParallelBailHook<\n [Node.Async, (result: any) => void],\n any\n >();\n private basePlugin: AsyncNodePlugin | undefined;\n\n name = \"AsyncNode\";\n\n private resolvedMapping = new Map<string, any>();\n\n private currentView: ViewInstance | undefined;\n\n /**\n * Updates the node asynchronously based on the result provided.\n * This method is responsible for handling the update logic of asynchronous nodes.\n * It checks if the node needs to be updated based on the new result and updates the mapping accordingly.\n * If an update is necessary, it triggers an asynchronous update on the view.\n * @param node The asynchronous node that might be updated.\n * @param result The result obtained from resolving the async node. This could be any data structure or value.\n * @param options Options provided for node resolution, including a potential parseNode function to process the result.\n * @param view The view instance where the node resides. This can be undefined if the view is not currently active.\n */\n private handleAsyncUpdate(\n node: Node.Async,\n result: any,\n options: Resolve.NodeResolveOptions,\n view: ViewInstance | undefined,\n ) {\n const parsedNode =\n options.parseNode && result ? options.parseNode(result) : undefined;\n\n if (this.resolvedMapping.get(node.id) !== parsedNode) {\n this.resolvedMapping.set(node.id, parsedNode ? parsedNode : node);\n view?.updateAsync();\n }\n }\n\n /**\n * Handles the asynchronous API integration for resolving nodes.\n * This method sets up a hook on the resolver's `beforeResolve` event to process async nodes.\n * @param resolver The resolver instance to attach the hook to.\n * @param view\n */\n applyResolver(resolver: Resolver) {\n resolver.hooks.beforeResolve.tap(this.name, (node, options) => {\n let resolvedNode;\n if (this.isAsync(node)) {\n const mappedValue = this.resolvedMapping.get(node.id);\n if (mappedValue) {\n resolvedNode = mappedValue;\n }\n } else {\n resolvedNode = null;\n }\n\n const newNode = resolvedNode || node;\n if (!resolvedNode && node?.type === NodeType.Async) {\n queueMicrotask(async () => {\n const result = await this.basePlugin?.hooks.onAsyncNode.call(\n node,\n (result) => {\n this.handleAsyncUpdate(node, result, options, this.currentView);\n },\n );\n this.handleAsyncUpdate(node, result, options, this.currentView);\n });\n\n return node;\n }\n return newNode;\n });\n }\n\n private isAsync(node: Node.Node | null): node is Node.Async {\n return node?.type === NodeType.Async;\n }\n\n private isDeterminedAsync(obj: any) {\n return obj && Object.prototype.hasOwnProperty.call(obj, \"async\");\n }\n\n applyParser(parser: Parser) {\n parser.hooks.parseNode.tap(\n this.name,\n (\n obj: any,\n nodeType: Node.ChildrenTypes,\n options: ParseObjectOptions,\n childOptions?: ParseObjectChildOptions,\n ) => {\n if (this.isDeterminedAsync(obj)) {\n const parsedAsync = parser.parseObject(\n omit(obj, \"async\"),\n nodeType,\n options,\n );\n const parsedNodeId = getNodeID(parsedAsync);\n\n if (parsedAsync === null || !parsedNodeId) {\n return childOptions ? [] : null;\n }\n\n const asyncAST = parser.createASTNode(\n {\n id: parsedNodeId,\n type: NodeType.Async,\n value: parsedAsync,\n },\n obj,\n );\n\n if (childOptions) {\n return asyncAST\n ? [\n {\n path: [...childOptions.path, childOptions.key],\n value: asyncAST,\n },\n ]\n : [];\n }\n\n return asyncAST;\n }\n },\n );\n }\n\n apply(view: ViewInstance): void {\n this.currentView = view;\n view.hooks.parser.tap(\"async\", this.applyParser.bind(this));\n view.hooks.resolver.tap(\"async\", this.applyResolver.bind(this));\n }\n\n applyPlugin(asyncNodePlugin: AsyncNodePlugin): void {\n this.basePlugin = asyncNodePlugin;\n }\n}\n","import { Builder } from \"@player-ui/player\";\nimport type { AsyncTransformFunc } from \"./types\";\n\n/**\n * Util function to generate transform function for async asset\n * @param asset - async asset to apply beforeResolve transform\n * @param transformedAssetType: transformed asset type for rendering\n * @param wrapperAssetType: container asset type\n * @param flatten: flatten the streamed in content\n * @returns - wrapper asset with children of transformed asset and async node\n */\n\nexport const asyncTransform: AsyncTransformFunc = (\n assetId,\n wrapperAssetType,\n asset,\n flatten,\n) => {\n const id = \"async-\" + assetId;\n\n const asyncNode = Builder.asyncNode(id, flatten);\n let multiNode;\n let assetNode;\n\n if (asset) {\n assetNode = Builder.assetWrapper(asset);\n multiNode = Builder.multiNode(assetNode, asyncNode);\n } else {\n multiNode = Builder.multiNode(asyncNode);\n }\n\n const wrapperAsset = Builder.asset({\n id: wrapperAssetType + \"-\" + id,\n type: wrapperAssetType,\n });\n\n Builder.addChild(wrapperAsset, [\"values\"], multiNode);\n\n return wrapperAsset;\n};\n"],"mappings":"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","names":["result"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/index.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/async-node/core/src/transform.ts"],"sourcesContent":["import { NodeType, getNodeID } from \"@player-ui/player\";\nimport type {\n Player,\n PlayerPlugin,\n Node,\n ParseObjectOptions,\n ParseObjectChildOptions,\n ViewInstance,\n Parser,\n ViewPlugin,\n Resolver,\n Resolve,\n} from \"@player-ui/player\";\nimport { AsyncParallelBailHook, SyncBailHook } from \"tapable-ts\";\nimport queueMicrotask from \"queue-microtask\";\nimport { omit } from \"timm\";\n\nexport * from \"./types\";\nexport * from \"./transform\";\n\n/** Object type for storing data related to a single `apply` of the `AsyncNodePluginPlugin`\n * This object should be setup once per ViewInstance to keep any cached info just for that view to avoid conflicts of shared async node ids across different view states.\n */\ntype AsyncPluginContext = {\n /** Map of async node id to resolved content */\n nodeResolveCache: Map<string, any>;\n /** The view instance this context is attached to. */\n view: ViewInstance;\n /** Map of async node id to promises being used to resolve them */\n inProgressNodes: Set<string>;\n};\n\nexport interface AsyncNodePluginOptions {\n /** A set of plugins to load */\n plugins?: AsyncNodeViewPlugin[];\n}\n\nexport interface AsyncNodeViewPlugin extends ViewPlugin {\n /** Use this to tap into the async node plugin hooks */\n applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;\n\n asyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;\n}\nexport type AsyncHandler = (\n node: Node.Async,\n callback?: (result: any) => void,\n) => Promise<any>;\n\n/** Hook declaration for the AsyncNodePlugin */\ntype AsyncNodeHooks = {\n /** Async hook to get content for an async node */\n onAsyncNode: AsyncParallelBailHook<[Node.Async, (result: any) => void], any>;\n /** Sync hook to manage errors coming from the onAsyncNode hook. Return a fallback node or null to render a fallback. The first argument of passed in the call is the error thrown. */\n onAsyncNodeError: SyncBailHook<[Error, Node.Async], any>;\n};\n\n/**\n * Async node plugin used to resolve async nodes in the content\n * If an async node is present, allow users to provide a replacement node to be rendered when ready\n */\nexport class AsyncNodePlugin implements PlayerPlugin {\n private plugins: AsyncNodeViewPlugin[] | undefined;\n private playerInstance: Player | undefined;\n\n constructor(options: AsyncNodePluginOptions, asyncHandler?: AsyncHandler) {\n if (options?.plugins) {\n this.plugins = options.plugins;\n options.plugins.forEach((plugin) => {\n plugin.applyPlugin(this);\n });\n }\n\n if (asyncHandler) {\n this.hooks.onAsyncNode.tap(\n \"async\",\n async (node: Node.Async, callback) => {\n return await asyncHandler(node, callback);\n },\n );\n }\n }\n\n public readonly hooks: AsyncNodeHooks = {\n onAsyncNode: new AsyncParallelBailHook(),\n onAsyncNodeError: new SyncBailHook(),\n };\n\n getPlayerInstance(): Player | undefined {\n return this.playerInstance;\n }\n\n name = \"AsyncNode\";\n\n apply(player: Player): void {\n this.playerInstance = player;\n\n player.hooks.viewController.tap(this.name, (viewController) => {\n viewController.hooks.view.tap(this.name, (view) => {\n this.plugins?.forEach((plugin) => {\n plugin.apply(view);\n });\n });\n });\n }\n}\n\nexport class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {\n public asyncNode: AsyncParallelBailHook<\n [Node.Async, (result: any) => void],\n any\n > = new AsyncParallelBailHook();\n private basePlugin: AsyncNodePlugin | undefined;\n\n name = \"AsyncNode\";\n\n /**\n * Parses the node from the result and triggers an asynchronous view update if necessary.\n * @param node The asynchronous node that might be updated.\n * @param result The result obtained from resolving the async node. This could be any data structure or value.\n * @param options Options provided for node resolution, including a potential parseNode function to process the result.\n * @param view The view instance where the node resides. This can be undefined if the view is not currently active.\n */\n private parseNodeAndUpdate(\n node: Node.Async,\n context: AsyncPluginContext,\n result: any,\n options: Resolve.NodeResolveOptions,\n ) {\n const parsedNode =\n options.parseNode && result ? options.parseNode(result) : undefined;\n\n this.handleAsyncUpdate(node, context, parsedNode);\n }\n\n /**\n * Updates the node asynchronously based on the result provided.\n * This method is responsible for handling the update logic of asynchronous nodes.\n * It checks if the node needs to be updated based on the new result and updates the mapping accordingly.\n * If an update is necessary, it triggers an asynchronous update on the view.\n * @param node The asynchronous node that might be updated.\n * @param newNode The new node to replace the async node.\n * @param view The view instance where the node resides. This can be undefined if the view is not currently active.\n */\n private handleAsyncUpdate(\n node: Node.Async,\n context: AsyncPluginContext,\n newNode?: Node.Node | null,\n ) {\n const { nodeResolveCache, view } = context;\n if (nodeResolveCache.get(node.id) !== newNode) {\n nodeResolveCache.set(node.id, newNode ? newNode : node);\n view.updateAsync();\n }\n }\n\n /**\n * Handles the asynchronous API integration for resolving nodes.\n * This method sets up a hook on the resolver's `beforeResolve` event to process async nodes.\n * @param resolver The resolver instance to attach the hook to.\n * @param view\n */\n applyResolver(resolver: Resolver, context: AsyncPluginContext): void {\n resolver.hooks.beforeResolve.tap(this.name, (node, options) => {\n if (!this.isAsync(node)) {\n return node;\n }\n\n const resolvedNode = context.nodeResolveCache.get(node.id);\n if (resolvedNode !== undefined) {\n return resolvedNode;\n }\n\n if (context.inProgressNodes.has(node.id)) {\n return node;\n }\n\n // Track that the node is in progress.\n context.inProgressNodes.add(node.id);\n queueMicrotask(() => {\n this.runAsyncNode(node, context, options).finally();\n });\n\n return node;\n });\n }\n\n private async runAsyncNode(\n node: Node.Async,\n context: AsyncPluginContext,\n options: Resolve.NodeResolveOptions,\n ) {\n try {\n const result = await this.basePlugin?.hooks.onAsyncNode.call(\n node,\n (result) => {\n this.parseNodeAndUpdate(node, context, result, options);\n },\n );\n\n // Stop tracking before the next update is triggered\n context.inProgressNodes.delete(node.id);\n this.parseNodeAndUpdate(node, context, result, options);\n } catch (e: unknown) {\n const error = e instanceof Error ? e : new Error(String(e));\n const result = this.basePlugin?.hooks.onAsyncNodeError.call(error, node);\n\n if (result === undefined) {\n const playerState = this.basePlugin?.getPlayerInstance()?.getState();\n\n if (playerState?.status === \"in-progress\") {\n playerState.fail(error);\n }\n\n return;\n }\n\n options.logger?.error(\n \"Async node handling failed and resolved with a fallback. Error:\",\n error,\n );\n\n // Stop tracking before the next update is triggered\n context.inProgressNodes.delete(node.id);\n this.parseNodeAndUpdate(node, context, result, options);\n }\n }\n\n private isAsync(node: Node.Node | null): node is Node.Async {\n return node?.type === NodeType.Async;\n }\n\n private isDeterminedAsync(obj: any) {\n return obj && Object.prototype.hasOwnProperty.call(obj, \"async\");\n }\n\n applyParser(parser: Parser): void {\n parser.hooks.parseNode.tap(\n this.name,\n (\n obj: any,\n nodeType: Node.ChildrenTypes,\n options: ParseObjectOptions,\n childOptions?: ParseObjectChildOptions,\n ) => {\n if (this.isDeterminedAsync(obj)) {\n const parsedAsync = parser.parseObject(\n omit(obj, \"async\"),\n nodeType,\n options,\n );\n const parsedNodeId = getNodeID(parsedAsync);\n\n if (parsedAsync === null || !parsedNodeId) {\n return childOptions ? [] : null;\n }\n\n const asyncAST = parser.createASTNode(\n {\n id: parsedNodeId,\n type: NodeType.Async,\n value: parsedAsync,\n },\n obj,\n );\n\n if (childOptions) {\n return asyncAST\n ? [\n {\n path: [...childOptions.path, childOptions.key],\n value: asyncAST,\n },\n ]\n : [];\n }\n\n return asyncAST;\n }\n },\n );\n }\n\n apply(view: ViewInstance): void {\n const context: AsyncPluginContext = {\n nodeResolveCache: new Map(),\n inProgressNodes: new Set(),\n view,\n };\n\n view.hooks.parser.tap(\"async\", this.applyParser.bind(this));\n view.hooks.resolver.tap(\"async\", (resolver) => {\n this.applyResolver(resolver, context);\n });\n }\n\n applyPlugin(asyncNodePlugin: AsyncNodePlugin): void {\n this.basePlugin = asyncNodePlugin;\n }\n}\n","import { Builder } from \"@player-ui/player\";\nimport type { AsyncTransformFunc } from \"./types\";\n\n/**\n * Util function to generate transform function for async asset\n * @param asset - async asset to apply beforeResolve transform\n * @param transformedAssetType: transformed asset type for rendering\n * @param wrapperAssetType: container asset type\n * @param flatten: flatten the streamed in content\n * @returns - wrapper asset with children of transformed asset and async node\n */\n\nexport const asyncTransform: AsyncTransformFunc = (\n assetId,\n wrapperAssetType,\n asset,\n flatten,\n) => {\n const id = \"async-\" + assetId;\n\n const asyncNode = Builder.asyncNode(id, flatten);\n let multiNode;\n let assetNode;\n\n if (asset) {\n assetNode = Builder.assetWrapper(asset);\n multiNode = Builder.multiNode(assetNode, asyncNode);\n } else {\n multiNode = Builder.multiNode(asyncNode);\n }\n\n const wrapperAsset = Builder.asset({\n id: wrapperAssetType + \"-\" + id,\n type: wrapperAssetType,\n });\n\n Builder.addChild(wrapperAsset, [\"values\"], multiNode);\n\n return wrapperAsset;\n};\n"],"mappings":";AAAA,SAAS,UAAU,iBAAiB;AAapC,SAAS,uBAAuB,oBAAoB;AACpD,OAAO,oBAAoB;AAC3B,SAAS,YAAY;;;ACfrB,SAAS,eAAe;AAYjB,IAAM,iBAAqC,CAChD,SACA,kBACA,OACA,YACG;AACH,QAAM,KAAK,WAAW;AAEtB,QAAM,YAAY,QAAQ,UAAU,IAAI,OAAO;AAC/C,MAAI;AACJ,MAAI;AAEJ,MAAI,OAAO;AACT,gBAAY,QAAQ,aAAa,KAAK;AACtC,gBAAY,QAAQ,UAAU,WAAW,SAAS;AAAA,EACpD,OAAO;AACL,gBAAY,QAAQ,UAAU,SAAS;AAAA,EACzC;AAEA,QAAM,eAAe,QAAQ,MAAM;AAAA,IACjC,IAAI,mBAAmB,MAAM;AAAA,IAC7B,MAAM;AAAA,EACR,CAAC;AAED,UAAQ,SAAS,cAAc,CAAC,QAAQ,GAAG,SAAS;AAEpD,SAAO;AACT;;;ADqBO,IAAM,kBAAN,MAA8C;AAAA,EAInD,YAAY,SAAiC,cAA6B;AAkB1E,SAAgB,QAAwB;AAAA,MACtC,aAAa,IAAI,sBAAsB;AAAA,MACvC,kBAAkB,IAAI,aAAa;AAAA,IACrC;AAMA,gBAAO;AA1BL,QAAI,SAAS,SAAS;AACpB,WAAK,UAAU,QAAQ;AACvB,cAAQ,QAAQ,QAAQ,CAAC,WAAW;AAClC,eAAO,YAAY,IAAI;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,QAAI,cAAc;AAChB,WAAK,MAAM,YAAY;AAAA,QACrB;AAAA,QACA,OAAO,MAAkB,aAAa;AACpC,iBAAO,MAAM,aAAa,MAAM,QAAQ;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAOA,oBAAwC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA,EAIA,MAAM,QAAsB;AAC1B,SAAK,iBAAiB;AAEtB,WAAO,MAAM,eAAe,IAAI,KAAK,MAAM,CAAC,mBAAmB;AAC7D,qBAAe,MAAM,KAAK,IAAI,KAAK,MAAM,CAAC,SAAS;AACjD,aAAK,SAAS,QAAQ,CAAC,WAAW;AAChC,iBAAO,MAAM,IAAI;AAAA,QACnB,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAEO,IAAM,wBAAN,MAA2D;AAAA,EAA3D;AACL,SAAO,YAGH,IAAI,sBAAsB;AAG9B,gBAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASC,mBACN,MACA,SACA,QACA,SACA;AACA,UAAM,aACJ,QAAQ,aAAa,SAAS,QAAQ,UAAU,MAAM,IAAI;AAE5D,SAAK,kBAAkB,MAAM,SAAS,UAAU;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,kBACN,MACA,SACA,SACA;AACA,UAAM,EAAE,kBAAkB,KAAK,IAAI;AACnC,QAAI,iBAAiB,IAAI,KAAK,EAAE,MAAM,SAAS;AAC7C,uBAAiB,IAAI,KAAK,IAAI,UAAU,UAAU,IAAI;AACtD,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,UAAoB,SAAmC;AACnE,aAAS,MAAM,cAAc,IAAI,KAAK,MAAM,CAAC,MAAM,YAAY;AAC7D,UAAI,CAAC,KAAK,QAAQ,IAAI,GAAG;AACvB,eAAO;AAAA,MACT;AAEA,YAAM,eAAe,QAAQ,iBAAiB,IAAI,KAAK,EAAE;AACzD,UAAI,iBAAiB,QAAW;AAC9B,eAAO;AAAA,MACT;AAEA,UAAI,QAAQ,gBAAgB,IAAI,KAAK,EAAE,GAAG;AACxC,eAAO;AAAA,MACT;AAGA,cAAQ,gBAAgB,IAAI,KAAK,EAAE;AACnC,qBAAe,MAAM;AACnB,aAAK,aAAa,MAAM,SAAS,OAAO,EAAE,QAAQ;AAAA,MACpD,CAAC;AAED,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,aACZ,MACA,SACA,SACA;AACA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,YAAY,MAAM,YAAY;AAAA,QACtD;AAAA,QACA,CAACA,YAAW;AACV,eAAK,mBAAmB,MAAM,SAASA,SAAQ,OAAO;AAAA,QACxD;AAAA,MACF;AAGA,cAAQ,gBAAgB,OAAO,KAAK,EAAE;AACtC,WAAK,mBAAmB,MAAM,SAAS,QAAQ,OAAO;AAAA,IACxD,SAAS,GAAY;AACnB,YAAM,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAC1D,YAAM,SAAS,KAAK,YAAY,MAAM,iBAAiB,KAAK,OAAO,IAAI;AAEvE,UAAI,WAAW,QAAW;AACxB,cAAM,cAAc,KAAK,YAAY,kBAAkB,GAAG,SAAS;AAEnE,YAAI,aAAa,WAAW,eAAe;AACzC,sBAAY,KAAK,KAAK;AAAA,QACxB;AAEA;AAAA,MACF;AAEA,cAAQ,QAAQ;AAAA,QACd;AAAA,QACA;AAAA,MACF;AAGA,cAAQ,gBAAgB,OAAO,KAAK,EAAE;AACtC,WAAK,mBAAmB,MAAM,SAAS,QAAQ,OAAO;AAAA,IACxD;AAAA,EACF;AAAA,EAEQ,QAAQ,MAA4C;AAC1D,WAAO,MAAM,SAAS,SAAS;AAAA,EACjC;AAAA,EAEQ,kBAAkB,KAAU;AAClC,WAAO,OAAO,OAAO,UAAU,eAAe,KAAK,KAAK,OAAO;AAAA,EACjE;AAAA,EAEA,YAAY,QAAsB;AAChC,WAAO,MAAM,UAAU;AAAA,MACrB,KAAK;AAAA,MACL,CACE,KACA,UACA,SACA,iBACG;AACH,YAAI,KAAK,kBAAkB,GAAG,GAAG;AAC/B,gBAAM,cAAc,OAAO;AAAA,YACzB,KAAK,KAAK,OAAO;AAAA,YACjB;AAAA,YACA;AAAA,UACF;AACA,gBAAM,eAAe,UAAU,WAAW;AAE1C,cAAI,gBAAgB,QAAQ,CAAC,cAAc;AACzC,mBAAO,eAAe,CAAC,IAAI;AAAA,UAC7B;AAEA,gBAAM,WAAW,OAAO;AAAA,YACtB;AAAA,cACE,IAAI;AAAA,cACJ,MAAM,SAAS;AAAA,cACf,OAAO;AAAA,YACT;AAAA,YACA;AAAA,UACF;AAEA,cAAI,cAAc;AAChB,mBAAO,WACH;AAAA,cACE;AAAA,gBACE,MAAM,CAAC,GAAG,aAAa,MAAM,aAAa,GAAG;AAAA,gBAC7C,OAAO;AAAA,cACT;AAAA,YACF,IACA,CAAC;AAAA,UACP;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,MAA0B;AAC9B,UAAM,UAA8B;AAAA,MAClC,kBAAkB,oBAAI,IAAI;AAAA,MAC1B,iBAAiB,oBAAI,IAAI;AAAA,MACzB;AAAA,IACF;AAEA,SAAK,MAAM,OAAO,IAAI,SAAS,KAAK,YAAY,KAAK,IAAI,CAAC;AAC1D,SAAK,MAAM,SAAS,IAAI,SAAS,CAAC,aAAa;AAC7C,WAAK,cAAc,UAAU,OAAO;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,iBAAwC;AAClD,SAAK,aAAa;AAAA,EACpB;AACF;","names":["result"]}
|
package/package.json
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"types"
|
|
7
7
|
],
|
|
8
8
|
"name": "@player-ui/async-node-plugin",
|
|
9
|
-
"version": "0.13.0-next.
|
|
9
|
+
"version": "0.13.0-next.4",
|
|
10
10
|
"main": "dist/cjs/index.cjs",
|
|
11
11
|
"peerDependencies": {
|
|
12
|
-
"@player-ui/player": "0.13.0-next.
|
|
12
|
+
"@player-ui/player": "0.13.0-next.4"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@player-ui/
|
|
16
|
-
"@player-ui/
|
|
15
|
+
"@player-ui/check-path-plugin": "workspace:*",
|
|
16
|
+
"@player-ui/partial-match-registry": "workspace:*"
|
|
17
17
|
},
|
|
18
18
|
"module": "dist/index.legacy-esm.js",
|
|
19
19
|
"types": "types/index.d.ts",
|