Skip to content

Comments

feat: add entity records CRUD commands#259

Open
ayal wants to merge 10 commits intomainfrom
feat/entity-records-crud
Open

feat: add entity records CRUD commands#259
ayal wants to merge 10 commits intomainfrom
feat/entity-records-crud

Conversation

@ayal
Copy link

@ayal ayal commented Feb 17, 2026

Note

Description

This PR adds full CRUD support for entity records via a new entities records subcommand group. Users can now list, get, create, update, and delete records for any entity directly from the CLI, authenticated via a token exchange from the platform token to an app-user token with RLS bypass headers for admin-level access.

The entities push command was also refactored: previously it was registered as the top-level entities command; it is now a proper subcommand under a new entities parent, enabling the records subcommand to be added alongside it.

Related Issue

None

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Other (please describe):

Changes Made

  • Added entities records list command with --filter, --sort, --limit, --skip, and --fields options
  • Added entities records get command to fetch a single record by ID
  • Added entities records create command accepting record data via --data (inline JSON/JSONC) or --file
  • Added entities records update command accepting update fields via --data or --file
  • Added entities records delete command with an interactive confirmation prompt and --yes flag to skip it
  • Added getAppUserClient() in base44-client.ts that exchanges the platform token for an app-user token and attaches X-Bypass-RLS / X-Bypass-Entities-Filter-Limit headers
  • Added records-api.ts with core listRecords, getRecord, createRecord, updateRecord, deleteRecord functions with Zod validation on all responses
  • Added records-schema.ts with EntityRecordSchema (loose object) and DeleteRecordResponseSchema
  • Refactored entities push from a top-level command into a subcommand under a new entities parent command group
  • Extended Base44APIMock with helpers for mocking all record endpoints and the token-exchange endpoint
  • Added toContainInStdout assertion helper to CLIResultMatcher
  • Updated README command table with the five new entities records commands

Testing

  • I have tested these changes locally
  • I have added/updated tests as needed
  • All tests pass (npm test)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings
  • I have updated docs/ (AGENTS.md) if I made architectural changes

Additional Notes

The delete command uses CLIExitError(0) for a clean exit when the user cancels the confirmation prompt, consistent with the project's error handling patterns. The EntityRecordSchema uses z.looseObject to allow arbitrary dynamic data fields beyond the required system fields (id, created_date, updated_date, created_by).


🤖 Generated by Claude | 2026-02-23 00:00 UTC

Add `base44 entities records <list|get|create|update|delete>` commands
for managing entity records via the CLI. Uses the new admin entities
router (/api/apps/{app_id}/admin/entities) which authenticates via
AppAdminRouter and bypasses RLS.

Includes:
- Core API layer (records-api.ts, records-schema.ts)
- CLI commands for list, get, create, update, delete
- Refactored entities command to support subcommands (push + records)
- Integration tests and API mocks

Co-authored-by: Cursor <cursoragent@cursor.com>
@ayal ayal force-pushed the feat/entity-records-crud branch from e553740 to 3bd687f Compare February 17, 2026 13:14
@github-actions
Copy link
Contributor

github-actions bot commented Feb 17, 2026

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/cli@0.0.34-pr.259.6bdf9f0

Prefer not to change any import paths? Install using npm alias so your code still imports base44:

npm i "base44@npm:@base44-preview/cli@0.0.34-pr.259.6bdf9f0"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "base44": "npm:@base44-preview/cli@0.0.34-pr.259.6bdf9f0"
  }
}

Preview published to npm registry — try new features instantly!

@claude
Copy link

claude bot commented Feb 17, 2026

README check ran. Found discrepancies and attempted to update README.md, but edits require approval. Issues found: (1) Missing commands: eject, connectors pull/push, entities records, types generate; (2) Outdated descriptions for create, deploy, link, logout, whoami, agents pull/push. Manual update needed.

- Add KyResponse type annotation to let response declarations
- Remove unused export from ListRecordsOptions interface
- Replace console.log with process.stdout.write for JSON output
  so test framework's stdout spy can capture it
- Apply Biome formatting fixes

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link

claude bot commented Feb 17, 2026

README check ran. 7 issue(s) found and applied: updated create/deploy/link/logout/whoami/agents descriptions to match CLI code, added eject command, added connectors pull/push commands, added entities records command, added types generate command. README.md has been updated in this branch.

Add eject, connectors pull/push, entities records CRUD, and types
generate. Fix descriptions to match actual CLI --help output.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kfirstri kfirstri self-requested a review February 18, 2026 10:49
@kfirstri kfirstri moved this from Backlog to Ready in CLI Development Feb 18, 2026
updated_date: z.string(),
created_by: z.string().optional(),
})
.passthrough();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use looseObject, passthough is obsolete in zod4

}

