@lpdjs/firestore-repo-service 2.6.8 → 2.6.9

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';var fs=require('fs'),path=require('path'),process$1=require('process'),promises=require('readline/promises');var O={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function M(e,t=O){let s=new Set(t.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!s.has(o.toLowerCase())).map(o=>t.casing==="kebab"?re(o):o).join("/")}function re(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function H(e,t,s){let r=B(e),o=B(t),n=0;for(;n<r.length&&n<o.length&&r[n]===o[n];)n++;let c=r.length-n,i=o.slice(n),d=(i[i.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),f=s===""?d:`${d}${s}`;return i[i.length-1]=f,(c===0?"./":"../".repeat(c))+i.join("/")}function B(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}var ce="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function W(e,t){let s=path.dirname(t.outFile);fs.mkdirSync(s,{recursive:true});let r=t.banner??ce,o=(t.now??new Date).toISOString(),n=t.importExtension,c=[],i=[],a=[];e.forEach((f,u)=>{let b=H(s,f.absPath,n),v=M(f.relDir,t.derive);c.push(`import mod${u} from ${JSON.stringify(b)};`),i.push(` { __derivedPath: ${JSON.stringify(v)}, mod: mod${u} },`),a.push({source:f.relPath,url:v});});let d=`${r}// Generated at ${o} \u2014 ${e.length} route file${e.length===1?"":"s"}.
2
+ 'use strict';var fs=require('fs'),path=require('path'),process$1=require('process'),promises=require('readline/promises');var O={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function M(e,t=O){let s=new Set(t.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!s.has(o.toLowerCase())).map(o=>t.casing==="kebab"?oe(o):o).join("/")}function oe(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function W(e,t,s){let r=H(e),o=H(t),i=0;for(;i<r.length&&i<o.length&&r[i]===o[i];)i++;let c=r.length-i,n=o.slice(i),d=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),f=s===""?d:`${d}${s}`;return n[n.length-1]=f,(c===0?"./":"../".repeat(c))+n.join("/")}function H(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,r)=>!(r===0&&s===""))}var ae="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function J(e,t){let s=path.dirname(t.outFile);fs.mkdirSync(s,{recursive:true});let r=t.banner??ae,o=(t.now??new Date).toISOString(),i=t.importExtension,c=[],n=[],a=[];e.forEach((f,p)=>{let P=W(s,f.absPath,i),y=M(f.relDir,t.derive);c.push(`import mod${p} from ${JSON.stringify(P)};`),n.push(` { __derivedPath: ${JSON.stringify(y)}, mod: mod${p} },`),a.push({source:f.relPath,url:y});});let d=`${r}// Generated at ${o} \u2014 ${e.length} route file${e.length===1?"":"s"}.
3
3
 
4
4
  import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-service/servers/hono";
5
5
 
@@ -8,16 +8,16 @@ import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-serv
8
8
 
9
9
  `:`
10
10
  `)+`const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [
11
- `+i.join(`
12
- `)+(i.length?`
11
+ `+n.join(`
12
+ `)+(n.length?`
13
13
  `:"")+`];
14
14
 
15
15
  export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) => {
16
16
  const list = Array.isArray(mod) ? mod : [mod];
17
17
  return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));
18
18
  });
