Skip to content

transform combinator - #31

Open
jbolda wants to merge 3 commits into
mainfrom
model-schema-transforming
Open

jbolda wants to merge 3 commits into
mainfrom
model-schema-transforming

Conversation

@jbolda

@jbolda jbolda commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Motivation

Adding a transform combinator that allows one to make adjustments based on the input of multiple options (or nested transform()). Useful for when values depend on other values.

Approach

Added a new combinator. The types are able to resolve as we only focus on the specific options that are given to the transform() combinator.

@pkg-pr-new

pkg-pr-new Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/configliere@31

commit: 2c41ae0

@jbolda jbolda changed the title transformModel combinator transform combinator Sep 11, 2026
@jbolda
jbolda marked this pull request as ready for review September 11, 2026 20:06
@jbolda
jbolda changed the base branch from project-tom-hagen-readme to main September 23, 2026 15:24
@jbolda
jbolda force-pushed the model-schema-transforming branch from 1dce896 to c2313e2 Compare September 23, 2026 16:17
@jbolda
jbolda added this pull request to stack #34 September 23, 2026 16:22
@cowboyd

cowboyd commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Some more robo feedback which makes sense (basically it was able to create breakage):

Blocking issue

The callback’s inferred input and its runtime input disagree.

The type uses ModelOf<Fold<RouteZero, E>>, so an outer transform may legally consume fields produced by a nested transform. Runtime scope, however, is computed only from parameter keys:


let added = keys(next).filter(...);


Then the callback receives:


options: pick(model, op.keys)


I verified this compiles and crashes:

transform(
  ({ options }) => ({
    outer: options.inner.toUpperCase(),
  }),
  transform(
    ({ options }) => ({ inner: options.raw }),
    option(name("raw"), schema(type("string"))),
  ),
)


options.inner is typed as string but is undefined at runtime.

The same problem occurs when a transform’s elements cross a checkpoint(): the type includes the aggregate folded model, while runtime collects keys from only the final phase. Either:

• restrict contents to same-phase parameter declarations, or
• make runtime scoping faithfully track the complete typed output of the nested batch.

Paradigm fit

The underlying idea fits well:

• Parameters capture source values.
• A later operation derives the application model.
• It runs after phase binding.
• Standard Schema issues remain part of the normal issue channel.
• The inferred runtime model reflects derived values.

But this API feels narrower and more structural than our stated “any valid A → B transformation” paradigm. It introduces a special AddModelTransform operation, a special callback context, and a batch-with-transformer construct. More importantly, it calls itself a model transform while actually doing an object merge:



model = { ...model, ...result };


That cannot remove fields or turn an input model into a genuinely different value. It is presently a scoped derivation/refinement combinator, not a holistic model transformation.

I’d decide that distinction first:

• If this is deliberately “derive fields from a group of parameters,” name and constrain it accordingly.
• If it is the previously open “holistic model transform,” give it honest A → B semantics and let the output become the model rather than implicitly merging it.

Other concerns

• context.phase exposes parameter definitions and schemas to application logic. That couples transformations to definition internals and invites “config ball” behavior. The test comparing context.phase.port.schema directly conflicts with the repository guidance against inspecting phases and parameters.
• The nestedApplications test asserts an implementation call count, which the testing conventions explicitly prohibit.
• The PR promotes model transforms from an open question to an invariant in the same change that selects and implements the API. Given our “API sketch → exact types → tests → implementation” rule, I’d keep this as a proposed design until the semantics above are settled.
• Raising the static-fold chunk from 20 to 40 looks like an unexplained type-system workaround and globally increases checker work. It should be justified independently or avoided.
• Transform-generated issues need an explicit addressing policy. Raw Standard Schema paths such as ["port"] may need route/parameter qualification to remain consistent with normalized Configliere diagnostics.
• “Options” is misleading if transform() accepts arbitrary route elements, nested transforms, controls, routes, checkpoints, and custom functions.

The implementation is otherwise quite clean, and the existing tests plus lint pass. I think the useful core is real, but the API boundary needs one more design pass—especially around whether this is scoped derivation or true model transformation.

@jbolda
jbolda force-pushed the model-schema-transforming branch from c2313e2 to 8ce877d Compare September 24, 2026 06:00
@cowboyd
cowboyd force-pushed the model-schema-transforming branch from 4f2944e to dcf5645 Compare September 26, 2026 16:31
@cowboyd
cowboyd force-pushed the model-schema-transforming branch from dcf5645 to 2c41ae0 Compare September 26, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants