@lpdjs/firestore-repo-service 2.0.1 → 2.1.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 (44) hide show
  1. package/README.md +93 -0
  2. package/dist/index-28xuzvxq.d.cts +530 -0
  3. package/dist/index-nquQrxW6.d.ts +530 -0
  4. package/dist/index.cjs +527 -886
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +53 -395
  7. package/dist/index.d.ts +53 -395
  8. package/dist/index.js +528 -878
  9. package/dist/index.js.map +1 -1
  10. package/dist/servers/admin/index.cjs +557 -0
  11. package/dist/servers/admin/index.cjs.map +1 -0
  12. package/dist/servers/admin/index.d.cts +4 -0
  13. package/dist/servers/admin/index.d.ts +4 -0
  14. package/dist/servers/admin/index.js +557 -0
  15. package/dist/servers/admin/index.js.map +1 -0
  16. package/dist/servers/crud/index.cjs +13 -0
  17. package/dist/servers/crud/index.cjs.map +1 -0
  18. package/dist/servers/crud/index.d.cts +180 -0
  19. package/dist/servers/crud/index.d.ts +180 -0
  20. package/dist/servers/crud/index.js +13 -0
  21. package/dist/servers/crud/index.js.map +1 -0
  22. package/dist/servers/index.cjs +567 -0
  23. package/dist/servers/index.cjs.map +1 -0
  24. package/dist/servers/index.d.cts +159 -0
  25. package/dist/servers/index.d.ts +159 -0
  26. package/dist/servers/index.js +567 -0
  27. package/dist/servers/index.js.map +1 -0
  28. package/dist/sync/bigquery.cjs +10 -0
  29. package/dist/sync/bigquery.cjs.map +1 -0
  30. package/dist/sync/bigquery.d.cts +58 -0
  31. package/dist/sync/bigquery.d.ts +58 -0
  32. package/dist/sync/bigquery.js +10 -0
  33. package/dist/sync/bigquery.js.map +1 -0
  34. package/dist/sync/index.cjs +92 -0
  35. package/dist/sync/index.cjs.map +1 -0
  36. package/dist/sync/index.d.cts +305 -0
  37. package/dist/sync/index.d.ts +305 -0
  38. package/dist/sync/index.js +92 -0
  39. package/dist/sync/index.js.map +1 -0
  40. package/dist/types-C0YpUzog.d.cts +902 -0
  41. package/dist/types-C0YpUzog.d.ts +902 -0
  42. package/dist/types-CwjqHFNC.d.cts +268 -0
  43. package/dist/types-CwjqHFNC.d.ts +268 -0
  44. package/package.json +72 -9
package/README.md CHANGED
@@ -22,6 +22,8 @@ Un service de repository type-safe pour Firestore avec génération automatique
22
22
  - 🔄 **Transactions** : Opérations transactionnelles type-safe
23
23
  - 🔗 **Relations** : Populate avec select typé (champs projetés)
24
24
  - 📄 **Pagination** : Curseurs, include avec relations, select
25
+ - 🔄 **Firestore → SQL Sync** : Réplication vers BigQuery via Pub/Sub avec admin UI
26
+ - 🖥️ **Serveur Admin** : UI admin auto-générée avec formulaires Zod, filtrage, navigation de relations
25
27
 
26
28
  ## 📦 Installation
27
29
 
@@ -594,6 +596,87 @@ for (const post of page.data) {
594
596
  }
