@nest-admin/nestjs 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/admin-ui/assets/index-AyWOamlt.js +50 -0
- package/dist/admin-ui/assets/index-AyWOamlt.js.map +1 -0
- package/dist/admin-ui/assets/index-D4Eh84eD.css +2 -0
- package/dist/admin-ui/index.html +14 -0
- package/dist/chunk-7IXLRGGQ.js +356 -0
- package/dist/chunk-7IXLRGGQ.js.map +1 -0
- package/dist/drizzle.cjs +895 -0
- package/dist/drizzle.cjs.map +1 -0
- package/dist/drizzle.d.cts +335 -0
- package/dist/drizzle.d.ts +335 -0
- package/dist/drizzle.js +756 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/index.cjs +3247 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1652 -0
- package/dist/index.d.ts +1652 -0
- package/dist/index.js +2901 -0
- package/dist/index.js.map +1 -0
- package/dist/prisma.cjs +1159 -0
- package/dist/prisma.cjs.map +1 -0
- package/dist/prisma.d.cts +585 -0
- package/dist/prisma.d.ts +585 -0
- package/dist/prisma.js +995 -0
- package/dist/prisma.js.map +1 -0
- package/package.json +130 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.16.1_64048c8b0f36b1e1c1865447e0ade244/node_modules/tsup/assets/cjs_shims.js","../src/module.ts","../../core/src/auth/account.ts","../../core/src/metadata/created-field.ts","../../core/src/metadata/display-field.ts","../../core/src/metadata/relation-shape.ts","../../core/src/config/resources.ts","../../core/src/config/overrides.ts","../../core/src/errors/errors.ts","../src/admin/controller.ts","../src/auth/guard.ts","../src/tokens.ts","../src/http/execution-context.ts","../src/http/exception.filter.ts","../src/http/response.ts","../src/admin/service.ts","../src/auth/built-in.ts","../src/auth/password.ts","../src/auth/session.ts","../src/dashboard/service.ts","../src/http/query-parser.ts","../src/dashboard/contract.ts","../src/admin/metadata.dto.ts","../src/auth/controller.ts","../src/auth/contract.ts","../src/auth/resource.ts","../src/mount-path.ts","../src/ui/assets.ts","../src/ui/colour.ts","../src/ui/theme.ts","../src/ui/controller.ts"],"sourcesContent":["/**\n * `@nest-admin/nestjs` - the NestJS integration and the single published\n * package.\n *\n * ```ts\n * import { AdminModule } from '@nest-admin/nestjs'\n * import { PrismaAdapter } from '@nest-admin/nestjs/prisma'\n *\n * @Module({\n * imports: [AdminModule.forRoot({ adapter: new PrismaAdapter({ client: prisma }) })],\n * })\n * export class AppModule {}\n * ```\n *\n * The integration source imports `@nest-admin/core` only - it has no idea\n * which ORM is underneath. ORM adapters reach consumers through dedicated\n * subpaths (see `./prisma`).\n *\n * Implemented: the admin HTTP API (metadata + generic CRUD) behind a\n * host-supplied authentication boundary.\n * Not implemented: static serving of the admin UI, resource-level permissions,\n * and the configuration engine.\n */\n\nexport type { AdminAction, AdminActionResult, AdminActionsByModel } from './actions/contract.js'\n\nexport type { AdminHookContext, AdminHooks, AdminHooksByModel } from './hooks/contract.js'\n\nexport {\n AdminModule,\n type AdminModuleAsyncOptions,\n type AdminModuleFactoryOptions,\n type AdminModuleOptions,\n type AdminModuleOptionsFactory,\n} from './module.js'\n\n// The authentication boundary the consuming application implements. The guard\n// that calls it is internal - a consumer supplies the decision, not the wiring.\nexport { unsafeAllowAllRequests, type AdminAuth } from './auth/contract.js'\n\n// An implementation of that boundary, for applications that do not have an\n// identity system of their own. The contract above is unchanged and is still\n// the only way in; this is one thing that satisfies it.\nexport { adminAccountOf, builtInAuth, type BuiltInAuthOptions } from './auth/built-in.js'\nexport { hashAdminPassword, verifyAdminPassword } from './auth/password.js'\nexport { generateSessionSecret } from './auth/session.js'\nexport type { AdminAccount, AdminAccountStore, AdminAccountSummary } from '@nest-admin/core'\n\n// Resource-level authorization: which models a principal may see and act on.\n// The enforcement point is internal; the consumer supplies only the decision.\nexport type { AdminOperation, AdminResourceAuth, ResourceAuthorization } from './auth/resource.js'\n\n// What the dashboard shows. A closed set of four kinds, drawn by the interface\n// from data - the same arrangement as actions, and for the same reason.\nexport type {\n AdminDashboard,\n ChartWidget,\n CountWidget,\n DashboardWidget,\n ListWidget,\n StatResult,\n StatWidget,\n WidgetSpan,\n} from './dashboard/contract.js'\n\n// The HTTP contract. Exported as types so a consumer - and the future admin\n// UI - can type responses without restating the shapes.\nexport type {\n AdminErrorCode,\n AdminResponse,\n ErrorResponse,\n PageMeta,\n SuccessResponse,\n} from './http/response.js'\n\nexport type {\n FieldDto,\n FieldKindDto,\n MetadataDto,\n ActionDto,\n ModelDto,\n RelationDto,\n} from './admin/metadata.dto.js'\n\n// Core contracts are re-exported so consumers of the single public package can\n// type their own adapters and configuration without a second install.\nexport type {\n AdminErrorKind,\n ConstraintKind,\n FieldKind,\n FieldOverride,\n FieldWidget,\n FieldMetadata,\n FilterOperator,\n FilterRule,\n ListQuery,\n ModelMetadata,\n ModelOverride,\n ModelOverrides,\n OrmAdapter,\n Page,\n RecordData,\n RecordId,\n RelationCardinality,\n RelationMetadata,\n SortDirection,\n SortRule,\n} from '@nest-admin/core'\n\nexport {\n AdapterError,\n ConstraintError,\n FieldNotFoundError,\n ForbiddenError,\n InvalidQueryError,\n // Errors cross bundle boundaries, so `instanceof` is not reliable for them -\n // see the note in Core's `errors.ts`. Consumers that need to recognise a\n // framework error must have the same guard the framework uses.\n isNestAdminError,\n ModelNotFoundError,\n NestAdminError,\n RecordNotFoundError,\n UnauthorizedError,\n ValidationError,\n} from '@nest-admin/core'\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * `AdminModule` - the NestJS integration.\n *\n * ```ts\n * AdminModule.forRoot({\n * adapter: new PrismaAdapter({ client: prisma }),\n * auth: myAdminAuth,\n * })\n * ```\n *\n * The module wires an `OrmAdapter` and an `AdminAuth` into the admin HTTP\n * layer and does nothing else. It does not construct a database client, does\n * not authenticate anyone, does not read configuration from disk, and holds no\n * module-level mutable state - so two instances in the same process cannot\n * interfere with each other.\n *\n * It is not `@Global()`: making a library's providers globally visible in\n * someone else's application is a decision the application should make.\n */\nimport type { ModelOverrides, OrmAdapter, ResourceSelection } from '@nest-admin/core'\nimport {\n Logger,\n Module,\n type DynamicModule,\n type FactoryProvider,\n type ModuleMetadata,\n type Provider,\n type Type,\n} from '@nestjs/common'\nimport { RouterModule } from '@nestjs/core'\n\nimport { AdminController } from './admin/controller.js'\nimport { AdminAuthController } from './auth/controller.js'\nimport { AdminService } from './admin/service.js'\nimport { warnIfUnsafe, type AdminAuth } from './auth/contract.js'\nimport { AdminAuthGuard } from './auth/guard.js'\nimport { allowAllResources, type AdminResourceAuth } from './auth/resource.js'\nimport type { AdminActionsByModel } from './actions/contract.js'\nimport type { AdminHooksByModel } from './hooks/contract.js'\nimport { AdminExceptionFilter } from './http/exception.filter.js'\nimport { normaliseMountPath } from './mount-path.js'\nimport { uiAvailable, uiRoot } from './ui/assets.js'\nimport type { AdminDashboard } from './dashboard/contract.js'\nimport { assertUsableTheme, type AdminTheme } from './ui/theme.js'\nimport { AdminUiController } from './ui/controller.js'\nimport {\n ADMIN_ACTIONS,\n ADMIN_DASHBOARD,\n ADMIN_ADAPTER,\n ADMIN_AUTH,\n ADMIN_HOOKS,\n ADMIN_MODELS,\n ADMIN_MOUNT_PATH,\n ADMIN_OPTIONS,\n ADMIN_RESOURCE_AUTH,\n ADMIN_RESOURCES,\n ADMIN_THEME,\n ADMIN_UI_ROOT,\n} from './tokens.js'\n\nexport interface AdminModuleOptions {\n /**\n * The ORM adapter the admin reads and writes through.\n *\n * Constructed by the consuming application, never by the framework: under\n * Prisma 7 a client is built from a driver adapter, so only the application\n * knows the provider, the credentials and the connection strategy.\n */\n readonly adapter: OrmAdapter\n\n /**\n * Decides whether a request may reach the admin.\n *\n * **Required, deliberately.** The admin exposes every record in the database\n * and, through `/admin/meta`, the shape of the entire schema. An optional\n * option defaulting to \"open\" would mean a forgotten line in a config file\n * silently publishes the database - the failure would be invisible until\n * someone else found it.\n *\n * For local development and examples, pass `unsafeAllowAllRequests()`, which\n * is explicit at the call site and warns at startup.\n */\n readonly auth: AdminAuth\n\n /**\n * Decides which models this principal may see and act on.\n *\n * Optional, defaulting to allowing every model. Unlike `auth`, that default\n * is not a hole: `auth` is required, so the door is already shut, and\n * omitting this only means everyone admitted sees the whole schema - exactly\n * the behaviour before the option existed. Requiring it would break every\n * existing consumer to express a rule most applications do not have.\n *\n * Supply it when some models should be invisible or read-only to some\n * principals. A model denied for `'metadata'` disappears from\n * `GET /admin/meta`; a model denied for any other operation makes the request\n * fail with 403 before the ORM adapter is called.\n */\n readonly resourceAuth?: AdminResourceAuth\n\n /**\n * Where the admin is mounted. Defaults to `/admin`.\n *\n * Accepts `admin`, `/admin` and `/admin/` alike, and may be nested, as in\n * `/internal/admin`. It cannot be empty or `/`: these routes end in\n * `:model`, so mounting them at the root would capture every unmatched\n * request in the host application.\n *\n * The API and the UI move together. There is one mount point, not two.\n */\n readonly path?: string\n\n /**\n * Which models the admin exposes at all. Defaults to every model the adapter\n * reports.\n *\n * Structural, and not a substitute for `resourceAuth`: this is the same for\n * every principal, so an excluded model answers 404 rather than 403. Use it\n * for tables that are not domain data - session stores, migration\n * bookkeeping, queues - and `resourceAuth` for who may do what.\n *\n * A name that matches no model fails at startup rather than being ignored: a\n * typo in `exclude` would otherwise leave the model exposed.\n */\n readonly resources?: ResourceSelection\n\n /**\n * Per-model configuration: labels, widgets, ordering, and the two that are\n * enforced rather than suggested - hidden and readOnly.\n *\n * A hidden field is removed from the metadata every layer reads, so it cannot\n * be filtered, sorted, written, or returned. A name matching no model or\n * field fails at startup.\n */\n readonly models?: ModelOverrides\n\n /**\n * Application code that runs around a write, per model.\n *\n * Where hashing a password, deriving a slug or writing an audit row goes -\n * none of which can be inferred from a column type. See `AdminHooks`.\n */\n readonly hooks?: AdminHooksByModel\n\n /**\n * Buttons the application adds, per model.\n *\n * CRUD covers what a schema implies; \"publish\" and \"resend the invitation\"\n * are obvious to the domain and invisible to the database. See `AdminAction`.\n */\n readonly actions?: AdminActionsByModel\n\n /**\n * Branding the served page applies without a rebuild: an accent colour, a\n * title, a logo. Structural, because the page is rendered before any\n * provider exists.\n */\n readonly theme?: AdminTheme\n\n /**\n * What the dashboard shows.\n *\n * Omit it and the dashboard is built from the schema: a count per model, the\n * newest records, and a month of activity. Declaring widgets replaces that\n * rather than adding to it - a dashboard is a page someone designed, and\n * half-designed is worse than either.\n */\n readonly dashboard?: AdminDashboard\n\n /**\n * Directory holding the built admin UI.\n *\n * Defaults to the copy bundled inside this package, which is what a consumer\n * wants and why it is optional. Overriding it exists for this repository's\n * own tests, which run from `src` while the built UI lives in `dist`.\n *\n * @internal\n */\n readonly uiRoot?: string\n}\n\n/**\n * Reject options that cannot work, as early as the caller allows.\n *\n * For `forRoot` that is module construction; for `forRootAsync` it is whenever\n * the factory resolves, which is still during application start-up. Either way\n * it beats the alternative - an injection error on the first request, long\n * after the mistake and nowhere near it.\n *\n * `caller` names the method in the message so the reader is pointed at the call\n * they actually wrote.\n */\nfunction assertUsableOptions(options: AdminModuleOptions, caller: string): void {\n if (!options?.adapter) {\n throw new Error(\n `AdminModule.${caller}() requires an \\`adapter\\`. ` +\n 'Construct one in your application, for example ' +\n '`new PrismaAdapter({ client: prisma })`.',\n )\n }\n\n if (!options.auth || typeof options.auth.authorize !== 'function') {\n throw new Error(\n `AdminModule.${caller}() requires an \\`auth\\` implementation with an ` +\n '`authorize(context)` method. The admin API exposes every record and ' +\n 'the whole schema, so it is never public by default. ' +\n 'For local development only, pass `unsafeAllowAllRequests()`.',\n )\n }\n\n if (options.resourceAuth && typeof options.resourceAuth.authorize !== 'function') {\n throw new Error(\n `AdminModule.${caller}() was given a \\`resourceAuth\\` without an ` +\n '`authorize(resource)` method. Omit it to allow every model, or ' +\n 'supply an implementation.',\n )\n }\n\n warnIfUnsafe(options.auth)\n}\n\n/**\n * What a `forRootAsync` factory returns.\n *\n * Everything except the structural options, which are decided when the module\n * is defined and so cannot come from a provider - see `forRootAsync`.\n */\nexport type AdminModuleFactoryOptions = Omit<AdminModuleOptions, 'path' | 'uiRoot' | 'theme'>\n\n/** Supply options from a class rather than a factory function. */\nexport interface AdminModuleOptionsFactory {\n createAdminOptions(): AdminModuleFactoryOptions | Promise<AdminModuleFactoryOptions>\n}\n\nexport interface AdminModuleAsyncOptions {\n /** As `AdminModuleOptions.path`. Structural, so it is not from the factory. */\n readonly path?: string\n\n /** @internal As `AdminModuleOptions.uiRoot`. */\n readonly uiRoot?: string\n\n /**\n * As `AdminModuleOptions.theme`. Structural, so it is not from the factory:\n * the shell is rendered from it and no provider exists at that point.\n */\n readonly theme?: AdminTheme\n\n /** Modules whose providers the factory needs. */\n readonly imports?: ModuleMetadata['imports']\n\n /** Providers passed to `useFactory`, in order. */\n readonly inject?: FactoryProvider['inject']\n\n readonly useFactory?: (\n ...args: never[]\n ) => AdminModuleFactoryOptions | Promise<AdminModuleFactoryOptions>\n\n /** Instantiated by Nest, then asked for the options. */\n readonly useClass?: Type<AdminModuleOptionsFactory>\n\n /** An options factory the application already provides elsewhere. */\n readonly useExisting?: Type<AdminModuleOptionsFactory>\n}\n\n/** Say once, at startup, that the API works but the interface is not there. */\nfunction warnIfUiMissing(resolvedUiRoot: string, mountPath: string): void {\n if (uiAvailable(resolvedUiRoot)) return\n\n // Not fatal - the API is perfectly usable on its own, and a source checkout\n // that has not run the UI build lands here. Said once, at startup, rather\n // than as a 404 someone has to reverse-engineer.\n new Logger('NestAdmin').warn(\n `The admin UI was not found in this build; ${mountPath} will return 404. ` +\n `The API under ${mountPath} is unaffected.`,\n )\n}\n\n/**\n * The parts of the module that do not depend on how the options arrived.\n *\n * Both entry points produce the same routes, controllers and services; they\n * differ only in how the four option providers get their values, which is why\n * those are passed in.\n */\nfunction defineModule(\n mountPath: string,\n resolvedUiRoot: string,\n theme: AdminTheme | undefined,\n optionProviders: readonly Provider[],\n extraImports: ModuleMetadata['imports'] = [],\n): DynamicModule {\n return {\n module: AdminModule,\n imports: [\n ...extraImports,\n // The mount path is applied here, not on the controllers: `@Controller()`\n // is evaluated when the class is defined, long before either entry point\n // sees any options. `RouterModule` prefixes the module's routes and\n // preserves controller order, which the collision rule below depends on.\n RouterModule.register([{ path: mountPath, module: AdminModule }]),\n ],\n // Order matters and is the whole answer to the route collision. The UI\n // controller binds exactly two paths - the mount path itself and\n // `assets/:file` - and is matched first, so `assets` can never be read as a\n // model name. The auth controller claims `auth/*` next, for the same\n // reason and at the same cost: a model named `auth` is unreachable, as one\n // named `assets` or `actions` already was. Everything else falls through to\n // the API controller.\n controllers: [AdminUiController, AdminAuthController, AdminController],\n providers: [\n ...optionProviders,\n { provide: ADMIN_UI_ROOT, useValue: resolvedUiRoot },\n { provide: ADMIN_MOUNT_PATH, useValue: mountPath },\n { provide: ADMIN_THEME, useValue: theme },\n AdminService,\n // Provided so Nest can resolve them for `@UseGuards` / `@UseFilters` on\n // the controller. Deliberately not APP_GUARD or APP_FILTER: either would\n // take over behaviour for the whole host application rather than just the\n // admin routes.\n AdminAuthGuard,\n AdminExceptionFilter,\n ],\n exports: [AdminService],\n }\n}\n\n/**\n * Providers that produce the resolved options object for `forRootAsync`.\n *\n * Validation happens here rather than in each derived provider, so a bad\n * factory result is reported once, as the options resolve, and names the\n * method the reader called.\n */\n/**\n * The options that cannot come from the factory.\n *\n * Routes are registered and the shell is rendered when the module is defined,\n * which is before any provider exists - so these three are read from the\n * `forRootAsync` call itself, beside `imports` and `inject`.\n */\nconst STRUCTURAL_OPTIONS = ['path', 'uiRoot', 'theme'] as const\n\n/**\n * Refuse a structural option returned from the factory.\n *\n * `AdminModuleFactoryOptions` omits these three, so this looks like something\n * TypeScript already prevents. It does not: excess property checking applies\n * to an object literal assigned directly to a typed target, and a factory’s\n * return value reaches that target through a *function* type, where the check\n * does not run. The compiler accepts it and the option is silently dropped.\n *\n * Which is not hypothetical - this repository’s own reference consumer put\n * `theme` inside `useFactory`, typechecked clean, and served an unbranded\n * page. The only symptom was a colour that never arrived.\n */\nfunction assertNoStructuralOptions(resolved: AdminModuleFactoryOptions): void {\n const misplaced = STRUCTURAL_OPTIONS.filter((key) => key in (resolved as object))\n if (misplaced.length === 0) return\n\n const one = misplaced.length === 1\n throw new Error(\n `AdminModule.forRootAsync() received ${misplaced.join(', ')} from its factory. ` +\n `${one ? 'That option is' : 'Those options are'} structural: routes are registered ` +\n `before any provider exists, so ${one ? 'it' : 'they'} must be passed to ` +\n `forRootAsync() itself, beside \\`imports\\` and \\`inject\\`, rather than returned ` +\n `from \\`useFactory\\`.`,\n )\n}\n\nfunction optionsProviders(options: AdminModuleAsyncOptions): Provider[] {\n const validate = (resolved: AdminModuleFactoryOptions): AdminModuleOptions => {\n assertNoStructuralOptions(resolved)\n assertUsableOptions(resolved as AdminModuleOptions, 'forRootAsync')\n return resolved as AdminModuleOptions\n }\n\n if (options.useFactory) {\n return [\n {\n provide: ADMIN_OPTIONS,\n useFactory: async (...args: never[]) => validate(await options.useFactory!(...args)),\n inject: options.inject ?? [],\n },\n ]\n }\n\n const factoryClass = options.useExisting ?? options.useClass\n return [\n // `useClass` has to be instantiated by Nest before it can be asked;\n // `useExisting` is already provided by the application.\n ...(options.useClass ? [{ provide: options.useClass, useClass: options.useClass }] : []),\n {\n provide: ADMIN_OPTIONS,\n useFactory: async (factory: AdminModuleOptionsFactory) =>\n validate(await factory.createAdminOptions()),\n inject: [factoryClass as Type<AdminModuleOptionsFactory>],\n },\n ]\n}\n\n@Module({})\nexport class AdminModule {\n static forRoot(options: AdminModuleOptions): DynamicModule {\n assertUsableOptions(options, 'forRoot')\n\n // Throws on an unusable value, so a bad path fails at startup rather than\n // as a 404 on a route nobody can find.\n const mountPath = normaliseMountPath(options.path)\n\n const resolvedUiRoot = options.uiRoot ?? uiRoot()\n warnIfUiMissing(resolvedUiRoot, mountPath)\n assertUsableTheme(options.theme)\n\n return defineModule(mountPath, resolvedUiRoot, options.theme, [\n { provide: ADMIN_ADAPTER, useValue: options.adapter },\n { provide: ADMIN_RESOURCES, useValue: options.resources },\n { provide: ADMIN_MODELS, useValue: options.models },\n { provide: ADMIN_HOOKS, useValue: options.hooks },\n { provide: ADMIN_ACTIONS, useValue: options.actions },\n { provide: ADMIN_DASHBOARD, useValue: options.dashboard },\n { provide: ADMIN_AUTH, useValue: options.auth },\n // Always provided, so injection resolves whether or not the consumer\n // supplied a policy. The default permits every model.\n { provide: ADMIN_RESOURCE_AUTH, useValue: options.resourceAuth ?? allowAllResources() },\n ])\n }\n\n /**\n * The same module, with the adapter and the auth policy resolved through DI.\n *\n * For the ordinary case where those things are not available when the module\n * is declared: a `PrismaService` that belongs to another module, a connection\n * string that comes from `ConfigService`.\n *\n * ```ts\n * AdminModule.forRootAsync({\n * imports: [PrismaModule, ConfigModule],\n * inject: [PrismaService, ConfigService],\n * useFactory: (prisma: PrismaService, config: ConfigService) => ({\n * adapter: new PrismaAdapter({ client: prisma }),\n * auth: new SessionAdminAuth(config.get('ADMIN_ROLE')),\n * }),\n * })\n * ```\n *\n * `path` stays on this object rather than coming from the factory. Routes are\n * registered when the module is defined, which is before any provider has\n * been instantiated, so the mount path cannot wait for an injection - and a\n * `path` returned from the factory would be silently ignored, which is worse\n * than not offering it.\n */\n static forRootAsync(options: AdminModuleAsyncOptions): DynamicModule {\n if (!options?.useFactory && !options?.useClass && !options?.useExisting) {\n throw new Error(\n 'AdminModule.forRootAsync() requires one of `useFactory`, `useClass` ' +\n 'or `useExisting`. To configure the admin directly, use forRoot().',\n )\n }\n\n const mountPath = normaliseMountPath(options.path)\n const resolvedUiRoot = options.uiRoot ?? uiRoot()\n warnIfUiMissing(resolvedUiRoot, mountPath)\n assertUsableTheme(options.theme)\n\n // Each option provider reads from the single resolved object, so the\n // factory runs once however many of its values are injected.\n const derive = (\n token: symbol,\n read: (resolved: AdminModuleOptions) => unknown,\n ): FactoryProvider => ({ provide: token, useFactory: read, inject: [ADMIN_OPTIONS] })\n\n return defineModule(\n mountPath,\n resolvedUiRoot,\n options.theme,\n [\n ...optionsProviders(options),\n derive(ADMIN_ADAPTER, (resolved) => resolved.adapter),\n derive(ADMIN_RESOURCES, (resolved) => resolved.resources),\n derive(ADMIN_MODELS, (resolved) => resolved.models),\n derive(ADMIN_HOOKS, (resolved) => resolved.hooks),\n derive(ADMIN_ACTIONS, (resolved) => resolved.actions),\n derive(ADMIN_DASHBOARD, (resolved) => resolved.dashboard),\n derive(ADMIN_AUTH, (resolved) => resolved.auth),\n derive(ADMIN_RESOURCE_AUTH, (resolved) => resolved.resourceAuth ?? allowAllResources()),\n ],\n options.imports ?? [],\n )\n }\n}\n","/**\n * Where admin accounts live, as a contract.\n *\n * ## Why this exists at all\n *\n * Until 0.9.0 the answer to \"who may open the admin?\" was always the host\n * application's: it already had sessions, and `AdminAuth` asked it one\n * question. That is still right for a team that has an identity system, and\n * nothing about it changes.\n *\n * It is a wall for everyone else. An application with no login of its own had\n * to write a password hash, a session cookie and a form before the admin could\n * go anywhere near production - which is a strange thing to ask of a package\n * whose whole claim is that you do not build an admin.\n *\n * ## Why it is a contract rather than a table\n *\n * The same reason `OrmAdapter` is. An admin whose accounts can only live in\n * Prisma has learned about Prisma, and the second ORM would find out the hard\n * way. Everything here is plain data and promises; nothing knows what a\n * database is.\n *\n * ## These accounts are not the application's users\n *\n * Deliberately, and this is the point most worth getting right. The people who\n * administer a system are usually not rows in the table they administer, and\n * conflating the two means a customer record with a password that opens the\n * admin. The store is separate storage - a different model, or a different\n * database entirely - and the admin never reads or writes the application's\n * own users to decide who may sign in.\n */\n\n/** One account that may sign in to the admin. */\nexport interface AdminAccount {\n readonly id: string\n\n /**\n * What is typed into the login form.\n *\n * Called `email` because that is what it almost always is, and a name people\n * recognise is worth more than one that covers a case nobody has. A store is\n * free to hold usernames in it.\n */\n readonly email: string\n\n /** Shown in the interface. Falls back to the email when absent. */\n readonly name?: string | undefined\n\n /**\n * The stored password hash, in whatever form the hasher produced.\n *\n * Read by the sign-in check and by nothing else. It must never reach a\n * response, and the account the interface is told about is a projection that\n * does not include it.\n */\n readonly passwordHash: string\n\n /**\n * Suspended without being deleted.\n *\n * Distinct from removing the row: an account that has done things is worth\n * keeping for the record, and \"cannot sign in\" is not the same fact as\n * \"never existed\".\n */\n readonly disabled?: boolean | undefined\n}\n\n/**\n * The account as the interface may see it.\n *\n * A separate type rather than a comment on {@link AdminAccount}, because \"do\n * not send the hash\" is a rule that gets forgotten and a type that cannot\n * carry it does not.\n */\nexport interface AdminAccountSummary {\n readonly id: string\n readonly email: string\n readonly name?: string | undefined\n}\n\n/** Everything but the hash. The only shape that may reach a client. */\nexport function summarise(account: AdminAccount): AdminAccountSummary {\n return {\n id: account.id,\n email: account.email,\n ...(account.name !== undefined ? { name: account.name } : {}),\n }\n}\n\n/**\n * How the admin reaches its accounts.\n *\n * Read-only by design. Creating and editing accounts is the application's\n * business: it owns the storage, it knows whether that is a migration, a seed\n * script or a form somewhere else, and an admin that could mint its own\n * administrators is an escalation waiting for its first mistake.\n */\nexport interface AdminAccountStore {\n /**\n * Find an account by what was typed into the login form.\n *\n * Matching is the store's decision, and it should be case-insensitive on the\n * local part in practice: someone who registered as `Ada@example.com` will\n * type `ada@example.com` eventually.\n *\n * Returns `null` when there is none. The caller must not behave observably\n * differently for `null` than for a wrong password - see the sign-in code.\n */\n findByEmail(email: string): Promise<AdminAccount | null>\n\n /**\n * Find an account by its id, for a request that arrives with a session.\n *\n * Called on every authenticated request, so it should be cheap. It is also\n * what makes a disabled or deleted account stop working immediately rather\n * than when its session happens to expire.\n */\n findById(id: string): Promise<AdminAccount | null>\n\n /**\n * How many accounts exist.\n *\n * Used once, at startup, to say so when the answer is zero - an admin nobody\n * can sign in to is a configuration mistake that otherwise announces itself\n * as a login form that rejects everything.\n */\n count(): Promise<number>\n\n /**\n * Note that an account signed in. Optional.\n *\n * A store that does not care about this can leave it out; the sign-in path\n * does not wait for it and a failure is logged rather than surfaced, because\n * \"your login worked but we could not write down that it did\" is not\n * something the person signing in can act on.\n */\n recordLogin?(id: string): Promise<void>\n\n /**\n * What this store reads, for diagnostics. Optional.\n *\n * A model name, a table, a directory - whatever names the storage in a way a\n * person would recognise. It exists so a startup check can say something\n * useful rather than something generic: an admin that exposes its own\n * account model as an editable resource is an escalation, and a warning that\n * cannot name the model is a warning nobody acts on.\n *\n * Never used to decide anything, and never sent to a client.\n */\n readonly describes?: string\n}\n","/**\n * Which field records when a row appeared.\n *\n * A dashboard's most useful question is \"how much of this arrived recently\",\n * and answering it needs one column: the timestamp a record was created. Every\n * conventional schema has one and none of them declare it as such.\n *\n * ## Why this is a guess\n *\n * The metadata cannot tell a creation timestamp from any other generated date.\n * Prisma reports both `@default(now())` and `@updatedAt` the same way - the\n * adapter collapses them into `isGenerated`, because for *editing* they are the\n * same thing: neither is asked of a person. That is the right call for a form\n * and leaves nothing to distinguish them here.\n *\n * So this reads names, exactly as `displayFieldFor` does, and for the same\n * reason: the convention is near-universal and the alternative is a dashboard\n * that shows nothing until every application has annotated its schema.\n *\n * ## What it refuses to guess\n *\n * `updatedAt` and its variants are excluded rather than merely ranked lower. A\n * chart of \"records updated per day\" plotted under the heading \"new records\"\n * is worse than no chart: it is confidently wrong, and nothing about it looks\n * wrong. Where the convention is not followed, the answer is `undefined` and\n * the widget is simply not offered.\n */\nimport type { FieldMetadata, ModelMetadata } from './model.js'\n\n/**\n * Names that mean \"when this was created\", most conventional first.\n *\n * Snake case is included because a schema mapped onto an existing database\n * often keeps the column names it found there.\n */\nconst CREATED = ['createdAt', 'created_at', 'created', 'createdOn', 'insertedAt', 'inserted_at']\n\n/**\n * Names that must never be taken for it.\n *\n * Checked case-insensitively and by prefix, so `updatedAt`, `updated_at` and\n * `updateTime` are all excluded. Being wrong here is silent: a chart titled\n * \"new this month\" that is actually counting edits.\n */\nconst NOT_CREATED = ['updated', 'modified', 'deleted', 'archived', 'expires', 'expired']\n\nfunction isDate(field: FieldMetadata): boolean {\n return field.kind === 'datetime' && !field.isList && !field.relation\n}\n\nfunction excluded(name: string): boolean {\n const lower = name.toLowerCase()\n return NOT_CREATED.some((word) => lower.startsWith(word))\n}\n\n/**\n * The field that records when a record of this model was created, if the model\n * follows the convention.\n *\n * Order of preference:\n *\n * 1. a conventional name, most conventional first;\n * 2. the only remaining generated date on the model - a model with exactly\n * one date the database fills in has no ambiguity to resolve;\n * 3. nothing.\n *\n * The third is a real answer. A widget that cannot be built correctly is not\n * offered, which is a better outcome than one built on a column that means\n * something else.\n */\nexport function createdFieldFor(model: ModelMetadata): string | undefined {\n const dates = model.fields.filter(isDate)\n\n for (const conventional of CREATED) {\n const match = dates.find((field) => field.name.toLowerCase() === conventional.toLowerCase())\n if (match) return match.name\n }\n\n const generated = dates.filter((field) => field.isGenerated && !excluded(field.name))\n return generated.length === 1 ? generated[0]?.name : undefined\n}\n","/**\n * Which field names a record when it has to be referred to in one line.\n *\n * A relation is stored as an id, and an id is not something a person can read.\n * An admin that renders `cmtf50g710000mocjbygyfyfr` where it means \"Ada\n * Lovelace\" is technically correct and useless, so every model needs one field\n * that stands for the record.\n *\n * The rule lives in Core rather than in an adapter because it is a question\n * about a *model*, not about an ORM: the same reasoning applies whatever\n * produced the metadata. Two places need the answer and must agree on it - the\n * adapter, which selects the column when loading a relation, and the metadata\n * document, which tells the UI what to render.\n */\nimport type { FieldMetadata, ModelMetadata } from './model.js'\n\n/**\n * Conventional names for \"the human-readable one\", most specific first.\n *\n * Ordered by how strongly the name implies a label. `name` and `title` are\n * unambiguous; `email` is a real identifier people recognise; `slug` is a\n * last resort among the conventional names because it is machine-shaped, but\n * it is still readable, which an id is not.\n */\nconst CONVENTIONAL = ['name', 'title', 'label', 'displayName', 'username', 'email', 'slug']\n\n/** Could this field stand in for the record in a list or a dropdown? */\nfunction isReadable(field: FieldMetadata): boolean {\n return (\n field.kind === 'string' &&\n !field.isList &&\n !field.relation &&\n // A generated string is a cuid or a uuid: readable characters, no meaning.\n !field.isGenerated\n )\n}\n\n/**\n * Pick the field that names a record of this model.\n *\n * Order of preference:\n *\n * 1. a conventional name (`name`, `title`, ...), most specific first;\n * 2. any other unique string - unique suggests it identifies the record;\n * 3. any other plain string;\n * 4. the first primary-key field.\n *\n * The last step is the honest fallback rather than a good answer: a model with\n * nothing but an id and a timestamp has no readable field, and showing the id\n * is better than showing nothing. Adapters and applications may override the\n * result; this is the default, not a rule.\n */\nexport function displayFieldFor(model: ModelMetadata): string {\n // A declared choice wins outright. The rule below is a guess, and the\n // application knows things the schema does not.\n if (model.displayField !== undefined) return model.displayField\n\n const readable = model.fields.filter(isReadable)\n\n for (const candidate of CONVENTIONAL) {\n const match = readable.find((field) => field.name === candidate)\n if (match) return match.name\n }\n\n const unique = readable.find((field) => field.isUnique && !field.isId)\n if (unique) return unique.name\n\n const plain = readable.find((field) => !field.isId)\n if (plain) return plain.name\n\n return model.primaryKey[0] ?? model.fields[0]?.name ?? 'id'\n}\n","/**\n * Reading a to-many relation from the parent's side.\n *\n * From `User`, both `posts` (one-to-many) and `Post.tags` (many-to-many) look\n * identical: a list of related records. What differs is where the link is\n * stored, and therefore what changing it means.\n *\n * one-to-many the child owns a column. Attaching a post to a user rewrites\n * `post.authorId`, which also *detaches it from whoever had it*.\n * Detaching means clearing that column, which is impossible if\n * it is required.\n *\n * many-to-many neither side owns a column; the link lives in a join table.\n * Attaching and detaching add and remove a row there and change\n * nothing about either record.\n *\n * An interface that offers the same buttons for both is lying about one of\n * them, so the difference is resolved here, once, from metadata both adapters\n * already produce.\n */\nimport type { FieldMetadata, ModelMetadata } from './model.js'\n\nexport type RelationShape = 'to-one' | 'one-to-many' | 'many-to-many'\n\n/**\n * The field on the target model that is the other half of this relation.\n *\n * Matched by relation name, which is the only reliable pairing: two relations\n * between the same models (`author` and `reviewer`, both to `User`) are\n * otherwise indistinguishable. Returns `undefined` when the name is absent -\n * an adapter that does not supply one - or when the target is not in `models`.\n */\nexport function inverseRelationField(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): FieldMetadata | undefined {\n const relation = field.relation\n if (!relation?.name) return undefined\n\n const target = models.find((model) => model.name === relation.targetModel)\n if (!target) return undefined\n\n return target.fields.find(\n (candidate) => candidate.relation?.name === relation.name && candidate !== field,\n )\n}\n\n/**\n * What kind of relation this is, from the side the field is declared on.\n *\n * A to-many whose other half is also a list is a many-to-many. Without an\n * inverse to look at - no relation name, or a target outside this admin - a\n * to-many is reported as `one-to-many`, the more conservative answer: it is the\n * shape whose write operations have preconditions, so treating a many-to-many\n * as one costs a refused detach rather than a corrupted record.\n */\nexport function relationShape(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): RelationShape | undefined {\n const relation = field.relation\n if (!relation) return undefined\n if (relation.cardinality === 'one') return 'to-one'\n\n const inverse = inverseRelationField(field, models)\n return inverse?.relation?.cardinality === 'many' ? 'many-to-many' : 'one-to-many'\n}\n\n/**\n * Why a one-to-many relation cannot be detached, or `undefined` if it can.\n *\n * Detaching means clearing the child's foreign key, and a required column\n * cannot be cleared. The database would refuse it; saying so first is the\n * difference between \"you cannot remove this here, delete the record instead\"\n * and a constraint violation.\n */\nexport function detachBlockedReason(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): string | undefined {\n if (relationShape(field, models) !== 'one-to-many') return undefined\n\n const inverse = inverseRelationField(field, models)\n if (!inverse?.isRequired) return undefined\n\n const target = field.relation?.targetModel ?? 'the related model'\n return (\n `${target}.${inverse.name} is required, so a ${target} record cannot exist ` +\n `without one. Delete the record, or point it at something else, instead of ` +\n `detaching it.`\n )\n}\n","/**\n * Which models the admin exposes at all.\n *\n * Distinct from resource authorization, and the two answer different questions.\n * A `ResourceSelection` is structural: it decides what the admin *is*, the same\n * for everyone, and a model outside it does not exist as far as the admin is\n * concerned. `AdminResourceAuth` is per-principal: the model exists, and this\n * caller may or may not act on it.\n *\n * That difference is visible in the response. An excluded model answers 404 -\n * there is no such resource - where a denied one answers 403.\n */\n\nexport interface ResourceSelection {\n /**\n * When present, only these models are exposed. Everything else is dropped,\n * including models added to the schema later - which is the point: an\n * allow-list does not quietly grow when someone edits the schema.\n */\n readonly include?: readonly string[]\n\n /**\n * Models removed from the selection, applied after `include`.\n *\n * The usual reason is a table that is not domain data: session stores,\n * migration bookkeeping, queue tables.\n */\n readonly exclude?: readonly string[]\n}\n\n/** Anything with a name - `ModelMetadata`, or a test's stand-in for one. */\ninterface Named {\n readonly name: string\n}\n\n/**\n * Apply a selection, preserving the adapter's own order.\n *\n * Order comes from the schema rather than from `include`, so that adding a name\n * to the list does not silently reshuffle the admin. Deciding the order models\n * appear in is a separate feature and is not this option's job.\n */\nexport function selectModels<T extends Named>(\n models: readonly T[],\n selection?: ResourceSelection,\n): readonly T[] {\n if (!selection) return models\n\n const included = selection.include ? new Set(selection.include) : undefined\n const excluded = new Set(selection.exclude ?? [])\n\n return models.filter(\n (model) => (included === undefined || included.has(model.name)) && !excluded.has(model.name),\n )\n}\n\n/**\n * Names in the selection that no model answers to.\n *\n * Worth reporting rather than ignoring: a typo in `exclude` leaves the model\n * exposed, which is the opposite of what was asked for and is invisible until\n * someone finds the table in the admin. A typo in `include` is louder - the\n * model simply never appears - but has the same cause.\n */\nexport function unknownSelectionNames<T extends Named>(\n models: readonly T[],\n selection?: ResourceSelection,\n): readonly string[] {\n if (!selection) return []\n\n const known = new Set(models.map((model) => model.name))\n const referenced = [...(selection.include ?? []), ...(selection.exclude ?? [])]\n\n return [...new Set(referenced.filter((name) => !known.has(name)))]\n}\n","/**\n * Per-model and per-field configuration.\n *\n * The schema says what a model *is*; this says how the admin should treat it.\n * Two different questions, so they are two different inputs - a column being a\n * string is a fact about the database, and that column being a password is a\n * fact about the application.\n *\n * The overrides divide into two kinds, and the difference matters:\n *\n * behaviour `hidden`, `readOnly`, `displayField`. Enforced. A hidden\n * field is removed from the metadata every layer reads, so it\n * cannot be filtered, sorted, written or returned - see\n * `applyOverrides`.\n *\n * behaviour `writeOnly` too - accepted on a write and stripped from\n * every read.\n *\n * presentation `label`, `widget`, `order`. Passed to the client, which is\n * free to ignore them. Nothing depends on them being honoured.\n *\n * Anything in the first group that were only presentation would be a security\n * hole with a reassuring name.\n */\nimport type { FieldMetadata, ModelMetadata } from '../metadata/model.js'\n\n/**\n * How a field should be edited, when its type does not say enough.\n *\n * A `string` column may be a sentence, a password, an address or a colour, and\n * the schema cannot tell them apart. Deliberately a closed list: a client has\n * to know how to render each one, so an open string would mean silently\n * falling back to a plain input and no way to notice.\n */\nexport type FieldWidget = 'textarea' | 'password' | 'email' | 'url' | 'color' | 'json'\n\nexport interface FieldOverride {\n /**\n * Remove the field from the admin entirely.\n *\n * **Enforced, not cosmetic.** The field is dropped from the metadata before\n * anything reads it, so it is absent from the schema document, rejected in\n * filters and sorts, refused in writes, and stripped from every response.\n * A password hash is the reason this exists.\n */\n readonly hidden?: boolean\n\n /** Show the field, refuse to write it. Generated columns are already this. */\n readonly readOnly?: boolean\n\n /**\n * Write the field, never read it back. The mirror of `readOnly`.\n *\n * **Enforced, not cosmetic.** The column is left out of the query the adapter\n * makes and out of the projection applied to the result, so it is absent from\n * a list, from a detail page and from the record a write returns - while\n * still being accepted in the write itself.\n *\n * A password is what this is for. `hidden` is the wrong tool: it refuses the\n * field in both directions, so a hidden password column can never be set.\n */\n readonly writeOnly?: boolean\n\n /** What to call it, when the column name is not what people call the thing. */\n readonly label?: string\n\n /** How to edit it. See {@link FieldWidget}. */\n readonly widget?: FieldWidget\n\n /** Where it sits among the others. Lower comes first; unset comes last. */\n readonly order?: number\n}\n\n/**\n * Icons a model may be given in the navigation.\n *\n * A closed list, for the same reason `FieldWidget` is one: the interface has to\n * know how to draw each name, so an open string would mean silently rendering\n * nothing and no way to notice. It is also a bundle decision - the icon set has\n * about fifteen hundred entries, and only the ones named here are shipped.\n *\n * Chosen to cover what an admin's resources usually are rather than to be\n * complete. A model with no icon is drawn without one, which is the default and\n * is not a lesser state: identical icons down a column are decoration, and the\n * navigation reads better with none than with thirty of the same shape.\n */\nexport type ModelIcon =\n | 'users'\n | 'user'\n | 'building'\n | 'box'\n | 'package'\n | 'tag'\n | 'shopping-cart'\n | 'credit-card'\n | 'receipt'\n | 'file-text'\n | 'folder'\n | 'image'\n | 'calendar'\n | 'clock'\n | 'mail'\n | 'message-square'\n | 'bell'\n | 'star'\n | 'map-pin'\n | 'globe'\n | 'settings'\n | 'key'\n | 'shield'\n | 'database'\n | 'table'\n | 'layers'\n | 'list'\n | 'chart-bar'\n | 'activity'\n | 'truck'\n | 'gift'\n | 'bookmark'\n | 'link'\n\nexport interface ModelOverride {\n /** What to call the model. */\n readonly label?: string\n\n /**\n * Which icon to show beside it in the navigation.\n *\n * Presentational: the client may ignore it, and nothing depends on it being\n * honoured. See {@link ModelIcon} for why the list is closed.\n */\n readonly icon?: ModelIcon\n\n /**\n * Which field names a record, overriding what would be detected.\n *\n * The detection rule guesses well on conventional schemas and has no way to\n * know that a `code` column is the one people recognise.\n */\n readonly displayField?: string\n\n /** Where the model sits in the resource list. Lower first; unset last. */\n readonly order?: number\n\n readonly fields?: Readonly<Record<string, FieldOverride>>\n}\n\nexport type ModelOverrides = Readonly<Record<string, ModelOverride>>\n\n/** The override for one field, if the application declared one. */\nexport function fieldOverride(\n overrides: ModelOverrides | undefined,\n model: string,\n field: string,\n): FieldOverride | undefined {\n return overrides?.[model]?.fields?.[field]\n}\n\n/** Is this field one the application refuses to write? */\nexport function isReadOnly(\n overrides: ModelOverrides | undefined,\n model: string,\n field: FieldMetadata,\n): boolean {\n // Generated values are read-only whatever the configuration says: they are\n // the database's to produce, and were never writable.\n return field.isGenerated || fieldOverride(overrides, model, field.name)?.readOnly === true\n}\n\n/**\n * The models as the admin should see them.\n *\n * Hidden fields are **removed** rather than marked, so that every layer\n * downstream is correct without knowing this option exists. The query parser\n * rejects a filter on a field it cannot find; the metadata mapper cannot\n * describe one; write validation refuses one. A flag would have needed each of\n * those to remember to check it.\n *\n * A declared `displayField` is carried through the same way, so the adapter and\n * the metadata document agree on it without either consulting the config.\n */\nexport function applyOverrides(\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): readonly ModelMetadata[] {\n if (!overrides) return models\n\n return models.map((model) => {\n const override = overrides[model.name]\n if (!override) return model\n\n const hidden = new Set(\n Object.entries(override.fields ?? {})\n .filter(([, field]) => field.hidden === true)\n .map(([name]) => name),\n )\n\n const writeOnly = new Set(\n Object.entries(override.fields ?? {})\n .filter(([, field]) => field.writeOnly === true)\n .map(([name]) => name),\n )\n\n const kept = hidden.size === 0 ? model.fields : model.fields.filter((f) => !hidden.has(f.name))\n\n return {\n ...model,\n ...(override.displayField !== undefined ? { displayField: override.displayField } : {}),\n // Carried onto the metadata rather than looked up again later, so\n // everything downstream - the field scope, the projection, the DTO -\n // reads one flag instead of each re-deriving it from the configuration.\n fields:\n writeOnly.size === 0\n ? kept\n : kept.map((field) =>\n writeOnly.has(field.name) ? { ...field, writeOnly: true } : field,\n ),\n }\n })\n}\n\n/**\n * Names in the configuration that no model or field answers to.\n *\n * Reported so a typo fails at startup. The cost of ignoring one is not\n * symmetrical: a mistyped `label` is invisible and harmless, but a mistyped\n * `passwordHash` leaves the real column exposed while the configuration looks\n * like it is protecting it.\n */\nexport function unknownOverrideNames(\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): readonly string[] {\n if (!overrides) return []\n\n const unknown: string[] = []\n\n for (const [modelName, override] of Object.entries(overrides)) {\n const model = models.find((candidate) => candidate.name === modelName)\n if (!model) {\n unknown.push(modelName)\n continue\n }\n\n const names = new Set(model.fields.map((field) => field.name))\n\n if (override.displayField !== undefined && !names.has(override.displayField)) {\n unknown.push(`${modelName}.${override.displayField}`)\n }\n\n for (const fieldName of Object.keys(override.fields ?? {})) {\n if (!names.has(fieldName)) unknown.push(`${modelName}.${fieldName}`)\n }\n }\n\n return unknown\n}\n\n/**\n * Hidden fields that make creating a record impossible.\n *\n * A column that is required, is not produced by the database, and has no\n * default is a value the *caller* must supply. Hiding it removes the only way\n * to supply it, so every create fails - and fails in the database, as a\n * constraint violation the admin can only report as an internal error.\n *\n * Reported at startup for that reason: the configuration is self-defeating, and\n * the symptom otherwise appears far from the cause.\n */\nexport function unwritableHiddenFields(\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): readonly string[] {\n if (!overrides) return []\n\n const blocked: string[] = []\n\n for (const [modelName, override] of Object.entries(overrides)) {\n const model = models.find((candidate) => candidate.name === modelName)\n if (!model) continue\n\n for (const [fieldName, field] of Object.entries(override.fields ?? {})) {\n if (field.hidden !== true) continue\n\n const declared = model.fields.find((candidate) => candidate.name === fieldName)\n if (!declared) continue\n\n if (declared.isRequired && !declared.isGenerated && declared.defaultValue === undefined) {\n blocked.push(`${modelName}.${fieldName}`)\n }\n }\n }\n\n return blocked\n}\n","/**\n * Framework error vocabulary.\n *\n * Deliberately small. These exist so that adapters raise ORM-independent\n * errors and the transport layer can map them to status codes without knowing\n * which ORM produced them. Resist growing this taxonomy - add a new type only\n * when a caller genuinely needs to branch on it.\n *\n * ## Why these are not identified with `instanceof`\n *\n * A published bundle can contain more than one copy of this module. The\n * package ships two CommonJS entrypoints and each inlines its own copy of\n * Core, so an error thrown inside the Prisma adapter is an instance of a\n * *different* `FieldNotFoundError` class than the one the exception filter\n * holds. `instanceof` compares class identity, so it answered `false` and\n * every adapter-raised error was mapped to a generic 500 - a caller who\n * mistyped a sort field got \"internal error\" instead of \"unknown field\".\n *\n * That was invisible to this repository's own tests, which resolve Core to a\n * single source module, and only appeared when the built package was installed\n * and run. So errors are identified by *value* rather than identity: a\n * `Symbol.for` brand, which duplicate copies agree on by definition, plus a\n * stable `kind` string. Neither depends on which copy created the object.\n *\n * `scripts/verify-packed-consumer.mjs` asserts the arrangement every release:\n * one shared copy in ESM, one per entrypoint in CJS. If that ever changes, the\n * count changes there first.\n *\n * @experimental Draft contract. Expected to change during MVP implementation.\n */\n\n/**\n * Cross-copy brand.\n *\n * `Symbol.for` resolves through the global symbol registry, so two copies of\n * this file agree on the key where two `Symbol()` calls would not.\n */\nconst BRAND = Symbol.for('nest-admin.error')\n\n/**\n * Stable discriminator for each error type.\n *\n * A declared string rather than the class, so it survives duplicate bundles,\n * and rather than `name`, so it survives minification.\n */\nexport type AdminErrorKind =\n | 'unauthorized'\n | 'forbidden'\n | 'model-not-found'\n | 'field-not-found'\n | 'record-not-found'\n | 'invalid-query'\n /** Application code refused the input. Its message reaches the client. */\n | 'validation'\n /** The database refused the write: unique, foreign key, or required. */\n | 'constraint'\n | 'adapter'\n /** A subclass that declared no kind of its own. Treated as internal. */\n | 'unknown'\n\n/**\n * Base error type. Every error raised by Nest Admin extends it so that the\n * NestJS integration can distinguish framework errors from application errors\n * without depending on concrete subclasses.\n */\nexport class NestAdminError extends Error {\n /**\n * Which error this is.\n *\n * Subclasses override it with a literal. The base value covers anything that\n * extends this class without declaring one - the Prisma schema errors, for\n * instance - which the transport layer treats as internal.\n */\n readonly kind: AdminErrorKind = 'unknown'\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = new.target.name\n // Non-enumerable, so it can never reach a serialised response body.\n Object.defineProperty(this, BRAND, { value: true, enumerable: false })\n }\n}\n\n/**\n * Is this one of ours?\n *\n * Works across duplicate copies of this module, which `instanceof` does not.\n */\nexport function isNestAdminError(value: unknown): value is NestAdminError {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[BRAND] === true\n )\n}\n\n/** The requested model is not part of the admin's resource set. */\nexport class ModelNotFoundError extends NestAdminError {\n override readonly kind = 'model-not-found' as const\n\n constructor(\n readonly model: string,\n readonly availableModels: readonly string[] = [],\n ) {\n const known = availableModels.length > 0 ? ` Known models: ${availableModels.join(', ')}.` : ''\n super(`Unknown model \"${model}\".${known}`)\n }\n}\n\n/** A referenced field does not exist on the model, or cannot be used this way. */\nexport class FieldNotFoundError extends NestAdminError {\n override readonly kind = 'field-not-found' as const\n\n constructor(\n readonly model: string,\n readonly field: string,\n reason?: string,\n ) {\n super(`Unknown field \"${field}\" on model \"${model}\".${reason ? ` ${reason}` : ''}`)\n }\n}\n\n/** No record matched the given identifier. */\nexport class RecordNotFoundError extends NestAdminError {\n override readonly kind = 'record-not-found' as const\n\n constructor(\n readonly model: string,\n readonly id: unknown,\n ) {\n super(`No ${model} record found for id ${JSON.stringify(id)}.`)\n }\n}\n\n/**\n * The query is structurally invalid - an unusable operator/field combination,\n * a malformed value, or a request the adapter cannot express.\n */\nexport class InvalidQueryError extends NestAdminError {\n override readonly kind = 'invalid-query' as const\n}\n\n/**\n * The input is not acceptable, and the caller should be told why.\n *\n * Raised by application code - a hook rejecting a value, a rule the schema\n * cannot express - rather than by the framework. It exists because such a\n * refusal has to reach the person who typed the value, and the alternatives are\n * wrong in one direction or the other: `InvalidQueryError` claims the *query*\n * was malformed, and anything unrecognised becomes a generic 500 with the\n * message withheld.\n *\n * The message **is** forwarded to the client, which is the point of it and also\n * the responsibility that comes with it: whatever goes in is published.\n *\n * Naming the fields it is about is optional and worth doing. An interface that\n * knows which input was refused can say so next to that input, where the person\n * is looking, instead of in a banner above a form they then have to re-read.\n */\nexport class ValidationError extends NestAdminError {\n override readonly kind = 'validation' as const\n\n constructor(\n message: string,\n /** The inputs this is about. Empty when it is about the record as a whole. */\n readonly fields: readonly string[] = [],\n options?: { cause?: unknown },\n ) {\n super(message, options)\n }\n}\n\n/**\n * What the database refused, and about which fields.\n *\n * The distinction that matters is between a request that is *wrong* and a\n * database that is *broken*. A duplicate email, a foreign key pointing at\n * nothing, a missing required value - these are ordinary mistakes a person\n * makes in a form, and until they were told apart from a real failure the admin\n * answered every one of them with \"an internal error occurred\".\n *\n * The message is built here, from the constraint and the field names, rather\n * than taken from the ORM. An ORM's own text carries file paths, generated\n * query fragments and the values that collided, none of which should be\n * published - which is exactly why the generic 500 existed in the first place.\n */\nexport type ConstraintKind =\n /** A value that has to be unique is not. */\n | 'unique'\n /** A reference points at a record that is not there, or is still referenced. */\n | 'foreign-key'\n /** A value the database requires was not supplied. */\n | 'required'\n\nexport class ConstraintError extends NestAdminError {\n override readonly kind = 'constraint' as const\n\n constructor(\n readonly constraint: ConstraintKind,\n readonly model: string,\n /** The columns involved. Empty when the ORM did not say. */\n readonly fields: readonly string[] = [],\n ) {\n super(describeConstraint(constraint, model, fields))\n }\n}\n\n/**\n * A sentence for the person who filled in the form.\n *\n * Written from the field names alone, so it is safe to forward. Where the ORM\n * did not name a field the wording stays true rather than guessing at one.\n */\nfunction describeConstraint(\n constraint: ConstraintKind,\n model: string,\n fields: readonly string[],\n): string {\n const named = fields.length > 0 ? fields.join(', ') : undefined\n\n switch (constraint) {\n case 'unique':\n return named\n ? `Another ${model} already has this ${named}.`\n : `Another ${model} already has one of these values.`\n\n case 'foreign-key':\n return named\n ? `The ${named} does not refer to an existing record, or the record it refers to is still in use.`\n : `A reference on this ${model} does not point at an existing record, or is still in use.`\n\n case 'required':\n return named ? `${named} is required.` : `A required value on this ${model} is missing.`\n }\n}\n\n/**\n * The underlying ORM or database failed. Always wraps the original error as\n * `cause` so the real failure is never lost.\n */\nexport class AdapterError extends NestAdminError {\n override readonly kind = 'adapter' as const\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n }\n}\n\n/**\n * No authenticated identity was presented with the request.\n *\n * Raised by the host application's admin auth implementation, never by Core\n * itself - Core has no notion of a request, a header or a session, and must\n * not acquire one. It exists here so the transport layer can map it without\n * knowing which framework produced it.\n *\n * The default message is deliberately uninformative. An authentication failure\n * must not reveal whether a credential was absent, malformed, expired or\n * simply wrong.\n */\nexport class UnauthorizedError extends NestAdminError {\n override readonly kind = 'unauthorized' as const\n\n constructor(message = 'Authentication is required to access the admin API.') {\n super(message)\n }\n}\n\n/**\n * An identity was established, but it is not permitted to do this.\n *\n * Deliberately distinct from {@link UnauthorizedError}: collapsing the two\n * leaves a client unable to tell \"log in\" from \"you cannot do this\", and\n * pushes that guesswork into every consumer.\n */\nexport class ForbiddenError extends NestAdminError {\n override readonly kind = 'forbidden' as const\n\n constructor(message = 'You do not have permission to access the admin API.') {\n super(message)\n }\n}\n","/**\n * The single generic admin controller.\n *\n * There is deliberately no `UsersController` or `ProductsController`: models\n * are addressed by name at runtime, so one controller serves every model the\n * adapter reports. Generating per-model controllers would put the schema in\n * two places and make the admin unable to follow a schema change without a\n * rebuild.\n *\n * Routes:\n *\n * GET /admin/meta\n * GET /admin/:model\n * GET /admin/:model/:id\n * POST /admin/:model\n * PATCH /admin/:model/:id\n * DELETE /admin/:model/:id\n *\n * `:model` is the model name exactly as the adapter reports it - `User`, not\n * `users`. Declaring the literal segments - `meta`, `dashboard`, `actions` -\n * before `:model` is what keeps them reachable: route order decides, so a\n * literal declared afterwards is swallowed by the parameter and answers 404.\n * The cost is that a model of that name would be unreachable.\n */\nimport { InvalidQueryError, type RecordData, type RecordId } from '@nest-admin/core'\nimport type { ExecutionContext } from '@nestjs/common'\nimport {\n Body,\n Controller,\n Delete,\n Get,\n Param,\n Patch,\n Post,\n Query,\n UseFilters,\n UseGuards,\n} from '@nestjs/common'\n\nimport type { AdminActionResult } from '../actions/contract.js'\nimport { AdminAuthGuard } from '../auth/guard.js'\nimport { AdminContext } from '../http/execution-context.js'\nimport { AdminExceptionFilter } from '../http/exception.filter.js'\nimport { success, successPage, type SuccessResponse } from '../http/response.js'\nimport type { RawQuery } from '../http/query-parser.js'\nimport type { MetadataDto } from './metadata.dto.js'\nimport type { DashboardDto } from '../dashboard/service.js'\nimport { AdminService, type BulkDeleteResult } from './service.js'\n\n@Controller()\n// Guards at controller scope cover every handler below, including `meta`.\n// Applied here rather than as an APP_GUARD so the host application's own\n// routes keep their own authentication - see auth/guard.ts.\n@UseGuards(AdminAuthGuard)\n@UseFilters(AdminExceptionFilter)\nexport class AdminController {\n constructor(private readonly service: AdminService) {}\n\n /**\n * Declared before `:model` so the literal segment wins. A model named\n * exactly `meta` would be shadowed; Prisma model names are conventionally\n * capitalised (`Meta`) and matching is case-sensitive, so this is a narrow\n * and documented corner.\n */\n /**\n * `POST /admin/actions/:model/:action[/:id]` - run an application action.\n *\n * Under a reserved first segment, and declared before every `:model` route,\n * so `actions` is matched literally. The same arrangement already reserves\n * `meta` here and `assets` in the UI controller; the cost is that a model\n * called `actions` would be unreachable, which is documented rather than\n * guarded against.\n */\n @Post('actions/:model/:action')\n async runListAction(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('action') action: string,\n ): Promise<SuccessResponse<AdminActionResult>> {\n return success(await this.service.runAction(context, model, action))\n }\n\n @Post('actions/:model/:action/:id')\n async runRecordAction(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('action') action: string,\n @Param('id') id: string,\n ): Promise<SuccessResponse<AdminActionResult>> {\n return success(await this.service.runAction(context, model, action, id))\n }\n\n @Get('meta')\n async meta(@AdminContext() context: ExecutionContext): Promise<SuccessResponse<MetadataDto>> {\n return success(await this.service.getMetadata(context))\n }\n\n /**\n * `GET /admin/dashboard` - what the landing page shows.\n *\n * Declared before `:model` so the literal segment wins, as `meta` and\n * `actions` already are. The cost is the same and is documented with them: a\n * model named `dashboard` would be unreachable.\n */\n @Get('dashboard')\n async dashboard(\n @AdminContext() context: ExecutionContext,\n ): Promise<SuccessResponse<DashboardDto>> {\n return success(await this.service.getDashboard(context))\n }\n\n @Get(':model')\n async list(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Query() query: RawQuery,\n ): Promise<SuccessResponse<readonly RecordData[]>> {\n const page = await this.service.list(context, model, query)\n return successPage(page.data, { total: page.total, page: page.page, perPage: page.perPage })\n }\n\n @Get(':model/:id')\n async findOne(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('id') id: string,\n ): Promise<SuccessResponse<RecordData>> {\n return success(await this.service.findOne(context, model, id))\n }\n\n @Post(':model')\n async create(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Body() body: RecordData,\n ): Promise<SuccessResponse<RecordData>> {\n return success(await this.service.create(context, model, body))\n }\n\n @Patch(':model/:id')\n async update(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('id') id: string,\n @Body() body: RecordData,\n ): Promise<SuccessResponse<RecordData>> {\n return success(await this.service.update(context, model, id, body))\n }\n\n /**\n * Returns 200 with a `null` payload rather than 204, so every admin endpoint\n * answers with the same envelope and a client needs one response shape.\n */\n @Delete(':model/:id')\n async remove(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('id') id: string,\n ): Promise<SuccessResponse<null>> {\n await this.service.delete(context, model, id)\n return success(null)\n }\n\n /**\n * `DELETE /admin/:model` with `{ \"ids\": [...] }` - delete several records.\n *\n * One segment, so it cannot be confused with `/:model/:id`. The ids are in\n * the body rather than the query string because a selection of two hundred\n * would not survive a URL length limit, and a request that silently deletes\n * the first N of what was asked for is worse than one that fails.\n *\n * Answers 200 with both lists even when some records survived; see\n * `deleteMany` for why a partial result is not an error.\n */\n @Delete(':model')\n async removeMany(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Body() body: { ids?: unknown },\n ): Promise<SuccessResponse<BulkDeleteResult>> {\n const ids: unknown = body?.ids\n if (!Array.isArray(ids) || ids.some((id) => typeof id !== 'string' && typeof id !== 'number')) {\n throw new InvalidQueryError(\n 'Deleting records requires a body of the form { \"ids\": [\"...\", \"...\"] }.',\n )\n }\n\n return success(await this.service.deleteMany(context, model, ids as RecordId[]))\n }\n\n /**\n * `GET /admin/:model/:id/:relation` - a page of related records.\n *\n * Three segments, so it cannot be confused with `/:model/:id`. The query\n * string is the ordinary list query and describes the records being returned,\n * not the record they hang off.\n */\n @Get(':model/:id/:relation')\n async listRelated(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('id') id: string,\n @Param('relation') relation: string,\n @Query() query: RawQuery,\n ): Promise<SuccessResponse<readonly RecordData[]>> {\n const page = await this.service.listRelated(context, model, id, relation, query)\n return successPage(page.data, { total: page.total, page: page.page, perPage: page.perPage })\n }\n\n /**\n * `POST /admin/:model/:id/:relation` with `{ \"id\": \"...\" }` - link a record.\n *\n * The body carries only an id: this attaches something that already exists.\n * Creating a record and linking it in one request is a different operation\n * and is not this one.\n */\n @Post(':model/:id/:relation')\n async attachRelated(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('id') id: string,\n @Param('relation') relation: string,\n @Body() body: { id?: unknown },\n ): Promise<SuccessResponse<null>> {\n const targetId = body?.id\n if (typeof targetId !== 'string' && typeof targetId !== 'number') {\n throw new InvalidQueryError(\n 'Attaching a related record requires a body of the form { \"id\": \"...\" }.',\n )\n }\n\n await this.service.attachRelated(context, model, id, relation, targetId)\n return success(null)\n }\n\n /** `DELETE /admin/:model/:id/:relation/:targetId` - unlink, without deleting. */\n @Delete(':model/:id/:relation/:targetId')\n async detachRelated(\n @AdminContext() context: ExecutionContext,\n @Param('model') model: string,\n @Param('id') id: string,\n @Param('relation') relation: string,\n @Param('targetId') targetId: string,\n ): Promise<SuccessResponse<null>> {\n await this.service.detachRelated(context, model, id, relation, targetId)\n return success(null)\n }\n}\n","/**\n * The guard that enforces the admin authentication boundary.\n *\n * It contains no authentication logic of its own. Its entire job is to call\n * the `AdminAuth` the consuming application supplied and translate the outcome\n * into something the exception filter can map.\n *\n * It is attached to the admin controller with `@UseGuards`, never registered\n * as an `APP_GUARD`. A library that installs a global guard would start\n * authenticating the host application's own routes - the same reasoning that\n * kept the exception filter off `APP_FILTER` in Phase 3.\n */\nimport { ForbiddenError } from '@nest-admin/core'\nimport { Inject, Injectable, type CanActivate, type ExecutionContext } from '@nestjs/common'\n\nimport { ADMIN_AUTH } from '../tokens.js'\nimport type { AdminAuth } from './contract.js'\n\n@Injectable()\nexport class AdminAuthGuard implements CanActivate {\n constructor(@Inject(ADMIN_AUTH) private readonly auth: AdminAuth) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n // Errors thrown by `authorize` are intentionally not caught. `Unauthorized`\n // and `Forbidden` are the documented way to deny, and the filter maps them\n // to 401 and 403. Anything else the host throws is a bug in the host's auth\n // code, and the filter turns it into a generic 500 without echoing its\n // message - so a stray error cannot become an accidental allow, nor leak.\n const decision = await this.auth.authorize(context)\n\n // `void` means allowed. `false` is accepted as a denial so a guard written\n // in the reflexive NestJS style fails closed; see AdminAuth.authorize.\n if (decision === false) {\n throw new ForbiddenError()\n }\n\n return true\n }\n}\n","/**\n * Injection tokens.\n *\n * Symbols rather than strings so they cannot collide with a token in the host\n * application, and cannot be injected by accident.\n */\n\n/** The `OrmAdapter` the consuming application supplied to `AdminModule`. */\nexport const ADMIN_ADAPTER = Symbol('NEST_ADMIN_ADAPTER')\n\n/** The `AdminAuth` the consuming application supplied to `AdminModule`. */\nexport const ADMIN_AUTH = Symbol('NEST_ADMIN_AUTH')\n\n/** The optional `AdminResourceAuth` the application supplied to `AdminModule`. */\nexport const ADMIN_RESOURCE_AUTH = Symbol('NEST_ADMIN_RESOURCE_AUTH')\n\n/**\n * Directory holding the built admin UI.\n *\n * Internal, and not part of the public API. It exists so the package can be\n * tested from source against the real built artefact, which lives in `dist`\n * while the tests run from `src`.\n */\nexport const ADMIN_UI_ROOT = Symbol('NEST_ADMIN_UI_ROOT')\n\n/**\n * The normalised path the admin is mounted under, e.g. `/admin`.\n *\n * The router already knows it, but the UI controller needs it too: the served\n * HTML carries absolute asset URLs and hands the base to the browser.\n */\nexport const ADMIN_MOUNT_PATH = Symbol('NEST_ADMIN_MOUNT_PATH')\n\n/**\n * The `ResourceSelection` the application supplied, if any.\n *\n * Structural rather than per-principal: it decides which models the admin has\n * at all. Always provided, so injection resolves either way.\n */\nexport const ADMIN_RESOURCES = Symbol('NEST_ADMIN_RESOURCES')\n\n/**\n * The options object `forRootAsync` resolved.\n *\n * Internal. Every other option provider derives from it, so the factory runs\n * once no matter how many of its values are injected.\n */\nexport const ADMIN_OPTIONS = Symbol('NEST_ADMIN_OPTIONS')\n\n/**\n * Per-model configuration the application supplied, if any.\n *\n * Labels, widgets, ordering, and the two that are enforced rather than\n * suggested: `hidden` and `readOnly`.\n */\nexport const ADMIN_MODELS = Symbol('NEST_ADMIN_MODELS')\n\n/** Application code that runs around a write. */\nexport const ADMIN_HOOKS = Symbol('NEST_ADMIN_HOOKS')\n\n/** Application-defined actions, per model. */\nexport const ADMIN_ACTIONS = Symbol('NEST_ADMIN_ACTIONS')\n\n/**\n * The widgets an application put on the dashboard.\n *\n * Absent means a dashboard built from the schema alone, which is the common\n * case and the one that has to look right without anybody configuring it.\n */\nexport const ADMIN_DASHBOARD = Symbol('NEST_ADMIN_DASHBOARD')\n\n/** Branding the served page applies without a rebuild. */\nexport const ADMIN_THEME = Symbol('NEST_ADMIN_THEME')\n","/**\n * Hands the request's `ExecutionContext` to a controller handler.\n *\n * Resource authorization is enforced in `AdminService`, because that is the one\n * place both the metadata document and every CRUD operation pass through. But\n * the policy needs the request - that is where the host attached its principal -\n * and a service is not request-aware.\n *\n * The two obvious ways to bridge that are both worse than this one:\n *\n * - Making `AdminService` request-scoped changes the DI semantics of a provider\n * the module exports, and makes the controller request-scoped with it.\n * - Having the guard stash the context on the request object couples the\n * service to the guard having run.\n *\n * A parameter decorator's factory is handed the `ExecutionContext` directly, so\n * the controller can simply pass it down. No scope change, no hidden coupling.\n */\nimport { createParamDecorator, type ExecutionContext } from '@nestjs/common'\n\nexport const AdminContext = createParamDecorator(\n (_data: unknown, context: ExecutionContext): ExecutionContext => context,\n)\n","/**\n * Core errors -> HTTP responses.\n *\n * Centralised on purpose: controllers never construct HTTP exceptions, so the\n * mapping cannot drift between endpoints.\n *\n * This filter is applied to the admin controller with `@UseFilters`, not\n * registered as an `APP_FILTER`. A library that installs a global exception\n * filter would silently take over error handling for the entire host\n * application, which is not ours to change.\n */\nimport {\n isNestAdminError,\n type ConstraintError,\n type FieldNotFoundError,\n type ModelNotFoundError,\n type NestAdminError,\n type RecordNotFoundError,\n type ValidationError,\n} from '@nest-admin/core'\nimport {\n Catch,\n HttpException,\n HttpStatus,\n Logger,\n type ArgumentsHost,\n type ExceptionFilter,\n} from '@nestjs/common'\n\nimport { failure, type AdminErrorCode, type ErrorResponse } from './response.js'\n\ninterface MappedError {\n readonly status: number\n readonly code: AdminErrorCode\n readonly message: string\n readonly details?: Readonly<Record<string, unknown>>\n}\n\n/**\n * The generic failure. Used for everything not explicitly mapped below, so a\n * new internal error type can never start leaking its message by default.\n */\nconst INTERNAL: MappedError = {\n status: HttpStatus.INTERNAL_SERVER_ERROR,\n code: 'INTERNAL_ERROR',\n message: 'An internal error occurred while handling the request.',\n}\n\n/**\n * Map an error to its HTTP representation.\n *\n * Dispatch is on `error.kind`, not `instanceof`. The published package ships\n * two CommonJS entrypoints that each inline their own copy of Core, so an error\n * thrown inside the Prisma adapter is an instance of a different class object\n * than the one imported here - `instanceof` answered `false` and mapped every\n * adapter-raised error to a generic 500. See `errors.ts` in Core.\n *\n * Only errors on this allowlist have their message forwarded to the client.\n * That is a security decision, not a stylistic one: `AdapterError` wraps raw\n * ORM failures whose messages contain filesystem paths and generated query\n * fragments, and the Prisma schema errors carry absolute paths. Everything\n * unrecognised becomes the generic 500 above, and the real error is logged.\n */\nfunction mapError(error: unknown): MappedError {\n if (!isNestAdminError(error)) return INTERNAL\n\n switch (error.kind) {\n // Auth first. No `details` on either: echoing anything about why a request\n // was refused hands a prober information it did not have.\n case 'unauthorized':\n return {\n status: HttpStatus.UNAUTHORIZED,\n code: 'UNAUTHORIZED',\n message: error.message,\n }\n\n case 'forbidden':\n return {\n status: HttpStatus.FORBIDDEN,\n code: 'FORBIDDEN',\n message: error.message,\n }\n\n case 'model-not-found':\n return {\n status: HttpStatus.NOT_FOUND,\n code: 'MODEL_NOT_FOUND',\n message: error.message,\n details: { model: (error as ModelNotFoundError).model },\n }\n\n case 'record-not-found':\n return {\n status: HttpStatus.NOT_FOUND,\n code: 'RECORD_NOT_FOUND',\n message: error.message,\n details: {\n model: (error as RecordNotFoundError).model,\n id: (error as RecordNotFoundError).id,\n },\n }\n\n case 'field-not-found':\n return {\n status: HttpStatus.BAD_REQUEST,\n code: 'FIELD_NOT_FOUND',\n message: error.message,\n details: {\n model: (error as FieldNotFoundError).model,\n field: (error as FieldNotFoundError).field,\n },\n }\n\n // Raised by application code to refuse an input. The message is\n // forwarded, which is what it is for.\n case 'validation': {\n const refusal = error as ValidationError\n return {\n status: HttpStatus.BAD_REQUEST,\n code: 'VALIDATION_ERROR',\n message: error.message,\n // Only when it named them. An empty list would tell a client the\n // refusal is about no field in particular, which is not the same as\n // not saying - and the interface treats the two differently.\n ...(refusal.fields.length > 0 ? { details: { fields: refusal.fields } } : {}),\n }\n }\n\n // The database refused the write for a reason the caller can act on. The\n // message is built from field names rather than taken from the ORM, so it\n // carries no paths or query fragments and is safe to forward.\n case 'constraint': {\n const failure = error as ConstraintError\n return {\n // A unique clash or a reference still in use is a conflict with data\n // that already exists; a missing required value is a bad request.\n status: failure.constraint === 'required' ? HttpStatus.BAD_REQUEST : HttpStatus.CONFLICT,\n code: 'CONSTRAINT_VIOLATION',\n message: error.message,\n details: { constraint: failure.constraint, fields: failure.fields },\n }\n }\n\n case 'invalid-query':\n return {\n status: HttpStatus.BAD_REQUEST,\n code: 'INVALID_QUERY',\n message: error.message,\n }\n\n // 'adapter', 'unknown', and any kind added later without a mapping. The\n // default stays generic so a new internal error cannot start leaking.\n default:\n return INTERNAL\n }\n}\n\n/**\n * The message this error is allowed to show a client.\n *\n * Exported because a response can carry a failure without *being* one: a bulk\n * delete reports what happened to each record, and one of those may have been\n * refused. Forwarding the raw message there would hand a client exactly what\n * the filter exists to withhold, through a 200.\n */\nexport function clientMessage(error: unknown): string {\n return mapError(error).message\n}\n\n@Catch()\nexport class AdminExceptionFilter implements ExceptionFilter {\n private readonly logger = new Logger('NestAdmin')\n\n catch(exception: unknown, host: ArgumentsHost): void {\n // Let Nest's own exceptions through untouched - a 404 from an unmatched\n // route or a payload-too-large is not ours to reinterpret.\n if (exception instanceof HttpException) {\n throw exception\n }\n\n const mapped = mapError(exception)\n\n if (mapped.status >= HttpStatus.INTERNAL_SERVER_ERROR) {\n // The client gets a generic message; the operator gets everything.\n this.logger.error(\n isNestAdminError(exception) && exception.kind === 'adapter'\n ? `Adapter failure: ${exception.message}`\n : 'Unhandled error while handling an admin request',\n exception instanceof Error ? exception.stack : String(exception),\n )\n }\n\n const body: ErrorResponse = failure(mapped.code, mapped.message, mapped.details)\n const response = host.switchToHttp().getResponse<{\n status(code: number): { json(body: unknown): void }\n }>()\n response.status(mapped.status).json(body)\n }\n}\n\n/**\n * Exported for tests and for consumers that want the same mapping elsewhere.\n *\n * Delegates to Core's brand check rather than `instanceof` for the reason given\n * on `mapError`: duplicate copies of Core mean class identity is not reliable.\n */\nexport function isFrameworkError(error: unknown): error is NestAdminError {\n return isNestAdminError(error)\n}\n","/**\n * The response envelope.\n *\n * Every admin endpoint returns the same two shapes, so a generic frontend can\n * branch on one field rather than on status codes plus per-endpoint knowledge.\n *\n * Success: { success: true, data: <payload>, meta?: <pagination> }\n * Failure: { success: false, error: { code, message, details? } }\n *\n * @experimental The HTTP contract is expected to change before 1.0.\n */\n\n/** Pagination facts a list response carries alongside its rows. */\nexport interface PageMeta {\n readonly total: number\n readonly page: number\n readonly perPage: number\n}\n\nexport interface SuccessResponse<T> {\n readonly success: true\n readonly data: T\n readonly meta?: PageMeta\n}\n\n/**\n * Stable, machine-readable error codes.\n *\n * Clients branch on these, never on the human-readable message. Adding a code\n * is a compatible change; renaming one is not.\n */\nexport type AdminErrorCode =\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'MODEL_NOT_FOUND'\n | 'RECORD_NOT_FOUND'\n | 'FIELD_NOT_FOUND'\n | 'INVALID_QUERY'\n | 'VALIDATION_ERROR'\n | 'CONSTRAINT_VIOLATION'\n | 'INTERNAL_ERROR'\n\nexport interface ErrorResponse {\n readonly success: false\n readonly error: {\n readonly code: AdminErrorCode\n readonly message: string\n /** Structured context, e.g. `{ model, field }`. Never internal detail. */\n readonly details?: Readonly<Record<string, unknown>>\n }\n}\n\nexport type AdminResponse<T> = SuccessResponse<T> | ErrorResponse\n\nexport function success<T>(data: T): SuccessResponse<T> {\n return { success: true, data }\n}\n\n/**\n * A list response. Rows live in `data` and pagination in `meta`, rather than\n * nesting the whole `Page` under `data` - `data.data` would be an awkward thing\n * to hand a frontend.\n */\nexport function successPage<T>(data: readonly T[], meta: PageMeta): SuccessResponse<readonly T[]> {\n return { success: true, data, meta }\n}\n\nexport function failure(\n code: AdminErrorCode,\n message: string,\n details?: Readonly<Record<string, unknown>>,\n): ErrorResponse {\n return { success: false, error: { code, message, ...(details ? { details } : {}) } }\n}\n","/**\n * Coordinates admin operations between the HTTP layer and the ORM adapter.\n *\n * It speaks Core vocabulary only. It has no idea which ORM is underneath, and\n * it must stay that way: this is the layer that would otherwise accumulate\n * \"just this once\" ORM-specific branches.\n *\n * It is also the **single** resource-authorization boundary. The controller\n * stays thin, the metadata mapper stays a mapper, and the adapter stays\n * authorization-agnostic - so there is exactly one place to read to know what\n * is enforced, and exactly one place a mistake can hide.\n */\nimport {\n applyOverrides,\n detachBlockedReason,\n FieldNotFoundError,\n ForbiddenError,\n InvalidQueryError,\n isNestAdminError,\n isReadOnly,\n ModelNotFoundError,\n RecordNotFoundError,\n type ListQuery,\n type FieldMetadata,\n type ModelMetadata,\n type OrmAdapter,\n type Page,\n type RecordData,\n type RecordId,\n type ModelOverrides,\n type ResourceSelection,\n selectModels,\n unknownOverrideNames,\n unknownSelectionNames,\n unwritableHiddenFields,\n} from '@nest-admin/core'\nimport {\n Inject,\n Injectable,\n Logger,\n type ExecutionContext,\n type OnModuleInit,\n} from '@nestjs/common'\n\nimport { builtInRuntimeOf } from '../auth/built-in.js'\nimport { buildDashboard, type DashboardDto } from '../dashboard/service.js'\nimport type { AdminDashboard } from '../dashboard/contract.js'\nimport type { AdminAuth } from '../auth/contract.js'\nimport type { AdminOperation, AdminResourceAuth } from '../auth/resource.js'\nimport { clientMessage } from '../http/exception.filter.js'\nimport { parseListQuery, type RawQuery } from '../http/query-parser.js'\nimport type { AdminActionResult, AdminActionsByModel } from '../actions/contract.js'\nimport type { AdminHooksByModel } from '../hooks/contract.js'\nimport {\n ADMIN_ACTIONS,\n ADMIN_ADAPTER,\n ADMIN_AUTH,\n ADMIN_DASHBOARD,\n ADMIN_HOOKS,\n ADMIN_MODELS,\n ADMIN_RESOURCE_AUTH,\n ADMIN_RESOURCES,\n} from '../tokens.js'\nimport {\n toMetadataDto,\n type ActionDto,\n type MetadataDto,\n type ModelPermissionsDto,\n} from './metadata.dto.js'\n\n/**\n * How many records one bulk delete may name.\n *\n * Not a performance limit - it is a blast-radius limit. The loop below issues\n * one statement per record and runs every hook, so a request naming fifty\n * thousand ids would hold a connection for minutes and be unstoppable halfway\n * through. Two hundred is more than anyone selects by hand and small enough to\n * finish.\n */\n/** The fields a response may carry. Excludes anything marked write-only. */\nfunction readableFields(model: ModelMetadata): readonly FieldMetadata[] {\n return model.fields.filter((field) => field.writeOnly !== true)\n}\n\nexport const MAX_BULK_DELETE = 200\n\n/** What happened to each record a bulk delete named. */\nexport interface BulkDeleteResult {\n readonly deleted: readonly RecordId[]\n /** Records still in place, and why. Messages are already safe to show. */\n readonly failed: readonly { readonly id: RecordId; readonly message: string }[]\n}\n\n@Injectable()\nexport class AdminService implements OnModuleInit {\n constructor(\n @Inject(ADMIN_ADAPTER) private readonly adapter: OrmAdapter,\n @Inject(ADMIN_RESOURCE_AUTH) private readonly resourceAuth: AdminResourceAuth,\n @Inject(ADMIN_RESOURCES) private readonly resources: ResourceSelection | undefined,\n @Inject(ADMIN_MODELS) private readonly overrides: ModelOverrides | undefined,\n @Inject(ADMIN_HOOKS) private readonly hooks: AdminHooksByModel | undefined,\n @Inject(ADMIN_ACTIONS) private readonly actions: AdminActionsByModel | undefined,\n @Inject(ADMIN_AUTH) private readonly auth: AdminAuth,\n @Inject(ADMIN_DASHBOARD) private readonly dashboard: AdminDashboard | undefined,\n ) {}\n\n private readonly logger = new Logger('NestAdmin')\n\n /**\n * Fail at boot on a selection that names a model the schema does not have.\n *\n * A typo in `exclude` leaves the model exposed - the opposite of what was\n * asked for, and invisible until someone finds the table in the admin. It\n * cannot be checked in `forRoot`, because the model list comes from the\n * adapter and asking for it is asynchronous; this is the first moment it can\n * be known, and it is still before the first request.\n */\n async onModuleInit(): Promise<void> {\n const schema = await this.adapter.getModels()\n const known = schema.map((model) => model.name)\n\n const missingResources = unknownSelectionNames(schema, this.resources)\n if (missingResources.length > 0) {\n throw new Error(\n `AdminModule \\`resources\\` names ${missingResources.length === 1 ? 'a model' : 'models'} ` +\n `that the schema does not have: ${missingResources.join(', ')}. ` +\n `Known models: ${known.join(', ')}.`,\n )\n }\n\n // Checked against the *selected* models, so `models: { Session: … }` on a\n // model that `resources` excluded is reported as unknown rather than\n // silently having no effect.\n const missingOverrides = unknownOverrideNames(\n selectModels(schema, this.resources),\n this.overrides,\n )\n if (missingOverrides.length > 0) {\n throw new Error(\n `AdminModule \\`models\\` names ${missingOverrides.length === 1 ? 'a model or field' : 'models or fields'} ` +\n `this admin does not have: ${missingOverrides.join(', ')}. ` +\n `A typo in \\`hidden\\` leaves the real column exposed, so this is an error rather than a warning.`,\n )\n }\n\n // A required column with no default is a value the caller has to supply, so\n // hiding it means no record can ever be created. The database reports that\n // as a constraint violation, which the admin can only pass on as an\n // internal error - a long way from the line that caused it.\n await this.checkBuiltInAuth(selectModels(schema, this.resources))\n\n const unwritable = unwritableHiddenFields(selectModels(schema, this.resources), this.overrides)\n if (unwritable.length > 0) {\n const one = unwritable.length === 1\n throw new Error(\n `AdminModule \\`models\\` hides ${unwritable.join(', ')}, ` +\n `${one ? 'which is a required field' : 'which are required fields'} with no default. ` +\n `Hiding ${one ? 'it' : 'them'} leaves no way to supply a value, so every create would ` +\n `fail. Give the column a default, make it optional, or leave it visible.`,\n )\n }\n }\n\n /**\n * Two things about the built-in authentication that are only knowable here.\n *\n * Warnings rather than boot failures, and the distinction is deliberate.\n * Both describe a *deployment* that is wrong rather than a configuration\n * that cannot work - and an admin that refuses to start because its account\n * table is empty is an admin nobody can seed, because the seed script\n * imports the module.\n */\n private async checkBuiltInAuth(exposed: readonly ModelMetadata[]): Promise<void> {\n const runtime = builtInRuntimeOf(this.auth)\n if (!runtime) return\n\n /*\n * An account model that is also an editable resource.\n *\n * Anyone who may edit it can set another account’s password hash, or\n * clear `disabled` on their own - which is every permission the admin has,\n * reachable from a table that looks like any other.\n */\n const accountModel = runtime.store.describes\n if (accountModel !== undefined && exposed.some((model) => model.name === accountModel)) {\n this.logger.warn(\n `AdminModule exposes \"${accountModel}\" as a resource, and it is also where ` +\n 'the admin keeps its own accounts. Anyone who may edit it can grant ' +\n `themselves anything the admin can do. Exclude it with ` +\n `resources: { exclude: [\"${accountModel}\"] }.`,\n )\n }\n\n /*\n * No accounts at all.\n *\n * Otherwise the symptom is a login form that rejects every correct\n * password, which reads as a broken build rather than an empty table.\n */\n try {\n if ((await runtime.store.count()) === 0) {\n this.logger.warn(\n 'AdminModule is using builtInAuth() and the account store is empty, so ' +\n 'nobody can sign in. Create the first account with hashAdminPassword().',\n )\n }\n } catch (cause) {\n // A store that cannot be counted will not answer a login either, and\n // saying so at startup beats finding out at the login form.\n this.logger.warn(`Could not read the admin account store: ${String(cause)}`)\n }\n }\n\n /**\n * What the dashboard shows this principal.\n *\n * Authorized the way everything else is, and *before* anything is queried: a\n * widget over a model this principal may not list is absent from the\n * document, so a dashboard cannot become a way to count rows of a table\n * nobody would let you open.\n *\n * The exposed model list is passed in rather than looked up again inside, so\n * \"which models does this person see\" is answered once, here, by the same\n * code that answers it for the metadata document.\n */\n async getDashboard(context: ExecutionContext): Promise<DashboardDto> {\n const models = await this.exposedModels()\n const permitted: ModelMetadata[] = []\n\n for (const model of models) {\n if (await this.permits(context, model.name, 'list')) permitted.push(model)\n }\n\n return buildDashboard({\n adapter: this.adapter,\n models: permitted,\n declared: this.dashboard,\n context,\n labels: Object.fromEntries(\n Object.entries(this.overrides ?? {}).map(([name, override]) => [name, override?.label]),\n ),\n })\n }\n\n /**\n * The public metadata document a frontend renders resources from.\n *\n * Models the principal may not see are filtered out **before** mapping, so a\n * denied model never reaches the DTO at all - not its name, fields, relations,\n * primary key or enum values. The response is not \"everything, minus some\";\n * it is a description of the schema this principal has.\n */\n async getMetadata(context: ExecutionContext): Promise<MetadataDto> {\n const models = await this.exposedModels()\n\n const visible: ModelMetadata[] = []\n for (const model of models) {\n if (await this.isVisible(context, model.name)) visible.push(model)\n }\n\n return toMetadataDto(\n visible,\n this.overrides,\n await this.permissionsFor(context, visible),\n await this.actionsFor(context, visible),\n )\n }\n\n /**\n * List records.\n *\n * Metadata is resolved first - it decides whether the model is part of this\n * admin at all - and authorization second, so a denied model still never\n * reaches `adapter.list`. Query parsing needs that metadata anyway: only the\n * schema knows whether `price` should arrive as a number or a string.\n */\n async list(\n context: ExecutionContext,\n model: string,\n rawQuery: RawQuery,\n ): Promise<Page<RecordData>> {\n const metadata = await this.requireModel(model)\n await this.assertAllowed(context, model, 'list')\n return this.projectPage(\n metadata,\n await this.adapter.list(\n model,\n this.scopeToFields(metadata, parseListQuery(rawQuery, metadata)),\n ),\n )\n }\n\n /**\n * Fetch one record.\n *\n * The adapter returns `null` for a missing record; over HTTP that is a 404,\n * so it is turned into an error here rather than in the controller.\n */\n async findOne(context: ExecutionContext, model: string, id: RecordId): Promise<RecordData> {\n const metadata = await this.requireModel(model)\n await this.assertAllowed(context, model, 'read')\n const record = await this.adapter.findOne(model, id)\n if (record === null) throw new RecordNotFoundError(model, id)\n return this.project(metadata, record)\n }\n\n async create(context: ExecutionContext, model: string, data: RecordData): Promise<RecordData> {\n const metadata = await this.requireModel(model)\n await this.assertAllowed(context, model, 'create')\n this.assertWritable(metadata, data)\n\n const prepared = await this.runBefore(context, metadata, 'beforeCreate', data)\n const created = await this.adapter.create(model, prepared)\n await this.runAfter(context, model, 'afterCreate', { record: created })\n\n return this.project(metadata, created)\n }\n\n async update(\n context: ExecutionContext,\n model: string,\n id: RecordId,\n data: RecordData,\n ): Promise<RecordData> {\n const metadata = await this.requireModel(model)\n await this.assertAllowed(context, model, 'update')\n this.assertWritable(metadata, data)\n\n const prepared = await this.runBefore(context, metadata, 'beforeUpdate', data, id)\n const updated = await this.adapter.update(model, id, prepared)\n await this.runAfter(context, model, 'afterUpdate', { id, record: updated })\n\n return this.project(metadata, updated)\n }\n\n /**\n * Delete several records, and say what happened to each.\n *\n * ## Why this is a loop and not a `deleteMany`\n *\n * The adapter contract has no bulk delete, and giving it one would mean\n * every adapter had to have one. More to the point, hooks are per-record: an\n * application that refuses to delete a pinned post must still refuse it when\n * the post is one of forty checkboxes. A single `deleteMany` would step past\n * every one of those refusals at once, which is the opposite of what a\n * confirmation dialog leads someone to expect.\n *\n * ## Why a partial result is a success\n *\n * Deleting thirty records where two are still referenced is not a failed\n * request - twenty-eight rows are gone, and an error response would say\n * nothing about which. So the response is a 200 carrying both lists, and the\n * interface reports them. Nothing is rolled back, and `§ Known Limitations`\n * says so: this is not a transaction, exactly as hooks are not.\n */\n async deleteMany(\n context: ExecutionContext,\n model: string,\n ids: readonly RecordId[],\n ): Promise<BulkDeleteResult> {\n await this.requireModel(model)\n // Once, for the operation - not once per record. The permission is to\n // delete records of this model, and it does not change mid-loop.\n await this.assertAllowed(context, model, 'delete')\n\n if (ids.length === 0) {\n throw new InvalidQueryError('Deleting records requires a body of the form { \"ids\": [...] }.')\n }\n if (ids.length > MAX_BULK_DELETE) {\n throw new InvalidQueryError(\n `Refusing to delete ${ids.length} records in one request. The limit is ${MAX_BULK_DELETE}.`,\n )\n }\n\n const before = this.hooks?.[model]?.beforeDelete\n const deleted: RecordId[] = []\n const failed: Array<{ id: RecordId; message: string }> = []\n\n for (const id of ids) {\n try {\n if (before) await before({ context, model, id })\n await this.adapter.delete(model, id)\n await this.runAfter(context, model, 'afterDelete', { id })\n deleted.push(id)\n } catch (cause) {\n // Through the filter's own rule, so a refusal explains itself and an\n // internal failure stays generic. A 200 is not a licence to leak.\n failed.push({ id, message: clientMessage(cause) })\n }\n }\n\n return { deleted, failed }\n }\n\n async delete(context: ExecutionContext, model: string, id: RecordId): Promise<void> {\n await this.requireModel(model)\n await this.assertAllowed(context, model, 'delete')\n\n const before = this.hooks?.[model]?.beforeDelete\n if (before) await before({ context, model, id })\n\n await this.adapter.delete(model, id)\n await this.runAfter(context, model, 'afterDelete', { id })\n }\n\n /**\n * A page of the records on the far side of a to-many relation.\n *\n * Authorized against **both** models, and the distinction matters. Reading\n * `/User/u1/posts` returns Post records, so a principal who may read a User\n * but not list Posts must not receive them through the back door of a\n * relation. The parent decides whether this record may be opened at all; the\n * target decides whether its records may be listed.\n */\n async listRelated(\n context: ExecutionContext,\n model: string,\n id: RecordId,\n relationField: string,\n rawQuery: RawQuery,\n ): Promise<Page<RecordData>> {\n const parent = await this.requireModel(model)\n await this.assertAllowed(context, model, 'read')\n\n const target = await this.requireRelationTarget(parent, relationField)\n await this.assertAllowed(context, target.name, 'list')\n\n // Parsed against the target's metadata: the query describes the records\n // being listed, not the one they hang off.\n return this.projectPage(\n target,\n await this.adapter.listRelated(\n model,\n id,\n relationField,\n this.scopeToFields(target, parseListQuery(rawQuery, target)),\n ),\n )\n }\n\n /**\n * Link an existing record to this one.\n *\n * Requires `update` on both models. Across a one-to-many the child's foreign\n * key is what actually changes, so permitting this with rights over the\n * parent alone would let someone edit records they cannot otherwise touch.\n */\n async attachRelated(\n context: ExecutionContext,\n model: string,\n id: RecordId,\n relationField: string,\n targetId: RecordId,\n ): Promise<void> {\n const target = await this.assertMayRelink(context, model, relationField)\n await this.assertAllowed(context, target.name, 'update')\n\n await this.adapter.attachRelated(model, id, relationField, targetId)\n }\n\n /**\n * Unlink a record from this one, leaving both in place.\n *\n * Refused up front when the relation cannot be broken - a child whose foreign\n * key is required cannot exist without a parent, so there is nothing to\n * detach it to. Saying so is better than forwarding a constraint violation.\n */\n async detachRelated(\n context: ExecutionContext,\n model: string,\n id: RecordId,\n relationField: string,\n targetId: RecordId,\n ): Promise<void> {\n const target = await this.assertMayRelink(context, model, relationField)\n await this.assertAllowed(context, target.name, 'update')\n\n const parent = await this.requireModel(model)\n const field = parent.fields.find((candidate) => candidate.name === relationField)\n const blocked = field ? detachBlockedReason(field, await this.exposedModels()) : undefined\n if (blocked) throw new InvalidQueryError(blocked)\n\n await this.adapter.detachRelated(model, id, relationField, targetId)\n }\n\n /** Shared preamble for attach and detach: the parent must be updatable. */\n private async assertMayRelink(\n context: ExecutionContext,\n model: string,\n relationField: string,\n ): Promise<ModelMetadata> {\n const parent = await this.requireModel(model)\n await this.assertAllowed(context, model, 'update')\n return this.requireRelationTarget(parent, relationField)\n }\n\n /**\n * The model on the far side of a to-many relation field.\n *\n * Resolved through the exposed set, so a relation pointing at a model this\n * admin does not expose reads as an unknown field rather than as a route\n * into it.\n */\n private async requireRelationTarget(\n parent: ModelMetadata,\n relationField: string,\n ): Promise<ModelMetadata> {\n const field = parent.fields.find((candidate) => candidate.name === relationField)\n\n if (!field?.relation || field.relation.cardinality !== 'many') {\n throw new FieldNotFoundError(\n parent.name,\n relationField,\n 'Only a to-many relation can be listed this way.',\n )\n }\n\n const target = (await this.exposedModels()).find(\n (candidate) => candidate.name === field.relation?.targetModel,\n )\n if (!target) throw new FieldNotFoundError(parent.name, relationField)\n\n return target\n }\n\n /**\n * A record as this admin is allowed to return it.\n *\n * A whitelist against the effective metadata, which is what makes `hidden`\n * a guarantee rather than a request. The adapter reads whole rows - it knows\n * nothing about admin configuration - so a hidden column arrives here and is\n * dropped before anything can serialise it.\n *\n * Whitelisting rather than deleting the hidden names also covers a column the\n * adapter reports that the metadata does not describe: if it is not part of\n * this admin, it does not leave it.\n */\n private project(model: ModelMetadata, record: RecordData): RecordData {\n const allowed = new Set(readableFields(model).map((field) => field.name))\n const projected: RecordData = {}\n\n for (const [key, value] of Object.entries(record)) {\n if (allowed.has(key)) projected[key] = value\n }\n\n return projected\n }\n\n /**\n * Tell the adapter which fields this admin exposes.\n *\n * The adapter reads a schema, not a configuration, so without this a hidden\n * column would still be searched by free text, sorted and filtered on, and\n * read from the database - each of them a way to learn a value nobody is\n * meant to see. `project` would still keep it out of the response, but\n * \"you cannot read it\" is a weaker promise than \"it was never fetched\".\n */\n /**\n * Which columns the adapter is allowed to return.\n *\n * Not every field the model has: a `writeOnly` one is accepted on a write and\n * must never come back, so it is left out of the query itself rather than\n * removed from the answer afterwards. The projection below removes it a\n * second time, which is deliberate - see `FieldMetadata.writeOnly`.\n */\n private scopeToFields(model: ModelMetadata, query: ListQuery): ListQuery {\n return { ...query, fields: readableFields(model).map((field) => field.name) }\n }\n\n private projectPage(model: ModelMetadata, page: Page<RecordData>): Page<RecordData> {\n return { ...page, data: page.data.map((record) => this.project(model, record)) }\n }\n\n /**\n * Reject a write that names a field this admin will not write.\n *\n * The adapter validates too, but against the *schema* - it would accept a\n * hidden or read-only column, because from where it stands those are ordinary\n * writable ones. This is the only layer that knows the difference.\n */\n private assertWritable(model: ModelMetadata, data: RecordData): void {\n for (const key of Object.keys(data)) {\n const field = model.fields.find((candidate) => candidate.name === key)\n\n // Hidden fields are absent from the metadata, so an attempt to write one\n // is indistinguishable from a typo - which is the intended answer.\n if (!field) throw new FieldNotFoundError(model.name, key)\n\n if (isReadOnly(this.overrides, model.name, field)) {\n throw new FieldNotFoundError(\n model.name,\n key,\n field.isGenerated\n ? 'This value is produced by the database.'\n : 'This field is configured as read-only.',\n )\n }\n }\n }\n\n /**\n * What this principal may do with each visible model.\n *\n * Asked of the same policy the requests go through, so the document and the\n * enforcement cannot disagree. A policy that throws `ForbiddenError` is read\n * as a denial, exactly as `isVisible` reads it; anything else it throws is a\n * bug and propagates.\n *\n * Without this the interface offers `New`, `Edit` and `Delete` to a\n * principal for whom every one of them would be refused - a button that\n * exists only to produce a 403 is worse than no button.\n */\n private async permissionsFor(\n context: ExecutionContext,\n models: readonly ModelMetadata[],\n ): Promise<ReadonlyMap<string, ModelPermissionsDto>> {\n const permissions = new Map<string, ModelPermissionsDto>()\n\n for (const model of models) {\n const permits = async (operation: AdminOperation): Promise<boolean> =>\n this.permits(context, model.name, operation)\n\n permissions.set(model.name, {\n list: await permits('list'),\n read: await permits('read'),\n create: await permits('create'),\n update: await permits('update'),\n delete: await permits('delete'),\n })\n }\n\n return permissions\n }\n\n /** The policy's answer for one operation, with a thrown denial read as `false`. */\n private async permits(\n context: ExecutionContext,\n model: string,\n operation: AdminOperation,\n ): Promise<boolean> {\n try {\n return (await this.resourceAuth.authorize({ context, model, operation })) !== false\n } catch (error) {\n if (isNestAdminError(error) && error.kind === 'forbidden') return false\n throw error\n }\n }\n\n /**\n * Run a `before` hook, if the model has one.\n *\n * The result is validated again rather than trusted: a hook is application\n * code, and the rule that a hidden or read-only field cannot be written is\n * not one it should be able to step around by accident.\n */\n private async runBefore(\n context: ExecutionContext,\n metadata: ModelMetadata,\n hook: 'beforeCreate' | 'beforeUpdate',\n data: RecordData,\n id?: RecordId,\n ): Promise<RecordData> {\n const handler = this.hooks?.[metadata.name]?.[hook]\n if (!handler) return data\n\n const result = await (hook === 'beforeCreate'\n ? (handler as (args: never) => RecordData | Promise<RecordData>)({\n context,\n model: metadata.name,\n data,\n } as never)\n : (handler as (args: never) => RecordData | Promise<RecordData>)({\n context,\n model: metadata.name,\n id,\n data,\n } as never))\n\n this.assertWritable(metadata, result)\n return result\n }\n\n /**\n * Run an `after` hook, if the model has one.\n *\n * Nothing is rolled back if it throws - the write already happened - so the\n * failure is reported as it is rather than dressed up as a failed write.\n */\n private async runAfter(\n context: ExecutionContext,\n model: string,\n hook: 'afterCreate' | 'afterUpdate' | 'afterDelete',\n args: Record<string, unknown>,\n ): Promise<void> {\n const handler = this.hooks?.[model]?.[hook]\n if (!handler) return\n\n await (handler as (a: never) => void | Promise<void>)({\n context,\n model,\n ...args,\n } as never)\n }\n\n /**\n * The actions this principal may run, per model.\n *\n * Filtered by the policy before it reaches the document, so an action that\n * would be refused is simply not there - the interface cannot draw a button\n * for something it was never told about.\n */\n private async actionsFor(\n context: ExecutionContext,\n models: readonly ModelMetadata[],\n ): Promise<ReadonlyMap<string, readonly ActionDto[]>> {\n const byModel = new Map<string, readonly ActionDto[]>()\n\n for (const model of models) {\n const declared = this.actions?.[model.name] ?? []\n if (declared.length === 0) continue\n if (!(await this.permits(context, model.name, 'action'))) continue\n\n byModel.set(\n model.name,\n declared.map((action) => ({\n name: action.name,\n label: action.label ?? action.name,\n scope: action.scope,\n ...(action.confirm !== undefined ? { confirm: action.confirm } : {}),\n ...(action.danger !== undefined ? { danger: action.danger } : {}),\n })),\n )\n }\n\n return byModel\n }\n\n /**\n * Run one application-defined action.\n *\n * Authorized as `'action'` rather than as the operation it resembles: an\n * action can do anything, so a policy should be able to decide about it on\n * its own terms.\n *\n * A `'record'` action is given the id; a `'list'` one is not, and passing an\n * id to it - or omitting one from a record action - is a request that does\n * not match the action that was declared.\n */\n async runAction(\n context: ExecutionContext,\n model: string,\n name: string,\n id?: RecordId,\n ): Promise<AdminActionResult> {\n await this.requireModel(model)\n await this.assertAllowed(context, model, 'action')\n\n const action = (this.actions?.[model] ?? []).find((candidate) => candidate.name === name)\n if (!action) {\n throw new FieldNotFoundError(model, name, 'No such action.')\n }\n\n if (action.scope === 'record' && id === undefined) {\n throw new InvalidQueryError(`Action \"${name}\" applies to one record and needs an id.`)\n }\n if (action.scope === 'list' && id !== undefined) {\n throw new InvalidQueryError(`Action \"${name}\" applies to the whole model, not to a record.`)\n }\n\n return (await action.run({ context, model, ...(id === undefined ? {} : { id }) })) ?? {}\n }\n\n // ------------------------------------------------------- resource policy\n\n /**\n * Deny the request unless the policy permits this operation on this model.\n *\n * Called before any adapter operation. Both a `false` return and a thrown\n * `ForbiddenError` mean the same thing here, so a host may use whichever\n * reads better. Anything else the policy throws propagates untouched and the\n * exception filter turns it into a generic 500 - a broken policy fails the\n * request rather than quietly allowing it.\n */\n private async assertAllowed(\n context: ExecutionContext,\n model: string,\n operation: AdminOperation,\n ): Promise<void> {\n const decision = await this.resourceAuth.authorize({ context, model, operation })\n if (decision === false) throw new ForbiddenError()\n }\n\n /**\n * Is this model visible in the metadata document?\n *\n * Same policy, different consequence. A denial here must **hide** the model\n * rather than fail the request: surfacing a 403 from `GET /admin/meta` would\n * tell the caller that a model they cannot see exists, which is the side\n * channel this whole phase is meant to close.\n *\n * A `ForbiddenError` is therefore caught and read as \"not visible\". Any other\n * error is rethrown - a bug in the policy must surface as a 500, not silently\n * reshape the schema a client is shown.\n */\n private async isVisible(context: ExecutionContext, model: string): Promise<boolean> {\n try {\n const decision = await this.resourceAuth.authorize({ context, model, operation: 'metadata' })\n return decision !== false\n } catch (error) {\n // Not `instanceof`: the policy is the host application's, and its\n // `ForbiddenError` may come from a different copy of Core than this one.\n if (isNestAdminError(error) && error.kind === 'forbidden') return false\n throw error\n }\n }\n\n /**\n * The models this admin exposes, after the configured selection.\n *\n * Every path goes through here, so an excluded model is absent from the\n * metadata document and unknown to every route.\n */\n private async exposedModels(): Promise<readonly ModelMetadata[]> {\n return applyOverrides(\n selectModels(await this.adapter.getModels(), this.resources),\n this.overrides,\n )\n }\n\n /**\n * Resolve a model name to its metadata, or fail with 404.\n *\n * Called before the policy on every operation, and that order is deliberate.\n * Whether a model exists is structural - the same answer for everyone - so an\n * excluded model answers 404 rather than 403, and does so identically for\n * every principal. Asking the policy first would make a model that is not\n * part of this admin look like one the caller merely lacks access to.\n */\n\n private async requireModel(model: string): Promise<ModelMetadata> {\n const models = await this.exposedModels()\n const found = models.find((candidate) => candidate.name === model)\n if (!found) {\n throw new ModelNotFoundError(\n model,\n models.map((candidate) => candidate.name),\n )\n }\n return found\n }\n}\n","/**\n * An `AdminAuth` that ships in the box.\n *\n * ## This does not move the boundary\n *\n * `AdminAuth` is unchanged and still the only way in. An application with its\n * own identity system implements it and never sees any of this. What changes is\n * that an application *without* one no longer has to write a password hash, a\n * cookie and a form before the admin can be put behind a login.\n *\n * So there are three answers to \"who may open this?\", and a consumer picks one:\n *\n * auth: unsafeAllowAllRequests() development only, warns at startup\n * auth: myOwnAuth an application that already has identity\n * auth: builtInAuth({ ... }) a login page, sessions and a store\n *\n * ## The accounts are separate from the application's users\n *\n * By construction: the store is a contract over storage the application\n * nominates, and the intended shape is a model of its own. The admin never\n * consults the application's user table to decide who may sign in, and adding\n * a customer never adds someone who can administer the system.\n */\nimport {\n summarise,\n UnauthorizedError,\n type AdminAccount,\n type AdminAccountStore,\n type AdminAccountSummary,\n} from '@nest-admin/core'\nimport { Logger, type ExecutionContext } from '@nestjs/common'\n\nimport type { AdminAuth } from './contract.js'\nimport { NO_SUCH_ACCOUNT, verifyAdminPassword } from './password.js'\nimport { MIN_SECRET_LENGTH, readSession, shouldRenew, signSession } from './session.js'\n\nconst logger = new Logger('NestAdmin')\n\n/** Twelve hours. Long enough for a working day, short enough to matter. */\nconst DEFAULT_MAX_AGE = 12 * 60 * 60\n\nexport interface BuiltInAuthOptions {\n /**\n * Where the accounts live.\n *\n * `prismaAccountStore` from `@nest-admin/nestjs/prisma` covers the usual\n * case; anything satisfying the contract works.\n */\n readonly store: AdminAccountStore\n\n readonly session: {\n /**\n * The key the session cookie is signed with. **Required.**\n *\n * At least 32 characters, checked at startup. A short secret is a\n * forgeable cookie, and the failure is silent: everything works, and\n * anybody can mint a session for any account.\n *\n * Read it from the environment. A secret in source control is a secret\n * everyone who has ever cloned the repository knows.\n */\n readonly secret: string\n\n /** How long a session lasts, in seconds. Twelve hours by default. */\n readonly maxAge?: number\n\n /** The cookie's name. Change it only to avoid a collision. */\n readonly cookieName?: string\n\n /**\n * Send the cookie only over HTTPS.\n *\n * Left unset it is decided per request: on for everything except\n * localhost, which is what makes the admin work in development without\n * being insecure anywhere else. Set it to `true` to require HTTPS always.\n */\n readonly secure?: boolean\n }\n\n /** Failed attempts before a pause. Ten by default. */\n readonly maxAttempts?: number\n\n /** How long that pause lasts, in seconds. Fifteen minutes by default. */\n readonly lockoutSeconds?: number\n}\n\n/** The parts of a built-in auth the login routes need. */\nexport interface BuiltInAuthRuntime {\n readonly store: AdminAccountStore\n readonly secret: string\n readonly maxAge: number\n readonly cookieName: string\n readonly secure: boolean | undefined\n /** Try an email and password. `undefined` when they do not match. */\n signIn(email: unknown, password: unknown, from: string): Promise<AdminAccount | undefined>\n}\n\n/**\n * Recognising a built-in auth without adding anything to `AdminAuth`.\n *\n * `Symbol.for` rather than a private symbol, for the reason the error taxonomy\n * gives: the published package inlines its own copy of this module per\n * entrypoint, and two copies agree on a registered symbol where two `Symbol()`\n * calls would not.\n */\nconst RUNTIME = Symbol.for('nest-admin.built-in-auth')\n\n/** The runtime behind an auth, if it is one of ours. */\nexport function builtInRuntimeOf(auth: unknown): BuiltInAuthRuntime | undefined {\n return typeof auth === 'object' && auth !== null\n ? ((auth as Record<symbol, BuiltInAuthRuntime | undefined>)[RUNTIME] ?? undefined)\n : undefined\n}\n\n/**\n * The account this request signed in as.\n *\n * For a `resourceAuth` policy or a hook that needs to know who is asking.\n * `undefined` when the admin is not using the built-in auth, which is why it\n * is optional rather than assumed.\n */\nexport function adminAccountOf(context: ExecutionContext): AdminAccountSummary | undefined {\n const request = context.switchToHttp().getRequest<{ adminAccount?: AdminAccountSummary }>()\n return request?.adminAccount\n}\n\nexport function builtInAuth(options: BuiltInAuthOptions): AdminAuth {\n const secret = options.session?.secret\n if (typeof secret !== 'string' || secret.length < MIN_SECRET_LENGTH) {\n throw new Error(\n `builtInAuth() requires \\`session.secret\\` of at least ${MIN_SECRET_LENGTH} characters. ` +\n 'A short secret can be guessed, and a guessed one mints a session for any account. ' +\n 'Read it from the environment rather than writing it here.',\n )\n }\n\n if (!options.store || typeof options.store.findByEmail !== 'function') {\n throw new Error(\n 'builtInAuth() requires a `store`. Use `prismaAccountStore({ client })` from ' +\n '`@nest-admin/nestjs/prisma`, or supply your own AdminAccountStore.',\n )\n }\n\n const maxAge = options.session.maxAge ?? DEFAULT_MAX_AGE\n const cookieName = options.session.cookieName ?? 'nest_admin_session'\n const attempts = new Attempts(options.maxAttempts ?? 10, options.lockoutSeconds ?? 15 * 60)\n\n const runtime: BuiltInAuthRuntime = {\n store: options.store,\n secret,\n maxAge,\n cookieName,\n secure: options.session.secure,\n\n async signIn(email, password, from) {\n if (typeof email !== 'string' || typeof password !== 'string') return undefined\n if (attempts.lockedOut(from)) return undefined\n\n const account = await options.store.findByEmail(email.trim().toLowerCase())\n\n /*\n * The verification runs whether or not the account exists.\n *\n * Returning early for an unknown email answers in microseconds while a\n * real one takes a hundred milliseconds, and that difference is a list of\n * which addresses are registered. `NO_SUCH_ACCOUNT` is a hash of a random\n * string nobody kept, so the work is the same and the answer is no.\n */\n const stored = account?.passwordHash ?? (await NO_SUCH_ACCOUNT)\n const correct = await verifyAdminPassword(password, stored)\n\n if (!correct || !account || account.disabled === true) {\n attempts.failed(from)\n return undefined\n }\n\n attempts.succeeded(from)\n\n // Not awaited, and a failure is logged rather than surfaced: \"your login\n // worked but we could not write down that it did\" is not something the\n // person signing in can act on.\n void options.store.recordLogin?.(account.id).catch((cause: unknown) => {\n logger.warn(`Could not record a login: ${String(cause)}`)\n })\n\n return account\n },\n }\n\n const auth: AdminAuth = {\n async authorize(context) {\n const request = context.switchToHttp().getRequest<AuthenticatedRequest>()\n const token = cookieFrom(request?.headers?.cookie, cookieName)\n\n const id = token === undefined ? undefined : readSession(token, secret)\n if (id === undefined) throw new UnauthorizedError('Sign in to continue.')\n\n /*\n * The account is loaded on every request rather than trusted from the\n * cookie.\n *\n * It is the difference between \"sessions expire eventually\" and\n * \"disabling an account works now\". It also means a token cannot outlive\n * the thing it names: delete the row and the next request is refused.\n */\n const account = await options.store.findById(id)\n if (!account || account.disabled === true) {\n throw new UnauthorizedError('Sign in to continue.')\n }\n\n request.adminAccount = summarise(account)\n\n // Halfway through its life, so an active session never expires under\n // someone while an abandoned one still dies on schedule.\n if (token !== undefined && shouldRenew(token, secret, maxAge)) {\n setSessionCookie(\n context.switchToHttp().getResponse(),\n signSession(account.id, secret, maxAge),\n runtime,\n request,\n )\n }\n },\n }\n\n Object.defineProperty(auth, RUNTIME, { value: runtime, enumerable: false })\n return auth\n}\n\ninterface AuthenticatedRequest {\n readonly headers?: Record<string, string | string[] | undefined>\n readonly socket?: { readonly remoteAddress?: string }\n readonly ip?: string\n adminAccount?: AdminAccountSummary\n}\n\n/**\n * One cookie out of the header, without a cookie parser.\n *\n * A dependency for eleven lines is a poor trade in a package that has one, and\n * the format is `name=value; name=value` - not a grammar worth importing.\n * Values are decoded because a signature is base64url and survives encoding\n * either way, but a future value might not.\n */\nexport function cookieFrom(header: unknown, name: string): string | undefined {\n if (typeof header !== 'string') return undefined\n\n for (const part of header.split(';')) {\n const eq = part.indexOf('=')\n if (eq < 0) continue\n if (part.slice(0, eq).trim() !== name) continue\n\n const value = part.slice(eq + 1).trim()\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n }\n\n return undefined\n}\n\n/**\n * Write the session cookie.\n *\n * `httpOnly` so script cannot read it, which is what turns a cross-site\n * scripting bug into a smaller problem than a stolen session. `sameSite=Lax`\n * is the CSRF defence for the whole admin API: a cross-site POST, PATCH or\n * DELETE does not carry the cookie at all, so a forged request arrives\n * unauthenticated. `Strict` would be marginally stronger and would also log\n * someone out when they follow a link to the admin from anywhere else.\n *\n * `Secure` is on unless the request came from localhost, so development works\n * over http without the flag being off anywhere it matters.\n */\nexport function setSessionCookie(\n response: unknown,\n token: string,\n runtime: BuiltInAuthRuntime,\n request?: AuthenticatedRequest,\n): void {\n writeCookie(\n response,\n [\n `${runtime.cookieName}=${token}`,\n 'Path=/',\n 'HttpOnly',\n 'SameSite=Lax',\n `Max-Age=${runtime.maxAge}`,\n ...(isSecure(runtime.secure, request) ? ['Secure'] : []),\n ].join('; '),\n )\n}\n\n/** Remove it, with the same attributes - a browser matches on those too. */\nexport function clearSessionCookie(\n response: unknown,\n runtime: BuiltInAuthRuntime,\n request?: AuthenticatedRequest,\n): void {\n writeCookie(\n response,\n [\n `${runtime.cookieName}=`,\n 'Path=/',\n 'HttpOnly',\n 'SameSite=Lax',\n 'Max-Age=0',\n ...(isSecure(runtime.secure, request) ? ['Secure'] : []),\n ].join('; '),\n )\n}\n\nfunction isSecure(configured: boolean | undefined, request?: AuthenticatedRequest): boolean {\n if (configured !== undefined) return configured\n\n const host = String(request?.headers?.['host'] ?? '')\n const local = /^(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d+)?$/.test(host)\n return !local\n}\n\n/** `setHeader` on Node's response; `header` on Fastify's reply. */\nfunction writeCookie(response: unknown, value: string): void {\n const target = response as {\n setHeader?: (name: string, value: string) => void\n header?: (name: string, value: string) => void\n }\n if (typeof target?.setHeader === 'function') return target.setHeader('Set-Cookie', value)\n if (typeof target?.header === 'function') target.header('Set-Cookie', value)\n}\n\n/**\n * Somewhere to count failed attempts against.\n *\n * The address plus the email, so one person guessing at one account cannot\n * lock out everybody, and a spread of guesses from one place still adds up.\n */\nexport function attemptKey(request: AuthenticatedRequest | undefined, email: unknown): string {\n const address =\n (typeof request?.ip === 'string' ? request.ip : undefined) ??\n request?.socket?.remoteAddress ??\n 'unknown'\n return `${address}|${typeof email === 'string' ? email.trim().toLowerCase() : ''}`\n}\n\n/**\n * Failed sign-ins, counted in memory.\n *\n * In memory, and therefore per process: behind several instances an attacker\n * gets the allowance once per instance. Said plainly rather than implied,\n * because the alternative is a shared store this package would have to invent,\n * and slowing an attack down by an order of magnitude without a dependency is\n * worth more than the difference between that and stopping it.\n *\n * It is not a substitute for a rate limiter at the edge, and does not pretend\n * to be one.\n */\nclass Attempts {\n readonly #failures = new Map<string, { count: number; since: number; until: number }>()\n\n constructor(\n private readonly max: number,\n private readonly seconds: number,\n ) {}\n\n lockedOut(key: string): boolean {\n const entry = this.#failures.get(key)\n if (!entry) return false\n\n // Counting, but not locked yet. An earlier version deleted the entry here,\n // which reset the count on every attempt and meant the lockout never\n // triggered at all - a rate limiter that rate-limits nothing. Found by the\n // test that tries the right password after ten wrong ones.\n if (entry.until === 0) return false\n\n if (entry.until > Date.now()) return true\n\n // The window passed. Forget the whole thing rather than leaving someone\n // one attempt away from being locked out again forever.\n this.#failures.delete(key)\n return false\n }\n\n failed(key: string): void {\n const now = Date.now()\n const existing = this.#failures.get(key)\n\n // Failures that are older than the lockout window do not count towards\n // the next one. Otherwise a typo in March and nine more in September add\n // up to a lockout nobody can explain.\n const entry =\n existing && now - existing.since < this.seconds * 1000\n ? existing\n : { count: 0, since: now, until: 0 }\n\n entry.count += 1\n if (entry.count >= this.max) entry.until = now + this.seconds * 1000\n this.#failures.set(key, entry)\n\n // Nothing else prunes this map, and an attacker choosing a new email each\n // time would otherwise grow it without limit.\n if (this.#failures.size > 10_000) this.#prune()\n }\n\n succeeded(key: string): void {\n this.#failures.delete(key)\n }\n\n #prune(): void {\n const now = Date.now()\n for (const [key, entry] of this.#failures) {\n const locked = entry.until > now\n const recent = now - entry.since < this.seconds * 1000\n if (!locked && !recent) this.#failures.delete(key)\n }\n }\n}\n","/**\n * Turning a password into something safe to store, and checking it again.\n *\n * ## Why scrypt and not bcrypt or argon2\n *\n * Both are better-known and both are native modules. This package has exactly\n * one runtime dependency and no compiled code, which is why it installs the\n * same way on every platform and every Node version - and a password hash is a\n * poor reason to give that up. `node:crypto` ships scrypt, which is a memory-\n * hard KDF designed for this and is not a compromise.\n *\n * What is *not* acceptable is a plain digest. SHA-256 is designed to be fast,\n * and fast is the entire problem: a modern card tries billions of them a\n * second. The parameters below are chosen to make one attempt cost something.\n *\n * ## The stored form\n *\n * scrypt$N$r$p$<salt hex>$<hash hex>\n *\n * The parameters travel with the hash rather than living in this file. That is\n * what makes them changeable: raising the cost later leaves every existing\n * password verifiable with the parameters it was made with, and the alternative\n * is a migration nobody can run because the plaintext is gone.\n */\nimport { randomBytes, scrypt, timingSafeEqual } from 'node:crypto'\nimport { promisify } from 'node:util'\n\nconst derive = promisify(scrypt) as (\n password: string,\n salt: string,\n keylen: number,\n options: { N: number; r: number; p: number; maxmem: number },\n) => Promise<Buffer>\n\n/**\n * Cost parameters.\n *\n * `N = 2^15` is a deliberate step up from Node's default of 2^14: it puts one\n * derivation in the region of a hundred milliseconds, which nobody signing in\n * notices and an attacker pays for every guess. `maxmem` has to be raised with\n * it, because Node's default ceiling is sized for the default `N` and scrypt\n * throws rather than quietly using less memory.\n */\nconst PARAMS = { N: 2 ** 15, r: 8, p: 1 } as const\nconst MAXMEM = 128 * PARAMS.N * PARAMS.r * 2\nconst KEY_LENGTH = 64\nconst SALT_BYTES = 16\n\n/** `$` as a code point, so a template literal never eats it in transit. */\nconst SEP = String.fromCharCode(36)\n\n/**\n * Hash a password for storage.\n *\n * Exported for the application, because creating accounts is its business -\n * a seed script, a migration, or a form of its own. The admin never mints an\n * administrator; see `AdminAccountStore` for why.\n */\nexport async function hashAdminPassword(password: string): Promise<string> {\n if (typeof password !== 'string' || password.length === 0) {\n throw new Error('A password is required.')\n }\n\n const salt = randomBytes(SALT_BYTES).toString('hex')\n const key = await derive(password, salt, KEY_LENGTH, { ...PARAMS, maxmem: MAXMEM })\n\n return ['scrypt', PARAMS.N, PARAMS.r, PARAMS.p, salt, key.toString('hex')].join(SEP)\n}\n\n/**\n * Does this password match this stored hash?\n *\n * Never throws for a malformed or unrecognised hash - it answers `false`. A\n * store holding something this function does not understand is a configuration\n * problem, and turning it into a 500 on the login route would tell an attacker\n * that the account exists and that its record is unusual.\n *\n * The comparison is `timingSafeEqual`, not `===`. String equality returns as\n * soon as two bytes differ, and the difference is measurable often enough to\n * recover a hash a byte at a time.\n */\nexport async function verifyAdminPassword(password: string, stored: string): Promise<boolean> {\n const parsed = parse(stored)\n if (!parsed) return false\n\n try {\n const key = await derive(password, parsed.salt, parsed.hash.length / 2, {\n N: parsed.N,\n r: parsed.r,\n p: parsed.p,\n maxmem: 128 * parsed.N * parsed.r * 2,\n })\n const expected = Buffer.from(parsed.hash, 'hex')\n return key.length === expected.length && timingSafeEqual(key, expected)\n } catch {\n // A hash whose recorded parameters are outside what this Node build will\n // do. Answering `false` keeps the login route uniform; the operator finds\n // out from the account not working, not from a stack trace on the wire.\n return false\n }\n}\n\ninterface Parsed {\n readonly N: number\n readonly r: number\n readonly p: number\n readonly salt: string\n readonly hash: string\n}\n\nfunction parse(stored: unknown): Parsed | undefined {\n if (typeof stored !== 'string') return undefined\n\n const [scheme, n, r, p, salt, hash] = stored.split(SEP)\n if (scheme !== 'scrypt' || salt === undefined || hash === undefined) return undefined\n\n const N = Number(n)\n const rounds = Number(r)\n const parallel = Number(p)\n\n // A power of two, within a range that cannot be used to ask this process for\n // an unbounded amount of memory. A hash is not trusted input in the usual\n // sense, but it is read from storage the admin does not own.\n const usable =\n Number.isInteger(N) &&\n N >= 2 ** 12 &&\n N <= 2 ** 20 &&\n (N & (N - 1)) === 0 &&\n Number.isInteger(rounds) &&\n rounds > 0 &&\n rounds <= 32 &&\n Number.isInteger(parallel) &&\n parallel > 0 &&\n parallel <= 16 &&\n /^[0-9a-f]+$/i.test(salt) &&\n /^[0-9a-f]+$/i.test(hash) &&\n hash.length % 2 === 0\n\n return usable ? { N, r: rounds, p: parallel, salt, hash } : undefined\n}\n\n/**\n * A hash that no password matches, for accounts that do not exist.\n *\n * The sign-in path verifies against this when the email is unknown, so a\n * request for a real account and one for an imaginary account take the same\n * time. Without it, \"no such account\" returns in microseconds and \"wrong\n * password\" takes a hundred milliseconds, and the difference is a list of\n * which addresses are registered.\n *\n * Built once at module load, from a random secret nobody keeps.\n */\nexport const NO_SUCH_ACCOUNT: Promise<string> = hashAdminPassword(randomBytes(32).toString('hex'))\n","/**\n * The session, as a signed cookie.\n *\n * ## Stateless, and what that costs\n *\n * There is no session table. The cookie carries the account id and an expiry,\n * signed with a secret only the server has, so a request can be authenticated\n * without a round trip to storage for the session itself.\n *\n * The cost is real and worth stating rather than discovering: **a session\n * cannot be revoked before it expires.** Two things soften it. Lifetimes are\n * short and renew as they are used, so an abandoned session dies on its own.\n * And every authenticated request loads the account, so disabling or deleting\n * one stops it working immediately - which is the revocation people actually\n * need. What remains unrevocable is one specific stolen cookie, until it\n * expires or the secret is rotated.\n *\n * ## What is in it\n *\n * v1.<base64url payload>.<base64url HMAC-SHA256>\n *\n * The payload is `{ sub, exp }` and nothing else. Not the email, not the name,\n * not a role: a cookie is readable by whoever holds it, and none of that is\n * worth putting in front of them to save one lookup. It is also why the\n * account is fetched per request rather than trusted from the token - a\n * cookie issued yesterday would otherwise still claim yesterday's permissions.\n */\nimport { createHmac, randomBytes, timingSafeEqual } from 'node:crypto'\n\n/** Bumped if the payload's shape ever changes, so old cookies simply fail. */\nconst VERSION = 'v1'\n\ninterface Payload {\n /** The account id. */\n readonly sub: string\n /** Expiry, as a Unix timestamp in seconds. */\n readonly exp: number\n}\n\nconst encode = (value: Buffer | string): string => Buffer.from(value as never).toString('base64url')\n\n/**\n * Issue a token for an account.\n *\n * `lifetime` is in seconds. There is no \"remember me\": a longer session is a\n * longer window for a stolen cookie, and the renewal below already means an\n * active person is never asked to sign in again.\n */\nexport function signSession(accountId: string, secret: string, lifetime: number): string {\n const payload: Payload = {\n sub: accountId,\n exp: Math.floor(Date.now() / 1000) + lifetime,\n }\n\n const body = `${VERSION}.${encode(JSON.stringify(payload))}`\n return `${body}.${sign(body, secret)}`\n}\n\n/**\n * The account id a token names, or `undefined`.\n *\n * `undefined` covers every failure without distinguishing them: expired,\n * tampered with, from an older version, or simply not a token. A caller has\n * the same response to all of them - ask for a sign-in - and telling them\n * apart on the wire would say whether a forgery was close.\n */\nexport function readSession(token: unknown, secret: string): string | undefined {\n if (typeof token !== 'string') return undefined\n\n const cut = token.lastIndexOf('.')\n if (cut < 1) return undefined\n\n const body = token.slice(0, cut)\n const presented = token.slice(cut + 1)\n\n if (!body.startsWith(`${VERSION}.`)) return undefined\n if (!matches(presented, sign(body, secret))) return undefined\n\n try {\n const payload = JSON.parse(\n Buffer.from(body.slice(VERSION.length + 1), 'base64url').toString('utf8'),\n ) as Payload\n\n if (typeof payload.sub !== 'string' || payload.sub === '') return undefined\n if (typeof payload.exp !== 'number' || payload.exp * 1000 <= Date.now()) return undefined\n\n return payload.sub\n } catch {\n // Signed, and still not JSON. Only reachable with the secret, so this is a\n // bug rather than an attack - but it is still not a reason to throw at\n // whoever is holding the cookie.\n return undefined\n }\n}\n\n/**\n * Is this token far enough through its life to be reissued?\n *\n * Renewing on every request would set a cookie on every response, which is\n * noise; renewing at the halfway point means an active session never expires\n * under someone and an abandoned one still dies on schedule.\n */\nexport function shouldRenew(token: string, secret: string, lifetime: number): boolean {\n const cut = token.lastIndexOf('.')\n if (cut < 1 || !matches(token.slice(cut + 1), sign(token.slice(0, cut), secret))) return false\n\n try {\n const payload = JSON.parse(\n Buffer.from(token.slice(VERSION.length + 1, cut), 'base64url').toString('utf8'),\n ) as Payload\n const remaining = payload.exp - Math.floor(Date.now() / 1000)\n return remaining < lifetime / 2\n } catch {\n return false\n }\n}\n\nfunction sign(body: string, secret: string): string {\n return createHmac('sha256', secret).update(body).digest('base64url')\n}\n\n/**\n * Compare two signatures without leaking how far they matched.\n *\n * `===` on strings returns at the first differing byte, and the timing\n * difference is measurable often enough to reconstruct a signature one byte at\n * a time. The length check first is safe: a signature's length is not a secret.\n */\nfunction matches(presented: string, expected: string): boolean {\n const a = Buffer.from(presented)\n const b = Buffer.from(expected)\n return a.length === b.length && timingSafeEqual(a, b)\n}\n\n/**\n * The floor for a session secret.\n *\n * 32 characters, checked at startup rather than trusted. A short secret is a\n * forgeable cookie, and the failure mode is silent: everything works, and\n * anybody can mint a session for any account.\n */\nexport const MIN_SECRET_LENGTH = 32\n\n/** A secret of the right shape, for a consumer that needs one generated. */\nexport function generateSessionSecret(): string {\n return randomBytes(32).toString('base64url')\n}\n","/**\n * Building the dashboard document.\n *\n * Everything a widget needs is resolved here and sent as data. The interface\n * draws four shapes and never learns what any of them mean - the same\n * arrangement as actions, and for the same reason: a dashboard that needed a UI\n * change per widget would be a dashboard nobody could add to.\n *\n * ## Authorization first, then work\n *\n * A widget over a model this principal cannot list is dropped before anything\n * is queried. Not hidden by the interface - absent from the document, like a\n * hidden model and a refused action. So a dashboard is not a way to count rows\n * of a table you are not allowed to open.\n *\n * ## Failures are per widget\n *\n * One widget that throws becomes one widget that says it could not load. A\n * dashboard is several independent questions on one page, and letting the\n * slowest or the most broken of them take the others down is the wrong shape\n * for that - especially since `stat` runs application code.\n */\nimport {\n createdFieldFor,\n displayFieldFor,\n type FilterRule,\n type ModelMetadata,\n type OrmAdapter,\n type RecordData,\n} from '@nest-admin/core'\nimport { Logger, type ExecutionContext } from '@nestjs/common'\n\nimport { parseFilterExpression } from '../http/query-parser.js'\nimport {\n defaultSpan,\n modelOf,\n type AdminDashboard,\n type DashboardWidget,\n type WidgetSpan,\n} from './contract.js'\n\nconst logger = new Logger('NestAdmin')\n\n/** A widget as it crosses the wire. */\nexport interface WidgetDto {\n readonly id: string\n readonly kind: 'count' | 'list' | 'chart' | 'stat'\n readonly title: string\n readonly description?: string\n readonly span: WidgetSpan\n /** Which model it reads, so the interface can link to it. */\n readonly model?: string\n /** The list screen this widget is a summary of, as `field:op:value`. */\n readonly filter?: string\n /** Whatever the kind needs. Never a function, never a query. */\n readonly data?: unknown\n /** Set instead of `data` when this one failed. Never carries a cause. */\n readonly failed?: boolean\n}\n\nexport interface DashboardDto {\n readonly widgets: readonly WidgetDto[]\n /** True when nothing was declared and this was built from the schema. */\n readonly generated: boolean\n}\n\nexport interface CountData {\n readonly value: number\n readonly delta?: number\n readonly hint?: string\n}\n\nexport interface ListData {\n readonly records: readonly { readonly id: string; readonly label: string }[]\n readonly total: number\n}\n\nexport interface ChartData {\n readonly points: readonly { readonly at: string; readonly value: number }[]\n readonly total: number\n}\n\n/** Ninety buckets is already a wide chart; see `chartOf` for why it is capped. */\nconst MAX_BUCKETS = 90\n\nconst DAY = 86_400_000\n\nexport interface DashboardInput {\n readonly adapter: OrmAdapter\n /** Models this principal may list. Already filtered by the policy. */\n readonly models: readonly ModelMetadata[]\n readonly declared: AdminDashboard | undefined\n readonly context: ExecutionContext\n /**\n * What each model is called in this admin.\n *\n * The generated dashboard names models, and a model's name is not\n * necessarily what anyone calls it: an application that labels `User` as\n * \"People\" would otherwise get \"People\" in the sidebar and \"User\" on the\n * dashboard, which reads as two different things.\n *\n * Only the generated widgets use it. A declared widget already carries the\n * title its author wrote, and overriding that would be presumptuous.\n */\n readonly labels?: Readonly<Record<string, string | undefined>>\n}\n\nexport async function buildDashboard(input: DashboardInput): Promise<DashboardDto> {\n const declared = input.declared\n const generated = declared === undefined || declared.length === 0\n const widgets = generated ? generateFrom(input.models, input.labels ?? {}) : declared\n\n const visible = new Set(input.models.map((model) => model.name))\n\n const resolved = await Promise.all(\n widgets\n // Dropped before anything is queried, so a dashboard cannot count rows\n // of a table this principal may not open.\n .filter((widget) => {\n const model = modelOf(widget)\n return model === undefined || visible.has(model)\n })\n .map((widget, index) => resolve(widget, index, input)),\n )\n\n return { widgets: resolved, generated }\n}\n\n/**\n * A dashboard from the schema alone.\n *\n * A count for every model the principal can see, and - for models that record\n * when a row was created - the newest few and a month of activity for the\n * busiest one.\n *\n * \"The busiest\" is decided by nothing here: the models arrive in the order the\n * configuration put them in, and the first with a creation timestamp is used.\n * Guessing which table matters most would be guessing about a business, and a\n * dashboard that leads with the wrong one is worse than one that leads with the\n * first.\n */\nfunction generateFrom(\n models: readonly ModelMetadata[],\n labels: Readonly<Record<string, string | undefined>>,\n): readonly DashboardWidget[] {\n const labelOf = (model: ModelMetadata): string => labels[model.name] ?? model.name\n\n const widgets: DashboardWidget[] = models.map((model) => ({\n kind: 'count' as const,\n title: labelOf(model),\n model: model.name,\n }))\n\n const dated = models.filter((model) => createdFieldFor(model) !== undefined)\n\n const first = dated[0]\n if (first) {\n widgets.push({\n kind: 'chart',\n title: `New ${labelOf(first)}`,\n description: 'Over the last 30 days.',\n model: first.name,\n span: 2,\n })\n }\n\n for (const model of dated.slice(0, 2)) {\n widgets.push({\n kind: 'list',\n title: `Recent ${labelOf(model)}`,\n model: model.name,\n span: 2,\n })\n }\n\n return widgets\n}\n\nasync function resolve(\n widget: DashboardWidget,\n index: number,\n input: DashboardInput,\n): Promise<WidgetDto> {\n const base = {\n // Stable within one document, and only used as a React key: a title is not\n // unique and a position changes when a widget above it is dropped.\n id: `${widget.kind}-${index}`,\n kind: widget.kind,\n title: widget.title,\n span: widget.span ?? defaultSpan(widget),\n ...(widget.description !== undefined ? { description: widget.description } : {}),\n ...(widget.kind !== 'stat' ? { model: widget.model } : {}),\n ...(widget.kind !== 'stat' && widget.filter !== undefined ? { filter: widget.filter } : {}),\n } satisfies Omit<WidgetDto, 'data' | 'failed'>\n\n try {\n return { ...base, data: await dataFor(widget, input) }\n } catch (cause) {\n // One widget, not the page. The message is logged and not forwarded - a\n // `stat` runs application code, and its errors carry whatever that code's\n // errors carry.\n logger.warn(`Dashboard widget \"${widget.title}\" failed: ${String(cause)}`)\n return { ...base, failed: true }\n }\n}\n\nasync function dataFor(widget: DashboardWidget, input: DashboardInput): Promise<unknown> {\n switch (widget.kind) {\n case 'stat':\n return widget.load({ context: input.context })\n case 'count':\n return countOf(widget, input)\n case 'list':\n return listOf(widget, input)\n case 'chart':\n return chartOf(widget, input)\n }\n}\n\n/**\n * How many records match.\n *\n * Asks for one row and reads the total the adapter already returns, rather than\n * adding a `count` to the `OrmAdapter` contract. That contract is about to be\n * frozen at 1.0 and every method on it is a method every future adapter has to\n * implement; a page total that is already there is not worth one.\n */\nasync function countOf(\n widget: Extract<DashboardWidget, { kind: 'count' }>,\n input: DashboardInput,\n): Promise<CountData> {\n const model = modelFor(widget.model, input)\n const declared = filtersFor(widget.filter, model)\n\n const page = await input.adapter.list(widget.model, {\n perPage: 1,\n ...(declared.length > 0 ? { filters: declared } : {}),\n })\n\n if (widget.compareDays === undefined) return { value: page.total }\n\n const created = createdFieldFor(model)\n // No creation timestamp means no \"before\". The count is still correct; only\n // the comparison is missing, so the widget loses a line rather than\n // disappearing.\n if (created === undefined) return { value: page.total }\n\n const since = new Date(Date.now() - widget.compareDays * DAY).toISOString()\n const recent = await input.adapter.list(widget.model, {\n perPage: 1,\n filters: [...declared, { field: created, operator: 'gte', value: since }],\n })\n\n const before = page.total - recent.total\n return {\n value: page.total,\n // Everything is new when there was nothing before, and dividing by zero to\n // say so would produce Infinity on a brand-new install.\n ...(before > 0 ? { delta: Math.round((recent.total / before) * 100) } : {}),\n hint: `${recent.total} in the last ${widget.compareDays} days`,\n }\n}\n\nasync function listOf(\n widget: Extract<DashboardWidget, { kind: 'list' }>,\n input: DashboardInput,\n): Promise<ListData> {\n const model = modelFor(widget.model, input)\n const created = createdFieldFor(model)\n const label = displayFieldFor(model)\n const key = model.primaryKey[0] ?? 'id'\n const declared = filtersFor(widget.filter, model)\n\n const page = await input.adapter.list(widget.model, {\n perPage: Math.min(widget.limit ?? 5, 10),\n // Newest first where the model says which those are; otherwise whatever\n // order the adapter returns, which is better than refusing to show a list.\n ...(created ? { sort: [{ field: created, direction: 'desc' as const }] } : {}),\n ...(declared.length > 0 ? { filters: declared } : {}),\n })\n\n return {\n total: page.total,\n records: page.data.map((record: RecordData) => ({\n id: String(record[key] ?? ''),\n label: readable(record[label]) ?? String(record[key] ?? ''),\n })),\n }\n}\n\n/**\n * How many records appeared per bucket.\n *\n * One count per bucket, run concurrently. That is a query per day rather than\n * one grouped query, and it is a deliberate trade: `OrmAdapter` has no\n * `groupBy`, adding one before the 1.0 freeze would put it in every future\n * adapter, and thirty parallel counts against an indexed column is a dashboard\n * that loads in one round trip's worth of wall clock.\n *\n * The bucket count is capped for the same reason it is not unbounded: this is\n * the one place where a configuration value turns directly into a number of\n * queries.\n */\nasync function chartOf(\n widget: Extract<DashboardWidget, { kind: 'chart' }>,\n input: DashboardInput,\n): Promise<ChartData> {\n const model = modelFor(widget.model, input)\n const created = createdFieldFor(model)\n if (created === undefined) {\n throw new Error(`${widget.model} has no creation timestamp, so it cannot be charted.`)\n }\n\n const bucket = widget.bucket ?? 'day'\n const count = Math.min(widget.buckets ?? 30, MAX_BUCKETS)\n const size = bucket === 'day' ? DAY : bucket === 'week' ? 7 * DAY : 30 * DAY\n\n const declared = filtersFor(widget.filter, model)\n\n const now = Date.now()\n const starts = Array.from({ length: count }, (_, index) => now - (count - index) * size)\n\n const points = await Promise.all(\n starts.map(async (start) => {\n const page = await input.adapter.list(widget.model, {\n perPage: 1,\n filters: [\n ...declared,\n { field: created, operator: 'gte', value: new Date(start).toISOString() },\n { field: created, operator: 'lt', value: new Date(start + size).toISOString() },\n ],\n })\n return { at: new Date(start).toISOString(), value: page.total }\n }),\n )\n\n return { points, total: points.reduce((sum, point) => sum + point.value, 0) }\n}\n\n/**\n * The metadata for a widget's model.\n *\n * Always present in practice - a widget over a model this principal cannot see\n * was dropped before anything got here. Throwing rather than carrying on\n * without it means a widget whose model went missing fails visibly instead of\n * quietly reading the wrong column.\n */\nfunction modelFor(name: string, input: DashboardInput): ModelMetadata {\n const model = input.models.find((candidate) => candidate.name === name)\n if (model === undefined) throw new Error(`${name} is not an exposed model.`)\n return model\n}\n\n/**\n * A declared filter, parsed by the same code the list screen's URL goes\n * through - so `active:eq:true` is the boolean in both places, an unknown\n * operator is refused, and a value is coerced against the field it names.\n */\nfunction filtersFor(filter: string | undefined, model: ModelMetadata): readonly FilterRule[] {\n return filter === undefined ? [] : [parseFilterExpression(filter, model)]\n}\n\n/** A value worth showing as a label, or nothing. */\nfunction readable(value: unknown): string | undefined {\n if (typeof value === 'string' && value !== '') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return undefined\n}\n","/**\n * HTTP query string -> Core `ListQuery`.\n *\n * The adapter is typed; HTTP is not. Everything arriving over the wire is a\n * string, so this module is where strings become numbers, booleans and dates,\n * and where malformed input is rejected before it can reach an ORM.\n *\n * Keeping this at the HTTP boundary is deliberate: the adapter must never\n * learn to parse query strings, or every future adapter would have to.\n *\n * ## Syntax\n *\n * ?page=2\n * ?perPage=25\n * ?search=ada\n * ?sort=email:asc&sort=createdAt:desc (repeatable, order preserved)\n * ?filter=age:gte:18&filter=role:in:ADMIN,USER\n *\n * `sort` and `filter` use the same colon-delimited form rather than bracket\n * syntax (`filter[age][gte]=18`), which parses differently on every platform.\n * Bracket syntax is rejected with a 400 rather than ignored - see\n * `rejectUnknownParameters` for why that took two guards.\n *\n * A filter is split into at most three parts, so colons inside a value survive:\n * `filter=startedAt:gte:2024-01-01T00:00:00Z` reads as\n * field `startedAt`, operator `gte`, value `2024-01-01T00:00:00Z`.\n *\n * @experimental The HTTP contract is expected to change before 1.0.\n */\nimport {\n InvalidQueryError,\n type FieldMetadata,\n type FilterOperator,\n type FilterRule,\n type ListQuery,\n type ModelMetadata,\n type SortDirection,\n type SortRule,\n} from '@nest-admin/core'\n\n/** Raw query object as an HTTP platform hands it over. */\nexport type RawQuery = Record<string, unknown>\n\n/**\n * The operators Core defines. Restated as a runtime set because a TypeScript\n * union cannot validate a string arriving over HTTP.\n *\n * Kept in lockstep with Core's `FilterOperator`; a compile-time check below\n * fails the build if the two ever drift.\n */\nconst FILTER_OPERATORS = [\n 'eq',\n 'ne',\n 'contains',\n 'startsWith',\n 'endsWith',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'in',\n] as const\n\n// Fails to compile if Core adds an operator this parser does not accept.\nconst _exhaustive: readonly FilterOperator[] = FILTER_OPERATORS\nvoid _exhaustive\n\nconst SORT_DIRECTIONS = new Set<string>(['asc', 'desc'])\n\n/**\n * Reject a parameter that arrived as a structure rather than text.\n *\n * Express parses `?filter[age][gte]=18` into `{ filter: { age: { gte: '18' } } }`.\n * Every value this parser understands is a string or a list of strings, so an\n * object could only ever be bracket syntax - which this API does not use.\n *\n * It previously fell through as \"no value\", which meant the request **succeeded\n * with the filter silently dropped**: a caller believed it had filtered and\n * received every record instead. Returning more rows than asked for, quietly,\n * is worse than refusing the request, so this is now a 400 that names the\n * syntax the server does accept.\n */\nfunction rejectStructuredValue(name: string, value: unknown): void {\n if (value === undefined || value === null) return\n if (typeof value === 'string') return\n if (Array.isArray(value) && value.every((item) => typeof item === 'string')) return\n\n throw new InvalidQueryError(\n `\"${name}\" must be a plain value, not a nested structure. ` +\n 'This API uses colon syntax - for example ' +\n '\"?filter=age:gte:18\" and \"?sort=email:asc\", not \"?filter[age][gte]=18\".',\n )\n}\n\n/** Every parameter this API understands. Anything else is a client mistake. */\nconst KNOWN_PARAMETERS = new Set(['page', 'perPage', 'search', 'sort', 'filter'])\n\n/**\n * Reject query parameters the API does not define.\n *\n * The motivating case is bracket syntax, and it needs this *as well as*\n * `rejectStructuredValue` because how it arrives depends on the platform's\n * query parser. Measured on Express 5 under NestJS 12, which uses the simple\n * parser, `?filter[age][gte]=18` arrives as a literal key:\n *\n * { 'filter[age][gte]': '18' }\n *\n * so nothing lands on `filter` at all. Under an extended parser (`qs`) the same\n * URL arrives as a nested object on `filter` instead. One guard catches each\n * shape, which is what keeps the behaviour identical on either platform.\n *\n * Either way the old outcome was the dangerous one: the request succeeded and\n * the filter was silently dropped, so a caller believed it had filtered and got\n * every record back.\n *\n * The strictness is deliberate beyond that case. An unrecognised parameter is\n * always a bug - a typo, a stale client, a half-migrated integration - and\n * ignoring it is what let this go unnoticed in the first place.\n */\nfunction rejectUnknownParameters(raw: RawQuery): void {\n const unknown = Object.keys(raw).filter((key) => !KNOWN_PARAMETERS.has(key))\n if (unknown.length === 0) return\n\n const looksBracketed = unknown.some((key) => key.includes('['))\n const hint = looksBracketed\n ? ' This API uses colon syntax: \"?filter=age:gte:18\", not \"?filter[age][gte]=18\".'\n : ''\n\n throw new InvalidQueryError(\n `Unknown query parameter${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}. ` +\n `Supported: ${[...KNOWN_PARAMETERS].join(', ')}.${hint}`,\n )\n}\n\n/** Normalise a query value that may be absent, a string, or repeated. */\nfunction toStringList(name: string, value: unknown): string[] {\n rejectStructuredValue(name, value)\n if (value === undefined || value === null) return []\n if (Array.isArray(value)) return value.filter((item): item is string => typeof item === 'string')\n return typeof value === 'string' ? [value] : []\n}\n\nfunction toSingleString(name: string, value: unknown): string | undefined {\n rejectStructuredValue(name, value)\n if (typeof value === 'string') return value\n // A repeated scalar param is a client bug; take the last rather than fail.\n if (Array.isArray(value)) {\n const strings = value.filter((item): item is string => typeof item === 'string')\n return strings.at(-1)\n }\n return undefined\n}\n\nfunction parsePositiveInteger(raw: string | undefined, name: string): number | undefined {\n if (raw === undefined || raw === '') return undefined\n // Number() would accept '1e3', ' 12 ' and '0x10'. An explicit digit test\n // keeps the accepted set to what a pager actually produces.\n if (!/^\\d+$/.test(raw)) {\n throw new InvalidQueryError(\n `\"${name}\" must be a positive integer, received ${JSON.stringify(raw)}.`,\n )\n }\n const parsed = Number(raw)\n if (parsed < 1) {\n throw new InvalidQueryError(`\"${name}\" must be >= 1, received ${JSON.stringify(raw)}.`)\n }\n return parsed\n}\n\nfunction parseSort(raw: unknown): readonly SortRule[] | undefined {\n const entries = toStringList('sort', raw).filter((entry) => entry.trim() !== '')\n if (entries.length === 0) return undefined\n\n return entries.map((entry) => {\n const separator = entry.lastIndexOf(':')\n if (separator <= 0 || separator === entry.length - 1) {\n throw new InvalidQueryError(`Invalid sort \"${entry}\". Expected \"field:asc\" or \"field:desc\".`)\n }\n const field = entry.slice(0, separator)\n const direction = entry.slice(separator + 1)\n\n if (!SORT_DIRECTIONS.has(direction)) {\n throw new InvalidQueryError(\n `Invalid sort direction \"${direction}\" in \"${entry}\". Expected \"asc\" or \"desc\".`,\n )\n }\n return { field, direction: direction as SortDirection }\n })\n}\n\n/**\n * Coerce a filter value to the type the field declares.\n *\n * Without this every value reaches the ORM as a string, and `price gte \"30\"`\n * either errors or compares lexically. When the field is unknown the value is\n * passed through untouched: the adapter owns field-name validation and will\n * reject it with a precise error, so validating it here too would duplicate\n * that rule in a second place.\n */\nfunction coerceScalar(raw: string, field: FieldMetadata | undefined, context: string): unknown {\n if (!field) return raw\n\n switch (field.kind) {\n case 'number': {\n const parsed = Number(raw)\n if (raw.trim() === '' || !Number.isFinite(parsed)) {\n throw new InvalidQueryError(`${context}: \"${raw}\" is not a valid number.`)\n }\n return parsed\n }\n case 'boolean': {\n if (raw === 'true') return true\n if (raw === 'false') return false\n throw new InvalidQueryError(\n `${context}: \"${raw}\" is not a valid boolean (use true or false).`,\n )\n }\n case 'datetime': {\n const parsed = new Date(raw)\n if (Number.isNaN(parsed.getTime())) {\n throw new InvalidQueryError(`${context}: \"${raw}\" is not a valid date.`)\n }\n return parsed\n }\n default:\n return raw\n }\n}\n\n/**\n * `in` receives a comma-separated list. Values containing a comma cannot be\n * expressed - a documented limitation of the syntax, not an oversight.\n */\nfunction coerceList(raw: string, field: FieldMetadata | undefined, context: string): unknown[] {\n if (raw === '') return []\n return raw.split(',').map((part) => coerceScalar(part, field, context))\n}\n\nfunction parseFilters(raw: unknown, model: ModelMetadata): readonly FilterRule[] | undefined {\n const entries = toStringList('filter', raw).filter((entry) => entry.trim() !== '')\n if (entries.length === 0) return undefined\n\n return entries.map((entry) => {\n // Split into at most three parts so colons inside the value survive.\n const firstSeparator = entry.indexOf(':')\n const secondSeparator = firstSeparator === -1 ? -1 : entry.indexOf(':', firstSeparator + 1)\n\n if (firstSeparator <= 0 || secondSeparator === -1) {\n throw new InvalidQueryError(\n `Invalid filter \"${entry}\". Expected \"field:operator:value\", ` +\n `for example \"email:contains:example.com\".`,\n )\n }\n\n const fieldName = entry.slice(0, firstSeparator)\n const operator = entry.slice(firstSeparator + 1, secondSeparator)\n const rawValue = entry.slice(secondSeparator + 1)\n\n if (!FILTER_OPERATORS.includes(operator as FilterOperator)) {\n throw new InvalidQueryError(\n `Unknown filter operator \"${operator}\" in \"${entry}\". ` +\n `Supported operators: ${FILTER_OPERATORS.join(', ')}.`,\n )\n }\n\n const field = model.fields.find((candidate) => candidate.name === fieldName)\n const context = `Filter \"${entry}\"`\n const value =\n operator === 'in'\n ? coerceList(rawValue, field, context)\n : coerceScalar(rawValue, field, context)\n\n return { field: fieldName, operator: operator as FilterOperator, value }\n })\n}\n\n/**\n * One `field:operator:value` expression, coerced against the schema.\n *\n * The dashboard's declared filters use the same syntax as the list screen's\n * URL, and they have to mean the same thing: `active:eq:true` is the boolean\n * `true` in both places, not the string. Sharing the parser is what guarantees\n * that - a second implementation would drift, and its drift would be silent,\n * because a filter that coerces wrongly returns no rows rather than an error.\n */\nexport function parseFilterExpression(entry: string, model: ModelMetadata): FilterRule {\n const rules = parseFilters(entry, model)\n const rule = rules?.[0]\n if (rule === undefined) {\n throw new InvalidQueryError(\n `Expected a filter of the form \"field:operator:value\", got \"${entry}\".`,\n )\n }\n return rule\n}\n\n/**\n * Build a `ListQuery` from a raw HTTP query object.\n *\n * `model` is required because value coercion is type-directed: only the schema\n * knows that `price` is a number and `active` a boolean.\n */\nexport function parseListQuery(raw: RawQuery, model: ModelMetadata): ListQuery {\n rejectUnknownParameters(raw)\n\n const page = parsePositiveInteger(toSingleString('page', raw['page']), 'page')\n const perPage = parsePositiveInteger(toSingleString('perPage', raw['perPage']), 'perPage')\n const sort = parseSort(raw['sort'])\n const filters = parseFilters(raw['filter'], model)\n const search = toSingleString('search', raw['search'])\n\n return {\n ...(page !== undefined ? { page } : {}),\n ...(perPage !== undefined ? { perPage } : {}),\n ...(sort ? { sort } : {}),\n ...(filters ? { filters } : {}),\n ...(search !== undefined && search !== '' ? { search } : {}),\n }\n}\n","/**\n * What an application puts on the dashboard.\n *\n * ## A closed set of four\n *\n * `count`, `list`, `chart`, `stat`. Closed for the same reason `FieldWidget` is:\n * the interface has to know how to draw each one, so an open string would mean\n * rendering nothing and no way to notice.\n *\n * It is also the line this release does not cross. An arbitrary React component\n * would mean the consuming application builds and bundles one, which is exactly\n * the thing this package exists not to make people do - and the reason custom\n * pages have been out of scope since 0.6.0.\n *\n * ## Three of them are declarative on purpose\n *\n * `count`, `list` and `chart` name a model and a filter; the server does the\n * work. That is not just terseness. A widget that names a model can be\n * *authorized*: one over a resource this principal cannot see is absent from\n * the document, the same way a hidden model and a refused action already are.\n * A widget built from a closure could not be checked, only trusted.\n *\n * `stat` is the escape hatch and has no model, because the number it shows may\n * come from anywhere - a payment processor, a queue, three tables joined. It\n * runs application code, so the application's own rules apply to it.\n *\n * ## Nothing is configured by default\n *\n * An admin with no `dashboard` option still gets one, built from metadata\n * alone: a count per model, and recent records where the schema says when a\n * record was created. Declaring widgets replaces that rather than adding to it,\n * because a dashboard is a page someone designed, and half-designed is worse\n * than either.\n */\nimport type { ExecutionContext } from '@nestjs/common'\n\n/** How wide a widget sits in the four-column grid. */\nexport type WidgetSpan = 1 | 2 | 3 | 4\n\ninterface Common {\n /** Shown above it. The one thing every widget needs. */\n readonly title: string\n /** A sentence under the title, when the title cannot carry it alone. */\n readonly description?: string\n /** Columns out of four. Sensible per kind when omitted. */\n readonly span?: WidgetSpan\n}\n\n/**\n * A single number, from a model.\n *\n * The most common thing on any dashboard, and the reason it is declarative:\n * \"how many open orders\" is a model, a filter, and nothing else.\n */\nexport interface CountWidget extends Common {\n readonly kind: 'count'\n readonly model: string\n /** `field:op:value`, the same syntax the list screen's URL uses. */\n readonly filter?: string\n /**\n * Compare against the same count a period ago, and show the change.\n *\n * Needs the model to have a creation timestamp; the comparison is silently\n * omitted when it does not, rather than the widget disappearing.\n */\n readonly compareDays?: number\n}\n\n/** A few records, most recent first where the model says which those are. */\nexport interface ListWidget extends Common {\n readonly kind: 'list'\n readonly model: string\n readonly filter?: string\n /** How many rows. Five by default; more than ten belongs on the list screen. */\n readonly limit?: number\n}\n\n/** How many records appeared per day, week or month. */\nexport interface ChartWidget extends Common {\n readonly kind: 'chart'\n readonly model: string\n readonly filter?: string\n readonly bucket?: 'day' | 'week' | 'month'\n /** How many buckets. Thirty by default, ninety at most - see the service. */\n readonly buckets?: number\n}\n\n/**\n * A number the application works out for itself.\n *\n * The escape hatch, and the only widget that runs application code. Whatever it\n * returns is shown; whatever it throws becomes a widget that says it could not\n * load, rather than a dashboard that does not.\n */\nexport interface StatWidget extends Common {\n readonly kind: 'stat'\n readonly load: (args: { readonly context: ExecutionContext }) => Promise<StatResult> | StatResult\n}\n\nexport interface StatResult {\n /** Shown large. A string is passed through, so it can carry a currency. */\n readonly value: string | number\n /** A change against some previous period, as a percentage. */\n readonly delta?: number\n /** Under the value. \"vs last month\", \"across 4 regions\". */\n readonly hint?: string\n}\n\nexport type DashboardWidget = CountWidget | ListWidget | ChartWidget | StatWidget\n\n/**\n * The dashboard an application declares.\n *\n * An array rather than a keyed object: a dashboard is read top to bottom, and\n * the order things appear in is part of the design.\n */\nexport type AdminDashboard = readonly DashboardWidget[]\n\n/** Which model a widget reads, when it reads one. Used to authorize it. */\nexport function modelOf(widget: DashboardWidget): string | undefined {\n return widget.kind === 'stat' ? undefined : widget.model\n}\n\n/** How wide a widget is when it does not say. */\nexport function defaultSpan(widget: DashboardWidget): WidgetSpan {\n switch (widget.kind) {\n // A number is small; a chart needs room to be read; a list is a column of\n // rows and looks thin at a quarter width.\n case 'chart':\n return 2\n case 'list':\n return 2\n default:\n return 1\n }\n}\n","/**\n * The public HTTP representation of model metadata.\n *\n * This is the contract between the backend and any future admin frontend, and\n * it is deliberately declared separately from Core's `ModelMetadata` rather\n * than serialised straight from it. Two reasons:\n *\n * 1. Core's contract is marked `@experimental` and will keep moving. The wire\n * format must not move with it by accident.\n * 2. An explicit mapper is a whitelist. If a future adapter puts something\n * ORM-specific on `FieldMetadata`, it cannot silently reach a client.\n *\n * Nothing here mentions Prisma, DMMF, or any ORM. Replacing the Prisma adapter\n * with another one must not change a single byte of this shape.\n *\n * @experimental The HTTP contract is expected to change before 1.0.\n */\nimport {\n detachBlockedReason,\n displayFieldFor,\n type ModelIcon,\n fieldOverride,\n isReadOnly,\n inverseRelationField,\n relationShape,\n type FieldMetadata,\n type ModelMetadata,\n type ModelOverrides,\n} from '@nest-admin/core'\n\n/** Mirrors Core's `FieldKind`, restated so the wire format is self-contained. */\nexport type FieldKindDto =\n 'string' | 'number' | 'boolean' | 'datetime' | 'enum' | 'json' | 'relation' | 'unknown'\n\nexport interface RelationDto {\n readonly targetModel: string\n readonly cardinality: 'one' | 'many'\n\n /**\n * Scalar field on this model holding the key, for a to-one relation.\n *\n * The UI needs it twice over: it is the field a form submits when the user\n * picks a related record, and the field a filter is expressed in. Absent on\n * to-many relations, which have no column on this side.\n */\n readonly from?: string\n\n /** Field on the target the key points at - usually its id. */\n readonly to?: string\n\n /**\n * Where the link is stored, which decides what may be done to it.\n *\n * Computed on the server rather than left for the client to derive, for the\n * same reason as `displayField`: working it out needs the other half of the\n * relation, and two implementations of that rule would drift. A client that\n * guessed wrong would offer a button that cannot work.\n */\n readonly shape?: 'to-one' | 'one-to-many' | 'many-to-many'\n\n /**\n * Why records cannot be detached from this relation, when they cannot.\n *\n * Present only for a one-to-many whose child key is required: such a child\n * cannot exist without a parent, so there is nothing to detach it to.\n */\n readonly detachBlocked?: string\n\n /**\n * The column on the target model that points back at this one.\n *\n * What \"all the posts by this author\" is expressed as:\n * `?filter=<targetForeignKey>:eq:<parentId>`. Present only for a one-to-many,\n * since a many-to-many has no such column on either side.\n *\n * Sent rather than derived, for the same reason as `shape`: finding it means\n * pairing the two halves of the relation, and a rule implemented twice is a\n * rule that will eventually disagree with itself.\n */\n readonly targetForeignKey?: string\n}\n\nexport interface FieldDto {\n readonly name: string\n readonly kind: FieldKindDto\n /** Part of the model's primary key. */\n readonly isId: boolean\n readonly isRequired: boolean\n readonly isUnique: boolean\n readonly isList: boolean\n /**\n * Produced by the database or ORM (`cuid()`, `now()`, `autoincrement()`,\n * `@updatedAt`). Display it; do not ask the user for it.\n */\n readonly isGenerated: boolean\n /**\n * Literal default to pre-fill on create. Present only for editable fields\n * that declare one - a generated value has no literal to pre-fill.\n */\n readonly defaultValue?: unknown\n /** Present when `kind` is `'enum'`. */\n readonly enumValues?: readonly string[]\n\n /**\n * What to call the field, when the column name is not what people call it.\n *\n * Absent unless the application said so. A client falls back to `name`.\n */\n readonly label?: string\n\n /**\n * How the field should be edited, when its kind does not say enough.\n *\n * A `string` column may be a sentence, a password or a colour, and the schema\n * cannot tell them apart.\n */\n readonly widget?: 'textarea' | 'password' | 'email' | 'url' | 'color' | 'json'\n\n /**\n * The admin will refuse to write this field.\n *\n * True for generated columns, and for anything the application marked\n * read-only. Enforced: a write naming it is rejected, so a client that\n * ignores this gets a 400 rather than a surprise.\n */\n readonly readOnly: boolean\n\n /**\n * The admin accepts this field on a write and never sends it back.\n *\n * Sent so the interface knows the blank it shows is not the stored value. A\n * password field that looked empty because the record had none would be a\n * different thing entirely.\n */\n readonly writeOnly?: boolean\n\n /** Present when `kind` is `relation`. */\n readonly relation?: RelationDto\n}\n\n/** Which operations a principal may perform on one model. */\nexport interface ModelPermissionsDto {\n readonly list: boolean\n readonly read: boolean\n readonly create: boolean\n readonly update: boolean\n readonly delete: boolean\n}\n\n/** An application-defined button the interface should draw. */\nexport interface ActionDto {\n readonly name: string\n readonly label: string\n readonly scope: 'record' | 'list'\n /** Ask this before running. Absent means run straight away. */\n readonly confirm?: string\n /** Draw it as destructive. */\n readonly danger?: boolean\n}\n\nexport interface ModelDto {\n readonly name: string\n /** Field names forming the primary key. Single-column in this version. */\n readonly primaryKey: readonly string[]\n readonly fields: readonly FieldDto[]\n\n /**\n * Field that names a record of this model in one line.\n *\n * Sent rather than left for the UI to guess, because the guess would have to\n * match what the adapter already selected when it loaded the relation. Both\n * come from one rule in Core, so they cannot disagree.\n */\n readonly displayField: string\n\n /**\n * What this principal may do with the model.\n *\n * Sent so the interface can stop offering actions that will be refused. It is\n * a description of the policy's answers, not the enforcement: every request is\n * checked again when it arrives, and a client that ignores this gets a 403\n * rather than access.\n *\n * `metadata` is not among them - a model the principal cannot see is absent\n * from this document entirely.\n */\n readonly can: ModelPermissionsDto\n\n /**\n * Application-defined actions this principal may run.\n *\n * Already filtered by the policy: an action that would be refused is absent,\n * so the interface never draws a button that cannot work.\n */\n readonly actions: readonly ActionDto[]\n\n /** What to call the model. Absent unless the application said so. */\n readonly label?: string\n\n /**\n * Which icon to draw beside it in the navigation.\n *\n * One of a closed set the interface knows how to render - see `ModelIcon` in\n * Core. Absent unless the application named one, and absent is a real answer:\n * the same icon repeated down a column is decoration.\n */\n readonly icon?: ModelIcon\n}\n\nexport interface MetadataDto {\n readonly models: readonly ModelDto[]\n}\n\n/**\n * The answer when no policy was consulted.\n *\n * Only reachable from a caller that passes no permissions at all, which is the\n * tests and nothing else - the service always supplies them. Permissive is the\n * right default here precisely because it is not the enforcement: the request\n * is checked when it arrives regardless.\n */\nconst ALL_PERMITTED: ModelPermissionsDto = {\n list: true,\n read: true,\n create: true,\n update: true,\n delete: true,\n}\n\n/**\n * The column on the target that points back, for a one-to-many.\n *\n * `undefined` for every other shape: a to-one owns its own column, and a\n * many-to-many has none on either side.\n */\nfunction targetForeignKeyOf(\n field: FieldMetadata,\n models: readonly ModelMetadata[],\n): string | undefined {\n if (relationShape(field, models) !== 'one-to-many') return undefined\n return inverseRelationField(field, models)?.relation?.from\n}\n\n/**\n * Order the way the application asked, then the way the schema declares.\n *\n * A declared `order` wins; anything without one keeps its schema position,\n * after everything that has one. Sorting on a missing value would otherwise\n * reshuffle the fields nobody configured.\n */\nfunction byOrder<T>(items: readonly T[], orderOf: (item: T) => number | undefined): readonly T[] {\n return [...items]\n .map((item, index) => ({ item, index, order: orderOf(item) }))\n .sort((a, b) => {\n if (a.order === b.order) return a.index - b.index\n if (a.order === undefined) return 1\n if (b.order === undefined) return -1\n return a.order - b.order\n })\n .map((entry) => entry.item)\n}\n\nfunction toFieldDto(\n field: FieldMetadata,\n modelName: string,\n models: readonly ModelMetadata[],\n overrides: ModelOverrides | undefined,\n): FieldDto {\n const override = fieldOverride(overrides, modelName, field.name)\n\n // Built property by property on purpose. A spread would forward anything a\n // future adapter attaches to FieldMetadata straight onto the wire.\n return {\n name: field.name,\n kind: field.kind,\n isId: field.isId,\n isRequired: field.isRequired,\n isUnique: field.isUnique,\n isList: field.isList,\n isGenerated: field.isGenerated,\n readOnly: isReadOnly(overrides, modelName, field),\n ...(field.writeOnly === true ? { writeOnly: true } : {}),\n ...(override?.label !== undefined ? { label: override.label } : {}),\n ...(override?.widget !== undefined ? { widget: override.widget } : {}),\n ...(field.defaultValue !== undefined ? { defaultValue: field.defaultValue } : {}),\n ...(field.enumValues ? { enumValues: [...field.enumValues] } : {}),\n ...(field.relation\n ? {\n relation: {\n targetModel: field.relation.targetModel,\n cardinality: field.relation.cardinality,\n ...(field.relation.from !== undefined ? { from: field.relation.from } : {}),\n ...(field.relation.to !== undefined ? { to: field.relation.to } : {}),\n ...(relationShape(field, models) !== undefined\n ? { shape: relationShape(field, models) }\n : {}),\n ...(detachBlockedReason(field, models) !== undefined\n ? { detachBlocked: detachBlockedReason(field, models) }\n : {}),\n ...(targetForeignKeyOf(field, models) !== undefined\n ? { targetForeignKey: targetForeignKeyOf(field, models) }\n : {}),\n },\n }\n : {}),\n }\n}\n\n/**\n * Build the metadata document from the models that belong in it.\n *\n * Relation fields pointing at a model that is **not** in `models` are dropped.\n * This is a document-coherence rule, not a permission rule - the mapper makes\n * no authorization decision and does not know one was made. It simply refuses\n * to emit a reference to something the document does not contain, because a\n * dangling `targetModel` is not renderable by any client.\n *\n * It also closes a real leak. When a caller filters the model list - as\n * resource authorization does - dropping `Post` while keeping `User.posts`\n * would still publish the hidden model's name through `relation.targetModel`,\n * and the relation field's own name along with it.\n */\nexport function toMetadataDto(\n models: readonly ModelMetadata[],\n overrides?: ModelOverrides,\n permissions?: ReadonlyMap<string, ModelPermissionsDto>,\n actions?: ReadonlyMap<string, readonly ActionDto[]>,\n): MetadataDto {\n const present = new Set(models.map((model) => model.name))\n\n return {\n models: byOrder(models, (model) => overrides?.[model.name]?.order).map((model) => ({\n name: model.name,\n primaryKey: [...model.primaryKey],\n displayField: displayFieldFor(model),\n can: permissions?.get(model.name) ?? ALL_PERMITTED,\n actions: actions?.get(model.name) ?? [],\n ...(overrides?.[model.name]?.label !== undefined\n ? { label: overrides[model.name]?.label }\n : {}),\n ...(overrides?.[model.name]?.icon !== undefined ? { icon: overrides[model.name]?.icon } : {}),\n fields: byOrder(\n model.fields.filter((field) => !field.relation || present.has(field.relation.targetModel)),\n (field) => fieldOverride(overrides, model.name, field.name)?.order,\n ).map((field) => toFieldDto(field, model.name, models, overrides)),\n })),\n }\n}\n","/**\n * Signing in, signing out, and asking who you are.\n *\n * ## Why this controller is not behind the guard\n *\n * Everything else in the admin is. These three cannot be: a login route that\n * requires a session is a door that only opens from inside. So the guard is\n * off here and each handler is responsible for its own answer - which is why\n * they are short and why the only thing they do is call into `built-in.ts`.\n *\n * ## Why it exists even when nobody uses it\n *\n * Routes are registered when the module is defined; the `auth` a consumer\n * chose arrives later, from a provider. Rather than make the choice structural\n * - and force every application to declare its auth in two places - the routes\n * are always registered and answer `404` when the configured auth is not one\n * of ours. An application using its own `AdminAuth` sees an admin with no login\n * endpoints, which is what it should see.\n */\nimport { InvalidQueryError, summarise, UnauthorizedError } from '@nest-admin/core'\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n Inject,\n NotFoundException,\n Post,\n Req,\n Res,\n UseFilters,\n} from '@nestjs/common'\n\nimport { AdminExceptionFilter } from '../http/exception.filter.js'\nimport { success, type SuccessResponse } from '../http/response.js'\nimport { ADMIN_AUTH } from '../tokens.js'\nimport {\n attemptKey,\n builtInRuntimeOf,\n clearSessionCookie,\n cookieFrom,\n setSessionCookie,\n type BuiltInAuthRuntime,\n} from './built-in.js'\nimport type { AdminAuth } from './contract.js'\nimport { readSession, signSession } from './session.js'\n\n/** What the interface is told about whoever is signed in. Never the hash. */\ninterface SessionResponse {\n readonly account: ReturnType<typeof summarise> | null\n}\n\n@Controller('auth')\n@UseFilters(AdminExceptionFilter)\nexport class AdminAuthController {\n constructor(@Inject(ADMIN_AUTH) private readonly auth: AdminAuth) {}\n\n /**\n * `GET /admin/auth/session` - who is signed in, if anyone.\n *\n * Answers `200` with `account: null` rather than `401` for an absent\n * session. The interface asks this before it has any reason to think anybody\n * is signed in, and an error is the wrong shape for \"no, and that is fine\" -\n * it would put a failure in the console on every visit to the login page.\n */\n @Get('session')\n async session(@Req() request: RawRequest): Promise<SuccessResponse<SessionResponse>> {\n const runtime = this.runtime()\n const token = cookieFrom(request?.headers?.cookie, runtime.cookieName)\n const id = token === undefined ? undefined : readSession(token, runtime.secret)\n\n if (id === undefined) return success({ account: null })\n\n const account = await runtime.store.findById(id)\n return success({\n account: !account || account.disabled === true ? null : summarise(account),\n })\n }\n\n /**\n * `POST /admin/auth/login` with `{ email, password }`.\n *\n * `200` and a cookie, or `401` and nothing. There is exactly one failure\n * message: an unknown address, a wrong password, a disabled account and a\n * locked-out one are indistinguishable from outside. Telling them apart is\n * convenient for the person signing in perhaps twice a year, and a list of\n * which addresses are registered for everyone else.\n */\n @Post('login')\n @HttpCode(200)\n async login(\n @Req() request: RawRequest,\n @Res({ passthrough: true }) response: unknown,\n @Body() body: { email?: unknown; password?: unknown },\n ): Promise<SuccessResponse<SessionResponse>> {\n const runtime = this.runtime()\n assertSameOrigin(request)\n\n if (typeof body?.email !== 'string' || typeof body?.password !== 'string') {\n throw new InvalidQueryError('Signing in requires an email and a password.')\n }\n\n const account = await runtime.signIn(body.email, body.password, attemptKey(request, body.email))\n\n if (!account) {\n throw new UnauthorizedError('Those details do not match an account.')\n }\n\n /*\n * A new token, always.\n *\n * Whatever cookie arrived with this request is discarded rather than kept\n * or upgraded. That is what stops session fixation: an attacker who can\n * plant a cookie before someone signs in must not still hold a valid one\n * afterwards.\n */\n setSessionCookie(\n response,\n signSession(account.id, runtime.secret, runtime.maxAge),\n runtime,\n request,\n )\n\n return success({ account: summarise(account) })\n }\n\n /**\n * `POST /admin/auth/logout`.\n *\n * A POST rather than a GET, so a link or an image somewhere else cannot sign\n * someone out by being loaded. Always `200`: signing out when you were not\n * signed in is not a failure, it is the state you asked for.\n */\n @Post('logout')\n @HttpCode(200)\n logout(\n @Req() request: RawRequest,\n @Res({ passthrough: true }) response: unknown,\n ): SuccessResponse<SessionResponse> {\n const runtime = this.runtime()\n assertSameOrigin(request)\n clearSessionCookie(response, runtime, request)\n return success({ account: null })\n }\n\n /**\n * The built-in auth's runtime, or a 404.\n *\n * Not a 500: an application using its own `AdminAuth` has no login routes,\n * and \"this endpoint does not exist here\" is exactly true.\n */\n private runtime(): BuiltInAuthRuntime {\n const runtime = builtInRuntimeOf(this.auth)\n if (!runtime) {\n throw new NotFoundException(\n 'This admin is not using the built-in authentication, so it has no login routes.',\n )\n }\n return runtime\n }\n}\n\ninterface RawRequest {\n readonly method?: string\n readonly headers?: Record<string, string | string[] | undefined>\n readonly socket?: { readonly remoteAddress?: string }\n readonly ip?: string\n}\n\n/**\n * Refuse a state-changing request that came from somewhere else.\n *\n * The session cookie is already `SameSite=Lax`, which means a cross-site POST\n * does not carry it and arrives unauthenticated - that is the real defence.\n * This is the second lock: it costs one comparison and it covers the case where\n * a future change loosens the cookie, or a browser is older than the attribute.\n *\n * Checked only when `Origin` is present. Browsers send it on every\n * cross-origin request and on same-origin writes; a script or a curl command\n * sends neither, and refusing those would break using the API from a terminal\n * for no security gain - a program that can set headers can set this one.\n */\nfunction assertSameOrigin(request: RawRequest | undefined): void {\n const origin = header(request, 'origin')\n if (origin === undefined) return\n\n const host = header(request, 'host')\n if (host === undefined) return\n\n let sent: string\n try {\n sent = new URL(origin).host\n } catch {\n throw new UnauthorizedError('This request did not come from the admin.')\n }\n\n if (sent !== host) {\n throw new UnauthorizedError('This request did not come from the admin.')\n }\n}\n\nfunction header(request: RawRequest | undefined, name: string): string | undefined {\n const value = request?.headers?.[name]\n return typeof value === 'string' ? value : undefined\n}\n","/**\n * The admin authentication boundary.\n *\n * Nest Admin does not authenticate anyone. The consuming application already\n * has an identity system - sessions, JWTs, an API gateway, mTLS, whatever it\n * is - and a framework that invented a second one would be adding a security\n * surface, not removing one.\n *\n * So the contract is a single decision: *may this request reach the admin?*\n * Everything about how identity was established stays in the host application.\n *\n * Nothing here inspects a header, a cookie, or a token, and nothing here knows\n * what a user is.\n */\nimport { ForbiddenError, UnauthorizedError } from '@nest-admin/core'\nimport { Logger, type ExecutionContext } from '@nestjs/common'\n\n/**\n * Implemented by the consuming application and passed to\n * `AdminModule.forRoot({ auth })`.\n *\n * ```ts\n * const auth: AdminAuth = {\n * authorize(context) {\n * const request = context.switchToHttp().getRequest()\n * if (!request.user) throw new UnauthorizedError()\n * if (!request.user.isStaff) throw new ForbiddenError()\n * },\n * }\n * ```\n */\nexport interface AdminAuth {\n /**\n * Decide whether the request may proceed.\n *\n * Return (or resolve) normally to allow it. To deny it, throw:\n *\n * - {@link UnauthorizedError} - no identity was established. 401.\n * - {@link ForbiddenError} - an identity exists but may not do this. 403.\n *\n * Throwing is the intended way to deny, because it forces the caller to say\n * *which* denial it is. A client cannot act on \"denied\"; it can act on \"log\n * in\" versus \"you may not do this\".\n *\n * Returning `false` is also treated as a denial, mapped to `403`, so a guard\n * written in the reflexive NestJS style still fails closed rather than\n * silently allowing the request. Prefer throwing: `false` cannot express the\n * 401/403 distinction, and 403 is only the safer of the two guesses.\n *\n * May be synchronous or asynchronous.\n *\n * @param context The NestJS execution context. Use it to reach the request -\n * including any principal the host application already attached, and the\n * `model` route parameter on per-model routes.\n */\n authorize(context: ExecutionContext): void | boolean | Promise<void | boolean>\n}\n\nconst logger = new Logger('NestAdmin')\n\n/**\n * An {@link AdminAuth} that permits every request. **The admin API becomes\n * completely public.**\n *\n * It exists because `auth` is required, and a required option with no escape\n * hatch pushes people toward writing their own always-allow implementation -\n * which is the same hole, only invisible in review. This one is deliberately\n * hard to mistake for anything else: the name says `unsafe`, and it logs a\n * warning every time an application starts with it.\n *\n * Intended for local development, examples and tests. Never for a deployed\n * application.\n */\nexport function unsafeAllowAllRequests(): AdminAuth {\n const auth: AdminAuth = {\n authorize(): void {\n // Nothing. That is the point, and why the name says so.\n },\n }\n unsafeInstances.add(auth)\n return auth\n}\n\n/**\n * Instances produced by {@link unsafeAllowAllRequests}.\n *\n * A `WeakSet` rather than a marker property on the object: nothing is added to\n * the public shape of `AdminAuth`, a consumer cannot set the flag by accident\n * or on purpose, and the entry disappears with the instance.\n */\nconst unsafeInstances = new WeakSet<AdminAuth>()\n\n/** Warn loudly when an application boots with authentication disabled. */\nexport function warnIfUnsafe(auth: AdminAuth): void {\n if (unsafeInstances.has(auth)) {\n logger.warn(\n 'AdminModule is running with unsafeAllowAllRequests(): every admin route, ' +\n 'including /admin/meta, is public. Do not deploy this.',\n )\n }\n}\n","/**\n * The resource authorization boundary.\n *\n * Phase 4 gave the host a say over whether a *request* may enter the admin at\n * all (`AdminAuth`). This answers a narrower question: may this principal touch\n * *this model*, for *this operation*?\n *\n * The two are deliberately separate contracts. A host that only needs \"staff\n * only\" implements `AdminAuth` and stops; a host that needs \"support can read\n * Users but nobody outside finance sees Payment\" adds this one. Folding them\n * together would force every consumer to think about resources whether or not\n * they have per-resource rules.\n *\n * Why this exists at all: a host can already deny per model from `AdminAuth`,\n * because the guard sees `params.model`. But `GET /admin/meta` has no `:model`\n * segment, so route-level checks cannot stop the metadata endpoint from\n * describing every table in the database - and the admin UI renders itself from\n * that endpoint. Resource authorization has to live where metadata is produced.\n */\nimport type { ExecutionContext } from '@nestjs/common'\n\n/**\n * What the caller is trying to do.\n *\n * `'metadata'` is the odd one out: it is not an operation on records, it asks\n * whether the model should be *visible* to this principal at all. A model that\n * fails a `'metadata'` check disappears from `GET /admin/meta` entirely.\n */\nexport type AdminOperation =\n | 'metadata'\n | 'list'\n | 'read'\n | 'create'\n | 'update'\n | 'delete'\n /**\n * An application-defined action.\n *\n * Distinct from `update` because an action can do anything, including things\n * no CRUD route offers, so a policy should be able to decide about it\n * separately. A policy written before actions existed does not recognise the\n * value and denies it, which is the right direction to fail in.\n */\n | 'action'\n\n/** Everything the policy is given to decide with. */\nexport interface ResourceAuthorization {\n /**\n * The NestJS execution context for the request being served. Use it to reach\n * whatever principal the host application attached to the request - exactly\n * as in `AdminAuth.authorize`, so one accessor works for both contracts.\n */\n readonly context: ExecutionContext\n /** The model name as the schema declares it, e.g. `User`. */\n readonly model: string\n readonly operation: AdminOperation\n}\n\n/**\n * Implemented by the consuming application and passed to\n * `AdminModule.forRoot({ resourceAuth })`.\n *\n * ```ts\n * const resourceAuth: AdminResourceAuth = {\n * authorize({ context, model, operation }) {\n * const { user } = context.switchToHttp().getRequest()\n * if (model === 'AuditLog') return user.isAdmin\n * if (operation === 'delete') return user.isAdmin\n * return true\n * },\n * }\n * ```\n */\nexport interface AdminResourceAuth {\n /**\n * Decide whether this principal may perform `operation` on `model`.\n *\n * Return `true`, or return nothing, to allow. To deny, return `false` or\n * throw `ForbiddenError`. Both are treated identically - unlike\n * `AdminAuth`, there is no 401/403 ambiguity to resolve here, because a\n * request that reached this point has already passed authentication.\n *\n * The consequence of a denial depends on the operation:\n *\n * - `'metadata'` - the model is **omitted** from `GET /admin/meta`. It is not\n * an error; the response simply describes a smaller schema.\n * - everything else - the request fails with `403 FORBIDDEN`, and the ORM\n * adapter is never called.\n *\n * Anything else thrown is treated as a bug in the host's policy: the request\n * fails with a generic 500 and the real error is logged. A failing policy\n * never allows access.\n *\n * May be synchronous or asynchronous.\n */\n authorize(resource: ResourceAuthorization): void | boolean | Promise<void | boolean>\n}\n\n/**\n * The default when `resourceAuth` is omitted: every model is visible and every\n * operation permitted.\n *\n * Unlike `auth`, this default is not a hole. `auth` is still required, so the\n * door is already shut; omitting `resourceAuth` only means \"everyone who gets\n * in sees everything\", which is exactly the behaviour before this option\n * existed. Making it required would break every existing consumer to express a\n * rule most applications do not have.\n */\nexport function allowAllResources(): AdminResourceAuth {\n return {\n authorize(): true {\n return true\n },\n }\n}\n","/**\n * The path the admin is mounted under.\n *\n * One string reaches three places that must agree, so it is normalised once,\n * here, rather than defended against separately in each of them:\n *\n * - the router, which prefixes every controller route;\n * - the served `index.html`, whose asset URLs are absolute;\n * - the browser, which builds API URLs from it.\n *\n * A mismatch between any two of those is a blank admin with a 404 in the\n * console, which is a poor thing to debug.\n */\n\n/** The mount path used when the application does not choose one. */\nexport const DEFAULT_MOUNT_PATH = '/admin'\n\n/**\n * Canonical form: a single leading slash, no trailing slash, no empty segments.\n *\n * Accepts what people actually type - `admin`, `/admin`, `admin/`, `/admin/` -\n * and answers `/admin` for all of them.\n */\nexport function normaliseMountPath(path: string | undefined): string {\n if (path === undefined) return DEFAULT_MOUNT_PATH\n\n if (typeof path !== 'string') {\n throw new TypeError(`AdminModule \\`path\\` must be a string, received ${typeof path}.`)\n }\n\n const segments = path.split('/').filter((segment) => segment.length > 0)\n\n if (segments.length === 0) {\n // Mounting at the root would put `:model` on `/`, so the admin would answer\n // every unmatched request in the host application - including routes the\n // host defines later, which would then fail in a way that points nowhere\n // near this option.\n throw new Error(\n 'AdminModule `path` cannot be empty or \"/\". The admin routes end in ' +\n '`:model`, so mounting them at the root would capture every unmatched ' +\n 'request in the application. Choose a path such as \"/admin\".',\n )\n }\n\n for (const segment of segments) {\n // Unreserved URL characters only. That rules out route patterns (`:model`,\n // `*`) which would make the path un-buildable, and it also means the value\n // can be written into the served HTML without escaping - there is no\n // character left in it that could close a tag or a string.\n if (!/^[A-Za-z0-9._~-]+$/.test(segment)) {\n throw new Error(\n `AdminModule \\`path\\` segment \"${segment}\" is not a plain path segment. ` +\n 'Use letters, digits, and any of . _ ~ - : the admin builds both URLs ' +\n 'and HTML from this value, so it cannot contain patterns or markup.',\n )\n }\n }\n\n return `/${segments.join('/')}`\n}\n","/**\n * Locating and reading the built admin UI.\n *\n * The UI is compiled by `packages/admin-ui` and copied into this package's `dist`\n * at build time, so a consumer who installs the published package gets the\n * interface without cloning the repository or running Vite.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { dirname, join, resolve, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { renderTheme, type AdminTheme } from './theme.js'\n\n/**\n * Where the built UI sits relative to the compiled bundle.\n *\n * `import.meta.url` works in both the ESM and CJS outputs because tsup is\n * configured with `shims: true`, which rewrites it for the CJS build. Resolving\n * from the bundle rather than `process.cwd()` matters: a consumer starts their\n * application from their own directory, not ours.\n */\nfunction moduleDirectory(): string {\n return dirname(fileURLToPath(import.meta.url))\n}\n\n/** Absolute path of the directory holding `index.html` and `assets/`. */\nexport function uiRoot(): string {\n return join(moduleDirectory(), 'admin-ui')\n}\n\n/** Is a built UI actually present? */\nexport function uiAvailable(root: string = uiRoot()): boolean {\n return existsSync(join(root, 'index.html'))\n}\n\n/** Content types for what a Vite build emits. Anything else is a download. */\nconst CONTENT_TYPES: Readonly<Record<string, string>> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.ico': 'image/x-icon',\n '.webp': 'image/webp',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n}\n\nexport function contentTypeFor(fileName: string): string {\n const dot = fileName.lastIndexOf('.')\n const extension = dot === -1 ? '' : fileName.slice(dot).toLowerCase()\n return CONTENT_TYPES[extension] ?? 'application/octet-stream'\n}\n\n/**\n * Read a file from the UI's `assets/` directory.\n *\n * Returns `undefined` when the file does not exist or the name tries to escape\n * the directory. Two independent guards, because serving arbitrary files off a\n * consumer's disk is the worst thing this package could do:\n *\n * 1. the name must look like a plain build artefact - no separators, no `..`;\n * 2. the resolved path must still sit inside the assets directory.\n *\n * The route only binds a single path segment, so a traversal attempt would have\n * to survive URL decoding *and* both checks.\n */\nexport function readAsset(fileName: string, root: string = uiRoot()): Buffer | undefined {\n if (!/^[\\w.-]+$/.test(fileName) || fileName.includes('..')) return undefined\n\n const assetsDirectory = join(root, 'assets')\n const candidate = resolve(assetsDirectory, fileName)\n\n if (!candidate.startsWith(assetsDirectory + sep)) return undefined\n if (!existsSync(candidate) || !statSync(candidate).isFile()) return undefined\n\n return readFileSync(candidate)\n}\n\n/** The SPA shell, or `undefined` when the UI was not bundled. */\nexport function readIndexHtml(root: string = uiRoot()): Buffer | undefined {\n const indexPath = join(root, 'index.html')\n return existsSync(indexPath) ? readFileSync(indexPath) : undefined\n}\n\n/**\n * The base path Vite is configured to emit into asset URLs.\n *\n * A placeholder rather than a real default, because the mount path is not known\n * until the application calls `forRoot`. Matching on a plausible-looking value\n * such as `/admin/` would risk rewriting something that merely resembled it;\n * this string appears in the build for exactly one reason.\n *\n * Keep in step with `base` in `packages/admin-ui/vite.config.ts`.\n */\nexport const UI_BASE_PLACEHOLDER = '/__nest-admin-base__'\n\n/**\n * The SPA shell with every URL pointed at the configured mount path.\n *\n * Two things in the shell need it. The asset tags Vite emits are absolute, so\n * they carry the placeholder and are rewritten. The application also needs the\n * base at runtime to build API URLs, and it cannot infer it: the SPA uses hash\n * routing, so `/panel/User` and `/panel#/User` are indistinguishable from\n * inside the page. It is injected as a global instead.\n *\n * `mountPath` is validated by `normaliseMountPath` down to unreserved URL\n * characters, so it needs no escaping here - there is nothing in it that can\n * close a script tag or a string literal.\n */\nexport function renderShell(\n mountPath: string,\n root: string = uiRoot(),\n theme?: AdminTheme,\n): Buffer | undefined {\n const shell = readIndexHtml(root)\n if (!shell) return undefined\n\n const injected =\n ` <script>window.__NEST_ADMIN_BASE__ = \"${mountPath}\"</script>\\n` +\n ` ${renderTheme(theme)}\\n </head>`\n\n let html = shell\n .toString('utf8')\n .split(`${UI_BASE_PLACEHOLDER}/`)\n .join(`${mountPath}/`)\n .replace('</head>', injected)\n\n // Replaced rather than appended: the shell already has a title, and adding a\n // second would leave two in the document for the browser to choose between.\n if (theme?.title !== undefined) {\n html = html.replace(/<title>[^<]*<\\/title>/, `<title>${theme.title}</title>`)\n }\n\n return Buffer.from(html, 'utf8')\n}\n","/**\n * Just enough colour arithmetic to keep a brand colour readable.\n *\n * ## Why the server does this at all\n *\n * An application sets one hex value. The interface has to put text on top of\n * it, and place it against both a light page and a dark one - three\n * relationships from one number, any of which can be unreadable.\n *\n * 0.7.0 found exactly that: the active navigation item was white on the accent,\n * which measured 2.52:1 in dark mode - on the one element whose job is to say\n * where you are. It was fixed by hand, for one pairing. Doing the arithmetic\n * here fixes the class: whatever colour arrives, the foreground chosen for it\n * is the one that contrasts, and a colour too dark to see on a dark page is\n * lightened until it can be.\n *\n * CSS cannot do this. `oklch(from …)` relative colours would come close, but\n * \"pick whichever of black or white contrasts better\" is a branch, and a\n * stylesheet has no branches. The server has the value and can simply look.\n *\n * ## The maths\n *\n * sRGB relative luminance and the contrast ratio, both from WCAG 2. Not a\n * perceptual colour space: the ratio is what the accessibility floor is\n * defined in, and matching the definition matters more here than matching\n * perception.\n */\n\n/** WCAG AA for normal text: anything read as words on the page. */\nconst READABLE = 4.5\n\n/**\n * WCAG AA for a shape rather than for words.\n *\n * A filled button is identified by the label on it, and that label carries its\n * own contrast - so the fill itself only has to be distinguishable from the\n * page, not readable against it.\n */\nconst VISIBLE = 3\n\n/** `#rgb` or `#rrggbb` to channel values in 0..1. Assumes it has been validated. */\nfunction channels(hex: string): [number, number, number] {\n const value =\n hex.length === 4\n ? hex\n .slice(1)\n .split('')\n .map((part) => part + part)\n .join('')\n : hex.slice(1)\n\n return [\n Number.parseInt(value.slice(0, 2), 16) / 255,\n Number.parseInt(value.slice(2, 4), 16) / 255,\n Number.parseInt(value.slice(4, 6), 16) / 255,\n ]\n}\n\nfunction toHex([r, g, b]: readonly [number, number, number]): string {\n const part = (value: number): string =>\n Math.round(Math.min(1, Math.max(0, value)) * 255)\n .toString(16)\n .padStart(2, '0')\n return `#${part(r)}${part(g)}${part(b)}`\n}\n\n/** WCAG relative luminance. */\nfunction luminance(rgb: readonly [number, number, number]): number {\n const [r, g, b] = rgb.map((channel) =>\n channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4,\n ) as [number, number, number]\n return 0.2126 * r + 0.7152 * g + 0.0722 * b\n}\n\nfunction contrast(a: readonly [number, number, number], b: readonly [number, number, number]) {\n const [lighter, darker] = [luminance(a), luminance(b)].sort((x, y) => y - x) as [number, number]\n return (lighter + 0.05) / (darker + 0.05)\n}\n\n/** The palette's own near-white and near-black, so the result matches the theme. */\nconst LIGHT_INK: readonly [number, number, number] = [0.985, 0.985, 0.99]\nconst DARK_INK: readonly [number, number, number] = [0.09, 0.1, 0.12]\n\n/** Backgrounds the brand colour has to be visible against. Kept in step with index.css. */\nconst LIGHT_PAGE: readonly [number, number, number] = [0.976, 0.98, 0.984]\nconst DARK_PAGE: readonly [number, number, number] = [0.09, 0.1, 0.12]\n\n/**\n * Text that can be read on this colour.\n *\n * Whichever of the palette's near-white and near-black contrasts better - not\n * whichever is prettier, and never a fixed choice. A mid-tone brand can fail\n * both, and then the better of two bad options is still the right answer and\n * the caller is told nothing, because there is nothing it could do about it.\n */\nexport function readableInk(hex: string): string {\n const brand = channels(hex)\n return contrast(brand, LIGHT_INK) >= contrast(brand, DARK_INK)\n ? toHex(LIGHT_INK)\n : toHex(DARK_INK)\n}\n\n/** Move a colour towards white (`1`) or black (`0`) by `amount`. */\nfunction mix(\n rgb: readonly [number, number, number],\n towards: 0 | 1,\n amount: number,\n): [number, number, number] {\n return rgb.map((channel) => channel + (towards - channel) * amount) as [number, number, number]\n}\n\n/**\n * The brand colour, adjusted until it can be read on this page.\n *\n * A navy that reads beautifully on white is nearly invisible on a near-black\n * page, and a bright yellow is invisible on white. Rather than refusing the\n * colour or shipping something unreadable, it is moved towards the page's\n * opposite in small steps until it clears the floor - keeping its hue, which\n * is what \"our brand colour\" means to whoever chose it.\n *\n * ## Two floors, because there are two jobs\n *\n * `role: 'fill'` is a button or a badge, where the label carries the contrast\n * and the fill only has to be a distinguishable shape - 3:1. `role: 'text'` is\n * a link on the page, which has to be read - 4.5:1.\n *\n * They were one token until an interface reviewer said the buttons looked\n * washed out in dark mode. They were right, and the measurement said so too:\n * holding a fill to the text floor forces it light enough that only near-black\n * text is readable on it, which is legible and looks faded. Separating them\n * lets a dark-mode button be a saturated fill with a light label, and lets link\n * text stay light enough to read.\n *\n * The cost is honest and worth stating: a very light or very dark brand comes\n * out shifted. That is preferable to shipping a colour the interface cannot\n * use, and the hue - the part people recognise - is what survives.\n *\n * Returns the colour unchanged when it already contrasts, which is the common\n * case and the reason most applications never see any of this.\n */\nexport function visibleOn(\n hex: string,\n page: 'light' | 'dark',\n role: 'fill' | 'text' = 'text',\n): string {\n const background = page === 'dark' ? DARK_PAGE : LIGHT_PAGE\n const towards = page === 'dark' ? 1 : 0\n\n const floor = role === 'fill' ? VISIBLE : READABLE\n\n let colour = channels(hex)\n // Sixteen steps of 5%: enough to travel the whole range, small enough that a\n // colour that barely fails is barely changed.\n for (let step = 0; step < 16; step++) {\n if (contrast(colour, background) >= floor) break\n colour = mix(colour, towards, 0.05)\n }\n\n return toHex(colour)\n}\n\n/** Exported for tests: does this pairing clear the floor for body text? */\nexport function isReadable(foreground: string, background: string): boolean {\n return contrast(channels(foreground), channels(background)) >= READABLE\n}\n\n/** Exported for tests. */\nexport function contrastRatio(a: string, b: string): number {\n return contrast(channels(a), channels(b))\n}\n","/**\n * Branding the served page applies without a rebuild.\n *\n * The alternative is asking every application that wants its own colour to\n * fork the interface and run a bundler, which is the thing most admin\n * libraries make people do and the reason they stop using them.\n *\n * ## These values are written into HTML\n *\n * They come from the application's configuration rather than from a request, so\n * this is not a cross-site scripting boundary in the usual sense - but a\n * template that interpolates unchecked strings into a page is a mistake waiting\n * for the first configuration read from a database or an environment variable.\n * Each value is validated to a shape that cannot carry markup, and rejected at\n * startup if it does not fit.\n */\nimport { readableInk, visibleOn } from './colour.js'\n\nexport interface AdminTheme {\n /**\n * Accent colour, as a CSS hex value - `#0b6e6e` or `#0b6`.\n *\n * Hex only. A full CSS colour grammar would mean parsing one, and a value\n * this small is not worth a parser; named colours and `rgb()` are excluded\n * for the same reason.\n */\n readonly brandColor?: string\n\n /** Page title and the name shown in the header. Plain text. */\n readonly title?: string\n\n /**\n * Logo shown beside the title.\n *\n * An `http(s)` URL or a `data:image/...` URI. Other schemes are refused:\n * `javascript:` in an image source is the obvious one, but the rule is a\n * whitelist rather than a blacklist so there is nothing to keep up with.\n */\n readonly logoUrl?: string\n\n /**\n * Which appearance to start from, before anyone chooses.\n *\n * `'system'` follows the viewer's operating system and is the default. A\n * viewer's own choice, once made, wins over this and is remembered by their\n * browser - so this sets the first impression rather than a policy.\n */\n readonly appearance?: 'system' | 'light' | 'dark'\n}\n\nconst HEX_COLOUR = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/\n\n/** Plain text with nothing that could open a tag, an entity or an attribute. */\nconst SAFE_TEXT = /^[^<>&\"'`\\\\]{1,64}$/\n\nconst SAFE_URL = /^(?:https?:\\/\\/[^\\s<>\"'`\\\\]+|data:image\\/[a-z+]+;base64,[A-Za-z0-9+/=]+)$/\n\nconst APPEARANCES: ReadonlySet<string> = new Set(['system', 'light', 'dark'])\n\n/** Every option this theme has. Anything else is a mistake, not a hint. */\nconst THEME_KEYS: ReadonlySet<string> = new Set(['brandColor', 'title', 'logoUrl', 'appearance'])\n\n/**\n * Reject a theme that cannot be rendered safely.\n *\n * At startup, so a bad value is a boot failure rather than a broken page - and\n * so the message names the option rather than appearing as mangled HTML.\n */\nexport function assertUsableTheme(theme: AdminTheme | undefined): void {\n if (!theme) return\n\n if (theme.brandColor !== undefined && !HEX_COLOUR.test(theme.brandColor)) {\n throw new Error(\n `AdminModule \\`theme.brandColor\\` must be a hex colour such as \"#0b6e6e\", ` +\n `received ${JSON.stringify(theme.brandColor)}.`,\n )\n }\n\n if (theme.title !== undefined && !SAFE_TEXT.test(theme.title)) {\n throw new Error(\n `AdminModule \\`theme.title\\` must be plain text of at most 64 characters, ` +\n `without < > & \" ' \\` or backslashes. It is written into the served page.`,\n )\n }\n\n if (theme.logoUrl !== undefined && !SAFE_URL.test(theme.logoUrl)) {\n throw new Error(\n `AdminModule \\`theme.logoUrl\\` must be an http(s) URL or a data:image URI, ` +\n `received ${JSON.stringify(theme.logoUrl)}.`,\n )\n }\n\n if (theme.appearance !== undefined && !APPEARANCES.has(theme.appearance)) {\n throw new Error(\n `AdminModule \\`theme.appearance\\` must be \"system\", \"light\" or \"dark\", ` +\n `received ${JSON.stringify(theme.appearance)}.`,\n )\n }\n\n /*\n * An unknown key is a setting that silently does nothing.\n *\n * Every other part of the configuration refuses an unrecognised name at\n * startup - `resources`, `models`, and the field overrides all do. Theming\n * did not, and the cost of that showed up in this repository's own reference\n * consumer: it configured `accent` where the option is called `brandColor`,\n * the page stayed grey, and nothing anywhere said why.\n */\n const unknown = Object.keys(theme).filter((key) => !THEME_KEYS.has(key))\n if (unknown.length > 0) {\n throw new Error(\n `AdminModule \\`theme\\` has no option${unknown.length === 1 ? '' : 's'} called ` +\n `${unknown.join(', ')}. Known options: ${[...THEME_KEYS].join(', ')}.`,\n )\n }\n}\n\n/**\n * The theme as markup to insert into the shell.\n *\n * A `<style>` block for the colour, so it reaches CSS without the interface\n * having to apply it, and a global for the parts the application reads. The\n * page title is not here: the shell already has one, and a second would leave\n * two in the document. `renderShell` replaces it instead.\n *\n * Everything here has been through `assertUsableTheme`.\n */\nexport function renderTheme(theme: AdminTheme | undefined): string {\n if (!theme || (!theme.brandColor && !theme.title && !theme.logoUrl && !theme.appearance)) {\n return ''\n }\n\n const style = theme.brandColor ? `<style>${brandRules(theme.brandColor)}</style>` : ''\n\n const globals = JSON.stringify({\n ...(theme.title !== undefined ? { title: theme.title } : {}),\n ...(theme.logoUrl !== undefined ? { logoUrl: theme.logoUrl } : {}),\n ...(theme.appearance !== undefined ? { appearance: theme.appearance } : {}),\n })\n\n return `${style}<script>window.__NEST_ADMIN_THEME__ = ${globals}</script>`\n}\n\n/**\n * The brand colour, as rules for both palettes.\n *\n * ## Why it sets `--primary` rather than `--accent`\n *\n * They are different roles in the token system: `--primary` is the colour of a\n * button, and `--accent` is the pale surface a row takes on hover. Writing a\n * brand colour into the second turns every hover into a solid block of it.\n *\n * ## Why one hex becomes six values\n *\n * A single colour cannot answer the questions the interface asks of it: what\n * text can be read on top of it, whether it can be seen against a light page\n * and against a dark one, and - separately - whether it can be *read* as link\n * text on each. A fill and a piece of text have different floors, so they get\n * different values. `colour.ts` answers all of it, and says there why the\n * server does this rather than the stylesheet.\n *\n * The dark rule is scoped to `.dark`, the class the stylesheet already keys\n * off. Specificity is on its side - a class beats `:root` - so the dark\n * variant wins where it applies without either rule needing `!important`.\n */\nfunction brandRules(brand: string): string {\n const rules = (page: 'light' | 'dark'): string => {\n const fill = visibleOn(brand, page, 'fill')\n const text = visibleOn(brand, page, 'text')\n return `--primary:${fill};--primary-foreground:${readableInk(fill)};--link:${text}`\n }\n\n return `:root{${rules('light')}}.dark{${rules('dark')}}`\n}\n","/**\n * Serves the built admin UI.\n *\n * ## Why a separate controller\n *\n * The API and the UI share the `/admin` prefix, and `@Get(':model')` would\n * happily match `assets`. Registering these routes on their own controller,\n * listed *before* `AdminController`, makes the precedence explicit and\n * testable rather than an accident of declaration order inside one class.\n *\n * It also puts the security boundary somewhere a reader can see it: this\n * controller has **no** `AdminAuthGuard`, and `AdminController` has one.\n *\n * ## Why the UI shell is not behind authentication\n *\n * These routes return a static HTML shell and a JavaScript bundle. They contain\n * no records, no schema, and no configuration - the bundle is byte-identical\n * for every visitor and learns what exists only by calling `/admin/meta`, which\n * *is* guarded. Serving them publicly is the ordinary SPA arrangement.\n *\n * Guarding them instead would render a JSON 401 in the browser rather than a\n * page that can explain itself, and would stop a host from putting its own\n * login redirect in front of the admin. The data boundary is unchanged: an\n * unauthenticated visitor loads the shell, its first request is refused, and\n * the UI shows a signed-out state.\n *\n * ## Why there is no SPA fallback route\n *\n * The UI uses hash routing (`/admin#/User/u1`), so every deep link is still a\n * request for `/admin`. A catch-all fallback would have to match `/admin/*`,\n * which is precisely the space the API occupies - it would shadow every model\n * route. Hash routing is what keeps this to two static routes.\n */\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n StreamableFile,\n} from '@nestjs/common'\n\nimport { ADMIN_MOUNT_PATH, ADMIN_THEME, ADMIN_UI_ROOT } from '../tokens.js'\nimport { contentTypeFor, readAsset, renderShell } from './assets.js'\nimport type { AdminTheme } from './theme.js'\n\n// No path here. The module registers this controller under the application's\n// configured mount path through `RouterModule`, so a path on the decorator\n// would nest it a second time.\n@Controller()\nexport class AdminUiController {\n /**\n * The rendered shell, built on first use.\n *\n * It depends only on the bundled file and the mount path, and neither changes\n * while the application runs. Held on the instance rather than in a\n * module-level cache: the package ships two bundles that each inline their\n * own copy of a module, so module-level state is not shared between them.\n */\n private shell?: Buffer | undefined\n\n constructor(\n @Inject(ADMIN_UI_ROOT) private readonly root: string,\n @Inject(ADMIN_MOUNT_PATH) private readonly mountPath: string,\n @Inject(ADMIN_THEME) private readonly theme: AdminTheme | undefined,\n ) {}\n\n /**\n * `GET /admin` - the SPA shell.\n *\n * `no-cache` rather than a long max-age: the HTML names hashed asset files,\n * so a cached shell would keep pointing at a previous deployment's bundle.\n */\n @Get()\n @Header('Cache-Control', 'no-cache')\n index(): StreamableFile {\n this.shell ??= renderShell(this.mountPath, this.root, this.theme)\n const html = this.shell\n if (!html) {\n throw new NotFoundException(\n 'The admin UI was not bundled with this package. ' +\n 'This build is missing dist/admin-ui - see docs/publishing.md.',\n )\n }\n return new StreamableFile(html, { type: 'text/html; charset=utf-8' })\n }\n\n /**\n * `GET /admin/assets/:file`\n *\n * A single path segment, so a nested path cannot be requested at all; the\n * reader in `assets.ts` re-checks the name and the resolved location anyway.\n *\n * Vite emits content-hashed filenames, so these are immutable and safe to\n * cache for a long time.\n */\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const contents = readAsset(file, this.root)\n if (!contents) throw new NotFoundException(`No admin UI asset named \"${file}\".`)\n\n return new StreamableFile(contents, { type: contentTypeFor(file) })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;ACKA,IAAMA,mBAAmB,6BACvB,OAAOC,aAAa,cAChB,IAAIC,IAAI,QAAQC,UAAAA,EAAY,EAAEC,OAC7BH,SAASI,iBAAiBJ,SAASI,cAAcC,QAAQC,YAAW,MAAO,WAC1EN,SAASI,cAAcG,MACvB,IAAIN,IAAI,WAAWD,SAASQ,OAAO,EAAEL,MALpB;AAOlB,IAAMM,gBAAgCV,iCAAAA;;;ACQ7C,IAAAW,kBAQO;AACP,IAAAC,gBAA6B;;;ACoDtB,SAAS,UAAU,SAA4C;AACpE,SAAO;IACL,IAAI,QAAQ;IACZ,OAAO,QAAQ;IACf,GAAI,QAAQ,SAAS,SAAY;MAAE,MAAM,QAAQ;IAAA,IAAS,CAAA;EAAC;AAE/D;AANgB;AC9ChB,IAAM,UAAU;EAAC;EAAa;EAAc;EAAW;EAAa;EAAc;;AASlF,IAAM,cAAc;EAAC;EAAW;EAAY;EAAW;EAAY;EAAW;;AAE9E,SAAS,OAAO,OAA+B;AAC7C,SAAO,MAAM,SAAS,cAAc,CAAC,MAAM,UAAU,CAAC,MAAM;AAC9D;AAFS;AAIT,SAAS,SAAS,MAAuB;AACvC,QAAM,QAAQ,KAAK,YAAA;AACnB,SAAO,YAAY,KAAK,CAAC,SAAS,MAAM,WAAW,IAAI,CAAC;AAC1D;AAHS;AAoBF,SAAS,gBAAgB,OAA0C;AACxE,QAAM,QAAQ,MAAM,OAAO,OAAO,MAAM;AAExC,aAAW,gBAAgB,SAAS;AAClC,UAAM,QAAQ,MAAM,KAAK,CAAC,UAAU,MAAM,KAAK,YAAA,MAAkB,aAAa,YAAA,CAAa;AAC3F,QAAI,MAAA,QAAc,MAAM;EAC1B;AAEA,QAAM,YAAY,MAAM,OAAO,CAAC,UAAU,MAAM,eAAe,CAAC,SAAS,MAAM,IAAI,CAAC;AACpF,SAAO,UAAU,WAAW,IAAI,UAAU,CAAC,GAAG,OAAO;AACvD;AAVgB;AC9ChB,IAAM,eAAe;EAAC;EAAQ;EAAS;EAAS;EAAe;EAAY;EAAS;;AAGpF,SAAS,WAAW,OAA+B;AACjD,SACE,MAAM,SAAS,YACf,CAAC,MAAM,UACP,CAAC,MAAM;EAEP,CAAC,MAAM;AAEX;AARS;AAyBF,SAAS,gBAAgB,OAA8B;AAG5D,MAAI,MAAM,iBAAiB,OAAW,QAAO,MAAM;AAEnD,QAAMC,YAAW,MAAM,OAAO,OAAO,UAAU;AAE/C,aAAW,aAAa,cAAc;AACpC,UAAM,QAAQA,UAAS,KAAK,CAAC,UAAU,MAAM,SAAS,SAAS;AAC/D,QAAI,MAAA,QAAc,MAAM;EAC1B;AAEA,QAAM,SAASA,UAAS,KAAK,CAAC,UAAU,MAAM,YAAY,CAAC,MAAM,IAAI;AACrE,MAAI,OAAA,QAAe,OAAO;AAE1B,QAAM,QAAQA,UAAS,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI;AAClD,MAAI,MAAA,QAAc,MAAM;AAExB,SAAO,MAAM,WAAW,CAAC,KAAK,MAAM,OAAO,CAAC,GAAG,QAAQ;AACzD;AAnBgB;ACpBT,SAAS,qBACd,OACA,QAC2B;AAC3B,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,UAAU,KAAM,QAAO;AAE5B,QAAM,SAAS,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,SAAS,WAAW;AACzE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SAAO,OAAO,OAAO,KACnB,CAAC,cAAc,UAAU,UAAU,SAAS,SAAS,QAAQ,cAAc,KAAA;AAE/E;AAbgB;AAwBT,SAAS,cACd,OACA,QAC2B;AAC3B,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,SAAS,gBAAgB,MAAO,QAAO;AAE3C,QAAM,UAAU,qBAAqB,OAAO,MAAM;AAClD,SAAO,SAAS,UAAU,gBAAgB,SAAS,iBAAiB;AACtE;AAVgB;AAoBT,SAAS,oBACd,OACA,QACoB;AACpB,MAAI,cAAc,OAAO,MAAM,MAAM,cAAe,QAAO;AAE3D,QAAM,UAAU,qBAAqB,OAAO,MAAM;AAClD,MAAI,CAAC,SAAS,WAAY,QAAO;AAEjC,QAAM,SAAS,MAAM,UAAU,eAAe;AAC9C,SACE,GAAG,MAAM,IAAI,QAAQ,IAAI,sBAAsB,MAAM;AAIzD;AAfgB;AClCT,SAAS,aACd,QACA,WACc;AACd,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,WAAW,UAAU,UAAU,IAAI,IAAI,UAAU,OAAO,IAAI;AAClE,QAAMC,YAAW,IAAI,IAAI,UAAU,WAAW,CAAA,CAAE;AAEhD,SAAO,OAAO,OACZ,CAAC,WAAW,aAAa,UAAa,SAAS,IAAI,MAAM,IAAA,MAAU,CAACA,UAAS,IAAI,MAAM,IAAI,CAAA;AAE/F;AAZgB;AAsBT,SAAS,sBACd,QACA,WACmB;AACnB,MAAI,CAAC,UAAW,QAAO,CAAA;AAEvB,QAAM,QAAQ,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AACvD,QAAM,aAAa;OAAK,UAAU,WAAW,CAAA;OAAS,UAAU,WAAW,CAAA;;AAE3E,SAAO;OAAI,IAAI,IAAI,WAAW,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;;AAClE;AAVgB;ACsFT,SAAS,cACd,WACA,OACA,OAC2B;AAC3B,SAAO,YAAY,KAAK,GAAG,SAAS,KAAK;AAC3C;AANgB;AAST,SAAS,WACd,WACA,OACA,OACS;AAGT,SAAO,MAAM,eAAe,cAAc,WAAW,OAAO,MAAM,IAAI,GAAG,aAAa;AACxF;AARgB;AAsBT,SAAS,eACd,QACA,WAC0B;AAC1B,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,OAAO,IAAI,CAAC,UAAA;AACjB,UAAM,WAAW,UAAU,MAAM,IAAI;AACrC,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,SAAS,IAAI,IACjB,OAAO,QAAQ,SAAS,UAAU,CAAA,CAAE,EACjC,OAAO,CAAC,CAAA,EAAG,KAAK,MAAM,MAAM,WAAW,IAAI,EAC3C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,CAAA;AAGzB,UAAM,YAAY,IAAI,IACpB,OAAO,QAAQ,SAAS,UAAU,CAAA,CAAE,EACjC,OAAO,CAAC,CAAA,EAAG,KAAK,MAAM,MAAM,cAAc,IAAI,EAC9C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,CAAA;AAGzB,UAAM,OAAO,OAAO,SAAS,IAAI,MAAM,SAAS,MAAM,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,IAAI,CAAC;AAE9F,WAAO;MACL,GAAG;MACH,GAAI,SAAS,iBAAiB,SAAY;QAAE,cAAc,SAAS;MAAA,IAAiB,CAAA;;;;MAIpF,QACE,UAAU,SAAS,IACf,OACA,KAAK,IAAI,CAAC,UACR,UAAU,IAAI,MAAM,IAAI,IAAI;QAAE,GAAG;QAAO,WAAW;MAAA,IAAS,KAAA;IAC9D;EAEV,CAAC;AACH;AAtCgB;AAgDT,SAAS,qBACd,QACA,WACmB;AACnB,MAAI,CAAC,UAAW,QAAO,CAAA;AAEvB,QAAM,UAAoB,CAAA;AAE1B,aAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC7D,UAAM,QAAQ,OAAO,KAAK,CAAC,cAAc,UAAU,SAAS,SAAS;AACrE,QAAI,CAAC,OAAO;AACV,cAAQ,KAAK,SAAS;AACtB;IACF;AAEA,UAAM,QAAQ,IAAI,IAAI,MAAM,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AAE7D,QAAI,SAAS,iBAAiB,UAAa,CAAC,MAAM,IAAI,SAAS,YAAY,GAAG;AAC5E,cAAQ,KAAK,GAAG,SAAS,IAAI,SAAS,YAAY,EAAE;IACtD;AAEA,eAAW,aAAa,OAAO,KAAK,SAAS,UAAU,CAAA,CAAE,GAAG;AAC1D,UAAI,CAAC,MAAM,IAAI,SAAS,EAAG,SAAQ,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;IACrE;EACF;AAEA,SAAO;AACT;AA3BgB;AAwCT,SAAS,uBACd,QACA,WACmB;AACnB,MAAI,CAAC,UAAW,QAAO,CAAA;AAEvB,QAAM,UAAoB,CAAA;AAE1B,aAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC7D,UAAM,QAAQ,OAAO,KAAK,CAAC,cAAc,UAAU,SAAS,SAAS;AACrE,QAAI,CAAC,MAAO;AAEZ,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,SAAS,UAAU,CAAA,CAAE,GAAG;AACtE,UAAI,MAAM,WAAW,KAAM;AAE3B,YAAM,WAAW,MAAM,OAAO,KAAK,CAAC,cAAc,UAAU,SAAS,SAAS;AAC9E,UAAI,CAAC,SAAU;AAEf,UAAI,SAAS,cAAc,CAAC,SAAS,eAAe,SAAS,iBAAiB,QAAW;AACvF,gBAAQ,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;MAC1C;IACF;EACF;AAEA,SAAO;AACT;AAzBgB;ACxOhB,IAAM,QAAQ,uBAAO,IAAI,kBAAkB;AA4BpC,IAAM,iBAAN,cAA6B,MAAM;SAAA;;;;;;;;;;EAQ/B,OAAuB;EAEhC,YAAY,SAAiB,SAA+B;AAC1D,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO,WAAW;AAEvB,WAAO,eAAe,MAAM,OAAO;MAAE,OAAO;MAAM,YAAY;IAAA,CAAO;EACvE;AACF;AAOO,SAAS,iBAAiB,OAAyC;AACxE,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAkC,KAAK,MAAM;AAElD;AANgB;AAST,IAAM,qBAAN,cAAiC,eAAe;SAAA;;;EAGrD,YACW,OACA,kBAAqC,CAAA,GAC9C;AACA,UAAM,QAAQ,gBAAgB,SAAS,IAAI,kBAAkB,gBAAgB,KAAK,IAAI,CAAC,MAAM;AAC7F,UAAM,kBAAkB,KAAK,KAAK,KAAK,EAAE;AAJhC,SAAA,QAAA;AACA,SAAA,kBAAA;EAIX;EALW;EACA;EAJO,OAAO;AAS3B;AAGO,IAAM,qBAAN,cAAiC,eAAe;SAAA;;;EAGrD,YACW,OACA,OACT,QACA;AACA,UAAM,kBAAkB,KAAK,eAAe,KAAK,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,EAAE;AAJzE,SAAA,QAAA;AACA,SAAA,QAAA;EAIX;EALW;EACA;EAJO,OAAO;AAS3B;AAGO,IAAM,sBAAN,cAAkC,eAAe;SAAA;;;EAGtD,YACW,OACA,IACT;AACA,UAAM,MAAM,KAAK,wBAAwB,KAAK,UAAU,EAAE,CAAC,GAAG;AAHrD,SAAA,QAAA;AACA,SAAA,KAAA;EAGX;EAJW;EACA;EAJO,OAAO;AAQ3B;AAMO,IAAM,oBAAN,cAAgC,eAAe;SAAA;;;EAClC,OAAO;AAC3B;AAmBO,IAAM,kBAAN,cAA8B,eAAe;SAAA;;;EAGlD,YACE,SAES,SAA4B,CAAA,GACrC,SACA;AACA,UAAM,SAAS,OAAO;AAHb,SAAA,SAAA;EAIX;EAJW;EALO,OAAO;AAU3B;AAwBO,IAAM,kBAAN,cAA8B,eAAe;SAAA;;;EAGlD,YACW,YACA,OAEA,SAA4B,CAAA,GACrC;AACA,UAAM,mBAAmB,YAAY,OAAO,MAAM,CAAC;AAL1C,SAAA,aAAA;AACA,SAAA,QAAA;AAEA,SAAA,SAAA;EAGX;EANW;EACA;EAEA;EANO,OAAO;AAU3B;AAQA,SAAS,mBACP,YACA,OACA,QACQ;AACR,QAAM,QAAQ,OAAO,SAAS,IAAI,OAAO,KAAK,IAAI,IAAI;AAEtD,UAAQ,YAAA;IACN,KAAK;AACH,aAAO,QACH,WAAW,KAAK,qBAAqB,KAAK,MAC1C,WAAW,KAAK;IAEtB,KAAK;AACH,aAAO,QACH,OAAO,KAAK,uFACZ,uBAAuB,KAAK;IAElC,KAAK;AACH,aAAO,QAAQ,GAAG,KAAK,kBAAkB,4BAA4B,KAAK;EAAA;AAEhF;AArBS;AA2BF,IAAM,eAAN,cAA2B,eAAe;SAAA;;;EAC7B,OAAO;EAEzB,YAAY,SAAiB,SAA+B;AAC1D,UAAM,SAAS,OAAO;EACxB;AACF;AAcO,IAAM,oBAAN,cAAgC,eAAe;SAAA;;;EAClC,OAAO;EAEzB,YAAY,UAAU,uDAAuD;AAC3E,UAAM,OAAO;EACf;AACF;AASO,IAAM,iBAAN,cAA6B,eAAe;SAAA;;;EAC/B,OAAO;EAEzB,YAAY,UAAU,uDAAuD;AAC3E,UAAM,OAAO;EACf;AACF;;;AC/PA,IAAAC,iBAWO;;;ACxBP,oBAA4E;;;ACLrE,IAAMC,gBAAgBC,uBAAO,oBAAA;AAG7B,IAAMC,aAAaD,uBAAO,iBAAA;AAG1B,IAAME,sBAAsBF,uBAAO,0BAAA;AASnC,IAAMG,gBAAgBH,uBAAO,oBAAA;AAQ7B,IAAMI,mBAAmBJ,uBAAO,uBAAA;AAQhC,IAAMK,kBAAkBL,uBAAO,sBAAA;AAQ/B,IAAMM,gBAAgBN,uBAAO,oBAAA;AAQ7B,IAAMO,eAAeP,uBAAO,mBAAA;AAG5B,IAAMQ,cAAcR,uBAAO,kBAAA;AAG3B,IAAMS,gBAAgBT,uBAAO,oBAAA;AAQ7B,IAAMU,kBAAkBV,uBAAO,sBAAA;AAG/B,IAAMW,cAAcX,uBAAO,kBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ADrD3B,IAAMY,iBAAN,MAAMA;SAAAA;;;;EACX,YAAiDC,MAAiB;SAAjBA,OAAAA;EAAkB;EAEnE,MAAMC,YAAYC,SAA6C;AAM7D,UAAMC,WAAW,MAAM,KAAKH,KAAKI,UAAUF,OAAAA;AAI3C,QAAIC,aAAa,OAAO;AACtB,YAAM,IAAIE,eAAAA;IACZ;AAEA,WAAO;EACT;AACF;;;;;;;;;;;AEpBA,IAAAC,iBAA4D;AAErD,IAAMC,mBAAeC,qCAC1B,CAACC,OAAgBC,YAAgDA,OAAAA;;;ACDnE,IAAAC,iBAOO;;;AC2BA,SAASC,QAAWC,MAAO;AAChC,SAAO;IAAED,SAAS;IAAMC;EAAK;AAC/B;AAFgBD;AAST,SAASE,YAAeD,MAAoBE,MAAc;AAC/D,SAAO;IAAEH,SAAS;IAAMC;IAAME;EAAK;AACrC;AAFgBD;AAIT,SAASE,QACdC,MACAC,SACAC,SAA2C;AAE3C,SAAO;IAAEP,SAAS;IAAOQ,OAAO;MAAEH;MAAMC;MAAS,GAAIC,UAAU;QAAEA;MAAQ,IAAI,CAAC;IAAG;EAAE;AACrF;AANgBH;;;;;;;;;;;;;;;;;ADzBhB,IAAMK,WAAwB;EAC5BC,QAAQC,0BAAWC;EACnBC,MAAM;EACNC,SAAS;AACX;AAiBA,SAASC,SAASC,OAAc;AAC9B,MAAI,CAACC,iBAAiBD,KAAAA,EAAQ,QAAOP;AAErC,UAAQO,MAAME,MAAI;;;IAGhB,KAAK;AACH,aAAO;QACLR,QAAQC,0BAAWQ;QACnBN,MAAM;QACNC,SAASE,MAAMF;MACjB;IAEF,KAAK;AACH,aAAO;QACLJ,QAAQC,0BAAWS;QACnBP,MAAM;QACNC,SAASE,MAAMF;MACjB;IAEF,KAAK;AACH,aAAO;QACLJ,QAAQC,0BAAWU;QACnBR,MAAM;QACNC,SAASE,MAAMF;QACfQ,SAAS;UAAEC,OAAQP,MAA6BO;QAAM;MACxD;IAEF,KAAK;AACH,aAAO;QACLb,QAAQC,0BAAWU;QACnBR,MAAM;QACNC,SAASE,MAAMF;QACfQ,SAAS;UACPC,OAAQP,MAA8BO;UACtCC,IAAKR,MAA8BQ;QACrC;MACF;IAEF,KAAK;AACH,aAAO;QACLd,QAAQC,0BAAWc;QACnBZ,MAAM;QACNC,SAASE,MAAMF;QACfQ,SAAS;UACPC,OAAQP,MAA6BO;UACrCG,OAAQV,MAA6BU;QACvC;MACF;;;IAIF,KAAK,cAAc;AACjB,YAAMC,UAAUX;AAChB,aAAO;QACLN,QAAQC,0BAAWc;QACnBZ,MAAM;QACNC,SAASE,MAAMF;;;;QAIf,GAAIa,QAAQC,OAAOC,SAAS,IAAI;UAAEP,SAAS;YAAEM,QAAQD,QAAQC;UAAO;QAAE,IAAI,CAAC;MAC7E;IACF;;;;IAKA,KAAK,cAAc;AACjB,YAAME,WAAUd;AAChB,aAAO;;;QAGLN,QAAQoB,SAAQC,eAAe,aAAapB,0BAAWc,cAAcd,0BAAWqB;QAChFnB,MAAM;QACNC,SAASE,MAAMF;QACfQ,SAAS;UAAES,YAAYD,SAAQC;UAAYH,QAAQE,SAAQF;QAAO;MACpE;IACF;IAEA,KAAK;AACH,aAAO;QACLlB,QAAQC,0BAAWc;QACnBZ,MAAM;QACNC,SAASE,MAAMF;MACjB;;;IAIF;AACE,aAAOL;EACX;AACF;AA5FSM;AAsGF,SAASkB,cAAcjB,OAAc;AAC1C,SAAOD,SAASC,KAAAA,EAAOF;AACzB;AAFgBmB;AAKT,IAAMC,uBAAN,MAAMA;SAAAA;;;EACMC,SAAS,IAAIC,sBAAO,WAAA;EAErCC,MAAMC,WAAoBC,MAA2B;AAGnD,QAAID,qBAAqBE,8BAAe;AACtC,YAAMF;IACR;AAEA,UAAMG,SAAS1B,SAASuB,SAAAA;AAExB,QAAIG,OAAO/B,UAAUC,0BAAWC,uBAAuB;AAErD,WAAKuB,OAAOnB,MACVC,iBAAiBqB,SAAAA,KAAcA,UAAUpB,SAAS,YAC9C,oBAAoBoB,UAAUxB,OAAO,KACrC,mDACJwB,qBAAqBI,QAAQJ,UAAUK,QAAQC,OAAON,SAAAA,CAAAA;IAE1D;AAEA,UAAMO,OAAsBf,QAAQW,OAAO5B,MAAM4B,OAAO3B,SAAS2B,OAAOnB,OAAO;AAC/E,UAAMwB,WAAWP,KAAKQ,aAAY,EAAGC,YAAW;AAGhDF,aAASpC,OAAO+B,OAAO/B,MAAM,EAAEuC,KAAKJ,IAAAA;EACtC;AACF;;;;;;AElKA,IAAAK,iBAMO;;;ACZP,IAAAC,iBAA8C;;;ACN9C,yBAAqD;AACrD,uBAA0B;AAE1B,IAAMC,aAASC,4BAAUC,yBAAAA;AAgBzB,IAAMC,SAAS;EAAEC,GAAG,KAAK;EAAIC,GAAG;EAAGC,GAAG;AAAE;AACxC,IAAMC,SAAS,MAAMJ,OAAOC,IAAID,OAAOE,IAAI;AAC3C,IAAMG,aAAa;AACnB,IAAMC,aAAa;AAGnB,IAAMC,MAAMC,OAAOC,aAAa,EAAA;AAShC,eAAsBC,kBAAkBC,UAAgB;AACtD,MAAI,OAAOA,aAAa,YAAYA,SAASC,WAAW,GAAG;AACzD,UAAM,IAAIC,MAAM,yBAAA;EAClB;AAEA,QAAMC,WAAOC,gCAAYT,UAAAA,EAAYU,SAAS,KAAA;AAC9C,QAAMC,MAAM,MAAMpB,OAAOc,UAAUG,MAAMT,YAAY;IAAE,GAAGL;IAAQkB,QAAQd;EAAO,CAAA;AAEjF,SAAO;IAAC;IAAUJ,OAAOC;IAAGD,OAAOE;IAAGF,OAAOG;IAAGW;IAAMG,IAAID,SAAS,KAAA;IAAQG,KAAKZ,GAAAA;AAClF;AATsBG;AAuBtB,eAAsBU,oBAAoBT,UAAkBU,QAAc;AACxE,QAAMC,SAASC,MAAMF,MAAAA;AACrB,MAAI,CAACC,OAAQ,QAAO;AAEpB,MAAI;AACF,UAAML,MAAM,MAAMpB,OAAOc,UAAUW,OAAOR,MAAMQ,OAAOE,KAAKZ,SAAS,GAAG;MACtEX,GAAGqB,OAAOrB;MACVC,GAAGoB,OAAOpB;MACVC,GAAGmB,OAAOnB;MACVe,QAAQ,MAAMI,OAAOrB,IAAIqB,OAAOpB,IAAI;IACtC,CAAA;AACA,UAAMuB,WAAWC,OAAOC,KAAKL,OAAOE,MAAM,KAAA;AAC1C,WAAOP,IAAIL,WAAWa,SAASb,cAAUgB,oCAAgBX,KAAKQ,QAAAA;EAChE,QAAQ;AAIN,WAAO;EACT;AACF;AAnBsBL;AA6BtB,SAASG,MAAMF,QAAe;AAC5B,MAAI,OAAOA,WAAW,SAAU,QAAOQ;AAEvC,QAAM,CAACC,QAAQC,GAAG7B,GAAGC,GAAGW,MAAMU,IAAAA,IAAQH,OAAOW,MAAMzB,GAAAA;AACnD,MAAIuB,WAAW,YAAYhB,SAASe,UAAaL,SAASK,OAAW,QAAOA;AAE5E,QAAM5B,IAAIgC,OAAOF,CAAAA;AACjB,QAAMG,SAASD,OAAO/B,CAAAA;AACtB,QAAMiC,WAAWF,OAAO9B,CAAAA;AAKxB,QAAMiC,SACJH,OAAOI,UAAUpC,CAAAA,KACjBA,KAAK,KAAK,MACVA,KAAK,KAAK,OACTA,IAAKA,IAAI,OAAQ,KAClBgC,OAAOI,UAAUH,MAAAA,KACjBA,SAAS,KACTA,UAAU,MACVD,OAAOI,UAAUF,QAAAA,KACjBA,WAAW,KACXA,YAAY,MACZ,eAAeG,KAAKxB,IAAAA,KACpB,eAAewB,KAAKd,IAAAA,KACpBA,KAAKZ,SAAS,MAAM;AAEtB,SAAOwB,SAAS;IAAEnC;IAAGC,GAAGgC;IAAQ/B,GAAGgC;IAAUrB;IAAMU;EAAK,IAAIK;AAC9D;AA7BSN;AA0CF,IAAMgB,kBAAmC7B,sBAAkBK,gCAAY,EAAA,EAAIC,SAAS,KAAA,CAAA;;;AC7H3F,IAAAwB,sBAAyD;AAGzD,IAAMC,UAAU;AAShB,IAAMC,SAAS,wBAACC,UAAmCC,OAAOC,KAAKF,KAAAA,EAAgBG,SAAS,WAAA,GAAzE;AASR,SAASC,YAAYC,WAAmBC,QAAgBC,UAAgB;AAC7E,QAAMC,UAAmB;IACvBC,KAAKJ;IACLK,KAAKC,KAAKC,MAAMC,KAAKC,IAAG,IAAK,GAAA,IAAQP;EACvC;AAEA,QAAMQ,OAAO,GAAGjB,OAAAA,IAAWC,OAAOiB,KAAKC,UAAUT,OAAAA,CAAAA,CAAAA;AACjD,SAAO,GAAGO,IAAAA,IAAQG,KAAKH,MAAMT,MAAAA,CAAAA;AAC/B;AARgBF;AAkBT,SAASe,YAAYC,OAAgBd,QAAc;AACxD,MAAI,OAAOc,UAAU,SAAU,QAAOC;AAEtC,QAAMC,MAAMF,MAAMG,YAAY,GAAA;AAC9B,MAAID,MAAM,EAAG,QAAOD;AAEpB,QAAMN,OAAOK,MAAMI,MAAM,GAAGF,GAAAA;AAC5B,QAAMG,YAAYL,MAAMI,MAAMF,MAAM,CAAA;AAEpC,MAAI,CAACP,KAAKW,WAAW,GAAG5B,OAAAA,GAAU,EAAG,QAAOuB;AAC5C,MAAI,CAACM,QAAQF,WAAWP,KAAKH,MAAMT,MAAAA,CAAAA,EAAU,QAAOe;AAEpD,MAAI;AACF,UAAMb,UAAUQ,KAAKY,MACnB3B,OAAOC,KAAKa,KAAKS,MAAM1B,QAAQ+B,SAAS,CAAA,GAAI,WAAA,EAAa1B,SAAS,MAAA,CAAA;AAGpE,QAAI,OAAOK,QAAQC,QAAQ,YAAYD,QAAQC,QAAQ,GAAI,QAAOY;AAClE,QAAI,OAAOb,QAAQE,QAAQ,YAAYF,QAAQE,MAAM,OAAQG,KAAKC,IAAG,EAAI,QAAOO;AAEhF,WAAOb,QAAQC;EACjB,QAAQ;AAIN,WAAOY;EACT;AACF;AA3BgBF;AAoCT,SAASW,YAAYV,OAAed,QAAgBC,UAAgB;AACzE,QAAMe,MAAMF,MAAMG,YAAY,GAAA;AAC9B,MAAID,MAAM,KAAK,CAACK,QAAQP,MAAMI,MAAMF,MAAM,CAAA,GAAIJ,KAAKE,MAAMI,MAAM,GAAGF,GAAAA,GAAMhB,MAAAA,CAAAA,EAAU,QAAO;AAEzF,MAAI;AACF,UAAME,UAAUQ,KAAKY,MACnB3B,OAAOC,KAAKkB,MAAMI,MAAM1B,QAAQ+B,SAAS,GAAGP,GAAAA,GAAM,WAAA,EAAanB,SAAS,MAAA,CAAA;AAE1E,UAAM4B,YAAYvB,QAAQE,MAAMC,KAAKC,MAAMC,KAAKC,IAAG,IAAK,GAAA;AACxD,WAAOiB,YAAYxB,WAAW;EAChC,QAAQ;AACN,WAAO;EACT;AACF;AAbgBuB;AAehB,SAASZ,KAAKH,MAAcT,QAAc;AACxC,aAAO0B,gCAAW,UAAU1B,MAAAA,EAAQ2B,OAAOlB,IAAAA,EAAMmB,OAAO,WAAA;AAC1D;AAFShB;AAWT,SAASS,QAAQF,WAAmBU,UAAgB;AAClD,QAAMC,IAAInC,OAAOC,KAAKuB,SAAAA;AACtB,QAAMY,IAAIpC,OAAOC,KAAKiC,QAAAA;AACtB,SAAOC,EAAEP,WAAWQ,EAAER,cAAUS,qCAAgBF,GAAGC,CAAAA;AACrD;AAJSV;AAaF,IAAMY,oBAAoB;AAG1B,SAASC,wBAAAA;AACd,aAAOC,iCAAY,EAAA,EAAItC,SAAS,WAAA;AAClC;AAFgBqC;;;AF5GhB,IAAME,SAAS,IAAIC,sBAAO,WAAA;AAG1B,IAAMC,kBAAkB,KAAK,KAAK;AAkElC,IAAMC,UAAUC,uBAAOC,IAAI,0BAAA;AAGpB,SAASC,iBAAiBC,MAAa;AAC5C,SAAO,OAAOA,SAAS,YAAYA,SAAS,OACtCA,KAAwDJ,OAAAA,KAAYK,SACtEA;AACN;AAJgBF;AAaT,SAASG,eAAeC,SAAyB;AACtD,QAAMC,UAAUD,QAAQE,aAAY,EAAGC,WAAU;AACjD,SAAOF,SAASG;AAClB;AAHgBL;AAKT,SAASM,YAAYC,SAA2B;AACrD,QAAMC,SAASD,QAAQE,SAASD;AAChC,MAAI,OAAOA,WAAW,YAAYA,OAAOE,SAASC,mBAAmB;AACnE,UAAM,IAAIC,MACR,yDAAyDD,iBAAAA,0JAEvD;EAEN;AAEA,MAAI,CAACJ,QAAQM,SAAS,OAAON,QAAQM,MAAMC,gBAAgB,YAAY;AACrE,UAAM,IAAIF,MACR,gJACE;EAEN;AAEA,QAAMG,SAASR,QAAQE,QAAQM,UAAUtB;AACzC,QAAMuB,aAAaT,QAAQE,QAAQO,cAAc;AACjD,QAAMC,WAAW,IAAIC,SAASX,QAAQY,eAAe,IAAIZ,QAAQa,kBAAkB,KAAK,EAAA;AAExF,QAAMC,UAA8B;IAClCR,OAAON,QAAQM;IACfL;IACAO;IACAC;IACAM,QAAQf,QAAQE,QAAQa;IAExB,MAAMC,OAAOC,OAAOC,UAAUC,MAAI;AAChC,UAAI,OAAOF,UAAU,YAAY,OAAOC,aAAa,SAAU,QAAO1B;AACtE,UAAIkB,SAASU,UAAUD,IAAAA,EAAO,QAAO3B;AAErC,YAAM6B,UAAU,MAAMrB,QAAQM,MAAMC,YAAYU,MAAMK,KAAI,EAAGC,YAAW,CAAA;AAUxE,YAAMC,SAASH,SAASI,gBAAiB,MAAMC;AAC/C,YAAMC,UAAU,MAAMC,oBAAoBV,UAAUM,MAAAA;AAEpD,UAAI,CAACG,WAAW,CAACN,WAAWA,QAAQQ,aAAa,MAAM;AACrDnB,iBAASoB,OAAOX,IAAAA;AAChB,eAAO3B;MACT;AAEAkB,eAASqB,UAAUZ,IAAAA;AAKnB,WAAKnB,QAAQM,MAAM0B,cAAcX,QAAQY,EAAE,EAAEC,MAAM,CAACC,UAAAA;AAClDnD,eAAOoD,KAAK,6BAA6BC,OAAOF,KAAAA,CAAAA,EAAQ;MAC1D,CAAA;AAEA,aAAOd;IACT;EACF;AAEA,QAAM9B,OAAkB;IACtB,MAAM+C,UAAU5C,SAAO;AACrB,YAAMC,UAAUD,QAAQE,aAAY,EAAGC,WAAU;AACjD,YAAM0C,QAAQC,WAAW7C,SAAS8C,SAASC,QAAQjC,UAAAA;AAEnD,YAAMwB,KAAKM,UAAU/C,SAAYA,SAAYmD,YAAYJ,OAAOtC,MAAAA;AAChE,UAAIgC,OAAOzC,OAAW,OAAM,IAAIoD,kBAAkB,sBAAA;AAUlD,YAAMvB,UAAU,MAAMrB,QAAQM,MAAMuC,SAASZ,EAAAA;AAC7C,UAAI,CAACZ,WAAWA,QAAQQ,aAAa,MAAM;AACzC,cAAM,IAAIe,kBAAkB,sBAAA;MAC9B;AAEAjD,cAAQG,eAAegD,UAAUzB,OAAAA;AAIjC,UAAIkB,UAAU/C,UAAauD,YAAYR,OAAOtC,QAAQO,MAAAA,GAAS;AAC7DwC,yBACEtD,QAAQE,aAAY,EAAGqD,YAAW,GAClCC,YAAY7B,QAAQY,IAAIhC,QAAQO,MAAAA,GAChCM,SACAnB,OAAAA;MAEJ;IACF;EACF;AAEAwD,SAAOC,eAAe7D,MAAMJ,SAAS;IAAEkE,OAAOvC;IAASwC,YAAY;EAAM,CAAA;AACzE,SAAO/D;AACT;AArGgBQ;AAsHT,SAASyC,WAAWe,SAAiBC,MAAY;AACtD,MAAI,OAAOD,YAAW,SAAU,QAAO/D;AAEvC,aAAWiE,QAAQF,QAAOG,MAAM,GAAA,GAAM;AACpC,UAAMC,KAAKF,KAAKG,QAAQ,GAAA;AACxB,QAAID,KAAK,EAAG;AACZ,QAAIF,KAAKI,MAAM,GAAGF,EAAAA,EAAIrC,KAAI,MAAOkC,KAAM;AAEvC,UAAMH,QAAQI,KAAKI,MAAMF,KAAK,CAAA,EAAGrC,KAAI;AACrC,QAAI;AACF,aAAOwC,mBAAmBT,KAAAA;IAC5B,QAAQ;AACN,aAAOA;IACT;EACF;AAEA,SAAO7D;AACT;AAjBgBgD;AAgCT,SAASQ,iBACde,UACAxB,OACAzB,SACAnB,SAA8B;AAE9BqE,cACED,UACA;IACE,GAAGjD,QAAQL,UAAU,IAAI8B,KAAAA;IACzB;IACA;IACA;IACA,WAAWzB,QAAQN,MAAM;OACrByD,SAASnD,QAAQC,QAAQpB,OAAAA,IAAW;MAAC;QAAY,CAAA;IACrDuE,KAAK,IAAA,CAAA;AAEX;AAjBgBlB;AAoBT,SAASmB,mBACdJ,UACAjD,SACAnB,SAA8B;AAE9BqE,cACED,UACA;IACE,GAAGjD,QAAQL,UAAU;IACrB;IACA;IACA;IACA;OACIwD,SAASnD,QAAQC,QAAQpB,OAAAA,IAAW;MAAC;QAAY,CAAA;IACrDuE,KAAK,IAAA,CAAA;AAEX;AAhBgBC;AAkBhB,SAASF,SAASG,YAAiCzE,SAA8B;AAC/E,MAAIyE,eAAe5E,OAAW,QAAO4E;AAErC,QAAMC,OAAOhC,OAAO1C,SAAS8C,UAAU,MAAA,KAAW,EAAA;AAClD,QAAM6B,QAAQ,4CAA4CC,KAAKF,IAAAA;AAC/D,SAAO,CAACC;AACV;AANSL;AAST,SAASD,YAAYD,UAAmBV,OAAa;AACnD,QAAMmB,SAAST;AAIf,MAAI,OAAOS,QAAQC,cAAc,WAAY,QAAOD,OAAOC,UAAU,cAAcpB,KAAAA;AACnF,MAAI,OAAOmB,QAAQjB,WAAW,WAAYiB,QAAOjB,OAAO,cAAcF,KAAAA;AACxE;AAPSW;AAeF,SAASU,WAAW/E,SAA2CsB,OAAc;AAClF,QAAM0D,WACH,OAAOhF,SAASiF,OAAO,WAAWjF,QAAQiF,KAAKpF,WAChDG,SAASkF,QAAQC,iBACjB;AACF,SAAO,GAAGH,OAAAA,IAAW,OAAO1D,UAAU,WAAWA,MAAMK,KAAI,EAAGC,YAAW,IAAK,EAAA;AAChF;AANgBmD;AAoBhB,IAAM/D,WAAN,MAAMA,UAAAA;EAtWN,OAsWMA;;;;;EACK,YAAY,oBAAIoE,IAAAA;EAEzB,YACmBC,KACAC,SACjB;SAFiBD,MAAAA;SACAC,UAAAA;EAChB;EAEH7D,UAAU8D,KAAsB;AAC9B,UAAMC,QAAQ,KAAK,UAAUC,IAAIF,GAAAA;AACjC,QAAI,CAACC,MAAO,QAAO;AAMnB,QAAIA,MAAME,UAAU,EAAG,QAAO;AAE9B,QAAIF,MAAME,QAAQC,KAAKC,IAAG,EAAI,QAAO;AAIrC,SAAK,UAAUC,OAAON,GAAAA;AACtB,WAAO;EACT;EAEApD,OAAOoD,KAAmB;AACxB,UAAMK,MAAMD,KAAKC,IAAG;AACpB,UAAME,WAAW,KAAK,UAAUL,IAAIF,GAAAA;AAKpC,UAAMC,QACJM,YAAYF,MAAME,SAASC,QAAQ,KAAKT,UAAU,MAC9CQ,WACA;MAAEE,OAAO;MAAGD,OAAOH;MAAKF,OAAO;IAAE;AAEvCF,UAAMQ,SAAS;AACf,QAAIR,MAAMQ,SAAS,KAAKX,IAAKG,OAAME,QAAQE,MAAM,KAAKN,UAAU;AAChE,SAAK,UAAUW,IAAIV,KAAKC,KAAAA;AAIxB,QAAI,KAAK,UAAUU,OAAO,IAAQ,MAAK,OAAM;EAC/C;EAEA9D,UAAUmD,KAAmB;AAC3B,SAAK,UAAUM,OAAON,GAAAA;EACxB;EAEA,SAAM;AACJ,UAAMK,MAAMD,KAAKC,IAAG;AACpB,eAAW,CAACL,KAAKC,KAAAA,KAAU,KAAK,WAAW;AACzC,YAAMW,SAASX,MAAME,QAAQE;AAC7B,YAAMQ,SAASR,MAAMJ,MAAMO,QAAQ,KAAKT,UAAU;AAClD,UAAI,CAACa,UAAU,CAACC,OAAQ,MAAK,UAAUP,OAAON,GAAAA;IAChD;EACF;AACF;;;AGnYA,IAAAc,iBAA8C;;;ACoB9C,IAAMC,mBAAmB;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAOF,IAAMC,kBAAkB,oBAAIC,IAAY;EAAC;EAAO;CAAO;AAevD,SAASC,sBAAsBC,MAAcC,OAAc;AACzD,MAAIA,UAAUC,UAAaD,UAAU,KAAM;AAC3C,MAAI,OAAOA,UAAU,SAAU;AAC/B,MAAIE,MAAMC,QAAQH,KAAAA,KAAUA,MAAMI,MAAM,CAACC,SAAS,OAAOA,SAAS,QAAA,EAAW;AAE7E,QAAM,IAAIC,kBACR,IAAIP,IAAAA,mKAEF;AAEN;AAVSD;AAaT,IAAMS,mBAAmB,oBAAIV,IAAI;EAAC;EAAQ;EAAW;EAAU;EAAQ;CAAS;AAwBhF,SAASW,wBAAwBC,KAAa;AAC5C,QAAMC,UAAUC,OAAOC,KAAKH,GAAAA,EAAKI,OAAO,CAACC,QAAQ,CAACP,iBAAiBQ,IAAID,GAAAA,CAAAA;AACvE,MAAIJ,QAAQM,WAAW,EAAG;AAE1B,QAAMC,iBAAiBP,QAAQQ,KAAK,CAACJ,QAAQA,IAAIK,SAAS,GAAA,CAAA;AAC1D,QAAMC,OAAOH,iBACT,mFACA;AAEJ,QAAM,IAAIX,kBACR,0BAA0BI,QAAQM,SAAS,IAAI,MAAM,EAAA,KAAON,QAAQW,KAAK,IAAA,CAAA,gBACzD;OAAId;IAAkBc,KAAK,IAAA,CAAA,IAASD,IAAAA,EAAM;AAE9D;AAbSZ;AAgBT,SAASc,aAAavB,MAAcC,OAAc;AAChDF,wBAAsBC,MAAMC,KAAAA;AAC5B,MAAIA,UAAUC,UAAaD,UAAU,KAAM,QAAO,CAAA;AAClD,MAAIE,MAAMC,QAAQH,KAAAA,EAAQ,QAAOA,MAAMa,OAAO,CAACR,SAAyB,OAAOA,SAAS,QAAA;AACxF,SAAO,OAAOL,UAAU,WAAW;IAACA;MAAS,CAAA;AAC/C;AALSsB;AAOT,SAASC,eAAexB,MAAcC,OAAc;AAClDF,wBAAsBC,MAAMC,KAAAA;AAC5B,MAAI,OAAOA,UAAU,SAAU,QAAOA;AAEtC,MAAIE,MAAMC,QAAQH,KAAAA,GAAQ;AACxB,UAAMwB,UAAUxB,MAAMa,OAAO,CAACR,SAAyB,OAAOA,SAAS,QAAA;AACvE,WAAOmB,QAAQC,GAAG,EAAC;EACrB;AACA,SAAOxB;AACT;AATSsB;AAWT,SAASG,qBAAqBjB,KAAyBV,MAAY;AACjE,MAAIU,QAAQR,UAAaQ,QAAQ,GAAI,QAAOR;AAG5C,MAAI,CAAC,QAAQ0B,KAAKlB,GAAAA,GAAM;AACtB,UAAM,IAAIH,kBACR,IAAIP,IAAAA,0CAA8C6B,KAAKC,UAAUpB,GAAAA,CAAAA,GAAO;EAE5E;AACA,QAAMqB,SAASC,OAAOtB,GAAAA;AACtB,MAAIqB,SAAS,GAAG;AACd,UAAM,IAAIxB,kBAAkB,IAAIP,IAAAA,4BAAgC6B,KAAKC,UAAUpB,GAAAA,CAAAA,GAAO;EACxF;AACA,SAAOqB;AACT;AAdSJ;AAgBT,SAASM,UAAUvB,KAAY;AAC7B,QAAMwB,UAAUX,aAAa,QAAQb,GAAAA,EAAKI,OAAO,CAACqB,UAAUA,MAAMC,KAAI,MAAO,EAAA;AAC7E,MAAIF,QAAQjB,WAAW,EAAG,QAAOf;AAEjC,SAAOgC,QAAQG,IAAI,CAACF,UAAAA;AAClB,UAAMG,YAAYH,MAAMI,YAAY,GAAA;AACpC,QAAID,aAAa,KAAKA,cAAcH,MAAMlB,SAAS,GAAG;AACpD,YAAM,IAAIV,kBAAkB,iBAAiB4B,KAAAA,0CAA+C;IAC9F;AACA,UAAMK,QAAQL,MAAMM,MAAM,GAAGH,SAAAA;AAC7B,UAAMI,YAAYP,MAAMM,MAAMH,YAAY,CAAA;AAE1C,QAAI,CAACzC,gBAAgBmB,IAAI0B,SAAAA,GAAY;AACnC,YAAM,IAAInC,kBACR,2BAA2BmC,SAAAA,SAAkBP,KAAAA,8BAAmC;IAEpF;AACA,WAAO;MAAEK;MAAOE;IAAsC;EACxD,CAAA;AACF;AAnBST;AA8BT,SAASU,aAAajC,KAAa8B,OAAkCI,SAAe;AAClF,MAAI,CAACJ,MAAO,QAAO9B;AAEnB,UAAQ8B,MAAMK,MAAI;IAChB,KAAK,UAAU;AACb,YAAMd,SAASC,OAAOtB,GAAAA;AACtB,UAAIA,IAAI0B,KAAI,MAAO,MAAM,CAACJ,OAAOc,SAASf,MAAAA,GAAS;AACjD,cAAM,IAAIxB,kBAAkB,GAAGqC,OAAAA,MAAalC,GAAAA,0BAA6B;MAC3E;AACA,aAAOqB;IACT;IACA,KAAK,WAAW;AACd,UAAIrB,QAAQ,OAAQ,QAAO;AAC3B,UAAIA,QAAQ,QAAS,QAAO;AAC5B,YAAM,IAAIH,kBACR,GAAGqC,OAAAA,MAAalC,GAAAA,+CAAkD;IAEtE;IACA,KAAK,YAAY;AACf,YAAMqB,SAAS,IAAIgB,KAAKrC,GAAAA;AACxB,UAAIsB,OAAOgB,MAAMjB,OAAOkB,QAAO,CAAA,GAAK;AAClC,cAAM,IAAI1C,kBAAkB,GAAGqC,OAAAA,MAAalC,GAAAA,wBAA2B;MACzE;AACA,aAAOqB;IACT;IACA;AACE,aAAOrB;EACX;AACF;AA5BSiC;AAkCT,SAASO,WAAWxC,KAAa8B,OAAkCI,SAAe;AAChF,MAAIlC,QAAQ,GAAI,QAAO,CAAA;AACvB,SAAOA,IAAIyC,MAAM,GAAA,EAAKd,IAAI,CAACe,SAAST,aAAaS,MAAMZ,OAAOI,OAAAA,CAAAA;AAChE;AAHSM;AAKT,SAASG,aAAa3C,KAAc4C,OAAoB;AACtD,QAAMpB,UAAUX,aAAa,UAAUb,GAAAA,EAAKI,OAAO,CAACqB,UAAUA,MAAMC,KAAI,MAAO,EAAA;AAC/E,MAAIF,QAAQjB,WAAW,EAAG,QAAOf;AAEjC,SAAOgC,QAAQG,IAAI,CAACF,UAAAA;AAElB,UAAMoB,iBAAiBpB,MAAMqB,QAAQ,GAAA;AACrC,UAAMC,kBAAkBF,mBAAmB,KAAK,KAAKpB,MAAMqB,QAAQ,KAAKD,iBAAiB,CAAA;AAEzF,QAAIA,kBAAkB,KAAKE,oBAAoB,IAAI;AACjD,YAAM,IAAIlD,kBACR,mBAAmB4B,KAAAA,+EAC0B;IAEjD;AAEA,UAAMuB,YAAYvB,MAAMM,MAAM,GAAGc,cAAAA;AACjC,UAAMI,WAAWxB,MAAMM,MAAMc,iBAAiB,GAAGE,eAAAA;AACjD,UAAMG,WAAWzB,MAAMM,MAAMgB,kBAAkB,CAAA;AAE/C,QAAI,CAACI,iBAAiBzC,SAASuC,QAAAA,GAA6B;AAC1D,YAAM,IAAIpD,kBACR,4BAA4BoD,QAAAA,SAAiBxB,KAAAA,2BACnB0B,iBAAiBvC,KAAK,IAAA,CAAA,GAAQ;IAE5D;AAEA,UAAMkB,QAAQc,MAAMQ,OAAOC,KAAK,CAACC,cAAcA,UAAUhE,SAAS0D,SAAAA;AAClE,UAAMd,UAAU,WAAWT,KAAAA;AAC3B,UAAMlC,QACJ0D,aAAa,OACTT,WAAWU,UAAUpB,OAAOI,OAAAA,IAC5BD,aAAaiB,UAAUpB,OAAOI,OAAAA;AAEpC,WAAO;MAAEJ,OAAOkB;MAAWC;MAAsC1D;IAAM;EACzE,CAAA;AACF;AApCSoD;AA+CF,SAASY,sBAAsB9B,OAAemB,OAAoB;AACvE,QAAMY,QAAQb,aAAalB,OAAOmB,KAAAA;AAClC,QAAMa,OAAOD,QAAQ,CAAA;AACrB,MAAIC,SAASjE,QAAW;AACtB,UAAM,IAAIK,kBACR,8DAA8D4B,KAAAA,IAAS;EAE3E;AACA,SAAOgC;AACT;AATgBF;AAiBT,SAASG,eAAe1D,KAAe4C,OAAoB;AAChE7C,0BAAwBC,GAAAA;AAExB,QAAM2D,OAAO1C,qBAAqBH,eAAe,QAAQd,IAAI,MAAA,CAAO,GAAG,MAAA;AACvE,QAAM4D,UAAU3C,qBAAqBH,eAAe,WAAWd,IAAI,SAAA,CAAU,GAAG,SAAA;AAChF,QAAM6D,OAAOtC,UAAUvB,IAAI,MAAA,CAAO;AAClC,QAAM8D,UAAUnB,aAAa3C,IAAI,QAAA,GAAW4C,KAAAA;AAC5C,QAAMmB,SAASjD,eAAe,UAAUd,IAAI,QAAA,CAAS;AAErD,SAAO;IACL,GAAI2D,SAASnE,SAAY;MAAEmE;IAAK,IAAI,CAAC;IACrC,GAAIC,YAAYpE,SAAY;MAAEoE;IAAQ,IAAI,CAAC;IAC3C,GAAIC,OAAO;MAAEA;IAAK,IAAI,CAAC;IACvB,GAAIC,UAAU;MAAEA;IAAQ,IAAI,CAAC;IAC7B,GAAIC,WAAWvE,UAAauE,WAAW,KAAK;MAAEA;IAAO,IAAI,CAAC;EAC5D;AACF;AAhBgBL;;;ACvLT,SAASM,QAAQC,QAAuB;AAC7C,SAAOA,OAAOC,SAAS,SAASC,SAAYF,OAAOG;AACrD;AAFgBJ;AAKT,SAASK,YAAYJ,QAAuB;AACjD,UAAQA,OAAOC,MAAI;;;IAGjB,KAAK;AACH,aAAO;IACT,KAAK;AACH,aAAO;IACT;AACE,aAAO;EACX;AACF;AAXgBG;;;AFnFhB,IAAMC,UAAS,IAAIC,sBAAO,WAAA;AA0C1B,IAAMC,cAAc;AAEpB,IAAMC,MAAM;AAsBZ,eAAsBC,eAAeC,OAAqB;AACxD,QAAMC,WAAWD,MAAMC;AACvB,QAAMC,YAAYD,aAAaE,UAAaF,SAASG,WAAW;AAChE,QAAMC,UAAUH,YAAYI,aAAaN,MAAMO,QAAQP,MAAMQ,UAAU,CAAC,CAAA,IAAKP;AAE7E,QAAMQ,UAAU,IAAIC,IAAIV,MAAMO,OAAOI,IAAI,CAACC,UAAUA,MAAMC,IAAI,CAAA;AAE9D,QAAMC,WAAW,MAAMC,QAAQC,IAC7BX,QAGGY,OAAO,CAACC,WAAAA;AACP,UAAMN,QAAQO,QAAQD,MAAAA;AACtB,WAAON,UAAUT,UAAaM,QAAQW,IAAIR,KAAAA;EAC5C,CAAA,EACCD,IAAI,CAACO,QAAQG,UAAUC,QAAQJ,QAAQG,OAAOrB,KAAAA,CAAAA,CAAAA;AAGnD,SAAO;IAAEK,SAASS;IAAUZ;EAAU;AACxC;AAnBsBH;AAkCtB,SAASO,aACPC,QACAC,QAAoD;AAEpD,QAAMe,UAAU,wBAACX,UAAiCJ,OAAOI,MAAMC,IAAI,KAAKD,MAAMC,MAA9D;AAEhB,QAAMR,UAA6BE,OAAOI,IAAI,CAACC,WAAW;IACxDY,MAAM;IACNC,OAAOF,QAAQX,KAAAA;IACfA,OAAOA,MAAMC;EACf,EAAA;AAEA,QAAMa,QAAQnB,OAAOU,OAAO,CAACL,UAAUe,gBAAgBf,KAAAA,MAAWT,MAAAA;AAElE,QAAMyB,QAAQF,MAAM,CAAA;AACpB,MAAIE,OAAO;AACTvB,YAAQwB,KAAK;MACXL,MAAM;MACNC,OAAO,OAAOF,QAAQK,KAAAA,CAAAA;MACtBE,aAAa;MACblB,OAAOgB,MAAMf;MACbkB,MAAM;IACR,CAAA;EACF;AAEA,aAAWnB,SAASc,MAAMM,MAAM,GAAG,CAAA,GAAI;AACrC3B,YAAQwB,KAAK;MACXL,MAAM;MACNC,OAAO,UAAUF,QAAQX,KAAAA,CAAAA;MACzBA,OAAOA,MAAMC;MACbkB,MAAM;IACR,CAAA;EACF;AAEA,SAAO1B;AACT;AAnCSC;AAqCT,eAAegB,QACbJ,QACAG,OACArB,OAAqB;AAErB,QAAMiC,OAAO;;;IAGXC,IAAI,GAAGhB,OAAOM,IAAI,IAAIH,KAAAA;IACtBG,MAAMN,OAAOM;IACbC,OAAOP,OAAOO;IACdM,MAAMb,OAAOa,QAAQI,YAAYjB,MAAAA;IACjC,GAAIA,OAAOY,gBAAgB3B,SAAY;MAAE2B,aAAaZ,OAAOY;IAAY,IAAI,CAAC;IAC9E,GAAIZ,OAAOM,SAAS,SAAS;MAAEZ,OAAOM,OAAON;IAAM,IAAI,CAAC;IACxD,GAAIM,OAAOM,SAAS,UAAUN,OAAOD,WAAWd,SAAY;MAAEc,QAAQC,OAAOD;IAAO,IAAI,CAAC;EAC3F;AAEA,MAAI;AACF,WAAO;MAAE,GAAGgB;MAAMG,MAAM,MAAMC,QAAQnB,QAAQlB,KAAAA;IAAO;EACvD,SAASsC,OAAO;AAId3C,IAAAA,QAAO4C,KAAK,qBAAqBrB,OAAOO,KAAK,aAAae,OAAOF,KAAAA,CAAAA,EAAQ;AACzE,WAAO;MAAE,GAAGL;MAAMQ,QAAQ;IAAK;EACjC;AACF;AA1BenB;AA4Bf,eAAee,QAAQnB,QAAyBlB,OAAqB;AACnE,UAAQkB,OAAOM,MAAI;IACjB,KAAK;AACH,aAAON,OAAOwB,KAAK;QAAEC,SAAS3C,MAAM2C;MAAQ,CAAA;IAC9C,KAAK;AACH,aAAOC,QAAQ1B,QAAQlB,KAAAA;IACzB,KAAK;AACH,aAAO6C,OAAO3B,QAAQlB,KAAAA;IACxB,KAAK;AACH,aAAO8C,QAAQ5B,QAAQlB,KAAAA;EAC3B;AACF;AAXeqC;AAqBf,eAAeO,QACb1B,QACAlB,OAAqB;AAErB,QAAMY,QAAQmC,SAAS7B,OAAON,OAAOZ,KAAAA;AACrC,QAAMC,WAAW+C,WAAW9B,OAAOD,QAAQL,KAAAA;AAE3C,QAAMqC,OAAO,MAAMjD,MAAMkD,QAAQC,KAAKjC,OAAON,OAAO;IAClDwC,SAAS;IACT,GAAInD,SAASG,SAAS,IAAI;MAAEiD,SAASpD;IAAS,IAAI,CAAC;EACrD,CAAA;AAEA,MAAIiB,OAAOoC,gBAAgBnD,OAAW,QAAO;IAAEoD,OAAON,KAAKO;EAAM;AAEjE,QAAMC,UAAU9B,gBAAgBf,KAAAA;AAIhC,MAAI6C,YAAYtD,OAAW,QAAO;IAAEoD,OAAON,KAAKO;EAAM;AAEtD,QAAME,QAAQ,IAAIC,KAAKA,KAAKC,IAAG,IAAK1C,OAAOoC,cAAcxD,GAAAA,EAAK+D,YAAW;AACzE,QAAMC,SAAS,MAAM9D,MAAMkD,QAAQC,KAAKjC,OAAON,OAAO;IACpDwC,SAAS;IACTC,SAAS;SAAIpD;MAAU;QAAE8D,OAAON;QAASO,UAAU;QAAOT,OAAOG;MAAM;;EACzE,CAAA;AAEA,QAAMO,SAAShB,KAAKO,QAAQM,OAAON;AACnC,SAAO;IACLD,OAAON,KAAKO;;;IAGZ,GAAIS,SAAS,IAAI;MAAEC,OAAOC,KAAKC,MAAON,OAAON,QAAQS,SAAU,GAAA;IAAK,IAAI,CAAC;IACzEI,MAAM,GAAGP,OAAON,KAAK,gBAAgBtC,OAAOoC,WAAW;EACzD;AACF;AAlCeV;AAoCf,eAAeC,OACb3B,QACAlB,OAAqB;AAErB,QAAMY,QAAQmC,SAAS7B,OAAON,OAAOZ,KAAAA;AACrC,QAAMyD,UAAU9B,gBAAgBf,KAAAA;AAChC,QAAM0D,QAAQC,gBAAgB3D,KAAAA;AAC9B,QAAM4D,MAAM5D,MAAM6D,WAAW,CAAA,KAAM;AACnC,QAAMxE,WAAW+C,WAAW9B,OAAOD,QAAQL,KAAAA;AAE3C,QAAMqC,OAAO,MAAMjD,MAAMkD,QAAQC,KAAKjC,OAAON,OAAO;IAClDwC,SAASe,KAAKO,IAAIxD,OAAOyD,SAAS,GAAG,EAAA;;;IAGrC,GAAIlB,UAAU;MAAEmB,MAAM;QAAC;UAAEb,OAAON;UAASoB,WAAW;QAAgB;;IAAG,IAAI,CAAC;IAC5E,GAAI5E,SAASG,SAAS,IAAI;MAAEiD,SAASpD;IAAS,IAAI,CAAC;EACrD,CAAA;AAEA,SAAO;IACLuD,OAAOP,KAAKO;IACZsB,SAAS7B,KAAKb,KAAKzB,IAAI,CAACoE,YAAwB;MAC9C7C,IAAIM,OAAOuC,OAAOP,GAAAA,KAAQ,EAAA;MAC1BF,OAAOU,SAASD,OAAOT,KAAAA,CAAM,KAAK9B,OAAOuC,OAAOP,GAAAA,KAAQ,EAAA;IAC1D,EAAA;EACF;AACF;AAzBe3B;AAwCf,eAAeC,QACb5B,QACAlB,OAAqB;AAErB,QAAMY,QAAQmC,SAAS7B,OAAON,OAAOZ,KAAAA;AACrC,QAAMyD,UAAU9B,gBAAgBf,KAAAA;AAChC,MAAI6C,YAAYtD,QAAW;AACzB,UAAM,IAAI8E,MAAM,GAAG/D,OAAON,KAAK,sDAAsD;EACvF;AAEA,QAAMsE,SAAShE,OAAOgE,UAAU;AAChC,QAAMC,QAAQhB,KAAKO,IAAIxD,OAAOkE,WAAW,IAAIvF,WAAAA;AAC7C,QAAMwF,OAAOH,WAAW,QAAQpF,MAAMoF,WAAW,SAAS,IAAIpF,MAAM,KAAKA;AAEzE,QAAMG,WAAW+C,WAAW9B,OAAOD,QAAQL,KAAAA;AAE3C,QAAMgD,MAAMD,KAAKC,IAAG;AACpB,QAAM0B,SAASC,MAAMC,KAAK;IAAEpF,QAAQ+E;EAAM,GAAG,CAACM,GAAGpE,UAAUuC,OAAOuB,QAAQ9D,SAASgE,IAAAA;AAEnF,QAAMK,SAAS,MAAM3E,QAAQC,IAC3BsE,OAAO3E,IAAI,OAAOgF,UAAAA;AAChB,UAAM1C,OAAO,MAAMjD,MAAMkD,QAAQC,KAAKjC,OAAON,OAAO;MAClDwC,SAAS;MACTC,SAAS;WACJpD;QACH;UAAE8D,OAAON;UAASO,UAAU;UAAOT,OAAO,IAAII,KAAKgC,KAAAA,EAAO9B,YAAW;QAAG;QACxE;UAAEE,OAAON;UAASO,UAAU;UAAMT,OAAO,IAAII,KAAKgC,QAAQN,IAAAA,EAAMxB,YAAW;QAAG;;IAElF,CAAA;AACA,WAAO;MAAE+B,IAAI,IAAIjC,KAAKgC,KAAAA,EAAO9B,YAAW;MAAIN,OAAON,KAAKO;IAAM;EAChE,CAAA,CAAA;AAGF,SAAO;IAAEkC;IAAQlC,OAAOkC,OAAOG,OAAO,CAACC,KAAKC,UAAUD,MAAMC,MAAMxC,OAAO,CAAA;EAAG;AAC9E;AAlCeT;AA4Cf,SAASC,SAASlC,MAAcb,OAAqB;AACnD,QAAMY,QAAQZ,MAAMO,OAAOyF,KAAK,CAACC,cAAcA,UAAUpF,SAASA,IAAAA;AAClE,MAAID,UAAUT,OAAW,OAAM,IAAI8E,MAAM,GAAGpE,IAAAA,2BAA+B;AAC3E,SAAOD;AACT;AAJSmC;AAWT,SAASC,WAAW/B,QAA4BL,OAAoB;AAClE,SAAOK,WAAWd,SAAY,CAAA,IAAK;IAAC+F,sBAAsBjF,QAAQL,KAAAA;;AACpE;AAFSoC;AAKT,SAASgC,SAASzB,OAAc;AAC9B,MAAI,OAAOA,UAAU,YAAYA,UAAU,GAAI,QAAOA;AACtD,MAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAAW,QAAOf,OAAOe,KAAAA;AAC3E,SAAOpD;AACT;AAJS6E;;;AG9IT,IAAMmB,gBAAqC;EACzCC,MAAM;EACNC,MAAM;EACNC,QAAQ;EACRC,QAAQ;EACRC,QAAQ;AACV;AAQA,SAASC,mBACPC,OACAC,QAAgC;AAEhC,MAAIC,cAAcF,OAAOC,MAAAA,MAAY,cAAe,QAAOE;AAC3D,SAAOC,qBAAqBJ,OAAOC,MAAAA,GAASI,UAAUC;AACxD;AANSP;AAeT,SAASQ,QAAWC,OAAqBC,SAAwC;AAC/E,SAAO;OAAID;IACRE,IAAI,CAACC,MAAMC,WAAW;IAAED;IAAMC;IAAOC,OAAOJ,QAAQE,IAAAA;EAAM,EAAA,EAC1DG,KAAK,CAACC,GAAGC,MAAAA;AACR,QAAID,EAAEF,UAAUG,EAAEH,MAAO,QAAOE,EAAEH,QAAQI,EAAEJ;AAC5C,QAAIG,EAAEF,UAAUV,OAAW,QAAO;AAClC,QAAIa,EAAEH,UAAUV,OAAW,QAAO;AAClC,WAAOY,EAAEF,QAAQG,EAAEH;EACrB,CAAA,EACCH,IAAI,CAACO,UAAUA,MAAMN,IAAI;AAC9B;AAVSJ;AAYT,SAASW,WACPlB,OACAmB,WACAlB,QACAmB,WAAqC;AAErC,QAAMC,WAAWC,cAAcF,WAAWD,WAAWnB,MAAMuB,IAAI;AAI/D,SAAO;IACLA,MAAMvB,MAAMuB;IACZC,MAAMxB,MAAMwB;IACZC,MAAMzB,MAAMyB;IACZC,YAAY1B,MAAM0B;IAClBC,UAAU3B,MAAM2B;IAChBC,QAAQ5B,MAAM4B;IACdC,aAAa7B,MAAM6B;IACnBC,UAAUC,WAAWX,WAAWD,WAAWnB,KAAAA;IAC3C,GAAIA,MAAMgC,cAAc,OAAO;MAAEA,WAAW;IAAK,IAAI,CAAC;IACtD,GAAIX,UAAUY,UAAU9B,SAAY;MAAE8B,OAAOZ,SAASY;IAAM,IAAI,CAAC;IACjE,GAAIZ,UAAUa,WAAW/B,SAAY;MAAE+B,QAAQb,SAASa;IAAO,IAAI,CAAC;IACpE,GAAIlC,MAAMmC,iBAAiBhC,SAAY;MAAEgC,cAAcnC,MAAMmC;IAAa,IAAI,CAAC;IAC/E,GAAInC,MAAMoC,aAAa;MAAEA,YAAY;WAAIpC,MAAMoC;;IAAY,IAAI,CAAC;IAChE,GAAIpC,MAAMK,WACN;MACEA,UAAU;QACRgC,aAAarC,MAAMK,SAASgC;QAC5BC,aAAatC,MAAMK,SAASiC;QAC5B,GAAItC,MAAMK,SAASC,SAASH,SAAY;UAAEG,MAAMN,MAAMK,SAASC;QAAK,IAAI,CAAC;QACzE,GAAIN,MAAMK,SAASkC,OAAOpC,SAAY;UAAEoC,IAAIvC,MAAMK,SAASkC;QAAG,IAAI,CAAC;QACnE,GAAIrC,cAAcF,OAAOC,MAAAA,MAAYE,SACjC;UAAEqC,OAAOtC,cAAcF,OAAOC,MAAAA;QAAQ,IACtC,CAAC;QACL,GAAIwC,oBAAoBzC,OAAOC,MAAAA,MAAYE,SACvC;UAAEuC,eAAeD,oBAAoBzC,OAAOC,MAAAA;QAAQ,IACpD,CAAC;QACL,GAAIF,mBAAmBC,OAAOC,MAAAA,MAAYE,SACtC;UAAEwC,kBAAkB5C,mBAAmBC,OAAOC,MAAAA;QAAQ,IACtD,CAAC;MACP;IACF,IACA,CAAC;EACP;AACF;AA5CSiB;AA4DF,SAAS0B,cACd3C,QACAmB,WACAyB,aACAC,SAAmD;AAEnD,QAAMC,UAAU,IAAIC,IAAI/C,OAAOS,IAAI,CAACuC,UAAUA,MAAM1B,IAAI,CAAA;AAExD,SAAO;IACLtB,QAAQM,QAAQN,QAAQ,CAACgD,UAAU7B,YAAY6B,MAAM1B,IAAI,GAAGV,KAAAA,EAAOH,IAAI,CAACuC,WAAW;MACjF1B,MAAM0B,MAAM1B;MACZ2B,YAAY;WAAID,MAAMC;;MACtBC,cAAcC,gBAAgBH,KAAAA;MAC9BI,KAAKR,aAAaS,IAAIL,MAAM1B,IAAI,KAAK9B;MACrCqD,SAASA,SAASQ,IAAIL,MAAM1B,IAAI,KAAK,CAAA;MACrC,GAAIH,YAAY6B,MAAM1B,IAAI,GAAGU,UAAU9B,SACnC;QAAE8B,OAAOb,UAAU6B,MAAM1B,IAAI,GAAGU;MAAM,IACtC,CAAC;MACL,GAAIb,YAAY6B,MAAM1B,IAAI,GAAGgC,SAASpD,SAAY;QAAEoD,MAAMnC,UAAU6B,MAAM1B,IAAI,GAAGgC;MAAK,IAAI,CAAC;MAC3FC,QAAQjD,QACN0C,MAAMO,OAAOC,OAAO,CAACzD,UAAU,CAACA,MAAMK,YAAY0C,QAAQW,IAAI1D,MAAMK,SAASgC,WAAW,CAAA,GACxF,CAACrC,UAAUsB,cAAcF,WAAW6B,MAAM1B,MAAMvB,MAAMuB,IAAI,GAAGV,KAAAA,EAC7DH,IAAI,CAACV,UAAUkB,WAAWlB,OAAOiD,MAAM1B,MAAMtB,QAAQmB,SAAAA,CAAAA;IACzD,EAAA;EACF;AACF;AAzBgBwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;APlPhB,SAASe,eAAeC,OAAoB;AAC1C,SAAOA,MAAMC,OAAOC,OAAO,CAACC,UAAUA,MAAMC,cAAc,IAAA;AAC5D;AAFSL;AAIF,IAAMM,kBAAkB;AAUxB,IAAMC,eAAN,MAAMA;SAAAA;;;;;;;;;;;EACX,YAC0CC,SACMC,cACJC,WACHC,WACDC,OACEC,SACHC,MACKC,WAC1C;SARwCP,UAAAA;SACMC,eAAAA;SACJC,YAAAA;SACHC,YAAAA;SACDC,QAAAA;SACEC,UAAAA;SACHC,OAAAA;SACKC,YAAAA;EACzC;EAEcC,SAAS,IAAIC,sBAAO,WAAA;;;;;;;;;;EAWrC,MAAMC,eAA8B;AAClC,UAAMC,SAAS,MAAM,KAAKX,QAAQY,UAAS;AAC3C,UAAMC,QAAQF,OAAOG,IAAI,CAACrB,UAAUA,MAAMsB,IAAI;AAE9C,UAAMC,mBAAmBC,sBAAsBN,QAAQ,KAAKT,SAAS;AACrE,QAAIc,iBAAiBE,SAAS,GAAG;AAC/B,YAAM,IAAIC,MACR,mCAAmCH,iBAAiBE,WAAW,IAAI,YAAY,QAAA,mCAC3CF,iBAAiBI,KAAK,IAAA,CAAA,mBACvCP,MAAMO,KAAK,IAAA,CAAA,GAAQ;IAE1C;AAKA,UAAMC,mBAAmBC,qBACvBC,aAAaZ,QAAQ,KAAKT,SAAS,GACnC,KAAKC,SAAS;AAEhB,QAAIkB,iBAAiBH,SAAS,GAAG;AAC/B,YAAM,IAAIC,MACR,gCAAgCE,iBAAiBH,WAAW,IAAI,qBAAqB,kBAAA,8BACtDG,iBAAiBD,KAAK,IAAA,CAAA,mGAC8C;IAEvG;AAMA,UAAM,KAAKI,iBAAiBD,aAAaZ,QAAQ,KAAKT,SAAS,CAAA;AAE/D,UAAMuB,aAAaC,uBAAuBH,aAAaZ,QAAQ,KAAKT,SAAS,GAAG,KAAKC,SAAS;AAC9F,QAAIsB,WAAWP,SAAS,GAAG;AACzB,YAAMS,MAAMF,WAAWP,WAAW;AAClC,YAAM,IAAIC,MACR,gCAAgCM,WAAWL,KAAK,IAAA,CAAA,KAC3CO,MAAM,8BAA8B,2BAAA,4BAC7BA,MAAM,OAAO,MAAA,iIACkD;IAE/E;EACF;;;;;;;;;;EAWA,MAAcH,iBAAiBI,SAAkD;AAC/E,UAAMC,UAAUC,iBAAiB,KAAKxB,IAAI;AAC1C,QAAI,CAACuB,QAAS;AASd,UAAME,eAAeF,QAAQG,MAAMC;AACnC,QAAIF,iBAAiBG,UAAaN,QAAQO,KAAK,CAAC1C,UAAUA,MAAMsB,SAASgB,YAAAA,GAAe;AACtF,WAAKvB,OAAO4B,KACV,wBAAwBL,YAAAA,0LAGKA,YAAAA,OAAmB;IAEpD;AAQA,QAAI;AACF,UAAK,MAAMF,QAAQG,MAAMK,MAAK,MAAQ,GAAG;AACvC,aAAK7B,OAAO4B,KACV,8IACE;MAEN;IACF,SAASE,OAAO;AAGd,WAAK9B,OAAO4B,KAAK,2CAA2CG,OAAOD,KAAAA,CAAAA,EAAQ;IAC7E;EACF;;;;;;;;;;;;;EAcA,MAAME,aAAaC,SAAkD;AACnE,UAAMC,SAAS,MAAM,KAAKC,cAAa;AACvC,UAAMC,YAA6B,CAAA;AAEnC,eAAWnD,SAASiD,QAAQ;AAC1B,UAAI,MAAM,KAAKG,QAAQJ,SAAShD,MAAMsB,MAAM,MAAA,EAAS6B,WAAUE,KAAKrD,KAAAA;IACtE;AAEA,WAAOsD,eAAe;MACpB/C,SAAS,KAAKA;MACd0C,QAAQE;MACRI,UAAU,KAAKzC;MACfkC;MACAQ,QAAQC,OAAOC,YACbD,OAAOE,QAAQ,KAAKjD,aAAa,CAAC,CAAA,EAAGW,IAAI,CAAC,CAACC,MAAMsC,QAAAA,MAAc;QAACtC;QAAMsC,UAAUC;OAAM,CAAA;IAE1F,CAAA;EACF;;;;;;;;;EAUA,MAAMC,YAAYd,SAAiD;AACjE,UAAMC,SAAS,MAAM,KAAKC,cAAa;AAEvC,UAAMa,UAA2B,CAAA;AACjC,eAAW/D,SAASiD,QAAQ;AAC1B,UAAI,MAAM,KAAKe,UAAUhB,SAAShD,MAAMsB,IAAI,EAAGyC,SAAQV,KAAKrD,KAAAA;IAC9D;AAEA,WAAOiE,cACLF,SACA,KAAKrD,WACL,MAAM,KAAKwD,eAAelB,SAASe,OAAAA,GACnC,MAAM,KAAKI,WAAWnB,SAASe,OAAAA,CAAAA;EAEnC;;;;;;;;;EAUA,MAAMK,KACJpB,SACAhD,OACAqE,UAC2B;AAC3B,UAAMC,WAAW,MAAM,KAAKC,aAAavE,KAAAA;AACzC,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,MAAA;AACzC,WAAO,KAAKyE,YACVH,UACA,MAAM,KAAK/D,QAAQ6D,KACjBpE,OACA,KAAK0E,cAAcJ,UAAUK,eAAeN,UAAUC,QAAAA,CAAAA,CAAAA,CAAAA;EAG5D;;;;;;;EAQA,MAAMM,QAAQ5B,SAA2BhD,OAAe6E,IAAmC;AACzF,UAAMP,WAAW,MAAM,KAAKC,aAAavE,KAAAA;AACzC,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,MAAA;AACzC,UAAM8E,SAAS,MAAM,KAAKvE,QAAQqE,QAAQ5E,OAAO6E,EAAAA;AACjD,QAAIC,WAAW,KAAM,OAAM,IAAIC,oBAAoB/E,OAAO6E,EAAAA;AAC1D,WAAO,KAAKG,QAAQV,UAAUQ,MAAAA;EAChC;EAEA,MAAMG,OAAOjC,SAA2BhD,OAAekF,MAAuC;AAC5F,UAAMZ,WAAW,MAAM,KAAKC,aAAavE,KAAAA;AACzC,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,QAAA;AACzC,SAAKmF,eAAeb,UAAUY,IAAAA;AAE9B,UAAME,WAAW,MAAM,KAAKC,UAAUrC,SAASsB,UAAU,gBAAgBY,IAAAA;AACzE,UAAMI,UAAU,MAAM,KAAK/E,QAAQ0E,OAAOjF,OAAOoF,QAAAA;AACjD,UAAM,KAAKG,SAASvC,SAAShD,OAAO,eAAe;MAAE8E,QAAQQ;IAAQ,CAAA;AAErE,WAAO,KAAKN,QAAQV,UAAUgB,OAAAA;EAChC;EAEA,MAAME,OACJxC,SACAhD,OACA6E,IACAK,MACqB;AACrB,UAAMZ,WAAW,MAAM,KAAKC,aAAavE,KAAAA;AACzC,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,QAAA;AACzC,SAAKmF,eAAeb,UAAUY,IAAAA;AAE9B,UAAME,WAAW,MAAM,KAAKC,UAAUrC,SAASsB,UAAU,gBAAgBY,MAAML,EAAAA;AAC/E,UAAMY,UAAU,MAAM,KAAKlF,QAAQiF,OAAOxF,OAAO6E,IAAIO,QAAAA;AACrD,UAAM,KAAKG,SAASvC,SAAShD,OAAO,eAAe;MAAE6E;MAAIC,QAAQW;IAAQ,CAAA;AAEzE,WAAO,KAAKT,QAAQV,UAAUmB,OAAAA;EAChC;;;;;;;;;;;;;;;;;;;;;EAsBA,MAAMC,WACJ1C,SACAhD,OACA2F,KAC2B;AAC3B,UAAM,KAAKpB,aAAavE,KAAAA;AAGxB,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,QAAA;AAEzC,QAAI2F,IAAIlE,WAAW,GAAG;AACpB,YAAM,IAAImE,kBAAkB,gEAAA;IAC9B;AACA,QAAID,IAAIlE,SAASpB,iBAAiB;AAChC,YAAM,IAAIuF,kBACR,sBAAsBD,IAAIlE,MAAM,yCAAyCpB,eAAAA,GAAkB;IAE/F;AAEA,UAAMwF,SAAS,KAAKlF,QAAQX,KAAAA,GAAQ8F;AACpC,UAAMC,UAAsB,CAAA;AAC5B,UAAMC,SAAmD,CAAA;AAEzD,eAAWnB,MAAMc,KAAK;AACpB,UAAI;AACF,YAAIE,OAAQ,OAAMA,OAAO;UAAE7C;UAAShD;UAAO6E;QAAG,CAAA;AAC9C,cAAM,KAAKtE,QAAQ0F,OAAOjG,OAAO6E,EAAAA;AACjC,cAAM,KAAKU,SAASvC,SAAShD,OAAO,eAAe;UAAE6E;QAAG,CAAA;AACxDkB,gBAAQ1C,KAAKwB,EAAAA;MACf,SAAShC,OAAO;AAGdmD,eAAO3C,KAAK;UAAEwB;UAAIqB,SAASC,cAActD,KAAAA;QAAO,CAAA;MAClD;IACF;AAEA,WAAO;MAAEkD;MAASC;IAAO;EAC3B;EAEA,MAAMC,OAAOjD,SAA2BhD,OAAe6E,IAA6B;AAClF,UAAM,KAAKN,aAAavE,KAAAA;AACxB,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,QAAA;AAEzC,UAAM6F,SAAS,KAAKlF,QAAQX,KAAAA,GAAQ8F;AACpC,QAAID,OAAQ,OAAMA,OAAO;MAAE7C;MAAShD;MAAO6E;IAAG,CAAA;AAE9C,UAAM,KAAKtE,QAAQ0F,OAAOjG,OAAO6E,EAAAA;AACjC,UAAM,KAAKU,SAASvC,SAAShD,OAAO,eAAe;MAAE6E;IAAG,CAAA;EAC1D;;;;;;;;;;EAWA,MAAMuB,YACJpD,SACAhD,OACA6E,IACAwB,eACAhC,UAC2B;AAC3B,UAAMiC,SAAS,MAAM,KAAK/B,aAAavE,KAAAA;AACvC,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,MAAA;AAEzC,UAAMuG,SAAS,MAAM,KAAKC,sBAAsBF,QAAQD,aAAAA;AACxD,UAAM,KAAK7B,cAAcxB,SAASuD,OAAOjF,MAAM,MAAA;AAI/C,WAAO,KAAKmD,YACV8B,QACA,MAAM,KAAKhG,QAAQ6F,YACjBpG,OACA6E,IACAwB,eACA,KAAK3B,cAAc6B,QAAQ5B,eAAeN,UAAUkC,MAAAA,CAAAA,CAAAA,CAAAA;EAG1D;;;;;;;;EASA,MAAME,cACJzD,SACAhD,OACA6E,IACAwB,eACAK,UACe;AACf,UAAMH,SAAS,MAAM,KAAKI,gBAAgB3D,SAAShD,OAAOqG,aAAAA;AAC1D,UAAM,KAAK7B,cAAcxB,SAASuD,OAAOjF,MAAM,QAAA;AAE/C,UAAM,KAAKf,QAAQkG,cAAczG,OAAO6E,IAAIwB,eAAeK,QAAAA;EAC7D;;;;;;;;EASA,MAAME,cACJ5D,SACAhD,OACA6E,IACAwB,eACAK,UACe;AACf,UAAMH,SAAS,MAAM,KAAKI,gBAAgB3D,SAAShD,OAAOqG,aAAAA;AAC1D,UAAM,KAAK7B,cAAcxB,SAASuD,OAAOjF,MAAM,QAAA;AAE/C,UAAMgF,SAAS,MAAM,KAAK/B,aAAavE,KAAAA;AACvC,UAAMG,QAAQmG,OAAOrG,OAAO4G,KAAK,CAACC,cAAcA,UAAUxF,SAAS+E,aAAAA;AACnE,UAAMU,UAAU5G,QAAQ6G,oBAAoB7G,OAAO,MAAM,KAAK+C,cAAa,CAAA,IAAMT;AACjF,QAAIsE,QAAS,OAAM,IAAInB,kBAAkBmB,OAAAA;AAEzC,UAAM,KAAKxG,QAAQqG,cAAc5G,OAAO6E,IAAIwB,eAAeK,QAAAA;EAC7D;;EAGA,MAAcC,gBACZ3D,SACAhD,OACAqG,eACwB;AACxB,UAAMC,SAAS,MAAM,KAAK/B,aAAavE,KAAAA;AACvC,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,QAAA;AACzC,WAAO,KAAKwG,sBAAsBF,QAAQD,aAAAA;EAC5C;;;;;;;;EASA,MAAcG,sBACZF,QACAD,eACwB;AACxB,UAAMlG,QAAQmG,OAAOrG,OAAO4G,KAAK,CAACC,cAAcA,UAAUxF,SAAS+E,aAAAA;AAEnE,QAAI,CAAClG,OAAO8G,YAAY9G,MAAM8G,SAASC,gBAAgB,QAAQ;AAC7D,YAAM,IAAIC,mBACRb,OAAOhF,MACP+E,eACA,iDAAA;IAEJ;AAEA,UAAME,UAAU,MAAM,KAAKrD,cAAa,GAAI2D,KAC1C,CAACC,cAAcA,UAAUxF,SAASnB,MAAM8G,UAAUG,WAAAA;AAEpD,QAAI,CAACb,OAAQ,OAAM,IAAIY,mBAAmBb,OAAOhF,MAAM+E,aAAAA;AAEvD,WAAOE;EACT;;;;;;;;;;;;;EAcQvB,QAAQhF,OAAsB8E,QAAgC;AACpE,UAAMuC,UAAU,IAAIC,IAAIvH,eAAeC,KAAAA,EAAOqB,IAAI,CAAClB,UAAUA,MAAMmB,IAAI,CAAA;AACvE,UAAMiG,YAAwB,CAAC;AAE/B,eAAW,CAACC,KAAKC,KAAAA,KAAUhE,OAAOE,QAAQmB,MAAAA,GAAS;AACjD,UAAIuC,QAAQK,IAAIF,GAAAA,EAAMD,WAAUC,GAAAA,IAAOC;IACzC;AAEA,WAAOF;EACT;;;;;;;;;;;;;;;;;;EAmBQ7C,cAAc1E,OAAsB2H,OAA6B;AACvE,WAAO;MAAE,GAAGA;MAAO1H,QAAQF,eAAeC,KAAAA,EAAOqB,IAAI,CAAClB,UAAUA,MAAMmB,IAAI;IAAE;EAC9E;EAEQmD,YAAYzE,OAAsB4H,MAA0C;AAClF,WAAO;MAAE,GAAGA;MAAM1C,MAAM0C,KAAK1C,KAAK7D,IAAI,CAACyD,WAAW,KAAKE,QAAQhF,OAAO8E,MAAAA,CAAAA;IAAS;EACjF;;;;;;;;EASQK,eAAenF,OAAsBkF,MAAwB;AACnE,eAAWsC,OAAO/D,OAAOoE,KAAK3C,IAAAA,GAAO;AACnC,YAAM/E,QAAQH,MAAMC,OAAO4G,KAAK,CAACC,cAAcA,UAAUxF,SAASkG,GAAAA;AAIlE,UAAI,CAACrH,MAAO,OAAM,IAAIgH,mBAAmBnH,MAAMsB,MAAMkG,GAAAA;AAErD,UAAIM,WAAW,KAAKpH,WAAWV,MAAMsB,MAAMnB,KAAAA,GAAQ;AACjD,cAAM,IAAIgH,mBACRnH,MAAMsB,MACNkG,KACArH,MAAM4H,cACF,4CACA,wCAAA;MAER;IACF;EACF;;;;;;;;;;;;;EAcA,MAAc7D,eACZlB,SACAC,QACmD;AACnD,UAAM+E,cAAc,oBAAIC,IAAAA;AAExB,eAAWjI,SAASiD,QAAQ;AAC1B,YAAMG,UAAU,8BAAO8E,cACrB,KAAK9E,QAAQJ,SAAShD,MAAMsB,MAAM4G,SAAAA,GADpB;AAGhBF,kBAAYG,IAAInI,MAAMsB,MAAM;QAC1B8C,MAAM,MAAMhB,QAAQ,MAAA;QACpBgF,MAAM,MAAMhF,QAAQ,MAAA;QACpB6B,QAAQ,MAAM7B,QAAQ,QAAA;QACtBoC,QAAQ,MAAMpC,QAAQ,QAAA;QACtB6C,QAAQ,MAAM7C,QAAQ,QAAA;MACxB,CAAA;IACF;AAEA,WAAO4E;EACT;;EAGA,MAAc5E,QACZJ,SACAhD,OACAkI,WACkB;AAClB,QAAI;AACF,aAAQ,MAAM,KAAK1H,aAAa6H,UAAU;QAAErF;QAAShD;QAAOkI;MAAU,CAAA,MAAQ;IAChF,SAASI,OAAO;AACd,UAAIC,iBAAiBD,KAAAA,KAAUA,MAAME,SAAS,YAAa,QAAO;AAClE,YAAMF;IACR;EACF;;;;;;;;EASA,MAAcjD,UACZrC,SACAsB,UACAmE,MACAvD,MACAL,IACqB;AACrB,UAAM6D,UAAU,KAAK/H,QAAQ2D,SAAShD,IAAI,IAAImH,IAAAA;AAC9C,QAAI,CAACC,QAAS,QAAOxD;AAErB,UAAMyD,SAAS,OAAOF,SAAS,iBAC1BC,QAA8D;MAC7D1F;MACAhD,OAAOsE,SAAShD;MAChB4D;IACF,CAAA,IACCwD,QAA8D;MAC7D1F;MACAhD,OAAOsE,SAAShD;MAChBuD;MACAK;IACF,CAAA;AAEJ,SAAKC,eAAeb,UAAUqE,MAAAA;AAC9B,WAAOA;EACT;;;;;;;EAQA,MAAcpD,SACZvC,SACAhD,OACAyI,MACAG,MACe;AACf,UAAMF,UAAU,KAAK/H,QAAQX,KAAAA,IAASyI,IAAAA;AACtC,QAAI,CAACC,QAAS;AAEd,UAAOA,QAA+C;MACpD1F;MACAhD;MACA,GAAG4I;IACL,CAAA;EACF;;;;;;;;EASA,MAAczE,WACZnB,SACAC,QACoD;AACpD,UAAM4F,UAAU,oBAAIZ,IAAAA;AAEpB,eAAWjI,SAASiD,QAAQ;AAC1B,YAAMM,WAAW,KAAK3C,UAAUZ,MAAMsB,IAAI,KAAK,CAAA;AAC/C,UAAIiC,SAAS9B,WAAW,EAAG;AAC3B,UAAI,CAAE,MAAM,KAAK2B,QAAQJ,SAAShD,MAAMsB,MAAM,QAAA,EAAY;AAE1DuH,cAAQV,IACNnI,MAAMsB,MACNiC,SAASlC,IAAI,CAACyH,YAAY;QACxBxH,MAAMwH,OAAOxH;QACbuC,OAAOiF,OAAOjF,SAASiF,OAAOxH;QAC9ByH,OAAOD,OAAOC;QACd,GAAID,OAAOE,YAAYvG,SAAY;UAAEuG,SAASF,OAAOE;QAAQ,IAAI,CAAC;QAClE,GAAIF,OAAOG,WAAWxG,SAAY;UAAEwG,QAAQH,OAAOG;QAAO,IAAI,CAAC;MACjE,EAAA,CAAA;IAEJ;AAEA,WAAOJ;EACT;;;;;;;;;;;;EAaA,MAAMK,UACJlG,SACAhD,OACAsB,MACAuD,IAC4B;AAC5B,UAAM,KAAKN,aAAavE,KAAAA;AACxB,UAAM,KAAKwE,cAAcxB,SAAShD,OAAO,QAAA;AAEzC,UAAM8I,UAAU,KAAKlI,UAAUZ,KAAAA,KAAU,CAAA,GAAI6G,KAAK,CAACC,cAAcA,UAAUxF,SAASA,IAAAA;AACpF,QAAI,CAACwH,QAAQ;AACX,YAAM,IAAI3B,mBAAmBnH,OAAOsB,MAAM,iBAAA;IAC5C;AAEA,QAAIwH,OAAOC,UAAU,YAAYlE,OAAOpC,QAAW;AACjD,YAAM,IAAImD,kBAAkB,WAAWtE,IAAAA,0CAA8C;IACvF;AACA,QAAIwH,OAAOC,UAAU,UAAUlE,OAAOpC,QAAW;AAC/C,YAAM,IAAImD,kBAAkB,WAAWtE,IAAAA,gDAAoD;IAC7F;AAEA,WAAQ,MAAMwH,OAAOK,IAAI;MAAEnG;MAAShD;MAAO,GAAI6E,OAAOpC,SAAY,CAAC,IAAI;QAAEoC;MAAG;IAAG,CAAA,KAAO,CAAC;EACzF;;;;;;;;;;;EAaA,MAAcL,cACZxB,SACAhD,OACAkI,WACe;AACf,UAAMkB,WAAW,MAAM,KAAK5I,aAAa6H,UAAU;MAAErF;MAAShD;MAAOkI;IAAU,CAAA;AAC/E,QAAIkB,aAAa,MAAO,OAAM,IAAIC,eAAAA;EACpC;;;;;;;;;;;;;EAcA,MAAcrF,UAAUhB,SAA2BhD,OAAiC;AAClF,QAAI;AACF,YAAMoJ,WAAW,MAAM,KAAK5I,aAAa6H,UAAU;QAAErF;QAAShD;QAAOkI,WAAW;MAAW,CAAA;AAC3F,aAAOkB,aAAa;IACtB,SAASd,OAAO;AAGd,UAAIC,iBAAiBD,KAAAA,KAAUA,MAAME,SAAS,YAAa,QAAO;AAClE,YAAMF;IACR;EACF;;;;;;;EAQA,MAAcpF,gBAAmD;AAC/D,WAAOoG,eACLxH,aAAa,MAAM,KAAKvB,QAAQY,UAAS,GAAI,KAAKV,SAAS,GAC3D,KAAKC,SAAS;EAElB;;;;;;;;;;EAYA,MAAc6D,aAAavE,OAAuC;AAChE,UAAMiD,SAAS,MAAM,KAAKC,cAAa;AACvC,UAAMqG,QAAQtG,OAAO4D,KAAK,CAACC,cAAcA,UAAUxF,SAAStB,KAAAA;AAC5D,QAAI,CAACuJ,OAAO;AACV,YAAM,IAAIC,mBACRxJ,OACAiD,OAAO5B,IAAI,CAACyF,cAAcA,UAAUxF,IAAI,CAAA;IAE5C;AACA,WAAOiI;EACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AN3xBO,IAAME,kBAAN,MAAMA;SAAAA;;;;EACX,YAA6BC,SAAuB;SAAvBA,UAAAA;EAAwB;;;;;;;;;;;;;;;;EAiBrD,MACMC,cACYC,SACAC,OACCC,QAC4B;AAC7C,WAAOC,QAAQ,MAAM,KAAKL,QAAQM,UAAUJ,SAASC,OAAOC,MAAAA,CAAAA;EAC9D;EAEA,MACMG,gBACYL,SACAC,OACCC,QACJI,IACgC;AAC7C,WAAOH,QAAQ,MAAM,KAAKL,QAAQM,UAAUJ,SAASC,OAAOC,QAAQI,EAAAA,CAAAA;EACtE;EAEA,MACMC,KAAqBP,SAAkE;AAC3F,WAAOG,QAAQ,MAAM,KAAKL,QAAQU,YAAYR,OAAAA,CAAAA;EAChD;;;;;;;;EASA,MACMS,UACYT,SACwB;AACxC,WAAOG,QAAQ,MAAM,KAAKL,QAAQY,aAAaV,OAAAA,CAAAA;EACjD;EAEA,MACMW,KACYX,SACAC,OACPW,OACwC;AACjD,UAAMC,OAAO,MAAM,KAAKf,QAAQa,KAAKX,SAASC,OAAOW,KAAAA;AACrD,WAAOE,YAAYD,KAAKE,MAAM;MAAEC,OAAOH,KAAKG;MAAOH,MAAMA,KAAKA;MAAMI,SAASJ,KAAKI;IAAQ,CAAA;EAC5F;EAEA,MACMC,QACYlB,SACAC,OACHK,IACyB;AACtC,WAAOH,QAAQ,MAAM,KAAKL,QAAQoB,QAAQlB,SAASC,OAAOK,EAAAA,CAAAA;EAC5D;EAEA,MACMa,OACYnB,SACAC,OACRmB,MAC8B;AACtC,WAAOjB,QAAQ,MAAM,KAAKL,QAAQqB,OAAOnB,SAASC,OAAOmB,IAAAA,CAAAA;EAC3D;EAEA,MACMC,OACYrB,SACAC,OACHK,IACLc,MAC8B;AACtC,WAAOjB,QAAQ,MAAM,KAAKL,QAAQuB,OAAOrB,SAASC,OAAOK,IAAIc,IAAAA,CAAAA;EAC/D;;;;;EAMA,MACME,OACYtB,SACAC,OACHK,IACmB;AAChC,UAAM,KAAKR,QAAQyB,OAAOvB,SAASC,OAAOK,EAAAA;AAC1C,WAAOH,QAAQ,IAAA;EACjB;;;;;;;;;;;;EAaA,MACMqB,WACYxB,SACAC,OACRmB,MACoC;AAC5C,UAAMK,MAAeL,MAAMK;AAC3B,QAAI,CAACC,MAAMC,QAAQF,GAAAA,KAAQA,IAAIG,KAAK,CAACtB,OAAO,OAAOA,OAAO,YAAY,OAAOA,OAAO,QAAA,GAAW;AAC7F,YAAM,IAAIuB,kBACR,yEAAA;IAEJ;AAEA,WAAO1B,QAAQ,MAAM,KAAKL,QAAQgC,WAAW9B,SAASC,OAAOwB,GAAAA,CAAAA;EAC/D;;;;;;;;EASA,MACMM,YACY/B,SACAC,OACHK,IACM0B,UACVpB,OACwC;AACjD,UAAMC,OAAO,MAAM,KAAKf,QAAQiC,YAAY/B,SAASC,OAAOK,IAAI0B,UAAUpB,KAAAA;AAC1E,WAAOE,YAAYD,KAAKE,MAAM;MAAEC,OAAOH,KAAKG;MAAOH,MAAMA,KAAKA;MAAMI,SAASJ,KAAKI;IAAQ,CAAA;EAC5F;;;;;;;;EASA,MACMgB,cACYjC,SACAC,OACHK,IACM0B,UACXZ,MACwB;AAChC,UAAMc,WAAWd,MAAMd;AACvB,QAAI,OAAO4B,aAAa,YAAY,OAAOA,aAAa,UAAU;AAChE,YAAM,IAAIL,kBACR,yEAAA;IAEJ;AAEA,UAAM,KAAK/B,QAAQmC,cAAcjC,SAASC,OAAOK,IAAI0B,UAAUE,QAAAA;AAC/D,WAAO/B,QAAQ,IAAA;EACjB;;EAGA,MACMgC,cACYnC,SACAC,OACHK,IACM0B,UACAE,UACa;AAChC,UAAM,KAAKpC,QAAQqC,cAAcnC,SAASC,OAAOK,IAAI0B,UAAUE,QAAAA;AAC/D,WAAO/B,QAAQ,IAAA;EACjB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AcnOA,IAAAiC,iBAWO;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAMC,sBAAN,MAAMA;SAAAA;;;;EACX,YAAiDC,MAAiB;SAAjBA,OAAAA;EAAkB;;;;;;;;;EAUnE,MACMC,QAAeC,SAAgE;AACnF,UAAMC,UAAU,KAAKA,QAAO;AAC5B,UAAMC,QAAQC,WAAWH,SAASI,SAASC,QAAQJ,QAAQK,UAAU;AACrE,UAAMC,KAAKL,UAAUM,SAAYA,SAAYC,YAAYP,OAAOD,QAAQS,MAAM;AAE9E,QAAIH,OAAOC,OAAW,QAAOG,QAAQ;MAAEC,SAAS;IAAK,CAAA;AAErD,UAAMA,UAAU,MAAMX,QAAQY,MAAMC,SAASP,EAAAA;AAC7C,WAAOI,QAAQ;MACbC,SAAS,CAACA,WAAWA,QAAQG,aAAa,OAAO,OAAOC,UAAUJ,OAAAA;IACpE,CAAA;EACF;;;;;;;;;;EAWA,MAEMK,MACGjB,SACqBkB,UACpBC,MACmC;AAC3C,UAAMlB,UAAU,KAAKA,QAAO;AAC5BmB,qBAAiBpB,OAAAA;AAEjB,QAAI,OAAOmB,MAAME,UAAU,YAAY,OAAOF,MAAMG,aAAa,UAAU;AACzE,YAAM,IAAIC,kBAAkB,8CAAA;IAC9B;AAEA,UAAMX,UAAU,MAAMX,QAAQuB,OAAOL,KAAKE,OAAOF,KAAKG,UAAUG,WAAWzB,SAASmB,KAAKE,KAAK,CAAA;AAE9F,QAAI,CAACT,SAAS;AACZ,YAAM,IAAIc,kBAAkB,wCAAA;IAC9B;AAUAC,qBACET,UACAU,YAAYhB,QAAQL,IAAIN,QAAQS,QAAQT,QAAQ4B,MAAM,GACtD5B,SACAD,OAAAA;AAGF,WAAOW,QAAQ;MAAEC,SAASI,UAAUJ,OAAAA;IAAS,CAAA;EAC/C;;;;;;;;EAWAkB,OACS9B,SACqBkB,UACM;AAClC,UAAMjB,UAAU,KAAKA,QAAO;AAC5BmB,qBAAiBpB,OAAAA;AACjB+B,uBAAmBb,UAAUjB,SAASD,OAAAA;AACtC,WAAOW,QAAQ;MAAEC,SAAS;IAAK,CAAA;EACjC;;;;;;;EAQQX,UAA8B;AACpC,UAAMA,UAAU+B,iBAAiB,KAAKlC,IAAI;AAC1C,QAAI,CAACG,SAAS;AACZ,YAAM,IAAIgC,iCACR,iFAAA;IAEJ;AACA,WAAOhC;EACT;AACF;;;;;;;;;;;;;;;IApEWiC,aAAa;;;;;;;;;;;;;;;;IA6CbA,aAAa;;;;;;;;;;;;;;;;;;AA6CxB,SAASd,iBAAiBpB,SAA+B;AACvD,QAAMmC,SAASC,OAAOpC,SAAS,QAAA;AAC/B,MAAImC,WAAW3B,OAAW;AAE1B,QAAM6B,OAAOD,OAAOpC,SAAS,MAAA;AAC7B,MAAIqC,SAAS7B,OAAW;AAExB,MAAI8B;AACJ,MAAI;AACFA,WAAO,IAAIC,IAAIJ,MAAAA,EAAQE;EACzB,QAAQ;AACN,UAAM,IAAIX,kBAAkB,2CAAA;EAC9B;AAEA,MAAIY,SAASD,MAAM;AACjB,UAAM,IAAIX,kBAAkB,2CAAA;EAC9B;AACF;AAjBSN;AAmBT,SAASgB,OAAOpC,SAAiCwC,MAAY;AAC3D,QAAMC,QAAQzC,SAASI,UAAUoC,IAAAA;AACjC,SAAO,OAAOC,UAAU,WAAWA,QAAQjC;AAC7C;AAHS4B;;;AC1LT,IAAAM,iBAA8C;AA2C9C,IAAMC,UAAS,IAAIC,sBAAO,WAAA;AAenB,SAASC,yBAAAA;AACd,QAAMC,OAAkB;IACtBC,YAAAA;IAEA;EACF;AACAC,kBAAgBC,IAAIH,IAAAA;AACpB,SAAOA;AACT;AARgBD;AAiBhB,IAAMG,kBAAkB,oBAAIE,QAAAA;AAGrB,SAASC,aAAaL,MAAe;AAC1C,MAAIE,gBAAgBI,IAAIN,IAAAA,GAAO;AAC7BH,IAAAA,QAAOU,KACL,gIACE;EAEN;AACF;AAPgBF;;;ACeT,SAASG,oBAAAA;AACd,SAAO;IACLC,YAAAA;AACE,aAAO;IACT;EACF;AACF;AANgBD;;;AC7FT,IAAME,qBAAqB;AAQ3B,SAASC,mBAAmBC,MAAwB;AACzD,MAAIA,SAASC,OAAW,QAAOH;AAE/B,MAAI,OAAOE,SAAS,UAAU;AAC5B,UAAM,IAAIE,UAAU,mDAAmD,OAAOF,IAAAA,GAAO;EACvF;AAEA,QAAMG,WAAWH,KAAKI,MAAM,GAAA,EAAKC,OAAO,CAACC,YAAYA,QAAQC,SAAS,CAAA;AAEtE,MAAIJ,SAASI,WAAW,GAAG;AAKzB,UAAM,IAAIC,MACR,qMAEE;EAEN;AAEA,aAAWF,WAAWH,UAAU;AAK9B,QAAI,CAAC,qBAAqBM,KAAKH,OAAAA,GAAU;AACvC,YAAM,IAAIE,MACR,iCAAiCF,OAAAA,wKAE/B;IAEN;EACF;AAEA,SAAO,IAAIH,SAASO,KAAK,GAAA,CAAA;AAC3B;AApCgBX;;;AChBhB,qBAAmD;AACnD,uBAA4C;AAC5C,sBAA8B;;;ACoB9B,IAAMY,WAAW;AASjB,IAAMC,UAAU;AAGhB,SAASC,SAASC,KAAW;AAC3B,QAAMC,QACJD,IAAIE,WAAW,IACXF,IACGG,MAAM,CAAA,EACNC,MAAM,EAAA,EACNC,IAAI,CAACC,SAASA,OAAOA,IAAAA,EACrBC,KAAK,EAAA,IACRP,IAAIG,MAAM,CAAA;AAEhB,SAAO;IACLK,OAAOC,SAASR,MAAME,MAAM,GAAG,CAAA,GAAI,EAAA,IAAM;IACzCK,OAAOC,SAASR,MAAME,MAAM,GAAG,CAAA,GAAI,EAAA,IAAM;IACzCK,OAAOC,SAASR,MAAME,MAAM,GAAG,CAAA,GAAI,EAAA,IAAM;;AAE7C;AAfSJ;AAiBT,SAASW,MAAM,CAACC,GAAGC,GAAGC,CAAAA,GAAqC;AACzD,QAAMP,OAAO,wBAACL,UACZa,KAAKC,MAAMD,KAAKE,IAAI,GAAGF,KAAKG,IAAI,GAAGhB,KAAAA,CAAAA,IAAU,GAAA,EAC1CiB,SAAS,EAAA,EACTC,SAAS,GAAG,GAAA,GAHJ;AAIb,SAAO,IAAIb,KAAKK,CAAAA,CAAAA,GAAKL,KAAKM,CAAAA,CAAAA,GAAKN,KAAKO,CAAAA,CAAAA;AACtC;AANSH;AAST,SAASU,UAAUC,KAAsC;AACvD,QAAM,CAACV,GAAGC,GAAGC,CAAAA,IAAKQ,IAAIhB,IAAI,CAACiB,YACzBA,WAAW,UAAUA,UAAU,UAAUA,UAAU,SAAS,UAAU,GAAA;AAExE,SAAO,SAASX,IAAI,SAASC,IAAI,SAASC;AAC5C;AALSO;AAOT,SAASG,SAASC,GAAsCX,GAAoC;AAC1F,QAAM,CAACY,SAASC,MAAAA,IAAU;IAACN,UAAUI,CAAAA;IAAIJ,UAAUP,CAAAA;IAAIc,KAAK,CAACC,GAAGC,MAAMA,IAAID,CAAAA;AAC1E,UAAQH,UAAU,SAASC,SAAS;AACtC;AAHSH;AAMT,IAAMO,YAA+C;EAAC;EAAO;EAAO;;AACpE,IAAMC,WAA8C;EAAC;EAAM;EAAK;;AAGhE,IAAMC,aAAgD;EAAC;EAAO;EAAM;;AACpE,IAAMC,YAA+C;EAAC;EAAM;EAAK;;AAU1D,SAASC,YAAYlC,KAAW;AACrC,QAAMmC,QAAQpC,SAASC,GAAAA;AACvB,SAAOuB,SAASY,OAAOL,SAAAA,KAAcP,SAASY,OAAOJ,QAAAA,IACjDrB,MAAMoB,SAAAA,IACNpB,MAAMqB,QAAAA;AACZ;AALgBG;AAQhB,SAASE,IACPf,KACAgB,SACAC,QAAc;AAEd,SAAOjB,IAAIhB,IAAI,CAACiB,YAAYA,WAAWe,UAAUf,WAAWgB,MAAAA;AAC9D;AANSF;AAqCF,SAASG,UACdvC,KACAwC,MACAC,OAAwB,QAAM;AAE9B,QAAMC,aAAaF,SAAS,SAASP,YAAYD;AACjD,QAAMK,UAAUG,SAAS,SAAS,IAAI;AAEtC,QAAMG,QAAQF,SAAS,SAAS3C,UAAUD;AAE1C,MAAI+C,SAAS7C,SAASC,GAAAA;AAGtB,WAAS6C,OAAO,GAAGA,OAAO,IAAIA,QAAQ;AACpC,QAAItB,SAASqB,QAAQF,UAAAA,KAAeC,MAAO;AAC3CC,aAASR,IAAIQ,QAAQP,SAAS,IAAA;EAChC;AAEA,SAAO3B,MAAMkC,MAAAA;AACf;AAnBgBL;;;AC1FhB,IAAMO,aAAa;AAGnB,IAAMC,YAAY;AAElB,IAAMC,WAAW;AAEjB,IAAMC,cAAmC,oBAAIC,IAAI;EAAC;EAAU;EAAS;CAAO;AAG5E,IAAMC,aAAkC,oBAAID,IAAI;EAAC;EAAc;EAAS;EAAW;CAAa;AAQzF,SAASE,kBAAkBC,OAA6B;AAC7D,MAAI,CAACA,MAAO;AAEZ,MAAIA,MAAMC,eAAeC,UAAa,CAACT,WAAWU,KAAKH,MAAMC,UAAU,GAAG;AACxE,UAAM,IAAIG,MACR,qFACcC,KAAKC,UAAUN,MAAMC,UAAU,CAAA,GAAI;EAErD;AAEA,MAAID,MAAMO,UAAUL,UAAa,CAACR,UAAUS,KAAKH,MAAMO,KAAK,GAAG;AAC7D,UAAM,IAAIH,MACR,mJAC4E;EAEhF;AAEA,MAAIJ,MAAMQ,YAAYN,UAAa,CAACP,SAASQ,KAAKH,MAAMQ,OAAO,GAAG;AAChE,UAAM,IAAIJ,MACR,sFACcC,KAAKC,UAAUN,MAAMQ,OAAO,CAAA,GAAI;EAElD;AAEA,MAAIR,MAAMS,eAAeP,UAAa,CAACN,YAAYc,IAAIV,MAAMS,UAAU,GAAG;AACxE,UAAM,IAAIL,MACR,kFACcC,KAAKC,UAAUN,MAAMS,UAAU,CAAA,GAAI;EAErD;AAWA,QAAME,UAAUC,OAAOC,KAAKb,KAAAA,EAAOc,OAAO,CAACC,QAAQ,CAACjB,WAAWY,IAAIK,GAAAA,CAAAA;AACnE,MAAIJ,QAAQK,SAAS,GAAG;AACtB,UAAM,IAAIZ,MACR,sCAAsCO,QAAQK,WAAW,IAAI,KAAK,GAAA,WAC7DL,QAAQM,KAAK,IAAA,CAAA,oBAAyB;SAAInB;MAAYmB,KAAK,IAAA,CAAA,GAAQ;EAE5E;AACF;AA/CgBlB;AA2DT,SAASmB,YAAYlB,OAA6B;AACvD,MAAI,CAACA,SAAU,CAACA,MAAMC,cAAc,CAACD,MAAMO,SAAS,CAACP,MAAMQ,WAAW,CAACR,MAAMS,YAAa;AACxF,WAAO;EACT;AAEA,QAAMU,QAAQnB,MAAMC,aAAa,UAAUmB,WAAWpB,MAAMC,UAAU,CAAA,aAAc;AAEpF,QAAMoB,UAAUhB,KAAKC,UAAU;IAC7B,GAAIN,MAAMO,UAAUL,SAAY;MAAEK,OAAOP,MAAMO;IAAM,IAAI,CAAC;IAC1D,GAAIP,MAAMQ,YAAYN,SAAY;MAAEM,SAASR,MAAMQ;IAAQ,IAAI,CAAC;IAChE,GAAIR,MAAMS,eAAeP,SAAY;MAAEO,YAAYT,MAAMS;IAAW,IAAI,CAAC;EAC3E,CAAA;AAEA,SAAO,GAAGU,KAAAA,yCAA8CE,OAAAA;AAC1D;AAdgBH;AAsChB,SAASE,WAAWE,OAAa;AAC/B,QAAMC,QAAQ,wBAACC,SAAAA;AACb,UAAMC,OAAOC,UAAUJ,OAAOE,MAAM,MAAA;AACpC,UAAMG,OAAOD,UAAUJ,OAAOE,MAAM,MAAA;AACpC,WAAO,aAAaC,IAAAA,yBAA6BG,YAAYH,IAAAA,CAAAA,WAAgBE,IAAAA;EAC/E,GAJc;AAMd,SAAO,SAASJ,MAAM,OAAA,CAAA,UAAkBA,MAAM,MAAA,CAAA;AAChD;AARSH;;;AFhJT,SAASS,kBAAAA;AACP,aAAOC,8BAAQC,+BAAc,aAAe,CAAA;AAC9C;AAFSF;AAKF,SAASG,SAAAA;AACd,aAAOC,uBAAKJ,gBAAAA,GAAmB,UAAA;AACjC;AAFgBG;AAKT,SAASE,YAAYC,OAAeH,OAAAA,GAAQ;AACjD,aAAOI,+BAAWH,uBAAKE,MAAM,YAAA,CAAA;AAC/B;AAFgBD;AAKhB,IAAMG,gBAAkD;EACtD,SAAS;EACT,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,SAAS;EACT,UAAU;EACV,QAAQ;AACV;AAEO,SAASC,eAAeC,UAAgB;AAC7C,QAAMC,MAAMD,SAASE,YAAY,GAAA;AACjC,QAAMC,YAAYF,QAAQ,KAAK,KAAKD,SAASI,MAAMH,GAAAA,EAAKI,YAAW;AACnE,SAAOP,cAAcK,SAAAA,KAAc;AACrC;AAJgBJ;AAmBT,SAASO,UAAUN,UAAkBJ,OAAeH,OAAAA,GAAQ;AACjE,MAAI,CAAC,YAAYc,KAAKP,QAAAA,KAAaA,SAASQ,SAAS,IAAA,EAAO,QAAOC;AAEnE,QAAMC,sBAAkBhB,uBAAKE,MAAM,QAAA;AACnC,QAAMe,gBAAYC,0BAAQF,iBAAiBV,QAAAA;AAE3C,MAAI,CAACW,UAAUE,WAAWH,kBAAkBI,oBAAAA,EAAM,QAAOL;AACzD,MAAI,KAACZ,2BAAWc,SAAAA,KAAc,KAACI,yBAASJ,SAAAA,EAAWK,OAAM,EAAI,QAAOP;AAEpE,aAAOQ,6BAAaN,SAAAA;AACtB;AAVgBL;AAaT,SAASY,cAActB,OAAeH,OAAAA,GAAQ;AACnD,QAAM0B,gBAAYzB,uBAAKE,MAAM,YAAA;AAC7B,aAAOC,2BAAWsB,SAAAA,QAAaF,6BAAaE,SAAAA,IAAaV;AAC3D;AAHgBS;AAeT,IAAME,sBAAsB;AAe5B,SAASC,YACdC,WACA1B,OAAeH,OAAAA,GACf8B,OAAkB;AAElB,QAAMC,QAAQN,cAActB,IAAAA;AAC5B,MAAI,CAAC4B,MAAO,QAAOf;AAEnB,QAAMgB,WACJ,2CAA2CH,SAAAA;IACtCI,YAAYH,KAAAA,CAAAA;;AAEnB,MAAII,OAAOH,MACRI,SAAS,MAAA,EACTC,MAAM,GAAGT,mBAAAA,GAAsB,EAC/B1B,KAAK,GAAG4B,SAAAA,GAAY,EACpBQ,QAAQ,WAAWL,QAAAA;AAItB,MAAIF,OAAOQ,UAAUtB,QAAW;AAC9BkB,WAAOA,KAAKG,QAAQ,yBAAyB,UAAUP,MAAMQ,KAAK,UAAU;EAC9E;AAEA,SAAOC,OAAOC,KAAKN,MAAM,MAAA;AAC3B;AAzBgBN;;;AGpFhB,IAAAa,kBAQO;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,IAAMC,oBAAN,MAAMA;SAAAA;;;;;;;;;;;;;;EASHC;EAER,YAC0CC,MACGC,WACLC,OACtC;SAHwCF,OAAAA;SACGC,YAAAA;SACLC,QAAAA;EACrC;;;;;;;EAUHC,QAAwB;AACtB,SAAKJ,UAAUK,YAAY,KAAKH,WAAW,KAAKD,MAAM,KAAKE,KAAK;AAChE,UAAMG,OAAO,KAAKN;AAClB,QAAI,CAACM,MAAM;AACT,YAAM,IAAIC,kCACR,+GACE;IAEN;AACA,WAAO,IAAIC,+BAAeF,MAAM;MAAEG,MAAM;IAA2B,CAAA;EACrE;;;;;;;;;;EAaAC,MAAqBC,MAA8B;AACjD,UAAMC,WAAWC,UAAUF,MAAM,KAAKV,IAAI;AAC1C,QAAI,CAACW,SAAU,OAAM,IAAIL,kCAAkB,4BAA4BI,IAAAA,IAAQ;AAE/E,WAAO,IAAIH,+BAAeI,UAAU;MAAEH,MAAMK,eAAeH,IAAAA;IAAM,CAAA;EACnE;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;A7BvFC,SAAAI,cAAA,YAAA,QAAA,KAAA,MAAA;;;;;;;;;;;;;AAAA,OAAAA,eAAA;AA8KD,SAASC,oBAAoBC,SAA6BC,QAAc;AACtE,MAAI,CAACD,SAASE,SAAS;AACrB,UAAM,IAAIC,MACR,eAAeF,MAAAA,uHAEb;EAEN;AAEA,MAAI,CAACD,QAAQI,QAAQ,OAAOJ,QAAQI,KAAKC,cAAc,YAAY;AACjE,UAAM,IAAIF,MACR,eAAeF,MAAAA,yOAGb;EAEN;AAEA,MAAID,QAAQM,gBAAgB,OAAON,QAAQM,aAAaD,cAAc,YAAY;AAChF,UAAM,IAAIF,MACR,eAAeF,MAAAA,uIAEb;EAEN;AAEAM,eAAaP,QAAQI,IAAI;AAC3B;AA3BSL;AAyET,SAASS,gBAAgBC,gBAAwBC,WAAiB;AAChE,MAAIC,YAAYF,cAAAA,EAAiB;AAKjC,MAAIG,uBAAO,WAAA,EAAaC,KACtB,6CAA6CH,SAAAA,mCAC1BA,SAAAA,iBAA0B;AAEjD;AAVSF;AAmBT,SAASM,aACPJ,WACAD,gBACAM,OACAC,iBACAC,eAA0C,CAAA,GAAE;AAE5C,SAAO;IACLC,QAAQC;IACRC,SAAS;SACJH;;;;;MAKHI,2BAAaC,SAAS;QAAC;UAAEC,MAAMb;UAAWQ,QAAQC;QAAY;OAAE;;;;;;;;;IASlEK,aAAa;MAACC;MAAmBC;MAAqBC;;IACtDC,WAAW;SACNZ;MACH;QAAEa,SAASC;QAAeC,UAAUtB;MAAe;MACnD;QAAEoB,SAASG;QAAkBD,UAAUrB;MAAU;MACjD;QAAEmB,SAASI;QAAaF,UAAUhB;MAAM;MACxCmB;;;;;MAKAC;MACAC;;IAEFC,SAAS;MAACH;;EACZ;AACF;AAxCSpB;AAwDT,IAAMwB,qBAAqB;EAAC;EAAQ;EAAU;;AAe9C,SAASC,0BAA0BC,UAAmC;AACpE,QAAMC,YAAYH,mBAAmBI,OAAO,CAACC,QAAQA,OAAQH,QAAAA;AAC7D,MAAIC,UAAUG,WAAW,EAAG;AAE5B,QAAMC,MAAMJ,UAAUG,WAAW;AACjC,QAAM,IAAIzC,MACR,uCAAuCsC,UAAUK,KAAK,IAAA,CAAA,sBACjDD,MAAM,mBAAmB,mBAAA,qEACMA,MAAM,OAAO,MAAA,wHAEzB;AAE5B;AAZSN;AAcT,SAASQ,iBAAiB/C,SAAgC;AACxD,QAAMgD,WAAW,wBAACR,aAAAA;AAChBD,8BAA0BC,QAAAA;AAC1BzC,wBAAoByC,UAAgC,cAAA;AACpD,WAAOA;EACT,GAJiB;AAMjB,MAAIxC,QAAQiD,YAAY;AACtB,WAAO;MACL;QACEpB,SAASqB;QACTD,YAAY,iCAAUE,SAAkBH,SAAS,MAAMhD,QAAQiD,WAAU,GAAKE,IAAAA,CAAAA,GAAlE;QACZC,QAAQpD,QAAQoD,UAAU,CAAA;MAC5B;;EAEJ;AAEA,QAAMC,eAAerD,QAAQsD,eAAetD,QAAQuD;AACpD,SAAO;;;OAGDvD,QAAQuD,WAAW;MAAC;QAAE1B,SAAS7B,QAAQuD;QAAUA,UAAUvD,QAAQuD;MAAS;QAAK,CAAA;IACrF;MACE1B,SAASqB;MACTD,YAAY,8BAAOO,YACjBR,SAAS,MAAMQ,QAAQC,mBAAkB,CAAA,GAD/B;MAEZL,QAAQ;QAACC;;IACX;;AAEJ;AA7BSN;AAgCF,IAAM5B,cAAN,MAAMA;EAjZb,OAiZaA;;;EACX,OAAOuC,QAAQ1D,SAA4C;AACzDD,wBAAoBC,SAAS,SAAA;AAI7B,UAAMU,YAAYiD,mBAAmB3D,QAAQuB,IAAI;AAEjD,UAAMd,iBAAiBT,QAAQ4D,UAAUA,OAAAA;AACzCpD,oBAAgBC,gBAAgBC,SAAAA;AAChCmD,sBAAkB7D,QAAQe,KAAK;AAE/B,WAAOD,aAAaJ,WAAWD,gBAAgBT,QAAQe,OAAO;MAC5D;QAAEc,SAASiC;QAAe/B,UAAU/B,QAAQE;MAAQ;MACpD;QAAE2B,SAASkC;QAAiBhC,UAAU/B,QAAQgE;MAAU;MACxD;QAAEnC,SAASoC;QAAclC,UAAU/B,QAAQkE;MAAO;MAClD;QAAErC,SAASsC;QAAapC,UAAU/B,QAAQoE;MAAM;MAChD;QAAEvC,SAASwC;QAAetC,UAAU/B,QAAQsE;MAAQ;MACpD;QAAEzC,SAAS0C;QAAiBxC,UAAU/B,QAAQwE;MAAU;MACxD;QAAE3C,SAAS4C;QAAY1C,UAAU/B,QAAQI;MAAK;;;MAG9C;QAAEyB,SAAS6C;QAAqB3C,UAAU/B,QAAQM,gBAAgBqE,kBAAAA;MAAoB;KACvF;EACH;;;;;;;;;;;;;;;;;;;;;;;;;EA0BA,OAAOC,aAAa5E,SAAiD;AACnE,QAAI,CAACA,SAASiD,cAAc,CAACjD,SAASuD,YAAY,CAACvD,SAASsD,aAAa;AACvE,YAAM,IAAInD,MACR,uIACE;IAEN;AAEA,UAAMO,YAAYiD,mBAAmB3D,QAAQuB,IAAI;AACjD,UAAMd,iBAAiBT,QAAQ4D,UAAUA,OAAAA;AACzCpD,oBAAgBC,gBAAgBC,SAAAA;AAChCmD,sBAAkB7D,QAAQe,KAAK;AAI/B,UAAM8D,UAAS,wBACbC,OACAC,UACqB;MAAElD,SAASiD;MAAO7B,YAAY8B;MAAM3B,QAAQ;QAACF;;IAAe,IAHpE;AAKf,WAAOpC,aACLJ,WACAD,gBACAT,QAAQe,OACR;SACKgC,iBAAiB/C,OAAAA;MACpB6E,QAAOf,eAAe,CAACtB,aAAaA,SAAStC,OAAO;MACpD2E,QAAOd,iBAAiB,CAACvB,aAAaA,SAASwB,SAAS;MACxDa,QAAOZ,cAAc,CAACzB,aAAaA,SAAS0B,MAAM;MAClDW,QAAOV,aAAa,CAAC3B,aAAaA,SAAS4B,KAAK;MAChDS,QAAOR,eAAe,CAAC7B,aAAaA,SAAS8B,OAAO;MACpDO,QAAON,iBAAiB,CAAC/B,aAAaA,SAASgC,SAAS;MACxDK,QAAOJ,YAAY,CAACjC,aAAaA,SAASpC,IAAI;MAC9CyE,QAAOH,qBAAqB,CAAClC,aAAaA,SAASlC,gBAAgBqE,kBAAAA,CAAAA;OAErE3E,QAAQoB,WAAW,CAAA,CAAE;EAEzB;AACF;;;;","names":["getImportMetaUrl","document","URL","__filename","href","currentScript","tagName","toUpperCase","src","baseURI","importMetaUrl","import_common","import_core","readable","excluded","import_common","ADMIN_ADAPTER","Symbol","ADMIN_AUTH","ADMIN_RESOURCE_AUTH","ADMIN_UI_ROOT","ADMIN_MOUNT_PATH","ADMIN_RESOURCES","ADMIN_OPTIONS","ADMIN_MODELS","ADMIN_HOOKS","ADMIN_ACTIONS","ADMIN_DASHBOARD","ADMIN_THEME","AdminAuthGuard","auth","canActivate","context","decision","authorize","ForbiddenError","import_common","AdminContext","createParamDecorator","_data","context","import_common","success","data","successPage","meta","failure","code","message","details","error","INTERNAL","status","HttpStatus","INTERNAL_SERVER_ERROR","code","message","mapError","error","isNestAdminError","kind","UNAUTHORIZED","FORBIDDEN","NOT_FOUND","details","model","id","BAD_REQUEST","field","refusal","fields","length","failure","constraint","CONFLICT","clientMessage","AdminExceptionFilter","logger","Logger","catch","exception","host","HttpException","mapped","Error","stack","String","body","response","switchToHttp","getResponse","json","import_common","import_common","derive","promisify","scrypt","PARAMS","N","r","p","MAXMEM","KEY_LENGTH","SALT_BYTES","SEP","String","fromCharCode","hashAdminPassword","password","length","Error","salt","randomBytes","toString","key","maxmem","join","verifyAdminPassword","stored","parsed","parse","hash","expected","Buffer","from","timingSafeEqual","undefined","scheme","n","split","Number","rounds","parallel","usable","isInteger","test","NO_SUCH_ACCOUNT","import_node_crypto","VERSION","encode","value","Buffer","from","toString","signSession","accountId","secret","lifetime","payload","sub","exp","Math","floor","Date","now","body","JSON","stringify","sign","readSession","token","undefined","cut","lastIndexOf","slice","presented","startsWith","matches","parse","length","shouldRenew","remaining","createHmac","update","digest","expected","a","b","timingSafeEqual","MIN_SECRET_LENGTH","generateSessionSecret","randomBytes","logger","Logger","DEFAULT_MAX_AGE","RUNTIME","Symbol","for","builtInRuntimeOf","auth","undefined","adminAccountOf","context","request","switchToHttp","getRequest","adminAccount","builtInAuth","options","secret","session","length","MIN_SECRET_LENGTH","Error","store","findByEmail","maxAge","cookieName","attempts","Attempts","maxAttempts","lockoutSeconds","runtime","secure","signIn","email","password","from","lockedOut","account","trim","toLowerCase","stored","passwordHash","NO_SUCH_ACCOUNT","correct","verifyAdminPassword","disabled","failed","succeeded","recordLogin","id","catch","cause","warn","String","authorize","token","cookieFrom","headers","cookie","readSession","UnauthorizedError","findById","summarise","shouldRenew","setSessionCookie","getResponse","signSession","Object","defineProperty","value","enumerable","header","name","part","split","eq","indexOf","slice","decodeURIComponent","response","writeCookie","isSecure","join","clearSessionCookie","configured","host","local","test","target","setHeader","attemptKey","address","ip","socket","remoteAddress","Map","max","seconds","key","entry","get","until","Date","now","delete","existing","since","count","set","size","locked","recent","import_common","FILTER_OPERATORS","SORT_DIRECTIONS","Set","rejectStructuredValue","name","value","undefined","Array","isArray","every","item","InvalidQueryError","KNOWN_PARAMETERS","rejectUnknownParameters","raw","unknown","Object","keys","filter","key","has","length","looksBracketed","some","includes","hint","join","toStringList","toSingleString","strings","at","parsePositiveInteger","test","JSON","stringify","parsed","Number","parseSort","entries","entry","trim","map","separator","lastIndexOf","field","slice","direction","coerceScalar","context","kind","isFinite","Date","isNaN","getTime","coerceList","split","part","parseFilters","model","firstSeparator","indexOf","secondSeparator","fieldName","operator","rawValue","FILTER_OPERATORS","fields","find","candidate","parseFilterExpression","rules","rule","parseListQuery","page","perPage","sort","filters","search","modelOf","widget","kind","undefined","model","defaultSpan","logger","Logger","MAX_BUCKETS","DAY","buildDashboard","input","declared","generated","undefined","length","widgets","generateFrom","models","labels","visible","Set","map","model","name","resolved","Promise","all","filter","widget","modelOf","has","index","resolve","labelOf","kind","title","dated","createdFieldFor","first","push","description","span","slice","base","id","defaultSpan","data","dataFor","cause","warn","String","failed","load","context","countOf","listOf","chartOf","modelFor","filtersFor","page","adapter","list","perPage","filters","compareDays","value","total","created","since","Date","now","toISOString","recent","field","operator","before","delta","Math","round","hint","label","displayFieldFor","key","primaryKey","min","limit","sort","direction","records","record","readable","Error","bucket","count","buckets","size","starts","Array","from","_","points","start","at","reduce","sum","point","find","candidate","parseFilterExpression","ALL_PERMITTED","list","read","create","update","delete","targetForeignKeyOf","field","models","relationShape","undefined","inverseRelationField","relation","from","byOrder","items","orderOf","map","item","index","order","sort","a","b","entry","toFieldDto","modelName","overrides","override","fieldOverride","name","kind","isId","isRequired","isUnique","isList","isGenerated","readOnly","isReadOnly","writeOnly","label","widget","defaultValue","enumValues","targetModel","cardinality","to","shape","detachBlockedReason","detachBlocked","targetForeignKey","toMetadataDto","permissions","actions","present","Set","model","primaryKey","displayField","displayFieldFor","can","get","icon","fields","filter","has","readableFields","model","fields","filter","field","writeOnly","MAX_BULK_DELETE","AdminService","adapter","resourceAuth","resources","overrides","hooks","actions","auth","dashboard","logger","Logger","onModuleInit","schema","getModels","known","map","name","missingResources","unknownSelectionNames","length","Error","join","missingOverrides","unknownOverrideNames","selectModels","checkBuiltInAuth","unwritable","unwritableHiddenFields","one","exposed","runtime","builtInRuntimeOf","accountModel","store","describes","undefined","some","warn","count","cause","String","getDashboard","context","models","exposedModels","permitted","permits","push","buildDashboard","declared","labels","Object","fromEntries","entries","override","label","getMetadata","visible","isVisible","toMetadataDto","permissionsFor","actionsFor","list","rawQuery","metadata","requireModel","assertAllowed","projectPage","scopeToFields","parseListQuery","findOne","id","record","RecordNotFoundError","project","create","data","assertWritable","prepared","runBefore","created","runAfter","update","updated","deleteMany","ids","InvalidQueryError","before","beforeDelete","deleted","failed","delete","message","clientMessage","listRelated","relationField","parent","target","requireRelationTarget","attachRelated","targetId","assertMayRelink","detachRelated","find","candidate","blocked","detachBlockedReason","relation","cardinality","FieldNotFoundError","targetModel","allowed","Set","projected","key","value","has","query","page","keys","isReadOnly","isGenerated","permissions","Map","operation","set","read","authorize","error","isNestAdminError","kind","hook","handler","result","args","byModel","action","scope","confirm","danger","runAction","run","decision","ForbiddenError","applyOverrides","found","ModelNotFoundError","AdminController","service","runListAction","context","model","action","success","runAction","runRecordAction","id","meta","getMetadata","dashboard","getDashboard","list","query","page","successPage","data","total","perPage","findOne","create","body","update","remove","delete","removeMany","ids","Array","isArray","some","InvalidQueryError","deleteMany","listRelated","relation","attachRelated","targetId","detachRelated","import_common","AdminAuthController","auth","session","request","runtime","token","cookieFrom","headers","cookie","cookieName","id","undefined","readSession","secret","success","account","store","findById","disabled","summarise","login","response","body","assertSameOrigin","email","password","InvalidQueryError","signIn","attemptKey","UnauthorizedError","setSessionCookie","signSession","maxAge","logout","clearSessionCookie","builtInRuntimeOf","NotFoundException","passthrough","origin","header","host","sent","URL","name","value","import_common","logger","Logger","unsafeAllowAllRequests","auth","authorize","unsafeInstances","add","WeakSet","warnIfUnsafe","has","warn","allowAllResources","authorize","DEFAULT_MOUNT_PATH","normaliseMountPath","path","undefined","TypeError","segments","split","filter","segment","length","Error","test","join","READABLE","VISIBLE","channels","hex","value","length","slice","split","map","part","join","Number","parseInt","toHex","r","g","b","Math","round","min","max","toString","padStart","luminance","rgb","channel","contrast","a","lighter","darker","sort","x","y","LIGHT_INK","DARK_INK","LIGHT_PAGE","DARK_PAGE","readableInk","brand","mix","towards","amount","visibleOn","page","role","background","floor","colour","step","HEX_COLOUR","SAFE_TEXT","SAFE_URL","APPEARANCES","Set","THEME_KEYS","assertUsableTheme","theme","brandColor","undefined","test","Error","JSON","stringify","title","logoUrl","appearance","has","unknown","Object","keys","filter","key","length","join","renderTheme","style","brandRules","globals","brand","rules","page","fill","visibleOn","text","readableInk","moduleDirectory","dirname","fileURLToPath","uiRoot","join","uiAvailable","root","existsSync","CONTENT_TYPES","contentTypeFor","fileName","dot","lastIndexOf","extension","slice","toLowerCase","readAsset","test","includes","undefined","assetsDirectory","candidate","resolve","startsWith","sep","statSync","isFile","readFileSync","readIndexHtml","indexPath","UI_BASE_PLACEHOLDER","renderShell","mountPath","theme","shell","injected","renderTheme","html","toString","split","replace","title","Buffer","from","import_common","AdminUiController","shell","root","mountPath","theme","index","renderShell","html","NotFoundException","StreamableFile","type","asset","file","contents","readAsset","contentTypeFor","_ts_decorate","assertUsableOptions","options","caller","adapter","Error","auth","authorize","resourceAuth","warnIfUnsafe","warnIfUiMissing","resolvedUiRoot","mountPath","uiAvailable","Logger","warn","defineModule","theme","optionProviders","extraImports","module","AdminModule","imports","RouterModule","register","path","controllers","AdminUiController","AdminAuthController","AdminController","providers","provide","ADMIN_UI_ROOT","useValue","ADMIN_MOUNT_PATH","ADMIN_THEME","AdminService","AdminAuthGuard","AdminExceptionFilter","exports","STRUCTURAL_OPTIONS","assertNoStructuralOptions","resolved","misplaced","filter","key","length","one","join","optionsProviders","validate","useFactory","ADMIN_OPTIONS","args","inject","factoryClass","useExisting","useClass","factory","createAdminOptions","forRoot","normaliseMountPath","uiRoot","assertUsableTheme","ADMIN_ADAPTER","ADMIN_RESOURCES","resources","ADMIN_MODELS","models","ADMIN_HOOKS","hooks","ADMIN_ACTIONS","actions","ADMIN_DASHBOARD","dashboard","ADMIN_AUTH","ADMIN_RESOURCE_AUTH","allowAllResources","forRootAsync","derive","token","read"]}
|