@rohal12/spindle 0.51.3 → 0.52.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/pkg/format.js +1 -1
- package/dist/pkg/headless.js +4833 -1603
- package/dist/pkg/macro-registry.json +7 -7
- package/dist/pkg/story-variables.js +1658 -189
- package/package.json +5 -2
- package/src/automation/runner.ts +2 -1
- package/src/class-registry.ts +277 -90
- package/src/components/Passage.tsx +2 -2
- package/src/components/PassageDialog.tsx +2 -5
- package/src/components/StoryInterface.tsx +2 -4
- package/src/components/macros/Button.tsx +9 -32
- package/src/components/macros/Checkbox.tsx +10 -4
- package/src/components/macros/Computed.tsx +19 -13
- package/src/components/macros/Dialog.tsx +4 -1
- package/src/components/macros/For.tsx +33 -4
- package/src/components/macros/If.tsx +8 -0
- package/src/components/macros/Include.tsx +15 -24
- package/src/components/macros/MacroError.tsx +2 -1
- package/src/components/macros/MacroLink.tsx +14 -46
- package/src/components/macros/Meter.tsx +26 -40
- package/src/components/macros/Nobr.tsx +1 -0
- package/src/components/macros/PassageDisplay.tsx +3 -0
- package/src/components/macros/Print.tsx +4 -0
- package/src/components/macros/Radiobutton.tsx +27 -2
- package/src/components/macros/SaveManager.tsx +39 -14
- package/src/components/macros/Span.tsx +1 -0
- package/src/components/macros/StoryTitle.tsx +1 -0
- package/src/components/macros/Switch.tsx +13 -0
- package/src/components/macros/Unset.tsx +30 -10
- package/src/components/macros/VarDisplay.tsx +21 -4
- package/src/components/macros/Watch.tsx +88 -40
- package/src/components/macros/Widget.tsx +20 -1
- package/src/components/macros/WidgetInvocation.tsx +20 -158
- package/src/components/macros/arg-utils.ts +226 -0
- package/src/components/macros/detached-body.tsx +68 -0
- package/src/components/macros/option-utils.ts +10 -5
- package/src/define-macro.ts +44 -38
- package/src/execute-mutation.ts +499 -28
- package/src/expression.ts +88 -272
- package/src/hooks/use-action.ts +18 -3
- package/src/hooks/use-interpolate.ts +36 -5
- package/src/index.tsx +10 -1
- package/src/interpolation.ts +394 -96
- package/src/js-lexer.ts +1460 -0
- package/src/markup/code-attributes.ts +64 -0
- package/src/markup/markdown.ts +188 -9
- package/src/markup/render.tsx +552 -113
- package/src/markup/tokenizer.ts +601 -119
- package/src/prng.ts +8 -8
- package/src/registry.ts +35 -0
- package/src/saves/save-manager.ts +368 -153
- package/src/saves/storage.ts +24 -7
- package/src/saves/types.ts +20 -6
- package/src/store.ts +549 -137
- package/src/story-api.ts +46 -81
- package/src/story-init.ts +1 -1
- package/src/story-variables.ts +98 -102
- package/src/triggers.ts +6 -2
- package/src/utils/error-message.ts +12 -0
- package/src/utils/live-locals.ts +10 -3
- package/src/utils/namespace.ts +71 -0
- package/src/utils/object-path.ts +194 -0
- package/src/utils/stable-key.ts +82 -0
- package/src/widgets/widget-registry.ts +9 -0
- package/types/index.d.ts +43 -7
- package/types/tooling.d.ts +1 -0
package/src/saves/storage.ts
CHANGED
|
@@ -4,7 +4,13 @@ import type { SaveRecord, PlaythroughRecord, StorageBackend } from './types';
|
|
|
4
4
|
// Memory backend
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* In-memory storage. Like the other backends it stores copies: records and
|
|
9
|
+
* values go in and come out cloned, so changing a record after storing it,
|
|
10
|
+
* or one that was read, changes nothing stored.
|
|
11
|
+
*/
|
|
7
12
|
export function createMemoryBackend(): StorageBackend {
|
|
13
|
+
const copy = <T>(value: T): T => structuredClone(value);
|
|
8
14
|
const saves = new Map<string, SaveRecord>();
|
|
9
15
|
const playthroughs = new Map<string, PlaythroughRecord>();
|
|
10
16
|
const meta = new Map<string, unknown>();
|
|
@@ -13,11 +19,12 @@ export function createMemoryBackend(): StorageBackend {
|
|
|
13
19
|
type: 'memory',
|
|
14
20
|
|
|
15
21
|
async putSave(record: SaveRecord): Promise<void> {
|
|
16
|
-
saves.set(record.meta.id, record);
|
|
22
|
+
saves.set(record.meta.id, copy(record));
|
|
17
23
|
},
|
|
18
24
|
|
|
19
25
|
async getSave(id: string): Promise<SaveRecord | undefined> {
|
|
20
|
-
|
|
26
|
+
const record = saves.get(id);
|
|
27
|
+
return record && copy(record);
|
|
21
28
|
},
|
|
22
29
|
|
|
23
30
|
async deleteSave(id: string): Promise<void> {
|
|
@@ -25,7 +32,7 @@ export function createMemoryBackend(): StorageBackend {
|
|
|
25
32
|
},
|
|
26
33
|
|
|
27
34
|
async getSavesByIfid(ifid: string): Promise<SaveRecord[]> {
|
|
28
|
-
return [...saves.values()].filter((s) => s.meta.ifid === ifid);
|
|
35
|
+
return [...saves.values()].filter((s) => s.meta.ifid === ifid).map(copy);
|
|
29
36
|
},
|
|
30
37
|
|
|
31
38
|
async deleteSavesByIfid(ifid: string): Promise<void> {
|
|
@@ -48,11 +55,13 @@ export function createMemoryBackend(): StorageBackend {
|
|
|
48
55
|
},
|
|
49
56
|
|
|
50
57
|
async putPlaythrough(record: PlaythroughRecord): Promise<void> {
|
|
51
|
-
playthroughs.set(record.id, record);
|
|
58
|
+
playthroughs.set(record.id, copy(record));
|
|
52
59
|
},
|
|
53
60
|
|
|
54
61
|
async getPlaythroughsByIfid(ifid: string): Promise<PlaythroughRecord[]> {
|
|
55
|
-
return [...playthroughs.values()]
|
|
62
|
+
return [...playthroughs.values()]
|
|
63
|
+
.filter((p) => p.ifid === ifid)
|
|
64
|
+
.map(copy);
|
|
56
65
|
},
|
|
57
66
|
|
|
58
67
|
async deletePlaythroughsByIfid(ifid: string): Promise<void> {
|
|
@@ -67,11 +76,11 @@ export function createMemoryBackend(): StorageBackend {
|
|
|
67
76
|
},
|
|
68
77
|
|
|
69
78
|
async getMeta<T = unknown>(key: string): Promise<T | undefined> {
|
|
70
|
-
return meta.get(key) as T | undefined;
|
|
79
|
+
return copy(meta.get(key)) as T | undefined;
|
|
71
80
|
},
|
|
72
81
|
|
|
73
82
|
async setMeta(key: string, value: unknown): Promise<void> {
|
|
74
|
-
meta.set(key, value);
|
|
83
|
+
meta.set(key, copy(value));
|
|
75
84
|
},
|
|
76
85
|
|
|
77
86
|
async deleteMeta(key: string): Promise<void> {
|
|
@@ -154,6 +163,14 @@ export function createLocalStorageBackend(): StorageBackend {
|
|
|
154
163
|
|
|
155
164
|
async putSave(record: SaveRecord): Promise<void> {
|
|
156
165
|
const id = record.meta.id;
|
|
166
|
+
// A save rewritten under another playthrough (an overwrite after a
|
|
167
|
+
// restart) leaves its old playthrough's index
|
|
168
|
+
const previous = lsGet<SaveRecord>(`${LS_SAVE_PREFIX}${id}`);
|
|
169
|
+
if (previous && previous.meta.playthroughId !== record.meta.playthroughId)
|
|
170
|
+
lsIndexRemove(
|
|
171
|
+
`${LS_IDX_SAVES_PT_PREFIX}${previous.meta.playthroughId}`,
|
|
172
|
+
id,
|
|
173
|
+
);
|
|
157
174
|
lsSet(`${LS_SAVE_PREFIX}${id}`, record);
|
|
158
175
|
lsIndexAdd(`${LS_IDX_SAVES_PREFIX}${record.meta.ifid}`, id);
|
|
159
176
|
lsIndexAdd(`${LS_IDX_SAVES_PT_PREFIX}${record.meta.playthroughId}`, id);
|
package/src/saves/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PRNGSnapshot } from '../prng';
|
|
2
|
+
import { isDeserializable } from '../class-registry';
|
|
2
3
|
|
|
3
4
|
/** History moment as persisted in saves (full variable snapshots). */
|
|
4
5
|
export interface SaveHistoryMoment {
|
|
@@ -25,6 +26,11 @@ export interface SaveMeta {
|
|
|
25
26
|
createdAt: string;
|
|
26
27
|
updatedAt: string;
|
|
27
28
|
title: string;
|
|
29
|
+
/**
|
|
30
|
+
* True once the player has named the save (renameSave). Overwriting the
|
|
31
|
+
* save keeps such a title; a generated one is generated anew.
|
|
32
|
+
*/
|
|
33
|
+
userTitle?: boolean;
|
|
28
34
|
passage: string;
|
|
29
35
|
custom: Record<string, unknown>;
|
|
30
36
|
estimatedBytes?: number;
|
|
@@ -97,11 +103,16 @@ function isOptionalPRNGSnapshot(value: unknown): boolean {
|
|
|
97
103
|
);
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
/** A variable container whose values deserialize() can restore. */
|
|
107
|
+
function isSerializedVariables(value: unknown): boolean {
|
|
108
|
+
return isRecord(value) && isDeserializable(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
function isSaveHistoryMoment(value: unknown): value is SaveHistoryMoment {
|
|
101
112
|
return (
|
|
102
113
|
isRecord(value) &&
|
|
103
114
|
typeof value.passage === 'string' &&
|
|
104
|
-
|
|
115
|
+
isSerializedVariables(value.variables) &&
|
|
105
116
|
typeof value.timestamp === 'number' &&
|
|
106
117
|
isOptionalPRNGSnapshot(value.prng)
|
|
107
118
|
);
|
|
@@ -109,18 +120,21 @@ function isSaveHistoryMoment(value: unknown): value is SaveHistoryMoment {
|
|
|
109
120
|
|
|
110
121
|
/**
|
|
111
122
|
* Full check of a payload from outside the running story (an imported save):
|
|
112
|
-
* every history moment is well formed,
|
|
113
|
-
* points at a moment of the payload's passage
|
|
114
|
-
*
|
|
123
|
+
* every history moment is well formed, `historyIndex` is an integer that
|
|
124
|
+
* points at a moment of the payload's passage, and the encoded values (Map,
|
|
125
|
+
* Set, Date, RegExp, class instances) in the live variables and every
|
|
126
|
+
* moment's variables have the shape deserialize() expects. Anything that
|
|
127
|
+
* passes can be stored and later loaded.
|
|
115
128
|
*/
|
|
116
129
|
function isImportablePayload(value: unknown): value is SavePayload {
|
|
117
130
|
if (!isRecord(value)) return false;
|
|
118
131
|
if (typeof value.passage !== 'string') return false;
|
|
119
|
-
if (!
|
|
132
|
+
if (!isSerializedVariables(value.variables)) return false;
|
|
120
133
|
|
|
121
134
|
const { history, historyIndex } = value;
|
|
122
135
|
if (!Array.isArray(history) || history.length === 0) return false;
|
|
123
|
-
|
|
136
|
+
// Array.from: every() skips the holes of a sparse array
|
|
137
|
+
if (!Array.from(history).every(isSaveHistoryMoment)) return false;
|
|
124
138
|
if (
|
|
125
139
|
typeof historyIndex !== 'number' ||
|
|
126
140
|
!Number.isInteger(historyIndex) ||
|