@lpdjs/firestore-repo-service 2.4.3 → 2.6.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +92 -50
  2. package/dist/{create-servers-B9dTUhvR.d.cts → create-servers-B4GrBqdA.d.cts} +6 -6
  3. package/dist/{create-servers-BFhdPPeo.d.ts → create-servers-CVudVM8e.d.ts} +6 -6
  4. package/dist/{firebase-auth-D1APf9PA.d.cts → firebase-auth-Dpvrd8MP.d.cts} +13 -0
  5. package/dist/{firebase-auth-D1APf9PA.d.ts → firebase-auth-Dpvrd8MP.d.ts} +13 -0
  6. package/dist/history/index.cjs +1 -1
  7. package/dist/history/index.cjs.map +1 -1
  8. package/dist/history/index.d.cts +10 -4
  9. package/dist/history/index.d.ts +10 -4
  10. package/dist/history/index.js +1 -1
  11. package/dist/history/index.js.map +1 -1
  12. package/dist/{index-BxurOEz1.d.ts → index-DzO9MfNI.d.cts} +9 -2
  13. package/dist/{index-BmagC7uw.d.cts → index-oFhGCBrY.d.ts} +9 -2
  14. package/dist/index.cjs +84 -84
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +9 -9
  17. package/dist/index.d.ts +9 -9
  18. package/dist/index.js +84 -84
  19. package/dist/index.js.map +1 -1
  20. package/dist/{openapi-ML_1hTx2.d.cts → openapi-B2w5tVRR.d.cts} +1 -1
  21. package/dist/{openapi-DIoQV_yQ.d.ts → openapi-DB8bXZB-.d.ts} +1 -1
  22. package/dist/{queue-xMOZxY0M.d.cts → queue-B8YUTnBT.d.cts} +20 -5
  23. package/dist/{queue-CVchaGAh.d.ts → queue-DYmbVDu5.d.ts} +20 -5
  24. package/dist/{read-BSyLao3I.d.cts → read-CTWZjxyh.d.cts} +1 -1
  25. package/dist/{read-BSyLao3I.d.ts → read-CTWZjxyh.d.ts} +1 -1
  26. package/dist/servers/admin/index.cjs +48 -48
  27. package/dist/servers/admin/index.cjs.map +1 -1
  28. package/dist/servers/admin/index.d.cts +4 -4
  29. package/dist/servers/admin/index.d.ts +4 -4
  30. package/dist/servers/admin/index.js +48 -48
  31. package/dist/servers/admin/index.js.map +1 -1
  32. package/dist/servers/auth/index.cjs +25 -12
  33. package/dist/servers/auth/index.cjs.map +1 -1
  34. package/dist/servers/auth/index.d.cts +1 -1
  35. package/dist/servers/auth/index.d.ts +1 -1
  36. package/dist/servers/auth/index.js +25 -12
  37. package/dist/servers/auth/index.js.map +1 -1
  38. package/dist/servers/crud/index.cjs +2 -2
  39. package/dist/servers/crud/index.cjs.map +1 -1
  40. package/dist/servers/crud/index.d.cts +6 -6
  41. package/dist/servers/crud/index.d.ts +6 -6
  42. package/dist/servers/crud/index.js +2 -2
  43. package/dist/servers/crud/index.js.map +1 -1
  44. package/dist/servers/hono/cli.cjs +142 -53
  45. package/dist/servers/hono/cli.cjs.map +1 -1
  46. package/dist/servers/hono/cli.js +142 -53
  47. package/dist/servers/hono/cli.js.map +1 -1
  48. package/dist/servers/hono/index.cjs +5 -5
  49. package/dist/servers/hono/index.cjs.map +1 -1
  50. package/dist/servers/hono/index.d.cts +241 -24
  51. package/dist/servers/hono/index.d.ts +241 -24
  52. package/dist/servers/hono/index.js +5 -5
  53. package/dist/servers/hono/index.js.map +1 -1
  54. package/dist/servers/index.cjs +98 -98
  55. package/dist/servers/index.cjs.map +1 -1
  56. package/dist/servers/index.d.cts +9 -9
  57. package/dist/servers/index.d.ts +9 -9
  58. package/dist/servers/index.js +98 -98
  59. package/dist/servers/index.js.map +1 -1
  60. package/dist/sync/bigquery.cjs +3 -3
  61. package/dist/sync/bigquery.cjs.map +1 -1
  62. package/dist/sync/bigquery.d.cts +18 -2
  63. package/dist/sync/bigquery.d.ts +18 -2
  64. package/dist/sync/bigquery.js +3 -3
  65. package/dist/sync/bigquery.js.map +1 -1
  66. package/dist/sync/index.cjs +37 -37
  67. package/dist/sync/index.cjs.map +1 -1
  68. package/dist/sync/index.d.cts +5 -5
  69. package/dist/sync/index.d.ts +5 -5
  70. package/dist/sync/index.js +37 -37
  71. package/dist/sync/index.js.map +1 -1
  72. package/dist/{types-5vgXdUM2.d.ts → types-BHZ-Gk-s.d.ts} +71 -4
  73. package/dist/{types-ChzVPw4k.d.ts → types-FLGn8CAI.d.ts} +15 -1
  74. package/dist/{types-BtdC0Qhu.d.cts → types-GvexCqrq.d.cts} +71 -4
  75. package/dist/{types-BgIGWlR1.d.cts → types-wcX7xfdo.d.cts} +15 -1
  76. package/package.json +2 -2