if (options.file) {
const content = await readTextFile(options.file);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a readJsonFile you can also use

async function parseRecordData(
options: CreateRecordCommandOptions,
): Promise<Record<string, unknown>> {
if (options.data) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can add a hook to the commander to check wether --data or --file but not both? currently if a user passes both just the --data will actually be taken into consideration

export function getRecordsGetCommand(context: CLIContext): Command {
return new Command("get")
.description("Get a single entity record by ID")
.argument("<entity-name>", "Name of the entity (e.g. Users, Products)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want any validation for this thing? I guess the backend will throw if entity does not exists

file?: string;
}

async function parseUpdateData(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we use this both here and in create.ts , maybe it's worth extracting? (notice we also need to use the readJsonFile method)

Comment on lines 77 to 78
log.success(`Record created with ID: ${record.id}`);
process.stdout.write(`${JSON.stringify(record, null, 2)}\n`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use normal logs and stuff

you should do

return { outroMessage: `Record created with ID: ${record.id}`}

to make it end nicely.
and also lets just log the JSON with the log.success or log.info or soemthing.

This is relevant for all the new commands

return new Command("list")
.description("List entity records")
.argument("<entity-name>", "Name of the entity (e.g. Users, Products)")
.option("-f, --filter <json>", "JSON query filter")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we need to give a bit mor information here. we also need this to be in the base44 cli SKILL so it will have examples, but lets just say what is expected?

Copy link
Collaborator

@kfirstri kfirstri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments :) @claude start working on them please.

@claude
Copy link

claude bot commented Feb 22, 2026

Claude finished @kfirstri's task in 5m 43s —— View job


Review Changes Applied

  • Read all relevant files
  • Fix passthrough()looseObject in records-schema.ts
  • Find and understand readJsonFile utility
  • Extract shared data/file reading logic into a utility (used by both create and update)
  • Add mutual exclusion check for --data and --file
  • Fix output formatting to use proper clack patterns for all new commands
  • Improve help text for list command
  • Run typecheck and tests (all 16 entity record tests pass)
  • Commit and push changes

Summary of changes (commit 9b24161)

records-schema.ts — Replaced z.object({...}).passthrough() with z.looseObject({...}) as required for Zod v4.

New records/parseRecordData.ts — Shared utility extracted from both create.ts and update.ts:

  • Throws InvalidInputError if both --data and --file are passed simultaneously
  • Parses inline --data string with JSON5
  • Uses the existing readJsonFile() for --file (which handles JSON5, FileNotFoundError, and ConfigInvalidError automatically)

Output formatting — Applied consistently across all five commands:

  • JSON output now uses log.info() instead of process.stdout.write()
  • Success messages moved to outroMessage return value (e.g. Record created with ID: xyz, Record xyz updated, Found N Customer record(s))
  • Removed unused log import from delete.ts

list.ts help text — All options now have more descriptive text with examples (e.g. --filter shows '{"status":"active"}' or '{"age":{"$gt":18}}', --sort explains the - prefix for descending).

tests/cli/entities_records.spec.ts — Updated two assertions from "Found N record(s)" to "Found N Customer record(s)" to match the new outroMessage format.
|

…k output patterns

- Replace z.object().passthrough() with z.looseObject() in records-schema.ts (zod4 compat)
- Extract shared parseRecordData() utility used by both create and update commands
  - Uses readJsonFile() for --file (handles JSON5, FileNotFoundError, ConfigInvalidError)
  - Adds mutual exclusion check: cannot use both --data and --file simultaneously
- Replace process.stdout.write with log.info() for JSON output across all record commands
- Move success messages to outroMessage return value (create, update, list, delete)
- Improve --filter, --sort, --skip, --fields help text in list command with examples
- Update test assertions to match new "Found N <entity> record(s)" outroMessage format

Co-authored-by: Kfir Stri <kfirstri@users.noreply.github.com>

export async function parseRecordData(
options: RecordDataOptions,
exampleHint: string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude lets make this optional and only use in the exception if available

Copy link

@claude claude bot Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @kfirstri's task in 1m 55s —— View job


Task Complete

  • Read parseRecordData.ts to understand what needs to be made optional
  • Make the change (exampleHint is now optional, only used in error hints when provided)
  • Run typecheck and tests (all 172 pass)
  • Commit and push

Summary

Made exampleHint optional in parseRecordData.ts (exampleHint?: string). The two error throws that use it now conditionally include the hints only when exampleHint is provided:

// Before
throw new InvalidInputError("Invalid JSON in --data flag.", {
  hints: [{ message: `Example: --data '${exampleHint}'` }],
});

// After
throw new InvalidInputError(
  "Invalid JSON in --data flag.",
  exampleHint
    ? { hints: [{ message: `Example: --data '${exampleHint}'` }] }
    : undefined,
);

Same pattern applied to the "Provide record data with --data or --file flag" error. Callers (create.ts, update.ts) continue to pass their example hints as before — no changes needed there.

… if provided

Co-authored-by: Kfir Stri <kfirstri@users.noreply.github.com>
@kfirstri
Copy link
Collaborator

This PR is ready and waiting for backend changes to be merged first

Exchange the platform token for an app-user token via the existing
GET /auth/token endpoint, then call the standard RuntimeRouter entity
endpoints with X-Bypass-RLS instead of the custom admin entities router.

This eliminates the need for backend changes (admin_entities_router.py)
and gives the CLI access to the full RuntimeRouter surface for future
features.
@ayal
Copy link
Author

ayal commented Feb 23, 2026

@netanelgilad @kfirstri — heads up on the latest changes:

Following Netanel's suggestion, I refactored the entity records API to use a token exchange approach instead of the custom admin entities router in the backend:

  1. The CLI exchanges its platform token for an app-user token via the existing GET /api/apps/{app_id}/auth/token endpoint
  2. It then calls the standard RuntimeRouter entity endpoints with X-Bypass-RLS: true and X-Bypass-Entities-Filter-Limit: true

This means no backend changes are needed — the apper PR #3852 has been closed. The CLI commands and their behavior remain exactly the same; only the internal auth/routing layer changed.

Changed files:

  • src/core/clients/base44-client.ts — new getAppUserClient() that handles the token exchange
  • src/core/clients/index.ts — exports getAppUserClient
  • src/core/resources/entity/records-api.ts — uses getAppUserClient() + /entities/{name} paths instead of /admin/entities/{name}
  • tests/cli/testkit/Base44APIMock.ts — updated mocks for token exchange and new paths

All tests pass. Ready for review!

# Conflicts:
#	tests/cli/testkit/Base44APIMock.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

2 participants