@routevn/creator-model 1.2.6 → 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 +174 -57
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
|
@@ -3298,12 +3298,13 @@ const validateLayoutElementData = ({
|
|
|
3298
3298
|
|
|
3299
3299
|
if (
|
|
3300
3300
|
data.direction !== undefined &&
|
|
3301
|
+
data.direction !== "absolute" &&
|
|
3301
3302
|
data.direction !== "horizontal" &&
|
|
3302
3303
|
data.direction !== "vertical"
|
|
3303
3304
|
) {
|
|
3304
3305
|
return invalidFromErrorFactory(
|
|
3305
3306
|
errorFactory,
|
|
3306
|
-
`${path}.direction must be 'horizontal' or 'vertical' when provided`,
|
|
3307
|
+
`${path}.direction must be 'absolute', 'horizontal' or 'vertical' when provided`,
|
|
3307
3308
|
);
|
|
3308
3309
|
}
|
|
3309
3310
|
|
|
@@ -15477,50 +15478,118 @@ export const validatePayload = ({ type, payload }) => {
|
|
|
15477
15478
|
});
|
|
15478
15479
|
};
|
|
15479
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
|
+
|
|
15480
15575
|
export const validateAgainstState = ({ state, command }) => {
|
|
15481
15576
|
return captureValidation(() => {
|
|
15482
15577
|
const normalizedState = normalizeStateCollections(state);
|
|
15483
15578
|
|
|
15484
|
-
if (!isPlainObject(command)) {
|
|
15485
|
-
return invalidPrecondition("command must be an object");
|
|
15486
|
-
}
|
|
15487
|
-
|
|
15488
|
-
const payloadResult = validatePayload(command);
|
|
15489
|
-
if (!payloadResult.valid) {
|
|
15490
|
-
return invalidPayload(
|
|
15491
|
-
payloadResult.error.message,
|
|
15492
|
-
toDomainErrorDetails(payloadResult.error),
|
|
15493
|
-
);
|
|
15494
|
-
}
|
|
15495
|
-
|
|
15496
15579
|
const stateResult = validateState({ state: normalizedState });
|
|
15497
15580
|
if (!stateResult.valid) {
|
|
15498
|
-
|
|
15499
|
-
return invalidInvariant(
|
|
15500
|
-
stateResult.error.message,
|
|
15501
|
-
toDomainErrorDetails(stateResult.error),
|
|
15502
|
-
);
|
|
15503
|
-
}
|
|
15504
|
-
|
|
15505
|
-
return invalidState(
|
|
15506
|
-
stateResult.error.message,
|
|
15507
|
-
toDomainErrorDetails(stateResult.error),
|
|
15508
|
-
);
|
|
15581
|
+
return normalizeCurrentStateValidationResult(stateResult);
|
|
15509
15582
|
}
|
|
15510
15583
|
|
|
15511
|
-
const
|
|
15512
|
-
|
|
15513
|
-
|
|
15584
|
+
const validationResult = validateCommandDefinitionAgainstState({
|
|
15585
|
+
state: normalizedState,
|
|
15586
|
+
command,
|
|
15587
|
+
});
|
|
15588
|
+
if (!validationResult.valid) {
|
|
15589
|
+
return validationResult;
|
|
15514
15590
|
}
|
|
15515
15591
|
|
|
15516
|
-
|
|
15517
|
-
definition.validateAgainstState({
|
|
15518
|
-
state: normalizedState,
|
|
15519
|
-
payload: command.payload,
|
|
15520
|
-
}),
|
|
15521
|
-
);
|
|
15522
|
-
|
|
15523
|
-
return normalizeStateResult(validationResult);
|
|
15592
|
+
return VALID_RESULT;
|
|
15524
15593
|
});
|
|
15525
15594
|
};
|
|
15526
15595
|
|
|
@@ -15533,49 +15602,97 @@ export const processCommand = ({ state, command }) => {
|
|
|
15533
15602
|
command.type.startsWith("spritesheet.") ||
|
|
15534
15603
|
command.type.startsWith("particle."));
|
|
15535
15604
|
|
|
15536
|
-
|
|
15537
|
-
|
|
15605
|
+
const stateResult = validateState({ state: normalizedState });
|
|
15606
|
+
if (!stateResult.valid) {
|
|
15607
|
+
return normalizeCurrentStateValidationResult(stateResult);
|
|
15538
15608
|
}
|
|
15539
15609
|
|
|
15540
|
-
const
|
|
15610
|
+
const validationResult = validateCommandDefinitionAgainstState({
|
|
15541
15611
|
state: normalizedState,
|
|
15542
15612
|
command,
|
|
15543
15613
|
});
|
|
15544
|
-
if (!
|
|
15545
|
-
return
|
|
15614
|
+
if (!validationResult.valid) {
|
|
15615
|
+
return validationResult;
|
|
15546
15616
|
}
|
|
15547
15617
|
|
|
15548
|
-
const
|
|
15549
|
-
if (!definition) {
|
|
15550
|
-
return invalidPrecondition(`unknown command type '${command.type}'`);
|
|
15551
|
-
}
|
|
15552
|
-
|
|
15553
|
-
const nextState = definition.reduce({
|
|
15618
|
+
const applyResult = applyCommandDefinition({
|
|
15554
15619
|
state: structuredClone(
|
|
15555
15620
|
shouldMaterializeNormalizedState ? normalizedState : state,
|
|
15556
15621
|
),
|
|
15622
|
+
definition: validationResult.definition,
|
|
15557
15623
|
payload: command.payload,
|
|
15558
15624
|
});
|
|
15559
|
-
if (
|
|
15560
|
-
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;
|
|
15561
15634
|
}
|
|
15562
15635
|
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
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);
|
|
15569
15650
|
const stateResult = validateState({
|
|
15570
|
-
state:
|
|
15651
|
+
state: normalizedState,
|
|
15571
15652
|
});
|
|
15572
15653
|
if (!stateResult.valid) {
|
|
15573
|
-
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;
|
|
15574
15691
|
}
|
|
15575
15692
|
|
|
15576
15693
|
return {
|
|
15577
15694
|
valid: true,
|
|
15578
|
-
state:
|
|
15695
|
+
state: workingState,
|
|
15579
15696
|
};
|
|
15580
15697
|
});
|
|
15581
15698
|
};
|