@routevn/creator-model 1.1.10 → 1.1.12

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 (3) hide show
  1. package/README.md +20 -0
  2. package/package.json +5 -2
  3. package/src/model.js +28 -15
package/README.md CHANGED
@@ -5,6 +5,9 @@ Shared RouteVN domain model package.
5
5
  Repo rules and contribution expectations are in
6
6
  [GUIDELINES.md](./GUIDELINES.md).
7
7
 
8
+ Schema compatibility maintenance rules are in
9
+ [docs/schema-compatibility.md](./docs/schema-compatibility.md).
10
+
8
11
  This repo is intended to be the single source of truth for:
9
12
 
10
13
  - state validation
@@ -79,6 +82,9 @@ Design rules:
79
82
  - pure functions whenever possible
80
83
  - command payload shape is validated separately from state-aware preconditions
81
84
  - `SCHEMA_VERSION` is the source of truth for persisted command schema versioning
85
+ - `SCHEMA_VERSION` must stay aligned with the minor version from `package.json`
86
+ - patch releases must not change persisted schema compatibility
87
+ - `bun run test:compat` is the required compatibility gate for model changes
82
88
  - `processCommand()` is the authoritative state transition
83
89
  - model state should contain project-owned runtime data only
84
90
  - app-owned metadata like project id, name, and description should stay out of
@@ -190,6 +196,20 @@ YAML Puty specs use [tests/support/putyApi.js](./tests/support/putyApi.js) as a
190
196
  small adapter so the declarative `throws:` assertions can stay concise while the
191
197
  real public API returns `{ valid: ... }` result objects.
192
198
 
199
+ Compatibility fixtures live under `tests/compat/schema-<n>/`.
200
+
201
+ - `payloads/` fixtures are frozen command payload shapes for that schema version
202
+ - `states/` fixtures are frozen persisted-state snapshots for that schema version
203
+ - `streams/` fixtures are frozen command sequences for that schema version
204
+ - current tests must continue to validate/replay every archived compatibility
205
+ fixture from the same or older schema versions
206
+ - current schema payload coverage must include `minimal.yaml` and `full.yaml` for
207
+ every public command type
208
+
209
+ See also:
210
+
211
+ - [docs/schema-compatibility.md](./docs/schema-compatibility.md)
212
+
193
213
  ## Current Scope
194
214
 
195
215
  Currently implemented command types:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routevn/creator-model",
3
- "version": "1.1.10",
3
+ "version": "1.1.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,10 +27,13 @@
27
27
  },
28
28
  "scripts": {
29
29
  "test": "bunx vitest run",
30
+ "test:compat": "bunx vitest run tests/compatibility-fixtures.test.js",
30
31
  "test:watch": "bunx vitest",
31
- "bench": "bun scripts/benchmark-process-command.js"
32
+ "bench": "bun scripts/benchmark-process-command.js",
33
+ "generate:compat-fixtures": "node scripts/generate-compat-fixtures.js"
32
34
  },
33
35
  "devDependencies": {
36
+ "js-yaml": "^4.1.0",
34
37
  "puty": "0.1.2",
35
38
  "vitest": "^3.2.1"
36
39
  }
package/src/model.js CHANGED
@@ -145,7 +145,7 @@ const LAYOUT_ELEMENT_BASE_TYPES = [
145
145
  "container-ref-confirm-dialog-ok",
146
146
  "container-ref-confirm-dialog-cancel",
147
147
  ];
148
- export const SCHEMA_VERSION = 2;
148
+ export const SCHEMA_VERSION = 1;
149
149
  const LAYOUT_CONTAINER_ELEMENT_TYPES = [
150
150
  "folder",
151
151
  "container",
@@ -1357,30 +1357,21 @@ const validateMaskDefinition = ({ mask, path, errorFactory }) => {
1357
1357
  }
1358
1358
  }
1359
1359
 
1360
- if (
1361
- item.texture !== undefined &&
1362
- !isNonEmptyString(item.texture)
1363
- ) {
1360
+ if (item.texture !== undefined && !isNonEmptyString(item.texture)) {
1364
1361
  return invalidFromErrorFactory(
1365
1362
  errorFactory,
1366
1363
  `${itemPath}.texture must be a non-empty string when provided`,
1367
1364
  );
1368
1365
  }
1369
1366
 
1370
- if (
1371
- item.imageId !== undefined &&
1372
- !isNonEmptyString(item.imageId)
1373
- ) {
1367
+ if (item.imageId !== undefined && !isNonEmptyString(item.imageId)) {
1374
1368
  return invalidFromErrorFactory(
1375
1369
  errorFactory,
1376
1370
  `${itemPath}.imageId must be a non-empty string when provided`,
1377
1371
  );
1378
1372
  }
1379
1373
 
1380
- if (
1381
- !isNonEmptyString(item.texture) &&
1382
- !isNonEmptyString(item.imageId)
1383
- ) {
1374
+ if (!isNonEmptyString(item.texture) && !isNonEmptyString(item.imageId)) {
1384
1375
  return invalidFromErrorFactory(
1385
1376
  errorFactory,
1386
1377
  `${itemPath} must define texture or imageId`,
@@ -1987,7 +1978,15 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
1987
1978
  allowedKeys:
1988
1979
  variableType === "folder"
1989
1980
  ? ["id", "type", "name", "description"]
1990
- : ["id", "type", "name", "description", "scope", "default", "value"],
1981
+ : [
1982
+ "id",
1983
+ "type",
1984
+ "name",
1985
+ "description",
1986
+ "scope",
1987
+ "default",
1988
+ "value",
1989
+ ],
1991
1990
  path: itemPath,
1992
1991
  errorFactory,
1993
1992
  });
@@ -2474,6 +2473,7 @@ const validateLayoutElementData = ({
2474
2473
  "y",
2475
2474
  "width",
2476
2475
  "height",
2476
+ "aspectRatioLock",
2477
2477
  "anchorX",
2478
2478
  "anchorY",
2479
2479
  "scaleX",
@@ -2574,6 +2574,16 @@ const validateLayoutElementData = ({
2574
2574
  }
2575
2575
  }
2576
2576
 
2577
+ if (
2578
+ data.aspectRatioLock !== undefined &&
2579
+ (!isFiniteNumber(data.aspectRatioLock) || data.aspectRatioLock <= 0)
2580
+ ) {
2581
+ return invalidFromErrorFactory(
2582
+ errorFactory,
2583
+ `${path}.aspectRatioLock must be a finite number greater than 0 when provided`,
2584
+ );
2585
+ }
2586
+
2577
2587
  if (
2578
2588
  data.initialValue !== undefined &&
2579
2589
  !isFiniteNumber(data.initialValue) &&
@@ -2959,6 +2969,7 @@ const validateLayoutElementItems = ({ items, path, errorFactory }) => {
2959
2969
  "y",
2960
2970
  "width",
2961
2971
  "height",
2972
+ "aspectRatioLock",
2962
2973
  "anchorX",
2963
2974
  "anchorY",
2964
2975
  "scaleX",
@@ -4490,7 +4501,9 @@ export const assertInvariants = ({ state }) => {
4490
4501
  }
4491
4502
  }
4492
4503
 
4493
- for (const [animationId, animation] of Object.entries(state.animations.items)) {
4504
+ for (const [animationId, animation] of Object.entries(
4505
+ state.animations.items,
4506
+ )) {
4494
4507
  if (animation.type !== "animation") {
4495
4508
  continue;
4496
4509
  }