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.
| Parameter | Type | Description |
|---|---|---|
bundleCrud | string | One of phprCrud, ticketCrud, configCrud, accountCrud |
Each value reads its own set of .env variables:
bundleCrud | Env vars read |
|---|---|
phprCrud | PHPR_OAUTH_CLIENT_ID, PHPR_OAUTH_CLIENT_SECRET, PHPR_OAUTH_TOKEN_URL |
ticketCrud | TICKET_OAUTH_CLIENT_ID, TICKET_OAUTH_CLIENT_SECRET, TICKET_OAUTH_TOKEN_URL |
configCrud | CONFIG_OAUTH_CLIENT_ID, CONFIG_OAUTH_CLIENT_SECRET, CONFIG_OAUTH_TOKEN_URL |
accountCrud | ACCOUNT_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/entitiesReturns: 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 }>
| Key | Description |
|---|---|
fields | Object keyed by field name — scalar and typed fields with type metadata |
unfilteredFields | Object — 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>
| Key | Source in response | Description |
|---|---|---|
resourceName | data.resourceName | API slug used as the base route, e.g. accounting_accounts |
availableSerializationGroups | data.availableSerializationGroups | Array of all serialization group names registered for this entity |
implementedInterfaces | data.class_object_data.implemented_interfaces | PHP interface names the entity implements |
writeableProperties | data.writable_properties | Fields the API accepts on write — used to whitelist form inputs |
apiFilters | data.apiFilters | Filter class map; OrderFilter.gobal.properties is parsed into sortableFields |
tableName | data.class_object_data.tableName | Database 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[]=standardShowThe 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[]=listingReturns: 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=trueReturns: Promise<Object> — subset of fields where required constraint is set.
checkCacheExists(simplifiedName)
Checks whether all four flat-file caches exist for an entity.
| Parameter | Example |
|---|---|
simplifiedName | "AccountingBundle/Account" |
Cache file paths checked:
config/fields/{simplifiedName}.json
config/showFields/{simplifiedName}.json
config/listingFields/{simplifiedName}.json
config/requiredFields/{simplifiedName}.jsonReturns: { 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
| Parameter | Example values |
|---|---|
entity | "AccountingBundle/Account", "User", "TicketBundle%5CTicket" |
suffix | "Show" or "Listing" |
availableGroups | Array 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:
| Priority | Alternative | Example |
|---|---|---|
| 1 | lowerFirst(entityName) + suffix | "accountShow" |
| 2 | bundleName + 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":
| Group | Description |
|---|---|
"show" | Generic show group shared across entities |
"standardShow" | Standard show group — common across bundles |
For suffix = "Listing":
| Group | Description |
|---|---|
"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" + suffix | e.g. "accountTaggingListing" |
lowerFirst(entityName) + "TaggingList" | e.g. "accountTaggingList" |
serializationGroup + "Tagging" + suffix | e.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"]
| Step | Action | Result |
|---|---|---|
| 1 | Extract bundle | bundleName = "accounting", entityName = "Account" |
| 2 | Build primary | fullGroup = "accountingAccountListing" |
| 3 | Check in groups | ✅ found — add to matchedGroups |
| 4 | Skip fallbacks | Primary matched |
| 5 | Append standard listing | Add "standardListing", "id", "listing" (all present in availableGroups) |
| 6 | Build query | ?groups[]=accountingAccountListing&groups[]=standardListing&groups[]=id&groups[]=listing |
Non-Bundle Entity Example
Entity: User, suffix: Show
availableGroups: ["userShow", "standardShow", "show"]
| Step | Action | Result |
|---|---|---|
| 1 | No bundle suffix | suffix = availableGroups.find(g => g.includes("Show")) = "userShow" |
| 2 | serializationGroup = lowerFirst("User") = "user", fullGroup = "user" + "userShow" = "userShow" | — |
| 3 | Check in groups | ✅ "userShow" found |
| 5 | Append "show", "standardShow" | Both present |
| 6 | Query | ?groups[]=userShow&groups[]=show&groups[]=standardShow |
src/json/json-generator.js
generateJson(options)
Creates a new JSON version for an entity. Supports two modes.
| Option | Type | Description |
|---|---|---|
entity | string | Entity path, e.g. "AccountingBundle/Account" |
baseUrl | string | API base URL (required for fresh mode) |
bundleCrud | string | Bundle identifier for entity name encoding |
fromApi | boolean | Force API fetch even if cache exists |
fromCache | boolean | Use cache files if available |
fromVersion | string|null | Base version for incremental mode (e.g. "v0") |
Fresh mode (when fromVersion is null and baseUrl is provided):
- Calls
encodeEntityName()to build the PHP FQCN - Calls
fetchFromApi(): runsMetaDataEntityList,ResourceNameList,RequiredFieldsin parallel, thenSerializationShowFields+SerializationListingFieldsin parallel - Saves raw snapshot to
data.json
Incremental mode (when fromVersion is set):
- Loads
{fromVersion}/data.json - Loads
{fromVersion}/changes.json(if it exists) - Calls
applyChanges(baseData, baseChanges, 'json')to produce the starting point - 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.
| Option | Type | Description |
|---|---|---|
entity | string | Entity path |
version | string | Target version (default: latest) |
fromVersion | string | "latest" to auto-select, "fresh" to skip inheritance, or a specific "vN" |
Inheritance behaviour:
fromVersion | What 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 provided | Interactive 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.
| Input | bundleCrud | Output |
|---|---|---|
"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.
layer | Delegates 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:
| Order | Section | Target in data |
|---|---|---|
| 1 | changes.listingFields | data.listingFields |
| 2 | changes.formModalFields | data.formModalFields (created if absent) |
| 3 | changes.showFields | data.showFields |
| 4 | changes.formFields | data.fields + data.requiredFields |
| 5 | changes.filterFields | data.requiredFields + data.defaultFilters |
| 6 | changes.sideboxFields | data.sideboxFields |
| 7 | changes.exclusionList | deletes from data.listingFields and data.fields |
| 8 | changes.actions | data.actions (full replacement) |
formFields required/optional handling: Four aliases all supported:
| Key | Meaning |
|---|---|
makeRequired | Add field names to requiredFields |
makeOptional | Remove field names from requiredFields |
required | Same as makeRequired |
optional | Same 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| Scenario | Result |
|---|---|
Field in selected only | Included |
Field in hide only | Excluded |
Field in both selected and hide | Excluded (hide wins) |
selected is empty / absent | All 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 key | Target | Function used |
|---|---|---|
mainColumns | data.mainColumns | applyObjectArrayChanges(…, 'key') |
filterFields | data.filterFields | applyObjectArrayChanges(…, 'key') |
formInputs | data.formInputs | applyFormSectionChanges() |
displayFields | data.displayFields | applyFormSectionChanges() |
customActions | data.customActions | applyCustomActionChanges() |
defaultActions | data.defaultActions | applyCustomActionChanges() |
applyObjectArrayChanges(array, changes, keyField)
Modifies an array of objects. Operations applied in this order:
hide— remove objects whosekeyFieldvalue is in the hide listmodify— shallow-merge override properties onto matched objectsorder— 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).
hide— removes field keys (or objects with matching.key) from every section’sfieldsarraysectionOrder— reorders sections bytitle
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):
- Relation types (
MANY_TO_ONE,ONE_TO_MANY,MANY_TO_MANY,ONE_TO_ONE) →SELECT_ASYNC - Currency name/format match →
CURRENCY - Percent name/format match →
PERCENT - Boolean type →
BOOLEAN - Integer types →
NUMBER - Decimal →
DECIMAL - Float →
FLOAT - Text →
TEXTAREA - Date →
DATE - Datetime →
DATETIME - File →
FILE - String with special name patterns (
icon,color,*Phone*,*Email*) →ICON,COLOR,TEL,EMAIL - String (default) →
TEXT - JSON document →
JSON - 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 → targetResourcelist - 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.
| Option | Type | Description |
|---|---|---|
entity | string | Entity path |
fromVersion | string | Source version (default: latest) |
dryRun | boolean | Preview only — no files written |
Sections matched from legacy data:
| Legacy export | Target in changes.json |
|---|---|
listingData | listingFields.selected |
showData (sections) | showFields.sections |
formData (sections) | formFields.sections |
listingFilters | filterFields.defaults |
Field matching algorithm (applied within each section):
- Exact normalized match (
name.toLowerCase().replace(/\s+/g, '')) - Alias lookup — hard-coded synonyms for common legacy label differences:
| Legacy label (normalized) | Mapped to |
|---|---|
internalnote, internalcomment | notes |
noteontheinvoice, condition | conditions |
automaticemails, automaticemail | autoEmail |
automaticinterests, automaticinterest | autoInterests |
enabled | disabledAt |
createddate, created | createdAt |
updatedate, update, updated | updatedAt |
comptabilisable | accountable |
deliverydate | deliveryDate |
clientreference, clientref | clientRef |
conversionrate | conversionRate |
uniqueid, uniqueidentifier | uniqueId |
datestart | date |
deliverydatestart | deliveryDate |
tags, tagging | tag |
- Substring match (one name contains the other)
- Prefix/suffix stripping: removes
internal,external,auto,automaticprefixes anddate,time,atsuffixes 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 }