@@ -1,7 +1,192 @@
1
- import { Env, MiddlewareHandler, Context, Hono } from 'hono';
1
+ import { Env, Context, MiddlewareHandler, Hono } from 'hono';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { HttpsOptions } from 'firebase-functions/v2/https';
3
4
  import { z, ZodError } from 'zod';
4
5
 
6
+ /**
7
+ * Global DI services container for the Hono server.
8
+ *
9
+ * Lets you declare all your singletons (repositories, SDK clients, useCases,
10
+ * loggers…) **once**, then inject them anywhere — routes, interceptors,
11
+ * cron jobs, Firestore triggers, tests.
12
+ *
13
+ * ## How it works
14
+ *
15
+ * - Each service is constructed **lazily** on first access and cached for
16
+ * the process lifetime (perfect for Cloud Functions cold-start).
17
+ * - Providers may be **classes** (auto-injected: `new Class(services)`)
18
+ * or **factories** (`(services) => instance`). Mix both freely.
19
+ * - Inter-service dependencies are inferred either by destructuring the
20
+ * factory argument (`postRepo: ({ db }) => new PostRepo(db)`) or by
21
+ * reading `this.services.*` inside a class.
22
+ * - A built-in `ctx` service exposes the **current request's** Hono
23
+ * `Context` via `AsyncLocalStorage`. UseCases access
24
+ * `this.services.ctx.c.get("user")` without any plumbing.
25
+ * - Cycles are detected at first access with a clear error.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // src/services.ts
30
+ * import { createServices } from "@lpdjs/firestore-repo-service/servers/hono";
31
+ * import { PostRepo } from "./domains/posts/PostRepo.js";
32
+ * import { CreatePostUseCase } from "./domains/posts/useCases/createPost/useCase.js";
33
+ *
34
+ * export const services = createServices({
35
+ * postRepo: PostRepo, // class form (auto-injected)
36
+ * createPostUseCase: CreatePostUseCase, // class form (auto-injected)
37
+ * });
38
+ *
39
+ * export type Services = typeof services;
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // src/apis.ts
45
+ * import { services } from "./services.js";
46
+ * export const apis = createApiRegistry(
47
+ * { v1: { basePath: "/v1", ... } },
48
+ * { services },
49
+ * );
50
+ * ```
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // Inside a useCase registered as a class
55
+ * import type { Services } from "../../services.js";
56
+ *
57
+ * export class CreatePostUseCase {
58
+ * constructor(private readonly services: Services) {}
59
+ *
60
+ * async execute(input: { title: string }) {
61
+ * const user = this.services.ctx.c.get("user");
62
+ * return this.services.postRepo.create({ ...input, authorId: user.id });
63
+ * }
64
+ * }
65
+ * ```
66
+ */
67
+
68
+ /**
69
+ * Per-request context exposed to every service / useCase.
70
+ * The instance is a **stable singleton** but its `c` getter resolves to the
71
+ * Hono `Context` of the currently-handled request via `AsyncLocalStorage`.
72
+ *
73
+ * Outside of a request (cron, manual scripts, tests), wrap your call in
74
+ * {@link withRequestContext} to supply a context, otherwise accessing `c`
75
+ * will throw.
76
+ */
77
+ interface RequestContext<TEnv extends Env = Env> {
78
+ /** Hono `Context` of the currently-handled request. */
79
+ readonly c: Context<TEnv>;
80
+ /**
81
+ * Same as `c` but returns `undefined` instead of throwing when called
82
+ * outside a request scope. Useful for opportunistic logging.
83
+ */
84
+ readonly maybeC: Context<TEnv> | undefined;
85
+ }
86
+ /**
87
+ * Hono middleware installed automatically by `HonoServer` when a `services`
88
+ * container is provided. Populates the AsyncLocalStorage so the built-in
89
+ * `ctx` service resolves to the current request.
90
+ *
91
+ * Exported for advanced cases (custom server / non-Hono adapter); you do not
92
+ * need to call this manually in the standard flow.
93
+ */
94
+ declare function createRequestContextMiddleware(): MiddlewareHandler;
95
+ /**
96
+ * Run `fn` with a synthetic request context — required when invoking
97
+ * services outside an HTTP handler (cron jobs, Firestore triggers, scripts,
98
+ * unit tests). Inside `fn`, `services.ctx.c` resolves to the supplied `c`.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * // A cron that reuses a useCase
103
+ * export const dailyTask = onSchedule("every 24 hours", async () => {
104
+ * await withRequestContext({ c: fakeContext() }, async () => {
105
+ * await services.createPostUseCase.execute({ ... });
106
+ * });
107
+ * });
108
+ * ```
109
+ */
110
+ declare function withRequestContext<T>(ctx: {
111
+ c: Context;
112
+ }, fn: () => Promise<T> | T): Promise<T>;
113
+ /**
114
+ * Reserved service name — the built-in request context. User factories
115
+ * cannot override it.
116
+ */
117
+ declare const CTX_KEY: "ctx";
118
+ /**
119
+ * Helper that derives the public services type from an output map.
120
+ * Each entry in `TMap` is the instance type returned by its provider.
121
+ * The built-in `ctx` is always present.
122
+ */
123
+ type ServicesOf<TMap> = {
124
+ readonly ctx: RequestContext;
125
+ } & {
126
+ readonly [K in keyof TMap]: TMap[K];
127
+ };
128
+ /**
129
+ * A single provider entry — either a factory `(deps) => R` or a class
130
+ * `new (deps) => R`. `deps` is the *complete* services proxy
131
+ * (siblings + built-in `ctx`).
132
+ */
133
+ type ServiceProvider<TMap, R> = ((deps: ServicesOf<TMap>) => R) | (new (deps: ServicesOf<TMap>) => R);
134
+ /**
135
+ * A provider map — each value is either a factory or a class constructor.
136
+ */
137
+ type ServiceProviderMap<TMap> = {
138
+ [K in keyof TMap]: K extends typeof CTX_KEY ? never : ServiceProvider<TMap, TMap[K]>;
139
+ };
140
+ /**
141
+ * Extract the instance/return type from a single provider value.
142
+ */
143
+ type ProviderReturn<P> = P extends new (...args: any) => infer R ? R : P extends (...args: any) => infer R ? R : never;
144
+ /**
145
+ * Compute the output service map from an inferred provider map.
146
+ */
147
+ type MapFromProviders<P> = {
148
+ [K in keyof P]: ProviderReturn<P[K]>;
149
+ };
150
+ /**
151
+ * Container returned by {@link createServices}. Behaves like a plain object
152
+ * keyed by service name — accessing a key triggers lazy instantiation.
153
+ * Use `type Services = typeof services` to derive the public type.
154
+ */
155
+ type ServicesContainer<TMap> = TMap;
156
+ /**
157
+ * Build a lazy singleton DI container.
158
+ *
159
+ * @param providers A map of service name → factory function **or** class
160
+ * constructor. The single argument (factory deps / first
161
+ * ctor param) receives a deps proxy typed as the full
162
+ * services map (siblings + the built-in `ctx`).
163
+ *
164
+ * @example Factory form
165
+ * ```ts
166
+ * db: () => getFirestore(),
167
+ * postRepo: ({ db }) => new PostRepo(db),
168
+ * ```
169
+ *
170
+ * @example Class form (zero-boilerplate auto-injection)
171
+ * ```ts
172
+ * class CreatePostUseCase {
173
+ * constructor(private readonly services: Services) {}
174
+ * async execute() { return this.services.postRepo.list(); }
175
+ * }
176
+ *
177
+ * createServices({
178
+ * postRepo: PostRepo, // ← bare class
179
+ * createPostUseCase: CreatePostUseCase, // ← bare class
180
+ * });
181
+ * ```
182
+ */
183
+ declare function createServices<P extends Record<string, ((deps: any) => unknown) | (new (deps: any) => unknown)>>(providers: P): ServicesContainer<ServicesOf<MapFromProviders<P>>>;
184
+ /**
185
+ * Opaque container type used by registry / server signatures that don't
186
+ * need to know the concrete services map.
187
+ */
188
+ type AnyServicesContainer = ServicesContainer<Record<string, any>>;
189
+
5
190
  /**
6
191
  * Public types for the Hono file-based API server.
7
192
  *
@@ -16,11 +201,17 @@ type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
16
201
  /** Where the validated payload comes from. */
