@gzl10/nexus-backend 0.19.0 → 0.20.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/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var init_package = __esm({
14
14
  "package.json"() {
15
15
  package_default = {
16
16
  name: "@gzl10/nexus-backend",
17
- version: "0.19.0",
17
+ version: "0.20.0",
18
18
  description: "Backend as a Service (BaaS) with Express 5, Knex and CASL",
19
19
  type: "module",
20
20
  main: "./dist/index.js",
@@ -9287,18 +9287,20 @@ function createSystemController(ctx) {
9287
9287
  /**
9288
9288
  * GET /system/capabilities
9289
9289
  * Public endpoint — no authentication required.
9290
- * Returns backend version and registered plugin codes.
9290
+ * Resolves all registered capabilities (core + plugin-contributed).
9291
9291
  */
9292
- getCapabilities(_req, res) {
9293
- res.set("Cache-Control", "public, max-age=300");
9294
- const manifest = engine.getCoreManifest();
9295
- const plugins = engine.getPlugins();
9296
- const body = {
9297
- version: manifest.version,
9298
- plugins: plugins.map((p) => p.code),
9299
- locales: ctx.locales
9300
- };
9301
- res.json(body);
9292
+ async getCapabilities(_req, res) {
9293
+ try {
9294
+ res.set("Cache-Control", "public, max-age=300");
9295
+ const body = await ctx.capabilities.resolve();
9296
+ res.json(body);
9297
+ } catch (err) {
9298
+ ctx.core.logger.error({ err }, "Failed to resolve capabilities");
9299
+ res.set("Cache-Control", "no-cache");
9300
+ res.status(500).json({
9301
+ error: { code: "INTERNAL_ERROR", message: "Failed to resolve capabilities" }
9302
+ });
9303
+ }
9302
9304
  }
9303
9305
  };
9304
9306
  function toManifestDTO(manifest, req) {
@@ -9871,8 +9873,8 @@ function registerCoreVars(registry2) {
9871
9873
  registry2.register("auth", "core", [
9872
9874
  {
9873
9875
  name: "AUTH_SECRET",
9874
- description: { en: "JWT signing secret. Must be at least 32 characters. Use a cryptographically random string in production", es: "Clave de firma JWT. M\xEDnimo 32 caracteres. Usar una cadena criptogr\xE1ficamente aleatoria en producci\xF3n" },
9875
- required: true,
9876
+ description: { en: "JWT signing secret (optional). If not set, auto-generated and persisted in database. Must be at least 32 characters. Set explicitly for multi-instance deployments", es: "Clave de firma JWT (opcional). Si no se establece, se genera autom\xE1ticamente y se persiste en la base de datos. M\xEDnimo 32 caracteres. Establecer expl\xEDcitamente para despliegues multi-instancia" },
9877
+ required: false,
9876
9878
  sensitive: true
9877
9879
  },
9878
9880
  {
@@ -10389,96 +10391,70 @@ var init_system = __esm({
10389
10391
  }
10390
10392
  });
10391
10393
 
10392
- // src/modules/ui-settings/ui-branding.entity.ts
10393
- import { useTextField as useTextField7, useImageField } from "@gzl10/nexus-sdk/fields";
10394
- var uiBrandingEntity;
10395
- var init_ui_branding_entity = __esm({
10396
- "src/modules/ui-settings/ui-branding.entity.ts"() {
10394
+ // src/modules/ui-settings/ui-style-guide.entity.ts
10395
+ import { useTextField as useTextField7, useImageField, useSelectField as useSelectField6, useColorField, useNumberField as useNumberField4, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
10396
+ var uiStyleGuideEntity;
10397
+ var init_ui_style_guide_entity = __esm({
10398
+ "src/modules/ui-settings/ui-style-guide.entity.ts"() {
10397
10399
  "use strict";
10398
- uiBrandingEntity = {
10400
+ uiStyleGuideEntity = {
10399
10401
  type: "single",
10400
10402
  realtime: "sync",
10401
- key: "ui_branding",
10402
- label: { en: "Branding", es: "Identidad Corporativa" },
10403
- icon: "mdi:palette-swatch-outline",
10403
+ key: "ui_style_guide",
10404
+ label: { en: "Style Guide", es: "Gu\xEDa de Estilos" },
10405
+ icon: "mdi:palette-outline",
10404
10406
  public: true,
10405
- routePrefix: "/ui-branding",
10407
+ routePrefix: "/ui-style-guide",
10406
10408
  defaults: {
10407
10409
  appName: "Nexus",
10408
10410
  logo: null,
10409
10411
  logoDark: null,
10410
- favicon: null
10412
+ favicon: null,
10413
+ primaryColor: "#3B82F6",
10414
+ font: "space-grotesk",
10415
+ typographyScale: "default",
10416
+ borderRadius: 8,
10417
+ glassEffect: true,
10418
+ theme: "system",
10419
+ loginLayout: "centered"
10411
10420
  },
10412
10421
  fields: {
10413
10422
  appName: useTextField7({
10414
10423
  label: { en: "App Name", es: "Nombre de la App" },
10415
- hint: { en: "Displayed in the header, browser tab and emails", es: "Se muestra en el header, pesta\xF1a del navegador y emails" },
10424
+ hint: { en: "Displayed in header, browser tab and emails", es: "Se muestra en header, pesta\xF1a del navegador y emails" },
10416
10425
  size: 100,
10417
10426
  required: true
10418
10427
  }),
10419
10428
  logo: useImageField({
10420
10429
  label: { en: "Logo (Light Theme)", es: "Logo (Tema Claro)" },
10421
- hint: { en: "Recommended: SVG or PNG with transparent background, max 200px height", es: "Recomendado: SVG o PNG con fondo transparente, m\xE1x 200px de alto" },
10430
+ hint: { en: "SVG or PNG with transparent background, max 200px height", es: "SVG o PNG con fondo transparente, m\xE1x 200px de alto" },
10422
10431
  folder: "branding",
10423
10432
  isPublic: true,
10424
10433
  dedupe: true
10425
10434
  }),
10426
10435
  logoDark: useImageField({
10427
10436
  label: { en: "Logo (Dark Theme)", es: "Logo (Tema Oscuro)" },
10428
- hint: { en: "Optional: Use a lighter version for dark backgrounds", es: "Opcional: Usa una versi\xF3n m\xE1s clara para fondos oscuros" },
10437
+ hint: { en: "Lighter version for dark backgrounds", es: "Versi\xF3n m\xE1s clara para fondos oscuros" },
10429
10438
  folder: "branding",
10430
10439
  isPublic: true,
10431
10440
  dedupe: true
10432
10441
  }),
10433
10442
  favicon: useImageField({
10434
10443
  label: { en: "Favicon", es: "Favicon" },
10435
- hint: { en: "Browser tab icon. Recommended: 32x32 or 64x64 PNG/ICO", es: "Icono de pesta\xF1a del navegador. Recomendado: 32x32 o 64x64 PNG/ICO" },
10444
+ hint: { en: "32x32 or 64x64 PNG/ICO", es: "32x32 o 64x64 PNG/ICO" },
10436
10445
  accept: "image/x-icon,image/png,image/svg+xml",
10437
10446
  maxSize: "256KB",
10438
10447
  folder: "branding",
10439
10448
  isPublic: true,
10440
10449
  dedupe: true
10441
- })
10442
- },
10443
- casl: {
10444
- subject: "UiBranding",
10445
- permissions: {
10446
- ADMIN: { actions: ["read", "update"] }
10447
- }
10448
- }
10449
- };
10450
- }
10451
- });
10452
-
10453
- // src/modules/ui-settings/ui-theme.entity.ts
10454
- import { useSelectField as useSelectField6, useColorField } from "@gzl10/nexus-sdk/fields";
10455
- var uiThemeEntity;
10456
- var init_ui_theme_entity = __esm({
10457
- "src/modules/ui-settings/ui-theme.entity.ts"() {
10458
- "use strict";
10459
- uiThemeEntity = {
10460
- type: "single",
10461
- realtime: "sync",
10462
- key: "ui_theme",
10463
- label: { en: "Theme & Colors", es: "Tema y Colores" },
10464
- icon: "mdi:palette",
10465
- public: true,
10466
- routePrefix: "/ui-theme",
10467
- defaults: {
10468
- // Typography
10469
- font: "space-grotesk",
10470
- // Theme & Colors
10471
- theme: "system",
10472
- primaryColor: "#3B82F6",
10473
- dopamineTheme: "none",
10474
- // Layout
10475
- loginLayout: "centered"
10476
- },
10477
- fields: {
10478
- // === Typography ===
10450
+ }),
10451
+ primaryColor: useColorField({
10452
+ label: { en: "Primary Color", es: "Color Principal" },
10453
+ hint: { en: "Accent color for buttons, links and highlights", es: "Color de acento para botones, enlaces y resaltados" }
10454
+ }),
10479
10455
  font: useSelectField6({
10480
10456
  label: { en: "Font", es: "Fuente" },
10481
- hint: { en: "Primary font for headings and UI elements", es: "Fuente principal para t\xEDtulos y elementos de interfaz" },
10457
+ hint: { en: "Primary font for headings and UI", es: "Fuente principal para t\xEDtulos e interfaz" },
10482
10458
  options: [
10483
10459
  { value: "space-grotesk", label: "Space Grotesk" },
10484
10460
  { value: "inter", label: "Inter" },
@@ -10488,37 +10464,38 @@ var init_ui_theme_entity = __esm({
10488
10464
  { value: "system", label: { en: "System Default", es: "Sistema" } }
10489
10465
  ]
10490
10466
  }),
10491
- // === Theme & Colors ===
10467
+ typographyScale: useSelectField6({
10468
+ label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
10469
+ hint: { en: "Font size scaling (WCAG 1.4.4)", es: "Escala de tama\xF1os de fuente (WCAG 1.4.4)" },
10470
+ options: [
10471
+ { value: "compact", label: { en: "Compact", es: "Compacta" } },
10472
+ { value: "default", label: { en: "Default", es: "Por defecto" } },
10473
+ { value: "relaxed", label: { en: "Relaxed", es: "Relajada" } }
10474
+ ]
10475
+ }),
10476
+ borderRadius: useNumberField4({
10477
+ label: { en: "Border Radius", es: "Radio de Bordes" },
10478
+ hint: { en: "Corner roundness in pixels (0 = sharp, 16 = very round)", es: "Redondeo de esquinas en p\xEDxeles (0 = cuadrado, 16 = muy redondo)" },
10479
+ defaultValue: 8,
10480
+ validation: { min: 0, max: 16 }
10481
+ }),
10482
+ glassEffect: useSwitchField2({
10483
+ label: { en: "Glass Effect", es: "Efecto Glass" },
10484
+ hint: { en: "Glassmorphism blur on cards and modals", es: "Desenfoque glassmorphism en cards y modales" },
10485
+ defaultValue: true
10486
+ }),
10492
10487
  theme: useSelectField6({
10493
10488
  label: { en: "Theme", es: "Tema" },
10494
- hint: { en: "System follows your device preferences", es: "Sistema sigue las preferencias de tu dispositivo" },
10489
+ hint: { en: "System follows device preferences", es: "Sistema sigue las preferencias del dispositivo" },
10495
10490
  options: [
10496
10491
  { value: "light", label: { en: "Light", es: "Claro" } },
10497
10492
  { value: "dark", label: { en: "Dark", es: "Oscuro" } },
10498
10493
  { value: "system", label: { en: "System", es: "Sistema" } }
10499
10494
  ]
10500
10495
  }),
10501
- dopamineTheme: useSelectField6({
10502
- label: { en: "Dopamine Theme", es: "Tema Dopamina" },
10503
- hint: { en: "Vibrant color presets optimized for light and dark modes (2025/2026 trends)", es: "Presets de colores vibrantes optimizados para modo claro y oscuro (tendencias 2025/2026)" },
10504
- options: [
10505
- { value: "none", label: { en: "None (Custom Color)", es: "Ninguno (Color personalizado)" } },
10506
- { value: "electric", label: { en: "Electric (Cobalt Blue)", es: "El\xE9ctrico (Azul Cobalto)" } },
10507
- { value: "sunset", label: { en: "Sunset (Coral Red)", es: "Atardecer (Rojo Coral)" } },
10508
- { value: "ocean", label: { en: "Ocean (Teal)", es: "Oc\xE9ano (Verde Azulado)" } },
10509
- { value: "forest", label: { en: "Forest (Mint)", es: "Bosque (Menta)" } },
10510
- { value: "lavender", label: { en: "Lavender (Violet)", es: "Lavanda (Violeta)" } },
10511
- { value: "cherry", label: { en: "Cherry (Fuchsia)", es: "Cereza (Fucsia)" } },
10512
- { value: "amber", label: { en: "Amber (Golden)", es: "\xC1mbar (Dorado)" } },
10513
- { value: "tangerine", label: { en: "Tangerine (Orange)", es: "Mandarina (Naranja)" } },
10514
- { value: "slate", label: { en: "Slate (Cool Gray)", es: "Pizarra (Gris Fr\xEDo)" } },
10515
- { value: "bronze", label: { en: "Bronze (Earth)", es: "Bronce (Tierra)" } }
10516
- ]
10517
- }),
10518
- // === Login Layout ===
10519
10496
  loginLayout: useSelectField6({
10520
10497
  label: { en: "Login Layout", es: "Dise\xF1o de Login" },
10521
- hint: { en: "Visual layout for authentication pages", es: "Dise\xF1o visual para p\xE1ginas de autenticaci\xF3n" },
10498
+ hint: { en: "Auth page visual layout", es: "Dise\xF1o visual de p\xE1ginas de autenticaci\xF3n" },
10522
10499
  options: [
10523
10500
  { value: "centered", label: { en: "Centered", es: "Centrado" } },
10524
10501
  { value: "split", label: { en: "Split", es: "Dividido" } },
@@ -10526,140 +10503,10 @@ var init_ui_theme_entity = __esm({
10526
10503
  { value: "floating", label: { en: "Floating", es: "Flotante" } },
10527
10504
  { value: "minimal", label: { en: "Minimal", es: "Minimalista" } }
10528
10505
  ]
10529
- }),
10530
- primaryColor: {
10531
- ...useColorField({
10532
- label: { en: "Primary Color", es: "Color Principal" },
10533
- hint: { en: "Custom accent color for buttons, links and highlights", es: "Color de acento personalizado para botones, enlaces y resaltados" }
10534
- }),
10535
- // Hide when a dopamine theme is active (show only if 'none')
10536
- hidden: { field: "dopamineTheme", $ne: "none" }
10537
- }
10538
- },
10539
- casl: {
10540
- subject: "UiTheme",
10541
- permissions: {
10542
- ADMIN: { actions: ["read", "update"] }
10543
- }
10544
- }
10545
- };
10546
- }
10547
- });
10548
-
10549
- // src/modules/ui-settings/ui-effects.entity.ts
10550
- import { useSelectField as useSelectField7, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
10551
- var uiEffectsEntity;
10552
- var init_ui_effects_entity = __esm({
10553
- "src/modules/ui-settings/ui-effects.entity.ts"() {
10554
- "use strict";
10555
- uiEffectsEntity = {
10556
- type: "single",
10557
- realtime: "sync",
10558
- key: "ui_effects",
10559
- label: { en: "Visual Effects", es: "Efectos Visuales" },
10560
- icon: "mdi:shimmer",
10561
- public: true,
10562
- routePrefix: "/ui-effects",
10563
- defaults: {
10564
- glassIntensity: "medium",
10565
- borderStyle: "rounded",
10566
- enableAnimations: true,
10567
- enableOrganicShapes: false
10568
- },
10569
- fields: {
10570
- glassIntensity: useSelectField7({
10571
- label: { en: "Glass Intensity", es: "Intensidad Glass" },
10572
- hint: { en: "Glassmorphism blur effect on cards and modals", es: "Efecto de desenfoque glassmorphism en cards y modales" },
10573
- options: [
10574
- { value: "none", label: { en: "None", es: "Ninguno" } },
10575
- { value: "low", label: { en: "Low", es: "Baja" } },
10576
- { value: "medium", label: { en: "Medium", es: "Media" } },
10577
- { value: "high", label: { en: "High", es: "Alta" } }
10578
- ]
10579
- }),
10580
- borderStyle: useSelectField7({
10581
- label: { en: "Border Style", es: "Estilo de Bordes" },
10582
- hint: { en: "Corner radius for buttons, cards and inputs", es: "Radio de esquinas para botones, cards e inputs" },
10583
- options: [
10584
- { value: "sharp", label: { en: "Sharp", es: "Cuadrados" } },
10585
- { value: "rounded", label: { en: "Rounded", es: "Redondeados" } },
10586
- { value: "organic", label: { en: "Organic", es: "Org\xE1nicos" } }
10587
- ]
10588
- }),
10589
- enableAnimations: useSwitchField2({
10590
- label: { en: "Enable Animations", es: "Habilitar Animaciones" },
10591
- hint: { en: "Micro-interactions and transitions (hover, focus, page changes)", es: "Micro-interacciones y transiciones (hover, focus, cambios de p\xE1gina)" },
10592
- defaultValue: true,
10593
- meta: { sortable: true }
10594
- }),
10595
- enableOrganicShapes: useSwitchField2({
10596
- label: { en: "Enable Organic Shapes", es: "Habilitar Formas Org\xE1nicas" },
10597
- hint: { en: "Asymmetric border-radius for a more natural, playful look", es: "Border-radius asim\xE9trico para un aspecto m\xE1s natural y divertido" },
10598
- defaultValue: false,
10599
- meta: { sortable: true },
10600
- hidden: { field: "borderStyle", $ne: "organic" }
10601
10506
  })
10602
10507
  },
10603
10508
  casl: {
10604
- subject: "UiEffects",
10605
- permissions: {
10606
- ADMIN: { actions: ["read", "update"] }
10607
- }
10608
- }
10609
- };
10610
- }
10611
- });
10612
-
10613
- // src/modules/ui-settings/ui-accessibility.entity.ts
10614
- import { useSelectField as useSelectField8, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
10615
- var uiAccessibilityEntity;
10616
- var init_ui_accessibility_entity = __esm({
10617
- "src/modules/ui-settings/ui-accessibility.entity.ts"() {
10618
- "use strict";
10619
- uiAccessibilityEntity = {
10620
- type: "single",
10621
- realtime: "sync",
10622
- key: "ui_accessibility",
10623
- label: { en: "Accessibility", es: "Accesibilidad" },
10624
- icon: "mdi:human",
10625
- public: true,
10626
- routePrefix: "/ui-accessibility",
10627
- defaults: {
10628
- typographyScale: "default",
10629
- reducedMotion: false,
10630
- highContrast: false
10631
- },
10632
- fields: {
10633
- typographyScale: useSelectField8({
10634
- label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
10635
- hint: { en: "Adjusts font sizes for better readability (WCAG 1.4.4)", es: "Ajusta tama\xF1os de fuente para mejor legibilidad (WCAG 1.4.4)" },
10636
- options: [
10637
- { value: "compact", label: { en: "Compact (smaller)", es: "Compacta (m\xE1s peque\xF1a)" } },
10638
- { value: "default", label: { en: "Default", es: "Por defecto" } },
10639
- { value: "relaxed", label: { en: "Relaxed (larger)", es: "Relajada (m\xE1s grande)" } }
10640
- ]
10641
- }),
10642
- reducedMotion: useSwitchField3({
10643
- label: { en: "Reduced Motion", es: "Movimiento Reducido" },
10644
- defaultValue: false,
10645
- meta: { sortable: true },
10646
- hint: {
10647
- en: "Minimizes animations for users sensitive to motion (WCAG 2.1)",
10648
- es: "Minimiza animaciones para usuarios sensibles al movimiento (WCAG 2.1)"
10649
- }
10650
- }),
10651
- highContrast: useSwitchField3({
10652
- label: { en: "High Contrast", es: "Alto Contraste" },
10653
- defaultValue: false,
10654
- meta: { sortable: true },
10655
- hint: {
10656
- en: "Increases text contrast for better readability (WCAG AAA)",
10657
- es: "Aumenta el contraste del texto para mejor legibilidad (WCAG AAA)"
10658
- }
10659
- })
10660
- },
10661
- casl: {
10662
- subject: "UiAccessibility",
10509
+ subject: "UiStyleGuide",
10663
10510
  permissions: {
10664
10511
  ADMIN: { actions: ["read", "update"] }
10665
10512
  }
@@ -10669,44 +10516,62 @@ var init_ui_accessibility_entity = __esm({
10669
10516
  });
10670
10517
 
10671
10518
  // src/modules/ui-settings/index.ts
10519
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
10520
+ import { join as join6 } from "path";
10672
10521
  var uiSettingsModule;
10673
10522
  var init_ui_settings = __esm({
10674
10523
  "src/modules/ui-settings/index.ts"() {
10675
10524
  "use strict";
10676
- init_ui_branding_entity();
10677
- init_ui_theme_entity();
10678
- init_ui_effects_entity();
10679
- init_ui_accessibility_entity();
10680
- init_ui_branding_entity();
10681
- init_ui_theme_entity();
10682
- init_ui_effects_entity();
10683
- init_ui_accessibility_entity();
10525
+ init_ui_style_guide_entity();
10526
+ init_ui_style_guide_entity();
10684
10527
  uiSettingsModule = {
10685
10528
  name: "ui-settings",
10686
- label: { en: "UI Settings", es: "Configuraci\xF3n de UI" },
10529
+ label: { en: "Style Guide", es: "Gu\xEDa de Estilos" },
10687
10530
  icon: "mdi:palette-outline",
10688
10531
  description: {
10689
- en: "User interface configuration: branding, themes, visual effects, and accessibility",
10690
- es: "Configuraci\xF3n de interfaz de usuario: marca, temas, efectos visuales y accesibilidad"
10532
+ en: "Brand identity and visual style configuration",
10533
+ es: "Configuraci\xF3n de identidad de marca y estilo visual"
10691
10534
  },
10692
10535
  type: "core",
10693
10536
  category: "settings",
10694
10537
  dependencies: ["logger"],
10695
- definitions: [
10696
- uiBrandingEntity,
10697
- uiThemeEntity,
10698
- uiEffectsEntity,
10699
- uiAccessibilityEntity
10700
- ],
10701
- routePrefix: "/ui-settings"
10538
+ definitions: [uiStyleGuideEntity],
10539
+ routePrefix: "/ui-settings",
10540
+ /**
10541
+ * Seed style guide from data/seeds/ui-style-guide.json if it exists.
10542
+ * Idempotent: only inserts if no record exists yet.
10543
+ *
10544
+ * Workflow:
10545
+ * 1. Configure style guide in dev via admin UI
10546
+ * 2. Export: GET /api/v1/ui-settings/ui-style-guide → save to data/seeds/ui-style-guide.json
10547
+ * 3. Commit the seed file
10548
+ * 4. On other environments, seed runs automatically on startup
10549
+ */
10550
+ seed: async (ctx) => {
10551
+ const db2 = ctx.db.knex;
10552
+ const existing = await db2("single_records").where("key", "ui_style_guide").first();
10553
+ if (existing) return;
10554
+ const seedPath = join6(process.cwd(), "data", "seeds", "ui-style-guide.json");
10555
+ if (!existsSync5(seedPath)) return;
10556
+ try {
10557
+ const seedData = JSON.parse(readFileSync4(seedPath, "utf-8"));
10558
+ await db2("single_records").insert({
10559
+ key: "ui_style_guide",
10560
+ value: JSON.stringify(seedData)
10561
+ });
10562
+ ctx.core.logger.info("Seeded ui_style_guide from data/seeds/ui-style-guide.json");
10563
+ } catch {
10564
+ ctx.core.logger.warn("Failed to parse data/seeds/ui-style-guide.json, skipping seed");
10565
+ }
10566
+ }
10702
10567
  };
10703
10568
  }
10704
10569
  });
10705
10570
 
10706
10571
  // src/modules/storage/drivers/filesystem.driver.ts
10707
- import { createReadStream, createWriteStream, existsSync as existsSync5, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
10572
+ import { createReadStream, createWriteStream, existsSync as existsSync6, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
10708
10573
  import { readFile, readdir, stat, copyFile, rename, mkdir } from "fs/promises";
10709
- import { join as join6, dirname as dirname4, extname, basename } from "path";
10574
+ import { join as join7, dirname as dirname4, extname, basename } from "path";
10710
10575
  import { createHash } from "crypto";
10711
10576
  var FilesystemDriver;
10712
10577
  var init_filesystem_driver = __esm({
@@ -10720,7 +10585,7 @@ var init_filesystem_driver = __esm({
10720
10585
  this.basePath = config3.basePath;
10721
10586
  this.baseUrl = config3.baseUrl;
10722
10587
  this.generateId = config3.generateId;
10723
- if (!existsSync5(this.basePath)) {
10588
+ if (!existsSync6(this.basePath)) {
10724
10589
  mkdirSync2(this.basePath, { recursive: true });
10725
10590
  }
10726
10591
  }
@@ -10730,9 +10595,9 @@ var init_filesystem_driver = __esm({
10730
10595
  const diskFilename = `${id}${ext}`;
10731
10596
  const folder = options?.folder || "";
10732
10597
  const relativePath = folder ? `${folder}/${diskFilename}` : diskFilename;
10733
- const fullPath = join6(this.basePath, relativePath);
10598
+ const fullPath = join7(this.basePath, relativePath);
10734
10599
  const dir = dirname4(fullPath);
10735
- if (!existsSync5(dir)) {
10600
+ if (!existsSync6(dir)) {
10736
10601
  mkdirSync2(dir, { recursive: true });
10737
10602
  }
10738
10603
  const hash = createHash("sha256").update(buffer).digest("hex");
@@ -10756,25 +10621,25 @@ var init_filesystem_driver = __esm({
10756
10621
  };
10757
10622
  }
10758
10623
  async get(path4) {
10759
- const fullPath = join6(this.basePath, path4);
10760
- if (!existsSync5(fullPath)) {
10624
+ const fullPath = join7(this.basePath, path4);
10625
+ if (!existsSync6(fullPath)) {
10761
10626
  throw new Error(`File not found: ${path4}`);
10762
10627
  }
10763
10628
  return createReadStream(fullPath);
10764
10629
  }
10765
10630
  async getBuffer(path4) {
10766
- const fullPath = join6(this.basePath, path4);
10631
+ const fullPath = join7(this.basePath, path4);
10767
10632
  return readFile(fullPath);
10768
10633
  }
10769
10634
  async delete(path4) {
10770
- const fullPath = join6(this.basePath, path4);
10771
- if (existsSync5(fullPath)) {
10635
+ const fullPath = join7(this.basePath, path4);
10636
+ if (existsSync6(fullPath)) {
10772
10637
  unlinkSync(fullPath);
10773
10638
  }
10774
10639
  }
10775
10640
  async exists(path4) {
10776
- const fullPath = join6(this.basePath, path4);
10777
- return existsSync5(fullPath);
10641
+ const fullPath = join7(this.basePath, path4);
10642
+ return existsSync6(fullPath);
10778
10643
  }
10779
10644
  getUrl(path4) {
10780
10645
  if (!this.baseUrl) return null;
@@ -10784,14 +10649,14 @@ var init_filesystem_driver = __esm({
10784
10649
  // EXTENDED METHODS
10785
10650
  // ============================================================================
10786
10651
  async list(folder) {
10787
- const targetPath = folder ? join6(this.basePath, folder) : this.basePath;
10788
- if (!existsSync5(targetPath)) {
10652
+ const targetPath = folder ? join7(this.basePath, folder) : this.basePath;
10653
+ if (!existsSync6(targetPath)) {
10789
10654
  return [];
10790
10655
  }
10791
10656
  const entries = await readdir(targetPath, { withFileTypes: true });
10792
10657
  const results = [];
10793
10658
  for (const entry of entries) {
10794
- const entryPath = join6(targetPath, entry.name);
10659
+ const entryPath = join7(targetPath, entry.name);
10795
10660
  const relativePath = folder ? `${folder}/${entry.name}` : entry.name;
10796
10661
  const stats = await stat(entryPath);
10797
10662
  results.push({
@@ -10805,10 +10670,10 @@ var init_filesystem_driver = __esm({
10805
10670
  return results;
10806
10671
  }
10807
10672
  async copy(src, dst) {
10808
- const srcPath = join6(this.basePath, src);
10809
- const dstPath = join6(this.basePath, dst);
10673
+ const srcPath = join7(this.basePath, src);
10674
+ const dstPath = join7(this.basePath, dst);
10810
10675
  const dstDir = dirname4(dstPath);
10811
- if (!existsSync5(dstDir)) {
10676
+ if (!existsSync6(dstDir)) {
10812
10677
  await mkdir(dstDir, { recursive: true });
10813
10678
  }
10814
10679
  await copyFile(srcPath, dstPath);
@@ -10831,10 +10696,10 @@ var init_filesystem_driver = __esm({
10831
10696
  };
10832
10697
  }
10833
10698
  async move(src, dst) {
10834
- const srcPath = join6(this.basePath, src);
10835
- const dstPath = join6(this.basePath, dst);
10699
+ const srcPath = join7(this.basePath, src);
10700
+ const dstPath = join7(this.basePath, dst);
10836
10701
  const dstDir = dirname4(dstPath);
10837
- if (!existsSync5(dstDir)) {
10702
+ if (!existsSync6(dstDir)) {
10838
10703
  await mkdir(dstDir, { recursive: true });
10839
10704
  }
10840
10705
  await rename(srcPath, dstPath);
@@ -10857,7 +10722,7 @@ var init_filesystem_driver = __esm({
10857
10722
  };
10858
10723
  }
10859
10724
  async getMetadata(path4) {
10860
- const fullPath = join6(this.basePath, path4);
10725
+ const fullPath = join7(this.basePath, path4);
10861
10726
  const stats = await stat(fullPath);
10862
10727
  return {
10863
10728
  path: path4,
@@ -10871,8 +10736,8 @@ var init_filesystem_driver = __esm({
10871
10736
  throw new Error("Signed URLs are not supported by the filesystem driver");
10872
10737
  }
10873
10738
  async createFolder(path4) {
10874
- const fullPath = join6(this.basePath, path4);
10875
- if (!existsSync5(fullPath)) {
10739
+ const fullPath = join7(this.basePath, path4);
10740
+ if (!existsSync6(fullPath)) {
10876
10741
  await mkdir(fullPath, { recursive: true });
10877
10742
  }
10878
10743
  }
@@ -11103,8 +10968,8 @@ var init_s3_driver = __esm({
11103
10968
  });
11104
10969
 
11105
10970
  // src/modules/storage/storage.config.ts
11106
- import { join as join7, isAbsolute } from "path";
11107
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
10971
+ import { join as join8, isAbsolute } from "path";
10972
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
11108
10973
  function getDefaultScope(driver) {
11109
10974
  return driver === "s3" ? DEFAULT_S3_SCOPE : DEFAULT_FILESYSTEM_SCOPE;
11110
10975
  }
@@ -11119,7 +10984,7 @@ function resolveStoragePath(path4, projPath) {
11119
10984
  return path4;
11120
10985
  }
11121
10986
  const cleanPath = path4.startsWith("./") ? path4.slice(2) : path4;
11122
- return join7(projPath, "data", cleanPath);
10987
+ return join8(projPath, "data", cleanPath);
11123
10988
  }
11124
10989
  async function getConfigByScope(db2, scope) {
11125
10990
  if (!generateIdFn) {
@@ -11150,7 +11015,7 @@ function buildConfigFromRow(row) {
11150
11015
  if (row.driver === "filesystem") {
11151
11016
  const fsMeta = metadata;
11152
11017
  config3.basePath = resolveStoragePath(fsMeta.basePath || "./storage", projectPath);
11153
- if (!existsSync6(config3.basePath)) {
11018
+ if (!existsSync7(config3.basePath)) {
11154
11019
  mkdirSync3(config3.basePath, { recursive: true });
11155
11020
  }
11156
11021
  } else if (row.driver === "s3") {
@@ -11182,7 +11047,7 @@ function buildConfigFromEnv(driver) {
11182
11047
  if (driver === "filesystem") {
11183
11048
  const rawPath = process.env["STORAGE_PATH"] || "./storage";
11184
11049
  config3.basePath = resolveStoragePath(rawPath, projectPath);
11185
- if (!existsSync6(config3.basePath)) {
11050
+ if (!existsSync7(config3.basePath)) {
11186
11051
  mkdirSync3(config3.basePath, { recursive: true });
11187
11052
  }
11188
11053
  } else if (driver === "s3") {
@@ -11621,7 +11486,7 @@ var init_storage_service = __esm({
11621
11486
  });
11622
11487
 
11623
11488
  // src/modules/storage/storage.entity.ts
11624
- import { useIdField as useIdField3, useTextField as useTextField8, useSelectField as useSelectField9, useUrlField, useNumberField as useNumberField4, useCheckboxField as useCheckboxField3, useJsonField as useJsonField2, useMetadataField, usePublicField } from "@gzl10/nexus-sdk/fields";
11489
+ import { useIdField as useIdField3, useTextField as useTextField8, useSelectField as useSelectField7, useUrlField, useNumberField as useNumberField5, useCheckboxField as useCheckboxField3, useJsonField as useJsonField2, useMetadataField, usePublicField } from "@gzl10/nexus-sdk/fields";
11625
11490
  var DEFAULT_MAX_SIZE2, storageConfigEntity, storageFilesEntity;
11626
11491
  var init_storage_entity = __esm({
11627
11492
  "src/modules/storage/storage.entity.ts"() {
@@ -11656,7 +11521,7 @@ var init_storage_entity = __esm({
11656
11521
  hint: { en: "Unique identifier (e.g. default_filesystem, default_s3)", es: "Identificador \xFAnico (ej: default_filesystem, default_s3)" }
11657
11522
  }),
11658
11523
  driver: {
11659
- ...useSelectField9({
11524
+ ...useSelectField7({
11660
11525
  label: { en: "Driver", es: "Controlador" },
11661
11526
  required: true,
11662
11527
  hint: { en: "Storage backend", es: "Backend de almacenamiento" },
@@ -11673,7 +11538,7 @@ var init_storage_entity = __esm({
11673
11538
  size: 500,
11674
11539
  nullable: true
11675
11540
  }),
11676
- max_file_size: useNumberField4({
11541
+ max_file_size: useNumberField5({
11677
11542
  label: { en: "Max File Size (bytes)", es: "Tama\xF1o m\xE1ximo de archivo (bytes)" },
11678
11543
  hint: { en: "Maximum size in bytes (default: 10MB = 10485760)", es: "Tama\xF1o m\xE1ximo en bytes (default: 10MB = 10485760)" },
11679
11544
  nullable: false,
@@ -11866,7 +11731,7 @@ var init_storage_entity = __esm({
11866
11731
  index: true,
11867
11732
  meta: { sortable: true, searchable: true }
11868
11733
  }),
11869
- size: useNumberField4({
11734
+ size: useNumberField5({
11870
11735
  label: { en: "Size", es: "Tama\xF1o" },
11871
11736
  required: true,
11872
11737
  nullable: false,
@@ -12408,7 +12273,7 @@ var init_storage = __esm({
12408
12273
  });
12409
12274
 
12410
12275
  // src/modules/users/users.entity.ts
12411
- import { useIdField as useIdField4, useSelectField as useSelectField10, useEmailField, usePasswordField, useTextField as useTextField9, useDatetimeField as useDatetimeField3, useCheckboxField as useCheckboxField4, useImageField as useImageField2, useNameField as useNameField2, useMetadataField as useMetadataField2, useDescriptionField as useDescriptionField2 } from "@gzl10/nexus-sdk/fields";
12276
+ import { useIdField as useIdField4, useSelectField as useSelectField8, useEmailField, usePasswordField, useTextField as useTextField9, useDatetimeField as useDatetimeField3, useCheckboxField as useCheckboxField4, useImageField as useImageField2, useNameField as useNameField2, useMetadataField as useMetadataField2, useDescriptionField as useDescriptionField2 } from "@gzl10/nexus-sdk/fields";
12412
12277
  import { z as z2 } from "zod";
12413
12278
  var userEntity, roleEntity, userRoleEntity;
12414
12279
  var init_users_entity = __esm({
@@ -12482,7 +12347,7 @@ var init_users_entity = __esm({
12482
12347
  label: { en: "Marketing Opt-in", es: "Aceptar marketing" },
12483
12348
  meta: { exportable: true, showInForm: false, showInDisplay: false }
12484
12349
  }),
12485
- locale: useSelectField10({
12350
+ locale: useSelectField8({
12486
12351
  label: { en: "Language", es: "Idioma" },
12487
12352
  options: [
12488
12353
  { value: "es", label: { en: "Spanish", es: "Espa\xF1ol" } },
@@ -12492,13 +12357,13 @@ var init_users_entity = __esm({
12492
12357
  meta: { sortable: true },
12493
12358
  defaultValue: "en"
12494
12359
  }),
12495
- timezone: useSelectField10({
12360
+ timezone: useSelectField8({
12496
12361
  label: { en: "Timezone", es: "Zona horaria" },
12497
12362
  master: "timezones",
12498
12363
  meta: { sortable: true },
12499
12364
  defaultValue: "timezones:Europe/Madrid"
12500
12365
  }),
12501
- type: useSelectField10({
12366
+ type: useSelectField8({
12502
12367
  label: { en: "Type", es: "Tipo" },
12503
12368
  defaultValue: "human",
12504
12369
  options: [
@@ -12667,7 +12532,7 @@ var init_users_entity = __esm({
12667
12532
  expose: false,
12668
12533
  fields: {
12669
12534
  id: useIdField4(),
12670
- user_id: useSelectField10({
12535
+ user_id: useSelectField8({
12671
12536
  label: { en: "User", es: "Usuario" },
12672
12537
  required: true,
12673
12538
  table: "users",
@@ -12678,7 +12543,7 @@ var init_users_entity = __esm({
12678
12543
  labelField: "name",
12679
12544
  meta: { searchable: true }
12680
12545
  }),
12681
- role_id: useSelectField10({
12546
+ role_id: useSelectField8({
12682
12547
  label: { en: "Role", es: "Rol" },
12683
12548
  required: true,
12684
12549
  table: "roles",
@@ -13622,7 +13487,7 @@ var init_users = __esm({
13622
13487
  });
13623
13488
 
13624
13489
  // src/modules/auth/auth.entity.ts
13625
- import { useIdField as useIdField5, useTextField as useTextField10, useSelectField as useSelectField11, useDatetimeField as useDatetimeField4, useEmailField as useEmailField2, useMetadataField as useMetadataField3, useExpiresAtField } from "@gzl10/nexus-sdk/fields";
13490
+ import { useIdField as useIdField5, useTextField as useTextField10, useSelectField as useSelectField9, useDatetimeField as useDatetimeField4, useEmailField as useEmailField2, useMetadataField as useMetadataField3, useExpiresAtField } from "@gzl10/nexus-sdk/fields";
13626
13491
  var refreshTokenEntity, authIdentitiesEntity;
13627
13492
  var init_auth_entity = __esm({
13628
13493
  "src/modules/auth/auth.entity.ts"() {
@@ -13703,7 +13568,7 @@ var init_auth_entity = __esm({
13703
13568
  order: 5,
13704
13569
  fields: {
13705
13570
  id: useIdField5(),
13706
- user_id: useSelectField11({
13571
+ user_id: useSelectField9({
13707
13572
  label: { en: "User", es: "Usuario" },
13708
13573
  table: "users",
13709
13574
  column: "id",
@@ -13787,8 +13652,327 @@ var init_auth_routes = __esm({
13787
13652
  }
13788
13653
  });
13789
13654
 
13790
- // src/modules/auth/auth.config.ts
13655
+ // src/config/env.ts
13791
13656
  import { z as z4 } from "zod";
13657
+ function resolveConfig() {
13658
+ env = envSchema.parse(process.env);
13659
+ process.env["TZ"] = env.TZ;
13660
+ resolvedConfig = {
13661
+ nodeEnv: env.NODE_ENV,
13662
+ port: env.PORT,
13663
+ host: "0.0.0.0",
13664
+ ui: {
13665
+ enabled: env.NEXUS_UI_ENABLED,
13666
+ base: env.NEXUS_UI_BASE,
13667
+ path: env.NEXUS_UI_PATH
13668
+ },
13669
+ corsOrigin: env.CORS_ORIGIN,
13670
+ databaseUrl: env.DATABASE_URL,
13671
+ adminEmail: env.ADMIN_EMAIL,
13672
+ adminPassword: env.ADMIN_PASSWORD,
13673
+ timezone: env.TZ
13674
+ };
13675
+ return resolvedConfig;
13676
+ }
13677
+ function getConfig() {
13678
+ if (!resolvedConfig) {
13679
+ return resolveConfig();
13680
+ }
13681
+ return resolvedConfig;
13682
+ }
13683
+ function resetConfig() {
13684
+ resolvedConfig = null;
13685
+ }
13686
+ var envSchema, env, resolvedConfig;
13687
+ var init_env = __esm({
13688
+ "src/config/env.ts"() {
13689
+ "use strict";
13690
+ envSchema = z4.object({
13691
+ NODE_ENV: z4.enum(["development", "production", "test"]).default("development"),
13692
+ PORT: z4.coerce.number().default(3e3),
13693
+ CORS_ORIGIN: z4.string().default("*"),
13694
+ BACKEND_URL: z4.string().optional(),
13695
+ DATABASE_URL: z4.string().default("file:./dev.db"),
13696
+ REDIS_URL: z4.string().url().optional(),
13697
+ REDIS_PREFIX: z4.string().default("nexus"),
13698
+ ADMIN_EMAIL: z4.string().email().optional(),
13699
+ ADMIN_PASSWORD: z4.string().min(6).optional(),
13700
+ COOKIE_DOMAIN: z4.string().optional(),
13701
+ TZ: z4.string().default("UTC"),
13702
+ TRUST_PROXY: z4.coerce.boolean().default(false),
13703
+ NEXUS_UI_ENABLED: z4.coerce.boolean().default(true),
13704
+ NEXUS_UI_BASE: z4.string().default("/"),
13705
+ NEXUS_UI_PATH: z4.string().default("../ui/dist"),
13706
+ NEXUS_CHECK_DRIFT: z4.coerce.boolean().optional(),
13707
+ NEXUS_FAIL_ON_DRIFT: z4.coerce.boolean().optional(),
13708
+ FRPC_SERVER: z4.string().optional(),
13709
+ FRPC_SERVER_PORT: z4.coerce.number().default(7e3),
13710
+ FRPC_TOKEN: z4.string().optional(),
13711
+ FRPC_SUBDOMAIN: z4.string().optional()
13712
+ });
13713
+ env = envSchema.parse(process.env);
13714
+ process.env["TZ"] = env.TZ;
13715
+ resolvedConfig = null;
13716
+ }
13717
+ });
13718
+
13719
+ // src/db/sql-utils.ts
13720
+ function extractTableFromSql(sql, type2) {
13721
+ const match = sql.match(SQL_PATTERNS[type2]);
13722
+ return match?.[1];
13723
+ }
13724
+ function extractTableFromSelect(sql) {
13725
+ return extractTableFromSql(sql, "select");
13726
+ }
13727
+ function extractTableFromInsert(sql) {
13728
+ return extractTableFromSql(sql, "insert");
13729
+ }
13730
+ function extractTableFromUpdate(sql) {
13731
+ return extractTableFromSql(sql, "update");
13732
+ }
13733
+ function extractTableFromDelete(sql) {
13734
+ return extractTableFromSql(sql, "delete");
13735
+ }
13736
+ var SQL_PATTERNS;
13737
+ var init_sql_utils = __esm({
13738
+ "src/db/sql-utils.ts"() {
13739
+ "use strict";
13740
+ SQL_PATTERNS = {
13741
+ select: /from\s+["'`]?(\w+)["'`]?/i,
13742
+ insert: /insert into\s+["'`]?(\w+)["'`]?/i,
13743
+ update: /update\s+["'`]?(\w+)["'`]?/i,
13744
+ delete: /delete from\s+["'`]?(\w+)["'`]?/i
13745
+ };
13746
+ }
13747
+ });
13748
+
13749
+ // src/db/sqlite-compat.ts
13750
+ function createSqliteBooleanProcessor() {
13751
+ const booleanColumns = /* @__PURE__ */ new Map();
13752
+ function registerBooleanColumn2(table, column) {
13753
+ if (!booleanColumns.has(table)) {
13754
+ booleanColumns.set(table, /* @__PURE__ */ new Set());
13755
+ }
13756
+ booleanColumns.get(table).add(column);
13757
+ }
13758
+ function convertBooleans2(table, row) {
13759
+ const columns = booleanColumns.get(table);
13760
+ if (!columns || columns.size === 0) return row;
13761
+ const result = { ...row };
13762
+ for (const column of columns) {
13763
+ if (column in result) {
13764
+ const value = result[column];
13765
+ if (value === 0 || value === 1) {
13766
+ result[column] = value === 1;
13767
+ }
13768
+ }
13769
+ }
13770
+ return result;
13771
+ }
13772
+ function postProcess(result, queryContext) {
13773
+ const sql = queryContext?.sql?.toLowerCase() ?? "";
13774
+ if (!sql.startsWith("select")) return result;
13775
+ const table = extractTableFromSelect(sql);
13776
+ if (!table) return result;
13777
+ if (Array.isArray(result)) {
13778
+ return result.map(
13779
+ (row) => typeof row === "object" && row !== null ? convertBooleans2(table, row) : row
13780
+ );
13781
+ }
13782
+ if (typeof result === "object" && result !== null) {
13783
+ return convertBooleans2(table, result);
13784
+ }
13785
+ return result;
13786
+ }
13787
+ function clear() {
13788
+ booleanColumns.clear();
13789
+ }
13790
+ return { registerBooleanColumn: registerBooleanColumn2, convertBooleans: convertBooleans2, postProcess, clear };
13791
+ }
13792
+ var defaultProcessor, registerBooleanColumn, convertBooleans, sqlitePostProcess;
13793
+ var init_sqlite_compat = __esm({
13794
+ "src/db/sqlite-compat.ts"() {
13795
+ "use strict";
13796
+ init_sql_utils();
13797
+ defaultProcessor = createSqliteBooleanProcessor();
13798
+ registerBooleanColumn = defaultProcessor.registerBooleanColumn;
13799
+ convertBooleans = defaultProcessor.convertBooleans;
13800
+ sqlitePostProcess = defaultProcessor.postProcess;
13801
+ }
13802
+ });
13803
+
13804
+ // src/config/database.ts
13805
+ import { join as join9, dirname as dirname6, isAbsolute as isAbsolute2 } from "path";
13806
+ import { mkdirSync as mkdirSync4 } from "fs";
13807
+ function getDatabaseConfig() {
13808
+ const url = getConfig().databaseUrl;
13809
+ if (IN_MEMORY_RE.test(url)) {
13810
+ return {
13811
+ client: "better-sqlite3",
13812
+ connection: { filename: ":memory:" },
13813
+ useNullAsDefault: true,
13814
+ postProcessResponse: sqlitePostProcess,
13815
+ pool: { min: 0, max: 1 }
13816
+ };
13817
+ }
13818
+ if (url.startsWith("file:") || url.startsWith("sqlite:")) {
13819
+ let filename = url.replace(/^(file:|sqlite:)/, "");
13820
+ if (!isAbsolute2(filename)) {
13821
+ filename = join9(getProjectPath(), "data", filename);
13822
+ }
13823
+ mkdirSync4(dirname6(filename), { recursive: true });
13824
+ return {
13825
+ client: "better-sqlite3",
13826
+ connection: { filename },
13827
+ useNullAsDefault: true,
13828
+ postProcessResponse: sqlitePostProcess
13829
+ };
13830
+ }
13831
+ if (url.startsWith("postgresql://") || url.startsWith("postgres://")) {
13832
+ return {
13833
+ client: "pg",
13834
+ connection: url,
13835
+ pool: { min: 2, max: 10 }
13836
+ };
13837
+ }
13838
+ if (url.startsWith("mysql://")) {
13839
+ const offsetMinutes = (/* @__PURE__ */ new Date()).getTimezoneOffset();
13840
+ const offsetHours = Math.abs(Math.floor(offsetMinutes / 60));
13841
+ const offsetMins = Math.abs(offsetMinutes % 60);
13842
+ const sign = offsetMinutes <= 0 ? "+" : "-";
13843
+ const tzOffset = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMins).padStart(2, "0")}`;
13844
+ return {
13845
+ client: "mysql2",
13846
+ connection: {
13847
+ uri: url,
13848
+ timezone: tzOffset
13849
+ // mysql2 requiere formato "+HH:MM"
13850
+ },
13851
+ pool: { min: 2, max: 10 }
13852
+ };
13853
+ }
13854
+ throw new Error(`Unsupported database URL: ${url}`);
13855
+ }
13856
+ function getDatabaseType() {
13857
+ const url = getConfig().databaseUrl;
13858
+ if (IN_MEMORY_RE.test(url) || url.startsWith("file:") || url.startsWith("sqlite:")) return "sqlite";
13859
+ if (url.startsWith("postgresql://") || url.startsWith("postgres://")) return "postgresql";
13860
+ if (url.startsWith("mysql://")) return "mysql";
13861
+ return "sqlite";
13862
+ }
13863
+ function getDatabasePath() {
13864
+ const url = getConfig().databaseUrl;
13865
+ if (IN_MEMORY_RE.test(url)) return ":memory:";
13866
+ if (url.startsWith("file:") || url.startsWith("sqlite:")) {
13867
+ let filename = url.replace(/^(file:|sqlite:)/, "");
13868
+ if (!isAbsolute2(filename)) {
13869
+ filename = join9(getProjectPath(), "data", filename);
13870
+ }
13871
+ return filename;
13872
+ }
13873
+ try {
13874
+ const parsed = new URL(url);
13875
+ parsed.password = "***";
13876
+ return parsed.toString();
13877
+ } catch {
13878
+ return url.replace(/:\/\/[^@]+@/, "://***@");
13879
+ }
13880
+ }
13881
+ var IN_MEMORY_RE;
13882
+ var init_database = __esm({
13883
+ "src/config/database.ts"() {
13884
+ "use strict";
13885
+ init_env();
13886
+ init_paths();
13887
+ init_sqlite_compat();
13888
+ IN_MEMORY_RE = /^((file:|sqlite:)?:memory:?)$/;
13889
+ }
13890
+ });
13891
+
13892
+ // src/core/crypto/secret-resolver.ts
13893
+ import { randomBytes } from "crypto";
13894
+ function setSecretResolverDb(db2) {
13895
+ _db = db2;
13896
+ }
13897
+ function _resetSecretResolver() {
13898
+ _secret = null;
13899
+ _db = null;
13900
+ }
13901
+ async function initAuthSecret(logger2) {
13902
+ const envSecret = process.env["AUTH_SECRET"];
13903
+ if (envSecret) {
13904
+ if (envSecret.length < 32) {
13905
+ throw new Error(`AUTH_SECRET must be at least 32 characters (current: ${envSecret.length})`);
13906
+ }
13907
+ _secret = envSecret;
13908
+ return;
13909
+ }
13910
+ if (!_db) {
13911
+ throw new Error("Secret resolver: database not initialized. Call setSecretResolverDb() first.");
13912
+ }
13913
+ const existing = await _db(SINGLE_RECORDS).where("key", SECRET_KEY).first();
13914
+ if (existing?.value) {
13915
+ const parsed2 = typeof existing.value === "string" ? JSON.parse(existing.value) : existing.value;
13916
+ if (parsed2.secret) {
13917
+ _secret = parsed2.secret;
13918
+ logger2.info("AUTH_SECRET loaded from database");
13919
+ warnIfProduction(logger2);
13920
+ return;
13921
+ }
13922
+ }
13923
+ const generated = randomBytes(SECRET_LENGTH).toString("base64url");
13924
+ const value = JSON.stringify({ secret: generated });
13925
+ const id = randomBytes(13).toString("base64url");
13926
+ const now = /* @__PURE__ */ new Date();
13927
+ const dialect = getDatabaseType();
13928
+ if (dialect === "postgresql") {
13929
+ await _db.raw(
13930
+ `INSERT INTO ${SINGLE_RECORDS} (id, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT (key) DO NOTHING`,
13931
+ [id, SECRET_KEY, value, now, now]
13932
+ );
13933
+ } else if (dialect === "mysql") {
13934
+ await _db.raw(
13935
+ "INSERT IGNORE INTO `single_records` (id, `key`, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
13936
+ [id, SECRET_KEY, value, now, now]
13937
+ );
13938
+ } else {
13939
+ await _db.raw(
13940
+ `INSERT OR IGNORE INTO ${SINGLE_RECORDS} (id, key, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
13941
+ [id, SECRET_KEY, value, now, now]
13942
+ );
13943
+ }
13944
+ const persisted = await _db(SINGLE_RECORDS).where("key", SECRET_KEY).first();
13945
+ const parsed = typeof persisted.value === "string" ? JSON.parse(persisted.value) : persisted.value;
13946
+ _secret = parsed.secret;
13947
+ logger2.info("AUTH_SECRET auto-generated and persisted in database");
13948
+ warnIfProduction(logger2);
13949
+ }
13950
+ function warnIfProduction(logger2) {
13951
+ if (process.env["NODE_ENV"] === "production") {
13952
+ logger2.warn("AUTH_SECRET not set via environment. Using auto-generated secret from database. For multi-instance deployments, set AUTH_SECRET explicitly.");
13953
+ }
13954
+ }
13955
+ function getAuthSecret() {
13956
+ if (_secret) return _secret;
13957
+ const envSecret = process.env["AUTH_SECRET"];
13958
+ if (envSecret) return envSecret;
13959
+ throw new Error("AUTH_SECRET not initialized. Call initAuthSecret() during startup.");
13960
+ }
13961
+ var SINGLE_RECORDS, SECRET_KEY, SECRET_LENGTH, _secret, _db;
13962
+ var init_secret_resolver = __esm({
13963
+ "src/core/crypto/secret-resolver.ts"() {
13964
+ "use strict";
13965
+ init_database();
13966
+ SINGLE_RECORDS = "single_records";
13967
+ SECRET_KEY = "nexus_auth_secret";
13968
+ SECRET_LENGTH = 48;
13969
+ _secret = null;
13970
+ _db = null;
13971
+ }
13972
+ });
13973
+
13974
+ // src/modules/auth/auth.config.ts
13975
+ import { z as z5 } from "zod";
13792
13976
  function configError(module, errors) {
13793
13977
  console.error(`
13794
13978
  ${"\u2550".repeat(60)}`);
@@ -13803,12 +13987,6 @@ function parseAuthEnv() {
13803
13987
  if (!result.success) {
13804
13988
  const errors = result.error.issues.map((issue) => {
13805
13989
  const path4 = issue.path.join(".");
13806
- if (path4 === "AUTH_SECRET" && issue.code === "invalid_type") {
13807
- return "AUTH_SECRET is required. Set it in your .env file or environment.";
13808
- }
13809
- if (path4 === "AUTH_SECRET" && issue.code === "too_small") {
13810
- return `AUTH_SECRET must be at least 32 characters (current: ${String(process.env["AUTH_SECRET"]).length})`;
13811
- }
13812
13990
  return `${path4}: ${issue.message}`;
13813
13991
  });
13814
13992
  configError("auth", errors);
@@ -13827,7 +14005,7 @@ function getAuthEnv() {
13827
14005
  function getAuthConfig() {
13828
14006
  const authEnv = getAuthEnv();
13829
14007
  return {
13830
- secret: authEnv.AUTH_SECRET,
14008
+ secret: getAuthSecret(),
13831
14009
  accessExpires: authEnv.AUTH_ACCESS_EXPIRES,
13832
14010
  refreshExpires: authEnv.AUTH_REFRESH_EXPIRES,
13833
14011
  rateLimitMax: authEnv.AUTH_RATE_LIMIT_MAX,
@@ -13844,24 +14022,24 @@ var authEnvSchema, _authEnv;
13844
14022
  var init_auth_config = __esm({
13845
14023
  "src/modules/auth/auth.config.ts"() {
13846
14024
  "use strict";
13847
- authEnvSchema = z4.object({
13848
- AUTH_SECRET: z4.string().min(32, "AUTH_SECRET must be at least 32 characters"),
13849
- AUTH_ACCESS_EXPIRES: z4.string().default("15m"),
13850
- AUTH_REFRESH_EXPIRES: z4.string().default("7d"),
14025
+ init_secret_resolver();
14026
+ authEnvSchema = z5.object({
14027
+ AUTH_ACCESS_EXPIRES: z5.string().default("15m"),
14028
+ AUTH_REFRESH_EXPIRES: z5.string().default("7d"),
13851
14029
  // Rate limiting (default: 5 requests per 15 minutes)
13852
- AUTH_RATE_LIMIT_MAX: z4.coerce.number().default(5),
13853
- AUTH_RATE_LIMIT_WINDOW: z4.coerce.number().default(900),
14030
+ AUTH_RATE_LIMIT_MAX: z5.coerce.number().default(5),
14031
+ AUTH_RATE_LIMIT_WINDOW: z5.coerce.number().default(900),
13854
14032
  // seconds
13855
14033
  // Cookie domain for SSO across subdomains (e.g., '.example.com')
13856
- AUTH_COOKIE_DOMAIN: z4.string().optional(),
14034
+ AUTH_COOKIE_DOMAIN: z5.string().optional(),
13857
14035
  // Challenge threshold: failed attempts before requiring OTP (default: 2)
13858
- AUTH_CHALLENGE_THRESHOLD: z4.coerce.number().default(2),
14036
+ AUTH_CHALLENGE_THRESHOLD: z5.coerce.number().default(2),
13859
14037
  // Skip OTP verification for registration (DEVELOPMENT ONLY - rejected in production)
13860
- AUTH_SKIP_REGISTER_OTP: z4.coerce.boolean().default(false),
14038
+ AUTH_SKIP_REGISTER_OTP: z5.coerce.boolean().default(false),
13861
14039
  // Disable self-registration via POST /auth/register (default: false)
13862
- AUTH_DISABLE_REGISTRATION: z4.coerce.boolean().default(false),
14040
+ AUTH_DISABLE_REGISTRATION: z5.coerce.boolean().default(false),
13863
14041
  // Disable auto-creation of users on first OIDC login (default: false)
13864
- AUTH_DISABLE_AUTO_CREATE: z4.coerce.boolean().default(false)
14042
+ AUTH_DISABLE_AUTO_CREATE: z5.coerce.boolean().default(false)
13865
14043
  });
13866
14044
  }
13867
14045
  });
@@ -14019,7 +14197,7 @@ var init_auth_middleware = __esm({
14019
14197
  });
14020
14198
 
14021
14199
  // src/modules/auth/auth.pat.entity.ts
14022
- import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as useSelectField12, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
14200
+ import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as useSelectField10, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
14023
14201
  var personalTokenEntity;
14024
14202
  var init_auth_pat_entity = __esm({
14025
14203
  "src/modules/auth/auth.pat.entity.ts"() {
@@ -14036,7 +14214,7 @@ var init_auth_pat_entity = __esm({
14036
14214
  routePrefix: "/personal-tokens",
14037
14215
  fields: {
14038
14216
  id: useIdField6(),
14039
- user_id: useSelectField12({
14217
+ user_id: useSelectField10({
14040
14218
  label: { en: "User", es: "Usuario" },
14041
14219
  table: "users",
14042
14220
  column: "id",
@@ -14071,7 +14249,7 @@ var init_auth_pat_entity = __esm({
14071
14249
  unique: true,
14072
14250
  meta: { exportable: false }
14073
14251
  }),
14074
- scope: useSelectField12({
14252
+ scope: useSelectField10({
14075
14253
  label: { en: "Permission", es: "Permiso" },
14076
14254
  required: true,
14077
14255
  options: [
@@ -14096,44 +14274,8 @@ var init_auth_pat_entity = __esm({
14096
14274
  permissions: {
14097
14275
  "*": [
14098
14276
  { actions: ["read", "delete"], conditions: { user_id: "${user.id}" } }
14099
- ]
14100
- }
14101
- }
14102
- };
14103
- }
14104
- });
14105
-
14106
- // src/modules/auth/actions/providers.action.ts
14107
- var providersAction;
14108
- var init_providers_action = __esm({
14109
- "src/modules/auth/actions/providers.action.ts"() {
14110
- "use strict";
14111
- init_auth_config();
14112
- providersAction = {
14113
- key: "providers",
14114
- label: { en: "Get Auth Providers", es: "Obtener proveedores de autenticaci\xF3n" },
14115
- icon: "mdi:account-key",
14116
- scope: "module",
14117
- hidden: true,
14118
- method: "GET",
14119
- skipAuth: true,
14120
- handler: async (ctx) => {
14121
- const providerServices = ctx.services.getBySuffix(".provider");
14122
- const results = await Promise.all(
14123
- providerServices.map(async ({ service }) => {
14124
- try {
14125
- return await service.getInfo();
14126
- } catch {
14127
- return null;
14128
- }
14129
- })
14130
- );
14131
- const providers = results.filter((info) => info !== null);
14132
- const config3 = getAuthConfig();
14133
- return {
14134
- providers,
14135
- registrationEnabled: !config3.disableRegistration
14136
- };
14277
+ ]
14278
+ }
14137
14279
  }
14138
14280
  };
14139
14281
  }
@@ -14695,7 +14837,6 @@ var authActions;
14695
14837
  var init_actions2 = __esm({
14696
14838
  "src/modules/auth/actions/index.ts"() {
14697
14839
  "use strict";
14698
- init_providers_action();
14699
14840
  init_login_action();
14700
14841
  init_register_action();
14701
14842
  init_forgot_password_action();
@@ -14711,7 +14852,6 @@ var init_actions2 = __esm({
14711
14852
  init_list_tokens_action();
14712
14853
  init_revoke_token_action();
14713
14854
  init_impersonate_action();
14714
- init_providers_action();
14715
14855
  init_login_action();
14716
14856
  init_register_action();
14717
14857
  init_forgot_password_action();
@@ -14727,7 +14867,6 @@ var init_actions2 = __esm({
14727
14867
  init_list_tokens_action();
14728
14868
  init_revoke_token_action();
14729
14869
  authActions = [
14730
- providersAction,
14731
14870
  loginAction,
14732
14871
  registerAction,
14733
14872
  forgotPasswordAction,
@@ -14946,7 +15085,7 @@ var init_otp_manager = __esm({
14946
15085
  });
14947
15086
 
14948
15087
  // src/modules/auth/auth.service.ts
14949
- import { createHash as createHash4, randomBytes } from "crypto";
15088
+ import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
14950
15089
  function createAuthService(ctx) {
14951
15090
  const { errors, abilities, crypto: crypto3 } = ctx.core;
14952
15091
  const { generateId: generateId4 } = ctx.core;
@@ -15363,7 +15502,7 @@ function createAuthService(ctx) {
15363
15502
  },
15364
15503
  // === Personal Access Tokens ===
15365
15504
  async createPersonalToken(userId, input, requestInfo) {
15366
- const rawToken = "nxs_" + randomBytes(32).toString("hex");
15505
+ const rawToken = "nxs_" + randomBytes2(32).toString("hex");
15367
15506
  const tokenHash = createHash4("sha256").update(rawToken).digest("hex");
15368
15507
  const tokenPrefix = "nxs_..." + rawToken.slice(-6);
15369
15508
  const id = generateId4();
@@ -15529,38 +15668,38 @@ var init_auth_service = __esm({
15529
15668
  });
15530
15669
 
15531
15670
  // src/modules/auth/auth.types.ts
15532
- import { z as z5 } from "zod";
15671
+ import { z as z6 } from "zod";
15533
15672
  var loginSchema, registerSchema, forgotPasswordSchema, resetPasswordSchema, createPersonalTokenSchema;
15534
15673
  var init_auth_types = __esm({
15535
15674
  "src/modules/auth/auth.types.ts"() {
15536
15675
  "use strict";
15537
- loginSchema = z5.object({
15538
- email: z5.string().email("Invalid email"),
15539
- password: z5.string().min(1, "Password required"),
15540
- otp: z5.string().length(6).optional(),
15541
- deviceId: z5.string().max(64).optional(),
15542
- deviceName: z5.string().max(100).optional()
15676
+ loginSchema = z6.object({
15677
+ email: z6.string().email("Invalid email"),
15678
+ password: z6.string().min(1, "Password required"),
15679
+ otp: z6.string().length(6).optional(),
15680
+ deviceId: z6.string().max(64).optional(),
15681
+ deviceName: z6.string().max(100).optional()
15543
15682
  });
15544
- registerSchema = z5.object({
15545
- email: z5.string().email("Invalid email"),
15546
- password: z5.string().min(8, "Password must be at least 8 characters"),
15547
- name: z5.string().min(2, "Name must be at least 2 characters"),
15548
- otp: z5.string().length(6).optional(),
15549
- deviceId: z5.string().max(64).optional(),
15550
- deviceName: z5.string().max(100).optional()
15683
+ registerSchema = z6.object({
15684
+ email: z6.string().email("Invalid email"),
15685
+ password: z6.string().min(8, "Password must be at least 8 characters"),
15686
+ name: z6.string().min(2, "Name must be at least 2 characters"),
15687
+ otp: z6.string().length(6).optional(),
15688
+ deviceId: z6.string().max(64).optional(),
15689
+ deviceName: z6.string().max(100).optional()
15551
15690
  });
15552
- forgotPasswordSchema = z5.object({
15553
- email: z5.string().email("Invalid email")
15691
+ forgotPasswordSchema = z6.object({
15692
+ email: z6.string().email("Invalid email")
15554
15693
  });
15555
- resetPasswordSchema = z5.object({
15556
- email: z5.string().email("Invalid email"),
15557
- otp: z5.string().length(6, "OTP must be 6 digits"),
15558
- newPassword: z5.string().min(8, "Password must be at least 8 characters")
15694
+ resetPasswordSchema = z6.object({
15695
+ email: z6.string().email("Invalid email"),
15696
+ otp: z6.string().length(6, "OTP must be 6 digits"),
15697
+ newPassword: z6.string().min(8, "Password must be at least 8 characters")
15559
15698
  });
15560
- createPersonalTokenSchema = z5.object({
15561
- name: z5.string().min(1).max(100),
15562
- scope: z5.enum(["readonly", "readwrite"]),
15563
- expires_at: z5.string().datetime().optional()
15699
+ createPersonalTokenSchema = z6.object({
15700
+ name: z6.string().min(1).max(100),
15701
+ scope: z6.enum(["readonly", "readwrite"]),
15702
+ expires_at: z6.string().datetime().optional()
15564
15703
  });
15565
15704
  }
15566
15705
  });
@@ -15574,12 +15713,12 @@ async function seed3(ctx) {
15574
15713
  const db2 = ctx.db.knex;
15575
15714
  const { logger: logger2, generateId: generateId4 } = ctx.core;
15576
15715
  const { nowTimestamp: nowTimestamp2 } = ctx.db;
15577
- const hasTable = await db2.schema.hasTable(SINGLE_RECORDS);
15716
+ const hasTable = await db2.schema.hasTable(SINGLE_RECORDS2);
15578
15717
  if (!hasTable) {
15579
15718
  logger2.debug("single_records table not found, skipping auth seed");
15580
15719
  return;
15581
15720
  }
15582
- const existing = await db2(SINGLE_RECORDS).where({ key: AUTH_CONFIG_KEY }).first();
15721
+ const existing = await db2(SINGLE_RECORDS2).where({ key: AUTH_CONFIG_KEY }).first();
15583
15722
  if (existing) {
15584
15723
  logger2.debug("Auth config already seeded");
15585
15724
  return;
@@ -15592,7 +15731,7 @@ async function seed3(ctx) {
15592
15731
  rate_limit_window: parseInt(process.env["AUTH_RATE_LIMIT_WINDOW"] || "") || 900,
15593
15732
  cookie_domain: process.env["AUTH_COOKIE_DOMAIN"] || null
15594
15733
  };
15595
- await db2(SINGLE_RECORDS).insert({
15734
+ await db2(SINGLE_RECORDS2).insert({
15596
15735
  id: generateId4(),
15597
15736
  key: AUTH_CONFIG_KEY,
15598
15737
  value: JSON.stringify(defaults),
@@ -15601,11 +15740,11 @@ async function seed3(ctx) {
15601
15740
  });
15602
15741
  logger2.info("Auth config seeded from environment variables");
15603
15742
  }
15604
- var SINGLE_RECORDS, AUTH_CONFIG_KEY;
15743
+ var SINGLE_RECORDS2, AUTH_CONFIG_KEY;
15605
15744
  var init_auth_seed = __esm({
15606
15745
  "src/modules/auth/auth.seed.ts"() {
15607
15746
  "use strict";
15608
- SINGLE_RECORDS = "single_records";
15747
+ SINGLE_RECORDS2 = "single_records";
15609
15748
  AUTH_CONFIG_KEY = "auth_config";
15610
15749
  }
15611
15750
  });
@@ -15671,7 +15810,7 @@ var init_auth = __esm({
15671
15810
  });
15672
15811
 
15673
15812
  // src/modules/mail/mail.config.ts
15674
- import { z as z6 } from "zod";
15813
+ import { z as z7 } from "zod";
15675
15814
  function getMailConfig() {
15676
15815
  return {
15677
15816
  host: mailEnv.SMTP_HOST,
@@ -15685,13 +15824,13 @@ var mailEnvSchema, mailEnv;
15685
15824
  var init_mail_config = __esm({
15686
15825
  "src/modules/mail/mail.config.ts"() {
15687
15826
  "use strict";
15688
- mailEnvSchema = z6.object({
15689
- SMTP_HOST: z6.string().default("localhost"),
15690
- SMTP_PORT: z6.coerce.number().default(1025),
15691
- SMTP_SECURE: z6.string().default("false").transform((v) => v === "true" || v === "1"),
15692
- SMTP_USER: z6.string().optional(),
15693
- SMTP_PASS: z6.string().optional(),
15694
- SMTP_FROM: z6.string().default("noreply@nexus.local")
15827
+ mailEnvSchema = z7.object({
15828
+ SMTP_HOST: z7.string().default("localhost"),
15829
+ SMTP_PORT: z7.coerce.number().default(1025),
15830
+ SMTP_SECURE: z7.string().default("false").transform((v) => v === "true" || v === "1"),
15831
+ SMTP_USER: z7.string().optional(),
15832
+ SMTP_PASS: z7.string().optional(),
15833
+ SMTP_FROM: z7.string().default("noreply@nexus.local")
15695
15834
  });
15696
15835
  mailEnv = mailEnvSchema.parse(process.env);
15697
15836
  }
@@ -15699,8 +15838,8 @@ var init_mail_config = __esm({
15699
15838
 
15700
15839
  // src/modules/mail/mail.service.ts
15701
15840
  import nodemailer from "nodemailer";
15702
- import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
15703
- import { join as join8 } from "path";
15841
+ import { readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
15842
+ import { join as join10 } from "path";
15704
15843
  function getMailService() {
15705
15844
  if (!mailServiceInstance) {
15706
15845
  throw new Error("MailService not initialized. Call initMailService() first.");
@@ -15717,8 +15856,8 @@ var init_mail_service = __esm({
15717
15856
  "src/modules/mail/mail.service.ts"() {
15718
15857
  "use strict";
15719
15858
  init_mail_config();
15720
- TEMPLATE_REL_PATH = join8("public", "mail", "base.html");
15721
- LOGO_REL_PATH = join8("public", "nexus", "nexus-light-512.png");
15859
+ TEMPLATE_REL_PATH = join10("public", "mail", "base.html");
15860
+ LOGO_REL_PATH = join10("public", "nexus", "nexus-light-512.png");
15722
15861
  mailServiceInstance = null;
15723
15862
  MailService = class {
15724
15863
  transporter;
@@ -15732,10 +15871,10 @@ var init_mail_service = __esm({
15732
15871
  this.logger = logger2.child({ service: "mail" });
15733
15872
  this.loggerService = loggerService;
15734
15873
  const libPath = options?.libPath ?? process.cwd();
15735
- this.template = readFileSync4(join8(libPath, TEMPLATE_REL_PATH), "utf-8");
15736
- const logoPath = join8(libPath, LOGO_REL_PATH);
15737
- if (existsSync7(logoPath)) {
15738
- const logoBase64 = readFileSync4(logoPath).toString("base64");
15874
+ this.template = readFileSync5(join10(libPath, TEMPLATE_REL_PATH), "utf-8");
15875
+ const logoPath = join10(libPath, LOGO_REL_PATH);
15876
+ if (existsSync8(logoPath)) {
15877
+ const logoBase64 = readFileSync5(logoPath).toString("base64");
15739
15878
  this.defaultLogoUrl = `data:image/png;base64,${logoBase64}`;
15740
15879
  } else {
15741
15880
  this.defaultLogoUrl = "";
@@ -15827,7 +15966,7 @@ var init_mail_service = __esm({
15827
15966
  });
15828
15967
 
15829
15968
  // src/modules/mail/mail.entity.ts
15830
- import { useIdField as useIdField7, useTextField as useTextField12, useSelectField as useSelectField13, useNumberField as useNumberField5, useSwitchField as useSwitchField4, useEmailField as useEmailField3, usePasswordField as usePasswordField2, useTextareaField as useTextareaField3, useTagsField as useTagsField2, useDatetimeField as useDatetimeField6 } from "@gzl10/nexus-sdk/fields";
15969
+ import { useIdField as useIdField7, useTextField as useTextField12, useSelectField as useSelectField11, useNumberField as useNumberField6, useSwitchField as useSwitchField3, useEmailField as useEmailField3, usePasswordField as usePasswordField2, useTextareaField as useTextareaField3, useTagsField as useTagsField2, useDatetimeField as useDatetimeField6 } from "@gzl10/nexus-sdk/fields";
15831
15970
  import nodemailer2 from "nodemailer";
15832
15971
  async function getMailConfigFromDB(ctx) {
15833
15972
  const configService = ctx.services["config"];
@@ -15896,12 +16035,12 @@ var init_mail_entity = __esm({
15896
16035
  nullable: false,
15897
16036
  hint: { en: "Default: SMTP_HOST env var", es: "Por defecto: variable SMTP_HOST" }
15898
16037
  }),
15899
- port: useNumberField5({
16038
+ port: useNumberField6({
15900
16039
  label: { en: "Port", es: "Puerto" },
15901
16040
  nullable: false,
15902
16041
  hint: { en: "Default: SMTP_PORT env var", es: "Por defecto: variable SMTP_PORT" }
15903
16042
  }),
15904
- secure: useSwitchField4({
16043
+ secure: useSwitchField3({
15905
16044
  label: { en: "TLS/SSL", es: "TLS/SSL" },
15906
16045
  hint: { en: "Default: SMTP_SECURE env var", es: "Por defecto: variable SMTP_SECURE" }
15907
16046
  }),
@@ -16104,7 +16243,7 @@ var init_mail_entity = __esm({
16104
16243
  meta: { searchable: true, sortable: true }
16105
16244
  }),
16106
16245
  status: {
16107
- ...useSelectField13({
16246
+ ...useSelectField11({
16108
16247
  label: { en: "Status", es: "Estado" },
16109
16248
  options: [
16110
16249
  { value: "pending", label: { en: "Pending", es: "Pendiente" } },
@@ -16128,7 +16267,7 @@ var init_mail_entity = __esm({
16128
16267
  label: { en: "Error", es: "Error" },
16129
16268
  nullable: true
16130
16269
  }),
16131
- sent_by: useSelectField13({
16270
+ sent_by: useSelectField11({
16132
16271
  label: { en: "Sent by", es: "Enviado por" },
16133
16272
  table: "users",
16134
16273
  column: "id",
@@ -16337,7 +16476,7 @@ var init_observability_service = __esm({
16337
16476
  });
16338
16477
 
16339
16478
  // src/modules/observability/observability.config.ts
16340
- import { z as z7 } from "zod";
16479
+ import { z as z8 } from "zod";
16341
16480
  function getOtelConfig() {
16342
16481
  if (cachedConfig) return cachedConfig;
16343
16482
  const env2 = otelEnvSchema.parse(process.env);
@@ -16354,12 +16493,12 @@ var otelEnvSchema, cachedConfig;
16354
16493
  var init_observability_config = __esm({
16355
16494
  "src/modules/observability/observability.config.ts"() {
16356
16495
  "use strict";
16357
- otelEnvSchema = z7.object({
16358
- OTEL_ENABLED: z7.coerce.boolean().default(false),
16359
- OTEL_SERVICE_NAME: z7.string().default("nexus"),
16360
- OTEL_PROMETHEUS_PORT: z7.coerce.number().int().min(0).max(65535).default(9464),
16361
- OTEL_EXPORTER_OTLP_ENDPOINT: z7.string().url().optional(),
16362
- OTEL_TRACE_SAMPLE_RATE: z7.coerce.number().min(0).max(1).default(1)
16496
+ otelEnvSchema = z8.object({
16497
+ OTEL_ENABLED: z8.coerce.boolean().default(false),
16498
+ OTEL_SERVICE_NAME: z8.string().default("nexus"),
16499
+ OTEL_PROMETHEUS_PORT: z8.coerce.number().int().min(0).max(65535).default(9464),
16500
+ OTEL_EXPORTER_OTLP_ENDPOINT: z8.string().url().optional(),
16501
+ OTEL_TRACE_SAMPLE_RATE: z8.coerce.number().min(0).max(1).default(1)
16363
16502
  });
16364
16503
  cachedConfig = null;
16365
16504
  }
@@ -16763,7 +16902,7 @@ var init_toggle_plugin_action = __esm({
16763
16902
  });
16764
16903
 
16765
16904
  // src/modules/plugins/plugins.entity.ts
16766
- import { useTextField as useTextField13, useSelectField as useSelectField14, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
16905
+ import { useTextField as useTextField13, useSelectField as useSelectField12, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
16767
16906
  import { OFFICIAL_PLUGINS } from "@gzl10/nexus-sdk";
16768
16907
  var allowPluginManagement, pluginsEntity;
16769
16908
  var init_plugins_entity = __esm({
@@ -16805,7 +16944,7 @@ var init_plugins_entity = __esm({
16805
16944
  size: 20,
16806
16945
  nullable: false
16807
16946
  }),
16808
- category: useSelectField14({
16947
+ category: useSelectField12({
16809
16948
  label: { en: "Category", es: "Categor\xEDa" },
16810
16949
  options: [
16811
16950
  { value: "content", label: { en: "Content", es: "Contenido" } },
@@ -16884,7 +17023,7 @@ var init_plugins_entity = __esm({
16884
17023
 
16885
17024
  // src/modules/plugins/plugins.routes.ts
16886
17025
  import { Router } from "express";
16887
- import { existsSync as existsSync8 } from "fs";
17026
+ import { existsSync as existsSync9 } from "fs";
16888
17027
  function createPluginRoutes(ctx) {
16889
17028
  const router = Router();
16890
17029
  let imageMap = null;
@@ -16892,7 +17031,7 @@ function createPluginRoutes(ctx) {
16892
17031
  if (!imageMap) {
16893
17032
  const discovered = await ctx.core.plugins.discover();
16894
17033
  imageMap = new Map(
16895
- discovered.filter((p) => p.image && existsSync8(p.image)).map((p) => [p.code, p.image])
17034
+ discovered.filter((p) => p.image && existsSync9(p.image)).map((p) => [p.code, p.image])
16896
17035
  );
16897
17036
  }
16898
17037
  return imageMap;
@@ -16947,7 +17086,7 @@ var init_plugins = __esm({
16947
17086
  import {
16948
17087
  useIdField as useIdField8,
16949
17088
  useTextField as useTextField14,
16950
- useSelectField as useSelectField15,
17089
+ useSelectField as useSelectField13,
16951
17090
  useTextareaField as useTextareaField4,
16952
17091
  useJsonField as useJsonField3,
16953
17092
  useDatetimeField as useDatetimeField7,
@@ -16989,7 +17128,7 @@ var init_audit_entity = __esm({
16989
17128
  }),
16990
17129
  validation: { min: 1, max: 100 }
16991
17130
  },
16992
- actor_id: useSelectField15({
17131
+ actor_id: useSelectField13({
16993
17132
  label: { en: "Actor", es: "Actor" },
16994
17133
  table: "users",
16995
17134
  column: "id",
@@ -17276,7 +17415,11 @@ import { Server as SocketServer } from "socket.io";
17276
17415
  import jwt3 from "jsonwebtoken";
17277
17416
  import { DEFAULT_TENANT_ID, entityRoom } from "@gzl10/nexus-sdk";
17278
17417
  function initSocketIO(httpServer, options) {
17279
- jwtSecret = options?.jwtSecret ?? process.env["AUTH_SECRET"] ?? null;
17418
+ try {
17419
+ jwtSecret = options?.jwtSecret ?? getAuthSecret();
17420
+ } catch {
17421
+ jwtSecret = null;
17422
+ }
17280
17423
  if (!jwtSecret) {
17281
17424
  logger.warn("Socket.IO authentication disabled: AUTH_SECRET not configured. All connections will be treated as guests.");
17282
17425
  }
@@ -17521,6 +17664,7 @@ var init_socket = __esm({
17521
17664
  "use strict";
17522
17665
  init_emitter();
17523
17666
  init_logger();
17667
+ init_secret_resolver();
17524
17668
  io = null;
17525
17669
  jwtSecret = null;
17526
17670
  userSockets = /* @__PURE__ */ new Map();
@@ -18072,70 +18216,6 @@ var init_validate_middleware = __esm({
18072
18216
  }
18073
18217
  });
18074
18218
 
18075
- // src/config/env.ts
18076
- import { z as z8 } from "zod";
18077
- function resolveConfig() {
18078
- env = envSchema.parse(process.env);
18079
- process.env["TZ"] = env.TZ;
18080
- resolvedConfig = {
18081
- nodeEnv: env.NODE_ENV,
18082
- port: env.PORT,
18083
- host: "0.0.0.0",
18084
- ui: {
18085
- enabled: env.NEXUS_UI_ENABLED,
18086
- base: env.NEXUS_UI_BASE,
18087
- path: env.NEXUS_UI_PATH
18088
- },
18089
- corsOrigin: env.CORS_ORIGIN,
18090
- databaseUrl: env.DATABASE_URL,
18091
- adminEmail: env.ADMIN_EMAIL,
18092
- adminPassword: env.ADMIN_PASSWORD,
18093
- timezone: env.TZ
18094
- };
18095
- return resolvedConfig;
18096
- }
18097
- function getConfig() {
18098
- if (!resolvedConfig) {
18099
- return resolveConfig();
18100
- }
18101
- return resolvedConfig;
18102
- }
18103
- function resetConfig() {
18104
- resolvedConfig = null;
18105
- }
18106
- var envSchema, env, resolvedConfig;
18107
- var init_env = __esm({
18108
- "src/config/env.ts"() {
18109
- "use strict";
18110
- envSchema = z8.object({
18111
- NODE_ENV: z8.enum(["development", "production", "test"]).default("development"),
18112
- PORT: z8.coerce.number().default(3e3),
18113
- CORS_ORIGIN: z8.string().default("*"),
18114
- BACKEND_URL: z8.string().optional(),
18115
- DATABASE_URL: z8.string().default("file:./dev.db"),
18116
- REDIS_URL: z8.string().url().optional(),
18117
- REDIS_PREFIX: z8.string().default("nexus"),
18118
- ADMIN_EMAIL: z8.string().email().optional(),
18119
- ADMIN_PASSWORD: z8.string().min(6).optional(),
18120
- COOKIE_DOMAIN: z8.string().optional(),
18121
- TZ: z8.string().default("UTC"),
18122
- TRUST_PROXY: z8.coerce.boolean().default(false),
18123
- NEXUS_UI_ENABLED: z8.coerce.boolean().default(true),
18124
- NEXUS_UI_BASE: z8.string().default("/"),
18125
- NEXUS_UI_PATH: z8.string().default("../ui/dist"),
18126
- NEXUS_CHECK_DRIFT: z8.coerce.boolean().optional(),
18127
- NEXUS_FAIL_ON_DRIFT: z8.coerce.boolean().optional(),
18128
- FRPC_SERVER: z8.string().optional(),
18129
- FRPC_SERVER_PORT: z8.coerce.number().default(7e3),
18130
- FRPC_TOKEN: z8.string().optional(),
18131
- FRPC_SUBDOMAIN: z8.string().optional()
18132
- });
18133
- env = envSchema.parse(process.env);
18134
- process.env["TZ"] = env.TZ;
18135
- resolvedConfig = null;
18136
- }
18137
- });
18138
-
18139
18219
  // src/core/middleware/error.middleware.ts
18140
18220
  import { ZodError } from "zod";
18141
18221
  import { ForbiddenError as CASLForbiddenError2 } from "@casl/ability";
@@ -18313,20 +18393,17 @@ var init_hash = __esm({
18313
18393
  });
18314
18394
 
18315
18395
  // src/core/crypto/symmetric.ts
18316
- import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, hkdfSync } from "crypto";
18396
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, hkdfSync } from "crypto";
18317
18397
  function getKey() {
18318
18398
  if (derivedKey) return derivedKey;
18319
- const secret = process.env["AUTH_SECRET"];
18320
- if (!secret) {
18321
- throw new Error("AUTH_SECRET not configured. Required for symmetric encryption.");
18322
- }
18399
+ const secret = getAuthSecret();
18323
18400
  const keyBytes = hkdfSync("sha256", secret, "", HKDF_INFO, KEY_LENGTH);
18324
18401
  derivedKey = Buffer.from(keyBytes);
18325
18402
  return derivedKey;
18326
18403
  }
18327
18404
  function encrypt(plaintext) {
18328
18405
  const key = getKey();
18329
- const iv = randomBytes2(IV_LENGTH);
18406
+ const iv = randomBytes3(IV_LENGTH);
18330
18407
  const cipher = createCipheriv(ALGORITHM, key, iv);
18331
18408
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
18332
18409
  const authTag = cipher.getAuthTag();
@@ -18350,6 +18427,7 @@ var ALGORITHM, IV_LENGTH, KEY_LENGTH, HKDF_INFO, derivedKey;
18350
18427
  var init_symmetric = __esm({
18351
18428
  "src/core/crypto/symmetric.ts"() {
18352
18429
  "use strict";
18430
+ init_secret_resolver();
18353
18431
  ALGORITHM = "aes-256-gcm";
18354
18432
  IV_LENGTH = 12;
18355
18433
  KEY_LENGTH = 32;
@@ -18575,8 +18653,8 @@ var init_safe_json = __esm({
18575
18653
 
18576
18654
  // src/core/spa-handler.ts
18577
18655
  import express from "express";
18578
- import { resolve, join as join9 } from "path";
18579
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
18656
+ import { resolve, join as join11 } from "path";
18657
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
18580
18658
  function createServeSPA(app, httpServer) {
18581
18659
  return async (endpoint, distPath, options = {}) => {
18582
18660
  const {
@@ -18598,7 +18676,7 @@ function createServeSPA(app, httpServer) {
18598
18676
  registeredEndpoints.add(endpoint);
18599
18677
  if (env.NODE_ENV === "development" && viteSrc) {
18600
18678
  const srcPath = resolve(getProjectPath(), viteSrc);
18601
- if (!existsSync9(srcPath)) {
18679
+ if (!existsSync10(srcPath)) {
18602
18680
  logger.warn({ endpoint, viteSrc, resolved: srcPath }, "Vite source not found \u2014 falling back to static");
18603
18681
  } else {
18604
18682
  const mounted = await mountViteDevMiddleware(app, endpoint, srcPath, httpServer);
@@ -18612,6 +18690,7 @@ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
18612
18690
  try {
18613
18691
  const vite = await import("vite");
18614
18692
  const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18693
+ const singletonDeps = ["vue", "vue-router", "pinia", "naive-ui"];
18615
18694
  const server2 = await vite.createServer({
18616
18695
  root: srcPath,
18617
18696
  server: {
@@ -18619,6 +18698,12 @@ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
18619
18698
  allowedHosts: true,
18620
18699
  hmr: httpServer ? { server: httpServer } : true
18621
18700
  },
18701
+ resolve: {
18702
+ dedupe: singletonDeps
18703
+ },
18704
+ optimizeDeps: {
18705
+ include: singletonDeps
18706
+ },
18622
18707
  plugins: [{
18623
18708
  name: "nexus-config-inject",
18624
18709
  transformIndexHtml(html) {
@@ -18654,24 +18739,24 @@ function mountStaticSPA(app, endpoint, distPath, options) {
18654
18739
  resolvedPath = distPath;
18655
18740
  } else {
18656
18741
  const projectPath2 = resolve(getProjectPath(), distPath);
18657
- if (existsSync9(projectPath2)) {
18742
+ if (existsSync10(projectPath2)) {
18658
18743
  resolvedPath = projectPath2;
18659
18744
  } else {
18660
18745
  resolvedPath = resolve(getLibPath(), distPath);
18661
18746
  }
18662
18747
  }
18663
- if (!existsSync9(resolvedPath)) {
18748
+ if (!existsSync10(resolvedPath)) {
18664
18749
  logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18665
18750
  return;
18666
18751
  }
18667
- const indexPath = join9(resolvedPath, index);
18668
- if (!existsSync9(indexPath)) {
18752
+ const indexPath = join11(resolvedPath, index);
18753
+ if (!existsSync10(indexPath)) {
18669
18754
  logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18670
18755
  }
18671
18756
  app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18672
18757
  let injectedHtml = "";
18673
- if (existsSync9(indexPath)) {
18674
- const rawHtml = readFileSync5(indexPath, "utf-8");
18758
+ if (existsSync10(indexPath)) {
18759
+ const rawHtml = readFileSync6(indexPath, "utf-8");
18675
18760
  const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18676
18761
  const nexusConfig = JSON.stringify({ apiUrl });
18677
18762
  injectedHtml = rawHtml.replace(
@@ -19246,11 +19331,7 @@ var init_cache = __esm({
19246
19331
  // src/core/jwt/index.ts
19247
19332
  import jwt4 from "jsonwebtoken";
19248
19333
  function getSecret() {
19249
- const secret = process.env["AUTH_SECRET"];
19250
- if (!secret) {
19251
- throw new Error("AUTH_SECRET not configured. Set AUTH_SECRET env var.");
19252
- }
19253
- return secret;
19334
+ return getAuthSecret();
19254
19335
  }
19255
19336
  function verifyAccessToken(token) {
19256
19337
  return jwt4.verify(token, getSecret());
@@ -19258,6 +19339,7 @@ function verifyAccessToken(token) {
19258
19339
  var init_jwt = __esm({
19259
19340
  "src/core/jwt/index.ts"() {
19260
19341
  "use strict";
19342
+ init_secret_resolver();
19261
19343
  }
19262
19344
  });
19263
19345
 
@@ -19367,91 +19449,6 @@ var init_schema_helpers = __esm({
19367
19449
  }
19368
19450
  });
19369
19451
 
19370
- // src/db/sql-utils.ts
19371
- function extractTableFromSql(sql, type2) {
19372
- const match = sql.match(SQL_PATTERNS[type2]);
19373
- return match?.[1];
19374
- }
19375
- function extractTableFromSelect(sql) {
19376
- return extractTableFromSql(sql, "select");
19377
- }
19378
- function extractTableFromInsert(sql) {
19379
- return extractTableFromSql(sql, "insert");
19380
- }
19381
- function extractTableFromUpdate(sql) {
19382
- return extractTableFromSql(sql, "update");
19383
- }
19384
- function extractTableFromDelete(sql) {
19385
- return extractTableFromSql(sql, "delete");
19386
- }
19387
- var SQL_PATTERNS;
19388
- var init_sql_utils = __esm({
19389
- "src/db/sql-utils.ts"() {
19390
- "use strict";
19391
- SQL_PATTERNS = {
19392
- select: /from\s+["'`]?(\w+)["'`]?/i,
19393
- insert: /insert into\s+["'`]?(\w+)["'`]?/i,
19394
- update: /update\s+["'`]?(\w+)["'`]?/i,
19395
- delete: /delete from\s+["'`]?(\w+)["'`]?/i
19396
- };
19397
- }
19398
- });
19399
-
19400
- // src/db/sqlite-compat.ts
19401
- function createSqliteBooleanProcessor() {
19402
- const booleanColumns = /* @__PURE__ */ new Map();
19403
- function registerBooleanColumn2(table, column) {
19404
- if (!booleanColumns.has(table)) {
19405
- booleanColumns.set(table, /* @__PURE__ */ new Set());
19406
- }
19407
- booleanColumns.get(table).add(column);
19408
- }
19409
- function convertBooleans2(table, row) {
19410
- const columns = booleanColumns.get(table);
19411
- if (!columns || columns.size === 0) return row;
19412
- const result = { ...row };
19413
- for (const column of columns) {
19414
- if (column in result) {
19415
- const value = result[column];
19416
- if (value === 0 || value === 1) {
19417
- result[column] = value === 1;
19418
- }
19419
- }
19420
- }
19421
- return result;
19422
- }
19423
- function postProcess(result, queryContext) {
19424
- const sql = queryContext?.sql?.toLowerCase() ?? "";
19425
- if (!sql.startsWith("select")) return result;
19426
- const table = extractTableFromSelect(sql);
19427
- if (!table) return result;
19428
- if (Array.isArray(result)) {
19429
- return result.map(
19430
- (row) => typeof row === "object" && row !== null ? convertBooleans2(table, row) : row
19431
- );
19432
- }
19433
- if (typeof result === "object" && result !== null) {
19434
- return convertBooleans2(table, result);
19435
- }
19436
- return result;
19437
- }
19438
- function clear() {
19439
- booleanColumns.clear();
19440
- }
19441
- return { registerBooleanColumn: registerBooleanColumn2, convertBooleans: convertBooleans2, postProcess, clear };
19442
- }
19443
- var defaultProcessor, registerBooleanColumn, convertBooleans, sqlitePostProcess;
19444
- var init_sqlite_compat = __esm({
19445
- "src/db/sqlite-compat.ts"() {
19446
- "use strict";
19447
- init_sql_utils();
19448
- defaultProcessor = createSqliteBooleanProcessor();
19449
- registerBooleanColumn = defaultProcessor.registerBooleanColumn;
19450
- convertBooleans = defaultProcessor.convertBooleans;
19451
- sqlitePostProcess = defaultProcessor.postProcess;
19452
- }
19453
- });
19454
-
19455
19452
  // src/runtime/types.ts
19456
19453
  var init_types = __esm({
19457
19454
  "src/runtime/types.ts"() {
@@ -23737,8 +23734,8 @@ var init_runtime = __esm({
23737
23734
  });
23738
23735
 
23739
23736
  // src/db/seed-runner.ts
23740
- import { existsSync as existsSync10 } from "fs";
23741
- import { join as join10 } from "path";
23737
+ import { existsSync as existsSync11 } from "fs";
23738
+ import { join as join12 } from "path";
23742
23739
  import { pathToFileURL } from "url";
23743
23740
  async function runModuleSeed(mod, ctx) {
23744
23741
  let seeded = false;
@@ -23746,8 +23743,8 @@ async function runModuleSeed(mod, ctx) {
23746
23743
  await mod.seed(ctx);
23747
23744
  seeded = true;
23748
23745
  } else {
23749
- const seedPath = join10(getLibPath(), "dist", "modules", mod.name, `${mod.name}.seed.js`);
23750
- if (existsSync10(seedPath)) {
23746
+ const seedPath = join12(getLibPath(), "dist", "modules", mod.name, `${mod.name}.seed.js`);
23747
+ if (existsSync11(seedPath)) {
23751
23748
  const seedModule = await import(pathToFileURL(seedPath).href);
23752
23749
  if (typeof seedModule.seed === "function") {
23753
23750
  await seedModule.seed(ctx);
@@ -23901,94 +23898,6 @@ var init_migration_sources = __esm({
23901
23898
  }
23902
23899
  });
23903
23900
 
23904
- // src/config/database.ts
23905
- import { join as join11, dirname as dirname6, isAbsolute as isAbsolute2 } from "path";
23906
- import { mkdirSync as mkdirSync4 } from "fs";
23907
- function getDatabaseConfig() {
23908
- const url = getConfig().databaseUrl;
23909
- if (IN_MEMORY_RE.test(url)) {
23910
- return {
23911
- client: "better-sqlite3",
23912
- connection: { filename: ":memory:" },
23913
- useNullAsDefault: true,
23914
- postProcessResponse: sqlitePostProcess,
23915
- pool: { min: 0, max: 1 }
23916
- };
23917
- }
23918
- if (url.startsWith("file:") || url.startsWith("sqlite:")) {
23919
- let filename = url.replace(/^(file:|sqlite:)/, "");
23920
- if (!isAbsolute2(filename)) {
23921
- filename = join11(getProjectPath(), "data", filename);
23922
- }
23923
- mkdirSync4(dirname6(filename), { recursive: true });
23924
- return {
23925
- client: "better-sqlite3",
23926
- connection: { filename },
23927
- useNullAsDefault: true,
23928
- postProcessResponse: sqlitePostProcess
23929
- };
23930
- }
23931
- if (url.startsWith("postgresql://") || url.startsWith("postgres://")) {
23932
- return {
23933
- client: "pg",
23934
- connection: url,
23935
- pool: { min: 2, max: 10 }
23936
- };
23937
- }
23938
- if (url.startsWith("mysql://")) {
23939
- const offsetMinutes = (/* @__PURE__ */ new Date()).getTimezoneOffset();
23940
- const offsetHours = Math.abs(Math.floor(offsetMinutes / 60));
23941
- const offsetMins = Math.abs(offsetMinutes % 60);
23942
- const sign = offsetMinutes <= 0 ? "+" : "-";
23943
- const tzOffset = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMins).padStart(2, "0")}`;
23944
- return {
23945
- client: "mysql2",
23946
- connection: {
23947
- uri: url,
23948
- timezone: tzOffset
23949
- // mysql2 requiere formato "+HH:MM"
23950
- },
23951
- pool: { min: 2, max: 10 }
23952
- };
23953
- }
23954
- throw new Error(`Unsupported database URL: ${url}`);
23955
- }
23956
- function getDatabaseType() {
23957
- const url = getConfig().databaseUrl;
23958
- if (IN_MEMORY_RE.test(url) || url.startsWith("file:") || url.startsWith("sqlite:")) return "sqlite";
23959
- if (url.startsWith("postgresql://") || url.startsWith("postgres://")) return "postgresql";
23960
- if (url.startsWith("mysql://")) return "mysql";
23961
- return "sqlite";
23962
- }
23963
- function getDatabasePath() {
23964
- const url = getConfig().databaseUrl;
23965
- if (IN_MEMORY_RE.test(url)) return ":memory:";
23966
- if (url.startsWith("file:") || url.startsWith("sqlite:")) {
23967
- let filename = url.replace(/^(file:|sqlite:)/, "");
23968
- if (!isAbsolute2(filename)) {
23969
- filename = join11(getProjectPath(), "data", filename);
23970
- }
23971
- return filename;
23972
- }
23973
- try {
23974
- const parsed = new URL(url);
23975
- parsed.password = "***";
23976
- return parsed.toString();
23977
- } catch {
23978
- return url.replace(/:\/\/[^@]+@/, "://***@");
23979
- }
23980
- }
23981
- var IN_MEMORY_RE;
23982
- var init_database = __esm({
23983
- "src/config/database.ts"() {
23984
- "use strict";
23985
- init_env();
23986
- init_paths();
23987
- init_sqlite_compat();
23988
- IN_MEMORY_RE = /^((file:|sqlite:)?:memory:?)$/;
23989
- }
23990
- });
23991
-
23992
23901
  // src/db/query-interceptor.ts
23993
23902
  function setupQueryInterceptor(knexInstance) {
23994
23903
  const queryTimings = /* @__PURE__ */ new Map();
@@ -24629,7 +24538,7 @@ var init_migration_helpers = __esm({
24629
24538
  // src/db/migration-generator.ts
24630
24539
  import path2 from "path";
24631
24540
  import fs2 from "fs/promises";
24632
- import { readFileSync as readFileSync6, mkdirSync as mkdirSync5, realpathSync } from "fs";
24541
+ import { readFileSync as readFileSync7, mkdirSync as mkdirSync5, realpathSync } from "fs";
24633
24542
  function getColumnIndexBytes(field) {
24634
24543
  if (!field?.db) return 255 * MYSQL_BYTES_PER_CHAR;
24635
24544
  const size = field.db.size ?? 255;
@@ -26543,8 +26452,8 @@ var init_db = __esm({
26543
26452
  });
26544
26453
 
26545
26454
  // src/config/load-config.ts
26546
- import { join as join12 } from "path";
26547
- import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync, statSync } from "fs";
26455
+ import { join as join13 } from "path";
26456
+ import { existsSync as existsSync12, readFileSync as readFileSync8, readdirSync, statSync } from "fs";
26548
26457
  import { pathToFileURL as pathToFileURL2 } from "url";
26549
26458
  function isPluginManifest(value) {
26550
26459
  if (!value || typeof value !== "object" || typeof value === "function") return false;
@@ -26560,25 +26469,25 @@ function extractPluginManifest(mod) {
26560
26469
  return null;
26561
26470
  }
26562
26471
  function resolvePluginEntry(projectPath2, pkgName) {
26563
- const pkgDir = join12(projectPath2, "node_modules", pkgName);
26564
- const pkgJsonPath = join12(pkgDir, "package.json");
26565
- if (!existsSync11(pkgJsonPath)) return null;
26472
+ const pkgDir = join13(projectPath2, "node_modules", pkgName);
26473
+ const pkgJsonPath = join13(pkgDir, "package.json");
26474
+ if (!existsSync12(pkgJsonPath)) return null;
26566
26475
  try {
26567
- const pluginPkg = JSON.parse(readFileSync7(pkgJsonPath, "utf-8"));
26476
+ const pluginPkg = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
26568
26477
  const exports = pluginPkg.exports;
26569
26478
  const importEntry = exports?.["."]?.import;
26570
26479
  const entry = (typeof importEntry === "string" ? importEntry : importEntry?.default) ?? (typeof exports === "string" ? exports : null) ?? pluginPkg.main ?? pluginPkg.module ?? "index.js";
26571
- const entryPath = join12(pkgDir, entry);
26572
- return existsSync11(entryPath) ? entryPath : null;
26480
+ const entryPath = join13(pkgDir, entry);
26481
+ return existsSync12(entryPath) ? entryPath : null;
26573
26482
  } catch {
26574
26483
  return null;
26575
26484
  }
26576
26485
  }
26577
26486
  async function discoverPlugins(projectPath2) {
26578
- const pkgPath = join12(projectPath2, "package.json");
26487
+ const pkgPath = join13(projectPath2, "package.json");
26579
26488
  let pkg2;
26580
26489
  try {
26581
- pkg2 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
26490
+ pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
26582
26491
  } catch {
26583
26492
  return [];
26584
26493
  }
@@ -26600,21 +26509,21 @@ async function discoverPlugins(projectPath2) {
26600
26509
  const manifest = extractPluginManifest(mod);
26601
26510
  if (manifest) {
26602
26511
  if (!manifest.migrationsDir) {
26603
- const defaultMigrationsDir = join12(projectPath2, "node_modules", pkgName, "migrations");
26604
- if (existsSync11(defaultMigrationsDir)) {
26512
+ const defaultMigrationsDir = join13(projectPath2, "node_modules", pkgName, "migrations");
26513
+ if (existsSync12(defaultMigrationsDir)) {
26605
26514
  manifest.migrationsDir = defaultMigrationsDir;
26606
26515
  }
26607
26516
  }
26608
26517
  if (!manifest.image) {
26609
- const imagePath = join12(projectPath2, "node_modules", pkgName, "image.png");
26610
- if (existsSync11(imagePath)) {
26518
+ const imagePath = join13(projectPath2, "node_modules", pkgName, "image.png");
26519
+ if (existsSync12(imagePath)) {
26611
26520
  manifest.image = imagePath;
26612
26521
  }
26613
26522
  }
26614
26523
  if (!manifest.llms) {
26615
- const llmsPath = join12(projectPath2, "node_modules", pkgName, "llms.txt");
26616
- if (existsSync11(llmsPath)) {
26617
- manifest.llms = readFileSync7(llmsPath, "utf-8");
26524
+ const llmsPath = join13(projectPath2, "node_modules", pkgName, "llms.txt");
26525
+ if (existsSync12(llmsPath)) {
26526
+ manifest.llms = readFileSync8(llmsPath, "utf-8");
26618
26527
  }
26619
26528
  }
26620
26529
  discovered.push(manifest);
@@ -26650,8 +26559,8 @@ function extractModuleManifests(mod) {
26650
26559
  return manifests;
26651
26560
  }
26652
26561
  async function discoverModules(projectPath2) {
26653
- const modulesDir = process.env["MODULES_DIR"] || join12(projectPath2, "src", "modules");
26654
- if (!existsSync11(modulesDir)) return [];
26562
+ const modulesDir = process.env["MODULES_DIR"] || join13(projectPath2, "src", "modules");
26563
+ if (!existsSync12(modulesDir)) return [];
26655
26564
  let entries;
26656
26565
  try {
26657
26566
  entries = readdirSync(modulesDir);
@@ -26660,7 +26569,7 @@ async function discoverModules(projectPath2) {
26660
26569
  }
26661
26570
  const discovered = [];
26662
26571
  for (const entry of entries) {
26663
- const entryPath = join12(modulesDir, entry);
26572
+ const entryPath = join13(modulesDir, entry);
26664
26573
  try {
26665
26574
  if (!statSync(entryPath).isDirectory()) continue;
26666
26575
  } catch {
@@ -26668,8 +26577,8 @@ async function discoverModules(projectPath2) {
26668
26577
  }
26669
26578
  let indexPath = null;
26670
26579
  for (const ext of ["index.js", "index.ts"]) {
26671
- const candidate = join12(entryPath, ext);
26672
- if (existsSync11(candidate)) {
26580
+ const candidate = join13(entryPath, ext);
26581
+ if (existsSync12(candidate)) {
26673
26582
  indexPath = candidate;
26674
26583
  break;
26675
26584
  }
@@ -26783,6 +26692,49 @@ var init_events_api = __esm({
26783
26692
  }
26784
26693
  });
26785
26694
 
26695
+ // src/engine/capabilities-registry.ts
26696
+ var CapabilitiesRegistry;
26697
+ var init_capabilities_registry = __esm({
26698
+ "src/engine/capabilities-registry.ts"() {
26699
+ "use strict";
26700
+ CapabilitiesRegistry = class {
26701
+ resolvers = /* @__PURE__ */ new Map();
26702
+ logger;
26703
+ constructor(logger2) {
26704
+ this.logger = logger2;
26705
+ }
26706
+ register(key, resolver) {
26707
+ if (this.resolvers.has(key)) {
26708
+ this.logger?.warn({ key }, "Capability key already registered, overwriting");
26709
+ }
26710
+ this.resolvers.set(key, resolver);
26711
+ }
26712
+ async resolve() {
26713
+ const entries = Array.from(this.resolvers.entries());
26714
+ const results = await Promise.allSettled(
26715
+ entries.map(async ([key, resolver]) => {
26716
+ const value = await resolver();
26717
+ return [key, value];
26718
+ })
26719
+ );
26720
+ const capabilities = {};
26721
+ for (const result of results) {
26722
+ if (result.status === "fulfilled") {
26723
+ const [key, value] = result.value;
26724
+ capabilities[key] = value;
26725
+ } else {
26726
+ this.logger?.warn(
26727
+ { err: result.reason?.message ?? String(result.reason) },
26728
+ "Capability resolver failed"
26729
+ );
26730
+ }
26731
+ }
26732
+ return capabilities;
26733
+ }
26734
+ };
26735
+ }
26736
+ });
26737
+
26786
26738
  // src/engine/context.ts
26787
26739
  import { ForbiddenError as CASLForbiddenError3, subject } from "@casl/ability";
26788
26740
  import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2, DEFAULT_LOCALES } from "@gzl10/nexus-sdk";
@@ -27056,6 +27008,9 @@ function createModuleContext() {
27056
27008
  return name in adaptersRegistry;
27057
27009
  }
27058
27010
  };
27011
+ const capabilitiesRegistry = new CapabilitiesRegistry(
27012
+ logger.child({ service: "capabilities" })
27013
+ );
27059
27014
  const ctx = {
27060
27015
  tenantId: DEFAULT_TENANT_ID2,
27061
27016
  core: coreContext,
@@ -27066,6 +27021,7 @@ function createModuleContext() {
27066
27021
  engine: engineContext,
27067
27022
  services: servicesContext,
27068
27023
  adapters: adaptersContext,
27024
+ capabilities: capabilitiesRegistry,
27069
27025
  // Root-level shortcuts for frequently used utilities
27070
27026
  events: createEventsApi(nexusEvents, logger),
27071
27027
  createRouter: () => createRouter(),
@@ -27079,6 +27035,9 @@ function createModuleContext() {
27079
27035
  createEntityRouter: (controller, def) => createEntityRouter(controller, def, ctx)
27080
27036
  };
27081
27037
  ctx.db.seedModule = (mod) => runModuleSeed(mod, ctx);
27038
+ capabilitiesRegistry.register("version", () => getCoreManifest().version);
27039
+ capabilitiesRegistry.register("plugins", () => getPlugins().map((p) => p.code));
27040
+ capabilitiesRegistry.register("locales", () => platformLocales);
27082
27041
  return ctx;
27083
27042
  }
27084
27043
  var platformLocales, sharedCacheManager, sharedTempAdapter;
@@ -27094,6 +27053,7 @@ var init_context = __esm({
27094
27053
  init_plugin_ops();
27095
27054
  init_load_config();
27096
27055
  init_events_api();
27056
+ init_capabilities_registry();
27097
27057
  init_seed_runner();
27098
27058
  init_cache_manager();
27099
27059
  platformLocales = DEFAULT_LOCALES;
@@ -27113,6 +27073,7 @@ var init_engine = __esm({
27113
27073
  init_subject_extractor();
27114
27074
  init_definition_extractors();
27115
27075
  init_context();
27076
+ init_capabilities_registry();
27116
27077
  }
27117
27078
  });
27118
27079
 
@@ -27855,7 +27816,7 @@ var init_port_check = __esm({
27855
27816
  // src/core/tunnel.ts
27856
27817
  import { spawn, execSync as execSync2 } from "child_process";
27857
27818
  import { writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
27858
- import { join as join13 } from "path";
27819
+ import { join as join14 } from "path";
27859
27820
  import { tmpdir } from "os";
27860
27821
  function buildFrpcConfig(config3) {
27861
27822
  const lines = [
@@ -27893,7 +27854,7 @@ async function startTunnel(config3) {
27893
27854
  return false;
27894
27855
  }
27895
27856
  const toml = buildFrpcConfig(config3);
27896
- tmpConfigPath = join13(tmpdir(), `nexus-frpc-${process.pid}.toml`);
27857
+ tmpConfigPath = join14(tmpdir(), `nexus-frpc-${process.pid}.toml`);
27897
27858
  writeFileSync2(tmpConfigPath, toml);
27898
27859
  tunnelProcess = spawn("frpc", ["-c", tmpConfigPath], {
27899
27860
  stdio: ["ignore", "pipe", "pipe"]
@@ -27984,22 +27945,22 @@ var init_shared = __esm({
27984
27945
  });
27985
27946
 
27986
27947
  // src/cli/migrate-commands.ts
27987
- import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
27988
- import { join as join14 } from "path";
27948
+ import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
27949
+ import { join as join15 } from "path";
27989
27950
  import { pathToFileURL as pathToFileURL3 } from "url";
27990
27951
  import Table from "cli-table3";
27991
27952
  import { consola as consola2 } from "consola";
27992
27953
  async function loadSelfPlugin() {
27993
27954
  const projectPath2 = getProjectPath();
27994
- const pkgPath = join14(projectPath2, "package.json");
27995
- if (!existsSync12(pkgPath)) return;
27955
+ const pkgPath = join15(projectPath2, "package.json");
27956
+ if (!existsSync13(pkgPath)) return;
27996
27957
  try {
27997
- const pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
27958
+ const pkg2 = JSON.parse(readFileSync9(pkgPath, "utf-8"));
27998
27959
  const pkgName = pkg2?.name;
27999
27960
  if (!pkgName || !/nexus-plugin-/.test(pkgName)) return;
28000
- const srcEntry = join14(projectPath2, "src", "index.ts");
28001
- const distEntry = join14(projectPath2, "dist", "index.js");
28002
- if (existsSync12(srcEntry)) {
27961
+ const srcEntry = join15(projectPath2, "src", "index.ts");
27962
+ const distEntry = join15(projectPath2, "dist", "index.js");
27963
+ if (existsSync13(srcEntry)) {
28003
27964
  try {
28004
27965
  const { tsImport } = await import("tsx/esm/api");
28005
27966
  const mod = await tsImport(
@@ -28009,7 +27970,7 @@ async function loadSelfPlugin() {
28009
27970
  const manifest = extractPluginManifest(mod);
28010
27971
  if (manifest) {
28011
27972
  if (!manifest.migrationsDir) {
28012
- manifest.migrationsDir = join14(projectPath2, "migrations");
27973
+ manifest.migrationsDir = join15(projectPath2, "migrations");
28013
27974
  }
28014
27975
  registerPlugin(manifest);
28015
27976
  return;
@@ -28018,13 +27979,13 @@ async function loadSelfPlugin() {
28018
27979
  console.error(` \u26A0 Failed to load plugin src/index.ts: ${err.message}`);
28019
27980
  }
28020
27981
  }
28021
- if (existsSync12(distEntry)) {
27982
+ if (existsSync13(distEntry)) {
28022
27983
  try {
28023
27984
  const mod = await import(pathToFileURL3(distEntry).href);
28024
27985
  const manifest = extractPluginManifest(mod);
28025
27986
  if (manifest) {
28026
27987
  if (!manifest.migrationsDir) {
28027
- manifest.migrationsDir = join14(projectPath2, "migrations");
27988
+ manifest.migrationsDir = join15(projectPath2, "migrations");
28028
27989
  }
28029
27990
  registerPlugin(manifest);
28030
27991
  return;
@@ -28074,8 +28035,8 @@ __export(seed_commands_exports, {
28074
28035
  handleSeedExport: () => handleSeedExport,
28075
28036
  importSeedFiles: () => importSeedFiles
28076
28037
  });
28077
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
28078
- import { join as join15, basename as basename4 } from "path";
28038
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
28039
+ import { join as join16, basename as basename4 } from "path";
28079
28040
  import { consola as consola3 } from "consola";
28080
28041
  function deserializeJsonFields(record, fields) {
28081
28042
  const result = { ...record };
@@ -28104,7 +28065,7 @@ async function handleSeedExport(entity) {
28104
28065
  const db2 = getDb();
28105
28066
  try {
28106
28067
  const modules = getOrderedModules();
28107
- const seedDir = join15(getProjectPath(), "data", "seeds");
28068
+ const seedDir = join16(getProjectPath(), "data", "seeds");
28108
28069
  const seedableEntities = [];
28109
28070
  for (const mod of modules) {
28110
28071
  for (const def of mod.definitions ?? []) {
@@ -28125,7 +28086,7 @@ async function handleSeedExport(entity) {
28125
28086
  }
28126
28087
  return;
28127
28088
  }
28128
- if (!existsSync13(seedDir)) {
28089
+ if (!existsSync14(seedDir)) {
28129
28090
  mkdirSync6(seedDir, { recursive: true });
28130
28091
  }
28131
28092
  for (const { module: modName, table, fields } of seedableEntities) {
@@ -28137,7 +28098,7 @@ async function handleSeedExport(entity) {
28137
28098
  const exported = rows.map(
28138
28099
  (row) => deserializeJsonFields(row, fields)
28139
28100
  );
28140
- const filePath = join15(seedDir, `${table}.json`);
28101
+ const filePath = join16(seedDir, `${table}.json`);
28141
28102
  writeFileSync3(filePath, JSON.stringify(exported, null, 2) + "\n", "utf-8");
28142
28103
  consola3.success(`${table}: exported ${rows.length} records to data/seeds/${table}.json (module: ${modName})`);
28143
28104
  }
@@ -28149,8 +28110,8 @@ async function handleSeedExport(entity) {
28149
28110
  }
28150
28111
  }
28151
28112
  async function importSeedFiles(db2, modules, logger2) {
28152
- const seedDir = join15(getProjectPath(), "data", "seeds");
28153
- if (!existsSync13(seedDir)) return;
28113
+ const seedDir = join16(getProjectPath(), "data", "seeds");
28114
+ if (!existsSync14(seedDir)) return;
28154
28115
  const files = readdirSync2(seedDir).filter((f) => f.endsWith(".json"));
28155
28116
  if (files.length === 0) return;
28156
28117
  const seedableDefs = /* @__PURE__ */ new Map();
@@ -28171,8 +28132,8 @@ async function importSeedFiles(db2, modules, logger2) {
28171
28132
  logger2.debug(`data/seeds/${file}: skipped (entity "${table}" is not seedable)`);
28172
28133
  continue;
28173
28134
  }
28174
- const filePath = join15(seedDir, file);
28175
- const raw = readFileSync9(filePath, "utf-8");
28135
+ const filePath = join16(seedDir, file);
28136
+ const raw = readFileSync10(filePath, "utf-8");
28176
28137
  let records;
28177
28138
  try {
28178
28139
  records = JSON.parse(raw);
@@ -28418,6 +28379,12 @@ async function runMigrationsAndSeeds(config3) {
28418
28379
  loadCoreModules();
28419
28380
  logger.info({ type: getDatabaseType(), path: getDatabasePath() }, "Database ready");
28420
28381
  await ensureSystemTables(getDb());
28382
+ setSecretResolverDb(getDb());
28383
+ await initAuthSecret(logger);
28384
+ const isPM2Cluster = !!(process.env["PM2_HOME"] && process.env["NODE_APP_INSTANCE"]);
28385
+ if (isPM2Cluster && getDatabaseType() === "sqlite") {
28386
+ logger.warn("SQLite detected with PM2 cluster mode. SQLite does not support concurrent writes reliably. Use PostgreSQL for multi-process deployments, or set PM2 instances to 1.");
28387
+ }
28421
28388
  const fileConfig = await loadNexusConfig();
28422
28389
  const effectiveConfig = { ...config3 };
28423
28390
  const programmaticPluginNames = new Set((config3?.plugins ?? []).map((p) => p.name));
@@ -28672,6 +28639,7 @@ async function stop() {
28672
28639
  resetConfigCache();
28673
28640
  clearCustomCaslRules();
28674
28641
  clearSeedPermissions();
28642
+ _resetSecretResolver();
28675
28643
  await resetServeSPA();
28676
28644
  return;
28677
28645
  }
@@ -28707,6 +28675,7 @@ async function stop() {
28707
28675
  resetConfigCache();
28708
28676
  clearCustomCaslRules();
28709
28677
  clearSeedPermissions();
28678
+ _resetSecretResolver();
28710
28679
  await resetServeSPA();
28711
28680
  currentConfig = void 0;
28712
28681
  server = null;
@@ -28776,6 +28745,7 @@ var init_server = __esm({
28776
28745
  init_ensure_system_tables();
28777
28746
  init_load_config();
28778
28747
  init_tunnel();
28748
+ init_secret_resolver();
28779
28749
  server = null;
28780
28750
  gracefulShutdownRegistered = false;
28781
28751
  setupGracefulShutdown();