@dudousxd/nestjs-inertia-client 1.0.7 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog — @dudousxd/nestjs-inertia-client
|
|
2
2
|
|
|
3
|
+
## 2.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix(codegen): remove @tanstack/query-core dependency — generated api.ts uses plain object literals
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @dudousxd/nestjs-inertia@2.0.1
|
|
11
|
+
|
|
12
|
+
## 2.0.0
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- feat(codegen): add queryKey() helper for typed cache invalidation — api.crew.getCrew.queryKey()
|
|
17
|
+
|
|
18
|
+
- Updated dependencies []:
|
|
19
|
+
- @dudousxd/nestjs-inertia@2.0.0
|
|
20
|
+
|
|
3
21
|
## 1.0.7
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -275,7 +275,7 @@ function invalidate(qc, name, queryArgs) {
|
|
|
275
275
|
__name(invalidate, "invalidate");
|
|
276
276
|
|
|
277
277
|
// src/index.ts
|
|
278
|
-
var VERSION = "
|
|
278
|
+
var VERSION = "2.0.1";
|
|
279
279
|
// Annotate the CommonJS export names for ESM import in node:
|
|
280
280
|
0 && (module.exports = {
|
|
281
281
|
ApiHttpError,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/fetcher/errors.ts","../src/fetcher/url-builder.ts","../src/fetcher/fetcher.ts","../src/contract/contract.ts","../src/contract/apply-contract.decorator.ts","../src/contract/contract-validation.pipe.ts","../src/contract/metadata.ts","../src/contract/as.decorator.ts","../src/invalidate.ts"],"sourcesContent":["export const VERSION = '1.0.7';\n\nexport { createFetcher } from './fetcher/fetcher.js';\nexport { ApiHttpError } from './fetcher/errors.js';\nexport { buildUrl } from './fetcher/url-builder.js';\nexport { defineContract } from './contract/contract.js';\nexport { ApplyContract } from './contract/apply-contract.decorator.js';\nexport { As, ROUTE_NAME_METADATA } from './contract/as.decorator.js';\nexport { ContractValidationPipe } from './contract/contract-validation.pipe.js';\nexport { CONTRACT_METADATA, getContract } from './contract/metadata.js';\nexport type { ApplyContractOptions } from './contract/apply-contract.decorator.js';\nexport { invalidate } from './invalidate.js';\nexport type { FetcherOptions, Fetcher } from './fetcher/fetcher.js';\nexport type { ContractDef } from './contract/contract.js';\n// SSR helpers are available via the `./ssr` subpath export (dist/ssr/hydrate.js)\n","export class ApiHttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body: unknown,\n ) {\n super(`HTTP ${status} ${statusText}`);\n this.name = 'ApiHttpError';\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401;\n }\n get isForbidden(): boolean {\n return this.status === 403;\n }\n get isNotFound(): boolean {\n return this.status === 404;\n }\n get isClient(): boolean {\n return this.status >= 400 && this.status < 500;\n }\n get isServer(): boolean {\n return this.status >= 500;\n }\n\n /**\n * Returns a JSON-serializable representation of the error.\n * The `body` field is **redacted by default** to prevent accidental logging of\n * sensitive response bodies (e.g. API error payloads that may contain PII).\n *\n * Pass `verbose = true` to include the full body in the output.\n *\n * @example\n * ```ts\n * // Safe: body is redacted\n * JSON.stringify(err); // { ..., body: '[redacted]' }\n *\n * // Verbose: includes full body (use only in trusted contexts)\n * JSON.stringify(err.toJSON(true)); // { ..., body: { message: '...' } }\n * ```\n */\n toJSON(verbose = false): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n statusText: this.statusText,\n body: verbose ? this.body : '[redacted — pass verbose=true to include]',\n };\n }\n\n static async fromResponse(res: Response): Promise<ApiHttpError> {\n const ct = res.headers.get('content-type') ?? '';\n const body = ct.includes('application/json')\n ? await res.json().catch(() => null)\n : await res.text().catch(() => '');\n return new ApiHttpError(res.status, res.statusText, body);\n }\n}\n","export interface BuildUrlOptions {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n}\n\n/**\n * Build a URL from a path template, path params, query params, and an optional base URL.\n *\n * Examples:\n * buildUrl('/users/:id', { params: { id: 42 } }) → '/users/42'\n * buildUrl('/users', { query: { active: true } }) → '/users?active=true'\n * buildUrl('/users', {}, 'https://api.test') → 'https://api.test/users'\n */\nexport function buildUrl(path: string, opts: BuildUrlOptions = {}, baseUrl?: string): string {\n // Interpolate path params — encodeURIComponent prevents path traversal\n // e.g. { id: '../admin' } → '/users/..%2Fadmin' not '/users/../admin'\n let resolved = path.replace(/:(\\w+)/g, (_match, key: string) => {\n const val = opts.params?.[key];\n if (val === undefined || val === null) {\n throw new Error(`Missing param: ${key}`);\n }\n return encodeURIComponent(String(val));\n });\n\n // Build query string\n const qs = new URLSearchParams();\n if (opts.query) {\n for (const [k, v] of Object.entries(opts.query)) {\n if (v !== undefined) {\n qs.set(k, String(v));\n }\n }\n }\n const qsStr = qs.toString();\n if (qsStr) resolved += `?${qsStr}`;\n\n // Prepend base URL if provided\n if (baseUrl) {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n return base + resolved;\n }\n return resolved;\n}\n","import { ApiHttpError } from './errors.js';\nimport { buildUrl } from './url-builder.js';\n\nexport interface FetcherOptions {\n baseUrl?: string;\n /** Called once per request; allows dynamic auth tokens. */\n headers?: () => Record<string, string>;\n /** Injection seam for tests; default `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Invoked with the error before it is re-thrown. */\n onError?: (err: ApiHttpError) => void;\n}\n\nexport interface Fetcher {\n get<T>(path: string, opts?: RequestOpts): Promise<T>;\n post<T>(path: string, opts?: RequestOpts): Promise<T>;\n put<T>(path: string, opts?: RequestOpts): Promise<T>;\n patch<T>(path: string, opts?: RequestOpts): Promise<T>;\n delete<T>(path: string, opts?: RequestOpts): Promise<T>;\n}\n\ninterface RequestOpts {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n body?: unknown;\n}\n\nfunction isFormData(b: unknown): b is FormData {\n return typeof FormData !== 'undefined' && b instanceof FormData;\n}\n\nexport function createFetcher(opts: FetcherOptions = {}): Fetcher {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const baseUrl = opts.baseUrl ?? '';\n\n async function request<T>(method: string, path: string, ro: RequestOpts = {}): Promise<T> {\n if (!fetchImpl) {\n throw new Error('No fetch implementation: pass opts.fetch or set globalThis.fetch');\n }\n const url = buildUrl(path, ro, baseUrl);\n const headers: Record<string, string> = { ...opts.headers?.() };\n let body: string | FormData | undefined = undefined;\n\n if (ro.body !== undefined) {\n if (isFormData(ro.body)) {\n body = ro.body;\n // Do NOT set Content-Type — the runtime sets it with the multipart boundary\n } else {\n body = JSON.stringify(ro.body);\n headers['content-type'] = 'application/json';\n }\n }\n\n if (!headers.accept) {\n headers.accept = 'application/json';\n }\n\n const res = await fetchImpl(url, { method, headers, ...(body !== undefined ? { body } : {}) });\n\n if (!res.ok) {\n const err = await ApiHttpError.fromResponse(res);\n opts.onError?.(err);\n throw err;\n }\n\n if (res.status === 204) return undefined as T;\n\n const ct = res.headers.get('content-type') ?? '';\n if (ct.includes('application/json')) return (await res.json()) as T;\n return (await res.text()) as unknown as T;\n }\n\n return {\n get: <T>(p: string, ro?: RequestOpts) => request<T>('GET', p, ro),\n post: <T>(p: string, ro?: RequestOpts) => request<T>('POST', p, ro),\n put: <T>(p: string, ro?: RequestOpts) => request<T>('PUT', p, ro),\n patch: <T>(p: string, ro?: RequestOpts) => request<T>('PATCH', p, ro),\n delete: <T>(p: string, ro?: RequestOpts) => request<T>('DELETE', p, ro),\n };\n}\n","import type { z } from 'zod';\n\nexport interface ContractDef<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown> {\n query?: z.ZodType<Q>;\n body?: z.ZodType<B>;\n response: z.ZodType<R>;\n params?: z.ZodType<P>;\n error?: z.ZodType<E>;\n}\n\nexport function defineContract<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown>(\n def: ContractDef<Q, B, R, P, E>,\n): ContractDef<Q, B, R, P, E> {\n return def;\n}\n","import { SetMetadata, UsePipes, applyDecorators } from '@nestjs/common';\nimport { ContractValidationPipe } from './contract-validation.pipe.js';\nimport type { ContractDef } from './contract.js';\nimport { CONTRACT_METADATA } from './metadata.js';\n\nexport interface ApplyContractOptions {\n /**\n * When `true`, validates incoming `body` and `query` against the Contract's\n * Zod schemas at runtime using a NestJS pipe.\n *\n * On validation failure a `BadRequestException` is thrown with the Zod\n * issues serialized in the response body:\n * `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n *\n * **Default: `false`** — schemas are read at codegen-time only, and no\n * runtime validation is performed. This matches the behaviour prior to\n * v0.9 and avoids breaking existing apps. Enable explicitly to enforce\n * the contract at runtime.\n */\n validate?: boolean;\n}\n\nexport function ApplyContract<C extends ContractDef>(\n c: C,\n opts: ApplyContractOptions = {},\n): MethodDecorator {\n const decorators: (ClassDecorator | MethodDecorator)[] = [SetMetadata(CONTRACT_METADATA, c)];\n\n if (opts.validate) {\n decorators.push(UsePipes(new ContractValidationPipe(c)));\n }\n\n return applyDecorators(...(decorators as MethodDecorator[]));\n}\n","import {\n type ArgumentMetadata,\n BadRequestException,\n Injectable,\n type PipeTransform,\n} from '@nestjs/common';\nimport type { ContractDef } from './contract.js';\n\n/**\n * NestJS pipe that validates incoming `body` and `query` against the\n * Contract's Zod schemas at runtime. Installed automatically when\n * `@ApplyContract(c, { validate: true })` is used.\n *\n * On validation failure throws a `BadRequestException` whose response body\n * contains `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n */\n@Injectable()\nexport class ContractValidationPipe<C extends ContractDef> implements PipeTransform {\n constructor(private readonly contract: C) {}\n\n transform(value: unknown, metadata: ArgumentMetadata): unknown {\n let schema:\n | {\n safeParse: (v: unknown) => {\n success: boolean;\n data: unknown;\n error: { issues: unknown[] };\n };\n }\n | undefined;\n\n if (metadata.type === 'body' && this.contract.body) {\n schema = this.contract.body as typeof schema;\n } else if (metadata.type === 'query' && this.contract.query) {\n schema = this.contract.query as typeof schema;\n } else {\n // Unhandled type — pass through unchanged\n return value;\n }\n\n // schema is always defined here because we checked above\n const parsed = schema!.safeParse(value);\n if (!parsed.success) {\n throw new BadRequestException({\n message: 'Contract validation failed',\n issues: parsed.error.issues,\n });\n }\n return parsed.data;\n }\n}\n","import type { ContractDef } from './contract.js';\n\nexport const CONTRACT_METADATA = Symbol.for('nestjs-inertia:contract');\n\ntype AnyContract = ContractDef<unknown, unknown, unknown, unknown, unknown>;\n\nexport function getContract(target: unknown): AnyContract | undefined {\n if (typeof target !== 'function') return undefined;\n return (Reflect.getMetadata(CONTRACT_METADATA, target) ?? undefined) as AnyContract | undefined;\n}\n","import { SetMetadata } from '@nestjs/common';\n\nexport const ROUTE_NAME_METADATA = Symbol.for('nestjs-inertia:route-name');\n\n/**\n * Override the auto-derived route name on a controller class or method.\n *\n * Codegen composes the final name as `${classPortion}.${methodPortion}`:\n * - **Class portion**: class-level `@As(...)` value if present, else the class name\n * with the `Controller` suffix stripped and first letter lowercased.\n * - **Method portion**: method-level `@As(...)` value if present, else the method name.\n *\n * Both can be multi-segment (contain dots). Each segment is validated against\n * `/^[a-z][a-zA-Z0-9]*$/`.\n *\n * @example Class-level override\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * list() { ... } // → 'crew.list'\n * }\n * ```\n *\n * @example Method-level override only\n * ```ts\n * @Controller('/api/v1/crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('directory.fetch') // → 'crew.directory.fetch'\n * list() { ... }\n * }\n * ```\n *\n * @example Both class and method\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew.admin')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('top10') // → 'crew.admin.top10'\n * list() { ... }\n * }\n * ```\n */\nexport const As = (name: string): ClassDecorator & MethodDecorator =>\n SetMetadata(ROUTE_NAME_METADATA, name);\n","import type { QueryClient } from '@tanstack/query-core';\n\n/**\n * Invalidates queries matching `name` (and optionally `queryArgs`).\n *\n * - `invalidate(qc, 'users.list')` → invalidates `['users.list']`\n * - `invalidate(qc, 'users.list', { id: 1 })` → invalidates `['users.list', { id: 1 }]`\n */\nexport function invalidate(qc: QueryClient, name: string, queryArgs?: unknown): Promise<void> {\n const queryKey = queryArgs === undefined ? [name] : [name, queryArgs];\n return qc.invalidateQueries({ queryKey });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;ACAO,IAAMA,eAAN,MAAMA,sBAAqBC,MAAAA;EAAlC,OAAkCA;;;;;;EAChC,YACkBC,QACAC,YACAC,MAChB;AACA,UAAM,QAAQF,MAAAA,IAAUC,UAAAA,EAAY,GAAA,KAJpBD,SAAAA,QAAAA,KACAC,aAAAA,YAAAA,KACAC,OAAAA;AAGhB,SAAKC,OAAO;EACd;EAEA,IAAIC,iBAA0B;AAC5B,WAAO,KAAKJ,WAAW;EACzB;EACA,IAAIK,cAAuB;AACzB,WAAO,KAAKL,WAAW;EACzB;EACA,IAAIM,aAAsB;AACxB,WAAO,KAAKN,WAAW;EACzB;EACA,IAAIO,WAAoB;AACtB,WAAO,KAAKP,UAAU,OAAO,KAAKA,SAAS;EAC7C;EACA,IAAIQ,WAAoB;AACtB,WAAO,KAAKR,UAAU;EACxB;;;;;;;;;;;;;;;;;EAkBAS,OAAOC,UAAU,OAAgC;AAC/C,WAAO;MACLP,MAAM,KAAKA;MACXQ,SAAS,KAAKA;MACdX,QAAQ,KAAKA;MACbC,YAAY,KAAKA;MACjBC,MAAMQ,UAAU,KAAKR,OAAO;IAC9B;EACF;EAEA,aAAaU,aAAaC,KAAsC;AAC9D,UAAMC,KAAKD,IAAIE,QAAQC,IAAI,cAAA,KAAmB;AAC9C,UAAMd,OAAOY,GAAGG,SAAS,kBAAA,IACrB,MAAMJ,IAAIK,KAAI,EAAGC,MAAM,MAAM,IAAA,IAC7B,MAAMN,IAAIO,KAAI,EAAGD,MAAM,MAAM,EAAA;AACjC,WAAO,IAAIrB,cAAae,IAAIb,QAAQa,IAAIZ,YAAYC,IAAAA;EACtD;AACF;;;AC9CO,SAASmB,SAASC,MAAcC,OAAwB,CAAC,GAAGC,SAAgB;AAGjF,MAAIC,WAAWH,KAAKI,QAAQ,WAAW,CAACC,QAAQC,QAAAA;AAC9C,UAAMC,MAAMN,KAAKO,SAASF,GAAAA;AAC1B,QAAIC,QAAQE,UAAaF,QAAQ,MAAM;AACrC,YAAM,IAAIG,MAAM,kBAAkBJ,GAAAA,EAAK;IACzC;AACA,WAAOK,mBAAmBC,OAAOL,GAAAA,CAAAA;EACnC,CAAA;AAGA,QAAMM,KAAK,IAAIC,gBAAAA;AACf,MAAIb,KAAKc,OAAO;AACd,eAAW,CAACC,GAAGC,CAAAA,KAAMC,OAAOC,QAAQlB,KAAKc,KAAK,GAAG;AAC/C,UAAIE,MAAMR,QAAW;AACnBI,WAAGO,IAAIJ,GAAGJ,OAAOK,CAAAA,CAAAA;MACnB;IACF;EACF;AACA,QAAMI,QAAQR,GAAGS,SAAQ;AACzB,MAAID,MAAOlB,aAAY,IAAIkB,KAAAA;AAG3B,MAAInB,SAAS;AACX,UAAMqB,OAAOrB,QAAQsB,SAAS,GAAA,IAAOtB,QAAQuB,MAAM,GAAG,EAAC,IAAKvB;AAC5D,WAAOqB,OAAOpB;EAChB;AACA,SAAOA;AACT;AA7BgBJ;;;ACchB,SAAS2B,WAAWC,GAAU;AAC5B,SAAO,OAAOC,aAAa,eAAeD,aAAaC;AACzD;AAFSF;AAIF,SAASG,cAAcC,OAAuB,CAAC,GAAC;AACrD,QAAMC,YAAYD,KAAKE,SAASC,WAAWD;AAC3C,QAAME,UAAUJ,KAAKI,WAAW;AAEhC,iBAAeC,QAAWC,QAAgBC,MAAcC,KAAkB,CAAC,GAAC;AAC1E,QAAI,CAACP,WAAW;AACd,YAAM,IAAIQ,MAAM,kEAAA;IAClB;AACA,UAAMC,MAAMC,SAASJ,MAAMC,IAAIJ,OAAAA;AAC/B,UAAMQ,UAAkC;MAAE,GAAGZ,KAAKY,UAAO;IAAK;AAC9D,QAAIC,OAAsCC;AAE1C,QAAIN,GAAGK,SAASC,QAAW;AACzB,UAAIlB,WAAWY,GAAGK,IAAI,GAAG;AACvBA,eAAOL,GAAGK;MAEZ,OAAO;AACLA,eAAOE,KAAKC,UAAUR,GAAGK,IAAI;AAC7BD,gBAAQ,cAAA,IAAkB;MAC5B;IACF;AAEA,QAAI,CAACA,QAAQK,QAAQ;AACnBL,cAAQK,SAAS;IACnB;AAEA,UAAMC,MAAM,MAAMjB,UAAUS,KAAK;MAAEJ;MAAQM;MAAS,GAAIC,SAASC,SAAY;QAAED;MAAK,IAAI,CAAC;IAAG,CAAA;AAE5F,QAAI,CAACK,IAAIC,IAAI;AACX,YAAMC,MAAM,MAAMC,aAAaC,aAAaJ,GAAAA;AAC5ClB,WAAKuB,UAAUH,GAAAA;AACf,YAAMA;IACR;AAEA,QAAIF,IAAIM,WAAW,IAAK,QAAOV;AAE/B,UAAMW,KAAKP,IAAIN,QAAQc,IAAI,cAAA,KAAmB;AAC9C,QAAID,GAAGE,SAAS,kBAAA,EAAqB,QAAQ,MAAMT,IAAIU,KAAI;AAC3D,WAAQ,MAAMV,IAAIW,KAAI;EACxB;AAnCexB;AAqCf,SAAO;IACLqB,KAAK,wBAAII,GAAWtB,OAAqBH,QAAW,OAAOyB,GAAGtB,EAAAA,GAAzD;IACLuB,MAAM,wBAAID,GAAWtB,OAAqBH,QAAW,QAAQyB,GAAGtB,EAAAA,GAA1D;IACNwB,KAAK,wBAAIF,GAAWtB,OAAqBH,QAAW,OAAOyB,GAAGtB,EAAAA,GAAzD;IACLyB,OAAO,wBAAIH,GAAWtB,OAAqBH,QAAW,SAASyB,GAAGtB,EAAAA,GAA3D;IACP0B,QAAQ,wBAAIJ,GAAWtB,OAAqBH,QAAW,UAAUyB,GAAGtB,EAAAA,GAA5D;EACV;AACF;AAhDgBT;;;ACrBT,SAASoC,eACdC,KAA+B;AAE/B,SAAOA;AACT;AAJgBD;;;ACVhB,IAAAE,iBAAuD;;;ACAvD,oBAKO;;;;;;;;;;;;AAYA,IAAMC,yBAAN,MAAMA;SAAAA;;;;EACX,YAA6BC,UAAa;SAAbA,WAAAA;EAAc;EAE3CC,UAAUC,OAAgBC,UAAqC;AAC7D,QAAIC;AAUJ,QAAID,SAASE,SAAS,UAAU,KAAKL,SAASM,MAAM;AAClDF,eAAS,KAAKJ,SAASM;IACzB,WAAWH,SAASE,SAAS,WAAW,KAAKL,SAASO,OAAO;AAC3DH,eAAS,KAAKJ,SAASO;IACzB,OAAO;AAEL,aAAOL;IACT;AAGA,UAAMM,SAASJ,OAAQK,UAAUP,KAAAA;AACjC,QAAI,CAACM,OAAOE,SAAS;AACnB,YAAM,IAAIC,kCAAoB;QAC5BC,SAAS;QACTC,QAAQL,OAAOM,MAAMD;MACvB,CAAA;IACF;AACA,WAAOL,OAAOO;EAChB;AACF;;;;;;;;;;AChDO,IAAMC,oBAAoBC,uBAAOC,IAAI,yBAAA;AAIrC,SAASC,YAAYC,QAAe;AACzC,MAAI,OAAOA,WAAW,WAAY,QAAOC;AACzC,SAAQC,QAAQC,YAAYP,mBAAmBI,MAAAA,KAAWC;AAC5D;AAHgBF;;;AFgBT,SAASK,cACdC,GACAC,OAA6B,CAAC,GAAC;AAE/B,QAAMC,aAAmD;QAACC,4BAAYC,mBAAmBJ,CAAAA;;AAEzF,MAAIC,KAAKI,UAAU;AACjBH,eAAWI,SAAKC,yBAAS,IAAIC,uBAAuBR,CAAAA,CAAAA,CAAAA;EACtD;AAEA,aAAOS,gCAAAA,GAAoBP,UAAAA;AAC7B;AAXgBH;;;AGtBhB,IAAAW,iBAA4B;AAErB,IAAMC,sBAAsBC,uBAAOC,IAAI,2BAAA;AA+CvC,IAAMC,KAAK,wBAACC,aACjBC,4BAAYL,qBAAqBI,IAAAA,GADjB;;;ACzCX,SAASE,WAAWC,IAAiBC,MAAcC,WAAmB;AAC3E,QAAMC,WAAWD,cAAcE,SAAY;IAACH;MAAQ;IAACA;IAAMC;;AAC3D,SAAOF,GAAGK,kBAAkB;IAAEF;EAAS,CAAA;AACzC;AAHgBJ;;;ATRT,IAAMO,UAAU;","names":["ApiHttpError","Error","status","statusText","body","name","isUnauthorized","isForbidden","isNotFound","isClient","isServer","toJSON","verbose","message","fromResponse","res","ct","headers","get","includes","json","catch","text","buildUrl","path","opts","baseUrl","resolved","replace","_match","key","val","params","undefined","Error","encodeURIComponent","String","qs","URLSearchParams","query","k","v","Object","entries","set","qsStr","toString","base","endsWith","slice","isFormData","b","FormData","createFetcher","opts","fetchImpl","fetch","globalThis","baseUrl","request","method","path","ro","Error","url","buildUrl","headers","body","undefined","JSON","stringify","accept","res","ok","err","ApiHttpError","fromResponse","onError","status","ct","get","includes","json","text","p","post","put","patch","delete","defineContract","def","import_common","ContractValidationPipe","contract","transform","value","metadata","schema","type","body","query","parsed","safeParse","success","BadRequestException","message","issues","error","data","CONTRACT_METADATA","Symbol","for","getContract","target","undefined","Reflect","getMetadata","ApplyContract","c","opts","decorators","SetMetadata","CONTRACT_METADATA","validate","push","UsePipes","ContractValidationPipe","applyDecorators","import_common","ROUTE_NAME_METADATA","Symbol","for","As","name","SetMetadata","invalidate","qc","name","queryArgs","queryKey","undefined","invalidateQueries","VERSION"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/fetcher/errors.ts","../src/fetcher/url-builder.ts","../src/fetcher/fetcher.ts","../src/contract/contract.ts","../src/contract/apply-contract.decorator.ts","../src/contract/contract-validation.pipe.ts","../src/contract/metadata.ts","../src/contract/as.decorator.ts","../src/invalidate.ts"],"sourcesContent":["export const VERSION = '2.0.1';\n\nexport { createFetcher } from './fetcher/fetcher.js';\nexport { ApiHttpError } from './fetcher/errors.js';\nexport { buildUrl } from './fetcher/url-builder.js';\nexport { defineContract } from './contract/contract.js';\nexport { ApplyContract } from './contract/apply-contract.decorator.js';\nexport { As, ROUTE_NAME_METADATA } from './contract/as.decorator.js';\nexport { ContractValidationPipe } from './contract/contract-validation.pipe.js';\nexport { CONTRACT_METADATA, getContract } from './contract/metadata.js';\nexport type { ApplyContractOptions } from './contract/apply-contract.decorator.js';\nexport { invalidate } from './invalidate.js';\nexport type { FetcherOptions, Fetcher } from './fetcher/fetcher.js';\nexport type { ContractDef } from './contract/contract.js';\n// SSR helpers are available via the `./ssr` subpath export (dist/ssr/hydrate.js)\n","export class ApiHttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body: unknown,\n ) {\n super(`HTTP ${status} ${statusText}`);\n this.name = 'ApiHttpError';\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401;\n }\n get isForbidden(): boolean {\n return this.status === 403;\n }\n get isNotFound(): boolean {\n return this.status === 404;\n }\n get isClient(): boolean {\n return this.status >= 400 && this.status < 500;\n }\n get isServer(): boolean {\n return this.status >= 500;\n }\n\n /**\n * Returns a JSON-serializable representation of the error.\n * The `body` field is **redacted by default** to prevent accidental logging of\n * sensitive response bodies (e.g. API error payloads that may contain PII).\n *\n * Pass `verbose = true` to include the full body in the output.\n *\n * @example\n * ```ts\n * // Safe: body is redacted\n * JSON.stringify(err); // { ..., body: '[redacted]' }\n *\n * // Verbose: includes full body (use only in trusted contexts)\n * JSON.stringify(err.toJSON(true)); // { ..., body: { message: '...' } }\n * ```\n */\n toJSON(verbose = false): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n statusText: this.statusText,\n body: verbose ? this.body : '[redacted — pass verbose=true to include]',\n };\n }\n\n static async fromResponse(res: Response): Promise<ApiHttpError> {\n const ct = res.headers.get('content-type') ?? '';\n const body = ct.includes('application/json')\n ? await res.json().catch(() => null)\n : await res.text().catch(() => '');\n return new ApiHttpError(res.status, res.statusText, body);\n }\n}\n","export interface BuildUrlOptions {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n}\n\n/**\n * Build a URL from a path template, path params, query params, and an optional base URL.\n *\n * Examples:\n * buildUrl('/users/:id', { params: { id: 42 } }) → '/users/42'\n * buildUrl('/users', { query: { active: true } }) → '/users?active=true'\n * buildUrl('/users', {}, 'https://api.test') → 'https://api.test/users'\n */\nexport function buildUrl(path: string, opts: BuildUrlOptions = {}, baseUrl?: string): string {\n // Interpolate path params — encodeURIComponent prevents path traversal\n // e.g. { id: '../admin' } → '/users/..%2Fadmin' not '/users/../admin'\n let resolved = path.replace(/:(\\w+)/g, (_match, key: string) => {\n const val = opts.params?.[key];\n if (val === undefined || val === null) {\n throw new Error(`Missing param: ${key}`);\n }\n return encodeURIComponent(String(val));\n });\n\n // Build query string\n const qs = new URLSearchParams();\n if (opts.query) {\n for (const [k, v] of Object.entries(opts.query)) {\n if (v !== undefined) {\n qs.set(k, String(v));\n }\n }\n }\n const qsStr = qs.toString();\n if (qsStr) resolved += `?${qsStr}`;\n\n // Prepend base URL if provided\n if (baseUrl) {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n return base + resolved;\n }\n return resolved;\n}\n","import { ApiHttpError } from './errors.js';\nimport { buildUrl } from './url-builder.js';\n\nexport interface FetcherOptions {\n baseUrl?: string;\n /** Called once per request; allows dynamic auth tokens. */\n headers?: () => Record<string, string>;\n /** Injection seam for tests; default `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Invoked with the error before it is re-thrown. */\n onError?: (err: ApiHttpError) => void;\n}\n\nexport interface Fetcher {\n get<T>(path: string, opts?: RequestOpts): Promise<T>;\n post<T>(path: string, opts?: RequestOpts): Promise<T>;\n put<T>(path: string, opts?: RequestOpts): Promise<T>;\n patch<T>(path: string, opts?: RequestOpts): Promise<T>;\n delete<T>(path: string, opts?: RequestOpts): Promise<T>;\n}\n\ninterface RequestOpts {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n body?: unknown;\n}\n\nfunction isFormData(b: unknown): b is FormData {\n return typeof FormData !== 'undefined' && b instanceof FormData;\n}\n\nexport function createFetcher(opts: FetcherOptions = {}): Fetcher {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const baseUrl = opts.baseUrl ?? '';\n\n async function request<T>(method: string, path: string, ro: RequestOpts = {}): Promise<T> {\n if (!fetchImpl) {\n throw new Error('No fetch implementation: pass opts.fetch or set globalThis.fetch');\n }\n const url = buildUrl(path, ro, baseUrl);\n const headers: Record<string, string> = { ...opts.headers?.() };\n let body: string | FormData | undefined = undefined;\n\n if (ro.body !== undefined) {\n if (isFormData(ro.body)) {\n body = ro.body;\n // Do NOT set Content-Type — the runtime sets it with the multipart boundary\n } else {\n body = JSON.stringify(ro.body);\n headers['content-type'] = 'application/json';\n }\n }\n\n if (!headers.accept) {\n headers.accept = 'application/json';\n }\n\n const res = await fetchImpl(url, { method, headers, ...(body !== undefined ? { body } : {}) });\n\n if (!res.ok) {\n const err = await ApiHttpError.fromResponse(res);\n opts.onError?.(err);\n throw err;\n }\n\n if (res.status === 204) return undefined as T;\n\n const ct = res.headers.get('content-type') ?? '';\n if (ct.includes('application/json')) return (await res.json()) as T;\n return (await res.text()) as unknown as T;\n }\n\n return {\n get: <T>(p: string, ro?: RequestOpts) => request<T>('GET', p, ro),\n post: <T>(p: string, ro?: RequestOpts) => request<T>('POST', p, ro),\n put: <T>(p: string, ro?: RequestOpts) => request<T>('PUT', p, ro),\n patch: <T>(p: string, ro?: RequestOpts) => request<T>('PATCH', p, ro),\n delete: <T>(p: string, ro?: RequestOpts) => request<T>('DELETE', p, ro),\n };\n}\n","import type { z } from 'zod';\n\nexport interface ContractDef<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown> {\n query?: z.ZodType<Q>;\n body?: z.ZodType<B>;\n response: z.ZodType<R>;\n params?: z.ZodType<P>;\n error?: z.ZodType<E>;\n}\n\nexport function defineContract<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown>(\n def: ContractDef<Q, B, R, P, E>,\n): ContractDef<Q, B, R, P, E> {\n return def;\n}\n","import { SetMetadata, UsePipes, applyDecorators } from '@nestjs/common';\nimport { ContractValidationPipe } from './contract-validation.pipe.js';\nimport type { ContractDef } from './contract.js';\nimport { CONTRACT_METADATA } from './metadata.js';\n\nexport interface ApplyContractOptions {\n /**\n * When `true`, validates incoming `body` and `query` against the Contract's\n * Zod schemas at runtime using a NestJS pipe.\n *\n * On validation failure a `BadRequestException` is thrown with the Zod\n * issues serialized in the response body:\n * `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n *\n * **Default: `false`** — schemas are read at codegen-time only, and no\n * runtime validation is performed. This matches the behaviour prior to\n * v0.9 and avoids breaking existing apps. Enable explicitly to enforce\n * the contract at runtime.\n */\n validate?: boolean;\n}\n\nexport function ApplyContract<C extends ContractDef>(\n c: C,\n opts: ApplyContractOptions = {},\n): MethodDecorator {\n const decorators: (ClassDecorator | MethodDecorator)[] = [SetMetadata(CONTRACT_METADATA, c)];\n\n if (opts.validate) {\n decorators.push(UsePipes(new ContractValidationPipe(c)));\n }\n\n return applyDecorators(...(decorators as MethodDecorator[]));\n}\n","import {\n type ArgumentMetadata,\n BadRequestException,\n Injectable,\n type PipeTransform,\n} from '@nestjs/common';\nimport type { ContractDef } from './contract.js';\n\n/**\n * NestJS pipe that validates incoming `body` and `query` against the\n * Contract's Zod schemas at runtime. Installed automatically when\n * `@ApplyContract(c, { validate: true })` is used.\n *\n * On validation failure throws a `BadRequestException` whose response body\n * contains `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n */\n@Injectable()\nexport class ContractValidationPipe<C extends ContractDef> implements PipeTransform {\n constructor(private readonly contract: C) {}\n\n transform(value: unknown, metadata: ArgumentMetadata): unknown {\n let schema:\n | {\n safeParse: (v: unknown) => {\n success: boolean;\n data: unknown;\n error: { issues: unknown[] };\n };\n }\n | undefined;\n\n if (metadata.type === 'body' && this.contract.body) {\n schema = this.contract.body as typeof schema;\n } else if (metadata.type === 'query' && this.contract.query) {\n schema = this.contract.query as typeof schema;\n } else {\n // Unhandled type — pass through unchanged\n return value;\n }\n\n // schema is always defined here because we checked above\n const parsed = schema!.safeParse(value);\n if (!parsed.success) {\n throw new BadRequestException({\n message: 'Contract validation failed',\n issues: parsed.error.issues,\n });\n }\n return parsed.data;\n }\n}\n","import type { ContractDef } from './contract.js';\n\nexport const CONTRACT_METADATA = Symbol.for('nestjs-inertia:contract');\n\ntype AnyContract = ContractDef<unknown, unknown, unknown, unknown, unknown>;\n\nexport function getContract(target: unknown): AnyContract | undefined {\n if (typeof target !== 'function') return undefined;\n return (Reflect.getMetadata(CONTRACT_METADATA, target) ?? undefined) as AnyContract | undefined;\n}\n","import { SetMetadata } from '@nestjs/common';\n\nexport const ROUTE_NAME_METADATA = Symbol.for('nestjs-inertia:route-name');\n\n/**\n * Override the auto-derived route name on a controller class or method.\n *\n * Codegen composes the final name as `${classPortion}.${methodPortion}`:\n * - **Class portion**: class-level `@As(...)` value if present, else the class name\n * with the `Controller` suffix stripped and first letter lowercased.\n * - **Method portion**: method-level `@As(...)` value if present, else the method name.\n *\n * Both can be multi-segment (contain dots). Each segment is validated against\n * `/^[a-z][a-zA-Z0-9]*$/`.\n *\n * @example Class-level override\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * list() { ... } // → 'crew.list'\n * }\n * ```\n *\n * @example Method-level override only\n * ```ts\n * @Controller('/api/v1/crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('directory.fetch') // → 'crew.directory.fetch'\n * list() { ... }\n * }\n * ```\n *\n * @example Both class and method\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew.admin')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('top10') // → 'crew.admin.top10'\n * list() { ... }\n * }\n * ```\n */\nexport const As = (name: string): ClassDecorator & MethodDecorator =>\n SetMetadata(ROUTE_NAME_METADATA, name);\n","import type { QueryClient } from '@tanstack/query-core';\n\n/**\n * Invalidates queries matching `name` (and optionally `queryArgs`).\n *\n * - `invalidate(qc, 'users.list')` → invalidates `['users.list']`\n * - `invalidate(qc, 'users.list', { id: 1 })` → invalidates `['users.list', { id: 1 }]`\n */\nexport function invalidate(qc: QueryClient, name: string, queryArgs?: unknown): Promise<void> {\n const queryKey = queryArgs === undefined ? [name] : [name, queryArgs];\n return qc.invalidateQueries({ queryKey });\n}\n"],"mappings":"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","names":["ApiHttpError","Error","status","statusText","body","name","isUnauthorized","isForbidden","isNotFound","isClient","isServer","toJSON","verbose","message","fromResponse","res","ct","headers","get","includes","json","catch","text","buildUrl","path","opts","baseUrl","resolved","replace","_match","key","val","params","undefined","Error","encodeURIComponent","String","qs","URLSearchParams","query","k","v","Object","entries","set","qsStr","toString","base","endsWith","slice","isFormData","b","FormData","createFetcher","opts","fetchImpl","fetch","globalThis","baseUrl","request","method","path","ro","Error","url","buildUrl","headers","body","undefined","JSON","stringify","accept","res","ok","err","ApiHttpError","fromResponse","onError","status","ct","get","includes","json","text","p","post","put","patch","delete","defineContract","def","import_common","ContractValidationPipe","contract","transform","value","metadata","schema","type","body","query","parsed","safeParse","success","BadRequestException","message","issues","error","data","CONTRACT_METADATA","Symbol","for","getContract","target","undefined","Reflect","getMetadata","ApplyContract","c","opts","decorators","SetMetadata","CONTRACT_METADATA","validate","push","UsePipes","ContractValidationPipe","applyDecorators","import_common","ROUTE_NAME_METADATA","Symbol","for","As","name","SetMetadata","invalidate","qc","name","queryArgs","queryKey","undefined","invalidateQueries","VERSION"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -170,6 +170,6 @@ declare function getContract(target: unknown): AnyContract | undefined;
|
|
|
170
170
|
*/
|
|
171
171
|
declare function invalidate(qc: QueryClient, name: string, queryArgs?: unknown): Promise<void>;
|
|
172
172
|
|
|
173
|
-
declare const VERSION = "
|
|
173
|
+
declare const VERSION = "2.0.1";
|
|
174
174
|
|
|
175
175
|
export { ApiHttpError, ApplyContract, type ApplyContractOptions, As, CONTRACT_METADATA, type ContractDef, ContractValidationPipe, type Fetcher, type FetcherOptions, ROUTE_NAME_METADATA, VERSION, buildUrl, createFetcher, defineContract, getContract, invalidate };
|
package/dist/index.d.ts
CHANGED
|
@@ -170,6 +170,6 @@ declare function getContract(target: unknown): AnyContract | undefined;
|
|
|
170
170
|
*/
|
|
171
171
|
declare function invalidate(qc: QueryClient, name: string, queryArgs?: unknown): Promise<void>;
|
|
172
172
|
|
|
173
|
-
declare const VERSION = "
|
|
173
|
+
declare const VERSION = "2.0.1";
|
|
174
174
|
|
|
175
175
|
export { ApiHttpError, ApplyContract, type ApplyContractOptions, As, CONTRACT_METADATA, type ContractDef, ContractValidationPipe, type Fetcher, type FetcherOptions, ROUTE_NAME_METADATA, VERSION, buildUrl, createFetcher, defineContract, getContract, invalidate };
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fetcher/errors.ts","../src/fetcher/url-builder.ts","../src/fetcher/fetcher.ts","../src/contract/contract.ts","../src/contract/apply-contract.decorator.ts","../src/contract/contract-validation.pipe.ts","../src/contract/metadata.ts","../src/contract/as.decorator.ts","../src/invalidate.ts","../src/index.ts"],"sourcesContent":["export class ApiHttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body: unknown,\n ) {\n super(`HTTP ${status} ${statusText}`);\n this.name = 'ApiHttpError';\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401;\n }\n get isForbidden(): boolean {\n return this.status === 403;\n }\n get isNotFound(): boolean {\n return this.status === 404;\n }\n get isClient(): boolean {\n return this.status >= 400 && this.status < 500;\n }\n get isServer(): boolean {\n return this.status >= 500;\n }\n\n /**\n * Returns a JSON-serializable representation of the error.\n * The `body` field is **redacted by default** to prevent accidental logging of\n * sensitive response bodies (e.g. API error payloads that may contain PII).\n *\n * Pass `verbose = true` to include the full body in the output.\n *\n * @example\n * ```ts\n * // Safe: body is redacted\n * JSON.stringify(err); // { ..., body: '[redacted]' }\n *\n * // Verbose: includes full body (use only in trusted contexts)\n * JSON.stringify(err.toJSON(true)); // { ..., body: { message: '...' } }\n * ```\n */\n toJSON(verbose = false): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n statusText: this.statusText,\n body: verbose ? this.body : '[redacted — pass verbose=true to include]',\n };\n }\n\n static async fromResponse(res: Response): Promise<ApiHttpError> {\n const ct = res.headers.get('content-type') ?? '';\n const body = ct.includes('application/json')\n ? await res.json().catch(() => null)\n : await res.text().catch(() => '');\n return new ApiHttpError(res.status, res.statusText, body);\n }\n}\n","export interface BuildUrlOptions {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n}\n\n/**\n * Build a URL from a path template, path params, query params, and an optional base URL.\n *\n * Examples:\n * buildUrl('/users/:id', { params: { id: 42 } }) → '/users/42'\n * buildUrl('/users', { query: { active: true } }) → '/users?active=true'\n * buildUrl('/users', {}, 'https://api.test') → 'https://api.test/users'\n */\nexport function buildUrl(path: string, opts: BuildUrlOptions = {}, baseUrl?: string): string {\n // Interpolate path params — encodeURIComponent prevents path traversal\n // e.g. { id: '../admin' } → '/users/..%2Fadmin' not '/users/../admin'\n let resolved = path.replace(/:(\\w+)/g, (_match, key: string) => {\n const val = opts.params?.[key];\n if (val === undefined || val === null) {\n throw new Error(`Missing param: ${key}`);\n }\n return encodeURIComponent(String(val));\n });\n\n // Build query string\n const qs = new URLSearchParams();\n if (opts.query) {\n for (const [k, v] of Object.entries(opts.query)) {\n if (v !== undefined) {\n qs.set(k, String(v));\n }\n }\n }\n const qsStr = qs.toString();\n if (qsStr) resolved += `?${qsStr}`;\n\n // Prepend base URL if provided\n if (baseUrl) {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n return base + resolved;\n }\n return resolved;\n}\n","import { ApiHttpError } from './errors.js';\nimport { buildUrl } from './url-builder.js';\n\nexport interface FetcherOptions {\n baseUrl?: string;\n /** Called once per request; allows dynamic auth tokens. */\n headers?: () => Record<string, string>;\n /** Injection seam for tests; default `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Invoked with the error before it is re-thrown. */\n onError?: (err: ApiHttpError) => void;\n}\n\nexport interface Fetcher {\n get<T>(path: string, opts?: RequestOpts): Promise<T>;\n post<T>(path: string, opts?: RequestOpts): Promise<T>;\n put<T>(path: string, opts?: RequestOpts): Promise<T>;\n patch<T>(path: string, opts?: RequestOpts): Promise<T>;\n delete<T>(path: string, opts?: RequestOpts): Promise<T>;\n}\n\ninterface RequestOpts {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n body?: unknown;\n}\n\nfunction isFormData(b: unknown): b is FormData {\n return typeof FormData !== 'undefined' && b instanceof FormData;\n}\n\nexport function createFetcher(opts: FetcherOptions = {}): Fetcher {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const baseUrl = opts.baseUrl ?? '';\n\n async function request<T>(method: string, path: string, ro: RequestOpts = {}): Promise<T> {\n if (!fetchImpl) {\n throw new Error('No fetch implementation: pass opts.fetch or set globalThis.fetch');\n }\n const url = buildUrl(path, ro, baseUrl);\n const headers: Record<string, string> = { ...opts.headers?.() };\n let body: string | FormData | undefined = undefined;\n\n if (ro.body !== undefined) {\n if (isFormData(ro.body)) {\n body = ro.body;\n // Do NOT set Content-Type — the runtime sets it with the multipart boundary\n } else {\n body = JSON.stringify(ro.body);\n headers['content-type'] = 'application/json';\n }\n }\n\n if (!headers.accept) {\n headers.accept = 'application/json';\n }\n\n const res = await fetchImpl(url, { method, headers, ...(body !== undefined ? { body } : {}) });\n\n if (!res.ok) {\n const err = await ApiHttpError.fromResponse(res);\n opts.onError?.(err);\n throw err;\n }\n\n if (res.status === 204) return undefined as T;\n\n const ct = res.headers.get('content-type') ?? '';\n if (ct.includes('application/json')) return (await res.json()) as T;\n return (await res.text()) as unknown as T;\n }\n\n return {\n get: <T>(p: string, ro?: RequestOpts) => request<T>('GET', p, ro),\n post: <T>(p: string, ro?: RequestOpts) => request<T>('POST', p, ro),\n put: <T>(p: string, ro?: RequestOpts) => request<T>('PUT', p, ro),\n patch: <T>(p: string, ro?: RequestOpts) => request<T>('PATCH', p, ro),\n delete: <T>(p: string, ro?: RequestOpts) => request<T>('DELETE', p, ro),\n };\n}\n","import type { z } from 'zod';\n\nexport interface ContractDef<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown> {\n query?: z.ZodType<Q>;\n body?: z.ZodType<B>;\n response: z.ZodType<R>;\n params?: z.ZodType<P>;\n error?: z.ZodType<E>;\n}\n\nexport function defineContract<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown>(\n def: ContractDef<Q, B, R, P, E>,\n): ContractDef<Q, B, R, P, E> {\n return def;\n}\n","import { SetMetadata, UsePipes, applyDecorators } from '@nestjs/common';\nimport { ContractValidationPipe } from './contract-validation.pipe.js';\nimport type { ContractDef } from './contract.js';\nimport { CONTRACT_METADATA } from './metadata.js';\n\nexport interface ApplyContractOptions {\n /**\n * When `true`, validates incoming `body` and `query` against the Contract's\n * Zod schemas at runtime using a NestJS pipe.\n *\n * On validation failure a `BadRequestException` is thrown with the Zod\n * issues serialized in the response body:\n * `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n *\n * **Default: `false`** — schemas are read at codegen-time only, and no\n * runtime validation is performed. This matches the behaviour prior to\n * v0.9 and avoids breaking existing apps. Enable explicitly to enforce\n * the contract at runtime.\n */\n validate?: boolean;\n}\n\nexport function ApplyContract<C extends ContractDef>(\n c: C,\n opts: ApplyContractOptions = {},\n): MethodDecorator {\n const decorators: (ClassDecorator | MethodDecorator)[] = [SetMetadata(CONTRACT_METADATA, c)];\n\n if (opts.validate) {\n decorators.push(UsePipes(new ContractValidationPipe(c)));\n }\n\n return applyDecorators(...(decorators as MethodDecorator[]));\n}\n","import {\n type ArgumentMetadata,\n BadRequestException,\n Injectable,\n type PipeTransform,\n} from '@nestjs/common';\nimport type { ContractDef } from './contract.js';\n\n/**\n * NestJS pipe that validates incoming `body` and `query` against the\n * Contract's Zod schemas at runtime. Installed automatically when\n * `@ApplyContract(c, { validate: true })` is used.\n *\n * On validation failure throws a `BadRequestException` whose response body\n * contains `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n */\n@Injectable()\nexport class ContractValidationPipe<C extends ContractDef> implements PipeTransform {\n constructor(private readonly contract: C) {}\n\n transform(value: unknown, metadata: ArgumentMetadata): unknown {\n let schema:\n | {\n safeParse: (v: unknown) => {\n success: boolean;\n data: unknown;\n error: { issues: unknown[] };\n };\n }\n | undefined;\n\n if (metadata.type === 'body' && this.contract.body) {\n schema = this.contract.body as typeof schema;\n } else if (metadata.type === 'query' && this.contract.query) {\n schema = this.contract.query as typeof schema;\n } else {\n // Unhandled type — pass through unchanged\n return value;\n }\n\n // schema is always defined here because we checked above\n const parsed = schema!.safeParse(value);\n if (!parsed.success) {\n throw new BadRequestException({\n message: 'Contract validation failed',\n issues: parsed.error.issues,\n });\n }\n return parsed.data;\n }\n}\n","import type { ContractDef } from './contract.js';\n\nexport const CONTRACT_METADATA = Symbol.for('nestjs-inertia:contract');\n\ntype AnyContract = ContractDef<unknown, unknown, unknown, unknown, unknown>;\n\nexport function getContract(target: unknown): AnyContract | undefined {\n if (typeof target !== 'function') return undefined;\n return (Reflect.getMetadata(CONTRACT_METADATA, target) ?? undefined) as AnyContract | undefined;\n}\n","import { SetMetadata } from '@nestjs/common';\n\nexport const ROUTE_NAME_METADATA = Symbol.for('nestjs-inertia:route-name');\n\n/**\n * Override the auto-derived route name on a controller class or method.\n *\n * Codegen composes the final name as `${classPortion}.${methodPortion}`:\n * - **Class portion**: class-level `@As(...)` value if present, else the class name\n * with the `Controller` suffix stripped and first letter lowercased.\n * - **Method portion**: method-level `@As(...)` value if present, else the method name.\n *\n * Both can be multi-segment (contain dots). Each segment is validated against\n * `/^[a-z][a-zA-Z0-9]*$/`.\n *\n * @example Class-level override\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * list() { ... } // → 'crew.list'\n * }\n * ```\n *\n * @example Method-level override only\n * ```ts\n * @Controller('/api/v1/crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('directory.fetch') // → 'crew.directory.fetch'\n * list() { ... }\n * }\n * ```\n *\n * @example Both class and method\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew.admin')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('top10') // → 'crew.admin.top10'\n * list() { ... }\n * }\n * ```\n */\nexport const As = (name: string): ClassDecorator & MethodDecorator =>\n SetMetadata(ROUTE_NAME_METADATA, name);\n","import type { QueryClient } from '@tanstack/query-core';\n\n/**\n * Invalidates queries matching `name` (and optionally `queryArgs`).\n *\n * - `invalidate(qc, 'users.list')` → invalidates `['users.list']`\n * - `invalidate(qc, 'users.list', { id: 1 })` → invalidates `['users.list', { id: 1 }]`\n */\nexport function invalidate(qc: QueryClient, name: string, queryArgs?: unknown): Promise<void> {\n const queryKey = queryArgs === undefined ? [name] : [name, queryArgs];\n return qc.invalidateQueries({ queryKey });\n}\n","export const VERSION = '1.0.7';\n\nexport { createFetcher } from './fetcher/fetcher.js';\nexport { ApiHttpError } from './fetcher/errors.js';\nexport { buildUrl } from './fetcher/url-builder.js';\nexport { defineContract } from './contract/contract.js';\nexport { ApplyContract } from './contract/apply-contract.decorator.js';\nexport { As, ROUTE_NAME_METADATA } from './contract/as.decorator.js';\nexport { ContractValidationPipe } from './contract/contract-validation.pipe.js';\nexport { CONTRACT_METADATA, getContract } from './contract/metadata.js';\nexport type { ApplyContractOptions } from './contract/apply-contract.decorator.js';\nexport { invalidate } from './invalidate.js';\nexport type { FetcherOptions, Fetcher } from './fetcher/fetcher.js';\nexport type { ContractDef } from './contract/contract.js';\n// SSR helpers are available via the `./ssr` subpath export (dist/ssr/hydrate.js)\n"],"mappings":"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","names":["ApiHttpError","Error","status","statusText","body","name","isUnauthorized","isForbidden","isNotFound","isClient","isServer","toJSON","verbose","message","fromResponse","res","ct","headers","get","includes","json","catch","text","buildUrl","path","opts","baseUrl","resolved","replace","_match","key","val","params","undefined","Error","encodeURIComponent","String","qs","URLSearchParams","query","k","v","Object","entries","set","qsStr","toString","base","endsWith","slice","isFormData","b","FormData","createFetcher","opts","fetchImpl","fetch","globalThis","baseUrl","request","method","path","ro","Error","url","buildUrl","headers","body","undefined","JSON","stringify","accept","res","ok","err","ApiHttpError","fromResponse","onError","status","ct","get","includes","json","text","p","post","put","patch","delete","defineContract","def","SetMetadata","UsePipes","applyDecorators","BadRequestException","Injectable","ContractValidationPipe","contract","transform","value","metadata","schema","type","body","query","parsed","safeParse","success","BadRequestException","message","issues","error","data","CONTRACT_METADATA","Symbol","for","getContract","target","undefined","Reflect","getMetadata","ApplyContract","c","opts","decorators","SetMetadata","CONTRACT_METADATA","validate","push","UsePipes","ContractValidationPipe","applyDecorators","SetMetadata","ROUTE_NAME_METADATA","Symbol","for","As","name","SetMetadata","invalidate","qc","name","queryArgs","queryKey","undefined","invalidateQueries","VERSION"]}
|
|
1
|
+
{"version":3,"sources":["../src/fetcher/errors.ts","../src/fetcher/url-builder.ts","../src/fetcher/fetcher.ts","../src/contract/contract.ts","../src/contract/apply-contract.decorator.ts","../src/contract/contract-validation.pipe.ts","../src/contract/metadata.ts","../src/contract/as.decorator.ts","../src/invalidate.ts","../src/index.ts"],"sourcesContent":["export class ApiHttpError extends Error {\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body: unknown,\n ) {\n super(`HTTP ${status} ${statusText}`);\n this.name = 'ApiHttpError';\n }\n\n get isUnauthorized(): boolean {\n return this.status === 401;\n }\n get isForbidden(): boolean {\n return this.status === 403;\n }\n get isNotFound(): boolean {\n return this.status === 404;\n }\n get isClient(): boolean {\n return this.status >= 400 && this.status < 500;\n }\n get isServer(): boolean {\n return this.status >= 500;\n }\n\n /**\n * Returns a JSON-serializable representation of the error.\n * The `body` field is **redacted by default** to prevent accidental logging of\n * sensitive response bodies (e.g. API error payloads that may contain PII).\n *\n * Pass `verbose = true` to include the full body in the output.\n *\n * @example\n * ```ts\n * // Safe: body is redacted\n * JSON.stringify(err); // { ..., body: '[redacted]' }\n *\n * // Verbose: includes full body (use only in trusted contexts)\n * JSON.stringify(err.toJSON(true)); // { ..., body: { message: '...' } }\n * ```\n */\n toJSON(verbose = false): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n statusText: this.statusText,\n body: verbose ? this.body : '[redacted — pass verbose=true to include]',\n };\n }\n\n static async fromResponse(res: Response): Promise<ApiHttpError> {\n const ct = res.headers.get('content-type') ?? '';\n const body = ct.includes('application/json')\n ? await res.json().catch(() => null)\n : await res.text().catch(() => '');\n return new ApiHttpError(res.status, res.statusText, body);\n }\n}\n","export interface BuildUrlOptions {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n}\n\n/**\n * Build a URL from a path template, path params, query params, and an optional base URL.\n *\n * Examples:\n * buildUrl('/users/:id', { params: { id: 42 } }) → '/users/42'\n * buildUrl('/users', { query: { active: true } }) → '/users?active=true'\n * buildUrl('/users', {}, 'https://api.test') → 'https://api.test/users'\n */\nexport function buildUrl(path: string, opts: BuildUrlOptions = {}, baseUrl?: string): string {\n // Interpolate path params — encodeURIComponent prevents path traversal\n // e.g. { id: '../admin' } → '/users/..%2Fadmin' not '/users/../admin'\n let resolved = path.replace(/:(\\w+)/g, (_match, key: string) => {\n const val = opts.params?.[key];\n if (val === undefined || val === null) {\n throw new Error(`Missing param: ${key}`);\n }\n return encodeURIComponent(String(val));\n });\n\n // Build query string\n const qs = new URLSearchParams();\n if (opts.query) {\n for (const [k, v] of Object.entries(opts.query)) {\n if (v !== undefined) {\n qs.set(k, String(v));\n }\n }\n }\n const qsStr = qs.toString();\n if (qsStr) resolved += `?${qsStr}`;\n\n // Prepend base URL if provided\n if (baseUrl) {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n return base + resolved;\n }\n return resolved;\n}\n","import { ApiHttpError } from './errors.js';\nimport { buildUrl } from './url-builder.js';\n\nexport interface FetcherOptions {\n baseUrl?: string;\n /** Called once per request; allows dynamic auth tokens. */\n headers?: () => Record<string, string>;\n /** Injection seam for tests; default `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Invoked with the error before it is re-thrown. */\n onError?: (err: ApiHttpError) => void;\n}\n\nexport interface Fetcher {\n get<T>(path: string, opts?: RequestOpts): Promise<T>;\n post<T>(path: string, opts?: RequestOpts): Promise<T>;\n put<T>(path: string, opts?: RequestOpts): Promise<T>;\n patch<T>(path: string, opts?: RequestOpts): Promise<T>;\n delete<T>(path: string, opts?: RequestOpts): Promise<T>;\n}\n\ninterface RequestOpts {\n params?: Record<string, unknown>;\n query?: Record<string, unknown>;\n body?: unknown;\n}\n\nfunction isFormData(b: unknown): b is FormData {\n return typeof FormData !== 'undefined' && b instanceof FormData;\n}\n\nexport function createFetcher(opts: FetcherOptions = {}): Fetcher {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n const baseUrl = opts.baseUrl ?? '';\n\n async function request<T>(method: string, path: string, ro: RequestOpts = {}): Promise<T> {\n if (!fetchImpl) {\n throw new Error('No fetch implementation: pass opts.fetch or set globalThis.fetch');\n }\n const url = buildUrl(path, ro, baseUrl);\n const headers: Record<string, string> = { ...opts.headers?.() };\n let body: string | FormData | undefined = undefined;\n\n if (ro.body !== undefined) {\n if (isFormData(ro.body)) {\n body = ro.body;\n // Do NOT set Content-Type — the runtime sets it with the multipart boundary\n } else {\n body = JSON.stringify(ro.body);\n headers['content-type'] = 'application/json';\n }\n }\n\n if (!headers.accept) {\n headers.accept = 'application/json';\n }\n\n const res = await fetchImpl(url, { method, headers, ...(body !== undefined ? { body } : {}) });\n\n if (!res.ok) {\n const err = await ApiHttpError.fromResponse(res);\n opts.onError?.(err);\n throw err;\n }\n\n if (res.status === 204) return undefined as T;\n\n const ct = res.headers.get('content-type') ?? '';\n if (ct.includes('application/json')) return (await res.json()) as T;\n return (await res.text()) as unknown as T;\n }\n\n return {\n get: <T>(p: string, ro?: RequestOpts) => request<T>('GET', p, ro),\n post: <T>(p: string, ro?: RequestOpts) => request<T>('POST', p, ro),\n put: <T>(p: string, ro?: RequestOpts) => request<T>('PUT', p, ro),\n patch: <T>(p: string, ro?: RequestOpts) => request<T>('PATCH', p, ro),\n delete: <T>(p: string, ro?: RequestOpts) => request<T>('DELETE', p, ro),\n };\n}\n","import type { z } from 'zod';\n\nexport interface ContractDef<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown> {\n query?: z.ZodType<Q>;\n body?: z.ZodType<B>;\n response: z.ZodType<R>;\n params?: z.ZodType<P>;\n error?: z.ZodType<E>;\n}\n\nexport function defineContract<Q = unknown, B = unknown, R = unknown, P = unknown, E = unknown>(\n def: ContractDef<Q, B, R, P, E>,\n): ContractDef<Q, B, R, P, E> {\n return def;\n}\n","import { SetMetadata, UsePipes, applyDecorators } from '@nestjs/common';\nimport { ContractValidationPipe } from './contract-validation.pipe.js';\nimport type { ContractDef } from './contract.js';\nimport { CONTRACT_METADATA } from './metadata.js';\n\nexport interface ApplyContractOptions {\n /**\n * When `true`, validates incoming `body` and `query` against the Contract's\n * Zod schemas at runtime using a NestJS pipe.\n *\n * On validation failure a `BadRequestException` is thrown with the Zod\n * issues serialized in the response body:\n * `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n *\n * **Default: `false`** — schemas are read at codegen-time only, and no\n * runtime validation is performed. This matches the behaviour prior to\n * v0.9 and avoids breaking existing apps. Enable explicitly to enforce\n * the contract at runtime.\n */\n validate?: boolean;\n}\n\nexport function ApplyContract<C extends ContractDef>(\n c: C,\n opts: ApplyContractOptions = {},\n): MethodDecorator {\n const decorators: (ClassDecorator | MethodDecorator)[] = [SetMetadata(CONTRACT_METADATA, c)];\n\n if (opts.validate) {\n decorators.push(UsePipes(new ContractValidationPipe(c)));\n }\n\n return applyDecorators(...(decorators as MethodDecorator[]));\n}\n","import {\n type ArgumentMetadata,\n BadRequestException,\n Injectable,\n type PipeTransform,\n} from '@nestjs/common';\nimport type { ContractDef } from './contract.js';\n\n/**\n * NestJS pipe that validates incoming `body` and `query` against the\n * Contract's Zod schemas at runtime. Installed automatically when\n * `@ApplyContract(c, { validate: true })` is used.\n *\n * On validation failure throws a `BadRequestException` whose response body\n * contains `{ message: 'Contract validation failed', issues: ZodIssue[] }`.\n */\n@Injectable()\nexport class ContractValidationPipe<C extends ContractDef> implements PipeTransform {\n constructor(private readonly contract: C) {}\n\n transform(value: unknown, metadata: ArgumentMetadata): unknown {\n let schema:\n | {\n safeParse: (v: unknown) => {\n success: boolean;\n data: unknown;\n error: { issues: unknown[] };\n };\n }\n | undefined;\n\n if (metadata.type === 'body' && this.contract.body) {\n schema = this.contract.body as typeof schema;\n } else if (metadata.type === 'query' && this.contract.query) {\n schema = this.contract.query as typeof schema;\n } else {\n // Unhandled type — pass through unchanged\n return value;\n }\n\n // schema is always defined here because we checked above\n const parsed = schema!.safeParse(value);\n if (!parsed.success) {\n throw new BadRequestException({\n message: 'Contract validation failed',\n issues: parsed.error.issues,\n });\n }\n return parsed.data;\n }\n}\n","import type { ContractDef } from './contract.js';\n\nexport const CONTRACT_METADATA = Symbol.for('nestjs-inertia:contract');\n\ntype AnyContract = ContractDef<unknown, unknown, unknown, unknown, unknown>;\n\nexport function getContract(target: unknown): AnyContract | undefined {\n if (typeof target !== 'function') return undefined;\n return (Reflect.getMetadata(CONTRACT_METADATA, target) ?? undefined) as AnyContract | undefined;\n}\n","import { SetMetadata } from '@nestjs/common';\n\nexport const ROUTE_NAME_METADATA = Symbol.for('nestjs-inertia:route-name');\n\n/**\n * Override the auto-derived route name on a controller class or method.\n *\n * Codegen composes the final name as `${classPortion}.${methodPortion}`:\n * - **Class portion**: class-level `@As(...)` value if present, else the class name\n * with the `Controller` suffix stripped and first letter lowercased.\n * - **Method portion**: method-level `@As(...)` value if present, else the method name.\n *\n * Both can be multi-segment (contain dots). Each segment is validated against\n * `/^[a-z][a-zA-Z0-9]*$/`.\n *\n * @example Class-level override\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * list() { ... } // → 'crew.list'\n * }\n * ```\n *\n * @example Method-level override only\n * ```ts\n * @Controller('/api/v1/crew')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('directory.fetch') // → 'crew.directory.fetch'\n * list() { ... }\n * }\n * ```\n *\n * @example Both class and method\n * ```ts\n * @Controller('/api/v1/crew')\n * @As('crew.admin')\n * class CrewController {\n * @Get()\n * @ApplyContract(ListCrew)\n * @As('top10') // → 'crew.admin.top10'\n * list() { ... }\n * }\n * ```\n */\nexport const As = (name: string): ClassDecorator & MethodDecorator =>\n SetMetadata(ROUTE_NAME_METADATA, name);\n","import type { QueryClient } from '@tanstack/query-core';\n\n/**\n * Invalidates queries matching `name` (and optionally `queryArgs`).\n *\n * - `invalidate(qc, 'users.list')` → invalidates `['users.list']`\n * - `invalidate(qc, 'users.list', { id: 1 })` → invalidates `['users.list', { id: 1 }]`\n */\nexport function invalidate(qc: QueryClient, name: string, queryArgs?: unknown): Promise<void> {\n const queryKey = queryArgs === undefined ? [name] : [name, queryArgs];\n return qc.invalidateQueries({ queryKey });\n}\n","export const VERSION = '2.0.1';\n\nexport { createFetcher } from './fetcher/fetcher.js';\nexport { ApiHttpError } from './fetcher/errors.js';\nexport { buildUrl } from './fetcher/url-builder.js';\nexport { defineContract } from './contract/contract.js';\nexport { ApplyContract } from './contract/apply-contract.decorator.js';\nexport { As, ROUTE_NAME_METADATA } from './contract/as.decorator.js';\nexport { ContractValidationPipe } from './contract/contract-validation.pipe.js';\nexport { CONTRACT_METADATA, getContract } from './contract/metadata.js';\nexport type { ApplyContractOptions } from './contract/apply-contract.decorator.js';\nexport { invalidate } from './invalidate.js';\nexport type { FetcherOptions, Fetcher } from './fetcher/fetcher.js';\nexport type { ContractDef } from './contract/contract.js';\n// SSR helpers are available via the `./ssr` subpath export (dist/ssr/hydrate.js)\n"],"mappings":"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","names":["ApiHttpError","Error","status","statusText","body","name","isUnauthorized","isForbidden","isNotFound","isClient","isServer","toJSON","verbose","message","fromResponse","res","ct","headers","get","includes","json","catch","text","buildUrl","path","opts","baseUrl","resolved","replace","_match","key","val","params","undefined","Error","encodeURIComponent","String","qs","URLSearchParams","query","k","v","Object","entries","set","qsStr","toString","base","endsWith","slice","isFormData","b","FormData","createFetcher","opts","fetchImpl","fetch","globalThis","baseUrl","request","method","path","ro","Error","url","buildUrl","headers","body","undefined","JSON","stringify","accept","res","ok","err","ApiHttpError","fromResponse","onError","status","ct","get","includes","json","text","p","post","put","patch","delete","defineContract","def","SetMetadata","UsePipes","applyDecorators","BadRequestException","Injectable","ContractValidationPipe","contract","transform","value","metadata","schema","type","body","query","parsed","safeParse","success","BadRequestException","message","issues","error","data","CONTRACT_METADATA","Symbol","for","getContract","target","undefined","Reflect","getMetadata","ApplyContract","c","opts","decorators","SetMetadata","CONTRACT_METADATA","validate","push","UsePipes","ContractValidationPipe","applyDecorators","SetMetadata","ROUTE_NAME_METADATA","Symbol","for","As","name","SetMetadata","invalidate","qc","name","queryArgs","queryKey","undefined","invalidateQueries","VERSION"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-inertia-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Tuyau-style typed HTTP client for @dudousxd/nestjs-inertia, built on TanStack Query v5 core.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@inertiajs/svelte": "^3.0.0",
|
|
58
58
|
"svelte": "^5.0.0",
|
|
59
59
|
"zod": "^3.22.0",
|
|
60
|
-
"@dudousxd/nestjs-inertia": "
|
|
60
|
+
"@dudousxd/nestjs-inertia": "2.0.1"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
63
63
|
"@tanstack/query-core": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"svelte": "^5.55.9",
|
|
108
108
|
"vitest": "^3.0.0",
|
|
109
109
|
"zod": "^3.23.0",
|
|
110
|
-
"@dudousxd/nestjs-inertia": "
|
|
110
|
+
"@dudousxd/nestjs-inertia": "2.0.1"
|
|
111
111
|
},
|
|
112
112
|
"engines": {
|
|
113
113
|
"node": ">=20"
|