@lpdjs/firestore-repo-service 2.6.11 → 2.6.12

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.
@@ -76,9 +76,9 @@ declare function createServers<TRepos extends Record<string, ConfiguredRepositor
76
76
  * Returns a Cloud Function when `onRequest` was passed to `createServers`,
77
77
  * otherwise the raw HTTP handler.
78
78
  */
79
- admin(options: BoundAdminServerOptions<TRepos>): firebase_functions_v2_https.HttpsFunction | (((req: any, res: any) => Promise<void>) & {
79
+ admin(options: BoundAdminServerOptions<TRepos>): (((req: any, res: any) => Promise<void>) & {
80
80
  httpsOptions?: HttpsOptions;
81
- });
81
+ }) | firebase_functions_v2_https.HttpsFunction;
82
82
  /**
83
83
  * Build the CRUD REST API handler with `repo` auto-injected from each
84
84
  * registry key. Returns a Cloud Function when `onRequest` was passed to
@@ -76,9 +76,9 @@ declare function createServers<TRepos extends Record<string, ConfiguredRepositor
76
76
  * Returns a Cloud Function when `onRequest` was passed to `createServers`,
77
77
  * otherwise the raw HTTP handler.
78
78
  */
79
- admin(options: BoundAdminServerOptions<TRepos>): firebase_functions_v2_https.HttpsFunction | (((req: any, res: any) => Promise<void>) & {
79
+ admin(options: BoundAdminServerOptions<TRepos>): (((req: any, res: any) => Promise<void>) & {
80
80
  httpsOptions?: HttpsOptions;
81
- });
81
+ }) | firebase_functions_v2_https.HttpsFunction;
82
82
  /**
83
83
  * Build the CRUD REST API handler with `repo` auto-injected from each
84
84
  * registry key. Returns a Cloud Function when `onRequest` was passed to
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@ import { Query, QuerySnapshot, Firestore } from 'firebase-admin/firestore';
3
3
  import { z } from 'zod';
4
4
  import { h as QueryOptions, P as PaginationOptions, f as RepositoryConfig, i as RelationConfig, C as ConfiguredRepository } from './types-C_alF2Xe.cjs';
5
5
  export { A as ApiResponse, b as CrudRepoConfig, a as CrudServerOptions, E as ExtractDocumentRefSignature, j as ExtractUpdateSignature, g as FieldPath, F as FieldRole, t as GenerateGetMethods, u as GenerateQueryMethods, G as GetOptions, k as GetResult, I as IncludeConfigTyped, L as ListResponseData, o as PaginationResult, v as PaginationWithIncludeOptionsTyped, w as PopulateOptionsTyped, Q as QueryRequestBody, l as RelationalKeys, R as RepoFieldPath, e as RepoRelationKeys, p as SystemBackfillFailure, q as SystemBackfillOptions, r as SystemBackfillResult, U as UserFieldPath, W as WhereClause, m as createPaginationIterator, n as executePaginatedQuery } from './types-C_alF2Xe.cjs';
6
- export { B as BoundAdminRepoConfig, a as BoundAdminServerOptions, b as BoundCrudRepoConfig, d as BoundCrudServerOptions, e as BoundFirestoreSyncConfig, C as CreateServersDeps, c as createServers } from './create-servers-BV-E4Rp-.cjs';
6
+ export { B as BoundAdminRepoConfig, a as BoundAdminServerOptions, b as BoundCrudRepoConfig, d as BoundCrudServerOptions, e as BoundFirestoreSyncConfig, C as CreateServersDeps, c as createServers } from './create-servers-Bq9lnpg6.cjs';
7
7
  export { a as AdminRepoConfig, b as AdminRepoEntry, A as AdminServerOptions, B as BasicAuthConfig } from './index-CXWiqnFs.cjs';
8
8
  export { M as MiniRouter } from './firebase-auth-t1CAR-lp.cjs';
9
9
  import 'firebase-functions/v2/firestore';
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { Query, QuerySnapshot, Firestore } from 'firebase-admin/firestore';
3
3
  import { z } from 'zod';
4
4
  import { h as QueryOptions, P as PaginationOptions, f as RepositoryConfig, i as RelationConfig, C as ConfiguredRepository } from './types-C6I3WtJS.js';
5
5
  export { A as ApiResponse, b as CrudRepoConfig, a as CrudServerOptions, E as ExtractDocumentRefSignature, j as ExtractUpdateSignature, g as FieldPath, F as FieldRole, t as GenerateGetMethods, u as GenerateQueryMethods, G as GetOptions, k as GetResult, I as IncludeConfigTyped, L as ListResponseData, o as PaginationResult, v as PaginationWithIncludeOptionsTyped, w as PopulateOptionsTyped, Q as QueryRequestBody, l as RelationalKeys, R as RepoFieldPath, e as RepoRelationKeys, p as SystemBackfillFailure, q as SystemBackfillOptions, r as SystemBackfillResult, U as UserFieldPath, W as WhereClause, m as createPaginationIterator, n as executePaginatedQuery } from './types-C6I3WtJS.js';
6
- export { B as BoundAdminRepoConfig, a as BoundAdminServerOptions, b as BoundCrudRepoConfig, d as BoundCrudServerOptions, e as BoundFirestoreSyncConfig, C as CreateServersDeps, c as createServers } from './create-servers-BhUavex0.js';
6
+ export { B as BoundAdminRepoConfig, a as BoundAdminServerOptions, b as BoundCrudRepoConfig, d as BoundCrudServerOptions, e as BoundFirestoreSyncConfig, C as CreateServersDeps, c as createServers } from './create-servers-dXpZiSOT.js';
7
7
  export { a as AdminRepoConfig, b as AdminRepoEntry, A as AdminServerOptions, B as BasicAuthConfig } from './index-4gzZw5m_.js';
8
8
  export { M as MiniRouter } from './firebase-auth-t1CAR-lp.js';
9
9
  import 'firebase-functions/v2/firestore';
@@ -1,10 +1,10 @@
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"?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"}.
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 J(e,r=I){let s=new Set(r.skipSegments.map(o=>o.toLowerCase()));return "/"+e.split("/").filter(Boolean).filter(o=>!s.has(o.toLowerCase())).map(o=>r.casing==="kebab"?ne(o):o).join("/")}function ne(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function Z(e,r,s){let t=W(e),o=W(r),i=0;for(;i<t.length&&i<o.length&&t[i]===o[i];)i++;let a=t.length-i,n=o.slice(i),f=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),g=s===""?f:`${f}${s}`;return n[n.length-1]=g,(a===0?"./":"../".repeat(a))+n.join("/")}function W(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,t)=>!(t===0&&s===""))}var pe="/**\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 K(e,r){let s=path.dirname(r.outFile);fs.mkdirSync(s,{recursive:true});let t=r.banner??pe,o=(r.now??new Date).toISOString(),i=r.importExtension,a=[],n=[],c=[];e.forEach((g,p)=>{let b=Z(s,g.absPath,i),h=J(g.relDir,r.derive);a.push(`import mod${p} from ${JSON.stringify(b)};`),n.push(` { __derivedPath: ${JSON.stringify(h)}, mod: mod${p} },`),c.push({source:g.relPath,url:h});});let f=`${t}// 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
 
6
- `+c.join(`
7
- `)+(c.length?`
6
+ `+a.join(`
7
+ `)+(a.length?`
8
8
 
9
9
  `:`
10
10
  `)+`const __defs: { __derivedPath: string; mod: RouteModuleDefault }[] = [
@@ -16,8 +16,8 @@ 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 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
19
+ `;return fs.writeFileSync(r.outFile,f,"utf8"),{outFile:r.outFile,routeCount:e.length,derivedPaths:c}}var L={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function V(e,r=L){let s=[];return Y(e,e,r,s),s.sort((t,o)=>t.relPath.localeCompare(o.relPath)),s}function Y(e,r,s,t){let o;try{o=fs.readdirSync(r);}catch{return}for(let i of o){if(s.excludeSegments.includes(i))continue;let a=path.join(r,i),n;try{n=fs.statSync(a);}catch{continue}if(n.isDirectory())Y(e,a,s,t);else if(n.isFile()&&i===s.routesFile){let c=path.relative(e,a).split(path.sep).join("/"),f=c.replace(/\/?[^/]+$/,"");t.push({absPath:a,relPath:c,relDir:f});}}}var se=".frsrc.json";function U(e=process.cwd()){let r=path.resolve(e,se);if(!fs.existsSync(r))return {};try{return JSON.parse(fs.readFileSync(r,"utf8"))}catch{return {}}}function ve(e,r=process.cwd()){let s=path.resolve(r,se),o={...U(r),...e};return fs.writeFileSync(s,`${JSON.stringify(o,null,2)}
20
+ `,"utf8"),s}function ye(e){let[r,...s]=e,t={};for(let o=0;o<s.length;o++){let i=s[o];if(!i.startsWith("--"))continue;let a=i.slice(2),n=s[o+1];n&&!n.startsWith("--")?(t[a]=n,o++):t[a]=true;}return {command:r??"help",flags:t}}function X(){console.log(`frs \u2014 Hono file-based codegen
21
21
 
22
22
  Usage:
23
23
  frs init [flags]
@@ -83,21 +83,20 @@ 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 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
- * ${w} \u2014 pure business logic, no HTTP awareness.
86
+ `);}function ee(e){if(typeof e=="string")return e.split(",").map(r=>r.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,t)=>t??"",askChoice:async(s,t,o)=>o??"",askBool:async(s,t)=>t,close:()=>{}};let r=promises.createInterface({input:process$1.stdin,output:process$1.stdout});return {async ask(s,t){let o=t?` (${t})`:"";return (await r.question(`? ${s}${o} \u203A `)).trim()||t||""},async askChoice(s,t,o){let i=` [${t.join("/")}${o?`, default: ${o}`:""}]`;for(;;){let a=(await r.question(`? ${s}${i} \u203A `)).trim().toLowerCase();if(!a&&o)return o;if(t.includes(a))return a;console.log(` invalid choice \u2014 pick one of: ${t.join(", ")}`);}},async askBool(s,t){let o=` (${t?"Y/n":"y/N"})`,i=(await r.question(`? ${s}${o} \u203A `)).trim().toLowerCase();return i?i==="y"||i==="yes"||i==="true":t},close:()=>r.close()}}async function xe(e){let r=U(),s=m(e.root)??r.root;s||(console.error("[frs] --root is required (or run `frs init` to write it to .frsrc.json)"),process.exit(2));let t=path.resolve(process.cwd(),s);fs.existsSync(t)||(console.error(`[frs] root not found: ${t}`),process.exit(2));let o=m(e.out)??r.out??"__generated__/routes.ts",i=ee(e.skip)??I.skipSegments,a=m(e.casing)==="kebab"?"kebab":I.casing,n={skipSegments:i,casing:a},c=m(e.ext)??".js",f=ee(e.exclude)??L.excludeSegments,g=m(e["routes-file"])??L.routesFile,b=V(t,{routesFile:g,excludeSegments:f});b.length===0&&console.warn(`[frs] no "${g}" files found under ${t} \u2014 generated an empty manifest.`);let h=K(b,{outFile:path.resolve(t,o),derive:n,importExtension:c});if(!e.silent){console.log(`[frs] wrote ${h.outFile} (${h.routeCount} route${h.routeCount===1?"":"s"})`);for(let{source:P,url:y}of h.derivedPaths)console.log(` ${y.padEnd(48)} \u2190 ${P}`);}}async function we(e,r){let s=r.yes===true,t=te(s),o=U();try{let i=e&&!e.startsWith("--")?e:void 0;i||(i=(await t.ask("Route name (e.g. createPost)")).trim(),i||(console.error("[frs] route name is required"),process.exit(2)));let a=m(r.domain);a||(a=(await t.ask("Domain name (e.g. posts)")).trim(),a||(console.error("[frs] --domain is required"),process.exit(2)));let n=m(r.root)??o.root??"src/domains",c=m(r.method)?.toLowerCase();c||(c=await t.askChoice("HTTP method",["get","post","put","patch","delete"],"post")),["get","post","put","patch","delete"].includes(c)||(console.error(`[frs] invalid --method: ${c}`),process.exit(2));let f=m(r.api);if(!f){let v=o.apis?.[0]??"v1";f=(await t.ask("API tag",v)).trim()||v;}let g=m(r["usecase-folder"])??o.useCaseFolder??"useCases",p=r["with-usecase"]===void 0?s?!0:await t.askBool("Scaffold useCase.ts?",!0):r["with-usecase"]!==!1,b=r["with-test"]===void 0?s||!p?p:await t.askBool("Scaffold useCase.test.ts (Vitest)?",!0):r["with-test"]!==!1,h=r.force===!0,P=path.resolve(process.cwd(),n),y=path.resolve(P,a,g,i),F=path.resolve(y,"routes.ts"),x=`${a}.${i}.useCase`,A=path.resolve(y,`${x}.ts`),C=path.resolve(y,`${x}.test.ts`);fs.mkdirSync(y,{recursive:!0});let R=v=>v.charAt(0).toUpperCase()+v.slice(1),$=`${R(a)}${R(i)}UseCase`,D="@lpdjs/firestore-repo-service/servers/hono",w=Ce(P,y),E=Se(P,y),_=c==="get"?"// GET \u2192 lu depuis les query params":`// ${c.toUpperCase()} \u2192 lu depuis le body JSON`,q=`/**
87
+ * ${$} \u2014 pure business logic, no HTTP awareness.
88
88
  *
89
89
  * Owns its Zod \`input\` / \`output\` schemas (declared as \`static\` members, the
90
90
  * single source of truth shared with \`routes.ts\`) and runs the logic in
91
- * \`execute\`. The shared \`services\` container is injected by the \`UseCase\` base
92
- * class via the constructor.
91
+ * \`execute\`. Extends \`AppUseCase\`, so \`this.services\`, \`this.logger\` and
92
+ * \`this.error\` are all available.
93
93
  */
