@rohal12/spindle 0.50.1 → 0.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/story-init.ts CHANGED
@@ -4,6 +4,8 @@ import { tokenize } from './markup/tokenizer';
4
4
  import { buildAST } from './markup/ast';
5
5
  import { renderNodes } from './markup/render';
6
6
  import { setSaveTitlePassage } from './saves/save-manager';
7
+ import { emit } from './event-emitter';
8
+ import type { SavePayload } from './saves/types';
7
9
 
8
10
  /** Hidden container holding the currently mounted StoryInit tree. */
9
11
  let storyInitContainer: HTMLElement | null = null;
@@ -60,3 +62,24 @@ export function executeStoryInit() {
60
62
  setSaveTitlePassage(saveTitlePassage.content);
61
63
  }
62
64
  }
65
+
66
+ /**
67
+ * Initialize a freshly booted story: run StoryInit, restore `session` (the
68
+ * state a page refresh left behind) if there is one, then fire `storyinit`
69
+ * once all state is settled. Without a session, what the `storyinit`
70
+ * handlers set synchronously belongs to the start moment, like StoryInit's
71
+ * changes; a restored session keeps the history it recorded.
72
+ */
73
+ export function initializeStory(session?: SavePayload): void {
74
+ executeStoryInit();
75
+
76
+ if (session) {
77
+ useStoryStore.getState().loadFromPayload(session);
78
+ }
79
+
80
+ emit('storyinit');
81
+
82
+ if (!session) {
83
+ recordStoryInitState();
84
+ }
85
+ }
@@ -26,6 +26,13 @@ const FOR_LOCAL_RE = /\{for\s+@(\w+)(?:\s*,\s*@(\w+))?\s+of\b/g;
26
26
 
27
27
  const VALID_VAR_TYPES = new Set<string>(['number', 'string', 'boolean']);
28
28
 
29
+ /** Boxed sample values whose members a primitive of each type can access. */
30
+ const PRIMITIVE_SAMPLES: Partial<Record<VarType, object>> = {
31
+ string: Object(''),
32
+ number: Object(0),
33
+ boolean: Object(false),
34
+ };
35
+
29
36
  function inferSchema(value: unknown): FieldSchema {
30
37
  if (Array.isArray(value)) {
31
38
  return { type: 'array' };
@@ -138,6 +145,17 @@ function validateRef(
138
145
  // so any field access on an array is allowed.
139
146
  if (current.type === 'array') return null;
140
147
 
148
+ // Primitives expose their wrapper's built-ins (length, toUpperCase,
149
+ // toFixed, etc.). Keep validating past properties of primitive type.
150
+ const sample = PRIMITIVE_SAMPLES[current.type];
151
+ if (sample && part in sample) {
152
+ const member: unknown = sample[part as keyof typeof sample];
153
+ const memberType = typeof member;
154
+ if (!VALID_VAR_TYPES.has(memberType)) return null;
155
+ current = { type: memberType as VarType };
156
+ continue;
157
+ }
158
+
141
159
  if (current.type !== 'object' || !current.fields) {
142
160
  return `Cannot access field "${part}" on $${parts.slice(0, i).join('.')} (type: ${current.type})`;
143
161
  }
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).