rpc4next 0.5.0 → 0.5.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/README.md CHANGED
@@ -8,6 +8,18 @@ Inspired by Hono RPC and Pathpida, **rpc4next** automatically generates a type-s
8
8
 
9
9
  ---
10
10
 
11
+ ## Development Notes
12
+
13
+ This repository is a monorepo.
14
+
15
+ - `packages/rpc4next` contains the runtime client/server helpers.
16
+ - `packages/rpc4next-cli` contains the Next.js scanner and type generator.
17
+ - `integration/next-app` is the real end-to-end fixture app used to verify generated artifacts, runtime behavior, and browser usage together.
18
+
19
+ When a change affects scanner behavior, generated path structure output, params generation, or integration fixture routes, regenerate the integration artifacts and review those diffs as part of the change.
20
+
21
+ ---
22
+
11
23
  ## ✨ Features
12
24
 
13
25
  - ✅ ルート、パラメータ、クエリパラメータ、 リクエストボディ、レスポンスの型安全なクライアント生成
@@ -68,6 +80,21 @@ npx rpc4next <baseDir> <outputPath>
68
80
 
69
81
  `rpc4next` command is provided by the `rpc4next-cli` package.
70
82
 
83
+ ```json
84
+ {
85
+ "baseDir": "app",
86
+ "outputPath": "src/generated/rpc.ts",
87
+ "paramsFile": "params.ts"
88
+ }
89
+ ```
90
+
91
+ `rpc4next.config.json` を実行ディレクトリに置くと、固定値を CLI 引数に繰り返し書かずに済みます。
92
+
93
+ ```bash
94
+ npx rpc4next
95
+ npx rpc4next --watch
96
+ ```
97
+
71
98
  - `<baseDir>`: Next.js の Appルータが配置されたベースディレクトリ
72
99
  - `<outputPath>`: 生成された型定義ファイルの出力先
73
100
 
