@gzl10/nexus-backend 0.16.8 → 0.18.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.16.8",
109
+ version: "0.18.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",
@@ -162,7 +162,7 @@ var init_package = __esm({
162
162
  "jwt"
163
163
  ],
164
164
  scripts: {
165
- dev: "tsx watch src/main.ts",
165
+ dev: "node --watch-path=./src --import tsx/esm src/main.ts",
166
166
  build: "tsup",
167
167
  start: "node dist/main.js",
168
168
  nexus: "tsx src/cli.ts",
@@ -218,22 +218,6 @@ var init_package = __esm({
218
218
  zod: "^3.24.0"
219
219
  },
220
220
  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-links": "workspace:*",
228
- "@gzl10/nexus-plugin-notifications": "workspace:*",
229
- "@gzl10/nexus-plugin-oidc-server": "workspace:^",
230
- "@gzl10/nexus-plugin-plane": "workspace:^",
231
- "@gzl10/nexus-plugin-prisma": "workspace:^",
232
- "@gzl10/nexus-plugin-remote": "workspace:*",
233
- "@gzl10/nexus-plugin-schedules": "workspace:*",
234
- "@gzl10/nexus-plugin-scim": "workspace:^",
235
- "@gzl10/nexus-plugin-tags": "workspace:*",
236
- "@gzl10/nexus-plugin-webhooks": "workspace:*",
237
221
  "@types/bcryptjs": "^2.4.0",
238
222
  "@types/compression": "^1.8.1",
239
223
  "@types/cookie-parser": "^1.4.10",
@@ -246,7 +230,16 @@ var init_package = __esm({
246
230
  "pino-pretty": "^13.1.3",
247
231
  "socket.io-client": "^4.8.3",
248
232
  supertest: "^7.2.2",
249
- tsx: "^4.21.0"
233
+ tsx: "^4.21.0",
234
+ vite: "^8.0.3"
235
+ },
236
+ peerDependencies: {
237
+ vite: ">=6.0.0"
238
+ },
239
+ peerDependenciesMeta: {
240
+ vite: {
241
+ optional: true
242
+ }
250
243
  },
251
244
  publishConfig: {
252
245
  access: "public",
@@ -1147,15 +1140,17 @@ async function isWorkspacePackage(name) {
1147
1140
  }
1148
1141
  }
1149
1142
  async function installPlugin(name, opts) {
1143
+ const isWorkspace = await isWorkspacePackage(name);
1150
1144
  let pkg2;
1151
1145
  if (opts?.version) {
1152
1146
  pkg2 = `${name}@${opts.version}`;
1153
- } else if (await isWorkspacePackage(name)) {
1147
+ } else if (isWorkspace) {
1154
1148
  pkg2 = `${name}@workspace:*`;
1155
1149
  } else {
1156
1150
  pkg2 = name;
1157
1151
  }
1158
- await execAsync(`pnpm add -D ${pkg2}`);
1152
+ const flag = isWorkspace ? "-D " : "";
1153
+ await execAsync(`pnpm add ${flag}${pkg2}`);
1159
1154
  const filePath = getPluginsFilePath(opts?.projectPath);
1160
1155
  const plugins = readPluginsFile(filePath);
1161
1156
  plugins[name] = { enabled: true };
@@ -1246,7 +1241,7 @@ var init_table_prefix = __esm({
1246
1241
  }
1247
1242
  });
1248
1243
 
1249
- // src/engine/store.ts
1244
+ // src/engine/module-store.ts
1250
1245
  function resetStore() {
1251
1246
  moduleStore.modules.length = 0;
1252
1247
  moduleStore.plugins.clear();
@@ -1257,8 +1252,8 @@ function resetStore() {
1257
1252
  moduleStore.tableToSubject.clear();
1258
1253
  }
1259
1254
  var moduleStore;
1260
- var init_store = __esm({
1261
- "src/engine/store.ts"() {
1255
+ var init_module_store = __esm({
1256
+ "src/engine/module-store.ts"() {
1262
1257
  "use strict";
1263
1258
  moduleStore = {
1264
1259
  /** Registered modules with source metadata */
@@ -1311,7 +1306,7 @@ var init_id = __esm({
1311
1306
  }
1312
1307
  });
1313
1308
 
1314
- // src/engine/extractors.ts
1309
+ // src/engine/definition-extractors.ts
1315
1310
  function getTableAndSubject(def) {
1316
1311
  const caslSubject = def.casl?.subject;
1317
1312
  if (!TYPES_WITH_TABLE.has(def.type)) {
@@ -1379,8 +1374,8 @@ function validateModuleDependencies(modules) {
1379
1374
  }
1380
1375
  }
1381
1376
  var TYPES_WITH_TABLE;
1382
- var init_extractors = __esm({
1383
- "src/engine/extractors.ts"() {
1377
+ var init_definition_extractors = __esm({
1378
+ "src/engine/definition-extractors.ts"() {
1384
1379
  "use strict";
1385
1380
  TYPES_WITH_TABLE = /* @__PURE__ */ new Set(["collection", "reference", "event", "config", "temp", "view", void 0]);
1386
1381
  }
@@ -1500,9 +1495,9 @@ var init_registry = __esm({
1500
1495
  "use strict";
1501
1496
  init_plugin_ops();
1502
1497
  init_table_prefix();
1503
- init_store();
1498
+ init_module_store();
1504
1499
  init_id();
1505
- init_extractors();
1500
+ init_definition_extractors();
1506
1501
  }
1507
1502
  });
1508
1503
 
@@ -1554,7 +1549,28 @@ var init_paths = __esm({
1554
1549
  }
1555
1550
  });
1556
1551
 
1557
- // src/engine/queries.ts
1552
+ // src/engine/module-queries.ts
1553
+ var module_queries_exports = {};
1554
+ __export(module_queries_exports, {
1555
+ getCoreManifest: () => getCoreManifest,
1556
+ getCoreModules: () => getCoreModules,
1557
+ getModule: () => getModule,
1558
+ getModules: () => getModules,
1559
+ getOrderedModules: () => getOrderedModules,
1560
+ getOrderedModulesInternal: () => getOrderedModulesInternal,
1561
+ getPlugin: () => getPlugin,
1562
+ getPluginByCode: () => getPluginByCode,
1563
+ getPlugins: () => getPlugins,
1564
+ getRegisteredSubjects: () => getRegisteredSubjects,
1565
+ getSubjectForTable: () => getSubjectForTable,
1566
+ getUserManifest: () => getUserManifest,
1567
+ getUserModules: () => getUserModules,
1568
+ hasModule: () => hasModule,
1569
+ hasPlugin: () => hasPlugin,
1570
+ hasPluginByCode: () => hasPluginByCode,
1571
+ hasUserApp: () => hasUserApp,
1572
+ isValidSubject: () => isValidSubject
1573
+ });
1558
1574
  import { join as join4 } from "path";
1559
1575
  import { readFileSync as readFileSync3 } from "fs";
1560
1576
  function readPackageJson(dir) {
@@ -1588,12 +1604,22 @@ function getOrderedModulesInternal() {
1588
1604
  moduleStore.modules.forEach(visit);
1589
1605
  return sorted;
1590
1606
  }
1607
+ function getModule(name) {
1608
+ const mod = moduleStore.modules.find((m) => m.name === name);
1609
+ return mod ? toModuleManifest(mod) : void 0;
1610
+ }
1611
+ function getPlugin(name) {
1612
+ return moduleStore.plugins.get(name);
1613
+ }
1591
1614
  function getPlugins() {
1592
1615
  return [...moduleStore.plugins.values()];
1593
1616
  }
1594
1617
  function getRegisteredSubjects() {
1595
1618
  return [...moduleStore.subjects];
1596
1619
  }
1620
+ function isValidSubject(subject2) {
1621
+ return moduleStore.subjects.has(subject2);
1622
+ }
1597
1623
  function getSubjectForTable(table) {
1598
1624
  return moduleStore.tableToSubject.get(table);
1599
1625
  }
@@ -1640,10 +1666,10 @@ function getPluginByCode(code) {
1640
1666
  function hasPluginByCode(code) {
1641
1667
  return moduleStore.pluginsByCode.has(code);
1642
1668
  }
1643
- var init_queries = __esm({
1644
- "src/engine/queries.ts"() {
1669
+ var init_module_queries = __esm({
1670
+ "src/engine/module-queries.ts"() {
1645
1671
  "use strict";
1646
- init_store();
1672
+ init_module_store();
1647
1673
  init_paths();
1648
1674
  }
1649
1675
  });
@@ -2127,12 +2153,16 @@ var init_definitions = __esm({
2127
2153
  labelPlural: { en: "Masters", es: "Maestros" },
2128
2154
  labelField: "label",
2129
2155
  timestamps: true,
2156
+ availableDisplayModes: ["board"],
2157
+ groupBy: "type",
2158
+ groupableFields: ["type"],
2159
+ //columnDragFields: ['is_active'],
2130
2160
  fields: {
2131
2161
  id: useTextField2({
2132
2162
  label: { en: "ID", es: "ID" },
2133
2163
  required: true,
2134
2164
  size: 100,
2135
- hint: { en: "Format: type:code (e.g. countries:ES)", es: "Formato: type:code (ej. countries:ES)" },
2165
+ hidden: true,
2136
2166
  meta: { sortable: true }
2137
2167
  }),
2138
2168
  type: useTextField2({
@@ -2153,9 +2183,20 @@ var init_definitions = __esm({
2153
2183
  is_active: isActiveField,
2154
2184
  metadata: useJsonField({
2155
2185
  label: { en: "Metadata", es: "Metadatos" },
2156
- hint: { en: "Type-specific fields (symbol, flag, etc.)", es: "Campos espec\xEDficos del tipo" }
2186
+ hint: {
2187
+ en: "Type-specific fields (symbol, flag, etc.)",
2188
+ es: "Campos espec\xEDficos del tipo"
2189
+ }
2157
2190
  })
2158
2191
  },
2192
+ hooks: () => ({
2193
+ beforeCreate: async (data) => {
2194
+ if (data["type"] && data["code"] && !data["id"]) {
2195
+ data["id"] = `${data["type"]}:${data["code"]}`;
2196
+ }
2197
+ return data;
2198
+ }
2199
+ }),
2159
2200
  defaultSort: { field: "order", order: "asc" },
2160
2201
  indexes: [{ columns: ["type", "code"], unique: true }],
2161
2202
  casl: { subject: "Master", permissions: masterCaslPermissions }
@@ -2164,6 +2205,10 @@ var init_definitions = __esm({
2164
2205
  });
2165
2206
 
2166
2207
  // src/modules/masters/registry.ts
2208
+ var registry_exports = {};
2209
+ __export(registry_exports, {
2210
+ createMasterRegistry: () => createMasterRegistry
2211
+ });
2167
2212
  function createMasterRegistry() {
2168
2213
  const registrations = [];
2169
2214
  return {
@@ -8842,15 +8887,18 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8842
8887
  const meta = field["meta"];
8843
8888
  return meta?.["sortable"] === true && !field["hidden"];
8844
8889
  });
8890
+ const explicitGroupable = def["groupableFields"];
8845
8891
  const groupableInputTypes = ["select", "switch", "checkbox", "radio", "tags"];
8846
- const groupableFields = fieldEntries.filter(([, f]) => {
8892
+ const autoGroupableFields = fieldEntries.filter(([, f]) => {
8847
8893
  const field = f;
8848
8894
  const inputType = field["input"];
8849
8895
  return groupableInputTypes.includes(inputType ?? "") && !field["hidden"];
8850
8896
  });
8897
+ const resolvedGroupableFields = explicitGroupable ?? (autoGroupableFields.length > 0 ? autoGroupableFields.map(([name]) => name) : void 0);
8851
8898
  const entityType = def["type"] ?? "collection";
8852
8899
  const explicitDisplayMode = def["displayMode"];
8853
- const defaultDisplayMode = explicitDisplayMode ?? (["tree", "dag"].includes(entityType) ? "tree" : "table");
8900
+ const explicitAvailableModes = def["availableDisplayModes"];
8901
+ const defaultDisplayMode = explicitDisplayMode ?? (explicitAvailableModes?.length === 1 ? explicitAvailableModes[0] : void 0) ?? (["tree", "dag"].includes(entityType) ? "tree" : "table");
8854
8902
  const entityIdent = def["table"] ?? def["key"];
8855
8903
  return {
8856
8904
  id: def._id,
@@ -8870,9 +8918,11 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8870
8918
  displayMode: explicitDisplayMode,
8871
8919
  defaultDisplayMode,
8872
8920
  availableDisplayModes: (() => {
8921
+ const explicit = def["availableDisplayModes"];
8922
+ if (explicit?.length) return explicit;
8873
8923
  const modes = ["table", "list", "masonry"];
8874
8924
  if (["tree", "dag"].includes(entityType)) modes.push("tree");
8875
- if (groupableFields.length > 0) modes.push("board");
8925
+ if ((resolvedGroupableFields?.length ?? 0) > 0) modes.push("board");
8876
8926
  if (def["calendarFrom"]) modes.push("calendar");
8877
8927
  return modes;
8878
8928
  })(),
@@ -8882,10 +8932,8 @@ function toEntityDefinitionDTO(def, _engine, moduleName) {
8882
8932
  value: name,
8883
8933
  label: f["label"]
8884
8934
  })),
8885
- groupableFields: groupableFields.length > 0 ? groupableFields.map(([name, f]) => ({
8886
- value: name,
8887
- label: f["label"]
8888
- })) : void 0,
8935
+ groupableFields: resolvedGroupableFields,
8936
+ columnDragFields: def["columnDragFields"],
8889
8937
  groupBy: def["groupBy"],
8890
8938
  subgroupBy: def["subgroupBy"],
8891
8939
  calendarFrom: def["calendarFrom"],
@@ -8915,7 +8963,7 @@ function toModuleDTO(mod, ctx) {
8915
8963
  dependencies: mod.dependencies ?? [],
8916
8964
  routePrefix: mod.routePrefix ?? `/${mod.name}`,
8917
8965
  subjects: ctx.engine.getModuleSubjects(mod),
8918
- definitions: (mod.definitions ?? []).map(
8966
+ definitions: (mod.definitions ?? []).filter((def) => ("expose" in def ? def.expose : true) !== false).map(
8919
8967
  (def) => toEntityDefinitionDTO(def, ctx.engine, mod.name)
8920
8968
  ),
8921
8969
  actions: (mod.actions ?? []).length > 0 ? (mod.actions ?? []).filter((a) => !a.hidden).map((a) => toActionDTO(a)) : void 0,
@@ -8925,21 +8973,6 @@ function toModuleDTO(mod, ctx) {
8925
8973
  hasInit: !!mod.init
8926
8974
  };
8927
8975
  }
8928
- function getOrderedModulesViaContext(ctx) {
8929
- return ctx.engine.getModules();
8930
- }
8931
- async function runModuleSeedViaContext(mod, ctx) {
8932
- if (!mod.seed) {
8933
- return false;
8934
- }
8935
- try {
8936
- await mod.seed(ctx);
8937
- return true;
8938
- } catch (err) {
8939
- ctx.core.logger.error({ module: mod.name, err }, "Seed failed");
8940
- return false;
8941
- }
8942
- }
8943
8976
  var init_system_helpers = __esm({
8944
8977
  "src/modules/system/system.helpers.ts"() {
8945
8978
  "use strict";
@@ -9174,7 +9207,8 @@ function createSystemController(ctx) {
9174
9207
  const plugins = engine.getPlugins();
9175
9208
  const body = {
9176
9209
  version: manifest.version,
9177
- plugins: plugins.map((p) => p.code)
9210
+ plugins: plugins.map((p) => p.code),
9211
+ locales: ctx.locales
9178
9212
  };
9179
9213
  res.json(body);
9180
9214
  }
@@ -10061,7 +10095,6 @@ var SYSTEM_TABLES, factoryResetAction;
10061
10095
  var init_factory_reset_action = __esm({
10062
10096
  "src/modules/system/actions/factory-reset.action.ts"() {
10063
10097
  "use strict";
10064
- init_system_helpers();
10065
10098
  SYSTEM_TABLES = /* @__PURE__ */ new Set([
10066
10099
  "_nexus_migrations",
10067
10100
  "_nexus_migration_lock",
@@ -10104,8 +10137,8 @@ var init_factory_reset_action = __esm({
10104
10137
  source: "core:system",
10105
10138
  action: "factory_reset",
10106
10139
  actorId: authReq.user?.id,
10107
- ip: req.ip,
10108
- userAgent: req.headers["user-agent"]
10140
+ ip: req?.ip,
10141
+ userAgent: req?.headers["user-agent"]
10109
10142
  });
10110
10143
  await new Promise((resolve2) => setImmediate(resolve2));
10111
10144
  const allTables = await getAllTables(knex3);
@@ -10143,11 +10176,11 @@ var init_factory_reset_action = __esm({
10143
10176
  } catch {
10144
10177
  }
10145
10178
  ctx.core.logger.info({ tables: dataTables.length }, "All data tables cleared");
10146
- const modules = getOrderedModulesViaContext(ctx);
10179
+ const modules = ctx.engine.getModules();
10147
10180
  let modulesSeeded = 0;
10148
10181
  for (const mod of modules) {
10149
10182
  try {
10150
- const seeded = await runModuleSeedViaContext(mod, ctx);
10183
+ const seeded = await ctx.db.seedModule(mod);
10151
10184
  if (seeded) modulesSeeded++;
10152
10185
  } catch (err) {
10153
10186
  ctx.core.logger.error({ module: mod.name, err }, "Seed failed during factory reset");
@@ -10201,8 +10234,8 @@ var init_restart_server_action = __esm({
10201
10234
  source: "core:system",
10202
10235
  action: "server_restart",
10203
10236
  actorId: authReq.user?.id,
10204
- ip: req.ip,
10205
- userAgent: req.headers["user-agent"]
10237
+ ip: req?.ip,
10238
+ userAgent: req?.headers["user-agent"]
10206
10239
  });
10207
10240
  setTimeout(() => process.exit(0), 500);
10208
10241
  return { success: true, message: "Server is restarting..." };
@@ -11824,7 +11857,7 @@ function createUploadMiddleware(ctx, options) {
11824
11857
  const rateLimit2 = ctx.core.middleware.rateLimit({
11825
11858
  windowMs: 60 * 1e3,
11826
11859
  max: 20,
11827
- message: "Demasiados uploads, intenta en 1 minuto"
11860
+ message: "Too many uploads, try again in 1 minute"
11828
11861
  });
11829
11862
  const upload = multer({
11830
11863
  storage: multer.memoryStorage(),
@@ -12071,7 +12104,7 @@ function createStorageRoutes(ctx) {
12071
12104
  }
12072
12105
  res.status(201).json(results);
12073
12106
  };
12074
- const uploadRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1e3, max: 20, message: "Demasiados uploads, intenta en 1 minuto" });
12107
+ const uploadRateLimit = ctx.core.middleware.rateLimit({ windowMs: 60 * 1e3, max: 20, message: "Too many uploads, try again in 1 minute" });
12075
12108
  if (auth) {
12076
12109
  router.post("/upload/multiple", uploadRateLimit, auth, upload.array("files", 10), uploadMultiple);
12077
12110
  } else {
@@ -12105,7 +12138,7 @@ function createStorageRoutes(ctx) {
12105
12138
  delete filesController.create;
12106
12139
  delete filesController.update;
12107
12140
  filesController.delete = async (req, res) => {
12108
- const id = req.params["id"];
12141
+ const id = String(req.params["id"] ?? "");
12109
12142
  if (!id) throw new ValidationError2("VALIDATION_ERROR");
12110
12143
  const storageService = getStorageService();
12111
12144
  const file = await storageService.getById(id);
@@ -12369,7 +12402,7 @@ var init_users_entity = __esm({
12369
12402
  ],
12370
12403
  nullable: true,
12371
12404
  meta: { sortable: true },
12372
- defaultValue: "es"
12405
+ defaultValue: "en"
12373
12406
  }),
12374
12407
  timezone: useSelectField9({
12375
12408
  label: { en: "Timezone", es: "Zona horaria" },
@@ -12447,7 +12480,7 @@ var init_users_entity = __esm({
12447
12480
  middleware: (ctx) => ctx.core.middleware.rateLimit({
12448
12481
  windowMs: 15 * 60 * 1e3,
12449
12482
  max: 5,
12450
- message: "Demasiados intentos, intenta en 15 minutos"
12483
+ message: "Too many attempts, try again in 15 minutes"
12451
12484
  }),
12452
12485
  handler: async (ctx, input) => {
12453
12486
  const {
@@ -12541,9 +12574,9 @@ var init_users_entity = __esm({
12541
12574
  labelField: "role_id",
12542
12575
  timestamps: true,
12543
12576
  order: 15,
12544
- routePrefix: "/user-roles",
12545
12577
  hidden: true,
12546
12578
  // Pivot table - managed via Users UI
12579
+ expose: false,
12547
12580
  fields: {
12548
12581
  id: useIdField4(),
12549
12582
  user_id: useSelectField9({
@@ -12596,7 +12629,7 @@ function createUsersRoutes(ctx) {
12596
12629
  if (originalUsersDelete) {
12597
12630
  usersController.delete = async (req, res) => {
12598
12631
  const authReq = req;
12599
- const id = req.params["id"] ?? "";
12632
+ const id = String(req.params["id"] ?? "");
12600
12633
  if (authReq.user?.id === id) {
12601
12634
  throw new ctx.core.errors.ForbiddenError("No puedes eliminarte a ti mismo");
12602
12635
  }
@@ -12671,7 +12704,7 @@ function createUsersRoutes(ctx) {
12671
12704
  "/:userId/roles",
12672
12705
  auth,
12673
12706
  async (req, res) => {
12674
- const roles = await usersService.getUserRoles(req.params["userId"]);
12707
+ const roles = await usersService.getUserRoles(String(req.params["userId"] ?? ""));
12675
12708
  res.json(roles);
12676
12709
  }
12677
12710
  );
@@ -12681,18 +12714,19 @@ function createUsersRoutes(ctx) {
12681
12714
  validate2({ body: z4.object({ roleId: z4.string().min(1) }) }),
12682
12715
  async (req, res) => {
12683
12716
  const { roleId } = req.body;
12684
- await usersService.assignRole(req.params["userId"], roleId);
12717
+ const userId = String(req.params["userId"] ?? "");
12718
+ await usersService.assignRole(userId, roleId);
12685
12719
  ctx.events.notify("audit.log", {
12686
12720
  source: "core:users",
12687
12721
  action: "role_assigned",
12688
12722
  actorId: req.user?.id,
12689
12723
  resourceType: "user",
12690
- resourceId: req.params["userId"],
12724
+ resourceId: userId,
12691
12725
  ip: req.ip,
12692
12726
  userAgent: req.headers["user-agent"],
12693
12727
  metadata: { roleId }
12694
12728
  });
12695
- const roles = await usersService.getUserRoles(req.params["userId"]);
12729
+ const roles = await usersService.getUserRoles(userId);
12696
12730
  res.json(roles);
12697
12731
  }
12698
12732
  );
@@ -12702,18 +12736,19 @@ function createUsersRoutes(ctx) {
12702
12736
  validate2({ body: z4.object({ roleIds: z4.array(z4.string()) }) }),
12703
12737
  async (req, res) => {
12704
12738
  const { roleIds } = req.body;
12705
- await usersService.setRoles(req.params["userId"], roleIds);
12739
+ const userId = String(req.params["userId"] ?? "");
12740
+ await usersService.setRoles(userId, roleIds);
12706
12741
  ctx.events.notify("audit.log", {
12707
12742
  source: "core:users",
12708
12743
  action: "roles_replaced",
12709
12744
  actorId: req.user?.id,
12710
12745
  resourceType: "user",
12711
- resourceId: req.params["userId"],
12746
+ resourceId: userId,
12712
12747
  ip: req.ip,
12713
12748
  userAgent: req.headers["user-agent"],
12714
12749
  metadata: { roleIds }
12715
12750
  });
12716
- const roles = await usersService.getUserRoles(req.params["userId"]);
12751
+ const roles = await usersService.getUserRoles(userId);
12717
12752
  res.json(roles);
12718
12753
  }
12719
12754
  );
@@ -12721,16 +12756,18 @@ function createUsersRoutes(ctx) {
12721
12756
  "/:userId/roles/:roleId",
12722
12757
  auth,
12723
12758
  async (req, res) => {
12724
- await usersService.removeRole(req.params["userId"], req.params["roleId"]);
12759
+ const userId = String(req.params["userId"] ?? "");
12760
+ const roleId = String(req.params["roleId"] ?? "");
12761
+ await usersService.removeRole(userId, roleId);
12725
12762
  ctx.events.notify("audit.log", {
12726
12763
  source: "core:users",
12727
12764
  action: "role_removed",
12728
12765
  actorId: req.user?.id,
12729
12766
  resourceType: "user",
12730
- resourceId: req.params["userId"],
12767
+ resourceId: userId,
12731
12768
  ip: req.ip,
12732
12769
  userAgent: req.headers["user-agent"],
12733
- metadata: { roleId: req.params["roleId"] }
12770
+ metadata: { roleId }
12734
12771
  });
12735
12772
  res.json({ success: true });
12736
12773
  }
@@ -13508,8 +13545,8 @@ var init_auth_entity = __esm({
13508
13545
  label: { en: "Refresh Token", es: "Token de refresco" },
13509
13546
  labelPlural: { en: "Refresh Tokens", es: "Tokens de refresco" },
13510
13547
  labelField: "id",
13511
- routePrefix: "/tokens",
13512
13548
  retention: { days: 7, expiresField: "expires_at" },
13549
+ expose: false,
13513
13550
  fields: {
13514
13551
  id: useIdField5(),
13515
13552
  token: useTextField9({
@@ -13574,8 +13611,8 @@ var init_auth_entity = __esm({
13574
13611
  labelField: "provider",
13575
13612
  timestamps: true,
13576
13613
  hidden: true,
13614
+ expose: false,
13577
13615
  order: 5,
13578
- routePrefix: "/identities",
13579
13616
  fields: {
13580
13617
  id: useIdField5(),
13581
13618
  user_id: useSelectField10({
@@ -16625,7 +16662,7 @@ var init_plugins_entity = __esm({
16625
16662
  label: "Plugins",
16626
16663
  icon: "mdi:puzzle",
16627
16664
  labelField: "code",
16628
- routePrefix: "/plugins",
16665
+ routePrefix: "/",
16629
16666
  defaultSort: { field: "name", order: "asc" },
16630
16667
  fields: {
16631
16668
  name: useTextField12({
@@ -17038,12 +17075,12 @@ var init_loader = __esm({
17038
17075
  "src/engine/loader.ts"() {
17039
17076
  "use strict";
17040
17077
  init_registry();
17041
- init_extractors();
17078
+ init_definition_extractors();
17042
17079
  init_modules();
17043
17080
  }
17044
17081
  });
17045
17082
 
17046
- // src/engine/subjectExtractor.ts
17083
+ // src/engine/subject-extractor.ts
17047
17084
  function getModuleSubjects(mod) {
17048
17085
  const subjects = /* @__PURE__ */ new Set();
17049
17086
  for (const def of mod.definitions ?? []) {
@@ -17052,10 +17089,10 @@ function getModuleSubjects(mod) {
17052
17089
  }
17053
17090
  return [...subjects];
17054
17091
  }
17055
- var init_subjectExtractor = __esm({
17056
- "src/engine/subjectExtractor.ts"() {
17092
+ var init_subject_extractor = __esm({
17093
+ "src/engine/subject-extractor.ts"() {
17057
17094
  "use strict";
17058
- init_extractors();
17095
+ init_definition_extractors();
17059
17096
  }
17060
17097
  });
17061
17098
 
@@ -17133,7 +17170,7 @@ function initSocketIO(httpServer, options) {
17133
17170
  maxHttpBufferSize: 1e6
17134
17171
  // 1MB - match Express json body limit
17135
17172
  });
17136
- logger.info({ maxHttpBufferSize: 1e6, cors: corsOrigin }, "Socket.IO initialized");
17173
+ logger.info({ cors: corsOrigin }, "Socket.IO initialized");
17137
17174
  io.use((socket, next) => {
17138
17175
  const token = socket.handshake.auth?.["token"] || socket.handshake.query?.["token"];
17139
17176
  if (token && typeof token === "string" && jwtSecret) {
@@ -17156,7 +17193,6 @@ function initSocketIO(httpServer, options) {
17156
17193
  logger.warn({ code: err.code, message: err.message }, "Socket.IO connection error");
17157
17194
  });
17158
17195
  io.on("connection", handleConnection);
17159
- logger.info("Socket.IO initialized");
17160
17196
  nexusEvents.emitEvent("socket.initialized");
17161
17197
  return io;
17162
17198
  }
@@ -17737,6 +17773,12 @@ var init_app_error = __esm({
17737
17773
 
17738
17774
  // src/core/abilities/ability.factory.ts
17739
17775
  import { AbilityBuilder, createMongoAbility } from "@casl/ability";
17776
+ function setSeedPermissions(perms) {
17777
+ seedPermissions = perms;
17778
+ }
17779
+ function clearSeedPermissions() {
17780
+ seedPermissions = null;
17781
+ }
17740
17782
  function setCustomCaslRules(fn) {
17741
17783
  customCaslRules = fn;
17742
17784
  }
@@ -17794,16 +17836,28 @@ async function defineAbilityFor(user, roleNames) {
17794
17836
  if (customCaslRules) {
17795
17837
  await customCaslRules(user, { can, cannot });
17796
17838
  }
17839
+ if (seedPermissions && !isSuperuser) {
17840
+ for (const roleName of roleNames) {
17841
+ const rolePerms = seedPermissions.get(roleName);
17842
+ if (!rolePerms) continue;
17843
+ for (const [subject2, actions] of rolePerms) {
17844
+ for (const action of actions) {
17845
+ can(action, subject2);
17846
+ }
17847
+ }
17848
+ }
17849
+ }
17797
17850
  return build();
17798
17851
  }
17799
17852
  function packRules(ability) {
17800
17853
  return ability.rules;
17801
17854
  }
17802
- var customCaslRules, entityDefinitionsRegistry, SUPERUSER_ROLES;
17855
+ var customCaslRules, seedPermissions, entityDefinitionsRegistry, SUPERUSER_ROLES;
17803
17856
  var init_ability_factory = __esm({
17804
17857
  "src/core/abilities/ability.factory.ts"() {
17805
17858
  "use strict";
17806
17859
  init_logger();
17860
+ seedPermissions = null;
17807
17861
  entityDefinitionsRegistry = null;
17808
17862
  SUPERUSER_ROLES = ["ADMIN", "OWNER"];
17809
17863
  }
@@ -17874,7 +17928,7 @@ var init_validate_middleware = __esm({
17874
17928
  import { z as z8 } from "zod";
17875
17929
  function resolveConfig() {
17876
17930
  env = envSchema.parse(process.env);
17877
- process.env.TZ = env.TZ;
17931
+ process.env["TZ"] = env.TZ;
17878
17932
  resolvedConfig = {
17879
17933
  nodeEnv: env.NODE_ENV,
17880
17934
  port: env.PORT,
@@ -17929,7 +17983,7 @@ var init_env = __esm({
17929
17983
  FRPC_SUBDOMAIN: z8.string().optional()
17930
17984
  });
17931
17985
  env = envSchema.parse(process.env);
17932
- process.env.TZ = env.TZ;
17986
+ process.env["TZ"] = env.TZ;
17933
17987
  resolvedConfig = null;
17934
17988
  }
17935
17989
  });
@@ -18356,7 +18410,7 @@ var init_sequence = __esm({
18356
18410
  }
18357
18411
  });
18358
18412
 
18359
- // src/core/utils/error-handler.ts
18413
+ // src/core/utils/safe-json.ts
18360
18414
  function safeJsonParse(logger2, jsonString, fallback, context) {
18361
18415
  try {
18362
18416
  return JSON.parse(jsonString);
@@ -18365,8 +18419,8 @@ function safeJsonParse(logger2, jsonString, fallback, context) {
18365
18419
  return fallback;
18366
18420
  }
18367
18421
  }
18368
- var init_error_handler = __esm({
18369
- "src/core/utils/error-handler.ts"() {
18422
+ var init_safe_json = __esm({
18423
+ "src/core/utils/safe-json.ts"() {
18370
18424
  "use strict";
18371
18425
  }
18372
18426
  });
@@ -18375,14 +18429,15 @@ var init_error_handler = __esm({
18375
18429
  import express from "express";
18376
18430
  import { resolve, join as join9 } from "path";
18377
18431
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
18378
- function createServeSPA(app) {
18379
- return (endpoint, distPath, options = {}) => {
18432
+ function createServeSPA(app, httpServer) {
18433
+ return async (endpoint, distPath, options = {}) => {
18380
18434
  const {
18381
18435
  maxAge = "1d",
18382
18436
  etag = true,
18383
18437
  immutable = false,
18384
18438
  index = "index.html",
18385
- absolute = false
18439
+ absolute = false,
18440
+ viteSrc
18386
18441
  } = options;
18387
18442
  if (endpoint === "/api" || endpoint.startsWith("/api/")) {
18388
18443
  logger.error(`Cannot mount SPA on ${endpoint} - reserved for API routes`);
@@ -18393,58 +18448,117 @@ function createServeSPA(app) {
18393
18448
  return;
18394
18449
  }
18395
18450
  registeredEndpoints.add(endpoint);
18396
- let resolvedPath;
18397
- if (absolute) {
18398
- resolvedPath = distPath;
18399
- } else {
18400
- const projectPath2 = resolve(getProjectPath(), distPath);
18401
- if (existsSync9(projectPath2)) {
18402
- resolvedPath = projectPath2;
18451
+ if (env.NODE_ENV === "development" && viteSrc) {
18452
+ const srcPath = resolve(getProjectPath(), viteSrc);
18453
+ if (!existsSync9(srcPath)) {
18454
+ logger.warn({ endpoint, viteSrc, resolved: srcPath }, "Vite source not found \u2014 falling back to static");
18403
18455
  } else {
18404
- resolvedPath = resolve(getLibPath(), distPath);
18456
+ const mounted = await mountViteDevMiddleware(app, endpoint, srcPath, httpServer);
18457
+ if (mounted) return;
18405
18458
  }
18406
18459
  }
18407
- if (!existsSync9(resolvedPath)) {
18408
- logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18409
- return;
18460
+ mountStaticSPA(app, endpoint, distPath, { maxAge, etag, immutable, index, absolute });
18461
+ };
18462
+ }
18463
+ async function mountViteDevMiddleware(app, endpoint, srcPath, httpServer) {
18464
+ try {
18465
+ const vite = await import("vite");
18466
+ const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18467
+ const server2 = await vite.createServer({
18468
+ root: srcPath,
18469
+ server: {
18470
+ middlewareMode: true,
18471
+ allowedHosts: true,
18472
+ hmr: httpServer ? { server: httpServer } : true
18473
+ },
18474
+ plugins: [{
18475
+ name: "nexus-config-inject",
18476
+ transformIndexHtml(html) {
18477
+ const config3 = JSON.stringify({ apiUrl });
18478
+ return html.replace("</head>", `<script>window.__NEXUS__=${config3}</script>
18479
+ </head>`);
18480
+ }
18481
+ }],
18482
+ appType: "spa",
18483
+ clearScreen: false
18484
+ });
18485
+ viteServers.push(server2);
18486
+ if (endpoint === "/") {
18487
+ app.use(server2.middlewares);
18488
+ } else {
18489
+ app.use(endpoint, server2.middlewares);
18410
18490
  }
18411
- const indexPath = join9(resolvedPath, index);
18412
- if (!existsSync9(indexPath)) {
18413
- logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18414
- }
18415
- app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18416
- let injectedHtml = "";
18417
- if (existsSync9(indexPath)) {
18418
- const rawHtml = readFileSync5(indexPath, "utf-8");
18419
- const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18420
- const nexusConfig = JSON.stringify({ apiUrl });
18421
- injectedHtml = rawHtml.replace(
18422
- "</head>",
18423
- `<script>window.__NEXUS__=${nexusConfig}</script>
18424
- </head>`
18425
- );
18491
+ logger.info({ path: srcPath }, `Vite dev server mounted at ${endpoint} (HMR enabled)`);
18492
+ return true;
18493
+ } catch (err) {
18494
+ if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") {
18495
+ logger.warn(`vite not installed \u2014 falling back to static serving for ${endpoint}`);
18496
+ return false;
18426
18497
  }
18427
- const fallbackHandler = (_req, res) => {
18428
- if (!injectedHtml) {
18429
- res.status(404).send("index.html not found");
18430
- return;
18431
- }
18432
- res.set("Cache-Control", "no-cache, no-store, must-revalidate");
18433
- res.type("html").send(injectedHtml);
18434
- };
18435
- if (endpoint === "/") {
18436
- app.get("{*splat}", fallbackHandler);
18498
+ logger.error({ err }, `Failed to mount Vite dev server at ${endpoint}`);
18499
+ return false;
18500
+ }
18501
+ }
18502
+ function mountStaticSPA(app, endpoint, distPath, options) {
18503
+ const { maxAge, etag, immutable, index, absolute } = options;
18504
+ let resolvedPath;
18505
+ if (absolute) {
18506
+ resolvedPath = distPath;
18507
+ } else {
18508
+ const projectPath2 = resolve(getProjectPath(), distPath);
18509
+ if (existsSync9(projectPath2)) {
18510
+ resolvedPath = projectPath2;
18437
18511
  } else {
18438
- app.get(endpoint, fallbackHandler);
18439
- app.get(`${endpoint}/{*splat}`, fallbackHandler);
18512
+ resolvedPath = resolve(getLibPath(), distPath);
18440
18513
  }
18441
- logger.info({ path: resolvedPath }, `SPA mounted at ${endpoint}`);
18514
+ }
18515
+ if (!existsSync9(resolvedPath)) {
18516
+ logger.warn({ endpoint, distPath, hint: "Build the frontend first" }, `SPA directory not found: ${resolvedPath}`);
18517
+ return;
18518
+ }
18519
+ const indexPath = join9(resolvedPath, index);
18520
+ if (!existsSync9(indexPath)) {
18521
+ logger.warn({ endpoint, index }, `Index file not found: ${indexPath}`);
18522
+ }
18523
+ app.use(endpoint, express.static(resolvedPath, { maxAge, etag, immutable }));
18524
+ let injectedHtml = "";
18525
+ if (existsSync9(indexPath)) {
18526
+ const rawHtml = readFileSync5(indexPath, "utf-8");
18527
+ const apiUrl = env.BACKEND_URL ? `${env.BACKEND_URL}/api/v1` : "/api/v1";
18528
+ const nexusConfig = JSON.stringify({ apiUrl });
18529
+ injectedHtml = rawHtml.replace(
18530
+ "</head>",
18531
+ `<script>window.__NEXUS__=${nexusConfig}</script>
18532
+ </head>`
18533
+ );
18534
+ }
18535
+ const fallbackHandler = (_req, res) => {
18536
+ if (!injectedHtml) {
18537
+ res.status(404).send("index.html not found");
18538
+ return;
18539
+ }
18540
+ res.set("Cache-Control", "no-cache, no-store, must-revalidate");
18541
+ res.type("html").send(injectedHtml);
18442
18542
  };
18543
+ if (endpoint === "/") {
18544
+ app.get("{*splat}", fallbackHandler);
18545
+ } else {
18546
+ app.get(endpoint, fallbackHandler);
18547
+ app.get(`${endpoint}/{*splat}`, fallbackHandler);
18548
+ }
18549
+ logger.info({ path: resolvedPath }, `SPA mounted at ${endpoint}`);
18443
18550
  }
18444
- function resetServeSPAEndpoints() {
18551
+ async function resetServeSPA() {
18552
+ for (const server2 of viteServers) {
18553
+ try {
18554
+ await server2.close();
18555
+ } catch {
18556
+ }
18557
+ }
18558
+ viteServers.length = 0;
18445
18559
  registeredEndpoints.clear();
18446
18560
  }
18447
- var registeredEndpoints;
18561
+ var registeredEndpoints, viteServers;
18448
18562
  var init_spa_handler = __esm({
18449
18563
  "src/core/spa-handler.ts"() {
18450
18564
  "use strict";
@@ -18452,6 +18566,7 @@ var init_spa_handler = __esm({
18452
18566
  init_logger();
18453
18567
  init_env();
18454
18568
  registeredEndpoints = /* @__PURE__ */ new Set();
18569
+ viteServers = [];
18455
18570
  }
18456
18571
  });
18457
18572
 
@@ -19022,7 +19137,7 @@ var init_core = __esm({
19022
19137
  init_id();
19023
19138
  init_sequence();
19024
19139
  init_paths();
19025
- init_error_handler();
19140
+ init_safe_json();
19026
19141
  init_spa_handler();
19027
19142
  init_cache();
19028
19143
  init_jwt();
@@ -19737,7 +19852,9 @@ var init_base_service = __esm({
19737
19852
  const countResult = await qb.clone().count("* as count").first();
19738
19853
  const total = Number(countResult?.count ?? 0);
19739
19854
  qb = this.applySortingWithDefaults(qb, query);
19740
- qb = qb.limit(limit).offset(offset);
19855
+ if (limit > 0) {
19856
+ qb = qb.limit(limit).offset(offset);
19857
+ }
19741
19858
  const rawItems = await qb;
19742
19859
  const items = this.parseJsonFieldsFromArray(rawItems);
19743
19860
  const processedItems = await this.afterFindAll(items);
@@ -19822,7 +19939,7 @@ var init_base_service = __esm({
19822
19939
  });
19823
19940
  }
19824
19941
  const total = result.length;
19825
- const paginatedItems = result.slice(offset, offset + limit);
19942
+ const paginatedItems = limit === 0 ? result : result.slice(offset, offset + limit);
19826
19943
  return this.buildPaginatedResult(paginatedItems, total, page, limit);
19827
19944
  }
19828
19945
  /**
@@ -20077,14 +20194,14 @@ var init_base_service = __esm({
20077
20194
  * Build paginated result from items and count
20078
20195
  */
20079
20196
  buildPaginatedResult(items, total, page, limit) {
20080
- const totalPages = Math.ceil(total / limit);
20197
+ const totalPages = limit === 0 ? 1 : Math.ceil(total / limit);
20081
20198
  return {
20082
20199
  items,
20083
20200
  total,
20084
- page,
20085
- limit,
20201
+ page: limit === 0 ? 1 : page,
20202
+ limit: limit === 0 ? total : limit,
20086
20203
  totalPages,
20087
- hasNext: page < totalPages
20204
+ hasNext: limit === 0 ? false : page < totalPages
20088
20205
  };
20089
20206
  }
20090
20207
  /**
@@ -20093,8 +20210,8 @@ var init_base_service = __esm({
20093
20210
  getPagination(query) {
20094
20211
  const maxLimit = query?.maxLimit ?? 100;
20095
20212
  const page = Math.max(1, query?.page ?? 1);
20096
- const limit = Math.min(maxLimit, Math.max(1, query?.limit ?? 20));
20097
- const offset = (page - 1) * limit;
20213
+ const limit = query?.limit === 0 ? 0 : Math.min(maxLimit, Math.max(1, query?.limit ?? 20));
20214
+ const offset = limit === 0 ? 0 : (page - 1) * limit;
20098
20215
  return { page, limit, offset };
20099
20216
  }
20100
20217
  /**
@@ -21741,7 +21858,8 @@ function createEntityController(service, definition, ctx) {
21741
21858
  async list(req, res) {
21742
21859
  checkPermission(req, "read");
21743
21860
  const page = Math.max(1, parseInt(req.query["page"]) || 1);
21744
- const limit = Math.min(100, Math.max(1, parseInt(req.query["limit"]) || 20));
21861
+ const rawLimit = parseInt(req.query["limit"]);
21862
+ const limit = rawLimit === 0 ? 0 : Math.min(100, Math.max(1, rawLimit || 20));
21745
21863
  let filters;
21746
21864
  if (req.query["filters"]) {
21747
21865
  filters = parseFilters(req.query["filters"], ctx.core.errors);
@@ -21784,7 +21902,7 @@ function createEntityController(service, definition, ctx) {
21784
21902
  * Get single entity
21785
21903
  */
21786
21904
  async get(req, res) {
21787
- const id = req.params["id"] ?? "";
21905
+ const id = String(req.params["id"] ?? "");
21788
21906
  const entity = await service.findById(id);
21789
21907
  if (!entity) {
21790
21908
  throw new ctx.core.errors.NotFoundError(`${resolveLocalized6(definition.label, "en")} not found`);
@@ -21835,7 +21953,7 @@ function createEntityController(service, definition, ctx) {
21835
21953
  if (!service.update) {
21836
21954
  throw new ctx.core.errors.ForbiddenError(`${resolveLocalized6(definition.label, "en")} does not support update`);
21837
21955
  }
21838
- const id = req.params["id"] ?? "";
21956
+ const id = String(req.params["id"] ?? "");
21839
21957
  const existing = await service.findById(id);
21840
21958
  if (!existing) {
21841
21959
  throw new ctx.core.errors.NotFoundError(`${resolveLocalized6(definition.label, "en")} not found`);
@@ -21867,7 +21985,7 @@ function createEntityController(service, definition, ctx) {
21867
21985
  if (!service.delete) {
21868
21986
  throw new ctx.core.errors.ForbiddenError(`${resolveLocalized6(definition.label, "en")} does not support delete`);
21869
21987
  }
21870
- const id = req.params["id"] ?? "";
21988
+ const id = String(req.params["id"] ?? "");
21871
21989
  const existing = await service.findById(id);
21872
21990
  if (!existing) {
21873
21991
  throw new ctx.core.errors.NotFoundError(`${resolveLocalized6(definition.label, "en")} not found`);
@@ -22066,7 +22184,7 @@ function createActionHandler(action, definition, ctx, scope = "row") {
22066
22184
  const caslSubject = (action.casl && "subject" in action.casl ? action.casl.subject : void 0) ?? definition.casl?.subject ?? capitalizeFirst(entityName ?? "Entity");
22067
22185
  const hasCasl = !action.skipAuth && (!!definition.casl || !!action.casl);
22068
22186
  return async (req, res) => {
22069
- const recordId = scope === "row" ? req.params["id"] : void 0;
22187
+ const recordId = scope === "row" ? String(req.params["id"] ?? "") : void 0;
22070
22188
  const authReq = req;
22071
22189
  if (hasCasl && !authReq.ability) {
22072
22190
  ctx.core.logger.warn({ action: "execute", subject: caslSubject, actionKey: action.key, reqId: req.requestId, decision: "deny", reason: "unauthenticated" }, "authz:deny");
@@ -22140,6 +22258,8 @@ var init_entity_controller = __esm({
22140
22258
  // src/runtime/routes/entity.routes.ts
22141
22259
  function createEntityRouter(controller, definition, ctx, service) {
22142
22260
  const router = ctx.createRouter();
22261
+ const expose = "expose" in definition ? definition.expose : true;
22262
+ if (expose === false) return router;
22143
22263
  const type2 = definition.type ?? "collection";
22144
22264
  const isSingleton = (type2 === "single" || type2 === "config") && !("scopeField" in definition && definition.scopeField);
22145
22265
  const entityDef = definition;
@@ -22234,44 +22354,44 @@ function createEntityRouter(controller, definition, ctx, service) {
22234
22354
  res.json(await treeSvc.findRoots());
22235
22355
  }));
22236
22356
  router.post("/:id/move", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22237
- const id = req.params["id"] ?? "";
22357
+ const id = String(req.params["id"] ?? "");
22238
22358
  const { parentId } = req.body;
22239
22359
  res.json(await treeSvc.move(id, parentId ?? null));
22240
22360
  }));
22241
22361
  router.get("/:id/ancestors", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22242
- const id = req.params["id"] ?? "";
22362
+ const id = String(req.params["id"] ?? "");
22243
22363
  res.json(await treeSvc.getAncestors(id));
22244
22364
  }));
22245
22365
  router.get("/:id/descendants", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22246
- const id = req.params["id"] ?? "";
22366
+ const id = String(req.params["id"] ?? "");
22247
22367
  res.json(await treeSvc.getDescendants(id));
22248
22368
  }));
22249
22369
  router.get("/:id/children", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22250
- const id = req.params["id"] ?? "";
22370
+ const id = String(req.params["id"] ?? "");
22251
22371
  res.json(await treeSvc.findChildren(id));
22252
22372
  }));
22253
22373
  }
22254
22374
  if (type2 === "dag" && service) {
22255
22375
  const dagSvc = service;
22256
22376
  router.get("/:id/parents", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22257
- const id = req.params["id"] ?? "";
22377
+ const id = String(req.params["id"] ?? "");
22258
22378
  res.json(await dagSvc.getParents(id));
22259
22379
  }));
22260
22380
  router.put("/:id/parents", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22261
- const id = req.params["id"] ?? "";
22381
+ const id = String(req.params["id"] ?? "");
22262
22382
  const { parentIds } = req.body;
22263
22383
  await dagSvc.setParents(id, parentIds);
22264
22384
  res.status(204).send();
22265
22385
  }));
22266
22386
  router.post("/:id/parents", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22267
- const id = req.params["id"] ?? "";
22387
+ const id = String(req.params["id"] ?? "");
22268
22388
  const { parentId } = req.body;
22269
22389
  await dagSvc.addParent(id, parentId);
22270
22390
  res.status(204).send();
22271
22391
  }));
22272
22392
  router.delete("/:id/parents/:parentId", ...entityMiddleware, ...authMiddleware, asyncHandler(async (req, res) => {
22273
- const id = req.params["id"] ?? "";
22274
- const parentId = req.params["parentId"] ?? "";
22393
+ const id = String(req.params["id"] ?? "");
22394
+ const parentId = String(req.params["parentId"] ?? "");
22275
22395
  await dagSvc.removeParent(id, parentId);
22276
22396
  res.status(204).send();
22277
22397
  }));
@@ -23205,17 +23325,18 @@ async function createModuleRouters(ctx, definitions, modulePrefix) {
23205
23325
  const runtime = await createEntityRuntimeAsync(ctx, definition);
23206
23326
  const route = inferEntityRoutePath(definition);
23207
23327
  const entityLabel = resolveLocalized7(definition.label, "en");
23208
- if (modulePrefix && route === modulePrefix) {
23328
+ const isExposed = !("expose" in definition && definition.expose === false);
23329
+ if (isExposed && modulePrefix && route === modulePrefix) {
23209
23330
  ctx.core.logger.warn(
23210
23331
  `Entity "${entityLabel}" inferred route "${route}" duplicates module prefix \u2014 add routePrefix to the entity definition to fix`
23211
23332
  );
23212
23333
  }
23213
- if (routeMap.has(route)) {
23334
+ if (isExposed && routeMap.has(route)) {
23214
23335
  ctx.core.logger.warn(
23215
23336
  `Entity "${entityLabel}" route "${route}" collides with "${routeMap.get(route)}" in the same module`
23216
23337
  );
23217
23338
  }
23218
- routeMap.set(route, entityLabel);
23339
+ if (isExposed) routeMap.set(route, entityLabel);
23219
23340
  router.use(route, runtime.router);
23220
23341
  const key = getServiceKey(definition);
23221
23342
  if (ctx.services.has(key)) {
@@ -23431,7 +23552,7 @@ var init_runtime = __esm({
23431
23552
  }
23432
23553
  });
23433
23554
 
23434
- // src/db/module-runner.ts
23555
+ // src/db/seed-runner.ts
23435
23556
  import { existsSync as existsSync10 } from "fs";
23436
23557
  import { join as join10 } from "path";
23437
23558
  import { pathToFileURL } from "url";
@@ -23499,8 +23620,8 @@ function hasSeedData(seed5) {
23499
23620
  if (!Array.isArray(seed5) && "source" in seed5 && seed5.source === "url") return true;
23500
23621
  return Array.isArray(seed5) && seed5.length > 0;
23501
23622
  }
23502
- var init_module_runner = __esm({
23503
- "src/db/module-runner.ts"() {
23623
+ var init_seed_runner = __esm({
23624
+ "src/db/seed-runner.ts"() {
23504
23625
  "use strict";
23505
23626
  init_runtime();
23506
23627
  init_paths();
@@ -23603,7 +23724,7 @@ var init_migration_sources = __esm({
23603
23724
  init_shared();
23604
23725
  init_paths();
23605
23726
  init_plugin_ops();
23606
- init_store();
23727
+ init_module_store();
23607
23728
  }
23608
23729
  });
23609
23730
 
@@ -23936,7 +24057,7 @@ async function runMigrations(knexInstance, sources) {
23936
24057
  const executedMigrations = await knex3("_nexus_migrations").where({ status: "completed" }).select("name").then((rows) => new Set(rows.map((r) => r.name)));
23937
24058
  const pendingMigrations = migrationFiles.filter((m) => !executedMigrations.has(m.name));
23938
24059
  if (pendingMigrations.length === 0) {
23939
- logger.info("No pending migrations");
24060
+ logger.debug("No pending migrations");
23940
24061
  return;
23941
24062
  }
23942
24063
  const batch = await getNextBatch(knex3);
@@ -24336,6 +24457,27 @@ var init_migration_helpers = __esm({
24336
24457
  import path2 from "path";
24337
24458
  import fs2 from "fs/promises";
24338
24459
  import { readFileSync as readFileSync6, mkdirSync as mkdirSync5, realpathSync } from "fs";
24460
+ function getColumnIndexBytes(field) {
24461
+ if (!field?.db) return 255 * MYSQL_BYTES_PER_CHAR;
24462
+ const size = field.db.size ?? 255;
24463
+ if (field.db.type === "text") return 0;
24464
+ if (field.db.type === "string") return size * MYSQL_BYTES_PER_CHAR;
24465
+ if (field.db.type === "integer") return 4;
24466
+ if (field.db.type === "boolean") return 1;
24467
+ if (field.db.type === "datetime" || field.db.type === "date") return 8;
24468
+ if (field.db.type === "uuid") return 16;
24469
+ return size * MYSQL_BYTES_PER_CHAR;
24470
+ }
24471
+ function warnIfIndexExceedsMySQLLimit(table, columns, unique, fields) {
24472
+ if (!fields) return;
24473
+ const totalBytes = columns.reduce((sum, col) => sum + getColumnIndexBytes(fields[col]), 0);
24474
+ if (totalBytes > MYSQL_MAX_INDEX_BYTES) {
24475
+ const indexType = unique ? "UNIQUE" : "INDEX";
24476
+ logger.warn(
24477
+ `${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.`
24478
+ );
24479
+ }
24480
+ }
24339
24481
  async function detectSchemaDrift(knexInstance) {
24340
24482
  const knex3 = knexInstance ?? getDb();
24341
24483
  const entities = getAllPersistentEntities();
@@ -24443,6 +24585,7 @@ function computeSchemaDiff(entities, currentSchema, options) {
24443
24585
  for (const idx of entityIndexes) {
24444
24586
  const key = normalizeKey(idx.columns, !!idx.unique);
24445
24587
  if (!currentKeys.has(key)) {
24588
+ warnIfIndexExceedsMySQLLimit(tableName, idx.columns, !!idx.unique, entity.fields);
24446
24589
  diff.newIndexes.push({ columns: idx.columns, unique: !!idx.unique });
24447
24590
  }
24448
24591
  }
@@ -24614,7 +24757,7 @@ function formatDriftMessage(drift) {
24614
24757
  lines.push('Run "pnpm migrate:dev" to generate and apply migrations.');
24615
24758
  return lines.join("\n");
24616
24759
  }
24617
- var PERSISTENT_TYPES;
24760
+ var MYSQL_MAX_INDEX_BYTES, MYSQL_BYTES_PER_CHAR, PERSISTENT_TYPES;
24618
24761
  var init_migration_generator = __esm({
24619
24762
  "src/db/migration-generator.ts"() {
24620
24763
  "use strict";
@@ -24623,9 +24766,11 @@ var init_migration_generator = __esm({
24623
24766
  init_paths();
24624
24767
  init_schema_reader();
24625
24768
  init_engine();
24626
- init_queries();
24769
+ init_module_queries();
24627
24770
  init_migration_helpers();
24628
- init_store();
24771
+ init_module_store();
24772
+ MYSQL_MAX_INDEX_BYTES = 3072;
24773
+ MYSQL_BYTES_PER_CHAR = 4;
24629
24774
  PERSISTENT_TYPES = /* @__PURE__ */ new Set([
24630
24775
  "collection",
24631
24776
  "tree",
@@ -24679,6 +24824,13 @@ async function runAllGeneratedMigrations(ctx, modules) {
24679
24824
  table.string("created_by", 26).nullable();
24680
24825
  table.string("updated_by", 26).nullable();
24681
24826
  }
24827
+ const hasSoftDelete = entityDef.softDelete;
24828
+ if (hasSoftDelete) {
24829
+ table.timestamp("deleted_at").nullable();
24830
+ }
24831
+ if (entityDef.type === "tree") {
24832
+ table.string("parent_id", 26).nullable().references(`${tableName}.id`).onDelete("SET NULL");
24833
+ }
24682
24834
  });
24683
24835
  const entityIndexes = entityDef.indexes;
24684
24836
  if (entityIndexes?.length) {
@@ -24795,7 +24947,16 @@ function addColumn(table, fieldName, field, db2) {
24795
24947
  column.unique();
24796
24948
  }
24797
24949
  if (dbConfig.default !== void 0) {
24798
- column.defaultTo(dbConfig.default);
24950
+ const isJsonColumn = dbConfig.type === "json" || dbConfig.type === "array";
24951
+ if (isJsonColumn && typeof dbConfig.default === "string") {
24952
+ try {
24953
+ column.defaultTo(JSON.parse(dbConfig.default));
24954
+ } catch {
24955
+ column.defaultTo(dbConfig.default);
24956
+ }
24957
+ } else {
24958
+ column.defaultTo(dbConfig.default);
24959
+ }
24799
24960
  } else if (dbConfig.defaultFn === "now") {
24800
24961
  column.defaultTo(db2.fn.now());
24801
24962
  }
@@ -26183,7 +26344,7 @@ var init_db = __esm({
26183
26344
  init_schema_helpers();
26184
26345
  init_sql_utils();
26185
26346
  init_sqlite_compat();
26186
- init_module_runner();
26347
+ init_seed_runner();
26187
26348
  init_ensure_system_tables();
26188
26349
  init_migration_sources();
26189
26350
  init_migration_runner();
@@ -26273,12 +26434,20 @@ async function discoverPlugins(projectPath2) {
26273
26434
  manifest.image = imagePath;
26274
26435
  }
26275
26436
  }
26437
+ if (!manifest.llms) {
26438
+ const llmsPath = join12(projectPath2, "node_modules", pkgName, "llms.txt");
26439
+ if (existsSync11(llmsPath)) {
26440
+ manifest.llms = readFileSync7(llmsPath, "utf-8");
26441
+ }
26442
+ }
26276
26443
  discovered.push(manifest);
26277
26444
  } else {
26278
26445
  logger.warn({ plugin: pkgName }, "Plugin discovery: no PluginManifest export, skipping");
26279
26446
  }
26280
26447
  } catch (err) {
26281
- logger.warn({ plugin: pkgName, err: err.message }, "Plugin discovery: failed to import");
26448
+ const errorMsg = err.message;
26449
+ logger.warn({ plugin: pkgName, err: errorMsg }, "Plugin discovery: failed to import");
26450
+ console.error(` \u26A0 Plugin '${pkgName}' failed to load: ${errorMsg}`);
26282
26451
  }
26283
26452
  }
26284
26453
  return discovered;
@@ -26346,7 +26515,9 @@ async function discoverModules(projectPath2) {
26346
26515
  discovered.push(...manifests);
26347
26516
  }
26348
26517
  } catch (err) {
26349
- logger.warn({ module: entry, err: err.message }, "Module discovery: failed to import");
26518
+ const errorMsg = err.message;
26519
+ logger.warn({ module: entry, err: errorMsg }, "Module discovery: failed to import");
26520
+ console.error(` \u26A0 Module '${entry}' failed to load: ${errorMsg}`);
26350
26521
  }
26351
26522
  }
26352
26523
  return discovered;
@@ -26437,8 +26608,11 @@ var init_events_api = __esm({
26437
26608
 
26438
26609
  // src/engine/context.ts
26439
26610
  import { ForbiddenError as CASLForbiddenError3, subject } from "@casl/ability";
26440
- import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2 } from "@gzl10/nexus-sdk";
26611
+ import { DEFAULT_TENANT_ID as DEFAULT_TENANT_ID2, DEFAULT_LOCALES } from "@gzl10/nexus-sdk";
26441
26612
  import { Redis as Redis2 } from "ioredis";
26613
+ function setLocales(locales) {
26614
+ platformLocales = locales;
26615
+ }
26442
26616
  function getSharedCacheManager() {
26443
26617
  if (!sharedCacheManager) {
26444
26618
  const redisUrl = env.REDIS_URL;
@@ -26514,7 +26688,7 @@ function createModuleContext() {
26514
26688
  const defaultAdapter = createKnexAdapter(knex3);
26515
26689
  const defaultSchemaAdapter = createKnexSchemaAdapter(knex3);
26516
26690
  adaptersRegistry["temp"] = { data: getSharedTempAdapter() };
26517
- logger.debug(env.REDIS_URL ? "Temp adapter: Redis (shared)" : "Temp adapter: InMemory (shared)");
26691
+ logger.trace(env.REDIS_URL ? "Temp adapter: Redis (shared)" : "Temp adapter: InMemory (shared)");
26518
26692
  const middleware = {
26519
26693
  validate,
26520
26694
  rateLimit: createRateLimit,
@@ -26622,7 +26796,9 @@ function createModuleContext() {
26622
26796
  throw new Error(`Knex connection for adapter "${adapter}" not found. Available: ${Object.keys(knexConnections).join(", ")}`);
26623
26797
  }
26624
26798
  return conn;
26625
- }
26799
+ },
26800
+ // Placeholder — bound after ctx construction (needs full ModuleContext)
26801
+ seedModule: null
26626
26802
  };
26627
26803
  const configContext = {
26628
26804
  env,
@@ -26715,7 +26891,8 @@ function createModuleContext() {
26715
26891
  adapters: adaptersContext,
26716
26892
  // Root-level shortcuts for frequently used utilities
26717
26893
  events: createEventsApi(nexusEvents, logger),
26718
- createRouter: () => createRouter()
26894
+ createRouter: () => createRouter(),
26895
+ locales: platformLocales
26719
26896
  };
26720
26897
  servicesRegistry["cacheManager"] = getSharedCacheManager();
26721
26898
  ctx.runtime = {
@@ -26724,9 +26901,10 @@ function createModuleContext() {
26724
26901
  createEntityController: (service, def) => createEntityController(service, def, ctx),
26725
26902
  createEntityRouter: (controller, def) => createEntityRouter(controller, def, ctx)
26726
26903
  };
26904
+ ctx.db.seedModule = (mod) => runModuleSeed(mod, ctx);
26727
26905
  return ctx;
26728
26906
  }
26729
- var sharedCacheManager, sharedTempAdapter;
26907
+ var platformLocales, sharedCacheManager, sharedTempAdapter;
26730
26908
  var init_context = __esm({
26731
26909
  "src/engine/context.ts"() {
26732
26910
  "use strict";
@@ -26739,7 +26917,9 @@ var init_context = __esm({
26739
26917
  init_plugin_ops();
26740
26918
  init_load_config();
26741
26919
  init_events_api();
26920
+ init_seed_runner();
26742
26921
  init_cache_manager();
26922
+ platformLocales = DEFAULT_LOCALES;
26743
26923
  sharedCacheManager = null;
26744
26924
  sharedTempAdapter = null;
26745
26925
  }
@@ -26750,11 +26930,11 @@ var init_engine = __esm({
26750
26930
  "src/engine/index.ts"() {
26751
26931
  "use strict";
26752
26932
  init_registry();
26753
- init_queries();
26933
+ init_module_queries();
26754
26934
  init_loader();
26755
- init_store();
26756
- init_subjectExtractor();
26757
- init_extractors();
26935
+ init_module_store();
26936
+ init_subject_extractor();
26937
+ init_definition_extractors();
26758
26938
  init_context();
26759
26939
  }
26760
26940
  });
@@ -27290,7 +27470,7 @@ async function createApp(options = {}) {
27290
27470
  // Only accept arrays and objects
27291
27471
  }));
27292
27472
  app.use(cookieParser());
27293
- const serveSPA = createServeSPA(app);
27473
+ const serveSPA = createServeSPA(app, options.httpServer);
27294
27474
  if (options.beforeRoutes) {
27295
27475
  const result = options.beforeRoutes(app, serveSPA);
27296
27476
  if (result instanceof Promise) {
@@ -27384,11 +27564,11 @@ async function createApp(options = {}) {
27384
27564
  });
27385
27565
  const sortedSpas = [...servedSpas].sort((a, b) => b.endpoint.length - a.endpoint.length);
27386
27566
  for (const spa of sortedSpas) {
27387
- serveSPA(spa.endpoint, spa.path, spa);
27567
+ await serveSPA(spa.endpoint, spa.path, { ...spa, viteSrc: spa.viteSrc });
27388
27568
  }
27389
27569
  const { ui } = getConfig();
27390
27570
  if (ui.enabled) {
27391
- serveSPA(ui.base, ui.path);
27571
+ await serveSPA(ui.base, ui.path, { viteSrc: "../ui" });
27392
27572
  }
27393
27573
  app.use(errorMiddleware);
27394
27574
  return app;
@@ -27414,58 +27594,32 @@ var init_app = __esm({
27414
27594
  }
27415
27595
  });
27416
27596
 
27417
- // src/core/utils/net.ts
27597
+ // src/core/utils/port-check.ts
27418
27598
  import net from "net";
27419
27599
  import { execSync } from "child_process";
27420
27600
  function findProcessOnPort(port) {
27421
27601
  try {
27422
27602
  const output = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: "utf-8" });
27423
27603
  const pid = parseInt(output.trim().split("\n")[0] ?? "", 10);
27424
- return isNaN(pid) ? null : pid;
27604
+ if (isNaN(pid)) return null;
27605
+ let name = "unknown";
27606
+ try {
27607
+ name = execSync(`ps -o comm= -p ${pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27608
+ } catch {
27609
+ }
27610
+ return { pid, name };
27425
27611
  } catch {
27426
27612
  return null;
27427
27613
  }
27428
27614
  }
27429
- function isSameProcessGroup(pid) {
27430
- if (pid === process.pid || pid === process.ppid) return true;
27431
- try {
27432
- const ourPgid = execSync(`ps -o pgid= -p ${process.pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27433
- const targetPgid = execSync(`ps -o pgid= -p ${pid} 2>/dev/null`, { encoding: "utf-8" }).trim();
27434
- return ourPgid === targetPgid;
27435
- } catch {
27436
- return false;
27437
- }
27438
- }
27439
- function killProcessOnPort(port) {
27440
- const pid = findProcessOnPort(port);
27441
- if (!pid) return false;
27442
- if (isSameProcessGroup(pid)) {
27443
- logger.warn({ port, pid }, `Skipping same process group (PID ${pid}) on port ${port}`);
27444
- return false;
27445
- }
27446
- try {
27447
- process.kill(pid, "SIGTERM");
27448
- logger.warn({ port, pid }, `Killed process ${pid} on port ${port}`);
27449
- return true;
27450
- } catch {
27451
- return false;
27452
- }
27453
- }
27454
27615
  async function checkPortAvailable(port, host = "0.0.0.0") {
27455
27616
  return new Promise((resolve2, reject) => {
27456
27617
  const server2 = net.createServer();
27457
27618
  server2.once("error", (err) => {
27458
27619
  if (err.code === "EADDRINUSE") {
27459
- if (process.env["NODE_ENV"] !== "production") {
27460
- if (killProcessOnPort(port)) {
27461
- setTimeout(() => {
27462
- checkPortAvailable(port, host).then(resolve2).catch(reject);
27463
- }, 500);
27464
- return;
27465
- }
27466
- }
27467
- logger.error({ port }, `Port ${port} is already in use`);
27468
- reject(new Error(`Port ${port} is already in use`));
27620
+ const proc = findProcessOnPort(port);
27621
+ 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`;
27622
+ reject(new Error(msg));
27469
27623
  } else {
27470
27624
  reject(err);
27471
27625
  }
@@ -27476,10 +27630,9 @@ async function checkPortAvailable(port, host = "0.0.0.0") {
27476
27630
  server2.listen(port, host);
27477
27631
  });
27478
27632
  }
27479
- var init_net = __esm({
27480
- "src/core/utils/net.ts"() {
27633
+ var init_port_check = __esm({
27634
+ "src/core/utils/port-check.ts"() {
27481
27635
  "use strict";
27482
- init_logger();
27483
27636
  }
27484
27637
  });
27485
27638
 
@@ -27574,6 +27727,150 @@ var init_tunnel = __esm({
27574
27727
  }
27575
27728
  });
27576
27729
 
27730
+ // src/db/seed-context.ts
27731
+ var seed_context_exports = {};
27732
+ __export(seed_context_exports, {
27733
+ createSeedContext: () => createSeedContext
27734
+ });
27735
+ function createSeedContext(deps) {
27736
+ const { knex: db2, generateId: generateId4, hashPassword: hashPassword2, pluginPrefixes, logger: logger2 } = deps;
27737
+ const permissionsMap = /* @__PURE__ */ new Map();
27738
+ const seedContext = {
27739
+ masters: {
27740
+ register(type2, entries, options) {
27741
+ deps.masterRegistry.register(type2, entries, options);
27742
+ }
27743
+ },
27744
+ roles: {
27745
+ async add(role) {
27746
+ const hasTable = await db2.schema.hasTable("roles");
27747
+ if (!hasTable) return;
27748
+ const existing = await db2("roles").where({ name: role.name }).first();
27749
+ if (!existing) {
27750
+ const description = role.description ? JSON.stringify(typeof role.description === "string" ? { en: role.description } : role.description) : null;
27751
+ await db2("roles").insert({
27752
+ id: generateId4(),
27753
+ name: role.name,
27754
+ description,
27755
+ is_system: role.is_system ?? false
27756
+ });
27757
+ logger2.info({ role: role.name }, "Seeded role");
27758
+ }
27759
+ if (role.permissions) {
27760
+ let rolePerms = permissionsMap.get(role.name);
27761
+ if (!rolePerms) {
27762
+ rolePerms = /* @__PURE__ */ new Map();
27763
+ permissionsMap.set(role.name, rolePerms);
27764
+ }
27765
+ for (const [subject2, actions] of Object.entries(role.permissions)) {
27766
+ const normalized = subject2.charAt(0).toUpperCase() + subject2.slice(1);
27767
+ rolePerms.set(normalized, actions);
27768
+ }
27769
+ }
27770
+ }
27771
+ },
27772
+ users: {
27773
+ async add(user) {
27774
+ const hasTable = await db2.schema.hasTable("users");
27775
+ if (!hasTable) return;
27776
+ const existing = await db2("users").where({ email: user.email }).first();
27777
+ if (existing) return;
27778
+ const userId = generateId4();
27779
+ await db2("users").insert({
27780
+ id: userId,
27781
+ type: user.type ?? "human",
27782
+ email: user.email,
27783
+ password: await hashPassword2(user.password),
27784
+ name: user.name ?? user.email
27785
+ });
27786
+ if (user.roles?.length) {
27787
+ for (const roleName of user.roles) {
27788
+ const role = await db2("roles").where({ name: roleName }).first();
27789
+ if (role) {
27790
+ await db2("user_roles").insert({
27791
+ id: generateId4(),
27792
+ user_id: userId,
27793
+ role_id: role.id
27794
+ });
27795
+ } else {
27796
+ logger2.warn({ role: roleName, user: user.email }, "Role not found, skipping assignment");
27797
+ }
27798
+ }
27799
+ }
27800
+ logger2.info({ email: user.email, roles: user.roles }, "Seeded user");
27801
+ }
27802
+ },
27803
+ plugin(pluginName) {
27804
+ return {
27805
+ entity(entityName) {
27806
+ return {
27807
+ async upsert(data, options) {
27808
+ let prefix = pluginPrefixes.get(pluginName) ?? "";
27809
+ if (!prefix) {
27810
+ for (const [key2, pfx] of pluginPrefixes) {
27811
+ if (pluginName === key2 || pluginName.endsWith(`-${key2}`)) {
27812
+ prefix = pfx;
27813
+ break;
27814
+ }
27815
+ }
27816
+ }
27817
+ const fullTable = `${prefix}${entityName}`;
27818
+ const hasTable = await db2.schema.hasTable(fullTable);
27819
+ if (!hasTable) {
27820
+ logger2.warn({ table: fullTable, plugin: pluginName }, "Table not found for plugin entity seed");
27821
+ return 0;
27822
+ }
27823
+ const key = options?.key ?? "id";
27824
+ let seeded = 0;
27825
+ for (const row of data) {
27826
+ const keyValue = row[key];
27827
+ if (keyValue != null) {
27828
+ const existing = await db2(fullTable).where({ [key]: keyValue }).first();
27829
+ if (existing) continue;
27830
+ }
27831
+ const insertData = { ...row };
27832
+ if (key === "id" && !insertData["id"]) {
27833
+ insertData["id"] = generateId4();
27834
+ }
27835
+ await db2(fullTable).insert(insertData);
27836
+ seeded++;
27837
+ }
27838
+ if (seeded > 0) {
27839
+ logger2.info({ table: fullTable, seeded }, "Seeded plugin entity data");
27840
+ }
27841
+ return seeded;
27842
+ }
27843
+ };
27844
+ }
27845
+ };
27846
+ },
27847
+ async raw(table, data) {
27848
+ logger2.warn({ table }, "Using seed.raw() \u2014 prefer typed helpers when available");
27849
+ const hasTable = await db2.schema.hasTable(table);
27850
+ if (!hasTable) {
27851
+ logger2.warn({ table }, "Table not found for raw seed");
27852
+ return 0;
27853
+ }
27854
+ const rows = Array.isArray(data) ? data : [data];
27855
+ await db2(table).insert(rows);
27856
+ return rows.length;
27857
+ }
27858
+ };
27859
+ return {
27860
+ ctx: seedContext,
27861
+ flushPermissions: () => {
27862
+ if (permissionsMap.size > 0 && deps.onPermissionsCollected) {
27863
+ deps.onPermissionsCollected(permissionsMap);
27864
+ }
27865
+ }
27866
+ };
27867
+ }
27868
+ var init_seed_context = __esm({
27869
+ "src/db/seed-context.ts"() {
27870
+ "use strict";
27871
+ }
27872
+ });
27873
+
27577
27874
  // src/core/server.ts
27578
27875
  var server_exports = {};
27579
27876
  __export(server_exports, {
@@ -27582,7 +27879,9 @@ __export(server_exports, {
27582
27879
  start: () => start,
27583
27880
  stop: () => stop
27584
27881
  });
27882
+ import http from "http";
27585
27883
  import { entityRoom as entityRoom6 } from "@gzl10/nexus-sdk";
27884
+ import { DEFAULT_LOCALES as DEFAULT_LOCALES2 } from "@gzl10/nexus-sdk";
27586
27885
  async function runMigrationsAndSeeds(config3) {
27587
27886
  initLoggerService(getLoggerConfig());
27588
27887
  setLoggerInstance(getPinoLogger());
@@ -27635,7 +27934,7 @@ async function runMigrationsAndSeeds(config3) {
27635
27934
  const sources = buildMigrationSources();
27636
27935
  const migrationFiles = await loadAllMigrationFiles(sources);
27637
27936
  if (migrationFiles.length > 0) {
27638
- logger.info({ count: migrationFiles.length }, "Deploying pending migrations...");
27937
+ logger.info({ sources: sources.length, files: migrationFiles.length }, "Running migration deploy...");
27639
27938
  try {
27640
27939
  await runMigrations(void 0, sources);
27641
27940
  } catch (err) {
@@ -27644,7 +27943,7 @@ async function runMigrationsAndSeeds(config3) {
27644
27943
  throw err;
27645
27944
  }
27646
27945
  }
27647
- logger.info("Checking schema drift...");
27946
+ logger.debug("Checking schema drift...");
27648
27947
  const drift = await detectSchemaDrift();
27649
27948
  if (drift && (drift.newTables.length > 0 || drift.alteredTables.length > 0)) {
27650
27949
  const message = formatDriftMessage(drift);
@@ -27691,7 +27990,7 @@ ${dirs}`);
27691
27990
  }
27692
27991
  const allDefinitions = modules.flatMap((m) => m.definitions ?? []);
27693
27992
  await createMemoryTables(allDefinitions);
27694
- logger.info("Running seeds...");
27993
+ logger.debug("Running seeds...");
27695
27994
  for (const mod of modules) {
27696
27995
  try {
27697
27996
  await runModuleSeed(mod, ctx);
@@ -27699,12 +27998,39 @@ ${dirs}`);
27699
27998
  logger.error({ module: mod.name, err }, "Seed failed - continuing with next module");
27700
27999
  }
27701
28000
  }
28001
+ if (config3?.onSeed) {
28002
+ const { getPlugins: getPlugins2 } = await Promise.resolve().then(() => (init_module_queries(), module_queries_exports));
28003
+ const { createMasterRegistry: createMasterRegistry2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
28004
+ const masterRegistry = ctx.services.has("masters") ? ctx.services.get("masters") : createMasterRegistry2();
28005
+ const pluginPrefixes = /* @__PURE__ */ new Map();
28006
+ for (const plugin of getPlugins2()) {
28007
+ pluginPrefixes.set(plugin.code, `${plugin.code}_`);
28008
+ const shortName = plugin.name.replace(/^@[^/]+\/nexus-plugin-/, "");
28009
+ if (shortName !== plugin.code) {
28010
+ pluginPrefixes.set(shortName, `${plugin.code}_`);
28011
+ }
28012
+ }
28013
+ const { createSeedContext: createSeedContext2 } = await Promise.resolve().then(() => (init_seed_context(), seed_context_exports));
28014
+ const { ctx: seedCtx, flushPermissions } = createSeedContext2({
28015
+ knex: ctx.db.knex,
28016
+ generateId: ctx.core.generateId,
28017
+ hashPassword: ctx.core.crypto.hashPassword,
28018
+ masterRegistry,
28019
+ pluginPrefixes,
28020
+ logger: ctx.core.logger,
28021
+ onPermissionsCollected: (perms) => setSeedPermissions(perms)
28022
+ });
28023
+ await config3.onSeed(seedCtx);
28024
+ await masterRegistry.seed(ctx);
28025
+ flushPermissions();
28026
+ }
27702
28027
  }
27703
28028
  async function start(config3) {
27704
28029
  if (server) {
27705
28030
  throw new Error("Server already running. Call stop() first.");
27706
28031
  }
27707
28032
  currentConfig = config3;
28033
+ setLocales(config3?.locales ?? DEFAULT_LOCALES2);
27708
28034
  if (env.NODE_ENV === "development" && env.FRPC_SERVER && env.FRPC_SUBDOMAIN && !env.BACKEND_URL) {
27709
28035
  process.env["BACKEND_URL"] = getTunnelUrl(env.FRPC_SUBDOMAIN, env.FRPC_SERVER);
27710
28036
  }
@@ -27729,13 +28055,17 @@ async function start(config3) {
27729
28055
  await initTelemetry2();
27730
28056
  await runMigrationsAndSeeds(config3);
27731
28057
  const effectiveCorsOrigins = buildEffectiveCorsOrigins(env.CORS_ORIGIN, config3?.spas);
28058
+ const httpServer = http.createServer();
27732
28059
  const app = await createApp({
27733
28060
  beforeRoutes: config3?.beforeRoutes,
27734
28061
  afterRoutes: config3?.afterRoutes,
27735
- spas: config3?.spas
28062
+ spas: config3?.spas,
28063
+ httpServer
27736
28064
  });
28065
+ httpServer.on("request", app);
27737
28066
  return new Promise((resolve2) => {
27738
- server = app.listen(resolved.port, resolved.host, async () => {
28067
+ server = httpServer;
28068
+ httpServer.listen(resolved.port, resolved.host, async () => {
27739
28069
  const timeoutMs = parseInt(process.env["REQUEST_TIMEOUT_MS"] || "30000", 10);
27740
28070
  if (timeoutMs > 0) {
27741
28071
  server.setTimeout(timeoutMs);
@@ -27771,12 +28101,10 @@ async function start(config3) {
27771
28101
  });
27772
28102
  }
27773
28103
  const baseUrl = env.BACKEND_URL || `http://localhost:${actualPort}`;
27774
- logger.info({ libPath: getLibPath(), projectPath: getProjectPath() }, "Paths");
27775
- logger.info(`API: ${baseUrl}/api/v1`);
27776
- if (resolved.ui.enabled) {
27777
- logger.info(`UI: ${baseUrl}`);
27778
- }
27779
- logger.info({ port: actualPort, mode: resolved.nodeEnv }, "Server started");
28104
+ logger.debug({ libPath: getLibPath(), projectPath: getProjectPath() }, "Paths");
28105
+ const urls = { api: `${baseUrl}/api/v1` };
28106
+ if (resolved.ui.enabled) urls["ui"] = baseUrl;
28107
+ logger.info({ port: actualPort, mode: resolved.nodeEnv, ...urls }, "Server started");
27780
28108
  nexusEvents.emitEvent("server.started", { port: actualPort, host: resolved.host });
27781
28109
  if (config3?.onReady) {
27782
28110
  try {
@@ -27802,7 +28130,8 @@ async function stop() {
27802
28130
  await resetSharedAdapters();
27803
28131
  resetConfigCache();
27804
28132
  clearCustomCaslRules();
27805
- resetServeSPAEndpoints();
28133
+ clearSeedPermissions();
28134
+ await resetServeSPA();
27806
28135
  return;
27807
28136
  }
27808
28137
  if (currentConfig?.beforeClose) {
@@ -27836,7 +28165,8 @@ async function stop() {
27836
28165
  await resetSharedAdapters();
27837
28166
  resetConfigCache();
27838
28167
  clearCustomCaslRules();
27839
- resetServeSPAEndpoints();
28168
+ clearSeedPermissions();
28169
+ await resetServeSPA();
27840
28170
  currentConfig = void 0;
27841
28171
  server = null;
27842
28172
  nexusEvents.emitEvent("server.stopped");
@@ -27896,7 +28226,7 @@ var init_server = __esm({
27896
28226
  init_cors();
27897
28227
  init_logger();
27898
28228
  init_error_middleware();
27899
- init_net();
28229
+ init_port_check();
27900
28230
  init_engine();
27901
28231
  init_context();
27902
28232
  init_db();