@lpdjs/firestore-repo-service 2.4.3 → 2.6.2-beta.0

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.
Files changed (76) hide show
  1. package/README.md +92 -50
  2. package/dist/{create-servers-B9dTUhvR.d.cts → create-servers-B4GrBqdA.d.cts} +6 -6
  3. package/dist/{create-servers-BFhdPPeo.d.ts → create-servers-CVudVM8e.d.ts} +6 -6
  4. package/dist/{firebase-auth-D1APf9PA.d.cts → firebase-auth-Dpvrd8MP.d.cts} +13 -0
  5. package/dist/{firebase-auth-D1APf9PA.d.ts → firebase-auth-Dpvrd8MP.d.ts} +13 -0
  6. package/dist/history/index.cjs +1 -1
  7. package/dist/history/index.cjs.map +1 -1
  8. package/dist/history/index.d.cts +10 -4
  9. package/dist/history/index.d.ts +10 -4
  10. package/dist/history/index.js +1 -1
  11. package/dist/history/index.js.map +1 -1
  12. package/dist/{index-BxurOEz1.d.ts → index-DzO9MfNI.d.cts} +9 -2
  13. package/dist/{index-BmagC7uw.d.cts → index-oFhGCBrY.d.ts} +9 -2
  14. package/dist/index.cjs +84 -84
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +9 -9
  17. package/dist/index.d.ts +9 -9
  18. package/dist/index.js +84 -84
  19. package/dist/index.js.map +1 -1
  20. package/dist/{openapi-ML_1hTx2.d.cts → openapi-B2w5tVRR.d.cts} +1 -1
  21. package/dist/{openapi-DIoQV_yQ.d.ts → openapi-DB8bXZB-.d.ts} +1 -1
  22. package/dist/{queue-xMOZxY0M.d.cts → queue-B8YUTnBT.d.cts} +20 -5
  23. package/dist/{queue-CVchaGAh.d.ts → queue-DYmbVDu5.d.ts} +20 -5
  24. package/dist/{read-BSyLao3I.d.cts → read-CTWZjxyh.d.cts} +1 -1
  25. package/dist/{read-BSyLao3I.d.ts → read-CTWZjxyh.d.ts} +1 -1
  26. package/dist/servers/admin/index.cjs +48 -48
  27. package/dist/servers/admin/index.cjs.map +1 -1
  28. package/dist/servers/admin/index.d.cts +4 -4
  29. package/dist/servers/admin/index.d.ts +4 -4
  30. package/dist/servers/admin/index.js +48 -48
  31. package/dist/servers/admin/index.js.map +1 -1
  32. package/dist/servers/auth/index.cjs +25 -12
  33. package/dist/servers/auth/index.cjs.map +1 -1
  34. package/dist/servers/auth/index.d.cts +1 -1
  35. package/dist/servers/auth/index.d.ts +1 -1
  36. package/dist/servers/auth/index.js +25 -12
  37. package/dist/servers/auth/index.js.map +1 -1
  38. package/dist/servers/crud/index.cjs +2 -2
  39. package/dist/servers/crud/index.cjs.map +1 -1
  40. package/dist/servers/crud/index.d.cts +6 -6
  41. package/dist/servers/crud/index.d.ts +6 -6
  42. package/dist/servers/crud/index.js +2 -2
  43. package/dist/servers/crud/index.js.map +1 -1
  44. package/dist/servers/hono/cli.cjs +142 -53
  45. package/dist/servers/hono/cli.cjs.map +1 -1
  46. package/dist/servers/hono/cli.js +142 -53
  47. package/dist/servers/hono/cli.js.map +1 -1
  48. package/dist/servers/hono/index.cjs +5 -5
  49. package/dist/servers/hono/index.cjs.map +1 -1
  50. package/dist/servers/hono/index.d.cts +241 -24
  51. package/dist/servers/hono/index.d.ts +241 -24
  52. package/dist/servers/hono/index.js +5 -5
  53. package/dist/servers/hono/index.js.map +1 -1
  54. package/dist/servers/index.cjs +98 -98
  55. package/dist/servers/index.cjs.map +1 -1
  56. package/dist/servers/index.d.cts +9 -9
  57. package/dist/servers/index.d.ts +9 -9
  58. package/dist/servers/index.js +98 -98
  59. package/dist/servers/index.js.map +1 -1
  60. package/dist/sync/bigquery.cjs +3 -3
  61. package/dist/sync/bigquery.cjs.map +1 -1
  62. package/dist/sync/bigquery.d.cts +18 -2
  63. package/dist/sync/bigquery.d.ts +18 -2
  64. package/dist/sync/bigquery.js +3 -3
  65. package/dist/sync/bigquery.js.map +1 -1
  66. package/dist/sync/index.cjs +37 -37
  67. package/dist/sync/index.cjs.map +1 -1
  68. package/dist/sync/index.d.cts +5 -5
  69. package/dist/sync/index.d.ts +5 -5
  70. package/dist/sync/index.js +37 -37
  71. package/dist/sync/index.js.map +1 -1
  72. package/dist/{types-5vgXdUM2.d.ts → types-BHZ-Gk-s.d.ts} +71 -4
  73. package/dist/{types-ChzVPw4k.d.ts → types-FLGn8CAI.d.ts} +15 -1
  74. package/dist/{types-BtdC0Qhu.d.cts → types-GvexCqrq.d.cts} +71 -4
  75. package/dist/{types-BgIGWlR1.d.cts → types-wcX7xfdo.d.cts} +15 -1
  76. package/package.json +2 -2
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';var path=require('path'),fs=require('fs'),promises=require('readline/promises'),process$1=require('process');var b={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function I(e,s=b){let t=new Set(s.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!t.has(o.toLowerCase())).map(o=>s.casing==="kebab"?Z(o):o).join("/")}function Z(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function T(e,s,t){let r=E(e),o=E(s),i=0;for(;i<r.length&&i<o.length&&r[i]===o[i];)i++;let a=r.length-i,n=o.slice(i),d=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),c=t===""?d:`${d}${t}`;return n[n.length-1]=c,(a===0?"./":"../".repeat(a))+n.join("/")}function E(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((t,r)=>!(r===0&&t===""))}var S={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function N(e,s=S){let t=[];return q(e,e,s,t),t.sort((r,o)=>r.relPath.localeCompare(o.relPath)),t}function q(e,s,t,r){let o;try{o=fs.readdirSync(s);}catch{return}for(let i of o){if(t.excludeSegments.includes(i))continue;let a=path.join(s,i),n;try{n=fs.statSync(a);}catch{continue}if(n.isDirectory())q(e,a,t,r);else if(n.isFile()&&i===t.routesFile){let l=path.relative(e,a).split(path.sep).join("/"),d=l.replace(/\/?[^/]+$/,"");r.push({absPath:a,relPath:l,relDir:d});}}}var ne="/**\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 L(e,s){let t=path.dirname(s.outFile);fs.mkdirSync(t,{recursive:true});let r=s.banner??ne,o=(s.now??new Date).toISOString(),i=s.importExtension,a=[],n=[],l=[];e.forEach((c,h)=>{let g=T(t,c.absPath,i),y=I(c.relDir,s.derive);a.push(`import mod${h} from ${JSON.stringify(g)};`),n.push(` { __derivedPath: ${JSON.stringify(y)}, mod: mod${h} },`),l.push({source:c.relPath,url:y});});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 I={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function L(e,t=I){let s=new Set(t.skipSegments.map(r=>r.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(r=>!s.has(r.toLowerCase())).map(r=>t.casing==="kebab"?X(r):r).join("/")}function X(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function U(e,t,s){let o=q(e),r=q(t),i=0;for(;i<o.length&&i<r.length&&o[i]===r[i];)i++;let c=o.length-i,n=r.slice(i),g=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),p=s===""?g:`${g}${s}`;return n[n.length-1]=p,(c===0?"./":"../".repeat(c))+n.join("/")}function q(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,o)=>!(o===0&&s===""))}var re="/**\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 G(e,t){let s=path.dirname(t.outFile);fs.mkdirSync(s,{recursive:true});let o=t.banner??re,r=(t.now??new Date).toISOString(),i=t.importExtension,c=[],n=[],a=[];e.forEach((p,l)=>{let v=U(s,p.absPath,i),y=L(p.relDir,t.derive);c.push(`import mod${l} from ${JSON.stringify(v)};`),n.push(` { __derivedPath: ${JSON.stringify(y)}, mod: mod${l} },`),a.push({source:p.relPath,url:y});});let g=`${o}// Generated at ${r} \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
 
