From d1cfb761d276f3e1e6d9dbd379fa71b04e0f29c5 Mon Sep 17 00:00:00 2001 From: Sam Curry Date: Sun, 26 Apr 2026 15:18:24 +0800 Subject: [PATCH 1/2] doc: add jsdoc to BaseRequestInfo and bump to 3.0.0-beta.7 Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 4 ++-- package.json | 2 +- src/request-info.ts | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2befe02..5688b6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@makerx/graphql-core", - "version": "3.0.0-beta.6", + "version": "3.0.0-beta.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@makerx/graphql-core", - "version": "3.0.0-beta.6", + "version": "3.0.0-beta.7", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", diff --git a/package.json b/package.json index 2cf54ba..775f60a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@makerx/graphql-core", - "version": "3.0.0-beta.6", + "version": "3.0.0-beta.7", "private": false, "description": "A set of core GraphQL utilities that MakerX uses to build GraphQL APIs", "author": "MakerX", diff --git a/src/request-info.ts b/src/request-info.ts index 240e5a7..a85cb59 100644 --- a/src/request-info.ts +++ b/src/request-info.ts @@ -3,20 +3,44 @@ import type { Request } from 'express' import type { IncomingMessage } from 'http' import type { TLSSocket } from 'tls' +/** + * Normalised metadata describing an inbound HTTP request or WebSocket upgrade, used as a + * common shape for logging, tracing, and request-scoped context across HTTP and subscription + * transports. Extends `Record` so consumers can augment it with additional + * fields without losing type compatibility. + */ export interface BaseRequestInfo extends Record { + /** Unique identifier for the request, taken from the `x-request-id` header or generated as a UUID. */ requestId: string + /** Transport that produced the request: `http` for Express requests, `subscription` for WebSocket upgrades. */ source: 'http' | 'subscription' + /** Resolved scheme of the request, accounting for TLS termination and `x-forwarded-proto`. */ protocol: 'http' | 'https' | 'ws' | 'wss' + /** Hostname resolved from `x-forwarded-host`, the `host` header, or the Express hostname fallback. */ host: string + /** Port resolved from the host header, omitted when it matches the default for the protocol. */ port?: number + /** HTTP method (e.g. `GET`, `POST`); empty string when not provided on the underlying request. */ method: string + /** Base URL in the form `protocol://host[:port]`, with default ports omitted. */ baseUrl: string + /** + * Request path with query string (not an absolute URL), e.g. `/graphql?op=Foo`. Sourced from + * Express `req.originalUrl` for HTTP (preserved across router mounts/rewrites) or raw `req.url` + * for WebSocket upgrades. Combine with `baseUrl` to form an absolute URL. + */ url: string + /** Value of the `origin` header, or empty string when absent. */ origin: string + /** Value of the `referer` header, when present. */ referer?: string + /** Value of the `x-correlation-id` header for cross-service request correlation, when present. */ correlationId?: string + /** Azure Application Request Routing log id from the `x-arr-log-id` header, when present. */ arrLogId?: string + /** Client IP from the first `x-forwarded-for` entry, falling back to the socket remote address. */ clientIp?: string + /** Value of the `user-agent` header, when present. */ userAgent?: string } From 69900ecc3305ded3756f3f72f655c0b5fc0ce43b Mon Sep 17 00:00:00 2001 From: Sam Curry Date: Sun, 26 Apr 2026 15:31:22 +0800 Subject: [PATCH 2/2] refactor(shield)!: mirror graphql-shield rule signature in createRule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: createRule is now curried as createRule(name?, options?)(fn), matching graphql-shield's rule(). The previous (fn, cache?) form and the implicit 'strict' cache default are removed — pass { cache: 'strict' } explicitly if needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 8 +++++--- src/shield.ts | 34 +++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9fe78f3..a56ba0e 100644 --- a/README.md +++ b/README.md @@ -236,13 +236,15 @@ Convenience builders that read `ctx.user.roles` / `ctx.user.scopes`. All use gra ### createRule -Typed wrapper around graphql-shield's `rule(...)` for ad-hoc rules. Unlike the underlying `rule`, `createRule` types `parent`, `args` and `ctx` to your generics. +Strongly typed wrapper around graphql-shield's `rule(...)`. Mirrors the underlying call signature — `createRule(name?, options?)(fn)` — but types `parent`, `args` and `ctx` on `fn` to your generics instead of `any`. ```ts -const isOwner = createRule((parent, _, ctx) => parent.ownerId === ctx.user?.id) +const isOwner = createRule()((parent, _, ctx) => parent.ownerId === ctx.user?.id) + +const isAdmin = createRule('isAdmin', { cache: 'contextual' })((_, __, ctx) => ctx.user?.roles.includes('admin') === true) ``` -Defaults to `'strict'` cache; pass `'contextual'` as the second argument for rules whose result depends only on `ctx`. +Generics are ``. The `name` and `options` arguments are forwarded verbatim to graphql-shield's `rule`. ### combineRuleWithAll diff --git a/src/shield.ts b/src/shield.ts index 6cec2a5..8425cc9 100644 --- a/src/shield.ts +++ b/src/shield.ts @@ -1,3 +1,4 @@ +import type { GraphQLResolveInfo } from 'graphql' import type { allow, and, chain, IRules, or, race } from 'graphql-shield' import { rule, shield } from 'graphql-shield' import type { GraphQLContext } from './context' @@ -10,29 +11,32 @@ type RuleCombinator = typeof chain | typeof race | typeof or | typeof and // what it does export. export type ShieldRule = ReturnType<(typeof allow)['getRules']>[number] +type RuleConstructorOptions = NonNullable[1]> +type RuleResult = boolean | string | Error +type ShieldRuleFn = Parameters>[0] + /** - * Thin typed wrapper around graphql-shield's `rule(...)` for building ad-hoc shield rules. - * - * Lets you pass a plain predicate (sync or async, or returning an `Error`) and get back a - * {@link ShieldRule} with the `parent`, `args`, and `ctx` arguments typed to your generics — - * graphql-shield's native `rule` types these as `any`. - * - * Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on - * `ctx` (e.g. the current user), so it can be reused across fields in the same request. + * Strongly typed wrapper around graphql-shield's `rule(...)`. * - * @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error. - * @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only. + * Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the + * same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on + * `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the + * underlying `rule` produces. * * Usage: * - * const isOwner = createRule( + * const isOwner = createRule()( * (parent, _, ctx) => parent.ownerId === ctx.user?.id, * ) + * + * const isAdmin = createRule('isAdmin', { cache: 'contextual' })( + * (_, __, ctx) => ctx.user?.roles.includes('admin') === true, + * ) */ -export const createRule = ( - logic: (parent: TParent, args: TArgs, ctx: TContext) => boolean | Promise | Error, - cache: 'contextual' | 'strict' = 'strict', -): ShieldRule => rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx)) +export const createRule = + (name?: string | RuleConstructorOptions, options?: RuleConstructorOptions) => + (fn: (parent: TParent, args: TArgs, ctx: TContext, info: GraphQLResolveInfo) => RuleResult | Promise): ShieldRule => + rule(name, options)(fn as ShieldRuleFn) /** * Combines a given rule with all existing defined rules using the provided combinator