19
- `;return fs.writeFileSync(t.outFile,d,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:a}}var I={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function J(e,t=I){let s=[];return K(e,e,t,s),s.sort((r,o)=>r.relPath.localeCompare(o.relPath)),s}function K(e,t,s,r){let o;try{o=fs.readdirSync(t);}catch{return}for(let n of o){if(s.excludeSegments.includes(n))continue;let c=path.join(t,n),i;try{i=fs.statSync(c);}catch{continue}if(i.isDirectory())K(e,c,s,r);else if(i.isFile()&&n===s.routesFile){let a=path.relative(e,c).split(path.sep).join("/"),d=a.replace(/\/?[^/]+$/,"");r.push({absPath:c,relPath:a,relDir:d});}}}var X=".frsrc.json";function T(e=process.cwd()){let t=path.resolve(e,X);if(!fs.existsSync(t))return {};try{return JSON.parse(fs.readFileSync(t,"utf8"))}catch{return {}}}function ge(e,t=process.cwd()){let s=path.resolve(t,X),o={...T(t),...e};return fs.writeFileSync(s,`${JSON.stringify(o,null,2)}
20
- `,"utf8"),s}function he(e){let[t,...s]=e,r={};for(let o=0;o<s.length;o++){let n=s[o];if(!n.startsWith("--"))continue;let c=n.slice(2),i=s[o+1];i&&!i.startsWith("--")?(r[c]=i,o++):r[c]=true;}return {command:t??"help",flags:r}}function Y(){console.log(`frs \u2014 Hono file-based codegen
19
+ `;return fs.writeFileSync(t.outFile,d,"utf8"),{outFile:t.outFile,routeCount:e.length,derivedPaths:a}}var E={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function K(e,t=E){let s=[];return V(e,e,t,s),s.sort((r,o)=>r.relPath.localeCompare(o.relPath)),s}function V(e,t,s,r){let o;try{o=fs.readdirSync(t);}catch{return}for(let i of o){if(s.excludeSegments.includes(i))continue;let c=path.join(t,i),n;try{n=fs.statSync(c);}catch{continue}if(n.isDirectory())V(e,c,s,r);else if(n.isFile()&&i===s.routesFile){let a=path.relative(e,c).split(path.sep).join("/"),d=a.replace(/\/?[^/]+$/,"");r.push({absPath:c,relPath:a,relDir:d});}}}var ee=".frsrc.json";function T(e=process.cwd()){let t=path.resolve(e,ee);if(!fs.existsSync(t))return {};try{return JSON.parse(fs.readFileSync(t,"utf8"))}catch{return {}}}function he(e,t=process.cwd()){let s=path.resolve(t,ee),o={...T(t),...e};return fs.writeFileSync(s,`${JSON.stringify(o,null,2)}
20
+ `,"utf8"),s}function ve(e){let[t,...s]=e,r={};for(let o=0;o<s.length;o++){let i=s[o];if(!i.startsWith("--"))continue;let c=i.slice(2),n=s[o+1];n&&!n.startsWith("--")?(r[c]=n,o++):r[c]=true;}return {command:t??"help",flags:r}}function Z(){console.log(`frs \u2014 Hono file-based codegen
21
21
 
22
22
  Usage:
23
23
  frs init [flags]
@@ -83,7 +83,7 @@ Examples:
83
83
  frs new createPost --domain posts --method post
84
84
  frs new listPosts --domain posts --method get --api v1
85
85
  frs add service postRepo
