@rohal12/spindle 0.52.7 → 0.52.8

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.
Files changed (46) hide show
  1. package/README.md +26 -0
  2. package/dist/pkg/format.js +1 -1
  3. package/dist/pkg/headless.js +710 -703
  4. package/dist/pkg/macro-registry.json +18 -0
  5. package/package.json +1 -1
  6. package/src/action-registry.ts +9 -0
  7. package/src/class-registry.ts +1 -5
  8. package/src/components/Passage.tsx +35 -39
  9. package/src/components/PassageDialog.tsx +2 -2
  10. package/src/components/StoryInterface.tsx +2 -2
  11. package/src/components/macros/Checkbox.tsx +3 -8
  12. package/src/components/macros/Computed.tsx +1 -1
  13. package/src/components/macros/Cycle.tsx +3 -6
  14. package/src/components/macros/ExprDisplay.tsx +12 -29
  15. package/src/components/macros/If.tsx +35 -41
  16. package/src/components/macros/Include.tsx +2 -1
  17. package/src/components/macros/Listbox.tsx +5 -7
  18. package/src/components/macros/Print.tsx +3 -5
  19. package/src/components/macros/Radiobutton.tsx +3 -5
  20. package/src/components/macros/Repeat.tsx +2 -1
  21. package/src/components/macros/Switch.tsx +6 -13
  22. package/src/components/macros/Timed.tsx +14 -18
  23. package/src/components/macros/Type.tsx +2 -1
  24. package/src/components/macros/VarDisplay.tsx +12 -32
  25. package/src/components/macros/Widget.tsx +49 -20
  26. package/src/components/macros/branches.ts +18 -0
  27. package/src/components/macros/display.ts +19 -0
  28. package/src/components/macros/input-macro.ts +33 -11
  29. package/src/components/macros/macro-args.ts +7 -0
  30. package/src/components/macros/option-utils.ts +10 -7
  31. package/src/define-macro.ts +4 -6
  32. package/src/expression.ts +58 -40
  33. package/src/hooks/use-action.ts +7 -15
  34. package/src/index.tsx +5 -15
  35. package/src/interpolation.ts +34 -41
  36. package/src/markup/ast.ts +4 -3
  37. package/src/markup/render.tsx +4 -12
  38. package/src/registry.ts +13 -12
  39. package/src/saves/save-manager.ts +18 -13
  40. package/src/saves/storage.ts +19 -20
  41. package/src/story-api.ts +30 -73
  42. package/src/structural.ts +1 -18
  43. package/src/types-drift-check.ts +37 -50
  44. package/src/utils/macro-names.ts +40 -0
  45. package/src/utils/namespace.ts +17 -0
  46. package/src/widgets/widget-registry.ts +5 -4
@@ -27,6 +27,7 @@ import { getMacro, getMacroText, isSubMacro } from './registry';
27
27
  import type { MacroTextContext } from './registry';
28
28
  import { getWidget } from './widgets/widget-registry';
29
29
  import { splitArgs } from './components/macros/arg-utils';
30
+ import { display } from './components/macros/display';
30
31
  import { splitSigilTemplate } from './markup/code-attributes';
