@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/main.js CHANGED
@@ -106,7 +106,7 @@ var init_package = __esm({
106
106
  "package.json"() {
107
107
  package_default = {
108
108
  name: "@gzl10/nexus-backend",
109
- version: "0.17.0",
109
+ version: "0.19.0",
110
110
  description: "Backend as a Service (BaaS) with Express 5, Knex and CASL",
111
111
  type: "module",
112
112
  main: "./dist/index.js",
@@ -118,6 +118,14 @@ var init_package = __esm({
118
118
  ".": {
119
119
  import: "./dist/index.js",
120
120
  types: "./dist/index.d.ts"
121
+ },
122
+ "./testing": {
123
+ import: "./dist/testing/index.js",
124
+ types: "./dist/testing/index.d.ts"
125
+ },
126
+ "./migrations": {
127
+ import: "./dist/migration-helpers/index.js",
128
+ types: "./dist/migration-helpers/index.d.ts"
121
129
  }
122
130
  },
123
131
  files: [
@@ -162,7 +170,7 @@ var init_package = __esm({
162
170
  "jwt"
163
171
  ],
164
172
  scripts: {
165
- dev: "tsx watch src/main.ts",
173
+ dev: "node --watch-path=./src --import tsx/esm src/main.ts",
166
174
  build: "tsup",
167
175
  start: "node dist/main.js",
168
176
  nexus: "tsx src/cli.ts",
@@ -218,39 +226,34 @@ var init_package = __esm({
218
226
  zod: "^3.24.0"
219
227
  },
220
228
  devDependencies: {
221
- "@gzl10/nexus-plugin-ai": "workspace:^",
222
- "@gzl10/nexus-plugin-auth-providers": "workspace:^",
223
- "@gzl10/nexus-plugin-charts": "workspace:^",
224
- "@gzl10/nexus-plugin-cms": "workspace:^",
225
- "@gzl10/nexus-plugin-compliance": "workspace:^",
226
- "@gzl10/nexus-plugin-docker": "workspace:^",
227
- "@gzl10/nexus-plugin-feeds": "workspace:^",
228
- "@gzl10/nexus-plugin-importer": "workspace:^",
229
- "@gzl10/nexus-plugin-links": "workspace:*",
230
- "@gzl10/nexus-plugin-notifications": "workspace:*",
231
- "@gzl10/nexus-plugin-oidc-server": "workspace:^",
232
- "@gzl10/nexus-plugin-plane": "workspace:^",
233
- "@gzl10/nexus-plugin-prisma": "workspace:^",
234
- "@gzl10/nexus-plugin-remote": "workspace:*",
235
- "@gzl10/nexus-plugin-schedules": "workspace:*",
236
- "@gzl10/nexus-plugin-scim": "workspace:^",
237
- "@gzl10/nexus-plugin-scraper": "workspace:^",
238
- "@gzl10/nexus-plugin-secrets": "workspace:^",
239
- "@gzl10/nexus-plugin-tags": "workspace:*",
240
- "@gzl10/nexus-plugin-webhooks": "workspace:*",
241
229
  "@types/bcryptjs": "^2.4.0",
242
230
  "@types/compression": "^1.8.1",
243
231
  "@types/cookie-parser": "^1.4.10",
244
232
  "@types/cors": "^2.8.19",
245
233
  "@types/express": "^5.0.6",
234
+ "@types/express-serve-static-core": "^5.1.1",
246
235
  "@types/jsonwebtoken": "^9.0.10",
247
236
  "@types/multer": "^2.1.0",
248
237
  "@types/nodemailer": "^7.0.11",
238
+ "@types/qs": "^6.15.0",
249
239
  "@types/supertest": "^6.0.3",
250
240
  "pino-pretty": "^13.1.3",
251
241
  "socket.io-client": "^4.8.3",
252
242
  supertest: "^7.2.2",
253
- tsx: "^4.21.0"
243
+ tsx: "^4.21.0",
244
+ vite: "^8.0.3"
245
+ },
246
+ peerDependencies: {
247
+ vite: ">=6.0.0",
248
+ vitest: ">=3.0.0"
249
+ },
250
+ peerDependenciesMeta: {
251
+ vite: {
252
+ optional: true
253
+ },
254
+ vitest: {
255
+ optional: true
256
+ }
254
257
  },
255
258
  publishConfig: {
256
259
  access: "public",
@@ -1151,6 +1154,14 @@ async function isWorkspacePackage(name) {
1151
1154
  }
1152
1155
  }
1153
1156
  async function installPlugin(name, opts) {
1157
+ const projectPath2 = opts?.projectPath ?? process.cwd();
1158
+ const pkgPath = join2(projectPath2, "package.json");
1159
+ if (existsSync2(pkgPath)) {
1160
+ const pkg3 = JSON.parse(readFileSync(pkgPath, "utf-8"));
1161
+ if (pkg3.name === "@gzl10/nexus-backend") {
1162
+ throw new Error("Cannot install plugins inside @gzl10/nexus-backend. Run this command from a Nexus project directory.");
1163
+ }
1164
+ }
1154
1165
  const isWorkspace = await isWorkspacePackage(name);
1155
1166
  let pkg2;
1156
1167
  if (opts?.version) {
@@ -1167,11 +1178,28 @@ async function installPlugin(name, opts) {
1167
1178
  plugins[name] = { enabled: true };
1168
1179
  writePluginsFile(plugins, filePath);
1169
1180
  }
1181
+ function isPackageInstalled(name, projectPath2) {
1182
+ const base = projectPath2 || process.cwd();
1183
+ const pkgPath = join2(base, "package.json");
1184
+ try {
1185
+ const pkg2 = JSON.parse(readFileSync(pkgPath, "utf-8"));
1186
+ return !!(pkg2.dependencies?.[name] || pkg2.devDependencies?.[name]);
1187
+ } catch {
1188
+ return false;
1189
+ }
1190
+ }
1170
1191
  async function uninstallPlugin(name, projectPath2) {
1171
- await execAsync(`pnpm remove ${name}`);
1172
1192
  const filePath = getPluginsFilePath(projectPath2);
1173
1193
  const plugins = readPluginsFile(filePath);
1174
- if (name in plugins) {
1194
+ const isRegistered = name in plugins;
1195
+ const isInstalled = isPackageInstalled(name, projectPath2);
1196
+ if (!isRegistered && !isInstalled) {
1197
+ throw new Error(`Plugin ${shortPluginName(name)} is not installed`);
1198
+ }
1199
+ if (isInstalled) {
1200
+ await execAsync(`pnpm remove ${name}`);
1201
+ }
1202
+ if (isRegistered) {
1175
1203
  delete plugins[name];
1176
1204
  writePluginsFile(plugins, filePath);
1177
1205
  }
@@ -1252,7 +1280,7 @@ var init_table_prefix = __esm({
1252
1280
  }
1253
1281
  });
1254
1282
 
1255
- // src/engine/store.ts
1283
+ // src/engine/module-store.ts
1256
1284
  function resetStore() {
1257
1285
  moduleStore.modules.length = 0;
1258
1286
  moduleStore.plugins.clear();
@@ -1263,8 +1291,8 @@ function resetStore() {
1263
1291
  moduleStore.tableToSubject.clear();
1264
1292
  }
1265
1293
  var moduleStore;
1266
- var init_store = __esm({
1267
- "src/engine/store.ts"() {
1294
+ var init_module_store = __esm({
1295
+ "src/engine/module-store.ts"() {
1268
1296
  "use strict";
1269
1297
  moduleStore = {
1270
1298
  /** Registered modules with source metadata */
@@ -1317,7 +1345,7 @@ var init_id = __esm({
1317
1345
  }
1318
1346
  });
1319
1347
 
1320
- // src/engine/extractors.ts
1348
+ // src/engine/definition-extractors.ts
1321
1349
  function getTableAndSubject(def) {
1322
1350
  const caslSubject = def.casl?.subject;
1323
1351
  if (!TYPES_WITH_TABLE.has(def.type)) {
@@ -1385,8 +1413,8 @@ function validateModuleDependencies(modules) {
1385
1413
  }
1386
1414
  }
1387
1415
  var TYPES_WITH_TABLE;
1388
- var init_extractors = __esm({
1389
- "src/engine/extractors.ts"() {
1416
+ var init_definition_extractors = __esm({
1417
+ "src/engine/definition-extractors.ts"() {
1390
1418
  "use strict";
1391
1419
  TYPES_WITH_TABLE = /* @__PURE__ */ new Set(["collection", "reference", "event", "config", "temp", "view", void 0]);
1392
1420
  }
@@ -1506,9 +1534,9 @@ var init_registry = __esm({
1506
1534
  "use strict";
1507
1535
  init_plugin_ops();
1508
1536
  init_table_prefix();
1509
- init_store();
1537
+ init_module_store();
1510
1538
  init_id();
1511
- init_extractors();
1539
+ init_definition_extractors();
1512
1540
  }
1513
1541
  });
1514
1542
 
@@ -1560,7 +1588,28 @@ var init_paths = __esm({
1560
1588
  }
1561
1589
  });
1562
1590
 
1563
- // src/engine/queries.ts
1591
+ // src/engine/module-queries.ts
1592
+ var module_queries_exports = {};
1593
+ __export(module_queries_exports, {
1594
+ getCoreManifest: () => getCoreManifest,
1595
+ getCoreModules: () => getCoreModules,
1596
+ getModule: () => getModule,
1597
+ getModules: () => getModules,
1598
+ getOrderedModules: () => getOrderedModules,
1599
+ getOrderedModulesInternal: () => getOrderedModulesInternal,
1600
+ getPlugin: () => getPlugin,
1601
+ getPluginByCode: () => getPluginByCode,
1602
+ getPlugins: () => getPlugins,
1603
+ getRegisteredSubjects: () => getRegisteredSubjects,
1604
+ getSubjectForTable: () => getSubjectForTable,
1605
+ getUserManifest: () => getUserManifest,
1606
+ getUserModules: () => getUserModules,
1607
+ hasModule: () => hasModule,
1608
+ hasPlugin: () => hasPlugin,
1609
+ hasPluginByCode: () => hasPluginByCode,
1610
+ hasUserApp: () => hasUserApp,
1611
+ isValidSubject: () => isValidSubject
1612
+ });
1564
1613
  import { join as join4 } from "path";
1565
1614
  import { readFileSync as readFileSync3 } from "fs";
1566
1615
  function readPackageJson(dir) {
@@ -1594,12 +1643,22 @@ function getOrderedModulesInternal() {
1594
1643
  moduleStore.modules.forEach(visit);
1595
1644
  return sorted;
1596
1645
  }
1646
+ function getModule(name) {
1647
+ const mod = moduleStore.modules.find((m) => m.name === name);
1648
+ return mod ? toModuleManifest(mod) : void 0;
1649
+ }
1650
+ function getPlugin(name) {
1651
+ return moduleStore.plugins.get(name);
1652
+ }
1597
1653
  function getPlugins() {
1598
1654
  return [...moduleStore.plugins.values()];
1599
1655
  }
1600
1656
  function getRegisteredSubjects() {
1601
1657
  return [...moduleStore.subjects];
1602
1658
  }
1659
+ function isValidSubject(subject2) {
1660
+ return moduleStore.subjects.has(subject2);
1661
+ }
1603
1662
  function getSubjectForTable(table) {
1604
1663
  return moduleStore.tableToSubject.get(table);
1605
1664
  }
@@ -1646,10 +1705,10 @@ function getPluginByCode(code) {
1646
1705
  function hasPluginByCode(code) {
1647
1706
  return moduleStore.pluginsByCode.has(code);
1648
1707
  }
1649
- var init_queries = __esm({
1650
- "src/engine/queries.ts"() {
1708
+ var init_module_queries = __esm({
1709
+ "src/engine/module-queries.ts"() {
1651
1710
  "use strict";
1652
- init_store();
1711
+ init_module_store();
1653
1712
  init_paths();
1654
1713
  }
1655
1714
  });
@@ -2128,17 +2187,22 @@ var init_definitions = __esm({
2128
2187
  };
2129
2188
  mastersEntity = {
2130
2189
  table: "masters",
2190
+ seedable: true,
2131
2191
  routePrefix: "/",
2132
2192
  label: { en: "Master", es: "Maestro" },
2133
2193
  labelPlural: { en: "Masters", es: "Maestros" },
2134
2194
  labelField: "label",
2135
2195
  timestamps: true,
2196
+ availableDisplayModes: ["board"],
2197
+ groupBy: "type",
2198
+ groupableFields: ["type"],
2199
+ //columnDragFields: ['is_active'],
2136
2200
  fields: {
2137
2201
  id: useTextField2({
2138
2202
  label: { en: "ID", es: "ID" },
2139
2203
  required: true,
2140
2204
  size: 100,
2141
- hint: { en: "Format: type:code (e.g. countries:ES)", es: "Formato: type:code (ej. countries:ES)" },
2205
+ hidden: true,
2142
2206
  meta: { sortable: true }
2143
2207
  }),
2144
2208
  type: useTextField2({
@@ -2155,13 +2219,31 @@ var init_definitions = __esm({
2155
2219
  meta: { sortable: true, searchable: true }
2156
2220
  }),
2157
2221
  label: useLocalizedField({ label: { en: "Name", es: "Nombre" } }),
2158
- order: orderField,
2222
+ order: { ...orderField, meta: { ...orderField.meta, showInDisplay: false } },
2159
2223
  is_active: isActiveField,
2160
2224
  metadata: useJsonField({
2161
2225
  label: { en: "Metadata", es: "Metadatos" },
2162
- hint: { en: "Type-specific fields (symbol, flag, etc.)", es: "Campos espec\xEDficos del tipo" }
2226
+ hint: {
2227
+ en: "Type-specific fields (symbol, flag, etc.)",
2228
+ es: "Campos espec\xEDficos del tipo"
2229
+ },
2230
+ meta: { showInDisplay: false }
2163
2231
  })
2164
2232
  },
2233
+ hooks: () => ({
2234
+ beforeCreate: async (data) => {
2235
+ if (data["type"] && data["code"] && !data["id"]) {
2236
+ data["id"] = `${data["type"]}:${data["code"]}`;
2237
+ }
2238
+ return data;
2239
+ },
2240
+ beforeUpdate: async (_id, data) => {
2241
+ if ("type" in data || "code" in data) {
2242
+ throw new Error("Cannot update type or code of a master record. Delete and recreate instead.");
2243
+ }
2244
+ return data;
2245
+ }
2246
+ }),
2165
2247
  defaultSort: { field: "order", order: "asc" },
2166
2248
  indexes: [{ columns: ["type", "code"], unique: true }],
2167
2249
  casl: { subject: "Master", permissions: masterCaslPermissions }
@@ -2170,6 +2252,10 @@ var init_definitions = __esm({
2170
2252
  });
2171
2253
 
2172
2254
  // src/modules/masters/registry.ts
2255
+ var registry_exports = {};
2256
+ __export(registry_exports, {
2257
+ createMasterRegistry: () => createMasterRegistry
2258
+ });
2173
2259
  function createMasterRegistry() {
2174
2260
  const registrations = [];
2175
2261
  return {
@@ -2207,7 +2293,7 @@ function createMasterRegistry() {
2207
2293
  await ctx.db.knex("masters").insert(row).onConflict(["type", "code"]).merge();
2208
2294
  }
2209
2295
  } else {
2210
- await ctx.db.knex("masters").insert(rows);
2296
+ await ctx.db.knex("masters").insert(rows).onConflict(["type", "code"]).ignore();
2211
2297
  }
2212
2298
  }
2213
2299
  },
@@ -8640,15 +8726,12 @@ var init_document_types = __esm({
8640
8726
  }
8641
8727
  });
8642
8728
 
8643
- // src/modules/masters/index.ts
8644
- var PREDEFINED_MASTERS, mastersModule;
8645
- var init_masters = __esm({
8646
- "src/modules/masters/index.ts"() {
8729
+ // src/modules/masters/constants.ts
8730
+ var DEFAULT_MASTER_TYPES, PREDEFINED_MASTERS;
8731
+ var init_constants = __esm({
8732
+ "src/modules/masters/constants.ts"() {
8647
8733
  "use strict";
8648
- init_definitions();
8649
- init_registry2();
8650
- init_definitions();
8651
- init_registry2();
8734
+ DEFAULT_MASTER_TYPES = ["languages", "timezones"];
8652
8735
  PREDEFINED_MASTERS = {
8653
8736
  currencies: () => Promise.resolve().then(() => (init_currencies(), currencies_exports)).then((m) => m.default),
8654
8737
  languages: () => Promise.resolve().then(() => (init_languages(), languages_exports)).then((m) => m.default),
@@ -8665,15 +8748,137 @@ var init_masters = __esm({
8665
8748
  "phone-prefixes": () => Promise.resolve().then(() => (init_phone_prefixes(), phone_prefixes_exports)).then((m) => m.default),
8666
8749
  "document-types": () => Promise.resolve().then(() => (init_document_types(), document_types_exports)).then((m) => m.default)
8667
8750
  };
8751
+ }
8752
+ });
8753
+
8754
+ // src/modules/masters/actions/install-type.ts
8755
+ import { useSelectField as useSelectField2 } from "@gzl10/nexus-sdk/fields";
8756
+ var installTypeAction;
8757
+ var init_install_type = __esm({
8758
+ "src/modules/masters/actions/install-type.ts"() {
8759
+ "use strict";
8760
+ init_constants();
8761
+ installTypeAction = {
8762
+ key: "install-type",
8763
+ scope: "module",
8764
+ label: { en: "Install Master Type", es: "Instalar Tipo de Maestro" },
8765
+ icon: "mdi:database-plus",
8766
+ variant: "primary",
8767
+ output: {},
8768
+ input: {
8769
+ type: useSelectField2({
8770
+ label: { en: "Master Type", es: "Tipo de Maestro" },
8771
+ required: true,
8772
+ options: Object.keys(PREDEFINED_MASTERS).filter((t) => !DEFAULT_MASTER_TYPES.includes(t)).map((t) => ({
8773
+ value: t,
8774
+ label: t.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
8775
+ }))
8776
+ })
8777
+ },
8778
+ handler: async (ctx, input) => {
8779
+ if (process.env["NODE_ENV"] === "production") {
8780
+ throw new ctx.core.errors.ForbiddenError("Master type management is only available in development");
8781
+ }
8782
+ const { type: type2 } = input;
8783
+ const loader = PREDEFINED_MASTERS[type2];
8784
+ if (!loader) {
8785
+ throw new ctx.core.errors.AppError(`Unknown predefined master type: ${type2}`, 400);
8786
+ }
8787
+ const existing = await ctx.db.knex("masters").where({ type: type2 }).first();
8788
+ if (existing) {
8789
+ throw new ctx.core.errors.ConflictError(`Master type "${type2}" is already installed`);
8790
+ }
8791
+ const entries = await loader();
8792
+ const rows = entries.map((entry, i) => ({
8793
+ id: `${type2}:${entry.code}`,
8794
+ type: type2,
8795
+ code: entry.code,
8796
+ label: JSON.stringify(typeof entry.label === "string" ? { en: entry.label } : entry.label),
8797
+ order: entry.order ?? i,
8798
+ is_active: entry.is_active ?? true,
8799
+ metadata: entry.metadata ? JSON.stringify(entry.metadata) : null
8800
+ }));
8801
+ await ctx.db.knex("masters").insert(rows).onConflict(["type", "code"]).ignore();
8802
+ return { installed: type2, count: rows.length };
8803
+ }
8804
+ };
8805
+ }
8806
+ });
8807
+
8808
+ // src/modules/masters/actions/uninstall-type.ts
8809
+ import { useTextField as useTextField3 } from "@gzl10/nexus-sdk/fields";
8810
+ var uninstallTypeAction;
8811
+ var init_uninstall_type = __esm({
8812
+ "src/modules/masters/actions/uninstall-type.ts"() {
8813
+ "use strict";
8814
+ init_constants();
8815
+ uninstallTypeAction = {
8816
+ key: "uninstall-type",
8817
+ scope: "module",
8818
+ label: { en: "Uninstall Master Type", es: "Desinstalar Tipo de Maestro" },
8819
+ icon: "mdi:database-minus",
8820
+ variant: "danger",
8821
+ output: {},
8822
+ confirm: {
8823
+ type: "simple",
8824
+ title: { en: "Uninstall Master Type", es: "Desinstalar Tipo de Maestro" },
8825
+ message: {
8826
+ en: "This will delete ALL records of the selected type. This action cannot be undone.",
8827
+ es: "Esto eliminar\xE1 TODOS los registros del tipo seleccionado. Esta acci\xF3n no se puede deshacer."
8828
+ }
8829
+ },
8830
+ input: {
8831
+ type: useTextField3({
8832
+ label: { en: "Type slug to uninstall", es: "Slug del tipo a desinstalar" },
8833
+ required: true,
8834
+ hint: {
8835
+ en: 'Enter the master type slug (e.g., "currencies", "countries")',
8836
+ es: 'Introduce el slug del tipo (ej: "currencies", "countries")'
8837
+ }
8838
+ })
8839
+ },
8840
+ handler: async (ctx, input) => {
8841
+ if (process.env["NODE_ENV"] === "production") {
8842
+ throw new ctx.core.errors.ForbiddenError("Master type management is only available in development");
8843
+ }
8844
+ const { type: type2 } = input;
8845
+ if (DEFAULT_MASTER_TYPES.includes(type2)) {
8846
+ throw new ctx.core.errors.AppError(`Cannot uninstall default master type "${type2}" (required by core)`, 400);
8847
+ }
8848
+ const existing = await ctx.db.knex("masters").where({ type: type2 }).first();
8849
+ if (!existing) {
8850
+ throw new ctx.core.errors.NotFoundError(`Master type "${type2}" is not installed`);
8851
+ }
8852
+ const deleted = await ctx.db.knex("masters").where({ type: type2 }).del();
8853
+ return { uninstalled: type2, count: deleted };
8854
+ }
8855
+ };
8856
+ }
8857
+ });
8858
+
8859
+ // src/modules/masters/index.ts
8860
+ var mastersModule;
8861
+ var init_masters = __esm({
8862
+ "src/modules/masters/index.ts"() {
8863
+ "use strict";
8864
+ init_definitions();
8865
+ init_registry2();
8866
+ init_constants();
8867
+ init_install_type();
8868
+ init_uninstall_type();
8869
+ init_definitions();
8870
+ init_registry2();
8871
+ init_constants();
8668
8872
  mastersModule = {
8669
8873
  name: "masters",
8670
8874
  type: "core",
8671
- label: { en: "Master Data", es: "Datos maestros" },
8875
+ label: { en: "Masters", es: "Maestros" },
8672
8876
  icon: "mdi:database-outline",
8673
8877
  description: { en: "Reference data catalogs", es: "Cat\xE1logos de datos de referencia" },
8674
- category: "data",
8878
+ category: "settings",
8675
8879
  routePrefix: "/masters",
8676
8880
  definitions: [mastersEntity],
8881
+ actions: [installTypeAction, uninstallTypeAction],
8677
8882
  init: (ctx) => {
8678
8883
  if (!ctx.services.has("masters")) {
8679
8884
  ctx.services.register("masters", createMasterRegistry());
@@ -8684,13 +8889,14 @@ var init_masters = __esm({
8684
8889
  ctx.services.register("masters", createMasterRegistry());
8685
8890
  }
8686
8891
  const registry2 = ctx.services.get("masters");
8687
- const configMasters = ctx.config.resolved["masters"];
8688
- const typesToSeed = !configMasters || configMasters === "*" ? Object.keys(PREDEFINED_MASTERS) : configMasters.filter((t) => t in PREDEFINED_MASTERS);
8689
- for (const type2 of typesToSeed) {
8690
- const loader = PREDEFINED_MASTERS[type2];
8691
- if (!loader) continue;
8692
- const entries = await loader();
8693
- registry2.register(type2, entries, { seed: "if-empty" });
8892
+ const existing = await ctx.db.knex("masters").first();
8893
+ if (!existing) {
8894
+ for (const type2 of DEFAULT_MASTER_TYPES) {
8895
+ const loader = PREDEFINED_MASTERS[type2];
8896
+ if (!loader) continue;
8897
+ const entries = await loader();
8898
+ registry2.register(type2, entries, { seed: "if-empty" });
8899
+ }
8694
8900
  }
8695
8901
  await registry2.seed(ctx);
8696
8902
  }
@@ -8848,15 +9054,18 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8848
9054
  const meta = field["meta"];
8849
9055
  return meta?.["sortable"] === true && !field["hidden"];
8850
9056
  });
9057
+ const explicitGroupable = def["groupableFields"];
8851
9058
  const groupableInputTypes = ["select", "switch", "checkbox", "radio", "tags"];
8852
- const groupableFields = fieldEntries.filter(([, f]) => {
9059
+ const autoGroupableFields = fieldEntries.filter(([, f]) => {
8853
9060
  const field = f;
8854
9061
  const inputType = field["input"];
8855
9062
  return groupableInputTypes.includes(inputType ?? "") && !field["hidden"];
8856
9063
  });
9064
+ const resolvedGroupableFields = explicitGroupable ?? (autoGroupableFields.length > 0 ? autoGroupableFields.map(([name]) => name) : void 0);
8857
9065
  const entityType = def["type"] ?? "collection";
8858
9066
  const explicitDisplayMode = def["displayMode"];
8859
- const defaultDisplayMode = explicitDisplayMode ?? (["tree", "dag"].includes(entityType) ? "tree" : "table");
9067
+ const explicitAvailableModes = def["availableDisplayModes"];
9068
+ const defaultDisplayMode = explicitDisplayMode ?? (explicitAvailableModes?.length === 1 ? explicitAvailableModes[0] : void 0) ?? (["tree", "dag"].includes(entityType) ? "tree" : "table");
8860
9069
  const entityIdent = def["table"] ?? def["key"];
8861
9070
  return {
8862
9071
  id: def._id,
@@ -8876,9 +9085,11 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8876
9085
  displayMode: explicitDisplayMode,
8877
9086
  defaultDisplayMode,
8878
9087
  availableDisplayModes: (() => {
9088
+ const explicit = def["availableDisplayModes"];
9089
+ if (explicit?.length) return explicit;
8879
9090
  const modes = ["table", "list", "masonry"];
8880
9091
  if (["tree", "dag"].includes(entityType)) modes.push("tree");
8881
- if (groupableFields.length > 0) modes.push("board");
9092
+ if ((resolvedGroupableFields?.length ?? 0) > 0) modes.push("board");
8882
9093
  if (def["calendarFrom"]) modes.push("calendar");
8883
9094
  return modes;
8884
9095
  })(),
@@ -8888,10 +9099,8 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8888
9099
  value: name,
8889
9100
  label: f["label"]
8890
9101
  })),
8891
- groupableFields: groupableFields.length > 0 ? groupableFields.map(([name, f]) => ({
8892
- value: name,
8893
- label: f["label"]
8894
- })) : void 0,
9102
+ groupableFields: resolvedGroupableFields,
9103
+ columnDragFields: def["columnDragFields"],
8895
9104
  groupBy: def["groupBy"],
8896
9105
  subgroupBy: def["subgroupBy"],
8897
9106
  calendarFrom: def["calendarFrom"],
@@ -8931,21 +9140,6 @@ function toModuleDTO(mod, ctx) {
8931
9140
  hasInit: !!mod.init
8932
9141
  };
8933
9142
  }
8934
- function getOrderedModulesViaContext(ctx) {
8935
- return ctx.engine.getModules();
8936
- }
8937
- async function runModuleSeedViaContext(mod, ctx) {
8938
- if (!mod.seed) {
8939
- return false;
8940
- }
8941
- try {
8942
- await mod.seed(ctx);
8943
- return true;
8944
- } catch (err) {
8945
- ctx.core.logger.error({ module: mod.name, err }, "Seed failed");
8946
- return false;
8947
- }
8948
- }
8949
9143
  var init_system_helpers = __esm({
8950
9144
  "src/modules/system/system.helpers.ts"() {
8951
9145
  "use strict";
@@ -8966,6 +9160,7 @@ function toPageDTO(page) {
8966
9160
  dataSource: page.dataSource,
8967
9161
  widgets: page.widgets,
8968
9162
  component: page.component,
9163
+ contentEndpoint: page.contentEndpoint,
8969
9164
  meta: page.meta,
8970
9165
  layout: page.layout
8971
9166
  };
@@ -9180,7 +9375,8 @@ function createSystemController(ctx) {
9180
9375
  const plugins = engine.getPlugins();
9181
9376
  const body = {
9182
9377
  version: manifest.version,
9183
- plugins: plugins.map((p) => p.code)
9378
+ plugins: plugins.map((p) => p.code),
9379
+ locales: ctx.locales
9184
9380
  };
9185
9381
  res.json(body);
9186
9382
  }
@@ -9228,7 +9424,7 @@ var init_system_routes = __esm({
9228
9424
 
9229
9425
  // src/modules/system/system.entity.ts
9230
9426
  import * as os from "os";
9231
- import { useIconField, useTextField as useTextField3, useSelectField as useSelectField2, useNumberField as useNumberField2, useCheckboxField, useTagsField, useNameField, useDescriptionField } from "@gzl10/nexus-sdk/fields";
9427
+ import { useIconField, useTextField as useTextField4, useSelectField as useSelectField3, useNumberField as useNumberField2, useCheckboxField, useTagsField, useNameField, useDescriptionField } from "@gzl10/nexus-sdk/fields";
9232
9428
  var moduleEntity, osEntity;
9233
9429
  var init_system_entity = __esm({
9234
9430
  "src/modules/system/system.entity.ts"() {
@@ -9246,7 +9442,7 @@ var init_system_entity = __esm({
9246
9442
  name: useNameField({
9247
9443
  size: 50
9248
9444
  }),
9249
- label: useTextField3({
9445
+ label: useTextField4({
9250
9446
  label: { en: "Label", es: "Etiqueta" },
9251
9447
  size: 100,
9252
9448
  nullable: false,
@@ -9254,7 +9450,7 @@ var init_system_entity = __esm({
9254
9450
  }),
9255
9451
  icon: useIconField({ label: { en: "Icon", es: "Icono" }, size: 50 }),
9256
9452
  type: {
9257
- ...useSelectField2({
9453
+ ...useSelectField3({
9258
9454
  label: { en: "Type", es: "Tipo" },
9259
9455
  options: [
9260
9456
  { value: "core", label: { en: "Core", es: "Core" } },
@@ -9270,7 +9466,7 @@ var init_system_entity = __esm({
9270
9466
  description: useDescriptionField({
9271
9467
  mode: "text"
9272
9468
  }),
9273
- routePrefix: useTextField3({
9469
+ routePrefix: useTextField4({
9274
9470
  label: { en: "Route", es: "Ruta" },
9275
9471
  size: 50,
9276
9472
  nullable: true
@@ -9328,14 +9524,14 @@ var init_system_entity = __esm({
9328
9524
  order: 1,
9329
9525
  refreshInterval: 5e3,
9330
9526
  fields: {
9331
- hostname: useTextField3({
9527
+ hostname: useTextField4({
9332
9528
  label: { en: "Hostname", es: "Nombre de servidor" },
9333
9529
  size: 100,
9334
9530
  nullable: false,
9335
9531
  inputProps: { order: 1 }
9336
9532
  }),
9337
9533
  platform: {
9338
- ...useSelectField2({
9534
+ ...useSelectField3({
9339
9535
  label: { en: "Platform", es: "Plataforma" },
9340
9536
  options: [
9341
9537
  { value: "darwin", label: { en: "macOS", es: "macOS" } },
@@ -9349,7 +9545,7 @@ var init_system_entity = __esm({
9349
9545
  inputProps: { order: 2 }
9350
9546
  },
9351
9547
  arch: {
9352
- ...useSelectField2({
9548
+ ...useSelectField3({
9353
9549
  label: { en: "Architecture", es: "Arquitectura" },
9354
9550
  options: [
9355
9551
  { value: "x64", label: { en: "x64", es: "x64" } },
@@ -9362,7 +9558,7 @@ var init_system_entity = __esm({
9362
9558
  inputProps: { order: 3 }
9363
9559
  },
9364
9560
  type: {
9365
- ...useTextField3({
9561
+ ...useTextField4({
9366
9562
  label: { en: "Type", es: "Tipo" },
9367
9563
  size: 30,
9368
9564
  nullable: false
@@ -9370,7 +9566,7 @@ var init_system_entity = __esm({
9370
9566
  inputProps: { order: 4 }
9371
9567
  },
9372
9568
  release: {
9373
- ...useTextField3({
9569
+ ...useTextField4({
9374
9570
  label: { en: "Release", es: "Versi\xF3n" },
9375
9571
  size: 50,
9376
9572
  nullable: false
@@ -9393,7 +9589,7 @@ var init_system_entity = __esm({
9393
9589
  inputProps: { order: 7, format: "duration" }
9394
9590
  },
9395
9591
  cpuModel: {
9396
- ...useTextField3({
9592
+ ...useTextField4({
9397
9593
  label: { en: "CPU Model", es: "Modelo de CPU" },
9398
9594
  size: 100,
9399
9595
  nullable: false
@@ -9459,7 +9655,7 @@ var init_system_entity = __esm({
9459
9655
  });
9460
9656
 
9461
9657
  // src/modules/system/migration-history.entity.ts
9462
- import { useIdField as useIdField2, useTextField as useTextField4, useNumberField as useNumberField3, useSelectField as useSelectField3, useDatetimeField as useDatetimeField2, useTextareaField as useTextareaField2 } from "@gzl10/nexus-sdk/fields";
9658
+ import { useIdField as useIdField2, useTextField as useTextField5, useNumberField as useNumberField3, useSelectField as useSelectField4, useDatetimeField as useDatetimeField2, useTextareaField as useTextareaField2 } from "@gzl10/nexus-sdk/fields";
9463
9659
  var migrationHistoryEntity;
9464
9660
  var init_migration_history_entity = __esm({
9465
9661
  "src/modules/system/migration-history.entity.ts"() {
@@ -9476,9 +9672,9 @@ var init_migration_history_entity = __esm({
9476
9672
  calendarFrom: "executed_at",
9477
9673
  fields: {
9478
9674
  id: useIdField2(),
9479
- name: useTextField4({ label: { en: "Migration Name", es: "Nombre de la Migraci\xF3n" }, required: true, size: 255, unique: true, meta: { sortable: true, searchable: true } }),
9675
+ name: useTextField5({ label: { en: "Migration Name", es: "Nombre de la Migraci\xF3n" }, required: true, size: 255, unique: true, meta: { sortable: true, searchable: true } }),
9480
9676
  batch: useNumberField3({ label: { en: "Batch", es: "Lote" }, required: true, meta: { sortable: true } }),
9481
- status: useSelectField3({
9677
+ status: useSelectField4({
9482
9678
  label: { en: "Status", es: "Estado" },
9483
9679
  options: [
9484
9680
  { value: "running", label: { en: "Running", es: "Ejecutando" } },
@@ -9510,7 +9706,7 @@ var init_migration_history_entity = __esm({
9510
9706
  });
9511
9707
 
9512
9708
  // src/modules/system/env-config.entity.ts
9513
- import { useTextField as useTextField5, useSelectField as useSelectField4, useCheckboxField as useCheckboxField2 } from "@gzl10/nexus-sdk/fields";
9709
+ import { useTextField as useTextField6, useSelectField as useSelectField5, useCheckboxField as useCheckboxField2 } from "@gzl10/nexus-sdk/fields";
9514
9710
  function maskValue(value, sensitive) {
9515
9711
  if (!sensitive) return value;
9516
9712
  try {
@@ -9535,14 +9731,14 @@ var init_env_config_entity = __esm({
9535
9731
  routePrefix: "/env-config",
9536
9732
  defaultSort: { field: "category", order: "asc" },
9537
9733
  fields: {
9538
- name: useTextField5({
9734
+ name: useTextField6({
9539
9735
  label: { en: "Variable", es: "Variable" },
9540
9736
  size: 100,
9541
9737
  nullable: false,
9542
9738
  meta: { sortable: true, searchable: true }
9543
9739
  }),
9544
9740
  category: {
9545
- ...useSelectField4({
9741
+ ...useSelectField5({
9546
9742
  label: { en: "Category", es: "Categor\xEDa" },
9547
9743
  options: [
9548
9744
  { value: "server", label: { en: "Server", es: "Servidor" } },
@@ -9557,18 +9753,18 @@ var init_env_config_entity = __esm({
9557
9753
  meta: { sortable: true }
9558
9754
  })
9559
9755
  },
9560
- source: useTextField5({
9756
+ source: useTextField6({
9561
9757
  label: { en: "Source", es: "Origen" },
9562
9758
  size: 50,
9563
9759
  nullable: false,
9564
9760
  meta: { sortable: true }
9565
9761
  }),
9566
- value: useTextField5({
9762
+ value: useTextField6({
9567
9763
  label: { en: "Value", es: "Valor" },
9568
9764
  size: 255,
9569
9765
  nullable: true
9570
9766
  }),
9571
- default: useTextField5({
9767
+ default: useTextField6({
9572
9768
  label: { en: "Default", es: "Por defecto" },
9573
9769
  size: 100,
9574
9770
  nullable: true
@@ -10067,7 +10263,6 @@ var SYSTEM_TABLES, factoryResetAction;
10067
10263
  var init_factory_reset_action = __esm({
10068
10264
  "src/modules/system/actions/factory-reset.action.ts"() {
10069
10265
  "use strict";
10070
- init_system_helpers();
10071
10266
  SYSTEM_TABLES = /* @__PURE__ */ new Set([
10072
10267
  "_nexus_migrations",
10073
10268
  "_nexus_migration_lock",
@@ -10110,8 +10305,8 @@ var init_factory_reset_action = __esm({
10110
10305
  source: "core:system",
10111
10306
  action: "factory_reset",
10112
10307
  actorId: authReq.user?.id,
10113
- ip: req.ip,
10114
- userAgent: req.headers["user-agent"]
10308
+ ip: req?.ip,
10309
+ userAgent: req?.headers["user-agent"]
10115
10310
  });
10116
10311
  await new Promise((resolve2) => setImmediate(resolve2));
10117
10312
  const allTables = await getAllTables(knex3);
@@ -10149,11 +10344,11 @@ var init_factory_reset_action = __esm({
10149
10344
  } catch {
10150
10345
  }
10151
10346
  ctx.core.logger.info({ tables: dataTables.length }, "All data tables cleared");
10152
- const modules = getOrderedModulesViaContext(ctx);
10347
+ const modules = ctx.engine.getModules();
10153
10348
  let modulesSeeded = 0;
10154
10349
  for (const mod of modules) {
10155
10350
  try {
10156
- const seeded = await runModuleSeedViaContext(mod, ctx);
10351
+ const seeded = await ctx.db.seedModule(mod);
10157
10352
  if (seeded) modulesSeeded++;
10158
10353
  } catch (err) {
10159
10354
  ctx.core.logger.error({ module: mod.name, err }, "Seed failed during factory reset");
@@ -10207,8 +10402,8 @@ var init_restart_server_action = __esm({
10207
10402
  source: "core:system",
10208
10403
  action: "server_restart",
10209
10404
  actorId: authReq.user?.id,
10210
- ip: req.ip,
10211
- userAgent: req.headers["user-agent"]
10405
+ ip: req?.ip,
10406
+ userAgent: req?.headers["user-agent"]
10212
10407
  });
10213
10408
  setTimeout(() => process.exit(0), 500);
10214
10409
  return { success: true, message: "Server is restarting..." };
@@ -10275,7 +10470,7 @@ var init_system = __esm({
10275
10470
  });
10276
10471
 
10277
10472
  // src/modules/ui-settings/ui-branding.entity.ts
10278
- import { useTextField as useTextField6, useImageField } from "@gzl10/nexus-sdk/fields";
10473
+ import { useTextField as useTextField7, useImageField } from "@gzl10/nexus-sdk/fields";
10279
10474
  var uiBrandingEntity;
10280
10475
  var init_ui_branding_entity = __esm({
10281
10476
  "src/modules/ui-settings/ui-branding.entity.ts"() {
@@ -10295,7 +10490,7 @@ var init_ui_branding_entity = __esm({
10295
10490
  favicon: null
10296
10491
  },
10297
10492
  fields: {
10298
- appName: useTextField6({
10493
+ appName: useTextField7({
10299
10494
  label: { en: "App Name", es: "Nombre de la App" },
10300
10495
  hint: { en: "Displayed in the header, browser tab and emails", es: "Se muestra en el header, pesta\xF1a del navegador y emails" },
10301
10496
  size: 100,
@@ -10336,7 +10531,7 @@ var init_ui_branding_entity = __esm({
10336
10531
  });
10337
10532
 
10338
10533
  // src/modules/ui-settings/ui-theme.entity.ts
10339
- import { useSelectField as useSelectField5, useColorField } from "@gzl10/nexus-sdk/fields";
10534
+ import { useSelectField as useSelectField6, useColorField } from "@gzl10/nexus-sdk/fields";
10340
10535
  var uiThemeEntity;
10341
10536
  var init_ui_theme_entity = __esm({
10342
10537
  "src/modules/ui-settings/ui-theme.entity.ts"() {
@@ -10361,7 +10556,7 @@ var init_ui_theme_entity = __esm({
10361
10556
  },
10362
10557
  fields: {
10363
10558
  // === Typography ===
10364
- font: useSelectField5({
10559
+ font: useSelectField6({
10365
10560
  label: { en: "Font", es: "Fuente" },
10366
10561
  hint: { en: "Primary font for headings and UI elements", es: "Fuente principal para t\xEDtulos y elementos de interfaz" },
10367
10562
  options: [
@@ -10374,7 +10569,7 @@ var init_ui_theme_entity = __esm({
10374
10569
  ]
10375
10570
  }),
10376
10571
  // === Theme & Colors ===
10377
- theme: useSelectField5({
10572
+ theme: useSelectField6({
10378
10573
  label: { en: "Theme", es: "Tema" },
10379
10574
  hint: { en: "System follows your device preferences", es: "Sistema sigue las preferencias de tu dispositivo" },
10380
10575
  options: [
@@ -10383,7 +10578,7 @@ var init_ui_theme_entity = __esm({
10383
10578
  { value: "system", label: { en: "System", es: "Sistema" } }
10384
10579
  ]
10385
10580
  }),
10386
- dopamineTheme: useSelectField5({
10581
+ dopamineTheme: useSelectField6({
10387
10582
  label: { en: "Dopamine Theme", es: "Tema Dopamina" },
10388
10583
  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)" },
10389
10584
  options: [
@@ -10401,7 +10596,7 @@ var init_ui_theme_entity = __esm({
10401
10596
  ]
10402
10597
  }),
10403
10598
  // === Login Layout ===
10404
- loginLayout: useSelectField5({
10599
+ loginLayout: useSelectField6({
10405
10600
  label: { en: "Login Layout", es: "Dise\xF1o de Login" },
10406
10601
  hint: { en: "Visual layout for authentication pages", es: "Dise\xF1o visual para p\xE1ginas de autenticaci\xF3n" },
10407
10602
  options: [
@@ -10432,7 +10627,7 @@ var init_ui_theme_entity = __esm({
10432
10627
  });
10433
10628
 
10434
10629
  // src/modules/ui-settings/ui-effects.entity.ts
10435
- import { useSelectField as useSelectField6, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
10630
+ import { useSelectField as useSelectField7, useSwitchField as useSwitchField2 } from "@gzl10/nexus-sdk/fields";
10436
10631
  var uiEffectsEntity;
10437
10632
  var init_ui_effects_entity = __esm({
10438
10633
  "src/modules/ui-settings/ui-effects.entity.ts"() {
@@ -10452,7 +10647,7 @@ var init_ui_effects_entity = __esm({
10452
10647
  enableOrganicShapes: false
10453
10648
  },
10454
10649
  fields: {
10455
- glassIntensity: useSelectField6({
10650
+ glassIntensity: useSelectField7({
10456
10651
  label: { en: "Glass Intensity", es: "Intensidad Glass" },
10457
10652
  hint: { en: "Glassmorphism blur effect on cards and modals", es: "Efecto de desenfoque glassmorphism en cards y modales" },
10458
10653
  options: [
@@ -10462,7 +10657,7 @@ var init_ui_effects_entity = __esm({
10462
10657
  { value: "high", label: { en: "High", es: "Alta" } }
10463
10658
  ]
10464
10659
  }),
10465
- borderStyle: useSelectField6({
10660
+ borderStyle: useSelectField7({
10466
10661
  label: { en: "Border Style", es: "Estilo de Bordes" },
10467
10662
  hint: { en: "Corner radius for buttons, cards and inputs", es: "Radio de esquinas para botones, cards e inputs" },
10468
10663
  options: [
@@ -10496,7 +10691,7 @@ var init_ui_effects_entity = __esm({
10496
10691
  });
10497
10692
 
10498
10693
  // src/modules/ui-settings/ui-accessibility.entity.ts
10499
- import { useSelectField as useSelectField7, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
10694
+ import { useSelectField as useSelectField8, useSwitchField as useSwitchField3 } from "@gzl10/nexus-sdk/fields";
10500
10695
  var uiAccessibilityEntity;
10501
10696
  var init_ui_accessibility_entity = __esm({
10502
10697
  "src/modules/ui-settings/ui-accessibility.entity.ts"() {
@@ -10515,7 +10710,7 @@ var init_ui_accessibility_entity = __esm({
10515
10710
  highContrast: false
10516
10711
  },
10517
10712
  fields: {
10518
- typographyScale: useSelectField7({
10713
+ typographyScale: useSelectField8({
10519
10714
  label: { en: "Typography Scale", es: "Escala Tipogr\xE1fica" },
10520
10715
  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)" },
10521
10716
  options: [
@@ -11506,7 +11701,7 @@ var init_storage_service = __esm({
11506
11701
  });
11507
11702
 
11508
11703
  // src/modules/storage/storage.entity.ts
11509
- 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";
11704
+ 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";
11510
11705
  var DEFAULT_MAX_SIZE2, storageConfigEntity, storageFilesEntity;
11511
11706
  var init_storage_entity = __esm({
11512
11707
  "src/modules/storage/storage.entity.ts"() {
@@ -11532,7 +11727,7 @@ var init_storage_entity = __esm({
11532
11727
  },
11533
11728
  fields: {
11534
11729
  id: useIdField3(),
11535
- scope: useTextField7({
11730
+ scope: useTextField8({
11536
11731
  label: { en: "Scope", es: "\xC1mbito" },
11537
11732
  disabled: true,
11538
11733
  size: 100,
@@ -11541,7 +11736,7 @@ var init_storage_entity = __esm({
11541
11736
  hint: { en: "Unique identifier (e.g. default_filesystem, default_s3)", es: "Identificador \xFAnico (ej: default_filesystem, default_s3)" }
11542
11737
  }),
11543
11738
  driver: {
11544
- ...useSelectField8({
11739
+ ...useSelectField9({
11545
11740
  label: { en: "Driver", es: "Controlador" },
11546
11741
  required: true,
11547
11742
  hint: { en: "Storage backend", es: "Backend de almacenamiento" },
@@ -11564,7 +11759,7 @@ var init_storage_entity = __esm({
11564
11759
  nullable: false,
11565
11760
  displayProps: { format: "bytes" }
11566
11761
  }),
11567
- allowed_mime_types: useTextField7({
11762
+ allowed_mime_types: useTextField8({
11568
11763
  label: { en: "Allowed MIME Types", es: "Tipos MIME permitidos" },
11569
11764
  hint: { en: "Allowed types separated by comma (e.g. image/*,application/pdf)", es: "Tipos permitidos separados por coma (ej: image/*,application/pdf)" },
11570
11765
  size: 1e3,
@@ -11729,21 +11924,21 @@ var init_storage_entity = __esm({
11729
11924
  ],
11730
11925
  fields: {
11731
11926
  id: useIdField3(),
11732
- filename: useTextField7({
11927
+ filename: useTextField8({
11733
11928
  label: { en: "Original Filename", es: "Nombre original" },
11734
11929
  required: true,
11735
11930
  size: 255,
11736
11931
  nullable: false,
11737
11932
  meta: { sortable: true, searchable: true }
11738
11933
  }),
11739
- disk_filename: useTextField7({
11934
+ disk_filename: useTextField8({
11740
11935
  label: { en: "Disk Filename", es: "Nombre en disco" },
11741
11936
  required: true,
11742
11937
  hidden: true,
11743
11938
  size: 255,
11744
11939
  nullable: false
11745
11940
  }),
11746
- mimetype: useTextField7({
11941
+ mimetype: useTextField8({
11747
11942
  label: { en: "MIME Type", es: "Tipo MIME" },
11748
11943
  required: true,
11749
11944
  size: 100,
@@ -11759,14 +11954,14 @@ var init_storage_entity = __esm({
11759
11954
  meta: { sortable: true },
11760
11955
  displayProps: { format: "bytes" }
11761
11956
  }),
11762
- folder: useTextField7({
11957
+ folder: useTextField8({
11763
11958
  label: { en: "Folder", es: "Carpeta" },
11764
11959
  size: 100,
11765
11960
  nullable: true,
11766
11961
  index: true,
11767
11962
  meta: { searchable: true }
11768
11963
  }),
11769
- scope: useTextField7({
11964
+ scope: useTextField8({
11770
11965
  label: { en: "Storage Scope", es: "\xC1mbito de almacenamiento" },
11771
11966
  hidden: true,
11772
11967
  size: 50,
@@ -11777,7 +11972,7 @@ var init_storage_entity = __esm({
11777
11972
  es: "\xC1mbito de configuraci\xF3n de almacenamiento usado para este archivo"
11778
11973
  }
11779
11974
  }),
11780
- path: useTextField7({
11975
+ path: useTextField8({
11781
11976
  label: { en: "Full Path", es: "Ruta completa" },
11782
11977
  required: true,
11783
11978
  hidden: true,
@@ -11790,13 +11985,13 @@ var init_storage_entity = __esm({
11790
11985
  nullable: true,
11791
11986
  meta: { exportable: true, showInDisplay: false }
11792
11987
  }),
11793
- thumbnail_path: useTextField7({
11988
+ thumbnail_path: useTextField8({
11794
11989
  label: { en: "Thumbnail Path", es: "Ruta de miniatura" },
11795
11990
  size: 500,
11796
11991
  nullable: true,
11797
11992
  meta: { showInDisplay: false }
11798
11993
  }),
11799
- hash: useTextField7({
11994
+ hash: useTextField8({
11800
11995
  label: { en: "SHA256 Hash", es: "Hash SHA256" },
11801
11996
  hidden: true,
11802
11997
  size: 64,
@@ -11830,7 +12025,7 @@ function createUploadMiddleware(ctx, options) {
11830
12025
  const rateLimit2 = ctx.core.middleware.rateLimit({
11831
12026
  windowMs: 60 * 1e3,
11832
12027
  max: 20,
11833
- message: "Demasiados uploads, intenta en 1 minuto"
12028
+ message: "Too many uploads, try again in 1 minute"
11834
12029
  });
11835
12030
  const upload = multer({
11836
12031
  storage: multer.memoryStorage(),
@@ -12077,7 +12272,7 @@ function createStorageRoutes(ctx) {
12077
12272
  }
12078
12273
  res.status(201).json(results);
12079
12274
  };
12080
- const uploadRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1e3, max: 20, message: "Demasiados uploads, intenta en 1 minuto" });
12275
+ const uploadRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1e3, max: 20, message: "Too many uploads, try again in 1 minute" });
12081
12276
  if (auth) {
12082
12277
  router.post("/upload/multiple", uploadRateLimit, auth, upload.array("files", 10), uploadMultiple);
12083
12278
  } else {
@@ -12293,7 +12488,7 @@ var init_storage = __esm({
12293
12488
  });
12294
12489
 
12295
12490
  // src/modules/users/users.entity.ts
12296
- 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";
12491
+ 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";
12297
12492
  import { z as z3 } from "zod";
12298
12493
  var userEntity, roleEntity, userRoleEntity;
12299
12494
  var init_users_entity = __esm({
@@ -12356,7 +12551,7 @@ var init_users_entity = __esm({
12356
12551
  nullable: true,
12357
12552
  meta: { exportable: true, showInForm: false, showInDisplay: false }
12358
12553
  }),
12359
- consent_version: useTextField8({
12554
+ consent_version: useTextField9({
12360
12555
  label: { en: "Consent Version", es: "Versi\xF3n de consentimiento" },
12361
12556
  hidden: true,
12362
12557
  size: 20,
@@ -12367,7 +12562,7 @@ var init_users_entity = __esm({
12367
12562
  label: { en: "Marketing Opt-in", es: "Aceptar marketing" },
12368
12563
  meta: { exportable: true, showInForm: false, showInDisplay: false }
12369
12564
  }),
12370
- locale: useSelectField9({
12565
+ locale: useSelectField10({
12371
12566
  label: { en: "Language", es: "Idioma" },
12372
12567
  options: [
12373
12568
  { value: "es", label: { en: "Spanish", es: "Espa\xF1ol" } },
@@ -12375,15 +12570,15 @@ var init_users_entity = __esm({
12375
12570
  ],
12376
12571
  nullable: true,
12377
12572
  meta: { sortable: true },
12378
- defaultValue: "es"
12573
+ defaultValue: "en"
12379
12574
  }),
12380
- timezone: useSelectField9({
12575
+ timezone: useSelectField10({
12381
12576
  label: { en: "Timezone", es: "Zona horaria" },
12382
12577
  master: "timezones",
12383
12578
  meta: { sortable: true },
12384
12579
  defaultValue: "timezones:Europe/Madrid"
12385
12580
  }),
12386
- type: useSelectField9({
12581
+ type: useSelectField10({
12387
12582
  label: { en: "Type", es: "Tipo" },
12388
12583
  defaultValue: "human",
12389
12584
  options: [
@@ -12453,7 +12648,7 @@ var init_users_entity = __esm({
12453
12648
  middleware: (ctx) => ctx.core.middleware.rateLimit({
12454
12649
  windowMs: 15 * 60 * 1e3,
12455
12650
  max: 5,
12456
- message: "Demasiados intentos, intenta en 15 minutos"
12651
+ message: "Too many attempts, try again in 15 minutes"
12457
12652
  }),
12458
12653
  handler: async (ctx, input) => {
12459
12654
  const {
@@ -12552,7 +12747,7 @@ var init_users_entity = __esm({
12552
12747
  expose: false,
12553
12748
  fields: {
12554
12749
  id: useIdField4(),
12555
- user_id: useSelectField9({
12750
+ user_id: useSelectField10({
12556
12751
  label: { en: "User", es: "Usuario" },
12557
12752
  required: true,
12558
12753
  table: "users",
@@ -12563,7 +12758,7 @@ var init_users_entity = __esm({
12563
12758
  labelField: "name",
12564
12759
  meta: { searchable: true }
12565
12760
  }),
12566
- role_id: useSelectField9({
12761
+ role_id: useSelectField10({
12567
12762
  label: { en: "Role", es: "Rol" },
12568
12763
  required: true,
12569
12764
  table: "roles",
@@ -13507,7 +13702,7 @@ var init_users = __esm({
13507
13702
  });
13508
13703
 
13509
13704
  // src/modules/auth/auth.entity.ts
13510
- 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";
13705
+ 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";
13511
13706
  var refreshTokenEntity, authIdentitiesEntity;
13512
13707
  var init_auth_entity = __esm({
13513
13708
  "src/modules/auth/auth.entity.ts"() {
@@ -13522,7 +13717,7 @@ var init_auth_entity = __esm({
13522
13717
  expose: false,
13523
13718
  fields: {
13524
13719
  id: useIdField5(),
13525
- token: useTextField9({
13720
+ token: useTextField10({
13526
13721
  label: { en: "Token", es: "Token" },
13527
13722
  hidden: true,
13528
13723
  size: 255,
@@ -13549,14 +13744,14 @@ var init_auth_entity = __esm({
13549
13744
  nullable: true,
13550
13745
  meta: { sortable: true }
13551
13746
  }),
13552
- device_id: useTextField9({
13747
+ device_id: useTextField10({
13553
13748
  label: { en: "Device ID", es: "ID de dispositivo" },
13554
13749
  size: 64,
13555
13750
  index: true,
13556
13751
  nullable: true,
13557
13752
  meta: { searchable: true }
13558
13753
  }),
13559
- device_name: useTextField9({
13754
+ device_name: useTextField10({
13560
13755
  label: { en: "Device", es: "Dispositivo" },
13561
13756
  size: 100,
13562
13757
  nullable: true,
@@ -13588,7 +13783,7 @@ var init_auth_entity = __esm({
13588
13783
  order: 5,
13589
13784
  fields: {
13590
13785
  id: useIdField5(),
13591
- user_id: useSelectField10({
13786
+ user_id: useSelectField11({
13592
13787
  label: { en: "User", es: "Usuario" },
13593
13788
  table: "users",
13594
13789
  column: "id",
@@ -13600,7 +13795,7 @@ var init_auth_entity = __esm({
13600
13795
  labelField: "name",
13601
13796
  meta: { searchable: true }
13602
13797
  }),
13603
- provider: useTextField9({
13798
+ provider: useTextField10({
13604
13799
  label: { en: "Provider", es: "Proveedor" },
13605
13800
  required: true,
13606
13801
  size: 50,
@@ -13609,7 +13804,7 @@ var init_auth_entity = __esm({
13609
13804
  hint: { en: "e.g. pocketid, google, microsoft", es: "ej. pocketid, google, microsoft" },
13610
13805
  meta: { sortable: true, searchable: true }
13611
13806
  }),
13612
- provider_user_id: useTextField9({
13807
+ provider_user_id: useTextField10({
13613
13808
  label: { en: "Provider User ID", es: "ID de usuario del proveedor" },
13614
13809
  required: true,
13615
13810
  size: 255,
@@ -13904,7 +14099,7 @@ var init_auth_middleware = __esm({
13904
14099
  });
13905
14100
 
13906
14101
  // src/modules/auth/auth.pat.entity.ts
13907
- import { useIdField as useIdField6, useTextField as useTextField10, useSelectField as useSelectField11, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
14102
+ import { useIdField as useIdField6, useTextField as useTextField11, useSelectField as useSelectField12, useDatetimeField as useDatetimeField5, useExpiresAtField as useExpiresAtField2 } from "@gzl10/nexus-sdk/fields";
13908
14103
  var personalTokenEntity;
13909
14104
  var init_auth_pat_entity = __esm({
13910
14105
  "src/modules/auth/auth.pat.entity.ts"() {
@@ -13921,7 +14116,7 @@ var init_auth_pat_entity = __esm({
13921
14116
  routePrefix: "/personal-tokens",
13922
14117
  fields: {
13923
14118
  id: useIdField6(),
13924
- user_id: useSelectField11({
14119
+ user_id: useSelectField12({
13925
14120
  label: { en: "User", es: "Usuario" },
13926
14121
  table: "users",
13927
14122
  column: "id",
@@ -13933,7 +14128,7 @@ var init_auth_pat_entity = __esm({
13933
14128
  labelField: "name",
13934
14129
  meta: { searchable: true }
13935
14130
  }),
13936
- name: useTextField10({
14131
+ name: useTextField11({
13937
14132
  label: { en: "Name", es: "Nombre" },
13938
14133
  required: true,
13939
14134
  size: 100,
@@ -13941,14 +14136,14 @@ var init_auth_pat_entity = __esm({
13941
14136
  hint: { en: "Descriptive name for this token", es: "Nombre descriptivo para este token" },
13942
14137
  meta: { searchable: true }
13943
14138
  }),
13944
- token_prefix: useTextField10({
14139
+ token_prefix: useTextField11({
13945
14140
  label: { en: "Token", es: "Token" },
13946
14141
  size: 20,
13947
14142
  disabled: true,
13948
14143
  nullable: false,
13949
14144
  hint: { en: "Partial token for identification", es: "Token parcial para identificaci\xF3n" }
13950
14145
  }),
13951
- token_hash: useTextField10({
14146
+ token_hash: useTextField11({
13952
14147
  label: { en: "Token Hash", es: "Hash del token" },
13953
14148
  size: 64,
13954
14149
  hidden: true,
@@ -13956,7 +14151,7 @@ var init_auth_pat_entity = __esm({
13956
14151
  unique: true,
13957
14152
  meta: { exportable: false }
13958
14153
  }),
13959
- scope: useSelectField11({
14154
+ scope: useSelectField12({
13960
14155
  label: { en: "Permission", es: "Permiso" },
13961
14156
  required: true,
13962
14157
  options: [
@@ -15712,7 +15907,7 @@ var init_mail_service = __esm({
15712
15907
  });
15713
15908
 
15714
15909
  // src/modules/mail/mail.entity.ts
15715
- 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";
15910
+ 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";
15716
15911
  import nodemailer2 from "nodemailer";
15717
15912
  async function getMailConfigFromDB(ctx) {
15718
15913
  const configService = ctx.services["config"];
@@ -15775,7 +15970,7 @@ var init_mail_entity = __esm({
15775
15970
  },
15776
15971
  fields: {
15777
15972
  id: useIdField7(),
15778
- host: useTextField11({
15973
+ host: useTextField12({
15779
15974
  label: { en: "SMTP Host", es: "Host SMTP" },
15780
15975
  size: 255,
15781
15976
  nullable: false,
@@ -15795,7 +15990,7 @@ var init_mail_entity = __esm({
15795
15990
  nullable: false,
15796
15991
  hint: { en: "Default: SMTP_FROM env var", es: "Por defecto: variable SMTP_FROM" }
15797
15992
  }),
15798
- auth_user: useTextField11({
15993
+ auth_user: useTextField12({
15799
15994
  label: { en: "Auth User", es: "Usuario de autenticaci\xF3n" },
15800
15995
  size: 255,
15801
15996
  nullable: true,
@@ -15827,12 +16022,12 @@ var init_mail_entity = __esm({
15827
16022
  required: true,
15828
16023
  validation: { format: "email" }
15829
16024
  }),
15830
- subject: useTextField11({
16025
+ subject: useTextField12({
15831
16026
  label: { en: "Subject", es: "Asunto" },
15832
16027
  required: true,
15833
16028
  validation: { min: 1, max: 255 }
15834
16029
  }),
15835
- title: useTextField11({
16030
+ title: useTextField12({
15836
16031
  label: { en: "Title", es: "T\xEDtulo" },
15837
16032
  hint: { en: "Large title in email header", es: "T\xEDtulo grande en la cabecera del correo" }
15838
16033
  }),
@@ -15976,20 +16171,20 @@ var init_mail_entity = __esm({
15976
16171
  nullable: false,
15977
16172
  meta: { sortable: true }
15978
16173
  }),
15979
- to: useTextField11({
16174
+ to: useTextField12({
15980
16175
  label: { en: "Recipient(s)", es: "Destinatario(s)" },
15981
16176
  size: 1e3,
15982
16177
  nullable: false,
15983
16178
  meta: { searchable: true }
15984
16179
  }),
15985
- subject: useTextField11({
16180
+ subject: useTextField12({
15986
16181
  label: { en: "Subject", es: "Asunto" },
15987
16182
  size: 255,
15988
16183
  nullable: false,
15989
16184
  meta: { searchable: true, sortable: true }
15990
16185
  }),
15991
16186
  status: {
15992
- ...useSelectField12({
16187
+ ...useSelectField13({
15993
16188
  label: { en: "Status", es: "Estado" },
15994
16189
  options: [
15995
16190
  { value: "pending", label: { en: "Pending", es: "Pendiente" } },
@@ -16003,7 +16198,7 @@ var init_mail_entity = __esm({
16003
16198
  }),
16004
16199
  validation: { enum: ["pending", "sent", "failed", "bounced"] }
16005
16200
  },
16006
- message_id: useTextField11({
16201
+ message_id: useTextField12({
16007
16202
  label: { en: "Message ID", es: "ID de mensaje" },
16008
16203
  hidden: true,
16009
16204
  size: 255,
@@ -16013,7 +16208,7 @@ var init_mail_entity = __esm({
16013
16208
  label: { en: "Error", es: "Error" },
16014
16209
  nullable: true
16015
16210
  }),
16016
- sent_by: useSelectField12({
16211
+ sent_by: useSelectField13({
16017
16212
  label: { en: "Sent by", es: "Enviado por" },
16018
16213
  table: "users",
16019
16214
  column: "id",
@@ -16619,7 +16814,7 @@ var init_toggle_plugin_action = __esm({
16619
16814
  });
16620
16815
 
16621
16816
  // src/modules/plugins/plugins.entity.ts
16622
- import { useTextField as useTextField12, useSelectField as useSelectField13, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
16817
+ import { useTextField as useTextField13, useSelectField as useSelectField14, useCheckboxField as useCheckboxField5 } from "@gzl10/nexus-sdk/fields";
16623
16818
  import { OFFICIAL_PLUGINS } from "@gzl10/nexus-sdk";
16624
16819
  var allowPluginManagement, pluginsEntity;
16625
16820
  var init_plugins_entity = __esm({
@@ -16635,33 +16830,33 @@ var init_plugins_entity = __esm({
16635
16830
  label: "Plugins",
16636
16831
  icon: "mdi:puzzle",
16637
16832
  labelField: "code",
16638
- routePrefix: "/plugins",
16833
+ routePrefix: "/",
16639
16834
  defaultSort: { field: "name", order: "asc" },
16640
16835
  fields: {
16641
- name: useTextField12({
16836
+ name: useTextField13({
16642
16837
  label: { en: "Name", es: "Nombre" },
16643
16838
  size: 50,
16644
16839
  nullable: false,
16645
16840
  meta: { sortable: true, searchable: true }
16646
16841
  }),
16647
- code: useTextField12({
16842
+ code: useTextField13({
16648
16843
  label: { en: "Code", es: "C\xF3digo" },
16649
16844
  size: 10,
16650
16845
  nullable: false,
16651
16846
  meta: { sortable: true }
16652
16847
  }),
16653
- label: useTextField12({
16848
+ label: useTextField13({
16654
16849
  label: { en: "Label", es: "Etiqueta" },
16655
16850
  size: 100,
16656
16851
  nullable: false,
16657
16852
  meta: { sortable: true }
16658
16853
  }),
16659
- version: useTextField12({
16854
+ version: useTextField13({
16660
16855
  label: { en: "Version", es: "Versi\xF3n" },
16661
16856
  size: 20,
16662
16857
  nullable: false
16663
16858
  }),
16664
- category: useSelectField13({
16859
+ category: useSelectField14({
16665
16860
  label: { en: "Category", es: "Categor\xEDa" },
16666
16861
  options: [
16667
16862
  { value: "content", label: { en: "Content", es: "Contenido" } },
@@ -16802,8 +16997,8 @@ var init_plugins = __esm({
16802
16997
  // src/modules/audit/audit.entity.ts
16803
16998
  import {
16804
16999
  useIdField as useIdField8,
16805
- useTextField as useTextField13,
16806
- useSelectField as useSelectField14,
17000
+ useTextField as useTextField14,
17001
+ useSelectField as useSelectField15,
16807
17002
  useTextareaField as useTextareaField4,
16808
17003
  useJsonField as useJsonField3,
16809
17004
  useDatetimeField as useDatetimeField7,
@@ -16826,7 +17021,7 @@ var init_audit_entity = __esm({
16826
17021
  fields: {
16827
17022
  id: useIdField8(),
16828
17023
  source: {
16829
- ...useTextField13({
17024
+ ...useTextField14({
16830
17025
  label: { en: "Source", es: "Origen" },
16831
17026
  size: 100,
16832
17027
  nullable: false,
@@ -16836,7 +17031,7 @@ var init_audit_entity = __esm({
16836
17031
  validation: { min: 1, max: 100 }
16837
17032
  },
16838
17033
  action: {
16839
- ...useTextField13({
17034
+ ...useTextField14({
16840
17035
  label: { en: "Action", es: "Acci\xF3n" },
16841
17036
  size: 100,
16842
17037
  nullable: false,
@@ -16845,7 +17040,7 @@ var init_audit_entity = __esm({
16845
17040
  }),
16846
17041
  validation: { min: 1, max: 100 }
16847
17042
  },
16848
- actor_id: useSelectField14({
17043
+ actor_id: useSelectField15({
16849
17044
  label: { en: "Actor", es: "Actor" },
16850
17045
  table: "users",
16851
17046
  column: "id",
@@ -16862,20 +17057,20 @@ var init_audit_entity = __esm({
16862
17057
  nullable: true,
16863
17058
  meta: { searchable: true }
16864
17059
  }),
16865
- resource_type: useTextField13({
17060
+ resource_type: useTextField14({
16866
17061
  label: { en: "Resource Type", es: "Tipo de recurso" },
16867
17062
  size: 100,
16868
17063
  nullable: true,
16869
17064
  index: true,
16870
17065
  meta: { searchable: true }
16871
17066
  }),
16872
- resource_id: useTextField13({
17067
+ resource_id: useTextField14({
16873
17068
  label: { en: "Resource ID", es: "ID del recurso" },
16874
17069
  size: 100,
16875
17070
  nullable: true,
16876
17071
  meta: { searchable: true }
16877
17072
  }),
16878
- ip_address: useTextField13({
17073
+ ip_address: useTextField14({
16879
17074
  label: { en: "IP Address", es: "Direcci\xF3n IP" },
16880
17075
  size: 45,
16881
17076
  nullable: true,
@@ -17048,12 +17243,12 @@ var init_loader = __esm({
17048
17243
  "src/engine/loader.ts"() {
17049
17244
  "use strict";
17050
17245
  init_registry();
17051
- init_extractors();
17246
+ init_definition_extractors();
17052
17247
  init_modules();
17053
17248
  }
17054
17249
  });
17055
17250
 
17056
- // src/engine/subjectExtractor.ts
17251
+ // src/engine/subject-extractor.ts
17057
17252
  function getModuleSubjects(mod) {
17058
17253
  const subjects = /* @__PURE__ */ new Set();
17059
17254
  for (const def of mod.definitions ?? []) {
@@ -17062,10 +17257,10 @@ function getModuleSubjects(mod) {
17062
17257
  }
17063
17258
  return [...subjects];
17064
17259
  }
17065
- var init_subjectExtractor = __esm({
17066
- "src/engine/subjectExtractor.ts"() {
17260
+ var init_subject_extractor = __esm({
17261
+ "src/engine/subject-extractor.ts"() {
17067
17262
  "use strict";
17068
- init_extractors();
17263
+ init_definition_extractors();
17069
17264
  }
17070
17265
  });
17071
17266
 
@@ -17143,7 +17338,7 @@ function initSocketIO(httpServer, options) {
17143
17338
  maxHttpBufferSize: 1e6
17144
17339
  // 1MB - match Express json body limit
17145
17340
  });
17146
- logger.info({ maxHttpBufferSize: 1e6, cors: corsOrigin }, "Socket.IO initialized");
17341
+ logger.info({ cors: corsOrigin }, "Socket.IO initialized");
17147
17342
  io.use((socket, next) => {
17148
17343
  const token = socket.handshake.auth?.["token"] || socket.handshake.query?.["token"];
17149
17344
  if (token && typeof token === "string" && jwtSecret) {
@@ -17166,7 +17361,6 @@ function initSocketIO(httpServer, options) {
17166
17361
  logger.warn({ code: err.code, message: err.message }, "Socket.IO connection error");
17167
17362
  });
17168
17363
  io.on("connection", handleConnection);
17169
- logger.info("Socket.IO initialized");
17170
17364
  nexusEvents.emitEvent("socket.initialized");
17171
17365
  return io;
17172
17366
  }
@@ -17629,6 +17823,7 @@ var init_error_codes = __esm({
17629
17823
  DB_CONSTRAINT_UNIQUE: "DB_CONSTRAINT_UNIQUE",
17630
17824
  DB_CONSTRAINT_FK: "DB_CONSTRAINT_FK",
17631
17825
  DB_CONNECTION_ERROR: "DB_CONNECTION_ERROR",
17826
+ DATABASE_NOT_READY: "DATABASE_NOT_READY",
17632
17827
  // System
17633
17828
  SYSTEM_INTERNAL_ERROR: "SYSTEM_INTERNAL_ERROR"
17634
17829
  };
@@ -17747,6 +17942,12 @@ var init_app_error = __esm({
17747
17942
 
17748
17943
  // src/core/abilities/ability.factory.ts
17749
17944
  import { AbilityBuilder, createMongoAbility } from "@casl/ability";
17945
+ function setSeedPermissions(perms) {
17946
+ seedPermissions = perms;
17947
+ }
17948
+ function clearSeedPermissions() {
17949
+ seedPermissions = null;
17950
+ }
17750
17951
  function setCustomCaslRules(fn) {
17751
17952
  customCaslRules = fn;
17752
17953
  }
@@ -17804,16 +18005,28 @@ async function defineAbilityFor(user, roleNames) {
17804
18005
  if (customCaslRules) {
17805
18006
  await customCaslRules(user, { can, cannot });
17806
18007
  }
18008
+ if (seedPermissions && !isSuperuser) {
18009
+ for (const roleName of roleNames) {
18010
+ const rolePerms = seedPermissions.get(roleName);
18011
+ if (!rolePerms) continue;
18012
+ for (const [subject2, actions] of rolePerms) {
18013
+ for (const action of actions) {
18014
+ can(action, subject2);
18015
+ }
18016
+ }
18017
+ }
18018
+ }
17807
18019
  return build();
17808
18020
  }
17809
18021
  function packRules(ability) {
17810
18022
  return ability.rules;
17811
18023
  }
17812
- var customCaslRules, entityDefinitionsRegistry, SUPERUSER_ROLES;
18024
+ var customCaslRules, seedPermissions, entityDefinitionsRegistry, SUPERUSER_ROLES;
17813
18025
  var init_ability_factory = __esm({
17814
18026
  "src/core/abilities/ability.factory.ts"() {
17815
18027
  "use strict";
17816
18028
  init_logger();
18029
+ seedPermissions = null;
17817
18030
  entityDefinitionsRegistry = null;
17818
18031
  SUPERUSER_ROLES = ["ADMIN", "OWNER"];
17819
18032
  }
@@ -17918,7 +18131,7 @@ var init_env = __esm({
17918
18131
  envSchema = z8.object({
17919
18132
  NODE_ENV: z8.enum(["development", "production", "test"]).default("development"),
17920
18133
  PORT: z8.coerce.number().default(3e3),
17921
- CORS_ORIGIN: z8.string().default("http://localhost:3001"),
18134
+ CORS_ORIGIN: z8.string().default("*"),
17922
18135
  BACKEND_URL: z8.string().optional(),
17923
18136
  DATABASE_URL: z8.string().default("file:./dev.db"),
17924
18137
  REDIS_URL: z8.string().url().optional(),
@@ -18366,7 +18579,7 @@ var init_sequence = __esm({
18366
18579
  }
18367
18580
  });
18368
18581
 
18369
- // src/core/utils/error-handler.ts
18582
+ // src/core/utils/safe-json.ts
18370
18583
  function safeJsonParse(logger2, jsonString, fallback, context) {
18371
18584
  try {
18372
18585
  return JSON.parse(jsonString);
@@ -18375,8 +18588,8 @@ function safeJsonParse(logger2, jsonString, fallback, context) {
18375
18588
  return fallback;
18376
18589
  }
18377
18590
  }
18378
- var init_error_handler = __esm({
18379
- "src/core/utils/error-handler.ts"() {
18591
+ var init_safe_json = __esm({
18592
+ "src/core/utils/safe-json.ts"() {
18380
18593
  "use strict";
18381
18594
  }
18382
18595
  });
@@ -18385,14 +18598,15 @@ var init_error_handler = __esm({
18385
18598
  import express from "express";
18386
18599
  import { resolve, join as join9 } from "path";
18387
18600
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
18388
- function createServeSPA(app) {
18389
- return (endpoint, distPath, options = {}) => {
18601
+ function createServeSPA(app, httpServer) {
18602
+ return async (endpoint, distPath, options = {}) => {
18390
18603
  const {
18391
18604
  maxAge = "1d",
18392
18605
  etag = true,
18393
18606
  immutable = false,
18394
18607
  index = "index.html",
18395
- absolute = false
18608
+ absolute = false,
18609
+ viteSrc
18396
18610
  } = options;
18397
18611
  if (endpoint === "/api" || endpoint.startsWith("/api/")) {
18398
18612
  logger.error(`Cannot mount SPA on ${endpoint} - reserved for API routes`);
@@ -18403,58 +18617,117 @@ function createServeSPA(app) {
18403
18617
  return;
18404
18618
  }
18405
18619
  registeredEndpoints.add(endpoint);
18406
- let resolvedPath;
18407
- if (absolute) {
18408
- resolvedPath = distPath;
18409
- } else {
18410
- const projectPath2 = resolve(getProjectPath(), distPath);
18411
- if (existsSync9(projectPath2)) {
18412
- resolvedPath = projectPath2;
18620
+ if (env.NODE_ENV === "development" && viteSrc) {
18621
+ const srcPath = resolve(getProjectPath(), viteSrc);
18622
+ if (!existsSync9(srcPath)) {
18623
+ logger.warn({ endpoint, viteSrc, resolved: srcPath }, "Vite source not found \u2014 falling back to static");
18413
18624
  } else {
18414
- resolvedPath = resolve(getLibPath(), distPath);
18625
+ const mounted = await mountViteDevMiddleware(app, endpoint, srcPath, httpServer);
18626
+ if (mounted) return;
18415
18627
  }
18416
18628
  }
18417
- if (!existsSync9(resolvedPath)) {
18418
- logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18419
- return;
18629
+ mountStaticSPA(app, endpoint, distPath, { maxAge, etag, immutable, index, absolute });
18630
+ };
18631
+ }
18632
+ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
18633
+ try {
18634
+ const vite = await import("vite");
18635
+ const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18636
+ const server2 = await vite.createServer({
18637
+ root: srcPath,
18638
+ server: {
18639
+ middlewareMode: true,
18640
+ allowedHosts: true,
18641
+ hmr: httpServer ? { server: httpServer } : true
18642
+ },
18643
+ plugins: [{
18644
+ name: "nexus-config-inject",
18645
+ transformIndexHtml(html) {
18646
+ const config3 = JSON.stringify({ apiUrl });
18647
+ return html.replace("</head>", `<script>window.__NEXUS__=${config3}</script>
18648
+ </head>`);
18649
+ }
18650
+ }],
18651
+ appType: "spa",
18652
+ clearScreen: false
18653
+ });
18654
+ viteServers.push(server2);
18655
+ if (endpoint === "/") {
18656
+ app.use(server2.middlewares);
18657
+ } else {
18658
+ app.use(endpoint, server2.middlewares);
18420
18659
  }
18421
- const indexPath = join9(resolvedPath, index);
18422
- if (!existsSync9(indexPath)) {
18423
- logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18424
- }
18425
- app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18426
- let injectedHtml = "";
18427
- if (existsSync9(indexPath)) {
18428
- const rawHtml = readFileSync5(indexPath, "utf-8");
18429
- const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18430
- const nexusConfig = JSON.stringify({ apiUrl });
18431
- injectedHtml = rawHtml.replace(
18432
- "</head>",
18433
- `<script>window.__NEXUS__=${nexusConfig}</script>
18434
- </head>`
18435
- );
18660
+ logger.info({ path: srcPath }, `Vite dev server mounted at ${endpoint} (HMR enabled)`);
18661
+ return true;
18662
+ } catch (err) {
18663
+ if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") {
18664
+ logger.warn(`vite not installed \u2014 falling back to static serving for ${endpoint}`);
18665
+ return false;
18436
18666
  }
18437
- const fallbackHandler = (_req, res) => {
18438
- if (!injectedHtml) {
18439
- res.status(404).send("index.html not found");
18440
- return;
18441
- }
18442
- res.set("Cache-Control", "no-cache, no-store, must-revalidate");
18443
- res.type("html").send(injectedHtml);
18444
- };
18445
- if (endpoint === "/") {
18446
- app.get("{*splat}", fallbackHandler);
18667
+ logger.error({ err }, `Failed to mount Vite dev server at ${endpoint}`);
18668
+ return false;
18669
+ }
18670
+ }
18671
+ function mountStaticSPA(app, endpoint, distPath, options) {
18672
+ const { maxAge, etag, immutable, index, absolute } = options;
18673
+ let resolvedPath;
18674
+ if (absolute) {
18675
+ resolvedPath = distPath;
18676
+ } else {
18677
+ const projectPath2 = resolve(getProjectPath(), distPath);
18678
+ if (existsSync9(projectPath2)) {
18679
+ resolvedPath = projectPath2;
18447
18680
  } else {
18448
- app.get(endpoint, fallbackHandler);
18449
- app.get(`${endpoint}/{*splat}`, fallbackHandler);
18681
+ resolvedPath = resolve(getLibPath(), distPath);
18450
18682
  }
18451
- logger.info({ path: resolvedPath }, `SPA mounted at ${endpoint}`);
18683
+ }
18684
+ if (!existsSync9(resolvedPath)) {
18685
+ logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18686
+ return;
18687
+ }
18688
+ const indexPath = join9(resolvedPath, index);
18689
+ if (!existsSync9(indexPath)) {
18690
+ logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18691
+ }
18692
+ app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18693
+ let injectedHtml = "";
18694
+ if (existsSync9(indexPath)) {
18695
+ const rawHtml = readFileSync5(indexPath, "utf-8");
18696
+ const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18697
+ const nexusConfig = JSON.stringify({ apiUrl });
18698
+ injectedHtml = rawHtml.replace(
18699
+ "</head>",
18700
+ `<script>window.__NEXUS__=${nexusConfig}</script>
18701
+ </head>`
18702
+ );
18703
+ }
18704
+ const fallbackHandler = (_req, res) => {
18705
+ if (!injectedHtml) {
18706
+ res.status(404).send("index.html not found");
18707
+ return;
18708
+ }
18709
+ res.set("Cache-Control", "no-cache, no-store, must-revalidate");
18710
+ res.type("html").send(injectedHtml);
18452
18711
  };
18712
+ if (endpoint === "/") {
18713
+ app.get("{*splat}", fallbackHandler);
18714
+ } else {
18715
+ app.get(endpoint, fallbackHandler);
18716
+ app.get(`${endpoint}/{*splat}`, fallbackHandler);
18717
+ }
18718
+ logger.info({ path: resolvedPath }, `SPA mounted at ${endpoint}`);
18453
18719
  }
18454
- function resetServeSPAEndpoints() {
18720
+ async function resetServeSPA() {
18721
+ for (const server2 of viteServers) {
18722
+ try {
18723
+ await server2.close();
18724
+ } catch {
18725
+ }
18726
+ }
18727
+ viteServers.length = 0;
18455
18728
  registeredEndpoints.clear();
18456
18729
  }
18457
- var registeredEndpoints;
18730
+ var registeredEndpoints, viteServers;
18458
18731
  var init_spa_handler = __esm({
18459
18732
  "src/core/spa-handler.ts"() {
18460
18733
  "use strict";
@@ -18462,6 +18735,7 @@ var init_spa_handler = __esm({
18462
18735
  init_logger();
18463
18736
  init_env();
18464
18737
  registeredEndpoints = /* @__PURE__ */ new Set();
18738
+ viteServers = [];
18465
18739
  }
18466
18740
  });
18467
18741
 
@@ -19032,7 +19306,7 @@ var init_core = __esm({
19032
19306
  init_id();
19033
19307
  init_sequence();
19034
19308
  init_paths();
19035
- init_error_handler();
19309
+ init_safe_json();
19036
19310
  init_spa_handler();
19037
19311
  init_cache();
19038
19312
  init_jwt();
@@ -19747,7 +20021,9 @@ var init_base_service = __esm({
19747
20021
  const countResult = await qb.clone().count("* as count").first();
19748
20022
  const total = Number(countResult?.count ?? 0);
19749
20023
  qb = this.applySortingWithDefaults(qb, query);
19750
- qb = qb.limit(limit).offset(offset);
20024
+ if (limit > 0) {
20025
+ qb = qb.limit(limit).offset(offset);
20026
+ }
19751
20027
  const rawItems = await qb;
19752
20028
  const items = this.parseJsonFieldsFromArray(rawItems);
19753
20029
  const processedItems = await this.afterFindAll(items);
@@ -19832,7 +20108,7 @@ var init_base_service = __esm({
19832
20108
  });
19833
20109
  }
19834
20110
  const total = result.length;
19835
- const paginatedItems = result.slice(offset, offset + limit);
20111
+ const paginatedItems = limit === 0 ? result : result.slice(offset, offset + limit);
19836
20112
  return this.buildPaginatedResult(paginatedItems, total, page, limit);
19837
20113
  }
19838
20114
  /**
@@ -20087,14 +20363,14 @@ var init_base_service = __esm({
20087
20363
  * Build paginated result from items and count
20088
20364
  */
20089
20365
  buildPaginatedResult(items, total, page, limit) {
20090
- const totalPages = Math.ceil(total / limit);
20366
+ const totalPages = limit === 0 ? 1 : Math.ceil(total / limit);
20091
20367
  return {
20092
20368
  items,
20093
20369
  total,
20094
- page,
20095
- limit,
20370
+ page: limit === 0 ? 1 : page,
20371
+ limit: limit === 0 ? total : limit,
20096
20372
  totalPages,
20097
- hasNext: page < totalPages
20373
+ hasNext: limit === 0 ? false : page < totalPages
20098
20374
  };
20099
20375
  }
20100
20376
  /**
@@ -20103,8 +20379,8 @@ var init_base_service = __esm({
20103
20379
  getPagination(query) {
20104
20380
  const maxLimit = query?.maxLimit ?? 100;
20105
20381
  const page = Math.max(1, query?.page ?? 1);
20106
- const limit = Math.min(maxLimit, Math.max(1, query?.limit ?? 20));
20107
- const offset = (page - 1) * limit;
20382
+ const limit = query?.limit === 0 ? 0 : Math.min(maxLimit, Math.max(1, query?.limit ?? 20));
20383
+ const offset = limit === 0 ? 0 : (page - 1) * limit;
20108
20384
  return { page, limit, offset };
20109
20385
  }
20110
20386
  /**
@@ -20124,10 +20400,31 @@ var init_base_service = __esm({
20124
20400
  * Override in subclasses for entity-specific search
20125
20401
  */
20126
20402
  applySearch(qb, search) {
20403
+ const searchTerm = `%${search}%`;
20404
+ const searchableFields = [];
20405
+ const fields = "fields" in this.definition ? this.definition.fields : {};
20406
+ for (const [name, field] of Object.entries(fields)) {
20407
+ if (field.meta?.searchable) {
20408
+ searchableFields.push(name);
20409
+ }
20410
+ }
20127
20411
  if ("labelField" in this.definition && this.definition.labelField) {
20128
- const labelField = this.definition.labelField;
20129
- qb.where(labelField, "like", `%${search}%`);
20412
+ const lf = this.definition.labelField;
20413
+ if (!searchableFields.includes(lf)) {
20414
+ searchableFields.unshift(lf);
20415
+ }
20130
20416
  }
20417
+ if (searchableFields.length === 0) return qb;
20418
+ qb.where(function() {
20419
+ for (const fieldName of searchableFields) {
20420
+ const field = fields[fieldName];
20421
+ if (field?.db?.type === "json") {
20422
+ this.orWhereRaw(`CAST(?? AS TEXT) LIKE ?`, [fieldName, searchTerm]);
20423
+ } else {
20424
+ this.orWhere(fieldName, "like", searchTerm);
20425
+ }
20426
+ }
20427
+ });
20131
20428
  return qb;
20132
20429
  }
20133
20430
  /**
@@ -21751,7 +22048,8 @@ function createEntityController(service, definition, ctx) {
21751
22048
  async list(req, res) {
21752
22049
  checkPermission(req, "read");
21753
22050
  const page = Math.max(1, parseInt(req.query["page"]) || 1);
21754
- const limit = Math.min(100, Math.max(1, parseInt(req.query["limit"]) || 20));
22051
+ const rawLimit = parseInt(req.query["limit"]);
22052
+ const limit = rawLimit === 0 ? 0 : Math.min(100, Math.max(1, rawLimit || 20));
21755
22053
  let filters;
21756
22054
  if (req.query["filters"]) {
21757
22055
  filters = parseFilters(req.query["filters"], ctx.core.errors);
@@ -23217,17 +23515,18 @@ async function createModuleRouters(ctx, definitions, modulePrefix) {
23217
23515
  const runtime = await createEntityRuntimeAsync(ctx, definition);
23218
23516
  const route = inferEntityRoutePath(definition);
23219
23517
  const entityLabel = resolveLocalized7(definition.label, "en");
23220
- if (modulePrefix && route === modulePrefix) {
23518
+ const isExposed = !("expose" in definition && definition.expose === false);
23519
+ if (isExposed && modulePrefix && route === modulePrefix) {
23221
23520
  ctx.core.logger.warn(
23222
23521
  `Entity "${entityLabel}" inferred route "${route}" duplicates module prefix \u2014 add routePrefix to the entity definition to fix`
23223
23522
  );
23224
23523
  }
23225
- if (routeMap.has(route)) {
23524
+ if (isExposed && routeMap.has(route)) {
23226
23525
  ctx.core.logger.warn(
23227
23526
  `Entity "${entityLabel}" route "${route}" collides with "${routeMap.get(route)}" in the same module`
23228
23527
  );
23229
23528
  }
23230
- routeMap.set(route, entityLabel);
23529
+ if (isExposed) routeMap.set(route, entityLabel);
23231
23530
  router.use(route, runtime.router);
23232
23531
  const key = getServiceKey(definition);
23233
23532
  if (ctx.services.has(key)) {
@@ -23443,7 +23742,7 @@ var init_runtime = __esm({
23443
23742
  }
23444
23743
  });
23445
23744
 
23446
- // src/db/module-runner.ts
23745
+ // src/db/seed-runner.ts
23447
23746
  import { existsSync as existsSync10 } from "fs";
23448
23747
  import { join as join10 } from "path";
23449
23748
  import { pathToFileURL } from "url";
@@ -23511,8 +23810,8 @@ function hasSeedData(seed5) {
23511
23810
  if (!Array.isArray(seed5) && "source" in seed5 && seed5.source === "url") return true;
23512
23811
  return Array.isArray(seed5) && seed5.length > 0;
23513
23812
  }
23514
- var init_module_runner = __esm({
23515
- "src/db/module-runner.ts"() {
23813
+ var init_seed_runner = __esm({
23814
+ "src/db/seed-runner.ts"() {
23516
23815
  "use strict";
23517
23816
  init_runtime();
23518
23817
  init_paths();
@@ -23584,15 +23883,6 @@ var init_ensure_system_tables = __esm({
23584
23883
  }
23585
23884
  });
23586
23885
 
23587
- // src/cli/shared.ts
23588
- import { consola } from "consola";
23589
- var init_shared = __esm({
23590
- "src/cli/shared.ts"() {
23591
- "use strict";
23592
- init_logger();
23593
- }
23594
- });
23595
-
23596
23886
  // src/db/migration-sources.ts
23597
23887
  function buildMigrationSources() {
23598
23888
  const sources = [
@@ -23612,10 +23902,8 @@ function buildMigrationSources() {
23612
23902
  var init_migration_sources = __esm({
23613
23903
  "src/db/migration-sources.ts"() {
23614
23904
  "use strict";
23615
- init_shared();
23616
23905
  init_paths();
23617
- init_plugin_ops();
23618
- init_store();
23906
+ init_module_store();
23619
23907
  }
23620
23908
  });
23621
23909
 
@@ -23948,7 +24236,7 @@ async function runMigrations(knexInstance, sources) {
23948
24236
  const executedMigrations = await knex3("_nexus_migrations").where({ status: "completed" }).select("name").then((rows) => new Set(rows.map((r) => r.name)));
23949
24237
  const pendingMigrations = migrationFiles.filter((m) => !executedMigrations.has(m.name));
23950
24238
  if (pendingMigrations.length === 0) {
23951
- logger.info("No pending migrations");
24239
+ logger.debug("No pending migrations");
23952
24240
  return;
23953
24241
  }
23954
24242
  const batch = await getNextBatch(knex3);
@@ -24348,6 +24636,27 @@ var init_migration_helpers = __esm({
24348
24636
  import path2 from "path";
24349
24637
  import fs2 from "fs/promises";
24350
24638
  import { readFileSync as readFileSync6, mkdirSync as mkdirSync5, realpathSync } from "fs";
24639
+ function getColumnIndexBytes(field) {
24640
+ if (!field?.db) return 255 * MYSQL_BYTES_PER_CHAR;
24641
+ const size = field.db.size ?? 255;
24642
+ if (field.db.type === "text") return 0;
24643
+ if (field.db.type === "string") return size * MYSQL_BYTES_PER_CHAR;
24644
+ if (field.db.type === "integer") return 4;
24645
+ if (field.db.type === "boolean") return 1;
24646
+ if (field.db.type === "datetime" || field.db.type === "date") return 8;
24647
+ if (field.db.type === "uuid") return 16;
24648
+ return size * MYSQL_BYTES_PER_CHAR;
24649
+ }
24650
+ function warnIfIndexExceedsMySQLLimit(table, columns, unique, fields) {
24651
+ if (!fields) return;
24652
+ const totalBytes = columns.reduce((sum, col) => sum + getColumnIndexBytes(fields[col]), 0);
24653
+ if (totalBytes > MYSQL_MAX_INDEX_BYTES) {
24654
+ const indexType = unique ? "UNIQUE" : "INDEX";
24655
+ logger.warn(
24656
+ `${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.`
24657
+ );
24658
+ }
24659
+ }
24351
24660
  async function detectSchemaDrift(knexInstance) {
24352
24661
  const knex3 = knexInstance ?? getDb();
24353
24662
  const entities = getAllPersistentEntities();
@@ -24455,6 +24764,7 @@ function computeSchemaDiff(entities, currentSchema, options) {
24455
24764
  for (const idx of entityIndexes) {
24456
24765
  const key = normalizeKey(idx.columns, !!idx.unique);
24457
24766
  if (!currentKeys.has(key)) {
24767
+ warnIfIndexExceedsMySQLLimit(tableName, idx.columns, !!idx.unique, entity.fields);
24458
24768
  diff.newIndexes.push({ columns: idx.columns, unique: !!idx.unique });
24459
24769
  }
24460
24770
  }
@@ -24626,7 +24936,7 @@ function formatDriftMessage(drift) {
24626
24936
  lines.push('Run "pnpm migrate:dev" to generate and apply migrations.');
24627
24937
  return lines.join("\n");
24628
24938
  }
24629
- var PERSISTENT_TYPES;
24939
+ var MYSQL_MAX_INDEX_BYTES, MYSQL_BYTES_PER_CHAR, PERSISTENT_TYPES;
24630
24940
  var init_migration_generator = __esm({
24631
24941
  "src/db/migration-generator.ts"() {
24632
24942
  "use strict";
@@ -24635,9 +24945,11 @@ var init_migration_generator = __esm({
24635
24945
  init_paths();
24636
24946
  init_schema_reader();
24637
24947
  init_engine();
24638
- init_queries();
24948
+ init_module_queries();
24639
24949
  init_migration_helpers();
24640
- init_store();
24950
+ init_module_store();
24951
+ MYSQL_MAX_INDEX_BYTES = 3072;
24952
+ MYSQL_BYTES_PER_CHAR = 4;
24641
24953
  PERSISTENT_TYPES = /* @__PURE__ */ new Set([
24642
24954
  "collection",
24643
24955
  "tree",
@@ -24868,6 +25180,10 @@ function handleDbError(err) {
24868
25180
  { path: "foreignKey", message: "Foreign key constraint violation" }
24869
25181
  ]);
24870
25182
  }
25183
+ if (code === "42P01" || code === "ER_NO_SUCH_TABLE" || dbErr.errno === 1146 || msg.includes("no such table")) {
25184
+ logger.error({ err }, "Database table not found \u2014 database may need migration or was wiped");
25185
+ throw new AppError({ code: ErrorCodes.DATABASE_NOT_READY, message: "Database not ready" }, 503);
25186
+ }
24871
25187
  logger.error({ err }, "Unexpected database error");
24872
25188
  throw new AppError({ code: ErrorCodes.SYSTEM_INTERNAL_ERROR, message: "Internal database error" }, 500);
24873
25189
  }
@@ -26211,7 +26527,7 @@ var init_db = __esm({
26211
26527
  init_schema_helpers();
26212
26528
  init_sql_utils();
26213
26529
  init_sqlite_compat();
26214
- init_module_runner();
26530
+ init_seed_runner();
26215
26531
  init_ensure_system_tables();
26216
26532
  init_migration_sources();
26217
26533
  init_migration_runner();
@@ -26475,8 +26791,11 @@ var init_events_api = __esm({
26475
26791
 
26476
26792
  // src/engine/context.ts
26477
26793
  import { ForbiddenError as CASLForbiddenError3, subject } from "@casl/ability";
26478
- import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2 } from "@gzl10/nexus-sdk";
26794
+ import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2, DEFAULT_LOCALES } from "@gzl10/nexus-sdk";
26479
26795
  import { Redis as Redis2 } from "ioredis";
26796
+ function setLocales(locales) {
26797
+ platformLocales = locales;
26798
+ }
26480
26799
  function getSharedCacheManager() {
26481
26800
  if (!sharedCacheManager) {
26482
26801
  const redisUrl = env.REDIS_URL;
@@ -26552,7 +26871,7 @@ function createModuleContext() {
26552
26871
  const defaultAdapter = createKnexAdapter(knex3);
26553
26872
  const defaultSchemaAdapter = createKnexSchemaAdapter(knex3);
26554
26873
  adaptersRegistry["temp"] = { data: getSharedTempAdapter() };
26555
- logger.debug(env.REDIS_URL ? "Temp adapter: Redis (shared)" : "Temp adapter: InMemory (shared)");
26874
+ logger.trace(env.REDIS_URL ? "Temp adapter: Redis (shared)" : "Temp adapter: InMemory (shared)");
26556
26875
  const middleware = {
26557
26876
  validate,
26558
26877
  rateLimit: createRateLimit,
@@ -26660,7 +26979,9 @@ function createModuleContext() {
26660
26979
  throw new Error(`Knex connection for adapter "${adapter}" not found. Available: ${Object.keys(knexConnections).join(", ")}`);
26661
26980
  }
26662
26981
  return conn;
26663
- }
26982
+ },
26983
+ // Placeholder — bound after ctx construction (needs full ModuleContext)
26984
+ seedModule: null
26664
26985
  };
26665
26986
  const configContext = {
26666
26987
  env,
@@ -26753,7 +27074,8 @@ function createModuleContext() {
26753
27074
  adapters: adaptersContext,
26754
27075
  // Root-level shortcuts for frequently used utilities
26755
27076
  events: createEventsApi(nexusEvents, logger),
26756
- createRouter: () => createRouter()
27077
+ createRouter: () => createRouter(),
27078
+ locales: platformLocales
26757
27079
  };
26758
27080
  servicesRegistry["cacheManager"] = getSharedCacheManager();
26759
27081
  ctx.runtime = {
@@ -26762,9 +27084,10 @@ function createModuleContext() {
26762
27084
  createEntityController: (service, def) => createEntityController(service, def, ctx),
26763
27085
  createEntityRouter: (controller, def) => createEntityRouter(controller, def, ctx)
26764
27086
  };
27087
+ ctx.db.seedModule = (mod) => runModuleSeed(mod, ctx);
26765
27088
  return ctx;
26766
27089
  }
26767
- var sharedCacheManager, sharedTempAdapter;
27090
+ var platformLocales, sharedCacheManager, sharedTempAdapter;
26768
27091
  var init_context = __esm({
26769
27092
  "src/engine/context.ts"() {
26770
27093
  "use strict";
@@ -26777,7 +27100,9 @@ var init_context = __esm({
26777
27100
  init_plugin_ops();
26778
27101
  init_load_config();
26779
27102
  init_events_api();
27103
+ init_seed_runner();
26780
27104
  init_cache_manager();
27105
+ platformLocales = DEFAULT_LOCALES;
26781
27106
  sharedCacheManager = null;
26782
27107
  sharedTempAdapter = null;
26783
27108
  }
@@ -26788,11 +27113,11 @@ var init_engine = __esm({
26788
27113
  "src/engine/index.ts"() {
26789
27114
  "use strict";
26790
27115
  init_registry();
26791
- init_queries();
27116
+ init_module_queries();
26792
27117
  init_loader();
26793
- init_store();
26794
- init_subjectExtractor();
26795
- init_extractors();
27118
+ init_module_store();
27119
+ init_subject_extractor();
27120
+ init_definition_extractors();
26796
27121
  init_context();
26797
27122
  }
26798
27123
  });
@@ -27141,6 +27466,33 @@ async function setupModuleRoutes(app) {
27141
27466
  }
27142
27467
  }
27143
27468
  const caslRegistry = /* @__PURE__ */ new Map();
27469
+ function mergeActionPermissions(existing, incoming) {
27470
+ const merged = { ...existing };
27471
+ for (const [role, perm] of Object.entries(incoming)) {
27472
+ if (!merged[role]) {
27473
+ merged[role] = perm;
27474
+ } else {
27475
+ const existingArr = Array.isArray(merged[role]) ? merged[role] : [merged[role]];
27476
+ const incomingArr = Array.isArray(perm) ? perm : [perm];
27477
+ merged[role] = [...existingArr, ...incomingArr];
27478
+ }
27479
+ }
27480
+ return merged;
27481
+ }
27482
+ function registerActionCasl(action) {
27483
+ const casl = action.casl;
27484
+ if (!casl || !("subject" in casl) || !casl.subject || !("permissions" in casl) || !casl.permissions) return;
27485
+ const subject2 = casl.subject;
27486
+ const existing = caslRegistry.get(subject2);
27487
+ if (existing) {
27488
+ existing.permissions = mergeActionPermissions(existing.permissions, casl.permissions);
27489
+ } else {
27490
+ caslRegistry.set(subject2, {
27491
+ subject: subject2,
27492
+ permissions: casl.permissions ?? {}
27493
+ });
27494
+ }
27495
+ }
27144
27496
  for (const mod of modules) {
27145
27497
  for (const def of mod.definitions ?? []) {
27146
27498
  const casl = def.casl;
@@ -27150,6 +27502,12 @@ async function setupModuleRoutes(app) {
27150
27502
  subject: casl.subject,
27151
27503
  permissions: casl.permissions ?? {}
27152
27504
  });
27505
+ for (const action of def.actions ?? []) {
27506
+ registerActionCasl(action);
27507
+ }
27508
+ }
27509
+ for (const action of mod.actions ?? []) {
27510
+ registerActionCasl(action);
27153
27511
  }
27154
27512
  }
27155
27513
  setEntityDefinitions(caslRegistry);
@@ -27328,7 +27686,7 @@ async function createApp(options = {}) {
27328
27686
  // Only accept arrays and objects
27329
27687
  }));
27330
27688
  app.use(cookieParser());
27331
- const serveSPA = createServeSPA(app);
27689
+ const serveSPA = createServeSPA(app, options.httpServer);
27332
27690
  if (options.beforeRoutes) {
27333
27691
  const result = options.beforeRoutes(app, serveSPA);
27334
27692
  if (result instanceof Promise) {
@@ -27422,11 +27780,11 @@ async function createApp(options = {}) {
27422
27780
  });
27423
27781
  const sortedSpas = [...servedSpas].sort((a, b) => b.endpoint.length - a.endpoint.length);
27424
27782
  for (const spa of sortedSpas) {
27425
- serveSPA(spa.endpoint, spa.path, spa);
27783
+ await serveSPA(spa.endpoint, spa.path, { ...spa, viteSrc: spa.viteSrc });
27426
27784
  }
27427
27785
  const { ui } = getConfig();
27428
27786
  if (ui.enabled) {
27429
- serveSPA(ui.base, ui.path);
27787
+ await serveSPA(ui.base, ui.path, { viteSrc: "../ui" });
27430
27788
  }
27431
27789
  app.use(errorMiddleware);
27432
27790
  return app;
@@ -27452,58 +27810,32 @@ var init_app = __esm({
27452
27810
  }
27453
27811
  });
27454
27812
 
27455
- // src/core/utils/net.ts
27813
+ // src/core/utils/port-check.ts
27456
27814
  import net from "net";
27457
27815
  import { execSync } from "child_process";
27458
27816
  function findProcessOnPort(port) {
27459
27817
  try {
27460
27818
  const output = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: "utf-8" });
27461
27819
  const pid = parseInt(output.trim().split("\n")[0] ?? "", 10);
27462
- 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 };
27463
27827
  } catch {
27464
27828
  return null;
27465
27829
  }
27466
27830
  }
27467
- function isSameProcessGroup(pid) {
27468
- if (pid === process.pid || pid === process.ppid) return true;
27469
- try {
27470
- const ourPgid = execSync(`ps -o pgid= -p ${process.pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27471
- const targetPgid = execSync(`ps -o pgid= -p ${pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27472
- return ourPgid === targetPgid;
27473
- } catch {
27474
- return false;
27475
- }
27476
- }
27477
- function killProcessOnPort(port) {
27478
- const pid = findProcessOnPort(port);
27479
- if (!pid) return false;
27480
- if (isSameProcessGroup(pid)) {
27481
- logger.warn({ port, pid }, `Skipping same process group (PID ${pid}) on port ${port}`);
27482
- return false;
27483
- }
27484
- try {
27485
- process.kill(pid, "SIGTERM");
27486
- logger.warn({ port, pid }, `Killed process ${pid} on port ${port}`);
27487
- return true;
27488
- } catch {
27489
- return false;
27490
- }
27491
- }
27492
27831
  async function checkPortAvailable(port, host = "0.0.0.0") {
27493
27832
  return new Promise((resolve2, reject) => {
27494
27833
  const server2 = net.createServer();
27495
27834
  server2.once("error", (err) => {
27496
27835
  if (err.code === "EADDRINUSE") {
27497
- if (process.env["NODE_ENV"] !== "production") {
27498
- if (killProcessOnPort(port)) {
27499
- setTimeout(() => {
27500
- checkPortAvailable(port, host).then(resolve2).catch(reject);
27501
- }, 500);
27502
- return;
27503
- }
27504
- }
27505
- logger.error({ port }, `Port ${port} is already in use`);
27506
- 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));
27507
27839
  } else {
27508
27840
  reject(err);
27509
27841
  }
@@ -27514,10 +27846,9 @@ async function checkPortAvailable(port, host = "0.0.0.0") {
27514
27846
  server2.listen(port, host);
27515
27847
  });
27516
27848
  }
27517
- var init_net = __esm({
27518
- "src/core/utils/net.ts"() {
27849
+ var init_port_check = __esm({
27850
+ "src/core/utils/port-check.ts"() {
27519
27851
  "use strict";
27520
- init_logger();
27521
27852
  }
27522
27853
  });
27523
27854
 
@@ -27555,7 +27886,7 @@ function isFrpcInstalled() {
27555
27886
  return false;
27556
27887
  }
27557
27888
  }
27558
- function startTunnel(config3) {
27889
+ async function startTunnel(config3) {
27559
27890
  if (tunnelProcess) return true;
27560
27891
  if (!isFrpcInstalled()) {
27561
27892
  logger.warn("frpc binary not found \u2014 tunnel disabled. Install with: brew install frp");
@@ -27567,24 +27898,49 @@ function startTunnel(config3) {
27567
27898
  tunnelProcess = spawn("frpc", ["-c", tmpConfigPath], {
27568
27899
  stdio: ["ignore", "pipe", "pipe"]
27569
27900
  });
27570
- tunnelProcess.stdout?.on("data", (data) => {
27571
- const msg = data.toString().trim();
27572
- if (msg) logger.debug({ component: "tunnel" }, msg);
27573
- });
27574
- tunnelProcess.stderr?.on("data", (data) => {
27575
- const msg = data.toString().trim();
27576
- if (msg) logger.warn({ component: "tunnel" }, msg);
27577
- });
27578
- tunnelProcess.on("exit", (code) => {
27579
- if (code !== 0 && code !== null) {
27580
- logger.warn({ code, component: "tunnel" }, "frpc exited unexpectedly");
27581
- }
27582
- tunnelProcess = null;
27583
- 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);
27584
27943
  });
27585
- const url = getTunnelUrl(config3.subdomain, config3.server);
27586
- logger.info({ url, component: "tunnel" }, `Tunnel enabled \u2192 ${url}`);
27587
- return true;
27588
27944
  }
27589
27945
  function stopTunnel() {
27590
27946
  if (tunnelProcess) {
@@ -27602,13 +27958,388 @@ function cleanupConfig() {
27602
27958
  tmpConfigPath = null;
27603
27959
  }
27604
27960
  }
27605
- var tunnelProcess, tmpConfigPath;
27961
+ var tunnelProcess, tmpConfigPath, outputBuffer;
27606
27962
  var init_tunnel = __esm({
27607
27963
  "src/core/tunnel.ts"() {
27608
27964
  "use strict";
27609
27965
  init_core();
27610
27966
  tunnelProcess = null;
27611
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((m) => m.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";
27612
28343
  }
27613
28344
  });
27614
28345
 
@@ -27620,7 +28351,9 @@ __export(server_exports, {
27620
28351
  start: () => start,
27621
28352
  stop: () => stop
27622
28353
  });
28354
+ import http from "http";
27623
28355
  import { entityRoom as entityRoom6 } from "@gzl10/nexus-sdk";
28356
+ import { DEFAULT_LOCALES as DEFAULT_LOCALES2 } from "@gzl10/nexus-sdk";
27624
28357
  async function runMigrationsAndSeeds(config3) {
27625
28358
  initLoggerService(getLoggerConfig());
27626
28359
  setLoggerInstance(getPinoLogger());
@@ -27673,7 +28406,7 @@ async function runMigrationsAndSeeds(config3) {
27673
28406
  const sources = buildMigrationSources();
27674
28407
  const migrationFiles = await loadAllMigrationFiles(sources);
27675
28408
  if (migrationFiles.length > 0) {
27676
- logger.info({ count: migrationFiles.length }, "Deploying pending migrations...");
28409
+ logger.info({ sources: sources.length, files: migrationFiles.length }, "Running migration deploy...");
27677
28410
  try {
27678
28411
  await runMigrations(void 0, sources);
27679
28412
  } catch (err) {
@@ -27682,7 +28415,7 @@ async function runMigrationsAndSeeds(config3) {
27682
28415
  throw err;
27683
28416
  }
27684
28417
  }
27685
- logger.info("Checking schema drift...");
28418
+ logger.debug("Checking schema drift...");
27686
28419
  const drift = await detectSchemaDrift();
27687
28420
  if (drift && (drift.newTables.length > 0 || drift.alteredTables.length > 0)) {
27688
28421
  const message = formatDriftMessage(drift);
@@ -27729,7 +28462,16 @@ ${dirs}`);
27729
28462
  }
27730
28463
  const allDefinitions = modules.flatMap((m) => m.definitions ?? []);
27731
28464
  await createMemoryTables(allDefinitions);
27732
- logger.info("Running seeds...");
28465
+ try {
28466
+ const { importSeedFiles: importSeedFiles2 } = await Promise.resolve().then(() => (init_seed_commands(), seed_commands_exports));
28467
+ await importSeedFiles2(ctx.db.knex, modules, {
28468
+ info: (msg) => logger.info(msg),
28469
+ debug: (msg) => logger.debug(msg)
28470
+ });
28471
+ } catch (err) {
28472
+ logger.debug({ err }, "Seed file import skipped (no data/seeds/ or error)");
28473
+ }
28474
+ logger.debug("Running seeds...");
27733
28475
  for (const mod of modules) {
27734
28476
  try {
27735
28477
  await runModuleSeed(mod, ctx);
@@ -27737,14 +28479,41 @@ ${dirs}`);
27737
28479
  logger.error({ module: mod.name, err }, "Seed failed - continuing with next module");
27738
28480
  }
27739
28481
  }
28482
+ if (config3?.onSeed) {
28483
+ const { getPlugins: getPlugins2 } = await Promise.resolve().then(() => (init_module_queries(), module_queries_exports));
28484
+ const { createMasterRegistry: createMasterRegistry2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
28485
+ const masterRegistry = ctx.services.has("masters") ? ctx.services.get("masters") : createMasterRegistry2();
28486
+ const pluginPrefixes = /* @__PURE__ */ new Map();
28487
+ for (const plugin of getPlugins2()) {
28488
+ pluginPrefixes.set(plugin.code, `${plugin.code}_`);
28489
+ const shortName = plugin.name.replace(/^@[^/]+\/nexus-plugin-/, "");
28490
+ if (shortName !== plugin.code) {
28491
+ pluginPrefixes.set(shortName, `${plugin.code}_`);
28492
+ }
28493
+ }
28494
+ const { createSeedContext: createSeedContext2 } = await Promise.resolve().then(() => (init_seed_context(), seed_context_exports));
28495
+ const { ctx: seedCtx, flushPermissions } = createSeedContext2({
28496
+ knex: ctx.db.knex,
28497
+ generateId: ctx.core.generateId,
28498
+ hashPassword: ctx.core.crypto.hashPassword,
28499
+ masterRegistry,
28500
+ pluginPrefixes,
28501
+ logger: ctx.core.logger,
28502
+ onPermissionsCollected: (perms) => setSeedPermissions(perms)
28503
+ });
28504
+ await config3.onSeed(seedCtx);
28505
+ await masterRegistry.seed(ctx);
28506
+ flushPermissions();
28507
+ }
27740
28508
  }
27741
28509
  async function start(config3) {
27742
28510
  if (server) {
27743
28511
  throw new Error("Server already running. Call stop() first.");
27744
28512
  }
27745
28513
  currentConfig = config3;
27746
- if (env.NODE_ENV === "development" && env.FRPC_SERVER && env.FRPC_SUBDOMAIN && !env.BACKEND_URL) {
27747
- process.env["BACKEND_URL"] = getTunnelUrl(env.FRPC_SUBDOMAIN, env.FRPC_SERVER);
28514
+ setLocales(config3?.locales ?? DEFAULT_LOCALES2);
28515
+ if (env.NODE_ENV === "development" && env.FRPC_SERVER && !env.TRUST_PROXY) {
28516
+ process.env["TRUST_PROXY"] = "true";
27748
28517
  }
27749
28518
  const resolved = resolveConfig();
27750
28519
  if (resolved.port > 0) {
@@ -27767,13 +28536,17 @@ async function start(config3) {
27767
28536
  await initTelemetry2();
27768
28537
  await runMigrationsAndSeeds(config3);
27769
28538
  const effectiveCorsOrigins = buildEffectiveCorsOrigins(env.CORS_ORIGIN, config3?.spas);
28539
+ const httpServer = http.createServer();
27770
28540
  const app = await createApp({
27771
28541
  beforeRoutes: config3?.beforeRoutes,
27772
28542
  afterRoutes: config3?.afterRoutes,
27773
- spas: config3?.spas
28543
+ spas: config3?.spas,
28544
+ httpServer
27774
28545
  });
28546
+ httpServer.on("request", app);
27775
28547
  return new Promise((resolve2) => {
27776
- server = app.listen(resolved.port, resolved.host, async () => {
28548
+ server = httpServer;
28549
+ httpServer.listen(resolved.port, resolved.host, async () => {
27777
28550
  const timeoutMs = parseInt(process.env["REQUEST_TIMEOUT_MS"] || "30000", 10);
27778
28551
  if (timeoutMs > 0) {
27779
28552
  server.setTimeout(timeoutMs);
@@ -27799,22 +28572,24 @@ async function start(config3) {
27799
28572
  eventBridge.init();
27800
28573
  const addr = server.address();
27801
28574
  const actualPort = typeof addr === "object" && addr ? addr.port : resolved.port;
28575
+ let tunnelActive = false;
27802
28576
  if (env.NODE_ENV === "development" && env.FRPC_SERVER && env.FRPC_SUBDOMAIN) {
27803
- startTunnel({
28577
+ tunnelActive = await startTunnel({
27804
28578
  server: env.FRPC_SERVER,
27805
28579
  serverPort: env.FRPC_SERVER_PORT,
27806
28580
  token: env.FRPC_TOKEN,
27807
28581
  subdomain: env.FRPC_SUBDOMAIN,
27808
28582
  localPort: actualPort
27809
28583
  });
28584
+ if (tunnelActive && !env.BACKEND_URL) {
28585
+ process.env["BACKEND_URL"] = getTunnelUrl(env.FRPC_SUBDOMAIN, env.FRPC_SERVER);
28586
+ }
27810
28587
  }
27811
28588
  const baseUrl = env.BACKEND_URL || `http://localhost:${actualPort}`;
27812
- logger.info({ libPath: getLibPath(), projectPath: getProjectPath() }, "Paths");
27813
- logger.info(`API: ${baseUrl}/api/v1`);
27814
- if (resolved.ui.enabled) {
27815
- logger.info(`UI: ${baseUrl}`);
27816
- }
27817
- logger.info({ port: actualPort, mode: resolved.nodeEnv }, "Server started");
28589
+ logger.debug({ libPath: getLibPath(), projectPath: getProjectPath() }, "Paths");
28590
+ const urls = { api: `${baseUrl}/api/v1` };
28591
+ if (resolved.ui.enabled) urls["ui"] = baseUrl;
28592
+ logger.info({ port: actualPort, mode: resolved.nodeEnv, ...urls }, "Server started");
27818
28593
  nexusEvents.emitEvent("server.started", { port: actualPort, host: resolved.host });
27819
28594
  if (config3?.onReady) {
27820
28595
  try {
@@ -27840,7 +28615,8 @@ async function stop() {
27840
28615
  await resetSharedAdapters();
27841
28616
  resetConfigCache();
27842
28617
  clearCustomCaslRules();
27843
- resetServeSPAEndpoints();
28618
+ clearSeedPermissions();
28619
+ await resetServeSPA();
27844
28620
  return;
27845
28621
  }
27846
28622
  if (currentConfig?.beforeClose) {
@@ -27874,7 +28650,8 @@ async function stop() {
27874
28650
  await resetSharedAdapters();
27875
28651
  resetConfigCache();
27876
28652
  clearCustomCaslRules();
27877
- resetServeSPAEndpoints();
28653
+ clearSeedPermissions();
28654
+ await resetServeSPA();
27878
28655
  currentConfig = void 0;
27879
28656
  server = null;
27880
28657
  nexusEvents.emitEvent("server.stopped");
@@ -27934,7 +28711,7 @@ var init_server = __esm({
27934
28711
  init_cors();
27935
28712
  init_logger();
27936
28713
  init_error_middleware();
27937
- init_net();
28714
+ init_port_check();
27938
28715
  init_engine();
27939
28716
  init_context();
27940
28717
  init_db();