6
- `+a.join(`
7
- `)+(a.length?`
6
+ `+c.join(`
7
+ `)+(c.length?`
8
8
 
9
9
  `:`
10
10
  `)+`const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [
@@ -16,17 +16,22 @@ 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(s.outFile,d,"utf8"),{outFile:s.outFile,routeCount:e.length,derivedPaths:l}}function ce(e){let[s,...t]=e,r={};for(let o=0;o<t.length;o++){let i=t[o];if(!i.startsWith("--"))continue;let a=i.slice(2),n=t[o+1];n&&!n.startsWith("--")?(r[a]=n,o++):r[a]=true;}return {command:s??"help",flags:r}}function B(){console.log(`frs-hono \u2014 Hono file-based codegen
19
+ `;return fs.writeFileSync(t.outFile,g,"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 B(e,t=E){let s=[];return z(e,e,t,s),s.sort((o,r)=>o.relPath.localeCompare(r.relPath)),s}function z(e,t,s,o){let r;try{r=fs.readdirSync(t);}catch{return}for(let i of r){if(s.excludeSegments.includes(i))continue;let c=path.join(t,i),n;try{n=fs.statSync(c);}catch{continue}if(n.isDirectory())z(e,c,s,o);else if(n.isFile()&&i===s.routesFile){let a=path.relative(e,c).split(path.sep).join("/"),g=a.replace(/\/?[^/]+$/,"");o.push({absPath:c,relPath:a,relDir:g});}}}var V=".frsrc.json";function Y(e=process.cwd()){let t=path.resolve(e,V);if(!fs.existsSync(t))return {};try{return JSON.parse(fs.readFileSync(t,"utf8"))}catch{return {}}}function le(e,t=process.cwd()){let s=path.resolve(t,V),r={...Y(t),...e};return fs.writeFileSync(s,`${JSON.stringify(r,null,2)}
20
+ `,"utf8"),s}function de(e){let[t,...s]=e,o={};for(let r=0;r<s.length;r++){let i=s[r];if(!i.startsWith("--"))continue;let c=i.slice(2),n=s[r+1];n&&!n.startsWith("--")?(o[c]=n,r++):o[c]=true;}return {command:t??"help",flags:o}}function H(){console.log(`frs \u2014 Hono file-based codegen
20
21
 
21
22
  Usage:
22
- frs-hono init [flags]
23
- frs-hono gen [flags]
24
- frs-hono new <name> [flags]
25
- frs-hono help
23
+ frs init [flags]
24
+ frs gen [flags]
25
+ frs new <name> [flags]
26
+ frs add service <name> [flags]
27
+ frs help
26
28
 
27
29
  Flags (init):
28
30
  --root <dir> Domain root to create (default: src/domains)
29
31
  --apis-file <path> Path to the apis.ts file to create (default: src/apis.ts)
32
+ --services-file <path>
33
+ Path to the services.ts file to create
34
+ (default: src/services.ts)
30
35
  --apis <list> Comma-separated API tags to register (default: v1)
31
36
  --base-path <prefix> basePath shared by all APIs (default: derived from tag)
32
37
  --force Overwrite existing files
@@ -61,122 +66,206 @@ Flags (new <name>):
61
66
  --force Overwrite if files already exist
62
67
  --yes Skip prompts, use defaults / flag values
63
68
 
69
+ Flags (add service <name>):
70
+ --services-file <path>
71
+ Path to the services.ts file (default: src/services.ts)
72
+ --services-dir <dir> Directory hosting individual service files
73
+ (default: <dir-of-services-file>/services)
74
+ --force Overwrite existing files
75
+
64
76
  Examples:
65
- frs-hono init
66
- frs-hono new createPost --domain posts --method post
67
- frs-hono new listPosts --domain posts --method get --api v1
68
- `);}function G(e){if(typeof e=="string")return e.split(",").map(s=>s.trim()).filter(Boolean)}function p(e){return typeof e=="string"?e:void 0}function J(e){if(e||!process$1.stdin.isTTY)return {ask:async(t,r)=>r??"",askChoice:async(t,r,o)=>o??"",askBool:async(t,r)=>r,close:()=>{}};let s=promises.createInterface({input:process$1.stdin,output:process$1.stdout});return {async ask(t,r){let o=r?` (${r})`:"";return (await s.question(`? ${t}${o} \u203A `)).trim()||r||""},async askChoice(t,r,o){let i=` [${r.join("/")}${o?`, default: ${o}`:""}]`;for(;;){let a=(await s.question(`? ${t}${i} \u203A `)).trim().toLowerCase();if(!a&&o)return o;if(r.includes(a))return a;console.log(` invalid choice \u2014 pick one of: ${r.join(", ")}`);}},async askBool(t,r){let o=` (${r?"Y/n":"y/N"})`,i=(await s.question(`? ${t}${o} \u203A `)).trim().toLowerCase();return i?i==="y"||i==="yes"||i==="true":r},close:()=>s.close()}}async function ue(e){let s=p(e.root);s||(console.error("[frs-hono] --root is required"),process.exit(2));let t=path.resolve(process.cwd(),s);fs.existsSync(t)||(console.error(`[frs-hono] root not found: ${t}`),process.exit(2));let r=p(e.out)??"__generated__/routes.ts",o=G(e.skip)??b.skipSegments,i=p(e.casing)==="kebab"?"kebab":b.casing,a={skipSegments:o,casing:i},n=p(e.ext)??".js",l=G(e.exclude)??S.excludeSegments,d=p(e["routes-file"])??S.routesFile,h=N(t,{routesFile:d,excludeSegments:l});h.length===0&&console.warn(`[frs-hono] no "${d}" files found under ${t} \u2014 generated an empty manifest.`);let g=L(h,{outFile:path.resolve(t,r),derive:a,importExtension:n});if(!e.silent){console.log(`[frs-hono] wrote ${g.outFile} (${g.routeCount} route${g.routeCount===1?"":"s"})`);for(let{source:y,url:$}of g.derivedPaths)console.log(` ${$.padEnd(48)} \u2190 ${y}`);}}async function pe(e,s){let t=s.yes===true,r=J(t);try{let o=e&&!e.startsWith("--")?e:void 0;o||(o=(await r.ask("Route name (e.g. createPost)")).trim(),o||(console.error("[frs-hono] route name is required"),process.exit(2)));let i=p(s.domain);i||(i=(await r.ask("Domain name (e.g. posts)")).trim(),i||(console.error("[frs-hono] --domain is required"),process.exit(2)));let a=p(s.root)??"src/domains",n=p(s.method)?.toLowerCase();n||(n=await r.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(n)||(console.error(`[frs-hono] invalid --method: ${n}`),process.exit(2));let l=p(s.api);l||(l=(await r.ask("API tag","v1")).trim()||"v1");let d=p(s["usecase-folder"])??"useCases",c=s["with-usecase"]===void 0?t?!0:await r.askBool("Scaffold useCase.ts?",!0):s["with-usecase"]!==!1,h=s["with-test"]===void 0?t||!c?c:await r.askBool("Scaffold useCase.test.ts (Vitest)?",!0):s["with-test"]!==!1,g=s.force===!0,y=path.resolve(process.cwd(),a),$=path.resolve(y,i,d,o),P=path.resolve($,"routes.ts"),A=path.resolve($,"useCase.ts"),k=path.resolve($,"useCase.test.ts");fs.mkdirSync($,{recursive:!0});let f=`${o.charAt(0).toUpperCase()}${o.slice(1)}UseCase`,R=`/**
69
- * ${f} \u2014 pure business logic, no HTTP awareness.
77
+ frs init
78
+ frs new createPost --domain posts --method post
79
+ frs new listPosts --domain posts --method get --api v1
80
+ frs add service postRepo
81
+ `);}function W(e){if(typeof e=="string")return e.split(",").map(t=>t.trim()).filter(Boolean)}function f(e){return typeof e=="string"?e:void 0}function Z(e){if(e||!process$1.stdin.isTTY)return {ask:async(s,o)=>o??"",askChoice:async(s,o,r)=>r??"",askBool:async(s,o)=>o,close:()=>{}};let t=promises.createInterface({input:process$1.stdin,output:process$1.stdout});return {async ask(s,o){let r=o?` (${o})`:"";return (await t.question(`? ${s}${r} \u203A `)).trim()||o||""},async askChoice(s,o,r){let i=` [${o.join("/")}${r?`, default: ${r}`:""}]`;for(;;){let c=(await t.question(`? ${s}${i} \u203A `)).trim().toLowerCase();if(!c&&r)return r;if(o.includes(c))return c;console.log(` invalid choice \u2014 pick one of: ${o.join(", ")}`);}},async askBool(s,o){let r=` (${o?"Y/n":"y/N"})`,i=(await t.question(`? ${s}${r} \u203A `)).trim().toLowerCase();return i?i==="y"||i==="yes"||i==="true":o},close:()=>t.close()}}async function fe(e){let t=f(e.root);t||(console.error("[frs] --root is required"),process.exit(2));let s=path.resolve(process.cwd(),t);fs.existsSync(s)||(console.error(`[frs] root not found: ${s}`),process.exit(2));let o=f(e.out)??"__generated__/routes.ts",r=W(e.skip)??I.skipSegments,i=f(e.casing)==="kebab"?"kebab":I.casing,c={skipSegments:r,casing:i},n=f(e.ext)??".js",a=W(e.exclude)??E.excludeSegments,g=f(e["routes-file"])??E.routesFile,l=B(s,{routesFile:g,excludeSegments:a});l.length===0&&console.warn(`[frs] no "${g}" files found under ${s} \u2014 generated an empty manifest.`);let v=G(l,{outFile:path.resolve(s,o),derive:c,importExtension:n});if(!e.silent){console.log(`[frs] wrote ${v.outFile} (${v.routeCount} route${v.routeCount===1?"":"s"})`);for(let{source:y,url:$}of v.derivedPaths)console.log(` ${$.padEnd(48)} \u2190 ${y}`);}}async function me(e,t){let s=t.yes===true,o=Z(s);try{let r=e&&!e.startsWith("--")?e:void 0;r||(r=(await o.ask("Route name (e.g. createPost)")).trim(),r||(console.error("[frs] route name is required"),process.exit(2)));let i=f(t.domain);i||(i=(await o.ask("Domain name (e.g. posts)")).trim(),i||(console.error("[frs] --domain is required"),process.exit(2)));let c=f(t.root)??"src/domains",n=f(t.method)?.toLowerCase();n||(n=await o.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(n)||(console.error(`[frs] invalid --method: ${n}`),process.exit(2));let a=f(t.api);a||(a=(await o.ask("API tag","v1")).trim()||"v1");let g=f(t["usecase-folder"])??"useCases",p=t["with-usecase"]===void 0?s?!0:await o.askBool("Scaffold useCase.ts?",!0):t["with-usecase"]!==!1,l=t["with-test"]===void 0?s||!p?p:await o.askBool("Scaffold useCase.test.ts (Vitest)?",!0):t["with-test"]!==!1,v=t.force===!0,y=path.resolve(process.cwd(),c),$=path.resolve(y,i,g,r),b=path.resolve($,"routes.ts"),D=path.resolve($,"useCase.ts"),x=path.resolve($,"useCase.test.ts");fs.mkdirSync($,{recursive:!0});let m=`${r.charAt(0).toUpperCase()}${r.slice(1)}UseCase`,C=`/**
82
+ * ${m} \u2014 pure business logic, no HTTP awareness.
70
83
  * Reusable across multiple routes / cron jobs / triggers.
71
84
  */
72
85
 
73
- export interface ${f}Input {
86
+ export interface ${m}Input {
74
87
  // TODO: define the input shape
75
88
  example: string;
76
89
  }
77
90
 
78
- export interface ${f}Output {
91
+ export interface ${m}Output {
79
92
  // TODO: define the output shape
80
93
  id: string;
81
94
  }
82
95
 
83
- export class ${f} {
96
+ export class ${m} {
84
97
  // TODO: inject repositories / services via the constructor.
85
98
  // constructor(private readonly repo: SomeRepository) {}
86
99
 
87
- async execute(input: ${f}Input): Promise<${f}Output> {
100
+ async execute(input: ${m}Input): Promise<${m}Output> {
88
101
  // TODO: implement
89
102
  return { id: input.example };
90
103
  }
91
104
  }
92
- `,C=n==="get"?`z.object({
105
+ `,F=n==="get"?`z.object({
93
106
  // GET \u2192 lu depuis les query params
94
107
  example: z.string(),
95
108
  })`:`z.object({
96
109
  // ${n.toUpperCase()} \u2192 lu depuis le body JSON
97
110
  example: z.string(),
98
- })`,D=c?` const useCase = new ${f}();
111
+ })`,_=p?` const useCase = new ${m}();
99
112
  const data = await useCase.execute(input);
100
113
  return data;`:` // TODO: business logic
101
- return { id: input.example };`,u=c?`import { ${f} } from "./useCase.js";
102
- `:"",V=`import { z } from "zod";
103
- import { defineRoute } from "${p(s["apis-import"])??de(y,$)}";
104
- ${u}
114
+ return { id: input.example };`,A=p?`import { ${m} } from "./useCase.js";
115
+ `:"",S=`import { z } from "zod";
116
+ import { defineRoute } from "${f(t["apis-import"])??ve(y,$)}";
117
+ ${A}
105
118
  export default defineRoute({
106
- api: "${l}",
119
+ api: "${a}",
107
120
  method: "${n}",
108
121
 
109
- input: ${C},
122
+ input: ${F},
110
123
 
111
124
  output: z.object({
112
125
  id: z.string(),
113
126
  }),
114
127
 
115
- summary: "TODO: ${o}",
128
+ summary: "TODO: ${r}",
116
129
  tags: ["${i}"],
117
130
 
118
131
  handler: async ({ input }) => {
119
- ${D}
132
+ ${_}
120
133
  },
121
134
  });
122
- `,j=[],F=[],O=(v,Y)=>{if(fs.existsSync(v)&&!g){F.push(v);return}fs.writeFileSync(v,Y,"utf8"),j.push(v);};if(O(P,V),c&&O(A,R),c&&h){let v=`import { describe, it, expect } from "vitest";
123
- import { ${f} } from "./useCase.js";
135
+ `,j=[],u=[],R=(w,Q)=>{if(fs.existsSync(w)&&!v){u.push(w);return}fs.writeFileSync(w,Q,"utf8"),j.push(w);};if(R(b,S),p&&R(D,C),p&&l){let w=`import { describe, it, expect } from "vitest";
136
+ import { ${m} } from "./useCase.js";
124
137
 
125
- describe("${f}", () => {
138
+ describe("${m}", () => {
126
139
  it("returns a response shaped like the output schema", async () => {
127
- const useCase = new ${f}();
140
+ const useCase = new ${m}();
128
141
  const result = await useCase.execute({ example: "hello" });
129
142
  expect(result).toMatchObject({ id: expect.any(String) });
130
143
  });
131
144
 
132
145
  // TODO: add error-path tests, repository mocks, etc.
133
146
  });
134
- `;O(k,v);}for(let v of j)console.log(`[frs-hono] wrote ${v}`);for(let v of F)console.log(`[frs-hono] skipped ${v} (use --force to overwrite)`);console.log(`
135
- [frs-hono] reminder: run "frs-hono gen --root ${a}" to refresh the manifest.`);}finally{r.close();}}async function le(e){let s=e.yes===true,t=J(s);try{let r=e.force===!0,o=p(e.root);o||(o=(await t.ask("Domain root","src/domains")).trim()||"src/domains");let i=p(e["apis-file"]);i||(i=(await t.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let a=p(e.apis);a||(a=(await t.ask("API tags (comma-separated)","v1")).trim()||"v1");let n=a.split(",").map(u=>u.trim()).filter(Boolean);n.length===0&&(console.error("[frs-hono] at least one API tag is required"),process.exit(2));let l=p(e["base-path"]),d=path.resolve(process.cwd(),o),c=path.resolve(process.cwd(),i),h=path.resolve(d,"__generated__"),g=path.resolve(h,"routes.ts"),y=[],$=[],P=(u,x)=>{if(fs.mkdirSync(path.dirname(u),{recursive:!0}),fs.existsSync(u)&&!r){$.push(u);return}fs.writeFileSync(u,x,"utf8"),y.push(u);},k=`import { createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
147
+ `;R(x,w);}for(let w of j)console.log(`[frs] wrote ${w}`);for(let w of u)console.log(`[frs] skipped ${w} (use --force to overwrite)`);console.log(`
148
+ [frs] reminder: run "frs gen --root ${c}" to refresh the manifest.`);}finally{o.close();}}async function ge(e){let t=e.yes===true,s=Z(t);try{let o=e.force===!0,r=f(e.root);r||(r=(await s.ask("Domain root","src/domains")).trim()||"src/domains");let i=f(e["apis-file"]);i||(i=(await s.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let c=f(e["services-file"]);if(!c){let u=i.replace(/apis\.ts$/,"services.ts")||"src/services.ts";c=(await s.ask("services.ts location",u)).trim()||u;}let n=f(e.apis);n||(n=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let a=n.split(",").map(u=>u.trim()).filter(Boolean);a.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let g=f(e["base-path"]),p=path.resolve(process.cwd(),r),l=path.resolve(process.cwd(),i),v=path.resolve(process.cwd(),c),y=path.resolve(p,"__generated__"),$=path.resolve(y,"routes.ts"),b=[],D=[],x=(u,R)=>{if(fs.mkdirSync(path.dirname(u),{recursive:!0}),fs.existsSync(u)&&!o){D.push(u);return}fs.writeFileSync(u,R,"utf8"),b.push(u);},m=a.map(u=>{let R=g??`/${u}`;return ` ${u}: {
149
+ basePath: "${R}",
150
+ openapi: {
151
+ info: { title: "${u.toUpperCase()} API", version: "1.0.0", description: "" },
152
+ },
153
+ verbose: process.env["NODE_ENV"] !== "production",
154
+ },`}).join(`
155
+ `),C=`import { createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
156
+ import { services } from "${T(path.dirname(l),v)}";
136
157
 
137
158
  /**
138
159
  * Single source of truth for every API exposed by this project.
139
160
  * Add per-API middlewares, interceptors, OpenAPI metadata here.
161
+ *
162
+ * The shared \`services\` container is injected into every HonoServer the
163
+ * registry builds \u2014 handlers / interceptors receive it via \`{ services }\`
164
+ * and the built-in \`services.ctx.c\` resolves to the current request.
140
165
  */
141
- export const apis = createApiRegistry({
142
- ${n.map(u=>{let x=l??`/${u}`;return ` ${u}: {
143
- basePath: "${x}",
144
- openapi: {
145
- info: { title: "${u.toUpperCase()} API", version: "1.0.0", description: "" },
146
- },
147
- verbose: process.env["NODE_ENV"] !== "production",
148
- },`}).join(`
149
- `)}
150
- });
166
+ export const apis = createApiRegistry(
167
+ {
168
+ ${m}
169
+ },
170
+ { services },
171
+ );
151
172
 
152
173
  /** Typed helper used inside every route file. */
153
174
  export const defineRoute = apis.defineRoute;
154
- `;P(c,k);let f=`// AUTO-GENERATED by frs-hono \u2014 do not edit.
155
- // Run \`frs-hono gen --root ${o}\` to refresh.
175
+ `;x(l,C),x(v,`import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
176
+
177
+ /**
178
+ * Global DI container \u2014 declare every singleton (repositories, SDK
179
+ * clients, loggers, useCases) here. Each factory is invoked once on first
180
+ * access and the instance is cached for the process lifetime.
181
+ *
182
+ * Factories receive a typed proxy of every other service plus the
183
+ * built-in \`ctx\` (current request \`Context\` via AsyncLocalStorage).
184
+ * Destructure what you need \u2014 TypeScript will infer everything.
185
+ *
186
+ * NOTE: prefer **factory form with destructured deps** for anything that
187
+ * needs to reference its dependencies inside its own class \u2014 typing a
188
+ * field as the full \`Services\` would create a circular type alias.
189
+ * Classes can be passed directly only when they don't reference
190
+ * \`Services\` themselves (e.g. plain SDK wrappers).
191
+ *
192
+ * @example
193
+ * \`\`\`ts
194
+ * postRepo: ({ ctx }) => new PostRepo(ctx),
195
+ * createPostUseCase: ({ ctx, postRepo }) =>
196
+ * new CreatePostUseCase(ctx, postRepo),
197
+ * \`\`\`
198
+ */
199
+ export const services = createServices({
200
+ // TODO: declare your services here.
201
+ // Example:
202
+ // db: () => getFirestore(),
203
+ // postRepo: ({ ctx, db }) => new PostRepo(ctx, db),
204
+ });
205
+
206
+ /** Convenience type \u2014 \`function fn(svc: Services) { ... }\`. */
207
+ export type Services = typeof services;
208
+ `);let _=`// AUTO-GENERATED by frs \u2014 do not edit.
209
+ // Run \`frs gen --root ${r}\` to refresh.
156
210
 
157
211
  import type { AnyRouteDef } from "@lpdjs/firestore-repo-service/servers/hono";
158
212
 
159
213
  export const routes: AnyRouteDef[] = [];
160
- `;P(g,f);let R=z(path.dirname(c),c),C=z(path.dirname(c),g),D=n.length===1?`export const { ${n[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${n.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let u of y)console.log(`[frs-hono] wrote ${u}`);for(let u of $)console.log(`[frs-hono] skipped ${u} (use --force to overwrite)`);console.log(`
214
+ `;x($,_);let A=le({root:r,apisFile:i,servicesFile:c});b.push(A);let h=T(path.dirname(l),l),S=T(path.dirname(l),$),j=a.length===1?`export const { ${a[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${a.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let u of b)console.log(`[frs] wrote ${u}`);for(let u of D)console.log(`[frs] skipped ${u} (use --force to overwrite)`);console.log(`
161
215
  Next steps:
162
216
 
163
217
  1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):
164
218
 
165
219
  import { onRequest } from "firebase-functions/v2/https";
166
- import { apis } from "${R}";
167
- import { routes } from "${C}";
220
+ import { apis } from "${h}";
221
+ import { routes } from "${S}";
168
222
 
169
- ${D}
223
+ ${j}
170
224
  defaults: { region: "us-central1", invoker: "public" },
171
225
  });
172
226
 
173
227
  2. Scaffold a first route:
174
228
 
175
- frs-hono new createPost --domain posts --method post --api ${n[0]}
229
+ frs new createPost --domain posts --method post --api ${a[0]}
176
230
 
177
231
  3. Refresh the manifest before each build:
178
232
 
179
- frs-hono gen --root ${o}
180
- `);}finally{t.close();}}function z(e,s){let t=path.relative(e,s).replace(/\\/g,"/");return t=t.replace(/\.ts$/,".js"),t.startsWith(".")||(t=`./${t}`),t}function de(e,s){let t=["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 t){let a=path.resolve(o,i);if(fs.existsSync(a)){let n=path.relative(s,a).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js").replace(/\.js$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../apis.js"}async function fe(){let e=process.argv.slice(2),{command:s,flags:t}=ce(e);switch(s){case "init":await le(t);return;case "gen":await ue(t);return;case "new":await pe(e[1],t);return;case "help":case "--help":case "-h":B();return;default:console.error(`[frs-hono] unknown command: ${s}
181
- `),B(),process.exit(2);}}fe().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
233
+ frs gen --root ${r}
234
+ `);}finally{s.close();}}function T(e,t){let s=path.relative(e,t).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function he(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 o=s.force===true,r=Y(),c=[f(s["services-file"]),r.servicesFile,"src/services.ts","services.ts"].filter(h=>typeof h=="string"&&h.length>0),n;for(let h of c){let S=path.resolve(process.cwd(),h);if(fs.existsSync(S)){n=S;break}}if(!n){let h=c.map(S=>path.resolve(process.cwd(),S)).join(`
235
+ `);console.error(`[frs] services file not found. Tried:
236
+ ${h}
237
+ Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let a=f(s["services-dir"])??r.servicesDir??path.resolve(path.dirname(n),"services"),g=path.resolve(process.cwd(),a);fs.mkdirSync(g,{recursive:true});let p=`${t.charAt(0).toUpperCase()}${t.slice(1)}Service`,l=path.resolve(g,`${t}.ts`),v=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
238
+
239
+ /**
240
+ * ${p} \u2014 generated by \`frs add service ${t}\`.
241
+ *
242
+ * Registered with a **factory** in \`services.ts\` so dependencies are
243
+ * destructured at registration time. Add new constructor parameters here
244
+ * and update the factory line (\`({ ctx, otherSvc }) => new ${p}(ctx, otherSvc)\`)
245
+ * \u2014 TypeScript will tell you when something is missing.
246
+ *
247
+ * Async resources (DB connections, SDK clients) should stay lazy-loaded
248
+ * inside the class to keep cold-starts fast:
249
+ *
250
+ * @example
251
+ * \`\`\`ts
252
+ * private _client: SomeClient | undefined;
253
+ * get client(): SomeClient {
254
+ * return (this._client ??= new SomeClient({...}));
255
+ * }
256
+ * \`\`\`
257
+ */
258
+ export class ${p} {
259
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor
260
+ constructor(private readonly ctx: RequestContext) {}
261
+
262
+ hello(): string {
263
+ return \`hello from ${t} \u2014 user=\${this.ctx.maybeC?.get("user")?.id ?? "anonymous"}\`;
264
+ }
265
+ }
266
+ `;fs.existsSync(l)&&!o?console.log(`[frs] skipped ${l} (use --force to overwrite)`):(fs.writeFileSync(l,v,"utf8"),console.log(`[frs] wrote ${l}`));let y=fs.readFileSync(n,"utf8"),$=T(path.dirname(n),l),b=`import { ${p} } from "${$}";`,D=` ${t}: ({ ctx }) => new ${p}(ctx),`;if(y.includes(b)){console.log(`[frs] services.ts already registers "${t}" \u2014 skipping.`);return}let x=y.split(`
267
+ `),m=-1;for(let h=0;h<x.length;h++)/^import\s/.test(x[h])&&(m=h);m>=0?x.splice(m+1,0,b):x.unshift(b);let C=x.join(`
268
+ `),F=C.match(/createServices\s*\(\s*\{/);if(!F){console.error(`[frs] could not find \`createServices({\` in ${n} \u2014 register "${t}" manually.`);return}let _=F.index+F[0].length,A=C.slice(0,_)+`
269
+ `+D+C.slice(_);fs.writeFileSync(n,A,"utf8"),console.log(`[frs] updated ${n} (+ ${t})`);}function ve(e,t){let s=["apis.ts","apis.js","api.ts","api.js"],o=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let r of o)for(let i of s){let c=path.resolve(r,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"}async function ye(){let e=process.argv.slice(2),{command:t,flags:s}=de(e);switch(t){case "init":await ge(s);return;case "gen":await fe(s);return;case "new":await me(e[1],s);return;case "add":await he(e[1],e[2],s);return;case "help":case "--help":case "-h":H();return;default:console.error(`[frs] unknown command: ${t}
270
+ `),H(),process.exit(2);}}ye().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
182
271
  //# sourceMappingURL=cli.cjs.map