@base44-preview/cli 0.0.50-pr.487.fc1c45a → 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,8 +251274,21 @@ 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
+ };
251265
251290
  function secretKeyToFlag(key) {
251266
- return `--${key.replace(/^sso_/, "").replace(/_/g, "-")}`;
251291
+ return SECRET_KEY_TO_FLAG[key];
251267
251292
  }
251268
251293
  function exampleCommand(provider) {
251269
251294
  let cmd = `base44 auth sso enable --provider ${provider} --client-id <id> --client-secret <secret>`;
@@ -251289,6 +251314,9 @@ function validateProvider(provider) {
251289
251314
  return provider;
251290
251315
  }
251291
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
+ }
251292
251320
  let merged = options;
251293
251321
  if (options.file) {
251294
251322
  const fileConfig = await loadSSOConfigFile(options.file);
@@ -251352,9 +251380,9 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
251352
251380
  try {
251353
251381
  secrets = buildSSOSecrets(provider, secretOptions);
251354
251382
  } catch (error48) {
251355
- if (error48 instanceof InvalidInputError) {
251356
- const flagMessage = error48.message.replace(/sso_[a-z_]+/g, (key) => secretKeyToFlag(key));
251357
- throw new InvalidInputError(flagMessage, {
251383
+ if (error48 instanceof MissingSSOFieldsError) {
251384
+ const flagNames = error48.missingKeys.map(secretKeyToFlag);
251385
+ throw new InvalidInputError(`Missing required fields for ${error48.provider}: ${flagNames.join(", ")}`, {
251358
251386
  hints: [
251359
251387
  {
251360
251388
  message: `Example: ${exampleCommand(provider)}`,
@@ -251374,26 +251402,33 @@ async function ssoEnableAction({ isNonInteractive, runTask: runTask2 }, options)
251374
251402
  outroMessage: `SSO configured with ${provider} in local config. Run \`base44 auth push\` or \`base44 deploy\` to apply.`
251375
251403
  };
251376
251404
  }
251377
- async function ssoDisableAction({
251378
- runTask: runTask2
251379
- }) {
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
+ }
251380
251412
  const { project: project2 } = await readProjectConfig();
251381
251413
  const configDir = dirname11(project2.configPath);
251382
251414
  const authDir = join16(configDir, project2.authDir);
251383
- 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));
251384
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
+ }
251385
251420
  return {
251386
251421
  outroMessage: "SSO disabled in local config and credentials removed. Run `base44 auth push` or `base44 deploy` to apply."
251387
251422
  };
251388
251423
  }
251389
251424
  async function ssoAction(context, action, options) {
251390
251425
  if (action === "disable") {
251391
- return ssoDisableAction(context);
251426
+ return ssoDisableAction(context, options);
251392
251427
  }
251393
251428
  return ssoEnableAction(context, options);
251394
251429
  }
251395
251430
  function getSSOCommand() {
251396
- 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([
251397
251432
  "enable",
251398
251433
  "disable"
251399
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);
@@ -253658,15 +253693,21 @@ class FunctionManager {
253658
253693
  this.setupProcessHandlers(name2, process21);
253659
253694
  return this.waitForReady(name2, runningFunc);
253660
253695
  }
253661
- reload(functions) {
253662
- this.stopAll();
253696
+ async reload(functions) {
253697
+ await this.stopAll();
253663
253698
  this.functions = new Map(functions.map((f7) => [f7.name, f7]));
253664
253699
  }
253665
- stopAll() {
253666
- for (const [name2, { process: process21 }] of this.running) {
253700
+ async stopAll() {
253701
+ await Promise.all(Array.from(this.running, ([name2, { process: proc2 }]) => {
253667
253702
  this.logger.log(`Stopping function: ${name2}`);
253668
- process21.kill();
253669
- }
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
+ }));
253670
253711
  this.running.clear();
253671
253712
  this.starting.clear();
253672
253713
  }
@@ -254054,6 +254095,9 @@ class Database {
254054
254095
  getCollection(name2) {
254055
254096
  return this.collections.get(this.normalizeName(name2));
254056
254097
  }
254098
+ getSchema(entityName) {
254099
+ return this.schemas.get(this.normalizeName(entityName));
254100
+ }
254057
254101
  getCollectionNames() {
254058
254102
  return Array.from(this.collections.keys()).filter((name2) => {
254059
254103
  return !name2.startsWith(PRIVATE_COLLECTION_PREFIX);
@@ -254281,6 +254325,120 @@ In order to complete registration use this verification code: ${otpCode}
254281
254325
  // src/cli/dev/dev-server/routes/entities/entities-router.ts
254282
254326
  var import_express4 = __toESM(require_express(), 1);
254283
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
+
254284
254442
  // src/cli/dev/dev-server/db/entity-queries.ts
254285
254443
  function parseSort(sort) {
254286
254444
  if (!sort) {
@@ -254338,30 +254496,54 @@ var queryEntity = async (collection, reqQuery) => {
254338
254496
  return cursor3;
254339
254497
  };
254340
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
+
254341
254530
  // src/cli/dev/dev-server/routes/entities/entities-user-router.ts
254342
254531
  var import_express3 = __toESM(require_express(), 1);
254343
- var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
254344
254532
  function createUserRouter(db2, logger2) {
254345
254533
  const router = import_express3.Router({ mergeParams: true });
254346
254534
  const parseBody = import_express3.json();
254347
254535
  function withAuth(handler) {
254348
254536
  return async (req, res) => {
254349
- const auth2 = req.headers.authorization;
254350
- if (!auth2 || !auth2.startsWith("Bearer ")) {
254537
+ const currentUserResult = await resolveCurrentUser(db2, req);
254538
+ if (!currentUserResult.ok && (currentUserResult.reason === "missing" || currentUserResult.reason === "invalid")) {
254351
254539
  res.status(401).json({ error: "Unauthorized" });
254352
254540
  return;
254353
254541
  }
254354
- try {
254355
- const { payload } = import_jsonwebtoken2.default.decode(auth2.replace("Bearer ", ""), { complete: true }) ?? {};
254356
- const result = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: payload?.sub });
254357
- if (!result) {
254358
- res.status(404).json({ error: "Unable to read data for the current user" });
254359
- return;
254360
- }
254361
- await handler(req, res, result);
254362
- } catch {
254363
- 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;
254364
254545
  }
254546
+ await handler(req, res, currentUserResult.user);
254365
254547
  };
254366
254548
  }
254367
254549
  router.get("/:id", withAuth(async (req, res, currentUser) => {
@@ -254461,12 +254643,20 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254461
254643
  const parseBody = import_express4.json();
254462
254644
  function withCollection(handler) {
254463
254645
  return async (req, res) => {
254464
- const collection = db2.getCollection(req.params.entityName);
254646
+ const { entityName } = req.params;
254647
+ const collection = db2.getCollection(entityName);
254465
254648
  if (!collection) {
254466
- 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` });
254467
254655
  return;
254468
254656
  }
254469
- 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);
254470
254660
  };
254471
254661
  }
254472
254662
  function emit(appId, entityName, type, data) {
@@ -254484,9 +254674,31 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254484
254674
  }
254485
254675
  broadcast(appId, entityName, createData(data));
254486
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
+ }
254487
254699
  const userRouter = createUserRouter(db2, logger2);
254488
254700
  router.use("/User", userRouter);
254489
- router.get("/:entityName/:id", withCollection(async (req, res, collection) => {
254701
+ router.get("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
254490
254702
  const { entityName, id: id2 } = req.params;
254491
254703
  try {
254492
254704
  const doc2 = await collection.findOneAsync({ id: id2 });
@@ -254494,16 +254706,28 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254494
254706
  res.status(404).json({ error: `Record with id "${id2}" not found` });
254495
254707
  return;
254496
254708
  }
254497
- 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);
254498
254717
  } catch (error48) {
254499
254718
  logger2.error(`Error in GET /${entityName}/${id2}:`, error48);
254500
254719
  res.status(500).json({ error: "Internal server error" });
254501
254720
  }
254502
254721
  }));
254503
- router.get("/:entityName", withCollection(async (req, res, collection) => {
254722
+ router.get("/:entityName", withCollection(async (req, res, collection, schema10, currentUser) => {
254504
254723
  const { entityName } = req.params;
254505
254724
  try {
254506
- 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);
254507
254731
  } catch (error48) {
254508
254732
  if (error48 instanceof InvalidInputError) {
254509
254733
  res.status(400).json({ error: error48.message });
@@ -254513,20 +254737,16 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254513
254737
  }
254514
254738
  }
254515
254739
  }));
254516
- router.post("/:entityName", parseBody, withCollection(async (req, res, collection) => {
254740
+ router.post("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254517
254741
  const { appId, entityName } = req.params;
254518
254742
  try {
254519
254743
  const now = new Date().toISOString();
254520
- const { _id, ...body } = req.body;
254521
- const filteredBody = db2.prepareRecord(entityName, body);
254522
- db2.validate(entityName, filteredBody);
254523
- const record2 = {
254524
- ...filteredBody,
254525
- id: nanoid3(),
254526
- created_date: now,
254527
- updated_date: now
254528
- };
254529
- 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");
254530
254750
  emit(appId, entityName, "create", inserted);
254531
254751
  res.status(201).json(inserted);
254532
254752
  } catch (error48) {
@@ -254538,7 +254758,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254538
254758
  res.status(500).json({ error: "Internal server error" });
254539
254759
  }
254540
254760
  }));
254541
- router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection) => {
254761
+ router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254542
254762
  const { appId, entityName } = req.params;
254543
254763
  if (!Array.isArray(req.body)) {
254544
254764
  res.status(400).json({ error: "Request body must be an array" });
@@ -254547,17 +254767,15 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254547
254767
  try {
254548
254768
  const now = new Date().toISOString();
254549
254769
  const records = [];
254550
- for (const record2 of req.body) {
254551
- const filteredRecord = db2.prepareRecord(entityName, record2);
254552
- db2.validate(entityName, filteredRecord);
254553
- records.push({
254554
- ...filteredRecord,
254555
- id: nanoid3(),
254556
- created_date: now,
254557
- updated_date: now
254558
- });
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);
254559
254777
  }
254560
- const inserted = stripInternalFields(await collection.insertAsync(records));
254778
+ const inserted = applyFLS(stripInternalFields(await collection.insertAsync(records)), schema10, currentUser, "read");
254561
254779
  emit(appId, entityName, "create", inserted);
254562
254780
  res.status(201).json(inserted);
254563
254781
  } catch (error48) {
@@ -254569,11 +254787,24 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254569
254787
  res.status(500).json({ error: "Internal server error" });
254570
254788
  }
254571
254789
  }));
254572
- router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection) => {
254790
+ router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254573
254791
  const { appId, entityName, id: id2 } = req.params;
254574
254792
  const { id: _id, created_date: _created_date, ...body } = req.body;
254575
254793
  try {
254576
- 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");
254577
254808
  db2.validate(entityName, filteredBody, true);
254578
254809
  const updateData = {
254579
254810
  ...filteredBody,
@@ -254584,7 +254815,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254584
254815
  res.status(404).json({ error: `Record with id "${id2}" not found` });
254585
254816
  return;
254586
254817
  }
254587
- const updated = stripInternalFields(result.affectedDocuments);
254818
+ const updated = applyFLS(stripInternalFields(result.affectedDocuments), schema10, currentUser, "read");
254588
254819
  emit(appId, entityName, "update", updated);
254589
254820
  res.json(updated);
254590
254821
  } catch (error48) {
@@ -254596,30 +254827,48 @@ async function createEntityRoutes(db2, logger2, broadcast) {
254596
254827
  res.status(500).json({ error: "Internal server error" });
254597
254828
  }
254598
254829
  }));
254599
- router.delete("/:entityName/:id", withCollection(async (req, res, collection) => {
254830
+ router.delete("/:entityName/:id", withCollection(async (req, res, collection, schema10, currentUser) => {
254600
254831
  const { appId, entityName, id: id2 } = req.params;
254601
254832
  try {
254602
254833
  const doc2 = await collection.findOneAsync({ id: id2 });
254603
- const numRemoved = await collection.removeAsync({ id: id2 }, { multi: false });
254604
- if (numRemoved === 0) {
254834
+ if (!doc2) {
254605
254835
  res.status(404).json({ error: `Record with id "${id2}" not found` });
254606
254836
  return;
254607
254837
  }
254608
- if (doc2) {
254609
- 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;
254610
254843
  }
254844
+ await collection.removeAsync({ id: id2 }, { multi: false });
254845
+ emit(appId, entityName, "delete", stripInternalFields(doc2));
254611
254846
  res.json({ success: true });
254612
254847
  } catch (error48) {
254613
254848
  logger2.error(`Error in DELETE /${entityName}/${id2}:`, error48);
254614
254849
  res.status(500).json({ error: "Internal server error" });
254615
254850
  }
254616
254851
  }));
254617
- router.delete("/:entityName", parseBody, withCollection(async (req, res, collection) => {
254852
+ router.delete("/:entityName", parseBody, withCollection(async (req, res, collection, schema10, currentUser) => {
254618
254853
  const { entityName } = req.params;
254619
254854
  try {
254620
254855
  const query = req.body || {};
254621
- const numRemoved = await collection.removeAsync(query, { multi: true });
254622
- 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
+ }
254623
254872
  } catch (error48) {
254624
254873
  logger2.error(`Error in DELETE /${entityName}:`, error48);
254625
254874
  res.status(500).json({ error: "Internal server error" });
@@ -256419,7 +256668,9 @@ var DEFAULT_PORT = 4400;
256419
256668
  var BASE44_APP_URL = "https://base44.app";
256420
256669
  async function createDevServer(options8) {
256421
256670
  const { port: userPort } = options8;
256422
- 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
+ });
256423
256674
  const baseUrl = `http://localhost:${port}`;
256424
256675
  const { functions, entities, project: project2 } = await options8.loadResources();
256425
256676
  const app = import_express6.default();
@@ -256512,7 +256763,7 @@ async function createDevServer(options8) {
256512
256763
  const { functions: functions2, entities: entities2 } = await options8.loadResources();
256513
256764
  if (name2 === "functions") {
256514
256765
  const previousFunctionCount = functionManager.getFunctionNames().length;
256515
- functionManager.reload(functions2);
256766
+ await functionManager.reload(functions2);
256516
256767
  const names = functionManager.getFunctionNames();
256517
256768
  if (names.length > 0) {
256518
256769
  devLogger.log(`Reloaded functions: ${names.sort().join(", ")}`);
@@ -256537,10 +256788,10 @@ async function createDevServer(options8) {
256537
256788
  }
256538
256789
  });
256539
256790
  await base44ConfigWatcher.start();
256540
- const shutdown = () => {
256791
+ const shutdown = async () => {
256541
256792
  base44ConfigWatcher.close();
256542
256793
  io6.close();
256543
- functionManager.stopAll();
256794
+ await functionManager.stopAll();
256544
256795
  server.close();
256545
256796
  };
256546
256797
  process.on("SIGINT", shutdown);
@@ -261031,4 +261282,4 @@ export {
261031
261282
  CLIExitError
261032
261283
  };
261033
261284
 
261034
- //# debugId=CDE136D996499A7364756E2164756E21
261285
+ //# debugId=827C7659385FB39764756E2164756E21