31
32
  import {
32
33
  checkVariableName,
@@ -118,10 +119,6 @@ export function mapTextNodes(
118
119
  /** Deep enough for any real nesting; stops a widget that includes itself. */
119
120
  const MAX_DEPTH = 100;
120
121
 
121
- function display(value: unknown): string {
122
- return value == null ? '' : String(value);
123
- }
124
-
125
122
  /**
126
123
  * The value of a variable reference (`name` without its sigil, with an
127
124
  * optional dot path), as `{$name}` shows it in passage text (see
@@ -154,6 +151,17 @@ function resolveVariable(
154
151
  return value;
155
152
  }
156
153
 
154
+ /** Evaluate an expression in `scope`. */
155
+ function evaluateIn(expr: string, scope: TextScope): unknown {
156
+ return evaluate(
157
+ expr,
158
+ scope.variables,
159
+ scope.temporary,
160
+ scope.locals,
161
+ scope.transient,
162
+ );
163
+ }
164
+
157
165
  class TextEvaluator {
158
166
  readonly errors: TextError[] = [];
159
167
 
@@ -163,16 +171,6 @@ class TextEvaluator {
163
171
  return text;
164
172
  }
165
173
 
166
- private evaluate(expr: string, scope: TextScope): unknown {
167
- return evaluate(
168
- expr,
169
- scope.variables,
170
- scope.temporary,
171
- scope.locals,
172
- scope.transient,
173
- );
174
- }
175
-
176
174
  private node(node: ASTNode, scope: TextScope, depth: number): string {
177
175
  switch (node.type) {
178
176
  case 'text':
@@ -190,7 +188,7 @@ class TextEvaluator {
190
188
  return this.variable(node.scope, node.name, scope);
191
189
  case 'expression':
192
190
  try {
193
- return display(this.evaluate(node.expression, scope));
191
+ return display(evaluateIn(node.expression, scope));
194
192
  } catch (error) {
195
193
  this.errors.push({ macro: 'expression', error });
196
194
  return '';
@@ -255,7 +253,7 @@ class TextEvaluator {
255
253
  const expr = argExprs[i];
256
254
  if (expr !== undefined) {
257
255
  try {
258
- value = this.evaluate(expr, scope);
256
+ value = evaluateIn(expr, scope);
259
257
  } catch {
260
258
  value = undefined;
261
259
  }
@@ -279,7 +277,7 @@ class TextEvaluator {
279
277
  }
280
278
 
281
279
  const ctx: MacroTextContext = {
282
- evaluate: (expr) => this.evaluate(expr, scope),
280
+ evaluate: (expr) => evaluateIn(expr, scope),
283
281
  renderText: (nodes, locals) =>
284
282
  this.nested(
285
283
  node.name,
@@ -369,15 +367,7 @@ export function interpolateCode(
369
367
  }
370
368
  } else {
371
369
  try {
372
- text += display(
373
- evaluate(
374
- part.expr,
375
- scope.variables,
376
- scope.temporary,
377
- scope.locals,
378
- scope.transient,
379
- ),
380
- );
370
+ text += display(evaluateIn(part.expr, scope));
381
371
  } catch (error) {
382
372
  errors.push({ macro: 'expression', error });
383
373
  }
@@ -386,22 +376,28 @@ export function interpolateCode(
386
376
  return { text, errors };
387
377
  }
388
378
 
379
+ /** The namespaces of a TextScope, passed one by one. */
380
+ type ScopeArgs = [
381
+ variables: Record<string, unknown>,
382
+ temporary: Record<string, unknown>,
383
+ locals: Record<string, unknown>,
384
+ transient?: Record<string, unknown>,
385
+ ];
386
+
387
+ function scopeOf(
388
+ ...[variables, temporary, locals, transient = {}]: ScopeArgs
389
+ ): TextScope {
390
+ return { variables, temporary, locals, transient };
391
+ }
392
+
389
393
  /**
390
394
  * Evaluate text-only markup to a string, throwing the first error met.
391
395
  */
392
396
  export function interpolate(
393
397
  template: string,
394
- variables: Record<string, unknown>,
395
- temporary: Record<string, unknown>,
396
- locals: Record<string, unknown>,
397
- transient: Record<string, unknown> = {},
398
+ ...namespaces: ScopeArgs
398
399
  ): string {
399
- const { text, errors } = interpolateText(template, {
400
- variables,
401
- temporary,
402
- locals,
403
- transient,
404
- });
400
+ const { text, errors } = interpolateText(template, scopeOf(...namespaces));
405
401
  if (errors.length > 0) throw errors[0]!.error;
406
402
  return text;
407
403
  }
@@ -412,10 +408,7 @@ export function interpolate(
412
408
  */
413
409
  export function interpolateExpression(
414
410
  expr: string,
415
- variables: Record<string, unknown>,
416
- temporary: Record<string, unknown>,
417
- locals: Record<string, unknown>,
418
- transient: Record<string, unknown> = {},
411
+ ...namespaces: ScopeArgs
419
412
  ): string {
420
- return display(evaluate(expr, variables, temporary, locals, transient));
413
+ return display(evaluateIn(expr, scopeOf(...namespaces)));
421
414
  }
package/src/markup/ast.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  type Token,
7
7
  type VariableScope,
8
8
  } from './tokenizer';
9
+ import { NameSet } from '../utils/macro-names';
9
10
 
10
11
  export interface TextNode {
11
12
  type: 'text';
@@ -51,7 +52,7 @@ export type ASTNode =
51
52
  | HtmlNode;
52
53
 
53
54
  /** Macros that require a closing tag and can contain children */
54
- const BLOCK_MACROS = new Set([
55
+ const BLOCK_MACROS = new NameSet([
55
56
  'if',
56
57
  'for',
57
58
  'do',
@@ -70,12 +71,12 @@ const BLOCK_MACROS = new Set([
70
71
 
71
72
  /** Register a custom macro as a block macro so the AST builder nests children. */
72
73
  export function registerBlockMacro(name: string): void {
73
- BLOCK_MACROS.add(name.toLowerCase());
74
+ BLOCK_MACROS.add(name);
74
75
  }
75
76
 
76
77
  /** Unregister a custom block macro (for test cleanup). */
77
78
  export function unregisterBlockMacro(name: string): void {
78
- BLOCK_MACROS.delete(name.toLowerCase());
79
+ BLOCK_MACROS.delete(name);
79
80
  }
80
81
 
81
82
  /** Map from branch macro name → required parent macro name */
@@ -588,12 +588,9 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
588
588
 
589
589
  function ChildrenSlot() {
590
590
  const childrenAST = useContext(WidgetChildrenContext);
591
- const nobr = useContext(NobrContext);
592
- const inline = useContext(InlineContext);
593
- const raw = useContext(RawTextContext);
594
- const locals = useContext(LocalsValuesContext);
591
+ const renderOptions = useRenderOptions();
595
592
  if (!childrenAST || childrenAST.length === 0) return null;
596
- return <>{renderNodes(childrenAST, { nobr, locals, inline, raw })}</>;
593
+ return <>{renderNodes(childrenAST, renderOptions)}</>;
597
594
  }
598
595
 
599
596
  /**
@@ -669,10 +666,7 @@ function renderSingleNode(node: ASTNode): preact.ComponentChildren {
669
666
  return (
670
667
  <VarDisplay
671
668
  key={key}
672
- name={node.name}
673
- scope={node.scope}
674
- className={node.className}
675
- id={node.id}
669
+ node={node}
676
670
  />
677
671
  );
678
672
 
@@ -680,9 +674,7 @@ function renderSingleNode(node: ASTNode): preact.ComponentChildren {
680
674
  return (
681
675
  <ExprDisplay
682
676
  key={key}
683
- expression={node.expression}
684
- className={node.className}
685
- id={node.id}
677
+ node={node}
686
678
  />
687
679
  );
688
680
 
package/src/registry.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ComponentType } from 'preact';
2
2
  import type { ASTNode, Branch } from './markup/ast';
3
+ import { NameMap, NameSet } from './utils/macro-names';
3
4
 
4
5
  export interface MacroProps {
5
6
  rawArgs: string;
@@ -9,17 +10,17 @@ export interface MacroProps {
9
10
  branches?: Branch[];
10
11
  }
11
12
 
12
- const registry = new Map<string, ComponentType<MacroProps>>();
13
+ const registry = new NameMap<ComponentType<MacroProps>>();
13
14
 
14
15
  export function registerMacro(
15
16
  name: string,
16
17
  component: ComponentType<MacroProps>,
17
18
  ): void {
18
- registry.set(name.toLowerCase(), component);
19
+ registry.set(name, component);
19
20
  }
20
21
 
21
22
  export function getMacro(name: string): ComponentType<MacroProps> | undefined {
22
- return registry.get(name.toLowerCase());
23
+ return registry.get(name);
23
24
  }
24
25
 
25
26
  /**
@@ -42,29 +43,29 @@ export type MacroTextRenderer = (
42
43
  ctx: MacroTextContext,
43
44
  ) => string;
44
45
 
45
- const textRegistry = new Map<string, MacroTextRenderer>();
46
+ const textRegistry = new NameMap<MacroTextRenderer>();
46
47
 
47
48
  /** Register (or, with undefined, remove) the text form of a macro. */
48
49
  export function registerMacroText(
49
50
  name: string,
50
51
  text: MacroTextRenderer | undefined,
51
52
  ): void {
52
- if (text) textRegistry.set(name.toLowerCase(), text);
53
- else textRegistry.delete(name.toLowerCase());
53
+ if (text) textRegistry.set(name, text);
54
+ else textRegistry.delete(name);
54
55
  }
55
56
 
56
57
  export function getMacroText(name: string): MacroTextRenderer | undefined {
57
- return textRegistry.get(name.toLowerCase());
58
+ return textRegistry.get(name);
58
59
  }
59
60
 
60
- const subMacros = new Set<string>();
61
+ const subMacros = new NameSet();
61
62
 
62
63
  export function registerSubMacro(name: string): void {
63
- subMacros.add(name.toLowerCase());
64
+ subMacros.add(name);
64
65
  }
65
66
 
66
67
  export function isSubMacro(name: string): boolean {
67
- return subMacros.has(name.toLowerCase());
68
+ return subMacros.has(name);
68
69
  }
69
70
 
70
71
  /**
@@ -133,13 +134,13 @@ export interface MacroMetadata {
133
134
  parameters?: ParameterDef[];
134
135
  }
135
136
 
136
- const metadataRegistry = new Map<string, MacroMetadata>();
137
+ const metadataRegistry = new NameMap<MacroMetadata>();
137
138
 
138
139
  export function registerMacroMetadata(
139
140
  name: string,
140
141
  metadata: MacroMetadata,
141
142
  ): void {
142
- metadataRegistry.set(name.toLowerCase(), metadata);
143
+ metadataRegistry.set(name, metadata);
143
144
  }
144
145
 
145
146
  export function getMacroRegistry(): MacroMetadata[] {
@@ -9,7 +9,8 @@ import type {
9
9
  } from './types';
10
10
  import { estimatePayloadBytes, isSaveExport, isSavePayload } from './types';
11
11
  import { getBackend, resetBackend } from './storage';
12
- import { deepClone, serialize, deserialize } from '../class-registry';
12
+ import { serialize, deserialize } from '../class-registry';
13
+ import { deepClone } from '../structural';
13
14
  import { emit } from '../event-emitter';
14
15
  import { withoutDraws } from '../prng';
15
16
 
@@ -101,6 +102,18 @@ function queued<A extends unknown[], R>(
101
102
  return (...args) => inOrder(() => op(...args));
102
103
  }
103
104
 
105
+ /** An operation passing the save `saveId`, if it is stored, to `read`. */
106
+ const readingSave = <R>(read: (record: SaveRecord | undefined) => R) =>
107
+ queued(async (saveId: string) =>
108
+ read(await (await getBackend()).getSave(saveId)),
109
+ );
110
+
111
+ /** An operation passing the save a slot holds, if any, to `read`. */
112
+ const readingSlot = <R>(read: (record: SaveRecord | undefined) => R) =>
113
+ queued(async (ifid: string, slot?: string) =>
114
+ read(await slotRecord(ifid, slot)),
115
+ );
116
+
104
117
  // --- Playthroughs ---
105
118
 
106
119
  /**
@@ -371,9 +384,7 @@ export function deserializePayload(payload: SavePayload): SavePayload {
371
384
  const livePayload = (record: SaveRecord | undefined) =>
372
385
  record && deserializePayload(record.payload);
373
386
 
374
- export const loadSave = queued(async (saveId: string) =>
375
- livePayload(await (await getBackend()).getSave(saveId)),
376
- );
387
+ export const loadSave = readingSave(livePayload);
377
388
 
378
389
  /**
379
390
  * Delete a save record. If the default slot or a named slot holds it, that
@@ -623,9 +634,7 @@ async function hasQuickSaveNow(ifid: string, slot?: string): Promise<boolean> {
623
634
  return (await slotRecord(ifid, slot)) !== undefined;
624
635
  }
625
636
 
626
- export const loadQuickSave = queued(async (ifid: string, slot?: string) =>
627
- livePayload(await slotRecord(ifid, slot)),
628
- );
637
+ export const loadQuickSave = readingSlot(livePayload);
629
638
 
630
639
  /**
631
640
  * Read the save held in a slot (the default slot when `slot` is omitted) for
@@ -753,9 +762,7 @@ function toExport(record: SaveRecord | undefined): SaveExport | undefined {
753
762
  };
754
763
  }
755
764
 
756
- export const exportSave = queued(async (saveId: string) =>
757
- toExport(await (await getBackend()).getSave(saveId)),
758
- );
765
+ export const exportSave = readingSave(toExport);
759
766
 
760
767
  /**
761
768
  * Validate an export against the running story and build the record to store:
@@ -812,9 +819,7 @@ export function importSave(
812
819
  * Export the save held in a slot (default autosave slot when `slot` is omitted).
813
820
  * Returns undefined if the slot is empty.
814
821
  */
815
- export const exportSlotSave = queued(async (ifid: string, slot?: string) =>
816
- toExport(await slotRecord(ifid, slot)),
817
- );
822
+ export const exportSlotSave = readingSlot(toExport);
818
823
 
819
824
  /**
820
825
  * Import an exported save into a slot (default autosave slot when `slot` is
@@ -256,6 +256,19 @@ export function createLocalStorageBackend(): StorageBackend {
256
256
  const IDB_DB_NAME = 'spindle';
257
257
  const IDB_DB_VERSION = 1;
258
258
 
259
+ /** Each object store's key path, and the key path of each of its indexes. */
260
+ const IDB_STORES: Record<
261
+ TableName,
262
+ { keyPath: string; indexes: Record<string, string> }
263
+ > = {
264
+ saves: {
265
+ keyPath: 'meta.id',
266
+ indexes: { ifid: 'meta.ifid', playthroughId: 'meta.playthroughId' },
267
+ },
268
+ playthroughs: { keyPath: 'id', indexes: { ifid: 'ifid' } },
269
+ meta: { keyPath: 'key', indexes: {} },
270
+ };
271
+
259
272
  function createIDBBackend(): StorageBackend {
260
273
  let dbPromise: Promise<IDBDatabase> | null = null;
261
274
 
@@ -267,26 +280,12 @@ function createIDBBackend(): StorageBackend {
267
280
 
268
281
  req.onupgradeneeded = () => {
269
282
  const db = req.result;
270
-
271
- if (!db.objectStoreNames.contains('saves')) {
272
- const savesStore = db.createObjectStore('saves', {
273
- keyPath: 'meta.id',
274
- });
275
- savesStore.createIndex('ifid', 'meta.ifid', { unique: false });
276
- savesStore.createIndex('playthroughId', 'meta.playthroughId', {
277
- unique: false,
278
- });
279
- }
280
-
281
- if (!db.objectStoreNames.contains('playthroughs')) {
282
- const ptStore = db.createObjectStore('playthroughs', {
283
- keyPath: 'id',
284
- });
285
- ptStore.createIndex('ifid', 'ifid', { unique: false });
286
- }
287
-
288
- if (!db.objectStoreNames.contains('meta')) {
289
- db.createObjectStore('meta', { keyPath: 'key' });
283
+ for (const [name, { keyPath, indexes }] of Object.entries(IDB_STORES)) {
284
+ if (db.objectStoreNames.contains(name)) continue;
285
+ const store = db.createObjectStore(name, { keyPath });
286
+ for (const [index, path] of Object.entries(indexes)) {
287
+ store.createIndex(index, path, { unique: false });
288
+ }
290
289
  }
291
290
  };
292
291
 
package/src/story-api.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useStoryStore, trackRuntimeUnsub } from './store';
2
- import type { VariableNamespaces } from './store';
2
+ import type { StoryState, VariableNamespaces } from './store';
3
3
  import {
4
4
  on as emitterOn,
5
5
  emit,
@@ -24,7 +24,7 @@ import { registerClass } from './class-registry';
24
24
  import { frozenCopy, getActiveMutationScope } from './execute-mutation';
25
25
  import { getByPath, setByPath } from './utils/object-path';
26
26
  import { changedNames, checkVariableName, ownValue } from './utils/namespace';
27
- import { countQueries, currentPassage, previousPassage } from './expression';
27
+ import { historyQueries } from './expression';
28
28
  import { defineMacro } from './define-macro';
29
29
  import type { MacroDefinition } from './define-macro';
30
30
  import { getMacroRegistry as _getMacroRegistry } from './registry';
@@ -259,9 +259,15 @@ function setOne(draft: VariableNamespaces, name: string, value: unknown): void {
259
259
  }
260
260
  }
261
261
 
262
+ /** A method calling the store action `name`, as the store holds it then. */
263
+ function storeAction<K extends keyof StoryState>(name: K): StoryState[K] {
264
+ return ((...args: unknown[]) =>
265
+ (useStoryStore.getState()[name] as (...args: unknown[]) => unknown)(
266
+ ...args,
267
+ )) as StoryState[K];
268
+ }
269
+
262
270
  function createStoryAPI(): StoryAPI {
263
- const visits = countQueries(() => useStoryStore.getState().visitCounts);
264
- const renders = countQueries(() => useStoryStore.getState().renderCounts);
265
271
  return {
266
272
  get(name: string): unknown {
267
273
  const { isTransient, key } = parseName(name);
@@ -300,64 +306,23 @@ function createStoryAPI(): StoryAPI {
300
306
  // Called from running mutation code, these store actions commit the
301
307
  // code's writes so far before they record, replace or save state, and
302
308
  // the code goes on from the state they leave (see storyStateGuard).
303
- goto(passageName: string): void {
304
- useStoryStore.getState().navigate(passageName);
305
- },
306
-
307
- back(): void {
308
- useStoryStore.getState().goBack();
309
- },
310
-
311
- forward(): void {
312
- useStoryStore.getState().goForward();
313
- },
314
-
315
- restart(): void {
316
- useStoryStore.getState().restart();
317
- },
318
-
319
- save(slot?: string, custom?: Record<string, unknown>): Promise<void> {
320
- return useStoryStore.getState().save(slot, custom);
321
- },
322
-
323
- load(slot?: string): Promise<void> {
324
- return useStoryStore.getState().load(slot);
325
- },
326
-
327
- hasSave(slot?: string): boolean {
328
- return useStoryStore.getState().hasSave(slot);
329
- },
330
-
331
- getSaveInfo(slot?: string): Promise<SaveInfo | null> {
332
- return useStoryStore.getState().getSaveInfo(slot);
333
- },
334
-
335
- listSaves(): Promise<SaveInfo[]> {
336
- return useStoryStore.getState().listSaves();
337
- },
338
-
339
- deleteSave(slot?: string): Promise<void> {
340
- return useStoryStore.getState().deleteSave(slot);
341
- },
342
-
343
- exportSave(slot?: string): Promise<SaveExport | null> {
344
- return useStoryStore.getState().exportSave(slot);
345
- },
346
-
347
- importSave(data: unknown, slot?: string): Promise<SaveInfo> {
348
- return useStoryStore.getState().importSave(data, slot);
349
- },
350
-
351
- visited: visits.count,
352
- hasVisited: visits.has,
353
- hasVisitedAny: visits.any,
354
- hasVisitedAll: visits.all,
355
- rendered: renders.count,
356
- hasRendered: renders.has,
357
- hasRenderedAny: renders.any,
358
- hasRenderedAll: renders.all,
359
- currentPassage,
360
- previousPassage,
309
+ goto: storeAction('navigate'),
310
+ back: storeAction('goBack'),
311
+ forward: storeAction('goForward'),
312
+ restart: storeAction('restart'),
313
+ save: storeAction('save'),
314
+ load: storeAction('load'),
315
+ hasSave: storeAction('hasSave'),
316
+ getSaveInfo: storeAction('getSaveInfo'),
317
+ listSaves: storeAction('listSaves'),
318
+ deleteSave: storeAction('deleteSave'),
319
+ exportSave: storeAction('exportSave'),
320
+ importSave: storeAction('importSave'),
321
+
322
+ ...historyQueries(
323
+ () => useStoryStore.getState().visitCounts,
324
+ () => useStoryStore.getState().renderCounts,
325
+ ),
361
326
 
362
327
  get title(): string {
363
328
  return useStoryStore.getState().storyData?.name || '';
@@ -416,17 +381,9 @@ function createStoryAPI(): StoryAPI {
416
381
  return { usage: 0, quota: 0, estimateSupported: false };
417
382
  },
418
383
 
419
- clearGameData(): Promise<void> {
420
- return useStoryStore.getState().clearGameData();
421
- },
422
-
423
- clearAllData(): Promise<void> {
424
- return useStoryStore.getState().clearAllData();
425
- },
426
-
427
- deletePlaythrough(playthroughId: string): Promise<void> {
428
- return useStoryStore.getState().deletePlaythrough(playthroughId);
429
- },
384
+ clearGameData: storeAction('clearGameData'),
385
+ clearAllData: storeAction('clearAllData'),
386
+ deletePlaythrough: storeAction('deletePlaythrough'),
430
387
 
431
388
  get backend() {
432
389
  return getBackendType();
package/src/structural.ts CHANGED
@@ -4,26 +4,9 @@
4
4
  // what a change is.
5
5
 
6
6
  import { registeredClassName } from './class-registry';
7
- import { hasOwn } from './utils/namespace';
7
+ import { hasOwn, setOwn } from './utils/namespace';
8
8
  import { deleteByPath, getByPath, setByPath } from './utils/object-path';
9
9
 
10
- /**
11
- * Set an own enumerable property. A "__proto__" key is defined as an own
12
- * property (as JSON.parse does) instead of replacing the prototype.
13
- */
14
- export function setOwn(target: object, key: string, value: unknown): void {
15
- if (key === '__proto__') {
16
- Object.defineProperty(target, key, {
17
- value,
18
- enumerable: true,
19
- writable: true,
20
- configurable: true,
21
- });
22
- } else {
23
- (target as Record<string, unknown>)[key] = value;
24
- }
25
- }
26
-
27
10
  // --- Deep Clone ---
28
11
 
29
12
  function isPlainObject(value: unknown): value is Record<string, unknown> {