@rohal12/spindle 0.50.1 → 0.51.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.
@@ -3489,6 +3489,16 @@ function restorePRNGFromMoment(moment) {
3489
3489
  if (moment?.prng) restorePRNG(moment.prng.seed, moment.prng.pull);
3490
3490
  else if (moment) resetPRNG();
3491
3491
  }
3492
+ /**
3493
+ * Return `p` marked as handled: a caller that ignores the result of a
3494
+ * fire-and-forget operation (whose failure is already logged) gets no
3495
+ * unhandled-rejection report, while a caller that awaits it still sees the
3496
+ * error.
3497
+ */
3498
+ function handled(p) {
3499
+ p.catch(() => {});
3500
+ return p;
3501
+ }
3492
3502
  var useStoryStore = create()(immer((set, get) => ({
3493
3503
  storyData: null,
3494
3504
  currentPassage: "",
@@ -3754,8 +3764,8 @@ var useStoryStore = create()(immer((set, get) => ({
3754
3764
  },
3755
3765
  save: (slot, custom) => {
3756
3766
  const { storyData, playthroughId } = get();
3757
- if (!storyData) return;
3758
- saveWithHooks(slot, custom, () => get().getSavePayload(), async (payload) => {
3767
+ if (!storyData) return Promise.resolve();
3768
+ return handled(saveWithHooks(slot, custom, () => get().getSavePayload(), async (payload) => {
3759
3769
  set((state) => {
3760
3770
  state.saveError = null;
3761
3771
  });
@@ -3771,15 +3781,16 @@ var useStoryStore = create()(immer((set, get) => ({
3771
3781
  set((state) => {
3772
3782
  state.saveError = err instanceof Error ? err.message : "Failed to save";
3773
3783
  });
3774
- });
3784
+ throw err;
3785
+ }));
3775
3786
  },
3776
3787
  load: (slot) => {
3777
3788
  const { storyData } = get();
3778
- if (!storyData) return;
3789
+ if (!storyData) return Promise.resolve();
3779
3790
  set((state) => {
3780
3791
  state.loadError = null;
3781
3792
  });
3782
- loadQuickSave(storyData.ifid, slot).then((payload) => {
3793
+ return handled(loadQuickSave(storyData.ifid, slot).then((payload) => {
3783
3794
  if (!payload) return;
3784
3795
  get().loadFromPayload(payload, slot);
3785
3796
  }).catch((err) => {
@@ -3787,7 +3798,8 @@ var useStoryStore = create()(immer((set, get) => ({
3787
3798
  set((state) => {
3788
3799
  state.loadError = err instanceof Error ? err.message : "Failed to load";
3789
3800
  });
3790
- });
3801
+ throw err;
3802
+ }));
3791
3803
  },
3792
3804
  hasSave: (slot) => {
3793
3805
  const { storyData, knownSaves } = get();
@@ -3806,8 +3818,8 @@ var useStoryStore = create()(immer((set, get) => ({
3806
3818
  },
3807
3819
  deleteSave: (slot) => {
3808
3820
  const { storyData } = get();
3809
- if (!storyData) return;
3810
- deleteSlotSave(storyData.ifid, slot).then(() => {
3821
+ if (!storyData) return Promise.resolve();
3822
+ return handled(deleteSlotSave(storyData.ifid, slot).then(() => {
3811
3823
  set((state) => {
3812
3824
  const key = slot ?? "";
3813
3825
  const { [key]: _, ...rest } = state.knownSaves;
@@ -3815,7 +3827,8 @@ var useStoryStore = create()(immer((set, get) => ({
3815
3827
  });
3816
3828
  }).catch((err) => {
3817
3829
  console.error("spindle: failed to delete save", err);
3818
- });
3830
+ throw err;
3831
+ }));
3819
3832
  },
3820
3833
  exportSave: async (slot) => {
3821
3834
  const { storyData } = get();
@@ -4739,10 +4752,10 @@ function createStoryAPI() {
4739
4752
  useStoryStore.getState().restart();
4740
4753
  },
4741
4754
  save(slot, custom) {
4742
- useStoryStore.getState().save(slot, custom);
4755
+ return useStoryStore.getState().save(slot, custom);
4743
4756
  },
4744
4757
  load(slot) {
4745
- useStoryStore.getState().load(slot);
4758
+ return useStoryStore.getState().load(slot);
4746
4759
  },
4747
4760
  hasSave(slot) {
4748
4761
  return useStoryStore.getState().hasSave(slot);
@@ -4754,7 +4767,7 @@ function createStoryAPI() {
4754
4767
  return useStoryStore.getState().listSaves();
4755
4768
  },
4756
4769
  deleteSave(slot) {
4757
- useStoryStore.getState().deleteSave(slot);
4770
+ return useStoryStore.getState().deleteSave(slot);
4758
4771
  },
4759
4772
  exportSave(slot) {
4760
4773
  return useStoryStore.getState().exportSave(slot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohal12/spindle",
3
- "version": "0.50.1",
3
+ "version": "0.51.0",
4
4
  "type": "module",
5
5
  "description": "A Preact-based story format for Twine 2.",
6
6
  "license": "Unlicense",
package/src/store.ts CHANGED
@@ -370,12 +370,12 @@ export interface StoryState {
370
370
  updateVariables: (recipe: (draft: VariableNamespaces) => void) => void;
371
371
  trackRender: (passageName: string) => void;
372
372
  restart: () => void;
373
- save: (slot?: string, custom?: Record<string, unknown>) => void;
374
- load: (slot?: string) => void;
373
+ save: (slot?: string, custom?: Record<string, unknown>) => Promise<void>;
374
+ load: (slot?: string) => Promise<void>;
375
375
  hasSave: (slot?: string) => boolean;
376
376
  getSaveInfo: (slot?: string) => Promise<SaveInfo | null>;
377
377
  listSaves: () => Promise<SaveInfo[]>;
378
- deleteSave: (slot?: string) => void;
378
+ deleteSave: (slot?: string) => Promise<void>;
379
379
  exportSave: (slot?: string) => Promise<SaveExport | null>;
380
380
  importSave: (data: unknown, slot?: string) => Promise<SaveInfo>;
381
381
  clearGameData: () => void;
@@ -395,6 +395,17 @@ export interface StoryState {
395
395
  clearDeferredRender: () => void;
396
396
  }
397
397
 
398
+ /**
399
+ * Return `p` marked as handled: a caller that ignores the result of a
400
+ * fire-and-forget operation (whose failure is already logged) gets no
401
+ * unhandled-rejection report, while a caller that awaits it still sees the
402
+ * error.
403
+ */
404
+ function handled<T>(p: Promise<T>): Promise<T> {
405
+ p.catch(() => {});
406
+ return p;
407
+ }
408
+
398
409
  export const useStoryStore = create<StoryState>()(
399
410
  immer((set, get) => ({
400
411
  storyData: null,
@@ -781,52 +792,64 @@ export const useStoryStore = create<StoryState>()(
781
792
 
782
793
  save: (slot?: string, custom?: Record<string, unknown>) => {
783
794
  const { storyData, playthroughId } = get();
784
- if (!storyData) return;
785
-
786
- saveWithHooks(
787
- slot,
788
- custom,
789
- () => get().getSavePayload(),
790
- async (payload) => {
791
- set((state) => {
792
- state.saveError = null;
793
- });
794
- await quickSave(storyData.ifid, playthroughId, payload, slot, custom);
795
+ if (!storyData) return Promise.resolve();
796
+
797
+ return handled(
798
+ saveWithHooks(
799
+ slot,
800
+ custom,
801
+ () => get().getSavePayload(),
802
+ async (payload) => {
803
+ set((state) => {
804
+ state.saveError = null;
805
+ });
806
+ await quickSave(
807
+ storyData.ifid,
808
+ playthroughId,
809
+ payload,
810
+ slot,
811
+ custom,
812
+ );
813
+ set((state) => {
814
+ state.knownSaves = {
815
+ ...state.knownSaves,
816
+ [slot ?? '']: true,
817
+ };
818
+ });
819
+ },
820
+ ).catch((err) => {
821
+ console.error('spindle: failed to save', err);
795
822
  set((state) => {
796
- state.knownSaves = {
797
- ...state.knownSaves,
798
- [slot ?? '']: true,
799
- };
823
+ state.saveError =
824
+ err instanceof Error ? err.message : 'Failed to save';
800
825
  });
801
- },
802
- ).catch((err) => {
803
- console.error('spindle: failed to save', err);
804
- set((state) => {
805
- state.saveError =
806
- err instanceof Error ? err.message : 'Failed to save';
807
- });
808
- });
826
+ throw err;
827
+ }),
828
+ );
809
829
  },
810
830
 
811
831
  load: (slot?: string) => {
812
832
  const { storyData } = get();
813
- if (!storyData) return;
833
+ if (!storyData) return Promise.resolve();
814
834
 
815
835
  set((state) => {
816
836
  state.loadError = null;
817
837
  });
818
- loadQuickSave(storyData.ifid, slot)
819
- .then((payload) => {
820
- if (!payload) return;
821
- get().loadFromPayload(payload, slot);
822
- })
823
- .catch((err) => {
824
- console.error('spindle: failed to load save', err);
825
- set((state) => {
826
- state.loadError =
827
- err instanceof Error ? err.message : 'Failed to load';
828
- });
829
- });
838
+ return handled(
839
+ loadQuickSave(storyData.ifid, slot)
840
+ .then((payload) => {
841
+ if (!payload) return;
842
+ get().loadFromPayload(payload, slot);
843
+ })
844
+ .catch((err) => {
845
+ console.error('spindle: failed to load save', err);
846
+ set((state) => {
847
+ state.loadError =
848
+ err instanceof Error ? err.message : 'Failed to load';
849
+ });
850
+ throw err;
851
+ }),
852
+ );
830
853
  },
831
854
 
832
855
  hasSave: (slot?: string) => {
@@ -849,19 +872,22 @@ export const useStoryStore = create<StoryState>()(
849
872
 
850
873
  deleteSave: (slot?: string) => {
851
874
  const { storyData } = get();
852
- if (!storyData) return;
875
+ if (!storyData) return Promise.resolve();
853
876
 
854
- deleteSlotSave(storyData.ifid, slot)
855
- .then(() => {
856
- set((state) => {
857
- const key = slot ?? '';
858
- const { [key]: _, ...rest } = state.knownSaves;
859
- state.knownSaves = rest as Record<string, true>;
860
- });
861
- })
862
- .catch((err) => {
863
- console.error('spindle: failed to delete save', err);
864
- });
877
+ return handled(
878
+ deleteSlotSave(storyData.ifid, slot)
879
+ .then(() => {
880
+ set((state) => {
881
+ const key = slot ?? '';
882
+ const { [key]: _, ...rest } = state.knownSaves;
883
+ state.knownSaves = rest as Record<string, true>;
884
+ });
885
+ })
886
+ .catch((err) => {
887
+ console.error('spindle: failed to delete save', err);
888
+ throw err;
889
+ }),
890
+ );
865
891
  },
866
892
 
867
893
  exportSave: async (slot?: string): Promise<SaveExport | null> => {
package/src/story-api.ts CHANGED
@@ -155,12 +155,12 @@ export interface StoryAPI {
155
155
  back(): void;
156
156
  forward(): void;
157
157
  restart(): void;
158
- save(slot?: string, custom?: Record<string, unknown>): void;
159
- load(slot?: string): void;
158
+ save(slot?: string, custom?: Record<string, unknown>): Promise<void>;
159
+ load(slot?: string): Promise<void>;
160
160
  hasSave(slot?: string): boolean;
161
161
  getSaveInfo(slot?: string): Promise<SaveInfo | null>;
162
162
  listSaves(): Promise<SaveInfo[]>;
163
- deleteSave(slot?: string): void;
163
+ deleteSave(slot?: string): Promise<void>;
164
164
  exportSave(slot?: string): Promise<SaveExport | null>;
165
165
  importSave(data: unknown, slot?: string): Promise<SaveInfo>;
166
166
  visited(name?: string): number;
@@ -343,12 +343,12 @@ function createStoryAPI(): StoryAPI {
343
343
  useStoryStore.getState().restart();
344
344
  },
345
345
 
346
- save(slot?: string, custom?: Record<string, unknown>): void {
347
- useStoryStore.getState().save(slot, custom);
346
+ save(slot?: string, custom?: Record<string, unknown>): Promise<void> {
347
+ return useStoryStore.getState().save(slot, custom);
348
348
  },
349
349
 
350
- load(slot?: string): void {
351
- useStoryStore.getState().load(slot);
350
+ load(slot?: string): Promise<void> {
351
+ return useStoryStore.getState().load(slot);
352
352
  },
353
353
 
354
354
  hasSave(slot?: string): boolean {
@@ -363,8 +363,8 @@ function createStoryAPI(): StoryAPI {
363
363
  return useStoryStore.getState().listSaves();
364
364
  },
365
365
 
366
- deleteSave(slot?: string): void {
367
- useStoryStore.getState().deleteSave(slot);
366
+ deleteSave(slot?: string): Promise<void> {
367
+ return useStoryStore.getState().deleteSave(slot);
368
368
  },
369
369
 
370
370
  exportSave(slot?: string): Promise<SaveExport | null> {
package/types/index.d.ts CHANGED
@@ -541,11 +541,18 @@ export interface StoryAPI {
541
541
  /** Restart the story from the beginning. */
542
542
  restart(): void;
543
543
 
544
- /** Save the current state. Pass `slot` for a named save, `custom` for metadata. */
545
- save(slot?: string, custom?: Record<string, unknown>): void;
544
+ /**
545
+ * Save the current state. Pass `slot` for a named save, `custom` for metadata.
546
+ * Resolves once the save is persisted (after `aftersave` handlers ran and
547
+ * `hasSave(slot)` is true); rejects if persisting fails.
548
+ */
549
+ save(slot?: string, custom?: Record<string, unknown>): Promise<void>;
546
550
 
547
- /** Load a saved state (quick load). */
548
- load(slot?: string): void;
551
+ /**
552
+ * Load a saved state (quick load). Resolves once the loaded state is applied
553
+ * (immediately if the slot is empty); rejects if loading fails.
554
+ */
555
+ load(slot?: string): Promise<void>;
549
556
 
550
557
  /** Check whether a save exists. */
551
558
  hasSave(slot?: string): boolean;
@@ -556,8 +563,11 @@ export interface StoryAPI {
556
563
  /** List metadata for all known save slots. */
557
564
  listSaves(): Promise<SaveInfo[]>;
558
565
 
559
- /** Delete a save by slot name. */
560
- deleteSave(slot?: string): void;
566
+ /**
567
+ * Delete a save by slot name. Resolves once the save is removed and
568
+ * `hasSave(slot)` is false; rejects if deleting fails.
569
+ */
570
+ deleteSave(slot?: string): Promise<void>;
561
571
 
562
572
  /**
563
573
  * Export the save in a slot as a portable object (plain JSON).