86
- `);}function Z(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function m(e){return typeof e=="string"?e:void 0}function ee(e){if(e||!process$1.stdin.isTTY)return {ask:async(s,r)=>r??"",askChoice:async(s,r,o)=>o??"",askBool:async(s,r)=>r,close:()=>{}};let t=promises.createInterface({input:process$1.stdin,output:process$1.stdout});return {async ask(s,r){let o=r?` (${r})`:"";return (await t.question(`? ${s}${o} \u203A `)).trim()||r||""},async askChoice(s,r,o){let n=` [${r.join("/")}${o?`, default: ${o}`:""}]`;for(;;){let c=(await t.question(`? ${s}${n} \u203A `)).trim().toLowerCase();if(!c&&o)return o;if(r.includes(c))return c;console.log(` invalid choice \u2014 pick one of: ${r.join(", ")}`);}},async askBool(s,r){let o=` (${r?"Y/n":"y/N"})`,n=(await t.question(`? ${s}${o} \u203A `)).trim().toLowerCase();return n?n==="y"||n==="yes"||n==="true":r},close:()=>t.close()}}async function ve(e){let t=T(),s=m(e.root)??t.root;s||(console.error("[frs] --root is required (or run `frs init` to write it to .frsrc.json)"),process.exit(2));let r=path.resolve(process.cwd(),s);fs.existsSync(r)||(console.error(`[frs] root not found: ${r}`),process.exit(2));let o=m(e.out)??t.out??"__generated__/routes.ts",n=Z(e.skip)??O.skipSegments,c=m(e.casing)==="kebab"?"kebab":O.casing,i={skipSegments:n,casing:c},a=m(e.ext)??".js",d=Z(e.exclude)??I.excludeSegments,f=m(e["routes-file"])??I.routesFile,b=J(r,{routesFile:f,excludeSegments:d});b.length===0&&console.warn(`[frs] no "${f}" files found under ${r} \u2014 generated an empty manifest.`);let v=W(b,{outFile:path.resolve(r,o),derive:i,importExtension:a});if(!e.silent){console.log(`[frs] wrote ${v.outFile} (${v.routeCount} route${v.routeCount===1?"":"s"})`);for(let{source:S,url:g}of v.derivedPaths)console.log(` ${g.padEnd(48)} \u2190 ${S}`);}}async function ye(e,t){let s=t.yes===true,r=ee(s),o=T();try{let n=e&&!e.startsWith("--")?e:void 0;n||(n=(await r.ask("Route name (e.g. createPost)")).trim(),n||(console.error("[frs] route name is required"),process.exit(2)));let c=m(t.domain);c||(c=(await r.ask("Domain name (e.g. posts)")).trim(),c||(console.error("[frs] --domain is required"),process.exit(2)));let i=m(t.root)??o.root??"src/domains",a=m(t.method)?.toLowerCase();a||(a=await r.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(a)||(console.error(`[frs] invalid --method: ${a}`),process.exit(2));let d=m(t.api);if(!d){let $=o.apis?.[0]??"v1";d=(await r.ask("API tag",$)).trim()||$;}let f=m(t["usecase-folder"])??o.useCaseFolder??"useCases",u=t["with-usecase"]===void 0?s?!0:await r.askBool("Scaffold useCase.ts?",!0):t["with-usecase"]!==!1,b=t["with-test"]===void 0?s||!u?u:await r.askBool("Scaffold useCase.test.ts (Vitest)?",!0):t["with-test"]!==!1,v=t.force===!0,S=path.resolve(process.cwd(),i),g=path.resolve(S,c,f,n),D=path.resolve(g,"routes.ts"),y=`${c}.${n}.useCase`,k=path.resolve(g,`${y}.ts`),F=path.resolve(g,`${y}.test.ts`);fs.mkdirSync(g,{recursive:!0});let w=`${n.charAt(0).toUpperCase()}${n.slice(1)}UseCase`,P="@lpdjs/firestore-repo-service/servers/hono",_=be(S,g),h=a==="get"?"// GET \u2192 lu depuis les query params":`// ${a.toUpperCase()} \u2192 lu depuis le body JSON`,C=`/**
86
+ `);}function Q(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function m(e){return typeof e=="string"?e:void 0}function te(e){if(e||!process$1.stdin.isTTY)return {ask:async(s,r)=>r??"",askChoice:async(s,r,o)=>o??"",askBool:async(s,r)=>r,close:()=>{}};let t=promises.createInterface({input:process$1.stdin,output:process$1.stdout});return {async ask(s,r){let o=r?` (${r})`:"";return (await t.question(`? ${s}${o} \u203A `)).trim()||r||""},async askChoice(s,r,o){let i=` [${r.join("/")}${o?`, default: ${o}`:""}]`;for(;;){let c=(await t.question(`? ${s}${i} \u203A `)).trim().toLowerCase();if(!c&&o)return o;if(r.includes(c))return c;console.log(` invalid choice \u2014 pick one of: ${r.join(", ")}`);}},async askBool(s,r){let o=` (${r?"Y/n":"y/N"})`,i=(await t.question(`? ${s}${o} \u203A `)).trim().toLowerCase();return i?i==="y"||i==="yes"||i==="true":r},close:()=>t.close()}}async function ye(e){let t=T(),s=m(e.root)??t.root;s||(console.error("[frs] --root is required (or run `frs init` to write it to .frsrc.json)"),process.exit(2));let r=path.resolve(process.cwd(),s);fs.existsSync(r)||(console.error(`[frs] root not found: ${r}`),process.exit(2));let o=m(e.out)??t.out??"__generated__/routes.ts",i=Q(e.skip)??O.skipSegments,c=m(e.casing)==="kebab"?"kebab":O.casing,n={skipSegments:i,casing:c},a=m(e.ext)??".js",d=Q(e.exclude)??E.excludeSegments,f=m(e["routes-file"])??E.routesFile,P=K(r,{routesFile:f,excludeSegments:d});P.length===0&&console.warn(`[frs] no "${f}" files found under ${r} \u2014 generated an empty manifest.`);let y=J(P,{outFile:path.resolve(r,o),derive:n,importExtension:a});if(!e.silent){console.log(`[frs] wrote ${y.outFile} (${y.routeCount} route${y.routeCount===1?"":"s"})`);for(let{source:S,url:g}of y.derivedPaths)console.log(` ${g.padEnd(48)} \u2190 ${S}`);}}async function $e(e,t){let s=t.yes===true,r=te(s),o=T();try{let i=e&&!e.startsWith("--")?e:void 0;i||(i=(await r.ask("Route name (e.g. createPost)")).trim(),i||(console.error("[frs] route name is required"),process.exit(2)));let c=m(t.domain);c||(c=(await r.ask("Domain name (e.g. posts)")).trim(),c||(console.error("[frs] --domain is required"),process.exit(2)));let n=m(t.root)??o.root??"src/domains",a=m(t.method)?.toLowerCase();a||(a=await r.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(a)||(console.error(`[frs] invalid --method: ${a}`),process.exit(2));let d=m(t.api);if(!d){let v=o.apis?.[0]??"v1";d=(await r.ask("API tag",v)).trim()||v;}let f=m(t["usecase-folder"])??o.useCaseFolder??"useCases",p=t["with-usecase"]===void 0?s?!0:await r.askBool("Scaffold useCase.ts?",!0):t["with-usecase"]!==!1,P=t["with-test"]===void 0?s||!p?p:await r.askBool("Scaffold useCase.test.ts (Vitest)?",!0):t["with-test"]!==!1,y=t.force===!0,S=path.resolve(process.cwd(),n),g=path.resolve(S,c,f,i),A=path.resolve(g,"routes.ts"),$=`${c}.${i}.useCase`,R=path.resolve(g,`${$}.ts`),k=path.resolve(g,`${$}.test.ts`);fs.mkdirSync(g,{recursive:!0});let F=v=>v.charAt(0).toUpperCase()+v.slice(1),w=`${F(c)}${F(i)}UseCase`,D="@lpdjs/firestore-repo-service/servers/hono",h=Se(S,g),b=a==="get"?"// GET \u2192 lu depuis les query params":`// ${a.toUpperCase()} \u2192 lu depuis le body JSON`,L=`/**
87
87
  * ${w} \u2014 pure business logic, no HTTP awareness.
