Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .changeset/10719-retire-phase35-validation-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
'@object-ui/types': minor
---

⚠️ **BREAKING (scored `minor` per this repository's version-alignment
convention) — `@object-ui/types` no longer exports the eight "Phase 3.5"
validation types (objectui#10719).**

Removed from the package root: `AdvancedValidationSchema`,
`AdvancedValidationRule`, `ValidationRuleType`, `ValidationFunction`,
`AsyncValidationFunction`, `ValidationContext`, `AdvancedValidationResult` and
`AdvancedValidationError`.

They were the type half of `@object-ui/core`'s `ValidationEngine`, a client-side
field-rule engine with a snake_case rule vocabulary of its own. That engine was
retired under ADR-0049 enforce-or-remove (maintainer ruling A on objectui#7659),
and the same reason covers its types: nothing in this repository or in the
ObjectStack framework reads them. They are removed with no deprecation window.

**Replacement:** client-side field validation is declared on the field. A form
field's `validation` key is typed `FieldValidationRules`, whose `validate`
member takes a custom check, and `buildValidationRules(field)` from
`@object-ui/fields` compiles field metadata (`required`, length and value
bounds, `pattern`, and the checks a field's type implies) into react-hook-form
rules. There is no one-to-one port of an `AdvancedValidationSchema` object: a
host that typed its own rules with these names declares those constraints on
the field metadata instead, or keeps its own types.

Not affected: `FieldValidationRules`, `FieldValidationFunction`, the
object-level rule union `ObjectValidationRule` and its variants, and
`DesignerValidationRule` stay exported.

⚠️ Out-of-repository consumers are NOT MEASURED. This package is published, and
the zero-reader reading behind the ruling covers this repository and the
ObjectStack framework only; a host application that imports any of the eight
names stops compiling on upgrade.
4 changes: 4 additions & 0 deletions .changeset/6349-types-internal-name-collisions-batch-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ differ from similarly named validation function types in other packages (e.g., i
`field-types`)") for as long as both existed. The published names `ValidationFunction`
(data-protocol's) and `FieldValidationFunction` (field-types') are unchanged.

⚠️ **Dated note, 2026-09-27 — data-protocol's `ValidationFunction` is removed from
`@object-ui/types` by a later change, with the rest of the Phase 3.5 validation
types; `FieldValidationFunction` stays published — objectui#10719.**

Both `KNOWN_COLLISIONS` lines come down in the same change; that baseline fails in **both**
directions, so converging without deleting them is red too. 38 entries -> 36.
4 changes: 4 additions & 0 deletions .changeset/7357-retire-field-metadata-depends-on.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,7 @@ validation dependencies, a different concept), the object-schema documents the
metadata designer and `resolveActionParams` read (their snake legs read STORED
pre-strict documents — objectui#7642's census verdict), and plugin-gantt's
`dependenciesField: 'depends_on'`, which names a record data field, not this key.

⚠️ **Dated note, 2026-09-27 — `AdvancedValidationRule` is removed from
`@object-ui/types` by a later change, so its `depends_on` no longer exists —
objectui#10719.**
7 changes: 7 additions & 0 deletions .changeset/7659-retire-validation-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ result types in `@object-ui/types` (`AdvancedValidationSchema`,
comment on `ValidationContext.locale` changed, because the engine it described
is gone.

⚠️ **Dated note, 2026-09-27 — those types no longer keep their shapes: all
eight (`AdvancedValidationSchema`, `AdvancedValidationRule`,
`ValidationRuleType`, `ValidationFunction`, `AsyncValidationFunction`,
`ValidationContext`, `AdvancedValidationResult`, `AdvancedValidationError`) are
removed from `@object-ui/types` by a later change, on this same ruling —
objectui#10719.**

⚠️ Out-of-repository consumers are NOT MEASURED. This package is published, and
the zero-consumer reading behind the ruling covers this repository only; a host
application that imports any of the four names stops compiling on upgrade.
5 changes: 5 additions & 0 deletions .changeset/9909-machine-locale-sweep.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ does) avoids the notice.
`@object-ui/core` by a later change; `ValidationContext.locale` stays declared in
`@object-ui/types`, and at that change nothing in `@object-ui/core` reads it —
objectui#7659.**

⚠️ **Dated note, 2026-09-27 — `ValidationContext` itself is removed from
`@object-ui/types` by a later change, with the rest of the Phase 3.5
validation types it belonged to, so the `locale` member this entry adds no
longer exists — objectui#10719.**
- `@object-ui/plugin-report`: `exportReport`, `exportAsHTML` and `exportAsPDF`
take a trailing optional `locale` for the exported file's "Generated:" time,
and `LiveExportOptions` gains an optional `locale` that `exportWithLiveData`
Expand Down
4 changes: 2 additions & 2 deletions content/docs/plugins/plugin-form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ is bound to react-hook-form, whose initial values come from the form.
#### `validation` is an object keyed by rule name

There is no `ValidationRule` type in this repository. Near-spellings do exist —
`AdvancedValidationRule`, `ValidationRuleType`, `ObjectValidationRule` and
`DesignerValidationRule` — and none of them types this key. The type of the key
`ObjectValidationRule` and `DesignerValidationRule` — and neither of them types
this key. The type of the key
is `FieldValidationRules` (`packages/types/src/form.ts`), and it is **not** an
array of `{ type, value, message }` entries:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* objectui#10719 — the eight "Phase 3.5" field-validation types are RETIRED from
* `@object-ui/types`.
*
* `AdvancedValidationSchema`, `AdvancedValidationRule`, `ValidationRuleType`,
* `ValidationFunction`, `AsyncValidationFunction`, `ValidationContext`,
* `AdvancedValidationResult` and `AdvancedValidationError` were the type half of
* `@object-ui/core`'s `ValidationEngine` vocabulary. Maintainer ruling A on
* objectui#7659 (decision batch #40, item 4, ADR-0049 enforce-or-remove) retired
* the engine because nothing consumed it; triage carried that ruling to its type
* half on objectui#10719. The zero-reader measurement is recorded on the card and
* is not re-derived here.
*
* ⛔ Not this retirement: `FieldValidationRules` (the type of the live field
* `validation` key), `FieldValidationFunction`, `ObjectValidationRule` and
* `DesignerValidationRule`. Two of them are firing controls below, so this file
* also fails if the retirement ever takes one of them along.
*
* What this pins, both halves of the objectui#7659 shape:
*
* - compile time: none of the eight resolves through the root barrel (the face
* an external consumer imports) or through the declaring module. Compiled by
* this package's `tsconfig.test.json`, chained off `type-check`; a name that
* comes back fails there with "Unused '@ts-expect-error' directive". The
* controls resolve live neighbours through the identical `import('…')`
* spellings with no directive, so a broken specifier cannot satisfy the
* directives for the wrong reason.
* - runtime: the barrel and the declaring module are read as TEXT, so a
* re-introduced declaration or re-export reddens even in a run that never
* type-checks.
*
* ⚠️ Why the runtime half is a source read and not objectui#7659's
* `Object.keys(entry)` probe: all eight were `export type`, so none of them was
* ever an own key of the runtime namespace. Measured at this change on the tree
* before the retirement: the built entry's namespace carried none of the eight
* while its `.d.ts` still exported all of them. That probe reads the same in both
* worlds, so it cannot fail, and writing it would make this file look better
* covered than it is. objectui#8800's retirement pin in this directory records
* the same reason.
*
* Deleting this file is deleting the ruling. Bringing any of the eight back is a
* new published-contract decision, not an edit here.
*/

import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
// @ts-expect-error -- plain-JS shared helper, intentionally untyped (`allowJs: false`)
import { stripComments as strip } from '../../../../scripts/js-comment-mask.mjs';

/** Local annotation, since the import above is untyped -- the call site stays checked. */
const stripComments: (source: string) => string = strip;

/* ── Compile time: the eight are gone from the barrel and from the module ── */

// @ts-expect-error retired (objectui#10719): no longer exported from the `@object-ui/types` root barrel.
type _SchemaViaBarrel = import('../index.js').AdvancedValidationSchema;
// @ts-expect-error retired (objectui#10719), as above.
type _RuleViaBarrel = import('../index.js').AdvancedValidationRule;
// @ts-expect-error retired (objectui#10719), as above.
type _RuleTypeViaBarrel = import('../index.js').ValidationRuleType;
// @ts-expect-error retired (objectui#10719), as above.
type _FunctionViaBarrel = import('../index.js').ValidationFunction;
// @ts-expect-error retired (objectui#10719), as above.
type _AsyncFunctionViaBarrel = import('../index.js').AsyncValidationFunction;
// @ts-expect-error retired (objectui#10719), as above.
type _ContextViaBarrel = import('../index.js').ValidationContext;
// @ts-expect-error retired (objectui#10719), as above.
type _ResultViaBarrel = import('../index.js').AdvancedValidationResult;
// @ts-expect-error retired (objectui#10719), as above.
type _ErrorViaBarrel = import('../index.js').AdvancedValidationError;

// @ts-expect-error retired (objectui#10719): `../data-protocol` no longer declares it.
type _SchemaViaModule = import('../data-protocol.js').AdvancedValidationSchema;
// @ts-expect-error retired (objectui#10719), as above.
type _RuleViaModule = import('../data-protocol.js').AdvancedValidationRule;
// @ts-expect-error retired (objectui#10719), as above.
type _RuleTypeViaModule = import('../data-protocol.js').ValidationRuleType;
// @ts-expect-error retired (objectui#10719), as above.
type _FunctionViaModule = import('../data-protocol.js').ValidationFunction;
// @ts-expect-error retired (objectui#10719), as above.
type _AsyncFunctionViaModule = import('../data-protocol.js').AsyncValidationFunction;
// @ts-expect-error retired (objectui#10719), as above.
type _ContextViaModule = import('../data-protocol.js').ValidationContext;
// @ts-expect-error retired (objectui#10719), as above.
type _ResultViaModule = import('../data-protocol.js').AdvancedValidationResult;
// @ts-expect-error retired (objectui#10719), as above.
type _ErrorViaModule = import('../data-protocol.js').AdvancedValidationError;

// Controls, no directive: the same two query shapes resolve live neighbours, so
// the sixteen errors above are readings of the two modules and not of a bad path.
type _ObjectRuleViaBarrel = import('../index.js').ObjectValidationRule;
type _ObjectRuleViaModule = import('../data-protocol.js').ObjectValidationRule;
type _FieldRulesViaBarrel = import('../index.js').FieldValidationRules;

/* ── Runtime: the same two files, read as text ── */

/** Rooted at THIS file, never at `process.cwd()`: the two differ per invocation. */
const HERE = dirname(fileURLToPath(import.meta.url));
const read = (file: string): string => readFileSync(resolve(HERE, '..', file), 'utf8');

/** The eight names the retirement removed, and nothing else. */
const RETIRED = [
'AdvancedValidationSchema',
'AdvancedValidationRule',
'ValidationRuleType',
'ValidationFunction',
'AsyncValidationFunction',
'ValidationContext',
'AdvancedValidationResult',
'AdvancedValidationError',
] as const;

/** A top-level declaration of exactly `name`. */
const declares = (source: string, name: string): boolean =>
new RegExp(`^export (?:interface|type) ${name}\\b`, 'm').test(source);

/**
* Every name `source` publishes through an `export { … }` / `export type { … }`
* list, whether the list spans many lines or one. Comments go FIRST, through
* `scripts/js-comment-mask.mjs`: the barrel annotates its lists with `//` lines
* that carry commas and braces, and splitting before stripping would fuse a real
* name into comment text and hide it. The shared reader rather than a private
* regex pair, because a regex cannot see a string literal and would open a
* phantom comment at a glob or URL inside one, reading clean over the names
* after it. `stripComments`, not `maskComments`: this reports a set of names,
* never a line or an offset. Specifiers are then split rather than matched
* inside the braces, so `ObjectValidationRule` cannot match `ValidationRule…`,
* and `X as Y` counts as publishing `Y`.
*/
const reexported = (source: string): Set<string> => {
const code = stripComments(source);
const names = new Set<string>();
for (const m of code.matchAll(/\bexport\s+(?:type\s+)?\{([^}]*)\}/g)) {
for (const specifier of m[1].split(',')) {
const published = specifier.trim().replace(/^type\s+/, '').split(/\s+as\s+/).pop()?.trim();
if (published) names.add(published);
}
}
return names;
};

describe('@object-ui/types — the Phase 3.5 validation types stay retired (objectui#10719)', () => {
it('the declaring module declares none of the eight, and keeps its retirement note', () => {
const source = read('data-protocol.ts');

// Firing controls on the same matcher: a truncated read or a matcher that
// can never match would otherwise pass the absence assertion below.
expect(declares(source, 'ObjectValidationRule'), 'the object-level rule union is missing').toBe(true);
expect(declares(source, 'FilterFieldConfig'), 'the neighbouring filter type is missing').toBe(true);

expect(
RETIRED.filter((name) => declares(source, name)),
[
'data-protocol.ts declares a retired Phase 3.5 validation type again.',
'objectui#10719 removed them with ValidationEngine (maintainer ruling A on objectui#7659):',
'nothing read them. Client-side field validation is FieldValidationRules (form.ts).',
].join('\n'),
).toEqual([]);

// The note is where the retirement and its replacement are recorded in the
// file itself. It is a `//` comment, so declaration emit leaves it out of the
// published `.d.ts`.
expect(source).toContain('// RETIRED (objectui#10719)');
});

it('the root barrel re-exports none of the eight, while its neighbours still ship', () => {
const published = reexported(read('index.ts'));

expect(published.has('ObjectValidationRule'), 'the barrel read found no ObjectValidationRule').toBe(true);
expect(published.has('FieldValidationRules'), 'the barrel read found no FieldValidationRules').toBe(true);

expect(
RETIRED.filter((name) => published.has(name)),
'@object-ui/types re-exports a retired Phase 3.5 validation type again (objectui#10719).',
).toEqual([]);
});
});
Loading
Loading