Skip to Content
FrontendCrud V2Crud GeneratorGenerator v2API & Functions Reference

API & Functions Reference

Detailed reference for every function in the generator’s source modules — parameters, return values, side effects, and internal behaviour.


src/api/api.js

All HTTP calls are routed through apiTicketRequest(). A Bearer token is read from config/token.txt on every call — run ./index.js token first.


getOAuth2Token(bundleCrud)

Fetches an OAuth2 client_credentials token and writes it to config/token.txt.

ParameterTypeDescription
bundleCrudstringOne of phprCrud, ticketCrud, configCrud, accountCrud

Each value reads its own set of .env variables:

bundleCrudEnv vars read
phprCrudPHPR_OAUTH_CLIENT_ID, PHPR_OAUTH_CLIENT_SECRET, PHPR_OAUTH_TOKEN_URL
ticketCrudTICKET_OAUTH_CLIENT_ID, TICKET_OAUTH_CLIENT_SECRET, TICKET_OAUTH_TOKEN_URL
configCrudCONFIG_OAUTH_CLIENT_ID, CONFIG_OAUTH_CLIENT_SECRET, CONFIG_OAUTH_TOKEN_URL
accountCrudACCOUNT_OAUTH_CLIENT_ID, ACCOUNT_OAUTH_CLIENT_SECRET, ACCOUNT_OAUTH_TOKEN_URL

Returns: Promise<string> — the final "Bearer <token>" string.

Side effect: writes config/token.txt.

Throws if clientId or clientSecret env vars are missing. On a 401 response from any subsequent API call, the process exits with code 1 — re-run ./index.js token to refresh.


MetaDataEntitiesList(baseUrl)

Fetches the full list of entity class names from the API.

GET {baseUrl}/metadata/entities

Returns: Promise<string[]> — URL-encoded entity names, e.g. ["App%5CEntity%5CAccountingBundle%5CAccount", ...]

The \Entity\ portion is encoded to %5CEntity%5C so the names are safe to use directly in subsequent API URL paths.


MetaDataEntityList(baseUrl, entityName)

Fetches all field definitions for a single entity.

GET {baseUrl}/metadata/entity/{entityName}

Returns: Promise<{ fields, unfilteredFields }>

KeyDescription
fieldsObject keyed by field name — scalar and typed fields with type metadata
unfilteredFieldsObject — relational / association fields (MANY_TO_ONE, etc.) that are excluded from the default filtered list

ResourceNameList(baseUrl, entityName)

Fetches resource-level metadata for an entity — called in parallel with MetaDataEntityList.

GET {baseUrl}/metadata/entity/{entityName}

Hits the same endpoint as MetaDataEntityList but extracts different fields from the response.

Returns: Promise<Object>

KeySource in responseDescription
resourceNamedata.resourceNameAPI slug used as the base route, e.g. accounting_accounts
availableSerializationGroupsdata.availableSerializationGroupsArray of all serialization group names registered for this entity
implementedInterfacesdata.class_object_data.implemented_interfacesPHP interface names the entity implements
writeablePropertiesdata.writable_propertiesFields the API accepts on write — used to whitelist form inputs
apiFiltersdata.apiFiltersFilter class map; OrderFilter.gobal.properties is parsed into sortableFields
tableNamedata.class_object_data.tableNameDatabase table name

SerializationShowFields(baseUrl, entityName, availableSerializationGroups)

Fetches field definitions scoped to the entity’s Show serialization group(s).

GET {baseUrl}/metadata/entity/{entityName}?groups[]=accountingAccountShow&groups[]=show&groups[]=standardShow

The query string is built by createEntitySerialization(simpleName, 'Show', availableGroups) — see Serialization Group Resolution below.

Returns: Promise<Object> — merged fields + virtual_properties from the response.


SerializationListingFields(baseUrl, entityName, availableSerializationGroups)

Fetches field definitions scoped to the entity’s Listing serialization group(s).

GET {baseUrl}/metadata/entity/{entityName}?groups[]=accountingAccountListing&groups[]=standardListing&groups[]=id&groups[]=listing

Returns: Promise<Object> — merged fields + virtual_properties from the response.


