@yahoo/uds-create-config 5.56.4 → 5.57.1
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 +2 -0
- package/dist/entities/system/LinkedSystem.js +2 -2
- package/dist/entities/system/Token.js +1 -1
- package/dist/entities/system/composed-values.d.ts +2 -1
- package/dist/entities/system/composed-values.js +7 -0
- package/dist/entities/system/copy-plan.js +55 -7
- package/dist/entities/system/link-plan.d.ts +3 -0
- package/dist/entities/system/link-plan.js +24 -0
- package/dist/entities/system/timing.d.ts +28 -0
- package/dist/entities/system/timing.js +56 -0
- package/dist/framework/Config.d.ts +49 -1
- package/dist/framework/Config.js +98 -12
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/migrations/20261026125912_timing_value.d.ts +6 -0
- package/dist/migrations/20261026125912_timing_value.js +8 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/configs/system.js
CHANGED
|
@@ -30,6 +30,7 @@ import { directLayerCompositesMigration } from "../migrations/20261022124223_dir
|
|
|
30
30
|
import { cssContentScriptsMigration } from "../migrations/20261022152120_css_content_scripts.js";
|
|
31
31
|
import { linkTypeMigration } from "../migrations/20261022184639_link_type.js";
|
|
32
32
|
import { conditionalDefaultsMigration } from "../migrations/20261023152030_conditional_defaults.js";
|
|
33
|
+
import { timingValueMigration } from "../migrations/20261026125912_timing_value.js";
|
|
33
34
|
import { CanvasRole } from "../entities/system/CanvasRole.js";
|
|
34
35
|
import { GlobalStyle } from "../entities/system/GlobalStyle.js";
|
|
35
36
|
import { Guidance } from "../entities/system/Guidance.js";
|
|
@@ -193,5 +194,6 @@ if (!registeredSchemaMigrations().has(20261020183330)) {
|
|
|
193
194
|
if (!registeredSchemaMigrations().has(20261022152120)) registerSchemaMigrations(cssContentScriptsMigration);
|
|
194
195
|
if (!registeredSchemaMigrations().has(20261022184639)) registerSchemaMigrations(linkTypeMigration);
|
|
195
196
|
if (!registeredSchemaMigrations().has(20261023152030)) registerSchemaMigrations(conditionalDefaultsMigration);
|
|
197
|
+
if (!registeredSchemaMigrations().has(20261026125912)) registerSchemaMigrations(timingValueMigration);
|
|
196
198
|
//#endregion
|
|
197
199
|
export { System };
|
|
@@ -352,7 +352,7 @@ const LinkedSystemDefinition = defineEntity({
|
|
|
352
352
|
input: unlinkInput,
|
|
353
353
|
readOnly: false,
|
|
354
354
|
label: "Unlink from",
|
|
355
|
-
description: "Stop following a linked system's entities, in `link`'s shape or everything when `data` is omitted. The rows the declaration minted go with it, and so do the linked rows that point at them, since left linked they would bring them back; nothing of yours moves. A row of yours pointing at one of theirs refuses this — `copy` what is used first, or `break` with `keep: true`. `unlink-plan` names everything that goes.",
|
|
355
|
+
description: "Stop following a linked system's entities, in `link`'s shape or everything when `data` is omitted. The rows the declaration minted go with it, and so do the linked rows that point at them, since left linked they would bring them back, and the pin's `options` entries left with no row to adjust; nothing of yours moves. A row of yours pointing at one of theirs refuses this — `copy` what is used first, or `break` with `keep: true`. `unlink-plan` names everything that goes.",
|
|
356
356
|
scope: "item",
|
|
357
357
|
approval: "reference-change",
|
|
358
358
|
title: (entity) => `Unlink from ${entity}`,
|
|
@@ -374,7 +374,7 @@ const LinkedSystemDefinition = defineEntity({
|
|
|
374
374
|
input: unlinkInput,
|
|
375
375
|
readOnly: true,
|
|
376
376
|
label: "Plan an unlink",
|
|
377
|
-
description: "What `unlink` with the same input removes — the minted rows the narrower declaration no longer reaches, with the linked rows taken along because they point at them named in `cascade` — and why the config would refuse it, a collision it would leave among them. Writes nothing.",
|
|
377
|
+
description: "What `unlink` with the same input removes — the minted rows the narrower declaration no longer reaches, with the linked rows taken along because they point at them named in `cascade`, and the `options` entries of the pin that go with them in `dropped` — and why the config would refuse it, a collision it would leave among them. Writes nothing.",
|
|
378
378
|
scope: "item",
|
|
379
379
|
title: (entity) => `Plan unlinking from ${entity}`,
|
|
380
380
|
example: pinExample,
|
|
@@ -33,7 +33,7 @@ const tokenValue = z.union([
|
|
|
33
33
|
z.lazy(() => Token),
|
|
34
34
|
Font,
|
|
35
35
|
...composedValueSchemas
|
|
36
|
-
], { error: "a token's `value` is one CSS value as a string (`#1DB954`, `4px`, `\"Circular\", sans-serif`), a number, a ref to another token or to a font (`{ \"__ref\": \"font:<name>\" }`), a derived color (`{ \"op\": \"darken\", \"color\": { \"__ref\": \"token:color/brand\" }, \"amount\": 10 }`), a gradient,
|
|
36
|
+
], { error: "a token's `value` is one CSS value as a string (`#1DB954`, `4px`, `\"Circular\", sans-serif`), a number, a ref to another token or to a font (`{ \"__ref\": \"font:<name>\" }`), a derived color (`{ \"op\": \"darken\", \"color\": { \"__ref\": \"token:color/brand\" }, \"amount\": 10 }`), a gradient, a transition (`{ \"transition\": [{ \"properties\": [\"background-color\"], \"timing\": { \"__ref\": \"token:motion/color\" } }] }`), or a timing (`{ \"timing\": { \"duration\": { \"__ref\": \"token:motionDuration/150\" }, \"easing\": { \"__ref\": \"token:motionEasing/linear\" } } }`). It is required; a font family is a string value or a `font` entity, never a field of its own." });
|
|
37
37
|
/**
|
|
38
38
|
* What an override applies under: one modifier, or several that all hold at once (`dark` and `lg`
|
|
39
39
|
* together). Each is a ref, so the override is an ordinary edge: the dependency graph sees every
|
|
@@ -3,10 +3,11 @@ import { Add, Divide, Max, Min, Multiply, Subtract } from "./calc.js";
|
|
|
3
3
|
import { DerivedColor, Gradient, OklchColor } from "./color.js";
|
|
4
4
|
import { Shadow } from "./shadow.js";
|
|
5
5
|
import { Transition } from "./transition.js";
|
|
6
|
+
import { Timing } from "./timing.js";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
|
|
8
9
|
//#region src/entities/system/composed-values.d.ts
|
|
9
|
-
type ComposedValue = Multiply | Divide | Add | Subtract | Max | Min | OklchColor | DerivedColor | Gradient | Transition | Shadow;
|
|
10
|
+
type ComposedValue = Multiply | Divide | Add | Subtract | Max | Min | OklchColor | DerivedColor | Gradient | Transition | Timing | Shadow;
|
|
10
11
|
/** How the write engine spells a stored value: the helper's name and its positional arguments. */
|
|
11
12
|
interface ComposedValueAuthoring {
|
|
12
13
|
readonly helper: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { addAuthoring, addValue, calcOperands, divideAuthoring, divideValue, evaluateCalc, isAdd, isCalc, isDivide, isMax, isMin, isMultiply, isSubtract, maxAuthoring, maxValue, minAuthoring, minValue, multiplyAuthoring, multiplyValue, renderCalc, subtractAuthoring, subtractValue } from "./calc.js";
|
|
2
2
|
import { derivedColor, derivedColorAuthoring, gradient, gradientAuthoring, isDerivedColor, isGradient, isOklchColor, oklchAuthoring, oklchColor, renderDerivedColor, renderGradient, renderOklch } from "./color.js";
|
|
3
3
|
import { isShadow, renderShadow, shadowAuthoring, shadowValue } from "./shadow.js";
|
|
4
|
+
import { isTiming, renderTiming, timingAuthoring, timingValue } from "./timing.js";
|
|
4
5
|
import { isTransition, renderTransition, transitionAuthoring, transitionValue } from "./transition.js";
|
|
5
6
|
//#region src/entities/system/composed-values.ts
|
|
6
7
|
/** Arithmetic as a formula: `spacing × 4`, `1.25 ÷ 0.875`, `gap + (spacing × 2)`. A term that is
|
|
@@ -102,6 +103,12 @@ const composedValueForms = [
|
|
|
102
103
|
render: renderTransition,
|
|
103
104
|
authoring: transitionAuthoring
|
|
104
105
|
}),
|
|
106
|
+
form({
|
|
107
|
+
schema: timingValue,
|
|
108
|
+
is: isTiming,
|
|
109
|
+
render: renderTiming,
|
|
110
|
+
authoring: timingAuthoring
|
|
111
|
+
}),
|
|
105
112
|
form({
|
|
106
113
|
schema: shadowValue,
|
|
107
114
|
is: isShadow,
|
|
@@ -96,10 +96,20 @@ function planCopy({ config, input }) {
|
|
|
96
96
|
const taking = [];
|
|
97
97
|
const folders = [];
|
|
98
98
|
const reused = [];
|
|
99
|
+
/** Folders this config draws through another pin, which a copied ref binds to as it does a reused row. */
|
|
100
|
+
const heldFolders = /* @__PURE__ */ new Set();
|
|
99
101
|
/** Every `kind:path` `take` has answered, whichever way, so the walk below visits each once. */
|
|
100
102
|
const seen = /* @__PURE__ */ new Set();
|
|
101
103
|
const declared = declarationOf(config, slug);
|
|
102
104
|
const folder = (kind, path) => {
|
|
105
|
+
const through = config.groupBorrowedFrom({
|
|
106
|
+
kind,
|
|
107
|
+
groupPath: path
|
|
108
|
+
});
|
|
109
|
+
if (through !== void 0 && through !== slug && input.onConflict !== "replace") {
|
|
110
|
+
heldFolders.add(`${kind}:${path}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
103
113
|
if (!folders.some((f) => f.kind === kind && f.path === path)) folders.push({
|
|
104
114
|
kind,
|
|
105
115
|
path
|
|
@@ -258,6 +268,32 @@ function planCopy({ config, input }) {
|
|
|
258
268
|
}
|
|
259
269
|
}
|
|
260
270
|
if (taking.length === 0 && wholesale) rejectData(`"${slug}" has nothing to copy.`);
|
|
271
|
+
/**
|
|
272
|
+
* The files of a sibling a copied render imports, when the sibling itself isn't copied — this config
|
|
273
|
+
* holds that path already, through another pin or as its own. The render imports the source's
|
|
274
|
+
* module, not whatever answers the path here, so that module lands without the component, and so do
|
|
275
|
+
* the files of the siblings it imports in turn. A copied sibling brings its own files.
|
|
276
|
+
*/
|
|
277
|
+
const siblingFiles = ({ sibling, component }) => {
|
|
278
|
+
const key = `files:${sibling}`;
|
|
279
|
+
if (seen.has(key)) return;
|
|
280
|
+
seen.add(key);
|
|
281
|
+
if (taking.some((row) => row.kind === "component" && row.at === sibling)) return;
|
|
282
|
+
for (const path of renderSourceFiles({
|
|
283
|
+
source,
|
|
284
|
+
path: sibling
|
|
285
|
+
})) takeFile({
|
|
286
|
+
path,
|
|
287
|
+
component
|
|
288
|
+
});
|
|
289
|
+
for (const next of renderImportedComponents({
|
|
290
|
+
source,
|
|
291
|
+
path: sibling
|
|
292
|
+
})) siblingFiles({
|
|
293
|
+
sibling: next,
|
|
294
|
+
component
|
|
295
|
+
});
|
|
296
|
+
};
|
|
261
297
|
/** Take what `body` points at by ref. */
|
|
262
298
|
const follow = (body) => {
|
|
263
299
|
for (const target of collectRefs(body)) {
|
|
@@ -318,12 +354,18 @@ function planCopy({ config, input }) {
|
|
|
318
354
|
for (const sibling of renderImportedComponents({
|
|
319
355
|
source,
|
|
320
356
|
path: row.theirs
|
|
321
|
-
}))
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
357
|
+
})) {
|
|
358
|
+
take({
|
|
359
|
+
kind: "component",
|
|
360
|
+
theirs: sibling,
|
|
361
|
+
at: sibling,
|
|
362
|
+
named: false
|
|
363
|
+
});
|
|
364
|
+
siblingFiles({
|
|
365
|
+
sibling,
|
|
366
|
+
component: row.theirs
|
|
367
|
+
});
|
|
368
|
+
}
|
|
327
369
|
for (const path of renderSourceFiles({
|
|
328
370
|
source,
|
|
329
371
|
path: row.theirs
|
|
@@ -336,11 +378,17 @@ function planCopy({ config, input }) {
|
|
|
336
378
|
const arriving = new Set([
|
|
337
379
|
...taking.map((row) => `${row.kind}:${row.at}`),
|
|
338
380
|
...reused.map((row) => `${row.kind}:${row.path}`),
|
|
381
|
+
...heldFolders,
|
|
339
382
|
...folders.map((folder) => `${folder.kind}:${folder.path}`)
|
|
340
383
|
]);
|
|
341
384
|
const patches = [];
|
|
342
385
|
for (const { kind, path } of folders) {
|
|
343
|
-
const meta = ownMeta(
|
|
386
|
+
const meta = ownMeta(config.copyOfGroup({
|
|
387
|
+
slug,
|
|
388
|
+
kind,
|
|
389
|
+
path,
|
|
390
|
+
alongside: arriving
|
|
391
|
+
}) ?? {});
|
|
344
392
|
const pointed = config.groupBorrowedFrom({
|
|
345
393
|
kind,
|
|
346
394
|
groupPath: path
|
|
@@ -64,6 +64,9 @@ interface UnlinkPlan {
|
|
|
64
64
|
/** Linked rows unlinked with the named ones because they point at them: left linked, they would
|
|
65
65
|
* bring the named rows back on the next derive. */
|
|
66
66
|
readonly cascade: readonly EntityAddress[];
|
|
67
|
+
/** The pin's `options` entries that go with the unlink: one on a path that stops deriving, and one
|
|
68
|
+
* on a path drawn from elsewhere that names a row that goes. Neither has a row to adjust. */
|
|
69
|
+
readonly dropped: readonly EntityAddress[];
|
|
67
70
|
/** Why the config would refuse. `undefined` when it would go through. */
|
|
68
71
|
readonly blocked?: string;
|
|
69
72
|
/** The paths the unlink would leave two linked systems offering as different entities, when that
|
|
@@ -329,6 +329,7 @@ function planUnlink({ config, input }) {
|
|
|
329
329
|
declared: void 0,
|
|
330
330
|
removed: [],
|
|
331
331
|
cascade: [],
|
|
332
|
+
dropped: [],
|
|
332
333
|
patches: []
|
|
333
334
|
};
|
|
334
335
|
let declared;
|
|
@@ -397,16 +398,39 @@ function planUnlink({ config, input }) {
|
|
|
397
398
|
}
|
|
398
399
|
if (input.data !== void 0 && removed.length === 0) blocked = stillReached({ slug });
|
|
399
400
|
}
|
|
401
|
+
const dropped = after ? deadDirectives({
|
|
402
|
+
config,
|
|
403
|
+
after,
|
|
404
|
+
slug,
|
|
405
|
+
removed
|
|
406
|
+
}) : [];
|
|
407
|
+
if (dropped.length > 0) {
|
|
408
|
+
const update = patches[patches.length - 1];
|
|
409
|
+
if (dropped.length === config.sourceDirectives(slug).length) update.data.options = null;
|
|
410
|
+
else for (const { accessor, path } of dropped) update.data[`options.${accessor}.${path}`] = null;
|
|
411
|
+
}
|
|
400
412
|
return {
|
|
401
413
|
declared,
|
|
402
414
|
removed,
|
|
403
415
|
cascade,
|
|
416
|
+
dropped: dropped.map(({ kind, path }) => ({
|
|
417
|
+
kind,
|
|
418
|
+
path
|
|
419
|
+
})),
|
|
404
420
|
...blocked === void 0 ? {} : { blocked },
|
|
405
421
|
...refused.collisions ? { collisions: refused.collisions } : {},
|
|
406
422
|
patches
|
|
407
423
|
};
|
|
408
424
|
}
|
|
409
425
|
/**
|
|
426
|
+
* The pin's directives an unlink leaves with nothing to do: on a path that no longer derives through
|
|
427
|
+
* the pin, and either on a row the unlink retires or naming one.
|
|
428
|
+
*/
|
|
429
|
+
function deadDirectives({ config, after, slug, removed }) {
|
|
430
|
+
const gone = new Set(removed.map(({ kind, path }) => `${kind}:${path}`));
|
|
431
|
+
return config.sourceDirectives(slug).filter((directive) => directive.kind !== void 0 && after.resolve(directive.kind, directive.path)?.borrowedFrom !== slug && (gone.has(`${directive.kind}:${directive.path}`) || collectRefs(config.sourceOverlay(slug, directive.kind, directive.path)).some((target) => gone.has(target))));
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
410
434
|
* Leave paths of a linked system out, with a `!` entry each in the pin's `link`. Stronger than
|
|
411
435
|
* `unlink`: nothing derives there however the link reaches it, a linked token naming it included,
|
|
412
436
|
* and the linked tokens' overrides under an excluded modifier are left out of the build. `link` with
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TimingValue } from "./transition.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/entities/system/timing.d.ts
|
|
5
|
+
/** Each part is a literal (`'150ms'`, `'ease-out'`) or a ref to the token that names it. */
|
|
6
|
+
interface TimingParts {
|
|
7
|
+
readonly duration: TimingValue;
|
|
8
|
+
readonly easing: TimingValue;
|
|
9
|
+
readonly delay?: TimingValue;
|
|
10
|
+
}
|
|
11
|
+
interface Timing {
|
|
12
|
+
readonly timing: TimingParts;
|
|
13
|
+
}
|
|
14
|
+
/** Whether a value is a composed timing — the test the renderers branch on. A transition segment's
|
|
15
|
+
* own `timing` field is a literal or a ref, so it is not one. */
|
|
16
|
+
declare function isTiming(value: unknown): value is Timing;
|
|
17
|
+
/** `timing({ duration: Token.ref('motionDuration/150'), easing: Token.ref('motionEasing/linear') })`. */
|
|
18
|
+
declare function timing(parts: TimingParts): Timing;
|
|
19
|
+
/**
|
|
20
|
+
* A timing as CSS — `var(--uds-motion-duration-150) var(--uds-motion-easing-linear)`, in the order
|
|
21
|
+
* the `transition` shorthand reads it: the first time is the duration and the second the delay.
|
|
22
|
+
*
|
|
23
|
+
* `render` resolves the parts. It is passed in rather than imported because resolving a ref to a
|
|
24
|
+
* `var()` is the projection layer's job and this module is the value's shape.
|
|
25
|
+
*/
|
|
26
|
+
declare function renderTiming(value: Timing, render: (part: TimingValue) => string): string;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { Timing, TimingParts, isTiming, renderTiming, timing };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isRef, refSchema } from "../../framework/utils/refs.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/entities/system/timing.ts
|
|
4
|
+
/**
|
|
5
|
+
* A transition timing composed from a duration and an easing —
|
|
6
|
+
* `timing({ duration: Token.ref('motionDuration/150'), easing: Token.ref('motionEasing/linear') })`.
|
|
7
|
+
*
|
|
8
|
+
* A motion role (`motion/color`) is a duration and an easing that a system names together, and each
|
|
9
|
+
* is its own token. Spelled as one string, the pair is copied in, or names the variables as text,
|
|
10
|
+
* which a link to a system with another prefix leaves pointing at nothing. Held as parts, each is a
|
|
11
|
+
* ref, so it renders as that token's `var()` and the dependency graph sees it.
|
|
12
|
+
*
|
|
13
|
+
* Usable wherever a style value is, and as the `timing` a transition's segment refers to.
|
|
14
|
+
*/
|
|
15
|
+
const timingPart = z.union([z.string(), refSchema]);
|
|
16
|
+
const timingValue = z.strictObject({ timing: z.strictObject({
|
|
17
|
+
duration: timingPart,
|
|
18
|
+
easing: timingPart,
|
|
19
|
+
delay: timingPart.optional()
|
|
20
|
+
}) });
|
|
21
|
+
const isPart = (part) => typeof part === "string" || isRef(part);
|
|
22
|
+
/** Whether a value is a composed timing — the test the renderers branch on. A transition segment's
|
|
23
|
+
* own `timing` field is a literal or a ref, so it is not one. */
|
|
24
|
+
function isTiming(value) {
|
|
25
|
+
if (typeof value !== "object" || value === null) return false;
|
|
26
|
+
const parts = value.timing;
|
|
27
|
+
return typeof parts === "object" && parts !== null && isPart(parts.duration) && isPart(parts.easing) && (parts.delay === void 0 || isPart(parts.delay));
|
|
28
|
+
}
|
|
29
|
+
/** `timing({ duration: Token.ref('motionDuration/150'), easing: Token.ref('motionEasing/linear') })`. */
|
|
30
|
+
function timing(parts) {
|
|
31
|
+
return { timing: parts };
|
|
32
|
+
}
|
|
33
|
+
/** The `timing(...)` call that authors a stored value: its parts are the argument. */
|
|
34
|
+
function timingAuthoring(value) {
|
|
35
|
+
return {
|
|
36
|
+
helper: "timing",
|
|
37
|
+
args: [value.timing]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A timing as CSS — `var(--uds-motion-duration-150) var(--uds-motion-easing-linear)`, in the order
|
|
42
|
+
* the `transition` shorthand reads it: the first time is the duration and the second the delay.
|
|
43
|
+
*
|
|
44
|
+
* `render` resolves the parts. It is passed in rather than imported because resolving a ref to a
|
|
45
|
+
* `var()` is the projection layer's job and this module is the value's shape.
|
|
46
|
+
*/
|
|
47
|
+
function renderTiming(value, render) {
|
|
48
|
+
const { duration, easing, delay } = value.timing;
|
|
49
|
+
return [
|
|
50
|
+
duration,
|
|
51
|
+
easing,
|
|
52
|
+
delay
|
|
53
|
+
].filter((part) => part !== void 0).map(render).join(" ");
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { isTiming, renderTiming, timing, timingAuthoring, timingValue };
|
|
@@ -73,7 +73,7 @@ interface RecordedSource {
|
|
|
73
73
|
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
74
74
|
* that replaced it (see `detectedWireVersion`).
|
|
75
75
|
*/
|
|
76
|
-
declare const SERIALIZED_CONFIG_VERSION =
|
|
76
|
+
declare const SERIALIZED_CONFIG_VERSION = 20261026125912;
|
|
77
77
|
interface SerializedConfig {
|
|
78
78
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
79
79
|
readonly version: number;
|
|
@@ -1942,6 +1942,42 @@ declare class Config {
|
|
|
1942
1942
|
alongside?: ReadonlySet<string>;
|
|
1943
1943
|
extend?: Record<string, unknown>;
|
|
1944
1944
|
}): Record<string, unknown> | undefined;
|
|
1945
|
+
/**
|
|
1946
|
+
* One of a source's folders' meta as a copy stores it — its refs spelled from here as
|
|
1947
|
+
* {@link copyOf} spells a body's. The meta as the source holds it names its own pins by their slugs
|
|
1948
|
+
* there: the Bridge's `icons` folder names `package:@yahoo/uds-icons@yos`, and stored as it was that
|
|
1949
|
+
* names a pin this config doesn't have. `undefined` when the source has no folder there.
|
|
1950
|
+
*/
|
|
1951
|
+
copyOfGroup({
|
|
1952
|
+
slug,
|
|
1953
|
+
kind,
|
|
1954
|
+
path,
|
|
1955
|
+
alongside
|
|
1956
|
+
}: {
|
|
1957
|
+
slug: string;
|
|
1958
|
+
kind: string;
|
|
1959
|
+
path: string; /** `kind:path` targets landing as this config's own in the same edit. */
|
|
1960
|
+
alongside?: ReadonlySet<string>;
|
|
1961
|
+
}): Record<string, unknown> | undefined;
|
|
1962
|
+
/**
|
|
1963
|
+
* `body` with each ref routed through another system's pin (`@yos-v3~yos`) respelled through this
|
|
1964
|
+
* config's own pin to the same system, where that pin's source holds the target too: a copy then
|
|
1965
|
+
* names what this config reaches directly rather than keeping the pin it came through referenced.
|
|
1966
|
+
* Bare when this config already holds the target as a pointer through that pin.
|
|
1967
|
+
*/
|
|
1968
|
+
private throughDirectPins;
|
|
1969
|
+
/**
|
|
1970
|
+
* The pin of this config's own that reaches the system a routed source ends at — `yahoo-os` for
|
|
1971
|
+
* `yos-v3~yos`, when this config pins directly what `yos-v3` pins as `yos`. Matched on the pin
|
|
1972
|
+
* kind's `sourceOrigin`, so the two pins may name different versions. `undefined` for a source of
|
|
1973
|
+
* one hop, or one whose system no pin here reaches.
|
|
1974
|
+
*
|
|
1975
|
+
* The two pins match when they agree on every origin field both set, and share at least one. Not on
|
|
1976
|
+
* the whole key: a pin by path is stamped with its sibling's id when it's pushed, so the Bridge's
|
|
1977
|
+
* `yos` reads `configPath` and `systemId` where this config's pin to the same system names only the
|
|
1978
|
+
* id — and a `configPath` is relative to the config holding it, so it can't be compared across two.
|
|
1979
|
+
*/
|
|
1980
|
+
private directRoute;
|
|
1945
1981
|
/**
|
|
1946
1982
|
* Whether a copied row no longer matches what its source offers at the path it was copied from.
|
|
1947
1983
|
*
|
|
@@ -2255,6 +2291,16 @@ declare class Config {
|
|
|
2255
2291
|
* that reaches derivation — a deleted extension whose source reached the row — says what it did.
|
|
2256
2292
|
*/
|
|
2257
2293
|
private assertDerivedDropsUnreferenced;
|
|
2294
|
+
/**
|
|
2295
|
+
* Whether every directive of `pin` that names `target` sits on a path that no longer derives through
|
|
2296
|
+
* it — retired by this derivation, or drawn from another source already. Such a directive lays its
|
|
2297
|
+
* value over no row, so it goes with the link rather than being stranded by it: a pin's own
|
|
2298
|
+
* `options` never refuse removing its link. A directive on a row the pin still mints does, since
|
|
2299
|
+
* that row would be left pointing at nothing.
|
|
2300
|
+
*
|
|
2301
|
+
* `false` when `pin` is not a source, or names `target` somewhere other than a directive.
|
|
2302
|
+
*/
|
|
2303
|
+
private directivesGoWith;
|
|
2258
2304
|
/**
|
|
2259
2305
|
* This config with a row for every entity it inherits — the build's output. A pin patch runs this
|
|
2260
2306
|
* inside {@link apply}; a source attaching runs it through the session.
|
|
@@ -2513,6 +2559,8 @@ declare class Config {
|
|
|
2513
2559
|
*/
|
|
2514
2560
|
sourceIdentityKey(kind: string, path: string): string | undefined;
|
|
2515
2561
|
private sourceKey;
|
|
2562
|
+
/** Each of a pin's `of` fields as it reads, `''` where unset, in the kind's order. */
|
|
2563
|
+
private sourceFields;
|
|
2516
2564
|
/**
|
|
2517
2565
|
* Where what this config shows at `path` originates: the system it was first authored in, followed
|
|
2518
2566
|
* through however many pins it took to get here. `undefined` for a row this config authored itself.
|
package/dist/framework/Config.js
CHANGED
|
@@ -3935,25 +3935,85 @@ var Config = class Config {
|
|
|
3935
3935
|
path,
|
|
3936
3936
|
derives
|
|
3937
3937
|
});
|
|
3938
|
-
if (!theirs
|
|
3939
|
-
const laid = overlayBody({
|
|
3938
|
+
if (!theirs) return void 0;
|
|
3939
|
+
const laid = extend ? overlayBody({
|
|
3940
3940
|
overlay: {
|
|
3941
3941
|
extend,
|
|
3942
3942
|
hidden: false
|
|
3943
3943
|
},
|
|
3944
3944
|
theirs
|
|
3945
|
-
});
|
|
3946
|
-
|
|
3945
|
+
}) : void 0;
|
|
3946
|
+
return this.throughDirectPins(laid ? this.laidLinkedBody({
|
|
3947
3947
|
kind,
|
|
3948
3948
|
path,
|
|
3949
3949
|
body: theirs,
|
|
3950
3950
|
extend: laid,
|
|
3951
3951
|
source: this.sourceFor(slug)
|
|
3952
|
-
});
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3952
|
+
}) : theirs);
|
|
3953
|
+
}
|
|
3954
|
+
/**
|
|
3955
|
+
* One of a source's folders' meta as a copy stores it — its refs spelled from here as
|
|
3956
|
+
* {@link copyOf} spells a body's. The meta as the source holds it names its own pins by their slugs
|
|
3957
|
+
* there: the Bridge's `icons` folder names `package:@yahoo/uds-icons@yos`, and stored as it was that
|
|
3958
|
+
* names a pin this config doesn't have. `undefined` when the source has no folder there.
|
|
3959
|
+
*/
|
|
3960
|
+
copyOfGroup({ slug, kind, path, alongside }) {
|
|
3961
|
+
const source = this.sourceFor(slug);
|
|
3962
|
+
const meta = source?.groups(kind).find((group) => group.path === path)?.meta;
|
|
3963
|
+
if (!source || !meta) return void 0;
|
|
3964
|
+
return this.throughDirectPins(qualifyRefs({
|
|
3965
|
+
value: meta,
|
|
3966
|
+
source: slug,
|
|
3967
|
+
owns: (kindName) => source.ownedKinds().includes(kindName),
|
|
3968
|
+
namesItAlready: (target) => alongside?.has(`${kindOf(target)}:${pathOf(target)}`) === true || this.borrowsExactly(target, slug)
|
|
3969
|
+
}));
|
|
3970
|
+
}
|
|
3971
|
+
/**
|
|
3972
|
+
* `body` with each ref routed through another system's pin (`@yos-v3~yos`) respelled through this
|
|
3973
|
+
* config's own pin to the same system, where that pin's source holds the target too: a copy then
|
|
3974
|
+
* names what this config reaches directly rather than keeping the pin it came through referenced.
|
|
3975
|
+
* Bare when this config already holds the target as a pointer through that pin.
|
|
3976
|
+
*/
|
|
3977
|
+
throughDirectPins(body) {
|
|
3978
|
+
let respelled = body;
|
|
3979
|
+
for (const target of new Set(collectRefs(body))) {
|
|
3980
|
+
const { kind, path, source } = splitRef(target);
|
|
3981
|
+
if (source === void 0) continue;
|
|
3982
|
+
const direct = this.directRoute(source);
|
|
3983
|
+
if (direct === void 0 || !this.sourceFor(direct)?.resolve(kind, path)) continue;
|
|
3984
|
+
const through = ref(kind, path, direct);
|
|
3985
|
+
respelled = rewriteRefs(respelled, `${kind}:${path}@${source}`, this.borrowsExactly(through, direct) ? `${kind}:${path}` : through.__ref);
|
|
3986
|
+
}
|
|
3987
|
+
return respelled;
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* The pin of this config's own that reaches the system a routed source ends at — `yahoo-os` for
|
|
3991
|
+
* `yos-v3~yos`, when this config pins directly what `yos-v3` pins as `yos`. Matched on the pin
|
|
3992
|
+
* kind's `sourceOrigin`, so the two pins may name different versions. `undefined` for a source of
|
|
3993
|
+
* one hop, or one whose system no pin here reaches.
|
|
3994
|
+
*
|
|
3995
|
+
* The two pins match when they agree on every origin field both set, and share at least one. Not on
|
|
3996
|
+
* the whole key: a pin by path is stamped with its sibling's id when it's pushed, so the Bridge's
|
|
3997
|
+
* `yos` reads `configPath` and `systemId` where this config's pin to the same system names only the
|
|
3998
|
+
* id — and a `configPath` is relative to the config holding it, so it can't be compared across two.
|
|
3999
|
+
*/
|
|
4000
|
+
directRoute(source) {
|
|
4001
|
+
const hops = sourceHops(source);
|
|
4002
|
+
const last = hops.pop();
|
|
4003
|
+
if (last === void 0 || hops.length === 0) return void 0;
|
|
4004
|
+
let through = this;
|
|
4005
|
+
for (const hop of hops) through = through?.sourceFor(hop);
|
|
4006
|
+
if (!through) return void 0;
|
|
4007
|
+
for (const sourceKind of through.sourceKinds()) {
|
|
4008
|
+
const theirs = through.sourceFields(sourceKind, last, "sourceOrigin");
|
|
4009
|
+
if (theirs === void 0) continue;
|
|
4010
|
+
const direct = this.sourceSlugs().find((slug) => {
|
|
4011
|
+
const mine = this.sourceFields(sourceKind, slug, "sourceOrigin");
|
|
4012
|
+
const shared = theirs.flatMap((value, field) => value !== "" && mine?.[field] ? [field] : []);
|
|
4013
|
+
return shared.length > 0 && shared.every((field) => theirs[field] === mine?.[field]);
|
|
4014
|
+
});
|
|
4015
|
+
if (direct !== void 0) return direct;
|
|
4016
|
+
}
|
|
3957
4017
|
}
|
|
3958
4018
|
/**
|
|
3959
4019
|
* Whether a copied row no longer matches what its source offers at the path it was copied from.
|
|
@@ -4584,11 +4644,16 @@ var Config = class Config {
|
|
|
4584
4644
|
assertDerivedDropsUnreferenced({ action = "remove the link", derivation = this.rowDerivation() } = {}) {
|
|
4585
4645
|
const { snapshot } = this.state;
|
|
4586
4646
|
const contested = new Set(derivation.collisions.map(({ kind, path }) => `${kind}:${path}`));
|
|
4647
|
+
const retired = new Set(derivation.changes.filter((change) => change.value === void 0).map(({ kind, path }) => `${kind}:${path}`));
|
|
4587
4648
|
const stranded = [];
|
|
4588
4649
|
let collides = false;
|
|
4589
4650
|
for (const change of derivation.changes) {
|
|
4590
4651
|
if (change.value !== void 0) continue;
|
|
4591
|
-
const referrers = this.referrersOf(snapshot, change.kind, change.path).filter((referrer) => !isMintedAt(snapshot, referrer)
|
|
4652
|
+
const referrers = this.referrersOf(snapshot, change.kind, change.path).filter((referrer) => !isMintedAt(snapshot, referrer) && !this.directivesGoWith({
|
|
4653
|
+
pin: referrer,
|
|
4654
|
+
target: change,
|
|
4655
|
+
retired
|
|
4656
|
+
}));
|
|
4592
4657
|
if (referrers.length === 0) continue;
|
|
4593
4658
|
if (contested.has(`${change.kind}:${change.path}`)) collides = true;
|
|
4594
4659
|
stranded.push(`${change.kind}:${change.path} (used by ${referrers.join(", ")})`);
|
|
@@ -4598,6 +4663,23 @@ var Config = class Config {
|
|
|
4598
4663
|
reject("REFERRERS_BLOCK", `Cannot ${action} — it would take away ${stranded.join("; ")}. Copy what is used first (\`linked-system/copy\`), or break the link keeping its values (\`linked-system/break\` with \`keep: true\`).`);
|
|
4599
4664
|
}
|
|
4600
4665
|
/**
|
|
4666
|
+
* Whether every directive of `pin` that names `target` sits on a path that no longer derives through
|
|
4667
|
+
* it — retired by this derivation, or drawn from another source already. Such a directive lays its
|
|
4668
|
+
* value over no row, so it goes with the link rather than being stranded by it: a pin's own
|
|
4669
|
+
* `options` never refuse removing its link. A directive on a row the pin still mints does, since
|
|
4670
|
+
* that row would be left pointing at nothing.
|
|
4671
|
+
*
|
|
4672
|
+
* `false` when `pin` is not a source, or names `target` somewhere other than a directive.
|
|
4673
|
+
*/
|
|
4674
|
+
directivesGoWith({ pin, target, retired }) {
|
|
4675
|
+
if (!this.entityOf(kindOf(pin))?.sourceIdentity) return false;
|
|
4676
|
+
const slug = pathOf(pin);
|
|
4677
|
+
const aliases = new Set(this.refAliases(target.kind, target.path));
|
|
4678
|
+
const naming = this.sourceDirectives(slug).filter((directive) => directive.kind !== void 0 && collectRefs(this.sourceOverlay(slug, directive.kind, directive.path)).some((ref) => aliases.has(ref)));
|
|
4679
|
+
if (naming.length === 0) return false;
|
|
4680
|
+
return naming.every(({ kind, path }) => retired.has(`${kind}:${path}`) || this.resolve(kind, path)?.borrowedFrom !== slug);
|
|
4681
|
+
}
|
|
4682
|
+
/**
|
|
4601
4683
|
* This config with a row for every entity it inherits — the build's output. A pin patch runs this
|
|
4602
4684
|
* inside {@link apply}; a source attaching runs it through the session.
|
|
4603
4685
|
*
|
|
@@ -5415,6 +5497,10 @@ var Config = class Config {
|
|
|
5415
5497
|
return this.sourceKey(kind, path, "sourceIdentity");
|
|
5416
5498
|
}
|
|
5417
5499
|
sourceKey(kind, path, of) {
|
|
5500
|
+
return this.sourceFields(kind, path, of)?.join("\0");
|
|
5501
|
+
}
|
|
5502
|
+
/** Each of a pin's `of` fields as it reads, `''` where unset, in the kind's order. */
|
|
5503
|
+
sourceFields(kind, path, of) {
|
|
5418
5504
|
const entry = this.entityOf(kind);
|
|
5419
5505
|
const fields = entry?.[of] ?? entry?.sourceIdentity;
|
|
5420
5506
|
if (!fields) return void 0;
|
|
@@ -5423,7 +5509,7 @@ var Config = class Config {
|
|
|
5423
5509
|
return fields.map((field) => {
|
|
5424
5510
|
const value = body[field];
|
|
5425
5511
|
return value === void 0 ? "" : String(value);
|
|
5426
|
-
})
|
|
5512
|
+
});
|
|
5427
5513
|
}
|
|
5428
5514
|
/**
|
|
5429
5515
|
* Where what this config shows at `path` originates: the system it was first authored in, followed
|
|
@@ -6070,7 +6156,7 @@ var Config = class Config {
|
|
|
6070
6156
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
6071
6157
|
hydrateFrom(stored, options) {
|
|
6072
6158
|
const claimed = schemaVersionOf(stored);
|
|
6073
|
-
if (claimed >
|
|
6159
|
+
if (claimed > 20261026125912) throw new SchemaVersionTooNew(claimed);
|
|
6074
6160
|
const wire = upgradeSerializedConfig(stored);
|
|
6075
6161
|
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.`);
|
|
6076
6162
|
const leftover = wire.options;
|
|
@@ -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 = 20261026125912;
|
|
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 = 20261026125912;
|
|
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 > 20261026125912) 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 ?? 20261026125912;
|
|
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 ?? 20261026125912;
|
|
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 ?? 20261026125912;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|