@rohal12/spindle 0.59.6 → 0.59.7
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 +61 -21
- package/package.json +1 -1
- package/src/class-registry.ts +12 -0
- package/src/components/App.tsx +3 -5
- package/src/components/macros/Computed.tsx +3 -2
- package/src/components/macros/PassageDisplay.tsx +4 -3
- package/src/components/macros/SaveManager.tsx +3 -5
- package/src/execute-mutation.ts +3 -3
- package/src/index.tsx +22 -7
- package/src/saves/save-manager.ts +9 -2
- package/src/structural.ts +11 -5
package/dist/pkg/headless.js
CHANGED
|
@@ -12575,10 +12575,18 @@ var defineData = (target, key, value) => {
|
|
|
12575
12575
|
});
|
|
12576
12576
|
};
|
|
12577
12577
|
/**
|
|
12578
|
+
* A reducer extracts string keys, so devalue never sees the symbol keys of
|
|
12579
|
+
* the original object: refuse them here, as it does for a plain object.
|
|
12580
|
+
*/
|
|
12581
|
+
function refuseSymbolKeys(value) {
|
|
12582
|
+
if (Object.getOwnPropertySymbols(value).length > 0) throw new TypeError("spindle: Cannot save an object with symbol keys");
|
|
12583
|
+
}
|
|
12584
|
+
/**
|
|
12578
12585
|
* Own enumerable keys of `value`, as a plain object; for an error also its
|
|
12579
12586
|
* message, cause and (AggregateError) errors, which are not enumerable.
|
|
12580
12587
|
*/
|
|
12581
12588
|
function ownData(value) {
|
|
12589
|
+
refuseSymbolKeys(value);
|
|
12582
12590
|
const data = {};
|
|
12583
12591
|
if (value instanceof Error) {
|
|
12584
12592
|
for (const key of ERROR_HIDDEN_KEYS$1) if (hasOwn(value, key)) defineData(data, key, value[key]);
|
|
@@ -12605,6 +12613,7 @@ function reducers() {
|
|
|
12605
12613
|
if (!isObject(v) || Object.getPrototypeOf(v) !== Object.prototype) return false;
|
|
12606
12614
|
const keys = Object.keys(v);
|
|
12607
12615
|
if (!keys.some(isEscapedOneCharKey)) return false;
|
|
12616
|
+
refuseSymbolKeys(v);
|
|
12608
12617
|
if (keys.includes("__proto__")) throw new TypeError("spindle: Cannot save a property named \"__proto__\"");
|
|
12609
12618
|
return keys.flatMap((k) => [k, v[k]]);
|
|
12610
12619
|
};
|
|
@@ -13496,6 +13505,13 @@ function strictPairs(ab = /* @__PURE__ */ new Map(), ba = /* @__PURE__ */ new Ma
|
|
|
13496
13505
|
return pairs;
|
|
13497
13506
|
}
|
|
13498
13507
|
/**
|
|
13508
|
+
* Whether `a` and `b` are equal as deepEqual() has it and also share
|
|
13509
|
+
* references at the same places (a save of one loads as the other).
|
|
13510
|
+
*/
|
|
13511
|
+
function deepEqualStrict(a, b) {
|
|
13512
|
+
return equal(a, b, strictPairs());
|
|
13513
|
+
}
|
|
13514
|
+
/**
|
|
13499
13515
|
* `curr` with each part that equals (see deepEqualStrict) the part of
|
|
13500
13516
|
* `prev` at the same place replaced by `prev`'s: the history moments of a
|
|
13501
13517
|
* save then share what did not change between them, which a save stores
|
|
@@ -13765,13 +13781,14 @@ function underChange(path, keys) {
|
|
|
13765
13781
|
}
|
|
13766
13782
|
/**
|
|
13767
13783
|
* The segment standing for the entry `index` of a Map or Set in a path, or
|
|
13768
|
-
* for the `cause` or `errors` of an Error ('c', 'e')
|
|
13784
|
+
* for the `cause` or `errors` of an Error ('c', 'e'), or for the buffer a
|
|
13785
|
+
* typed array or DataView views ('b').
|
|
13769
13786
|
*/
|
|
13770
13787
|
var entryKey = (kind, index) => `\0${kind}${index}`;
|
|
13771
13788
|
var isEntryKey = (segment) => segment.startsWith("\0");
|
|
13772
13789
|
/**
|
|
13773
|
-
* What a Map, Set or
|
|
13774
|
-
* members, `cause` and `errors
|
|
13790
|
+
* What a Map, Set, Error or view holds in place of properties (keys and
|
|
13791
|
+
* values, members, `cause` and `errors`, the buffer), each with the segment standing for it in
|
|
13775
13792
|
* a path (see entryKey); undefined for any other value.
|
|
13776
13793
|
*/
|
|
13777
13794
|
function entryChildren(value) {
|
|
@@ -13787,7 +13804,8 @@ function entryChildren(value) {
|
|
|
13787
13804
|
for (const member of value) children.push([entryKey("s", index++), member]);
|
|
13788
13805
|
} else if (value instanceof Error) {
|
|
13789
13806
|
for (const key of ["cause", "errors"]) if (hasOwn(value, key)) children.push([entryKey(key === "cause" ? "c" : "e", 0), value[key]]);
|
|
13790
|
-
} else
|
|
13807
|
+
} else if (ArrayBuffer.isView(value)) children.push([entryKey("b", 0), value.buffer]);
|
|
13808
|
+
else return;
|
|
13791
13809
|
return children;
|
|
13792
13810
|
}
|
|
13793
13811
|
/**
|
|
@@ -14245,7 +14263,9 @@ function storedRecord(meta, payload) {
|
|
|
14245
14263
|
};
|
|
14246
14264
|
}
|
|
14247
14265
|
var createSave = queued(createSaveNow);
|
|
14248
|
-
async function createSaveNow(ifid,
|
|
14266
|
+
async function createSaveNow(ifid, playthroughPromise, payload, custom = {}) {
|
|
14267
|
+
const playthroughId = await playthroughPromise;
|
|
14268
|
+
if (!playthroughId) throw new Error("No playthrough");
|
|
14249
14269
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14250
14270
|
const record = storedRecord({
|
|
14251
14271
|
id: randomUUID(),
|
|
@@ -14261,6 +14281,10 @@ async function createSaveNow(ifid, playthroughId, payload, custom = {}) {
|
|
|
14261
14281
|
return record;
|
|
14262
14282
|
}
|
|
14263
14283
|
/**
|
|
14284
|
+
* A playthrough passed as a promise (see resolvePlaythroughId) is awaited
|
|
14285
|
+
* inside the operation, so the call takes its place in the order of
|
|
14286
|
+
* operations when it is made.
|
|
14287
|
+
*
|
|
14264
14288
|
* Replace the payload of an existing save, keeping its ID and `createdAt`.
|
|
14265
14289
|
* Pass the current `playthroughId` when the new payload comes from the
|
|
14266
14290
|
* running game: the save then holds that playthrough's state, so it is
|
|
@@ -14276,7 +14300,8 @@ async function createSaveNow(ifid, playthroughId, payload, custom = {}) {
|
|
|
14276
14300
|
* before, so slot keys and metadata not passed again are kept.
|
|
14277
14301
|
*/
|
|
14278
14302
|
var overwriteSave = queued(overwriteSaveNow);
|
|
14279
|
-
async function overwriteSaveNow(saveId, payload, custom,
|
|
14303
|
+
async function overwriteSaveNow(saveId, payload, custom, playthroughPromise) {
|
|
14304
|
+
const playthroughId = await playthroughPromise;
|
|
14280
14305
|
const backend = await getBackend();
|
|
14281
14306
|
const existing = await backend.getSave(saveId);
|
|
14282
14307
|
if (!existing) return void 0;
|
|
@@ -14983,9 +15008,18 @@ function relink(draft, work, changes) {
|
|
|
14983
15008
|
function cloneNamespaces(from) {
|
|
14984
15009
|
const seen = /* @__PURE__ */ new Map();
|
|
14985
15010
|
return {
|
|
14986
|
-
variables: deepClone(from.variables, {
|
|
14987
|
-
|
|
14988
|
-
|
|
15011
|
+
variables: deepClone(from.variables, {
|
|
15012
|
+
keepUnregistered: true,
|
|
15013
|
+
seen
|
|
15014
|
+
}),
|
|
15015
|
+
temporary: deepClone(from.temporary, {
|
|
15016
|
+
keepUnregistered: true,
|
|
15017
|
+
seen
|
|
15018
|
+
}),
|
|
15019
|
+
transient: deepClone(from.transient, {
|
|
15020
|
+
keepUnregistered: true,
|
|
15021
|
+
seen
|
|
15022
|
+
})
|
|
14989
15023
|
};
|
|
14990
15024
|
}
|
|
14991
15025
|
/** The three variable namespaces of `state`, as an object to walk. */
|
|
@@ -16734,10 +16768,10 @@ function App() {
|
|
|
16734
16768
|
/* @__PURE__ */ jsx(TriggerDialogHost, {}),
|
|
16735
16769
|
/* @__PURE__ */ jsx(RuntimeErrors, {})
|
|
16736
16770
|
] });
|
|
16737
|
-
return
|
|
16738
|
-
value:
|
|
16771
|
+
return /* @__PURE__ */ jsx(NobrContext.Provider, {
|
|
16772
|
+
value: nobr,
|
|
16739
16773
|
children: content
|
|
16740
|
-
})
|
|
16774
|
+
});
|
|
16741
16775
|
}
|
|
16742
16776
|
//#endregion
|
|
16743
16777
|
//#region src/parser.ts
|
|
@@ -18779,7 +18813,7 @@ function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRe
|
|
|
18779
18813
|
logMacroError(`computed ${rawArgs}`, err, location);
|
|
18780
18814
|
return;
|
|
18781
18815
|
}
|
|
18782
|
-
if (!
|
|
18816
|
+
if (!deepEqualStrict(prevRef.current, newValue)) {
|
|
18783
18817
|
prevRef.current = newValue;
|
|
18784
18818
|
if (isLocal) try {
|
|
18785
18819
|
localsUpdate(name, newValue);
|
|
@@ -19715,9 +19749,7 @@ function SaveManagerContent() {
|
|
|
19715
19749
|
const playthrough = resolvePlaythroughId();
|
|
19716
19750
|
try {
|
|
19717
19751
|
await saveWithHooks(void 0, void 0, beginSave, async (payload) => {
|
|
19718
|
-
|
|
19719
|
-
if (!ptId) throw new Error("No playthrough");
|
|
19720
|
-
return createSave(ifid, ptId, payload);
|
|
19752
|
+
return createSave(ifid, playthrough, payload);
|
|
19721
19753
|
});
|
|
19722
19754
|
showStatus("Save created");
|
|
19723
19755
|
await refresh();
|
|
@@ -19729,7 +19761,7 @@ function SaveManagerContent() {
|
|
|
19729
19761
|
const playthrough = resolvePlaythroughId();
|
|
19730
19762
|
try {
|
|
19731
19763
|
await saveWithHooks(void 0, void 0, beginSave, async (payload) => {
|
|
19732
|
-
if (!await overwriteSave(saveId, payload, void 0,
|
|
19764
|
+
if (!await overwriteSave(saveId, payload, void 0, playthrough)) throw new Error("Save not found");
|
|
19733
19765
|
});
|
|
19734
19766
|
showStatus("Save overwritten");
|
|
19735
19767
|
await refresh();
|
|
@@ -20566,13 +20598,13 @@ defineMacro({
|
|
|
20566
20598
|
useEffect(() => {
|
|
20567
20599
|
const store = useStoryStore.getState();
|
|
20568
20600
|
const { history, historyIndex, transitionConfig } = store;
|
|
20569
|
-
const next = store.consumeNextTransition();
|
|
20570
20601
|
const prevLen = prevHistoryLenRef.current;
|
|
20571
20602
|
prevHistoryLenRef.current = history.length;
|
|
20572
20603
|
const isFirstLoad = !displayedPassage;
|
|
20573
20604
|
const isRestart = history.length === 1 && historyIndex === 0 && prevLen > 1;
|
|
20574
20605
|
const isLoad = history.length > 1 && historyIndex === history.length - 1 && prevLen > 1 && Math.abs(history.length - prevLen) > 1;
|
|
20575
20606
|
if (navigationId === displayed.id) return;
|
|
20607
|
+
const next = store.consumeNextTransition();
|
|
20576
20608
|
cancelTransition();
|
|
20577
20609
|
const targetPassage = storyData?.passages.get(currentPassage);
|
|
20578
20610
|
if (!targetPassage) {
|
|
@@ -20753,6 +20785,15 @@ function stopWithErrors(errors) {
|
|
|
20753
20785
|
throw new Error(`spindle: ${errors.length} validation error(s):\n${errors.join("\n")}`);
|
|
20754
20786
|
}
|
|
20755
20787
|
/**
|
|
20788
|
+
* The undeclared variables the passages refer to, with the macros known now:
|
|
20789
|
+
* the input macros that bind a variable (registered by author JS or
|
|
20790
|
+
* StoryInit) and the roles of their parameters decide what is a reference.
|
|
20791
|
+
*/
|
|
20792
|
+
function variableErrors(storyData, schema) {
|
|
20793
|
+
const storeVarMacros = getMacroRegistry().filter((m) => m.storeVar).map((m) => m.name);
|
|
20794
|
+
return validatePassages(storyData.passages, schema, storeVarMacros);
|
|
20795
|
+
}
|
|
20796
|
+
/**
|
|
20756
20797
|
* The markup errors (malformed markup, unknown macros, syntax errors in
|
|
20757
20798
|
* code) of the passages for which `only` holds, with the macros and widgets
|
|
20758
20799
|
* known now.
|
|
@@ -20801,9 +20842,8 @@ function boot() {
|
|
|
20801
20842
|
throw new Error(`spindle: ${msg}`);
|
|
20802
20843
|
}
|
|
20803
20844
|
const schema = parseStoryVariables(storyVarsPassage.content);
|
|
20804
|
-
const storeVarMacros = getMacroRegistry().filter((m) => m.storeVar).map((m) => m.name);
|
|
20805
20845
|
for (const name of blockWidgetNames(storyData.passages.values())) registerBlockMacro(name);
|
|
20806
|
-
const errors =
|
|
20846
|
+
const errors = variableErrors(storyData, schema);
|
|
20807
20847
|
errors.push(...markupErrors(storyData, (p) => p.name === "StoryInit"));
|
|
20808
20848
|
let transientDefaults = {};
|
|
20809
20849
|
const storyTransientsPassage = storyData.passages.get("StoryTransients");
|
|
@@ -20818,7 +20858,7 @@ function boot() {
|
|
|
20818
20858
|
useStoryStore.getState().init(storyData, defaults, transientDefaults);
|
|
20819
20859
|
enterRuntimePhase();
|
|
20820
20860
|
initializeStory(loadSession(storyData.ifid));
|
|
20821
|
-
const markup = markupErrors(storyData, (p) => p.name !== "StoryInit");
|
|
20861
|
+
const markup = [...variableErrors(storyData, schema), ...markupErrors(storyData, (p) => p.name !== "StoryInit")];
|
|
20822
20862
|
if (markup.length > 0) stopWithErrors(markup);
|
|
20823
20863
|
for (const [, passage] of storyData.passages) if (passage.tags.includes("widget")) {
|
|
20824
20864
|
const widgetAST = parseMarkup(passage.content);
|
package/package.json
CHANGED
package/src/class-registry.ts
CHANGED
|
@@ -110,11 +110,22 @@ const defineData = (target: object, key: string, value: unknown): void => {
|
|
|
110
110
|
});
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* A reducer extracts string keys, so devalue never sees the symbol keys of
|
|
115
|
+
* the original object: refuse them here, as it does for a plain object.
|
|
116
|
+
*/
|
|
117
|
+
function refuseSymbolKeys(value: object): void {
|
|
118
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
119
|
+
throw new TypeError('spindle: Cannot save an object with symbol keys');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
/**
|
|
114
124
|
* Own enumerable keys of `value`, as a plain object; for an error also its
|
|
115
125
|
* message, cause and (AggregateError) errors, which are not enumerable.
|
|
116
126
|
*/
|
|
117
127
|
function ownData(value: object): Record<string, unknown> {
|
|
128
|
+
refuseSymbolKeys(value);
|
|
118
129
|
const data: Record<string, unknown> = {};
|
|
119
130
|
if (value instanceof Error) {
|
|
120
131
|
for (const key of ERROR_HIDDEN_KEYS) {
|
|
@@ -163,6 +174,7 @@ function reducers(): Record<string, (value: unknown) => unknown> {
|
|
|
163
174
|
}
|
|
164
175
|
const keys = Object.keys(v);
|
|
165
176
|
if (!keys.some(isEscapedOneCharKey)) return false;
|
|
177
|
+
refuseSymbolKeys(v);
|
|
166
178
|
if (keys.includes('__proto__')) {
|
|
167
179
|
throw new TypeError(
|
|
168
180
|
'spindle: Cannot save a property named "__proto__"',
|
package/src/components/App.tsx
CHANGED
|
@@ -41,9 +41,7 @@ export function App() {
|
|
|
41
41
|
</>
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
content
|
|
48
|
-
);
|
|
44
|
+
// One provider whatever `nobr` is: a changing tree shape would remount the
|
|
45
|
+
// passage and run its mount-only macros again
|
|
46
|
+
return <NobrContext.Provider value={nobr}>{content}</NobrContext.Provider>;
|
|
49
47
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useStoryStore } from '../../store';
|
|
2
2
|
import { evaluate } from '../../expression';
|
|
3
3
|
import { readState } from '../../execute-mutation';
|
|
4
|
-
import {
|
|
4
|
+
import { deepEqualStrict } from '../../structural';
|
|
5
5
|
import { defineMacro } from '../../define-macro';
|
|
6
6
|
import { MacroError, logMacroError } from './MacroError';
|
|
7
7
|
import { currentSourceLocation } from '../../utils/source-location';
|
|
@@ -67,7 +67,8 @@ function computeAndApply(
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
// Strict: a change of only the aliases inside the result is a change
|
|
71
|
+
if (!deepEqualStrict(prevRef.current, newValue)) {
|
|
71
72
|
prevRef.current = newValue;
|
|
72
73
|
if (isLocal) {
|
|
73
74
|
try {
|
|
@@ -131,9 +131,6 @@ defineMacro({
|
|
|
131
131
|
const store = useStoryStore.getState();
|
|
132
132
|
const { history, historyIndex, transitionConfig } = store;
|
|
133
133
|
|
|
134
|
-
// Always consume the next transition (one-shot), regardless of what we do
|
|
135
|
-
const next = store.consumeNextTransition();
|
|
136
|
-
|
|
137
134
|
// Determine if this is a first load, restart, or save-load
|
|
138
135
|
const prevLen = prevHistoryLenRef.current;
|
|
139
136
|
prevHistoryLenRef.current = history.length;
|
|
@@ -152,6 +149,10 @@ defineMacro({
|
|
|
152
149
|
return;
|
|
153
150
|
}
|
|
154
151
|
|
|
152
|
+
// One-shot, used up by the first navigation, whatever we do with it
|
|
153
|
+
// (not by the initial render, which no navigation caused)
|
|
154
|
+
const next = store.consumeNextTransition();
|
|
155
|
+
|
|
155
156
|
// Cancel any in-progress transition
|
|
156
157
|
cancelTransition();
|
|
157
158
|
|
|
@@ -127,9 +127,8 @@ export function SaveManagerContent() {
|
|
|
127
127
|
const playthrough = resolvePlaythroughId();
|
|
128
128
|
try {
|
|
129
129
|
await saveWithHooks(undefined, undefined, beginSave, async (payload) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return createSave(ifid, ptId, payload);
|
|
130
|
+
// Queued now, in call order with other storage operations
|
|
131
|
+
return createSave(ifid, playthrough, payload);
|
|
133
132
|
});
|
|
134
133
|
showStatus('Save created');
|
|
135
134
|
await refresh();
|
|
@@ -143,8 +142,7 @@ export function SaveManagerContent() {
|
|
|
143
142
|
try {
|
|
144
143
|
await saveWithHooks(undefined, undefined, beginSave, async (payload) => {
|
|
145
144
|
// Only the current playthrough's saves offer "Save Here"
|
|
146
|
-
|
|
147
|
-
if (!(await overwriteSave(saveId, payload, undefined, ptId))) {
|
|
145
|
+
if (!(await overwriteSave(saveId, payload, undefined, playthrough))) {
|
|
148
146
|
throw new Error('Save not found');
|
|
149
147
|
}
|
|
150
148
|
});
|
package/src/execute-mutation.ts
CHANGED
|
@@ -381,9 +381,9 @@ function relink(
|
|
|
381
381
|
function cloneNamespaces(from: VariableNamespaces): VariableNamespaces {
|
|
382
382
|
const seen = new Map<object, object>();
|
|
383
383
|
return {
|
|
384
|
-
variables: deepClone(from.variables, { seen }),
|
|
385
|
-
temporary: deepClone(from.temporary, { seen }),
|
|
386
|
-
transient: deepClone(from.transient, { seen }),
|
|
384
|
+
variables: deepClone(from.variables, { keepUnregistered: true, seen }),
|
|
385
|
+
temporary: deepClone(from.temporary, { keepUnregistered: true, seen }),
|
|
386
|
+
transient: deepClone(from.transient, { keepUnregistered: true, seen }),
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
389
|
|
package/src/index.tsx
CHANGED
|
@@ -62,6 +62,21 @@ function stopWithErrors(errors: string[]): never {
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The undeclared variables the passages refer to, with the macros known now:
|
|
67
|
+
* the input macros that bind a variable (registered by author JS or
|
|
68
|
+
* StoryInit) and the roles of their parameters decide what is a reference.
|
|
69
|
+
*/
|
|
70
|
+
function variableErrors(
|
|
71
|
+
storyData: StoryData,
|
|
72
|
+
schema: Parameters<typeof validatePassages>[1],
|
|
73
|
+
): string[] {
|
|
74
|
+
const storeVarMacros = getMacroRegistry()
|
|
75
|
+
.filter((m) => m.storeVar)
|
|
76
|
+
.map((m) => m.name);
|
|
77
|
+
return validatePassages(storyData.passages, schema, storeVarMacros);
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
/**
|
|
66
81
|
* The markup errors (malformed markup, unknown macros, syntax errors in
|
|
67
82
|
* code) of the passages for which `only` holds, with the macros and widgets
|
|
@@ -134,10 +149,6 @@ export function boot() {
|
|
|
134
149
|
}
|
|
135
150
|
|
|
136
151
|
const schema = parseStoryVariables(storyVarsPassage.content);
|
|
137
|
-
// Include input macros registered by author JS, which ran above.
|
|
138
|
-
const storeVarMacros = getMacroRegistry()
|
|
139
|
-
.filter((m) => m.storeVar)
|
|
140
|
-
.map((m) => m.name);
|
|
141
152
|
// Pass 1: Register the block widgets as block macros BEFORE any passage
|
|
142
153
|
// is parsed (validation and StoryInit included), so that passages
|
|
143
154
|
// invoking block widgets and widget bodies using other block widgets
|
|
@@ -146,7 +157,7 @@ export function boot() {
|
|
|
146
157
|
registerBlockMacro(name);
|
|
147
158
|
}
|
|
148
159
|
|
|
149
|
-
const errors =
|
|
160
|
+
const errors = variableErrors(storyData, schema);
|
|
150
161
|
// StoryInit's markup must be valid before it runs; the other passages are
|
|
151
162
|
// validated once it has run, as it may define macros (see below).
|
|
152
163
|
errors.push(...markupErrors(storyData, (p) => p.name === 'StoryInit'));
|
|
@@ -186,8 +197,12 @@ export function boot() {
|
|
|
186
197
|
// storyinit after all state is settled (defaults + StoryInit + session)
|
|
187
198
|
initializeStory(loadSession(storyData.ifid));
|
|
188
199
|
|
|
189
|
-
// Every other passage's markup, now that StoryInit may have defined macros
|
|
190
|
-
|
|
200
|
+
// Every other passage's markup, now that StoryInit may have defined macros;
|
|
201
|
+
// so are the variable references, which macros' parameters decide
|
|
202
|
+
const markup = [
|
|
203
|
+
...variableErrors(storyData, schema),
|
|
204
|
+
...markupErrors(storyData, (p) => p.name !== 'StoryInit'),
|
|
205
|
+
];
|
|
191
206
|
if (markup.length > 0) stopWithErrors(markup);
|
|
192
207
|
|
|
193
208
|
// Pass 2: Full parse and register widgets from passages tagged "widget"
|
|
@@ -291,10 +291,12 @@ export const createSave = queued(createSaveNow);
|
|
|
291
291
|
|
|
292
292
|
async function createSaveNow(
|
|
293
293
|
ifid: string,
|
|
294
|
-
|
|
294
|
+
playthroughPromise: string | PromiseLike<string>,
|
|
295
295
|
payload: SavePayload,
|
|
296
296
|
custom: Record<string, unknown> = {},
|
|
297
297
|
): Promise<SaveRecord> {
|
|
298
|
+
const playthroughId = await playthroughPromise;
|
|
299
|
+
if (!playthroughId) throw new Error('No playthrough');
|
|
298
300
|
const now = new Date().toISOString();
|
|
299
301
|
const meta: SaveMeta = {
|
|
300
302
|
id: randomUUID(),
|
|
@@ -313,6 +315,10 @@ async function createSaveNow(
|
|
|
313
315
|
}
|
|
314
316
|
|
|
315
317
|
/**
|
|
318
|
+
* A playthrough passed as a promise (see resolvePlaythroughId) is awaited
|
|
319
|
+
* inside the operation, so the call takes its place in the order of
|
|
320
|
+
* operations when it is made.
|
|
321
|
+
*
|
|
316
322
|
* Replace the payload of an existing save, keeping its ID and `createdAt`.
|
|
317
323
|
* Pass the current `playthroughId` when the new payload comes from the
|
|
318
324
|
* running game: the save then holds that playthrough's state, so it is
|
|
@@ -333,8 +339,9 @@ async function overwriteSaveNow(
|
|
|
333
339
|
saveId: string,
|
|
334
340
|
payload: SavePayload,
|
|
335
341
|
custom?: Record<string, unknown>,
|
|
336
|
-
|
|
342
|
+
playthroughPromise?: string | PromiseLike<string>,
|
|
337
343
|
): Promise<SaveRecord | undefined> {
|
|
344
|
+
const playthroughId = await playthroughPromise;
|
|
338
345
|
const backend = await getBackend();
|
|
339
346
|
const existing = await backend.getSave(saveId);
|
|
340
347
|
if (!existing) return undefined;
|
package/src/structural.ts
CHANGED
|
@@ -674,17 +674,20 @@ export function underChange(
|
|
|
674
674
|
|
|
675
675
|
/**
|
|
676
676
|
* The segment standing for the entry `index` of a Map or Set in a path, or
|
|
677
|
-
* for the `cause` or `errors` of an Error ('c', 'e')
|
|
677
|
+
* for the `cause` or `errors` of an Error ('c', 'e'), or for the buffer a
|
|
678
|
+
* typed array or DataView views ('b').
|
|
678
679
|
*/
|
|
679
|
-
const entryKey = (
|
|
680
|
-
|
|
680
|
+
const entryKey = (
|
|
681
|
+
kind: 'k' | 'v' | 's' | 'c' | 'e' | 'b',
|
|
682
|
+
index: number,
|
|
683
|
+
): string => `\0${kind}${index}`;
|
|
681
684
|
|
|
682
685
|
export const isEntryKey = (segment: string): boolean =>
|
|
683
686
|
segment.startsWith('\0');
|
|
684
687
|
|
|
685
688
|
/**
|
|
686
|
-
* What a Map, Set or
|
|
687
|
-
* members, `cause` and `errors
|
|
689
|
+
* What a Map, Set, Error or view holds in place of properties (keys and
|
|
690
|
+
* values, members, `cause` and `errors`, the buffer), each with the segment standing for it in
|
|
688
691
|
* a path (see entryKey); undefined for any other value.
|
|
689
692
|
*/
|
|
690
693
|
function entryChildren(value: object): [string, unknown][] | undefined {
|
|
@@ -707,6 +710,9 @@ function entryChildren(value: object): [string, unknown][] | undefined {
|
|
|
707
710
|
]);
|
|
708
711
|
}
|
|
709
712
|
}
|
|
713
|
+
} else if (ArrayBuffer.isView(value)) {
|
|
714
|
+
// Views of one buffer share it, which an alias of the buffer changes
|
|
715
|
+
children.push([entryKey('b', 0), value.buffer]);
|
|
710
716
|
} else {
|
|
711
717
|
return undefined;
|
|
712
718
|
}
|