@orpc/nest 0.0.0 → 0.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/dist/index.d.mts CHANGED
@@ -1,17 +1,36 @@
1
- import { AnyContractProcedure, HTTPPath, AnyContractRouter } from '@orpc/contract';
2
- import { BuilderConfig, ImplementerInternal } from '@orpc/server';
3
- export { ORPCError } from '@orpc/server';
4
1
  import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { ContractRouter, HTTPPath, AnyContractRouter, ContractProcedure } from '@orpc/contract';
3
+ import { Router } from '@orpc/server';
4
+ export { ImplementedProcedure, Implementer, ImplementerInternal, ImplementerInternalWithMiddlewares, ORPCError, ProcedureImplementer, RouterImplementer, RouterImplementerWithMiddlewares, implement } from '@orpc/server';
5
+ import { Promisable } from '@orpc/shared';
5
6
  import { Observable } from 'rxjs';
6
7
 
7
- declare function Implement(contract: AnyContractProcedure): MethodDecorator;
8
-
8
+ /**
9
+ * Decorator in controller handler to implement a oRPC contract.
10
+ *
11
+ * @see {@link https://orpc.unnoq.com/docs/openapi/nest/implement-contract#implement-your-contract NestJS Implement Contract Docs}
12
+ */
13
+ declare function Implement<T extends ContractRouter<any>>(contract: T): <U extends Promisable<Router<T, Record<never, never>>>>(target: Record<PropertyKey, any>, propertyKey: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => U>) => void;
9
14
  declare class ImplementInterceptor implements NestInterceptor {
10
15
  intercept(ctx: ExecutionContext, next: CallHandler<any>): Observable<any>;
11
16
  }
12
17
 
13
- declare function toFastifyPattern(path: HTTPPath): string;
14
-
15
- declare function implement<T extends AnyContractRouter>(contract: T, config?: BuilderConfig): ImplementerInternal<T, Record<never, never>, Record<never, never>>;
18
+ declare function toNestPattern(path: HTTPPath): string;
19
+ type PopulatedContractRouterPaths<T extends AnyContractRouter> = T extends ContractProcedure<infer UInputSchema, infer UOutputSchema, infer UErrors, infer UMeta> ? ContractProcedure<UInputSchema, UOutputSchema, UErrors, UMeta> : {
20
+ [K in keyof T]: T[K] extends AnyContractRouter ? PopulatedContractRouterPaths<T[K]> : never;
21
+ };
22
+ interface PopulateContractRouterPathsOptions {
23
+ path?: readonly string[];
24
+ }
25
+ /**
26
+ * populateContractRouterPaths is completely optional,
27
+ * because the procedure's path is required for NestJS implementation.
28
+ * This utility automatically populates any missing paths
29
+ * Using the router's keys + `/`.
30
+ *
31
+ * @see {@link https://orpc.unnoq.com/docs/openapi/nest/implement-contract#define-your-contract NestJS Implement Contract Docs}
32
+ */
33
+ declare function populateContractRouterPaths<T extends AnyContractRouter>(router: T, options?: PopulateContractRouterPathsOptions): PopulatedContractRouterPaths<T>;
16
34
 
17
- export { Implement, ImplementInterceptor, implement, toFastifyPattern };
35
+ export { Implement as Impl, Implement, ImplementInterceptor, populateContractRouterPaths, toNestPattern };
36
+ export type { PopulateContractRouterPathsOptions, PopulatedContractRouterPaths };
package/dist/index.d.ts CHANGED
@@ -1,17 +1,36 @@
1
- import { AnyContractProcedure, HTTPPath, AnyContractRouter } from '@orpc/contract';
2
- import { BuilderConfig, ImplementerInternal } from '@orpc/server';
3
- export { ORPCError } from '@orpc/server';
4
1
  import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { ContractRouter, HTTPPath, AnyContractRouter, ContractProcedure } from '@orpc/contract';