RequiredFields(baseUrl, entityName)

Fetches only the fields marked as required in the API schema.

GET {baseUrl}/metadata/entity/{entityName}?requiredOnly=true

Returns: Promise<Object> — subset of fields where required constraint is set.


checkCacheExists(simplifiedName)

Checks whether all four flat-file caches exist for an entity.

ParameterExample
simplifiedName"AccountingBundle/Account"

Cache file paths checked:

config/fields/{simplifiedName}.json config/showFields/{simplifiedName}.json config/listingFields/{simplifiedName}.json config/requiredFields/{simplifiedName}.json

Returns: { exists: { fields, showFields, listingFields, requiredFields, all }, paths }

exists.all is true only when all four files exist.


loadCachedMetadata(paths)

Reads all four cache files from disk.

Returns: { fieldsMetadata, showField, listingField, requiredField } — all as parsed JSON objects.


Serialization Group Resolution

createEntitySerialization(entity, suffix, availableGroups)utils/functions.js

This function builds the ?groups[]=… query string sent to the metadata API to scope fields to the correct serialization context.

Inputs

ParameterExample values
entity"AccountingBundle/Account", "User", "TicketBundle%5CTicket"
suffix"Show" or "Listing"
availableGroupsArray from ResourceNameList — e.g. ["accountingAccountShow", "standardShow", "id", ...]

Resolution Algorithm

Step 1 — Derive bundleName and entityName

The entity string is decoded (%5C\) and split on \.

"AccountingBundle/Account" → pathParts = ["AccountingBundle", "Account"] bundlePart = "AccountingBundle" entityName = "Account" (pathParts[2] if PHP FQCN, pathParts[0] otherwise)

Bundle entity (bundlePart ends with "Bundle"):

bundleName = "AccountingBundle".slice(0, -6).toLowerCase() = "accounting"

Non-bundle entity (no Bundle suffix in the path):

// suffix is replaced with the first availableGroup that contains the suffix string suffix = availableGroups.find(g => g.includes(suffix)) || suffix; // e.g. for suffix = "Show", if "userShow" is in availableGroups → suffix becomes "userShow"

Step 2 — Build the primary group name

serializationGroup = bundleName + entityName // bundle entity = "accounting" + "Account" = "accountingAccount" serializationGroup = lowerFirst(serializationGroup) = "accountingAccount" fullGroup = "accountingAccount" + "Show" = "accountingAccountShow"

Step 3 — Match against availableGroups (primary attempt)

If fullGroup ("accountingAccountShow") exists in availableGroups → use it. Stop here.


Step 4 — Primary fallback alternatives (in order, first match wins)

Tried only when Step 3 found no match:

PriorityAlternativeExample
1lowerFirst(entityName) + suffix"accountShow"
2bundleName + suffix"accountingShow"

If neither exists in availableGroups, the constructed fullGroup is added anyway (unverified — the API may return an empty result).


Step 5 — Append standard additional groups

Additional groups are always appended after the primary match (if they exist in availableGroups and are not already in the list):

For suffix = "Show":

GroupDescription
"show"Generic show group shared across entities
"standardShow"Standard show group — common across bundles

For suffix = "Listing":

GroupDescription
"standardListing"Standard listing group — most bundles include this
"tagTaggingList"Tagging relationship fields for listing
"id"Ensures the id field is always present in listing results
"listing"Generic listing group
lowerFirst(entityName) + "Tagging" + suffixe.g. "accountTaggingListing"
lowerFirst(entityName) + "TaggingList"e.g. "accountTaggingList"
serializationGroup + "Tagging" + suffixe.g. "accountingAccountTaggingListing"

Step 6 — Build the final query string

// Current format (post migration — supports newer API versions) '?' + matchedGroups.map(g => 'groups[]=' + g).join('&') // → "?groups[]=accountingAccountShow&groups[]=show&groups[]=standardShow" // Legacy format (commented out — older API versions) // '?' + 'group=' + matchedGroups.join(',') // → "?group=accountingAccountShow,show,standardShow"

The switch from ?group= to ?groups[]= was made to support newer API versions that expect array parameters. The old single-group format is preserved as a comment in the source (utils/functions.js) for reference.


