@player-ui/async-node-plugin 0.15.4--canary.881.37421 → 0.15.4--canary.885.37636

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.
@@ -8,11 +8,6 @@ import {
8
8
  BeforeTransformFunction,
9
9
  Flow,
10
10
  NodeType,
11
- Logger,
12
- ErrorTypes,
13
- ErrorSeverity,
14
- PlayerErrorMetadata,
15
- ErrorMetadata,
16
11
  } from "@player-ui/player";
17
12
  import { Player, Parser } from "@player-ui/player";
18
13
  import { waitFor } from "@testing-library/react";
@@ -23,19 +18,6 @@ import {
23
18
  } from "../index";
24
19
  import { CheckPathPlugin } from "@player-ui/check-path-plugin";
25
20
  import { Registry } from "@player-ui/partial-match-registry";
26
- import { ExpressionPlugin } from "@player-ui/expression-plugin";
27
- import { AsyncNodeError } from "../AsyncNodeError";
28
-
29
- class ErrorWithProps extends Error implements PlayerErrorMetadata {
30
- constructor(
31
- message: string,
32
- public type: string,
33
- public severity?: ErrorSeverity,
34
- public metadata?: ErrorMetadata,
35
- ) {
36
- super(message);
37
- }
38
- }
39
21
 
40
22
  const transform: BeforeTransformFunction = createAsyncTransform({
41
23
  transformAssetType: "chat-message",
@@ -97,35 +79,7 @@ const asyncAssetFrf: Flow = {
97
79
  VIEW_1: {
98
80
  state_type: "VIEW",
99
81
  ref: "my-view",
100
- transitions: {
101
- "*": "END",
102
- },
103
- },
104
- END: {
105
- state_type: "END",
106
- outcome: "done",
107
- },
108
- },
109
- },
110
- };
111
-
112
- const nonViewErrorFlow: Flow = {
113
- id: "test-flow",
114
- views: [],
115
- navigation: {
116
- BEGIN: "FLOW_1",
117
- FLOW_1: {
118
- startState: "ACTION_1",
119
- ACTION_1: {
120
- state_type: "ACTION",
121
- exp: ["captureError()"],
122
- transitions: {
123
- "*": "END",
124
- },
125
- },
126
- END: {
127
- state_type: "END",
128
- outcome: "done",
82
+ transitions: {},
129
83
  },
130
84
  },
131
85
  },
@@ -1121,7 +1075,7 @@ describe("view", () => {
1121
1075
 
1122
1076
  await waitFor(() => {
1123
1077
  expect(onAsyncNodeErrorCallback).toHaveBeenCalledWith(
1124
- expect.objectContaining({ cause: new Error("Promise Rejected") }),
1078
+ new Error("Promise Rejected"),
1125
1079
  expect.anything(),
1126
1080
  );
1127
1081
 
@@ -1151,241 +1105,14 @@ describe("view", () => {
1151
1105
 
1152
1106
  await waitFor(() => {
1153
1107
  expect(onAsyncNodeErrorCallback).toHaveBeenCalledWith(
1154
- expect.objectContaining({ cause: new Error("Promise Rejected") }),
1108
+ new Error("Promise Rejected"),
1155
1109
  expect.anything(),
1156
1110
  );
1157
1111
 
1158
1112
  const playerState = player.getState();
1159
1113
  expect(playerState.status).toBe("error");
1160
1114
  const errorState = playerState as ErrorState;
1161
- expect(errorState.error.message).toBe(
1162
- "An error occured during async node resolution. See cause for details.",
1163
- );
1164
- expect(errorState.error).toBeInstanceOf(AsyncNodeError);
1165
- expect((errorState.error as AsyncNodeError).cause?.message).toBe(
1166
- "Promise Rejected",
1167
- );
1168
- });
1169
- });
1170
-
1171
- test("should log and absorb errors occuring outside of an in-progress state", async () => {
1172
- const vitestLogger: Logger = {
1173
- debug: vi.fn(),
1174
- error: vi.fn(),
1175
- info: vi.fn(),
1176
- trace: vi.fn(),
1177
- warn: vi.fn(),
1178
- };
1179
- const plugin = new AsyncNodePlugin({
1180
- plugins: [new AsyncNodePluginPlugin()],
1181
- });
1182
-
1183
- let throwAsyncError: ((err: Error) => void) | undefined;
1184
-
1185
- plugin.hooks.onAsyncNode.tap("test", async () => {
1186
- return new Promise((_, rej) => {
1187
- throwAsyncError = rej;
1188
- });
1189
- });
1190
-
1191
- const plugins = [plugin, new TestAsyncPlugin()];
1192
-
1193
- const player = new Player({
1194
- plugins: plugins,
1195
- logger: vitestLogger,
1196
- });
1197
-
1198
- player.start(asyncAssetFrf);
1199
-
1200
- await vi.waitFor(() => {
1201
- expect(throwAsyncError).toBeDefined();
1202
- });
1203
-
1204
- (player.getState() as InProgressState).controllers.flow.transition(
1205
- "done",
1206
- );
1207
- await vi.waitFor(() => {
1208
- const playerState = player.getState();
1209
- expect(playerState.status).toBe("completed");
1210
- });
1211
-
1212
- throwAsyncError!(new Error("Test Error"));
1213
-
1214
- await vi.waitFor(() => {
1215
- const state = player.getState();
1216
- // should not leave completed state
1217
- expect(state.status).toBe("completed");
1218
-
1219
- expect(vitestLogger.warn).toHaveBeenCalledWith(
1220
- expect.any(String), // Message doesn't matter, just check that the logged error is correct
1221
- new Error("Test Error"),
1222
- );
1223
- });
1224
- });
1225
-
1226
- test("should do nothing with errors when not in a view state", async () => {
1227
- const plugin = new AsyncNodePlugin({
1228
- plugins: [new AsyncNodePluginPlugin()],
1229
- });
1230
- // Call capture error in an action state to make sure asyncnodeplugin doesn't try to handle this
1231
- const expPlugin = new ExpressionPlugin(
1232
- new Map([
1233
- [
1234
- "captureError",
1235
- () => {
1236
- (
1237
- player.getState() as InProgressState
1238
- ).controllers.error.captureError(
1239
- new ErrorWithProps(
1240
- "Test Error",
1241
- ErrorTypes.RENDER,
1242
- ErrorSeverity.ERROR,
1243
- { assetId: "asset" },
1244
- ),
1245
- );
1246
- },
1247
- ],
1248
- ]),
1249
- );
1250
- const plugins = [plugin, expPlugin, new TestAsyncPlugin()];
1251
-
1252
- const player = new Player({
1253
- plugins: plugins,
1254
- });
1255
-
1256
- player.start(nonViewErrorFlow).catch(() => {});
1257
-
1258
- await vi.waitFor(() => {
1259
- const state = player.getState();
1260
- expect(state.status).toBe("error");
1261
- expect(onAsyncNodeErrorCallback).not.toHaveBeenCalled();
1262
- });
1263
- });
1264
-
1265
- test("should fail to handle errors if the plugin is setup incorrectly", async () => {
1266
- const vitestLogger: Logger = {
1267
- debug: vi.fn(),
1268
- error: vi.fn(),
1269
- info: vi.fn(),
1270
- trace: vi.fn(),
1271
- warn: vi.fn(),
1272
- };
1273
- const nodePluginPlugin = new AsyncNodePluginPlugin();
1274
-
1275
- // Apply AsyncNodePluginPlugin in isolation to observe failures
1276
- const plugin: PlayerPlugin = {
1277
- name: "TestPlayerPlugin",
1278
- apply: (player: Player) => {
1279
- nodePluginPlugin.applyPlayer(player);
1280
- player.hooks.view.tap("test", (view) => {
1281
- nodePluginPlugin.apply(view);
1282
- });
1283
- },
1284
- };
1285
- const plugins = [plugin, new TestAsyncPlugin()];
1286
-
1287
- const player = new Player({
1288
- plugins: plugins,
1289
- logger: vitestLogger,
1290
- });
1291
- player.start(asyncAssetFrf).catch(() => {});
1292
-
1293
- await vi.waitFor(() => {
1294
- const state = player.getState();
1295
- expect(state.status).toBe("in-progress");
1296
- });
1297
-
1298
- (player.getState() as InProgressState).controllers.error.captureError(
1299
- new ErrorWithProps("Test Error", ErrorTypes.VIEW, ErrorSeverity.ERROR, {
1300
- node: {
1301
- type: NodeType.Async,
1302
- value: {},
1303
- },
1304
- }),
1305
- );
1306
-
1307
- await vi.waitFor(() => {
1308
- const state = player.getState();
1309
- expect(state.status).toBe("error");
1310
- expect(onAsyncNodeErrorCallback).not.toHaveBeenCalled();
1311
- expect(vitestLogger.warn).toHaveBeenCalledWith(
1312
- "[AsyncNodePlugin]: No plugin detected. Error handling will fail",
1313
- );
1314
- });
1315
- });
1316
-
1317
- test("should call onAsyncNodeError hook for any async node involved in generating the current one", async () => {
1318
- const plugin = new AsyncNodePlugin({
1319
- plugins: [new AsyncNodePluginPlugin()],
1320
- });
1321
-
1322
- const errorHandler = vi.fn((err: Error, node: Node.Async) => {
1323
- if (node.id === "async-chat-id-2") {
1324
- return {
1325
- asset: {
1326
- type: "text",
1327
- value: "text",
1328
- id: "FIXED",
1329
- },
1330
- };
1331
- }
1332
-
1333
- return undefined;
1334
- });
1335
-
1336
- plugin.hooks.onAsyncNodeError.tap("test", errorHandler);
1337
-
1338
- let id = 0;
1339
- plugin.hooks.onAsyncNode.tap("test", () => {
1340
- const messageId = id++;
1341
- if (messageId > 5) {
1342
- throw new Error("Test Error");
1343
- }
1344
-
1345
- return Promise.resolve({
1346
- asset: {
1347
- type: "chat-message",
1348
- id: `chat-id-${messageId}`,
1349
- value: {
1350
- id: `chat-id-text-${messageId}`,
1351
- type: "text",
1352
- value: `Test Message ${messageId}`,
1353
- },
1354
- },
1355
- });
1356
- });
1357
-
1358
- const player = new Player({
1359
- plugins: [plugin, new TestAsyncPlugin()],
1360
- });
1361
- player.start(asyncAssetFrf).catch(() => {});
1362
-
1363
- await vi.waitFor(() => {
1364
- expect(id).toBeGreaterThan(5);
1365
- expect(errorHandler).toHaveBeenCalledWith(
1366
- expect.anything(),
1367
- expect.objectContaining({
1368
- id: "async-chat-id-5",
1369
- }),
1370
- );
1371
- expect(errorHandler).toHaveBeenCalledWith(
1372
- expect.anything(),
1373
- expect.objectContaining({
1374
- id: "async-chat-id-4",
1375
- }),
1376
- );
1377
- expect(errorHandler).toHaveBeenCalledWith(
1378
- expect.anything(),
1379
- expect.objectContaining({
1380
- id: "async-chat-id-3",
1381
- }),
1382
- );
1383
- expect(errorHandler).toHaveBeenCalledWith(
1384
- expect.anything(),
1385
- expect.objectContaining({
1386
- id: "async-chat-id-2",
1387
- }),
1388
- );
1115
+ expect(errorState.error.message).toBe("Promise Rejected");
1389
1116
  });
1390
1117
  });
1391
1118
  });
package/src/index.ts CHANGED
@@ -10,17 +10,33 @@ import type {
10
10
  ViewPlugin,
11
11
  Resolver,
12
12
  Resolve,
13
+ ViewController,
13
14
  } from "@player-ui/player";
14
15
  import { AsyncSeriesBailHook, SyncBailHook } from "tapable-ts";
15
16
  import queueMicrotask from "queue-microtask";
16
- import { AsyncNodeError } from "./AsyncNodeError";
17
- import { AsyncNodeInfo, AsyncPluginContext } from "./internal-types";
18
- import { getNodeFromError } from "./utils";
19
17
 
20
18
  export * from "./types";
21
19
  export * from "./transform";
22
20
  export * from "./createAsyncTransform";
23
21
 
22
+ /** Object type for storing data related to a single `apply` of the `AsyncNodePluginPlugin`
23
+ * 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.
24
+ */
25
+ type AsyncPluginContext = {
26
+ /** Map of async node id to resolved content */
27
+ nodeResolveCache: Map<string, Node.Node>;
28
+ /** The view instance this context is attached to. */
29
+ view: ViewInstance;
30
+ /** The view controller this context is attached to. */
31
+ viewController: ViewController;
32
+ /** Map of async node id to promises being used to resolve them */
33
+ inProgressNodes: Set<string>;
34
+ /** Map of async node ids to the original node they represent.
35
+ * In some cases, async nodes are transformed into from other node types so the original reference is needed in order to trigger an update on the view when the async node changes.
36
+ */
37
+ originalNodeCache: Map<string, Set<Node.Node>>;
38
+ };
39
+
24
40
  export interface AsyncNodePluginOptions {
25
41
  /** A set of plugins to load */
26
42
  plugins?: AsyncNodeViewPlugin[];
@@ -126,10 +142,10 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
126
142
  node: Node.Async,
127
143
  context: AsyncPluginContext,
128
144
  result: any,
129
- parseFunction?: (content: any) => Node.Node | null,
145
+ options: Resolve.NodeResolveOptions,
130
146
  ) {
131
147
  let parsedNode =
132
- parseFunction && result ? parseFunction(result) : undefined;
148
+ options.parseNode && result ? options.parseNode(result) : undefined;
133
149
 
134
150
  if (parsedNode && node.onValueReceived) {
135
151
  parsedNode = node.onValueReceived(parsedNode);
@@ -152,41 +168,22 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
152
168
  context: AsyncPluginContext,
153
169
  newNode?: Node.Node | null,
154
170
  ) {
155
- const { asyncNodeCache: asyncNodeInfo, viewController } = context;
156
- const entry = asyncNodeInfo.get(node.id);
157
- if (!entry) {
158
- throw new Error("Failed to update async content. Cache entry not found");
159
- }
160
- if (entry.resolvedContent !== newNode) {
161
- entry.resolvedContent = newNode ? newNode : entry.asyncNode;
162
- viewController.updateViewAST(entry.updateNodes);
171
+ const { nodeResolveCache, viewController, originalNodeCache } = context;
172
+ if (nodeResolveCache.get(node.id) !== newNode) {
173
+ nodeResolveCache.set(node.id, newNode ? newNode : node);
174
+ const originalNode = originalNodeCache.get(node.id) ?? new Set([node]);
175
+ viewController.updateViewAST(originalNode);
163
176
  }
164
177
  }
165
178
 
166
179
  private hasValidMapping(
167
- cacheEntry: AsyncNodeInfo,
168
- ): cacheEntry is Required<AsyncNodeInfo> {
169
- return (
170
- cacheEntry.resolvedContent !== undefined &&
171
- cacheEntry.resolvedContent !== cacheEntry.asyncNode
172
- );
173
- }
174
-
175
- private getOrCreateAsyncNodeCacheEntry(
176
180
  node: Node.Async,
177
181
  context: AsyncPluginContext,
178
- ): AsyncNodeInfo {
179
- const { asyncNodeCache: asyncNodeInfo } = context;
180
- let entry = asyncNodeInfo.get(node.id);
181
- if (!entry) {
182
- entry = {
183
- asyncNode: node,
184
- updateNodes: new Set(),
185
- };
186
- asyncNodeInfo.set(node.id, entry);
187
- }
188
-
189
- return entry;
182
+ ): boolean {
183
+ const { nodeResolveCache } = context;
184
+ return (
185
+ nodeResolveCache.has(node.id) && nodeResolveCache.get(node.id) !== node
186
+ );
190
187
  }
191
188
 
192
189
  /**
@@ -196,32 +193,17 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
196
193
  * @param view
197
194
  */
198
195
  applyResolver(resolver: Resolver, context: AsyncPluginContext): void {
199
- const { assetIdCache } = context;
200
- resolver.hooks.afterNodeUpdate.tap(this.name, (original, _, update) => {
201
- if (
202
- update.node.type !== NodeType.Asset &&
203
- update.node.type !== NodeType.View
204
- ) {
205
- return;
206
- }
207
-
208
- assetIdCache.set(update.value.id, original);
209
- });
210
-
211
196
  resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
212
197
  if (!this.isAsync(node)) {
213
198
  return node === null ? node : this.resolveAsyncChildren(node, context);
214
199
  }
215
-
216
- const entry = this.getOrCreateAsyncNodeCacheEntry(node, context);
217
-
218
200
  if (options.node) {
219
- entry.updateNodes = new Set([options.node]);
220
- context.generatedByMap.set(options.node, node.id);
201
+ context.originalNodeCache.set(node.id, new Set([options.node]));
221
202
  }
222
203
 
223
- if (entry.resolvedContent !== undefined) {
224
- return this.resolveAsyncChildren(entry.resolvedContent, context);
204
+ const resolvedNode = context.nodeResolveCache.get(node.id);
205
+ if (resolvedNode !== undefined) {
206
+ return this.resolveAsyncChildren(resolvedNode, context);
225
207
  }
226
208
 
227
209
  if (context.inProgressNodes.has(node.id)) {
@@ -254,24 +236,20 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
254
236
  let index = 0;
255
237
  while (index < node.values.length) {
256
238
  const childNode = node.values[index];
257
- if (childNode?.type !== NodeType.Async) {
239
+ if (
240
+ childNode?.type !== NodeType.Async ||
241
+ !this.hasValidMapping(childNode, context)
242
+ ) {
258
243
  index++;
259
244
  continue;
260
245
  }
261
- const entry = this.getOrCreateAsyncNodeCacheEntry(childNode, context);
262
246
 
263
- if (!this.hasValidMapping(entry)) {
264
- index++;
265
- continue;
266
- }
267
-
268
- const mappedNode = entry.resolvedContent;
247
+ const mappedNode = context.nodeResolveCache.get(childNode.id)!;
269
248
  const nodeSet = new Set<Node.Node>();
270
249
  if (mappedNode.type === NodeType.MultiNode && childNode.flatten) {
271
250
  mappedNode.values.forEach((v: Node.Node) => {
272
251
  v.parent = node;
273
252
  nodeSet.add(v);
274
- context.originalParentMap.set(v, childNode);
275
253
  });
276
254
  node.values = [
277
255
  ...node.values.slice(0, index),
@@ -283,23 +261,17 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
283
261
  mappedNode.parent = node;
284
262
  nodeSet.add(mappedNode);
285
263
  }
286
- entry.updateNodes = nodeSet;
287
- for (const n of nodeSet) {
288
- context.generatedByMap.set(n, childNode.id);
289
- }
264
+ context.originalNodeCache.set(childNode.id, nodeSet);
290
265
  }
291
266
  } else if ("children" in node) {
292
267
  node.children?.forEach((c) => {
293
268
  // Similar to above, using a while loop lets us handle when async nodes produce more async nodes.
294
- while (c.value.type === NodeType.Async) {
295
- const entry = this.getOrCreateAsyncNodeCacheEntry(c.value, context);
296
- if (!this.hasValidMapping(entry)) {
297
- break;
298
- }
299
-
300
- const mappedNode = entry.resolvedContent;
301
- entry.updateNodes = new Set([mappedNode]);
302
- context.generatedByMap.set(mappedNode, c.value.id);
269
+ while (
270
+ c.value.type === NodeType.Async &&
271
+ this.hasValidMapping(c.value, context)
272
+ ) {
273
+ const mappedNode = context.nodeResolveCache.get(c.value.id)!;
274
+ context.originalNodeCache.set(c.value.id, new Set([mappedNode]));
303
275
  c.value = mappedNode;
304
276
  c.value.parent = node;
305
277
  }
@@ -318,31 +290,35 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
318
290
  const result = await this.basePlugin?.hooks.onAsyncNode.call(
319
291
  node,
320
292
  (result) => {
321
- this.parseNodeAndUpdate(node, context, result, options.parseNode);
293
+ this.parseNodeAndUpdate(node, context, result, options);
322
294
  },
323
295
  );
324
296
 
325
297
  // Stop tracking before the next update is triggered
326
298
  context.inProgressNodes.delete(node.id);
327
- this.parseNodeAndUpdate(node, context, result, options.parseNode);
299
+ this.parseNodeAndUpdate(node, context, result, options);
328
300
  } catch (e: unknown) {
329
- const cause = e instanceof Error ? e : new Error(String(e));
330
- const playerState = this.basePlugin?.getPlayerInstance()?.getState();
331
-
332
- if (playerState?.status !== "in-progress") {
333
- options.logger?.warn(
334
- "[AsyncNodePlugin]: An error occured during async node resolution, but the player instance is no londer running. Exception: ",
335
- cause,
336
- );
301
+ const error = e instanceof Error ? e : new Error(String(e));
302
+ const result = this.basePlugin?.hooks.onAsyncNodeError.call(error, node);
303
+
304
+ if (result === undefined) {
305
+ const playerState = this.basePlugin?.getPlayerInstance()?.getState();
306
+
307
+ if (playerState?.status === "in-progress") {
308
+ playerState.fail(error);
309
+ }
310
+
337
311
  return;
338
312
  }
339
313
 
340
- const error = new AsyncNodeError(
341
- node,
342
- "An error occured during async node resolution. See cause for details.",
343
- cause,
314
+ options.logger?.error(
315
+ "Async node handling failed and resolved with a fallback. Error:",
316
+ error,
344
317
  );
345
- playerState.controllers.error.captureError(error);
318
+
319
+ // Stop tracking before the next update is triggered
320
+ context.inProgressNodes.delete(node.id);
321
+ this.parseNodeAndUpdate(node, context, result, options);
346
322
  }
347
323
  }
348
324
 
@@ -409,112 +385,15 @@ export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
409
385
  }
410
386
 
411
387
  applyPlayer(player: Player): void {
412
- // TODO: Need a better mechanism for storing the current context.
413
- let currentContext: AsyncPluginContext | undefined = undefined;
414
- let parser: Parser | undefined = undefined;
415
-
416
- player.hooks.errorController.tap("async", (errorController) => {
417
- errorController.hooks.onError.tap("async", (playerError) => {
418
- if (currentContext === undefined) {
419
- return undefined;
420
- }
421
-
422
- /** Try to handle the error using the onAsyncNodeError hook. Returns true if new content is provided. */
423
- const tryHandleError = (asyncNode: Node.Async): boolean => {
424
- if (this.basePlugin === undefined) {
425
- player.logger.warn(
426
- `[AsyncNodePlugin]: No plugin detected. Error handling will fail`,
427
- );
428
- }
429
-
430
- let result: any = undefined;
431
- result = this.basePlugin?.hooks.onAsyncNodeError.call(
432
- playerError,
433
- asyncNode,
434
- );
435
-
436
- if (result === undefined) {
437
- return false;
438
- }
439
-
440
- player.logger?.warn(
441
- "[AsyncNodePlugin]: Async node handling failed and resolved with a fallback. Cause:",
442
- playerError.message,
443
- );
444
-
445
- // Stop tracking before the next update is triggered
446
- currentContext!.inProgressNodes.delete(asyncNode.id);
447
- this.parseNodeAndUpdate(
448
- asyncNode,
449
- currentContext!,
450
- result,
451
- parser?.parseObject.bind(parser),
452
- );
453
-
454
- return true;
455
- };
456
-
457
- const getNextNode = (node: Node.Node): Node.Node | undefined => {
458
- const parent =
459
- currentContext?.originalParentMap.get(node) ?? node.parent;
460
-
461
- if (!parent) {
462
- return undefined;
463
- }
464
-
465
- // asyncNodeCache has current asyncNode reference more up to date with what's happening in the resolver. Sometimes AsyncNodeError has old references so this helps us move up the tree more accurately
466
- return this.isAsync(parent)
467
- ? currentContext?.asyncNodeCache.get(parent.id)?.asyncNode
468
- : parent;
469
- };
470
-
471
- let node = getNodeFromError(playerError, currentContext);
472
- // If the node is an async node try, to handle errors with it first.
473
- if (node?.type === NodeType.Async && tryHandleError(node)) {
474
- return true;
475
- }
476
-
477
- // Loop through the nodes to see if something is generated by something else. Continue until the error is handled or there are no more nodes to check
478
- while (node !== undefined) {
479
- const generatedBy = currentContext.generatedByMap.get(node);
480
- if (generatedBy) {
481
- const entry = currentContext.asyncNodeCache.get(generatedBy);
482
-
483
- if (!entry) {
484
- node = getNextNode(node);
485
- continue;
486
- }
487
-
488
- const { asyncNode } = entry;
489
-
490
- // Don't return false when the error isn't handled to allow for cases where one async is generated by another. Give different nodes a chance to try to recover from the error.
491
- if (tryHandleError(asyncNode)) {
492
- return true;
493
- }
494
- }
495
-
496
- node = getNextNode(node);
497
- }
498
-
499
- return undefined;
500
- });
501
- });
502
-
503
388
  player.hooks.viewController.tap("async", (viewController) => {
504
389
  viewController.hooks.view.tap("async", (view) => {
505
- view.hooks.parser.tap(this.name, (p) => {
506
- parser = p;
507
- });
508
390
  const context: AsyncPluginContext = {
391
+ nodeResolveCache: new Map(),
509
392
  inProgressNodes: new Set(),
510
393
  view,
511
394
  viewController,
512
- generatedByMap: new Map(),
513
- assetIdCache: new Map(),
514
- asyncNodeCache: new Map(),
515
- originalParentMap: new Map(),
395
+ originalNodeCache: new Map(),
516
396
  };
517
- currentContext = context;
518
397
 
519
398
  view.hooks.resolver.tap("async", (resolver) => {
520
399
  this.applyResolver(resolver, context);
@@ -2,5 +2,3 @@ export * from "./extractNodeFromPath";
2
2
  export * from "./traverseAndReplace";
3
3
  export * from "./unwrapAsset";
4
4
  export * from "./requiresAssetWrapper";
5
- export * from "./isAsyncPlayerError";
6
- export * from "./getNodeFromError";