Conversation
Server-side bugs:
- serverError: err.messages typo masked appended server error info
- unaryCallProxy/clientStreamingCallProxy: return after error callback to
avoid double invocation
- serverStreamingCallProxy/bidiStreamingCallProxy: return after destroy()
to avoid ERR_STREAM_DESTROYED from a follow-up end()
Client-side bugs:
- unaryProxy: return after reject; drop redundant outer createClientError
wrap that double-wrapped errors
- clientStreamProxy: surface gRPC callback errors via writeEnd() instead
of throwing inside the callback (silent unhandled rejection)
- serverStreamProxy/bidiStreamProxy: drop call.on('error') throw; iterator
rejectionEvents already propagate errors to consumers
- Clients.prepareUrl: isDefaultClient was inverted, causing user-supplied
url in get()/getReal() to be ignored in favor of the default client
Refactors carried with the fixes:
- clientFactory: drop dead cacheKeyWithCt branch and constructor self-
assignment; tighten _loader type; accept undefined addr and fall back
to _clientAddrMap instead of the 'undefined:undefined' string check
- Clients: remove _initialized flag so clear()+init() actually re-inits
- Server: drop constructor self-assignment; parse listen() port via
lastIndexOf(':') for IPv6-friendly addresses
- ProtoLoader: drop no-op try/catch; rename shadowed param; cache an
in-flight init promise so concurrent init() calls share work
- Stream proxy types: replace undeclared Request/Response globals with
any; replace `Function` with ComposedMiddleware / explicit signatures
- iterator: drop legacy Symbol.asyncIterator polyfill and Function types
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Replace `expect(!x).toBeTruthy()` / `expect(!!x).toBeTruthy()` patterns with `toBeFalsy()`, `toBeTruthy()`, and `toBeDefined()` so assertions read directly. No coverage change. Co-Authored-By: Claude Opus 4.7 <[email protected]>
- Swap joi 18 for zod 4 (runtime dep) - Rewrite src/schema/loader.ts with zod, preserving passthrough semantics for channelOptions/loadOptions via z.record(string, unknown) - Wrap ZodError from assertProtoFileOptionsOptions in a plain Error to keep the "new ProtoLoader() params error: ..." message prefix - Drop dead attemptClientOptions and now-unused AddressSchema import from src/schema/client.ts - Narrow packagePrefix truthiness on a local in loader.ts so the zod-inferred optional type passes tsc - Add test/schema.test.ts covering 21 cases: protoFileOptions accept/ reject, default filling, passthrough, AddressSchema union, invalid port/host, credentials passthrough Co-Authored-By: Claude Opus 4.7 <[email protected]>
- Add an "Install" note that Node.js >= 18 is required, matching the engines field bumped in the recent dependency upgrade - Mention runtime validation via zod in the feature list Co-Authored-By: Claude Opus 4.7 <[email protected]>
…Start - Replace the abstract "business city" framing with a concrete description of what gRPCity does - Add a "Why gRPCity" section contrasting with raw @grpc/grpc-js - Annotate each feature with its concrete value rather than a bare label - Break Quick Start into numbered steps (proto / loader / server / client) with one-line guidance per step - List pnpm and yarn install commands alongside npm - Drop the duplicate documentation link, link LICENSE in the footer - Mirror all changes in README_CN.md for parity Co-Authored-By: Claude Opus 4.7 <[email protected]>
Util unit tests - test/utils/iterator.test.ts: queueing/blocking, multi-event, multiArgs, limit, limit=0, resolution/rejection events, filter, return() cleanup, for-await break, invalid limit, incompatible/legacy emitters - test/utils/compose.test.ts: ordering, sync throw, async reject, double next() rejection, non-array/non-function guards, shared context - test/utils/object.test.ts: nested paths, default fallback, intermediate null/undefined/primitive, falsy leaf preservation, non-object root Server public API - test/server.test.ts: use() varargs/array/no-arg, listen-time guards on add/inject/use, remove(), inject(), listen with host:port string, invalid address rejection, idempotent shutdown/forceShutdown Complex proto fixtures (example/proto/complex/) - common/types.proto: Status enum + Money (int64/int32) - inventory/item.proto: Category enum, nested Variant, repeated, map <string,double>, bytes, bool, google.protobuf.Timestamp, oneof change, oneof kind - inventory/service.proto: 4 RPC kinds incl. oneof unary - orders/order.proto, orders/service.proto: cross-package import, map<int64, Item>, oneof payment Complex proto integration - test/complexProto.test.ts: load/lookup, isDev+packagePrefix path rewrite, full-shape unary round-trip exercising every wire type, oneof branches, client/server/bidi streaming with rich payloads, multi- service registration on a single server, sparse messages with defaults=false; uses dynamic port allocation to avoid CI flakes Coverage: 94.94% statements / 81.98% branches / 96.73% functions Suites: 11, tests: 96. Co-Authored-By: Claude Opus 4.7 <[email protected]>
The root tsconfig.json previously only included src/ and did not declare jest in `types`. Editors opening test/*.ts fell back to an inferred project with no jest globals, surfacing "Cannot find name 'expect'" even though `pnpm test` succeeded via tsconfig.test.json. - Add tsconfig.build.json: dedicated production config with rootDir src, outDir lib, types ["node"]; build script switches to it - Repurpose tsconfig.json as the editor view: rootDir ".", noEmit true, include src+test, types ["jest", "node"]; relaxes noUnusedLocals/Parameters which are still enforced by the build config - Simplify tsconfig.test.json to just extend the root and enable isolatedModules (also silences ts-jest TS151002 warnings) Build artifacts and test/lint pipelines are unchanged (11 suites, 96 tests still pass). Co-Authored-By: Claude Opus 4.7 <[email protected]>
In grpc-js 1.14 calling call.destroy(err) on a ServerWritableStream /
ServerDuplexStream tears down the underlying HTTP/2 stream without
running the wrapper's own 'error' handler, so the client never receives
a status trailer and only fails after its deadline elapses (default
10s). The wrapper installs an 'error' listener that turns err into a
gRPC status via serverErrorToStatus and then end()s the stream,
which is exactly the path we want.
Switch the catch in serverStreamingCallProxy and bidiStreamingCallProxy
from call.destroy(err) to call.emit('error', err). Clients now observe
the original error message immediately.
Covered by test/streamErrors.test.ts (server-stream and bidi cases).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Existing stream tests covered only the happy path. Add four focused suites plus a small dynamic-port harness: - test/streamErrors.test.ts: server-side throw on each RPC kind surfaces on the client (regression coverage for the destroy/emit fix and the earlier A6-A8 client-side fixes) - test/streamLifecycle.test.ts: unary and server-stream deadline exceeded, mid-stream client cancel, empty client/bidi streams - test/streamScale.test.ts: 1000-message client stream, 1000-message server stream, 500-round-trip bidi — verifies count and ordering - test/streamMetadata.test.ts: client sends custom metadata, server reads it and echoes back via sendMetadata; assert response headers on each RPC kind - test/helpers/streamHarness.ts: dynamic free-port allocator and a shared loader factory so suites don't collide on hard-coded ports Coverage moves to 95.93% statements / 82.49% branches / 96.83% functions / 95.68% lines (15 suites, 112 tests). Co-Authored-By: Claude Opus 4.7 <[email protected]>
Metadata was previously only re-exported as a type, so users (and our
own tests) had to depend on @grpc/grpc-js directly to construct
metadata at runtime.
Re-export the runtime values Metadata and credentials, plus the type
shapes StatusObject, ChannelCredentials, ServerCredentials,
MetadataValue, and ChannelOptions. Callers can now construct request
metadata and credentials without importing @grpc/grpc-js:
import { ProtoLoader, Metadata, credentials } from 'grpcity'
Update test/streamMetadata.test.ts to use the new entry point.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
The Quick Start loader snippet imports with ESM syntax but uses __dirname, which only exists in CommonJS. Add a note under the snippet in both READMEs pointing ESM users at import.meta.dirname (Node.js >= 20.11) or fileURLToPath(import.meta.url) on Node 18. Co-Authored-By: Claude Opus 4.7 <[email protected]>
- Add a workspace dependency `grpcity: link:..` (and tsx as a devDep) so
examples can `import { ProtoLoader } from 'grpcity'` instead of the
../../lib/index.js paths that exposed the build layout
- Replace `path.dirname(new URL(import.meta.url).pathname)` with the
cross-platform `path.dirname(fileURLToPath(import.meta.url))` in every
example loader
- Rewrite example/README.md with setup instructions, a directory index,
and a recommended reading order; document cert regeneration
Examples touched in this commit: helloworld, stream, reflection (its
loader). The actual stream/reflection rewrites land in the next commit.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
…ection
- Remove example/simple — duplicates helloworld and is the cruder of
the two
- Remove example/asyncStream and replace example/stream with the async
version. The old example/stream used the legacy callback-based
client.call.* API which is no longer the recommended surface
- Remove example/reflection/{asyncStreamServer.js,helloworldServer.js},
which conflated reflection, multi-service registration, and middleware.
Replace with example/reflection/server.js focused on reflection only
- Add example/multiService/ to host the two-services-on-one-server +
middleware demo that was previously buried inside reflection/
Co-Authored-By: Claude Opus 4.7 <[email protected]>
…it samples
Five new self-contained examples covering scenarios that previously had
no runnable demonstration:
- tls/ — mutual TLS using the certs in example/certs
- middleware/ — client- and server-side (ctx, next) patterns
- errors/ — server throws + client deadline; reading
GrpcClientError fields (name, code, details)
- typescript/ — minimal TS flow runnable with `pnpm exec tsx`
- loader-init/ — focused walk-through of every loader.init() option
(defaults, isDev + packagePrefix, loadOptions overrides,
concurrent-safety) with a short README that documents
each parameter
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Prettier was flagging example/pnpm-lock.yaml as needing formatting after the example workspace gained dependencies. Add lib/, coverage/, and both pnpm-lock.yaml files to .prettierignore so generated and auto-managed files stop tripping `pnpm lint:prettier`. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Per-call options on every RPC now accept `signal: AbortSignal`.
Behaviour:
- pre-abort: signal.throwIfAborted() runs before any network call,
so an already-aborted signal surfaces as AbortError without ever
contacting the server
- mid-call abort: a once-listener calls call.cancel() under the hood;
the client receives a GrpcClientError with code 1 (CANCELLED)
- the listener is removed on every terminal status event so a
long-lived signal reused across many RPCs does not accumulate
listeners (verified by a regression test)
- when no signal is supplied, the proxies skip all of the above,
keeping the existing zero-allocation fast path
Also:
- src/client/clientSignal.ts: tiny helper that extracts the signal
from per-call options and returns the rest, so we never forward
our own concept to grpc-js's CallOptions
- src/client/clientError.ts: createClientError is now idempotent for
AbortError (passed through) and GrpcClientError (returned as-is)
to avoid double-wrapping
- src/index.ts: export the new ClientCallOptions type so TS callers
can spell out `{ signal }` without casts
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Nine cases in test/streamAbort.test.ts: - pre-abort path for unary, client stream, server stream, and bidi — asserts AbortError surfaces without an RPC being issued - mid-call abort for unary, server stream, and bidi — asserts the cancellation propagates and the consumer stops early - no-signal regression — confirms the zero-allocation path still resolves normally - listener leak guard — reuses one signal across five sequential unary calls and asserts getEventListeners(signal, 'abort') is empty afterwards, locking in the cleanup contract Co-Authored-By: Claude Opus 4.7 <[email protected]>
- example/abortSignal/: server + client demonstrating (1) pre-abort short-circuit (AbortError, no RPC sent), (2) AbortSignal.timeout() cancelling a slow unary, (3) one signal aborting a fan-out of concurrent RPCs, (4) a baseline call without a signal - example/README.md: index the new directory and add it to the recommended reading order alongside errors/ - README.md / README_CN.md: list AbortSignal in the feature table Co-Authored-By: Claude Opus 4.7 <[email protected]>
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.
No description provided.