Full Resolution Example

Entity: AccountingBundle/Account, suffix: Listing availableGroups: ["accountingAccountListing", "accountingAccountShow", "standardListing", "id", "listing", "show", "standardShow"]

StepActionResult
1Extract bundlebundleName = "accounting", entityName = "Account"
2Build primaryfullGroup = "accountingAccountListing"
3Check in groups✅ found — add to matchedGroups
4Skip fallbacksPrimary matched
5Append standard listingAdd "standardListing", "id", "listing" (all present in availableGroups)
6Build query?groups[]=accountingAccountListing&groups[]=standardListing&groups[]=id&groups[]=listing

Non-Bundle Entity Example

Entity: User, suffix: Show availableGroups: ["userShow", "standardShow", "show"]

StepActionResult
1No bundle suffixsuffix = availableGroups.find(g => g.includes("Show")) = "userShow"
2serializationGroup = lowerFirst("User") = "user", fullGroup = "user" + "userShow" = "userShow"
3Check in groups"userShow" found
5Append "show", "standardShow"Both present
6Query?groups[]=userShow&groups[]=show&groups[]=standardShow

src/json/json-generator.js


generateJson(options)

Creates a new JSON version for an entity. Supports two modes.

OptionTypeDescription
entitystringEntity path, e.g. "AccountingBundle/Account"
baseUrlstringAPI base URL (required for fresh mode)
bundleCrudstringBundle identifier for entity name encoding
fromApibooleanForce API fetch even if cache exists
fromCachebooleanUse cache files if available
fromVersionstring|nullBase version for incremental mode (e.g. "v0")

Fresh mode (when fromVersion is null and baseUrl is provided):

  1. Calls encodeEntityName() to build the PHP FQCN
  2. Calls fetchFromApi(): runs MetaDataEntityList, ResourceNameList, RequiredFields in parallel, then SerializationShowFields + SerializationListingFields in parallel
  3. Saves raw snapshot to data.json

Incremental mode (when fromVersion is set):

  1. Loads {fromVersion}/data.json
  2. Loads {fromVersion}/changes.json (if it exists)
  3. Calls applyChanges(baseData, baseChanges, 'json') to produce the starting point
  4. Saves result as the new version’s data.json

Returns: { success, entity, version, baseVersion, dataPath, data, mode }


generateJsonMigration(options)

Creates a changes.json template for an entity version. Interactively prompts for inheritance selection if fromVersion is not provided.

OptionTypeDescription
entitystringEntity path
versionstringTarget version (default: latest)
fromVersionstring"latest" to auto-select, "fresh" to skip inheritance, or a specific "vN"

Inheritance behaviour:

fromVersionWhat happens
"latest"Finds the most recent version that already has a changes.json and inherits from it via mergeChangesTemplates()
"fresh"Creates a blank template with no inherited selections
"vN"Inherits specifically from that version
Not providedInteractive CLI prompt listing available versions to choose from

If the target version already has a changes.json, a new version is created automatically before writing.

Returns: { success, entity, version, baseVersion, path }


encodeEntityName(entity, bundleCrud)

Converts a short entity path into a URL-encoded PHP FQCN for API calls.

InputbundleCrudOutput
"AccountingBundle/Account""phprCrud""App%5CEntity%5CAccountingBundle%5CAccount"
"AccountingBundle/Account""phprCrud" (PHPR)"PHPReaction%5CEntity%5CAccountingBundle%5CAccount"
"PHPReaction\\Entity\\User"any"PHPReaction%5CEntity%5CUser" (passed through as-is)

The prefix App\Entity\ vs PHPReaction\Entity\ is chosen based on whether bundleCrud contains "phpr" (case-insensitive).


src/core/changes-engine.js


applyChanges(data, changes, layer)

Entry point. Deep-clones data via JSON.parse(JSON.stringify(data)) before applying any changes — the original is never mutated.

layerDelegates to
"json"applyJsonChanges(result, changes)
"tsx"applyTsxChanges(result, changes)

applyJsonChanges(data, changes)

