@routevn/creator-model 1.2.7 → 1.2.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.
- package/README.md +12 -0
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/model.js +172 -56
package/README.md
CHANGED
|
@@ -42,6 +42,11 @@ processCommand({
|
|
|
42
42
|
state,
|
|
43
43
|
command: { type, payload },
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
replayCommands({
|
|
47
|
+
state,
|
|
48
|
+
commands: [{ type, payload }],
|
|
49
|
+
});
|
|
45
50
|
```
|
|
46
51
|
|
|
47
52
|
`SCHEMA_VERSION` is the exported schema version constant for persisted command
|
|
@@ -76,6 +81,12 @@ or:
|
|
|
76
81
|
{ valid: true, state: nextState }
|
|
77
82
|
```
|
|
78
83
|
|
|
84
|
+
`replayCommands()` returns the same result shape while applying a trusted
|
|
85
|
+
command tape against a single working state clone. It is intended for batch
|
|
86
|
+
history reconstruction paths such as offline repository boot/import replay,
|
|
87
|
+
where validating the full state before and after every individual command is
|
|
88
|
+
needlessly expensive.
|
|
89
|
+
|
|
79
90
|
Design rules:
|
|
80
91
|
|
|
81
92
|
- no classes
|
|
@@ -86,6 +97,7 @@ Design rules:
|
|
|
86
97
|
- patch releases must not change persisted schema compatibility
|
|
87
98
|
- `bun run test:compat` is the required compatibility gate for model changes
|
|
88
99
|
- `processCommand()` is the authoritative state transition
|
|
100
|
+
- `replayCommands()` is the fast batch replay path for trusted command tapes
|
|
89
101
|
- model state should contain project-owned runtime data only
|
|
90
102
|
- app-owned metadata like project id, name, and description should stay out of
|
|
91
103
|
this package
|
package/package.json
CHANGED
package/src/index.js
CHANGED
package/src/model.js
CHANGED
|
@@ -15478,50 +15478,118 @@ export const validatePayload = ({ type, payload }) => {
|
|
|
15478
15478
|
});
|
|
15479
15479
|
};
|
|
15480
15480
|
|
|
15481
|
+
const normalizeCurrentStateValidationResult = (stateResult) => {
|
|
15482
|
+
if (stateResult.valid) {
|
|
15483
|
+
return stateResult;
|
|
15484
|
+
}
|
|
15485
|
+
|
|
15486
|
+
if (stateResult.error.kind === "invariant") {
|
|
15487
|
+
return invalidInvariant(
|
|
15488
|
+
stateResult.error.message,
|
|
15489
|
+
toDomainErrorDetails(stateResult.error),
|
|
15490
|
+
);
|
|
15491
|
+
}
|
|
15492
|
+
|
|
15493
|
+
return invalidState(
|
|
15494
|
+
stateResult.error.message,
|
|
15495
|
+
toDomainErrorDetails(stateResult.error),
|
|
15496
|
+
);
|
|
15497
|
+
};
|
|
15498
|
+
|
|
15499
|
+
const validateCommandDefinitionAgainstState = ({ state, command }) => {
|
|
15500
|
+
if (!isPlainObject(command)) {
|
|
15501
|
+
return invalidPrecondition("command must be an object");
|
|
15502
|
+
}
|
|
15503
|
+
|
|
15504
|
+
const payloadResult = validatePayload(command);
|
|
15505
|
+
if (!payloadResult.valid) {
|
|
15506
|
+
return invalidPayload(
|
|
15507
|
+
payloadResult.error.message,
|
|
15508
|
+
toDomainErrorDetails(payloadResult.error),
|
|
15509
|
+
);
|
|
15510
|
+
}
|
|
15511
|
+
|
|
15512
|
+
const definition = getCommandDefinition({ type: command.type });
|
|
15513
|
+
if (!definition) {
|
|
15514
|
+
return invalidPrecondition(`unknown command type '${command.type}'`);
|
|
15515
|
+
}
|
|
15516
|
+
|
|
15517
|
+
const validationResult = captureValidation(() =>
|
|
15518
|
+
definition.validateAgainstState({
|
|
15519
|
+
state,
|
|
15520
|
+
payload: command.payload,
|
|
15521
|
+
}),
|
|
15522
|
+
);
|
|
15523
|
+
const normalizedValidationResult = normalizeStateResult(validationResult);
|
|
15524
|
+
if (!normalizedValidationResult.valid) {
|
|
15525
|
+
return normalizedValidationResult;
|
|
15526
|
+
}
|
|
15527
|
+
|
|
15528
|
+
return {
|
|
15529
|
+
valid: true,
|
|
15530
|
+
definition,
|
|
15531
|
+
};
|
|
15532
|
+
};
|
|
15533
|
+
|
|
15534
|
+
const applyCommandDefinition = ({ state, definition, payload }) => {
|
|
15535
|
+
const nextState = definition.reduce({
|
|
15536
|
+
state,
|
|
15537
|
+
payload,
|
|
15538
|
+
});
|
|
15539
|
+
if (nextState?.valid === false) {
|
|
15540
|
+
return nextState;
|
|
15541
|
+
}
|
|
15542
|
+
|
|
15543
|
+
return {
|
|
15544
|
+
valid: true,
|
|
15545
|
+
state: nextState === undefined ? state : nextState,
|
|
15546
|
+
};
|
|
15547
|
+
};
|
|
15548
|
+
|
|
15549
|
+
const appendReplayCommandContext = (result, { commandIndex, command } = {}) => {
|
|
15550
|
+
if (result?.valid !== false || !result.error) {
|
|
15551
|
+
return result;
|
|
15552
|
+
}
|
|
15553
|
+
|
|
15554
|
+
const details = isPlainObject(result.error.details)
|
|
15555
|
+
? structuredClone(result.error.details)
|
|
15556
|
+
: {};
|
|
15557
|
+
|
|
15558
|
+
if (Number.isInteger(commandIndex) && commandIndex >= 0) {
|
|
15559
|
+
details.commandIndex = commandIndex;
|
|
15560
|
+
}
|
|
15561
|
+
|
|
15562
|
+
if (isNonEmptyString(command?.type)) {
|
|
15563
|
+
details.commandType = command.type;
|
|
15564
|
+
}
|
|
15565
|
+
|
|
15566
|
+
return {
|
|
15567
|
+
valid: false,
|
|
15568
|
+
error: {
|
|
15569
|
+
...result.error,
|
|
15570
|
+
details,
|
|
15571
|
+
},
|
|
15572
|
+
};
|
|
15573
|
+
};
|
|
15574
|
+
|
|
15481
15575
|
export const validateAgainstState = ({ state, command }) => {
|
|
15482
15576
|
return captureValidation(() => {
|
|
15483
15577
|
const normalizedState = normalizeStateCollections(state);
|
|
15484
15578
|
|
|
15485
|
-
if (!isPlainObject(command)) {
|
|
15486
|
-
return invalidPrecondition("command must be an object");
|
|
15487
|
-
}
|
|
15488
|
-
|
|
15489
|
-
const payloadResult = validatePayload(command);
|
|
15490
|
-
if (!payloadResult.valid) {
|
|
15491
|
-
return invalidPayload(
|
|
15492
|
-
payloadResult.error.message,
|
|
15493
|
-
toDomainErrorDetails(payloadResult.error),
|
|
15494
|
-
);
|
|
15495
|
-
}
|
|
15496
|
-
|
|
15497
15579
|
const stateResult = validateState({ state: normalizedState });
|
|
15498
15580
|
if (!stateResult.valid) {
|
|
15499
|
-
|
|
15500
|
-
return invalidInvariant(
|
|
15501
|
-
stateResult.error.message,
|
|
15502
|
-
toDomainErrorDetails(stateResult.error),
|
|
15503
|
-
);
|
|
15504
|
-
}
|
|
15505
|
-
|
|
15506
|
-
return invalidState(
|
|
15507
|
-
stateResult.error.message,
|
|
15508
|
-
toDomainErrorDetails(stateResult.error),
|
|
15509
|
-
);
|
|
15581
|
+
return normalizeCurrentStateValidationResult(stateResult);
|
|
15510
15582
|
}
|
|
15511
15583
|
|
|
15512
|
-
const
|
|
15513
|
-
|
|
15514
|
-
|
|
15584
|
+
const validationResult = validateCommandDefinitionAgainstState({
|
|
15585
|
+
state: normalizedState,
|
|
15586
|
+
command,
|
|
15587
|
+
});
|
|
15588
|
+
if (!validationResult.valid) {
|
|
15589
|
+
return validationResult;
|
|
15515
15590
|
}
|
|
15516
15591
|
|
|
15517
|
-
|
|
15518
|
-
definition.validateAgainstState({
|
|
15519
|
-
state: normalizedState,
|
|
15520
|
-
payload: command.payload,
|
|
15521
|
-
}),
|
|
15522
|
-
);
|
|
15523
|
-
|
|
15524
|
-
return normalizeStateResult(validationResult);
|
|
15592
|
+
return VALID_RESULT;
|
|
15525
15593
|
});
|
|
15526
15594
|
};
|
|
15527
15595
|
|
|
@@ -15534,49 +15602,97 @@ export const processCommand = ({ state, command }) => {
|
|
|
15534
15602
|
command.type.startsWith("spritesheet.") ||
|
|
15535
15603
|
command.type.startsWith("particle."));
|
|
15536
15604
|
|
|
15537
|
-
|
|
15538
|
-
|
|
15605
|
+
const stateResult = validateState({ state: normalizedState });
|
|
15606
|
+
if (!stateResult.valid) {
|
|
15607
|
+
return normalizeCurrentStateValidationResult(stateResult);
|
|
15539
15608
|
}
|
|
15540
15609
|
|
|
15541
|
-
const
|
|
15610
|
+
const validationResult = validateCommandDefinitionAgainstState({
|
|
15542
15611
|
state: normalizedState,
|
|
15543
15612
|
command,
|
|
15544
15613
|
});
|
|
15545
|
-
if (!
|
|
15546
|
-
return
|
|
15614
|
+
if (!validationResult.valid) {
|
|
15615
|
+
return validationResult;
|
|
15547
15616
|
}
|
|
15548
15617
|
|
|
15549
|
-
const
|
|
15550
|
-
if (!definition) {
|
|
15551
|
-
return invalidPrecondition(`unknown command type '${command.type}'`);
|
|
15552
|
-
}
|
|
15553
|
-
|
|
15554
|
-
const nextState = definition.reduce({
|
|
15618
|
+
const applyResult = applyCommandDefinition({
|
|
15555
15619
|
state: structuredClone(
|
|
15556
15620
|
shouldMaterializeNormalizedState ? normalizedState : state,
|
|
15557
15621
|
),
|
|
15622
|
+
definition: validationResult.definition,
|
|
15558
15623
|
payload: command.payload,
|
|
15559
15624
|
});
|
|
15560
|
-
if (
|
|
15561
|
-
return
|
|
15625
|
+
if (!applyResult.valid) {
|
|
15626
|
+
return applyResult;
|
|
15627
|
+
}
|
|
15628
|
+
|
|
15629
|
+
const stateResultAfterCommand = validateState({
|
|
15630
|
+
state: applyResult.state,
|
|
15631
|
+
});
|
|
15632
|
+
if (!stateResultAfterCommand.valid) {
|
|
15633
|
+
return stateResultAfterCommand;
|
|
15562
15634
|
}
|
|
15563
15635
|
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15636
|
+
return {
|
|
15637
|
+
valid: true,
|
|
15638
|
+
state: applyResult.state,
|
|
15639
|
+
};
|
|
15640
|
+
});
|
|
15641
|
+
};
|
|
15642
|
+
|
|
15643
|
+
export const replayCommands = ({ state, commands }) => {
|
|
15644
|
+
return captureValidation(() => {
|
|
15645
|
+
if (!Array.isArray(commands)) {
|
|
15646
|
+
return invalidPrecondition("commands must be an array");
|
|
15647
|
+
}
|
|
15648
|
+
|
|
15649
|
+
const normalizedState = normalizeStateCollections(state);
|
|
15570
15650
|
const stateResult = validateState({
|
|
15571
|
-
state:
|
|
15651
|
+
state: normalizedState,
|
|
15572
15652
|
});
|
|
15573
15653
|
if (!stateResult.valid) {
|
|
15574
|
-
return stateResult;
|
|
15654
|
+
return normalizeCurrentStateValidationResult(stateResult);
|
|
15655
|
+
}
|
|
15656
|
+
|
|
15657
|
+
let workingState = structuredClone(normalizedState);
|
|
15658
|
+
for (let index = 0; index < commands.length; index += 1) {
|
|
15659
|
+
const command = commands[index];
|
|
15660
|
+
const validationResult = validateCommandDefinitionAgainstState({
|
|
15661
|
+
state: workingState,
|
|
15662
|
+
command,
|
|
15663
|
+
});
|
|
15664
|
+
if (!validationResult.valid) {
|
|
15665
|
+
return appendReplayCommandContext(validationResult, {
|
|
15666
|
+
commandIndex: index,
|
|
15667
|
+
command,
|
|
15668
|
+
});
|
|
15669
|
+
}
|
|
15670
|
+
|
|
15671
|
+
const applyResult = applyCommandDefinition({
|
|
15672
|
+
state: workingState,
|
|
15673
|
+
definition: validationResult.definition,
|
|
15674
|
+
payload: command.payload,
|
|
15675
|
+
});
|
|
15676
|
+
if (!applyResult.valid) {
|
|
15677
|
+
return appendReplayCommandContext(applyResult, {
|
|
15678
|
+
commandIndex: index,
|
|
15679
|
+
command,
|
|
15680
|
+
});
|
|
15681
|
+
}
|
|
15682
|
+
|
|
15683
|
+
workingState = applyResult.state;
|
|
15684
|
+
}
|
|
15685
|
+
|
|
15686
|
+
const finalStateResult = validateState({
|
|
15687
|
+
state: workingState,
|
|
15688
|
+
});
|
|
15689
|
+
if (!finalStateResult.valid) {
|
|
15690
|
+
return finalStateResult;
|
|
15575
15691
|
}
|
|
15576
15692
|
|
|
15577
15693
|
return {
|
|
15578
15694
|
valid: true,
|
|
15579
|
-
state:
|
|
15695
|
+
state: workingState,
|
|
15580
15696
|
};
|
|
15581
15697
|
});
|
|
15582
15698
|
};
|