@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.
- package/README.md +92 -50
- package/dist/{create-servers-B9dTUhvR.d.cts → create-servers-B4GrBqdA.d.cts} +6 -6
- package/dist/{create-servers-BFhdPPeo.d.ts → create-servers-CVudVM8e.d.ts} +6 -6
- package/dist/{firebase-auth-D1APf9PA.d.cts → firebase-auth-Dpvrd8MP.d.cts} +13 -0
- package/dist/{firebase-auth-D1APf9PA.d.ts → firebase-auth-Dpvrd8MP.d.ts} +13 -0
- package/dist/history/index.cjs +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +10 -4
- package/dist/history/index.d.ts +10 -4
- package/dist/history/index.js +1 -1
- package/dist/history/index.js.map +1 -1
- package/dist/{index-BxurOEz1.d.ts → index-DzO9MfNI.d.cts} +9 -2
- package/dist/{index-BmagC7uw.d.cts → index-oFhGCBrY.d.ts} +9 -2
- package/dist/index.cjs +84 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +84 -84
- package/dist/index.js.map +1 -1
- package/dist/{openapi-ML_1hTx2.d.cts → openapi-B2w5tVRR.d.cts} +1 -1
- package/dist/{openapi-DIoQV_yQ.d.ts → openapi-DB8bXZB-.d.ts} +1 -1
- package/dist/{queue-xMOZxY0M.d.cts → queue-B8YUTnBT.d.cts} +20 -5
- package/dist/{queue-CVchaGAh.d.ts → queue-DYmbVDu5.d.ts} +20 -5
- package/dist/{read-BSyLao3I.d.cts → read-CTWZjxyh.d.cts} +1 -1
- package/dist/{read-BSyLao3I.d.ts → read-CTWZjxyh.d.ts} +1 -1
- package/dist/servers/admin/index.cjs +48 -48
- package/dist/servers/admin/index.cjs.map +1 -1
- package/dist/servers/admin/index.d.cts +4 -4
- package/dist/servers/admin/index.d.ts +4 -4
- package/dist/servers/admin/index.js +48 -48
- package/dist/servers/admin/index.js.map +1 -1
- package/dist/servers/auth/index.cjs +25 -12
- package/dist/servers/auth/index.cjs.map +1 -1
- package/dist/servers/auth/index.d.cts +1 -1
- package/dist/servers/auth/index.d.ts +1 -1
- package/dist/servers/auth/index.js +25 -12
- package/dist/servers/auth/index.js.map +1 -1
- package/dist/servers/crud/index.cjs +2 -2
- package/dist/servers/crud/index.cjs.map +1 -1
- package/dist/servers/crud/index.d.cts +6 -6
- package/dist/servers/crud/index.d.ts +6 -6
- package/dist/servers/crud/index.js +2 -2
- package/dist/servers/crud/index.js.map +1 -1
- package/dist/servers/hono/cli.cjs +142 -53
- package/dist/servers/hono/cli.cjs.map +1 -1
- package/dist/servers/hono/cli.js +142 -53
- package/dist/servers/hono/cli.js.map +1 -1
- package/dist/servers/hono/index.cjs +5 -5
- package/dist/servers/hono/index.cjs.map +1 -1
- package/dist/servers/hono/index.d.cts +241 -24
- package/dist/servers/hono/index.d.ts +241 -24
- package/dist/servers/hono/index.js +5 -5
- package/dist/servers/hono/index.js.map +1 -1
- package/dist/servers/index.cjs +98 -98
- package/dist/servers/index.cjs.map +1 -1
- package/dist/servers/index.d.cts +9 -9
- package/dist/servers/index.d.ts +9 -9
- package/dist/servers/index.js +98 -98
- package/dist/servers/index.js.map +1 -1
- package/dist/sync/bigquery.cjs +3 -3
- package/dist/sync/bigquery.cjs.map +1 -1
- package/dist/sync/bigquery.d.cts +18 -2
- package/dist/sync/bigquery.d.ts +18 -2
- package/dist/sync/bigquery.js +3 -3
- package/dist/sync/bigquery.js.map +1 -1
- package/dist/sync/index.cjs +37 -37
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +5 -5
- package/dist/sync/index.d.ts +5 -5
- package/dist/sync/index.js +37 -37
- package/dist/sync/index.js.map +1 -1
- package/dist/{types-5vgXdUM2.d.ts → types-BHZ-Gk-s.d.ts} +71 -4
- package/dist/{types-ChzVPw4k.d.ts → types-FLGn8CAI.d.ts} +15 -1
- package/dist/{types-BtdC0Qhu.d.cts → types-GvexCqrq.d.cts} +71 -4
- package/dist/{types-BgIGWlR1.d.cts → types-wcX7xfdo.d.cts} +15 -1
- package/package.json +2 -2
package/dist/servers/hono/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import {existsSync,mkdirSync,writeFileSync,readFileSync,readdirSync,statSync}from'fs';import {resolve,dirname,relative,join,sep}from'path';import {stdin,stdout}from'process';import {createInterface}from'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=dirname(t.outFile);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
|
-
`+
|
|
7
|
-
`)+(
|
|
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 writeFileSync(
|
|
19
|
+
`;return 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=readdirSync(t);}catch{return}for(let i of r){if(s.excludeSegments.includes(i))continue;let c=join(t,i),n;try{n=statSync(c);}catch{continue}if(n.isDirectory())z(e,c,s,o);else if(n.isFile()&&i===s.routesFile){let a=relative(e,c).split(sep).join("/"),g=a.replace(/\/?[^/]+$/,"");o.push({absPath:c,relPath:a,relDir:g});}}}var V=".frsrc.json";function Y(e=process.cwd()){let t=resolve(e,V);if(!existsSync(t))return {};try{return JSON.parse(readFileSync(t,"utf8"))}catch{return {}}}function le(e,t=process.cwd()){let s=resolve(t,V),r={...Y(t),...e};return 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
|
|
23
|
-
frs
|
|
24
|
-
frs
|
|
25
|
-
frs
|
|
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
|
|
66
|
-
frs
|
|
67
|
-
frs
|
|
68
|
-
|
|
69
|
-
|
|
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||!stdin.isTTY)return {ask:async(s,o)=>o??"",askChoice:async(s,o,r)=>r??"",askBool:async(s,o)=>o,close:()=>{}};let t=createInterface({input:stdin,output: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=resolve(process.cwd(),t);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: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=resolve(process.cwd(),c),$=resolve(y,i,g,r),b=resolve($,"routes.ts"),D=resolve($,"useCase.ts"),x=resolve($,"useCase.test.ts");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 ${
|
|
86
|
+
export interface ${m}Input {
|
|
74
87
|
// TODO: define the input shape
|
|
75
88
|
example: string;
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
export interface ${
|
|
91
|
+
export interface ${m}Output {
|
|
79
92
|
// TODO: define the output shape
|
|
80
93
|
id: string;
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
export class ${
|
|
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: ${
|
|
100
|
+
async execute(input: ${m}Input): Promise<${m}Output> {
|
|
88
101
|
// TODO: implement
|
|
89
102
|
return { id: input.example };
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
|
-
`,
|
|
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
|
-
})`,
|
|
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 };`,
|
|
102
|
-
`:"",
|
|
103
|
-
import { defineRoute } from "${
|
|
104
|
-
${
|
|
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: "${
|
|
119
|
+
api: "${a}",
|
|
107
120
|
method: "${n}",
|
|
108
121
|
|
|
109
|
-
input: ${
|
|
122
|
+
input: ${F},
|
|
110
123
|
|
|
111
124
|
output: z.object({
|
|
112
125
|
id: z.string(),
|
|
113
126
|
}),
|
|
114
127
|
|
|
115
|
-
summary: "TODO: ${
|
|
128
|
+
summary: "TODO: ${r}",
|
|
116
129
|
tags: ["${i}"],
|
|
117
130
|
|
|
118
131
|
handler: async ({ input }) => {
|
|
119
|
-
${
|
|
132
|
+
${_}
|
|
120
133
|
},
|
|
121
134
|
});
|
|
122
|
-
`,j=[],
|
|
123
|
-
import { ${
|
|
135
|
+
`,j=[],u=[],R=(w,Q)=>{if(existsSync(w)&&!v){u.push(w);return}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("${
|
|
138
|
+
describe("${m}", () => {
|
|
126
139
|
it("returns a response shaped like the output schema", async () => {
|
|
127
|
-
const useCase = new ${
|
|
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
|
-
`;
|
|
135
|
-
[frs
|
|
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=resolve(process.cwd(),r),l=resolve(process.cwd(),i),v=resolve(process.cwd(),c),y=resolve(p,"__generated__"),$=resolve(y,"routes.ts"),b=[],D=[],x=(u,R)=>{if(mkdirSync(dirname(u),{recursive:!0}),existsSync(u)&&!o){D.push(u);return}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(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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
`;
|
|
155
|
-
|
|
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
|
-
`;
|
|
214
|
+
`;x($,_);let A=le({root:r,apisFile:i,servicesFile:c});b.push(A);let h=T(dirname(l),l),S=T(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 "${
|
|
167
|
-
import { routes } from "${
|
|
220
|
+
import { apis } from "${h}";
|
|
221
|
+
import { routes } from "${S}";
|
|
168
222
|
|
|
169
|
-
${
|
|
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
|
|
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
|
|
180
|
-
`);}finally{
|
|
181
|
-
`)
|
|
233
|
+
frs gen --root ${r}
|
|
234
|
+
`);}finally{s.close();}}function T(e,t){let s=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=resolve(process.cwd(),h);if(existsSync(S)){n=S;break}}if(!n){let h=c.map(S=>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??resolve(dirname(n),"services"),g=resolve(process.cwd(),a);mkdirSync(g,{recursive:true});let p=`${t.charAt(0).toUpperCase()}${t.slice(1)}Service`,l=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
|
+
`;existsSync(l)&&!o?console.log(`[frs] skipped ${l} (use --force to overwrite)`):(writeFileSync(l,v,"utf8"),console.log(`[frs] wrote ${l}`));let y=readFileSync(n,"utf8"),$=T(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(_);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,dirname(e),dirname(dirname(e))];for(let r of o)for(let i of s){let c=resolve(r,i);if(existsSync(c)){let n=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.js.map
|
|
182
271
|
//# sourceMappingURL=cli.js.map
|