Applies all sections of a changes.json onto a raw API metadata snapshot (data.json format). Processing order:

OrderSectionTarget in data
1changes.listingFieldsdata.listingFields
2changes.formModalFieldsdata.formModalFields (created if absent)
3changes.showFieldsdata.showFields
4changes.formFieldsdata.fields + data.requiredFields
5changes.filterFieldsdata.requiredFields + data.defaultFilters
6changes.sideboxFieldsdata.sideboxFields
7changes.exclusionListdeletes from data.listingFields and data.fields
8changes.actionsdata.actions (full replacement)

formFields required/optional handling: Four aliases all supported:

KeyMeaning
makeRequiredAdd field names to requiredFields
makeOptionalRemove field names from requiredFields
requiredSame as makeRequired
optionalSame as makeOptional

showFields sections object: When changes.showFields.sections is a non-empty object, all section values are flattened into a single selected array and passed to applyObjectFieldChanges. When no sections, the changes object is used directly.


applyObjectFieldChanges(fieldsObj, changes)

Filters a { fieldName: metadata } object by selected and hide arrays.

Priority: hide > selected > include all
ScenarioResult
Field in selected onlyIncluded
Field in hide onlyExcluded
Field in both selected and hideExcluded (hide wins)
selected is empty / absentAll fields included, only hide applied

This function is used for listingFields, showFields, and formFields (when no sections are defined).


applyTsxChanges(data, changes)

Applies changes to a TSX-layer data structure. All operations use the key field as the identifier for objects in arrays.

Change keyTargetFunction used
mainColumnsdata.mainColumnsapplyObjectArrayChanges(…, 'key')
filterFieldsdata.filterFieldsapplyObjectArrayChanges(…, 'key')
formInputsdata.formInputsapplyFormSectionChanges()
displayFieldsdata.displayFieldsapplyFormSectionChanges()
customActionsdata.customActionsapplyCustomActionChanges()
defaultActionsdata.defaultActionsapplyCustomActionChanges()

applyObjectArrayChanges(array, changes, keyField)

Modifies an array of objects. Operations applied in this order:

  1. hide — remove objects whose keyField value is in the hide list
  2. modify — shallow-merge override properties onto matched objects
  3. order — reorder by desired key sequence (unmatched items appended at end)

applyFormSectionChanges(sections, changes)

Handles arrays of { title, fields[] } section objects (used for formInputs and displayFields).

  1. hide — removes field keys (or objects with matching .key) from every section’s fields array
  2. sectionOrder — reorders sections by title

applyOrder(array, desiredOrder, keyField)

Reorders any array by a desired sequence. Items listed in desiredOrder come first (in that order); remaining items are appended at the end unchanged.

Works with both string arrays (keyField = null) and object arrays (keyField = "key" etc).


hasAnyChanges(changes)

Recursively checks whether a changes object contains any non-empty values (ignores _comment and description keys).

Used by the preview and migration commands to skip entities with empty change sets.


utils/functions.js — Key Utilities


FieldTypeGeneration(name, type, format)

Maps API field metadata to a ColumnTypeEnum string. See the full mapping table in the changes.json Reference → Field Type Auto-Detection.

Priority order (first match wins):

  1. Relation types (MANY_TO_ONE, ONE_TO_MANY, MANY_TO_MANY, ONE_TO_ONE) → SELECT_ASYNC
  2. Currency name/format match → CURRENCY
  3. Percent name/format match → PERCENT
  4. Boolean type → BOOLEAN
  5. Integer types → NUMBER
  6. Decimal → DECIMAL
  7. Float → FLOAT
  8. Text → TEXTAREA
  9. Date → DATE
  10. Datetime → DATETIME
  11. File → FILE
  12. String with special name patterns (icon, color, *Phone*, *Email*) → ICON, COLOR, TEL, EMAIL
  13. String (default) → TEXT
  14. JSON document → JSON
  15. Unknown → TEXT

Currency fields (name match list, combined with money format or numeric types): total, amount, price, cost, subTotal, totalAmount, taxesAmount, totalTax, totalTaxAmount, productSubTotal, totalToPay, convertedProductSubTotal, convertedSubTotal, convertedTaxesAmount, convertedTotal