@@ -323,7 +350,25 @@ async function callUserApi() {
323
350
  ## 🚧 Requirements
324
351
 
325
352
  - Next.js 14+ (App Router 使用)
326
- - Node.js 20.9.0+
353
+ - Node.js 20.19.2+
354
+ - aqua (Node.js / Bun のバージョン管理)
355
+
356
+ ```bash
357
+ aqua i
358
+ ```
359
+
360
+ - `aqua/aqua.yaml` を更新したら、チェックサムを更新してください
361
+
362
+ ```bash
363
+ aqua update-checksum
364
+ ```
365
+
366
+ - CI / ローカルで設定を強制する場合は以下を利用してください
367
+
368
+ ```bash
369
+ export AQUA_ENFORCE_CHECKSUM=true
370
+ export AQUA_ENFORCE_REQUIRE_CHECKSUM=true
371
+ ```
327
372
 
328
373
  ---
329
374
 
@@ -7,5 +7,9 @@ export declare const isDynamic: (key: string) => boolean;
7
7
  * Returns true if the key represents a catch-all or optional catch-all segment.
8
8
  */
9
9
  export declare const isCatchAllOrOptional: (key: string) => boolean;
10
+ /**
11
+ * Returns true if the key represents an optional catch-all segment.
12
+ */
13
+ export declare const isOptionalCatchAll: (key: string) => boolean;
10
14
  export declare const isHttpMethod: (value: string) => value is HttpMethodFuncKey;
11
- export declare const deepMerge: <T extends object, U extends object>(target: T, source: U) => T;
15
+ export declare const deepMerge: <T extends object, U extends object>(target: T, source: U) => T & U;
@@ -1 +1 @@
1
- import{CATCH_ALL_PREFIX as c,DYNAMIC_PREFIX as a,HTTP_METHOD_FUNC_KEYS as p,OPTIONAL_CATCH_ALL_PREFIX as u}from"rpc4next-shared";const y=t=>t.startsWith(a),l=t=>t.startsWith(c)||t.startsWith(u),d=new Set(p),A=t=>d.has(t),i=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),h=(t,e)=>{const s={...t};for(const n in e)if(Object.hasOwn(e,n)){const r=t[n],o=e[n];i(r)&&i(o)?s[n]=h(r,o):s[n]=o}return s};export{h as deepMerge,l as isCatchAllOrOptional,y as isDynamic,A as isHttpMethod};
1
+ import{CATCH_ALL_PREFIX as a,DYNAMIC_PREFIX as p,HTTP_METHOD_FUNC_KEYS as h,OPTIONAL_CATCH_ALL_PREFIX as i}from"rpc4next-shared";const g=t=>t.startsWith(p),y=t=>t.startsWith(a)||t.startsWith(i),A=t=>t.startsWith(i),u=new Set(h),T=t=>u.has(t),c=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),d=(t,s)=>{const e={...t};for(const n in s)if(Object.hasOwn(s,n)){const r=t[n],o=s[n];c(r)&&c(o)?e[n]=d(r,o):e[n]=o}return e};export{d as deepMerge,y as isCatchAllOrOptional,g as isDynamic,T as isHttpMethod,A as isOptionalCatchAll};
@@ -1 +1 @@
1
- import{searchParamsToObject as w}from"../lib/search-params";import{isCatchAllOrOptional as D}from"./client-utils";import{replaceDynamicSegments as x}from"./url";function o(n){if(n!=null)try{return decodeURIComponent(n)}catch{return n}}const L=n=>{const e=n.join("/").replace(/\/+/g,"/");return(e.startsWith("/")?e:`/${e}`).replace(/\/+$/,"")||"/"},C=(n,e)=>{const d=L(n),f=x(d,{optionalCatchAll:"(?:/(.*))?",catchAll:"/([^/]+(?:/[^/]+)*)",dynamic:"/([^/]+)"}),h=new RegExp(`^${f}(?:/)?$`);return g=>{let t;try{t=new URL(g,"http://dummy")}catch{return null}const m=t.pathname,u=h.exec(m);if(!u)return null;const i={};for(let a=0;a<e.length;a++){const l=e[a],s=l.replace(/^_+/,""),r=u[a+1];if(D(l))if(r==null||r==="")i[s]=void 0;else{const R=r.split("/").filter(Boolean).map(c=>o(c)).filter(c=>c!==void 0);i[s]=R}else i[s]=o(r)}const p=w(t.searchParams),P=t.hash?t.hash.slice(1):void 0,y=o(P);return{params:i,query:p,hash:y}}};export{C as matchPath};
1
+ import{searchParamsToObject as P}from"../lib/search-params";import{isCatchAllOrOptional as x}from"./client-utils";function a(n){if(n!=null)try{return decodeURIComponent(n)}catch{return n}}const D=n=>n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),O=(n,u)=>{const c=n.map(t=>t.replace(/^\/+|\/+$/g,"")).filter(Boolean),h=c.length===0?"/":c.reduce((t,e)=>u.includes(e)?e.startsWith("_____")?`${t}(?:/(.*))?`:e.startsWith("___")?`${t}/([^/]+(?:/[^/]+)*)`:`${t}/([^/]+)`:`${t}/${D(a(e))}`,""),g=new RegExp(`^${h}(?:/)?$`);return t=>{let e;try{e=new URL(t,"http://dummy")}catch{return null}const p=e.pathname,l=g.exec(p);if(!l)return null;const i={};for(let s=0;s<u.length;s++){const f=u[s],o=f.replace(/^_+/,""),r=l[s+1];if(x(f))if(r==null||r==="")i[o]=void 0;else{const R=r.split("/").filter(Boolean).map(d=>a(d)).filter(d=>d!==void 0);i[o]=R}else i[o]=a(r)}const m=P(e.searchParams),$=e.hash?e.hash.slice(1):void 0,_=a($);return{params:i,query:m,hash:_}}};export{O as matchPath};
@@ -2,4 +2,4 @@
2
2
  * Inspired by the design of Hono (https://github.com/honojs/hono)
3
3
  * and pathpida (https://github.com/aspida/pathpida)
4
4
  * particularly their routing structures and developer experience.
5
- */import{isDynamic as g}from"./client-utils";const l=(r,e,n,s,o)=>{const p=function(){};return new Proxy(p,{apply(a,u,t){const i=t[0],c=n[n.length-1],f=o[o.length-1];if(!c||!g(c))throw new Error(`Cannot apply a value: "${c??""}" is not a dynamic segment.`);if(i===void 0){const d=f??c;throw new Error(`Missing value for dynamic parameter: ${String(d)}`)}return l(r,e,n,{...s,[f]:i},o)},get(a,u){if(u==="then"||typeof u=="symbol")return;const t=String(u),i=r(t,{paths:n,params:s,dynamicKeys:o,options:e});return i!==void 0?i:g(t)?l(r,e,[...n,t],s,[...o,t]):l(r,e,[...n,t],s,o)}})},T=r=>(e="/",n={})=>l(r,n,[e],{},[]);export{T as makeCreateRpc};
5
+ */import{isDynamic as g,isOptionalCatchAll as w}from"./client-utils";const a=(r,e,n,l,o)=>{const p=function(){};return new Proxy(p,{apply(c,u,t){const i=t[0],s=n[n.length-1],f=o[o.length-1];if(!s||!g(s))throw new Error(`Cannot apply a value: "${s??""}" is not a dynamic segment.`);if(i===void 0&&!w(s)){const d=f??s;throw new Error(`Missing value for dynamic parameter: ${String(d)}`)}return a(r,e,n,{...l,[f]:i},o)},get(c,u){if(u==="then"||typeof u=="symbol")return;const t=String(u),i=r(t,{paths:n,params:l,dynamicKeys:o,options:e});return i!==void 0?i:g(t)?a(r,e,[...n,t],l,[...o,t]):a(r,e,[...n,t],l,o)}})},x=r=>(e="/",n={})=>a(r,n,[e],{},[]);export{x as makeCreateRpc};
@@ -1 +1 @@
1
- const $=t=>{if(!t)return"";const n=t.query?`?${new URLSearchParams(t.query).toString()}`:"",c=t.hash?`#${t.hash}`:"";return n+c},m=(t,n)=>t.replace(/\/_{5}(\w+)/g,n.optionalCatchAll).replace(/\/_{3}(\w+)/g,n.catchAll).replace(/\/_(\w+)/g,n.dynamic),u=(t,n,c)=>{const s=t.shift(),o=`/${t.join("/")}`,p=c.reduce((a,r)=>{const e=n[r];return Array.isArray(e)?a.replace(`/${r}`,`/${e.map(encodeURIComponent).join("/")}`):e===void 0?a.replace(`/${r}`,""):a.replace(`/${r}`,`/${encodeURIComponent(e)}`)},o);return a=>{const r=`${p}${$(a)}`,e=m(o,{optionalCatchAll:"/[[...$1]]",catchAll:"/[...$1]",dynamic:"/[$1]"}),i={};for(const l in n){const h=l.replace(/^_+/,"");i[h]=n[l]}return{pathname:e,params:i,path:s?`${s.replace(/\/$/,"")}${r}`:r,relativePath:r}}};export{$ as buildUrlSuffix,u as createUrl,m as replaceDynamicSegments};
1
+ const $=t=>{if(!t)return"";const r=t.query?`?${new URLSearchParams(t.query).toString()}`:"",c=t.hash?`#${t.hash}`:"";return r+c},f=(t,r)=>t.replace(/\/_{5}(\w+)/g,r.optionalCatchAll).replace(/\/_{3}(\w+)/g,r.catchAll).replace(/\/_(\w+)/g,r.dynamic),l=t=>{try{return decodeURIComponent(t)}catch{return t}},p=t=>{const r=t.join("/");return(r?`/${r}`:"/").replace(/\/+/g,"/").replace(/\/+$/,"")||"/"},g=t=>t.startsWith("_____")?`[[...${t.slice(5)}]]`:t.startsWith("___")?`[...${t.slice(3)}]`:t.startsWith("_")?`[${t.slice(1)}]`:l(t),m=(t,r,c)=>{const s=t.shift(),h=p(t.map(n=>c.includes(n)?n:l(n))),u=c.reduce((n,e)=>{const a=r[e];return Array.isArray(a)?n.replace(`/${e}`,`/${a.map(encodeURIComponent).join("/")}`):a===void 0?n.replace(`/${e}`,""):n.replace(`/${e}`,`/${encodeURIComponent(a)}`)},h);return n=>{const e=`${u}${$(n)}`,a=p(t.map(g)),i={};for(const o in r){const d=o.replace(/^_+/,"");i[d]=r[o]}return{pathname:a,params:i,path:s?`${s.replace(/\/$/,"")}${e}`:e,relativePath:e}}};export{$ as buildUrlSuffix,m as createUrl,f as replaceDynamicSegments};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rpc4next",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Inspired by Hono RPC and Pathpida, rpc4next brings a lightweight and intuitive RPC solution to Next.js, making server-client communication seamless",
5
5
  "keywords": [
6
6
  "next.js",
@@ -16,6 +16,9 @@
16
16
  "license": "MIT",
17
17
  "author": "watanabe-1",
18
18
  "type": "module",
19
+ "engines": {
20
+ "node": ">=20.19.2"
21
+ },
19
22
  "module": "dist/index.js",
20
23
  "types": "dist/index.d.ts",
21
24
  "files": [
@@ -93,10 +96,10 @@
93
96
  "test:watch": "vitest --watch",
94
97
  "lint": "biome check src",
95
98
  "lint:fix": "biome check --write src",
96
- "typecheck": "tsc -b tsconfig.build.json --noEmit"
99
+ "typecheck": "tsc -p tsconfig.build.json --noEmit"
97
100
  },
98
101
  "dependencies": {
99
- "rpc4next-shared": "^0.2.0"
102
+ "rpc4next-shared": "^0.3.0"
100
103
  },
101
104
  "peerDependencies": {
102
105
  "next": "^14.0.0 || ^15.0.0"