3
+ import { Router } from '@orpc/server';
4
+ export { ImplementedProcedure, Implementer, ImplementerInternal, ImplementerInternalWithMiddlewares, ORPCError, ProcedureImplementer, RouterImplementer, RouterImplementerWithMiddlewares, implement } from '@orpc/server';
5
+ import { Promisable } from '@orpc/shared';
5
6
  import { Observable } from 'rxjs';
6
7
 
7
- declare function Implement(contract: AnyContractProcedure): MethodDecorator;
8
-
8
+ /**
9
+ * Decorator in controller handler to implement a oRPC contract.
10
+ *
11
+ * @see {@link https://orpc.unnoq.com/docs/openapi/nest/implement-contract#implement-your-contract NestJS Implement Contract Docs}
12
+ */
13
+ declare function Implement<T extends ContractRouter<any>>(contract: T): <U extends Promisable<Router<T, Record<never, never>>>>(target: Record<PropertyKey, any>, propertyKey: string, descriptor: TypedPropertyDescriptor<(...args: any[]) => U>) => void;
9
14
  declare class ImplementInterceptor implements NestInterceptor {
10
15
  intercept(ctx: ExecutionContext, next: CallHandler<any>): Observable<any>;
11
16
  }
12
17
 
13
- declare function toFastifyPattern(path: HTTPPath): string;
14
-
15
- declare function implement<T extends AnyContractRouter>(contract: T, config?: BuilderConfig): ImplementerInternal<T, Record<never, never>, Record<never, never>>;
18
+ declare function toNestPattern(path: HTTPPath): string;
19
+ type PopulatedContractRouterPaths<T extends AnyContractRouter> = T extends ContractProcedure<infer UInputSchema, infer UOutputSchema, infer UErrors, infer UMeta> ? ContractProcedure<UInputSchema, UOutputSchema, UErrors, UMeta> : {
20
+ [K in keyof T]: T[K] extends AnyContractRouter ? PopulatedContractRouterPaths<T[K]> : never;
21
+ };
22
+ interface PopulateContractRouterPathsOptions {
23
+ path?: readonly string[];
24
+ }
25
+ /**
26
+ * populateContractRouterPaths is completely optional,
27
+ * because the procedure's path is required for NestJS implementation.
28
+ * This utility automatically populates any missing paths
29
+ * Using the router's keys + `/`.
30
+ *
31
+ * @see {@link https://orpc.unnoq.com/docs/openapi/nest/implement-contract#define-your-contract NestJS Implement Contract Docs}
32
+ */
33
+ declare function populateContractRouterPaths<T extends AnyContractRouter>(router: T, options?: PopulateContractRouterPathsOptions): PopulatedContractRouterPaths<T>;
16
34
 
17
- export { Implement, ImplementInterceptor, implement, toFastifyPattern };
35
+ export { Implement as Impl, Implement, ImplementInterceptor, populateContractRouterPaths, toNestPattern };
36
+ export type { PopulateContractRouterPathsOptions, PopulatedContractRouterPaths };
package/dist/index.mjs CHANGED
@@ -1,133 +1,86 @@
1
- import { call, ORPCError as ORPCError$1, implementerInternal } from '@orpc/server';
2
- export { ORPCError } from '@orpc/server';
3
- import { applyDecorators, Get, Head, Put, Patch, Delete, Post, UseInterceptors } from '@nestjs/common';
4
- import { fallbackContractConfig } from '@orpc/contract';
5
- import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializer, StandardBracketNotationSerializer, standardizeHTTPPath } from '@orpc/openapi-client/standard';
1
+ import { applyDecorators, Delete, Patch, Put, Post, Get, Head, UseInterceptors } from '@nestjs/common';
2
+ import { toORPCError } from '@orpc/client';
3
+ import { isContractProcedure, ContractProcedure, fallbackContractConfig } from '@orpc/contract';
4
+ import { standardizeHTTPPath, StandardOpenAPISerializer, StandardOpenAPIJsonSerializer, StandardBracketNotationSerializer } from '@orpc/openapi-client/standard';
6
5
  import { StandardOpenAPICodec } from '@orpc/openapi/standard';
