@yahoo/uds-create-config 5.27.1 → 5.29.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/react-native-system.d.ts +1 -0
- package/dist/configs/system.js +4 -0
- package/dist/entities/native/NativeSettings.d.ts +1 -0
- package/dist/entities/system/Font.d.ts +20 -1
- package/dist/entities/system/Font.js +50 -4
- package/dist/entities/system/LinkedSystem.d.ts +12 -0
- package/dist/entities/system/LinkedSystem.js +114 -18
- package/dist/entities/system/Settings.d.ts +3 -0
- package/dist/entities/system/Settings.js +7 -0
- package/dist/entities/system/link-declaration.d.ts +43 -2
- package/dist/entities/system/link-declaration.js +110 -15
- package/dist/entities/system/link-plan.js +109 -4
- package/dist/framework/Config.d.ts +6 -1
- package/dist/framework/Config.js +28 -6
- package/dist/framework/defineEntity.d.ts +4 -3
- package/dist/framework/defineEntity.js +1 -0
- package/dist/framework/registry.d.ts +5 -0
- package/dist/framework/registry.js +2 -1
- package/dist/framework/rejection.d.ts +4 -2
- package/dist/framework/schema-version.d.ts +1 -1
- package/dist/framework/schema-version.js +5 -5
- package/dist/framework/source-integrity.d.ts +9 -2
- package/dist/framework/source-integrity.js +21 -2
- package/dist/framework/utils/refs.d.ts +14 -2
- package/dist/framework/utils/refs.js +22 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/migrations/20261013182150_link_exclude_op.d.ts +13 -0
- package/dist/migrations/20261013182150_link_exclude_op.js +10 -0
- package/dist/migrations/20261013201548_font_faces.d.ts +9 -0
- package/dist/migrations/20261013201548_font_faces.js +10 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { rejectData } from "../../framework/rejection.js";
|
|
2
2
|
import { collectRefs, isMintedRow, ref, sourceOf, splitRef } from "../../framework/utils/refs.js";
|
|
3
3
|
import { blockedBy } from "../../framework/plan.js";
|
|
4
|
-
import {
|
|
4
|
+
import { excludedCondition } from "../../framework/source-integrity.js";
|
|
5
|
+
import { PIN_KIND, covers, declarationOf, entriesFor, excludePaths, excludes, includePaths, narrowDeclaration } from "./link-declaration.js";
|
|
5
6
|
import { planCopy, sourceEntries } from "./copy-plan.js";
|
|
6
7
|
//#region src/entities/system/link-plan.ts
|
|
7
8
|
/** The kinds `data` names, with what to take of each. An accessor this config has no kind for is a typo, refused by name. */
|
|
@@ -201,7 +202,7 @@ function planLink({ config, input }) {
|
|
|
201
202
|
} else {
|
|
202
203
|
const next = { ...current ?? {} };
|
|
203
204
|
for (const [accessor, gain] of gains) {
|
|
204
|
-
const before =
|
|
205
|
+
const before = entriesFor(current, accessor);
|
|
205
206
|
if (before === "*") continue;
|
|
206
207
|
if (gain === "*") {
|
|
207
208
|
next[accessor] = "*";
|
|
@@ -213,7 +214,13 @@ function planLink({ config, input }) {
|
|
|
213
214
|
path
|
|
214
215
|
}));
|
|
215
216
|
if (added.length === 0) continue;
|
|
216
|
-
next[accessor] =
|
|
217
|
+
next[accessor] = includePaths({
|
|
218
|
+
config,
|
|
219
|
+
slug,
|
|
220
|
+
kind: config.accessors()[accessor]?.kind ?? accessor,
|
|
221
|
+
entries: before ?? [],
|
|
222
|
+
paths: [...new Set(added)]
|
|
223
|
+
});
|
|
217
224
|
}
|
|
218
225
|
for (const [accessor, value] of Object.entries(next)) {
|
|
219
226
|
if (current !== void 0 && current[accessor] === value) continue;
|
|
@@ -361,6 +368,104 @@ function planUnlink({ config, input }) {
|
|
|
361
368
|
};
|
|
362
369
|
}
|
|
363
370
|
/**
|
|
371
|
+
* Leave paths of a linked system out, with a `!` entry each in the pin's `link`. Stronger than
|
|
372
|
+
* `unlink`: nothing derives there however the link reaches it, a linked token naming it included,
|
|
373
|
+
* and the linked tokens' overrides under an excluded modifier are left out of the build. `link` with
|
|
374
|
+
* the same path lifts it again.
|
|
375
|
+
*/
|
|
376
|
+
function planExclude({ config, input }) {
|
|
377
|
+
const { path: slug } = input;
|
|
378
|
+
const source = config.sourceFor(slug);
|
|
379
|
+
if (!source) rejectData(`Cannot exclude through "${slug}" — nothing has resolved that link.`);
|
|
380
|
+
const current = declarationOf(config, slug);
|
|
381
|
+
const next = current === void 0 ? {} : current === "*" ? { "*": "*" } : { ...current };
|
|
382
|
+
for (const { kind, accessor, paths } of wantedKinds({
|
|
383
|
+
config,
|
|
384
|
+
slug,
|
|
385
|
+
data: input.data,
|
|
386
|
+
verb: "exclude"
|
|
387
|
+
})) {
|
|
388
|
+
if (paths === "*") rejectData(`Cannot exclude every "${accessor}" through "${slug}" — name the paths to leave out, or \`unlink\` the kind.`, ["data", accessor]);
|
|
389
|
+
for (const path of paths) if (sourceEntries({
|
|
390
|
+
source,
|
|
391
|
+
kind,
|
|
392
|
+
path
|
|
393
|
+
}).length === 0) rejectData(`Cannot exclude "${path}" through "${slug}" — the source has no ${kind} there.`, ["data", accessor]);
|
|
394
|
+
next[accessor] = excludePaths(entriesFor(current, accessor), paths);
|
|
395
|
+
}
|
|
396
|
+
const declared = next;
|
|
397
|
+
const dropped = [];
|
|
398
|
+
const optionDeletes = {};
|
|
399
|
+
const options = config.storedBody("linked-system", slug).options ?? {};
|
|
400
|
+
for (const [accessor, entries] of Object.entries(options)) {
|
|
401
|
+
if (!entries || typeof entries !== "object") continue;
|
|
402
|
+
const kind = config.accessors()[accessor]?.kind ?? accessor;
|
|
403
|
+
for (const path of Object.keys(entries)) {
|
|
404
|
+
if (!excludes({
|
|
405
|
+
declared,
|
|
406
|
+
accessor,
|
|
407
|
+
path
|
|
408
|
+
})) continue;
|
|
409
|
+
dropped.push({
|
|
410
|
+
kind,
|
|
411
|
+
path
|
|
412
|
+
});
|
|
413
|
+
optionDeletes[`options.${accessor}.${path}`] = null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const patches = [{
|
|
417
|
+
kind: PIN_KIND,
|
|
418
|
+
operation: "update",
|
|
419
|
+
path: slug,
|
|
420
|
+
data: {
|
|
421
|
+
link: declared,
|
|
422
|
+
...optionDeletes
|
|
423
|
+
}
|
|
424
|
+
}];
|
|
425
|
+
if (Object.entries(input.data).every(([accessor, paths]) => paths.every((path) => excludes({
|
|
426
|
+
declared: current,
|
|
427
|
+
accessor,
|
|
428
|
+
path
|
|
429
|
+
})))) return {
|
|
430
|
+
declared: current,
|
|
431
|
+
removed: [],
|
|
432
|
+
leftOut: [],
|
|
433
|
+
dropped: [],
|
|
434
|
+
blocked: `Nothing would change: what "${slug}" declares already leaves everything named out.`,
|
|
435
|
+
patches: []
|
|
436
|
+
};
|
|
437
|
+
const blocked = blockedBy(() => config.applyAll(patches));
|
|
438
|
+
const removed = [];
|
|
439
|
+
const leftOut = [];
|
|
440
|
+
if (blocked === void 0) {
|
|
441
|
+
const after = config.applyAll(patches);
|
|
442
|
+
for (const kind of config.borrowableKinds(slug)) for (const item of config.list(kind)) {
|
|
443
|
+
if (item.borrowedFrom !== slug) continue;
|
|
444
|
+
if (after.resolve(kind, item.path)?.borrowedFrom === slug) continue;
|
|
445
|
+
removed.push({
|
|
446
|
+
kind,
|
|
447
|
+
path: item.path
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
for (const item of after.list("token")) {
|
|
451
|
+
if (item.borrowedFrom !== slug) continue;
|
|
452
|
+
const body = source.resolve("token", item.path)?.toJSON();
|
|
453
|
+
for (const [override, { on }] of Object.entries(body?.overrides ?? {})) if (excludedCondition(after, on)) leftOut.push({
|
|
454
|
+
token: item.path,
|
|
455
|
+
override
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
declared,
|
|
461
|
+
removed,
|
|
462
|
+
leftOut,
|
|
463
|
+
dropped,
|
|
464
|
+
...blocked === void 0 ? {} : { blocked },
|
|
465
|
+
patches
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
364
469
|
* The linked rows that point at what `data` names, followed to a fixpoint: a row pointing at one of
|
|
365
470
|
* those, and so on. Read off both ref graphs, since a linked row's refs are the source's spelling and
|
|
366
471
|
* this config's index holds them qualified.
|
|
@@ -832,4 +937,4 @@ function sourceRowStates({ config, slug }) {
|
|
|
832
937
|
return rows;
|
|
833
938
|
}
|
|
834
939
|
//#endregion
|
|
835
|
-
export { planBreak, planLink, planUnlink, sourceRowStates };
|
|
940
|
+
export { planBreak, planExclude, planLink, planUnlink, sourceRowStates };
|
|
@@ -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 = 20261013201548;
|
|
77
77
|
interface SerializedConfig {
|
|
78
78
|
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
79
79
|
readonly version: number;
|
|
@@ -1785,6 +1785,11 @@ declare class Config {
|
|
|
1785
1785
|
* subject is the overlay rather than whether a row is minted at all.
|
|
1786
1786
|
*/
|
|
1787
1787
|
sourceOverlay(slug: string, kind: string, path: string): SourceOverlay | undefined;
|
|
1788
|
+
/**
|
|
1789
|
+
* Whether the declared source at `slug` excludes one of its paths ({@link EntityKind.bulkExcludes}):
|
|
1790
|
+
* left out on purpose, so no row derives there. Matched on the path the source spells.
|
|
1791
|
+
*/
|
|
1792
|
+
sourceExcludes(slug: string, kind: string, path: string): boolean;
|
|
1788
1793
|
/**
|
|
1789
1794
|
* The field-path delta that records `overlay` as the pin's directive for one path, or `null` when
|
|
1790
1795
|
* there is nothing to write.
|
package/dist/framework/Config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./config-operation-kind.js";
|
|
2
2
|
import { ConfigFormatError, ConfigRejection, isConfigRejection, issuePath, refusalIssues, reject, rejectData, rejectInvalid, rejectReferrers } from "./rejection.js";
|
|
3
|
-
import {
|
|
3
|
+
import { MINTED, collectRefs, entityPathProblem, entityRefOf, isExtendedRef, isMintedRow, isRef, kindOf, memberOf, memberRef, pathOf, qualifyRefs, ref, rewriteRefNamespace, rewriteRefSource, rewriteRefs, routedSource, singletonRef, sourceHops, sourceOf, splitRef } from "./utils/refs.js";
|
|
4
4
|
import { describePatch } from "./plan.js";
|
|
5
5
|
import { applyPathDelta, isPlainObject, namedFields, pathSegments, sectionDelta, touchedFields } from "./utils/field-path.js";
|
|
6
6
|
import { METADATA_KEYS, declaredField } from "./utils/field-schema.js";
|
|
@@ -2023,7 +2023,8 @@ var Config = class Config {
|
|
|
2023
2023
|
if (!entry.schemas[verb]) reject("UNSUPPORTED", `${patch.kind} has no ${verb} operation`);
|
|
2024
2024
|
if (entry.singleton && "path" in patch && patch.path !== void 0 && patch.path !== entry.name) reject("INVALID_PATH", `${patch.kind} has one item, at "${entry.name}" — leave \`path\` out. A field is written by its path in \`data\` (\`{ "${patch.path}": … }\`) or through sub-update/<section>.`);
|
|
2025
2025
|
if (patch.operation === "create") {
|
|
2026
|
-
|
|
2026
|
+
const problem = entityPathProblem(patch.path);
|
|
2027
|
+
if (problem !== void 0) reject("INVALID_PATH", `Cannot create ${patch.kind} "${patch.path}" — ${problem}`);
|
|
2027
2028
|
const occupant = snapshot.get(patch.kind, patch.path);
|
|
2028
2029
|
if (occupant && !isMintedRow(occupant.toJSON())) reject("ALREADY_EXISTS", `${patch.kind} "${patch.path}" already exists`);
|
|
2029
2030
|
return snapshot.withChanges([this.wholeBodyChange({
|
|
@@ -2113,7 +2114,8 @@ var Config = class Config {
|
|
|
2113
2114
|
}]);
|
|
2114
2115
|
}
|
|
2115
2116
|
if (patch.operation === "rename") {
|
|
2116
|
-
|
|
2117
|
+
const problem = entityPathProblem(patch.to);
|
|
2118
|
+
if (problem !== void 0) reject("INVALID_PATH", `Cannot rename ${patch.kind} "${patch.path}" to "${patch.to}" — ${problem}`);
|
|
2117
2119
|
const occupant = patch.to === patch.path ? void 0 : snapshot.get(patch.kind, patch.to);
|
|
2118
2120
|
if (occupant && !isMintedRow(occupant.toJSON())) reject("ALREADY_EXISTS", `Cannot rename ${patch.kind} "${patch.path}" to "${patch.to}" — a ${patch.kind} already exists there.`);
|
|
2119
2121
|
const storedBody = stored;
|
|
@@ -3612,6 +3614,23 @@ var Config = class Config {
|
|
|
3612
3614
|
return this.sourceDirective(slug, kind, path).overlay;
|
|
3613
3615
|
}
|
|
3614
3616
|
/**
|
|
3617
|
+
* Whether the declared source at `slug` excludes one of its paths ({@link EntityKind.bulkExcludes}):
|
|
3618
|
+
* left out on purpose, so no row derives there. Matched on the path the source spells.
|
|
3619
|
+
*/
|
|
3620
|
+
sourceExcludes(slug, kind, path) {
|
|
3621
|
+
const accessor = this.accessorOf(kind);
|
|
3622
|
+
return this.sourceKinds().some((sourceKind) => {
|
|
3623
|
+
const excludes = this.entityOf(sourceKind)?.bulkExcludes;
|
|
3624
|
+
const pin = this.storedBody(sourceKind, slug);
|
|
3625
|
+
return excludes !== void 0 && pin !== void 0 && excludes({
|
|
3626
|
+
body: pin,
|
|
3627
|
+
kind,
|
|
3628
|
+
accessor,
|
|
3629
|
+
path
|
|
3630
|
+
});
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3615
3634
|
* The field-path delta that records `overlay` as the pin's directive for one path, or `null` when
|
|
3616
3635
|
* there is nothing to write.
|
|
3617
3636
|
*
|
|
@@ -4791,6 +4810,7 @@ var Config = class Config {
|
|
|
4791
4810
|
config: this,
|
|
4792
4811
|
source
|
|
4793
4812
|
})) continue;
|
|
4813
|
+
if (this.sourceExcludes(slug, kind, path)) continue;
|
|
4794
4814
|
const current = this.state.snapshot.get(kind, path);
|
|
4795
4815
|
if (current && !isMintedRow(current.toJSON())) continue;
|
|
4796
4816
|
if (placed.has(target)) continue;
|
|
@@ -5833,7 +5853,7 @@ var Config = class Config {
|
|
|
5833
5853
|
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
5834
5854
|
hydrateFrom(stored, options) {
|
|
5835
5855
|
const claimed = schemaVersionOf(stored);
|
|
5836
|
-
if (claimed >
|
|
5856
|
+
if (claimed > 20261013201548) throw new SchemaVersionTooNew(claimed);
|
|
5837
5857
|
const wire = upgradeSerializedConfig(stored);
|
|
5838
5858
|
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\`.`);
|
|
5839
5859
|
const leftover = wire.options;
|
|
@@ -5952,7 +5972,8 @@ function isPathSegment(meta, kind, raw) {
|
|
|
5952
5972
|
function loadItemChange(meta, kind, path, raw, phase = "author") {
|
|
5953
5973
|
const entry = meta.entities.get(kind);
|
|
5954
5974
|
if (!entry) throw new Error(`Unknown entity kind: ${kind}`);
|
|
5955
|
-
|
|
5975
|
+
const problem = phase === "author" ? entityPathProblem(path) : void 0;
|
|
5976
|
+
if (problem !== void 0) reject("INVALID_PATH", `${kind} "${path}" — ${problem}`);
|
|
5956
5977
|
if (phase === "author") refusePhantomsIn({
|
|
5957
5978
|
kind,
|
|
5958
5979
|
path,
|
|
@@ -6200,7 +6221,8 @@ function itemChange(entry, kind, path, raw, validate = false) {
|
|
|
6200
6221
|
function loadGroupChange(meta, kind, path, raw, phase = "author") {
|
|
6201
6222
|
const entry = meta.entities.get(kind);
|
|
6202
6223
|
if (!entry) throw new Error(`Unknown entity kind: ${kind}`);
|
|
6203
|
-
|
|
6224
|
+
const problem = phase === "author" ? entityPathProblem(path) : void 0;
|
|
6225
|
+
if (problem !== void 0) reject("INVALID_PATH", `${kind} group "${path}" — ${problem}`);
|
|
6204
6226
|
if (phase === "author") refusePhantomsIn({
|
|
6205
6227
|
kind,
|
|
6206
6228
|
path,
|
|
@@ -338,10 +338,11 @@ declare function defineEntity<F extends FieldsSchema, G extends FieldsSchema = z
|
|
|
338
338
|
*/
|
|
339
339
|
bulkAdopts?: (args: BulkQuery) => boolean;
|
|
340
340
|
/**
|
|
341
|
-
* Whether the pin
|
|
342
|
-
*
|
|
343
|
-
*
|
|
341
|
+
* Whether the pin leaves this path out on purpose — the counterpart to {@link EntityKind.bulkAdopts}.
|
|
342
|
+
* An excluded path derives no row however the link reaches it, so the name is free for this config
|
|
343
|
+
* to author, and the source's entity stays reachable qualified.
|
|
344
344
|
*/
|
|
345
|
+
bulkExcludes?: (args: BulkQuery) => boolean;
|
|
345
346
|
/**
|
|
346
347
|
* What the declared source lays over the entity at this path, when it declares anything — the
|
|
347
348
|
* bodies a minted row carries in place of an empty `extend`, so a bulk adjustment and a hand-written
|
|
@@ -120,6 +120,7 @@ function defineEntity(spec) {
|
|
|
120
120
|
bareMember: spec.bareMember,
|
|
121
121
|
sourceResolution: spec.sourceResolution,
|
|
122
122
|
bulkAdopts: spec.bulkAdopts,
|
|
123
|
+
bulkExcludes: spec.bulkExcludes,
|
|
123
124
|
bulkOverlay: spec.bulkOverlay,
|
|
124
125
|
bulkDirectives: spec.bulkDirectives,
|
|
125
126
|
bulkOverlayWrite: spec.bulkOverlayWrite,
|
|
@@ -260,6 +260,11 @@ interface EntityKind {
|
|
|
260
260
|
* nothing, and no delete is ever refused for it.
|
|
261
261
|
*/
|
|
262
262
|
readonly bulkAdopts?: (args: BulkQuery) => boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Whether the declaration leaves this path out on purpose. An excluded path derives no row, and an
|
|
265
|
+
* excluded modifier is one the source's tokens may vary under without this config stating it.
|
|
266
|
+
*/
|
|
267
|
+
readonly bulkExcludes?: (args: BulkQuery) => boolean;
|
|
263
268
|
/** What the declared source lays over the entity at this path, when it declares anything. */
|
|
264
269
|
readonly bulkOverlay?: (args: BulkQuery) => SourceOverlay | undefined;
|
|
265
270
|
/**
|
|
@@ -32,7 +32,7 @@ function titleCase(kind) {
|
|
|
32
32
|
/** Derive a kind's immutable metadata from its field schemas — the value a `defineEntity`/
|
|
33
33
|
* `defineDerivedEntity` attaches to its class and a `Config.register` stores in `#entities`. */
|
|
34
34
|
function buildKind(spec) {
|
|
35
|
-
const { name, prepareLinkedBody, rebaseLinkedBody, layerKeys, fields, groupFields, subEntities = {}, computed, label, labelPlural, schemas: userSchemas, derived, sourceIdentity, sourceOrigin, authoredRefs, linkable, localGroupFields, emergentGroups, singleton = false, bareMember, sourceResolution, bulkAdopts, bulkOverlay, bulkDirectives, bulkOverlayWrite, nameEdges, rewriteName, styleBags, uniqueLeaves, authoredValue, mentionFields, invariants, structuralBody } = spec;
|
|
35
|
+
const { name, prepareLinkedBody, rebaseLinkedBody, layerKeys, fields, groupFields, subEntities = {}, computed, label, labelPlural, schemas: userSchemas, derived, sourceIdentity, sourceOrigin, authoredRefs, linkable, localGroupFields, emergentGroups, singleton = false, bareMember, sourceResolution, bulkAdopts, bulkExcludes, bulkOverlay, bulkDirectives, bulkOverlayWrite, nameEdges, rewriteName, styleBags, uniqueLeaves, authoredValue, mentionFields, invariants, structuralBody } = spec;
|
|
36
36
|
const allComputed = {
|
|
37
37
|
sources: ({ member, config }) => sourceSlugs(member, config),
|
|
38
38
|
borrowedFrom: ({ config, path }) => config.borrowedFrom(name, path),
|
|
@@ -73,6 +73,7 @@ function buildKind(spec) {
|
|
|
73
73
|
...bareMember ? { bareMember } : {},
|
|
74
74
|
...sourceResolution ? { sourceResolution } : {},
|
|
75
75
|
...bulkAdopts ? { bulkAdopts } : {},
|
|
76
|
+
...bulkExcludes ? { bulkExcludes } : {},
|
|
76
77
|
...bulkOverlay ? { bulkOverlay } : {},
|
|
77
78
|
...bulkDirectives ? { bulkDirectives } : {},
|
|
78
79
|
...bulkOverlayWrite ? { bulkOverlayWrite } : {},
|
|
@@ -8,8 +8,10 @@ import { z } from "zod";
|
|
|
8
8
|
*/
|
|
9
9
|
type ConfigIssueCode = /** Nothing at that path / no such member — an update, delete or rename with no target. */'NOT_FOUND' /** A create over an existing path, or a rename onto a taken one. */ | 'ALREADY_EXISTS'
|
|
10
10
|
/**
|
|
11
|
-
* The address cannot be an identity — a path carrying `@` other
|
|
12
|
-
*
|
|
11
|
+
* The address cannot be an identity — a path `entityPathProblem` refuses: one carrying `@` other
|
|
12
|
+
* than as a leading npm scope, or `#`, or with an empty segment, or a segment starting with `!` or
|
|
13
|
+
* that is `*`. Each misreads as another address: a borrowed one, a member, a link's exclusion or
|
|
14
|
+
* its wildcard.
|
|
13
15
|
*
|
|
14
16
|
* A distinct code rather than `INVALID_DATA`: nothing about the data is wrong, and the surface that
|
|
15
17
|
* reports it is a name field rather than a form. This is a distinction `apply` genuinely makes.
|
|
@@ -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 = 20261013201548;
|
|
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 = 20261013201548;
|
|
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 > 20261013201548) 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 ?? 20261013201548;
|
|
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 ?? 20261013201548;
|
|
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 ?? 20261013201548;
|
|
183
183
|
const entries = [];
|
|
184
184
|
const held = [];
|
|
185
185
|
const retired = [];
|
|
@@ -232,7 +232,14 @@ interface UnstatableCondition {
|
|
|
232
232
|
consumer: ModifierCategory | 'plain';
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* Whether an override's condition names a modifier this config doesn't hold because a pin excludes
|
|
237
|
+
* it ({@link Config.sourceExcludes}). Such an override is left out, as the stylesheet leaves out a
|
|
238
|
+
* condition it cannot state, and the build doesn't refuse it.
|
|
239
|
+
*/
|
|
240
|
+
declare function excludedCondition(config: Config, on: unknown): boolean;
|
|
241
|
+
/** Borrowed conditions need a consumer modifier with the same name and category, unless the pin
|
|
242
|
+
* excludes that modifier. */
|
|
236
243
|
declare function unstatableConditions(config: Config): UnstatableCondition[];
|
|
237
244
|
/** A token of ours whose conditional value names another system's modifier. */
|
|
238
245
|
interface UnstatableOverride {
|
|
@@ -254,4 +261,4 @@ interface UnstatableOverride {
|
|
|
254
261
|
*/
|
|
255
262
|
declare function unstatableOverrides(config: Config): UnstatableOverride[];
|
|
256
263
|
//#endregion
|
|
257
|
-
export { DanglingDirective, DanglingRef, LeafAddress, MisdeclaredOverlay, OverlayVerb, RedundantQualifier, UnadoptedDirective, UnreadableBorrow, UnstatableCondition, UnstatableOverride, classifyOverlay, danglingDirectives, danglingSourcedRefs, leafAddresses, leafVerb, misdeclaredOverlays, overlayVerb, redundantQualifiers, unadoptedDirectives, unreadableBorrows, unstatableConditions, unstatableOverrides, valueAt };
|
|
264
|
+
export { DanglingDirective, DanglingRef, LeafAddress, MisdeclaredOverlay, OverlayVerb, RedundantQualifier, UnadoptedDirective, UnreadableBorrow, UnstatableCondition, UnstatableOverride, classifyOverlay, danglingDirectives, danglingSourcedRefs, excludedCondition, leafAddresses, leafVerb, misdeclaredOverlays, overlayVerb, redundantQualifiers, unadoptedDirectives, unreadableBorrows, unstatableConditions, unstatableOverrides, valueAt };
|
|
@@ -424,7 +424,24 @@ function danglingDirectives(config) {
|
|
|
424
424
|
}
|
|
425
425
|
return found;
|
|
426
426
|
}
|
|
427
|
-
/**
|
|
427
|
+
/**
|
|
428
|
+
* Whether an override's condition names a modifier this config doesn't hold because a pin excludes
|
|
429
|
+
* it ({@link Config.sourceExcludes}). Such an override is left out, as the stylesheet leaves out a
|
|
430
|
+
* condition it cannot state, and the build doesn't refuse it.
|
|
431
|
+
*/
|
|
432
|
+
function excludedCondition(config, on) {
|
|
433
|
+
return (Array.isArray(on) ? on : [on]).some((ref) => {
|
|
434
|
+
if (!isRef(ref)) return false;
|
|
435
|
+
const modifier = pathOf(ref);
|
|
436
|
+
if (config.resolve("modifier", modifier)) return false;
|
|
437
|
+
return config.sourceSlugs().some((slug) => {
|
|
438
|
+
const spelled = config.sourceFor(slug)?.resolve("modifier", modifier)?.path ?? modifier;
|
|
439
|
+
return config.sourceExcludes(slug, "modifier", spelled);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/** Borrowed conditions need a consumer modifier with the same name and category, unless the pin
|
|
444
|
+
* excludes that modifier. */
|
|
428
445
|
function unstatableConditions(config) {
|
|
429
446
|
const found = [];
|
|
430
447
|
for (const slug of config.sourceSlugs()) {
|
|
@@ -443,6 +460,8 @@ function unstatableConditions(config) {
|
|
|
443
460
|
if (!isRef(ref)) continue;
|
|
444
461
|
const modifier = pathOf(ref);
|
|
445
462
|
if (!config.resolve("modifier", modifier)) {
|
|
463
|
+
const spelled = source.resolve("modifier", modifier)?.path ?? modifier;
|
|
464
|
+
if (config.sourceExcludes(slug, "modifier", spelled)) continue;
|
|
446
465
|
found.push({
|
|
447
466
|
slug,
|
|
448
467
|
token,
|
|
@@ -575,4 +594,4 @@ function borrowedSlug(body) {
|
|
|
575
594
|
return isRef(body) ? sourceOf(body) : void 0;
|
|
576
595
|
}
|
|
577
596
|
//#endregion
|
|
578
|
-
export { borrowedSlug, classifyOverlay, danglingDirectives, danglingSourcedRefs, leafAddresses, leafVerb, misdeclaredOverlays, overlayVerb, redundantQualifiers, sourceSlugs, unadoptedDirectives, unreadableBorrows, unstatableConditions, unstatableOverrides, valueAt };
|
|
597
|
+
export { borrowedSlug, classifyOverlay, danglingDirectives, danglingSourcedRefs, excludedCondition, leafAddresses, leafVerb, misdeclaredOverlays, overlayVerb, redundantQualifiers, sourceSlugs, unadoptedDirectives, unreadableBorrows, unstatableConditions, unstatableOverrides, valueAt };
|
|
@@ -57,12 +57,24 @@ interface ExtendedRef<Delta extends Record<string, unknown> = Record<string, unk
|
|
|
57
57
|
* `if (!source) return ''` escape and the declaration vanishes), reports a false dangling ref naming a
|
|
58
58
|
* source that does not exist, and marks the item borrowed on every provenance surface. Nothing throws.
|
|
59
59
|
*
|
|
60
|
+
* The rest of the grammar a path is read in is refused the same way, since each misreads as silently:
|
|
61
|
+
* - `#` anywhere: the ref grammar splits on the first `#` before anything else, so `token:size/a#b`
|
|
62
|
+
* is a member ref (`b`) into `size/a`.
|
|
63
|
+
* - An empty segment (`''`, `a//b`, `/a`, `a/`): `/` is what makes a folder, and a pin's `link` covers
|
|
64
|
+
* a folder's members as the paths under `<folder>/`.
|
|
65
|
+
* - A segment starting with `!`: a `link` list entry `'!text'` leaves `text` out, so a path of that
|
|
66
|
+
* name couldn't be linked by name.
|
|
67
|
+
* - A segment that is `*`: in a `link` list, `'*'` is every path.
|
|
68
|
+
*
|
|
60
69
|
* Checked where a path becomes an identity — a create, a rename's target, an authored entry — and
|
|
61
70
|
* deliberately not on reads, so an existing bad path stays addressable and, more to the point,
|
|
62
71
|
* deletable.
|
|
63
72
|
*/
|
|
73
|
+
declare function entityPathProblem(path: string): string | undefined;
|
|
74
|
+
/** Whether `path` can be an entity's own path: {@link entityPathProblem} finds nothing wrong with it. */
|
|
64
75
|
declare function isEntityPath(path: string): boolean;
|
|
65
|
-
/** What a
|
|
76
|
+
/** What a path carrying a misplaced `@` should say, so a create, a rename and a register word it
|
|
77
|
+
* alike. {@link entityPathProblem} says it for every rule. */
|
|
66
78
|
declare const ENTITY_PATH_MESSAGE = "An entity path may not contain \"@\" except as a leading npm scope \u2014 \"@\" marks the source a ref is drawn from, so a path carrying one cannot be told apart from a borrowed address.";
|
|
67
79
|
/**
|
|
68
80
|
* A reference to another entity: `ref('token', 'color/brand')`. Resolution is prefix-or-leaf against
|
|
@@ -200,4 +212,4 @@ declare function rewriteRefNamespace(value: unknown, kind: string, from: string,
|
|
|
200
212
|
*/
|
|
201
213
|
declare function rewriteRefSource(value: unknown, from: string, to: string | undefined): unknown;
|
|
202
214
|
//#endregion
|
|
203
|
-
export { ENTITY_PATH_MESSAGE, ExtendableRef, ExtendedRef, MINTED, Ref, collectRefs, declarationText, entityRefOf, isEntityPath, isExtendedRef, isMintedRow, isRef, kindOf, memberOf, memberRef, orRef, pathOf, ref, refKey, refLeaf, refOfKey, refSchema, refTo, replaceRefMarkers, rewriteRefNamespace, rewriteRefSource, rewriteRefs, routedSource, sourceHops, sourceOf, splitRef, splitRefMarkers };
|
|
215
|
+
export { ENTITY_PATH_MESSAGE, ExtendableRef, ExtendedRef, MINTED, Ref, collectRefs, declarationText, entityPathProblem, entityRefOf, isEntityPath, isExtendedRef, isMintedRow, isRef, kindOf, memberOf, memberRef, orRef, pathOf, ref, refKey, refLeaf, refOfKey, refSchema, refTo, replaceRefMarkers, rewriteRefNamespace, rewriteRefSource, rewriteRefs, routedSource, sourceHops, sourceOf, splitRef, splitRefMarkers };
|
|
@@ -59,14 +59,33 @@ function memberOf(target) {
|
|
|
59
59
|
* `if (!source) return ''` escape and the declaration vanishes), reports a false dangling ref naming a
|
|
60
60
|
* source that does not exist, and marks the item borrowed on every provenance surface. Nothing throws.
|
|
61
61
|
*
|
|
62
|
+
* The rest of the grammar a path is read in is refused the same way, since each misreads as silently:
|
|
63
|
+
* - `#` anywhere: the ref grammar splits on the first `#` before anything else, so `token:size/a#b`
|
|
64
|
+
* is a member ref (`b`) into `size/a`.
|
|
65
|
+
* - An empty segment (`''`, `a//b`, `/a`, `a/`): `/` is what makes a folder, and a pin's `link` covers
|
|
66
|
+
* a folder's members as the paths under `<folder>/`.
|
|
67
|
+
* - A segment starting with `!`: a `link` list entry `'!text'` leaves `text` out, so a path of that
|
|
68
|
+
* name couldn't be linked by name.
|
|
69
|
+
* - A segment that is `*`: in a `link` list, `'*'` is every path.
|
|
70
|
+
*
|
|
62
71
|
* Checked where a path becomes an identity — a create, a rename's target, an authored entry — and
|
|
63
72
|
* deliberately not on reads, so an existing bad path stays addressable and, more to the point,
|
|
64
73
|
* deletable.
|
|
65
74
|
*/
|
|
75
|
+
function entityPathProblem(path) {
|
|
76
|
+
if (path.lastIndexOf("@") > 0) return ENTITY_PATH_MESSAGE;
|
|
77
|
+
if (path.includes("#")) return "An entity path may not contain \"#\" — \"#\" marks the member a ref points into, so a path carrying one reads as a member of a shorter path.";
|
|
78
|
+
const segments = path.split("/");
|
|
79
|
+
if (segments.some((segment) => segment === "")) return "An entity path may not have an empty segment — \"/\" separates a folder from its members, so \"\", \"a//b\", \"/a\" and \"a/\" name no folder a link or a ref can address.";
|
|
80
|
+
if (segments.some((segment) => segment.startsWith("!"))) return "An entity path segment may not start with \"!\" — a link's \"!path\" entry leaves that path out, so a path named that way could not be linked by name.";
|
|
81
|
+
if (segments.some((segment) => segment === "*")) return "An entity path segment may not be \"*\" — in a link, \"*\" stands for every path.";
|
|
82
|
+
}
|
|
83
|
+
/** Whether `path` can be an entity's own path: {@link entityPathProblem} finds nothing wrong with it. */
|
|
66
84
|
function isEntityPath(path) {
|
|
67
|
-
return path
|
|
85
|
+
return entityPathProblem(path) === void 0;
|
|
68
86
|
}
|
|
69
|
-
/** What a
|
|
87
|
+
/** What a path carrying a misplaced `@` should say, so a create, a rename and a register word it
|
|
88
|
+
* alike. {@link entityPathProblem} says it for every rule. */
|
|
70
89
|
const ENTITY_PATH_MESSAGE = "An entity path may not contain \"@\" except as a leading npm scope — \"@\" marks the source a ref is drawn from, so a path carrying one cannot be told apart from a borrowed address.";
|
|
71
90
|
/**
|
|
72
91
|
* A reference to another entity: `ref('token', 'color/brand')`. Resolution is prefix-or-leaf against
|
|
@@ -455,4 +474,4 @@ function canonicalCondition(when) {
|
|
|
455
474
|
return entries.length === 0 ? "*" : entries.map(([k, v]) => `${k}=${String(refLeaf(v))}`).join("&");
|
|
456
475
|
}
|
|
457
476
|
//#endregion
|
|
458
|
-
export { ENTITY_PATH_MESSAGE, MINTED, canonicalCondition, collectRefs, declarationText, entityRefOf, hasRefMarker, isEntityPath, isExtendedRef, isMintedRow, isRef, kindOf, memberOf, memberRef, orRef, pathOf, qualifyRefs, ref, refKey, refLeaf, refOfKey, refSchema, refTo, replaceRefMarkers, rewriteRefNamespace, rewriteRefSource, rewriteRefs, routedSource, singletonRef, sourceHops, sourceOf, splitRef, splitRefMarkers };
|
|
477
|
+
export { ENTITY_PATH_MESSAGE, MINTED, canonicalCondition, collectRefs, declarationText, entityPathProblem, entityRefOf, hasRefMarker, isEntityPath, isExtendedRef, isMintedRow, isRef, kindOf, memberOf, memberRef, orRef, pathOf, qualifyRefs, ref, refKey, refLeaf, refOfKey, refSchema, refTo, replaceRefMarkers, rewriteRefNamespace, rewriteRefSource, rewriteRefs, routedSource, singletonRef, sourceHops, sourceOf, splitRef, splitRefMarkers };
|