@platforma-sdk/model 1.54.13 → 1.55.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/bconfig/normalization.cjs +8 -1
- package/dist/bconfig/normalization.cjs.map +1 -1
- package/dist/bconfig/normalization.d.ts.map +1 -1
- package/dist/bconfig/normalization.js +8 -1
- package/dist/bconfig/normalization.js.map +1 -1
- package/dist/block_api_v3.d.ts +2 -2
- package/dist/block_api_v3.d.ts.map +1 -1
- package/dist/block_migrations.cjs +246 -214
- package/dist/block_migrations.cjs.map +1 -1
- package/dist/block_migrations.d.ts +180 -158
- package/dist/block_migrations.d.ts.map +1 -1
- package/dist/block_migrations.js +247 -214
- package/dist/block_migrations.js.map +1 -1
- package/dist/block_model.cjs +85 -35
- package/dist/block_model.cjs.map +1 -1
- package/dist/block_model.d.ts +66 -38
- package/dist/block_model.d.ts.map +1 -1
- package/dist/block_model.js +86 -36
- package/dist/block_model.js.map +1 -1
- package/dist/{builder.cjs → block_model_legacy.cjs} +2 -2
- package/dist/block_model_legacy.cjs.map +1 -0
- package/dist/{builder.d.ts → block_model_legacy.d.ts} +1 -1
- package/dist/block_model_legacy.d.ts.map +1 -0
- package/dist/{builder.js → block_model_legacy.js} +2 -2
- package/dist/block_model_legacy.js.map +1 -0
- package/dist/block_state_patch.d.ts +11 -1
- package/dist/block_state_patch.d.ts.map +1 -1
- package/dist/block_storage.cjs +126 -109
- package/dist/block_storage.cjs.map +1 -1
- package/dist/block_storage.d.ts +109 -112
- package/dist/block_storage.d.ts.map +1 -1
- package/dist/block_storage.js +126 -101
- package/dist/block_storage.js.map +1 -1
- package/dist/block_storage_callbacks.cjs +227 -0
- package/dist/block_storage_callbacks.cjs.map +1 -0
- package/dist/block_storage_callbacks.d.ts +113 -0
- package/dist/block_storage_callbacks.d.ts.map +1 -0
- package/dist/block_storage_callbacks.js +218 -0
- package/dist/block_storage_callbacks.js.map +1 -0
- package/dist/block_storage_facade.cjs +104 -0
- package/dist/block_storage_facade.cjs.map +1 -0
- package/dist/block_storage_facade.d.ts +168 -0
- package/dist/block_storage_facade.d.ts.map +1 -0
- package/dist/block_storage_facade.js +99 -0
- package/dist/block_storage_facade.js.map +1 -0
- package/dist/index.cjs +13 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/package.json.cjs +1 -1
- package/dist/package.json.js +1 -1
- package/dist/platforma.d.ts +11 -4
- package/dist/platforma.d.ts.map +1 -1
- package/dist/plugin_model.cjs +171 -0
- package/dist/plugin_model.cjs.map +1 -0
- package/dist/plugin_model.d.ts +162 -0
- package/dist/plugin_model.d.ts.map +1 -0
- package/dist/plugin_model.js +169 -0
- package/dist/plugin_model.js.map +1 -0
- package/dist/render/api.cjs +20 -21
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts +8 -8
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +20 -21
- package/dist/render/api.js.map +1 -1
- package/dist/render/internal.cjs.map +1 -1
- package/dist/render/internal.d.ts +1 -1
- package/dist/render/internal.d.ts.map +1 -1
- package/dist/render/internal.js.map +1 -1
- package/dist/version.cjs +4 -0
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +4 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/bconfig/normalization.ts +8 -1
- package/src/block_api_v3.ts +2 -2
- package/src/block_migrations.test.ts +141 -171
- package/src/block_migrations.ts +300 -285
- package/src/block_model.ts +205 -95
- package/src/{builder.ts → block_model_legacy.ts} +1 -1
- package/src/block_state_patch.ts +13 -1
- package/src/block_storage.test.ts +283 -95
- package/src/block_storage.ts +199 -188
- package/src/block_storage_callbacks.ts +326 -0
- package/src/block_storage_facade.ts +199 -0
- package/src/index.ts +7 -3
- package/src/platforma.ts +26 -7
- package/src/plugin_model.test.ts +168 -0
- package/src/plugin_model.ts +242 -0
- package/src/render/api.ts +26 -24
- package/src/render/internal.ts +1 -1
- package/src/typing.test.ts +1 -1
- package/src/version.ts +8 -0
- package/dist/block_storage_vm.cjs +0 -262
- package/dist/block_storage_vm.cjs.map +0 -1
- package/dist/block_storage_vm.d.ts +0 -59
- package/dist/block_storage_vm.d.ts.map +0 -1
- package/dist/block_storage_vm.js +0 -258
- package/dist/block_storage_vm.js.map +0 -1
- package/dist/branding.d.ts +0 -7
- package/dist/branding.d.ts.map +0 -1
- package/dist/builder.cjs.map +0 -1
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js.map +0 -1
- package/dist/sdk_info.cjs +0 -10
- package/dist/sdk_info.cjs.map +0 -1
- package/dist/sdk_info.d.ts +0 -5
- package/dist/sdk_info.d.ts.map +0 -1
- package/dist/sdk_info.js +0 -8
- package/dist/sdk_info.js.map +0 -1
- package/dist/unionize.d.ts +0 -12
- package/dist/unionize.d.ts.map +0 -1
- package/src/block_storage_vm.ts +0 -346
- package/src/branding.ts +0 -4
- package/src/sdk_info.ts +0 -9
- package/src/unionize.ts +0 -12
|
@@ -1,39 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
DataModelBuilder,
|
|
4
|
-
defaultRecover,
|
|
5
|
-
defineDataVersions,
|
|
6
|
-
makeDataVersioned,
|
|
7
|
-
} from "./block_migrations";
|
|
8
|
-
|
|
9
|
-
describe("defineDataVersions", () => {
|
|
10
|
-
it("throws on duplicate version values", () => {
|
|
11
|
-
expect(() =>
|
|
12
|
-
defineDataVersions({
|
|
13
|
-
V1: "v1",
|
|
14
|
-
V2: "v1", // duplicate!
|
|
15
|
-
}),
|
|
16
|
-
).toThrow("Duplicate version values: v1");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("throws on empty version values", () => {
|
|
20
|
-
expect(() =>
|
|
21
|
-
defineDataVersions({
|
|
22
|
-
V1: "v1",
|
|
23
|
-
V2: "", // empty!
|
|
24
|
-
}),
|
|
25
|
-
).toThrow("Version values must be non-empty strings (empty: V2)");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("allows unique version values", () => {
|
|
29
|
-
const versions = defineDataVersions({
|
|
30
|
-
V1: "v1",
|
|
31
|
-
V2: "v2",
|
|
32
|
-
});
|
|
33
|
-
expect(versions.V1).toBe("v1");
|
|
34
|
-
expect(versions.V2).toBe("v2");
|
|
35
|
-
});
|
|
36
|
-
});
|
|
2
|
+
import { DataModelBuilder, defaultRecover, makeDataVersioned } from "./block_migrations";
|
|
37
3
|
|
|
38
4
|
describe("makeDataVersioned", () => {
|
|
39
5
|
it("creates correct DataVersioned shape", () => {
|
|
@@ -44,19 +10,9 @@ describe("makeDataVersioned", () => {
|
|
|
44
10
|
|
|
45
11
|
describe("DataModel migrations", () => {
|
|
46
12
|
it("resets to initial data on unknown version", () => {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
type VersionedData = {
|
|
53
|
-
[Version.V1]: { count: number };
|
|
54
|
-
[Version.V2]: { count: number; label: string };
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const dataModel = new DataModelBuilder<VersionedData>()
|
|
58
|
-
.from(Version.V1)
|
|
59
|
-
.migrate(Version.V2, (v1) => ({ ...v1, label: "" }))
|
|
13
|
+
const dataModel = new DataModelBuilder()
|
|
14
|
+
.from<{ count: number }>("v1")
|
|
15
|
+
.migrate<{ count: number; label: string }>("v2", (v1) => ({ ...v1, label: "" }))
|
|
60
16
|
.init(() => ({ count: 0, label: "" }));
|
|
61
17
|
|
|
62
18
|
const result = dataModel.migrate(makeDataVersioned("legacy", { count: 42 }));
|
|
@@ -65,141 +21,155 @@ describe("DataModel migrations", () => {
|
|
|
65
21
|
expect(result.warning).toBe(`Unknown version 'legacy'`);
|
|
66
22
|
});
|
|
67
23
|
|
|
68
|
-
it("
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
24
|
+
it("throws at build time on duplicate version key", () => {
|
|
25
|
+
expect(() =>
|
|
26
|
+
new DataModelBuilder()
|
|
27
|
+
.from<{ count: number }>("v1")
|
|
28
|
+
.migrate<{ count: number; label: string }>("v2", (v1) => ({ ...v1, label: "" }))
|
|
29
|
+
.migrate<{ count: number; label: string; description: string }>("v1", (v2) => ({
|
|
30
|
+
...v2,
|
|
31
|
+
description: "",
|
|
32
|
+
})),
|
|
33
|
+
).toThrow("Duplicate version 'v1' in migration chain");
|
|
34
|
+
});
|
|
73
35
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.from(Version.V1)
|
|
81
|
-
.migrate(Version.V2, (v1) => ({ ...v1, label: "" }))
|
|
82
|
-
.recover((version, data) => {
|
|
83
|
-
if (version === "legacy" && typeof data === "object" && data !== null && "count" in data) {
|
|
84
|
-
return { count: (data as { count: number }).count, label: "recovered" };
|
|
85
|
-
}
|
|
86
|
-
return defaultRecover(version, data);
|
|
36
|
+
it("returns initial data on migration failure", () => {
|
|
37
|
+
const dataModel = new DataModelBuilder()
|
|
38
|
+
.from<{ numbers: number[] }>("v1")
|
|
39
|
+
.migrate<{ numbers: number[]; label: string }>("v2", (v1) => {
|
|
40
|
+
if (v1.numbers.includes(666)) throw new Error("Forbidden number");
|
|
41
|
+
return { ...v1, label: "ok" };
|
|
87
42
|
})
|
|
88
|
-
.init(() => ({
|
|
43
|
+
.init(() => ({ numbers: [], label: "" }));
|
|
89
44
|
|
|
90
|
-
const result = dataModel.migrate(makeDataVersioned("
|
|
45
|
+
const result = dataModel.migrate(makeDataVersioned("v1", { numbers: [666] }));
|
|
91
46
|
expect(result.version).toBe("v2");
|
|
92
|
-
expect(result.data).toStrictEqual({
|
|
93
|
-
expect(result.warning).
|
|
47
|
+
expect(result.data).toStrictEqual({ numbers: [], label: "" });
|
|
48
|
+
expect(result.warning).toBe(`Migration v1→v2 failed: Forbidden number`);
|
|
94
49
|
});
|
|
95
50
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
51
|
+
describe("recover()", () => {
|
|
52
|
+
it("recover() after from() — handles unknown version before any migrations run", () => {
|
|
53
|
+
const dataModel = new DataModelBuilder()
|
|
54
|
+
.from<{ count: number }>("v1")
|
|
55
|
+
.recover((version, data) => {
|
|
56
|
+
if (
|
|
57
|
+
version === "legacy" &&
|
|
58
|
+
typeof data === "object" &&
|
|
59
|
+
data !== null &&
|
|
60
|
+
"count" in data
|
|
61
|
+
) {
|
|
62
|
+
return { count: (data as { count: number }).count };
|
|
63
|
+
}
|
|
64
|
+
return defaultRecover(version, data);
|
|
65
|
+
})
|
|
66
|
+
.migrate<{ count: number; label: string }>("v2", (v1) => ({ ...v1, label: "default" }))
|
|
67
|
+
.init(() => ({ count: 0, label: "" }));
|
|
68
|
+
|
|
69
|
+
// Legacy data is recovered as V1 then goes through v1→v2 migration
|
|
70
|
+
const result = dataModel.migrate(makeDataVersioned("legacy", { count: 5 }));
|
|
71
|
+
expect(result.version).toBe("v2");
|
|
72
|
+
expect(result.data).toStrictEqual({ count: 5, label: "default" });
|
|
73
|
+
expect(result.warning).toBeUndefined();
|
|
100
74
|
});
|
|
101
75
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
76
|
+
it("recover() between migrations — recovered data goes through subsequent migrations", () => {
|
|
77
|
+
type V2 = { count: number; label: string };
|
|
78
|
+
type V3 = { count: number; label: string; description: string };
|
|
79
|
+
|
|
80
|
+
const dataModel = new DataModelBuilder()
|
|
81
|
+
.from<{ count: number }>("v1")
|
|
82
|
+
.migrate<V2>("v2", (v1) => ({ ...v1, label: "" }))
|
|
83
|
+
.recover((version, data) => {
|
|
84
|
+
if (
|
|
85
|
+
version === "legacy" &&
|
|
86
|
+
typeof data === "object" &&
|
|
87
|
+
data !== null &&
|
|
88
|
+
"count" in data
|
|
89
|
+
) {
|
|
90
|
+
return { count: (data as { count: number }).count, label: "recovered" };
|
|
91
|
+
}
|
|
92
|
+
return defaultRecover(version, data);
|
|
93
|
+
})
|
|
94
|
+
.migrate<V3>("v3", (v2) => ({ ...v2, description: "added" }))
|
|
95
|
+
.init(() => ({ count: 0, label: "", description: "" }));
|
|
96
|
+
|
|
97
|
+
const result = dataModel.migrate(makeDataVersioned("legacy", { count: 7 }));
|
|
98
|
+
expect(result.version).toBe("v3");
|
|
99
|
+
expect(result.data).toStrictEqual({ count: 7, label: "recovered", description: "added" });
|
|
100
|
+
expect(result.warning).toBeUndefined();
|
|
101
|
+
});
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
103
|
+
it("recover() at the end of chain — recovered data is the final type", () => {
|
|
104
|
+
type V2 = { count: number; label: string };
|
|
105
|
+
|
|
106
|
+
const dataModel = new DataModelBuilder()
|
|
107
|
+
.from<{ count: number }>("v1")
|
|
108
|
+
.migrate<V2>("v2", (v1) => ({ ...v1, label: "" }))
|
|
109
|
+
.recover((version, data) => {
|
|
110
|
+
if (
|
|
111
|
+
version === "legacy" &&
|
|
112
|
+
typeof data === "object" &&
|
|
113
|
+
data !== null &&
|
|
114
|
+
"count" in data
|
|
115
|
+
) {
|
|
116
|
+
return { count: (data as { count: number }).count, label: "recovered" };
|
|
117
|
+
}
|
|
118
|
+
return defaultRecover(version, data);
|
|
119
|
+
})
|
|
120
|
+
.init(() => ({ count: 0, label: "" }));
|
|
121
|
+
|
|
122
|
+
const result = dataModel.migrate(makeDataVersioned("legacy", { count: 9 }));
|
|
123
|
+
expect(result.version).toBe("v2");
|
|
124
|
+
expect(result.data).toStrictEqual({ count: 9, label: "recovered" });
|
|
125
|
+
expect(result.warning).toBeUndefined();
|
|
126
|
+
});
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
it("recover() delegates to defaultRecover for truly unknown versions", () => {
|
|
129
|
+
const dataModel = new DataModelBuilder()
|
|
130
|
+
.from<{ count: number }>("v1")
|
|
131
|
+
.migrate<{ count: number; label: string }>("v2", (v1) => ({ ...v1, label: "" }))
|
|
132
|
+
.recover((version, data) => defaultRecover(version, data))
|
|
133
|
+
.init(() => ({ count: 0, label: "" }));
|
|
134
|
+
|
|
135
|
+
const result = dataModel.migrate(makeDataVersioned("unknown", { count: 7 }));
|
|
136
|
+
expect(result.version).toBe("v2");
|
|
137
|
+
expect(result.data).toStrictEqual({ count: 0, label: "" });
|
|
138
|
+
expect(result.warning).toBe(`Unknown version 'unknown'`);
|
|
123
139
|
});
|
|
124
140
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
it("migration failure after recover() resets to initial data", () => {
|
|
142
|
+
type V2 = { count: number; label: string };
|
|
143
|
+
type V3 = { count: number; label: string; description: string };
|
|
144
|
+
|
|
145
|
+
const dataModel = new DataModelBuilder()
|
|
146
|
+
.from<{ count: number }>("v1")
|
|
147
|
+
.migrate<V2>("v2", (v1) => ({ ...v1, label: "" }))
|
|
148
|
+
.recover((version, data) => {
|
|
149
|
+
if (
|
|
150
|
+
version === "legacy" &&
|
|
151
|
+
typeof data === "object" &&
|
|
152
|
+
data !== null &&
|
|
153
|
+
"count" in data
|
|
154
|
+
) {
|
|
155
|
+
return { count: (data as { count: number }).count, label: "recovered" };
|
|
156
|
+
}
|
|
157
|
+
return defaultRecover(version, data);
|
|
158
|
+
})
|
|
159
|
+
.migrate<V3>("v3", (_v2) => {
|
|
160
|
+
throw new Error("v3 failed");
|
|
161
|
+
})
|
|
162
|
+
.init(() => ({ count: 0, label: "", description: "" }));
|
|
163
|
+
|
|
164
|
+
const result = dataModel.migrate(makeDataVersioned("legacy", { count: 7 }));
|
|
165
|
+
expect(result.version).toBe("v3");
|
|
166
|
+
expect(result.data).toStrictEqual({ count: 0, label: "", description: "" });
|
|
167
|
+
expect(result.warning).toBe("Migration v2→v3 failed: v3 failed");
|
|
168
|
+
});
|
|
139
169
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
it("recover() cannot be called twice — enforced by type (no recover() on WithRecover)", () => {
|
|
171
|
+
// This is a compile-time-only check — WithRecover has no recover() method.
|
|
172
|
+
// Verified by the absence of recover() in DataModelMigrationChainWithRecover.
|
|
173
|
+
});
|
|
144
174
|
});
|
|
145
175
|
});
|
|
146
|
-
|
|
147
|
-
function _compileTimeTypeChecks() {
|
|
148
|
-
const Version = defineDataVersions({
|
|
149
|
-
V1: "v1",
|
|
150
|
-
V2: "v2",
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
type VersionedData = {
|
|
154
|
-
[Version.V1]: { count: number };
|
|
155
|
-
[Version.V2]: { count: number; label: string };
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// Valid: complete migration chain
|
|
159
|
-
new DataModelBuilder<VersionedData>()
|
|
160
|
-
.from(Version.V1)
|
|
161
|
-
.migrate(Version.V2, (v1) => ({ ...v1, label: "" }))
|
|
162
|
-
.init(() => ({ count: 0, label: "" }));
|
|
163
|
-
|
|
164
|
-
// Valid: with recover()
|
|
165
|
-
new DataModelBuilder<VersionedData>()
|
|
166
|
-
.from(Version.V1)
|
|
167
|
-
.migrate(Version.V2, (v1) => ({ ...v1, label: "" }))
|
|
168
|
-
.recover((version, data) => defaultRecover(version, data))
|
|
169
|
-
.init(() => ({ count: 0, label: "" }));
|
|
170
|
-
|
|
171
|
-
new DataModelBuilder<VersionedData>()
|
|
172
|
-
// @ts-expect-error invalid initial version key
|
|
173
|
-
.from("v3");
|
|
174
|
-
|
|
175
|
-
new DataModelBuilder<VersionedData>()
|
|
176
|
-
.from(Version.V1)
|
|
177
|
-
// @ts-expect-error invalid migration target key
|
|
178
|
-
.migrate("v3", (v1) => ({ ...v1, label: "" }));
|
|
179
|
-
|
|
180
|
-
new DataModelBuilder<VersionedData>()
|
|
181
|
-
.from(Version.V1)
|
|
182
|
-
// @ts-expect-error migration return type must match target version
|
|
183
|
-
.migrate(Version.V2, (v1) => ({ ...v1, invalid: true }));
|
|
184
|
-
|
|
185
|
-
// Incomplete migration chain - V2 not covered
|
|
186
|
-
// This errors at compile-time with the `this` parameter constraint:
|
|
187
|
-
// "The 'this' context of type 'DataModelMigrationChain<..., "v1", "v2">' is not assignable to method's 'this' of type 'DataModelMigrationChain<..., "v1", never>'"
|
|
188
|
-
// Note: @ts-expect-error doesn't work reliably in unused functions
|
|
189
|
-
// new DataModelBuilder<VersionedData>()
|
|
190
|
-
// .from(Version.V1)
|
|
191
|
-
// .init(() => ({ count: 0 }));
|
|
192
|
-
|
|
193
|
-
new DataModelBuilder<VersionedData>()
|
|
194
|
-
.from(Version.V1)
|
|
195
|
-
.migrate(Version.V2, (v1) => ({ ...v1, label: "" }))
|
|
196
|
-
.recover((version, data) => defaultRecover(version, data))
|
|
197
|
-
// @ts-expect-error recover() returns builder without recover() method - cannot call twice (only init() available)
|
|
198
|
-
.recover((version, data) => defaultRecover(version, data));
|
|
199
|
-
|
|
200
|
-
new DataModelBuilder<VersionedData>()
|
|
201
|
-
.from(Version.V1)
|
|
202
|
-
.recover((version, data) => defaultRecover(version, data))
|
|
203
|
-
// @ts-expect-error recover() returns builder without migrate() method (only init() available)
|
|
204
|
-
.migrate(Version.V2, (v1) => ({ ...v1, label: "" }));
|
|
205
|
-
}
|