@novely/core 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/novely.ts
2
2
  import { dequal } from "dequal/lite";
3
- import { throttle } from "es-toolkit/function";
3
+ import { memoize as memoize4, throttle } from "es-toolkit/function";
4
4
  import { merge as deepmerge } from "es-toolkit/object";
5
- import { DEV as DEV4 } from "esm-env";
5
+ import { DEV as DEV5 } from "esm-env";
6
6
 
7
7
  // ../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/full/index.mjs
8
8
  function set(obj, key, val) {
@@ -245,6 +245,7 @@ var getVolumeFromStore = (store2) => {
245
245
  };
246
246
 
247
247
  // src/utilities/actions-processing.ts
248
+ import { DEV as DEV2 } from "esm-env";
248
249
  var isExitImpossible = (path) => {
249
250
  const blockStatements = path.filter(([item]) => isBlockStatement(item));
250
251
  const blockExitStatements = path.filter(([item]) => isBlockExitStatement(item));
@@ -256,40 +257,66 @@ var isExitImpossible = (path) => {
256
257
  }
257
258
  return !blockExitStatements.every(([name], i) => name && name.startsWith(blockStatements[i][0]));
258
259
  };
259
- var createReferFunction = (story) => {
260
- const refer = (path) => {
260
+ var createReferFunction = ({ story, onUnknownSceneHit }) => {
261
+ const refer = async (path) => {
262
+ const { promise: ready, resolve: setReady } = Promise.withResolvers();
261
263
  let current = story;
262
264
  let precurrent = story;
263
265
  const blocks = [];
264
- for (const [type, val] of path) {
265
- if (type === "jump") {
266
- precurrent = story;
267
- current = current[val];
268
- } else if (type === null) {
269
- precurrent = current;
270
- current = current[val];
271
- } else if (type === "choice") {
272
- blocks.push(precurrent);
273
- current = current[val + 1][1];
274
- } else if (type === "condition") {
275
- blocks.push(precurrent);
276
- current = current[2][val];
277
- } else if (type === "block") {
278
- blocks.push(precurrent);
279
- current = story[val];
280
- } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
281
- current = blocks.pop();
266
+ const refer2 = async () => {
267
+ for (const [type, val] of path) {
268
+ if (type === "jump") {
269
+ if (!current[val]) {
270
+ setReady(true);
271
+ await onUnknownSceneHit(val);
272
+ }
273
+ if (DEV2 && !story[val]) {
274
+ throw new Error(`Attempt to jump to unknown scene "${val}"`);
275
+ }
276
+ if (DEV2 && story[val].length === 0) {
277
+ throw new Error(`Attempt to jump to empty scene "${val}"`);
278
+ }
279
+ precurrent = story;
280
+ current = current[val];
281
+ } else if (type === null) {
282
+ precurrent = current;
283
+ current = current[val];
284
+ } else if (type === "choice") {
285
+ blocks.push(precurrent);
286
+ current = current[val + 1][1];
287
+ } else if (type === "condition") {
288
+ blocks.push(precurrent);
289
+ current = current[2][val];
290
+ } else if (type === "block") {
291
+ blocks.push(precurrent);
292
+ current = story[val];
293
+ } else if (type === "block:exit" || type === "choice:exit" || type === "condition:exit") {
294
+ current = blocks.pop();
295
+ }
282
296
  }
283
- }
284
- return current;
297
+ setReady(false);
298
+ return current;
299
+ };
300
+ const value = refer2();
301
+ const found = await ready;
302
+ return {
303
+ found,
304
+ value
305
+ };
306
+ };
307
+ const referGuarded = async (path) => {
308
+ return await (await refer(path)).value;
309
+ };
310
+ return {
311
+ refer,
312
+ referGuarded
285
313
  };
286
- return refer;
287
314
  };
288
- var exitPath = ({ path, refer, onExitImpossible }) => {
315
+ var exitPath = async ({ path, refer, onExitImpossible }) => {
289
316
  const last = path.at(-1);
290
317
  const ignore = [];
291
318
  let wasExitImpossible = false;
292
- if (!isAction(refer(path))) {
319
+ if (!isAction(await refer(path))) {
293
320
  if (last && isNull(last[0]) && isNumber(last[1])) {
294
321
  last[1]--;
295
322
  } else {
@@ -297,7 +324,7 @@ var exitPath = ({ path, refer, onExitImpossible }) => {
297
324
  }
298
325
  }
299
326
  if (isExitImpossible(path)) {
300
- const referred = refer(path);
327
+ const referred = await refer(path);
301
328
  if (isAction(referred) && isSkippedDuringRestore(referred[0])) {
302
329
  onExitImpossible?.();
303
330
  }
@@ -319,7 +346,7 @@ var exitPath = ({ path, refer, onExitImpossible }) => {
319
346
  path.push([`${name}:exit`]);
320
347
  const prev = findLastPathItemBeforeItemOfType(path.slice(0, i + 1), name);
321
348
  if (prev) path.push([null, prev[1] + 1]);
322
- if (!isAction(refer(path))) {
349
+ if (!isAction(await refer(path))) {
323
350
  path.pop();
324
351
  continue;
325
352
  }
@@ -338,12 +365,16 @@ var nextPath = (path) => {
338
365
  }
339
366
  return path;
340
367
  };
341
- var collectActionsBeforeBlockingAction = ({ path, refer, clone }) => {
368
+ var collectActionsBeforeBlockingAction = async ({
369
+ path,
370
+ refer,
371
+ clone
372
+ }) => {
342
373
  const collection = [];
343
- let action = refer(path);
374
+ let action = await refer(path);
344
375
  while (true) {
345
376
  if (action == void 0) {
346
- const { exitImpossible } = exitPath({
377
+ const { exitImpossible } = await exitPath({
347
378
  path,
348
379
  refer
349
380
  });
@@ -363,7 +394,7 @@ var collectActionsBeforeBlockingAction = ({ path, refer, clone }) => {
363
394
  if (!Array.isArray(branchContent)) continue;
364
395
  const virtualPath = clone(path);
365
396
  virtualPath.push(["choice", i], [null, 0]);
366
- const innerActions = collectActionsBeforeBlockingAction({
397
+ const innerActions = await collectActionsBeforeBlockingAction({
367
398
  path: virtualPath,
368
399
  refer,
369
400
  clone
@@ -376,7 +407,7 @@ var collectActionsBeforeBlockingAction = ({ path, refer, clone }) => {
376
407
  for (const condition of conditions) {
377
408
  const virtualPath = clone(path);
378
409
  virtualPath.push(["condition", condition], [null, 0]);
379
- const innerActions = collectActionsBeforeBlockingAction({
410
+ const innerActions = await collectActionsBeforeBlockingAction({
380
411
  path: virtualPath,
381
412
  refer,
382
413
  clone
@@ -397,7 +428,7 @@ var collectActionsBeforeBlockingAction = ({ path, refer, clone }) => {
397
428
  } else {
398
429
  nextPath(path);
399
430
  }
400
- action = refer(path);
431
+ action = await refer(path);
401
432
  }
402
433
  return collection;
403
434
  };
@@ -417,7 +448,7 @@ var getOppositeAction = (action) => {
417
448
  };
418
449
  return MAP[action];
419
450
  };
420
- var getActionsFromPath = (story, path, filter) => {
451
+ var getActionsFromPath = async ({ story, path, filter, referGuarded }) => {
421
452
  let current = story;
422
453
  let precurrent;
423
454
  let ignoreNestedBefore = null;
@@ -432,6 +463,7 @@ var getActionsFromPath = (story, path, filter) => {
432
463
  }, 0);
433
464
  const queue = [];
434
465
  const blocks = [];
466
+ await referGuarded(path);
435
467
  for (const [type, val] of path) {
436
468
  if (type === "jump") {
437
469
  precurrent = story;
@@ -575,7 +607,7 @@ var createQueueProcessor = (queue, options) => {
575
607
  }
576
608
  }
577
609
  const run = async (match) => {
578
- for await (const item of processedQueue) {
610
+ for (const item of processedQueue) {
579
611
  const result = match(item);
580
612
  if (isPromise(result)) {
581
613
  await result;
@@ -619,13 +651,13 @@ var createControlledPromise = () => {
619
651
 
620
652
  // src/utilities/resources.ts
621
653
  import { memoize as memoize2 } from "es-toolkit/function";
622
- import { DEV as DEV2 } from "esm-env";
654
+ import { DEV as DEV3 } from "esm-env";
623
655
  var getUrlFileExtension = (address) => {
624
656
  try {
625
657
  const { pathname } = new URL(address, location.href);
626
658
  return pathname.split(".").at(-1).split("!")[0].split(":")[0];
627
659
  } catch (error) {
628
- if (DEV2) {
660
+ if (DEV3) {
629
661
  console.error(new Error(`Could not construct URL "${address}".`, { cause: error }));
630
662
  }
631
663
  return "";
@@ -638,7 +670,7 @@ var fetchContentType = async (url, request) => {
638
670
  });
639
671
  return response.headers.get("Content-Type") || "";
640
672
  } catch (error) {
641
- if (DEV2) {
673
+ if (DEV3) {
642
674
  console.error(new Error(`Failed to fetch file at "${url}"`, { cause: error }));
643
675
  }
644
676
  return "";
@@ -1031,7 +1063,7 @@ var replace = (input, data, pluralization, actions, pr) => {
1031
1063
  };
1032
1064
 
1033
1065
  // src/utilities/actions.ts
1034
- import { DEV as DEV3 } from "esm-env";
1066
+ import { DEV as DEV4 } from "esm-env";
1035
1067
  var VIRTUAL_ACTIONS = ["say"];
1036
1068
  var buildActionObject = ({
1037
1069
  rendererActions,
@@ -1047,7 +1079,7 @@ var buildActionObject = ({
1047
1079
  if (action === "say") {
1048
1080
  action = "dialog";
1049
1081
  const [character] = props;
1050
- if (DEV3 && !characters[character]) {
1082
+ if (DEV4 && !characters[character]) {
1051
1083
  throw new Error(`Attempt to call Say action with unknown character "${character}"`);
1052
1084
  }
1053
1085
  } else if (action === "choice") {
@@ -1118,7 +1150,8 @@ var novely = ({
1118
1150
  cloneFunction: clone = klona,
1119
1151
  saveOnUnload = true,
1120
1152
  startKey = "start",
1121
- defaultTypewriterSpeed = DEFAULT_TYPEWRITER_SPEED
1153
+ defaultTypewriterSpeed = DEFAULT_TYPEWRITER_SPEED,
1154
+ storyOptions = { mode: "static" }
1122
1155
  }) => {
1123
1156
  const languages = Object.keys(translation);
1124
1157
  const limitScript = pLimit(1);
@@ -1128,21 +1161,27 @@ var novely = ({
1128
1161
  const dataLoaded = createControlledPromise();
1129
1162
  let initialScreenWasShown = false;
1130
1163
  let destroyed = false;
1164
+ if (storyOptions.mode === "dynamic") {
1165
+ storyOptions.preloadSaves ??= 4;
1166
+ }
1167
+ const storyLoad = storyOptions.mode === "static" ? noop : storyOptions.load;
1168
+ const onUnknownSceneHit = memoize4(async (scene) => {
1169
+ const part = await storyLoad(scene);
1170
+ if (part) {
1171
+ await script(part);
1172
+ }
1173
+ });
1131
1174
  const intime = (value) => {
1132
1175
  return times.add(value), value;
1133
1176
  };
1134
1177
  const scriptBase = async (part) => {
1135
1178
  if (destroyed) return;
1136
1179
  Object.assign(story, flatStory(part));
1137
- let loadingIsShown = false;
1138
1180
  if (!initialScreenWasShown) {
1139
1181
  renderer.ui.showLoading();
1140
- loadingIsShown = true;
1141
1182
  }
1142
1183
  if (preloadAssets === "blocking" && ASSETS_TO_PRELOAD.size > 0) {
1143
- if (!loadingIsShown) {
1144
- renderer.ui.showLoading();
1145
- }
1184
+ renderer.ui.showLoading();
1146
1185
  await handleAssetsPreloading({
1147
1186
  ...renderer.misc,
1148
1187
  limiter: limitAssetsDownload,
@@ -1179,7 +1218,7 @@ var novely = ({
1179
1218
  if (languages.includes(language)) {
1180
1219
  return language;
1181
1220
  }
1182
- if (DEV4) {
1221
+ if (DEV5) {
1183
1222
  throw new Error(
1184
1223
  `Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
1185
1224
  );
@@ -1195,11 +1234,23 @@ var novely = ({
1195
1234
  const coreData = store({
1196
1235
  dataLoaded: false
1197
1236
  });
1198
- const onDataLoadedPromise = ({ cancelled }) => {
1237
+ const onDataLoadedPromise = async ({ cancelled }) => {
1199
1238
  if (cancelled) {
1200
1239
  dataLoaded.promise.then(onDataLoadedPromise);
1201
1240
  return;
1202
1241
  }
1242
+ const preload = () => {
1243
+ const saves = [...storageData.get().saves].reverse();
1244
+ const sliced = saves.slice(0, storyOptions.mode === "dynamic" ? storyOptions.preloadSaves : 0);
1245
+ for (const [path] of sliced) {
1246
+ referGuarded(path);
1247
+ }
1248
+ };
1249
+ if (preloadAssets === "blocking") {
1250
+ await preload();
1251
+ } else {
1252
+ void preload();
1253
+ }
1203
1254
  coreData.update((data2) => {
1204
1255
  data2.dataLoaded = true;
1205
1256
  return data2;
@@ -1216,7 +1267,7 @@ var novely = ({
1216
1267
  };
1217
1268
  const throttledOnStorageDataChange = throttle(onStorageDataChange, throttleTimeout);
1218
1269
  const throttledEmergencyOnStorageDataChange = throttle(() => {
1219
- if (saveOnUnload === true || saveOnUnload === "prod" && !DEV4) {
1270
+ if (saveOnUnload === true || saveOnUnload === "prod" && !DEV5) {
1220
1271
  onStorageDataChange(storageData.get());
1221
1272
  }
1222
1273
  }, 10);
@@ -1225,7 +1276,7 @@ var novely = ({
1225
1276
  let stored = await storage.get();
1226
1277
  for (const migration of migrations) {
1227
1278
  stored = migration(stored);
1228
- if (DEV4 && !stored) {
1279
+ if (DEV5 && !stored) {
1229
1280
  throw new Error("Migrations should return a value.");
1230
1281
  }
1231
1282
  }
@@ -1303,7 +1354,7 @@ var novely = ({
1303
1354
  let interacted = 0;
1304
1355
  const restore = async (save2) => {
1305
1356
  if (isEmpty(story)) {
1306
- if (DEV4) {
1357
+ if (DEV5) {
1307
1358
  throw new Error(
1308
1359
  "Story is empty. You should call an `enine.script` function [https://novely.pages.dev/guide/story.html]"
1309
1360
  );
@@ -1325,7 +1376,14 @@ var novely = ({
1325
1376
  const previous = stack.previous;
1326
1377
  const [path] = stack.value = latest;
1327
1378
  renderer.ui.showScreen("game");
1328
- const { queue, skip, skipPreserve } = getActionsFromPath(story, path, false);
1379
+ const { found } = await refer(path);
1380
+ if (found) context.loading(true);
1381
+ const { queue, skip, skipPreserve } = await getActionsFromPath({
1382
+ story,
1383
+ path,
1384
+ filter: false,
1385
+ referGuarded
1386
+ });
1329
1387
  const {
1330
1388
  run,
1331
1389
  keep: { keep, characters: characters2, audio: audio2 }
@@ -1334,7 +1392,12 @@ var novely = ({
1334
1392
  skipPreserve
1335
1393
  });
1336
1394
  if (previous) {
1337
- const { queue: prevQueue } = getActionsFromPath(story, previous[0], false);
1395
+ const { queue: prevQueue } = await getActionsFromPath({
1396
+ story,
1397
+ path: previous[0],
1398
+ filter: false,
1399
+ referGuarded
1400
+ });
1338
1401
  for (let i = prevQueue.length - 1; i > queue.length - 1; i--) {
1339
1402
  const element = prevQueue[i];
1340
1403
  if (!isAction(element)) {
@@ -1352,6 +1415,7 @@ var novely = ({
1352
1415
  data: latest[1]
1353
1416
  });
1354
1417
  }
1418
+ context.loading(false);
1355
1419
  const lastQueueItem = queue.at(-1);
1356
1420
  const lastQueueItemRequiresUserAction = lastQueueItem && isBlockingAction(lastQueueItem);
1357
1421
  await run((item) => {
@@ -1368,10 +1432,13 @@ var novely = ({
1368
1432
  if (!context.meta.goingBack) {
1369
1433
  context.meta.restoring = false;
1370
1434
  }
1371
- render(context);
1435
+ await render(context);
1372
1436
  context.meta.restoring = context.meta.goingBack = false;
1373
1437
  };
1374
- const refer = createReferFunction(story);
1438
+ const { refer, referGuarded } = createReferFunction({
1439
+ story,
1440
+ onUnknownSceneHit
1441
+ });
1375
1442
  const exit = (force = false, saving = true) => {
1376
1443
  const ctx = renderer.getContext(MAIN_CONTEXT_KEY);
1377
1444
  const stack = useStack(ctx);
@@ -1423,8 +1490,16 @@ var novely = ({
1423
1490
  });
1424
1491
  }
1425
1492
  const [path, data2] = save2;
1426
- const { queue } = getActionsFromPath(story, path, true);
1427
1493
  const ctx = renderer.getContext(name);
1494
+ const { found } = await refer(path);
1495
+ if (found) ctx.loading(true);
1496
+ const { queue } = await getActionsFromPath({
1497
+ story,
1498
+ path,
1499
+ filter: true,
1500
+ referGuarded
1501
+ });
1502
+ ctx.loading(false);
1428
1503
  ctx.meta.restoring = true;
1429
1504
  ctx.meta.preview = true;
1430
1505
  const processor = createQueueProcessor(queue, {
@@ -1476,7 +1551,7 @@ var novely = ({
1476
1551
  };
1477
1552
  const getLanguageDisplayName = (lang) => {
1478
1553
  const language = translation[lang];
1479
- if (DEV4 && !language) {
1554
+ if (DEV5 && !language) {
1480
1555
  throw new Error(
1481
1556
  `Attempt to use unsupported language "${language}". Supported languages: ${languages.join(", ")}.`
1482
1557
  );
@@ -1513,21 +1588,26 @@ var novely = ({
1513
1588
  }
1514
1589
  return String(c) || "";
1515
1590
  };
1516
- const getDialogOverview = () => {
1591
+ const getDialogOverview = async () => {
1517
1592
  const { value: save2 } = useStack(MAIN_CONTEXT_KEY);
1518
1593
  const stateSnapshots = save2[3];
1519
1594
  if (stateSnapshots.length == 0) {
1520
1595
  return [];
1521
1596
  }
1522
- const { queue } = getActionsFromPath(story, save2[0], false);
1597
+ const { queue } = await getActionsFromPath({
1598
+ story,
1599
+ path: save2[0],
1600
+ filter: false,
1601
+ referGuarded
1602
+ });
1523
1603
  const [lang] = storageData.get().meta;
1524
1604
  const dialogItems = [];
1525
- for (let p = 0, a = stateSnapshots.length, i = queue.length - 1; a > 0; i--) {
1605
+ for (let p = 0, a = stateSnapshots.length, i = queue.length - 1; a > 0 && i > 0; i--) {
1526
1606
  const action2 = queue[i];
1527
1607
  if (action2[0] === "dialog") {
1528
1608
  const [_, name, text] = action2;
1529
1609
  let voice = void 0;
1530
- for (let j = i - 1; j > p; j--) {
1610
+ for (let j = i - 1; j > p && j > 0; j--) {
1531
1611
  const action3 = queue[j];
1532
1612
  if (isUserRequiredAction(action3) || isSkippedDuringRestore(action3[0])) break;
1533
1613
  if (action3[0] === "stopVoice") break;
@@ -1609,14 +1689,14 @@ var novely = ({
1609
1689
  matchActionOptions.push(ctx);
1610
1690
  if (!ctx.meta.preview) interactivity(true);
1611
1691
  },
1612
- onBeforeActionCall({ action: action2, props, ctx }) {
1692
+ async onBeforeActionCall({ action: action2, props, ctx }) {
1613
1693
  if (preloadAssets !== "automatic") return;
1614
1694
  if (ctx.meta.preview || ctx.meta.restoring) return;
1615
1695
  if (!isBlockingAction([action2, ...props])) return;
1616
1696
  try {
1617
- const collection = collectActionsBeforeBlockingAction({
1697
+ const collection = await collectActionsBeforeBlockingAction({
1618
1698
  path: nextPath(clone(useStack(ctx).value[0])),
1619
- refer,
1699
+ refer: referGuarded,
1620
1700
  clone
1621
1701
  });
1622
1702
  for (const [action3, ...props2] of collection) {
@@ -1697,11 +1777,11 @@ var novely = ({
1697
1777
  },
1698
1778
  showCharacter({ ctx, push }, [character, emotion, className, style]) {
1699
1779
  emotion ??= defaultEmotions[character];
1700
- if (DEV4 && !emotion) {
1780
+ if (DEV5 && !emotion) {
1701
1781
  throw new Error(`Attemp to show character "${character}" without emotion provided.`);
1702
1782
  }
1703
1783
  if (!emotion) return;
1704
- if (DEV4 && !characters[character].emotions[emotion]) {
1784
+ if (DEV5 && !characters[character].emotions[emotion]) {
1705
1785
  throw new Error(`Attempt to show character "${character}" with unknown emotion "${emotion}"`);
1706
1786
  }
1707
1787
  const handle = ctx.character(character);
@@ -1768,7 +1848,7 @@ var novely = ({
1768
1848
  const imageValue = image ? handleImageAsset(image) : "";
1769
1849
  return [templateReplace(content, data2), active$, visible$, onSelectWrapped, imageValue];
1770
1850
  });
1771
- if (DEV4 && transformedChoices.length === 0) {
1851
+ if (DEV5 && transformedChoices.length === 0) {
1772
1852
  throw new Error(
1773
1853
  `Running choice without variants to choose from, look at how to use Choice action properly [https://novely.pages.dev/guide/actions/choice#usage]`
1774
1854
  );
@@ -1780,7 +1860,7 @@ var novely = ({
1780
1860
  }
1781
1861
  const stack = useStack(ctx);
1782
1862
  const offset = isWithoutQuestion ? 0 : 1;
1783
- if (DEV4 && !transformedChoices[selected]) {
1863
+ if (DEV5 && !transformedChoices[selected]) {
1784
1864
  throw new Error("Choice children is empty, either add content there or make item not selectable");
1785
1865
  }
1786
1866
  stack.value[0].push(["choice", selected + offset], [null, 0]);
@@ -1789,12 +1869,6 @@ var novely = ({
1789
1869
  });
1790
1870
  },
1791
1871
  jump({ ctx, data: data2 }, [scene]) {
1792
- if (DEV4 && !story[scene]) {
1793
- throw new Error(`Attempt to jump to unknown scene "${scene}"`);
1794
- }
1795
- if (DEV4 && story[scene].length === 0) {
1796
- throw new Error(`Attempt to jump to empty scene "${scene}"`);
1797
- }
1798
1872
  const stack = useStack(ctx);
1799
1873
  stack.value[0] = [
1800
1874
  ["jump", scene],
@@ -1816,15 +1890,15 @@ var novely = ({
1816
1890
  );
1817
1891
  },
1818
1892
  condition({ ctx, data: data2 }, [condition, variants]) {
1819
- if (DEV4 && Object.values(variants).length === 0) {
1893
+ if (DEV5 && Object.values(variants).length === 0) {
1820
1894
  throw new Error(`Attempt to use Condition action with empty variants object`);
1821
1895
  }
1822
1896
  if (!ctx.meta.restoring) {
1823
1897
  const val = String(condition(data2));
1824
- if (DEV4 && !variants[val]) {
1898
+ if (DEV5 && !variants[val]) {
1825
1899
  throw new Error(`Attempt to go to unknown variant "${val}"`);
1826
1900
  }
1827
- if (DEV4 && variants[val].length === 0) {
1901
+ if (DEV5 && variants[val].length === 0) {
1828
1902
  throw new Error(`Attempt to go to empty variant "${val}"`);
1829
1903
  }
1830
1904
  const stack = useStack(ctx);
@@ -1878,7 +1952,7 @@ var novely = ({
1878
1952
  },
1879
1953
  animateCharacter({ ctx, push }, [character, className]) {
1880
1954
  const classes = className.split(" ");
1881
- if (DEV4 && classes.length === 0) {
1955
+ if (DEV5 && classes.length === 0) {
1882
1956
  throw new Error(
1883
1957
  "Attempt to use AnimateCharacter without classes. Classes should be provided [https://novely.pages.dev/guide/actions/animateCharacter.html]"
1884
1958
  );
@@ -1889,17 +1963,17 @@ var novely = ({
1889
1963
  },
1890
1964
  text({ ctx, data: data2, forward }, text) {
1891
1965
  const string = text.map((content) => templateReplace(content, data2)).join(" ");
1892
- if (DEV4 && string.length === 0) {
1966
+ if (DEV5 && string.length === 0) {
1893
1967
  throw new Error(`Action Text was called with empty string or array`);
1894
1968
  }
1895
1969
  ctx.clearBlockingActions("text");
1896
1970
  ctx.text(string, forward);
1897
1971
  },
1898
- exit({ ctx, data: data2 }) {
1972
+ async exit({ ctx, data: data2 }) {
1899
1973
  if (ctx.meta.restoring) return;
1900
- const { exitImpossible } = exitPath({
1974
+ const { exitImpossible } = await exitPath({
1901
1975
  path: useStack(ctx).value[0],
1902
- refer,
1976
+ refer: referGuarded,
1903
1977
  onExitImpossible: () => {
1904
1978
  match("end", [], {
1905
1979
  ctx,
@@ -1914,7 +1988,7 @@ var novely = ({
1914
1988
  render(ctx);
1915
1989
  },
1916
1990
  preload({ ctx, push }, [source]) {
1917
- if (DEV4 && preloadAssets !== "lazy") {
1991
+ if (DEV5 && preloadAssets !== "lazy") {
1918
1992
  console.error(
1919
1993
  `You do not need a preload action becase "preloadAssets" strategy was set to "${preloadAssets}"`
1920
1994
  );
@@ -1925,10 +1999,10 @@ var novely = ({
1925
1999
  push();
1926
2000
  },
1927
2001
  block({ ctx }, [scene]) {
1928
- if (DEV4 && !story[scene]) {
2002
+ if (DEV5 && !story[scene]) {
1929
2003
  throw new Error(`Attempt to call Block action with unknown scene "${scene}"`);
1930
2004
  }
1931
- if (DEV4 && story[scene].length === 0) {
2005
+ if (DEV5 && story[scene].length === 0) {
1932
2006
  throw new Error(`Attempt to call Block action with empty scene "${scene}"`);
1933
2007
  }
1934
2008
  if (!ctx.meta.restoring) {
@@ -1945,10 +2019,17 @@ var novely = ({
1945
2019
  preloadAssets,
1946
2020
  storageData
1947
2021
  });
1948
- const render = (ctx) => {
2022
+ const render = async (ctx) => {
1949
2023
  const stack = useStack(ctx);
1950
2024
  const [path, state] = stack.value;
1951
- const referred = refer(path);
2025
+ const { found, value } = await refer(path);
2026
+ if (found) {
2027
+ ctx.loading(true);
2028
+ }
2029
+ const referred = await value;
2030
+ if (found) {
2031
+ ctx.loading(false);
2032
+ }
1952
2033
  if (isAction(referred)) {
1953
2034
  const [action2, ...props] = referred;
1954
2035
  match(action2, props, {
@@ -2002,7 +2083,7 @@ var novely = ({
2002
2083
  };
2003
2084
  const setStorageData = (data2) => {
2004
2085
  if (destroyed) {
2005
- if (DEV4) {
2086
+ if (DEV5) {
2006
2087
  throw new Error(
2007
2088
  `function \`setStorageData\` was called after novely instance was destroyed. Data is not updater nor synced after destroy.`
2008
2089
  );
@@ -2296,8 +2377,8 @@ var JP = {
2296
2377
  };
2297
2378
 
2298
2379
  // src/asset.ts
2299
- import { memoize as memoize4, once } from "es-toolkit/function";
2300
- import { DEV as DEV5 } from "esm-env";
2380
+ import { memoize as memoize5, once } from "es-toolkit/function";
2381
+ import { DEV as DEV6 } from "esm-env";
2301
2382
 
2302
2383
  // src/audio-codecs.ts
2303
2384
  var cut = (str) => str.replace(/^no$/, "");
@@ -2360,7 +2441,7 @@ loadImageFormatsSupport();
2360
2441
 
2361
2442
  // src/asset.ts
2362
2443
  var generateRandomId = () => Math.random().toString(36);
2363
- var getType = memoize4(
2444
+ var getType = memoize5(
2364
2445
  (extensions) => {
2365
2446
  if (extensions.every((extension) => HOWLER_SUPPORTED_FILE_FORMATS.has(extension))) {
2366
2447
  return "audio";
@@ -2378,9 +2459,9 @@ var SUPPORT_MAPS = {
2378
2459
  image: supportsMap2,
2379
2460
  audio: supportsMap
2380
2461
  };
2381
- var assetPrivate = memoize4(
2462
+ var assetPrivate = memoize5(
2382
2463
  (variants) => {
2383
- if (DEV5 && variants.length === 0) {
2464
+ if (DEV6 && variants.length === 0) {
2384
2465
  throw new Error(`Attempt to use "asset" function without arguments`);
2385
2466
  }
2386
2467
  const map = {};
@@ -2402,7 +2483,7 @@ var assetPrivate = memoize4(
2402
2483
  return map[extension];
2403
2484
  }
2404
2485
  }
2405
- if (DEV5) {
2486
+ if (DEV6) {
2406
2487
  throw new Error(`No matching asset was found for ${variants.map((v) => `"${v}"`).join(", ")}`);
2407
2488
  }
2408
2489
  return "";