@gzl10/nexus-backend 0.17.0 → 0.19.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.17.0",
17
+ version: "0.19.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",
@@ -26,6 +26,14 @@ var init_package = __esm({
26
26
  ".": {
27
27
  import: "./dist/index.js",
28
28
  types: "./dist/index.d.ts"
29
+ },
30
+ "./testing": {
31
+ import: "./dist/testing/index.js",
32
+ types: "./dist/testing/index.d.ts"
33
+ },
34
+ "./migrations": {
35
+ import: "./dist/migration-helpers/index.js",
36
+ types: "./dist/migration-helpers/index.d.ts"
29
37
  }
30
38
  },
31
39
  files: [
@@ -70,7 +78,7 @@ var init_package = __esm({
70
78
  "jwt"
71
79
  ],
72
80
  scripts: {
73
- dev: "tsx watch src/main.ts",
81
+ dev: "node --watch-path=./src --import tsx/esm src/main.ts",
74
82
  build: "tsup",
75
83
  start: "node dist/main.js",
76
84
  nexus: "tsx src/cli.ts",
@@ -126,39 +134,34 @@ var init_package = __esm({
126
134
  zod: "^3.24.0"
127
135
  },
128
136
  devDependencies: {
129
- "@gzl10/nexus-plugin-ai": "workspace:^",
130
- "@gzl10/nexus-plugin-auth-providers": "workspace:^",
131
- "@gzl10/nexus-plugin-charts": "workspace:^",
132
- "@gzl10/nexus-plugin-cms": "workspace:^",
133
- "@gzl10/nexus-plugin-compliance": "workspace:^",
134
- "@gzl10/nexus-plugin-docker": "workspace:^",
135
- "@gzl10/nexus-plugin-feeds": "workspace:^",
136
- "@gzl10/nexus-plugin-importer": "workspace:^",
137
- "@gzl10/nexus-plugin-links": "workspace:*",
138
- "@gzl10/nexus-plugin-notifications": "workspace:*",
139
- "@gzl10/nexus-plugin-oidc-server": "workspace:^",
140
- "@gzl10/nexus-plugin-plane": "workspace:^",
141
- "@gzl10/nexus-plugin-prisma": "workspace:^",
142
- "@gzl10/nexus-plugin-remote": "workspace:*",
143
- "@gzl10/nexus-plugin-schedules": "workspace:*",
144
- "@gzl10/nexus-plugin-scim": "workspace:^",
145
- "@gzl10/nexus-plugin-scraper": "workspace:^",
146
- "@gzl10/nexus-plugin-secrets": "workspace:^",
147
- "@gzl10/nexus-plugin-tags": "workspace:*",
148
- "@gzl10/nexus-plugin-webhooks": "workspace:*",
149
137
  "@types/bcryptjs": "^2.4.0",
150
138
  "@types/compression": "^1.8.1",
151
139
  "@types/cookie-parser": "^1.4.10",
152
140
  "@types/cors": "^2.8.19",
153
141
  "@types/express": "^5.0.6",
142
+ "@types/express-serve-static-core": "^5.1.1",
154
143
  "@types/jsonwebtoken": "^9.0.10",
155
144
  "@types/multer": "^2.1.0",
156
145
  "@types/nodemailer": "^7.0.11",
146
+ "@types/qs": "^6.15.0",
157
147
  "@types/supertest": "^6.0.3",
158
148
  "pino-pretty": "^13.1.3",
159
149
  "socket.io-client": "^4.8.3",
160
150
  supertest: "^7.2.2",
161
- tsx: "^4.21.0"
151
+ tsx: "^4.21.0",
152
+ vite: "^8.0.3"
153
+ },
154
+ peerDependencies: {
155
+ vite: ">=6.0.0",
156
+ vitest: ">=3.0.0"
157
+ },
158
+ peerDependenciesMeta: {
159
+ vite: {
160
+ optional: true
161
+ },
162
+ vitest: {
163
+ optional: true
164
+ }
162
165
  },
163
166
  publishConfig: {
164
167
  access: "public",
@@ -1059,6 +1062,14 @@ async function isWorkspacePackage(name) {
1059
1062
  }
1060
1063
  }
1061
1064
  async function installPlugin(name, opts) {
1065
+ const projectPath2 = opts?.projectPath ?? process.cwd();
1066
+ const pkgPath = join2(projectPath2, "package.json");
1067
+ if (existsSync2(pkgPath)) {
1068
+ const pkg3 = JSON.parse(readFileSync(pkgPath, "utf-8"));
1069
+ if (pkg3.name === "@gzl10/nexus-backend") {
1070
+ throw new Error("Cannot install plugins inside @gzl10/nexus-backend. Run this command from a Nexus project directory.");
1071
+ }
1072
+ }
1062
1073
  const isWorkspace = await isWorkspacePackage(name);
1063
1074
  let pkg2;
1064
1075
  if (opts?.version) {
@@ -1075,11 +1086,28 @@ async function installPlugin(name, opts) {
1075
1086
  plugins[name] = { enabled: true };
1076
1087
  writePluginsFile(plugins, filePath);
1077
1088
  }
1089
+ function isPackageInstalled(name, projectPath2) {
1090
+ const base = projectPath2 || process.cwd();
1091
+ const pkgPath = join2(base, "package.json");
1092
+ try {
1093
+ const pkg2 = JSON.parse(readFileSync(pkgPath, "utf-8"));
1094
+ return !!(pkg2.dependencies?.[name] || pkg2.devDependencies?.[name]);
1095
+ } catch {
1096
+ return false;
1097
+ }
1098
+ }
1078
1099
  async function uninstallPlugin(name, projectPath2) {
1079
- await execAsync(`pnpm remove ${name}`);
1080
1100
  const filePath = getPluginsFilePath(projectPath2);
1081
1101
  const plugins = readPluginsFile(filePath);
1082
- if (name in plugins) {
1102
+ const isRegistered = name in plugins;
1103
+ const isInstalled = isPackageInstalled(name, projectPath2);
1104
+ if (!isRegistered && !isInstalled) {
1105
+ throw new Error(`Plugin ${shortPluginName(name)} is not installed`);
1106
+ }
1107
+ if (isInstalled) {
1108
+ await execAsync(`pnpm remove ${name}`);
1109
+ }
1110
+ if (isRegistered) {
1083
1111
  delete plugins[name];
1084
1112
  writePluginsFile(plugins, filePath);
1085
1113
  }
@@ -1160,7 +1188,7 @@ var init_table_prefix = __esm({
1160
1188
  }
1161
1189
  });
1162
1190
 
1163
- // src/engine/store.ts
1191
+ // src/engine/module-store.ts
1164
1192
  function resetStore() {
1165
1193
  moduleStore.modules.length = 0;
1166
1194
  moduleStore.plugins.clear();
@@ -1171,8 +1199,8 @@ function resetStore() {
1171
1199
  moduleStore.tableToSubject.clear();
1172
1200
  }
1173
1201
  var moduleStore;
1174
- var init_store = __esm({
1175
- "src/engine/store.ts"() {
1202
+ var init_module_store = __esm({
1203
+ "src/engine/module-store.ts"() {
1176
1204
  "use strict";
1177
1205
  moduleStore = {
1178
1206
  /** Registered modules with source metadata */
@@ -1225,7 +1253,7 @@ var init_id = __esm({
1225
1253
  }
1226
1254
  });
1227
1255
 
1228
- // src/engine/extractors.ts
1256
+ // src/engine/definition-extractors.ts
1229
1257
  function getTableAndSubject(def) {
1230
1258
  const caslSubject = def.casl?.subject;
1231
1259
  if (!TYPES_WITH_TABLE.has(def.type)) {
@@ -1293,8 +1321,8 @@ function validateModuleDependencies(modules) {
1293
1321
  }
1294
1322
  }
1295
1323
  var TYPES_WITH_TABLE;
1296
- var init_extractors = __esm({
1297
- "src/engine/extractors.ts"() {
1324
+ var init_definition_extractors = __esm({
1325
+ "src/engine/definition-extractors.ts"() {
1298
1326
  "use strict";
1299
1327
  TYPES_WITH_TABLE = /* @__PURE__ */ new Set(["collection", "reference", "event", "config", "temp", "view", void 0]);
1300
1328
  }
@@ -1414,9 +1442,9 @@ var init_registry = __esm({
1414
1442
  "use strict";
1415
1443
  init_plugin_ops();
1416
1444
  init_table_prefix();
1417
- init_store();
1445
+ init_module_store();
1418
1446
  init_id();
1419
- init_extractors();
1447
+ init_definition_extractors();
1420
1448
  }
1421
1449
  });
1422
1450
 
@@ -1479,7 +1507,28 @@ var init_paths = __esm({
1479
1507
  }
1480
1508
  });
1481
1509
 
1482
- // src/engine/queries.ts
1510
+ // src/engine/module-queries.ts
1511
+ var module_queries_exports = {};
1512
+ __export(module_queries_exports, {
1513
+ getCoreManifest: () => getCoreManifest,
1514
+ getCoreModules: () => getCoreModules,
1515
+ getModule: () => getModule,
1516
+ getModules: () => getModules,
1517
+ getOrderedModules: () => getOrderedModules,
1518
+ getOrderedModulesInternal: () => getOrderedModulesInternal,
1519
+ getPlugin: () => getPlugin,
1520
+ getPluginByCode: () => getPluginByCode,
1521
+ getPlugins: () => getPlugins,
1522
+ getRegisteredSubjects: () => getRegisteredSubjects,
1523
+ getSubjectForTable: () => getSubjectForTable,
1524
+ getUserManifest: () => getUserManifest,
1525
+ getUserModules: () => getUserModules,
1526
+ hasModule: () => hasModule,
1527
+ hasPlugin: () => hasPlugin,
1528
+ hasPluginByCode: () => hasPluginByCode,
1529
+ hasUserApp: () => hasUserApp,
1530
+ isValidSubject: () => isValidSubject
1531
+ });
1483
1532
  import { join as join4 } from "path";
1484
1533
  import { readFileSync as readFileSync3 } from "fs";
1485
1534
  function readPackageJson(dir) {
@@ -1575,10 +1624,10 @@ function getPluginByCode(code) {
1575
1624
  function hasPluginByCode(code) {
1576
1625
  return moduleStore.pluginsByCode.has(code);
1577
1626
  }
1578
- var init_queries = __esm({
1579
- "src/engine/queries.ts"() {
1627
+ var init_module_queries = __esm({
1628
+ "src/engine/module-queries.ts"() {
1580
1629
  "use strict";
1581
- init_store();
1630
+ init_module_store();
1582
1631
  init_paths();
1583
1632
  }
1584
1633
  });
@@ -2058,17 +2107,22 @@ var init_definitions = __esm({
2058
2107
  };
2059
2108
  mastersEntity = {
2060
2109
  table: "masters",
2110
+ seedable: true,
2061
2111
  routePrefix: "/",
2062
2112
  label: { en: "Master", es: "Maestro" },
2063
2113
  labelPlural: { en: "Masters", es: "Maestros" },
2064
2114
  labelField: "label",
2065
2115
  timestamps: true,
2116
+ availableDisplayModes: ["board"],
2117
+ groupBy: "type",
2118
+ groupableFields: ["type"],
2119
+ //columnDragFields: ['is_active'],
2066
2120
  fields: {
2067
2121
  id: useTextField2({
2068
2122
  label: { en: "ID", es: "ID" },
2069
2123
  required: true,
2070
2124
  size: 100,
2071
- hint: { en: "Format: type:code (e.g. countries:ES)", es: "Formato: type:code (ej. countries:ES)" },
2125
+ hidden: true,
2072
2126
  meta: { sortable: true }
2073
2127
  }),
2074
2128
  type: useTextField2({
@@ -2085,13 +2139,31 @@ var init_definitions = __esm({
2085
2139
  meta: { sortable: true, searchable: true }
2086
2140
  }),
2087
2141
  label: useLocalizedField({ label: { en: "Name", es: "Nombre" } }),
2088
- order: orderField,
2142
+ order: { ...orderField, meta: { ...orderField.meta, showInDisplay: false } },
2089
2143
  is_active: isActiveField,
2090
2144
  metadata: useJsonField({
2091
2145
  label: { en: "Metadata", es: "Metadatos" },
2092
- hint: { en: "Type-specific fields (symbol, flag, etc.)", es: "Campos espec\xEDficos del tipo" }
2146
+ hint: {
2147
+ en: "Type-specific fields (symbol, flag, etc.)",
2148
+ es: "Campos espec\xEDficos del tipo"
2149
+ },
2150
+ meta: { showInDisplay: false }
2093
2151
  })
2094
2152
  },
2153
+ hooks: () => ({
2154
+ beforeCreate: async (data) => {
2155
+ if (data["type"] && data["code"] && !data["id"]) {
2156
+ data["id"] = `${data["type"]}:${data["code"]}`;
2157
+ }
2158
+ return data;
2159
+ },
2160
+ beforeUpdate: async (_id, data) => {
2161
+ if ("type" in data || "code" in data) {
2162
+ throw new Error("Cannot update type or code of a master record. Delete and recreate instead.");
2163
+ }
2164
+ return data;
2165
+ }
2166
+ }),
2095
2167
  defaultSort: { field: "order", order: "asc" },
2096
2168
  indexes: [{ columns: ["type", "code"], unique: true }],
2097
2169
  casl: { subject: "Master", permissions: masterCaslPermissions }
@@ -2100,6 +2172,10 @@ var init_definitions = __esm({
2100
2172
  });
2101
2173
 
2102
2174
  // src/modules/masters/registry.ts
2175
+ var registry_exports = {};
2176
+ __export(registry_exports, {
2177
+ createMasterRegistry: () => createMasterRegistry
2178
+ });
2103
2179
  function createMasterRegistry() {
2104
2180
  const registrations = [];
2105
2181
  return {
@@ -2137,7 +2213,7 @@ function createMasterRegistry() {
2137
2213
  await ctx.db.knex("masters").insert(row).onConflict(["type", "code"]).merge();
2138
2214
  }
2139
2215
  } else {
2140
- await ctx.db.knex("masters").insert(rows);
2216
+ await ctx.db.knex("masters").insert(rows).onConflict(["type", "code"]).ignore();
2141
2217
  }
2142
2218
  }
2143
2219
  },
@@ -8570,15 +8646,12 @@ var init_document_types = __esm({
8570
8646
  }
8571
8647
  });
8572
8648
 
8573
- // src/modules/masters/index.ts
8574
- var PREDEFINED_MASTERS, mastersModule;
8575
- var init_masters = __esm({
8576
- "src/modules/masters/index.ts"() {
8649
+ // src/modules/masters/constants.ts
8650
+ var DEFAULT_MASTER_TYPES, PREDEFINED_MASTERS;
8651
+ var init_constants = __esm({
8652
+ "src/modules/masters/constants.ts"() {
8577
8653
  "use strict";
8578
- init_definitions();
8579
- init_registry2();
8580
- init_definitions();
8581
- init_registry2();
8654
+ DEFAULT_MASTER_TYPES = ["languages", "timezones"];
8582
8655
  PREDEFINED_MASTERS = {
8583
8656
  currencies: () => Promise.resolve().then(() => (init_currencies(), currencies_exports)).then((m2) => m2.default),
8584
8657
  languages: () => Promise.resolve().then(() => (init_languages(), languages_exports)).then((m2) => m2.default),
@@ -8595,15 +8668,137 @@ var init_masters = __esm({
8595
8668
  "phone-prefixes": () => Promise.resolve().then(() => (init_phone_prefixes(), phone_prefixes_exports)).then((m2) => m2.default),
8596
8669
  "document-types": () => Promise.resolve().then(() => (init_document_types(), document_types_exports)).then((m2) => m2.default)
8597
8670
  };
8671
+ }
8672
+ });
8673
+
8674
+ // src/modules/masters/actions/install-type.ts
8675
+ import { useSelectField as useSelectField2 } from "@gzl10/nexus-sdk/fields";
8676
+ var installTypeAction;
8677
+ var init_install_type = __esm({
8678
+ "src/modules/masters/actions/install-type.ts"() {
8679
+ "use strict";
8680
+ init_constants();
8681
+ installTypeAction = {
8682
+ key: "install-type",
8683
+ scope: "module",
8684
+ label: { en: "Install Master Type", es: "Instalar Tipo de Maestro" },
8685
+ icon: "mdi:database-plus",
8686
+ variant: "primary",
8687
+ output: {},
8688
+ input: {
8689
+ type: useSelectField2({
8690
+ label: { en: "Master Type", es: "Tipo de Maestro" },
8691
+ required: true,
8692
+ options: Object.keys(PREDEFINED_MASTERS).filter((t) => !DEFAULT_MASTER_TYPES.includes(t)).map((t) => ({
8693
+ value: t,
8694
+ label: t.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
8695
+ }))
8696
+ })
8697
+ },
8698
+ handler: async (ctx, input) => {
8699
+ if (process.env["NODE_ENV"] === "production") {
8700
+ throw new ctx.core.errors.ForbiddenError("Master type management is only available in development");
8701
+ }
8702
+ const { type: type2 } = input;
8703
+ const loader = PREDEFINED_MASTERS[type2];
8704
+ if (!loader) {
8705
+ throw new ctx.core.errors.AppError(`Unknown predefined master type: ${type2}`, 400);
8706
+ }
8707
+ const existing = await ctx.db.knex("masters").where({ type: type2 }).first();
8708
+ if (existing) {
8709
+ throw new ctx.core.errors.ConflictError(`Master type "${type2}" is already installed`);
8710
+ }
8711
+ const entries = await loader();
8712
+ const rows = entries.map((entry, i) => ({
8713
+ id: `${type2}:${entry.code}`,
8714
+ type: type2,
8715
+ code: entry.code,
8716
+ label: JSON.stringify(typeof entry.label === "string" ? { en: entry.label } : entry.label),
8717
+ order: entry.order ?? i,
8718
+ is_active: entry.is_active ?? true,
8719
+ metadata: entry.metadata ? JSON.stringify(entry.metadata) : null
8720
+ }));
8721
+ await ctx.db.knex("masters").insert(rows).onConflict(["type", "code"]).ignore();
8722
+ return { installed: type2, count: rows.length };
8723
+ }
8724
+ };
8725
+ }
8726
+ });
8727
+
8728
+ // src/modules/masters/actions/uninstall-type.ts
8729
+ import { useTextField as useTextField3 } from "@gzl10/nexus-sdk/fields";
8730
+ var uninstallTypeAction;
8731
+ var init_uninstall_type = __esm({
8732
+ "src/modules/masters/actions/uninstall-type.ts"() {
8733
+ "use strict";
8734
+ init_constants();
8735
+ uninstallTypeAction = {
8736
+ key: "uninstall-type",
8737
+ scope: "module",
8738
+ label: { en: "Uninstall Master Type", es: "Desinstalar Tipo de Maestro" },
8739
+ icon: "mdi:database-minus",
8740
+ variant: "danger",
8741
+ output: {},
8742
+ confirm: {
8743
+ type: "simple",
8744
+ title: { en: "Uninstall Master Type", es: "Desinstalar Tipo de Maestro" },
8745
+ message: {
8746
+ en: "This will delete ALL records of the selected type. This action cannot be undone.",
8747
+ es: "Esto eliminar\xE1 TODOS los registros del tipo seleccionado. Esta acci\xF3n no se puede deshacer."
8748
+ }
8749
+ },
8750
+ input: {
8751
+ type: useTextField3({
8752
+ label: { en: "Type slug to uninstall", es: "Slug del tipo a desinstalar" },
8753
+ required: true,
8754
+ hint: {
8755
+ en: 'Enter the master type slug (e.g., "currencies", "countries")',
8756
+ es: 'Introduce el slug del tipo (ej: "currencies", "countries")'
8757
+ }
8758
+ })
8759
+ },
8760
+ handler: async (ctx, input) => {
8761
+ if (process.env["NODE_ENV"] === "production") {
8762
+ throw new ctx.core.errors.ForbiddenError("Master type management is only available in development");
8763
+ }
8764
+ const { type: type2 } = input;
8765
+ if (DEFAULT_MASTER_TYPES.includes(type2)) {
8766
+ throw new ctx.core.errors.AppError(`Cannot uninstall default master type "${type2}" (required by core)`, 400);
8767
+ }
8768
+ const existing = await ctx.db.knex("masters").where({ type: type2 }).first();
8769
+ if (!existing) {
8770
+ throw new ctx.core.errors.NotFoundError(`Master type "${type2}" is not installed`);
8771
+ }
8772
+ const deleted = await ctx.db.knex("masters").where({ type: type2 }).del();
8773
+ return { uninstalled: type2, count: deleted };
8774
+ }
8775
+ };
8776
+ }
8777
+ });
8778
+
8779
+ // src/modules/masters/index.ts
8780
+ var mastersModule;
8781
+ var init_masters = __esm({
8782
+ "src/modules/masters/index.ts"() {
8783
+ "use strict";
8784
+ init_definitions();
8785
+ init_registry2();
8786
+ init_constants();
8787
+ init_install_type();
8788
+ init_uninstall_type();
8789
+ init_definitions();
8790
+ init_registry2();
8791
+ init_constants();
8598
8792
  mastersModule = {
8599
8793
  name: "masters",
8600
8794
  type: "core",
8601
- label: { en: "Master Data", es: "Datos maestros" },
8795
+ label: { en: "Masters", es: "Maestros" },
8602
8796
  icon: "mdi:database-outline",
8603
8797
  description: { en: "Reference data catalogs", es: "Cat\xE1logos de datos de referencia" },
8604
- category: "data",
8798
+ category: "settings",
8605
8799
  routePrefix: "/masters",
8606
8800
  definitions: [mastersEntity],
8801
+ actions: [installTypeAction, uninstallTypeAction],
8607
8802
  init: (ctx) => {
8608
8803
  if (!ctx.services.has("masters")) {
8609
8804
  ctx.services.register("masters", createMasterRegistry());
@@ -8614,13 +8809,14 @@ var init_masters = __esm({
8614
8809
  ctx.services.register("masters", createMasterRegistry());
8615
8810
  }
8616
8811
  const registry2 = ctx.services.get("masters");
8617
- const configMasters = ctx.config.resolved["masters"];
8618
- const typesToSeed = !configMasters || configMasters === "*" ? Object.keys(PREDEFINED_MASTERS) : configMasters.filter((t) => t in PREDEFINED_MASTERS);
8619
- for (const type2 of typesToSeed) {
8620
- const loader = PREDEFINED_MASTERS[type2];
8621
- if (!loader) continue;
8622
- const entries = await loader();
8623
- registry2.register(type2, entries, { seed: "if-empty" });
8812
+ const existing = await ctx.db.knex("masters").first();
8813
+ if (!existing) {
8814
+ for (const type2 of DEFAULT_MASTER_TYPES) {
8815
+ const loader = PREDEFINED_MASTERS[type2];
8816
+ if (!loader) continue;
8817
+ const entries = await loader();
8818
+ registry2.register(type2, entries, { seed: "if-empty" });
8819
+ }
8624
8820
  }
8625
8821
  await registry2.seed(ctx);
8626
8822
  }
@@ -8778,15 +8974,18 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8778
8974
  const meta = field["meta"];
8779
8975
  return meta?.["sortable"] === true && !field["hidden"];
8780
8976
  });
8977
+ const explicitGroupable = def["groupableFields"];
8781
8978
  const groupableInputTypes = ["select", "switch", "checkbox", "radio", "tags"];
8782
- const groupableFields = fieldEntries.filter(([, f]) => {
8979
+ const autoGroupableFields = fieldEntries.filter(([, f]) => {
8783
8980
  const field = f;
8784
8981
  const inputType = field["input"];
8785
8982
  return groupableInputTypes.includes(inputType ?? "") && !field["hidden"];
8786
8983
  });
8984
+ const resolvedGroupableFields = explicitGroupable ?? (autoGroupableFields.length > 0 ? autoGroupableFields.map(([name]) => name) : void 0);
8787
8985
  const entityType = def["type"] ?? "collection";
8788
8986
  const explicitDisplayMode = def["displayMode"];
8789
- const defaultDisplayMode = explicitDisplayMode ?? (["tree", "dag"].includes(entityType) ? "tree" : "table");
8987
+ const explicitAvailableModes = def["availableDisplayModes"];
8988
+ const defaultDisplayMode = explicitDisplayMode ?? (explicitAvailableModes?.length === 1 ? explicitAvailableModes[0] : void 0) ?? (["tree", "dag"].includes(entityType) ? "tree" : "table");
8790
8989
  const entityIdent = def["table"] ?? def["key"];
8791
8990
  return {
8792
8991
  id: def._id,
@@ -8806,9 +9005,11 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8806
9005
  displayMode: explicitDisplayMode,
8807
9006
  defaultDisplayMode,
8808
9007
  availableDisplayModes: (() => {
9008
+ const explicit = def["availableDisplayModes"];
9009
+ if (explicit?.length) return explicit;
8809
9010
  const modes = ["table", "list", "masonry"];
8810
9011
  if (["tree", "dag"].includes(entityType)) modes.push("tree");
8811
- if (groupableFields.length > 0) modes.push("board");
9012
+ if ((resolvedGroupableFields?.length ?? 0) > 0) modes.push("board");
8812
9013
  if (def["calendarFrom"]) modes.push("calendar");
8813
9014
  return modes;
8814
9015
  })(),
@@ -8818,10 +9019,8 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8818
9019
  value: name,
8819
9020
  label: f["label"]
8820
9021
  })),
8821
- groupableFields: groupableFields.length > 0 ? groupableFields.map(([name, f]) => ({
8822
- value: name,
8823
- label: f["label"]
8824
- })) : void 0,
9022
+ groupableFields: resolvedGroupableFields,
9023
+ columnDragFields: def["columnDragFields"],
8825
9024
  groupBy: def["groupBy"],
8826
9025
  subgroupBy: def["subgroupBy"],
8827
9026
  calendarFrom: def["calendarFrom"],
@@ -8861,21 +9060,6 @@ function toModuleDTO(mod, ctx) {
8861
9060
  hasInit: !!mod.init
8862
9061
  };
8863
9062
  }
8864
- function getOrderedModulesViaContext(ctx) {
8865
- return ctx.engine.getModules();
8866
- }
8867
- async function runModuleSeedViaContext(mod, ctx) {
8868
- if (!mod.seed) {
8869
- return false;
8870
- }
8871
- try {
8872
- await mod.seed(ctx);
8873
- return true;
8874
- } catch (err) {
8875
- ctx.core.logger.error({ module: mod.name, err }, "Seed failed");
8876
- return false;
8877
- }
8878
- }
8879
9063
  var init_system_helpers = __esm({
8880
9064
  "src/modules/system/system.helpers.ts"() {
8881
9065
  "use strict";
@@ -8896,6 +9080,7 @@ function toPageDTO(page) {
8896
9080
  dataSource: page.dataSource,
8897
9081
  widgets: page.widgets,
8898
9082
  component: page.component,
9083
+ contentEndpoint: page.contentEndpoint,
8899
9084
  meta: page.meta,
8900
9085
  layout: page.layout
8901
9086
  };
@@ -9110,7 +9295,8 @@ function createSystemController(ctx) {
9110
9295
  const plugins = engine.getPlugins();
9111
9296
  const body = {
9112
9297
  version: manifest.version,
9113
- plugins: plugins.map((p) => p.code)
9298
+ plugins: plugins.map((p) => p.code),
9299
+ locales: ctx.locales
9114
9300
  };
9115
9301
  res.json(body);
9116
9302
  }
@@ -9158,7 +9344,7 @@ var init_system_routes = __esm({
9158
9344
 
9159
9345
  // src/modules/system/system.entity.ts
9160
9346
  import * as os from "os";
9161
- import { useIconField, useTextField as useTextField3, useSelectField as useSelectField2, useNumberField as useNumberField2, useCheckboxField, useTagsField, useNameField, useDescriptionField } from "@gzl10/nexus-sdk/fields";
9347
+ import { useIconField, useTextField as useTextField4, useSelectField as useSelectField3, useNumberField as useNumberField2, useCheckboxField, useTagsField, useNameField, useDescriptionField } from "@gzl10/nexus-sdk/fields";
9162
9348
  var moduleEntity, osEntity;
9163
9349
  var init_system_entity = __esm({
9164
9350
  "src/modules/system/system.entity.ts"() {
@@ -9176,7 +9362,7 @@ var init_system_entity = __esm({
9176
9362
  name: useNameField({
9177
9363
  size: 50
9178
9364
  }),
9179
- label: useTextField3({
9365
+ label: useTextField4({
9180
9366
  label: { en: "Label", es: "Etiqueta" },
9181
9367
  size: 100,
9182
9368
  nullable: false,
@@ -9184,7 +9370,7 @@ var init_system_entity = __esm({
9184
9370
  }),
9185
9371
  icon: useIconField({ label: { en: "Icon", es: "Icono" }, size: 50 }),
9186
9372
  type: {
9187
- ...useSelectField2({
9373
+ ...useSelectField3({
9188
9374
  label: { en: "Type", es: "Tipo" },
9189
9375
  options: [
9190
9376
  { value: "core", label: { en: "Core", es: "Core" } },
@@ -9200,7 +9386,7 @@ var init_system_entity = __esm({
9200
9386
  description: useDescriptionField({
9201
9387
  mode: "text"
9202
9388
  }),
9203
- routePrefix: useTextField3({
9389
+ routePrefix: useTextField4({
9204
9390
  label: { en: "Route", es: "Ruta" },
9205
9391
  size: 50,
9206
9392
  nullable: true
@@ -9258,14 +9444,14 @@ var init_system_entity = __esm({
9258
9444
  order: 1,
9259
9445
  refreshInterval: 5e3,
9260
9446
  fields: {
9261
- hostname: useTextField3({
9447
+ hostname: useTextField4({
9262
9448
  label: { en: "Hostname", es: "Nombre de servidor" },
9263
9449
  size: 100,
9264
9450
  nullable: false,
9265
9451
  inputProps: { order: 1 }
9266
9452
  }),
9267
9453
  platform: {
9268
- ...useSelectField2({
9454
+ ...useSelectField3({
9269
9455
  label: { en: "Platform", es: "Plataforma" },
9270
9456
  options: [
9271
9457
  { value: "darwin", label: { en: "macOS", es: "macOS" } },
@@ -9279,7 +9465,7 @@ var init_system_entity = __esm({
9279
9465
  inputProps: { order: 2 }
9280
9466
  },
9281
9467
  arch: {
9282
- ...useSelectField2({
9468
+ ...useSelectField3({
9283
9469
  label: { en: "Architecture", es: "Arquitectura" },
9284
9470
  options: [
9285
9471
  { value: "x64", label: { en: "x64", es: "x64" } },
@@ -9292,7 +9478,7 @@ var init_system_entity = __esm({
9292
9478
  inputProps: { order: 3 }
9293
9479
  },
9294
9480
  type: {
9295
- ...useTextField3({
9481
+ ...useTextField4({
9296
9482
  label: { en: "Type", es: "Tipo" },
9297
9483
  size: 30,
9298
9484
  nullable: false
@@ -9300,7 +9486,7 @@ var init_system_entity = __esm({
9300
9486
  inputProps: { order: 4 }
9301
9487
  },
9302
9488
  release: {
9303
- ...useTextField3({
9489
+ ...useTextField4({
9304
9490
  label: { en: "Release", es: "Versi\xF3n" },
9305
9491
  size: 50,
9306
9492
  nullable: false
@@ -9323,7 +9509,7 @@ var init_system_entity = __esm({
9323
9509
  inputProps: { order: 7, format: "duration" }
9324
9510
  },
9325
9511
  cpuModel: {
9326
- ...useTextField3({
9512
+ ...useTextField4({
9327
9513
  label: { en: "CPU Model", es: "Modelo de CPU" },
9328
9514
  size: 100,
9329
9515
  nullable: false
@@ -9389,7 +9575,7 @@ var init_system_entity = __esm({
9389
9575
  });
9390
9576
 
9391
9577
  // src/modules/system/migration-history.entity.ts
9392
- import { useIdField as useIdField2, useTextField as useTextField4, useNumberField as useNumberField3, useSelectField as useSelectField3, useDatetimeField as useDatetimeField2, useTextareaField as useTextareaField2 } from "@gzl10/nexus-sdk/fields";
9578
+ import { useIdField as useIdField2, useTextField as useTextField5, useNumberField as useNumberField3, useSelectField as useSelectField4, useDatetimeField as useDatetimeField2, useTextareaField as useTextareaField2 } from "@gzl10/nexus-sdk/fields";
9393
9579
  var migrationHistoryEntity;
9394
9580
  var init_migration_history_entity = __esm({
9395
9581
  "src/modules/system/migration-history.entity.ts"() {
@@ -9406,9 +9592,9 @@ var init_migration_history_entity = __esm({
9406
9592
  calendarFrom: "executed_at",
9407
9593
  fields: {
9408
9594
  id: useIdField2(),
9409
- name: useTextField4({ label: { en: "Migration Name", es: "Nombre de la Migraci\xF3n" }, required: true, size: 255, unique: true, meta: { sortable: true, searchable: true } }),
9595
+ name: useTextField5({ label: { en: "Migration Name", es: "Nombre de la Migraci\xF3n" }, required: true, size: 255, unique: true, meta: { sortable: true, searchable: true } }),
9410
9596
  batch: useNumberField3({ label: { en: "Batch", es: "Lote" }, required: true, meta: { sortable: true } }),
9411
- status: useSelectField3({
9597
+ status: useSelectField4({
9412
9598
  label: { en: "Status", es: "Estado" },
9413
9599
  options: [
9414
9600
  { value: "running", label: { en: "Running", es: "Ejecutando" } },
@@ -9440,7 +9626,7 @@ var init_migration_history_entity = __esm({
9440
9626
  });
9441
9627
 
9442
9628
  // src/modules/system/env-config.entity.ts
9443
- import { useTextField as useTextField5, useSelectField as useSelectField4, useCheckboxField as useCheckboxField2 } from "@gzl10/nexus-sdk/fields";
9629
+ import { useTextField as useTextField6, useSelectField as useSelectField5, useCheckboxField as useCheckboxField2 } from "@gzl10/nexus-sdk/fields";
9444
9630
  function maskValue(value, sensitive) {
9445
9631
  if (!sensitive) return value;
9446
9632
  try {
@@ -9465,14 +9651,14 @@ var init_env_config_entity = __esm({
9465
9651
  routePrefix: "/env-config",
9466
9652
  defaultSort: { field: "category", order: "asc" },
9467
9653
  fields: {
9468
- name: useTextField5({
9654
+ name: useTextField6({
9469
9655
  label: { en: "Variable", es: "Variable" },
9470
9656
  size: 100,
9471
9657
  nullable: false,
9472
9658
  meta: { sortable: true, searchable: true }
9473
9659
  }),
9474
9660
  category: {
9475
- ...useSelectField4({
9661
+ ...useSelectField5({
9476
9662
  label: { en: "Category", es: "Categor\xEDa" },
9477
9663
  options: [
9478
9664
  { value: "server", label: { en: "Server", es: "Servidor" } },
@@ -9487,18 +9673,18 @@ var init_env_config_entity = __esm({
9487
9673
  meta: { sortable: true }
9488
9674
  })
9489
9675
  },
9490
- source: useTextField5({
9676
+ source: useTextField6({
9491
9677
  label: { en: "Source", es: "Origen" },
9492
9678
  size: 50,
9493
9679
  nullable: false,
9494
9680
  meta: { sortable: true }
9495
9681
  }),
9496
- value: useTextField5({
9682
+ value: useTextField6({
9497
9683
  label: { en: "Value", es: "Valor" },
9498
9684
  size: 255,
9499
9685
  nullable: true
9500
9686
  }),
9501
- default: useTextField5({
9687
+ default: useTextField6({
9502
9688
  label: { en: "Default", es: "Por defecto" },
9503
9689
  size: 100,
9504
9690
  nullable: true
@@ -9997,7 +10183,6 @@ var SYSTEM_TABLES, factoryResetAction;
9997
10183
  var init_factory_reset_action = __esm({
9998
10184
  "src/modules/system/actions/factory-reset.action.ts"() {
9999
10185
  "use strict";
10000
- init_system_helpers();
10001
10186
  SYSTEM_TABLES = /* @__PURE__ */ new Set([
10002
10187
  "_nexus_migrations",
10003
10188
  "_nexus_migration_lock",
@@ -10040,8 +10225,8 @@ var init_factory_reset_action = __esm({
10040
10225
  source: "core:system",
10041
10226
  action: "factory_reset",
10042
10227
  actorId: authReq.user?.id,
10043
- ip: req.ip,
10044
- userAgent: req.headers["user-agent"]
10228
+ ip: req?.ip,
10229
+ userAgent: req?.headers["user-agent"]
10045
10230
  });
10046
10231
  await new Promise((resolve2) => setImmediate(resolve2));
10047
10232
  const allTables = await getAllTables(knex3);
@@ -10079,11 +10264,11 @@ var init_factory_reset_action = __esm({
10079
10264
  } catch {
10080
10265
  }
10081
10266
  ctx.core.logger.info({ tables: dataTables.length }, "All data tables cleared");
10082
- const modules = getOrderedModulesViaContext(ctx);
10267
+ const modules = ctx.engine.getModules();
10083
10268
  let modulesSeeded = 0;
10084
10269
  for (const mod of modules) {
10085
10270
  try {
10086
- const seeded = await runModuleSeedViaContext(mod, ctx);
10271
+ const seeded = await ctx.db.seedModule(mod);
10087
10272
  if (seeded) modulesSeeded++;
10088
10273
  } catch (err) {
10089
10274
  ctx.core.logger.error({ module: mod.name, err }, "Seed failed during factory reset");
@@ -10137,8 +10322,8 @@ var init_restart_server_action = __esm({
10137
10322
  source: "core:system",
10138
10323
  action: "server_restart",
10139
10324
  actorId: authReq.user?.id,
10140
- ip: req.ip,
10141
- userAgent: req.headers["user-agent"]
10325
+ ip: req?.ip,
10326
+ userAgent: req?.headers["user-agent"]
10142
10327
  });
10143
10328
  setTimeout(() => process.exit(0), 500);
10144
10329
  return { success: true, message: "Server is restarting..." };
@@ -10205,7 +10390,7 @@ var init_system = __esm({
10205
10390
  });
10206
10391
 
10207
10392
  // src/modules/ui-settings/ui-branding.entity.ts
10208
- import { useTextField as useTextField6, useImageField } from "@gzl10/nexus-sdk/fields";
10393
+ import { useTextField as useTextField7, useImageField } from "@gzl10/nexus-sdk/fields";
10209
10394
  var uiBrandingEntity;
10210
10395
  var init_ui_branding_entity = __esm({
10211
10396
  "src/modules/ui-settings/ui-branding.entity.ts"() {
@@ -10225,7 +10410,7 @@ var init_ui_branding_entity = __esm({
10225
10410
  favicon: null
10226
10411
  },
10227
10412
  fields: {
10228
- appName: useTextField6({
10413
+ appName: useTextField7({
10229
10414
  label: { en: "App Name", es: "Nombre de la App" },
10230
10415
  hint: { en: "Displayed in the header, browser tab and emails", es: "Se muestra en el header, pesta\xF1a del navegador y emails" },
10231
10416
  size: 100,
@@ -10266,7 +10451,7 @@ var init_ui_branding_entity = __esm({
10266
10451
  });
10267
10452
 
10268
10453
  // src/modules/ui-settings/ui-theme.entity.ts
10269
- import { useSelectField as useSelectField5, useColorField } from "@gzl10/nexus-sdk/fields";
10454
+ import { useSelectField as useSelectField6, useColorField } from "@gzl10/nexus-sdk/fields";
10270
10455
  var uiThemeEntity;
10271
10456
  var init_ui_theme_entity = __esm({
10272
10457
  "src/modules/ui-settings/ui-theme.entity.ts"() {
@@ -10291,7 +10476,7 @@ var init_ui_theme_entity = __esm({
10291
10476
  },
10292
10477
  fields: {
10293
10478
  // === Typography ===
10294
- font: useSelectField5({
10479
+ font: useSelectField6({
10295
10480
  label: { en: "Font", es: "Fuente" },
10296
10481
  hint: { en: "Primary font for headings and UI elements", es: "Fuente principal para t\xEDtulos y elementos de interfaz" },
10297
10482
  options: [
@@ -10304,7 +10489,7 @@ var init_ui_theme_entity = __esm({
10304
10489
  ]
10305
10490
  }),
10306
10491
  // === Theme & Colors ===
10307
- theme: useSelectField5({
10492
+ theme: useSelectField6({
10308
10493
  label: { en: "Theme", es: "Tema" },
10309
10494
  hint: { en: "System follows your device preferences", es: "Sistema sigue las preferencias de tu dispositivo" },
10310
10495
  options: [
@@ -10313,7 +10498,7 @@ var init_ui_theme_entity = __esm({
10313
10498
  { value: "system", label: { en: "System", es: "Sistema" } }
10314
10499
  ]
10315
10500
  }),
10316
- dopamineTheme: useSelectField5({
10501
+ dopamineTheme: useSelectField6({
10317
10502
  label: { en: "Dopamine Theme", es: "Tema Dopamina" },
10318
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)" },
10319
10504
  options: [
@@ -10331,7 +10516,7 @@ var init_ui_theme_entity = __esm({
10331
10516
  ]
10332
10517
  }),
10333
10518
  // === Login Layout ===
10334
- loginLayout: useSelectField5({
10519
+ loginLayout: useSelectField6({
10335
10520
  label: { en: "Login Layout", es: "Dise\xF1o de Login" },
10336
10521
  hint: { en: "Visual layout for authentication pages", es: "Dise\xF1o visual para p\xE1ginas de autenticaci\xF3n" },
10337
10522
  options: [
@@ -10362,7 +10547,7 @@ var init_ui_theme_entity = __esm({
10362
10547
  });
10363
10548
 
10364
10549
  // src/modules/ui-settings/ui-effects.entity.ts
10365
- import { useSelectField as useSelectField6, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
10550
+ import { useSelectField as useSelectField7, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
10366
10551
  var uiEffectsEntity;
10367
10552
  var init_ui_effects_entity = __esm({
10368
10553
  "src/modules/ui-settings/ui-effects.entity.ts"() {
@@ -10382,7 +10567,7 @@ var init_ui_effects_entity = __esm({
10382
10567
  enableOrganicShapes: false
10383
10568
  },
10384
10569
  fields: {
10385
- glassIntensity: useSelectField6({
10570
+ glassIntensity: useSelectField7({
10386
10571
  label: { en: "Glass Intensity", es: "Intensidad Glass" },
10387
10572
  hint: { en: "Glassmorphism blur effect on cards and modals", es: "Efecto de desenfoque glassmorphism en cards y modales" },
10388
10573
  options: [
@@ -10392,7 +10577,7 @@ var init_ui_effects_entity = __esm({
10392
10577
  { value: "high", label: { en: "High", es: "Alta" } }
10393
10578
  ]
10394
10579
  }),
10395
- borderStyle: useSelectField6({
10580
+ borderStyle: useSelectField7({
10396
10581
  label: { en: "Border Style", es: "Estilo de Bordes" },
10397
10582
  hint: { en: "Corner radius for buttons, cards and inputs", es: "Radio de esquinas para botones, cards e inputs" },
10398
10583
  options: [
@@ -10426,7 +10611,7 @@ var init_ui_effects_entity = __esm({
10426
10611
  });
10427
10612
 
10428
10613
  // src/modules/ui-settings/ui-accessibility.entity.ts
10429
- import { useSelectField as useSelectField7, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
10614
+ import { useSelectField as useSelectField8, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
10430
10615
  var uiAccessibilityEntity;
10431
10616
  var init_ui_accessibility_entity = __esm({
10432
10617
  "src/modules/ui-settings/ui-accessibility.entity.ts"() {
@@ -10445,7 +10630,7 @@ var init_ui_accessibility_entity = __esm({
10445
10630
  highContrast: false
10446
10631
  },
10447
10632
  fields: {
10448
- typographyScale: useSelectField7({
10633
+ typographyScale: useSelectField8({
10449
10634
  label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
10450
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)" },
10451
10636
  options: [
@@ -11436,7 +11621,7 @@ var init_storage_service = __esm({
11436
11621
  });
11437
11622
 
11438
11623
  // src/modules/storage/storage.entity.ts
11439
- import { useIdField as useIdField3, useTextField as useTextField7, useSelectField as useSelectField8, useUrlField, useNumberField as useNumberField4, useCheckboxField as useCheckboxField3, useJsonField as useJsonField2, useMetadataField, usePublicField } from "@gzl10/nexus-sdk/fields";
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";
11440
11625
  var DEFAULT_MAX_SIZE2, storageConfigEntity, storageFilesEntity;
11441
11626
  var init_storage_entity = __esm({
11442
11627
  "src/modules/storage/storage.entity.ts"() {
@@ -11462,7 +11647,7 @@ var init_storage_entity = __esm({
11462
11647
  },
11463
11648
  fields: {
11464
11649
  id: useIdField3(),
11465
- scope: useTextField7({
11650
+ scope: useTextField8({
11466
11651
  label: { en: "Scope", es: "\xC1mbito" },
11467
11652
  disabled: true,
11468
11653
  size: 100,
@@ -11471,7 +11656,7 @@ var init_storage_entity = __esm({
11471
11656
  hint: { en: "Unique identifier (e.g. default_filesystem, default_s3)", es: "Identificador \xFAnico (ej: default_filesystem, default_s3)" }
11472
11657
  }),
11473
11658
  driver: {
11474
- ...useSelectField8({
11659
+ ...useSelectField9({
11475
11660
  label: { en: "Driver", es: "Controlador" },
11476
11661
  required: true,
11477
11662
  hint: { en: "Storage backend", es: "Backend de almacenamiento" },
@@ -11494,7 +11679,7 @@ var init_storage_entity = __esm({
11494
11679
  nullable: false,
11495
11680
  displayProps: { format: "bytes" }
11496
11681
  }),
11497
- allowed_mime_types: useTextField7({
11682
+ allowed_mime_types: useTextField8({
11498
11683
  label: { en: "Allowed MIME Types", es: "Tipos MIME permitidos" },
11499
11684
  hint: { en: "Allowed types separated by comma (e.g. image/*,application/pdf)", es: "Tipos permitidos separados por coma (ej: image/*,application/pdf)" },
11500
11685
  size: 1e3,
@@ -11659,21 +11844,21 @@ var init_storage_entity = __esm({
11659
11844
  ],
11660
11845
  fields: {
11661
11846
  id: useIdField3(),
11662
- filename: useTextField7({
11847
+ filename: useTextField8({
11663
11848
  label: { en: "Original Filename", es: "Nombre original" },
11664
11849
  required: true,
11665
11850
  size: 255,
11666
11851
  nullable: false,
11667
11852
  meta: { sortable: true, searchable: true }
11668
11853
  }),
11669
- disk_filename: useTextField7({
11854
+ disk_filename: useTextField8({
11670
11855
  label: { en: "Disk Filename", es: "Nombre en disco" },
11671
11856
  required: true,
11672
11857
  hidden: true,
11673
11858
  size: 255,
11674
11859
  nullable: false
11675
11860
  }),
11676
- mimetype: useTextField7({
11861
+ mimetype: useTextField8({
11677
11862
  label: { en: "MIME Type", es: "Tipo MIME" },
11678
11863
  required: true,
11679
11864
  size: 100,
@@ -11689,14 +11874,14 @@ var init_storage_entity = __esm({
11689
11874
  meta: { sortable: true },
11690
11875
  displayProps: { format: "bytes" }
11691
11876
  }),
11692
- folder: useTextField7({
11877
+ folder: useTextField8({
11693
11878
  label: { en: "Folder", es: "Carpeta" },
11694
11879
  size: 100,
11695
11880
  nullable: true,
11696
11881
  index: true,
11697
11882
  meta: { searchable: true }
11698
11883
  }),
11699
- scope: useTextField7({
11884
+ scope: useTextField8({
11700
11885
  label: { en: "Storage Scope", es: "\xC1mbito de almacenamiento" },
11701
11886
  hidden: true,
11702
11887
  size: 50,
@@ -11707,7 +11892,7 @@ var init_storage_entity = __esm({
11707
11892
  es: "\xC1mbito de configuraci\xF3n de almacenamiento usado para este archivo"
11708
11893
  }
11709
11894
  }),
11710
- path: useTextField7({
11895
+ path: useTextField8({
11711
11896
  label: { en: "Full Path", es: "Ruta completa" },
11712
11897
  required: true,
11713
11898
  hidden: true,
@@ -11720,13 +11905,13 @@ var init_storage_entity = __esm({
11720
11905
  nullable: true,
11721
11906
  meta: { exportable: true, showInDisplay: false }
11722
11907
  }),
11723
- thumbnail_path: useTextField7({
11908
+ thumbnail_path: useTextField8({
11724
11909
  label: { en: "Thumbnail Path", es: "Ruta de miniatura" },
11725
11910
  size: 500,
11726
11911
  nullable: true,
11727
11912
  meta: { showInDisplay: false }
11728
11913
  }),
11729
- hash: useTextField7({
11914
+ hash: useTextField8({
11730
11915
  label: { en: "SHA256 Hash", es: "Hash SHA256" },
11731
11916
  hidden: true,
11732
11917
  size: 64,
@@ -11760,7 +11945,7 @@ function createUploadMiddleware(ctx, options) {
11760
11945
  const rateLimit2 = ctx.core.middleware.rateLimit({
11761
11946
  windowMs: 60 * 1e3,
11762
11947
  max: 20,
11763
- message: "Demasiados uploads, intenta en 1 minuto"
11948
+ message: "Too many uploads, try again in 1 minute"
11764
11949
  });
11765
11950
  const upload = multer({
11766
11951
  storage: multer.memoryStorage(),
@@ -12007,7 +12192,7 @@ function createStorageRoutes(ctx) {
12007
12192
  }
12008
12193
  res.status(201).json(results);
12009
12194
  };
12010
- const uploadRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1e3, max: 20, message: "Demasiados uploads, intenta en 1 minuto" });
12195
+ const uploadRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1e3, max: 20, message: "Too many uploads, try again in 1 minute" });
12011
12196
  if (auth) {
12012
12197
  router.post("/upload/multiple", uploadRateLimit, auth, upload.array("files", 10), uploadMultiple);
12013
12198
  } else {
@@ -12223,7 +12408,7 @@ var init_storage = __esm({
12223
12408
  });
12224
12409
 
12225
12410
  // src/modules/users/users.entity.ts
12226
- import { useIdField as useIdField4, useSelectField as useSelectField9, useEmailField, usePasswordField, useTextField as useTextField8, useDatetimeField as useDatetimeField3, useCheckboxField as useCheckboxField4, useImageField as useImageField2, useNameField as useNameField2, useMetadataField as useMetadataField2, useDescriptionField as useDescriptionField2 } from "@gzl10/nexus-sdk/fields";
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";
12227
12412
  import { z as z2 } from "zod";
12228
12413
  var userEntity, roleEntity, userRoleEntity;
12229
12414
  var init_users_entity = __esm({
@@ -12286,7 +12471,7 @@ var init_users_entity = __esm({
12286
12471
  nullable: true,
12287
12472
  meta: { exportable: true, showInForm: false, showInDisplay: false }
12288
12473
  }),
12289
- consent_version: useTextField8({
12474
+ consent_version: useTextField9({
12290
12475
  label: { en: "Consent Version", es: "Versi\xF3n de consentimiento" },
12291
12476
  hidden: true,
12292
12477
  size: 20,
@@ -12297,7 +12482,7 @@ var init_users_entity = __esm({
12297
12482
  label: { en: "Marketing Opt-in", es: "Aceptar marketing" },
12298
12483
  meta: { exportable: true, showInForm: false, showInDisplay: false }
12299
12484
  }),
12300
- locale: useSelectField9({
12485
+ locale: useSelectField10({
12301
12486
  label: { en: "Language", es: "Idioma" },
12302
12487
  options: [
12303
12488
  { value: "es", label: { en: "Spanish", es: "Espa\xF1ol" } },
@@ -12305,15 +12490,15 @@ var init_users_entity = __esm({
12305
12490
  ],
12306
12491
  nullable: true,
12307
12492
  meta: { sortable: true },
12308
- defaultValue: "es"
12493
+ defaultValue: "en"
12309
12494
  }),
12310
- timezone: useSelectField9({
12495
+ timezone: useSelectField10({
12311
12496
  label: { en: "Timezone", es: "Zona horaria" },
12312
12497
  master: "timezones",
12313
12498
  meta: { sortable: true },
12314
12499
  defaultValue: "timezones:Europe/Madrid"
12315
12500
  }),
12316
- type: useSelectField9({
12501
+ type: useSelectField10({
12317
12502
  label: { en: "Type", es: "Tipo" },
12318
12503
  defaultValue: "human",
12319
12504
  options: [
@@ -12383,7 +12568,7 @@ var init_users_entity = __esm({
12383
12568
  middleware: (ctx) => ctx.core.middleware.rateLimit({
12384
12569
  windowMs: 15 * 60 * 1e3,
12385
12570
  max: 5,
12386
- message: "Demasiados intentos, intenta en 15 minutos"
12571
+ message: "Too many attempts, try again in 15 minutes"
12387
12572
  }),
12388
12573
  handler: async (ctx, input) => {
12389
12574
  const {
@@ -12482,7 +12667,7 @@ var init_users_entity = __esm({
12482
12667
  expose: false,
12483
12668
  fields: {
12484
12669
  id: useIdField4(),
12485
- user_id: useSelectField9({
12670
+ user_id: useSelectField10({
12486
12671
  label: { en: "User", es: "Usuario" },
12487
12672
  required: true,
12488
12673
  table: "users",
@@ -12493,7 +12678,7 @@ var init_users_entity = __esm({
12493
12678
  labelField: "name",
12494
12679
  meta: { searchable: true }
12495
12680
  }),
12496
- role_id: useSelectField9({
12681
+ role_id: useSelectField10({
12497
12682
  label: { en: "Role", es: "Rol" },
12498
12683
  required: true,
12499
12684
  table: "roles",
@@ -13437,7 +13622,7 @@ var init_users = __esm({
13437
13622
  });
13438
13623
 
13439
13624
  // src/modules/auth/auth.entity.ts
13440
- import { useIdField as useIdField5, useTextField as useTextField9, useSelectField as useSelectField10, useDatetimeField as useDatetimeField4, useEmailField as useEmailField2, useMetadataField as useMetadataField3, useExpiresAtField } from "@gzl10/nexus-sdk/fields";
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";
13441
13626
  var refreshTokenEntity, authIdentitiesEntity;
13442
13627
  var init_auth_entity = __esm({
13443
13628
  "src/modules/auth/auth.entity.ts"() {
@@ -13452,7 +13637,7 @@ var init_auth_entity = __esm({
13452
13637
  expose: false,
13453
13638
  fields: {
13454
13639
  id: useIdField5(),
13455
- token: useTextField9({
13640
+ token: useTextField10({
13456
13641
  label: { en: "Token", es: "Token" },
13457
13642
  hidden: true,
13458
13643
  size: 255,
@@ -13479,14 +13664,14 @@ var init_auth_entity = __esm({
13479
13664
  nullable: true,
13480
13665
  meta: { sortable: true }
13481
13666
  }),
13482
- device_id: useTextField9({
13667
+ device_id: useTextField10({
13483
13668
  label: { en: "Device ID", es: "ID de dispositivo" },
13484
13669
  size: 64,
13485
13670
  index: true,
13486
13671
  nullable: true,
13487
13672
  meta: { searchable: true }
13488
13673
  }),
13489
- device_name: useTextField9({
13674
+ device_name: useTextField10({
13490
13675
  label: { en: "Device", es: "Dispositivo" },
13491
13676
  size: 100,
13492
13677
  nullable: true,
@@ -13518,7 +13703,7 @@ var init_auth_entity = __esm({
13518
13703
  order: 5,
13519
13704
  fields: {
13520
13705
  id: useIdField5(),
13521
- user_id: useSelectField10({
13706
+ user_id: useSelectField11({
13522
13707
  label: { en: "User", es: "Usuario" },
13523
13708
  table: "users",
13524
13709
  column: "id",
@@ -13530,7 +13715,7 @@ var init_auth_entity = __esm({
13530
13715
  labelField: "name",
13531
13716
  meta: { searchable: true }
13532
13717
  }),
13533
- provider: useTextField9({
13718
+ provider: useTextField10({
13534
13719
  label: { en: "Provider", es: "Proveedor" },
13535
13720
  required: true,
13536
13721
  size: 50,
@@ -13539,7 +13724,7 @@ var init_auth_entity = __esm({
13539
13724
  hint: { en: "e.g. pocketid, google, microsoft", es: "ej. pocketid, google, microsoft" },
13540
13725
  meta: { sortable: true, searchable: true }
13541
13726
  }),
13542
- provider_user_id: useTextField9({
13727
+ provider_user_id: useTextField10({
13543
13728
  label: { en: "Provider User ID", es: "ID de usuario del proveedor" },
13544
13729
  required: true,
13545
13730
  size: 255,
@@ -13834,7 +14019,7 @@ var init_auth_middleware = __esm({
13834
14019
  });
13835
14020
 
13836
14021
  // src/modules/auth/auth.pat.entity.ts
13837
- import { useIdField as useIdField6, useTextField as useTextField10, useSelectField as useSelectField11, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
14022
+ import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as useSelectField12, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
13838
14023
  var personalTokenEntity;
13839
14024
  var init_auth_pat_entity = __esm({
13840
14025
  "src/modules/auth/auth.pat.entity.ts"() {
@@ -13851,7 +14036,7 @@ var init_auth_pat_entity = __esm({
13851
14036
  routePrefix: "/personal-tokens",
13852
14037
  fields: {
13853
14038
  id: useIdField6(),
13854
- user_id: useSelectField11({
14039
+ user_id: useSelectField12({
13855
14040
  label: { en: "User", es: "Usuario" },
13856
14041
  table: "users",
13857
14042
  column: "id",
@@ -13863,7 +14048,7 @@ var init_auth_pat_entity = __esm({
13863
14048
  labelField: "name",
13864
14049
  meta: { searchable: true }
13865
14050
  }),
13866
- name: useTextField10({
14051
+ name: useTextField11({
13867
14052
  label: { en: "Name", es: "Nombre" },
13868
14053
  required: true,
13869
14054
  size: 100,
@@ -13871,14 +14056,14 @@ var init_auth_pat_entity = __esm({
13871
14056
  hint: { en: "Descriptive name for this token", es: "Nombre descriptivo para este token" },
13872
14057
  meta: { searchable: true }
13873
14058
  }),
13874
- token_prefix: useTextField10({
14059
+ token_prefix: useTextField11({
13875
14060
  label: { en: "Token", es: "Token" },
13876
14061
  size: 20,
13877
14062
  disabled: true,
13878
14063
  nullable: false,
13879
14064
  hint: { en: "Partial token for identification", es: "Token parcial para identificaci\xF3n" }
13880
14065
  }),
13881
- token_hash: useTextField10({
14066
+ token_hash: useTextField11({
13882
14067
  label: { en: "Token Hash", es: "Hash del token" },
13883
14068
  size: 64,
13884
14069
  hidden: true,
@@ -13886,7 +14071,7 @@ var init_auth_pat_entity = __esm({
13886
14071
  unique: true,
13887
14072
  meta: { exportable: false }
13888
14073
  }),
13889
- scope: useSelectField11({
14074
+ scope: useSelectField12({
13890
14075
  label: { en: "Permission", es: "Permiso" },
13891
14076
  required: true,
13892
14077
  options: [
@@ -15642,7 +15827,7 @@ var init_mail_service = __esm({
15642
15827
  });
15643
15828
 
15644
15829
  // src/modules/mail/mail.entity.ts
15645
- import { useIdField as useIdField7, useTextField as useTextField11, useSelectField as useSelectField12, 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";
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";
15646
15831
  import nodemailer2 from "nodemailer";
15647
15832
  async function getMailConfigFromDB(ctx) {
15648
15833
  const configService = ctx.services["config"];
@@ -15705,7 +15890,7 @@ var init_mail_entity = __esm({
15705
15890
  },
15706
15891
  fields: {
15707
15892
  id: useIdField7(),
15708
- host: useTextField11({
15893
+ host: useTextField12({
15709
15894
  label: { en: "SMTP Host", es: "Host SMTP" },
15710
15895
  size: 255,
15711
15896
  nullable: false,
@@ -15725,7 +15910,7 @@ var init_mail_entity = __esm({
15725
15910
  nullable: false,
15726
15911
  hint: { en: "Default: SMTP_FROM env var", es: "Por defecto: variable SMTP_FROM" }
15727
15912
  }),
15728
- auth_user: useTextField11({
15913
+ auth_user: useTextField12({
15729
15914
  label: { en: "Auth User", es: "Usuario de autenticaci\xF3n" },
15730
15915
  size: 255,
15731
15916
  nullable: true,
@@ -15757,12 +15942,12 @@ var init_mail_entity = __esm({
15757
15942
  required: true,
15758
15943
  validation: { format: "email" }
15759
15944
  }),
15760
- subject: useTextField11({
15945
+ subject: useTextField12({
15761
15946
  label: { en: "Subject", es: "Asunto" },
15762
15947
  required: true,
15763
15948
  validation: { min: 1, max: 255 }
15764
15949
  }),
15765
- title: useTextField11({
15950
+ title: useTextField12({
15766
15951
  label: { en: "Title", es: "T\xEDtulo" },
15767
15952
  hint: { en: "Large title in email header", es: "T\xEDtulo grande en la cabecera del correo" }
15768
15953
  }),
@@ -15906,20 +16091,20 @@ var init_mail_entity = __esm({
15906
16091
  nullable: false,
15907
16092
  meta: { sortable: true }
15908
16093
  }),
15909
- to: useTextField11({
16094
+ to: useTextField12({
15910
16095
  label: { en: "Recipient(s)", es: "Destinatario(s)" },
15911
16096
  size: 1e3,
15912
16097
  nullable: false,
15913
16098
  meta: { searchable: true }
15914
16099
  }),
15915
- subject: useTextField11({
16100
+ subject: useTextField12({
15916
16101
  label: { en: "Subject", es: "Asunto" },
15917
16102
  size: 255,
15918
16103
  nullable: false,
15919
16104
  meta: { searchable: true, sortable: true }
15920
16105
  }),
15921
16106
  status: {
15922
- ...useSelectField12({
16107
+ ...useSelectField13({
15923
16108
  label: { en: "Status", es: "Estado" },
15924
16109
  options: [
15925
16110
  { value: "pending", label: { en: "Pending", es: "Pendiente" } },
@@ -15933,7 +16118,7 @@ var init_mail_entity = __esm({
15933
16118
  }),
15934
16119
  validation: { enum: ["pending", "sent", "failed", "bounced"] }
15935
16120
  },
15936
- message_id: useTextField11({
16121
+ message_id: useTextField12({
15937
16122
  label: { en: "Message ID", es: "ID de mensaje" },
15938
16123
  hidden: true,
15939
16124
  size: 255,
@@ -15943,7 +16128,7 @@ var init_mail_entity = __esm({
15943
16128
  label: { en: "Error", es: "Error" },
15944
16129
  nullable: true
15945
16130
  }),
15946
- sent_by: useSelectField12({
16131
+ sent_by: useSelectField13({
15947
16132
  label: { en: "Sent by", es: "Enviado por" },
15948
16133
  table: "users",
15949
16134
  column: "id",
@@ -16578,7 +16763,7 @@ var init_toggle_plugin_action = __esm({
16578
16763
  });
16579
16764
 
16580
16765
  // src/modules/plugins/plugins.entity.ts
16581
- import { useTextField as useTextField12, useSelectField as useSelectField13, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
16766
+ import { useTextField as useTextField13, useSelectField as useSelectField14, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
16582
16767
  import { OFFICIAL_PLUGINS } from "@gzl10/nexus-sdk";
16583
16768
  var allowPluginManagement, pluginsEntity;
16584
16769
  var init_plugins_entity = __esm({
@@ -16594,33 +16779,33 @@ var init_plugins_entity = __esm({
16594
16779
  label: "Plugins",
16595
16780
  icon: "mdi:puzzle",
16596
16781
  labelField: "code",
16597
- routePrefix: "/plugins",
16782
+ routePrefix: "/",
16598
16783
  defaultSort: { field: "name", order: "asc" },
16599
16784
  fields: {
16600
- name: useTextField12({
16785
+ name: useTextField13({
16601
16786
  label: { en: "Name", es: "Nombre" },
16602
16787
  size: 50,
16603
16788
  nullable: false,
16604
16789
  meta: { sortable: true, searchable: true }
16605
16790
  }),
16606
- code: useTextField12({
16791
+ code: useTextField13({
16607
16792
  label: { en: "Code", es: "C\xF3digo" },
16608
16793
  size: 10,
16609
16794
  nullable: false,
16610
16795
  meta: { sortable: true }
16611
16796
  }),
16612
- label: useTextField12({
16797
+ label: useTextField13({
16613
16798
  label: { en: "Label", es: "Etiqueta" },
16614
16799
  size: 100,
16615
16800
  nullable: false,
16616
16801
  meta: { sortable: true }
16617
16802
  }),
16618
- version: useTextField12({
16803
+ version: useTextField13({
16619
16804
  label: { en: "Version", es: "Versi\xF3n" },
16620
16805
  size: 20,
16621
16806
  nullable: false
16622
16807
  }),
16623
- category: useSelectField13({
16808
+ category: useSelectField14({
16624
16809
  label: { en: "Category", es: "Categor\xEDa" },
16625
16810
  options: [
16626
16811
  { value: "content", label: { en: "Content", es: "Contenido" } },
@@ -16761,8 +16946,8 @@ var init_plugins = __esm({
16761
16946
  // src/modules/audit/audit.entity.ts
16762
16947
  import {
16763
16948
  useIdField as useIdField8,
16764
- useTextField as useTextField13,
16765
- useSelectField as useSelectField14,
16949
+ useTextField as useTextField14,
16950
+ useSelectField as useSelectField15,
16766
16951
  useTextareaField as useTextareaField4,
16767
16952
  useJsonField as useJsonField3,
16768
16953
  useDatetimeField as useDatetimeField7,
@@ -16785,7 +16970,7 @@ var init_audit_entity = __esm({
16785
16970
  fields: {
16786
16971
  id: useIdField8(),
16787
16972
  source: {
16788
- ...useTextField13({
16973
+ ...useTextField14({
16789
16974
  label: { en: "Source", es: "Origen" },
16790
16975
  size: 100,
16791
16976
  nullable: false,
@@ -16795,7 +16980,7 @@ var init_audit_entity = __esm({
16795
16980
  validation: { min: 1, max: 100 }
16796
16981
  },
16797
16982
  action: {
16798
- ...useTextField13({
16983
+ ...useTextField14({
16799
16984
  label: { en: "Action", es: "Acci\xF3n" },
16800
16985
  size: 100,
16801
16986
  nullable: false,
@@ -16804,7 +16989,7 @@ var init_audit_entity = __esm({
16804
16989
  }),
16805
16990
  validation: { min: 1, max: 100 }
16806
16991
  },
16807
- actor_id: useSelectField14({
16992
+ actor_id: useSelectField15({
16808
16993
  label: { en: "Actor", es: "Actor" },
16809
16994
  table: "users",
16810
16995
  column: "id",
@@ -16821,20 +17006,20 @@ var init_audit_entity = __esm({
16821
17006
  nullable: true,
16822
17007
  meta: { searchable: true }
16823
17008
  }),
16824
- resource_type: useTextField13({
17009
+ resource_type: useTextField14({
16825
17010
  label: { en: "Resource Type", es: "Tipo de recurso" },
16826
17011
  size: 100,
16827
17012
  nullable: true,
16828
17013
  index: true,
16829
17014
  meta: { searchable: true }
16830
17015
  }),
16831
- resource_id: useTextField13({
17016
+ resource_id: useTextField14({
16832
17017
  label: { en: "Resource ID", es: "ID del recurso" },
16833
17018
  size: 100,
16834
17019
  nullable: true,
16835
17020
  meta: { searchable: true }
16836
17021
  }),
16837
- ip_address: useTextField13({
17022
+ ip_address: useTextField14({
16838
17023
  label: { en: "IP Address", es: "Direcci\xF3n IP" },
16839
17024
  size: 45,
16840
17025
  nullable: true,
@@ -17007,12 +17192,12 @@ var init_loader = __esm({
17007
17192
  "src/engine/loader.ts"() {
17008
17193
  "use strict";
17009
17194
  init_registry();
17010
- init_extractors();
17195
+ init_definition_extractors();
17011
17196
  init_modules();
17012
17197
  }
17013
17198
  });
17014
17199
 
17015
- // src/engine/subjectExtractor.ts
17200
+ // src/engine/subject-extractor.ts
17016
17201
  function getModuleSubjects(mod) {
17017
17202
  const subjects = /* @__PURE__ */ new Set();
17018
17203
  for (const def of mod.definitions ?? []) {
@@ -17021,10 +17206,10 @@ function getModuleSubjects(mod) {
17021
17206
  }
17022
17207
  return [...subjects];
17023
17208
  }
17024
- var init_subjectExtractor = __esm({
17025
- "src/engine/subjectExtractor.ts"() {
17209
+ var init_subject_extractor = __esm({
17210
+ "src/engine/subject-extractor.ts"() {
17026
17211
  "use strict";
17027
- init_extractors();
17212
+ init_definition_extractors();
17028
17213
  }
17029
17214
  });
17030
17215
 
@@ -17102,7 +17287,7 @@ function initSocketIO(httpServer, options) {
17102
17287
  maxHttpBufferSize: 1e6
17103
17288
  // 1MB - match Express json body limit
17104
17289
  });
17105
- logger.info({ maxHttpBufferSize: 1e6, cors: corsOrigin }, "Socket.IO initialized");
17290
+ logger.info({ cors: corsOrigin }, "Socket.IO initialized");
17106
17291
  io.use((socket, next) => {
17107
17292
  const token = socket.handshake.auth?.["token"] || socket.handshake.query?.["token"];
17108
17293
  if (token && typeof token === "string" && jwtSecret) {
@@ -17125,7 +17310,6 @@ function initSocketIO(httpServer, options) {
17125
17310
  logger.warn({ code: err.code, message: err.message }, "Socket.IO connection error");
17126
17311
  });
17127
17312
  io.on("connection", handleConnection);
17128
- logger.info("Socket.IO initialized");
17129
17313
  nexusEvents.emitEvent("socket.initialized");
17130
17314
  return io;
17131
17315
  }
@@ -17602,6 +17786,7 @@ var init_error_codes = __esm({
17602
17786
  DB_CONSTRAINT_UNIQUE: "DB_CONSTRAINT_UNIQUE",
17603
17787
  DB_CONSTRAINT_FK: "DB_CONSTRAINT_FK",
17604
17788
  DB_CONNECTION_ERROR: "DB_CONNECTION_ERROR",
17789
+ DATABASE_NOT_READY: "DATABASE_NOT_READY",
17605
17790
  // System
17606
17791
  SYSTEM_INTERNAL_ERROR: "SYSTEM_INTERNAL_ERROR"
17607
17792
  };
@@ -17720,6 +17905,12 @@ var init_app_error = __esm({
17720
17905
 
17721
17906
  // src/core/abilities/ability.factory.ts
17722
17907
  import { AbilityBuilder, createMongoAbility } from "@casl/ability";
17908
+ function setSeedPermissions(perms) {
17909
+ seedPermissions = perms;
17910
+ }
17911
+ function clearSeedPermissions() {
17912
+ seedPermissions = null;
17913
+ }
17723
17914
  function setCustomCaslRules(fn) {
17724
17915
  customCaslRules = fn;
17725
17916
  }
@@ -17777,6 +17968,17 @@ async function defineAbilityFor(user, roleNames) {
17777
17968
  if (customCaslRules) {
17778
17969
  await customCaslRules(user, { can, cannot });
17779
17970
  }
17971
+ if (seedPermissions && !isSuperuser) {
17972
+ for (const roleName of roleNames) {
17973
+ const rolePerms = seedPermissions.get(roleName);
17974
+ if (!rolePerms) continue;
17975
+ for (const [subject2, actions] of rolePerms) {
17976
+ for (const action of actions) {
17977
+ can(action, subject2);
17978
+ }
17979
+ }
17980
+ }
17981
+ }
17780
17982
  return build();
17781
17983
  }
17782
17984
  function packRules(ability) {
@@ -17785,11 +17987,12 @@ function packRules(ability) {
17785
17987
  function unpackRules(rules) {
17786
17988
  return createMongoAbility(rules);
17787
17989
  }
17788
- var customCaslRules, entityDefinitionsRegistry, SUPERUSER_ROLES;
17990
+ var customCaslRules, seedPermissions, entityDefinitionsRegistry, SUPERUSER_ROLES;
17789
17991
  var init_ability_factory = __esm({
17790
17992
  "src/core/abilities/ability.factory.ts"() {
17791
17993
  "use strict";
17792
17994
  init_logger();
17995
+ seedPermissions = null;
17793
17996
  entityDefinitionsRegistry = null;
17794
17997
  SUPERUSER_ROLES = ["ADMIN", "OWNER"];
17795
17998
  }
@@ -17907,7 +18110,7 @@ var init_env = __esm({
17907
18110
  envSchema = z8.object({
17908
18111
  NODE_ENV: z8.enum(["development", "production", "test"]).default("development"),
17909
18112
  PORT: z8.coerce.number().default(3e3),
17910
- CORS_ORIGIN: z8.string().default("http://localhost:3001"),
18113
+ CORS_ORIGIN: z8.string().default("*"),
17911
18114
  BACKEND_URL: z8.string().optional(),
17912
18115
  DATABASE_URL: z8.string().default("file:./dev.db"),
17913
18116
  REDIS_URL: z8.string().url().optional(),
@@ -18355,7 +18558,7 @@ var init_sequence = __esm({
18355
18558
  }
18356
18559
  });
18357
18560
 
18358
- // src/core/utils/error-handler.ts
18561
+ // src/core/utils/safe-json.ts
18359
18562
  function safeJsonParse(logger2, jsonString, fallback, context) {
18360
18563
  try {
18361
18564
  return JSON.parse(jsonString);
@@ -18364,8 +18567,8 @@ function safeJsonParse(logger2, jsonString, fallback, context) {
18364
18567
  return fallback;
18365
18568
  }
18366
18569
  }
18367
- var init_error_handler = __esm({
18368
- "src/core/utils/error-handler.ts"() {
18570
+ var init_safe_json = __esm({
18571
+ "src/core/utils/safe-json.ts"() {
18369
18572
  "use strict";
18370
18573
  }
18371
18574
  });
@@ -18374,14 +18577,15 @@ var init_error_handler = __esm({
18374
18577
  import express from "express";
18375
18578
  import { resolve, join as join9 } from "path";
18376
18579
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
18377
- function createServeSPA(app) {
18378
- return (endpoint, distPath, options = {}) => {
18580
+ function createServeSPA(app, httpServer) {
18581
+ return async (endpoint, distPath, options = {}) => {
18379
18582
  const {
18380
18583
  maxAge = "1d",
18381
18584
  etag = true,
18382
18585
  immutable = false,
18383
18586
  index = "index.html",
18384
- absolute = false
18587
+ absolute = false,
18588
+ viteSrc
18385
18589
  } = options;
18386
18590
  if (endpoint === "/api" || endpoint.startsWith("/api/")) {
18387
18591
  logger.error(`Cannot mount SPA on ${endpoint} - reserved for API routes`);
@@ -18392,58 +18596,117 @@ function createServeSPA(app) {
18392
18596
  return;
18393
18597
  }
18394
18598
  registeredEndpoints.add(endpoint);
18395
- let resolvedPath;
18396
- if (absolute) {
18397
- resolvedPath = distPath;
18398
- } else {
18399
- const projectPath2 = resolve(getProjectPath(), distPath);
18400
- if (existsSync9(projectPath2)) {
18401
- resolvedPath = projectPath2;
18599
+ if (env.NODE_ENV === "development" && viteSrc) {
18600
+ const srcPath = resolve(getProjectPath(), viteSrc);
18601
+ if (!existsSync9(srcPath)) {
18602
+ logger.warn({ endpoint, viteSrc, resolved: srcPath }, "Vite source not found \u2014 falling back to static");
18402
18603
  } else {
18403
- resolvedPath = resolve(getLibPath(), distPath);
18604
+ const mounted = await mountViteDevMiddleware(app, endpoint, srcPath, httpServer);
18605
+ if (mounted) return;
18404
18606
  }
18405
18607
  }
18406
- if (!existsSync9(resolvedPath)) {
18407
- logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18408
- return;
18608
+ mountStaticSPA(app, endpoint, distPath, { maxAge, etag, immutable, index, absolute });
18609
+ };
18610
+ }
18611
+ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
18612
+ try {
18613
+ const vite = await import("vite");
18614
+ const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18615
+ const server2 = await vite.createServer({
18616
+ root: srcPath,
18617
+ server: {
18618
+ middlewareMode: true,
18619
+ allowedHosts: true,
18620
+ hmr: httpServer ? { server: httpServer } : true
18621
+ },
18622
+ plugins: [{
18623
+ name: "nexus-config-inject",
18624
+ transformIndexHtml(html) {
18625
+ const config3 = JSON.stringify({ apiUrl });
18626
+ return html.replace("</head>", `<script>window.__NEXUS__=${config3}</script>
18627
+ </head>`);
18628
+ }
18629
+ }],
18630
+ appType: "spa",
18631
+ clearScreen: false
18632
+ });
18633
+ viteServers.push(server2);
18634
+ if (endpoint === "/") {
18635
+ app.use(server2.middlewares);
18636
+ } else {
18637
+ app.use(endpoint, server2.middlewares);
18409
18638
  }
18410
- const indexPath = join9(resolvedPath, index);
18411
- if (!existsSync9(indexPath)) {
18412
- logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18413
- }
18414
- app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18415
- let injectedHtml = "";
18416
- if (existsSync9(indexPath)) {
18417
- const rawHtml = readFileSync5(indexPath, "utf-8");
18418
- const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18419
- const nexusConfig = JSON.stringify({ apiUrl });
18420
- injectedHtml = rawHtml.replace(
18421
- "</head>",
18422
- `<script>window.__NEXUS__=${nexusConfig}</script>
18423
- </head>`
18424
- );
18639
+ logger.info({ path: srcPath }, `Vite dev server mounted at ${endpoint} (HMR enabled)`);
18640
+ return true;
18641
+ } catch (err) {
18642
+ if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") {
18643
+ logger.warn(`vite not installed \u2014 falling back to static serving for ${endpoint}`);
18644
+ return false;
18425
18645
  }
18426
- const fallbackHandler = (_req, res) => {
18427
- if (!injectedHtml) {
18428
- res.status(404).send("index.html not found");
18429
- return;
18430
- }
18431
- res.set("Cache-Control", "no-cache, no-store, must-revalidate");
18432
- res.type("html").send(injectedHtml);
18433
- };
18434
- if (endpoint === "/") {
18435
- app.get("{*splat}", fallbackHandler);
18646
+ logger.error({ err }, `Failed to mount Vite dev server at ${endpoint}`);
18647
+ return false;
18648
+ }
18649
+ }
18650
+ function mountStaticSPA(app, endpoint, distPath, options) {
18651
+ const { maxAge, etag, immutable, index, absolute } = options;
18652
+ let resolvedPath;
18653
+ if (absolute) {
18654
+ resolvedPath = distPath;
18655
+ } else {
18656
+ const projectPath2 = resolve(getProjectPath(), distPath);
18657
+ if (existsSync9(projectPath2)) {
18658
+ resolvedPath = projectPath2;
18436
18659
  } else {
18437
- app.get(endpoint, fallbackHandler);
18438
- app.get(`${endpoint}/{*splat}`, fallbackHandler);
18660
+ resolvedPath = resolve(getLibPath(), distPath);
18439
18661
  }
18440
- logger.info({ path: resolvedPath }, `SPA mounted at ${endpoint}`);
18662
+ }
18663
+ if (!existsSync9(resolvedPath)) {
18664
+ logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18665
+ return;
18666
+ }
18667
+ const indexPath = join9(resolvedPath, index);
18668
+ if (!existsSync9(indexPath)) {
18669
+ logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18670
+ }
18671
+ app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18672
+ let injectedHtml = "";
18673
+ if (existsSync9(indexPath)) {
18674
+ const rawHtml = readFileSync5(indexPath, "utf-8");
18675
+ const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18676
+ const nexusConfig = JSON.stringify({ apiUrl });
18677
+ injectedHtml = rawHtml.replace(
18678
+ "</head>",
18679
+ `<script>window.__NEXUS__=${nexusConfig}</script>
18680
+ </head>`
18681
+ );
18682
+ }
18683
+ const fallbackHandler = (_req, res) => {
18684
+ if (!injectedHtml) {
18685
+ res.status(404).send("index.html not found");
18686
+ return;
18687
+ }
18688
+ res.set("Cache-Control", "no-cache, no-store, must-revalidate");
18689
+ res.type("html").send(injectedHtml);
18441
18690
  };
18691
+ if (endpoint === "/") {
18692
+ app.get("{*splat}", fallbackHandler);
18693
+ } else {
18694
+ app.get(endpoint, fallbackHandler);
18695
+ app.get(`${endpoint}/{*splat}`, fallbackHandler);
18696
+ }
18697
+ logger.info({ path: resolvedPath }, `SPA mounted at ${endpoint}`);
18442
18698
  }
18443
- function resetServeSPAEndpoints() {
18699
+ async function resetServeSPA() {
18700
+ for (const server2 of viteServers) {
18701
+ try {
18702
+ await server2.close();
18703
+ } catch {
18704
+ }
18705
+ }
18706
+ viteServers.length = 0;
18444
18707
  registeredEndpoints.clear();
18445
18708
  }
18446
- var registeredEndpoints;
18709
+ var registeredEndpoints, viteServers;
18447
18710
  var init_spa_handler = __esm({
18448
18711
  "src/core/spa-handler.ts"() {
18449
18712
  "use strict";
@@ -18451,6 +18714,7 @@ var init_spa_handler = __esm({
18451
18714
  init_logger();
18452
18715
  init_env();
18453
18716
  registeredEndpoints = /* @__PURE__ */ new Set();
18717
+ viteServers = [];
18454
18718
  }
18455
18719
  });
18456
18720
 
@@ -19021,7 +19285,7 @@ var init_core = __esm({
19021
19285
  init_id();
19022
19286
  init_sequence();
19023
19287
  init_paths();
19024
- init_error_handler();
19288
+ init_safe_json();
19025
19289
  init_spa_handler();
19026
19290
  init_cache();
19027
19291
  init_jwt();
@@ -19736,7 +20000,9 @@ var init_base_service = __esm({
19736
20000
  const countResult = await qb.clone().count("* as count").first();
19737
20001
  const total = Number(countResult?.count ?? 0);
19738
20002
  qb = this.applySortingWithDefaults(qb, query);
19739
- qb = qb.limit(limit).offset(offset);
20003
+ if (limit > 0) {
20004
+ qb = qb.limit(limit).offset(offset);
20005
+ }
19740
20006
  const rawItems = await qb;
19741
20007
  const items = this.parseJsonFieldsFromArray(rawItems);
19742
20008
  const processedItems = await this.afterFindAll(items);
@@ -19821,7 +20087,7 @@ var init_base_service = __esm({
19821
20087
  });
19822
20088
  }
19823
20089
  const total = result.length;
19824
- const paginatedItems = result.slice(offset, offset + limit);
20090
+ const paginatedItems = limit === 0 ? result : result.slice(offset, offset + limit);
19825
20091
  return this.buildPaginatedResult(paginatedItems, total, page, limit);
19826
20092
  }
19827
20093
  /**
@@ -20076,14 +20342,14 @@ var init_base_service = __esm({
20076
20342
  * Build paginated result from items and count
20077
20343
  */
20078
20344
  buildPaginatedResult(items, total, page, limit) {
20079
- const totalPages = Math.ceil(total / limit);
20345
+ const totalPages = limit === 0 ? 1 : Math.ceil(total / limit);
20080
20346
  return {
20081
20347
  items,
20082
20348
  total,
20083
- page,
20084
- limit,
20349
+ page: limit === 0 ? 1 : page,
20350
+ limit: limit === 0 ? total : limit,
20085
20351
  totalPages,
20086
- hasNext: page < totalPages
20352
+ hasNext: limit === 0 ? false : page < totalPages
20087
20353
  };
20088
20354
  }
20089
20355
  /**
@@ -20092,8 +20358,8 @@ var init_base_service = __esm({
20092
20358
  getPagination(query) {
20093
20359
  const maxLimit = query?.maxLimit ?? 100;
20094
20360
  const page = Math.max(1, query?.page ?? 1);
20095
- const limit = Math.min(maxLimit, Math.max(1, query?.limit ?? 20));
20096
- const offset = (page - 1) * limit;
20361
+ const limit = query?.limit === 0 ? 0 : Math.min(maxLimit, Math.max(1, query?.limit ?? 20));
20362
+ const offset = limit === 0 ? 0 : (page - 1) * limit;
20097
20363
  return { page, limit, offset };
20098
20364
  }
20099
20365
  /**
@@ -20113,10 +20379,31 @@ var init_base_service = __esm({
20113
20379
  * Override in subclasses for entity-specific search
20114
20380
  */
20115
20381
  applySearch(qb, search) {
20382
+ const searchTerm = `%${search}%`;
20383
+ const searchableFields = [];
20384
+ const fields = "fields" in this.definition ? this.definition.fields : {};
20385
+ for (const [name, field] of Object.entries(fields)) {
20386
+ if (field.meta?.searchable) {
20387
+ searchableFields.push(name);
20388
+ }
20389
+ }
20116
20390
  if ("labelField" in this.definition && this.definition.labelField) {
20117
- const labelField = this.definition.labelField;
20118
- qb.where(labelField, "like", `%${search}%`);
20391
+ const lf = this.definition.labelField;
20392
+ if (!searchableFields.includes(lf)) {
20393
+ searchableFields.unshift(lf);
20394
+ }
20119
20395
  }
20396
+ if (searchableFields.length === 0) return qb;
20397
+ qb.where(function() {
20398
+ for (const fieldName of searchableFields) {
20399
+ const field = fields[fieldName];
20400
+ if (field?.db?.type === "json") {
20401
+ this.orWhereRaw(`CAST(?? AS TEXT) LIKE ?`, [fieldName, searchTerm]);
20402
+ } else {
20403
+ this.orWhere(fieldName, "like", searchTerm);
20404
+ }
20405
+ }
20406
+ });
20120
20407
  return qb;
20121
20408
  }
20122
20409
  /**
@@ -21740,7 +22027,8 @@ function createEntityController(service, definition, ctx) {
21740
22027
  async list(req, res) {
21741
22028
  checkPermission(req, "read");
21742
22029
  const page = Math.max(1, parseInt(req.query["page"]) || 1);
21743
- const limit = Math.min(100, Math.max(1, parseInt(req.query["limit"]) || 20));
22030
+ const rawLimit = parseInt(req.query["limit"]);
22031
+ const limit = rawLimit === 0 ? 0 : Math.min(100, Math.max(1, rawLimit || 20));
21744
22032
  let filters;
21745
22033
  if (req.query["filters"]) {
21746
22034
  filters = parseFilters(req.query["filters"], ctx.core.errors);
@@ -23221,17 +23509,18 @@ async function createModuleRouters(ctx, definitions, modulePrefix) {
23221
23509
  const runtime = await createEntityRuntimeAsync(ctx, definition);
23222
23510
  const route = inferEntityRoutePath(definition);
23223
23511
  const entityLabel = resolveLocalized7(definition.label, "en");
23224
- if (modulePrefix && route === modulePrefix) {
23512
+ const isExposed = !("expose" in definition && definition.expose === false);
23513
+ if (isExposed && modulePrefix && route === modulePrefix) {
23225
23514
  ctx.core.logger.warn(
23226
23515
  `Entity "${entityLabel}" inferred route "${route}" duplicates module prefix \u2014 add routePrefix to the entity definition to fix`
23227
23516
  );
23228
23517
  }
23229
- if (routeMap.has(route)) {
23518
+ if (isExposed && routeMap.has(route)) {
23230
23519
  ctx.core.logger.warn(
23231
23520
  `Entity "${entityLabel}" route "${route}" collides with "${routeMap.get(route)}" in the same module`
23232
23521
  );
23233
23522
  }
23234
- routeMap.set(route, entityLabel);
23523
+ if (isExposed) routeMap.set(route, entityLabel);
23235
23524
  router.use(route, runtime.router);
23236
23525
  const key = getServiceKey(definition);
23237
23526
  if (ctx.services.has(key)) {
@@ -23447,7 +23736,7 @@ var init_runtime = __esm({
23447
23736
  }
23448
23737
  });
23449
23738
 
23450
- // src/db/module-runner.ts
23739
+ // src/db/seed-runner.ts
23451
23740
  import { existsSync as existsSync10 } from "fs";
23452
23741
  import { join as join10 } from "path";
23453
23742
  import { pathToFileURL } from "url";
@@ -23515,8 +23804,8 @@ function hasSeedData(seed5) {
23515
23804
  if (!Array.isArray(seed5) && "source" in seed5 && seed5.source === "url") return true;
23516
23805
  return Array.isArray(seed5) && seed5.length > 0;
23517
23806
  }
23518
- var init_module_runner = __esm({
23519
- "src/db/module-runner.ts"() {
23807
+ var init_seed_runner = __esm({
23808
+ "src/db/seed-runner.ts"() {
23520
23809
  "use strict";
23521
23810
  init_runtime();
23522
23811
  init_paths();
@@ -23588,15 +23877,6 @@ var init_ensure_system_tables = __esm({
23588
23877
  }
23589
23878
  });
23590
23879
 
23591
- // src/cli/shared.ts
23592
- import { consola } from "consola";
23593
- var init_shared = __esm({
23594
- "src/cli/shared.ts"() {
23595
- "use strict";
23596
- init_logger();
23597
- }
23598
- });
23599
-
23600
23880
  // src/db/migration-sources.ts
23601
23881
  function buildMigrationSources() {
23602
23882
  const sources = [
@@ -23616,10 +23896,8 @@ function buildMigrationSources() {
23616
23896
  var init_migration_sources = __esm({
23617
23897
  "src/db/migration-sources.ts"() {
23618
23898
  "use strict";
23619
- init_shared();
23620
23899
  init_paths();
23621
- init_plugin_ops();
23622
- init_store();
23900
+ init_module_store();
23623
23901
  }
23624
23902
  });
23625
23903
 
@@ -23952,7 +24230,7 @@ async function runMigrations(knexInstance, sources) {
23952
24230
  const executedMigrations = await knex3("_nexus_migrations").where({ status: "completed" }).select("name").then((rows) => new Set(rows.map((r) => r.name)));
23953
24231
  const pendingMigrations = migrationFiles.filter((m2) => !executedMigrations.has(m2.name));
23954
24232
  if (pendingMigrations.length === 0) {
23955
- logger.info("No pending migrations");
24233
+ logger.debug("No pending migrations");
23956
24234
  return;
23957
24235
  }
23958
24236
  const batch = await getNextBatch(knex3);
@@ -24352,6 +24630,27 @@ var init_migration_helpers = __esm({
24352
24630
  import path2 from "path";
24353
24631
  import fs2 from "fs/promises";
24354
24632
  import { readFileSync as readFileSync6, mkdirSync as mkdirSync5, realpathSync } from "fs";
24633
+ function getColumnIndexBytes(field) {
24634
+ if (!field?.db) return 255 * MYSQL_BYTES_PER_CHAR;
24635
+ const size = field.db.size ?? 255;
24636
+ if (field.db.type === "text") return 0;
24637
+ if (field.db.type === "string") return size * MYSQL_BYTES_PER_CHAR;
24638
+ if (field.db.type === "integer") return 4;
24639
+ if (field.db.type === "boolean") return 1;
24640
+ if (field.db.type === "datetime" || field.db.type === "date") return 8;
24641
+ if (field.db.type === "uuid") return 16;
24642
+ return size * MYSQL_BYTES_PER_CHAR;
24643
+ }
24644
+ function warnIfIndexExceedsMySQLLimit(table, columns, unique, fields) {
24645
+ if (!fields) return;
24646
+ const totalBytes = columns.reduce((sum, col) => sum + getColumnIndexBytes(fields[col]), 0);
24647
+ if (totalBytes > MYSQL_MAX_INDEX_BYTES) {
24648
+ const indexType = unique ? "UNIQUE" : "INDEX";
24649
+ logger.warn(
24650
+ `${indexType} on ${table}(${columns.join(", ")}) requires ${totalBytes} bytes \u2014 exceeds MySQL utf8mb4 limit of ${MYSQL_MAX_INDEX_BYTES} bytes. Reduce column sizes or restructure the index for MySQL compatibility.`
24651
+ );
24652
+ }
24653
+ }
24355
24654
  async function detectSchemaDrift(knexInstance) {
24356
24655
  const knex3 = knexInstance ?? getDb();
24357
24656
  const entities = getAllPersistentEntities();
@@ -24459,6 +24758,7 @@ function computeSchemaDiff(entities, currentSchema, options) {
24459
24758
  for (const idx of entityIndexes) {
24460
24759
  const key = normalizeKey(idx.columns, !!idx.unique);
24461
24760
  if (!currentKeys.has(key)) {
24761
+ warnIfIndexExceedsMySQLLimit(tableName, idx.columns, !!idx.unique, entity.fields);
24462
24762
  diff.newIndexes.push({ columns: idx.columns, unique: !!idx.unique });
24463
24763
  }
24464
24764
  }
@@ -24630,7 +24930,7 @@ function formatDriftMessage(drift) {
24630
24930
  lines.push('Run "pnpm migrate:dev" to generate and apply migrations.');
24631
24931
  return lines.join("\n");
24632
24932
  }
24633
- var PERSISTENT_TYPES;
24933
+ var MYSQL_MAX_INDEX_BYTES, MYSQL_BYTES_PER_CHAR, PERSISTENT_TYPES;
24634
24934
  var init_migration_generator = __esm({
24635
24935
  "src/db/migration-generator.ts"() {
24636
24936
  "use strict";
@@ -24639,9 +24939,11 @@ var init_migration_generator = __esm({
24639
24939
  init_paths();
24640
24940
  init_schema_reader();
24641
24941
  init_engine();
24642
- init_queries();
24942
+ init_module_queries();
24643
24943
  init_migration_helpers();
24644
- init_store();
24944
+ init_module_store();
24945
+ MYSQL_MAX_INDEX_BYTES = 3072;
24946
+ MYSQL_BYTES_PER_CHAR = 4;
24645
24947
  PERSISTENT_TYPES = /* @__PURE__ */ new Set([
24646
24948
  "collection",
24647
24949
  "tree",
@@ -24872,6 +25174,10 @@ function handleDbError(err) {
24872
25174
  { path: "foreignKey", message: "Foreign key constraint violation" }
24873
25175
  ]);
24874
25176
  }
25177
+ if (code === "42P01" || code === "ER_NO_SUCH_TABLE" || dbErr.errno === 1146 || msg.includes("no such table")) {
25178
+ logger.error({ err }, "Database table not found \u2014 database may need migration or was wiped");
25179
+ throw new AppError({ code: ErrorCodes.DATABASE_NOT_READY, message: "Database not ready" }, 503);
25180
+ }
24875
25181
  logger.error({ err }, "Unexpected database error");
24876
25182
  throw new AppError({ code: ErrorCodes.SYSTEM_INTERNAL_ERROR, message: "Internal database error" }, 500);
24877
25183
  }
@@ -26215,7 +26521,7 @@ var init_db = __esm({
26215
26521
  init_schema_helpers();
26216
26522
  init_sql_utils();
26217
26523
  init_sqlite_compat();
26218
- init_module_runner();
26524
+ init_seed_runner();
26219
26525
  init_ensure_system_tables();
26220
26526
  init_migration_sources();
26221
26527
  init_migration_runner();
@@ -26479,8 +26785,11 @@ var init_events_api = __esm({
26479
26785
 
26480
26786
  // src/engine/context.ts
26481
26787
  import { ForbiddenError as CASLForbiddenError3, subject } from "@casl/ability";
26482
- import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2 } from "@gzl10/nexus-sdk";
26788
+ import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2, DEFAULT_LOCALES } from "@gzl10/nexus-sdk";
26483
26789
  import { Redis as Redis2 } from "ioredis";
26790
+ function setLocales(locales) {
26791
+ platformLocales = locales;
26792
+ }
26484
26793
  function getSharedCacheManager() {
26485
26794
  if (!sharedCacheManager) {
26486
26795
  const redisUrl = env.REDIS_URL;
@@ -26556,7 +26865,7 @@ function createModuleContext() {
26556
26865
  const defaultAdapter = createKnexAdapter(knex3);
26557
26866
  const defaultSchemaAdapter = createKnexSchemaAdapter(knex3);
26558
26867
  adaptersRegistry["temp"] = { data: getSharedTempAdapter() };
26559
- logger.debug(env.REDIS_URL ? "Temp adapter: Redis (shared)" : "Temp adapter: InMemory (shared)");
26868
+ logger.trace(env.REDIS_URL ? "Temp adapter: Redis (shared)" : "Temp adapter: InMemory (shared)");
26560
26869
  const middleware = {
26561
26870
  validate,
26562
26871
  rateLimit: createRateLimit,
@@ -26664,7 +26973,9 @@ function createModuleContext() {
26664
26973
  throw new Error(`Knex connection for adapter "${adapter}" not found. Available: ${Object.keys(knexConnections).join(", ")}`);
26665
26974
  }
26666
26975
  return conn;
26667
- }
26976
+ },
26977
+ // Placeholder — bound after ctx construction (needs full ModuleContext)
26978
+ seedModule: null
26668
26979
  };
26669
26980
  const configContext = {
26670
26981
  env,
@@ -26757,7 +27068,8 @@ function createModuleContext() {
26757
27068
  adapters: adaptersContext,
26758
27069
  // Root-level shortcuts for frequently used utilities
26759
27070
  events: createEventsApi(nexusEvents, logger),
26760
- createRouter: () => createRouter()
27071
+ createRouter: () => createRouter(),
27072
+ locales: platformLocales
26761
27073
  };
26762
27074
  servicesRegistry["cacheManager"] = getSharedCacheManager();
26763
27075
  ctx.runtime = {
@@ -26766,9 +27078,10 @@ function createModuleContext() {
26766
27078
  createEntityController: (service, def) => createEntityController(service, def, ctx),
26767
27079
  createEntityRouter: (controller, def) => createEntityRouter(controller, def, ctx)
26768
27080
  };
27081
+ ctx.db.seedModule = (mod) => runModuleSeed(mod, ctx);
26769
27082
  return ctx;
26770
27083
  }
26771
- var sharedCacheManager, sharedTempAdapter;
27084
+ var platformLocales, sharedCacheManager, sharedTempAdapter;
26772
27085
  var init_context = __esm({
26773
27086
  "src/engine/context.ts"() {
26774
27087
  "use strict";
@@ -26781,7 +27094,9 @@ var init_context = __esm({
26781
27094
  init_plugin_ops();
26782
27095
  init_load_config();
26783
27096
  init_events_api();
27097
+ init_seed_runner();
26784
27098
  init_cache_manager();
27099
+ platformLocales = DEFAULT_LOCALES;
26785
27100
  sharedCacheManager = null;
26786
27101
  sharedTempAdapter = null;
26787
27102
  }
@@ -26792,11 +27107,11 @@ var init_engine = __esm({
26792
27107
  "src/engine/index.ts"() {
26793
27108
  "use strict";
26794
27109
  init_registry();
26795
- init_queries();
27110
+ init_module_queries();
26796
27111
  init_loader();
26797
- init_store();
26798
- init_subjectExtractor();
26799
- init_extractors();
27112
+ init_module_store();
27113
+ init_subject_extractor();
27114
+ init_definition_extractors();
26800
27115
  init_context();
26801
27116
  }
26802
27117
  });
@@ -27145,6 +27460,33 @@ async function setupModuleRoutes(app) {
27145
27460
  }
27146
27461
  }
27147
27462
  const caslRegistry = /* @__PURE__ */ new Map();
27463
+ function mergeActionPermissions(existing, incoming) {
27464
+ const merged = { ...existing };
27465
+ for (const [role, perm] of Object.entries(incoming)) {
27466
+ if (!merged[role]) {
27467
+ merged[role] = perm;
27468
+ } else {
27469
+ const existingArr = Array.isArray(merged[role]) ? merged[role] : [merged[role]];
27470
+ const incomingArr = Array.isArray(perm) ? perm : [perm];
27471
+ merged[role] = [...existingArr, ...incomingArr];
27472
+ }
27473
+ }
27474
+ return merged;
27475
+ }
27476
+ function registerActionCasl(action) {
27477
+ const casl = action.casl;
27478
+ if (!casl || !("subject" in casl) || !casl.subject || !("permissions" in casl) || !casl.permissions) return;
27479
+ const subject2 = casl.subject;
27480
+ const existing = caslRegistry.get(subject2);
27481
+ if (existing) {
27482
+ existing.permissions = mergeActionPermissions(existing.permissions, casl.permissions);
27483
+ } else {
27484
+ caslRegistry.set(subject2, {
27485
+ subject: subject2,
27486
+ permissions: casl.permissions ?? {}
27487
+ });
27488
+ }
27489
+ }
27148
27490
  for (const mod of modules) {
27149
27491
  for (const def of mod.definitions ?? []) {
27150
27492
  const casl = def.casl;
@@ -27154,6 +27496,12 @@ async function setupModuleRoutes(app) {
27154
27496
  subject: casl.subject,
27155
27497
  permissions: casl.permissions ?? {}
27156
27498
  });
27499
+ for (const action of def.actions ?? []) {
27500
+ registerActionCasl(action);
27501
+ }
27502
+ }
27503
+ for (const action of mod.actions ?? []) {
27504
+ registerActionCasl(action);
27157
27505
  }
27158
27506
  }
27159
27507
  setEntityDefinitions(caslRegistry);
@@ -27338,7 +27686,7 @@ async function createApp(options = {}) {
27338
27686
  // Only accept arrays and objects
27339
27687
  }));
27340
27688
  app.use(cookieParser());
27341
- const serveSPA = createServeSPA(app);
27689
+ const serveSPA = createServeSPA(app, options.httpServer);
27342
27690
  if (options.beforeRoutes) {
27343
27691
  const result = options.beforeRoutes(app, serveSPA);
27344
27692
  if (result instanceof Promise) {
@@ -27432,11 +27780,11 @@ async function createApp(options = {}) {
27432
27780
  });
27433
27781
  const sortedSpas = [...servedSpas].sort((a, b) => b.endpoint.length - a.endpoint.length);
27434
27782
  for (const spa of sortedSpas) {
27435
- serveSPA(spa.endpoint, spa.path, spa);
27783
+ await serveSPA(spa.endpoint, spa.path, { ...spa, viteSrc: spa.viteSrc });
27436
27784
  }
27437
27785
  const { ui } = getConfig();
27438
27786
  if (ui.enabled) {
27439
- serveSPA(ui.base, ui.path);
27787
+ await serveSPA(ui.base, ui.path, { viteSrc: "../ui" });
27440
27788
  }
27441
27789
  app.use(errorMiddleware);
27442
27790
  return app;
@@ -27462,58 +27810,32 @@ var init_app = __esm({
27462
27810
  }
27463
27811
  });
27464
27812
 
27465
- // src/core/utils/net.ts
27813
+ // src/core/utils/port-check.ts
27466
27814
  import net from "net";
27467
27815
  import { execSync } from "child_process";
27468
27816
  function findProcessOnPort(port) {
27469
27817
  try {
27470
27818
  const output = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: "utf-8" });
27471
27819
  const pid = parseInt(output.trim().split("\n")[0] ?? "", 10);
27472
- return isNaN(pid) ? null : pid;
27820
+ if (isNaN(pid)) return null;
27821
+ let name = "unknown";
27822
+ try {
27823
+ name = execSync(`ps -o comm= -p ${pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27824
+ } catch {
27825
+ }
27826
+ return { pid, name };
27473
27827
  } catch {
27474
27828
  return null;
27475
27829
  }
27476
27830
  }
27477
- function isSameProcessGroup(pid) {
27478
- if (pid === process.pid || pid === process.ppid) return true;
27479
- try {
27480
- const ourPgid = execSync(`ps -o pgid= -p ${process.pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27481
- const targetPgid = execSync(`ps -o pgid= -p ${pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27482
- return ourPgid === targetPgid;
27483
- } catch {
27484
- return false;
27485
- }
27486
- }
27487
- function killProcessOnPort(port) {
27488
- const pid = findProcessOnPort(port);
27489
- if (!pid) return false;
27490
- if (isSameProcessGroup(pid)) {
27491
- logger.warn({ port, pid }, `Skipping same process group (PID ${pid}) on port ${port}`);
27492
- return false;
27493
- }
27494
- try {
27495
- process.kill(pid, "SIGTERM");
27496
- logger.warn({ port, pid }, `Killed process ${pid} on port ${port}`);
27497
- return true;
27498
- } catch {
27499
- return false;
27500
- }
27501
- }
27502
27831
  async function checkPortAvailable(port, host = "0.0.0.0") {
27503
27832
  return new Promise((resolve2, reject) => {
27504
27833
  const server2 = net.createServer();
27505
27834
  server2.once("error", (err) => {
27506
27835
  if (err.code === "EADDRINUSE") {
27507
- if (process.env["NODE_ENV"] !== "production") {
27508
- if (killProcessOnPort(port)) {
27509
- setTimeout(() => {
27510
- checkPortAvailable(port, host).then(resolve2).catch(reject);
27511
- }, 500);
27512
- return;
27513
- }
27514
- }
27515
- logger.error({ port }, `Port ${port} is already in use`);
27516
- reject(new Error(`Port ${port} is already in use`));
27836
+ const proc = findProcessOnPort(port);
27837
+ const msg = proc ? `Port ${port} is already in use by "${proc.name}" (PID ${proc.pid}). Stop that process first.` : `Port ${port} is already in use`;
27838
+ reject(new Error(msg));
27517
27839
  } else {
27518
27840
  reject(err);
27519
27841
  }
@@ -27524,10 +27846,9 @@ async function checkPortAvailable(port, host = "0.0.0.0") {
27524
27846
  server2.listen(port, host);
27525
27847
  });
27526
27848
  }
27527
- var init_net = __esm({
27528
- "src/core/utils/net.ts"() {
27849
+ var init_port_check = __esm({
27850
+ "src/core/utils/port-check.ts"() {
27529
27851
  "use strict";
27530
- init_logger();
27531
27852
  }
27532
27853
  });
27533
27854
 
@@ -27565,7 +27886,7 @@ function isFrpcInstalled() {
27565
27886
  return false;
27566
27887
  }
27567
27888
  }
27568
- function startTunnel(config3) {
27889
+ async function startTunnel(config3) {
27569
27890
  if (tunnelProcess) return true;
27570
27891
  if (!isFrpcInstalled()) {
27571
27892
  logger.warn("frpc binary not found \u2014 tunnel disabled. Install with: brew install frp");
@@ -27577,24 +27898,49 @@ function startTunnel(config3) {
27577
27898
  tunnelProcess = spawn("frpc", ["-c", tmpConfigPath], {
27578
27899
  stdio: ["ignore", "pipe", "pipe"]
27579
27900
  });
27580
- tunnelProcess.stdout?.on("data", (data) => {
27581
- const msg = data.toString().trim();
27582
- if (msg) logger.debug({ component: "tunnel" }, msg);
27583
- });
27584
- tunnelProcess.stderr?.on("data", (data) => {
27585
- const msg = data.toString().trim();
27586
- if (msg) logger.warn({ component: "tunnel" }, msg);
27587
- });
27588
- tunnelProcess.on("exit", (code) => {
27589
- if (code !== 0 && code !== null) {
27590
- logger.warn({ code, component: "tunnel" }, "frpc exited unexpectedly");
27591
- }
27592
- tunnelProcess = null;
27593
- cleanupConfig();
27901
+ outputBuffer = "";
27902
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
27903
+ return new Promise((resolve2) => {
27904
+ let settled = false;
27905
+ const collectOutput = (data) => {
27906
+ const msg = stripAnsi(data.toString()).trim();
27907
+ if (!msg) return;
27908
+ outputBuffer += msg + "\n";
27909
+ logger.debug({ component: "tunnel" }, msg);
27910
+ if (!settled && msg.includes("start proxy success")) {
27911
+ settled = true;
27912
+ const url = getTunnelUrl(config3.subdomain, config3.server);
27913
+ logger.info({ url, component: "tunnel" }, `Tunnel active \u2192 ${url}`);
27914
+ resolve2(true);
27915
+ }
27916
+ };
27917
+ tunnelProcess.stdout?.on("data", collectOutput);
27918
+ tunnelProcess.stderr?.on("data", collectOutput);
27919
+ tunnelProcess.on("exit", (code) => {
27920
+ if (code !== 0 && code !== null) {
27921
+ const output = outputBuffer.trim();
27922
+ const hint = output.includes("authorization failed") || output.includes("auth failed") || output.includes("invalid token") ? " \u2014 check FRPC_TOKEN is correct" : output.includes("login to the server failed") ? ` \u2014 ${output.split("\n").pop()}` : "";
27923
+ logger.error(
27924
+ { code, reason: output || void 0, component: "tunnel" },
27925
+ `frpc failed to start${hint}`
27926
+ );
27927
+ }
27928
+ tunnelProcess = null;
27929
+ outputBuffer = "";
27930
+ cleanupConfig();
27931
+ if (!settled) {
27932
+ settled = true;
27933
+ resolve2(false);
27934
+ }
27935
+ });
27936
+ setTimeout(() => {
27937
+ if (!settled) {
27938
+ settled = true;
27939
+ logger.warn({ component: "tunnel" }, "frpc connection timeout (5s) \u2014 tunnel may not be active");
27940
+ resolve2(false);
27941
+ }
27942
+ }, 5e3);
27594
27943
  });
27595
- const url = getTunnelUrl(config3.subdomain, config3.server);
27596
- logger.info({ url, component: "tunnel" }, `Tunnel enabled \u2192 ${url}`);
27597
- return true;
27598
27944
  }
27599
27945
  function stopTunnel() {
27600
27946
  if (tunnelProcess) {
@@ -27612,13 +27958,388 @@ function cleanupConfig() {
27612
27958
  tmpConfigPath = null;
27613
27959
  }
27614
27960
  }
27615
- var tunnelProcess, tmpConfigPath;
27961
+ var tunnelProcess, tmpConfigPath, outputBuffer;
27616
27962
  var init_tunnel = __esm({
27617
27963
  "src/core/tunnel.ts"() {
27618
27964
  "use strict";
27619
27965
  init_core();
27620
27966
  tunnelProcess = null;
27621
27967
  tmpConfigPath = null;
27968
+ outputBuffer = "";
27969
+ }
27970
+ });
27971
+
27972
+ // src/cli/shared.ts
27973
+ import { consola } from "consola";
27974
+ function initCli() {
27975
+ const config3 = getLoggerConfig();
27976
+ initLoggerService({ ...config3, level: "error", sentry: void 0 });
27977
+ setLoggerInstance(getPinoLogger());
27978
+ }
27979
+ var init_shared = __esm({
27980
+ "src/cli/shared.ts"() {
27981
+ "use strict";
27982
+ init_logger();
27983
+ }
27984
+ });
27985
+
27986
+ // src/cli/migrate-commands.ts
27987
+ import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
27988
+ import { join as join14 } from "path";
27989
+ import { pathToFileURL as pathToFileURL3 } from "url";
27990
+ import Table from "cli-table3";
27991
+ import { consola as consola2 } from "consola";
27992
+ async function loadSelfPlugin() {
27993
+ const projectPath2 = getProjectPath();
27994
+ const pkgPath = join14(projectPath2, "package.json");
27995
+ if (!existsSync12(pkgPath)) return;
27996
+ try {
27997
+ const pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
27998
+ const pkgName = pkg2?.name;
27999
+ 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)) {
28003
+ try {
28004
+ const { tsImport } = await import("tsx/esm/api");
28005
+ const mod = await tsImport(
28006
+ pathToFileURL3(srcEntry).href,
28007
+ import.meta.url
28008
+ );
28009
+ const manifest = extractPluginManifest(mod);
28010
+ if (manifest) {
28011
+ if (!manifest.migrationsDir) {
28012
+ manifest.migrationsDir = join14(projectPath2, "migrations");
28013
+ }
28014
+ registerPlugin(manifest);
28015
+ return;
28016
+ }
28017
+ } catch (err) {
28018
+ console.error(` \u26A0 Failed to load plugin src/index.ts: ${err.message}`);
28019
+ }
28020
+ }
28021
+ if (existsSync12(distEntry)) {
28022
+ try {
28023
+ const mod = await import(pathToFileURL3(distEntry).href);
28024
+ const manifest = extractPluginManifest(mod);
28025
+ if (manifest) {
28026
+ if (!manifest.migrationsDir) {
28027
+ manifest.migrationsDir = join14(projectPath2, "migrations");
28028
+ }
28029
+ registerPlugin(manifest);
28030
+ return;
28031
+ }
28032
+ } catch (err) {
28033
+ console.error(` \u26A0 Failed to load plugin dist/index.js: ${err.message}`);
28034
+ }
28035
+ }
28036
+ } catch {
28037
+ }
28038
+ }
28039
+ async function loadModulesForMigration() {
28040
+ loadCoreModules();
28041
+ await loadSelfPlugin();
28042
+ const config3 = await loadNexusConfig();
28043
+ if (config3.plugins?.length) {
28044
+ const sorted = topologicalSortPlugins(config3.plugins);
28045
+ for (const plugin of sorted) {
28046
+ registerPlugin(plugin);
28047
+ }
28048
+ }
28049
+ const coreNames = new Set(getOrderedModules().map((m2) => m2.name));
28050
+ for (const mod of config3.modules ?? []) {
28051
+ if (!coreNames.has(mod.name)) {
28052
+ registerModule(mod, { source: "standalone" });
28053
+ }
28054
+ }
28055
+ }
28056
+ var init_migrate_commands = __esm({
28057
+ "src/cli/migrate-commands.ts"() {
28058
+ "use strict";
28059
+ init_database();
28060
+ init_engine();
28061
+ init_load_config();
28062
+ init_shared();
28063
+ init_paths();
28064
+ init_connection();
28065
+ init_ensure_system_tables();
28066
+ init_migration_generator();
28067
+ init_migration_runner();
28068
+ }
28069
+ });
28070
+
28071
+ // src/cli/seed-commands.ts
28072
+ var seed_commands_exports = {};
28073
+ __export(seed_commands_exports, {
28074
+ handleSeedExport: () => handleSeedExport,
28075
+ importSeedFiles: () => importSeedFiles
28076
+ });
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";
28079
+ import { consola as consola3 } from "consola";
28080
+ function deserializeJsonFields(record, fields) {
28081
+ const result = { ...record };
28082
+ for (const [name, field] of Object.entries(fields)) {
28083
+ if (field.db?.type === "json" && typeof result[name] === "string") {
28084
+ try {
28085
+ result[name] = JSON.parse(result[name]);
28086
+ } catch {
28087
+ }
28088
+ }
28089
+ }
28090
+ return result;
28091
+ }
28092
+ function serializeJsonFields(record, fields) {
28093
+ const result = { ...record };
28094
+ for (const [name, field] of Object.entries(fields)) {
28095
+ if (field.db?.type === "json" && typeof result[name] === "object" && result[name] !== null) {
28096
+ result[name] = JSON.stringify(result[name]);
28097
+ }
28098
+ }
28099
+ return result;
28100
+ }
28101
+ async function handleSeedExport(entity) {
28102
+ initCli();
28103
+ await loadModulesForMigration();
28104
+ const db2 = getDb();
28105
+ try {
28106
+ const modules = getOrderedModules();
28107
+ const seedDir = join15(getProjectPath(), "data", "seeds");
28108
+ const seedableEntities = [];
28109
+ for (const mod of modules) {
28110
+ for (const def of mod.definitions ?? []) {
28111
+ if (!("seedable" in def) || !def.seedable) continue;
28112
+ if (entity && def.table !== entity) continue;
28113
+ seedableEntities.push({
28114
+ module: mod.name,
28115
+ table: def.table,
28116
+ fields: def.fields
28117
+ });
28118
+ }
28119
+ }
28120
+ if (seedableEntities.length === 0) {
28121
+ if (entity) {
28122
+ consola3.warn(`No seedable entity found with table "${entity}"`);
28123
+ } else {
28124
+ consola3.warn("No seedable entities found");
28125
+ }
28126
+ return;
28127
+ }
28128
+ if (!existsSync13(seedDir)) {
28129
+ mkdirSync6(seedDir, { recursive: true });
28130
+ }
28131
+ for (const { module: modName, table, fields } of seedableEntities) {
28132
+ const rows = await db2(table).orderBy("id");
28133
+ if (rows.length === 0) {
28134
+ consola3.info(`${table}: no records, skipping`);
28135
+ continue;
28136
+ }
28137
+ const exported = rows.map(
28138
+ (row) => deserializeJsonFields(row, fields)
28139
+ );
28140
+ const filePath = join15(seedDir, `${table}.json`);
28141
+ writeFileSync3(filePath, JSON.stringify(exported, null, 2) + "\n", "utf-8");
28142
+ consola3.success(`${table}: exported ${rows.length} records to data/seeds/${table}.json (module: ${modName})`);
28143
+ }
28144
+ } catch (err) {
28145
+ consola3.error("Seed export failed:", err);
28146
+ } finally {
28147
+ await db2.destroy();
28148
+ process.exit(0);
28149
+ }
28150
+ }
28151
+ async function importSeedFiles(db2, modules, logger2) {
28152
+ const seedDir = join15(getProjectPath(), "data", "seeds");
28153
+ if (!existsSync13(seedDir)) return;
28154
+ const files = readdirSync2(seedDir).filter((f) => f.endsWith(".json"));
28155
+ if (files.length === 0) return;
28156
+ const seedableDefs = /* @__PURE__ */ new Map();
28157
+ for (const mod of modules) {
28158
+ for (const rawDef of mod.definitions ?? []) {
28159
+ const def = rawDef;
28160
+ if (!def["seedable"] || !def["table"] || !def["fields"]) continue;
28161
+ seedableDefs.set(
28162
+ def["table"],
28163
+ { fields: def["fields"], module: mod.name }
28164
+ );
28165
+ }
28166
+ }
28167
+ for (const file of files) {
28168
+ const table = basename4(file, ".json");
28169
+ const defInfo = seedableDefs.get(table);
28170
+ if (!defInfo) {
28171
+ logger2.debug(`data/seeds/${file}: skipped (entity "${table}" is not seedable)`);
28172
+ continue;
28173
+ }
28174
+ const filePath = join15(seedDir, file);
28175
+ const raw = readFileSync9(filePath, "utf-8");
28176
+ let records;
28177
+ try {
28178
+ records = JSON.parse(raw);
28179
+ } catch {
28180
+ logger2.info(`data/seeds/${file}: skipped (invalid JSON)`);
28181
+ continue;
28182
+ }
28183
+ if (!Array.isArray(records) || records.length === 0) continue;
28184
+ const rows = records.map((r) => serializeJsonFields(r, defInfo.fields));
28185
+ for (const row of rows) {
28186
+ await db2(table).insert(row).onConflict("id").merge();
28187
+ }
28188
+ logger2.info(`Seeded ${rows.length} records into ${table} from data/seeds/${file}`);
28189
+ }
28190
+ }
28191
+ var init_seed_commands = __esm({
28192
+ "src/cli/seed-commands.ts"() {
28193
+ "use strict";
28194
+ init_engine();
28195
+ init_paths();
28196
+ init_connection();
28197
+ init_shared();
28198
+ init_migrate_commands();
28199
+ }
28200
+ });
28201
+
28202
+ // src/db/seed-context.ts
28203
+ var seed_context_exports = {};
28204
+ __export(seed_context_exports, {
28205
+ createSeedContext: () => createSeedContext
28206
+ });
28207
+ function createSeedContext(deps) {
28208
+ const { knex: db2, generateId: generateId4, hashPassword: hashPassword2, pluginPrefixes, logger: logger2 } = deps;
28209
+ const permissionsMap = /* @__PURE__ */ new Map();
28210
+ const seedContext = {
28211
+ masters: {
28212
+ register(type2, entries, options) {
28213
+ deps.masterRegistry.register(type2, entries, options);
28214
+ }
28215
+ },
28216
+ roles: {
28217
+ async add(role) {
28218
+ const hasTable = await db2.schema.hasTable("roles");
28219
+ if (!hasTable) return;
28220
+ const existing = await db2("roles").where({ name: role.name }).first();
28221
+ if (!existing) {
28222
+ const description = role.description ? JSON.stringify(typeof role.description === "string" ? { en: role.description } : role.description) : null;
28223
+ await db2("roles").insert({
28224
+ id: generateId4(),
28225
+ name: role.name,
28226
+ description,
28227
+ is_system: role.is_system ?? false
28228
+ });
28229
+ logger2.info({ role: role.name }, "Seeded role");
28230
+ }
28231
+ if (role.permissions) {
28232
+ let rolePerms = permissionsMap.get(role.name);
28233
+ if (!rolePerms) {
28234
+ rolePerms = /* @__PURE__ */ new Map();
28235
+ permissionsMap.set(role.name, rolePerms);
28236
+ }
28237
+ for (const [subject2, actions] of Object.entries(role.permissions)) {
28238
+ const normalized = subject2.charAt(0).toUpperCase() + subject2.slice(1);
28239
+ rolePerms.set(normalized, actions);
28240
+ }
28241
+ }
28242
+ }
28243
+ },
28244
+ users: {
28245
+ async add(user) {
28246
+ const hasTable = await db2.schema.hasTable("users");
28247
+ if (!hasTable) return;
28248
+ const existing = await db2("users").where({ email: user.email }).first();
28249
+ if (existing) return;
28250
+ const userId = generateId4();
28251
+ await db2("users").insert({
28252
+ id: userId,
28253
+ type: user.type ?? "human",
28254
+ email: user.email,
28255
+ password: await hashPassword2(user.password),
28256
+ name: user.name ?? user.email
28257
+ });
28258
+ if (user.roles?.length) {
28259
+ for (const roleName of user.roles) {
28260
+ const role = await db2("roles").where({ name: roleName }).first();
28261
+ if (role) {
28262
+ await db2("user_roles").insert({
28263
+ id: generateId4(),
28264
+ user_id: userId,
28265
+ role_id: role.id
28266
+ });
28267
+ } else {
28268
+ logger2.warn({ role: roleName, user: user.email }, "Role not found, skipping assignment");
28269
+ }
28270
+ }
28271
+ }
28272
+ logger2.info({ email: user.email, roles: user.roles }, "Seeded user");
28273
+ }
28274
+ },
28275
+ plugin(pluginName) {
28276
+ return {
28277
+ entity(entityName) {
28278
+ return {
28279
+ async upsert(data, options) {
28280
+ let prefix = pluginPrefixes.get(pluginName) ?? "";
28281
+ if (!prefix) {
28282
+ for (const [key2, pfx] of pluginPrefixes) {
28283
+ if (pluginName === key2 || pluginName.endsWith(`-${key2}`)) {
28284
+ prefix = pfx;
28285
+ break;
28286
+ }
28287
+ }
28288
+ }
28289
+ const fullTable = `${prefix}${entityName}`;
28290
+ const hasTable = await db2.schema.hasTable(fullTable);
28291
+ if (!hasTable) {
28292
+ logger2.warn({ table: fullTable, plugin: pluginName }, "Table not found for plugin entity seed");
28293
+ return 0;
28294
+ }
28295
+ const key = options?.key ?? "id";
28296
+ let seeded = 0;
28297
+ for (const row of data) {
28298
+ const keyValue = row[key];
28299
+ if (keyValue != null) {
28300
+ const existing = await db2(fullTable).where({ [key]: keyValue }).first();
28301
+ if (existing) continue;
28302
+ }
28303
+ const insertData = { ...row };
28304
+ if (key === "id" && !insertData["id"]) {
28305
+ insertData["id"] = generateId4();
28306
+ }
28307
+ await db2(fullTable).insert(insertData);
28308
+ seeded++;
28309
+ }
28310
+ if (seeded > 0) {
28311
+ logger2.info({ table: fullTable, seeded }, "Seeded plugin entity data");
28312
+ }
28313
+ return seeded;
28314
+ }
28315
+ };
28316
+ }
28317
+ };
28318
+ },
28319
+ async raw(table, data) {
28320
+ logger2.warn({ table }, "Using seed.raw() \u2014 prefer typed helpers when available");
28321
+ const hasTable = await db2.schema.hasTable(table);
28322
+ if (!hasTable) {
28323
+ logger2.warn({ table }, "Table not found for raw seed");
28324
+ return 0;
28325
+ }
28326
+ const rows = Array.isArray(data) ? data : [data];
28327
+ await db2(table).insert(rows);
28328
+ return rows.length;
28329
+ }
28330
+ };
28331
+ return {
28332
+ ctx: seedContext,
28333
+ flushPermissions: () => {
28334
+ if (permissionsMap.size > 0 && deps.onPermissionsCollected) {
28335
+ deps.onPermissionsCollected(permissionsMap);
28336
+ }
28337
+ }
28338
+ };
28339
+ }
28340
+ var init_seed_context = __esm({
28341
+ "src/db/seed-context.ts"() {
28342
+ "use strict";
27622
28343
  }
27623
28344
  });
27624
28345
 
@@ -27686,7 +28407,9 @@ var init_instrumentation = __esm({
27686
28407
  });
27687
28408
 
27688
28409
  // src/core/server.ts
28410
+ import http from "http";
27689
28411
  import { entityRoom as entityRoom6 } from "@gzl10/nexus-sdk";
28412
+ import { DEFAULT_LOCALES as DEFAULT_LOCALES2 } from "@gzl10/nexus-sdk";
27690
28413
  async function runMigrationsAndSeeds(config3) {
27691
28414
  initLoggerService(getLoggerConfig());
27692
28415
  setLoggerInstance(getPinoLogger());
@@ -27739,7 +28462,7 @@ async function runMigrationsAndSeeds(config3) {
27739
28462
  const sources = buildMigrationSources();
27740
28463
  const migrationFiles = await loadAllMigrationFiles(sources);
27741
28464
  if (migrationFiles.length > 0) {
27742
- logger.info({ count: migrationFiles.length }, "Deploying pending migrations...");
28465
+ logger.info({ sources: sources.length, files: migrationFiles.length }, "Running migration deploy...");
27743
28466
  try {
27744
28467
  await runMigrations(void 0, sources);
27745
28468
  } catch (err) {
@@ -27748,7 +28471,7 @@ async function runMigrationsAndSeeds(config3) {
27748
28471
  throw err;
27749
28472
  }
27750
28473
  }
27751
- logger.info("Checking schema drift...");
28474
+ logger.debug("Checking schema drift...");
27752
28475
  const drift = await detectSchemaDrift();
27753
28476
  if (drift && (drift.newTables.length > 0 || drift.alteredTables.length > 0)) {
27754
28477
  const message = formatDriftMessage(drift);
@@ -27795,7 +28518,16 @@ ${dirs}`);
27795
28518
  }
27796
28519
  const allDefinitions = modules.flatMap((m2) => m2.definitions ?? []);
27797
28520
  await createMemoryTables(allDefinitions);
27798
- logger.info("Running seeds...");
28521
+ try {
28522
+ const { importSeedFiles: importSeedFiles2 } = await Promise.resolve().then(() => (init_seed_commands(), seed_commands_exports));
28523
+ await importSeedFiles2(ctx.db.knex, modules, {
28524
+ info: (msg) => logger.info(msg),
28525
+ debug: (msg) => logger.debug(msg)
28526
+ });
28527
+ } catch (err) {
28528
+ logger.debug({ err }, "Seed file import skipped (no data/seeds/ or error)");
28529
+ }
28530
+ logger.debug("Running seeds...");
27799
28531
  for (const mod of modules) {
27800
28532
  try {
27801
28533
  await runModuleSeed(mod, ctx);
@@ -27803,14 +28535,41 @@ ${dirs}`);
27803
28535
  logger.error({ module: mod.name, err }, "Seed failed - continuing with next module");
27804
28536
  }
27805
28537
  }
28538
+ if (config3?.onSeed) {
28539
+ const { getPlugins: getPlugins2 } = await Promise.resolve().then(() => (init_module_queries(), module_queries_exports));
28540
+ const { createMasterRegistry: createMasterRegistry2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
28541
+ const masterRegistry = ctx.services.has("masters") ? ctx.services.get("masters") : createMasterRegistry2();
28542
+ const pluginPrefixes = /* @__PURE__ */ new Map();
28543
+ for (const plugin of getPlugins2()) {
28544
+ pluginPrefixes.set(plugin.code, `${plugin.code}_`);
28545
+ const shortName = plugin.name.replace(/^@[^/]+\/nexus-plugin-/, "");
28546
+ if (shortName !== plugin.code) {
28547
+ pluginPrefixes.set(shortName, `${plugin.code}_`);
28548
+ }
28549
+ }
28550
+ const { createSeedContext: createSeedContext2 } = await Promise.resolve().then(() => (init_seed_context(), seed_context_exports));
28551
+ const { ctx: seedCtx, flushPermissions } = createSeedContext2({
28552
+ knex: ctx.db.knex,
28553
+ generateId: ctx.core.generateId,
28554
+ hashPassword: ctx.core.crypto.hashPassword,
28555
+ masterRegistry,
28556
+ pluginPrefixes,
28557
+ logger: ctx.core.logger,
28558
+ onPermissionsCollected: (perms) => setSeedPermissions(perms)
28559
+ });
28560
+ await config3.onSeed(seedCtx);
28561
+ await masterRegistry.seed(ctx);
28562
+ flushPermissions();
28563
+ }
27806
28564
  }
27807
28565
  async function start(config3) {
27808
28566
  if (server) {
27809
28567
  throw new Error("Server already running. Call stop() first.");
27810
28568
  }
27811
28569
  currentConfig = config3;
27812
- if (env.NODE_ENV === "development" && env.FRPC_SERVER && env.FRPC_SUBDOMAIN && !env.BACKEND_URL) {
27813
- process.env["BACKEND_URL"] = getTunnelUrl(env.FRPC_SUBDOMAIN, env.FRPC_SERVER);
28570
+ setLocales(config3?.locales ?? DEFAULT_LOCALES2);
28571
+ if (env.NODE_ENV === "development" && env.FRPC_SERVER && !env.TRUST_PROXY) {
28572
+ process.env["TRUST_PROXY"] = "true";
27814
28573
  }
27815
28574
  const resolved = resolveConfig();
27816
28575
  if (resolved.port > 0) {
@@ -27833,13 +28592,17 @@ async function start(config3) {
27833
28592
  await initTelemetry2();
27834
28593
  await runMigrationsAndSeeds(config3);
27835
28594
  const effectiveCorsOrigins = buildEffectiveCorsOrigins(env.CORS_ORIGIN, config3?.spas);
28595
+ const httpServer = http.createServer();
27836
28596
  const app = await createApp({
27837
28597
  beforeRoutes: config3?.beforeRoutes,
27838
28598
  afterRoutes: config3?.afterRoutes,
27839
- spas: config3?.spas
28599
+ spas: config3?.spas,
28600
+ httpServer
27840
28601
  });
28602
+ httpServer.on("request", app);
27841
28603
  return new Promise((resolve2) => {
27842
- server = app.listen(resolved.port, resolved.host, async () => {
28604
+ server = httpServer;
28605
+ httpServer.listen(resolved.port, resolved.host, async () => {
27843
28606
  const timeoutMs = parseInt(process.env["REQUEST_TIMEOUT_MS"] || "30000", 10);
27844
28607
  if (timeoutMs > 0) {
27845
28608
  server.setTimeout(timeoutMs);
@@ -27865,22 +28628,24 @@ async function start(config3) {
27865
28628
  eventBridge.init();
27866
28629
  const addr = server.address();
27867
28630
  const actualPort = typeof addr === "object" && addr ? addr.port : resolved.port;
28631
+ let tunnelActive = false;
27868
28632
  if (env.NODE_ENV === "development" && env.FRPC_SERVER && env.FRPC_SUBDOMAIN) {
27869
- startTunnel({
28633
+ tunnelActive = await startTunnel({
27870
28634
  server: env.FRPC_SERVER,
27871
28635
  serverPort: env.FRPC_SERVER_PORT,
27872
28636
  token: env.FRPC_TOKEN,
27873
28637
  subdomain: env.FRPC_SUBDOMAIN,
27874
28638
  localPort: actualPort
27875
28639
  });
28640
+ if (tunnelActive && !env.BACKEND_URL) {
28641
+ process.env["BACKEND_URL"] = getTunnelUrl(env.FRPC_SUBDOMAIN, env.FRPC_SERVER);
28642
+ }
27876
28643
  }
27877
28644
  const baseUrl = env.BACKEND_URL || `http://localhost:${actualPort}`;
27878
- logger.info({ libPath: getLibPath(), projectPath: getProjectPath() }, "Paths");
27879
- logger.info(`API: ${baseUrl}/api/v1`);
27880
- if (resolved.ui.enabled) {
27881
- logger.info(`UI: ${baseUrl}`);
27882
- }
27883
- logger.info({ port: actualPort, mode: resolved.nodeEnv }, "Server started");
28645
+ logger.debug({ libPath: getLibPath(), projectPath: getProjectPath() }, "Paths");
28646
+ const urls = { api: `${baseUrl}/api/v1` };
28647
+ if (resolved.ui.enabled) urls["ui"] = baseUrl;
28648
+ logger.info({ port: actualPort, mode: resolved.nodeEnv, ...urls }, "Server started");
27884
28649
  nexusEvents.emitEvent("server.started", { port: actualPort, host: resolved.host });
27885
28650
  if (config3?.onReady) {
27886
28651
  try {
@@ -27906,7 +28671,8 @@ async function stop() {
27906
28671
  await resetSharedAdapters();
27907
28672
  resetConfigCache();
27908
28673
  clearCustomCaslRules();
27909
- resetServeSPAEndpoints();
28674
+ clearSeedPermissions();
28675
+ await resetServeSPA();
27910
28676
  return;
27911
28677
  }
27912
28678
  if (currentConfig?.beforeClose) {
@@ -27940,7 +28706,8 @@ async function stop() {
27940
28706
  await resetSharedAdapters();
27941
28707
  resetConfigCache();
27942
28708
  clearCustomCaslRules();
27943
- resetServeSPAEndpoints();
28709
+ clearSeedPermissions();
28710
+ await resetServeSPA();
27944
28711
  currentConfig = void 0;
27945
28712
  server = null;
27946
28713
  nexusEvents.emitEvent("server.stopped");
@@ -28000,7 +28767,7 @@ var init_server = __esm({
28000
28767
  init_cors();
28001
28768
  init_logger();
28002
28769
  init_error_middleware();
28003
- init_net();
28770
+ init_port_check();
28004
28771
  init_engine();
28005
28772
  init_context();
28006
28773
  init_db();