595
597
  ```
596
598
 
599
+ ## 🔄 Firestore → SQL Sync
600
+
601
+ Répliquez automatiquement vos collections Firestore vers BigQuery (ou toute base SQL) via Cloud Pub/Sub.
602
+
603
+ ### Architecture
604
+
605
+ ```
606
+ Firestore Triggers → Cloud Pub/Sub → Worker → BigQuery
607
+ ```
608
+
609
+ ### Démarrage rapide
610
+
611
+ ```typescript
612
+ import { createFirestoreSync } from "@lpdjs/firestore-repo-service/sync";
613
+ import { BigQueryAdapter } from "@lpdjs/firestore-repo-service/sync/bigquery";
614
+ import { BigQuery } from "@google-cloud/bigquery";
615
+ import { PubSub } from "@google-cloud/pubsub";
616
+ import * as firestoreTriggers from "firebase-functions/v2/firestore";
617
+ import * as pubsubHandler from "firebase-functions/v2/pubsub";
618
+ import { onRequest } from "firebase-functions/v2/https";
619
+
620
+ const sync = createFirestoreSync(repos, {
621
+ deps: { firestoreTriggers, pubsubHandler, pubsub: new PubSub() },
622
+ adapter: new BigQueryAdapter({
623
+ bigquery: new BigQuery({ projectId: "my-project" }),
624
+ datasetId: "firestore_sync",
625
+ }),
626
+ topicPrefix: "firestore-sync",
627
+ autoMigrate: true,
628
+ admin: {
629
+ auth: { type: "basic", username: "admin", password: "secret" },
630
+ featuresFlag: {
631
+ healthCheck: true,
632
+ manualSync: true,
633
+ viewQueue: true,
634
+ configCheck: true,
635
+ },
636
+ },
637
+ repos: {
638
+ users: { tableName: "users", columnMap: { docId: "user_id" } },
639
+ posts: { columnMap: { docId: "post_id" } },
640
+ // Collection groups nécessitent triggerPath
641
+ comments: { triggerPath: "posts/{postId}/comments/{docId}" },
642
+ },
643
+ });
644
+
645
+ // Export des Cloud Functions
646
+ export const {
647
+ users_onCreate, users_onUpdate, users_onDelete, sync_users,
648
+ posts_onCreate, posts_onUpdate, posts_onDelete, sync_posts,
649
+ comments_onCreate, comments_onUpdate, comments_onDelete, sync_comments,
650
+ } = sync.functions;
651
+
652
+ // Admin (optionnel)
653
+ export const syncAdmin = onRequest(sync.adminHandler!);
654
+ ```
655
+
656
+ ### Sync Admin
657
+
658
+ L'endpoint admin fournit :
659
+
660
+ - **Health Check** : Compare le schéma attendu (Zod) vs les colonnes BigQuery réelles
661
+ - **Force Sync** : Re-synchronise tous les documents d'une collection
662
+ - **View Queues** : Inspecte les éléments en attente
663
+ - **Config Check** : Vérifie APIs GCP, topics, tables — avec commandes `gcloud` pour corriger
664
+
665
+ ### Adaptateur personnalisé
666
+
667
+ Implémentez l'interface `SqlAdapter` pour d'autres bases de données :
668
+
669
+ ```typescript
670
+ import type { SqlAdapter } from "@lpdjs/firestore-repo-service/sync";
671
+
672
+ class MyAdapter implements SqlAdapter {
673
+ // tableExists, getTableColumns, createTable, insertRows,
674
+ // upsertRows, deleteRows, executeRaw
675
+ }
676
+ ```
677
+
678
+ 📚 **Documentation complète** : [frs.lpdjs.fr/guide/sync](https://frs.lpdjs.fr/guide/sync)
679
+
597
680
  ## 🔧 Types exportés
598
681
 
599
682
  ```typescript
@@ -616,6 +699,16 @@ import type {
616
699
  PaginationWithIncludeOptions,
617
700
  PaginationWithIncludeOptionsTyped,
618
701
  } from "@lpdjs/firestore-repo-service";
