Skip to main content

bynk_check/
firstparty.rs

1//! First-party standard adapters embedded in the toolchain (v0.17 §4.2).
2//!
3//! The `bynk` conformance surface is shipped with the compiler rather than
4//! authored by the user. When a project `consumes bynk`, the driver injects the
5//! adapter source below as a synthetic unit and provides its binding for the
6//! selected [`Platform`]. The `bynk` root namespace is reserved
7//! (`bynk.namespace.reserved`) so user code can never collide with it.
8
9/// The deploy platform — a selection axis distinct from the `--target
10/// {bundle,workers}` emit mode (§6.2). It chooses which `bynk-<platform>.ts`
11/// binding is linked for the `bynk` surface. v0.17 shipped `cloudflare`;
12/// v0.18 adds `node`, making the axis observable (and giving v0.19's
13/// platform-lock enforcement a second platform to fire against).
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
15pub enum Platform {
16    #[default]
17    Cloudflare,
18    Node,
19    /// The browser (in-browser track, slice 1 — the REPL/playground host). A
20    /// Tier-3 binding exposing the `bynk` surface over Web APIs, composed with
21    /// `BuildTarget::Bundle` only (a browser cannot do the Workers wire model).
22    Browser,
23}
24
25impl Platform {
26    /// The output filename of this platform's `bynk` binding module.
27    pub fn bynk_binding_filename(self) -> &'static str {
28        match self {
29            Platform::Cloudflare => "bynk-cloudflare.ts",
30            Platform::Node => "bynk-node.ts",
31            Platform::Browser => "bynk-browser.ts",
32        }
33    }
34
35    /// The TypeScript source of this platform's `bynk` binding.
36    pub fn bynk_binding_source(self) -> &'static str {
37        match self {
38            Platform::Cloudflare => BYNK_CLOUDFLARE_BINDING,
39            Platform::Node => BYNK_NODE_BINDING,
40            Platform::Browser => BYNK_BROWSER_BINDING,
41        }
42    }
43
44    /// The platform's stable name (for the `--platform` flag and diagnostics).
45    pub fn as_str(self) -> &'static str {
46        match self {
47            Platform::Cloudflare => "cloudflare",
48            Platform::Node => "node",
49            Platform::Browser => "browser",
50        }
51    }
52}
53
54/// The unit name of the reserved first-party surface adapter.
55pub const BYNK_UNIT: &str = "bynk";
56
57/// The capabilities the env-free `bynk` surface exports (its `exports
58/// capability { … }` line in `bynk.bynk`). First-party symbols live on
59/// synthetic files and are excluded from the binding index, so the
60/// unknown-name quick-fix (#852) offering `consumes bynk { … }` needs this
61/// explicit list. Kept in sync with the adapter source by
62/// `bynk_surface_capabilities_match_the_adapter`.
63pub const BYNK_SURFACE_CAPABILITIES: &[&str] = &[
64    "Clock",
65    "Random",
66    "Logger",
67    "Fetch",
68    "Secrets",
69    "Locale",
70    "Idempotency",
71    "Events",
72];
73
74/// The unit name of the first-party Cloudflare platform adapter (v0.19,
75/// decision 0026): inside the reserved `bynk.*` prefix, so no separate
76/// reservation rule is needed. The surface unit `bynk` stays the portability
77/// marker; `bynk.<platform>` units are the platform-locked ones.
78pub const CLOUDFLARE_UNIT: &str = "bynk.cloudflare";
79
80/// The fixed Worker binding name for the Kv namespace (decision C1, v0.19):
81/// one namespace, one `[[kv_namespaces]]` stanza, one `env.KV` field.
82pub const KV_BINDING_NAME: &str = "KV";
83
84/// v0.18 decision 0021 / v0.19 decision 0025: which first-party provider
85/// classes take the Worker `env` as a constructor argument, keyed by
86/// (unit, provider class). `SecretsProvider` accepts an optional env with a
87/// `globalThis` probe fallback; `WorkersKv` *requires* env on use — KV
88/// namespaces exist only on the Worker `env`, never on `globalThis`.
89pub fn provider_takes_env(unit: &str, provider: &str) -> bool {
90    matches!(
91        (unit, provider),
92        (BYNK_UNIT, "SecretsProvider") | (CLOUDFLARE_UNIT, "WorkersKv")
93    )
94}
95
96/// v0.19 decision 0024: which first-party units are **platform-native** —
97/// consuming one locks its deployment unit to the returned platform. This is
98/// the metadata that drives effective-platform computation, `env` resource
99/// typing, and `wrangler.toml` stanza derivation; no user-facing marker
100/// syntax exists (additive later, when third-party platform adapters become
101/// a goal).
102pub fn platform_of(unit: &str) -> Option<Platform> {
103    match unit {
104        CLOUDFLARE_UNIT => Some(Platform::Cloudflare),
105        _ => None,
106    }
107}
108
109/// The unit names of the first-party collection commons (v0.20b): the
110/// Bynk-written combinator stdlib over the built-in `List`/`Map` kernel.
111/// Inside the reserved `bynk.*` prefix; injected when `uses`-imported.
112pub const LIST_UNIT: &str = "bynk.list";
113pub const MAP_UNIT: &str = "bynk.map";
114
115/// `bynk.list` — combinators over the `List` kernel (`fold`, `prepend`,
116/// `length`, `get`, `foldEff`), written in ordinary Bynk (decision 0034):
117/// the first real consumer of v0.20a generics, lambdas, and effectful
118/// traversal. Order-preserving combinators build with `fold` + `prepend`
119/// and a final `reverse` — O(n) builds, never `append` (which would be
120/// O(n²) over the array lowering).
121pub const BYNK_LIST_SRC: &str = include_str!("firstparty/bynk.list.bynk");
122
123/// `bynk.map` — combinators over the `Map` kernel (`empty`, `insert`, `get`,
124/// `keys`, `length`). `fromList` is deliberately absent: Bynk has no pair
125/// type to spell a `List[(K, V)]` with, so map construction is `Map.empty()`
126/// + `insert` (revisit with tuples or generic records).
127pub const BYNK_MAP_SRC: &str = include_str!("firstparty/bynk.map.bynk");
128
129/// Inside the reserved `bynk.*` prefix; injected when `uses`-imported.
130pub const STRING_UNIT: &str = "bynk.string";
131
132/// `bynk.string` — Bynk-written helpers over the v0.22a string kernel
133/// (`concat`, the `List` `fold`, and the `Option` kernel methods). The
134/// kernel itself is compiler built-in (ADR 0046); only derived helpers
135/// live here. `join` folds to `Option[String]` so empty-string *elements*
136/// are joined faithfully (a bare `""` accumulator could not tell "nothing
137/// yet" from "first element was empty").
138pub const BYNK_STRING_SRC: &str = include_str!("firstparty/bynk.string.bynk");
139
140/// Inside the reserved `bynk.*` prefix; injected when `uses`-imported by
141/// application code (a message-bundle commons, ADR 0272) — never directly by
142/// the `bynk` adapter itself any more (see [`LOCALE_TYPES_UNIT`]).
143pub const LOCALE_UNIT: &str = "bynk.locale";
144
145/// `bynk.locale` — the bundle-free `render`/`renderArg` helpers and the
146/// `message`/`withText`/`withWhole`/`withNum`/`withMoment` builder API
147/// (slice 1: `render` formats `code` plus a deterministic sorted-by-key
148/// rendering of `params`; no lookup, no locale-dependent behaviour). `uses
149/// bynk.locale.types` for the types its own signatures name.
150pub const BYNK_LOCALE_SRC: &str = include_str!("firstparty/bynk.locale.bynk");
151
152/// Inside the reserved `bynk.*` prefix; injected when `uses`-imported. Also
153/// `uses`-imported by the `bynk` adapter itself, to give `capability Locale`'s
154/// `current() -> Effect[LocaleTag]` a type to name (an adapter may `uses` a
155/// commons; a commons cannot in turn reference an adapter). Split out of
156/// `bynk.locale` (locale-negotiation-slice-2 follow-up, #886) specifically so
157/// a context can reach `LocaleTag` without also pulling in `bynk.locale`'s
158/// own `render` — which previously collided with a message-bundle commons's
159/// own synthesised `render` (ADR 0272) the moment a context needed both.
160pub const LOCALE_TYPES_UNIT: &str = "bynk.locale.types";
161
162/// `bynk.locale.types` — `LocaleTag`, `MessageArg`, `Message`: a
163/// dependency-free leaf (no `uses` of its own) so any consumer, including
164/// the `bynk` adapter itself, can reach these types without also reaching
165/// `bynk.locale`'s value-level API.
166pub const BYNK_LOCALE_TYPES_SRC: &str = include_str!("firstparty/bynk.locale.types.bynk");
167
168/// The reserved `bynk` conformance-surface adapter (env-free core). It has no
169/// `binding` clause — the toolchain supplies one per platform (see
170/// [`Platform::bynk_binding_source`]).
171pub const BYNK_ADAPTER_SRC: &str = include_str!("firstparty/bynk.bynk");
172
173/// The Cloudflare binding for the `bynk` surface. Implements the canonical
174/// provider symbols against the platform host API. The refined `Uuid` is built
175/// through its emitted validating `.of` constructor (§4.4), treating the
176/// unreachable `Err` as a bug rather than trusting the value away.
177const BYNK_CLOUDFLARE_BINDING: &str = include_str!("firstparty/bindings/bynk-cloudflare.ts");
178
179/// The Node (≥ [`NODE_MAJOR_FLOOR`](crate::NODE_MAJOR_FLOOR)) binding for the
180/// `bynk` surface (v0.18). Deliberately
181/// near-identical to the cloudflare binding: `Date.now`, the global
182/// `crypto`/`fetch`, and `console` are the same host API on both runtimes —
183/// which is exactly the ambient-surface portability claim (spec §4.2). The
184/// `SecretsProvider` reads `process.env` through the same `globalThis` probe
185/// (never bare `process`, which would demand @types/node at the tsc gate).
186const BYNK_NODE_BINDING: &str = include_str!("firstparty/bindings/bynk-node.ts");
187
188/// The Browser binding for the `bynk` surface (in-browser track, slice 1). Tier-3:
189/// the same capability surface over Web APIs. `Clock`/`Random`/`Logger` are
190/// byte-identical to the Node binding (`Date.now`, Web Crypto `crypto.randomUUID`,
191/// `console` — all Web standards); the two substitutions are the playground safety
192/// boundary — `Fetch` is **withheld** (throws; no outbound egress from the
193/// playground origin) and `Secrets` is **unavailable** (throws; a browser has no
194/// secret store), both failing loudly rather than silently degrading.
195const BYNK_BROWSER_BINDING: &str = include_str!("firstparty/bindings/bynk-browser.ts");
196
197/// The first-party Cloudflare platform adapter (v0.19): the platform's real
198/// infrastructure capabilities, as they are — no portable intersection
199/// (decision 0016). The v0.19 surface was the minimal, collection-free `Kv`
200/// (decision 0023); v0.23 adds the `list` drain and `putTtl` (0050/0051);
201/// structured values are v0.22-codec composition, and `Queue` remains its
202/// own future increment. Like the `bynk` surface it has no `binding`
203/// clause — the toolchain supplies the binding.
204pub const CLOUDFLARE_ADAPTER_SRC: &str = include_str!("firstparty/bynk.cloudflare.bynk");
205
206/// Every first-party `.bynk` source, paired with its unit name — the single
207/// list every consumer iterates: hover (`describe_firstparty_symbol`),
208/// completion (`EMBEDDED_UNITS`), and the parse guard
209/// (`every_first_party_source_parses`). Before this list each consumer kept its
210/// own hand-maintained copy, and `bynk.locale`/`bynk.locale.types` were added to
211/// none of them — so the whole locale surface hovered as nothing and was absent
212/// from completion (#901). Add a first-party commons here once; the
213/// `firstparty_sources_cover_every_src_const` drift guard
214/// (`bynkc/tests/firstparty_sources.rs`) fails if a `*_SRC` const is left out.
215pub const FIRSTPARTY_SOURCES: &[(&str, &str)] = &[
216    (BYNK_UNIT, BYNK_ADAPTER_SRC),
217    (CLOUDFLARE_UNIT, CLOUDFLARE_ADAPTER_SRC),
218    (LIST_UNIT, BYNK_LIST_SRC),
219    (MAP_UNIT, BYNK_MAP_SRC),
220    (STRING_UNIT, BYNK_STRING_SRC),
221    (LOCALE_UNIT, BYNK_LOCALE_SRC),
222    (LOCALE_TYPES_UNIT, BYNK_LOCALE_TYPES_SRC),
223];
224
225/// The output path of the Cloudflare platform adapter's binding module,
226/// beside the adapter's emitted `bynk/cloudflare.ts` (distinct from the
227/// `bynk` *surface*'s per-platform `bynk-cloudflare.ts`).
228pub const CLOUDFLARE_BINDING_FILENAME: &str = "bynk/cloudflare.binding.ts";
229
230/// The Cloudflare platform adapter's binding. `WorkersKv` reads the Worker
231/// `env` explicitly (decision 0025): KV namespaces exist only on `env` —
232/// there is no `globalThis` path — so a missing binding is a clear runtime
233/// error rather than a silent fallback.
234const CLOUDFLARE_BINDING: &str = include_str!("firstparty/bindings/cloudflare.binding.ts");
235
236/// The toolchain-supplied binding for the Cloudflare platform adapter.
237pub fn cloudflare_binding_source() -> &'static str {
238    CLOUDFLARE_BINDING
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    /// The hardcoded [`BYNK_SURFACE_CAPABILITIES`] must match the adapter's
246    /// `exports capability { … }` line — otherwise the unknown-name quick-fix
247    /// (#852) would offer a stale `consumes bynk { … }` set.
248    #[test]
249    fn bynk_surface_capabilities_match_the_adapter() {
250        let src = BYNK_ADAPTER_SRC;
251        let line = src
252            .lines()
253            .find(|l| l.trim_start().starts_with("exports capability {"))
254            .expect("adapter declares `exports capability`");
255        let inner = line
256            .split_once('{')
257            .and_then(|(_, r)| r.split_once('}'))
258            .map(|(names, _)| names)
259            .expect("braced capability list");
260        let declared: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
261        assert_eq!(
262            declared, BYNK_SURFACE_CAPABILITIES,
263            "BYNK_SURFACE_CAPABILITIES is out of sync with bynk.bynk"
264        );
265    }
266}