17
202
  type PayloadSource = "json" | "query" | "form" | "param";
18
203
  /** Handler signature — receives a single typed context object. */
19
- type RouteHandler<TIn, TOut, TEnv extends Env = Env> = (ctx: {
204
+ type RouteHandler<TIn, TOut, TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> = (ctx: {
20
205
  /** Validated (and typed) request payload. `void` when no `input` schema is defined. */
21
206
  input: TIn;
22
207
  /** Raw Hono `Context` for headers, set status, redirect, etc. */
23
208
  c: Context<TEnv>;
209
+ /**
210
+ * Global DI services container — same instance shared by every handler.
211
+ * Empty `ServicesContainer` when the server was started without a
212
+ * `services` option.
213
+ */
214
+ services: TServices;
24
215
  }) => Promise<TOut | Response> | TOut | Response;
25
216
  /**
26
217
  * One route declaration. Default-exported by every `routes.ts` file inside the
@@ -123,7 +314,7 @@ declare class ValidationError extends Error {
123
314
  * }
124
315
  * ```
125
316
  */
126
- type RouteInterceptor<TEnv extends Env = Env> = (ctx: {
317
+ type RouteInterceptor<TEnv extends Env = Env, TServices extends AnyServicesContainer = AnyServicesContainer> = (ctx: {
127
318
  /**
128
319
  * Calls validation + handler and returns the raw value.
129
320
  * Throws {@link ValidationError} on Zod failure or any error thrown by the handler.
@@ -133,6 +324,8 @@ type RouteInterceptor<TEnv extends Env = Env> = (ctx: {
133
324
  route: AnyRouteDef;
134
325
  /** Hono request context. */
135
326
  c: Context<TEnv>;
327
+ /** Global DI services container. See {@link RouteHandler}. */
328
+ services: TServices;
136
329
  }) => Promise<Response | unknown> | Response | unknown;
137
330
  /** OpenAPI document info (subset of the spec used by the helper). */
138
331
  interface OpenAPIInfo {
@@ -196,6 +389,18 @@ interface HonoServerOptions<TEnv extends Env = Env> {
196
389
  * See {@link RouteInterceptor}.
197
390
  */
198
391
  interceptor?: RouteInterceptor<TEnv>;
392
+ /**
393
+ * Global DI services container (see {@link createServices}).
394
+ *
395
+ * When provided:
396
+ * - a middleware is installed automatically so the built-in `ctx`
397
+ * service resolves to the current request via `AsyncLocalStorage`;
398
+ * - `services` is passed into every handler and the interceptor.
399
+ *
400
+ * The same container instance should be shared across every API of your
401
+ * project — declare it once and pass it to {@link createApiRegistry}.
402
+ */
403
+ services?: AnyServicesContainer;
199
404
  }
200
405
 
201
406
  /**
@@ -235,7 +440,7 @@ declare class HonoServer<TEnv extends Env = Env> {
235
440
  * @param httpsOptions Options forwarded as the first argument to
236
441
  * `onRequest()` (region, memory, invoker, etc.).
237
442
  */
238
- toFunction(onRequest: OnRequestFn$1, httpsOptions?: Record<string, unknown>): any;
443
+ toFunction(onRequest: OnRequestFn$1, httpsOptions?: HttpsOptions): any;
239
444
  /** Generate (and cache) the OpenAPI 3.1 spec for the mounted routes. */
240
445
  buildOpenApiSpec(): Record<string, unknown>;
241
446
  private mountRoutes;
@@ -288,30 +493,37 @@ declare class HonoServer<TEnv extends Env = Env> {
288
493
  type OnRequestFn = (...args: any[]) => any;
289
494
  /**
290
495
  * Per-API configuration. Same shape as {@link HonoServerOptions} minus the
291
- * `routes` (resolved by the registry) and `api` (the registry key).
496
+ * `routes` (resolved by the registry), `api` (the registry key) and
497
+ * `services` (set globally on the registry — see
498
+ * {@link createApiRegistry}).
292
499
  */
293
- type ApiConfig<TEnv extends Env = Env> = Omit<HonoServerOptions<TEnv>, "routes" | "api">;
500
+ type ApiConfig<TEnv extends Env = Env> = Omit<HonoServerOptions<TEnv>, "routes" | "api" | "services">;
294
501
  /** Map of API tag → its config. */
295
502
  type ApiConfigMap = Record<string, ApiConfig>;
296
- interface ApiRegistry<TMap extends ApiConfigMap> {
503
+ /**
504
+ * Options accepted by {@link createApiRegistry} alongside the per-API
505
+ * configs.
506
+ */
507
+ interface ApiRegistryOptions<TServices extends AnyServicesContainer = AnyServicesContainer> {
508
+ /**
509
+ * Global DI services container shared across every API. See
510
+ * {@link ServicesContainer} / `createServices`. When provided, every
511
+ * `HonoServer` mounted by the registry receives it and exposes
512
+ * `services` to every handler / interceptor.
513
+ */
514
+ services?: TServices;
515
+ }
516
+ interface ApiRegistry<TMap extends ApiConfigMap, TServices extends AnyServicesContainer = AnyServicesContainer> {
297
517
  /** The registered configs (read-only). */
298
518
  readonly configs: TMap;
299
519
  /**
300
- * Typed `defineRoute` — the `api` field is constrained to `keyof TMap`.
301
- *
302
- * To expose the same logical endpoint under several APIs with different
303
- * `input` / `output` schemas, call `defineRoute` once per route and wrap
304
- * them in an array — per-call inference is preserved:
305
- *
306
- * ```ts
307
- * export default [
308
- * defineRoute({ api: "v1", input: V1Input, handler: ({ input }) => ... }),
309
- * defineRoute({ api: "v2", input: V2Input, handler: ({ input }) => ... }),
310
- * ];
311
- * ```
520
+ * Typed `defineRoute` — the `api` field is constrained to `keyof TMap`,
521
+ * and `handler({ services })` is typed with the concrete services
522
+ * container passed to {@link createApiRegistry}.
312
523
  */
313
- defineRoute<TIn extends z.ZodTypeAny | undefined = undefined, TOut extends z.ZodTypeAny | undefined = undefined>(def: Omit<RouteDef<TIn, TOut>, "api"> & {
524
+ defineRoute<TIn extends z.ZodTypeAny | undefined = undefined, TOut extends z.ZodTypeAny | undefined = undefined>(def: Omit<RouteDef<TIn, TOut>, "api" | "handler"> & {
314
525
  api: keyof TMap & string;
526
+ handler: RouteHandler<TIn extends z.ZodTypeAny ? z.infer<TIn> : void, TOut extends z.ZodTypeAny ? z.infer<TOut> : unknown, Env, TServices>;
315
527
  }): RouteDef<TIn, TOut> & {
316
528
  api: keyof TMap & string;
317
529
  };
@@ -325,8 +537,10 @@ interface ApiRegistry<TMap extends ApiConfigMap> {
325
537
  * @param opts Optional defaults and per-API overrides for `httpsOptions`.
326
538
  */
327
539
  toFunctions(routes: AnyRouteDef[], onRequest: OnRequestFn, opts?: {
328
- defaults?: Record<string, unknown>;
329
- per?: Partial<Record<keyof TMap & string, Record<string, unknown>>>;
540
+ /** Shared `HttpsOptions` applied to every generated function. */
541
+ defaults?: HttpsOptions;
542
+ /** Per-API overrides — merged on top of {@link defaults}. */
543
+ per?: Partial<Record<keyof TMap & string, HttpsOptions>>;
330
544
  }): {
331
545
  [K in keyof TMap & string]: ReturnType<OnRequestFn>;
332
546
  };
@@ -336,8 +550,11 @@ interface ApiRegistry<TMap extends ApiConfigMap> {
336
550
  /**
337
551
  * Factory — declare every API tag once and get back a typed `defineRoute`
338
552
  * + `toFunctions`. See the file-level example.
553
+ *
554
+ * @param configs API-tag → per-API config.
555
+ * @param options Cross-API options (shared services container, etc).
339
556
  */
340
- declare function createApiRegistry<const TMap extends ApiConfigMap>(configs: TMap): ApiRegistry<TMap>;
557
+ declare function createApiRegistry<const TMap extends ApiConfigMap, TServices extends AnyServicesContainer = AnyServicesContainer>(configs: TMap, options?: ApiRegistryOptions<TServices>): ApiRegistry<TMap, TServices>;
341
558
 
342
559
  /**
343
560
  * OpenAPI 3.1 spec generator from {@link RouteDef} entries.
@@ -452,4 +669,4 @@ declare function generateRoutesManifest(routes: ScannedRoute[], opts: GeneratorO
452
669
  /** Convenience helper used by the CLI — combines scan + generate in one call. */
453
670
  declare function generateFromRoot(rootAbs: string, outFileRel: string, derive: PathDeriveOptions, importExtension: string, scan: (root: string) => ScannedRoute[]): GenerationResult;
454
671
 
455
- export { type AnyRouteDef, type ApiConfig, type ApiConfigMap, type ApiRegistry, 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 RouteDef, type RouteHandler, type RouteInterceptor, type RouteModuleDefault, type ScannedRoute, type ScannerOptions, ValidationError, buildOpenApiDocument, createApiRegistry, derivePath, generateFromRoot, generateRoutesManifest, renderDocsHtml, scanRoutes, toImportSpecifier };
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 };