@base44-preview/cli 0.0.50-pr.484.ff9acc3 → 0.0.51-pr.480.51cf06a

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/cli/index.js CHANGED
@@ -235098,8 +235098,8 @@ var TOKEN_REFRESH_BUFFER_MS = 60 * 1000;
235098
235098
  var refreshPromise = null;
235099
235099
  async function readAuth() {
235100
235100
  try {
235101
- const parsed = await readJsonFile(getAuthFilePath());
235102
- const result = AuthDataSchema.safeParse(parsed);
235101
+ const authData = await readJsonFile(getAuthFilePath());
235102
+ const result = AuthDataSchema.safeParse(authData);
235103
235103
  if (!result.success) {
235104
235104
  throw new SchemaValidationError("Invalid authentication data", result.error, getAuthFilePath());
235105
235105
  }
@@ -241591,9 +241591,13 @@ var BackendFunctionToolConfigSchema = exports_external.object({
241591
241591
  function_name: exports_external.string().min(1),
241592
241592
  description: exports_external.string().default("agent backend function")
241593
241593
  });
241594
+ var CodeModeToolConfigSchema = exports_external.object({
241595
+ code_mode: exports_external.union([exports_external.boolean(), exports_external.record(exports_external.string(), exports_external.unknown())])
241596
+ });
241594
241597
  var ToolConfigSchema = exports_external.union([
241595
241598
  EntityToolConfigSchema,
241596
- BackendFunctionToolConfigSchema
241599
+ BackendFunctionToolConfigSchema,
241600
+ CodeModeToolConfigSchema
241597
241601
  ]);
241598
241602
  var AgentConfigSchema = exports_external.looseObject({
241599
241603
  name: exports_external.string().trim().min(1).max(100),
@@ -242133,6 +242137,16 @@ async function updateSSOConfig(authDir, provider, enable) {
242133
242137
  await writeAuthConfig(authDir, merged);
242134
242138
  return merged;
242135
242139
  }
242140
+
242141
+ class MissingSSOFieldsError extends InvalidInputError {
242142
+ missingKeys;
242143
+ provider;
242144
+ constructor(provider, missingKeys) {
242145
+ super(`Missing required fields for ${provider}: ${missingKeys.join(", ")}`);
242146
+ this.provider = provider;
242147
+ this.missingKeys = missingKeys;
242148
+ }
242149
+ }
242136
242150
  function buildSSOSecrets(provider, options) {
242137
242151
  const schema3 = SSO_PROVIDER_SCHEMAS[provider];
242138
242152
  const secrets = {};
@@ -242168,7 +242182,7 @@ function buildSSOSecrets(provider, options) {
242168
242182
  missing.push("sso_name" /* Name */);
242169
242183
  }
242170
242184
  if (missing.length > 0) {
242171
- throw new InvalidInputError(`Missing required fields for ${provider}: ${missing.join(", ")}`);
242185
+ throw new MissingSSOFieldsError(provider, missing);
242172
242186
  }
242173
242187
  return Object.fromEntries(Object.entries(secrets).filter(([, v]) => v.length > 0));
242174
242188
  }
@@ -243538,7 +243552,7 @@ import { join as join9 } from "node:path";
243538
243552
  // package.json
243539
243553
  var package_default = {
243540
243554
  name: "base44",
243541
- version: "0.0.50",
243555
+ version: "0.0.51",
243542
243556
  description: "Base44 CLI - Unified interface for managing Base44 applications",
243543
243557
  type: "module",
243544
243558
  bin: {
@@ -243593,6 +243607,7 @@ var package_default = {
243593
243607
  "@types/multer": "^2.0.0",
243594
243608
  "@types/node": "^22.10.5",
243595
243609
  "@vercel/detect-agent": "^1.1.0",
243610
+ outdent: "^0.8.0",
243596
243611
  chalk: "^5.6.2",
243597
243612
  chokidar: "^5.0.0",
243598
243613
  commander: "^12.1.0",
@@ -251218,7 +251233,7 @@ function getSocialLoginCommand() {
251218
251233
  // src/cli/commands/auth/sso.ts
251219
251234
  import { dirname as dirname11, join as join16, resolve as resolve3 } from "node:path";
251220
251235
  var SSOConfigFileSchema = exports_external.object({
251221
- provider: exports_external.enum(["google", "microsoft", "github", "okta", "custom"]),
251236
+ provider: exports_external.enum(Object.values(KNOWN_SSO_PROVIDERS)),
251222
251237
  clientId: exports_external.string(),
251223
251238
  clientSecret: exports_external.string(),
251224
251239
  scope: exports_external.string().optional(),
@@ -251236,10 +251251,7 @@ async function loadSSOConfigFile(filePath) {
251236
251251
  const raw2 = await readJsonFile(resolved);
251237
251252
  const result = SSOConfigFileSchema.safeParse(raw2);
251238
251253
  if (!result.success) {
251239
- const issues = result.error.issues.map((i2) => ` ${i2.path.join(".")}: ${i2.message}`).join(`
251240
- `);
251241
- throw new InvalidInputError(`Invalid SSO config file ${filePath}:
251242
- ${issues}`);
251254
+ throw new SchemaValidationError("Invalid SSO config file", result.error, filePath);
251243
251255
  }
251244
251256
  return result.data;
251245
251257
  }
@@ -251262,6 +251274,32 @@ function mergeFileWithFlags(fileConfig, options) {
251262
251274
  };
251263
251275
  }
251264
251276
  var providerNames = Object.keys(KNOWN_SSO_PROVIDERS);
251277
+ var SECRET_KEY_TO_FLAG = {
251278
+ ["sso_name" /* Name */]: "--sso-name",
251279
+ ["sso_client_id" /* ClientId */]: "--client-id",
251280
+ ["sso_client_secret" /* ClientSecret */]: "--client-secret",
251281
+ ["sso_scope" /* Scope */]: "--scope",
251282
+ ["sso_discovery_url" /* DiscoveryUrl */]: "--discovery-url",
251283
+ ["sso_tenant_id" /* TenantId */]: "--tenant-id",
251284
+ ["sso_auth_endpoint" /* AuthEndpoint */]: "--auth-endpoint",
251285
+ ["sso_token_endpoint" /* TokenEndpoint */]: "--token-endpoint",
251286
+ ["sso_userinfo_endpoint" /* UserinfoEndpoint */]: "--userinfo-endpoint",
251287
+ ["sso_okta_domain" /* OktaDomain */]: "--okta-domain",
251288
+ ["sso_jwks_uri" /* JwksUri */]: "--jwks-uri"
251289
+ };
251290
+ function secretKeyToFlag(key) {
251291
+ return SECRET_KEY_TO_FLAG[key];
251292
+ }
251293
+ function exampleCommand(provider) {
251294
+ let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
251295
+ if (provider === KNOWN_SSO_PROVIDERS.microsoft)
251296
+ cmd += " --tenant-id <id>";
251297
+ if (provider === KNOWN_SSO_PROVIDERS.okta)
251298
+ cmd += " --okta-domain <domain>";
251299
+ if (provider === KNOWN_SSO_PROVIDERS.custom)
251300
+ cmd += " --sso-name <name> --auth-endpoint <url> --token-endpoint <url> --userinfo-endpoint <url> --jwks-uri <url>";
251301
+ return cmd;
251302
+ }
251265
251303
  function validateProvider(provider) {
251266
251304
  if (!provider) {
251267
251305
  throw new InvalidInputError("Missing --provider.", {
@@ -251273,12 +251311,12 @@ function validateProvider(provider) {
251273
251311
  ]
251274
251312
  });
251275
251313
  }
251276
- if (!(provider in KNOWN_SSO_PROVIDERS)) {
251277
- throw new InvalidInputError(`Unknown provider "${provider}". Valid providers: ${providerNames.join(", ")}`);
251278
- }
251279
251314
  return provider;
251280
251315
  }
251281
251316
  async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options) {
251317
+ if (options.file && options.envFile) {
251318
+ throw new InvalidInputError("--file and --env-file cannot be used together. Provide the client secret either inside --file or via --env-file.");
251319
+ }
251282
251320
  let merged = options;
251283
251321
  if (options.file) {
251284
251322
  const fileConfig = await loadSSOConfigFile(options.file);
@@ -251338,7 +251376,23 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
251338
251376
  jwksUri: merged.jwksUri,
251339
251377
  ssoName: merged.ssoName
251340
251378
  };
251341
- const secrets = buildSSOSecrets(provider, secretOptions);
251379
+ let secrets;
251380
+ try {
251381
+ secrets = buildSSOSecrets(provider, secretOptions);
251382
+ } catch (error48) {
251383
+ if (error48 instanceof MissingSSOFieldsError) {
251384
+ const flagNames = error48.missingKeys.map(secretKeyToFlag);
251385
+ throw new InvalidInputError(`Missing required fields for ${error48.provider}: ${flagNames.join(", ")}`, {
251386
+ hints: [
251387
+ {
251388
+ message: `Example: ${exampleCommand(provider)}`,
251389
+ command: exampleCommand(provider)
251390
+ }
251391
+ ]
251392
+ });
251393
+ }
251394
+ throw error48;
251395
+ }
251342
251396
  const { project: project2 } = await readProjectConfig();
251343
251397
  const configDir = dirname11(project2.configPath);
251344
251398
  const authDir = join16(configDir, project2.authDir);
@@ -251348,29 +251402,36 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
251348
251402
  outroMessage: `SSO configured with ${provider} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
251349
251403
  };
251350
251404
  }
251351
- async function ssoDisableAction({
251352
- runTask: runTask2
251353
- }) {
251405
+ function hasEnableOnlyOptions(options) {
251406
+ return Boolean(options.provider || options.clientId || options.clientSecret || options.clientSecretStdin || options.envFile || options.file || options.scope || options.discoveryUrl || options.tenantId || options.oktaDomain || options.authEndpoint || options.tokenEndpoint || options.userinfoEndpoint || options.jwksUri || options.ssoName);
251407
+ }
251408
+ async function ssoDisableAction({ log, runTask: runTask2 }, options) {
251409
+ if (hasEnableOnlyOptions(options)) {
251410
+ throw new InvalidInputError("Configuration options cannot be used with disable. To disable SSO: base44 auth sso disable");
251411
+ }
251354
251412
  const { project: project2 } = await readProjectConfig();
251355
251413
  const configDir = dirname11(project2.configPath);
251356
251414
  const authDir = join16(configDir, project2.authDir);
251357
- await runTask2("Updating local auth config", async () => updateSSOConfig(authDir, null, false));
251415
+ const updated = await runTask2("Updating local auth config", async () => updateSSOConfig(authDir, null, false));
251358
251416
  await runTask2("Removing SSO credentials", async () => deleteSSOSecrets());
251417
+ if (!hasAnyLoginMethod(updated)) {
251418
+ log.warn("Disabling SSO will leave no login methods enabled. Users will be locked out.");
251419
+ }
251359
251420
  return {
251360
251421
  outroMessage: "SSO disabled in local config and credentials removed. Run `base44 auth push` or `base44 deploy` to apply."
251361
251422
  };
251362
251423
  }
251363
251424
  async function ssoAction(context, action, options) {
251364
251425
  if (action === "disable") {
251365
- return ssoDisableAction(context);
251426
+ return ssoDisableAction(context, options);
251366
251427
  }
251367
251428
  return ssoEnableAction(context, options);
251368
251429
  }
251369
251430
  function getSSOCommand() {
251370
- return new Base44Command("sso").description("Configure SSO identity provider (google, microsoft, github, okta, custom)").addArgument(new Argument("<action>", "enable or disable SSO").choices([
251431
+ return new Base44Command("sso").description("Configure SSO identity provider (google, microsoft, github, okta, custom). SSO and social login are mutually exclusive — enabling one disables the other in the local auth config.").addArgument(new Argument("<action>", "enable or disable SSO").choices([
251371
251432
  "enable",
251372
251433
  "disable"
251373
- ])).option("--provider <provider>", "SSO provider: google, microsoft, github, okta, custom").option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--client-secret-stdin", "Read client secret from stdin").option("--env-file <path>", "Read client secret from a .env file (key: sso_client_secret)").option("--file <path>", "JSON config file with all SSO settings").option("--scope <scope>", "OAuth scope (defaults per provider)").option("--discovery-url <url>", "OIDC discovery URL").option("--tenant-id <id>", "Microsoft tenant ID (required for microsoft)").option("--okta-domain <domain>", "Okta domain (required for okta)").option("--auth-endpoint <url>", "Authorization endpoint (required for custom)").option("--token-endpoint <url>", "Token endpoint (required for custom)").option("--userinfo-endpoint <url>", "Userinfo endpoint (required for custom)").option("--jwks-uri <url>", "JWKS URI (required for custom)").option("--sso-name <name>", "Provider display name (required for custom)").action(ssoAction);
251434
+ ])).addOption(new Option("--provider <provider>", "SSO provider").choices(Object.values(KNOWN_SSO_PROVIDERS))).option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--client-secret-stdin", "Read client secret from stdin").option("--env-file <path>", "Read client secret from a .env file (key: sso_client_secret)").option("--file <path>", "JSON config file with all SSO settings").option("--scope <scope>", "OAuth scope (defaults per provider)").option("--discovery-url <url>", "OIDC discovery URL").option("--tenant-id <id>", "Microsoft tenant ID (required for microsoft)").option("--okta-domain <domain>", "Okta domain (required for okta)").option("--auth-endpoint <url>", "Authorization endpoint (required for custom)").option("--token-endpoint <url>", "Token endpoint (required for custom)").option("--userinfo-endpoint <url>", "Userinfo endpoint (required for custom)").option("--jwks-uri <url>", "JWKS URI (required for custom)").option("--sso-name <name>", "Provider display name (required for custom)").action(ssoAction);
251374
251435
  }
251375
251436
 
251376
251437
  // src/cli/commands/auth/index.ts
@@ -253632,15 +253693,21 @@ class FunctionManager {
253632
253693
  this.setupProcessHandlers(name2, process21);
253633
253694
  return this.waitForReady(name2, runningFunc);
253634
253695
  }
253635
- reload(functions) {
253636
- this.stopAll();
253696
+ async reload(functions) {
253697
+ await this.stopAll();
253637
253698
  this.functions = new Map(functions.map((f7) => [f7.name, f7]));
253638
253699
  }
253639
- stopAll() {
253640
- for (const [name2, { process: process21 }] of this.running) {
253700
+ async stopAll() {
253701
+ await Promise.all(Array.from(this.running, ([name2, { process: proc2 }]) => {
253641
253702
  this.logger.log(`Stopping function: ${name2}`);
253642
- process21.kill();
253643
- }
253703
+ const exited = new Promise((r5) => proc2.once("exit", () => r5()));
253704
+ if (process.platform === "win32" && proc2.pid) {
253705
+ spawn2("taskkill", ["/pid", String(proc2.pid), "/T", "/F"]);
253706
+ } else {
253707
+ proc2.kill();
253708
+ }
253709
+ return exited;
253710
+ }));
253644
253711
  this.running.clear();
253645
253712
  this.starting.clear();
253646
253713
  }
@@ -254028,6 +254095,9 @@ class Database {
254028
254095
  getCollection(name2) {
254029
254096
  return this.collections.get(this.normalizeName(name2));
254030
254097
  }
254098
+ getSchema(entityName) {
254099
+ return this.schemas.get(this.normalizeName(entityName));
254100
+ }
254031
254101
  getCollectionNames() {
254032
254102
  return Array.from(this.collections.keys()).filter((name2) => {
254033
254103
  return !name2.startsWith(PRIVATE_COLLECTION_PREFIX);
@@ -254255,6 +254325,120 @@ In order to complete registration use this verification code: ${otpCode}
254255
254325
  // src/cli/dev/dev-server/routes/entities/entities-router.ts
254256
254326
  var import_express4 = __toESM(require_express(), 1);
254257
254327
 
254328
+ // src/cli/dev/dev-server/db/rls.ts
254329
+ function getRLSFieldValue(key2, source3) {
254330
+ const DATA_FIELD_PREFIX = "data.";
254331
+ return source3[key2.startsWith(DATA_FIELD_PREFIX) ? key2.slice(DATA_FIELD_PREFIX.length) : key2];
254332
+ }
254333
+ function resolveTemplate(value, user) {
254334
+ return value.replace(/\{\{user\.([\w.]+)\}\}/g, (_match, path18) => {
254335
+ return String(getRLSFieldValue(path18, user) ?? "");
254336
+ });
254337
+ }
254338
+ function evaluateOperator(recordValue, operator) {
254339
+ for (const [op2, opValue] of Object.entries(operator)) {
254340
+ switch (op2) {
254341
+ case "$in":
254342
+ if (!Array.isArray(opValue) || !opValue.includes(recordValue)) {
254343
+ return false;
254344
+ }
254345
+ break;
254346
+ case "$nin":
254347
+ if (!Array.isArray(opValue) || opValue.includes(recordValue)) {
254348
+ return false;
254349
+ }
254350
+ break;
254351
+ case "$ne":
254352
+ if (recordValue === opValue)
254353
+ return false;
254354
+ break;
254355
+ case "$all":
254356
+ if (!Array.isArray(recordValue) || !Array.isArray(opValue)) {
254357
+ return false;
254358
+ }
254359
+ if (!opValue.every((v10) => recordValue.includes(v10))) {
254360
+ return false;
254361
+ }
254362
+ break;
254363
+ default:
254364
+ return false;
254365
+ }
254366
+ }
254367
+ return true;
254368
+ }
254369
+ function evaluateUserCondition(condition, user) {
254370
+ for (const [key2, expected] of Object.entries(condition)) {
254371
+ const userValue = getRLSFieldValue(key2, user);
254372
+ if (typeof expected === "object" && expected !== null) {
254373
+ if (!evaluateOperator(userValue, expected))
254374
+ return false;
254375
+ } else {
254376
+ if (userValue !== expected)
254377
+ return false;
254378
+ }
254379
+ }
254380
+ return true;
254381
+ }
254382
+ function evaluateCondition(condition, record2, user) {
254383
+ for (const [key2, value] of Object.entries(condition)) {
254384
+ if (key2 === "user_condition") {
254385
+ if (!evaluateUserCondition(value, user))
254386
+ return false;
254387
+ continue;
254388
+ }
254389
+ if (key2 === "$or") {
254390
+ const conditions = value;
254391
+ if (!conditions.some((c8) => evaluateCondition(c8, record2, user)))
254392
+ return false;
254393
+ continue;
254394
+ }
254395
+ if (key2 === "$and") {
254396
+ const conditions = value;
254397
+ if (!conditions.every((c8) => evaluateCondition(c8, record2, user)))
254398
+ return false;
254399
+ continue;
254400
+ }
254401
+ if (key2 === "$nor") {
254402
+ const conditions = value;
254403
+ if (conditions.some((c8) => evaluateCondition(c8, record2, user)))
254404
+ return false;
254405
+ continue;
254406
+ }
254407
+ const recordValue = getRLSFieldValue(key2, record2);
254408
+ const resolvedValue = typeof value === "string" ? resolveTemplate(value, user) : value;
254409
+ if (typeof resolvedValue === "object" && resolvedValue !== null) {
254410
+ if (!evaluateOperator(recordValue, resolvedValue))
254411
+ return false;
254412
+ } else {
254413
+ if (recordValue !== resolvedValue)
254414
+ return false;
254415
+ }
254416
+ }
254417
+ return true;
254418
+ }
254419
+ function checkRLS(rule, record2, user) {
254420
+ if (rule === undefined)
254421
+ return true;
254422
+ if (typeof rule === "boolean")
254423
+ return rule;
254424
+ if (!user)
254425
+ return false;
254426
+ return evaluateCondition(rule, record2, user);
254427
+ }
254428
+ function applyFLS(record2, schema10, user, operation) {
254429
+ if (Array.isArray(record2)) {
254430
+ return record2.map((r5) => applyFLS(r5, schema10, user, operation));
254431
+ }
254432
+ const result = {};
254433
+ for (const [key2, value] of Object.entries(record2)) {
254434
+ const rule = schema10.properties[key2]?.rls?.[operation];
254435
+ if (!rule || checkRLS(rule, record2, user)) {
254436
+ result[key2] = value;
254437
+ }
254438
+ }
254439
+ return result;
254440
+ }
254441
+
254258
254442
  // src/cli/dev/dev-server/db/entity-queries.ts
254259
254443
  function parseSort(sort) {
254260
254444
  if (!sort) {
@@ -254312,30 +254496,54 @@ var queryEntity = async (collection, reqQuery) => {
254312
254496
  return cursor3;
254313
254497
  };
254314
254498
 
254499
+ // src/cli/dev/dev-server/routes/entities/current-user.ts
254500
+ var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
254501
+ function getSubject(payload) {
254502
+ if (!payload || typeof payload === "string") {
254503
+ return;
254504
+ }
254505
+ return payload.sub;
254506
+ }
254507
+ async function resolveCurrentUser(db2, req) {
254508
+ const auth2 = req.headers.authorization;
254509
+ if (!auth2?.startsWith("Bearer ")) {
254510
+ return { ok: false, reason: "missing" };
254511
+ }
254512
+ try {
254513
+ const decoded = import_jsonwebtoken2.default.decode(auth2.replace("Bearer ", ""), {
254514
+ complete: true
254515
+ });
254516
+ const subject = getSubject(decoded?.payload);
254517
+ if (!subject) {
254518
+ return { ok: false, reason: "invalid" };
254519
+ }
254520
+ const currentUser = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: subject });
254521
+ if (!currentUser) {
254522
+ return { ok: false, reason: "not_found" };
254523
+ }
254524
+ return { ok: true, user: currentUser };
254525
+ } catch {
254526
+ return { ok: false, reason: "invalid" };
254527
+ }
254528
+ }
254529
+
254315
254530
  // src/cli/dev/dev-server/routes/entities/entities-user-router.ts
254316
254531
  var import_express3 = __toESM(require_express(), 1);
254317
- var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
254318
254532
  function createUserRouter(db2, logger2) {
254319
254533
  const router = import_express3.Router({ mergeParams: true });
254320
254534
  const parseBody = import_express3.json();
254321
254535
  function withAuth(handler) {
254322
254536
  return async (req, res) => {
254323
- const auth2 = req.headers.authorization;
254324
- if (!auth2 || !auth2.startsWith("Bearer ")) {
254537
+ const currentUserResult = await resolveCurrentUser(db2, req);
254538
+ if (!currentUserResult.ok && (currentUserResult.reason === "missing" || currentUserResult.reason === "invalid")) {
254325
254539
  res.status(401).json({ error: "Unauthorized" });
254326
254540
  return;
254327
254541
  }
254328
- try {
254329
- const { payload } = import_jsonwebtoken2.default.decode(auth2.replace("Bearer ", ""), { complete: true }) ?? {};
254330
- const result = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: payload?.sub });
254331
- if (!result) {
254332
- res.status(404).json({ error: "Unable to read data for the current user" });
254333
- return;
254334
- }
254335
- await handler(req, res, result);
254336
- } catch {
254337
- res.status(401).json({ error: "Unauthorized" });
254542
+ if (!currentUserResult.ok) {
254543
+ res.status(404).json({ error: "Unable to read data for the current user" });
254544
+ return;
254338
254545
  }
254546
+ await handler(req, res, currentUserResult.user);
254339
254547
  };
254340
254548
  }
254341
254549
  router.get("/:id", withAuth(async (req, res, currentUser) => {
@@ -254435,12 +254643,20 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254435
254643
  const parseBody = import_express4.json();
254436
254644
  function withCollection(handler) {
254437
254645
  return async (req, res) => {
254438
- const collection = db2.getCollection(req.params.entityName);
254646
+ const { entityName } = req.params;
254647
+ const collection = db2.getCollection(entityName);
254439
254648
  if (!collection) {
254440
- res.status(404).json({ error: `Entity "${req.params.entityName}" not found` });
254649
+ res.status(404).json({ error: `Entity "${entityName}" not found` });
254650
+ return;
254651
+ }
254652
+ const schema10 = db2.getSchema(entityName);
254653
+ if (!schema10) {
254654
+ res.status(404).json({ error: `Schema for "${entityName}" not found` });
254441
254655
  return;
254442
254656
  }
254443
- await handler(req, res, collection);
254657
+ const currentUserResult = await resolveCurrentUser(db2, req);
254658
+ const currentUser = currentUserResult.ok ? currentUserResult.user : undefined;
254659
+ await handler(req, res, collection, schema10, currentUser);
254444
254660
  };
254445
254661
  }
254446
254662
  function emit(appId, entityName, type, data) {
@@ -254458,9 +254674,31 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254458
254674
  }
254459
254675
  broadcast(appId, entityName, createData(data));
254460
254676
  }
254677
+ function prepareCreateRecord(entityName, body, schema10, currentUser, now) {
254678
+ const { _id, ...recordBody } = body;
254679
+ const ownerFields = {
254680
+ created_by: currentUser?.email,
254681
+ created_by_id: currentUser?.id
254682
+ };
254683
+ if (!checkRLS(schema10.rls?.create, {
254684
+ ...recordBody,
254685
+ ...ownerFields
254686
+ }, currentUser)) {
254687
+ return;
254688
+ }
254689
+ const filteredBody = applyFLS(db2.prepareRecord(entityName, recordBody), schema10, currentUser, "write");
254690
+ db2.validate(entityName, filteredBody);
254691
+ return {
254692
+ ...filteredBody,
254693
+ id: nanoid3(),
254694
+ ...ownerFields,
254695
+ created_date: now,
254696
+ updated_date: now
254697
+ };
254698
+ }
254461
254699
  const userRouter = createUserRouter(db2, logger2);
254462
254700
  router.use("/User", userRouter);
254463
- router.get("/:entityName/:id", withCollection(async (req, res, collection) => {
254701
+ router.get("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
254464
254702
  const { entityName, id: id2 } = req.params;
254465
254703
  try {
254466
254704
  const doc2 = await collection.findOneAsync({ id: id2 });
@@ -254468,16 +254706,28 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254468
254706
  res.status(404).json({ error: `Record with id "${id2}" not found` });
254469
254707
  return;
254470
254708
  }
254471
- res.json(stripInternalFields(doc2));
254709
+ if (!checkRLS(schema10.rls?.read, doc2, currentUser)) {
254710
+ res.status(404).json({
254711
+ message: `Entity ${entityName} with ID ${id2} not found`
254712
+ });
254713
+ return;
254714
+ }
254715
+ const result = applyFLS(stripInternalFields(doc2), schema10, currentUser, "read");
254716
+ res.json(result);
254472
254717
  } catch (error48) {
254473
254718
  logger2.error(`Error in GET /${entityName}/${id2}:`, error48);
254474
254719
  res.status(500).json({ error: "Internal server error" });
254475
254720
  }
254476
254721
  }));
254477
- router.get("/:entityName", withCollection(async (req, res, collection) => {
254722
+ router.get("/:entityName", withCollection(async (req, res, collection, schema10, currentUser) => {
254478
254723
  const { entityName } = req.params;
254479
254724
  try {
254480
- res.json(stripInternalFields(await queryEntity(collection, req.query)));
254725
+ let results = stripInternalFields(await queryEntity(collection, req.query));
254726
+ if (schema10.rls?.read && schema10.rls.read !== true) {
254727
+ results = results.filter((doc2) => checkRLS(schema10.rls.read, doc2, currentUser));
254728
+ }
254729
+ results = results.map((doc2) => applyFLS(doc2, schema10, currentUser, "read"));
254730
+ res.json(results);
254481
254731
  } catch (error48) {
254482
254732
  if (error48 instanceof InvalidInputError) {
254483
254733
  res.status(400).json({ error: error48.message });
@@ -254487,20 +254737,16 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254487
254737
  }
254488
254738
  }
254489
254739
  }));
254490
- router.post("/:entityName", parseBody, withCollection(async (req, res, collection) => {
254740
+ router.post("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254491
254741
  const { appId, entityName } = req.params;
254492
254742
  try {
254493
254743
  const now = new Date().toISOString();
254494
- const { _id, ...body } = req.body;
254495
- const filteredBody = db2.prepareRecord(entityName, body);
254496
- db2.validate(entityName, filteredBody);
254497
- const record2 = {
254498
- ...filteredBody,
254499
- id: nanoid3(),
254500
- created_date: now,
254501
- updated_date: now
254502
- };
254503
- const inserted = stripInternalFields(await collection.insertAsync(record2));
254744
+ const record2 = prepareCreateRecord(entityName, req.body, schema10, currentUser, now);
254745
+ if (!record2) {
254746
+ res.status(403).json({ error: "Permission denied" });
254747
+ return;
254748
+ }
254749
+ const inserted = applyFLS(stripInternalFields(await collection.insertAsync(record2)), schema10, currentUser, "read");
254504
254750
  emit(appId, entityName, "create", inserted);
254505
254751
  res.status(201).json(inserted);
254506
254752
  } catch (error48) {
@@ -254512,7 +254758,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254512
254758
  res.status(500).json({ error: "Internal server error" });
254513
254759
  }
254514
254760
  }));
254515
- router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection) => {
254761
+ router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254516
254762
  const { appId, entityName } = req.params;
254517
254763
  if (!Array.isArray(req.body)) {
254518
254764
  res.status(400).json({ error: "Request body must be an array" });
@@ -254521,17 +254767,15 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254521
254767
  try {
254522
254768
  const now = new Date().toISOString();
254523
254769
  const records = [];
254524
- for (const record2 of req.body) {
254525
- const filteredRecord = db2.prepareRecord(entityName, record2);
254526
- db2.validate(entityName, filteredRecord);
254527
- records.push({
254528
- ...filteredRecord,
254529
- id: nanoid3(),
254530
- created_date: now,
254531
- updated_date: now
254532
- });
254770
+ for (const body of req.body) {
254771
+ const record2 = prepareCreateRecord(entityName, body, schema10, currentUser, now);
254772
+ if (!record2) {
254773
+ res.status(403).json({ error: "Permission denied" });
254774
+ return;
254775
+ }
254776
+ records.push(record2);
254533
254777
  }
254534
- const inserted = stripInternalFields(await collection.insertAsync(records));
254778
+ const inserted = applyFLS(stripInternalFields(await collection.insertAsync(records)), schema10, currentUser, "read");
254535
254779
  emit(appId, entityName, "create", inserted);
254536
254780
  res.status(201).json(inserted);
254537
254781
  } catch (error48) {
@@ -254543,11 +254787,24 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254543
254787
  res.status(500).json({ error: "Internal server error" });
254544
254788
  }
254545
254789
  }));
254546
- router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection) => {
254790
+ router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254547
254791
  const { appId, entityName, id: id2 } = req.params;
254548
254792
  const { id: _id, created_date: _created_date, ...body } = req.body;
254549
254793
  try {
254550
- const filteredBody = db2.prepareRecord(entityName, body, true);
254794
+ if (schema10.rls?.update !== undefined) {
254795
+ const existing = await collection.findOneAsync({ id: id2 });
254796
+ if (!existing) {
254797
+ res.status(404).json({ error: `Record with id "${id2}" not found` });
254798
+ return;
254799
+ }
254800
+ if (!checkRLS(schema10.rls.update, existing, currentUser)) {
254801
+ res.status(404).json({
254802
+ message: `Entity ${entityName} with ID ${id2} not found`
254803
+ });
254804
+ return;
254805
+ }
254806
+ }
254807
+ const filteredBody = applyFLS(db2.prepareRecord(entityName, body, true), schema10, currentUser, "write");
254551
254808
  db2.validate(entityName, filteredBody, true);
254552
254809
  const updateData = {
254553
254810
  ...filteredBody,
@@ -254558,7 +254815,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254558
254815
  res.status(404).json({ error: `Record with id "${id2}" not found` });
254559
254816
  return;
254560
254817
  }
254561
- const updated = stripInternalFields(result.affectedDocuments);
254818
+ const updated = applyFLS(stripInternalFields(result.affectedDocuments), schema10, currentUser, "read");
254562
254819
  emit(appId, entityName, "update", updated);
254563
254820
  res.json(updated);
254564
254821
  } catch (error48) {
@@ -254570,30 +254827,48 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254570
254827
  res.status(500).json({ error: "Internal server error" });
254571
254828
  }
254572
254829
  }));
254573
- router.delete("/:entityName/:id", withCollection(async (req, res, collection) => {
254830
+ router.delete("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
254574
254831
  const { appId, entityName, id: id2 } = req.params;
254575
254832
  try {
254576
254833
  const doc2 = await collection.findOneAsync({ id: id2 });
254577
- const numRemoved = await collection.removeAsync({ id: id2 }, { multi: false });
254578
- if (numRemoved === 0) {
254834
+ if (!doc2) {
254579
254835
  res.status(404).json({ error: `Record with id "${id2}" not found` });
254580
254836
  return;
254581
254837
  }
254582
- if (doc2) {
254583
- emit(appId, entityName, "delete", stripInternalFields(doc2));
254838
+ if (!checkRLS(schema10.rls?.delete, doc2, currentUser)) {
254839
+ res.status(404).json({
254840
+ message: `Entity ${entityName} with ID ${id2} not found`
254841
+ });
254842
+ return;
254584
254843
  }
254844
+ await collection.removeAsync({ id: id2 }, { multi: false });
254845
+ emit(appId, entityName, "delete", stripInternalFields(doc2));
254585
254846
  res.json({ success: true });
254586
254847
  } catch (error48) {
254587
254848
  logger2.error(`Error in DELETE /${entityName}/${id2}:`, error48);
254588
254849
  res.status(500).json({ error: "Internal server error" });
254589
254850
  }
254590
254851
  }));
254591
- router.delete("/:entityName", parseBody, withCollection(async (req, res, collection) => {
254852
+ router.delete("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254592
254853
  const { entityName } = req.params;
254593
254854
  try {
254594
254855
  const query = req.body || {};
254595
- const numRemoved = await collection.removeAsync(query, { multi: true });
254596
- res.json({ success: true, deleted: numRemoved });
254856
+ const rlsDelete = schema10?.rls?.delete;
254857
+ if (rlsDelete !== undefined && rlsDelete !== true) {
254858
+ if (rlsDelete === false) {
254859
+ res.status(403).json({ error: "Permission denied" });
254860
+ return;
254861
+ }
254862
+ const docs = await collection.findAsync(query);
254863
+ const allowedIds = docs.filter((doc2) => checkRLS(rlsDelete, doc2, currentUser)).map((doc2) => doc2.id);
254864
+ const numRemoved = await collection.removeAsync({ id: { $in: allowedIds } }, { multi: true });
254865
+ res.json({ success: true, deleted: numRemoved });
254866
+ } else {
254867
+ const numRemoved = await collection.removeAsync(query, {
254868
+ multi: true
254869
+ });
254870
+ res.json({ success: true, deleted: numRemoved });
254871
+ }
254597
254872
  } catch (error48) {
254598
254873
  logger2.error(`Error in DELETE /${entityName}:`, error48);
254599
254874
  res.status(500).json({ error: "Internal server error" });
@@ -256393,7 +256668,9 @@ var DEFAULT_PORT = 4400;
256393
256668
  var BASE44_APP_URL = "https://base44.app";
256394
256669
  async function createDevServer(options8) {
256395
256670
  const { port: userPort } = options8;
256396
- const port = userPort ?? await getPorts({ port: DEFAULT_PORT });
256671
+ const port = userPort ?? await getPorts({
256672
+ port: process.env.IS_TEST === "true" ? undefined : DEFAULT_PORT
256673
+ });
256397
256674
  const baseUrl = `http://localhost:${port}`;
256398
256675
  const { functions, entities, project: project2 } = await options8.loadResources();
256399
256676
  const app = import_express6.default();
@@ -256486,7 +256763,7 @@ async function createDevServer(options8) {
256486
256763
  const { functions: functions2, entities: entities2 } = await options8.loadResources();
256487
256764
  if (name2 === "functions") {
256488
256765
  const previousFunctionCount = functionManager.getFunctionNames().length;
256489
- functionManager.reload(functions2);
256766
+ await functionManager.reload(functions2);
256490
256767
  const names = functionManager.getFunctionNames();
256491
256768
  if (names.length > 0) {
256492
256769
  devLogger.log(`Reloaded functions: ${names.sort().join(", ")}`);
@@ -256511,10 +256788,10 @@ async function createDevServer(options8) {
256511
256788
  }
256512
256789
  });
256513
256790
  await base44ConfigWatcher.start();
256514
- const shutdown = () => {
256791
+ const shutdown = async () => {
256515
256792
  base44ConfigWatcher.close();
256516
256793
  io6.close();
256517
- functionManager.stopAll();
256794
+ await functionManager.stopAll();
256518
256795
  server.close();
256519
256796
  };
256520
256797
  process.on("SIGINT", shutdown);
@@ -261005,4 +261282,4 @@ export {
261005
261282
  CLIExitError
261006
261283
  };
261007
261284
 
261008
- //# debugId=3D7486B2A6408CC364756E2164756E21
261285
+ //# debugId=827C7659385FB39764756E2164756E21