88
88
  *
89
89
  * Owns its Zod \`input\` / \`output\` schemas (declared as \`static\` members, the
@@ -93,11 +93,11 @@ Examples:
93
93
  */
94
94
 
95
95
  import { z } from "zod";
96
- import { UseCase } from "${P}";
97
- import type { Services } from "${_}";
96
+ import { UseCase } from "${D}";
97
+ import type { Services } from "${h}";
98
98
 
99
99
  const input = z.object({
100
- ${h}
100
+ ${b}
101
101
  example: z.string(),
102
102
  });
103
103
 
@@ -116,22 +116,22 @@ export class ${w} extends UseCase<typeof input, typeof output, Services> {
116
116
  return { id: payload.example };
117
117
  }
118
118
  }
119
- `,N=a==="get"?`
120
- source: "query",`:"",l=u?`import { ${w} } from "./${y}.js";
121
- `:"",j=m(t["apis-import"])??xe(S,g),te=u?`import { defineRoutes } from "${P}";
122
- import { useCaseRoute } from "${j}";
123
- ${l}
119
+ `,l=a==="get"?`
120
+ source: "query",`:"",_=p?`import { ${w} } from "./${$}.js";
121
+ `:"",U=m(t["apis-import"])??Pe(S,g),se=p?`import { defineRoutes } from "${D}";
122
+ import { useCaseRoute } from "${U}";
123
+ ${_}
124
124
  export default defineRoutes([
125
125
  useCaseRoute(${w}, {
126
126
  api: "${d}",
127
- method: "${a}",${N}
128
- summary: "TODO: ${n}",
127
+ method: "${a}",${l}
128
+ summary: "TODO: ${i}",
129
129
  tags: ["${c}"],
130
130
  }),
131
131
  ]);
132
132
  `:`import { z } from "zod";
133
- import { defineRoutes } from "${P}";
134
- import { defineRoute } from "${j}";
133
+ import { defineRoutes } from "${D}";
134
+ import { defineRoute } from "${U}";
135
135
 
136
136
  export default defineRoutes([
137
137
  defineRoute({
@@ -139,7 +139,7 @@ export default defineRoutes([
139
139
  method: "${a}",
140
140
 
141
141
  input: z.object({
142
- ${h}
142
+ ${b}
143
143
  example: z.string(),
144
144
  }),
145
145
 
@@ -147,7 +147,7 @@ export default defineRoutes([
147
147
  id: z.string(),
148
148
  }),
149
149
 
150
- summary: "TODO: ${n}",
150
+ summary: "TODO: ${i}",
151
151
  tags: ["${c}"],
152
152
 
153
153
  handler: async ({ input }) => {
@@ -156,9 +156,9 @@ export default defineRoutes([
156
156
  },
157
157
  }),
158
158
  ]);
159
- `,z=[],G=[],q=($,se)=>{if(fs.existsSync($)&&!v){G.push($);return}fs.writeFileSync($,se,"utf8"),z.push($);};if(q(D,te),u&&q(k,C),u&&b){let $=`import { describe, it, expect } from "vitest";
160
- import type { Services } from "${_}";
161
- import { ${w} } from "./${y}.js";
159
+ `,z=[],G=[],N=(v,re)=>{if(fs.existsSync(v)&&!y){G.push(v);return}fs.writeFileSync(v,re,"utf8"),z.push(v);};if(N(A,se),p&&N(R,L),p&&P){let v=`import { describe, it, expect } from "vitest";
160
+ import type { Services } from "${h}";
161
+ import { ${w} } from "./${$}.js";
162
162
 
163
163
  describe("${w}", () => {
164
164
  it("returns a response shaped like the output schema", async () => {
@@ -172,28 +172,36 @@ describe("${w}", () => {
172
172
 
173
173
  // TODO: add error-path tests, repository mocks, etc.
174
174
  });
175
- `;q(F,$);}for(let $ of z)console.log(`[frs] wrote ${$}`);for(let $ of G)console.log(`[frs] skipped ${$} (use --force to overwrite)`);console.log(`
176
- [frs] reminder: run "frs gen --root ${i}" to refresh the manifest.`);}finally{r.close();}}async function $e(e){let t=e.yes===true,s=ee(t);try{let r=e.force===!0,o=m(e.root);o||(o=(await s.ask("Domain root","src/domains")).trim()||"src/domains");let n=m(e["apis-file"]);n||(n=(await s.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let c=m(e["services-file"]);if(!c){let l=n.replace(/apis\.ts$/,"services.ts")||"src/services.ts";c=(await s.ask("services.ts location",l)).trim()||l;}let i=m(e.apis);i||(i=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let a=i.split(",").map(l=>l.trim()).filter(Boolean);a.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let d=m(e["base-path"]),f=path.resolve(process.cwd(),o),u=path.resolve(process.cwd(),n),b=path.resolve(process.cwd(),c),v=path.resolve(f,"__generated__"),S=path.resolve(v,"routes.ts"),g=[],D=[],y=(l,j)=>{if(fs.mkdirSync(path.dirname(l),{recursive:!0}),fs.existsSync(l)&&!r){D.push(l);return}fs.writeFileSync(l,j,"utf8"),g.push(l);},k=a.map(l=>{let j=d??`/${l}`;return ` ${l}: {
177
- basePath: "${j}",
175
+ `;N(k,v);}for(let v of z)console.log(`[frs] wrote ${v}`);for(let v of G)console.log(`[frs] skipped ${v} (use --force to overwrite)`);console.log(`
176
+ [frs] reminder: run "frs gen --root ${n}" to refresh the manifest.`);}finally{r.close();}}async function we(e){let t=e.yes===true,s=te(t);try{let r=e.force===!0,o=m(e.root);o||(o=(await s.ask("Domain root","src/domains")).trim()||"src/domains");let i=m(e["apis-file"]);i||(i=(await s.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let c=m(e["services-file"]);if(!c){let l=i.replace(/apis\.ts$/,"services.ts")||"src/services.ts";c=(await s.ask("services.ts location",l)).trim()||l;}let n=m(e.apis);n||(n=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let a=n.split(",").map(l=>l.trim()).filter(Boolean);a.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let d=m(e["base-path"]),f=path.resolve(process.cwd(),o),p=path.resolve(process.cwd(),i),P=path.resolve(process.cwd(),c),y=path.resolve(f,"__generated__"),S=path.resolve(y,"routes.ts"),g=[],A=[],$=(l,_)=>{if(fs.mkdirSync(path.dirname(l),{recursive:!0}),fs.existsSync(l)&&!r){A.push(l);return}fs.writeFileSync(l,_,"utf8"),g.push(l);},R=a.map(l=>{let _=d??`/${l}`;return ` ${l}: {
177
+ basePath: "${_}",
178
178
  openapi: {
179
179
  info: { title: "${l.toUpperCase()} API", version: "1.0.0", description: "" },
180
180
  },
181
+ // Built-in error mapping. Extend BaseErrorHandler to map your own
182
+ // domain errors, then swap it in here (per API).
183
+ errorHandler: new BaseErrorHandler(),
184
+ // Structured console logger. Extend BaseLogger (override \`write\`) to
185
+ // route to your sink, then swap it in here (per API).
186
+ logger: new BaseLogger(),
181
187
  verbose: process.env["NODE_ENV"] !== "production",
182
188
  },`}).join(`
183
- `),F=`import { createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
184
- import { services } from "${E(path.dirname(u),b)}";
189
+ `),k=`import { BaseErrorHandler, BaseLogger, createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
190
+ import { services } from "${I(path.dirname(p),P)}";
185
191
 
186
192
  /**
187
193
  * Single source of truth for every API exposed by this project.
188
194
  * Add per-API middlewares, interceptors, OpenAPI metadata here.
189
195
  *
190
- * The shared \`services\` container is injected into every HonoServer the
191
- * registry builds \u2014 handlers / interceptors receive it via \`{ services }\`
192
- * and the built-in \`services.ctx.c\` resolves to the current request.
196
+ * Per-API resources injected into every handler / interceptor / error-handler
197
+ * context (override them per API above):
198
+ * - \`services\` \u2014 shared DI container (\`services.ctx.c\` = current request);
199
+ * - \`errorHandler\` \u2014 maps thrown errors \u2192 HTTP (extend \`BaseErrorHandler\`);
200
+ * - \`logger\` \u2014 structured logging (extend \`BaseLogger\`).
193
201
  */
194
202
  export const apis = createApiRegistry(
195
203
  {
196
- ${k}
204
+ ${R}
197
205
  },
198
206
  { services },
199
207
  );
@@ -201,7 +209,7 @@ ${k}
201
209
  /** Typed helpers used inside every route file. */
202
210
  export const defineRoute = apis.defineRoute;
203
211
  export const useCaseRoute = apis.useCaseRoute;
204
- `;y(u,F),y(b,`import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
212
+ `;$(p,k),$(P,`import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
205
213
 
206
214
  /**
207
215
  * Global DI container \u2014 declare every singleton (repositories, SDK
@@ -234,22 +242,22 @@ export const services = createServices({
234
242
 
235
243
  /** Convenience type \u2014 \`function fn(svc: Services) { ... }\`. */
236
244
  export type Services = typeof services;
237
- `);let P=`// AUTO-GENERATED by frs \u2014 do not edit.
245
+ `);let w=`// AUTO-GENERATED by frs \u2014 do not edit.
238
246
  // Run \`frs gen --root ${o}\` to refresh.
239
247
 
240
248
  import type { AnyRouteDef } from "@lpdjs/firestore-repo-service/servers/hono";
241
249
 
242
250
  export const routes: AnyRouteDef[] = [];
243
- `;y(S,P);let _=ge({root:o,apisFile:n,servicesFile:c,apis:a});g.push(_);let h=E(path.dirname(u),u),C=E(path.dirname(u),S),N=a.length===1?`export const { ${a[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${a.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let l of g)console.log(`[frs] wrote ${l}`);for(let l of D)console.log(`[frs] skipped ${l} (use --force to overwrite)`);console.log(`
251
+ `;$(S,w);let D=he({root:o,apisFile:i,servicesFile:c,apis:a});g.push(D);let h=I(path.dirname(p),p),b=I(path.dirname(p),S),L=a.length===1?`export const { ${a[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${a.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let l of g)console.log(`[frs] wrote ${l}`);for(let l of A)console.log(`[frs] skipped ${l} (use --force to overwrite)`);console.log(`
244
252
  Next steps:
245
253
 
246
254
  1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):
247
255
 
248
256
  import { onRequest } from "firebase-functions/v2/https";
249
257
  import { apis } from "${h}";
250
- import { routes } from "${C}";
258
+ import { routes } from "${b}";
251
259
 
252
- ${N}
260
+ ${L}
253
261
  defaults: { region: "us-central1", invoker: "public" },
254
262
  });
255
263
 
@@ -260,10 +268,10 @@ Next steps:
260
268
  3. Refresh the manifest before each build:
261
269
 
262
270
  frs gen --root ${o}
263
- `);}finally{s.close();}}function E(e,t){let s=path.relative(e,t).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function we(e,t,s){e!=="service"&&(console.error(`[frs] unknown "add" target: ${e??"(missing)"} \u2014 supported: service`),process.exit(2)),t||(console.error("[frs] service name is required: frs add service <name>"),process.exit(2));let r=s.force===true,o=T(),c=[m(s["services-file"]),o.servicesFile,"src/services.ts","services.ts"].filter(h=>typeof h=="string"&&h.length>0),i;for(let h of c){let C=path.resolve(process.cwd(),h);if(fs.existsSync(C)){i=C;break}}if(!i){let h=c.map(C=>path.resolve(process.cwd(),C)).join(`
271
+ `);}finally{s.close();}}function I(e,t){let s=path.relative(e,t).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function xe(e,t,s){e!=="service"&&(console.error(`[frs] unknown "add" target: ${e??"(missing)"} \u2014 supported: service`),process.exit(2)),t||(console.error("[frs] service name is required: frs add service <name>"),process.exit(2));let r=s.force===true,o=T(),c=[m(s["services-file"]),o.servicesFile,"src/services.ts","services.ts"].filter(h=>typeof h=="string"&&h.length>0),n;for(let h of c){let b=path.resolve(process.cwd(),h);if(fs.existsSync(b)){n=b;break}}if(!n){let h=c.map(b=>path.resolve(process.cwd(),b)).join(`
264
272
  `);console.error(`[frs] services file not found. Tried:
265
273
  ${h}
266
- Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let a=m(s["services-dir"])??o.servicesDir??path.resolve(path.dirname(i),"services"),d=path.resolve(process.cwd(),a);fs.mkdirSync(d,{recursive:true});let f=`${t.charAt(0).toUpperCase()}${t.slice(1)}Service`,u=path.resolve(d,`${t}.ts`),b=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
274
+ Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let a=m(s["services-dir"])??o.servicesDir??path.resolve(path.dirname(n),"services"),d=path.resolve(process.cwd(),a);fs.mkdirSync(d,{recursive:true});let f=`${t.charAt(0).toUpperCase()}${t.slice(1)}Service`,p=path.resolve(d,`${t}.ts`),P=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
267
275
 
268
276
  /**
269
277
  * ${f} \u2014 generated by \`frs add service ${t}\`.
@@ -292,9 +300,9 @@ export class ${f} {
292
300
  return \`hello from ${t} \u2014 user=\${this.ctx.maybeC?.get("user")?.id ?? "anonymous"}\`;
293
301
  }
294
302
  }
295
- `;fs.existsSync(u)&&!r?console.log(`[frs] skipped ${u} (use --force to overwrite)`):(fs.writeFileSync(u,b,"utf8"),console.log(`[frs] wrote ${u}`));let v=fs.readFileSync(i,"utf8"),S=E(path.dirname(i),u),g=`import { ${f} } from "${S}";`,D=` ${t}: ({ ctx }) => new ${f}(ctx),`;if(v.includes(g)){console.log(`[frs] services.ts already registers "${t}" \u2014 skipping.`);return}let y=v.split(`
296
- `),k=-1;for(let h=0;h<y.length;h++)/^import\s/.test(y[h])&&(k=h);k>=0?y.splice(k+1,0,g):y.unshift(g);let F=y.join(`
297
- `),w=F.match(/createServices\s*\(\s*\{/);if(!w){console.error(`[frs] could not find \`createServices({\` in ${i} \u2014 register "${t}" manually.`);return}let P=w.index+w[0].length,_=F.slice(0,P)+`
298
- `+D+F.slice(P);fs.writeFileSync(i,_,"utf8"),console.log(`[frs] updated ${i} (+ ${t})`);}function xe(e,t){let s=["apis.ts","apis.js","api.ts","api.js"],r=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of r)for(let n of s){let c=path.resolve(o,n);if(fs.existsSync(c)){let i=path.relative(t,c).replace(/\\/g,"/");return i=i.replace(/\.ts$/,".js").replace(/\.js$/,".js"),i.startsWith(".")||(i=`./${i}`),i}}return "../../../../apis.js"}function be(e,t){let s=["services.ts","services.js"],r=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of r)for(let n of s){let c=path.resolve(o,n);if(fs.existsSync(c)){let i=path.relative(t,c).replace(/\\/g,"/");return i=i.replace(/\.ts$/,".js"),i.startsWith(".")||(i=`./${i}`),i}}return "../../../../services.js"}async function Se(){let e=process.argv.slice(2),{command:t,flags:s}=he(e);switch(t){case "init":await $e(s);return;case "gen":await ve(s);return;case "new":await ye(e[1],s);return;case "add":await we(e[1],e[2],s);return;case "help":case "--help":case "-h":Y();return;default:console.error(`[frs] unknown command: ${t}
299
- `),Y(),process.exit(2);}}Se().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
303
+ `;fs.existsSync(p)&&!r?console.log(`[frs] skipped ${p} (use --force to overwrite)`):(fs.writeFileSync(p,P,"utf8"),console.log(`[frs] wrote ${p}`));let y=fs.readFileSync(n,"utf8"),S=I(path.dirname(n),p),g=`import { ${f} } from "${S}";`,A=` ${t}: ({ ctx }) => new ${f}(ctx),`;if(y.includes(g)){console.log(`[frs] services.ts already registers "${t}" \u2014 skipping.`);return}let $=y.split(`
304
+ `),R=-1;for(let h=0;h<$.length;h++)/^import\s/.test($[h])&&(R=h);R>=0?$.splice(R+1,0,g):$.unshift(g);let k=$.join(`
305
+ `),F=k.match(/createServices\s*\(\s*\{/);if(!F){console.error(`[frs] could not find \`createServices({\` in ${n} \u2014 register "${t}" manually.`);return}let w=F.index+F[0].length,D=k.slice(0,w)+`
306
+ `+A+k.slice(w);fs.writeFileSync(n,D,"utf8"),console.log(`[frs] updated ${n} (+ ${t})`);}function Pe(e,t){let s=["apis.ts","apis.js","api.ts","api.js"],r=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of r)for(let i of s){let c=path.resolve(o,i);if(fs.existsSync(c)){let n=path.relative(t,c).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js").replace(/\.js$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../apis.js"}function Se(e,t){let s=["services.ts","services.js"],r=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of r)for(let i of s){let c=path.resolve(o,i);if(fs.existsSync(c)){let n=path.relative(t,c).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../services.js"}async function be(){let e=process.argv.slice(2),{command:t,flags:s}=ve(e);switch(t){case "init":await we(s);return;case "gen":await ye(s);return;case "new":await $e(e[1],s);return;case "add":await xe(e[1],e[2],s);return;case "help":case "--help":case "-h":Z();return;default:console.error(`[frs] unknown command: ${t}
307
+ `),Z(),process.exit(2);}}be().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
300
308
  //# sourceMappingURL=cli.cjs.map