@lpdjs/firestore-repo-service 2.6.17 → 2.6.18

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,4 +1,4 @@
1
- # @lpdjs/firestore-repo-service (v2.6.17) — full documentation
1
+ # @lpdjs/firestore-repo-service (v2.6.18) — full documentation
2
2
 
3
3
  > ⚡ Type-safe Firestore ORM with auto-generated repositories, advanced queries, relations with typed select, pagination with include, batch/bulk operations, aggregations and transactions.
4
4
 
package/dist/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @lpdjs/firestore-repo-service (v2.6.17)
1
+ # @lpdjs/firestore-repo-service (v2.6.18)
2
2
 
3
3
  > ⚡ Type-safe Firestore ORM with auto-generated repositories, advanced queries, relations with typed select, pagination with include, batch/bulk operations, aggregations and transactions.
4
4
 
@@ -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'),url=require('url');var q={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function W(e,r=q){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"?ce(o):o).join("/")}function ce(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function K(e,r,s){let t=J(e),o=J(r),i=0;for(;i<t.length&&i<o.length&&t[i]===o[i];)i++;let a=t.length-i,n=o.slice(i),u=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),f=s===""?u:`${u}${s}`;return n[n.length-1]=f,(a===0?"./":"../".repeat(a))+n.join("/")}function J(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,t)=>!(t===0&&s===""))}var ue="/**\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 Q(e,r){let s=path.dirname(r.outFile);fs.mkdirSync(s,{recursive:true});let t=r.banner??ue,o=(r.now??new Date).toISOString(),i=r.importExtension,a=[],n=[],c=[];e.forEach((f,p)=>{let w=K(s,f.absPath,i),h=W(f.relDir,r.derive);a.push(`import mod${p} from ${JSON.stringify(w)};`),n.push(` { __derivedPath: ${JSON.stringify(h)}, mod: mod${p} },`),c.push({source:f.relPath,url:h});});let u=`${t}// 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'),url=require('url'),child_process=require('child_process');var q={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function W(e,r=q){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"?ce(o):o).join("/")}function ce(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function K(e,r,s){let t=J(e),o=J(r),i=0;for(;i<t.length&&i<o.length&&t[i]===o[i];)i++;let a=t.length-i,n=o.slice(i),l=(n[n.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),u=s===""?l:`${l}${s}`;return n[n.length-1]=u,(a===0?"./":"../".repeat(a))+n.join("/")}function J(e){return e.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((s,t)=>!(t===0&&s===""))}var ue="/**\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 Q(e,r){let s=path.dirname(r.outFile);fs.mkdirSync(s,{recursive:true});let t=r.banner??ue,o=(r.now??new Date).toISOString(),i=r.importExtension,a=[],n=[],c=[];e.forEach((u,p)=>{let w=K(s,u.absPath,i),h=W(u.relDir,r.derive);a.push(`import mod${p} from ${JSON.stringify(w)};`),n.push(` { __derivedPath: ${JSON.stringify(h)}, mod: mod${p} },`),c.push({source:u.relPath,url:h});});let l=`${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
 
@@ -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(r.outFile,u,"utf8"),{outFile:r.outFile,routeCount:e.length,derivedPaths:c}}var z={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function Z(e,r=z){let s=[];return V(e,e,r,s),s.sort((t,o)=>t.relPath.localeCompare(o.relPath)),s}function V(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())V(e,a,s,t);else if(n.isFile()&&i===s.routesFile){let c=path.relative(e,a).split(path.sep).join("/"),u=c.replace(/\/?[^/]+$/,"");t.push({absPath:a,relPath:c,relDir:u});}}}var te=".frsrc.json";function L(e=process.cwd()){let r=path.resolve(e,te);if(!fs.existsSync(r))return {};try{return JSON.parse(fs.readFileSync(r,"utf8"))}catch{return {}}}function oe(e,r=process.cwd()){let s=path.resolve(r,te),o={...L(r),...e};return fs.writeFileSync(s,`${JSON.stringify(o,null,2)}
20
- `,"utf8"),s}function we(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
19
+ `;return fs.writeFileSync(r.outFile,l,"utf8"),{outFile:r.outFile,routeCount:e.length,derivedPaths:c}}var z={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function Z(e,r=z){let s=[];return V(e,e,r,s),s.sort((t,o)=>t.relPath.localeCompare(o.relPath)),s}function V(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())V(e,a,s,t);else if(n.isFile()&&i===s.routesFile){let c=path.relative(e,a).split(path.sep).join("/"),l=c.replace(/\/?[^/]+$/,"");t.push({absPath:a,relPath:c,relDir:l});}}}var te=".frsrc.json";function L(e=process.cwd()){let r=path.resolve(e,te);if(!fs.existsSync(r))return {};try{return JSON.parse(fs.readFileSync(r,"utf8"))}catch{return {}}}function oe(e,r=process.cwd()){let s=path.resolve(r,te),o={...L(r),...e};return fs.writeFileSync(s,`${JSON.stringify(o,null,2)}
20
+ `,"utf8"),s}function $e(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]
@@ -105,7 +105,7 @@ Examples:
105
105
  frs add server crud
106
106
  frs add server sync
107
107
  frs sdk:spec --entry lib/crudServer.js --export api --out openapi.json
108
- `);}function ee(e){if(typeof e=="string")return e.split(",").map(r=>r.trim()).filter(Boolean)}function g(e){return typeof e=="string"?e:void 0}function ne(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 $e(e){let r=L(),s=g(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=g(e.out)??r.out??"__generated__/routes.ts",i=ee(e.skip)??q.skipSegments,a=g(e.casing)==="kebab"?"kebab":q.casing,n={skipSegments:i,casing:a},c=g(e.ext)??".js",u=ee(e.exclude)??z.excludeSegments,f=g(e["routes-file"])??z.routesFile,w=Z(t,{routesFile:f,excludeSegments:u});w.length===0&&console.warn(`[frs] no "${f}" files found under ${t} \u2014 generated an empty manifest.`);let h=Q(w,{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:$,url:y}of h.derivedPaths)console.log(` ${y.padEnd(48)} \u2190 ${$}`);}}async function Pe(e,r){let s=r.yes===true,t=ne(s),o=L();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=g(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=g(r.root)??o.root??"src/domains",c=g(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 u=g(r.api);if(!u){let b=o.apis?.[0]??"v1";u=(await t.ask("API tag",b)).trim()||b;}let f=g(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,w=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,$=path.resolve(process.cwd(),n),y=path.resolve($,a,f,i),R=path.resolve(y,"routes.ts"),x=`${a}.${i}.useCase`,C=path.resolve(y,`${x}.ts`),m=path.resolve(y,`${x}.test.ts`);fs.mkdirSync(y,{recursive:!0});let k=b=>b.charAt(0).toUpperCase()+b.slice(1),S=`${k(a)}${k(i)}UseCase`,j="@lpdjs/firestore-repo-service/servers/hono",P=Te($,y),E=je($,y),I=c==="get"?"// GET \u2192 lu depuis les query params":`// ${c.toUpperCase()} \u2192 lu depuis le body JSON`,N=`/**
108
+ `);}function ee(e){if(typeof e=="string")return e.split(",").map(r=>r.trim()).filter(Boolean)}function g(e){return typeof e=="string"?e:void 0}function ne(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 Pe(e){let r=L(),s=g(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=g(e.out)??r.out??"__generated__/routes.ts",i=ee(e.skip)??q.skipSegments,a=g(e.casing)==="kebab"?"kebab":q.casing,n={skipSegments:i,casing:a},c=g(e.ext)??".js",l=ee(e.exclude)??z.excludeSegments,u=g(e["routes-file"])??z.routesFile,w=Z(t,{routesFile:u,excludeSegments:l});w.length===0&&console.warn(`[frs] no "${u}" files found under ${t} \u2014 generated an empty manifest.`);let h=Q(w,{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:$,url:y}of h.derivedPaths)console.log(` ${y.padEnd(48)} \u2190 ${$}`);}}async function Se(e,r){let s=r.yes===true,t=ne(s),o=L();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=g(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=g(r.root)??o.root??"src/domains",c=g(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 l=g(r.api);if(!l){let b=o.apis?.[0]??"v1";l=(await t.ask("API tag",b)).trim()||b;}let u=g(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,w=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,$=path.resolve(process.cwd(),n),y=path.resolve($,a,u,i),R=path.resolve(y,"routes.ts"),x=`${a}.${i}.useCase`,C=path.resolve(y,`${x}.ts`),m=path.resolve(y,`${x}.test.ts`);fs.mkdirSync(y,{recursive:!0});let k=b=>b.charAt(0).toUpperCase()+b.slice(1),S=`${k(a)}${k(i)}UseCase`,j="@lpdjs/firestore-repo-service/servers/hono",P=je($,y),E=De($,y),I=c==="get"?"// GET \u2192 lu depuis les query params":`// ${c.toUpperCase()} \u2192 lu depuis le body JSON`,U=`/**
109
109
  * ${S} \u2014 pure business logic, no HTTP awareness.
110
110
  *
111
111
  * Owns its Zod \`input\` / \`output\` schemas (declared as \`static\` members, the
@@ -143,12 +143,12 @@ export class ${S} extends AppUseCase<typeof input, typeof output> {
143
143
  }
144
144
  `,H=c==="get"?`
145
145
  source: "query",`:"",M=p?`import { ${S} } from "./${x}.js";
146
- `:"",_=g(r["apis-import"])??Oe($,y),v=p?`import { defineRoutes } from "${j}";
146
+ `:"",_=g(r["apis-import"])??Te($,y),v=p?`import { defineRoutes } from "${j}";
147
147
  import { useCaseRoute } from "${_}";
148
148
  ${M}
149
149
  export default defineRoutes([
150
150
  useCaseRoute(${S}, {
151
- api: "${u}",
151
+ api: "${l}",
152
152
  method: "${c}",${H}
153
153
  summary: "TODO: ${i}",
154
154
  tags: ["${a}"],
@@ -160,7 +160,7 @@ import { defineRoute } from "${_}";
160
160
 
161
161
  export default defineRoutes([
162
162
  defineRoute({
163
- api: "${u}",
163
+ api: "${l}",
164
164
  method: "${c}",
165
165
 
166
166
  input: z.object({
@@ -181,7 +181,7 @@ export default defineRoutes([
181
181
  },
182
182
  }),
183
183
  ]);
184
- `,T=[],G=[],B=(b,ae)=>{if(fs.existsSync(b)&&!h){G.push(b);return}fs.writeFileSync(b,ae,"utf8"),T.push(b);};if(B(R,v),p&&B(C,N),p&&w){let b=`import { describe, it, expect } from "vitest";
184
+ `,T=[],G=[],B=(b,ae)=>{if(fs.existsSync(b)&&!h){G.push(b);return}fs.writeFileSync(b,ae,"utf8"),T.push(b);};if(B(R,v),p&&B(C,U),p&&w){let b=`import { describe, it, expect } from "vitest";
185
185
  import type { Services } from "${P}";
186
186
  import { ${S} } from "./${x}.js";
187
187
 
@@ -198,7 +198,7 @@ describe("${S}", () => {
198
198
  // TODO: add error-path tests, repository mocks, etc.
199
199
  });
200
200
  `;B(m,b);}for(let b of T)console.log(`[frs] wrote ${b}`);for(let b of G)console.log(`[frs] skipped ${b} (use --force to overwrite)`);console.log(`
201
- [frs] reminder: run "frs gen --root ${n}" to refresh the manifest.`);}finally{t.close();}}async function Se(e){let r=e.yes===true,s=ne(r);try{let t=e.force===!0,o=g(e.root);o||(o=(await s.ask("Domain root","src/domains")).trim()||"src/domains");let i=g(e["apis-file"]);i||(i=(await s.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let a=g(e["services-file"]);if(!a){let v=i.replace(/apis\.ts$/,"services.ts")||"src/services.ts";a=(await s.ask("services.ts location",v)).trim()||v;}let n=g(e.apis);n||(n=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let c=n.split(",").map(v=>v.trim()).filter(Boolean);c.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let u=g(e["base-path"]),f=path.resolve(process.cwd(),o),p=path.resolve(process.cwd(),i),w=path.resolve(process.cwd(),a),h=path.resolve(path.dirname(p),"app-error.ts"),$=path.resolve(path.dirname(p),"base-usecase.ts"),y=path.resolve(f,"__generated__"),R=path.resolve(y,"routes.ts"),x=[],C=[],m=(v,T)=>{if(fs.mkdirSync(path.dirname(v),{recursive:!0}),fs.existsSync(v)&&!t){C.push(v);return}fs.writeFileSync(v,T,"utf8"),x.push(v);},k=c.map(v=>{let T=u??`/${v}`;return ` ${v}: {
201
+ [frs] reminder: run "frs gen --root ${n}" to refresh the manifest.`);}finally{t.close();}}async function Ae(e){let r=e.yes===true,s=ne(r);try{let t=e.force===!0,o=g(e.root);o||(o=(await s.ask("Domain root","src/domains")).trim()||"src/domains");let i=g(e["apis-file"]);i||(i=(await s.ask("apis.ts location","src/apis.ts")).trim()||"src/apis.ts");let a=g(e["services-file"]);if(!a){let v=i.replace(/apis\.ts$/,"services.ts")||"src/services.ts";a=(await s.ask("services.ts location",v)).trim()||v;}let n=g(e.apis);n||(n=(await s.ask("API tags (comma-separated)","v1")).trim()||"v1");let c=n.split(",").map(v=>v.trim()).filter(Boolean);c.length===0&&(console.error("[frs] at least one API tag is required"),process.exit(2));let l=g(e["base-path"]),u=path.resolve(process.cwd(),o),p=path.resolve(process.cwd(),i),w=path.resolve(process.cwd(),a),h=path.resolve(path.dirname(p),"app-error.ts"),$=path.resolve(path.dirname(p),"base-usecase.ts"),y=path.resolve(u,"__generated__"),R=path.resolve(y,"routes.ts"),x=[],C=[],m=(v,T)=>{if(fs.mkdirSync(path.dirname(v),{recursive:!0}),fs.existsSync(v)&&!t){C.push(v);return}fs.writeFileSync(v,T,"utf8"),x.push(v);},k=c.map(v=>{let T=l??`/${v}`;return ` ${v}: {
202
202
  basePath: "${T}",
203
203
  openapi: {
204
204
  info: { title: "${v.toUpperCase()} API", version: "1.0.0", description: "" },
@@ -462,7 +462,7 @@ export abstract class AppUseCase<
462
462
  import type { AnyRouteDef } from "@lpdjs/firestore-repo-service/servers/hono";
463
463
 
464
464
  export const routes: AnyRouteDef[] = [];
465
- `;m(R,I);let N=oe({root:o,apisFile:i,servicesFile:a,apis:c});x.push(N);let H=F(path.dirname(p),p),M=F(path.dirname(p),R),_=c.length===1?`export const { ${c[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${c.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let v of x)console.log(`[frs] wrote ${v}`);for(let v of C)console.log(`[frs] skipped ${v} (use --force to overwrite)`);console.log(`
465
+ `;m(R,I);let U=oe({root:o,apisFile:i,servicesFile:a,apis:c});x.push(U);let H=F(path.dirname(p),p),M=F(path.dirname(p),R),_=c.length===1?`export const { ${c[0]} } = apis.toFunctions(routes, onRequest, {`:`export const { ${c.join(", ")} } = apis.toFunctions(routes, onRequest, {`;for(let v of x)console.log(`[frs] wrote ${v}`);for(let v of C)console.log(`[frs] skipped ${v} (use --force to overwrite)`);console.log(`
466
466
  Next steps:
467
467
 
468
468
  1. Wire the registry in your Functions entrypoint (e.g. src/index.ts):
@@ -482,17 +482,17 @@ Next steps:
482
482
  3. Refresh the manifest before each build:
483
483
 
484
484
  frs gen --root ${o}
485
- `);}finally{s.close();}}function F(e,r){let s=path.relative(e,r).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function Ae(e,r,s){if(e==="server"){await Fe(r,s);return}e!=="service"&&(console.error(`[frs] unknown "add" target: ${e??"(missing)"} \u2014 supported: service, server`),process.exit(2)),r||(console.error("[frs] service name is required: frs add service <name>"),process.exit(2));let t=s.force===true,o=L(),a=[g(s["services-file"]),o.servicesFile,"src/services.ts","services.ts"].filter(P=>typeof P=="string"&&P.length>0),n;for(let P of a){let E=path.resolve(process.cwd(),P);if(fs.existsSync(E)){n=E;break}}if(!n){let P=a.map(E=>path.resolve(process.cwd(),E)).join(`
485
+ `);}finally{s.close();}}function F(e,r){let s=path.relative(e,r).replace(/\\/g,"/");return s=s.replace(/\.ts$/,".js"),s.startsWith(".")||(s=`./${s}`),s}async function Ce(e,r,s){if(e==="server"){await Ee(r,s);return}e!=="service"&&(console.error(`[frs] unknown "add" target: ${e??"(missing)"} \u2014 supported: service, server`),process.exit(2)),r||(console.error("[frs] service name is required: frs add service <name>"),process.exit(2));let t=s.force===true,o=L(),a=[g(s["services-file"]),o.servicesFile,"src/services.ts","services.ts"].filter(P=>typeof P=="string"&&P.length>0),n;for(let P of a){let E=path.resolve(process.cwd(),P);if(fs.existsSync(E)){n=E;break}}if(!n){let P=a.map(E=>path.resolve(process.cwd(),E)).join(`
486
486
  `);console.error(`[frs] services file not found. Tried:
487
487
  ${P}
488
- Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let c=g(s["services-dir"])??o.servicesDir??path.resolve(path.dirname(n),"services"),u=path.resolve(process.cwd(),c);fs.mkdirSync(u,{recursive:true});let f=`${r.charAt(0).toUpperCase()}${r.slice(1)}Service`,p=path.resolve(u,`${r}.ts`),w=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
488
+ Run \`frs init\` first or pass --services-file <path>.`),process.exit(2);}let c=g(s["services-dir"])??o.servicesDir??path.resolve(path.dirname(n),"services"),l=path.resolve(process.cwd(),c);fs.mkdirSync(l,{recursive:true});let u=`${r.charAt(0).toUpperCase()}${r.slice(1)}Service`,p=path.resolve(l,`${r}.ts`),w=`import type { RequestContext } from "@lpdjs/firestore-repo-service/servers/hono";
489
489
 
490
490
  /**
491
- * ${f} \u2014 generated by \`frs add service ${r}\`.
491
+ * ${u} \u2014 generated by \`frs add service ${r}\`.
492
492
  *
493
493
  * Registered with a **factory** in \`services.ts\` so dependencies are
494
494
  * destructured at registration time. Add new constructor parameters here
495
- * and update the factory line (\`({ ctx, otherSvc }) => new ${f}(ctx, otherSvc)\`)
495
+ * and update the factory line (\`({ ctx, otherSvc }) => new ${u}(ctx, otherSvc)\`)
496
496
  * \u2014 TypeScript will tell you when something is missing.
497
497
  *
498
498
  * Async resources (DB connections, SDK clients) should stay lazy-loaded
@@ -506,7 +506,7 @@ Next steps:
506
506
  * }
507
507
  * \`\`\`
508
508
  */
509
- export class ${f} {
509
+ export class ${u} {
510
510
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
511
511
  constructor(private readonly ctx: RequestContext) {}
512
512
 
@@ -514,10 +514,10 @@ export class ${f} {
514
514
  return \`hello from ${r} \u2014 user=\${this.ctx.maybeC?.get("user")?.id ?? "anonymous"}\`;
515
515
  }
516
516
  }
517
- `;fs.existsSync(p)&&!t?console.log(`[frs] skipped ${p} (use --force to overwrite)`):(fs.writeFileSync(p,w,"utf8"),console.log(`[frs] wrote ${p}`));let h=fs.readFileSync(n,"utf8"),$=F(path.dirname(n),p),y=`import { ${f} } from "${$}";`,R=` ${r}: ({ ctx }) => new ${f}(ctx),`;if(h.includes(y)){console.log(`[frs] services.ts already registers "${r}" \u2014 skipping.`);return}let x=h.split(`
517
+ `;fs.existsSync(p)&&!t?console.log(`[frs] skipped ${p} (use --force to overwrite)`):(fs.writeFileSync(p,w,"utf8"),console.log(`[frs] wrote ${p}`));let h=fs.readFileSync(n,"utf8"),$=F(path.dirname(n),p),y=`import { ${u} } from "${$}";`,R=` ${r}: ({ ctx }) => new ${u}(ctx),`;if(h.includes(y)){console.log(`[frs] services.ts already registers "${r}" \u2014 skipping.`);return}let x=h.split(`
518
518
  `),C=-1;for(let P=0;P<x.length;P++)/^import\s/.test(x[P])&&(C=P);C>=0?x.splice(C+1,0,y):x.unshift(y);let m=x.join(`
519
519
  `),k=m.match(/createServices\s*\(\s*\{/);if(!k){console.error(`[frs] could not find \`createServices({\` in ${n} \u2014 register "${r}" manually.`);return}let S=k.index+k[0].length,j=m.slice(0,S)+`
520
- `+R+m.slice(S);fs.writeFileSync(n,j,"utf8"),console.log(`[frs] updated ${n} (+ ${r})`);}var ie="@lpdjs/firestore-repo-service";function Ce(){return `import { type Firestore, getFirestore } from "firebase-admin/firestore";
520
+ `+R+m.slice(S);fs.writeFileSync(n,j,"utf8"),console.log(`[frs] updated ${n} (+ ${r})`);}var ie="@lpdjs/firestore-repo-service";function ke(){return `import { type Firestore, getFirestore } from "firebase-admin/firestore";
521
521
  import {
522
522
  createRepositoryConfig,
523
523
  createRepositoryMapping,
@@ -548,7 +548,7 @@ export const repos = createRepositoryMapping(() => getFirestore(), {
548
548
  }),
549
549
  // TODO: add your repositories here.
550
550
  });
551
- `}function ke(e){return `import { onRequest } from "firebase-functions/v2/https";
551
+ `}function Re(e){return `import { onRequest } from "firebase-functions/v2/https";
552
552
  import { createServers } from "${ie}";
553
553
  import { repos } from "${e}";
554
554
 
@@ -560,7 +560,7 @@ import { repos } from "${e}";
560
560
  * ready-to-export Cloud Function.
561
561
  */
562
562
  export const servers = createServers(repos, { onRequest });
563
- `}function Re(e,r){return e==="admin"?`import { servers } from "${r}";
563
+ `}function Fe(e,r){return e==="admin"?`import { servers } from "${r}";
564
564
 
565
565
  /**
566
566
  * Admin UI server. Each key maps a repository (from the registry) to its admin
@@ -613,10 +613,10 @@ export const sync = servers.sync({
613
613
  // Optional bundled admin endpoint:
614
614
  // admin: { onRequest, basePath: "/__sync" },
615
615
  });
616
- `}async function Fe(e,r){let s=["admin","crud","sync"];(!e||!s.includes(e))&&(console.error(`[frs] server type is required: frs add server <${s.join("|")}>`),process.exit(2));let t=e,o=r.force===true,i=L(),n=g(r.dir)??(i.servers?.rootPath&&path.dirname(i.servers.rootPath))??(i.apisFile&&path.dirname(i.apisFile))??"src",c=i.servers?.reposPath??`${n}/repos.ts`,u=i.servers?.rootPath??`${n}/servers.ts`,f=t==="crud"?"crudServer.ts":`${t}Server.ts`,p=i.servers?.[t]??`${n}/${f}`,w=path.resolve(process.cwd(),c),h=path.resolve(process.cwd(),u),$=path.resolve(process.cwd(),p),y=[],R=[],x=(m,k)=>{if(fs.mkdirSync(path.dirname(m),{recursive:true}),fs.existsSync(m)&&!o){R.push(m);return}fs.writeFileSync(m,k,"utf8"),y.push(m);};x(w,Ce()),x(h,ke(F(path.dirname(h),w))),x($,Re(t,F(path.dirname($),h)));let C=oe({servers:{...i.servers,rootPath:u,reposPath:c,[t]:p}});y.push(C);for(let m of y)console.log(`[frs] wrote ${m}`);for(let m of R)console.log(`[frs] skipped ${m} (use --force to overwrite)`);console.log(`
617
- [frs] reminder: export the \`${t==="crud"?"api":t}\` from your Functions entrypoint (e.g. src/index.ts).`);}function re(e){if(!e||typeof e!="object"&&typeof e!="function")return null;let r=e.spec;if(typeof r=="function"){let s=r.call(e);return s&&typeof s=="object"?s:null}return typeof e.openapi=="string"?e:null}async function Ee(e){let r=g(e.entry);r||(console.error("[frs] --entry <module> is required: frs sdk:spec --entry <path> [--export <name>] [--out openapi.json]\n The module must be importable by Node (point at built JS, or run via a TS runtime such as `bun frs ...`)."),process.exit(2));let s=path.resolve(process.cwd(),r);fs.existsSync(s)||(console.error(`[frs] entry not found: ${s}`),process.exit(2)),/\.tsx?$/.test(s)&&!process.versions.bun&&(console.error(`[frs] cannot import a TypeScript entry under Node: ${r}
618
- Build it to JS first (e.g. tsc \u2192 lib/\u2026) and point --entry there, or run with a TS runtime: \`bunx frs sdk:spec ...\`.`),process.exit(2));let t=g(e.out)??"openapi.json",o=g(e.export),i;try{i=await import(url.pathToFileURL(s).href);}catch(u){console.error(`[frs] failed to import ${r}:
619
- `,u),process.exit(1);}let a=null,n=o??"";if(o)a=re(i[o]),a||(console.error(`[frs] export "${o}" is not a spec source. Expected a CRUD server (with .spec()) or an OpenAPI document.`),process.exit(2));else {for(let[u,f]of Object.entries(i)){let p=re(f);if(p){a=p,n=u;break}}a||(console.error(`[frs] no OpenAPI spec found in ${r}. Export a CRUD server (\`.spec()\`) or an OpenAPI document (e.g. \`export const openapi = apis.spec("v1", routes)\`), or pass --export <name>.`),process.exit(2));}let c=path.resolve(process.cwd(),t);fs.mkdirSync(path.dirname(c),{recursive:true}),fs.writeFileSync(c,`${JSON.stringify(a,null,2)}
620
- `,"utf8"),console.log(`[frs] wrote ${c} (OpenAPI ${String(a.openapi??"?")} from export "${n}")`);}function Oe(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 Te(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 je(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 De(){let e=process.argv.slice(2),{command:r,flags:s}=we(e);switch(r){case "init":await Se(s);return;case "gen":await $e(s);return;case "new":await Pe(e[1],s);return;case "add":await Ae(e[1],e[2],s);return;case "sdk:spec":await Ee(s);return;case "help":case "--help":case "-h":X();return;default:console.error(`[frs] unknown command: ${r}
621
- `),X(),process.exit(2);}}De().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
616
+ `}async function Ee(e,r){let s=["admin","crud","sync"];(!e||!s.includes(e))&&(console.error(`[frs] server type is required: frs add server <${s.join("|")}>`),process.exit(2));let t=e,o=r.force===true,i=L(),n=g(r.dir)??(i.servers?.rootPath&&path.dirname(i.servers.rootPath))??(i.apisFile&&path.dirname(i.apisFile))??"src",c=i.servers?.reposPath??`${n}/repos.ts`,l=i.servers?.rootPath??`${n}/servers.ts`,u=t==="crud"?"crudServer.ts":`${t}Server.ts`,p=i.servers?.[t]??`${n}/${u}`,w=path.resolve(process.cwd(),c),h=path.resolve(process.cwd(),l),$=path.resolve(process.cwd(),p),y=[],R=[],x=(m,k)=>{if(fs.mkdirSync(path.dirname(m),{recursive:true}),fs.existsSync(m)&&!o){R.push(m);return}fs.writeFileSync(m,k,"utf8"),y.push(m);};x(w,ke()),x(h,Re(F(path.dirname(h),w))),x($,Fe(t,F(path.dirname($),h)));let C=oe({servers:{...i.servers,rootPath:l,reposPath:c,[t]:p}});y.push(C);for(let m of y)console.log(`[frs] wrote ${m}`);for(let m of R)console.log(`[frs] skipped ${m} (use --force to overwrite)`);console.log(`
617
+ [frs] reminder: export the \`${t==="crud"?"api":t}\` from your Functions entrypoint (e.g. src/index.ts).`);}function re(e){if(!e||typeof e!="object"&&typeof e!="function")return null;let r=e.spec;if(typeof r=="function"){let s=r.call(e);return s&&typeof s=="object"?s:null}return typeof e.openapi=="string"?e:null}async function Oe(e){let r=g(e.entry);r||(console.error("[frs] --entry <module> is required: frs sdk:spec --entry <path> [--export <name>] [--out openapi.json]\n The module must be importable by Node (point at built JS, or run via a TS runtime such as `bun frs ...`)."),process.exit(2));let s=path.resolve(process.cwd(),r);if(fs.existsSync(s)||(console.error(`[frs] entry not found: ${s}`),process.exit(2)),/\.tsx?$/.test(s)&&!process.versions.bun){let l=process.argv.slice(1),u=child_process.spawnSync("bun",l,{stdio:"inherit"});u.error&&u.error.code==="ENOENT"&&(console.error(`[frs] a TypeScript entry needs a TS runtime, and \`bun\` was not found on PATH: ${r}
618
+ Either: (a) build it to JS first (e.g. tsc \u2192 lib/\u2026) and point --entry there, or (b) install bun and re-run, or run with \`bun frs sdk:spec ...\`.`),process.exit(2)),process.exit(u.status??0);}let t=g(e.out)??"openapi.json",o=g(e.export),i;try{i=await import(url.pathToFileURL(s).href);}catch(l){console.error(`[frs] failed to import ${r}:
619
+ `,l),process.exit(1);}let a=null,n=o??"";if(o)a=re(i[o]),a||(console.error(`[frs] export "${o}" is not a spec source. Expected a CRUD server (with .spec()) or an OpenAPI document.`),process.exit(2));else {for(let[l,u]of Object.entries(i)){let p=re(u);if(p){a=p,n=l;break}}a||(console.error(`[frs] no OpenAPI spec found in ${r}. Export a CRUD server (\`.spec()\`) or an OpenAPI document (e.g. \`export const openapi = apis.spec("v1", routes)\`), or pass --export <name>.`),process.exit(2));}let c=path.resolve(process.cwd(),t);fs.mkdirSync(path.dirname(c),{recursive:true}),fs.writeFileSync(c,`${JSON.stringify(a,null,2)}
620
+ `,"utf8"),console.log(`[frs] wrote ${c} (OpenAPI ${String(a.openapi??"?")} from export "${n}")`);}function Te(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 je(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 De(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 Le(){let e=process.argv.slice(2),{command:r,flags:s}=$e(e);switch(r){case "init":await Ae(s);return;case "gen":await Pe(s);return;case "new":await Se(e[1],s);return;case "add":await Ce(e[1],e[2],s);return;case "sdk:spec":await Oe(s);return;case "help":case "--help":case "-h":X();return;default:console.error(`[frs] unknown command: ${r}
621
+ `),X(),process.exit(2);}}Le().catch(e=>{console.error(e),process.exit(1);});//# sourceMappingURL=cli.cjs.map
622
622
  //# sourceMappingURL=cli.cjs.map