702
+
703
+ // Sync types
704
+ import type {
705
+ FirestoreSyncConfig,
706
+ SqlAdapter,
707
+ SqlColumn,
708
+ SqlTableDef,
709
+ RepoSyncConfig,
710
+ SyncAdminConfig,
711
+ } from "@lpdjs/firestore-repo-service/sync";
619
712
  ```
620
713
 
621
714
  ## 🧪 Tests avec l'émulateur
@@ -0,0 +1,530 @@
1
+ import { z } from 'zod';
2
+ import { C as ConfiguredRepository, R as RepositoryConfig, F as FieldPath, n as FieldRole } from './types-C0YpUzog.cjs';
3
+
4
+ /**
5
+ * Minimal zero-dependency HTTP router for Firebase Functions.
6
+ * Compatible with any Express-like (req, res) handler.
7
+ *
8
+ * Supports:
9
+ * - Named path parameters (e.g. "/repos/:name/:id")
10
+ * - GET, POST, DELETE methods
11
+ * - Global middleware (before each route)
12
+ * - 404 / error fallbacks
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { MiniRouter } from "@lpdjs/firestore-repo-service/servers/admin";
17
+ *
18
+ * // Create router
19
+ * const router = new MiniRouter();
20
+ *
21
+ * // Add global middleware (executed before every route)
22
+ * router.use(async (req, res, next) => {
23
+ * console.log(`${req.method} ${req.url}`);
24
+ * await next();
25
+ * });
26
+ *
27
+ * // Auth middleware
28
+ * router.use((req, res, next) => {
29
+ * if (!req.headers?.authorization) {
30
+ * res.status(401).send("Unauthorized");
31
+ * return;
32
+ * }
33
+ * next();
34
+ * });
35
+ *
36
+ * // Define routes with path parameters
37
+ * router.get("/users", async (req, res) => {
38
+ * res.json({ users: await getAllUsers() });
39
+ * });
40
+ *
41
+ * router.get("/users/:id", async (req, res) => {
42
+ * const user = await getUser(req.params.id); // Access path params
43
+ * if (!user) {
44
+ * res.status(404).send("User not found");
45
+ * return;
46
+ * }
47
+ * res.json(user);
48
+ * });
49
+ *
50
+ * router.post("/users", async (req, res) => {
51
+ * const user = await createUser(req.body);
52
+ * res.status(201).json(user);
53
+ * });
54
+ *
55
+ * router.delete("/users/:id", async (req, res) => {
56
+ * await deleteUser(req.params.id);
57
+ * res.status(204).end();
58
+ * });
59
+ *
60
+ * // Custom 404 handler
61
+ * router.onNotFound((req, res) => {
62
+ * res.status(404).json({ error: "Route not found", path: req.url });
63
+ * });
64
+ *
65
+ * // Custom error handler
66
+ * router.onError((err, req, res) => {
67
+ * console.error("Error:", err);
68
+ * res.status(500).json({ error: "Internal server error" });
69
+ * });
70
+ *
71
+ * // Use with Firebase Functions
72
+ * export const api = onRequest(async (req, res) => {
73
+ * await router.handle(req, res);
74
+ * });
75
+ * ```
76
+ */
77
+ type AnyReq = {
78
+ method?: string;
79
+ url?: string;
80
+ /** Express originalUrl — preserved before any router stripping, contains the full path including the Firebase Functions prefix */
81
+ originalUrl?: string;
82
+ path?: string;
83
+ headers?: Record<string, string | string[] | undefined>;
84
+ body?: unknown;
85
+ query?: Record<string, string | string[] | undefined>;
86
+ };
87
+ type AnyRes = {
88
+ status: (code: number) => AnyRes;
89
+ set: (key: string, value: string) => AnyRes;
90
+ send: (body: string) => void;
91
+ json: (body: unknown) => void;
92
+ end: () => void;
93
+ };
94
+ type RouteParams = Record<string, string>;
95
+ type RouteHandler = (req: AnyReq & {
96
+ params: RouteParams;
97
+ }, res: AnyRes) => void | Promise<void>;
98
+ type Middleware = (req: AnyReq & {
99
+ params: RouteParams;
100
+ }, res: AnyRes, next: () => void | Promise<void>) => void | Promise<void>;
101
+ declare class MiniRouter {
102
+ private routes;
103
+ private middlewares;
104
+ private notFoundHandler;
105
+ private errorHandler;
106
+ use(middleware: Middleware): this;
107
+ get(path: string, handler: RouteHandler): this;
108
+ post(path: string, handler: RouteHandler): this;
109
+ put(path: string, handler: RouteHandler): this;
110
+ patch(path: string, handler: RouteHandler): this;
111
+ delete(path: string, handler: RouteHandler): this;
112
+ onNotFound(handler: RouteHandler): this;
113
+ onError(handler: (err: unknown, req: AnyReq, res: AnyRes) => void): this;
114
+ private addRoute;
115
+ handle(req: AnyReq, res: AnyRes): Promise<void>;
116
+ private runMiddlewareChain;
117
+ }
118
+
119
+ interface PageOptions {
120
+ title: string;
121
+ breadcrumb?: {
122
+ label: string;
123
+ href?: string;
124
+ }[];
125
+ flash?: {
126
+ type: "success" | "error";
127
+ message: string;
128
+ };
129
+ basePath?: string;
130
+ }
131
+ /** Firestore WHERE operator */
132
+ type WhereOp = "==" | "!=" | "<" | "<=" | ">" | ">=" | "array-contains" | "array-contains-any";
133
+ /** One active filter — serialized in URL as fv_{field} + fo_{field} */
134
+ interface FilterState {
135
+ field: string;
136
+ op: WhereOp;
137
+ value: string;
138
+ }
139
+ /** Per-column metadata used to render appropriate filter inputs/operators */
140
+ interface ColumnMeta {
141
+ name: string;
142
+ /** Innermost Zod type name, e.g. "ZodString", "ZodNumber" */
143
+ zodType: string;
144
+ }
145
+ /** Active sort state for the list view */
146
+ interface SortState {
147
+ field: string;
148
+ dir: "asc" | "desc";
149
+ }
150
+ /**
151
+ * Metadata for one relational action column appended to the list table.
152
+ * Each entry produces a dedicated button column (not a cell replacement).
153
+ */
154
+ interface RelationalFieldMeta {
155
+ /** Field in this document whose value is used to build the link */
156
+ key: string;
157
+ /** Column header label, e.g. "Posts" or "Author" */
158
+ column: string;
159
+ /** Name of the target repository in the admin registry */
160
+ targetRepo: string;
161
+ /** Field name on the target repo used for the lookup */
162
+ targetKey: string;
163
+ /**
164
+ * - "one" → doc[key] = docId on the target → link to edit page
165
+ * - "many" → doc[key] = filter value on the target → link to filtered list
166
+ */
167
+ type: "one" | "many";
168
+ }
169
+
170
+ /**
171
+ * HTTP route handlers for the admin ORM server.
172
+ * Each handler corresponds to a URL pattern registered in the router.
173
+ *
174
+ * Routes:
175
+ * GET / → dashboard GET /:repoName → document list (paginated)
176
+ * GET /:repoName/create → create form
177
+ * POST /:repoName/create → submit create
178
+ * GET /:repoName/:id/edit → edit form (pre-filled)
179
+ * POST /:repoName/:id/edit → submit update
180
+ * POST /:repoName/:id/delete → delete document
181
+ */
182
+
183
+ interface AdminRepoEntry {
184
+ name: string;
185
+ path: string;
186
+ repo: ConfiguredRepository<RepositoryConfig<any, any, any, any, any, any, any, any, any, any>>;
187
+ schema: z.ZodObject<any>;
188
+ /** document key field name (default: "docId") */
189
+ documentKey?: string;
190
+ /** Field name that stores the full Firestore document path (e.g. "documentPath") */
191
+ pathKey?: string;
192
+ /** Whether this repo is a collection group (subcollection) */
193
+ isGroup?: boolean;
194
+ /** Parent key field names needed to build a subcollection document ref (auto-detected from refCb) */
195
+ parentKeys?: string[];
196
+ /** Field name for the creation timestamp (auto-set on create) */
197
+ createdKey?: string;
198
+ /** List of columns to display in the table (defaults to schema keys) */
199
+ listColumns?: string[];
200
+ /** Page size for list view (default: 25) */
201
+ pageSize?: number;
202
+ /** Fields exposed in the filter bar (defaults to all schema keys) */
203
+ filterableFields?: string[];
204
+ /** Fields shown in the edit form (defaults to all schema fields if unset) */
205
+ mutableFields?: string[];
206
+ /** Fields shown in the create form (defaults to all schema fields if unset) */
207
+ createFields?: string[];
208
+ /** Whether delete is allowed (default: false) */
209
+ allowDelete?: boolean;
210
+ /**
211
+ * Fields that link to another repository.
212
+ * Populated automatically from the repo's relationalKeys.
213
+ */
214
+ relationalMeta?: RelationalFieldMeta[];
215
+ }
216
+ type RepoRegistry = Record<string, AdminRepoEntry>;
217
+
218
+ /**
219
+ * @module servers/admin
220
+ *
221
+ * Creates a static ORM admin interface served as a Firebase HTTPS function.
222
+ *
223
+ * Features:
224
+ * - Dashboard listing all registered repositories
225
+ * - Document list with cursor-based pagination
226
+ * - Create / Edit / Delete forms generated from Zod schemas
227
+ * - Forms map **exactly** to the repository model type
228
+ * - Zero JavaScript framework — plain HTML + inline CSS + vanilla JS
229
+ * - Body parsing for `application/x-www-form-urlencoded` (default HTML forms)
230
+ * and `application/json` (API clients)
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * import * as functions from "firebase-functions";
235
+ * import { z } from "zod";
236
+ * import { createAdminServer } from "@lpdjs/firestore-repo-service/servers/admin";
237
+ *
238
+ * const postSchema = z.object({
239
+ * title: z.string().min(1),
240
+ * content: z.string(),
241
+ * status: z.enum(["draft", "published"]),
242
+ * authorId: z.string(),
243
+ * });
244
+ *
245
+ * export const adminApp = functions.https.onRequest(
246
+ * createAdminServer({
247
+ * basePath: "/admin",
248
+ * repos: {
249
+ * posts: { repo: repos.posts, schema: postSchema, path: "posts" },
250
+ * },
251
+ * })
252
+ * );
253
+ * ```
254
+ */
255
+
256
+ /**
257
+ * Extracts the model type `T` from a `ConfiguredRepository`.
258
+ * @internal
259
+ */
260
+ type RepoModelType<TRepo> = TRepo extends ConfiguredRepository<RepositoryConfig<infer T, any, any, any, any, any, any, any, any, any>> ? T : never;
261
+ /**
262
+ * Configuration for a single repository in the admin server.
263
+ *
264
+ * @template TRepo - The `ConfiguredRepository` type; used to derive typed field names.
265
+ *
266
+ * If the repository was created with `createRepositoryConfig(schema)(config)`,
267
+ * the `schema` field is optional — it is auto-detected from the repo.
268
+ * Otherwise, pass `schema` explicitly.
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * posts: {
273
+ * repo: repos.posts,
274
+ * fieldsConfig: {
275
+ * title: ["create", "mutable", "filterable"],
276
+ * content: ["create", "mutable"],
277
+ * status: ["create", "filterable"],
278
+ * },
279
+ * allowDelete: true,
280
+ * }
281
+ * ```
282
+ */
283
+ interface AdminRepoConfig<TRepo extends ConfiguredRepository<any> = ConfiguredRepository<any>> {
284
+ /** The configured repository instance. Drives type inference for all other fields. */
285
+ repo: TRepo;
286
+ /**
287
+ * Zod schema — optional when the repo was created with `createRepositoryConfig(schema)`.
288
+ * Pass explicitly for repos created with the legacy `createRepositoryConfig<T>()` form.
289
+ */
290
+ schema?: z.ZodObject<any>;
291
+ /** Firestore collection path (for display only) */
292
+ path: string;
293
+ /** Key used to identify documents (default: "docId") */
294
+ documentKey?: string;
295
+ /** Columns to display in the list view (default: all schema keys) */
296
+ listColumns?: string[];
297
+ /** Number of documents per page in the list view (default: 25) */
298
+ pageSize?: number;
299
+ /**
300
+ * Per-field role configuration.
301
+ * Each key is a model field (with autocomplete); the value is an array of roles.
302
+ *
303
+ * Roles:
304
+ * - `"create"` — field is shown in the create form
305
+ * - `"mutable"` — field is shown in the edit form
306
+ * - `"filterable"` — field is shown in the filter bar
307
+ *
308
+ * If omitted, all schema fields are shown in all contexts.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * fieldsConfig: {
313
+ * title: ["create", "mutable", "filterable"],
314
+ * content: ["create", "mutable"],
315
+ * status: ["create", "filterable"],
316
+ * }
317
+ * ```
318
+ */
319
+ fieldsConfig?: Partial<Record<FieldPath<RepoModelType<TRepo>>, readonly FieldRole[]>>;
320
+ /**
321
+ * Whether to show the delete button in the list view.
322
+ * Default: false — delete is disabled unless explicitly set to true.
323
+ */
324
+ allowDelete?: boolean;
325
+ /**
326
+ * Relational action columns appended to the list table.
327
+ * Each entry adds a dedicated button that navigates to the linked repository.
328
+ *
329
+ * - **type "one"** (e.g. `userId` on a post) → button links to the target
330
+ * document edit page: `/{targetRepo}/{value}/edit`
331
+ * - **type "many"** (e.g. `docId` on a user) → button links to the target
332
+ * repo list filtered by value: `/{targetRepo}?fv_{targetKey}={value}`
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * users: {
337
+ * repo: repos.users,
338
+ * relationalFields: [
339
+ * { key: "docId", column: "Posts" }, // many → list of posts by this user
340
+ * ]
341
+ * }
342
+ * posts: {
343
+ * repo: repos.posts,
344
+ * relationalFields: [
345
+ * { key: "userId", column: "Author" }, // one → edit page of the user
346
+ * ]
347
+ * }
348
+ * ```
349
+ */
350
+ relationalFields?: {
351
+ key: keyof RepoModelType<TRepo> & string;
352
+ column: string;
353
+ }[];
354
+ }
355
+ /**
356
+ * HTTP Basic Auth configuration.
357
+ * The browser will show a native login dialog.
358
+ */
359
+ interface BasicAuthConfig {
360
+ type: "basic";
361
+ /** Realm displayed in the browser login dialog */
362
+ realm?: string;
363
+ username: string;
364
+ password: string;
365
+ }
366
+ /**
367
+ * Options for `createAdminServer`.
368
+ *
369
+ * Made generic so TypeScript can infer the model type of each repo entry
370
+ * and provide autocomplete + type-checking on `fieldsConfig`.
371
+ *
372
+ * @template TRepos - Shape of the repos map (inferred automatically at the call site)
373
+ */
374
+ interface AdminServerOptions<TRepos extends Record<string, ConfiguredRepository<any>> = Record<string, ConfiguredRepository<any>>> {
375
+ /**
376
+ * Base URL path of the function (e.g. "/admin").
377
+ * Must match the path where the Firebase Function is mounted.
378
+ * Default: "/"
379
+ */
380
+ basePath?: string;
381
+ /**
382
+ * Repository entries keyed by a display name.
383
+ * TypeScript infers the model type from each `repo` field,
384
+ * so `fieldsConfig` keys are typed to that model's field paths.
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * repos: {
389
+ * posts: {
390
+ * repo: repos.posts,
391
+ * fieldsConfig: {
392
+ * title: ["create", "mutable", "filterable"],
393
+ * content: ["create", "mutable"],
394
+ * status: ["create", "filterable"],
395
+ * },
396
+ * },
397
+ * }
398
+ * ```
399
+ */
400
+ repos: {
401
+ [K in keyof TRepos]: AdminRepoConfig<TRepos[K]>;
402
+ };
403
+ /** Whether to parse URL-encoded bodies. Default: true. */
404
+ parseBody?: boolean;
405
+ /**
406
+ * Authentication guard executed before every request.
407
+ * - Pass a `BasicAuthConfig` to enable HTTP Basic Auth.
408
+ * - Pass a `Middleware` function for custom auth logic.
409
+ */
410
+ auth?: BasicAuthConfig | Middleware;
411
+ /**
412
+ * Additional middleware functions executed after auth, before route handlers.
413
+ */
414
+ middleware?: Middleware[];
415
+ }
416
+ /**
417
+ * Creates an Express-compatible request handler for the admin ORM UI.
418
+ * Generates a complete admin interface with dashboard, lists, and CRUD forms.
419
+ *
420
+ * @template TRepos - Shape of the repos map (inferred automatically)
421
+ * @param options - Admin server configuration
422
+ * @returns Express-compatible request handler for Firebase Functions
423
+ *
424
+ * @example
425
+ * ```typescript
426
+ * // Basic admin server
427
+ * import { onRequest } from "firebase-functions/https";
428
+ * import { createAdminServer } from "@lpdjs/firestore-repo-service/servers/admin";
429
+ *
430
+ * export const admin = onRequest(
431
+ * createAdminServer({
432
+ * basePath: "/admin",
433
+ * repos: {
434
+ * users: {
435
+ * repo: repos.users,
436
+ * path: "users",
437
+ * },
438
+ * posts: {
439
+ * repo: repos.posts,
440
+ * path: "posts",
441
+ * },
442
+ * },
443
+ * })
444
+ * );
445
+ *
446
+ * // With HTTP Basic Auth
447
+ * export const admin = onRequest(
448
+ * createAdminServer({
449
+ * basePath: "/admin",
450
+ * auth: {
451
+ * type: "basic",
452
+ * realm: "Admin Area",
453
+ * username: "admin",
454
+ * password: process.env.ADMIN_PASSWORD!,
455
+ * },
456
+ * repos: { ... },
457
+ * })
458
+ * );
459
+ *
460
+ * // With custom auth middleware
461
+ * export const admin = onRequest(
462
+ * createAdminServer({
463
+ * auth: async (req, res, next) => {
464
+ * const token = req.headers?.authorization?.replace("Bearer ", "");
465
+ * if (!token || !(await verifyToken(token))) {
466
+ * res.status(401).send("Unauthorized");
467
+ * return;
468
+ * }
469
+ * next();
470
+ * },
471
+ * repos: { ... },
472
+ * })
473
+ * );
474
+ *
475
+ * // Full configuration with all options
476
+ * export const admin = onRequest(
477
+ * createAdminServer({
478
+ * basePath: "/admin",
479
+ * parseBody: true, // Parse URL-encoded bodies
480
+ * auth: { type: "basic", username: "admin", password: "secret" },
481
+ * middleware: [loggingMiddleware], // Additional middleware
482
+ * repos: {
483
+ * posts: {
484
+ * repo: repos.posts,
485
+ * path: "posts",
486
+ * documentKey: "docId", // Field used as document ID
487
+ * listColumns: ["title", "status", "createdAt"], // Columns in list
488
+ * pageSize: 25, // Items per page in list
489
+ * fieldsConfig: {
490
+ * title: ["create", "mutable", "filterable"],
491
+ * content: ["create", "mutable"],
492
+ * status: ["create", "mutable", "filterable"],
493
+ * userId: ["filterable"],
494
+ * },
495
+ * allowDelete: true, // Enable delete button
496
+ * relationalFields: [ // Relation navigation buttons
497
+ * { key: "userId", column: "Author" }, // Link to user
498
+ * { key: "docId", column: "Comments" }, // Link to comments list
499
+ * ],
500
+ * },
501
+ * users: {
502
+ * repo: repos.users,
503
+ * path: "users",
504
+ * fieldsConfig: {
505
+ * name: ["create", "mutable"],
506
+ * email: ["create", "mutable", "filterable"],
507
+ * isActive: ["mutable", "filterable"],
508
+ * },
509
+ * allowDelete: false,
510
+ * relationalFields: [
511
+ * { key: "docId", column: "Posts" },
512
+ * ],
513
+ * },
514
+ * },
515
+ * })
516
+ * );
517
+ *
518
+ * // Routes generated automatically:
519
+ * // GET /admin/ → Dashboard (list of repos)
520
+ * // GET /admin/:repo → Document list with pagination
521
+ * // GET /admin/:repo/create → Create form
522
+ * // POST /admin/:repo/create → Submit create
523
+ * // GET /admin/:repo/:id/edit → Edit form
524
+ * // POST /admin/:repo/:id/edit → Submit edit
525
+ * // POST /admin/:repo/:id/delete → Delete document
526
+ * ```
527
+ */
528
+ declare function createAdminServer<TRepos extends Record<string, ConfiguredRepository<any>>>(options: AdminServerOptions<TRepos>): (req: any, res: any) => Promise<void>;
529
+
530
+ export { type AdminRepoConfig as A, type BasicAuthConfig as B, type ColumnMeta as C, type FilterState as F, MiniRouter as M, type PageOptions as P, type RelationalFieldMeta as R, type SortState as S, type AdminRepoEntry as a, type AdminServerOptions as b, createAdminServer as c, type Middleware as d, type RepoRegistry as e, type RouteHandler as f };