@lpdjs/firestore-repo-service 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { C as ConfiguredRepository, m as CrudServerOptions, O as OpenAPIDocument } from '../../types-CYVwoOQx.cjs';
2
- export { A as ApiResponse, B as BasicAuthConfig, l as CrudRepoConfig, r as CrudRepoEntry, s as CrudRepoRegistry, n as FieldRole, L as ListResponseData, M as Middleware, u as OpenAPISpecOptions, o as QueryRequestBody, p as RepoFieldPath, q as RepoRelationKeys, U as UserFieldPath, t as generateOpenAPISpec } from '../../types-CYVwoOQx.cjs';
1
+ import { HttpsOptions } from 'firebase-functions/v2/https';
2
+ import { C as ConfiguredRepository, m as CrudServerOptions, O as OpenAPIDocument } from '../../types-B5NdBY1Z.cjs';
3
+ export { A as ApiResponse, B as BasicAuthConfig, l as CrudRepoConfig, r as CrudRepoEntry, s as CrudRepoRegistry, n as FieldRole, L as ListResponseData, M as Middleware, u as OpenAPISpecOptions, o as QueryRequestBody, p as RepoFieldPath, q as RepoRelationKeys, U as UserFieldPath, t as generateOpenAPISpec } from '../../types-B5NdBY1Z.cjs';
3
4
  import 'zod';
4
5
  import 'firebase-admin/firestore';
5
6
 
@@ -177,7 +178,7 @@ import 'firebase-admin/firestore';
177
178
  */
178
179
  declare function createCrudServer<TRepos extends Record<string, ConfiguredRepository<any>>>(options: CrudServerOptions<TRepos>): ((req: any, res: any) => Promise<void>) & {
179
180
  spec: () => OpenAPIDocument;
180
- httpsOptions?: Record<string, unknown>;
181
+ httpsOptions?: HttpsOptions;
181
182
  };
182
183
 
183
184
  export { CrudServerOptions, OpenAPIDocument, createCrudServer };
@@ -1,5 +1,6 @@
1
- import { C as ConfiguredRepository, m as CrudServerOptions, O as OpenAPIDocument } from '../../types-CYVwoOQx.js';
2
- export { A as ApiResponse, B as BasicAuthConfig, l as CrudRepoConfig, r as CrudRepoEntry, s as CrudRepoRegistry, n as FieldRole, L as ListResponseData, M as Middleware, u as OpenAPISpecOptions, o as QueryRequestBody, p as RepoFieldPath, q as RepoRelationKeys, U as UserFieldPath, t as generateOpenAPISpec } from '../../types-CYVwoOQx.js';
1
+ import { HttpsOptions } from 'firebase-functions/v2/https';
2
+ import { C as ConfiguredRepository, m as CrudServerOptions, O as OpenAPIDocument } from '../../types-B5NdBY1Z.js';
3
+ export { A as ApiResponse, B as BasicAuthConfig, l as CrudRepoConfig, r as CrudRepoEntry, s as CrudRepoRegistry, n as FieldRole, L as ListResponseData, M as Middleware, u as OpenAPISpecOptions, o as QueryRequestBody, p as RepoFieldPath, q as RepoRelationKeys, U as UserFieldPath, t as generateOpenAPISpec } from '../../types-B5NdBY1Z.js';
3
4
  import 'zod';
4
5
  import 'firebase-admin/firestore';
5
6
 
@@ -177,7 +178,7 @@ import 'firebase-admin/firestore';
177
178
  */
178
179
  declare function createCrudServer<TRepos extends Record<string, ConfiguredRepository<any>>>(options: CrudServerOptions<TRepos>): ((req: any, res: any) => Promise<void>) & {
179
180
  spec: () => OpenAPIDocument;
180
- httpsOptions?: Record<string, unknown>;
181
+ httpsOptions?: HttpsOptions;
181
182
  };
182
183
 
183
184
  export { CrudServerOptions, OpenAPIDocument, createCrudServer };