6
+ import { getRouter, unlazy, isProcedure, call, ORPCError } from '@orpc/server';
7
+ export { ORPCError, implement } from '@orpc/server';
8
+ import { toArray, get } from '@orpc/shared';
7
9
  import { toStandardLazyRequest, sendStandardResponse } from '@orpc/standard-server-node';
8
10
  import { mergeMap } from 'rxjs';
9
- import '@orpc/shared';
11
+ import { toHttpPath } from '@orpc/client/standard';
10
12
 
11
- const COMMON_ORPC_ERROR_DEFS = {
12
- BAD_REQUEST: {
13
- status: 400,
14
- message: "Bad Request"
15
- },
16
- UNAUTHORIZED: {
17
- status: 401,
18
- message: "Unauthorized"
19
- },
20
- FORBIDDEN: {
21
- status: 403,
22
- message: "Forbidden"
23
- },
24
- NOT_FOUND: {
25
- status: 404,
26
- message: "Not Found"
27
- },
28
- METHOD_NOT_SUPPORTED: {
29
- status: 405,
30
- message: "Method Not Supported"
31
- },
32
- NOT_ACCEPTABLE: {
33
- status: 406,
34
- message: "Not Acceptable"
35
- },
36
- TIMEOUT: {
37
- status: 408,
38
- message: "Request Timeout"
39
- },
40
- CONFLICT: {
41
- status: 409,
42
- message: "Conflict"
43
- },
44
- PRECONDITION_FAILED: {
45
- status: 412,
46
- message: "Precondition Failed"
47
- },
48
- PAYLOAD_TOO_LARGE: {
49
- status: 413,
50
- message: "Payload Too Large"
51
- },
52
- UNSUPPORTED_MEDIA_TYPE: {
53
- status: 415,
54
- message: "Unsupported Media Type"
55
- },
56
- UNPROCESSABLE_CONTENT: {
57
- status: 422,
58
- message: "Unprocessable Content"
59
- },
60
- TOO_MANY_REQUESTS: {
61
- status: 429,
62
- message: "Too Many Requests"
63
- },
64
- CLIENT_CLOSED_REQUEST: {
65
- status: 499,
66
- message: "Client Closed Request"
67
- },
68
- INTERNAL_SERVER_ERROR: {
69
- status: 500,
70
- message: "Internal Server Error"
71
- },
72
- NOT_IMPLEMENTED: {
73
- status: 501,
74
- message: "Not Implemented"
75
- },
76
- BAD_GATEWAY: {
77
- status: 502,
78
- message: "Bad Gateway"
79
- },
80
- SERVICE_UNAVAILABLE: {
81
- status: 503,
82
- message: "Service Unavailable"
83
- },
84
- GATEWAY_TIMEOUT: {
85
- status: 504,
86
- message: "Gateway Timeout"
87
- }
88
- };
89
- function fallbackORPCErrorStatus(code, status) {
90
- return status ?? COMMON_ORPC_ERROR_DEFS[code]?.status ?? 500;
91
- }
92
- function fallbackORPCErrorMessage(code, message) {
93
- return message || COMMON_ORPC_ERROR_DEFS[code]?.message || code;
13
+ function toNestPattern(path) {
14
+ return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/*$1").replace(/\/\{([^}]+)\}/g, "/:$1");
94
15
  }
95
- class ORPCError extends Error {
96
- defined;
97
- code;
98
- status;
99
- data;
100
- constructor(code, ...[options]) {
101
- if (options?.status && !isORPCErrorStatus(options.status)) {
102
- throw new Error("[ORPCError] Invalid error status code.");
16
+ function populateContractRouterPaths(router, options = {}) {
17
+ const path = toArray(options.path);
18
+ if (isContractProcedure(router)) {
19
+ if (router["~orpc"].route.path === void 0) {
20
+ return new ContractProcedure({
21
+ ...router["~orpc"],
22
+ route: {
23
+ ...router["~orpc"].route,
24
+ path: toHttpPath(path)
25
+ }
26
+ });
103
27
  }
104
- const message = fallbackORPCErrorMessage(code, options?.message);
105
- super(message, options);
106
- this.code = code;
107
- this.status = fallbackORPCErrorStatus(code, options?.status);
108
- this.defined = options?.defined ?? false;
109
- this.data = options?.data;
28
+ return router;
110
29
  }
111
- toJSON() {
112
- return {
113
- defined: this.defined,
114
- code: this.code,
115
- status: this.status,
116
- message: this.message,
117
- data: this.data
118
- };
30
+ const populated = {};
31
+ for (const key in router) {
32
+ populated[key] = populateContractRouterPaths(router[key], { ...options, path: [...path, key] });
119
33
  }
120
- }
121
- function toORPCError(error) {
122
- return error instanceof ORPCError ? error : new ORPCError("INTERNAL_SERVER_ERROR", {
123
- message: "Internal server error",
124
- cause: error
125
- });
126
- }
127
- function isORPCErrorStatus(status) {
128
- return status < 200 || status >= 400;
34
+ return populated;
129
35
  }
130
36
 
37
+ const MethodDecoratorMap = {
38
+ HEAD: Head,
39
+ GET: Get,
40
+ POST: Post,
41
+ PUT: Put,
42
+ PATCH: Patch,
43
+ DELETE: Delete
44
+ };
45
+ function Implement(contract) {
46
+ if (isContractProcedure(contract)) {
47
+ const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
48
+ const path = contract["~orpc"].route.path;
49
+ if (path === void 0) {
50
+ throw new Error(`
51
+ @Implement decorator requires contract to have a 'path'.
52
+ Please define one using 'path' property on the '.route' method.
53
+ Or use "populateContractRouterPaths" utility to automatically fill in any missing paths.
54
+ `);
55
+ }
56
+ return (target, propertyKey, descriptor) => {
57
+ applyDecorators(
58
+ MethodDecoratorMap[method](toNestPattern(path)),
59
+ UseInterceptors(ImplementInterceptor)
60
+ )(target, propertyKey, descriptor);
61
+ };
62
+ }
63
+ return (target, propertyKey, descriptor) => {
64
+ for (const key in contract) {
65
+ let methodName = `${propertyKey}_${key}`;
66
+ let i = 0;
67
+ while (methodName in target) {
68
+ methodName = `${propertyKey}_${key}_${i++}`;
69
+ }
70
+ target[methodName] = async function(...args) {
71
+ const router = await descriptor.value.apply(this, args);
72
+ return getRouter(router, [key]);
73
+ };
74
+ for (const p of Reflect.getOwnMetadataKeys(target, propertyKey)) {
75
+ Reflect.defineMetadata(p, Reflect.getOwnMetadata(p, target, propertyKey), target, methodName);
76
+ }
77
+ for (const p of Reflect.getOwnMetadataKeys(target.constructor, propertyKey)) {
78
+ Reflect.defineMetadata(p, Reflect.getOwnMetadata(p, target.constructor, propertyKey), target.constructor, methodName);
79
+ }
80
+ Implement(get(contract, [key]))(target, methodName, Object.getOwnPropertyDescriptor(target, methodName));
81
+ }
82
+ };
83
+ }
131
84
  const codec = new StandardOpenAPICodec(
132
85
  new StandardOpenAPISerializer(
133
86
  new StandardOpenAPIJsonSerializer(),
@@ -137,7 +90,13 @@ const codec = new StandardOpenAPICodec(
137
90
  class ImplementInterceptor {
138
91
  intercept(ctx, next) {
139
92
  return next.handle().pipe(
140
- mergeMap(async (procedure) => {
93
+ mergeMap(async (impl) => {
94
+ const { default: procedure } = await unlazy(impl);
95
+ if (!isProcedure(procedure)) {
96
+ throw new Error(`
97
+ The return value of the @Implement controller handler must be a corresponding implemented router or procedure.
98
+ `);
99
+ }
141
100
  const req = ctx.switchToHttp().getRequest();
142
101
  const res = ctx.switchToHttp().getResponse();
143
102
  const nodeReq = "raw" in req ? req.raw : req;
@@ -154,7 +113,7 @@ class ImplementInterceptor {
154
113
  const output = await call(procedure, input);
155
114
  return codec.encode(output, procedure);
156
115
  } catch (e) {
157
- const error = isDecoding && !(e instanceof ORPCError$1) ? new ORPCError$1("BAD_REQUEST", {
116
+ const error = isDecoding && !(e instanceof ORPCError) ? new ORPCError("BAD_REQUEST", {
158
117
  message: `Malformed request. Ensure the request body is properly formatted and the 'Content-Type' header is set correctly.`,
159
118
  cause: e
160
119
  }) : toORPCError(e);
@@ -178,30 +137,4 @@ function flattenParams(params) {
178
137
  return flatten;
179
138
  }
180
139
 
181
- function toFastifyPattern(path) {
182
- return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/*$1").replace(/\/\{([^}]+)\}/g, "/:$1");
183
- }
184
-
185
- function Implement(contract) {
186
- const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
187
- const path = contract["~orpc"].route.path;
188
- if (path === void 0) {
189
- throw new Error(`
190
- oRPC Fastify integration requires procedure to have a 'path'.
191
- Please define one using 'path' property on the '.route' method.
192
- `);
193
- }
194
- return (target, propertyKey, descriptor) => {
195
- const MethodDecorator = method === "GET" ? Get : method === "HEAD" ? Head : method === "PUT" ? Put : method === "PATCH" ? Patch : method === "DELETE" ? Delete : Post;
196
- applyDecorators(
197
- MethodDecorator(toFastifyPattern(path)),
198
- UseInterceptors(ImplementInterceptor)
199
- )(target, propertyKey, descriptor);
200
- };
201
- }
202
-
203
- function implement(contract, config = {}) {
204
- return implementerInternal(contract, config, []);
205
- }
206
-
207
- export { Implement, ImplementInterceptor, implement, toFastifyPattern };
140
+ export { Implement as Impl, Implement, ImplementInterceptor, populateContractRouterPaths, toNestPattern };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/nest",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.0.1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -40,27 +40,32 @@
40
40
  }
41
41
  },
42
42
  "dependencies": {
43
- "@orpc/openapi-client": "1.2.0",
44
- "@orpc/server": "1.2.0",
45
43
  "@orpc/client": "1.2.0",
44
+ "@orpc/openapi": "1.2.0",
45
+ "@orpc/server": "1.2.0",
46
+ "@orpc/openapi-client": "1.2.0",
46
47
  "@orpc/shared": "1.2.0",
47
48
  "@orpc/standard-server": "1.2.0",
48
- "@orpc/standard-server-node": "1.2.0",
49
- "@orpc/openapi": "1.2.0"
49
+ "@orpc/standard-server-node": "1.2.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@nestjs/common": "^11.0.0",
52
+ "@nestjs/common": "^11.1.0",
53
53
  "@nestjs/core": "^11.0.0",
54
- "@nestjs/platform-express": "^11.0.0",
55
- "@nestjs/platform-fastify": "^11.0.0",
54
+ "@nestjs/platform-express": "^11.1.0",
55
+ "@nestjs/platform-fastify": "^11.1.0",
56
+ "@nestjs/testing": "^11.1.0",
57
+ "@ts-rest/core": "^3.52.1",
56
58
  "@types/express": "^5.0.1",
57
59
  "express": "^5.0.0",
58
60
  "fastify": "^5.0.0",
59
- "rxjs": "^7.0.0"
61
+ "rxjs": "^7.0.0",
62
+ "supertest": "^7.1.0",
63
+ "zod": "^3.24.4"
60
64
  },
61
65
  "scripts": {
62
66
  "build": "unbuild",
63
67
  "build:watch": "pnpm run build --watch",
64
- "type:check": "tsc -b"
68
+ "type:check": "tsc -b",
69
+ "type:check:test": "tsc -p tsconfig.test.json --noEmit"
65
70
  }
66
71
  }