Repository navigation
Conversation
extractItems flattens pulled page chunks with Stream.transformPull, and single-page lists use Stream.fromEffect. Both drop Effect's concurrent-merge machinery (Queue, Semaphore, MutableList) from every bundle that uses a paginated operation.
DISTILLED_SERVER_RETRY_HINT_CAP_MS is read from a caller-provided ConfigProvider (looked up by its context key) or process.env. Importing effect/Config and effect/ConfigProvider for this one optional knob added about 5 KB gzip to every SDK bundle.
The root "effect" barrel loads every Effect module at runtime: 3.3 MB of heap and about 35 ms on import for every SDK that uses error-category (Cloudflare among them).
Each generated operation allocated its own method object, five getters and several closures: 1.3 KB per operation (2.1 KB paginated) at import, so EC2's 807 operations cost about 1 MB before any call. Methods and getters now live on a shared prototype; .pages/.items are built on first access. 231 B / 272 B per operation.
A PURE marker lets a bundler drop an unused call, but its arguments are still evaluated, and an unmarked call among them counts as a side effect. So `S.Array(Tag.pipe(T.XmlName("item")))` kept `Tag` and everything it references in every bundle that imports the module. annotateNestedCalls marks every call and `new` in top-level initializers and class extends clauses, outside function bodies, after generation.
Output of annotateNestedCalls + oxfmt over packages/*/src/services: the same code the generator now writes. EC2 describeRegions bundle: 137 KB -> 75 KB gzip; Elasticsearch 68 KB -> 53 KB.
Service modules declare thousands of top-level S.suspend(...).annotate(...) schemas (EC2 2,575, Elasticsearch 3,272), and importing a module built all of them: about 550 B each. core/schema's suspend now returns a small object on Effect's schema prototype that builds the same AST on first .ast access: about 180 B each.
Generalizes the lazy suspend: a lazy schema holds a builder and pending annotations, and on first use of .ast (or an own member like an array's value) builds the real schema and copies its own properties onto itself. Top-level S.Array / S.Record consts (EC2 784, Elasticsearch 2,265) no longer construct at import.
V8 keeps a module's source text in the heap and stores it at one byte per character only while every character is Latin-1. One em dash in an API description doubled the whole file: AWS S3's compiled module retained 2,240 KB at import, 1,570 KB without its 3 non-Latin-1 comment characters. toLatin1 spells comment punctuation in ASCII (U+XXXX for the rest) and escapes characters inside string, template and type literals, so values are unchanged.
Output of toLatin1 + oxfmt over packages/*/src/services (707 files): the same code the generator now writes.
gcp and google-workspace also generate src/unstable-services through the same CLI.
Same reason as the generated modules: a non-Latin-1 character anywhere in a module (an em dash in a comment, usually) makes V8 store its whole source at two bytes per character. core's runtime modules alone went from 633 KB to 410 KB of import heap, paid by every SDK. latin1-source.test.ts keeps it that way for every package's src (codegen and tests excluded).
An Effect S.TaggedError class carries its own copies of about fifteen static methods plus a tagged struct schema: about 4.6 KB per class at import, and generated services declare 9,848 of them (EC2 248). core/schema's TaggedError now returns a small constructor that builds the real class on first construction or first schema static (ast, fields, make, ...), then inherits from it; decoding still yields instances of the declared class. About 1.3 KB per class until used.
The lazy TaggedError looked up Effect's error base at module load with an unmarked S.TaggedError() call, so every bundle importing core/schema kept Effect's class machinery even with no error classes (Resend +0.2 KB gzip). The lookup and the shared statics object are now created on first use.
schema-lab compares schema representations on real distilled shapes (EC2, S3, DynamoDB, Stripe, Cloudflare Workers): Effect Schema eager/lazy/JIT/AOT, valibot, zod/mini, typebox, and static descriptors; plus structural dedupe of named shapes (Moore partition refinement).
Specs repeat shapes under different names; each copy was its own runtime schema. dedupeSchemas keeps the first schema of each structural class (iterated to a fixpoint through references) and turns the rest into typed aliases, so exports and types are unchanged. Operation inputs keep their own object, and AWS XML/query modules are skipped because those protocols put a shape's identifier on the wire.
90,119 schema consts become aliases (Stripe 1,535, Cloudflare Workers 403).
WebCrypto is async-only, and signing one request made six promise round trips (two digests, four HMAC key imports+signs on cache miss, one on hit). On Node, Bun and Deno the signer now uses node:crypto (found via process.getBuiltinModule, so the module still loads elsewhere) and falls back to WebCrypto. Added latency per call: S3 listBuckets 352 -> 149 us, DynamoDB listTables 218 -> 103 us, STS getCallerIdentity 282 -> 125 us.
core/api: the call is a plain flatMap chain (no Effect.gen, protocol read from its cached context instead of provideContext), and retry machinery (Ref + schedule) is built only after a failure the policy retries; the first failure is replayed as attempt one so delays and counts are unchanged. AWS: hot protocol/client functions use Effect.fnUntraced instead of Effect.fn, which captured a stack trace per call. Added latency: Resend listLogs 27.6 -> 12.1 us, S3 listBuckets 149 -> 117 us, DynamoDB listTables 103 -> 81 us.
createHmac with a raw key re-prepares it on every call (7 us); a KeyObject made once per derived key costs 1.6 us. DynamoDB listTables added latency 81 -> 60 us.
Every response body went Effect Stream -> web ReadableStream -> new Response(stream).text(). Outputs with no streaming or payload member are now read with response.text directly (decided once per operation). Also drops four per-call Effect.logDebug calls. Added latency: S3 listBuckets 117 -> 66 us, DynamoDB listTables 60 -> 43 us, STS 125 -> 55 us.
V8 keeps every loaded module's source text in the heap, so doc comments in lib/*.js cost memory in every process that imports an SDK (44% of AWS S3's compiled service module). scripts/strip-lib-docs.ts removes whole-line JSDoc blocks from lib/**.js after tsc (pnpm build, the release workflow, the SDK bench's --build), keeping line numbers so source maps stay valid and checking that no string literal changed. Docs remain in .d.ts and src/. 17.9 MB removed across 2,778 files.
@distilled.cloud/core/crypto: a context reference (never in requirements) with SHA-256, HMAC-SHA256 and key import. The default uses node:crypto when the runtime has it and WebCrypto otherwise; Crypto.webCrypto / Crypto.nodeCrypto force one, Crypto.layer(impl) plugs in any other (HSM, native addon, test double). Effect's own Crypto service has no HMAC and is a required service, so it would change every operation's requirements. AWS SigV4 now signs through it; derived signing keys are cached as imported key handles, keyed by implementation.
References to leaf aliases (export const Status = S.String) now compare by the schema they alias, so structs that differ only in which string enum a field names merge too.
21,887 more schema consts become aliases.
Every export costs ~280 B per importing process (binding cell, namespace accessor, name string); Stripe's service module had 14,224. Generated modules now export only operations, error classes and types; schema consts are module-private and the ones no operation or error reaches are deleted. Schemas stay reachable as op.input / op.output (now part of OperationMethod's type). BREAKING: schema/enum values such as stripe.Customer or s3.PutObjectRequest are no longer exported; the types are. Use op.input / op.output.
440,717 schema consts made private, 135,290 unreachable ones removed.
Each schema initializer evaluated at import cost ~330 B (thunk closure, function record, lazy wrappers, annotation object). Initializers now live in builder functions of 8 schemas (switch on an index), and each const is one lazy object holding its builder and index; V8 compiles a builder on its first use and the code text stays only in the source. A packed-string interpreter was tried first and lost: string literals are stored twice (source + heap string). Import heap: Stripe service module 3.1 -> 1.5 MB, EC2 3.5 -> 2.1 MB.
302,975 schema initializers moved into builders.
A top-level const costs memory at import even when its schema is never built: a context slot, its name in the module scope info, module-body bytecode and the lazy wrapper. Private schema consts, their aliases and leaf aliases (S.String enums) are removed; references become __S(i), which makes the lazy wrapper on first reach and memoizes it. Operation configs keep their types through the original casts. Stripe's service module import: 1.36 MB -> 0.48 MB. Also: core/schema re-exports effect/Schema types only (a value export * cost ~70 KB per process for 345 bindings) plus the values code uses, and core's own errors use the lazy TaggedError.
…document scanner The separate plain-document scanner doubled the parser's code. The full parser now gets the speed in place: parsed objects inherit from an empty prototype that has no prototype itself (V8 keeps them in fast mode), ASCII names match a cheap non-Unicode pattern before the Unicode one, text without entities or CRs is not rebuilt, and element text is concatenated instead of joined. Small AWS responses parse in ~1.4 us (6-8 us at f966856). The differential tests now compare against the f966856 parser, kept as a test fixture.
…rger than before The call path added ~2.4-3.7 KB gzip per AWS program, mostly code kept twice (sync and effect versions). Now there is one of each: - Protocol handlers are serialize / deserializeText / deserializeStream / deserializeError; the lazy protocol traits only hand over the loaded handler. deserialize() in client/protocol.ts is the one 2xx path (stream outputs, else the body read as text), used by the response parser. - makeRequestBuilder takes the loaded handler and returns the request directly for operations without middleware or streaming payloads, an effect otherwise. protocol.ts compiles one plan per operation (builder + synchronous 2xx decoder) that call, encode and decode all use, and keeps one protocol handler per operation instead of three. - canonicalize builds the header record directly for ordinary headers and only goes through Headers for ones it would normalize or reject, so the separate URL-parts canonicalizer is gone. A test compares signatures, headers and URLs with the f966856 signer (kept as a test fixture) over 960 combinations. - Derived signing keys live in one small per-secret map shared by sync and async signing. SigningKeyCache is now an optional SigningKeyStore ({ get(scope) }) a scope can provide; Cache.get(cache, scope) adapts an effect Cache. - hashPayload signs the body's SHA-256 as X-Amz-Content-Sha256 (S3 Control, Glacier) with the context's crypto instead of a separate WebCrypto digest. - SigV2 (SimpleDB) moves to sigv2.ts and is reached through the AwsAuthSigv2 trait, so other programs do not bundle it.
…ficial probe official-probe.ts ran 200 mocked fetches before importing the SDK; probe.ts did not, so distilled's first call paid undici's first-use cost (~1.1 ms) and the official SDKs did not.
# Conflicts: # packages/aws/src/rules-engine/endpoint-resolver.ts
…cker instead of Effect Schema
…cies
The official suite now records size.{tarball,withDeps,unpackedWithDeps,packages}
for each official SDK: its npm tarball plus every dependency npm install
fetches, counted once. size.distilled holds the same measurement for the
distilled package it is compared with. Tarballs are rebuilt locally (tar +
gzip -9, within 1-3% of npm pack) because npm pack crashes in the pnpm
store; runs offline.
…ue entries, operation-name stems Module constants only data reads (service traits, endpoint rules) move into the refs of the holder of the roots that reach them and are built on first use. Constant expressions repeated across holders (the service trait pipe, a base URL) become value entries (~N, ~R:N). New holders only as many as moved constants free, so no module gains objects at import. An operation whose name recurs in its strings writes it once (API.named). #R:N replaces ^R:N.
The bundler renames namespace imports to short bindings already; the module-level alias consts only cost source, locals and bundle bytes.
A moved constant is parsed again when its holder first runs; for S3's 120 KB rules that added 0.5 ms to the first call.
…rototypes are made
…the full modules Each generated service module in lib/ moves to lib/services.full/ (what bundlers resolve: module/default conditions) and lib/services/ gets a stub (Node: module-sync condition) with one placeholder per operation and error class and no operation data. The first use loads the full module with require(esm); its op/opPaginated/error definitions adopt the placeholders, so identities stay single. Service barrels become lazy namespaces in Node.
…ion reaches bundle-lib ends each full service module with a slice index (a trailing comment bundlers drop): per top-level declaration its byte range and the names it reads. A stub placeholder's first use reads its operation's data and the holders and values it reaches from the file, instead of loading the whole module: data functions in their generated shape are read as data, other code is compiled with Function, function arguments of values (endpoint resolvers) only when called. Without an index the whole module loads as before. Lib code that imports a service by path names it through the package, so Node gets the stub and bundlers the full module.
…hole - The slice index is compact text with a fixed-width line table; names sit on their lines; data functions resolve their refs without compiling. - Function arguments of values (endpoint resolvers) compile only when called; a body compiled alone is a block, so its declarations shadow. - The first stub of a process reads its index in a microtask after import. - Error placeholders carry their class name (computed key, no own property); data functions are called with themselves as this. - Service modules under 8 KB stay whole for Node.
…ckage helper
V8 keeps a module's source on the heap and each import costs a module
~1 KB. Stubs now import only lib/_bundle/lazy.js, which passes its own URL
and the namespaces every stub imports to API.lazyService; they declare
'export const name=$o()' instead of a declaration plus an export list, and
error classes exported under their class name are destructured from a
proxy (export const{A,B}=$E), so each name is written once.
Provider heap, all services imported: aws 14.49 -> 13.29 MB, gcp 9.73 ->
9.00, azure 8.26 -> 7.67, cloudflare 3.22 -> 3.07; first call unchanged.
…oard) The page charts every benches/sdks run over time, compares experiments, and shows the official SDKs as reference lines. runs/ holds the 84 runs this branch was measured with, slimmed to the fields the page reads (pnpm slim).
# Conflicts: # packages/aws/src/services/emr.ts # packages/aws/src/services/lambda-core.ts # packages/aws/src/services/lambda-web.ts # packages/aws/src/services/mwaa.ts # packages/cloudflare/package.json # packages/cloudflare/src/services/durable_objects.ts # packages/cloudflare/src/services/magic_transit.ts # packages/cloudflare/src/services/ssl.ts # packages/cloudflare/src/services/stream.ts # packages/cloudflare/src/services/workers.ts # packages/cloudflare/src/services/workers_for_platforms.ts # packages/cloudflare/src/services/zaraz.ts # packages/cloudflare/src/services/zero_trust.ts # packages/fly-io/src/services/sprites.ts # packages/gcp/package.json # packages/gcp/src/services/dataplex_v1.ts # packages/gcp/src/services/storage_v1.ts # packages/turso/src/services/turso.ts # pnpm-lock.yaml # pnpm-workspace.yaml
Contributor
|
Install the packages built from this commit: @distilled.cloudpnpm install https://pkg.distilled.cloud/@distilled.cloud/acme/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/adyen/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/apache-superset/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/archil/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/argocd/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/auth0/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/aws/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/axiom/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/azure/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/boat-dev/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/celld/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/chronosphere/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/clerk/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/cloudflare/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/coinbase/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/coolify/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/core/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/customerio/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/datadog/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/daytona/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/digitalocean/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/discord/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/docker/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/doppler/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/elasticsearch/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/expo-eas/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/fly-io/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/forgejo/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/gcp/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/github/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/google-workspace/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/grafana/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/growthbook/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/gusto/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/hetzner/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/hostinger/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/huggingface/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/iceberg/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/infisical/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/inngest/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/intercom/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/kubernetes/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/launchdarkly/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/meilisearch/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/mercury/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/metabase/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/modal/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/modrinth/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/mongodb-atlas/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/neon/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/notion/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/okta/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/onepassword/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/opencode/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/ovh/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/paypal/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/plaid/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/planetscale/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/polar/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/porkbun/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/posthog/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/prisma/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/railway/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/redis-cloud/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/remote/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/render/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/resend/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/s2/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/sentry/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/slack/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/spacetimedb/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/squarespace/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/stackit/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/stripe/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/supabase/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/surrealdb/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/temporal/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/trigger-dev/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/turbopuffer/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/turso/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/typesense/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/unkey/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/vanta/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/vercel/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/whop/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/workos/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/xata/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/zendesk/pr:873:bd1a2capnpm install https://pkg.distilled.cloud/@distilled.cloud/zerossl/pr:873:bd1a2caPublished |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cuts the runtime footprint of every distilled SDK: bundle size, import memory, first call, and per-call latency, in normal and
ResponseValidation.strictmode. It also adds the per-SDK benchmark (benches/sdks) and the dashboard that tracked every step (benches/dashboard).Results
Median per program. A program is 1 auth setup + 1 operation for one SDK and protocol; 98 programs over all SDKs. Base is
mainat40a9500a7, head isf6019b376. Both are inbenches/dashboard/runs/.Import heap with every service of a provider imported:
Against the official SDKs (same call, official = import the one client package, distilled = import the whole provider):
Official → distilled. Distilled wins every cell except the Cloudflare first call.
What changed
1. Codegen: fewer, smaller, lazier schemas
core/src/codegen/dedupe.ts): Moore partition refinement merges structurally identical schemas, also through enum aliases. Stripe goes from 12,529 to 2,720 shapes. AWS keeps shapes whose identifier is an XML element or query action name, because merging those changes the wire format.op.input/op.output.codegen/pure.ts). An outer/*@__PURE__*/does not cover the calls in its arguments; EC2describeRegionskept 1,695 schemas before this.codegen/latin1.ts, guard testlatin1-source.test.ts). V8 stores a whole module source as two-byte if one character is outside Latin-1. Escaping 3 characters cut S3 import heap from 2,240 to 1,570 KB.2. schema-lite: plain schema trees for non-AWS SDKs
@distilled.cloud/core/schema-lite: plain AST-shaped nodes. AWS uses lite schemas too, with its own codec traits (packages/aws/src/lite.ts).Effect.gen.ResponseValidation.strict) checks responses with a compiled lite-AST checker (response-validation*.ts). Its compiler is built on first use, so only strict users pay for it. Effect Schema leaves the default bundle.3. Core runtime
effect/<Module>directly; the rooteffectbarrel loaded 3.3 MB of heap.core/configreplaceseffect/Config(about 280 KB heap and 5 KB gz per bundle).Effect.retry/Schedule; pagination withoutStream.flatMap(that pulled in Queue and Semaphore).Data-based and built on first use (breaking: they do not validate on construction;ParseError.causeis aSchemaMismatch).DISTILLED_DEBUG_HTTPis read once; credentials resolve without generators in 70 REST protocols.@distilled.cloud/core/crypto: a swappableCryptoservice (SHA-256, HMAC, key import) withCrypto.webCrypto,Crypto.nodeCryptoandCrypto.layer(impl).4. AWS
node:cryptowhere available, reused HMACKeyObjects, one-shot hashing, and no URL/Headers parsing.SigV4.SigningKeyCacheis a plain store (breaking).5. Build:
lib/layoutscripts/bundle-lib.ts): each package'slib/is bundled into a few group chunks. Output is minified (names kept), Latin-1, keeps PURE annotations, and has shifted source maps.lib/is now generated output (breaking for anyone who reads it).scripts/strip-lib-docs.ts) removes JSDoc from compiled JS.scripts/lazy-services.ts): under Node (module-syncexport condition) a service module is a stub that loads per-operation slices on first use. Bundlers get the full module (services.full). Each stub writes each export name once and imports one helper,lib/_bundle/lazy.js.6. Tooling
HttpClientErrorSchemaretention (it pinned about 15 KB gz of Effect Schema into every REST bundle) is a rolldown 1.2.5/1.2.6 tree-shaking bug, fixed in 1.2.7. The temporary Effect patch is removed. Alchemy should bump rolldown to ≥ 1.2.7 to get the same win.pnpm-workspace.yamloverrides 17@aws-sdk/*internals so the official AWS SDK used by the benchmark passes the minimum release age.7. Benchmarks and dashboard
benches/sdks: per SDK, offline. Download size, 1-auth + 1-operation bundle (normal and strict), import heap and objects, import time, first call, added latency, consumer typecheck cost. One program per protocol a provider uses. Anofficialsuite runs the same calls through the official AWS v3, Cloudflare and@google-cloud/storageSDKs.benches/schema-lab: Effect Schema (eager, lazy, JIT), valibot, zod-mini, typebox and own descriptors side by side. Own static descriptors won on every axis and keep strict validation.benches/dashboard: the page used for this work. It charts every run over time (normal and strict lines, official SDKs as reference lines), shows per-provider and per-service detail, and compares experiments with their baseline.runs/holds the 84 runs (46 timeline, 38 experiments) slimmed to 8.5 MB;pnpm slimslims new runs.Breaking changes
op.input/op.output.Data-based and do not validate on construction.ParseError.causeis aSchemaMismatch.SigV4.SigningKeyCacheis a plain store.lib/is minified, bundled, generated output.Tried and dropped
SchemaAOTCompiler(cannot compile throughSuspend; MBs of code) and JIT schemas.lib/.HttpClientErrorSchemato Effect: the cause is rolldown, and the IIFE breakstsc -bwith TS4023.ResponseValidation.strict; this branch reaches lower numbers with strict intact.Open items
.d.ts. This is an API decision.mainand regenerate the 5 conflicting packages.Checks
Run at
f6019b376. The 2 commits after it addbenches/dashboardand whitespace-only formatting.pnpm typecheck:ci: pass.pnpm vitest run: 1,538 tests pass.pnpm format:check: pass.