@@ -1,4 +1,4 @@
1
- import {z}from'zod';import {Timestamp}from'firebase-admin/firestore';function he(e){let t=[],n=e.replace(/[.*+?^${}()|[\]\\]/g,r=>r===":"?r:`\\${r}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(r,i)=>(t.push(i),"([^/]+)"));return {pattern:new RegExp(`^${n}$`),paramNames:t}}function Re(e){let t=e.path??e.url??"/",n=t.indexOf("?");return n===-1?t:t.slice(0,n)}var U=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(t,n)=>{n.status(404).send("Not Found");};this.errorHandler=(t,n,r)=>{console.error("[MiniRouter]",t),r.status(500).send("Internal Server Error");};}use(t){return this.middlewares.push(t),this}get(t,n){return this.addRoute("GET",t,n)}post(t,n){return this.addRoute("POST",t,n)}put(t,n){return this.addRoute("PUT",t,n)}patch(t,n){return this.addRoute("PATCH",t,n)}delete(t,n){return this.addRoute("DELETE",t,n)}onNotFound(t){return this.notFoundHandler=t,this}onError(t){return this.errorHandler=t,this}addRoute(t,n,r){let{pattern:i,paramNames:c}=he(n);return this.routes.push({method:t.toUpperCase(),pattern:i,paramNames:c,handler:r}),this}async handle(t,n){let r=(t.method??"GET").toUpperCase(),i=Re(t),c=null,u={};for(let h of this.routes){if(h.method!==r)continue;let b=i.match(h.pattern);if(b){c=h,u={},h.paramNames.forEach((D,x)=>{u[D]=decodeURIComponent(b[x+1]??"");});break}}let m=Object.assign(t,{params:u}),p=c?c.handler:this.notFoundHandler;try{await this.runMiddlewareChain(m,n,p);}catch(h){this.errorHandler(h,t,n);}}async runMiddlewareChain(t,n,r){let i=0,c=async()=>{if(i<this.middlewares.length){let u=this.middlewares[i++];await u(t,n,c);}else await r(t,n);};await c();}};var ne="preserve";function re(){return ne}function be(e){return typeof e=="object"&&e!==null&&typeof e._seconds=="number"&&typeof e._nanoseconds=="number"}function se(e){if(e==null)return null;if(e instanceof Date)return Number.isNaN(e.getTime())?null:e;if(e instanceof Timestamp)return e.toDate();if(be(e))return new Date(e._seconds*1e3+Math.floor(e._nanoseconds/1e6));if(typeof e=="string"){let t=new Date(e);return Number.isNaN(t.getTime())?null:t}if(typeof e=="number"){let t=new Date(e);return Number.isNaN(t.getTime())?null:t}return null}function oe(e){return e}var Oe=new Set(["<","<=",">",">=","!="]),Pe=new Set(["array-contains","array-contains-any"]);function W(e){return e==="desc"?"DESCENDING":"ASCENDING"}function Ce(e){let t=e.split("/").filter(Boolean);return t[t.length-1]??e}function Ie(e,t,n,r,i){let c=[],u=new Set;for(let p of r)if(p.op==="=="||p.op==="in"||p.op==="not-in"){if(u.has(p.field))continue;u.add(p.field),c.push({fieldPath:p.field,order:"ASCENDING"});}for(let p of r)if(Pe.has(p.op)){if(u.has(p.field))continue;u.add(p.field),c.push({fieldPath:p.field,arrayConfig:"CONTAINS"});}for(let p of r)if(Oe.has(p.op)){if(u.has(p.field))continue;u.add(p.field);let h=i?.field===p.field?W(i.dir):"ASCENDING";c.push({fieldPath:p.field,order:h});}if(i&&!u.has(i.field)&&c.push({fieldPath:i.field,order:W(i.dir)}),c.length===1&&n)return Se(e,t,c[0]);let m=i&&c.some(p=>p.fieldPath===i.field)?W(i.dir):"ASCENDING";return c.push({fieldPath:"__name__",order:m}),xe(e,t,n,c)}function xe(e,t,n,r,i="(default)"){let c=`projects/${e}/databases/${i}/collectionGroups/${t}/indexes/_`,u=[...Q(1,c),...H(2,n?2:1)];for(let h of r)u.push(...ie(3,ae(h)));let m=i==="(default)"?"-default-":i,p=encodeURIComponent(ce(u));return `https://console.firebase.google.com/project/${e}/firestore/databases/${m}/indexes?create_composite=${p}`}function Ae(e){return e.match(/https:\/\/console\.firebase\.google\.com[^\s)"]*/)?.[0]}function Z(e){let t=[],n=e>>>0;for(;n>=128;)t.push(n&127|128),n>>>=7;return t.push(n&127),t}function L(e,t){return e<<3|t}function Q(e,t){let n=Array.from(new TextEncoder().encode(t));return [L(e,2),...Z(n.length),...n]}function H(e,t){return [L(e,0),...Z(t)]}function ie(e,t){return [L(e,2),...Z(t.length),...t]}function ae(e){let t=[...Q(1,e.fieldPath)];return e.arrayConfig==="CONTAINS"?t.push(...H(3,1)):t.push(...H(2,e.order==="DESCENDING"?2:1)),t}function ce(e){let t=String.fromCharCode(...e),n;if(typeof Buffer<"u")n=Buffer.from(e).toString("base64");else if(typeof btoa<"u")n=btoa(t);else throw new Error("No base64 encoder available");return n.replace(/=+$/,"")}function Se(e,t,n,r="(default)"){let i=`projects/${e}/databases/${r}/collectionGroups/${t}/fields/${n.fieldPath}`,c=[...Q(1,i),...H(2,2),...ie(3,ae(n))],u=r==="(default)"?"-default-":r,m=encodeURIComponent(ce(c));return `https://console.firebase.google.com/project/${e}/firestore/databases/${u}/indexes/automatic?create_exemption=${m}`}function De(e){let t=e,n=[t?.firestore?.projectId,t?.firestore?.app?.options?.projectId,t?.firestore?._settings?.projectId,t?.firestore?.databaseId?.projectId,t?._firestore?.projectId];for(let i of n)if(typeof i=="string"&&i.length>0)return i;return process.env.GCLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT||process.env.FIREBASE_PROJECT_ID||void 0}function ve(e){let t=e;return t?t.code===9?true:typeof t.message=="string"?t.message.includes("requires an index"):false:false}function de(e,t){let n=e??{},r=ve(e),i;if(r&&(i=n.message?Ae(n.message):void 0,!i)){let c=De(t.ref);if(c){let u=Ce(t.path);i=Ie(c,u,t.isGroup,t.filters,t.sort);}}return {type:r?"index":"error",message:r?"This query requires a composite index that does not exist yet.":n.message??"Query failed",indexUrl:i}}function V(e,t,n=200){let r=oe(t);e.status(n).set("Content-Type","application/json; charset=utf-8").send(JSON.stringify(r));}function q(e,t,n,r=200){V(e,{success:true,data:t,meta:n},r);}function A(e,t,n=400){V(e,{success:false,error:t},n);}function J(e,t,n,r,i){let c=de(t,n),u=c.type==="index",m=u?424:500,h={success:false,error:u?c.message:i&&t instanceof Error?t.message:r};u&&(h.errorType="index",c.indexUrl&&(h.indexUrl=c.indexUrl)),V(e,h,m);}var ue="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";function Ne(){let e="";for(let t=0;t<20;t++)e+=ue.charAt(Math.floor(Math.random()*ue.length));return e}function _(e){let t=e._def??e.def;if(!t)return e;let n=t.typeName??t.type;if(n==="ZodDate"||n==="date")return z.preprocess(r=>se(r)??r,e);if(n==="ZodObject"||n==="object"){let r=e.shape,i={};for(let[c,u]of Object.entries(r))i[c]=_(u);return z.object(i)}if(n==="ZodArray"||n==="array"){let r=t.element??t.type;if(r)return z.array(_(r))}if(n==="ZodOptional"||n==="optional"){let r=t.innerType;if(r)return _(r).optional()}if(n==="ZodNullable"||n==="nullable"){let r=t.innerType;if(r)return _(r).nullable()}if(n==="ZodDefault"||n==="default"){let r=t.innerType,i=t.defaultValue;if(r){let c=_(r);return typeof i=="function"?c.default(i()):c.default(i)}}return e}function je(e,t,n=[]){let r=e.shape,i={},c=t&&t.length>0?t:Object.keys(r);for(let u of c){if(n.includes(u))continue;let m=u.split(".")[0];m&&r[m]&&(i[m]=r[m]);}return z.object(i)}function pe(e,t,n,r=false,i=[]){try{let c=je(e,n,i),u=r?c.partial():c;return {success:!0,data:(re()==="normalize"?_(u):u).parse(t)}}catch(c){return c instanceof z.ZodError?{success:false,error:`Validation failed: ${c.issues.map(m=>`${m.path.join(".")}: ${m.message}`).join(", ")}`}:{success:false,error:"Validation failed"}}}function Ee(e,t){let n=[],r=t?new Set(t):null,i={eq:"==",ne:"!=",lt:"<",lte:"<=",gt:">",gte:">=",in:"in",nin:"not-in",contains:"array-contains",containsAny:"array-contains-any"};for(let[c,u]of Object.entries(e)){if(u===void 0||["cursor","limit","pageSize","orderBy","orderDir","select"].includes(c))continue;let m=Array.isArray(u)?u[0]:u;if(m===void 0||m==="")continue;let p=c.match(/^(\w+)__(\w+)$/),h,b="==";if(p&&p[1]&&p[2]){h=p[1];let x=p[2];if(i[x])b=i[x];else continue}else if(!p)h=c;else continue;if(r&&!r.has(h))continue;let D=m;b==="in"||b==="not-in"||b==="array-contains-any"?D=m.split(",").map(x=>le(x.trim())):D=le(m),n.push({field:h,op:b,value:D});}return n}function le(e){if(e==="true")return true;if(e==="false")return false;if(e==="null")return null;let t=Number(e);return !isNaN(t)&&e!==""?t:e}function K(e){return e?{docId:e.id}:null}async function fe(e,t){if(!t||typeof t!="object")return;let n=t.docId;if(typeof n=="string")try{let r=e.repo.ref;if(typeof r.doc!="function")return;let i=await r.doc(n).get();return i.exists?i:void 0}catch{return}}function ye(e,t,n){function r(g,l){return !g||!e[g]?(A(l,`Repository "${g}" not found`,404),null):e[g]}function i(g,l){if(!l)return;let y=g[l];if(typeof y!="string"||!y)return;let s=y.split("/").filter(Boolean),d=[];for(let a=1;a<s.length;a+=2)d.push(s[a]);return d.length>0?d:void 0}async function c(g,l){let y=`by${g.documentKey.charAt(0).toUpperCase()}${g.documentKey.slice(1)}`,s=g.repo.get[y];if(typeof s=="function")try{let a=await s(l);if(a)return a}catch{}return (await g.repo.query.by({where:[[g.documentKey,"==",l]],limit:1}))[0]??null}async function u(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;let d=[],a;try{let o=g.query??{},f=Math.min(Number(o.pageSize)||s.pageSize,100),w=o.cursor,R=o.direction?.toLowerCase()==="prev"?"prev":"next",C=o.orderBy,S=o.orderDir?.toLowerCase()==="desc"?"desc":"asc",O=o.select,P=O?O.split(",").map(E=>E.trim()):void 0,I;s.allowedIncludes&&o.includes&&(I=(typeof o.includes=="string"?o.includes.split(",").map(G=>G.trim()):Array.isArray(o.includes)?o.includes:[]).filter(G=>typeof G=="string"&&s.allowedIncludes.includes(G)),I?.length===0&&(I=void 0));let N=Ee(o,s.filterableFields);d=N.map(E=>({field:E.field,op:E.op,value:String(E.value??"")})),C&&(a={field:C,dir:S});let k={pageSize:f,direction:R};if(w)try{let E=typeof w=="string"?JSON.parse(w):w;k.cursor=await fe(s,E);}catch{}C&&(k.orderBy=[{field:C,direction:S}]),N.length>0&&(k.where=N.map(E=>[E.field,E.op,E.value])),P&&(k.select=P),I&&(k.include=I);let j=await s.repo.query.paginate(k),ge={items:j.data,hasNextPage:j.hasNextPage,hasPrevPage:j.hasPrevPage,nextCursor:K(j.nextCursor),prevCursor:K(j.prevCursor)};q(l,ge,{pageSize:f,hasMore:j.hasNextPage});}catch(o){J(l,o,{ref:s.repo.ref,path:s.path,isGroup:!!s.isGroup,filters:d,sort:a},"Failed to fetch documents",n);}}async function m(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;let d=[],a;try{let o=g.body??{},f=Math.min(o.pageSize||s.pageSize,100),w=o.direction==="prev"?"prev":"next";o.where&&(d=o.where.map(O=>({field:String(O[0]),op:O[1],value:String(O[2]??"")}))),o.orderBy&&o.orderBy[0]&&(a={field:o.orderBy[0].field,dir:o.orderBy[0].direction==="desc"?"desc":"asc"});let R={pageSize:f,direction:w};if(o.cursor)try{let O=typeof o.cursor=="string"?JSON.parse(o.cursor):o.cursor;R.cursor=await fe(s,O);}catch{}if(s.allowedIncludes&&o.includes&&o.includes.length>0){let O=o.includes.filter(P=>typeof P=="string"?s.allowedIncludes.includes(P):typeof P=="object"&&P!==null&&"relation"in P&&typeof P.relation=="string"?s.allowedIncludes.includes(P.relation):!1);O.length>0&&(R.include=O);}if(o.where&&o.where.length>0){if(s.filterableFields){let O=new Set(s.filterableFields),P=o.where.filter(I=>!O.has(I[0]));if(P.length>0){A(l,`Fields not filterable: ${P.map(I=>I[0]).join(", ")}`,400);return}}R.where=o.where;}if(o.orWhere&&o.orWhere.length>0){if(s.filterableFields){let O=new Set(s.filterableFields),P=o.orWhere.filter(I=>!O.has(I[0]));if(P.length>0){A(l,`Fields not filterable: ${P.map(I=>I[0]).join(", ")}`,400);return}}R.orWhere=o.orWhere;}if(o.orWhereGroups&&o.orWhereGroups.length>0){if(s.filterableFields){let O=new Set(s.filterableFields);for(let P of o.orWhereGroups){let I=P.filter(N=>!O.has(N[0]));if(I.length>0){A(l,`Fields not filterable: ${I.map(N=>N[0]).join(", ")}`,400);return}}}R.orWhereGroups=o.orWhereGroups;}o.orderBy&&o.orderBy.length>0&&(R.orderBy=o.orderBy),o.select&&o.select.length>0&&(R.select=o.select);let C=await s.repo.query.paginate(R),S={items:C.data,hasNextPage:C.hasNextPage,hasPrevPage:C.hasPrevPage,nextCursor:K(C.nextCursor),prevCursor:K(C.prevCursor)};q(l,S,{pageSize:f,hasMore:C.hasNextPage});}catch(o){J(l,o,{ref:s.repo.ref,path:s.path,isGroup:!!s.isGroup,filters:d,sort:a},"Failed to query documents",n);}}async function p(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;let d=y.id;if(!d){A(l,"Document ID required",400);return}try{let a=await c(s,d);if(!a){A(l,"Document not found",404);return}q(l,a);}catch(a){J(l,a,{ref:s.repo.ref,path:s.path,isGroup:!!s.isGroup,filters:[{field:s.documentKey,op:"==",value:d}]},"Failed to fetch document",n);}}async function h(g,l){let y=g.params||{},s=r(y.repoName,l);if(s)try{let d=g.body??{},a=pe(s.schema,d,s.createFields,!1,s.systemKeys);if(!a.success){A(l,a.error,400);return}if(s.validate){let f=await s.validate(a.data,"create");if(f){A(l,f,400);return}}let o;if(s.isGroup&&s.parentKeys&&s.parentKeys.length>0){let f={...a.data};s.createdKey&&(f[s.createdKey]=new Date);let w=s.parentKeys.filter(S=>!f[S]);if(w.length>0){A(l,`Missing parent key(s) for subcollection create: ${w.join(", ")}`,400);return}let R=s.parentKeys.map(S=>f[S]),C=f[s.documentKey]||Ne();o=await s.repo.set(...R,C,f);}else o=await s.repo.create(a.data);q(l,o,void 0,201);}catch(d){let a=n&&d instanceof Error?d.message:"Failed to create document";A(l,a,500);}}async function b(g,l,y){let s=g.params||{},d=r(s.repoName,l);if(!d)return;let a=s.id;if(!a){A(l,"Document ID required",400);return}try{let o=g.body??{},f=pe(d.schema,o,d.mutableFields,y,d.systemKeys);if(!f.success){A(l,f.error,400);return}if(d.validate){let S=await d.validate(f.data,"update");if(S){A(l,S,400);return}}let w=await c(d,a),R=(w&&i(w,d.pathKey))??[a],C=await d.repo.update(...R,f.data);q(l,C);}catch(o){let f=n&&o instanceof Error?o.message:"Failed to update document";A(l,f,500);}}async function D(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;if(!s.allowDelete){A(l,"Delete not allowed for this repository",403);return}let d=y.id;if(!d){A(l,"Document ID required",400);return}try{let a=await c(s,d),o=(a&&i(a,s.pathKey))??[d];await s.repo.delete(...o),q(l,{deleted:!0});}catch(a){let o=n&&a instanceof Error?a.message:"Failed to delete document";A(l,o,500);}}function x(g,l){l.status(204).set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, OPTIONS").set("Access-Control-Allow-Headers","Content-Type, Authorization").set("Access-Control-Max-Age","86400").send("");}return {handleList:u,handleQuery:m,handleGet:p,handleCreate:h,handleUpdate:b,handleDelete:D,handleOptions:x}}function Y(e){try{return z.toJSONSchema(e,{target:"openapi-3.1",unrepresentable:"any",override:t=>{let n=t.zodSchema?._zod?.def;n&&(n.type==="date"?(t.jsonSchema.type="string",t.jsonSchema.format="date-time"):n.type==="bigint"&&(t.jsonSchema.type="string",t.jsonSchema.format="int64"));}})}catch(t){return typeof console<"u"&&console.warn&&console.warn("[generateOpenAPISpec] Failed to convert Zod schema to JSON Schema; falling back to {type:object}.",t),{type:"object"}}}function $(e){return {$ref:`#/components/schemas/${e}`}}function v(e){return {description:e,content:{"application/json":{schema:$("ErrorResponse")}}}}function B(e,t){return {description:e,content:{"application/json":{schema:{type:"object",properties:{success:{type:"boolean",enum:[true]},data:t},required:["success","data"]}}}}}function me(e){return {description:"Paginated list of documents",content:{"application/json":{schema:{type:"object",properties:{success:{type:"boolean",enum:[true]},data:{type:"object",properties:{items:{type:"array",items:e},nextCursor:{oneOf:[{type:"object"},{type:"null"}]},prevCursor:{oneOf:[{type:"object"},{type:"null"}]},hasNextPage:{type:"boolean"},hasPrevPage:{type:"boolean"}},required:["items","hasNextPage","hasPrevPage"]},meta:{type:"object",properties:{pageSize:{type:"integer"},hasMore:{type:"boolean"},cursor:{oneOf:[{type:"string"},{type:"null"}]}}}},required:["success","data"]}}}}}function $e(e){return [{name:"pageSize",in:"query",schema:{type:"integer",default:e.pageSize,maximum:100},description:"Number of items per page"},{name:"cursor",in:"query",schema:{type:"string"},description:"Base64 pagination cursor"},{name:"orderBy",in:"query",schema:{type:"string"},description:"Field name to order by"},{name:"orderDir",in:"query",schema:{type:"string",enum:["asc","desc"]},description:"Order direction"},{name:"select",in:"query",schema:{type:"string"},description:"Comma-separated list of fields to return"}]}function Te(e){let t=e.filterableFields??Object.keys(e.schema.shape),n=["eq","ne","lt","lte","gt","gte","in","nin","contains"],r=[];for(let i of t){r.push({name:i,in:"query",schema:{type:"string"},description:`Filter by ${i} (equality)`});for(let c of n)r.push({name:`${i}__${c}`,in:"query",schema:{type:"string"},description:`Filter ${i} with operator ${c}`});}return r}function ke(){return {type:"object",properties:{where:{type:"array",items:{type:"array",items:{},minItems:3,maxItems:3},description:"AND conditions: [field, operator, value][]"},orWhere:{type:"array",items:{type:"array",items:{},minItems:3,maxItems:3},description:"Simple OR conditions (each independently OR'd)"},orWhereGroups:{type:"array",items:{type:"array",items:{type:"array",items:{},minItems:3,maxItems:3}},description:"Advanced OR groups (AND within, OR across groups)"},orderBy:{type:"array",items:{type:"object",properties:{field:{type:"string"},direction:{type:"string",enum:["asc","desc"]}},required:["field"]}},select:{type:"array",items:{type:"string"},description:"Fields to select (projection)"},pageSize:{type:"integer",maximum:100,description:"Number of items per page"},cursor:{oneOf:[{type:"string"},{type:"object"}],description:"Pagination cursor"},direction:{type:"string",enum:["next","prev"],description:"Pagination direction"},includes:{type:"array",items:{oneOf:[{type:"string"},{type:"object",properties:{relation:{type:"string"},select:{type:"array",items:{type:"string"}}},required:["relation"]}]},description:"Relations to include (populate)"}}}}function Fe(e,t,n,r,i){let c={},u=e.name,m=`${t}/${e.name}`,p=`${m}/{${e.documentKey}}`,h={name:e.documentKey,in:"path",required:true,schema:{type:"string"},description:"Unique document identifier"};c[m]={get:{operationId:`list${F(e.name)}`,summary:`List ${e.name} (paginated)`,tags:[u],parameters:[...$e(e),...Te(e)],responses:{200:me($(n)),500:v("Internal server error")}},post:{operationId:`create${F(e.name)}`,summary:`Create a ${T(e.name)}`,tags:[u],requestBody:{required:true,content:{"application/json":{schema:$(r??n)}}},responses:{201:B("Document created",$(n)),400:v("Validation error"),500:v("Internal server error")}}},c[`${m}/query`]={post:{operationId:`query${F(e.name)}`,summary:`Query ${e.name} with advanced filters`,tags:[u],requestBody:{required:true,content:{"application/json":{schema:$("QueryRequestBody")}}},responses:{200:me($(n)),400:v("Invalid query"),500:v("Internal server error")}}};let b={};return b.get={operationId:`get${F(T(e.name))}`,summary:`Get a single ${T(e.name)}`,tags:[u],parameters:[h],responses:{200:B("Document found",$(n)),404:v("Document not found"),500:v("Internal server error")}},b.put={operationId:`update${F(T(e.name))}`,summary:`Update a ${T(e.name)} (full replace)`,tags:[u],parameters:[h],requestBody:{required:true,content:{"application/json":{schema:$(i??n)}}},responses:{200:B("Document updated",$(n)),400:v("Validation error"),404:v("Document not found"),500:v("Internal server error")}},b.patch={operationId:`patch${F(T(e.name))}`,summary:`Partially update a ${T(e.name)}`,tags:[u],parameters:[h],requestBody:{required:true,content:{"application/json":{schema:{allOf:[$(i??n)],description:"All fields are optional for partial updates"}}}},responses:{200:B("Document patched",$(n)),400:v("Validation error"),404:v("Document not found"),500:v("Internal server error")}},e.allowDelete&&(b.delete={operationId:`delete${F(T(e.name))}`,summary:`Delete a ${T(e.name)}`,tags:[u],parameters:[h],responses:{200:B("Document deleted",{type:"object",properties:{id:{type:"string"}}}),404:v("Document not found"),500:v("Internal server error")}}),c[p]=b,c}function ee(e,t,n={}){let{title:r="CRUD API",version:i="1.0.0",description:c,servers:u,auth:m=false}=n,p=t==="/"?"":t.replace(/\/$/,""),h={},b={},D=[];h.ErrorResponse={type:"object",properties:{success:{type:"boolean",enum:[false]},error:{type:"string"}},required:["success","error"]},h.QueryRequestBody=ke();for(let[y,s]of Object.entries(e)){let d=F(T(y)),a=`${d}Create`,o=`${d}Update`;h[d]=Y(s.schema);let f=P=>{let I=P&&P.length>0?P:Object.keys(s.schema.shape),N={};for(let k of I){let j=k.split(".")[0];j&&s.schema.shape[j]&&!s.systemKeys.includes(j)&&(N[j]=s.schema.shape[j]);}return N},w=null,R=f(s.createFields);Object.keys(R).length>0&&(h[a]=Y(z.object(R)),w=a);let C=null,S=f(s.mutableFields);Object.keys(S).length>0&&(h[o]=Y(z.object(S)),C=o);let O=Fe(s,p,d,w,C);Object.assign(b,O),D.push({name:y,description:`Operations on ${y} (collection: ${s.path})`});}let x={},g;return m==="basic"?(x.basicAuth={type:"http",scheme:"basic"},g=[{basicAuth:[]}]):m==="bearer"&&(x.bearerAuth={type:"http",scheme:"bearer",bearerFormat:"JWT"},g=[{bearerAuth:[]}]),{openapi:"3.1.0",info:{title:r,version:i,...c?{description:c}:{}},...u&&u.length>0?{servers:u}:{},paths:b,components:{schemas:h,...Object.keys(x).length>0?{securitySchemes:x}:{}},...g?{security:g}:{},tags:D}}function F(e){return e.charAt(0).toUpperCase()+e.slice(1)}function T(e){return e.endsWith("ies")?e.slice(0,-3)+"y":e.endsWith("ses")||e.endsWith("xes")||e.endsWith("zes")?e.slice(0,-2):e.endsWith("s")&&!e.endsWith("ss")?e.slice(0,-1):e}function qe(e,t){return `<!DOCTYPE html>
1
+ import {z}from'zod';import {Timestamp}from'firebase-admin/firestore';function he(e){let t=[],n=e.replace(/[.*+?^${}()|[\]\\]/g,r=>r===":"?r:`\\${r}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(r,i)=>(t.push(i),"([^/]+)"));return {pattern:new RegExp(`^${n}$`),paramNames:t}}function Re(e){let t=e.path??e.url??"/",n=t.indexOf("?");return n===-1?t:t.slice(0,n)}var H=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(t,n)=>{n.status(404).send("Not Found");};this.errorHandler=(t,n,r)=>{console.error("[MiniRouter]",t),r.status(500).send("Internal Server Error");};}use(t){return this.middlewares.push(t),this}get(t,n){return this.addRoute("GET",t,n)}post(t,n){return this.addRoute("POST",t,n)}put(t,n){return this.addRoute("PUT",t,n)}patch(t,n){return this.addRoute("PATCH",t,n)}delete(t,n){return this.addRoute("DELETE",t,n)}onNotFound(t){return this.notFoundHandler=t,this}onError(t){return this.errorHandler=t,this}addRoute(t,n,r){let{pattern:i,paramNames:c}=he(n);return this.routes.push({method:t.toUpperCase(),pattern:i,paramNames:c,handler:r}),this}async handle(t,n){let r=(t.method??"GET").toUpperCase(),i=Re(t),c=null,u={};for(let h of this.routes){if(h.method!==r)continue;let b=i.match(h.pattern);if(b){c=h,u={},h.paramNames.forEach((D,x)=>{u[D]=decodeURIComponent(b[x+1]??"");});break}}let m=Object.assign(t,{params:u}),p=c?c.handler:this.notFoundHandler;try{await this.runMiddlewareChain(m,n,p);}catch(h){this.errorHandler(h,t,n);}}async runMiddlewareChain(t,n,r){let i=0,c=async()=>{if(i<this.middlewares.length){let u=this.middlewares[i++];await u(t,n,c);}else await r(t,n);};await c();}};var ne="preserve";function re(){return ne}function be(e){return typeof e=="object"&&e!==null&&typeof e._seconds=="number"&&typeof e._nanoseconds=="number"}function se(e){if(e==null)return null;if(e instanceof Date)return Number.isNaN(e.getTime())?null:e;if(e instanceof Timestamp)return e.toDate();if(be(e))return new Date(e._seconds*1e3+Math.floor(e._nanoseconds/1e6));if(typeof e=="string"){let t=new Date(e);return Number.isNaN(t.getTime())?null:t}if(typeof e=="number"){let t=new Date(e);return Number.isNaN(t.getTime())?null:t}return null}function oe(e){return e}var Oe=new Set(["<","<=",">",">=","!="]),Pe=new Set(["array-contains","array-contains-any"]);function W(e){return e==="desc"?"DESCENDING":"ASCENDING"}function Ce(e){let t=e.split("/").filter(Boolean);return t[t.length-1]??e}function Ie(e,t,n,r,i){let c=[],u=new Set;for(let p of r)if(p.op==="=="||p.op==="in"||p.op==="not-in"){if(u.has(p.field))continue;u.add(p.field),c.push({fieldPath:p.field,order:"ASCENDING"});}for(let p of r)if(Pe.has(p.op)){if(u.has(p.field))continue;u.add(p.field),c.push({fieldPath:p.field,arrayConfig:"CONTAINS"});}for(let p of r)if(Oe.has(p.op)){if(u.has(p.field))continue;u.add(p.field);let h=i?.field===p.field?W(i.dir):"ASCENDING";c.push({fieldPath:p.field,order:h});}if(i&&!u.has(i.field)&&c.push({fieldPath:i.field,order:W(i.dir)}),c.length===1&&n)return Se(e,t,c[0]);let m=i&&c.some(p=>p.fieldPath===i.field)?W(i.dir):"ASCENDING";return c.push({fieldPath:"__name__",order:m}),xe(e,t,n,c)}function xe(e,t,n,r,i="(default)"){let c=`projects/${e}/databases/${i}/collectionGroups/${t}/indexes/_`,u=[...Q(1,c),...U(2,n?2:1)];for(let h of r)u.push(...ie(3,ae(h)));let m=i==="(default)"?"-default-":i,p=encodeURIComponent(ce(u));return `https://console.firebase.google.com/project/${e}/firestore/databases/${m}/indexes?create_composite=${p}`}function Ae(e){return e.match(/https:\/\/console\.firebase\.google\.com[^\s)"]*/)?.[0]}function Z(e){let t=[],n=e>>>0;for(;n>=128;)t.push(n&127|128),n>>>=7;return t.push(n&127),t}function L(e,t){return e<<3|t}function Q(e,t){let n=Array.from(new TextEncoder().encode(t));return [L(e,2),...Z(n.length),...n]}function U(e,t){return [L(e,0),...Z(t)]}function ie(e,t){return [L(e,2),...Z(t.length),...t]}function ae(e){let t=[...Q(1,e.fieldPath)];return e.arrayConfig==="CONTAINS"?t.push(...U(3,1)):t.push(...U(2,e.order==="DESCENDING"?2:1)),t}function ce(e){let t=String.fromCharCode(...e),n;if(typeof Buffer<"u")n=Buffer.from(e).toString("base64");else if(typeof btoa<"u")n=btoa(t);else throw new Error("No base64 encoder available");return n.replace(/=+$/,"")}function Se(e,t,n,r="(default)"){let i=`projects/${e}/databases/${r}/collectionGroups/${t}/fields/${n.fieldPath}`,c=[...Q(1,i),...U(2,2),...ie(3,ae(n))],u=r==="(default)"?"-default-":r,m=encodeURIComponent(ce(c));return `https://console.firebase.google.com/project/${e}/firestore/databases/${u}/indexes/automatic?create_exemption=${m}`}function De(e){let t=e,n=[t?.firestore?.projectId,t?.firestore?.app?.options?.projectId,t?.firestore?._settings?.projectId,t?.firestore?.databaseId?.projectId,t?._firestore?.projectId];for(let i of n)if(typeof i=="string"&&i.length>0)return i;return process.env.GCLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT||process.env.FIREBASE_PROJECT_ID||void 0}function ve(e){let t=e;return t?t.code===9?true:typeof t.message=="string"?t.message.includes("requires an index"):false:false}function de(e,t){let n=e??{},r=ve(e),i;if(r&&(i=n.message?Ae(n.message):void 0,!i)){let c=De(t.ref);if(c){let u=Ce(t.path);i=Ie(c,u,t.isGroup,t.filters,t.sort);}}return {type:r?"index":"error",message:r?"This query requires a composite index that does not exist yet.":n.message??"Query failed",indexUrl:i}}function V(e,t,n=200){let r=oe(t);e.status(n).set("Content-Type","application/json; charset=utf-8").send(JSON.stringify(r));}function q(e,t,n,r=200){V(e,{success:true,data:t,meta:n},r);}function A(e,t,n=400){V(e,{success:false,error:t},n);}function J(e,t,n,r,i){let c=de(t,n),u=c.type==="index",m=u?424:500,h={success:false,error:u?c.message:i&&t instanceof Error?t.message:r};u&&(h.errorType="index",c.indexUrl&&(h.indexUrl=c.indexUrl)),V(e,h,m);}var ue="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";function Ne(){let e="";for(let t=0;t<20;t++)e+=ue.charAt(Math.floor(Math.random()*ue.length));return e}function _(e){let t=e._def??e.def;if(!t)return e;let n=t.typeName??t.type;if(n==="ZodDate"||n==="date")return z.preprocess(r=>se(r)??r,e);if(n==="ZodObject"||n==="object"){let r=e.shape,i={};for(let[c,u]of Object.entries(r))i[c]=_(u);return z.object(i)}if(n==="ZodArray"||n==="array"){let r=t.element??t.type;if(r)return z.array(_(r))}if(n==="ZodOptional"||n==="optional"){let r=t.innerType;if(r)return _(r).optional()}if(n==="ZodNullable"||n==="nullable"){let r=t.innerType;if(r)return _(r).nullable()}if(n==="ZodDefault"||n==="default"){let r=t.innerType,i=t.defaultValue;if(r){let c=_(r);return typeof i=="function"?c.default(i()):c.default(i)}}return e}function je(e,t,n=[]){let r=e.shape,i={},c=t&&t.length>0?t:Object.keys(r);for(let u of c){if(n.includes(u))continue;let m=u.split(".")[0];m&&r[m]&&(i[m]=r[m]);}return z.object(i)}function pe(e,t,n,r=false,i=[]){try{let c=je(e,n,i),u=r?c.partial():c;return {success:!0,data:(re()==="normalize"?_(u):u).parse(t)}}catch(c){return c instanceof z.ZodError?{success:false,error:`Validation failed: ${c.issues.map(m=>`${m.path.join(".")}: ${m.message}`).join(", ")}`}:{success:false,error:"Validation failed"}}}function Ee(e,t){let n=[],r=t?new Set(t):null,i={eq:"==",ne:"!=",lt:"<",lte:"<=",gt:">",gte:">=",in:"in",nin:"not-in",contains:"array-contains",containsAny:"array-contains-any"};for(let[c,u]of Object.entries(e)){if(u===void 0||["cursor","limit","pageSize","orderBy","orderDir","select"].includes(c))continue;let m=Array.isArray(u)?u[0]:u;if(m===void 0||m==="")continue;let p=c.match(/^(\w+)__(\w+)$/),h,b="==";if(p&&p[1]&&p[2]){h=p[1];let x=p[2];if(i[x])b=i[x];else continue}else if(!p)h=c;else continue;if(r&&!r.has(h))continue;let D=m;b==="in"||b==="not-in"||b==="array-contains-any"?D=m.split(",").map(x=>le(x.trim())):D=le(m),n.push({field:h,op:b,value:D});}return n}function le(e){if(e==="true")return true;if(e==="false")return false;if(e==="null")return null;let t=Number(e);return !isNaN(t)&&e!==""?t:e}function K(e){return e?{docId:e.id}:null}async function fe(e,t){if(!t||typeof t!="object")return;let n=t.docId;if(typeof n=="string")try{let r=e.repo.ref;if(typeof r.doc!="function")return;let i=await r.doc(n).get();return i.exists?i:void 0}catch{return}}function ye(e,t,n){function r(g,l){return !g||!e[g]?(A(l,`Repository "${g}" not found`,404),null):e[g]}function i(g,l){if(!l)return;let y=g[l];if(typeof y!="string"||!y)return;let s=y.split("/").filter(Boolean),d=[];for(let a=1;a<s.length;a+=2)d.push(s[a]);return d.length>0?d:void 0}async function c(g,l){let y=`by${g.documentKey.charAt(0).toUpperCase()}${g.documentKey.slice(1)}`,s=g.repo.get[y];if(typeof s=="function")try{let a=await s(l);if(a)return a}catch{}return (await g.repo.query.by({where:[[g.documentKey,"==",l]],limit:1}))[0]??null}async function u(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;let d=[],a;try{let o=g.query??{},f=Math.min(Number(o.pageSize)||s.pageSize,100),w=o.cursor,R=o.direction?.toLowerCase()==="prev"?"prev":"next",C=o.orderBy,S=o.orderDir?.toLowerCase()==="desc"?"desc":"asc",O=o.select,P=O?O.split(",").map(E=>E.trim()):void 0,I;s.allowedIncludes&&o.includes&&(I=(typeof o.includes=="string"?o.includes.split(",").map(G=>G.trim()):Array.isArray(o.includes)?o.includes:[]).filter(G=>typeof G=="string"&&s.allowedIncludes.includes(G)),I?.length===0&&(I=void 0));let N=Ee(o,s.filterableFields);d=N.map(E=>({field:E.field,op:E.op,value:String(E.value??"")})),C&&(a={field:C,dir:S});let k={pageSize:f,direction:R};if(w)try{let E=typeof w=="string"?JSON.parse(w):w;k.cursor=await fe(s,E);}catch{}C&&(k.orderBy=[{field:C,direction:S}]),N.length>0&&(k.where=N.map(E=>[E.field,E.op,E.value])),P&&(k.select=P),I&&(k.include=I);let j=await s.repo.query.paginate(k),ge={items:j.data,hasNextPage:j.hasNextPage,hasPrevPage:j.hasPrevPage,nextCursor:K(j.nextCursor),prevCursor:K(j.prevCursor)};q(l,ge,{pageSize:f,hasMore:j.hasNextPage});}catch(o){J(l,o,{ref:s.repo.ref,path:s.path,isGroup:!!s.isGroup,filters:d,sort:a},"Failed to fetch documents",n);}}async function m(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;let d=[],a;try{let o=g.body??{},f=Math.min(o.pageSize||s.pageSize,100),w=o.direction==="prev"?"prev":"next";o.where&&(d=o.where.map(O=>({field:String(O[0]),op:O[1],value:String(O[2]??"")}))),o.orderBy&&o.orderBy[0]&&(a={field:o.orderBy[0].field,dir:o.orderBy[0].direction==="desc"?"desc":"asc"});let R={pageSize:f,direction:w};if(o.cursor)try{let O=typeof o.cursor=="string"?JSON.parse(o.cursor):o.cursor;R.cursor=await fe(s,O);}catch{}if(s.allowedIncludes&&o.includes&&o.includes.length>0){let O=o.includes.filter(P=>typeof P=="string"?s.allowedIncludes.includes(P):typeof P=="object"&&P!==null&&"relation"in P&&typeof P.relation=="string"?s.allowedIncludes.includes(P.relation):!1);O.length>0&&(R.include=O);}if(o.where&&o.where.length>0){if(s.filterableFields){let O=new Set(s.filterableFields),P=o.where.filter(I=>!O.has(I[0]));if(P.length>0){A(l,`Fields not filterable: ${P.map(I=>I[0]).join(", ")}`,400);return}}R.where=o.where;}if(o.orWhere&&o.orWhere.length>0){if(s.filterableFields){let O=new Set(s.filterableFields),P=o.orWhere.filter(I=>!O.has(I[0]));if(P.length>0){A(l,`Fields not filterable: ${P.map(I=>I[0]).join(", ")}`,400);return}}R.orWhere=o.orWhere;}if(o.orWhereGroups&&o.orWhereGroups.length>0){if(s.filterableFields){let O=new Set(s.filterableFields);for(let P of o.orWhereGroups){let I=P.filter(N=>!O.has(N[0]));if(I.length>0){A(l,`Fields not filterable: ${I.map(N=>N[0]).join(", ")}`,400);return}}}R.orWhereGroups=o.orWhereGroups;}o.orderBy&&o.orderBy.length>0&&(R.orderBy=o.orderBy),o.select&&o.select.length>0&&(R.select=o.select);let C=await s.repo.query.paginate(R),S={items:C.data,hasNextPage:C.hasNextPage,hasPrevPage:C.hasPrevPage,nextCursor:K(C.nextCursor),prevCursor:K(C.prevCursor)};q(l,S,{pageSize:f,hasMore:C.hasNextPage});}catch(o){J(l,o,{ref:s.repo.ref,path:s.path,isGroup:!!s.isGroup,filters:d,sort:a},"Failed to query documents",n);}}async function p(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;let d=y.id;if(!d){A(l,"Document ID required",400);return}try{let a=await c(s,d);if(!a){A(l,"Document not found",404);return}q(l,a);}catch(a){J(l,a,{ref:s.repo.ref,path:s.path,isGroup:!!s.isGroup,filters:[{field:s.documentKey,op:"==",value:d}]},"Failed to fetch document",n);}}async function h(g,l){let y=g.params||{},s=r(y.repoName,l);if(s)try{let d=g.body??{},a=pe(s.schema,d,s.createFields,!1,s.systemKeys);if(!a.success){A(l,a.error,400);return}if(s.validate){let f=await s.validate(a.data,"create");if(f){A(l,f,400);return}}let o;if(s.isGroup&&s.parentKeys&&s.parentKeys.length>0){let f={...a.data};s.createdKey&&(f[s.createdKey]=new Date);let w=s.parentKeys.filter(S=>!f[S]);if(w.length>0){A(l,`Missing parent key(s) for subcollection create: ${w.join(", ")}`,400);return}let R=s.parentKeys.map(S=>f[S]),C=f[s.documentKey]||Ne();o=await s.repo.set(...R,C,f);}else o=await s.repo.create(a.data);q(l,o,void 0,201);}catch(d){let a=n&&d instanceof Error?d.message:"Failed to create document";A(l,a,500);}}async function b(g,l,y){let s=g.params||{},d=r(s.repoName,l);if(!d)return;let a=s.id;if(!a){A(l,"Document ID required",400);return}try{let o=g.body??{},f=pe(d.schema,o,d.mutableFields,y,d.systemKeys);if(!f.success){A(l,f.error,400);return}if(d.validate){let S=await d.validate(f.data,"update");if(S){A(l,S,400);return}}let w=await c(d,a),R=(w&&i(w,d.pathKey))??[a],C=await d.repo.update(...R,f.data);q(l,C);}catch(o){let f=n&&o instanceof Error?o.message:"Failed to update document";A(l,f,500);}}async function D(g,l){let y=g.params||{},s=r(y.repoName,l);if(!s)return;if(!s.allowDelete){A(l,"Delete not allowed for this repository",403);return}let d=y.id;if(!d){A(l,"Document ID required",400);return}try{let a=await c(s,d),o=(a&&i(a,s.pathKey))??[d];await s.repo.delete(...o),q(l,{deleted:!0});}catch(a){let o=n&&a instanceof Error?a.message:"Failed to delete document";A(l,o,500);}}function x(g,l){l.status(204).set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, OPTIONS").set("Access-Control-Allow-Headers","Content-Type, Authorization").set("Access-Control-Max-Age","86400").send("");}return {handleList:u,handleQuery:m,handleGet:p,handleCreate:h,handleUpdate:b,handleDelete:D,handleOptions:x}}function Y(e){try{return z.toJSONSchema(e,{target:"openapi-3.1",unrepresentable:"any",override:t=>{let n=t.zodSchema?._zod?.def;n&&(n.type==="date"?(t.jsonSchema.type="string",t.jsonSchema.format="date-time"):n.type==="bigint"&&(t.jsonSchema.type="string",t.jsonSchema.format="int64"));}})}catch(t){return typeof console<"u"&&console.warn&&console.warn("[generateOpenAPISpec] Failed to convert Zod schema to JSON Schema; falling back to {type:object}.",t),{type:"object"}}}function $(e){return {$ref:`#/components/schemas/${e}`}}function v(e){return {description:e,content:{"application/json":{schema:$("ErrorResponse")}}}}function B(e,t){return {description:e,content:{"application/json":{schema:{type:"object",properties:{success:{type:"boolean",enum:[true]},data:t},required:["success","data"]}}}}}function me(e){return {description:"Paginated list of documents",content:{"application/json":{schema:{type:"object",properties:{success:{type:"boolean",enum:[true]},data:{type:"object",properties:{items:{type:"array",items:e},nextCursor:{oneOf:[{type:"object"},{type:"null"}]},prevCursor:{oneOf:[{type:"object"},{type:"null"}]},hasNextPage:{type:"boolean"},hasPrevPage:{type:"boolean"}},required:["items","hasNextPage","hasPrevPage"]},meta:{type:"object",properties:{pageSize:{type:"integer"},hasMore:{type:"boolean"},cursor:{oneOf:[{type:"string"},{type:"null"}]}}}},required:["success","data"]}}}}}function $e(e){return [{name:"pageSize",in:"query",schema:{type:"integer",default:e.pageSize,maximum:100},description:"Number of items per page"},{name:"cursor",in:"query",schema:{type:"string"},description:"Base64 pagination cursor"},{name:"orderBy",in:"query",schema:{type:"string"},description:"Field name to order by"},{name:"orderDir",in:"query",schema:{type:"string",enum:["asc","desc"]},description:"Order direction"},{name:"select",in:"query",schema:{type:"string"},description:"Comma-separated list of fields to return"}]}function Te(e){let t=e.filterableFields??Object.keys(e.schema.shape),n=["eq","ne","lt","lte","gt","gte","in","nin","contains"],r=[];for(let i of t){r.push({name:i,in:"query",schema:{type:"string"},description:`Filter by ${i} (equality)`});for(let c of n)r.push({name:`${i}__${c}`,in:"query",schema:{type:"string"},description:`Filter ${i} with operator ${c}`});}return r}function ke(){return {type:"object",properties:{where:{type:"array",items:{type:"array",items:{},minItems:3,maxItems:3},description:"AND conditions: [field, operator, value][]"},orWhere:{type:"array",items:{type:"array",items:{},minItems:3,maxItems:3},description:"Simple OR conditions (each independently OR'd)"},orWhereGroups:{type:"array",items:{type:"array",items:{type:"array",items:{},minItems:3,maxItems:3}},description:"Advanced OR groups (AND within, OR across groups)"},orderBy:{type:"array",items:{type:"object",properties:{field:{type:"string"},direction:{type:"string",enum:["asc","desc"]}},required:["field"]}},select:{type:"array",items:{type:"string"},description:"Fields to select (projection)"},pageSize:{type:"integer",maximum:100,description:"Number of items per page"},cursor:{oneOf:[{type:"string"},{type:"object"}],description:"Pagination cursor"},direction:{type:"string",enum:["next","prev"],description:"Pagination direction"},includes:{type:"array",items:{oneOf:[{type:"string"},{type:"object",properties:{relation:{type:"string"},select:{type:"array",items:{type:"string"}}},required:["relation"]}]},description:"Relations to include (populate)"}}}}function Fe(e,t,n,r,i){let c={},u=e.name,m=`${t}/${e.name}`,p=`${m}/{${e.documentKey}}`,h={name:e.documentKey,in:"path",required:true,schema:{type:"string"},description:"Unique document identifier"};c[m]={get:{operationId:`list${F(e.name)}`,summary:`List ${e.name} (paginated)`,tags:[u],parameters:[...$e(e),...Te(e)],responses:{200:me($(n)),500:v("Internal server error")}},post:{operationId:`create${F(e.name)}`,summary:`Create a ${T(e.name)}`,tags:[u],requestBody:{required:true,content:{"application/json":{schema:$(r??n)}}},responses:{201:B("Document created",$(n)),400:v("Validation error"),500:v("Internal server error")}}},c[`${m}/query`]={post:{operationId:`query${F(e.name)}`,summary:`Query ${e.name} with advanced filters`,tags:[u],requestBody:{required:true,content:{"application/json":{schema:$("QueryRequestBody")}}},responses:{200:me($(n)),400:v("Invalid query"),500:v("Internal server error")}}};let b={};return b.get={operationId:`get${F(T(e.name))}`,summary:`Get a single ${T(e.name)}`,tags:[u],parameters:[h],responses:{200:B("Document found",$(n)),404:v("Document not found"),500:v("Internal server error")}},b.put={operationId:`update${F(T(e.name))}`,summary:`Update a ${T(e.name)} (full replace)`,tags:[u],parameters:[h],requestBody:{required:true,content:{"application/json":{schema:$(i??n)}}},responses:{200:B("Document updated",$(n)),400:v("Validation error"),404:v("Document not found"),500:v("Internal server error")}},b.patch={operationId:`patch${F(T(e.name))}`,summary:`Partially update a ${T(e.name)}`,tags:[u],parameters:[h],requestBody:{required:true,content:{"application/json":{schema:{allOf:[$(i??n)],description:"All fields are optional for partial updates"}}}},responses:{200:B("Document patched",$(n)),400:v("Validation error"),404:v("Document not found"),500:v("Internal server error")}},e.allowDelete&&(b.delete={operationId:`delete${F(T(e.name))}`,summary:`Delete a ${T(e.name)}`,tags:[u],parameters:[h],responses:{200:B("Document deleted",{type:"object",properties:{id:{type:"string"}}}),404:v("Document not found"),500:v("Internal server error")}}),c[p]=b,c}function ee(e,t,n={}){let{title:r="CRUD API",version:i="1.0.0",description:c,servers:u,auth:m=false}=n,p=t==="/"?"":t.replace(/\/$/,""),h={},b={},D=[];h.ErrorResponse={type:"object",properties:{success:{type:"boolean",enum:[false]},error:{type:"string"}},required:["success","error"]},h.QueryRequestBody=ke();for(let[y,s]of Object.entries(e)){let d=F(T(y)),a=`${d}Create`,o=`${d}Update`;h[d]=Y(s.schema);let f=P=>{let I=P&&P.length>0?P:Object.keys(s.schema.shape),N={};for(let k of I){let j=k.split(".")[0];j&&s.schema.shape[j]&&!s.systemKeys.includes(j)&&(N[j]=s.schema.shape[j]);}return N},w=null,R=f(s.createFields);Object.keys(R).length>0&&(h[a]=Y(z.object(R)),w=a);let C=null,S=f(s.mutableFields);Object.keys(S).length>0&&(h[o]=Y(z.object(S)),C=o);let O=Fe(s,p,d,w,C);Object.assign(b,O),D.push({name:y,description:`Operations on ${y} (collection: ${s.path})`});}let x={},g;return m==="basic"?(x.basicAuth={type:"http",scheme:"basic"},g=[{basicAuth:[]}]):m==="bearer"&&(x.bearerAuth={type:"http",scheme:"bearer",bearerFormat:"JWT"},g=[{bearerAuth:[]}]),{openapi:"3.1.0",info:{title:r,version:i,...c?{description:c}:{}},...u&&u.length>0?{servers:u}:{},paths:b,components:{schemas:h,...Object.keys(x).length>0?{securitySchemes:x}:{}},...g?{security:g}:{},tags:D}}function F(e){return e.charAt(0).toUpperCase()+e.slice(1)}function T(e){return e.endsWith("ies")?e.slice(0,-3)+"y":e.endsWith("ses")||e.endsWith("xes")||e.endsWith("zes")?e.slice(0,-2):e.endsWith("s")&&!e.endsWith("ss")?e.slice(0,-1):e}function qe(e,t){return `<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8" />
@@ -9,5 +9,5 @@ import {z}from'zod';import {Timestamp}from'firebase-admin/firestore';function he
9
9
  <script id="api-reference" data-url="${t}"></script>
10
10
  <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
11
11
  </body>
12
- </html>`}function _e(e,t){let n=t==="/"?"":t.replace(/\/$/,"");if(process.env.FUNCTIONS_EMULATOR==="true"){let c=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??"demo-project",u=process.env.FUNCTION_REGION??"us-central1",m=process.env.FUNCTION_TARGET??"";return `/${c}/${u}/${m}${n}`}let r=process.env.K_SERVICE,i=e?.hostname??e?.headers?.host??"";return r&&i.includes("cloudfunctions.net")?`/${r.toLowerCase()}${n}`:n}async function ze(e){return typeof e.rawBody=="string"?e.rawBody:Buffer.isBuffer(e.rawBody)?e.rawBody.toString("utf8"):""}function et(e){let{basePath:t="/",repos:n,parseBody:r=true,auth:i,middleware:c=[],verbose:u=false,httpsOptions:m}=e,p=t==="/"?"":t.replace(/\/$/,""),h={};for(let[d,a]of Object.entries(n)){let o=a.schema??a.repo.schema??null;if(!o)throw new Error(`[createCrudServer] Repository "${d}" has no Zod schema. Either use createRepositoryConfig(schema)(config) or pass schema: explicitly.`);let f,w,R;if(a.fieldsConfig){let O=a.fieldsConfig;f=[],w=[],R=[];for(let[P,I]of Object.entries(O))for(let N of I)N==="filterable"?f.push(P):N==="mutable"?w.push(P):N==="create"&&R.push(P);f.length===0&&(f=void 0),w.length===0&&(w=void 0),R.length===0&&(R=void 0);}let C=(()=>{let O=a.repo._parentKeys;return O&&O.length>0?O:void 0})();if(C&&R)for(let O of C)R.includes(O)||R.push(O);let S={name:d,path:a.path,repo:a.repo,schema:o,systemKeys:a.repo._systemKeys??[a.documentKey??"docId"],documentKey:a.documentKey??"docId",pathKey:a.repo._pathKey??void 0,isGroup:!!a.repo._isGroup,parentKeys:C,createdKey:a.repo._createdKey??void 0,pageSize:a.pageSize??25,filterableFields:f,mutableFields:w,createFields:R,allowDelete:a.allowDelete??false,allowedIncludes:a.allowedIncludes,validate:a.validate};h[d]=S;}let b=ye(h,p,u),D=e.openapi,x=D&&typeof D=="object"?D:{},g=null;function l(){if(!g){let d=i&&typeof i!="function"?"basic":i?"bearer":false;g=ee(h,p,{...x,auth:x.auth??d});}return g}let y=new U;if(y.use((d,a,o)=>{a.set("Access-Control-Allow-Origin","*"),a.set("Access-Control-Allow-Credentials","true"),o();}),r&&y.use(async(d,a,o)=>{let f=d;if(String(f.headers?.["content-type"]??"").includes("application/json")){if(typeof f.body=="string")try{d.body=JSON.parse(f.body);}catch{}else if(Buffer.isBuffer(d.rawBody))try{let R=await ze(f);d.body=JSON.parse(R);}catch{}}await o();}),i)if(typeof i=="function")y.use(i);else {let d=i.realm??"API",a="Basic "+Buffer.from(`${i.username}:${i.password}`).toString("base64");y.use((o,f,w)=>{if((o.headers?.authorization??"")!==a){f.status(401).set("WWW-Authenticate",`Basic realm="${d}"`).set("Content-Type","application/json").send(JSON.stringify({success:false,error:"Unauthorized"}));return}w();});}for(let d of c)y.use(d);if(D!==false){let d=`${p}/__spec.json`,a=`${p}/__docs`;y.get(d,(o,f)=>{let w=l();f.status(200).set("Content-Type","application/json; charset=utf-8").send(JSON.stringify(w,null,2));}),y.get(a,(o,f)=>{let w=_e(o,p)+"/__spec.json",R=qe(x.title??"CRUD API",w);f.status(200).set("Content-Type","text/html; charset=utf-8").send(R);});}y.use((d,a,o)=>{if(d.method==="OPTIONS"){b.handleOptions(d,a);return}o();}),y.get(`${p}/:repoName`,b.handleList),y.post(`${p}/:repoName/query`,b.handleQuery),y.get(`${p}/:repoName/:id`,b.handleGet),y.post(`${p}/:repoName`,b.handleCreate),y.put(`${p}/:repoName/:id`,(d,a)=>b.handleUpdate(d,a,false)),y.patch(`${p}/:repoName/:id`,(d,a)=>b.handleUpdate(d,a,true)),y.delete(`${p}/:repoName/:id`,b.handleDelete);let s=async(d,a)=>{await y.handle(d,a);};return s.spec=l,m&&(s.httpsOptions=m),s}export{et as createCrudServer,ee as generateOpenAPISpec};//# sourceMappingURL=index.js.map
12
+ </html>`}function _e(e,t){let n=t==="/"?"":t.replace(/\/$/,"");if(process.env.FUNCTIONS_EMULATOR==="true"){let c=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??"demo-project",u=process.env.FUNCTION_REGION??"us-central1",m=process.env.FUNCTION_TARGET??"";return `/${c}/${u}/${m}${n}`}let r=process.env.K_SERVICE,i=e?.hostname??e?.headers?.host??"";return r&&i.includes("cloudfunctions.net")?`/${r.toLowerCase()}${n}`:n}async function ze(e){return typeof e.rawBody=="string"?e.rawBody:Buffer.isBuffer(e.rawBody)?e.rawBody.toString("utf8"):""}function et(e){let{basePath:t="/",repos:n,parseBody:r=true,auth:i,middleware:c=[],verbose:u=false,httpsOptions:m}=e,p=t==="/"?"":t.replace(/\/$/,""),h={};for(let[d,a]of Object.entries(n)){let o=a.schema??a.repo.schema??null;if(!o)throw new Error(`[createCrudServer] Repository "${d}" has no Zod schema. Either use createRepositoryConfig(schema)(config) or pass schema: explicitly.`);let f,w,R;if(a.fieldsConfig){let O=a.fieldsConfig;f=[],w=[],R=[];for(let[P,I]of Object.entries(O))for(let N of I)N==="filterable"?f.push(P):N==="mutable"?w.push(P):N==="create"&&R.push(P);f.length===0&&(f=void 0),w.length===0&&(w=void 0),R.length===0&&(R=void 0);}let C=(()=>{let O=a.repo._parentKeys;return O&&O.length>0?O:void 0})();if(C&&R)for(let O of C)R.includes(O)||R.push(O);let S={name:d,path:a.path,repo:a.repo,schema:o,systemKeys:a.repo._systemKeys??[a.documentKey??"docId"],documentKey:a.documentKey??"docId",pathKey:a.repo._pathKey??void 0,isGroup:!!a.repo._isGroup,parentKeys:C,createdKey:a.repo._createdKey??void 0,pageSize:a.pageSize??25,filterableFields:f,mutableFields:w,createFields:R,allowDelete:a.allowDelete??false,allowedIncludes:a.allowedIncludes,validate:a.validate};h[d]=S;}let b=ye(h,p,u),D=e.openapi,x=D&&typeof D=="object"?D:{},g=null;function l(){if(!g){let d=i&&typeof i!="function"?"basic":i?"bearer":false;g=ee(h,p,{...x,auth:x.auth??d});}return g}let y=new H;if(y.use((d,a,o)=>{a.set("Access-Control-Allow-Origin","*"),a.set("Access-Control-Allow-Credentials","true"),o();}),r&&y.use(async(d,a,o)=>{let f=d;if(String(f.headers?.["content-type"]??"").includes("application/json")){if(typeof f.body=="string")try{d.body=JSON.parse(f.body);}catch{}else if(Buffer.isBuffer(d.rawBody))try{let R=await ze(f);d.body=JSON.parse(R);}catch{}}await o();}),i)if(typeof i=="function")y.use(i);else {let d=i.realm??"API",a="Basic "+Buffer.from(`${i.username}:${i.password}`).toString("base64");y.use((o,f,w)=>{if((o.headers?.authorization??"")!==a){f.status(401).set("WWW-Authenticate",`Basic realm="${d}"`).set("Content-Type","application/json").send(JSON.stringify({success:false,error:"Unauthorized"}));return}w();});}for(let d of c)y.use(d);if(D!==false){let d=`${p}/__spec.json`,a=`${p}/__docs`;y.get(d,(o,f)=>{let w=l();f.status(200).set("Content-Type","application/json; charset=utf-8").send(JSON.stringify(w,null,2));}),y.get(a,(o,f)=>{let w=_e(o,p)+"/__spec.json",R=qe(x.title??"CRUD API",w);f.status(200).set("Content-Type","text/html; charset=utf-8").send(R);});}y.use((d,a,o)=>{if(d.method==="OPTIONS"){b.handleOptions(d,a);return}o();}),y.get(`${p}/:repoName`,b.handleList),y.post(`${p}/:repoName/query`,b.handleQuery),y.get(`${p}/:repoName/:id`,b.handleGet),y.post(`${p}/:repoName`,b.handleCreate),y.put(`${p}/:repoName/:id`,(d,a)=>b.handleUpdate(d,a,false)),y.patch(`${p}/:repoName/:id`,(d,a)=>b.handleUpdate(d,a,true)),y.delete(`${p}/:repoName/:id`,b.handleDelete);let s=async(d,a)=>{await y.handle(d,a);};return s.spec=l,m&&(s.httpsOptions=m),s}export{et as createCrudServer,ee as generateOpenAPISpec};//# sourceMappingURL=index.js.map
13
13
  //# sourceMappingURL=index.js.map