capitalize(text)

Converts a camelCase or snake_case identifier into a human-readable label. Handles acronyms (e.g. VAT, ID) by preserving their casing instead of title-casing them.

"accountingAccountType" → "Accounting Account Type" "vatAmount" → "VAT Amount" "tableName" → "Table Name"

deriveResourceNameFromEntity(entityClassName)

Derives the API resource slug from an entity class name without making an API call. Used as a fallback when resourceName is unavailable.

"AccountingBundle/AccountType" → "accounting_account_types" "App\Entity\AccountingBundle\Account" → "accounts" "InvoiceBundle/Type" → "invoice_types"

Logic: strips namespace prefixes, combines bundle prefix + entity name, converts to snake_case, pluralises.


filterObjectByFieldName(obj, exclusionList)

Removes entries from a field metadata object where field.fieldName appears in exclusionList.

Used in entity-processor.js to apply INPUT_EXCLUSION_LIST and LISTING_EXCLUSION_LIST before TSX generation.

Excluded from all inputs (INPUT_EXCLUSION_LIST): id, slug, createdAt, updatedAt, deletedAt, disabledAt, lvl, lft, rgt, lgt, customProperties, root, igrf, enabledVirtual, leaf, leafApi, level, levelApi, shortcode

Excluded from listing (LISTING_EXCLUSION_LIST): Same as above, minus id (which is always added back if not already present in listing fields).


createEntitySerialization(entity, suffix, availableGroups)

See the full Serialization Group Resolution section above.


src/json/json-applier.js


listJsonVersions(entity)

Prints and returns version info for the JSON layer of an entity. Output includes creation date and whether a changes.json exists for each version.

Returns: array of version info objects (same shape as getVersionInfo).


showJsonDiff(entity, fromVersion, toVersion)

Compares two JSON versions and prints added/removed fields for listingFields, filterFields, and requiredFields.

Returns: array of { section, added, removed } diff objects.


checkJsonChanges(options)

Reads the current changes.json for an entity and prints a structured summary of every section:

  • Listing fields: selected count + list, hidden count
  • Three form modal variants: selected + hidden counts
  • Show fields: section names + field counts per section, hidden count
  • Form fields: section count, required count, optional count, hidden count
  • Filter fields: defaults count + list, hidden count
  • Sidebox fields: count + fieldName → targetResource list
  • Actions: listing actions list, show page actions list

Returns: { success, entity, version, summary, changes }


matchScraperFields(options)

Matches legacy scraper data against the current changes.json and creates a new version with the matched selections written into it. The source data.json is copied unchanged; only changes.json gets the scraped selections.

OptionTypeDescription
entitystringEntity path
fromVersionstringSource version (default: latest)
dryRunbooleanPreview only — no files written

Sections matched from legacy data:

Legacy exportTarget in changes.json
listingDatalistingFields.selected
showData (sections)showFields.sections
formData (sections)formFields.sections
listingFiltersfilterFields.defaults

Field matching algorithm (applied within each section):

  1. Exact normalized match (name.toLowerCase().replace(/\s+/g, ''))
  2. Alias lookup — hard-coded synonyms for common legacy label differences:
Legacy label (normalized)Mapped to
internalnote, internalcommentnotes
noteontheinvoice, conditionconditions
automaticemails, automaticemailautoEmail
automaticinterests, automaticinterestautoInterests
enableddisabledAt
createddate, createdcreatedAt
updatedate, update, updatedupdatedAt
comptabilisableaccountable
deliverydatedeliveryDate
clientreference, clientrefclientRef
conversionrateconversionRate
uniqueid, uniqueidentifieruniqueId
datestartdate
deliverydatestartdeliveryDate
tags, taggingtag
  1. Substring match (one name contains the other)
  2. Prefix/suffix stripping: removes internal, external, auto, automatic prefixes and date, time, at suffixes before comparing

Requires: legacy data in ./scrapedLegacy/{Bundle}/{Entity}/index.js. See Legacy Scraper for how to populate this directory.

Returns: { success, entity, sourceVersion, newVersion, dataPath, changesPath, changes }

Last updated on