canonical / canonical/superhref

Have better internal types for config values

Open
#18 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
1
Forks
0
PR merge metrics
No merged PRs in 30d

Description

The three `ConfigValue` shapes (root codec, bare codecs map, `{ codecs, actions }` section) are told apart structurally at runtime by `isCodec` and `sectionOf` in `src/core/runtime/codec-guard.ts`. `sectionOf` is fragile on its own: it decides between a bare codecs map and a `{ codecs, actions }` pair by probing for a `.codecs` property and casting. Nothing at the type level discriminates the members, so the probe is only correct because the compile time validation in `core/types/validate/section.ts` reserves `codecs` and `actions` as codec keys. That is a broad implicit guarantee living far from the code that depends on it, and a config that bypasses the types (plain JS, `as any`) can get a codec field miscategorized as a codecs map or an action map.

The reservation is also an implementation detail leaking into the API: `codecs` and `actions` are already legal as root keys, and `bugs.codecs=x` is an ordinary URL param. With a real discriminant, users could use `codecs` and `actions` as section keys too. Only `patch` and `set` need to stay reserved, since they shadow real members of the bound object.

Lets make `withActions` is the only way to build a section with actions.

- Mark sections when they are built. `withActions` adds a hidden `Symbol` property to the object it returns:

```ts
const SECTION = Symbol();
const withActions = (codecs, actions) => ({ [SECTION]: true, codecs, actions });
```

Telling the shapes apart, at runtime and at the type level, becomes a check for that marker: section with actions has it, a bare codecs map does not. No more guessing from a `.codecs` property, and a `Symbol` cannot clash with user keys, which are strings. A hand written `{ codecs, actions }` object has no marker and its values are not codecs, so it is not a valid config value and fails to compile at that key.

- Convert the config once, up front. `superhref()` walks the raw config a single time and turns each value into one internal shape. `parse`, `patch`, `clear`, and `bind` read only that shape and never inspect the raw config again. The internal shape can be plain objects with a `kind` field, or a function per key that keeps its codecs and actions in its closure so there is nothing to inspect from outside, or small classes checked with `instanceof`. The `kind` field is the simplest and likely enough.

This is a top down rework of the internal type model rather than a local fix. It changes the `Ctx` shape, so it should come after the current PR stack lands. Public API impact: `ConfigValue` stops admitting direct `{ codecs, actions }` literals, and `codecs` and `actions` become legal section keys.

Contributor guide

Open the contributing guide

Research direction

Start with src/core/runtime/codec-guard.ts and core/types/validate/section.ts, then trace superhref(), Ctx, and the parse, patch, clear, and bind entry points. Define the internal discriminated shape and update the conversion so those operations no longer inspect raw config values. Done means withActions-marked sections are recognized, codecs and actions are legal section keys, and direct { codecs, actions } literals are rejected by the public types.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
developer-experience
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.