94
94
 
95
95
  import { z } from "zod";
96
- import { UseCase } from "${D}";
97
- import type { Services } from "${h}";
96
+ import { AppUseCase } from "${E}";
98
97
 
99
98
  const input = z.object({
100
- ${b}
99
+ ${_}
101
100
  example: z.string(),
102
101
  });
103
102
 
@@ -105,41 +104,45 @@ const output = z.object({
105
104
  id: z.string(),
106
105
  });
107
106
 
108
- export class ${w} extends UseCase<typeof input, typeof output, Services> {
107
+ export class ${$} extends AppUseCase<typeof input, typeof output> {
109
108
  static readonly input = input;
110
109
  static readonly output = output;
111
110
 
112
111
  async execute(
113
112
  payload: z.infer<typeof input>,
114
113
  ): Promise<z.infer<typeof output>> {
114
+ this.logger.info("${$} called", { example: payload.example });
115
+ // Guard example \u2014 mapped to HTTP by the AppErrorHandler:
116
+ // if (!payload.example) throw this.error.badRequest("example is required");
117
+
115
118
  // TODO: implement using \`this.services\`
116
119
  return { id: payload.example };
117
120
  }
118
121
  }
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
- ${_}
122
+ `,N=c==="get"?`
123
+ source: "query",`:"",z=p?`import { ${$} } from "./${x}.js";
124
+ `:"",O=m(r["apis-import"])??Pe(P,y),u=p?`import { defineRoutes } from "${D}";
125
+ import { useCaseRoute } from "${O}";
126
+ ${z}
124
127
  export default defineRoutes([
125
- useCaseRoute(${w}, {
126
- api: "${d}",
127
- method: "${a}",${l}
128
+ useCaseRoute(${$}, {
129
+ api: "${f}",
130
+ method: "${c}",${N}
128
131
  summary: "TODO: ${i}",
129
- tags: ["${c}"],
132
+ tags: ["${a}"],
130
133
  }),
131
134
  ]);
132
135
  `:`import { z } from "zod";
133
136
  import { defineRoutes } from "${D}";
134
- import { defineRoute } from "${U}";
137
+ import { defineRoute } from "${O}";
135
138
 
136
139
  export default defineRoutes([
137
140
  defineRoute({
138
- api: "${d}",
139
- method: "${a}",
141
+ api: "${f}",
142
+ method: "${c}",
140
143
 
141
144
  input: z.object({
142
- ${b}
145
+ ${_}
143
146
  example: z.string(),
144
147
  }),
145
148
 
@@ -148,7 +151,7 @@ export default defineRoutes([
148
151
  }),
149
152
 
150
153
  summary: "TODO: ${i}",
151
- tags: ["${c}"],
154
+ tags: ["${a}"],
152
155
 
153
156
  handler: async ({ input }) => {
154
157
  // TODO: business logic
@@ -156,38 +159,40 @@ export default defineRoutes([
156
159
  },
157
160
  }),
158
161
  ]);
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
+ `,j=[],M=[],B=(v,oe)=>{if(fs.existsSync(v)&&!h){M.push(v);return}fs.writeFileSync(v,oe,"utf8"),j.push(v);};if(B(F,u),p&&B(A,q),p&&b){let v=`import { describe, it, expect } from "vitest";
163
+ import type { Services } from "${w}";
164
+ import { ${$} } from "./${x}.js";
162
165
 
163
- describe("${w}", () => {
166
+ describe("${$}", () => {
164
167
  it("returns a response shaped like the output schema", async () => {
165
168
  // TODO: replace with real mocks for the services the useCase consumes.
166
169
  const services = {} as unknown as Services;
167
170
 
168
- const useCase = new ${w}(services);
171
+ const useCase = new ${$}(services);
169
172
  const result = await useCase.execute({ example: "hello" });
170
173
  expect(result).toMatchObject({ id: expect.any(String) });
171
174
  });
172
175
 
173
176
  // TODO: add error-path tests, repository mocks, etc.
174
177
  });
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
+ `;B(C,v);}for(let v of j)console.log(`[frs] wrote ${v}`);for(let v of M)console.log(`[frs] skipped ${v} (use --force to overwrite)`);console.log(`
179
+ [frs] reminder: run "frs gen --root ${n}" to refresh the manifest.`);}finally{t.close();}}async function $e(e){let r=e.yes===true,s=te(r);try{let t=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 a=m(e["services-file"]);if(!a){let u=i.replace(/apis\.ts$/,"services.ts")||"src/services.ts";a=(await s.ask("services.ts location",u)).trim()||u;}let n=m(e.apis);n||(n=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let c=n.split(",").map(u=>u.trim()).filter(Boolean);c.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let f=m(e["base-path"]),g=path.resolve(process.cwd(),o),p=path.resolve(process.cwd(),i),b=path.resolve(process.cwd(),a),h=path.resolve(path.dirname(p),"app-error.ts"),P=path.resolve(path.dirname(p),"base-usecase.ts"),y=path.resolve(g,"__generated__"),F=path.resolve(y,"routes.ts"),x=[],A=[],C=(u,j)=>{if(fs.mkdirSync(path.dirname(u),{recursive:!0}),fs.existsSync(u)&&!t){A.push(u);return}fs.writeFileSync(u,j,"utf8"),x.push(u);},R=c.map(u=>{let j=f??`/${u}`;return ` ${u}: {
180
+ basePath: "${j}",
178
181
  openapi: {
179
- info: { title: "${l.toUpperCase()} API", version: "1.0.0", description: "" },
182
+ info: { title: "${u.toUpperCase()} API", version: "1.0.0", description: "" },
180
183
  },
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(),
184
+ // Maps your AppError \u2192 HTTP (extend it in app-error.ts). \`gcpLogs\` adds a
185
+ // dev-only deep link to the matching GCP log in the error response.
186
+ errorHandler: new AppErrorHandler({
187
+ gcpLogs: { enabled: process.env["NODE_ENV"] !== "production" },
188
+ }),
189
+ // Shared structured logger (same instance exposed as \`this.logger\`).
190
+ logger: appLogger,
187
191
  verbose: process.env["NODE_ENV"] !== "production",
188
192
  },`}).join(`
189
- `),k=`import { BaseErrorHandler, BaseLogger, createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
190
- import { services } from "${I(path.dirname(p),P)}";
193
+ `),$=`import { createApiRegistry } from "@lpdjs/firestore-repo-service/servers/hono";
194
+ import { AppErrorHandler, appLogger } from "${k(path.dirname(p),h)}";
195
+ import { services } from "${k(path.dirname(p),b)}";
191
196
 
192
197
  /**
193
198
  * Single source of truth for every API exposed by this project.
@@ -196,8 +201,8 @@ import { services } from "${I(path.dirname(p),P)}";
196
201
  * Per-API resources injected into every handler / interceptor / error-handler
197
202
  * context (override them per API above):
198
203
  * - \`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\`).
204
+ * - \`errorHandler\` \u2014 maps thrown \`AppError\`s \u2192 HTTP (see app-error.ts);
205
+ * - \`logger\` \u2014 structured logging (also \`this.logger\` in useCases).
201
206
  */
202
207
  export const apis = createApiRegistry(
203
208
  {
@@ -209,7 +214,7 @@ ${R}
209
214
  /** Typed helpers used inside every route file. */
210
215
  export const defineRoute = apis.defineRoute;
211
216
  export const useCaseRoute = apis.useCaseRoute;
212
- `;$(p,k),$(P,`import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
217
+ `;C(p,$),C(b,`import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
213
218
 
214
219
  /**
215
220
  * Global DI container \u2014 declare every singleton (repositories, SDK
@@ -242,43 +247,160 @@ export const services = createServices({
242
247
 
243
248
  /** Convenience type \u2014 \`function fn(svc: Services) { ... }\`. */
244
249
  export type Services = typeof services;
245
- `);let w=`// AUTO-GENERATED by frs \u2014 do not edit.
250
+ `),C(h,`import {
251
+ BaseErrorHandler,
252
+ BaseLogger,
253
+ type ErrorHandlerContext,
254
+ type LogSeverity,
255
+ } from "@lpdjs/firestore-repo-service/servers/hono";
256
+
257
+ /**
258
+ * Domain error \u2014 pure business semantics, zero HTTP awareness. Thrown anywhere
259
+ * in useCases / handlers; the \`AppErrorHandler\` below maps it to an HTTP
260
+ * response. Add your own factory methods as your domain grows.
261
+ */
262
+ export class AppError extends Error {
263
+ readonly statusCode: number;
264
+ readonly userFacing: boolean;
265
+ readonly errorId: string;
266
+
267
+ private constructor(message: string, statusCode: number, userFacing = false) {
268
+ super(message);
269
+ this.name = "AppError";
270
+ this.statusCode = statusCode;
271
+ this.userFacing = userFacing;
272
+ this.errorId = Math.random().toString(36).slice(2, 12);
273
+ }
274
+
275
+ /** Business message shown directly to the user \u2014 HTTP 412. */
276
+ static userMessage(message: string): AppError {
277
+ return new AppError(message, 412, true);
278
+ }
279
+ /** Resource not found \u2014 HTTP 404. */
280
+ static notFound(resource = "Resource"): AppError {
281
+ return new AppError(\`\${resource} not found\`, 404);
282
+ }
283
+ /** Malformed request / invalid data \u2014 HTTP 400. */
284
+ static badRequest(detail = "invalid parameters"): AppError {
285
+ return new AppError(\`Bad request: \${detail}\`, 400);
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Project logger \u2014 extends \`BaseLogger\` and overrides the single \`write\` hook.
291
+ * Swap \`console\` for \`firebase-functions/v2\` \`logger\` in real code.
292
+ */
293
+ export class AppLogger extends BaseLogger {
294
+ protected override write(
295
+ severity: LogSeverity,
296
+ payload: Record<string, unknown>,
297
+ ): void {
298
+ // eslint-disable-next-line no-console
299
+ console.log(JSON.stringify({ severity, ...payload }));
300
+ }
301
+ }
302
+
303
+ /** Shared logger instance (per-API \`logger\` + \`this.logger\` in useCases). */
304
+ export const appLogger = new AppLogger();
305
+
306
+ /**
307
+ * Project error strategy \u2014 extends \`BaseErrorHandler\`: \`mapError\` handles our
308
+ * \`AppError\` (with an optional GCP logs deep link), \`logError\` routes through
309
+ * the logger, and unmatched errors fall back to the built-in mapping via
310
+ * \`super\`. Wired per API in apis.ts.
311
+ */
312
+ export class AppErrorHandler extends BaseErrorHandler {
313
+ protected override mapError({
314
+ error,
315
+ c,
316
+ }: ErrorHandlerContext): Response | null {
317
+ if (!(error instanceof AppError)) return null; // \u2192 built-in mapping
318
+
319
+ const logsUrl = this.gcpLogsUrl(error.errorId); // undefined when disabled
320
+ return c.json(
321
+ {
322
+ // expose the message only when it is meant for the user
323
+ error: error.userFacing ? error.message : "An error occurred",
324
+ errorId: error.errorId,
325
+ ...(logsUrl ? { logsUrl } : {}),
326
+ },
327
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
328
+ error.statusCode as any,
329
+ );
330
+ }
331
+
332
+ protected override logError({ error, logger }: ErrorHandlerContext): void {
333
+ const log = logger ?? appLogger;
334
+ if (error instanceof AppError && error.statusCode < 500) {
335
+ log.warn(error.message);
336
+ } else {
337
+ log.error(error);
338
+ }
339
+ }
340
+ }
341
+ `);let E=`import { UseCase } from "@lpdjs/firestore-repo-service/servers/hono";
342
+ import type { z } from "zod";
343
+ import { AppError, appLogger } from "${k(path.dirname(P),h)}";
344
+ import type { Services } from "${k(path.dirname(P),b)}";
345
+
346
+ /**
347
+ * Project base class for every useCase \u2014 extends the package's {@link UseCase}
348
+ * (which injects \`this.services\` via the constructor) and adds two shared
349
+ * ergonomics: \`this.logger\` (structured logger) and \`this.error\` (the
350
+ * {@link AppError} factory, mapped to HTTP by the \`AppErrorHandler\`).
351
+ * Subclasses still declare \`static input\` / \`static output\`.
352
+ */
353
+ export abstract class AppUseCase<
354
+ TInput extends z.ZodTypeAny = z.ZodTypeAny,
355
+ TOutput extends z.ZodTypeAny = z.ZodTypeAny,
356
+ > extends UseCase<TInput, TOutput, Services> {
357
+ /** Shared structured logger instance (same one injected per-API). */
358
+ protected readonly logger = appLogger;
359
+
360
+ /**
361
+ * Domain error factory \u2014 \`throw this.error.notFound(...)\` /
362
+ * \`this.error.badRequest(...)\` / \`this.error.userMessage(...)\`. The thrown
363
+ * {@link AppError} is mapped to an HTTP response by the \`AppErrorHandler\`.
364
+ */
365
+ protected readonly error = AppError;
366
+ }
367
+ `;C(P,E);let _=`// AUTO-GENERATED by frs \u2014 do not edit.
246
368
  // Run \`frs gen --root ${o}\` to refresh.
247
369
 
248
370
  import type { AnyRouteDef } from "@lpdjs/firestore-repo-service/servers/hono";
249
371
 
250
372
  export const routes: AnyRouteDef[] = [];
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(`
373
+ `;C(F,_);let q=ve({root:o,apisFile:i,servicesFile:a,apis:c});x.push(q);let N=k(path.dirname(p),p),z=k(path.dirname(p),F),O=c.length===1?`export const { ${c[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${c.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let u of x)console.log(`[frs] wrote ${u}`);for(let u of A)console.log(`[frs] skipped ${u} (use --force to overwrite)`);console.log(`
252
374
  Next steps:
253
375
 
254
376
  1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):
255
377
 
256
378
  import { onRequest } from "firebase-functions/v2/https";
257
- import { apis } from "${h}";
258
- import { routes } from "${b}";
379
+ import { apis } from "${N}";
380
+ import { routes } from "${z}";
259
381
 
260
- ${L}
382
+ ${O}
261
383
  defaults: { region: "us-central1", invoker: "public" },
262
384
  });
263
385
 
264
386
  2. Scaffold a first route:
265
387
 
266
- frs new createPost --domain posts --method post --api ${a[0]}
388
+ frs new createPost --domain posts --method post --api ${c[0]}
267
389
 
268
390
  3. Refresh the manifest before each build:
269
391
 
270
392
  frs gen --root ${o}
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(`
393
+ `);}finally{s.close();}}function k(e,r){let s=path.relative(e,r).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function be(e,r,s){e!=="service"&&(console.error(`[frs] unknown "add" target: ${e??"(missing)"} \u2014 supported: service`),process.exit(2)),r||(console.error("[frs] service name is required: frs add service <name>"),process.exit(2));let t=s.force===true,o=U(),a=[m(s["services-file"]),o.servicesFile,"src/services.ts","services.ts"].filter(w=>typeof w=="string"&&w.length>0),n;for(let w of a){let E=path.resolve(process.cwd(),w);if(fs.existsSync(E)){n=E;break}}if(!n){let w=a.map(E=>path.resolve(process.cwd(),E)).join(`
272
394
  `);console.error(`[frs] services file not found. Tried:
273
- ${h}
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";
395
+ ${w}
396
+ Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let c=m(s["services-dir"])??o.servicesDir??path.resolve(path.dirname(n),"services"),f=path.resolve(process.cwd(),c);fs.mkdirSync(f,{recursive:true});let g=`${r.charAt(0).toUpperCase()}${r.slice(1)}Service`,p=path.resolve(f,`${r}.ts`),b=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
275
397
 
276
398
  /**
277
- * ${f} \u2014 generated by \`frs add service ${t}\`.
399
+ * ${g} \u2014 generated by \`frs add service ${r}\`.
278
400
  *
279
401
  * Registered with a **factory** in \`services.ts\` so dependencies are
280
402
  * destructured at registration time. Add new constructor parameters here
281
- * and update the factory line (\`({ ctx, otherSvc }) => new ${f}(ctx, otherSvc)\`)
403
+ * and update the factory line (\`({ ctx, otherSvc }) => new ${g}(ctx, otherSvc)\`)
282
404
  * \u2014 TypeScript will tell you when something is missing.
283
405
  *
284
406
  * Async resources (DB connections, SDK clients) should stay lazy-loaded
@@ -292,17 +414,17 @@ Next steps:
292
414
  * }
293
415
  * \`\`\`
294
416
  */
295
- export class ${f} {
417
+ export class ${g} {
296
418
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
297
419
  constructor(private readonly ctx: RequestContext) {}
298
420
 
299
421
  hello(): string {
300
- return \`hello from ${t} \u2014 user=\${this.ctx.maybeC?.get("user")?.id ?? "anonymous"}\`;
422
+ return \`hello from ${r} \u2014 user=\${this.ctx.maybeC?.get("user")?.id ?? "anonymous"}\`;
301
423
  }
302
424
  }
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
425
+ `;fs.existsSync(p)&&!t?console.log(`[frs] skipped ${p} (use --force to overwrite)`):(fs.writeFileSync(p,b,"utf8"),console.log(`[frs] wrote ${p}`));let h=fs.readFileSync(n,"utf8"),P=k(path.dirname(n),p),y=`import { ${g} } from "${P}";`,F=` ${r}: ({ ctx }) => new ${g}(ctx),`;if(h.includes(y)){console.log(`[frs] services.ts already registers "${r}" \u2014 skipping.`);return}let x=h.split(`
426
+ `),A=-1;for(let w=0;w<x.length;w++)/^import\s/.test(x[w])&&(A=w);A>=0?x.splice(A+1,0,y):x.unshift(y);let C=x.join(`
427
+ `),R=C.match(/createServices\s*\(\s*\{/);if(!R){console.error(`[frs] could not find \`createServices({\` in ${n} \u2014 register "${r}" manually.`);return}let $=R.index+R[0].length,D=C.slice(0,$)+`
428
+ `+F+C.slice($);fs.writeFileSync(n,D,"utf8"),console.log(`[frs] updated ${n} (+ ${r})`);}function Pe(e,r){let s=["apis.ts","apis.js","api.ts","api.js"],t=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of t)for(let i of s){let a=path.resolve(o,i);if(fs.existsSync(a)){let n=path.relative(r,a).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js").replace(/\.js$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../apis.js"}function Ce(e,r){let s=["services.ts","services.js"],t=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of t)for(let i of s){let a=path.resolve(o,i);if(fs.existsSync(a)){let n=path.relative(r,a).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../services.js"}function Se(e,r){let s=["base-usecase.ts","base-usecase.js"],t=[e,path.dirname(e),path.dirname(path.dirname(e))];for(let o of t)for(let i of s){let a=path.resolve(o,i);if(fs.existsSync(a)){let n=path.relative(r,a).replace(/\\/g,"/");return n=n.replace(/\.ts$/,".js"),n.startsWith(".")||(n=`./${n}`),n}}return "../../../../base-usecase.js"}async function Ae(){let e=process.argv.slice(2),{command:r,flags:s}=ye(e);switch(r){case "init":await $e(s);return;case "gen":await xe(s);return;case "new":await we(e[1],s);return;case "add":await be(e[1],e[2],s);return;case "help":case "--help":case "-h":X();return;default:console.error(`[frs] unknown command: ${r}
429
+ `),X(),process.exit(2);}}Ae().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
308
430
  //# sourceMappingURL=cli.cjs.map