@hile/micro-contract
Share fixed-path unary Micro schemas and typed callers while retaining file-routed handlers and explicit local provider execution.Choose This Package When
Use When
Use this package for a shared, fixed-path, unary service contract whose DTOs and typed callers are consumed by other services or protocol adapters. Shared contracts contain namespace, operation keys, explicit paths, and Standard Schema input/output schemas; implementations remain in the owning service’s message files. The package root is runtime-neutral: importing a contract does not start a service, connect to Registry, or import provider handlers.@hile/micro-contract/internal is a framework integration subpath, not a business API. Application code must not use it to implement another executor or skip validation.
Do Not Use When
- Keep existing dynamic URL parameters, request streams, response streams, and exact-peer streams on native
defineMicroMessage(handler),Application.call,stream, andstreamPeerAPIs. Do not silently migrate their URLs or DTOs to this unary contract. - Do not use a contract as a Model catalog, HTTP route catalog, MCP capability catalog, dependency container, or runtime service discovery source.
- Do not infer local execution from namespace equality. Explicit local execution and RPC have different failure and routing semantics.
Install
@hile/micro. Schemas implement Standard Schema V1. Zod is one supported schema provider, not a runtime dependency of this package.
Imports
Copy-Paste Example
Shared contract, published by its owning service’s contract package:activate() is idempotent while active; it cannot reopen a closed binding. The same contract must be imported from one shared module instance in provider message files; reconstructed operation objects are not registered contract identities.
More Examples
Explicit remote and local calls
remote.ping() still calls app.call(namespace, path, ...) when the namespace is this Application’s own namespace. It may execute on another compatible replica. binding.local.ping() executes this Application’s installed handler and never performs Registry lookup or opens a transport connection.
Local options are an InvocationContext with context and signal, not RPC options: do not pass local timeout, retries, input, protocol, or stream settings. Create a bounded cancellation signal at the owning ingress and propagate it. Cross-service orchestration always uses the remote typed client.
Protocol adapters share the executor, not the Model implementation
Injectbinding.local into same-service HOM/MCP adapters. An HOM message validates HTTP input and maps the result to HTTP; a Tool or dynamic Resource validates its MCP input and maps its MCP result. Both call the selected local operation with their existing invocation. They do not import or execute the message definition’s .fn, and do not duplicate use-case composition or call Models directly.
There is no generic HTTP router, centralized handler mount table, generated path file, runtime path catalog, or operation-ID dispatcher. Native MCP provider Tools/Resources/Prompts remain the capability catalog. Prompts remain side-effect-free templates, not invocation or authorization boundaries. The typed contract itself does not expose an operation to HTTP or MCP.
Runtime And Lifecycle Notes
Public surface and addressing
defineMicroContract({ namespace, operations })returns immutable copied contract/operation metadata. Each operation has exactlypath,input, andoutput; operation keys are stable nonreserved JavaScript identifiers and paths must be unique.createMicroClient(caller, contract)accepts a structural caller exposingcall(namespace, path, data, options). It returns one typed function per operation without contacting the service at construction.defineMicroMessage(operation, handler)declares typed metadata. The handler receives only parseddataandinvocation; it must be loaded throughloadMicroContract(app, contract, messagesDirectory).loadMicroContractreturns{ local, activate, close }. File-system routes stay authoritative: after normal loader prefix/group/index mapping, each file path must exactly equal the shared explicitoperation.path. A wrong namespace, foreign operation, missing/duplicate implementation, path mismatch, raw/file route conflict, or concurrent load fails closed and rolls back the batch.- Paths are fixed canonical absolute URLs, not templates. Root
/is valid; query/hash, trailing or repeated separators, dot segments, percent-encoding, parameters, wildcards, and route-group syntax are not contract paths. Native dynamic messages may coexist outside the typed contract’s operation set.
Schema and JSON boundaries
RPC flow is client request parse → provider request parse → handler → provider response parse → client response parse. Explicit local flow uses the same provider executor, including provider request/response parsing, but has no remote client/network pipeline. Both schema input and parsed output must be canonical JSON data. Plain objects, dense arrays, finite numbers, strings, booleans, andnull are supported. Object properties with undefined are omitted and negative zero becomes zero. Root undefined, array holes/undefined, nonfinite numbers, BigInt, functions, symbols, enumerable accessors, cycles, class instances, Date, Map/Set, binary values, and streams are rejected; no toJSON or getter is invoked to serialize a DTO. Snapshots prevent mutation of caller/handler-owned objects from crossing the boundary by reference.
Schemas must be pure, deterministic, and safe to apply repeatedly to their own parsed values. Do not read services, authorize, mutate state, generate timestamps, or perform non-idempotent transforms in a schema. Parsing is real transformation, not just a type assertion: an output that another boundary rejects is an invalid shared wire contract. Keep HTTP/MCP-specific coercion in their adapter and use stable JSON DTOs in the shared contract.
MicroContractError exposes a safe finite status, phase, kind, namespace, operation, and path. Phases distinguish client/provider request/response checks; provider wire errors are reconstructed from finite statuses and do not preserve arbitrary remote exception types or schema issue contents. Never forward raw issues, input values, or internal causes to public clients. Business execution failures are safe Micro execution failures, not evidence that a command did not commit.
Retries and replicas
Typed remote calls default toretries: 0 for both reads and writes, explicitly overriding the native Application nonstream default. Local calls never retry. An explicit remote retry count only enables the underlying transport’s eligible retry handling; it does not retry provider contract rejection, HILE_MICRO_EXECUTION_FAILED, successful responses that fail client parsing, or replay a handler after local validation errors. Do not wrap response parsing in another automatic retry loop.
A typed handler may compose local or remote downstream operations. When a downstream failure is wrapped as the current operation’s safe execution failure, it still must not trigger replay of the whole orchestration; the wrapper does not falsely attribute the downstream schema phase to the current operation. Native unknown-error retry behavior is unchanged. A known typed execution failure is not an eligible transport failure, even when a caller explicitly enables retries.
Timeout/connection failure may happen after a write committed. Any opt-in retry needs owning-domain idempotency or a durable uniqueness boundary, one end-to-end attempt budget, and cross-replica correctness. Do not stack HOM, adapter, Micro, and caller retries independently. There is no automatic replica schema negotiation: keep every selectable replica compatible with callers during rolling deployment, use additive migrations, and preserve old URLs/DTOs until their consumers have migrated.
Admission and shutdown
Loading keeps local and inbound business admission closed, even if the internal listener is reachable. Only framework-owned control traffic can operate during startup.activate() opens admission after handlers, dependencies, and adapters are ready; raw/HOM/MCP business dispatch on that Application is included, not only typed operation URLs.
close() is idempotent. It first rejects new network business ingress, drains previously admitted outer invocations (including stream lifetime), then closes local admission and drains local executions. Existing outer handlers can still call the local executor during the first drain phase. Stop local producers/workers before closing; do not treat the first drain phase as permission to launch new jobs. Caller cancellation waits for cooperative iterator return()/finally cleanup rather than immediately treating that outer invocation as finished. One Application.shutdownTimeoutMs budget bounds both contract drain phases, defaulting to 30 seconds; expiry aborts outstanding invocations and forcibly releases registration accounting. This bounds close; cancellation cannot forcibly terminate arbitrary JavaScript or guarantee rollback of side effects.
binding.close() closes this binding, not the listener or domain dependencies. The teardown returned by app.listen() closes/drains the binding before disposing Micro transport resources. The composition root must still stop producers and clean up dependencies/attachments in their owning lifecycle order.
Local execution shares schema/context/error policy, but not wire copies, serialization cost, Registry selection, connection failures, circuit-breaker bookkeeping, or replica choice. Local and remote error instances/stacks also differ. This API is explicit locality, not a transparent RPC optimization.
Anti-Patterns
- Importing provider handlers/Models into a shared contract or another service.
- Using
@hile/micro-contract/internalfrom business packages. - Calling typed message
.fnor adding a second use-case executor in HTTP/MCP code. - Deriving local execution from namespace equality or exporting a process-global local caller.
- Generating paths from filenames, publishing a runtime path directory, or adding a fixed dispatcher endpoint.
- Renaming dynamic routes or changing existing DTOs just to fit the first unary contract version.
- Treating protocol isolation as public authorization or retry as exactly-once execution.
Verification Checklist
- Shared contract import and client construction work while the provider is offline.
- CI loads the actual source tree and compiled
disttree; missing/stale artifacts, path mismatches, duplicate owners, and parameter overlaps fail before activation. Load one tree per Application, never overlappingsrcanddistglobs. - Handler input/output types and runtime schema checks are tested independently; local and real Registry/WebSocket calls exercise the same handler invariants.
- Tests cover inactive/closed rejection, failed-load rollback, one load attempt, protocol misdelivery before side effects, cancellation, and full outer-stream drain.
- HTTP/MCP delegation tests verify validation/context propagation and no direct Model access. Native dynamic and streaming APIs retain their existing URLs and DTOs.
- Multi-replica tests cover compatible rolling versions, duplicate writes, timeout-after-commit, cancellation, and shutdown under load. Unit tests alone do not establish deployment compatibility.
Package-Local AI Guide
This package also shipsAI.md in npm so agents can read accurate examples after installation.