tjs-lang 0.8.7 → 0.9.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/CLAUDE.md +12 -4
- package/dist/experiments/ambient/probe.d.ts +52 -0
- package/dist/index.js +115 -171
- package/dist/index.js.map +4 -4
- package/dist/scripts/build-editors.d.ts +19 -0
- package/dist/src/css/dimensions.d.ts +23 -0
- package/dist/src/css/index.d.ts +85 -0
- package/dist/src/css/predicates.d.ts +38 -0
- package/dist/src/css/shorthands.d.ts +31 -0
- package/dist/src/css/style.d.ts +48 -0
- package/dist/src/lang/emitters/js.d.ts +9 -1
- package/dist/src/lang/index.d.ts +2 -2
- package/dist/src/lang/parser-transforms.d.ts +9 -5
- package/dist/src/lang/parser.d.ts +2 -0
- package/dist/src/lang/predicate.d.ts +60 -0
- package/dist/src/lang/transpiler.d.ts +3 -2
- package/dist/src/lang/types.d.ts +16 -0
- package/dist/src/schema/index.d.ts +12 -0
- package/dist/tjs-browser-from-ts.js +20 -20
- package/dist/tjs-browser-from-ts.js.map +3 -3
- package/dist/tjs-browser.js +100 -90
- package/dist/tjs-browser.js.map +4 -4
- package/dist/tjs-css.js +193 -0
- package/dist/tjs-css.js.map +7 -0
- package/dist/tjs-eval.js +43 -42
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +13 -13
- package/dist/tjs-from-ts.js.map +2 -2
- package/dist/tjs-lang.js +102 -92
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-runtime.js +10 -0
- package/dist/tjs-runtime.js.map +7 -0
- package/dist/tjs-schema.js +6 -0
- package/dist/tjs-schema.js.map +7 -0
- package/dist/tjs-vm.js +55 -54
- package/dist/tjs-vm.js.map +4 -4
- package/docs/ambient-contracts.md +261 -0
- package/editors/ace/ajs-mode.js +214 -233
- package/editors/codemirror/ajs-language.js +1570 -233
- package/editors/editors-build.test.ts +29 -0
- package/editors/monaco/ajs-monarch.js +239 -195
- package/llms.txt +4 -0
- package/package.json +35 -4
- package/src/css/css.test.ts +122 -0
- package/src/css/dimensions.test.ts +112 -0
- package/src/css/dimensions.ts +146 -0
- package/src/css/index.ts +243 -0
- package/src/css/perf.bench.test.ts +109 -0
- package/src/css/predicates.ts +232 -0
- package/src/css/property-aware.test.ts +84 -0
- package/src/css/shorthands.test.ts +90 -0
- package/src/css/shorthands.ts +113 -0
- package/src/css/style.test.ts +125 -0
- package/src/css/style.ts +134 -0
- package/src/index-tsfree.test.ts +58 -0
- package/src/lang/emit-verified-predicate.test.ts +95 -0
- package/src/lang/emitters/dts.test.ts +31 -0
- package/src/lang/emitters/dts.ts +23 -4
- package/src/lang/emitters/js.ts +33 -1
- package/src/lang/from-ts.test.ts +1 -1
- package/src/lang/generic-verified-predicate.test.ts +39 -0
- package/src/lang/index.ts +14 -5
- package/src/lang/parser-transforms.ts +109 -14
- package/src/lang/parser.ts +9 -2
- package/src/lang/predicate-evaluator.test.ts +49 -0
- package/src/lang/predicate-report.test.ts +54 -0
- package/src/lang/predicate.ts +268 -0
- package/src/lang/redos-lint.test.ts +81 -0
- package/src/lang/transpiler.ts +13 -0
- package/src/lang/type-verified-predicate.test.ts +83 -0
- package/src/lang/types.ts +17 -0
- package/src/lang/typescript-syntax.test.ts +2 -1
- package/src/schema/index.ts +62 -0
- package/src/schema/schema.test.ts +66 -0
- package/src/use-cases/bootstrap.test.ts +14 -4
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Ambient Contracts — probe reality, derive verified predicate contracts
|
|
2
|
+
|
|
3
|
+
> **Status: design note / idea (2026-07-03).** Not built. Captures the direction
|
|
4
|
+
> and a concrete first spike (CSS + DOM). Related: the predicate engine
|
|
5
|
+
> (`src/lang/predicate.ts`), the `tjs-lang/css` library (`src/css/`), the
|
|
6
|
+
> autocomplete introspection bridge (`demo/src/introspection-bridge.ts`,
|
|
7
|
+
> `editors/introspect-value.ts`), and the VM capability model (`src/vm/`).
|
|
8
|
+
|
|
9
|
+
## The itch
|
|
10
|
+
|
|
11
|
+
Static types are pessimistic about ambient runtime environments (the DOM, Node
|
|
12
|
+
globals, `window`, host objects) in ways that create ceremony without safety.
|
|
13
|
+
The canonical example:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
element.addEventListener('input', (e) => {
|
|
17
|
+
const value = e.target.value // ❌ TS: Property 'value' does not exist on 'EventTarget'
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`e.target` is typed `EventTarget | null`, and `EventTarget` has no `.value`, so
|
|
22
|
+
TypeScript forces `(e.target as HTMLInputElement).value` or a `instanceof` dance.
|
|
23
|
+
But the honest situation is: **at runtime the value is either there or it
|
|
24
|
+
isn't, and if it isn't you find out immediately.** The cast doesn't make it
|
|
25
|
+
safer — it just silences a compiler that can't see the real object. A runtime
|
|
26
|
+
predicate is the truthful tool here:
|
|
27
|
+
|
|
28
|
+
```tjs
|
|
29
|
+
// pure, total, verifiable — reads props, no cast, no lie
|
|
30
|
+
function hasValueTarget(e) {
|
|
31
|
+
return e != null && e.target != null && typeof e.target.value === 'string'
|
|
32
|
+
}
|
|
33
|
+
// usage: hasValueTarget(e) ? e.target.value : undefined — no ceremony
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That predicate is exactly what the verifier accepts (member access is pure;
|
|
37
|
+
`typeof` → `TypeOf`), compiles to native, and — crucially — is **serializable**.
|
|
38
|
+
So the thought: instead of hand-writing these, could tooling **probe a real
|
|
39
|
+
ambient environment and derive the predicate contracts automatically?**
|
|
40
|
+
|
|
41
|
+
## The payoff: author against an environment you're not running in
|
|
42
|
+
|
|
43
|
+
The headline use (user, 2026-07-03): **type-checking and autocompletion against a
|
|
44
|
+
non-ambient runtime — editing in a Node-based toolchain while _targeting_ the
|
|
45
|
+
browser** (or the reverse, or Electron, or a specific Bun). Normally you have no
|
|
46
|
+
target environment to introspect, so you fall back to a hand-maintained
|
|
47
|
+
`.d.ts` (`lib.dom`) that drifts from what the engine actually does.
|
|
48
|
+
|
|
49
|
+
Because a probed contract is a **serializable predicate**, it decouples _capture_
|
|
50
|
+
from _consumption_ in time and space: probe the real target once (locally or in
|
|
51
|
+
CI), bottle the contract, and a Node-side editor/typechecker consumes it with **no
|
|
52
|
+
target present**. It beats a `.d.ts` on three axes:
|
|
53
|
+
|
|
54
|
+
- **Grounded in reality, not a drifting spec** — it's what the target _actually_
|
|
55
|
+
exposes (we watched happy-dom diverge 678 vs 1 own-keys from real Chrome; a
|
|
56
|
+
captured contract is the real number).
|
|
57
|
+
- **Target-specific and versioned** — capture from the _actual_ target (this
|
|
58
|
+
browser, this Electron, this Bun), selectable/pinned, not a lowest-common-
|
|
59
|
+
denominator lib.
|
|
60
|
+
- **The same artifact validates at runtime** — a `.d.ts` evaporates; a predicate
|
|
61
|
+
contract can stay as a boundary guard, and its enumerable leaves feed
|
|
62
|
+
`suggest()` completions with real values.
|
|
63
|
+
|
|
64
|
+
Why it's plausible _here_ and not a research project: the contract format already
|
|
65
|
+
exists (`$predicate` / `tjs-lang/schema`), the autocomplete provider already has
|
|
66
|
+
an introspection hook (`getMembers` — point it at a captured contract instead of a
|
|
67
|
+
live object), and this is exactly the metadata the pinned "argument-type-driven
|
|
68
|
+
completion" item wants (e.g. `createElement`'s tag→type return shapes), sourced
|
|
69
|
+
from reality instead of hand-authored. See [[introspection-autocomplete]].
|
|
70
|
+
|
|
71
|
+
## Contract vs. shim — the distinction that decides feasibility
|
|
72
|
+
|
|
73
|
+
"A predicate that stands in for the DOM" can mean two very different things:
|
|
74
|
+
|
|
75
|
+
1. **A contract / validator** — "does this value behave enough like an
|
|
76
|
+
`HTMLElement` / an input event / a `CSSStyleDeclaration`?" Predicates are
|
|
77
|
+
_pure validators_, so this is squarely in scope: shape, property presence,
|
|
78
|
+
method arity, pure invariants.
|
|
79
|
+
2. **A behavioral substitute** — something that, when code calls
|
|
80
|
+
`document.createElement('div')`, actually _does_ it. Predicates **cannot** be
|
|
81
|
+
this (they're pure, stateless, effect-free). That's a shim — happy-dom/jsdom
|
|
82
|
+
territory.
|
|
83
|
+
|
|
84
|
+
The valuable, on-thesis move is **#1 as the bridge to #2**: probe the real
|
|
85
|
+
environment, emit a serializable predicate _contract_, and use it to **certify a
|
|
86
|
+
stand-in** in whatever setting. The predicate isn't the DOM — it's the portable,
|
|
87
|
+
verifiable spec a substitute must satisfy, auto-derived from reality and from the
|
|
88
|
+
surface a given program actually touches.
|
|
89
|
+
|
|
90
|
+
## Why it fits this project
|
|
91
|
+
|
|
92
|
+
- **The introspection probe already exists.** The autocomplete work runs code in
|
|
93
|
+
a real iframe and reads live scope (`introspect-value.ts`,
|
|
94
|
+
`introspection-bridge.ts`); Claude-in-Chrome can drive a _real_ browser. That's
|
|
95
|
+
the "kick the tires" mechanism.
|
|
96
|
+
- **Predicates are the serializable contract form** — verified, ReDoS-safe,
|
|
97
|
+
compile-to-native, travel as data (the `$predicate` keyword already embeds them
|
|
98
|
+
in JSON Schema, now evaluated by `tosijs-schema` via `tjs-lang/schema`).
|
|
99
|
+
- **The VM already models this as capabilities.** It injects `fetch`/`store`/
|
|
100
|
+
`llm`; "DOM-as-capability, predicate-validated at the boundary" is the same
|
|
101
|
+
pattern. A headless setting swaps the stub; the predicate catches divergence.
|
|
102
|
+
- **"Types are examples" already** — deriving a contract from _observed_ real
|
|
103
|
+
values is the same philosophy as `inferTypeFromValue`, sourced from a live probe
|
|
104
|
+
instead of a literal.
|
|
105
|
+
|
|
106
|
+
## The honest constraints
|
|
107
|
+
|
|
108
|
+
- **Most of the DOM's contract is _behavioral_** (layout, events, mutation),
|
|
109
|
+
which a pure predicate can't capture — only shape + pure invariants. So:
|
|
110
|
+
- **Scope to the used surface.** Trace which ambient APIs the code actually
|
|
111
|
+
touches (in the real iframe) and derive a contract for _that slice_, not all
|
|
112
|
+
of `lib.dom.d.ts`.
|
|
113
|
+
- **Behavioral conformance is a separate, impure harness** — run the same probe
|
|
114
|
+
battery against the real env and a candidate stub, diff the outputs. The
|
|
115
|
+
predicate is the pure shape-gate; the harness is the behavior-gate.
|
|
116
|
+
- **Snapshots drift.** Browsers evolve; a captured contract is a dated snapshot.
|
|
117
|
+
Fine — it's re-derivable, and versioning it is a feature (conformance over
|
|
118
|
+
time).
|
|
119
|
+
- **Non-determinism / side effects** must stay outside predicates (the `effects`
|
|
120
|
+
tag already enforces this): a predicate validates the _data_ crossing the
|
|
121
|
+
boundary, never performs the effect.
|
|
122
|
+
|
|
123
|
+
## Shape of the tooling (sketch)
|
|
124
|
+
|
|
125
|
+
1. **Probe** — in a real environment (browser via Claude-in-Chrome, or the
|
|
126
|
+
introspection iframe), walk an ambient object / a program's used surface and
|
|
127
|
+
record: property names + `typeof`, method names + `.length` (arity), and a few
|
|
128
|
+
sampled input→output pairs for pure-ish accessors.
|
|
129
|
+
2. **Derive** — turn the probe record into a predicate cluster (shape checks +
|
|
130
|
+
`typeof` guards + membership sets), run it through `verifyPredicate` (so the
|
|
131
|
+
emitted contract is itself certified safe), and `suggest()`-mine it for the
|
|
132
|
+
enumerable leaves.
|
|
133
|
+
3. **Emit** — a `$predicate` schema (naive validators see structure, aware ones
|
|
134
|
+
run the contract) + optionally a `.d.ts`-ish view for editors.
|
|
135
|
+
4. **Conform** — a harness that validates a stand-in (happy-dom, a hand stub, a
|
|
136
|
+
VM capability) against the contract, failing loud where it diverges. This is
|
|
137
|
+
what would have unblocked the Phase 5 real-`tosijs`-theme measurement (blocked
|
|
138
|
+
because `theme.ts` needs `HTMLElement` at import).
|
|
139
|
+
|
|
140
|
+
## First spike — CSS + DOM (the convergence point)
|
|
141
|
+
|
|
142
|
+
`element.style` (`CSSStyleDeclaration`) is the ideal first target: it's an
|
|
143
|
+
ambient, stateful host object, and we _already have a full CSS value grammar as
|
|
144
|
+
predicates_ (`tjs-lang/css`). So the loop is concrete and small:
|
|
145
|
+
|
|
146
|
+
- **`event.target` demo** — show the `hasValueTarget` predicate compiles, is
|
|
147
|
+
verified pure, is total, and validates a real input event while rejecting a
|
|
148
|
+
click on a `<div>`. The predicate TypeScript won't let you write cleanly is a
|
|
149
|
+
perfectly ordinary verified predicate. (This is the "asinine" antidote, and
|
|
150
|
+
needs no browser — synthetic event-shaped objects suffice for the unit; a real
|
|
151
|
+
browser confirms on live events.)
|
|
152
|
+
- **`CSSStyleDeclaration` probe** — introspect real `element.style` in a browser,
|
|
153
|
+
derive a shape contract, and diff a happy-dom `style` against it: where does the
|
|
154
|
+
stub lie? Leaf _values_ ride the existing CSS predicates (`isColorValue`,
|
|
155
|
+
`isDimension`, …), so this exercises probe → contract → conformance end-to-end
|
|
156
|
+
while reusing what phases 1–5 built.
|
|
157
|
+
|
|
158
|
+
## Ask the environment directly — `CSS.supports` (tosijs-ui port finding)
|
|
159
|
+
|
|
160
|
+
The tosijs 2.0 port arrived at the same idea from the other side (see
|
|
161
|
+
`../tosijs/TJS-PORT-DX.md`): the strongest form of "probe reality" for CSS is to
|
|
162
|
+
**ask the browser's own validator** — `CSS.supports(prop, value)` is exact, always
|
|
163
|
+
current with the engine's real support (vendor prefixes, new features, the full
|
|
164
|
+
`calc()`/`var()` grammar), and zero-maintenance. It closes the exact gap the
|
|
165
|
+
hand-authored grammar has: `isCssProperty('align-kontent')` returns `true`
|
|
166
|
+
(identifier-shaped), but `CSS.supports('align-kontent', …)` in a real browser
|
|
167
|
+
returns `false`. Property names for autocomplete come from the same place (a
|
|
168
|
+
throwaway element's `.style` keys / `CSSStyleDeclaration.prototype`).
|
|
169
|
+
|
|
170
|
+
This sharpens the contract-vs-fallback split by **when** validation runs:
|
|
171
|
+
|
|
172
|
+
- **Runtime, in a browser** → `CSS.supports` is ground truth (exact, free). A
|
|
173
|
+
`CSS.supports`-based check is predicate-_safe to run_ but references the ambient
|
|
174
|
+
`CSS` object, so it can't be statically _verified_ (it's effectful/ambient).
|
|
175
|
+
- **Transpile-time / SSR / Node** → no `CSS`, so the hand-authored grammar
|
|
176
|
+
predicates (`isColor`/`isLength`/`isStyleValueFor`) are the portable fallback.
|
|
177
|
+
|
|
178
|
+
So the ideal CSS-value check prefers `CSS.supports` when `typeof CSS !== 'undefined'`
|
|
179
|
+
and falls back to the grammar predicate otherwise — which is exactly the
|
|
180
|
+
probe-reality / portable-fallback pairing this whole note is about, one leaf down.
|
|
181
|
+
**Caveat (verified, and it's the same "the stand-in lies" finding): happy-dom
|
|
182
|
+
stubs `CSS.supports` to return `true` for everything** (even `width: banana`) — so
|
|
183
|
+
tests can't rely on it; ground truth needs a real browser or a real CSS engine.
|
|
184
|
+
|
|
185
|
+
## Findings from the first spike (2026-07-03)
|
|
186
|
+
|
|
187
|
+
Built in `experiments/ambient/` (`probe.ts` + two demo tests, 7 green):
|
|
188
|
+
|
|
189
|
+
- **The `event.target` predicate is ordinary.** `hasValueTarget(e)` (reads
|
|
190
|
+
`e.target.value`, the thing TS rejects) verifies pure/safe, compiles native, and
|
|
191
|
+
is total — no throw on a missing target. The cast TS demands buys nothing a
|
|
192
|
+
predicate doesn't. (`event-target.demo.test.ts`.)
|
|
193
|
+
- **Host objects hide their surface on the prototype.** Probed a real
|
|
194
|
+
`CSSStyleDeclaration` via happy-dom: `Object.keys(style)` is nearly empty, yet
|
|
195
|
+
`typeof style.color === 'string'` and `typeof style.setProperty === 'function'`.
|
|
196
|
+
So a naive own-enumerable probe derives an empty contract — **the probe must
|
|
197
|
+
walk the prototype chain** (`getOwnPropertyNames` per proto) or check a scoped
|
|
198
|
+
key list. `probeShape` now does both.
|
|
199
|
+
- **Scope to the used surface — the full proto chain is huge.** An `HTMLElement`'s
|
|
200
|
+
chain is hundreds of props; a whole-shape contract would be enormous and
|
|
201
|
+
over-specified. A `keys: [...]` scope (the surface a program actually touches,
|
|
202
|
+
traced from the real env) is the right input. Demonstrated: a 4-key contract for
|
|
203
|
+
`style` verifies safe, accepts a real happy-dom `style` and a conforming hand
|
|
204
|
+
stub, rejects a plain object missing the methods.
|
|
205
|
+
- **Reading accessors during a probe can have side effects** (`offsetWidth` forces
|
|
206
|
+
layout). A scoped key list bounds this; a stricter probe would read property
|
|
207
|
+
_descriptors_ rather than the values. Getters that throw are skipped.
|
|
208
|
+
- **CSS convergence holds.** Leaf values off the real object ride the existing
|
|
209
|
+
`tjs-lang/css` predicates (`isColorValue(style.color)`), so shape-gate (contract)
|
|
210
|
+
+ value-grammar (css) compose — the recursive style structure from CSS phase 4
|
|
211
|
+
is the same shape a DOM-derived contract would target.
|
|
212
|
+
|
|
213
|
+
Net: the probe→derive→verify→conform loop works end-to-end on a real host object;
|
|
214
|
+
the open work is sourcing the used-surface scope (trace) and the behavioral half.
|
|
215
|
+
|
|
216
|
+
### Real-browser findings (via haltija `/eval`, live Chrome — 2026-07-03)
|
|
217
|
+
|
|
218
|
+
Confirmed against a real browser (not happy-dom), which sharpened three points:
|
|
219
|
+
|
|
220
|
+
- **Builtins don't honor `new`.** `new HTMLDivElement()` → `TypeError: Failed to
|
|
221
|
+
construct 'HTMLDivElement': Illegal constructor`. You **don't need `new`** — the
|
|
222
|
+
shape is on the constructor's `.prototype` chain
|
|
223
|
+
(`HTMLDivElement → HTMLElement → Element → Node → EventTarget`), readable
|
|
224
|
+
statically, no instance, no side effects. Cleaner than instantiating.
|
|
225
|
+
- **The manager vends the type.** `document.createElement('div').constructor ===
|
|
226
|
+
HTMLDivElement` (and `'a' → HTMLAnchorElement`, `'input' → HTMLInputElement`).
|
|
227
|
+
So a factory's output gives you _both_ a live example (real default values — the
|
|
228
|
+
"types are examples" leaf) _and_ the type identity (`.constructor` → name +
|
|
229
|
+
`.constructor.prototype` → the surface). Two complementary probe sources that
|
|
230
|
+
cross-check.
|
|
231
|
+
- **Structural contracts are realm-portable; `instanceof` is not — proved.** An
|
|
232
|
+
`<iframe>`'s input: `instanceof HTMLInputElement` (top realm) = **false**, but
|
|
233
|
+
the structural predicate (`typeof x.value === 'string' && x.tagName === 'INPUT'`)
|
|
234
|
+
= **true**. This is the whole argument in one line: nominal DOM types are
|
|
235
|
+
realm-bound (the stand-in problem _is_ a realm problem), a derived structural
|
|
236
|
+
predicate crosses realms. (happy-dom couldn't show this — it shares one
|
|
237
|
+
`HTMLInputElement` across `Window`s; the real browser does.)
|
|
238
|
+
- **Bonus — the stand-in already diverges from reality.** Real Chrome's `style`
|
|
239
|
+
has **678 own keys** (CSS properties as own props); happy-dom's had ~1 (on the
|
|
240
|
+
proto). Same object, wildly different probe result — exactly the conformance gap
|
|
241
|
+
the harness exists to catch, and proof that you must probe _reality_, not a
|
|
242
|
+
stand-in, to derive the contract.
|
|
243
|
+
|
|
244
|
+
### Generalizes beyond the DOM — Node/Bun internals
|
|
245
|
+
|
|
246
|
+
The probe is environment-agnostic: `process`, `Buffer`, `node:fs`, `Bun.*`,
|
|
247
|
+
`globalThis` are ambient objects with the same shape. Derive a used-surface
|
|
248
|
+
contract from the _real_ runtime and certify a stand-in — e.g. "does my `process`
|
|
249
|
+
usage survive in the browser polyfill?" or "does Bun's `process` satisfy the
|
|
250
|
+
contract my code derived from Node's?". It's the realm/stand-in problem one level
|
|
251
|
+
up: cross-_runtime_ portability instead of cross-_frame_. Same tool.
|
|
252
|
+
|
|
253
|
+
## Open questions
|
|
254
|
+
|
|
255
|
+
- Where's the line between "derive automatically" and "author, then verify
|
|
256
|
+
against reality"? The probe likely _proposes_ a contract a human trims.
|
|
257
|
+
- Method behavior: can we derive useful pure invariants for methods
|
|
258
|
+
(idempotence, `getComputedStyle(x)` shape) or only signatures?
|
|
259
|
+
- Editor story: a derived `$predicate` contract as the source of both validation
|
|
260
|
+
_and_ autocomplete (the introspection-autocomplete north star) for host objects
|
|
261
|
+
that ship no good `.d.ts`.
|