@lpdjs/firestore-repo-service 2.6.2 → 2.6.3
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 +38 -14
- package/dist/servers/hono/cli.cjs +103 -79
- package/dist/servers/hono/cli.cjs.map +1 -1
- package/dist/servers/hono/cli.js +103 -79
- package/dist/servers/hono/cli.js.map +1 -1
- package/dist/servers/hono/index.cjs +3 -3
- package/dist/servers/hono/index.cjs.map +1 -1
- package/dist/servers/hono/index.d.cts +175 -16
- package/dist/servers/hono/index.d.ts +175 -16
- package/dist/servers/hono/index.js +3 -3
- package/dist/servers/hono/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -26,14 +26,14 @@ import { z, ZodError } from 'zod';
|
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
28
|
* ```ts
|
|
29
|
-
* // src/services.ts
|
|
29
|
+
* // src/services.ts — infrastructure singletons only (no useCases here)
|
|
30
30
|
* import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
31
31
|
* import { PostRepo } from "./domains/posts/PostRepo.js";
|
|
32
|
-
* import {
|
|
32
|
+
* import { Mailer } from "./services/Mailer.js";
|
|
33
33
|
*
|
|
34
34
|
* export const services = createServices({
|
|
35
|
-
* postRepo: PostRepo,
|
|
36
|
-
*
|
|
35
|
+
* postRepo: PostRepo, // class form (auto-injected)
|
|
36
|
+
* mailer: ({ ctx }) => new Mailer(ctx), // factory form
|
|
37
37
|
* });
|
|
38
38
|
*
|
|
39
39
|
* export type Services = typeof services;
|
|
@@ -51,15 +51,21 @@ import { z, ZodError } from 'zod';
|
|
|
51
51
|
*
|
|
52
52
|
* @example
|
|
53
53
|
* ```ts
|
|
54
|
-
* // Inside a useCase
|
|
54
|
+
* // Inside a useCase — extends the UseCase base, owns its Zod schemas
|
|
55
|
+
* import { z } from "zod";
|
|
56
|
+
* import { UseCase } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
55
57
|
* import type { Services } from "../../services.js";
|
|
56
58
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* const input = z.object({ title: z.string() });
|
|
60
|
+
* const output = z.object({ id: z.string() });
|
|
61
|
+
*
|
|
62
|
+
* export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {
|
|
63
|
+
* static readonly input = input;
|
|
64
|
+
* static readonly output = output;
|
|
59
65
|
*
|
|
60
|
-
* async execute(
|
|
66
|
+
* async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {
|
|
61
67
|
* const user = this.services.ctx.c.get("user");
|
|
62
|
-
* return this.services.postRepo.create({ ...
|
|
68
|
+
* return this.services.postRepo.create({ ...payload, authorId: user.id });
|
|
63
69
|
* }
|
|
64
70
|
* }
|
|
65
71
|
* ```
|
|
@@ -169,14 +175,14 @@ type ServicesContainer<TMap> = TMap;
|
|
|
169
175
|
*
|
|
170
176
|
* @example Class form (zero-boilerplate auto-injection)
|
|
171
177
|
* ```ts
|
|
172
|
-
* class
|
|
178
|
+
* class RepositoryService {
|
|
173
179
|
* constructor(private readonly services: Services) {}
|
|
174
|
-
*
|
|
180
|
+
* get posts() { return this.services.db.posts; }
|
|
175
181
|
* }
|
|
176
182
|
*
|
|
177
183
|
* createServices({
|
|
178
|
-
*
|
|
179
|
-
*
|
|
184
|
+
* db: () => getFirestore(), // ← factory
|
|
185
|
+
* repository: RepositoryService, // ← bare class (deps auto-injected)
|
|
180
186
|
* });
|
|
181
187
|
* ```
|
|
182
188
|
*/
|
|
@@ -268,9 +274,51 @@ type AnyRouteDef = RouteDef<any, any>;
|
|
|
268
274
|
* What `routes.ts` can default-export:
|
|
269
275
|
* - a single {@link RouteDef} (most common case),
|
|
270
276
|
* - or an array of {@link RouteDef} (e.g. expose the same useCase on
|
|
271
|
-
* multiple `api` tags or under multiple paths).
|
|
277
|
+
* multiple `api` tags or under multiple paths). `readonly` arrays are
|
|
278
|
+
* accepted so that tuple-preserving helpers like {@link defineRoutes} (or a
|
|
279
|
+
* plain `as const` array) can be default-exported directly.
|
|
280
|
+
*/
|
|
281
|
+
type RouteModuleDefault = AnyRouteDef | readonly AnyRouteDef[];
|
|
282
|
+
/**
|
|
283
|
+
* Identity helper that **preserves the tuple type** of a `routes.ts` default
|
|
284
|
+
* export. A plain array literal (`export default [a, b]`) collapses every
|
|
285
|
+
* element to a single common type, which would lose each route's individual
|
|
286
|
+
* schema. Wrapping the array with `defineRoutes([...])` keeps the per-route
|
|
287
|
+
* types intact so {@link RouteInput} / {@link RouteOutput} can aggregate them
|
|
288
|
+
* into a union.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* export default defineRoutes([
|
|
292
|
+
* defineRoute({ ... }),
|
|
293
|
+
* defineRoute({ ... }),
|
|
294
|
+
* ]);
|
|
295
|
+
*/
|
|
296
|
+
declare function defineRoutes<const T extends readonly AnyRouteDef[]>(routes: T): T;
|
|
297
|
+
/**
|
|
298
|
+
* Normalizes a `routes.ts` default export — a single {@link RouteDef} or an
|
|
299
|
+
* array of them — to a *union* of its individual route members.
|
|
272
300
|
*/
|
|
273
|
-
type
|
|
301
|
+
type RoutesUnion<T> = T extends readonly (infer R)[] ? R : T;
|
|
302
|
+
type InferRouteInput<R> = R extends {
|
|
303
|
+
input?: infer I;
|
|
304
|
+
} ? I extends z.ZodTypeAny ? z.infer<I> : void : void;
|
|
305
|
+
type InferRouteOutput<R> = R extends {
|
|
306
|
+
output?: infer O;
|
|
307
|
+
} ? O extends z.ZodTypeAny ? z.infer<O> : unknown : unknown;
|
|
308
|
+
/**
|
|
309
|
+
* Union of validated request payloads across every route in a `routes.ts`
|
|
310
|
+
* module. Use it to type a useCase input without duplicating the Zod schema:
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* type Routes = typeof import("./routes.js").default;
|
|
314
|
+
* export type CreatePostUseCaseInput = RouteInput<Routes>;
|
|
315
|
+
*/
|
|
316
|
+
type RouteInput<T> = InferRouteInput<RoutesUnion<T>>;
|
|
317
|
+
/**
|
|
318
|
+
* Union of success-response payloads across every route in a `routes.ts`
|
|
319
|
+
* module. Mirror of {@link RouteInput} for the useCase output type.
|
|
320
|
+
*/
|
|
321
|
+
type RouteOutput<T> = InferRouteOutput<RoutesUnion<T>>;
|
|
274
322
|
/**
|
|
275
323
|
* Thrown by the server when the incoming request fails Zod validation.
|
|
276
324
|
* Caught by the {@link RouteInterceptor} (if any) so users can shape the
|
|
@@ -447,6 +495,102 @@ declare class HonoServer<TEnv extends Env = Env> {
|
|
|
447
495
|
private mountOpenApi;
|
|
448
496
|
}
|
|
449
497
|
|
|
498
|
+
/**
|
|
499
|
+
* useCase ⇆ route bridge.
|
|
500
|
+
*
|
|
501
|
+
* A useCase owns its Zod `input` / `output` schemas (declared as `static`
|
|
502
|
+
* members) and the business logic in {@link UseCase.execute}. Routes never
|
|
503
|
+
* re-declare those schemas: they wire a useCase into an HTTP endpoint with the
|
|
504
|
+
* one-liner {@link ApiRegistry.useCaseRoute} (or the standalone
|
|
505
|
+
* {@link useCaseRoute}), keeping `routes.ts` flat and readable while the
|
|
506
|
+
* types can never drift from the schemas.
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```ts
|
|
510
|
+
* // useCase.ts — single source of truth for the I/O shape
|
|
511
|
+
* import { z } from "zod";
|
|
512
|
+
* import { UseCase } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
513
|
+
* import type { Services } from "../../../../services.js";
|
|
514
|
+
*
|
|
515
|
+
* const input = z.object({ example: z.string() });
|
|
516
|
+
* const output = z.object({ id: z.string(), warning: z.string().nullable() });
|
|
517
|
+
*
|
|
518
|
+
* export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {
|
|
519
|
+
* static readonly input = input;
|
|
520
|
+
* static readonly output = output;
|
|
521
|
+
*
|
|
522
|
+
* async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {
|
|
523
|
+
* return { id: payload.example, warning: null };
|
|
524
|
+
* }
|
|
525
|
+
* }
|
|
526
|
+
*
|
|
527
|
+
* // routes.ts — one line per route, `api` stays typed
|
|
528
|
+
* export default defineRoutes([
|
|
529
|
+
* useCaseRoute(CreatePostUseCase, { api: "v1", method: "post", tags: ["posts"] }),
|
|
530
|
+
* ]);
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Base class for every useCase — pure business logic, no HTTP awareness.
|
|
536
|
+
* The shared {@link AnyServicesContainer} is injected via the constructor; the
|
|
537
|
+
* `input` / `output` Zod schemas are declared as `static` members on the
|
|
538
|
+
* concrete subclass (see {@link UseCaseClass}).
|
|
539
|
+
*
|
|
540
|
+
* @typeParam TInput Zod schema of the validated request payload.
|
|
541
|
+
* @typeParam TOutput Zod schema of the success response.
|
|
542
|
+
* @typeParam TServices Concrete services container injected into the useCase.
|
|
543
|
+
*/
|
|
544
|
+
declare abstract class UseCase<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny, TServices extends AnyServicesContainer = AnyServicesContainer> {
|
|
545
|
+
protected readonly services: TServices;
|
|
546
|
+
constructor(services: TServices);
|
|
547
|
+
/** Run the business logic. Input is already validated against the schema. */
|
|
548
|
+
abstract execute(input: z.infer<TInput>): Promise<z.infer<TOutput>>;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Structural type of a concrete {@link UseCase} subclass — i.e. a constructor
|
|
552
|
+
* that also exposes the `static input` / `static output` schemas. Consumed by
|
|
553
|
+
* {@link useCaseRoute} to derive the route's Zod schemas and the handler types.
|
|
554
|
+
*/
|
|
555
|
+
interface UseCaseClass<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, TServices extends AnyServicesContainer = AnyServicesContainer> {
|
|
556
|
+
readonly input: TInput;
|
|
557
|
+
readonly output: TOutput;
|
|
558
|
+
new (services: TServices): UseCase<TInput, TOutput, TServices>;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* HTTP metadata for {@link useCaseRoute} — everything a route needs **except**
|
|
562
|
+
* the input/output schemas and the handler, which are derived from the useCase.
|
|
563
|
+
*/
|
|
564
|
+
interface UseCaseRouteMeta<TApi extends string = string> {
|
|
565
|
+
/** API tag the route is mounted under. */
|
|
566
|
+
api: TApi;
|
|
567
|
+
/** HTTP method. */
|
|
568
|
+
method: HttpMethod;
|
|
569
|
+
/** URL path. Defaults to the codegen-derived path when omitted. */
|
|
570
|
+
path?: string;
|
|
571
|
+
/** Where the payload comes from. Defaults per method (see {@link RouteDef}). */
|
|
572
|
+
source?: PayloadSource;
|
|
573
|
+
/** Success status code. Default: 200. */
|
|
574
|
+
status?: number;
|
|
575
|
+
summary?: string;
|
|
576
|
+
description?: string;
|
|
577
|
+
tags?: string[];
|
|
578
|
+
deprecated?: boolean;
|
|
579
|
+
security?: Array<Record<string, string[]>>;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Build a {@link RouteDef} from a useCase class and HTTP metadata. The route's
|
|
583
|
+
* `input` / `output` schemas are read from the useCase's `static` members and
|
|
584
|
+
* the handler instantiates the useCase with the request `services` and runs
|
|
585
|
+
* {@link UseCase.execute}.
|
|
586
|
+
*
|
|
587
|
+
* Prefer the registry-bound `apis.useCaseRoute` (returned by
|
|
588
|
+
* `createApiRegistry`) so that `meta.api` is narrowed to the registered tags.
|
|
589
|
+
*/
|
|
590
|
+
declare function useCaseRoute<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, TServices extends AnyServicesContainer, TApi extends string = string>(useCaseClass: UseCaseClass<TInput, TOutput, TServices>, meta: UseCaseRouteMeta<TApi>): RouteDef<TInput, TOutput> & {
|
|
591
|
+
api: TApi;
|
|
592
|
+
};
|
|
593
|
+
|
|
450
594
|
/**
|
|
451
595
|
* Typed multi-API registry.
|
|
452
596
|
*
|
|
@@ -475,6 +619,7 @@ declare class HonoServer<TEnv extends Env = Env> {
|
|
|
475
619
|
*
|
|
476
620
|
* // Use in routes — `api` is now typed "v1" | "webhooks".
|
|
477
621
|
* export const defineRoute = apis.defineRoute;
|
|
622
|
+
* export const useCaseRoute = apis.useCaseRoute;
|
|
478
623
|
*
|
|
479
624
|
* // index.ts (Cloud Functions entrypoint)
|
|
480
625
|
* import { onRequest } from "firebase-functions/v2/https";
|
|
@@ -527,6 +672,20 @@ interface ApiRegistry<TMap extends ApiConfigMap, TServices extends AnyServicesCo
|
|
|
527
672
|
}): RouteDef<TIn, TOut> & {
|
|
528
673
|
api: keyof TMap & string;
|
|
529
674
|
};
|
|
675
|
+
/**
|
|
676
|
+
* Typed `useCaseRoute` — wires a {@link UseCase} class into a route in one
|
|
677
|
+
* line. The route's `input` / `output` schemas are read from the useCase's
|
|
678
|
+
* `static` members and the handler instantiates it with the request
|
|
679
|
+
* `services`. The `api` field is constrained to `keyof TMap`.
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* export default defineRoutes([
|
|
683
|
+
* useCaseRoute(CreatePostUseCase, { api: "v1", method: "post", tags: ["posts"] }),
|
|
684
|
+
* ]);
|
|
685
|
+
*/
|
|
686
|
+
useCaseRoute<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>(useCaseClass: UseCaseClass<TIn, TOut, TServices>, meta: UseCaseRouteMeta<keyof TMap & string>): RouteDef<TIn, TOut> & {
|
|
687
|
+
api: keyof TMap & string;
|
|
688
|
+
};
|
|
530
689
|
/**
|
|
531
690
|
* Build one Cloud Function per registered API and return them as a map
|
|
532
691
|
* keyed by API tag — spread it directly into your `index.ts` exports.
|
|
@@ -669,4 +828,4 @@ declare function generateRoutesManifest(routes: ScannedRoute[], opts: GeneratorO
|
|
|
669
828
|
/** Convenience helper used by the CLI — combines scan + generate in one call. */
|
|
670
829
|
declare function generateFromRoot(rootAbs: string, outFileRel: string, derive: PathDeriveOptions, importExtension: string, scan: (root: string) => ScannedRoute[]): GenerationResult;
|
|
671
830
|
|
|
672
|
-
export { type AnyRouteDef, type AnyServicesContainer, type ApiConfig, type ApiConfigMap, type ApiRegistry, type ApiRegistryOptions, DEFAULT_DERIVE, DEFAULT_GENERATOR_BANNER, DEFAULT_SCANNER, type GenerationResult, type GeneratorOptions, HonoServer, type HonoServerOptions, type HttpMethod, type OpenAPIConfig, type OpenAPIInfo, type PathDeriveOptions, type PayloadSource, type RequestContext, type RouteDef, type RouteHandler, type RouteInterceptor, type RouteModuleDefault, type ScannedRoute, type ScannerOptions, type ServiceProvider, type ServiceProviderMap, type ServicesContainer, type ServicesOf, ValidationError, buildOpenApiDocument, createApiRegistry, createRequestContextMiddleware, createServices, derivePath, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier, withRequestContext };
|
|
831
|
+
export { type AnyRouteDef, type AnyServicesContainer, type ApiConfig, type ApiConfigMap, type ApiRegistry, type ApiRegistryOptions, DEFAULT_DERIVE, DEFAULT_GENERATOR_BANNER, DEFAULT_SCANNER, type GenerationResult, type GeneratorOptions, HonoServer, type HonoServerOptions, type HttpMethod, type OpenAPIConfig, type OpenAPIInfo, type PathDeriveOptions, type PayloadSource, type RequestContext, type RouteDef, type RouteHandler, type RouteInput, type RouteInterceptor, type RouteModuleDefault, type RouteOutput, type RoutesUnion, type ScannedRoute, type ScannerOptions, type ServiceProvider, type ServiceProviderMap, type ServicesContainer, type ServicesOf, UseCase, type UseCaseClass, type UseCaseRouteMeta, ValidationError, buildOpenApiDocument, createApiRegistry, createRequestContextMiddleware, createServices, defineRoutes, derivePath, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier, useCaseRoute, withRequestContext };
|
|
@@ -26,14 +26,14 @@ import { z, ZodError } from 'zod';
|
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
28
|
* ```ts
|
|
29
|
-
* // src/services.ts
|
|
29
|
+
* // src/services.ts — infrastructure singletons only (no useCases here)
|
|
30
30
|
* import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
31
31
|
* import { PostRepo } from "./domains/posts/PostRepo.js";
|
|
32
|
-
* import {
|
|
32
|
+
* import { Mailer } from "./services/Mailer.js";
|
|
33
33
|
*
|
|
34
34
|
* export const services = createServices({
|
|
35
|
-
* postRepo: PostRepo,
|
|
36
|
-
*
|
|
35
|
+
* postRepo: PostRepo, // class form (auto-injected)
|
|
36
|
+
* mailer: ({ ctx }) => new Mailer(ctx), // factory form
|
|
37
37
|
* });
|
|
38
38
|
*
|
|
39
39
|
* export type Services = typeof services;
|
|
@@ -51,15 +51,21 @@ import { z, ZodError } from 'zod';
|
|
|
51
51
|
*
|
|
52
52
|
* @example
|
|
53
53
|
* ```ts
|
|
54
|
-
* // Inside a useCase
|
|
54
|
+
* // Inside a useCase — extends the UseCase base, owns its Zod schemas
|
|
55
|
+
* import { z } from "zod";
|
|
56
|
+
* import { UseCase } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
55
57
|
* import type { Services } from "../../services.js";
|
|
56
58
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* const input = z.object({ title: z.string() });
|
|
60
|
+
* const output = z.object({ id: z.string() });
|
|
61
|
+
*
|
|
62
|
+
* export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {
|
|
63
|
+
* static readonly input = input;
|
|
64
|
+
* static readonly output = output;
|
|
59
65
|
*
|
|
60
|
-
* async execute(
|
|
66
|
+
* async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {
|
|
61
67
|
* const user = this.services.ctx.c.get("user");
|
|
62
|
-
* return this.services.postRepo.create({ ...
|
|
68
|
+
* return this.services.postRepo.create({ ...payload, authorId: user.id });
|
|
63
69
|
* }
|
|
64
70
|
* }
|
|
65
71
|
* ```
|
|
@@ -169,14 +175,14 @@ type ServicesContainer<TMap> = TMap;
|
|
|
169
175
|
*
|
|
170
176
|
* @example Class form (zero-boilerplate auto-injection)
|
|
171
177
|
* ```ts
|
|
172
|
-
* class
|
|
178
|
+
* class RepositoryService {
|
|
173
179
|
* constructor(private readonly services: Services) {}
|
|
174
|
-
*
|
|
180
|
+
* get posts() { return this.services.db.posts; }
|
|
175
181
|
* }
|
|
176
182
|
*
|
|
177
183
|
* createServices({
|
|
178
|
-
*
|
|
179
|
-
*
|
|
184
|
+
* db: () => getFirestore(), // ← factory
|
|
185
|
+
* repository: RepositoryService, // ← bare class (deps auto-injected)
|
|
180
186
|
* });
|
|
181
187
|
* ```
|
|
182
188
|
*/
|
|
@@ -268,9 +274,51 @@ type AnyRouteDef = RouteDef<any, any>;
|
|
|
268
274
|
* What `routes.ts` can default-export:
|
|
269
275
|
* - a single {@link RouteDef} (most common case),
|
|
270
276
|
* - or an array of {@link RouteDef} (e.g. expose the same useCase on
|
|
271
|
-
* multiple `api` tags or under multiple paths).
|
|
277
|
+
* multiple `api` tags or under multiple paths). `readonly` arrays are
|
|
278
|
+
* accepted so that tuple-preserving helpers like {@link defineRoutes} (or a
|
|
279
|
+
* plain `as const` array) can be default-exported directly.
|
|
280
|
+
*/
|
|
281
|
+
type RouteModuleDefault = AnyRouteDef | readonly AnyRouteDef[];
|
|
282
|
+
/**
|
|
283
|
+
* Identity helper that **preserves the tuple type** of a `routes.ts` default
|
|
284
|
+
* export. A plain array literal (`export default [a, b]`) collapses every
|
|
285
|
+
* element to a single common type, which would lose each route's individual
|
|
286
|
+
* schema. Wrapping the array with `defineRoutes([...])` keeps the per-route
|
|
287
|
+
* types intact so {@link RouteInput} / {@link RouteOutput} can aggregate them
|
|
288
|
+
* into a union.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* export default defineRoutes([
|
|
292
|
+
* defineRoute({ ... }),
|
|
293
|
+
* defineRoute({ ... }),
|
|
294
|
+
* ]);
|
|
295
|
+
*/
|
|
296
|
+
declare function defineRoutes<const T extends readonly AnyRouteDef[]>(routes: T): T;
|
|
297
|
+
/**
|
|
298
|
+
* Normalizes a `routes.ts` default export — a single {@link RouteDef} or an
|
|
299
|
+
* array of them — to a *union* of its individual route members.
|
|
272
300
|
*/
|
|
273
|
-
type
|
|
301
|
+
type RoutesUnion<T> = T extends readonly (infer R)[] ? R : T;
|
|
302
|
+
type InferRouteInput<R> = R extends {
|
|
303
|
+
input?: infer I;
|
|
304
|
+
} ? I extends z.ZodTypeAny ? z.infer<I> : void : void;
|
|
305
|
+
type InferRouteOutput<R> = R extends {
|
|
306
|
+
output?: infer O;
|
|
307
|
+
} ? O extends z.ZodTypeAny ? z.infer<O> : unknown : unknown;
|
|
308
|
+
/**
|
|
309
|
+
* Union of validated request payloads across every route in a `routes.ts`
|
|
310
|
+
* module. Use it to type a useCase input without duplicating the Zod schema:
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* type Routes = typeof import("./routes.js").default;
|
|
314
|
+
* export type CreatePostUseCaseInput = RouteInput<Routes>;
|
|
315
|
+
*/
|
|
316
|
+
type RouteInput<T> = InferRouteInput<RoutesUnion<T>>;
|
|
317
|
+
/**
|
|
318
|
+
* Union of success-response payloads across every route in a `routes.ts`
|
|
319
|
+
* module. Mirror of {@link RouteInput} for the useCase output type.
|
|
320
|
+
*/
|
|
321
|
+
type RouteOutput<T> = InferRouteOutput<RoutesUnion<T>>;
|
|
274
322
|
/**
|
|
275
323
|
* Thrown by the server when the incoming request fails Zod validation.
|
|
276
324
|
* Caught by the {@link RouteInterceptor} (if any) so users can shape the
|
|
@@ -447,6 +495,102 @@ declare class HonoServer<TEnv extends Env = Env> {
|
|
|
447
495
|
private mountOpenApi;
|
|
448
496
|
}
|
|
449
497
|
|
|
498
|
+
/**
|
|
499
|
+
* useCase ⇆ route bridge.
|
|
500
|
+
*
|
|
501
|
+
* A useCase owns its Zod `input` / `output` schemas (declared as `static`
|
|
502
|
+
* members) and the business logic in {@link UseCase.execute}. Routes never
|
|
503
|
+
* re-declare those schemas: they wire a useCase into an HTTP endpoint with the
|
|
504
|
+
* one-liner {@link ApiRegistry.useCaseRoute} (or the standalone
|
|
505
|
+
* {@link useCaseRoute}), keeping `routes.ts` flat and readable while the
|
|
506
|
+
* types can never drift from the schemas.
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```ts
|
|
510
|
+
* // useCase.ts — single source of truth for the I/O shape
|
|
511
|
+
* import { z } from "zod";
|
|
512
|
+
* import { UseCase } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
513
|
+
* import type { Services } from "../../../../services.js";
|
|
514
|
+
*
|
|
515
|
+
* const input = z.object({ example: z.string() });
|
|
516
|
+
* const output = z.object({ id: z.string(), warning: z.string().nullable() });
|
|
517
|
+
*
|
|
518
|
+
* export class CreatePostUseCase extends UseCase<typeof input, typeof output, Services> {
|
|
519
|
+
* static readonly input = input;
|
|
520
|
+
* static readonly output = output;
|
|
521
|
+
*
|
|
522
|
+
* async execute(payload: z.infer<typeof input>): Promise<z.infer<typeof output>> {
|
|
523
|
+
* return { id: payload.example, warning: null };
|
|
524
|
+
* }
|
|
525
|
+
* }
|
|
526
|
+
*
|
|
527
|
+
* // routes.ts — one line per route, `api` stays typed
|
|
528
|
+
* export default defineRoutes([
|
|
529
|
+
* useCaseRoute(CreatePostUseCase, { api: "v1", method: "post", tags: ["posts"] }),
|
|
530
|
+
* ]);
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Base class for every useCase — pure business logic, no HTTP awareness.
|
|
536
|
+
* The shared {@link AnyServicesContainer} is injected via the constructor; the
|
|
537
|
+
* `input` / `output` Zod schemas are declared as `static` members on the
|
|
538
|
+
* concrete subclass (see {@link UseCaseClass}).
|
|
539
|
+
*
|
|
540
|
+
* @typeParam TInput Zod schema of the validated request payload.
|
|
541
|
+
* @typeParam TOutput Zod schema of the success response.
|
|
542
|
+
* @typeParam TServices Concrete services container injected into the useCase.
|
|
543
|
+
*/
|
|
544
|
+
declare abstract class UseCase<TInput extends z.ZodTypeAny = z.ZodTypeAny, TOutput extends z.ZodTypeAny = z.ZodTypeAny, TServices extends AnyServicesContainer = AnyServicesContainer> {
|
|
545
|
+
protected readonly services: TServices;
|
|
546
|
+
constructor(services: TServices);
|
|
547
|
+
/** Run the business logic. Input is already validated against the schema. */
|
|
548
|
+
abstract execute(input: z.infer<TInput>): Promise<z.infer<TOutput>>;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Structural type of a concrete {@link UseCase} subclass — i.e. a constructor
|
|
552
|
+
* that also exposes the `static input` / `static output` schemas. Consumed by
|
|
553
|
+
* {@link useCaseRoute} to derive the route's Zod schemas and the handler types.
|
|
554
|
+
*/
|
|
555
|
+
interface UseCaseClass<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, TServices extends AnyServicesContainer = AnyServicesContainer> {
|
|
556
|
+
readonly input: TInput;
|
|
557
|
+
readonly output: TOutput;
|
|
558
|
+
new (services: TServices): UseCase<TInput, TOutput, TServices>;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* HTTP metadata for {@link useCaseRoute} — everything a route needs **except**
|
|
562
|
+
* the input/output schemas and the handler, which are derived from the useCase.
|
|
563
|
+
*/
|
|
564
|
+
interface UseCaseRouteMeta<TApi extends string = string> {
|
|
565
|
+
/** API tag the route is mounted under. */
|
|
566
|
+
api: TApi;
|
|
567
|
+
/** HTTP method. */
|
|
568
|
+
method: HttpMethod;
|
|
569
|
+
/** URL path. Defaults to the codegen-derived path when omitted. */
|
|
570
|
+
path?: string;
|
|
571
|
+
/** Where the payload comes from. Defaults per method (see {@link RouteDef}). */
|
|
572
|
+
source?: PayloadSource;
|
|
573
|
+
/** Success status code. Default: 200. */
|
|
574
|
+
status?: number;
|
|
575
|
+
summary?: string;
|
|
576
|
+
description?: string;
|
|
577
|
+
tags?: string[];
|
|
578
|
+
deprecated?: boolean;
|
|
579
|
+
security?: Array<Record<string, string[]>>;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Build a {@link RouteDef} from a useCase class and HTTP metadata. The route's
|
|
583
|
+
* `input` / `output` schemas are read from the useCase's `static` members and
|
|
584
|
+
* the handler instantiates the useCase with the request `services` and runs
|
|
585
|
+
* {@link UseCase.execute}.
|
|
586
|
+
*
|
|
587
|
+
* Prefer the registry-bound `apis.useCaseRoute` (returned by
|
|
588
|
+
* `createApiRegistry`) so that `meta.api` is narrowed to the registered tags.
|
|
589
|
+
*/
|
|
590
|
+
declare function useCaseRoute<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, TServices extends AnyServicesContainer, TApi extends string = string>(useCaseClass: UseCaseClass<TInput, TOutput, TServices>, meta: UseCaseRouteMeta<TApi>): RouteDef<TInput, TOutput> & {
|
|
591
|
+
api: TApi;
|
|
592
|
+
};
|
|
593
|
+
|
|
450
594
|
/**
|
|
451
595
|
* Typed multi-API registry.
|
|
452
596
|
*
|
|
@@ -475,6 +619,7 @@ declare class HonoServer<TEnv extends Env = Env> {
|
|
|
475
619
|
*
|
|
476
620
|
* // Use in routes — `api` is now typed "v1" | "webhooks".
|
|
477
621
|
* export const defineRoute = apis.defineRoute;
|
|
622
|
+
* export const useCaseRoute = apis.useCaseRoute;
|
|
478
623
|
*
|
|
479
624
|
* // index.ts (Cloud Functions entrypoint)
|
|
480
625
|
* import { onRequest } from "firebase-functions/v2/https";
|
|
@@ -527,6 +672,20 @@ interface ApiRegistry<TMap extends ApiConfigMap, TServices extends AnyServicesCo
|
|
|
527
672
|
}): RouteDef<TIn, TOut> & {
|
|
528
673
|
api: keyof TMap & string;
|
|
529
674
|
};
|
|
675
|
+
/**
|
|
676
|
+
* Typed `useCaseRoute` — wires a {@link UseCase} class into a route in one
|
|
677
|
+
* line. The route's `input` / `output` schemas are read from the useCase's
|
|
678
|
+
* `static` members and the handler instantiates it with the request
|
|
679
|
+
* `services`. The `api` field is constrained to `keyof TMap`.
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* export default defineRoutes([
|
|
683
|
+
* useCaseRoute(CreatePostUseCase, { api: "v1", method: "post", tags: ["posts"] }),
|
|
684
|
+
* ]);
|
|
685
|
+
*/
|
|
686
|
+
useCaseRoute<TIn extends z.ZodTypeAny, TOut extends z.ZodTypeAny>(useCaseClass: UseCaseClass<TIn, TOut, TServices>, meta: UseCaseRouteMeta<keyof TMap & string>): RouteDef<TIn, TOut> & {
|
|
687
|
+
api: keyof TMap & string;
|
|
688
|
+
};
|
|
530
689
|
/**
|
|
531
690
|
* Build one Cloud Function per registered API and return them as a map
|
|
532
691
|
* keyed by API tag — spread it directly into your `index.ts` exports.
|
|
@@ -669,4 +828,4 @@ declare function generateRoutesManifest(routes: ScannedRoute[], opts: GeneratorO
|
|
|
669
828
|
/** Convenience helper used by the CLI — combines scan + generate in one call. */
|
|
670
829
|
declare function generateFromRoot(rootAbs: string, outFileRel: string, derive: PathDeriveOptions, importExtension: string, scan: (root: string) => ScannedRoute[]): GenerationResult;
|
|
671
830
|
|
|
672
|
-
export { type AnyRouteDef, type AnyServicesContainer, type ApiConfig, type ApiConfigMap, type ApiRegistry, type ApiRegistryOptions, DEFAULT_DERIVE, DEFAULT_GENERATOR_BANNER, DEFAULT_SCANNER, type GenerationResult, type GeneratorOptions, HonoServer, type HonoServerOptions, type HttpMethod, type OpenAPIConfig, type OpenAPIInfo, type PathDeriveOptions, type PayloadSource, type RequestContext, type RouteDef, type RouteHandler, type RouteInterceptor, type RouteModuleDefault, type ScannedRoute, type ScannerOptions, type ServiceProvider, type ServiceProviderMap, type ServicesContainer, type ServicesOf, ValidationError, buildOpenApiDocument, createApiRegistry, createRequestContextMiddleware, createServices, derivePath, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier, withRequestContext };
|
|
831
|
+
export { type AnyRouteDef, type AnyServicesContainer, type ApiConfig, type ApiConfigMap, type ApiRegistry, type ApiRegistryOptions, DEFAULT_DERIVE, DEFAULT_GENERATOR_BANNER, DEFAULT_SCANNER, type GenerationResult, type GeneratorOptions, HonoServer, type HonoServerOptions, type HttpMethod, type OpenAPIConfig, type OpenAPIInfo, type PathDeriveOptions, type PayloadSource, type RequestContext, type RouteDef, type RouteHandler, type RouteInput, type RouteInterceptor, type RouteModuleDefault, type RouteOutput, type RoutesUnion, type ScannedRoute, type ScannerOptions, type ServiceProvider, type ServiceProviderMap, type ServicesContainer, type ServicesOf, UseCase, type UseCaseClass, type UseCaseRouteMeta, ValidationError, buildOpenApiDocument, createApiRegistry, createRequestContextMiddleware, createServices, defineRoutes, derivePath, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier, useCaseRoute, withRequestContext };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Hono}from'hono';import {getRequestListener}from'@hono/node-server';import {extendZodWithOpenApi,OpenAPIRegistry,OpenApiGeneratorV31}from'@asteasolutions/zod-to-openapi';import {z}from'zod';import {AsyncLocalStorage}from'async_hooks';import {readdirSync,statSync,mkdirSync,writeFileSync}from'fs';import {join,relative,sep,dirname}from'path';var
|
|
1
|
+
import {Hono}from'hono';import {getRequestListener}from'@hono/node-server';import {extendZodWithOpenApi,OpenAPIRegistry,OpenApiGeneratorV31}from'@asteasolutions/zod-to-openapi';import {z as z$1}from'zod';import {AsyncLocalStorage}from'async_hooks';import {readdirSync,statSync,mkdirSync,writeFileSync}from'fs';import {join,relative,sep,dirname}from'path';function Z(t){return t}var m=class extends Error{constructor(n,o){super("Request validation failed");this.zodError=n;this.source=o;this.statusCode=400;this.name="ValidationError";}};extendZodWithOpenApi(z$1);var I="Successful response";function G(t){return t==="get"?"query":"json"}function A(t,e,n){let o=new OpenAPIRegistry;if(n.securitySchemes)for(let[i,a]of Object.entries(n.securitySchemes))o.registerComponent("securitySchemes",i,a);for(let i of t){let a=i.method,p=i.source??G(a),u=V(e,i.path??"/"),f=i.status??200,c=K(a,p,i.input),y=b(p,i.input,"query"),d=b(p,i.input,"param"),l=W(a,u);o.registerPath({method:a,path:B(u),operationId:l,summary:i.summary,description:i.description,tags:i.tags,deprecated:i.deprecated,security:i.security,request:{...y?{query:y}:{},...d?{params:d}:{},...c?{body:c}:{}},responses:i.output?{[f]:{description:I,content:{"application/json":{schema:i.output}}}}:{[f]:{description:I}}});}return new OpenApiGeneratorV31(o.definitions).generateDocument({openapi:"3.1.0",info:n.info,servers:n.servers,security:n.security})}function K(t,e,n){return !n||t==="get"?null:e==="json"?{content:{"application/json":{schema:n}}}:e==="form"?{content:{"application/x-www-form-urlencoded":{schema:n}}}:null}function b(t,e,n){if(e&&(n==="query"&&t==="query"||n==="param"&&t==="param"))return e}function B(t){return t.replace(/:([A-Za-z0-9_]+)/g,"{$1}")}function V(t,e){let n=t.endsWith("/")?t.slice(0,-1):t,o=e.startsWith("/")?e:`/${e}`,r=`${n}${o}`;return r===""?"/":r}function W(t,e){let n=e.replace(/[{}]/g,"").replace(/\/+/g,"_").replace(/[^A-Za-z0-9_]/g,"").replace(/^_+|_+$/g,"");return `${t}_${n||"root"}`}function w(t,e){let n=t.replace(/"/g,""");return `<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
@@ -9,7 +9,7 @@ import {Hono}from'hono';import {getRequestListener}from'@hono/node-server';impor
|
|
|
9
9
|
<script id="api-reference" data-url="${n}"></script>
|
|
10
10
|
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
11
11
|
</body>
|
|
12
|
-
</html>`}var
|
|
12
|
+
</html>`}var h=new AsyncLocalStorage,Y=Object.freeze({get c(){let t=h.getStore();if(!t)throw new Error("[services] requestContext.c was accessed outside of a request. Wrap non-HTTP code paths (cron, triggers, scripts, tests) in `withRequestContext({ c }, () => ...)` to supply a Hono Context.");return t.c},get maybeC(){return h.getStore()?.c}});function O(){return async(t,e)=>{await h.run({c:t},async()=>{await e();});}}function Q(t,e){return h.run({c:t.c},async()=>e())}var v="ctx";function X(t){let e=new Map,n=[],o=new Proxy({},{get(r,s){if(typeof s!="string")return;if(s===v)return Y;if(e.has(s))return e.get(s);let i=t[s];if(typeof i!="function")throw new Error(`[services] unknown service "${s}". Registered: ${[v,...Object.keys(t)].join(", ")}`);if(n.includes(s))throw new Error(`[services] circular dependency detected: ${[...n,s].join(" \u2192 ")}`);n.push(s);try{let a=ee(i)?new i(o):i(o);return e.set(s,a),a}finally{n.pop();}},has(r,s){return typeof s!="string"?false:s===v||s in t},ownKeys(){return [v,...Object.keys(t)]},getOwnPropertyDescriptor(r,s){if(typeof s=="string"&&(s===v||s in t))return {enumerable:true,configurable:true}}});return o}function ee(t){return /^class[\s{]/.test(Function.prototype.toString.call(t))}var re=Object.freeze({}),g=class{constructor(e){this.cachedSpec=null;this.options=e,this.app=new Hono,this.mountedRoutes=oe(e.routes,e.api),e.services&&this.app.use("*",O());let n=[...e.middlewares??[],...e.globalMiddlewares??[]];for(let o of n)this.app.use("*",o);this.mountRoutes(),this.mountOpenApi(),e.notFound&&this.app.notFound(e.notFound),e.onError&&this.app.onError(e.onError);}get hono(){return this.app}get nodeHandler(){return getRequestListener(this.app.fetch,{overrideGlobalObjects:false})}toFunction(e,n){let o=this.nodeHandler;return n?e(n,o):e(o)}buildOpenApiSpec(){if(this.cachedSpec)return this.cachedSpec;if(!this.options.openapi)throw new Error("[HonoServer] openapi config not set");return this.cachedSpec=A(this.mountedRoutes,this.options.basePath??"",this.options.openapi),this.cachedSpec}mountRoutes(){let e=this.options.basePath??"",n=this.options.validateOutput??false,o=this.options.verbose??false;for(let r of this.mountedRoutes){if(!r.path)throw new Error(`[HonoServer] route "${r.method.toUpperCase()} (no path)" \u2014 missing \`path\`. Run the codegen so the path is derived from the file location, or set it explicitly.`);let s=P(e,r.path),i=r.middlewares??[],a=r.source??(r.method==="get"?"query":"json"),p=ie(r,a,n,this.options.interceptor,this.options.services),u=r.method.toUpperCase();this.app.on(u,[s],...i,p),o&&console.log(`[HonoServer] ${r.method.toUpperCase().padEnd(6)} ${s}`);}}mountOpenApi(){let e=this.options.openapi;if(!e)return;let n=e.path??"/openapi.json",o=e.docsPath===void 0?"/docs":e.docsPath,r=P(this.options.basePath??"",n),s=o===false?null:P(this.options.basePath??"",o);if(this.app.get(r,i=>i.json(this.buildOpenApiSpec())),s){let i=se(s,r);this.app.get(s,a=>a.html(w(i,e.info.title)));}}};function oe(t,e){return e?t.filter(n=>Array.isArray(n.api)?n.api.includes(e):n.api===e):t.slice()}function P(t,e){let n=t.endsWith("/")?t.slice(0,-1):t,o=e.startsWith("/")?e:`/${e}`,r=`${n}${o}`;return r===""?"/":r}function se(t,e){let n=t.split("/").filter(Boolean),o=e.split("/").filter(Boolean);n.pop();let r=0;for(;r<n.length&&r<o.length&&n[r]===o[r];)r++;let s=n.length-r;return [...Array(s).fill(".."),...o.slice(r)].join("/")||"./"}function ie(t,e,n,o,r){let s=t.input,i=t.output,a=t.status??200,p=r??re;return async u=>{let f=async()=>{let y;if(s){let l;try{l=await ue(u,e,t.method);}catch(x){throw new T(x instanceof Error?x.message:String(x))}let S=s.safeParse(l);if(!S.success)throw new m(S.error,e);y=S.data;}let d=await t.handler({input:y,c:u,services:p});if(n&&i&&!(d instanceof Response)){let l=i.safeParse(d);if(!l.success)throw new R(l.error);return l.data}return d},c;if(o)c=await o({next:f,route:t,c:u,services:p});else try{c=await f();}catch(y){let d=ae(u,y);if(d)return d;throw y}return c instanceof Response?c:u.json(c,a)}}var T=class extends Error{constructor(n){super(n);this.statusCode=400;this.name="BadRequestError";}},R=class extends Error{constructor(n){super("Output validation failed");this.zodError=n;this.statusCode=500;this.name="OutputValidationError";}};function ae(t,e){return e instanceof m?t.json({success:false,error:"Validation failed",issues:k(e.zodError)},400):e instanceof T?t.json({success:false,error:"Bad Request",message:e.message},400):e instanceof R?t.json({success:false,error:"Output validation failed",issues:k(e.zodError)},500):null}async function ue(t,e,n){switch(e){case "json":{if(n==="get")return t.req.query();let o=await t.req.text();if(!o)return {};try{return JSON.parse(o)}catch(r){throw new Error(`Invalid JSON body: ${r instanceof Error?r.message:String(r)}`)}}case "query":return t.req.query();case "form":return await t.req.parseBody();case "param":return t.req.param();default:return {}}}function k(t){return t.issues.map(e=>({path:e.path.join("."),code:e.code,message:e.message}))}var C=class{constructor(e){this.services=e;}};function E(t,e){return {...e,input:t.input,output:t.output,handler:({input:n,services:o})=>new t(o).execute(n)}}function pe(t,e){let n=e?.services;return {configs:t,defineRoute(o){return o},useCaseRoute(o,r){return E(o,r)},serverFor(o,r){let s=t[o];if(!s)throw new Error(`[ApiRegistry] unknown api "${o}". Registered: ${Object.keys(t).join(", ")}`);return new g({...s,api:o,routes:r,services:n})},toFunctions(o,r,s){let i={};for(let a of Object.keys(t)){let p={...s?.defaults??{},...s?.per?.[a]??{}},u=new g({...t[a],api:a,routes:o,services:n});i[a]=Object.keys(p).length?u.toFunction(r,p):u.toFunction(r);}return i}}}var H={skipSegments:["useCases","useCase","use-cases","use-case"],casing:"preserve"};function M(t,e=H){let n=new Set(e.skipSegments.map(r=>r.toLowerCase()));return "/"+t.split("/").filter(Boolean).filter(r=>!n.has(r.toLowerCase())).map(r=>e.casing==="kebab"?ce(r):r).join("/")}function ce(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase()}function D(t,e,n){let o=z(t),r=z(e),s=0;for(;s<o.length&&s<r.length&&o[s]===r[s];)s++;let i=o.length-s,a=r.slice(s),u=(a[a.length-1]??"").replace(/\.[mc]?[tj]sx?$/i,""),f=n===""?u:`${u}${n}`;return a[a.length-1]=f,(i===0?"./":"../".repeat(i))+a.join("/")}function z(t){return t.replace(/\\/g,"/").replace(/\/+$/,"").split("/").filter((n,o)=>!(o===0&&n===""))}var q={routesFile:"routes.ts",excludeSegments:["node_modules","__generated__","tests","__tests__",".turbo","dist","build",".next"]};function ge(t,e=q){let n=[];return F(t,t,e,n),n.sort((o,r)=>o.relPath.localeCompare(r.relPath)),n}function F(t,e,n,o){let r;try{r=readdirSync(e);}catch{return}for(let s of r){if(n.excludeSegments.includes(s))continue;let i=join(e,s),a;try{a=statSync(i);}catch{continue}if(a.isDirectory())F(t,i,n,o);else if(a.isFile()&&s===n.routesFile){let p=relative(t,i).split(sep).join("/"),u=p.replace(/\/?[^/]+$/,"");o.push({absPath:i,relPath:p,relDir:u});}}}var _="/**\n * AUTO-GENERATED by `@lpdjs/firestore-repo-service` Hono codegen.\n * Do not edit by hand \u2014 re-run `hono:gen` after adding / removing route files.\n */\n";function j(t,e){let n=dirname(e.outFile);mkdirSync(n,{recursive:true});let o=e.banner??_,r=(e.now??new Date).toISOString(),s=e.importExtension,i=[],a=[],p=[];t.forEach((f,c)=>{let y=D(n,f.absPath,s),d=M(f.relDir,e.derive);i.push(`import mod${c} from ${JSON.stringify(y)};`),a.push(` { __derivedPath: ${JSON.stringify(d)}, mod: mod${c} },`),p.push({source:f.relPath,url:d});});let u=`${o}// Generated at ${r} \u2014 ${t.length} route file${t.length===1?"":"s"}.
|
|
13
13
|
|
|
14
14
|
import type { AnyRouteDef, RouteModuleDefault } from "@lpdjs/firestore-repo-service/servers/hono";
|
|
15
15
|
|
|
@@ -26,5 +26,5 @@ export const routes: AnyRouteDef[] = __defs.flatMap(({ __derivedPath, mod }) =>
|
|
|
26
26
|
const list = Array.isArray(mod) ? mod : [mod];
|
|
27
27
|
return list.map((route) => ({ ...route, path: route.path ?? __derivedPath }));
|
|
28
28
|
});
|
|
29
|
-
`;return writeFileSync(e.outFile,
|
|
29
|
+
`;return writeFileSync(e.outFile,u,"utf8"),{outFile:e.outFile,routeCount:t.length,derivedPaths:p}}function Se(t,e,n,o,r){let s=r(t),i=join(t,e);return j(s,{outFile:i,derive:n,importExtension:o})}export{H as DEFAULT_DERIVE,_ as DEFAULT_GENERATOR_BANNER,q as DEFAULT_SCANNER,g as HonoServer,C as UseCase,m as ValidationError,A as buildOpenApiDocument,pe as createApiRegistry,O as createRequestContextMiddleware,X as createServices,Z as defineRoutes,M as derivePath,Se as generateFromRoot,j as generateRoutesManifest,w as renderDocsHtml,ge as scanRoutes,D as toImportSpecifier,E as useCaseRoute,Q as withRequestContext};//# sourceMappingURL=index.js.map
|
|
30
30
|
//# sourceMappingURL=index.js.map
|