@yahoo/uds-create-config 5.6.0 → 5.7.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/configs/system.js +3 -1
- package/dist/entities/system/Component.d.ts +5 -2
- package/dist/entities/system/Component.js +46 -6
- package/dist/entities/system/Motion.d.ts +41 -9
- package/dist/entities/system/Motion.js +73 -6
- package/dist/entities/system/Token.js +1 -1
- package/dist/entities/system/defineComponent.js +2 -7
- package/dist/entities/system/linked-token-values.js +1 -1
- package/dist/entities/system/motion-presets.d.ts +32 -0
- package/dist/entities/system/motion-presets.js +239 -0
- package/dist/entities/system/motion-variants.d.ts +73 -0
- package/dist/entities/system/motion-variants.js +270 -0
- package/dist/framework/Config.d.ts +1 -1
- package/dist/framework/Config.js +1 -1
- package/dist/framework/class-names.d.ts +11 -1
- package/dist/framework/class-names.js +42 -1
- package/dist/framework/defineSubEntity.d.ts +7 -2
- package/dist/framework/defineSubEntity.js +11 -3
- package/dist/framework/projections.js +1 -0
- package/dist/framework/render-spec.d.ts +5 -1
- package/dist/framework/render-spec.js +21 -2
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/index.d.ts +6 -4
- package/dist/index.js +8 -6
- package/dist/migrations/2.0.0/v1-artifact.js +1 -1
- package/dist/migrations/20261006125609_motion_variants_and_layout.d.ts +10 -0
- package/dist/migrations/20261006125609_motion_variants_and_layout.js +10 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Config } from "../../framework/Config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/entities/system/motion-variants.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A JS preset a component's style rule puts on one of its layers — `checkbox: { motion: 'press' }`.
|
|
6
|
+
* `preset` is the preset's path, or for one written in place the id {@link inlineMotionId} gives it.
|
|
7
|
+
*/
|
|
8
|
+
interface PlacedMotion {
|
|
9
|
+
readonly component: string;
|
|
10
|
+
readonly layer: string;
|
|
11
|
+
readonly preset: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A JS preset written in place as a rule's `motion` on a layer. It has no path, so it is named by
|
|
15
|
+
* where it sits: the component, the rule's key (its condition, `*` for the unconditional rule) and
|
|
16
|
+
* the layer. `invalid` holds why a body the build cannot read was left out, for the build to report.
|
|
17
|
+
*/
|
|
18
|
+
interface InlineMotion {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly component: string;
|
|
21
|
+
readonly rule: string;
|
|
22
|
+
readonly layer: string;
|
|
23
|
+
readonly body: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/** The id an in-place preset goes by — `Btn.root inline [disabled=true]`. Never a preset path, since a
|
|
26
|
+
* path holds no space. */
|
|
27
|
+
declare function inlineMotionId(component: string, rule: string, layer: string): string;
|
|
28
|
+
/** Whether a rule's `motion` value is a preset written in place, rather than a ref or a name. */
|
|
29
|
+
declare function isInlineMotion(value: unknown): value is Record<string, unknown>;
|
|
30
|
+
/** Every JS preset a rule writes in place, and every one the build cannot read as a JS preset. */
|
|
31
|
+
declare const inlineMotions: (config: Config) => {
|
|
32
|
+
readonly valid: readonly InlineMotion[];
|
|
33
|
+
readonly invalid: readonly (Omit<InlineMotion, 'body'> & {
|
|
34
|
+
reason: string;
|
|
35
|
+
})[];
|
|
36
|
+
};
|
|
37
|
+
/** A JS preset's body by its path, or an in-place one's by its id; `undefined` for anything else. */
|
|
38
|
+
declare function jsMotionBody(config: Config, preset: string): Record<string, unknown> | undefined;
|
|
39
|
+
/** The states a preset names a variant in. */
|
|
40
|
+
declare const MOTION_VARIANT_STATES: readonly ["initial", "animate", "exit", "whileHover", "whileTap", "whileFocus", "whileDrag"];
|
|
41
|
+
type Body = Record<string, unknown>;
|
|
42
|
+
/** The variant names one state holds: a name, a list of names, or none (values, `false`). */
|
|
43
|
+
declare function stateVariantNames(value: unknown): string[];
|
|
44
|
+
/** The variants a preset declares. */
|
|
45
|
+
declare function declaredVariants(body: Body | undefined): string[];
|
|
46
|
+
/** Every JS preset a style rule puts on a layer, across the system. */
|
|
47
|
+
declare const placedMotions: (config: Config) => readonly PlacedMotion[];
|
|
48
|
+
/**
|
|
49
|
+
* The presets that share variant names, each set closed over placement and composition.
|
|
50
|
+
*
|
|
51
|
+
* Two presets on one component's layers are in one set. So is a preset a component places and one a
|
|
52
|
+
* component it renders places, at any depth: the label passes through a component that places nothing
|
|
53
|
+
* on its way down, so the walk goes through those without joining them. A component that places
|
|
54
|
+
* nothing joins nothing, which keeps Button and Checkbox apart although both render Box.
|
|
55
|
+
*/
|
|
56
|
+
declare const motionVariantGroups: (config: Config) => readonly ReadonlySet<string>[];
|
|
57
|
+
/** A variant a placed preset declares that nothing animating with it ever names. */
|
|
58
|
+
interface OrphanedMotionVariant {
|
|
59
|
+
readonly preset: string;
|
|
60
|
+
readonly variant: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Every variant a placed preset declares that no state in its set names — what a rename of the name
|
|
64
|
+
* on one side leaves on the other, and what `motion/react` then never animates to, silently.
|
|
65
|
+
*
|
|
66
|
+
* Only presets a style rule places are checked: an unplaced preset's variants may answer whatever the
|
|
67
|
+
* app renders around it, and neither is a set holding a component with a `motion` prop. A placed one
|
|
68
|
+
* may be driven from outside the system too, by an app ancestor animating to the same name, which is
|
|
69
|
+
* why this warns and never refuses.
|
|
70
|
+
*/
|
|
71
|
+
declare function orphanedMotionVariants(config: Config): OrphanedMotionVariant[];
|
|
72
|
+
//#endregion
|
|
73
|
+
export { InlineMotion, MOTION_VARIANT_STATES, OrphanedMotionVariant, PlacedMotion, declaredVariants, inlineMotionId, inlineMotions, isInlineMotion, jsMotionBody, motionVariantGroups, orphanedMotionVariants, placedMotions, stateVariantNames };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { reject } from "../../framework/rejection.js";
|
|
2
|
+
import { isRef, kindOf, pathOf } from "../../framework/utils/refs.js";
|
|
3
|
+
import { isPlainObject } from "../../framework/utils/field-path.js";
|
|
4
|
+
import { createSliceMemo } from "../../framework/memo.js";
|
|
5
|
+
import { inlineJsMotion } from "./Motion.js";
|
|
6
|
+
//#region src/entities/system/motion-variants.ts
|
|
7
|
+
/** The id an in-place preset goes by — `Btn.root inline [disabled=true]`. Never a preset path, since a
|
|
8
|
+
* path holds no space. */
|
|
9
|
+
function inlineMotionId(component, rule, layer) {
|
|
10
|
+
return `${component}.${layer} inline [${rule}]`;
|
|
11
|
+
}
|
|
12
|
+
/** Whether a rule's `motion` value is a preset written in place, rather than a ref or a name. */
|
|
13
|
+
function isInlineMotion(value) {
|
|
14
|
+
return isPlainObject(value) && !isRef(value);
|
|
15
|
+
}
|
|
16
|
+
/** Every JS preset a rule writes in place, and every one the build cannot read as a JS preset. */
|
|
17
|
+
const inlineMotions = createSliceMemo({
|
|
18
|
+
kinds: ["component"],
|
|
19
|
+
compute: (config) => {
|
|
20
|
+
const valid = [];
|
|
21
|
+
const invalid = [];
|
|
22
|
+
for (const component of config.list("component")) {
|
|
23
|
+
const { styles = {} } = component.toJSON();
|
|
24
|
+
for (const [rule, entry] of Object.entries(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
|
|
25
|
+
const value = bag?.motion;
|
|
26
|
+
if (!isInlineMotion(value)) continue;
|
|
27
|
+
const id = inlineMotionId(component.path, rule, layer);
|
|
28
|
+
const parsed = inlineJsMotion.safeParse(value);
|
|
29
|
+
if (!parsed.success) {
|
|
30
|
+
invalid.push({
|
|
31
|
+
id,
|
|
32
|
+
component: component.path,
|
|
33
|
+
rule,
|
|
34
|
+
layer,
|
|
35
|
+
reason: parsed.error.issues.map((issue) => [issue.path.join("."), issue.message].filter(Boolean).join(": ")).join("; ")
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
valid.push({
|
|
40
|
+
id,
|
|
41
|
+
component: component.path,
|
|
42
|
+
rule,
|
|
43
|
+
layer,
|
|
44
|
+
body: {
|
|
45
|
+
...value,
|
|
46
|
+
runtime: "js"
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
valid,
|
|
53
|
+
invalid
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
/** A JS preset's body by its path, or an in-place one's by its id; `undefined` for anything else. */
|
|
58
|
+
function jsMotionBody(config, preset) {
|
|
59
|
+
const named = config.resolve("motion", preset)?.toJSON();
|
|
60
|
+
if (named) return named.runtime === "js" ? named : void 0;
|
|
61
|
+
return inlineMotions(config).valid.find((entry) => entry.id === preset)?.body;
|
|
62
|
+
}
|
|
63
|
+
/** The states a preset names a variant in. */
|
|
64
|
+
const MOTION_VARIANT_STATES = [
|
|
65
|
+
"initial",
|
|
66
|
+
"animate",
|
|
67
|
+
"exit",
|
|
68
|
+
"whileHover",
|
|
69
|
+
"whileTap",
|
|
70
|
+
"whileFocus",
|
|
71
|
+
"whileDrag"
|
|
72
|
+
];
|
|
73
|
+
const jsBody = (config, path) => jsMotionBody(config, path);
|
|
74
|
+
/** The variant names one state holds: a name, a list of names, or none (values, `false`). */
|
|
75
|
+
function stateVariantNames(value) {
|
|
76
|
+
if (typeof value === "string") return [value];
|
|
77
|
+
if (Array.isArray(value)) return value.filter((name) => typeof name === "string");
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
/** The variants a preset declares. */
|
|
81
|
+
function declaredVariants(body) {
|
|
82
|
+
const variants = body?.variants;
|
|
83
|
+
return variants && typeof variants === "object" ? Object.keys(variants) : [];
|
|
84
|
+
}
|
|
85
|
+
/** Every variant a preset's states name. */
|
|
86
|
+
function namedVariants(body) {
|
|
87
|
+
if (!body) return [];
|
|
88
|
+
return MOTION_VARIANT_STATES.flatMap((state) => stateVariantNames(body[state]));
|
|
89
|
+
}
|
|
90
|
+
/** Every JS preset a style rule puts on a layer, across the system. */
|
|
91
|
+
const placedMotions = createSliceMemo({
|
|
92
|
+
kinds: ["component", "motion"],
|
|
93
|
+
compute: (config) => {
|
|
94
|
+
const out = [];
|
|
95
|
+
for (const component of config.list("component")) {
|
|
96
|
+
const { styles = {} } = component.toJSON();
|
|
97
|
+
for (const [rule, entry] of Object.entries(styles)) for (const [layer, bag] of Object.entries(entry?.layers ?? {})) {
|
|
98
|
+
const named = bag?.motion;
|
|
99
|
+
const preset = isRef(named) ? pathOf(named.__ref) : typeof named === "string" ? named : isInlineMotion(named) ? inlineMotionId(component.path, rule, layer) : void 0;
|
|
100
|
+
if (preset === void 0 || !jsBody(config, preset)) continue;
|
|
101
|
+
out.push({
|
|
102
|
+
component: component.path,
|
|
103
|
+
layer,
|
|
104
|
+
preset
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
/** The components a component's layers render, resolved to the path each lives at. */
|
|
112
|
+
const composedComponents = (config, path) => {
|
|
113
|
+
const { layers = {} } = config.resolve("component", path)?.toJSON() ?? {};
|
|
114
|
+
return Object.values(layers).flatMap((element) => isRef(element) && kindOf(element) === "component" ? [config.resolve("component", pathOf(element))?.path ?? pathOf(element)] : []);
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* The presets that share variant names, each set closed over placement and composition.
|
|
118
|
+
*
|
|
119
|
+
* Two presets on one component's layers are in one set. So is a preset a component places and one a
|
|
120
|
+
* component it renders places, at any depth: the label passes through a component that places nothing
|
|
121
|
+
* on its way down, so the walk goes through those without joining them. A component that places
|
|
122
|
+
* nothing joins nothing, which keeps Button and Checkbox apart although both render Box.
|
|
123
|
+
*/
|
|
124
|
+
const motionVariantGroups = createSliceMemo({
|
|
125
|
+
kinds: ["component", "motion"],
|
|
126
|
+
compute: (config) => {
|
|
127
|
+
const parent = /* @__PURE__ */ new Map();
|
|
128
|
+
const find = (node) => {
|
|
129
|
+
let root = node;
|
|
130
|
+
while (parent.has(root) && parent.get(root) !== root) root = parent.get(root);
|
|
131
|
+
parent.set(node, root);
|
|
132
|
+
return root;
|
|
133
|
+
};
|
|
134
|
+
const join = (a, b) => {
|
|
135
|
+
const ra = find(a);
|
|
136
|
+
const rb = find(b);
|
|
137
|
+
if (ra !== rb) parent.set(ra, rb);
|
|
138
|
+
};
|
|
139
|
+
const placing = /* @__PURE__ */ new Set();
|
|
140
|
+
for (const { component, preset } of placedMotions(config)) {
|
|
141
|
+
placing.add(component);
|
|
142
|
+
join(`c\u0000${component}`, `p\u0000${preset}`);
|
|
143
|
+
}
|
|
144
|
+
for (const component of placing) {
|
|
145
|
+
const seen = new Set([component]);
|
|
146
|
+
const queue = composedComponents(config, component);
|
|
147
|
+
while (queue.length > 0) {
|
|
148
|
+
const next = queue.shift();
|
|
149
|
+
if (seen.has(next)) continue;
|
|
150
|
+
seen.add(next);
|
|
151
|
+
if (placing.has(next)) join(`c\u0000${component}`, `c\u0000${next}`);
|
|
152
|
+
queue.push(...composedComponents(config, next));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const groups = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const node of parent.keys()) {
|
|
157
|
+
if (node[0] !== "p") continue;
|
|
158
|
+
const root = find(node);
|
|
159
|
+
const group = groups.get(root) ?? /* @__PURE__ */ new Set();
|
|
160
|
+
group.add(node.slice(2));
|
|
161
|
+
groups.set(root, group);
|
|
162
|
+
}
|
|
163
|
+
return [...groups.values()];
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
/** The presets that share variant names with `preset` — itself alone when no rule places it. */
|
|
167
|
+
function motionVariantGroupOf(config, preset) {
|
|
168
|
+
return motionVariantGroups(config).find((group) => group.has(preset)) ?? new Set([preset]);
|
|
169
|
+
}
|
|
170
|
+
/** A preset's body with one variant name renamed, in its `variants` and in every state naming it. */
|
|
171
|
+
function renamedBody(body, from, to) {
|
|
172
|
+
const next = { ...body };
|
|
173
|
+
const variants = body.variants;
|
|
174
|
+
if (variants && Object.hasOwn(variants, from)) next.variants = Object.fromEntries(Object.entries(variants).map(([name, value]) => [name === from ? to : name, value]));
|
|
175
|
+
for (const state of MOTION_VARIANT_STATES) {
|
|
176
|
+
const value = body[state];
|
|
177
|
+
if (value === from) next[state] = to;
|
|
178
|
+
else if (Array.isArray(value)) next[state] = value.map((name) => name === from ? to : name);
|
|
179
|
+
}
|
|
180
|
+
return next;
|
|
181
|
+
}
|
|
182
|
+
const mentions = (body, name) => declaredVariants(body).includes(name) || namedVariants(body).includes(name);
|
|
183
|
+
/**
|
|
184
|
+
* Rename a variant across every preset that shares names with `path` ({@link motionVariantGroupOf}):
|
|
185
|
+
* the key in each `variants` that declares it, and each state that names it. Refused when `path` is
|
|
186
|
+
* not a JS preset that declares `from`, or when a preset in the set already declares `to`, since the
|
|
187
|
+
* two would become one variant.
|
|
188
|
+
*/
|
|
189
|
+
function renameMotionVariant(config, { path, from, to }) {
|
|
190
|
+
const own = jsBody(config, path);
|
|
191
|
+
if (!own) reject("NOT_FOUND", `"${path}" is not a JS motion preset, so it has no variants.`);
|
|
192
|
+
if (!declaredVariants(own).includes(from)) reject("NOT_FOUND", `"${path}" declares no variant "${from}".`);
|
|
193
|
+
if (from === to) return config;
|
|
194
|
+
const group = [...motionVariantGroupOf(config, path)];
|
|
195
|
+
const taken = group.filter((preset) => declaredVariants(jsBody(config, preset)).includes(to));
|
|
196
|
+
if (taken.length > 0) reject("ALREADY_EXISTS", `"${to}" is already a variant of ${taken.map((preset) => `"${preset}"`).join(", ")}, which animate${taken.length === 1 ? "s" : ""} with "${path}". Renaming "${from}" onto it would make the two one variant.`);
|
|
197
|
+
let next = config;
|
|
198
|
+
for (const preset of group) {
|
|
199
|
+
const body = jsBody(next, preset);
|
|
200
|
+
if (!body || !mentions(body, from)) continue;
|
|
201
|
+
const inline = inlineMotions(next).valid.find((entry) => entry.id === preset);
|
|
202
|
+
if (!inline) {
|
|
203
|
+
next = next.apply({
|
|
204
|
+
kind: "motion",
|
|
205
|
+
operation: "replace",
|
|
206
|
+
path: preset,
|
|
207
|
+
data: renamedBody(body, from, to)
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const component = next.resolve("component", inline.component)?.toJSON();
|
|
212
|
+
const bag = component?.styles?.[inline.rule]?.layers?.[inline.layer];
|
|
213
|
+
if (!component || !bag || !isInlineMotion(bag.motion)) continue;
|
|
214
|
+
next = next.apply({
|
|
215
|
+
kind: "component",
|
|
216
|
+
operation: "replace",
|
|
217
|
+
path: inline.component,
|
|
218
|
+
data: {
|
|
219
|
+
...component,
|
|
220
|
+
styles: {
|
|
221
|
+
...component.styles,
|
|
222
|
+
[inline.rule]: {
|
|
223
|
+
...component.styles?.[inline.rule],
|
|
224
|
+
layers: {
|
|
225
|
+
...component.styles?.[inline.rule]?.layers,
|
|
226
|
+
[inline.layer]: {
|
|
227
|
+
...bag,
|
|
228
|
+
motion: renamedBody(bag.motion, from, to)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return next;
|
|
237
|
+
}
|
|
238
|
+
const declaresMotionProp = (config, path) => {
|
|
239
|
+
const { props = {} } = config.resolve("component", path)?.toJSON() ?? {};
|
|
240
|
+
return Object.values(props).some((prop) => prop?.type === "motion");
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Every variant a placed preset declares that no state in its set names — what a rename of the name
|
|
244
|
+
* on one side leaves on the other, and what `motion/react` then never animates to, silently.
|
|
245
|
+
*
|
|
246
|
+
* Only presets a style rule places are checked: an unplaced preset's variants may answer whatever the
|
|
247
|
+
* app renders around it, and neither is a set holding a component with a `motion` prop. A placed one
|
|
248
|
+
* may be driven from outside the system too, by an app ancestor animating to the same name, which is
|
|
249
|
+
* why this warns and never refuses.
|
|
250
|
+
*/
|
|
251
|
+
function orphanedMotionVariants(config) {
|
|
252
|
+
const entries = placedMotions(config);
|
|
253
|
+
const placed = new Set(entries.map((entry) => entry.preset));
|
|
254
|
+
const open = new Set(entries.filter((entry) => declaresMotionProp(config, entry.component)).map((entry) => entry.preset));
|
|
255
|
+
const out = [];
|
|
256
|
+
for (const group of motionVariantGroups(config)) {
|
|
257
|
+
if ([...group].some((preset) => open.has(preset))) continue;
|
|
258
|
+
const named = new Set([...group].flatMap((preset) => namedVariants(jsBody(config, preset))));
|
|
259
|
+
for (const preset of group) {
|
|
260
|
+
if (!placed.has(preset)) continue;
|
|
261
|
+
for (const variant of declaredVariants(jsBody(config, preset))) if (!named.has(variant)) out.push({
|
|
262
|
+
preset,
|
|
263
|
+
variant
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
export { MOTION_VARIANT_STATES, declaredVariants, inlineMotionId, inlineMotions, isInlineMotion, jsMotionBody, motionVariantGroups, orphanedMotionVariants, placedMotions, renameMotionVariant, stateVariantNames };
|
|
@@ -72,7 +72,7 @@ interface RecordedSource {
|
|
|
72
72
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
73
73
|
* that replaced it (see `detectedWireVersion`).
|
|
74
74
|
*/
|
|
75
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
75
|
+
declare const SERIALIZED_CONFIG_VERSION = 20261006125609;
|
|
76
76
|
interface SerializedConfig {
|
|
77
77
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
78
78
|
readonly version: number;
|
package/dist/framework/Config.js
CHANGED
|
@@ -5346,7 +5346,7 @@ var Config = class Config {
|
|
|
5346
5346
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5347
5347
|
hydrateFrom(stored, options) {
|
|
5348
5348
|
const claimed = schemaVersionOf(stored);
|
|
5349
|
-
if (claimed >
|
|
5349
|
+
if (claimed > 20261006125609) throw new SchemaVersionTooNew(claimed);
|
|
5350
5350
|
const wire = upgradeSerializedConfig(stored);
|
|
5351
5351
|
if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating, or port the repo once with \`uds migrate\`.`);
|
|
5352
5352
|
const leftover = wire.options;
|
|
@@ -199,6 +199,16 @@ declare function inlineRuleComposites(config: Config, element: unknown, bag: Rec
|
|
|
199
199
|
* nothing until a rebuild, while a prop json-render applies on the spot.
|
|
200
200
|
*/
|
|
201
201
|
declare function layerRoutedProps(config: Config, path: string, state?: Record<string, unknown>): Record<string, Record<string, unknown>>;
|
|
202
|
+
/**
|
|
203
|
+
* The JS preset each layer animates by, for one instance: the `motion` of the last style rule that
|
|
204
|
+
* applies and names a JS preset for the layer, the unconditional rule first and the conditional ones
|
|
205
|
+
* in declaration order, which is the order the emitted module routes them in. A CSS preset is a
|
|
206
|
+
* class and stays with {@link styleRuleMotionClasses}.
|
|
207
|
+
*
|
|
208
|
+
* What lets the canvas play a preset a rule put on a layer before any build: the expansion renders
|
|
209
|
+
* that layer through motion's factory and hands it the preset's props.
|
|
210
|
+
*/
|
|
211
|
+
declare function layerJsMotionPresets(config: Config, path: string, state?: Record<string, unknown>): Record<string, string>;
|
|
202
212
|
/**
|
|
203
213
|
* The CSS MOTION classes a style rule puts on a layer, for one instance.
|
|
204
214
|
*
|
|
@@ -337,4 +347,4 @@ declare function modifierUtilitiesUsed(config: Config): Set<string>;
|
|
|
337
347
|
*/
|
|
338
348
|
declare function splitVariants(cls: string): string[];
|
|
339
349
|
//#endregion
|
|
340
|
-
export { ComponentRuleClass, PickedComposite, SplitStyleProps, arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOwnClasses, styleRuleMotionClasses };
|
|
350
|
+
export { ComponentRuleClass, PickedComposite, SplitStyleProps, arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOwnClasses, styleRuleMotionClasses };
|
|
@@ -4,6 +4,7 @@ import { createSliceMemo } from "./memo.js";
|
|
|
4
4
|
import { classPrefixOf, prefixed, varPrefixOf } from "../entities/system/Settings.js";
|
|
5
5
|
import { storedPreview } from "./preview-store.js";
|
|
6
6
|
import { authoredValue, className, isCustomPropertyKey, negatedCssValue, renderStyleValue, resolveComponentProps, stylePropEntries, stylePropLeafForToken, stylePropValueLeaves, stylePropertyPathFor } from "./projections.js";
|
|
7
|
+
import { inlineMotionId, isInlineMotion, jsMotionBody } from "../entities/system/motion-variants.js";
|
|
7
8
|
import { ruleApplies } from "./layer-styles.js";
|
|
8
9
|
//#region src/framework/class-names.ts
|
|
9
10
|
/**
|
|
@@ -529,6 +530,9 @@ function layerRoutedProps(config, path, state = {}) {
|
|
|
529
530
|
const declared = isRef(element) ? resolveComponentProps(config, pathOf(element.__ref)) : {};
|
|
530
531
|
for (const [prop, value] of Object.entries(bag ?? {})) {
|
|
531
532
|
if (prop.startsWith("_") || isCustomPropertyKey(prop)) continue;
|
|
533
|
+
if (prop === "motion") {
|
|
534
|
+
if ((config.resolve("motion", String(authoredValue(config, prop, value)))?.toJSON())?.runtime !== "css" || !isRef(element)) continue;
|
|
535
|
+
}
|
|
532
536
|
const styleKey = stylePropertyPathFor({
|
|
533
537
|
config,
|
|
534
538
|
key: prop
|
|
@@ -543,6 +547,43 @@ function layerRoutedProps(config, path, state = {}) {
|
|
|
543
547
|
return out;
|
|
544
548
|
}
|
|
545
549
|
/**
|
|
550
|
+
* The JS preset each layer animates by, for one instance: the `motion` of the last style rule that
|
|
551
|
+
* applies and names a JS preset for the layer, the unconditional rule first and the conditional ones
|
|
552
|
+
* in declaration order, which is the order the emitted module routes them in. A CSS preset is a
|
|
553
|
+
* class and stays with {@link styleRuleMotionClasses}.
|
|
554
|
+
*
|
|
555
|
+
* What lets the canvas play a preset a rule put on a layer before any build: the expansion renders
|
|
556
|
+
* that layer through motion's factory and hands it the preset's props.
|
|
557
|
+
*/
|
|
558
|
+
function layerJsMotionPresets(config, path, state = {}) {
|
|
559
|
+
const { styles = {}, defaultProps = {} } = config.resolve("component", path)?.toJSON() ?? {};
|
|
560
|
+
const resolved = {
|
|
561
|
+
...defaultProps,
|
|
562
|
+
...state
|
|
563
|
+
};
|
|
564
|
+
const rules = Object.entries(styles);
|
|
565
|
+
const unconditional = ([, rule]) => !rule.when || Object.keys(rule.when).length === 0;
|
|
566
|
+
const ordered = [...rules.filter(unconditional), ...rules.filter((entry) => !unconditional(entry))];
|
|
567
|
+
const out = {};
|
|
568
|
+
for (const [key, rule] of ordered) {
|
|
569
|
+
if (!ruleApplies(rule.when, resolved)) continue;
|
|
570
|
+
for (const [layer, bag] of Object.entries(rule.layers ?? {})) {
|
|
571
|
+
const named = bag?.motion;
|
|
572
|
+
if (isInlineMotion(named)) {
|
|
573
|
+
const id = inlineMotionId(path, key, layer);
|
|
574
|
+
if (jsMotionBody(config, id)) out[layer] = id;
|
|
575
|
+
else delete out[layer];
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
const preset = isRef(named) ? pathOf(named.__ref) : typeof named === "string" ? named : void 0;
|
|
579
|
+
if (preset === void 0) continue;
|
|
580
|
+
if ((config.resolve("motion", preset)?.toJSON())?.runtime === "js") out[layer] = preset;
|
|
581
|
+
else delete out[layer];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return out;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
546
587
|
* The CSS MOTION classes a style rule puts on a layer, for one instance.
|
|
547
588
|
*
|
|
548
589
|
* A preset's whole runtime is a class too, and a composite's route (forward it, and let the receiver
|
|
@@ -929,4 +970,4 @@ function splitVariants(cls) {
|
|
|
929
970
|
return segments;
|
|
930
971
|
}
|
|
931
972
|
//#endregion
|
|
932
|
-
export { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOwnClasses, styleRuleMotionClasses };
|
|
973
|
+
export { arbitraryClassName, arbitraryNeedsClass, classLeaf, classSelector, componentClassBase, componentCompositeClasses, componentLayerClass, componentMotionClasses, componentPropClasses, componentPropComposites, componentRuleClasses, forceModeAttribute, forceModeAttributeOf, forceModeProp, forceModePropGroup, forceModePropValue, forceModeProps, forceStateAttribute, inlineRuleComposites, isForceModeProp, kebabComponent, layerJsMotionPresets, layerRoutedProps, modifierUtilitiesUsed, motionClassName, ruleCondition, splitStyleProps, splitVariants, stylePropClassBase, stylePropClassName, stylePropClasses, stylePropOwnClasses, styleRuleMotionClasses };
|
|
@@ -159,8 +159,13 @@ declare function defineSubEntity<F extends z.ZodTypeAny>(spec: {
|
|
|
159
159
|
* every way into stored state takes it: `register`, an item's create or update, and op data.
|
|
160
160
|
*
|
|
161
161
|
* Two members with the same key are one member, the later one, as the key derivation on write
|
|
162
|
-
* already treats a record whose keys disagree with their members
|
|
162
|
+
* already treats a record whose keys disagree with their members — unless `merge` says how the two
|
|
163
|
+
* combine, in which case the member is `merge(earlier, later)`, placed where the later one was.
|
|
163
164
|
*/
|
|
164
|
-
declare function orList<F extends z.ZodTypeAny>(collection: SubEntityClass<F
|
|
165
|
+
declare function orList<F extends z.ZodTypeAny>(collection: SubEntityClass<F>, {
|
|
166
|
+
merge
|
|
167
|
+
}?: {
|
|
168
|
+
readonly merge?: (earlier: z.output<F>, later: z.output<F>) => z.output<F>;
|
|
169
|
+
}): z.ZodUnion<readonly [z.ZodPipe<z.ZodArray<F>, z.ZodTransform<Record<string, z.output<F>>, z.core.output<F>[]>>, SubEntityClass<F>]>;
|
|
165
170
|
//#endregion
|
|
166
171
|
export { SubEntityClass, defineSubEntity, orList };
|
|
@@ -124,14 +124,22 @@ function defineSubEntity(spec) {
|
|
|
124
124
|
* every way into stored state takes it: `register`, an item's create or update, and op data.
|
|
125
125
|
*
|
|
126
126
|
* Two members with the same key are one member, the later one, as the key derivation on write
|
|
127
|
-
* already treats a record whose keys disagree with their members
|
|
127
|
+
* already treats a record whose keys disagree with their members — unless `merge` says how the two
|
|
128
|
+
* combine, in which case the member is `merge(earlier, later)`, placed where the later one was.
|
|
128
129
|
*/
|
|
129
|
-
function orList(collection) {
|
|
130
|
+
function orList(collection, { merge } = {}) {
|
|
130
131
|
const key = collection.key;
|
|
131
132
|
if (!key) throw new Error(`orList: "${collection.name}" declares no \`key\`, so a list of its members has nothing to be keyed by.`);
|
|
132
133
|
const listed = z.array(collection.valueType).transform((members) => {
|
|
133
134
|
const keyed = {};
|
|
134
|
-
for (const member of members)
|
|
135
|
+
for (const member of members) {
|
|
136
|
+
const at = key(member);
|
|
137
|
+
const earlier = keyed[at];
|
|
138
|
+
if (earlier !== void 0 && merge) {
|
|
139
|
+
delete keyed[at];
|
|
140
|
+
keyed[at] = merge(earlier, member);
|
|
141
|
+
} else keyed[at] = member;
|
|
142
|
+
}
|
|
135
143
|
return keyed;
|
|
136
144
|
});
|
|
137
145
|
return z.union([listed, collection]);
|
|
@@ -476,6 +476,7 @@ function authoredValue(config, key, value) {
|
|
|
476
476
|
function authoredLeaf(config, key, stored) {
|
|
477
477
|
const path = pathOf(stored);
|
|
478
478
|
if (kindOf(stored) === "token") return stylePropLeafForToken(config, key, path);
|
|
479
|
+
if (kindOf(stored) === "motion") return path;
|
|
479
480
|
const slash = path.indexOf("/");
|
|
480
481
|
return slash === -1 ? path : path.slice(slash + 1);
|
|
481
482
|
}
|
|
@@ -92,6 +92,10 @@ interface ResolveContext {
|
|
|
92
92
|
* one — since it names a derived thing, not an entity.
|
|
93
93
|
*/
|
|
94
94
|
declare function wrappedElementKey(wrapper: string, type: string): string;
|
|
95
|
+
/** The factory a layer renders through when a style rule gives it a JS preset. The registry holds a
|
|
96
|
+
* pair of it with every element a layer renders, when the system has a JS preset (codegen's
|
|
97
|
+
* registry emitter). */
|
|
98
|
+
declare const MOTION_FACTORY = "package:motion/react#motion";
|
|
95
99
|
/**
|
|
96
100
|
* The node a tree ENTERS at — the one nothing else claims as a child.
|
|
97
101
|
*
|
|
@@ -497,4 +501,4 @@ declare function expandSpec(config: Config, spec: RenderSpec, options?: ExpandOp
|
|
|
497
501
|
*/
|
|
498
502
|
declare function expandComponent(config: Config, path: string, props?: Record<string, unknown>, options?: ExpandOptions): ExpansionResult;
|
|
499
503
|
//#endregion
|
|
500
|
-
export { ANATOMY_MARKER, ComponentSpec, ComponentSpecs, ComputedFunctions, ComputedValue, ExpandOptions, ExpansionResult, RenderElement, RenderSpec, RepeatRow, RepeatSpec, ResolveContext, SlotTarget, VisibilityOperator, VisibilityTerm, anatomyElementComponent, anatomyTextProp, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, routesOf, slotTargetsOf, specWithResolvedVisibility, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
504
|
+
export { ANATOMY_MARKER, ComponentSpec, ComponentSpecs, ComputedFunctions, ComputedValue, ExpandOptions, ExpansionResult, MOTION_FACTORY, RenderElement, RenderSpec, RepeatRow, RepeatSpec, ResolveContext, SlotTarget, VisibilityOperator, VisibilityTerm, anatomyElementComponent, anatomyTextProp, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, routesOf, slotTargetsOf, specWithResolvedVisibility, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
@@ -5,9 +5,10 @@ import { unconditionalPreviewProps } from "./preview-store.js";
|
|
|
5
5
|
import { authoredBag, authoredValue, declaredPropDefaults, memberFromLeaf, refListOf, resolveComponentProps, stylePropertyPathFor } from "./projections.js";
|
|
6
6
|
import { slotHostLayer } from "./slot-host.js";
|
|
7
7
|
import { forwardRoutes, forwardedLayers, intoName, isValueSlot, renderBindingsOf, routesContent } from "./prop-surface.js";
|
|
8
|
+
import { inlineMotionProps, jsMotionPresets } from "../entities/system/motion-presets.js";
|
|
8
9
|
import { withRequiredContentHidden } from "../entities/system/required-content-layers.js";
|
|
9
10
|
import { graftPreviewElements } from "../spec/preview-elements.js";
|
|
10
|
-
import { componentLayerClass, componentPropClasses, componentRuleClasses, layerRoutedProps, splitStyleProps, styleRuleMotionClasses } from "./class-names.js";
|
|
11
|
+
import { componentLayerClass, componentPropClasses, componentRuleClasses, layerJsMotionPresets, layerRoutedProps, splitStyleProps, styleRuleMotionClasses } from "./class-names.js";
|
|
11
12
|
//#region src/framework/render-spec.ts
|
|
12
13
|
/** A repeat source that is one prop binding: `{ "$state": "/stops" }`, settled by the cell alone. */
|
|
13
14
|
function isStateSource(source) {
|
|
@@ -49,6 +50,15 @@ function unwrapElementKey(key) {
|
|
|
49
50
|
if (open === -1 || !key.endsWith(")")) return void 0;
|
|
50
51
|
return key.slice(open + 1, -1);
|
|
51
52
|
}
|
|
53
|
+
/** The factory a layer renders through when a style rule gives it a JS preset. The registry holds a
|
|
54
|
+
* pair of it with every element a layer renders, when the system has a JS preset (codegen's
|
|
55
|
+
* registry emitter). */
|
|
56
|
+
const MOTION_FACTORY = "package:motion/react#motion";
|
|
57
|
+
/** Whether a spec type is already a motion element, which animates by its own props, so a rule's
|
|
58
|
+
* preset would animate it twice: `package:motion/react#motion.span`, or a factory-wrapped pair. */
|
|
59
|
+
function isMotionElementType(type) {
|
|
60
|
+
return type.startsWith(`package:motion/react#motion.`) || type.startsWith(`package:motion/react#motion(`);
|
|
61
|
+
}
|
|
52
62
|
/** The type a node renders as, wrapped when the node names a wrapper. */
|
|
53
63
|
function wrappedTypeOf(node, type) {
|
|
54
64
|
if (type === void 0) return void 0;
|
|
@@ -1220,6 +1230,11 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
|
|
|
1220
1230
|
});
|
|
1221
1231
|
const layerProps = instanceProps.layerProps ?? {};
|
|
1222
1232
|
const forwarded = layerRoutedProps(config, path, state);
|
|
1233
|
+
const layerMotions = layerJsMotionPresets(config, path, state);
|
|
1234
|
+
const presetProps = Object.keys(layerMotions).length > 0 ? {
|
|
1235
|
+
...jsMotionPresets(config),
|
|
1236
|
+
...inlineMotionProps(config)
|
|
1237
|
+
} : {};
|
|
1223
1238
|
const entryNode = entryKeyOf(anatomy);
|
|
1224
1239
|
const rootLayer = rootKeyOf(layers);
|
|
1225
1240
|
const hostNode = Object.keys(anatomy).find((key) => layerOf(anatomy[key]?.element) === rootLayer) ?? entryNode;
|
|
@@ -1311,6 +1326,9 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
|
|
|
1311
1326
|
const bound = boundPropOf(node.element);
|
|
1312
1327
|
if (bound) type = boundChoice(config, path, node.element, state[bound]);
|
|
1313
1328
|
type = wrappedTypeOf(node, type);
|
|
1329
|
+
const motionWrapped = type !== void 0 ? wrappedElementKey(MOTION_FACTORY, type) : void 0;
|
|
1330
|
+
const motionPreset = layer !== void 0 && !refOf(node.wrapper) && type !== void 0 && motionWrapped !== void 0 && !isMotionElementType(type) && sink.rendered?.has(motionWrapped) ? layerMotions[layer] : void 0;
|
|
1331
|
+
if (motionPreset !== void 0) type = motionWrapped;
|
|
1314
1332
|
if (!type) {
|
|
1315
1333
|
sink.unresolved.add(bound ? `component:${path}#${key} (${bound})` : `component:${path}#${key}`);
|
|
1316
1334
|
return;
|
|
@@ -1331,6 +1349,7 @@ function expandInstance(sink, path, instanceProps, content, hint, instanceSlots)
|
|
|
1331
1349
|
"data-uds-component": path,
|
|
1332
1350
|
"data-uds-layer": layer,
|
|
1333
1351
|
...forwarded[layer],
|
|
1352
|
+
...motionPreset !== void 0 ? presetProps[motionPreset] : {},
|
|
1334
1353
|
...Object.fromEntries((valueRoutes.get(layer) ?? []).filter(({ from }) => state[from] !== void 0).map(({ from, into }) => [into, state[from]])),
|
|
1335
1354
|
...isRoot ? rest : {},
|
|
1336
1355
|
...slot && slotValue !== void 0 ? { [intoName(slot.into)]: slotValue } : {},
|
|
@@ -1491,4 +1510,4 @@ function expandComponent(config, path, props = {}, options = {}) {
|
|
|
1491
1510
|
return expandSpec(config, instanceSpec(path, props), options);
|
|
1492
1511
|
}
|
|
1493
1512
|
//#endregion
|
|
1494
|
-
export { ANATOMY_MARKER, anatomyElementComponent, anatomyTextProp, andListOf, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, isExpression, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, routesOf, slotAnatomyPropOf, slotTargetsOf, specWithResolvedVisibility, spelled, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
1513
|
+
export { ANATOMY_MARKER, MOTION_FACTORY, anatomyElementComponent, anatomyTextProp, andListOf, boundElementType, componentSpec, deriveSpec, entryKeyOf, expandComponent, expandSpec, instanceSpec, isExpression, layerTextProp, liveStateProps, previewSpec, propInto, propsIntoLayer, readStatePointer, resolveValue, resolveVisibility, routeOf, routesOf, slotAnatomyPropOf, slotTargetsOf, specWithResolvedVisibility, spelled, statePointerProp, valueForwardsOf, visibilityStateProps, visibilityTerms, withoutTruthyTerm, wrappedElementKey };
|
|
@@ -15,7 +15,7 @@ import { Patch, SerializedConfig } from "./Config.js";
|
|
|
15
15
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
16
16
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
17
17
|
*/
|
|
18
|
-
declare const CURRENT_SCHEMA_VERSION =
|
|
18
|
+
declare const CURRENT_SCHEMA_VERSION = 20261006125609;
|
|
19
19
|
/** What the walkers need to see of an envelope — loose on purpose, so a typed `SerializedConfig`
|
|
20
20
|
* and a raw old-shape record both flow in without casts. */
|
|
21
21
|
interface VersionStamped {
|
|
@@ -13,7 +13,7 @@ import { ConfigFormatError } from "./rejection.js";
|
|
|
13
13
|
* sets this constant to its own older timestamp; the merge ref then carries a registered migration
|
|
14
14
|
* newer than the constant, and the assertion fails until the author re-mints.
|
|
15
15
|
*/
|
|
16
|
-
const CURRENT_SCHEMA_VERSION =
|
|
16
|
+
const CURRENT_SCHEMA_VERSION = 20261006125609;
|
|
17
17
|
/** The mint that moved `options` into the `settings` kind — restated here, rather than imported
|
|
18
18
|
* from the migration that owns it, because that migration imports this module. */
|
|
19
19
|
const SETTINGS_AS_KIND_VERSION = 20260908171448;
|
|
@@ -45,7 +45,7 @@ function registerSchemaMigrations(...migrations) {
|
|
|
45
45
|
for (const migration of migrations) {
|
|
46
46
|
if (migration.version !== 1 && !isMintTimestamp(migration.version)) throw new Error(`Schema migration version ${migration.version} is not a mint timestamp — mint one with \`date +%Y%m%d%H%M%S\`, like a supabase migration.`);
|
|
47
47
|
if (REGISTERED.has(migration.version)) throw new Error(`A schema migration at version ${migration.version} is already registered.`);
|
|
48
|
-
if (migration.version >
|
|
48
|
+
if (migration.version > 20261006125609) throw new Error(`Schema migration ${migration.version} is newer than CURRENT_SCHEMA_VERSION (${CURRENT_SCHEMA_VERSION}) — a stale branch merged, or the mint forgot to bump the constant. Re-mint: set CURRENT_SCHEMA_VERSION to the newest migration version.`);
|
|
49
49
|
REGISTERED.set(migration.version, migration);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -102,7 +102,7 @@ function pendingFor(version, target, options) {
|
|
|
102
102
|
* Already-current input passes through untouched — same reference, so hot paths pay nothing.
|
|
103
103
|
*/
|
|
104
104
|
function upgradeSerializedConfig(json, options) {
|
|
105
|
-
const target = options?.target ??
|
|
105
|
+
const target = options?.target ?? 20261006125609;
|
|
106
106
|
const version = detectedWireVersion(json);
|
|
107
107
|
if (version > target) throw new SchemaVersionTooNew(version, target);
|
|
108
108
|
const raw = json;
|
|
@@ -160,7 +160,7 @@ function upgradedSources(json, options) {
|
|
|
160
160
|
* meaning under the current schema and replay skips it — reported by the caller, not swallowed here.
|
|
161
161
|
*/
|
|
162
162
|
function upgradePatch(patch, writtenAt, options) {
|
|
163
|
-
const target = options?.target ??
|
|
163
|
+
const target = options?.target ?? 20261006125609;
|
|
164
164
|
if (writtenAt > target) throw new SchemaVersionTooNew(writtenAt, target);
|
|
165
165
|
let current = patch;
|
|
166
166
|
for (const migration of pendingFor(writtenAt, target, options)) {
|
|
@@ -179,7 +179,7 @@ function upgradePatch(patch, writtenAt, options) {
|
|
|
179
179
|
* stamp would cause.
|
|
180
180
|
*/
|
|
181
181
|
function upgradeDraftEntries(draft, options) {
|
|
182
|
-
const target = options?.target ??
|
|
182
|
+
const target = options?.target ?? 20261006125609;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|