@flusys/nestjs-entity-builder 9.1.2 → 9.2.0

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/README.md CHANGED
@@ -15,7 +15,7 @@ Works on PostgreSQL and MySQL (via `SchemaDialectAdapterService`). Import `Entit
15
15
 
16
16
  | Path | Purpose | Permission |
17
17
  | --- | --- | --- |
18
- | `entity-builder/entity-definitions/create-entity` | Create entity + table (+ initial fields) in one DDL transaction. `flowEndpoints` (any of the generic API controller's endpoints: `insert`, `insertMany`, `getById`, `getByIds`, `getAll`, `getByFilter`, `update`, `updateMany`, `bulkUpsert`, `delete`) also saves one ready-made flow for the entity after the commit - slug `<code-with-dashes>` (e.g. `customer-ticket`), named after the entity, every Request step with `permission` access asking for the entity's own permission (`entity_builder.entity.<code>.create` for insert / insertMany, `.read` for the reads, `.update` for update / updateMany, `.create` AND `.update` for bulkUpsert, `.delete` for delete - the actions created with the entity), since a flow's access is its only gate, each write endpoint transactional and each read endpoint without a transaction - with one Request step per endpoint on its own path, `POST api-flows/<code-with-dashes>/<endpoint-in-kebab-case>` (e.g. `customer-ticket/get-by-ids`, `ENTITY_FLOW_PATHS`), each with its own body; there is no Request step on the flow's own URL. Each endpoint's steps are ids `<endpoint>_<step>` (e.g. `getById_record`) laid out one below the other. Single endpoints are Request -> entity step -> respond with its result; `getAll` / `getByFilter` take an optional equality filter per scalar field (`getByFilter` answers the first match or 404); `getByIds` takes `ids` (`in` filter); `insertMany` / `updateMany` / `bulkUpsert` have a `list` body type - the request body is the list of records itself (`[{ ... }, { ... }]`), each item checked against the entity fields (insert: the insert inputs; update many: `id` required, the rest optional; bulk upsert: all optional) - are one write step on the list itself (`<endpoint>_records`, `target: many`, `items: input`, each item's fields read as `loop.item.<field>`, up to `FLOW_LIMITS.MAX_WRITE_ITEMS`), all or nothing in one transaction, answering the saved records in order (`context.<endpoint>_records.items`; `updateMany` updates each item by its own `id` and fails on one that does not exist; `bulkUpsert` is an update with `onNotFound: insert` - it updates an item with an `id` and creates one without, or whose record is gone). Every Request body declares each field by its type: a choice with its options, a multi-select as a list of those choices (`itemType: choice`, or `string` without options), a relation or file as an id (`uuid`), JSON as an object; write endpoints also copy the field's own `validationRules` a body can check (`min` / `max` on numbers, `minLength` / `maxLength` on text), so a bad value is refused per field before any step runs; read filters check only the type. `flowTexts` (`names` of each endpoint's Request step, `inputLabels` for `id` / `ids` / `search`, `noMatchMessage`) carries the text written into the flow in the creator's language - English for anything left out. The flow is saved after the commit: when it fails the entity stays and the failure comes back in the response's `warnings` (`entity_builder.schema.warning.endpoint.flow.failed`, with the cause as a nested message ref), next to a failed permission set-up (`...follow.up.failed`). It is saved unpublished (`version` 0), so its URLs answer 404 until someone publishes it. It is an ordinary flow the user can change or delete later. Needs `flow_definition.create` too; a slug already in use is a plan blocker | `entity_builder.entity_definition.create` |
18
+ | `entity-builder/entity-definitions/create-entity` | Create entity + table (+ initial fields) in one DDL transaction. `flowEndpoints` (any of the generic API controller's endpoints: `insert`, `insertMany`, `getById`, `getByIds`, `getAll`, `getByFilter`, `update`, `updateMany`, `bulkUpsert`, `delete`) also saves one ready-made flow for the entity after the commit - slug `<code-with-dashes>` (e.g. `customer-ticket`), named after the entity, every Request step with `permission` access asking for the entity's own permission (`entity_builder.entity.<code>.create` for insert / insertMany, `.read` for the reads, `.update` for update / updateMany, `.create` AND `.update` for bulkUpsert, `.delete` for delete - the actions created with the entity), since a flow's access is its only gate, each write endpoint transactional and each read endpoint without a transaction - with one Request step per endpoint on its own path, `POST api-flows/<code-with-dashes>/<endpoint-in-kebab-case>` (e.g. `customer-ticket/get-by-ids`, `ENTITY_FLOW_PATHS`), each with its own body; there is no Request step on the flow's own URL. Each endpoint's steps are ids `<endpoint>_<step>` (e.g. `getById_record`) laid out one below the other. Each Request step stands in for its controller endpoint (`config.contract`: `insert`, `insert_many`, `get_by_id`, `get_by_ids`, `get_all`, `get_by_filter`, `update`, `update_many`, `bulk_upsert`, `delete`) and every Respond step answers the controller envelope with its message key `<entity_code>.<action>.success` (e.g. `customer_ticket.get.all.success`, `.create.many.success`, `.delete.success`). Single endpoints are Request -> entity step -> `single` respond with its result (`getById` honours `select`); `getAll` takes `FilterAndPaginationDto` (`withDeleted` included) and `?q=` search through a `fromRequest` Find records step and answers `list` with paging `meta`; `getByFilter` takes an optional equality filter per scalar field and answers the first match or a 404 `message`; `getByIds` takes `ids` (`in` filter) and `select` (a `fromRequest` Find records step) and answers `list` with the controller's one-page `meta` (`page` 0, `pageSize` the count); `delete` takes `DeleteDto` (one id or a list), refuses `restore` / `permanent` with a Validate step (no flow step restores or purges) and answers `message` with `messageVariables.count`; `insertMany` / `updateMany` / `bulkUpsert` have a `list` body type - the request body is the list of records itself (`[{ ... }, { ... }]`), each item checked against the entity fields (insert: the insert inputs; update many: `id` required, the rest optional; bulk upsert: all optional) - are one write step on the list itself (`<endpoint>_records`, `target: many`, `items: input`, each item's fields read as `loop.item.<field>`, up to `FLOW_LIMITS.MAX_WRITE_ITEMS`), all or nothing in one transaction, answering `bulk` with the saved records in order (`context.<endpoint>_records.items`, `meta: { count, total, failed }`; `updateMany` updates each item by its own `id` and fails on one that does not exist; `bulkUpsert` is an update with `onNotFound: insert` - it updates an item with an `id` and creates one without, or whose record is gone). Every Request body declares each field by its type: a choice with its options, a multi-select as a list of those choices (`itemType: choice`, or `string` without options), a relation or file as an id (`uuid`), JSON as an object; write endpoints also copy the field's own `validationRules` a body can check (`min` / `max` on numbers, `minLength` / `maxLength` on text), so a bad value is refused per field before any step runs; read filters check only the type. `flowTexts` (`names` of each endpoint's Request step, `inputLabels.id` for get-by-filter / bulk-upsert, `noMatchMessage`, `deleteOnlyMessage`) carries the text written into the flow in the creator's language - English for anything left out. The flow is saved after the commit: when it fails the entity stays and the failure comes back in the response's `warnings` (`entity_builder.schema.warning.endpoint.flow.failed`, with the cause as a nested message ref), next to a failed permission set-up (`...follow.up.failed`). It is saved unpublished (`version` 0), so its URLs answer 404 until someone publishes it. It is an ordinary flow the user can change or delete later. Needs `flow_definition.create` too; a slug already in use is a plan blocker | `entity_builder.entity_definition.create` |
19
19
  | `entity-builder/entity-definitions/plan-create-entity` | Dry run of the above: the real `CREATE TABLE` SQL, nothing is created | `...entity_definition.create` |
20
20
  | `entity-builder/entity-definitions/plan-change` | Dry run of any change below: exact SQL + reverse SQL, data checks, blockers, warnings, affected flows. Plans count records (never show their values), so planning needs the permission that applies the change | `...entity_definition.update` (`...delete` for `drop_entity` / `purge_field`) |
21
21
  | `entity-builder/entity-definitions/apply-change` | Apply `add_field`, `update_field`, `deprecate_field`, `restore_field`, `update_entity`, `repair`. Answers the plan of what ran (`downSql` = the undo statements of what ran) plus `result` (the change's summary, e.g. repair's `fixed`) | `...entity_definition.update` |
@@ -35,8 +35,8 @@ Works on PostgreSQL and MySQL (via `SchemaDialectAdapterService`). Import `Entit
35
35
  | `entity-builder/flow-executions/{list,get}` | Run history; each run keeps the Request step it came in through (`endpointNodeId`, and `endpointPath` as it was then), and `list` filters by `status`, `trigger` (`webhook` / `test`) and `endpointNodeId` | `...flow_definition.read` |
36
36
  | `entity-builder/bundles/settings` | `{ readOnly, variables }`: whether the designer is locked here, and the names of the variables this environment defines | `...entity_definition.read` or `...flow_definition.read` |
37
37
  | `entity-builder/bundles/export` | `{ entityCodes?, flowSlugs? }`: entities and published flows as a bundle, with its `checksum` (see **Promoting between environments**) | `...entity_definition.read` + `...flow_definition.read` |
38
- | `entity-builder/bundles/plan-import` | Multipart: file `bundle` + `deprecateMissingFields?`: every step the import would run, in order, with each previewable step's real plan; nothing changes | `...entity_definition.create/update` + `...flow_definition.create/update` |
39
- | `entity-builder/bundles/apply-import` | Multipart: file `bundle` + `deprecateMissingFields?`, `confirm?`, `expectedChecksum?`, `expectedPlanChecksum?` (required with `confirm`): runs the plan; works while the designer is read-only | same as `plan-import` |
38
+ | `entity-builder/bundles/plan-import` | Multipart: file `bundle` + `deprecateMissingFields?`, `deactivateMissingFlows?`: every step the import would run, in order, with each previewable step's real plan; nothing changes | `...entity_definition.create/update` + `...flow_definition.create/update` |
39
+ | `entity-builder/bundles/apply-import` | Multipart: file `bundle` + `deprecateMissingFields?`, `deactivateMissingFlows?`, `confirm?`, `expectedChecksum?`, `expectedPlanChecksum?` (required with `confirm`): runs the plan; works while the designer is read-only | same as `plan-import` |
40
40
 
41
41
  `entity_builder.entity.*` is a wildcard grant covering every dynamic entity (seeded by `seed:admin`); it covers only dynamic entities, not the static schema-admin permissions (`entity_builder.entity_definition.*`, `entity_builder.field_definition.*`, `entity_builder.flow_definition.*`).
42
42
 
@@ -52,6 +52,7 @@ Works on PostgreSQL and MySQL (via `SchemaDialectAdapterService`). Import `Entit
52
52
  - Each change runs in one locked transaction and is logged in `eb_schema_change_log` with the SQL that ran and the reverse SQL (`downSql`). On PostgreSQL a change that fails rolls back completely. MySQL commits every DDL statement at once (plans warn `...warning.mysql.no.rollback`): a failed change is undone by running the reverse of each statement that had run, newest first; the error carries `undo: { reverted, pendingSql, dataNotReverted }` and, when an undo statement fails, the key `entity_builder.schema.failed.not.reverted` with the pending statements also in the `FAILED` log row's `downSql`. Backfilled values are not undone. Either way a `FAILED` log row keeps the statement that failed and the error is mapped to a human-readable one. A follow-up step that fails after commit (permission cleanup) never fails the change; it is reported as a warning.
53
53
  - Changes made outside the entity builder (a column dropped by hand) show up in `inspect` and are fixed by `repair`; extra columns are reported and never dropped automatically.
54
54
  - The entity/field metadata `update` endpoints are gone on purpose: every edit goes through `apply-change` so the table, the metadata and the schema cache cannot disagree.
55
+ - **Caching**: entity, field and flow definition reads (`get-all` / `get-by-id`) are cached through the shared `HybridCache` with version stamps. Every write bumps the stamp **after it commits**: a schema change bumps all three (`DefinitionCacheService.clearAll`, also after a failed MySQL change whose DDL had already committed), flow save / delete / publish / draft restore or discard bump the flow stamp, and a bundle import goes through those same services. The runtime never reads definitions from that cache: `api-flows/:slug`, Call flow steps and record access load the flow and the entity from the database on every call, so a flow switched off, unpublished or given a new API key on one instance stops working on every instance at once. The runtime `EntitySchema` of an entity is kept in process per tenant + entity id and rebuilt whenever the `schemaVersion` read from the database moves (dropped entities are forgotten on the next lookup). The per-step rate limit is counted in memory per instance and per tenant.
55
56
  - Identifiers must match `^[a-z][a-z0-9_]{2,59}$`, not be an SQL reserved word, and the table (`eb_<code>`) must not already exist. Every DDL change takes a per-entity lock (`pg_advisory_xact_lock` / `GET_LOCK`, names over 64 characters hashed) - plus the lock of any relation target it points at - and is written to `eb_schema_change_log`.
56
57
  - Every filter/sort key of a flow's **Find records** step and of a `lookup` is checked against the entity's real fields; unknown keys are rejected (never interpolated into SQL). A filter value is a plain value (`=`; `null` means `IS NULL`) or `{ op, value? }`. Each operator has a value kind (`FILTER_OP_VALUE_KIND`):
57
58
  - **single** - `eq`, `ne` (`null` = is / is not null), `gt`, `gte`, `lt`, `lte`, and the case-insensitive text operators `ieq`, `contains`, `not_contains`, `starts_with`, `ends_with` (`%`, `_`, `\` in the text match literally; a blank text is refused, since it would match every record).
@@ -97,7 +98,11 @@ A flow is an endpoint (`POST /api-flows/<slug>`) whose behaviour is a graph of s
97
98
  - **Several URLs in one flow**: a flow can hold more than one **Request** step, and each one carries everything about its URL and its runs in its `config` (`ITriggerConfig`): `path`, body (`bodyType`, `inputSchema`), access (`authMode`, `permissions`, and `apiKey` for API-key access), `isTransactional`, `timeoutMs` and `rateLimitPerMinute` - each left out at its default. The flow itself has none of these: it only groups its URLs under one slug, switches them on and off together (`isActive`), keeps their run history (`retainExecutions`) and is versioned as a whole. The one without a `path` answers on `POST /api-flows/<slug>`; each other one sets `config.path` (1-63 lowercase letters, digits and dashes, starting with a letter) and answers on `POST /api-flows/<slug>/<path>` - e.g. a `customer-ticker` flow with `insert` (an object) and `insert-many` (a list) sharing the steps after them. Saving needs at least one Request step (`trigger.missing`), at most one without a path (`trigger.main.duplicate`) and distinct, valid paths (`trigger.path.duplicate` / `.invalid`); every Request step's fields are checked the same way (errors on a path's fields are named `<path>: <field>`). A flow whose every Request step has a path answers 404 on its own URL. `flowEndpoints()` / `findEndpoint()` / `startEndpoint()` (`flow.types.ts`) turn the Request steps into `IFlowEndpoint`s with every setting's default filled in; the run starts at `IFlowRunState.startAt` and takes that step's transaction and time limit. Save checks each step's settings (`trigger.transaction.invalid`, `trigger.timeout.range` 1-120 s, `trigger.rate.limit.range` 0-100,000), and every warning that depends on them - record steps open without sign-in (`keyless.entity`), transactions, HTTP inside a transaction - looks only at the steps that Request step's runs reach. The test run takes `endpoint` (a path; left out, the flow's own URL - an unknown one is refused with `flow.endpoint.not.found`), and a Call flow step takes `config.endpoint` to start the called flow at one of its paths, checked on save against that path's body (`node.flow.endpoint.unknown`, or `.required` when the called flow has no own URL) and at run time (`error.called.flow.endpoint.not.found`).
98
99
  - **Body type** (a Request step's `config.bodyType`, default `object`, left out when `object`): `object` - the body is one object and the input fields are its keys (`input.<name>`); `list` - the body itself is a list, like `insert-many` (`POST api-flows/<slug>` with `[{...}, {...}]`), the input fields describe each item, every item is checked the same way (errors name the index: `[0].sku`; a body that is not a list is refused with `flow.input.body.not.list`), and the flow reads the list as `input` (a For each over `input`, or `input.0.<name>`). With no fields declared a list body is passed through as-is. A Call flow step sends a list-bodied flow one expression, `inputList`, instead of named `input`s; saving checks the step matches the called flow's body type (`node.flow.input.list.required` / `.unexpected`). The test run accepts a list `input` too.
99
100
  - **Input**: each Request step declares its request fields (`config.inputSchema`) (type, required, default, min/max, choices). The body is checked and converted with the same validator entity records use; undeclared keys are dropped; every problem is returned at once (400). An `object` field may declare its own `fields`; an `array` field may declare an `itemType` (any type but `array`) that every item must have - min/max/length/options then apply to each item - and a list of objects (`itemType: 'object'`) declares the `fields` of each item. Nested values are checked the same way (undeclared keys inside them are dropped too), errors name their path (`address.city`, `items[0].qty`), and nesting goes at most `FLOW_LIMITS.MAX_INPUT_DEPTH` (4) levels. Without `fields` / `itemType` the value is only checked to be an object / a list. Generated entity flows declare `ids` as a list of ids and a multi-select field as a list of its choices; their bulk flows use a `list` body whose items are the entity fields.
100
- - **Who can call it**: set on each Request step - there is no flow-level access. `config.authMode`: `jwt` (any signed-in user; the default, stored as no setting), `permission` (`config.permissions`: an AND / OR rule of permission codes (`ILogicNode` of nestjs-shared: `{ type: 'action', actionId: <code> }` or `{ type: 'group', operator: 'AND' | 'OR', children }`, nested up to 5 group levels, at most 50 codes; evaluated by `evaluatePermissionLogic()`, `*` / `prefix.*` grants match); none: `entity_builder.flow.<slug>.execute`, provisioned as an IAM action on publish - a rule names existing actions, which are not re-registered. The guard checks it with `SharedPermissionCacheService.assertPermissionLogic`), `api_key` (the step's own key, sent as `x-api-key`: the designer makes it (`fk_<8 hex>_<32 hex>`) and sends it in `config.apiKey` once; every save replaces it with `{ prefix, hash }` (SHA-256, `sealApiKeys()`), so the key itself is never stored and cannot be shown again. The guard compares in constant time against the published step's hash; a wrong or missing key answers 401 `flow.api.key.invalid`, and one step's key never opens another. A step without a key cannot be saved (`trigger.api.key.required`, or `.invalid` for a malformed one); a new key replaces the old one once the flow is published; a step that leaves API-key access drops its key), `public`, or `internal` - **other flows only**: the step has no URL (it answers 404), only other flows' Call flow steps start there, and that run takes the calling flow's user. A flow whose every Request step is `internal` is a **function** (`isFunctionFlow()`). A URL reached without signing in (`public`, `api_key`) runs with no `user`: permission checks and the caller's Company & Branch check deny, while its record steps run for anyone who can call it (validator warning `keyless.entity`, naming the URLs and the entities they read or change). Unknown, inactive and never-published flows all answer 404. Each Request step's per-caller-IP rate limit (`config.rateLimitPerMinute`, default 60, 0 = unlimited; counted per step) runs **before** credentials are checked (in memory per server instance, fixed one-minute windows, at most 10,000 tracked callers - the oldest window is dropped beyond that).
101
+ - **Request type** (a Request step's `config.contract`, left out for `custom`): which generic API controller endpoint the step stands in for (`FLOW_REQUEST_CONTRACTS`). It fixes the body type (`list` for `insert_many` / `update_many` / `bulk_upsert`, `object` otherwise; `bodyType` counts only for `custom`) and checks that endpoint's own DTO fields ahead of the declared ones (`contractFields`): `get_all` takes `FilterAndPaginationDto` - `filter`, `pagination: { currentPage, pageSize }` (defaults 0 / 10, at most `MAX_QUERY_LIMIT`), `sort` (`ASC` / `DESC` per plain field name), `select`, `withDeleted` - with search as `?q=`; `get_by_id` `{ id, select }`; `get_by_ids` `{ ids, select }` (`select` may be comma-separated); `update` / `update_many` a required `id` (with no declared fields the rest of the body is kept, only `id` checked); `delete` `DeleteDto` (`id` one id or a list, read as a list; `type`). A declared field may not reuse a contract field's name. Every query parameter is readable as `request.query.<name>` whatever the type.
102
+ - **Respond format** (a Respond step's `config.format`, default `raw`): `raw` answers `body` / `bodyExpression` as built. `single`, `list`, `bulk` and `message` answer the API controller envelope `{ success, message, messageKey, messageVariables?, data?, meta? }` with the author's `message`, `messageKey` (both required) and `messageVariables` (expressions); `success` is `statusCode < 400`, and `ResponseMetaInterceptor` adds `_meta` like any controller answer. `single`: `data` is the body. `list` (a `bodyExpression` list): `meta: { total, page, pageSize, count, hasMore, totalPages }`, where `total` / `page` / `pageSize` default to the list's length, the request's `pagination.currentPage` (else 0) and `pagination.pageSize` (else the list's length), exactly as `getAll` computes them. `bulk` (a list): `meta: { count, total, failed }`, `total` defaulting to the length of a list body. `message`: no data. A non-list `list` / `bulk` value or a negative / fractional count fails the step (`entity_builder.flow.error.respond.*`).
103
+ - **Find records from the request** (`entity_query` `config.fromRequest: true`): for a `get_all` Request step, the query also takes the request's `filter` (under the step's own filter, which wins), `sort` and `?q=` search (when the step sets none), `pagination` (else page 0 of `limit`; page size capped at `MAX_QUERY_LIMIT`), `select` (`id` always kept) and `withDeleted: true` (soft-deleted records too). Output: `{ items, total, page, pageSize }` (every query). A `get_by_ids` step reads it too, for its `select`. Reached from another request type it is a save warning.
104
+ - **Get record from the request** (`entity_get` `config.fromRequest: true`): for a `get_by_id` Request step, the record carries only the fields its `select` names (`id` always kept; every field when it names none). Reached from another request type it is a save warning.
105
+ - **Who can call it**: set on each Request step - there is no flow-level access. `config.authMode`: `jwt` (any signed-in user; the default, stored as no setting), `permission` (`config.permissions`: an AND / OR rule of permission codes (`ILogicNode` of nestjs-shared: `{ type: 'action', actionId: <code> }` or `{ type: 'group', operator: 'AND' | 'OR', children }`, nested up to 5 group levels, at most 50 codes; evaluated by `evaluatePermissionLogic()`, `*` / `prefix.*` grants match); none: `entity_builder.flow.<slug>.execute`, provisioned as an IAM action on publish - a rule names existing actions, which are not re-registered. The guard checks it with `SharedPermissionCacheService.assertPermissionLogic`), `api_key` (the step's own key, sent as `x-api-key`: the designer makes it (`fk_<8 hex>_<32 hex>`) and sends it in `config.apiKey` once; every save replaces it with `{ prefix, hash }` (SHA-256, `sealApiKeys()`), so the key itself is never stored and cannot be shown again. The guard compares in constant time against the published step's hash; a wrong or missing key answers 401 `flow.api.key.invalid`, and one step's key never opens another. A step without a key cannot be saved (`trigger.api.key.required`, or `.invalid` for a malformed one); a new key replaces the old one once the flow is published; a step that leaves API-key access drops its key), `public`, or `internal` - **other flows only**: the step has no URL (it answers 404), only other flows' Call flow steps start there, and that run takes the calling flow's user. A flow whose every Request step is `internal` is a **function** (`isFunctionFlow()`). A URL reached without signing in (`public`, `api_key`) runs with no `user`: permission checks and the caller's Company & Branch check deny, while its record steps run for anyone who can call it (validator warning `keyless.entity`, naming the URLs and the entities they read or change). Unknown, inactive and never-published flows all answer 404. Each Request step's per-caller-IP rate limit (`config.rateLimitPerMinute`, default 60, 0 = unlimited; counted per step) runs **before** credentials are checked (in memory per server instance and tenant, fixed one-minute windows, at most 10,000 tracked callers - the oldest window is dropped beyond that).
101
106
  - **Find record** filters accept the `{ op, value }` operators (the value side is an expression). A filter entry is an operator only when it is authored as exactly `{ op, value? }` with a known `op` (any other key, or an unknown `op`, and it is not a condition - so an expression such as `{ type: 'arithmetic', op: 'add', ... }` is a plain value). A value resolved at run time (from input, a variable, a step result) is only ever an operand: an object or list is compared for equality and refused as not one value, never read as an operator, so `{ "op": "not_null" }` sent as input cannot widen a Find, lookup, update or delete. On a Find an entry that resolves to nothing (for a range: either bound) is left out; on an update / delete by filter it fails the step (`flow.error.filter.value.missing`) instead of widening the write. A range's value must be `{ from, to }` (`flow.shape.filter.range.invalid`). Flows and lookups resolve every entry through `resolveFilterEntry()` (`rule-values.ts`).
102
107
  - **Checks cannot be carried past**: a failed Validate or Check permission step (like Save progress) always ends the run, whatever its `onError` says.
103
108
  - **Access is the gate**: a Request step's *Who can call it* decides who may start a run, and then every step reads and writes records with no per-entity permission check (`entity_builder.entity.<entity>.<action>` counts only where an access rule or a Check permission step names it, as the ready-made entity flow does) and may notify any company. Finer rules are the flow's own: a **Check permission** step, a **Company & Branch check**, or `HAS_PERMISSION()` in a condition. Since whoever may save flows decides what each URL exposes, grant `flow_definition.create` / `.update` like deploy rights.
@@ -116,18 +121,19 @@ A flow is an endpoint (`POST /api-flows/<slug>`) whose behaviour is a graph of s
116
121
 
117
122
  Entities and flows are data, so TypeORM migrations never see them. They move from dev to staging to production as a **bundle**: a JSON file kept in git, applied to each environment through the same services the designer uses (`DefinitionBundleService`).
118
123
 
119
- - **Export** (`bundles/export`, or `npm run entity-bundle -- export <file>` in `FLUSYS_NEST`) writes every entity that has a table and every **published** flow (a `DRAFT` entity and an unpublished flow are left out with a warning). Entities are keyed by `code` and fields by their `id` and `code`; a relation names its target by **entity code**, never by id. Entities, fields and flows are listed in code-point order, object keys are sorted at every level (PostgreSQL and MySQL return JSON keys in different orders), and there are no timestamps. The same definitions always give the same file and checksum, and a PR diff shows exactly what changes. Each Request step's API key is left out, and every occurrence of this environment's variable values in a flow becomes `${var:NAME}`.
120
- - **Plan** (`bundles/plan-import`) compares the bundle with this environment. Entities and fields are matched by id first, then by code among those no id claimed (`matchDefinitions`). An import keeps the ids definitions were designed with when they are free, so a field renamed in dev arrives as a **rename** (`update_field` with `code`, which rewrites the flows that use it), not as a new field. Steps run in this order: entities switched back on, new entities (their relation fields wait until every new entity exists), restore / rename / update / add / deprecate fields of existing entities (renames ordered so none takes a code another field still has, see **Renames**), the new entities' relation fields, entities switched off, then flows with called flows before their callers. Each step that can be previewed on its own carries the real `plan-change` / `plan-create-entity` result: SQL, data checks, blockers. Only a step that runs on something an earlier step creates, restores or frees (a new entity, a relation to one, a change to a restored field, a rename or new field taking a code a rename frees) goes without a preview, and it says so. A flow is compared with its **published** version (API keys ignored). It is validated here when nothing it depends on changes in the same import, and it is always checked against the importer's own permissions: code steps need `flow_definition.code`, exactly as for a save. `mayLoseData` is `destructive` (a previewed step loses data) or a structural change that could not be previewed (type, required, unique, soft delete, audit). `checksum` is the bundle's; `planChecksum` covers the bundle, the options and every step (kind, subject, changes, whether its preview loses data), not the SQL or row counts. A `DRAFT` entity (no table) in the bundle or in this environment blocks the import.
124
+ - **Export** (`bundles/export`, or `npm run entity-bundle -- export <file>` in `FLUSYS_NEST`) writes every entity that has a table and every **published** flow (a `DRAFT` entity and an unpublished flow are left out with a warning). Entities are keyed by `code` and fields by their `id` and `code`; a relation names its target by **entity code**, never by id. Entities, fields and flows are listed in code-point order, object keys are sorted at every level (PostgreSQL and MySQL return JSON keys in different orders), and there are no timestamps. The same definitions always give the same file and checksum, and a PR diff shows exactly what changes. Each Request step's API key is left out, and every occurrence of this environment's variable values in a flow becomes `${var:NAME}`. `entityCodes` / `flowSlugs` (CLI: `--entities a,b`, `--flows x,y`; an empty list there means everything) narrow the export. Its `warnings` then name what the selection leaves behind: a relation to an entity outside it (`entity_builder.bundle.export.relation.left.out`) and a Call flow step to a flow outside it (`...export.called.flow.left.out`). The environment that imports the bundle must already have those. An export also warns about each HTTP step that sends a credential written into the step, such as an `Authorization`/token/secret/key/password/cookie header or a query parameter of that kind. These warn as `entity_builder.bundle.flow.secret.written`, because the bundle holds them as plain text in git. A value from `${var:NAME}`, the input or the context is not flagged, and neither is a bare `Bearer` / `Basic` scheme.
125
+ - **Plan** (`bundles/plan-import`) compares the bundle with this environment. Entities and fields are matched by id first, then by code among those no id claimed (`matchDefinitions`). An import keeps the ids definitions were designed with when they are free, so a field renamed in dev arrives as a **rename** (`update_field` with `code`, which rewrites the flows that use it), not as a new field. Steps run in this order: entities switched back on, new entities (their relation fields wait until every new entity exists), restore / rename / update / add / deprecate fields of existing entities (renames ordered so none takes a code another field still has, see **Renames**), the new entities' relation fields, entities switched off, then flows with called flows before their callers, then (with `deactivateMissingFlows`) the flows switched off, callers before the flows they call. A step switching a flow off is checked like any save of it: the importer must be allowed to author it, and it must still validate. Each step that can be previewed on its own carries the real `plan-change` / `plan-create-entity` result: SQL, data checks, blockers. Only a step that runs on something an earlier step creates, restores or frees (a new entity, a relation to one, a change to a restored field, a rename or new field taking a code a rename frees) goes without a preview, and it says so. A flow is compared with its **published** version (API keys ignored). It is validated here when nothing it depends on changes in the same import, and it is always checked against the importer's own permissions: code steps need `flow_definition.code`, exactly as for a save. A created or updated flow whose step settings hold UUIDs (record, user, company or `emailConfigId` ids) warns `entity_builder.bundle.flow.fixed.ids` with the list: such an id belongs to the environment it was typed in. An id written as `${var:NAME}` is not counted, and an email template slug or a config id is not looked up in this environment. `mayLoseData` is `destructive` (a previewed step loses data) or a structural change that could not be previewed (type, required, unique, soft delete, audit). `checksum` is the bundle's; `planChecksum` covers the bundle, the options and every step (kind, subject, changes, whether its preview loses data), not the SQL or row counts. A `DRAFT` entity (no table) in the bundle or in this environment blocks the import. **Permissions are never promoted** (roles and grants are IAM data). With IAM (`PERMISSION_ACTION_REGISTRY` and its optional `findMissingCodes`), the plan asks once about every code involved. A step that creates permissions no role holds here yet warns `entity_builder.bundle.permissions.to.grant`: a new entity's create/read/update/delete codes, and the execute code of a flow whose URL asks for it. Grant those after the import. A created or updated flow whose access rules, Permission check steps or literal `HAS_PERMISSION` / `HAS_ANY_PERMISSION` / `HAS_ALL_PERMISSIONS` codes name a code this environment does not have, and the import does not create, warns `entity_builder.bundle.flow.permission.unknown`, because that check refuses everyone. Wildcard codes are not checked.
121
126
  - **Apply** (`bundles/apply-import`) refuses a bundle whose checksum is not `expectedChecksum` (409 `entity_builder.bundle.checksum.mismatch`: not the bundle that was reviewed). It then re-plans and refuses a plan that is not `expectedPlanChecksum` (409 `entity_builder.bundle.plan.changed`: the environment changed since the review), when anything is blocked (409, every reason in `errors` as `{ field: <subject>, messageKey, messageVariables }`, since `errors` is what the global exception filter passes on), or when `mayLoseData` and no `confirm: true` (400, the steps in `errors`) or no `expectedPlanChecksum` (400 `entity_builder.bundle.plan.checksum.required`: a confirmation covers the reviewed steps only, never ones the environment grew since). `confirm` is passed on to every step. Entity steps go through `SchemaSyncService.createEntity` / `SchemaEvolutionService.apply`, so each is one locked, logged transaction. Flow steps are saved through `FlowDefinitionService` (validated, permission-checked) and **published** as a new version with the note `Imported from bundle <checksum>`. The import is not one transaction (MySQL commits DDL at once): the first step that fails stops it (`completed: false`, its `error` and `blockers`), the earlier ones stay, and the later ones are `skipped`. Importing the same bundle again picks up where it stopped, because every step compares before it changes.
122
- - **Never removes anything.** Entities, fields and flows this environment has and the bundle does not are listed as `extras`. With `deprecateMissingFields` the missing fields are deprecated, which keeps their columns and data. Dropping stays a deliberate `apply-destructive-change`. An entity whose code changed (same id, different code) blocks the import, because an entity code cannot be renamed.
127
+ - **Never removes anything.** Entities, fields and flows this environment has and the bundle does not are listed as `extras`. With `deprecateMissingFields` the missing fields are deprecated, which keeps their columns and data. With `deactivateMissingFlows` every missing flow that is published and active gets a `deactivate_flow` step instead: its live version is saved with `isActive: false` and published (callers get 404; an unpublished draft is replaced, never published along with it, with `entity_builder.bundle.flow.draft.replaced`). A step for a flow that an active flow of the bundle still calls is blocked (`entity_builder.bundle.flow.deactivate.called`). This is how a flow deleted where it is designed stops answering in a read-only environment, where `delete` is refused. Dropping stays a deliberate `apply-destructive-change`. An entity whose code changed (same id, different code) blocks the import, because an entity code cannot be renamed.
128
+ - **One import at a time**: `apply-import` takes a session lock (`eb_bundle_import`: `pg_try_advisory_lock` on PostgreSQL, `GET_LOCK(name, 0)` on MySQL, through `SchemaDialectAdapterService.tryAcquireSessionLock`). It holds the lock on a connection of its own for the plan and every step, and releases it before that connection goes back to the pool. A second import meanwhile answers 409 `entity_builder.bundle.import.in.progress` at once. Planning takes no lock.
123
129
  - **API keys**: a Request step with API-key access keeps the key this environment already stores for that step (matched by node id). Otherwise a new key is generated, returned **once** in `apiKeys`, and only its hash is stored. A key is answered as soon as its flow is saved, so it is not lost when publishing fails afterwards (the next import keeps it).
124
130
  - **Ids**: a design-time id is reused only when no row has it, soft-deleted rows included.
125
131
  - **Renames**: renames run in an order that never takes a code another field still has. A rename waits for the one that frees its new code (`b -> c` before `a -> b`), a new field that takes a freed code is added after that rename, and a cycle (two fields that swapped codes) goes through a temporary code: `a -> tmp_<id>`, `b -> a`, `tmp_<id> -> b`, the first step warning `entity_builder.bundle.rename.via.temporary`. Each rename rewrites the references to its field, so they end up where the bundle has them.
126
132
  - **Variables** (every non-empty `ENTITY_BUILDER_VAR_<NAME>` env variable, read by the package itself through `EntityBuilderConfigService.getBundleVariables()`; `config.bundles.variables` adds to them and wins on the same name; a name must match `[A-Za-z_][A-Za-z0-9_]*`, any other is ignored): an export replaces each value (at least 4 characters, longest first, in one pass so a written placeholder is never rewritten) with `${var:NAME}`, and an import puts this environment's value back. Only a step's settings (`config`) are rewritten, never its id, type, name or position. A placeholder this environment does not define blocks the import. Use variables for URLs, IDs, tokens and other values that differ per environment, such as an `emailConfigId`. Keep values distinctive: a short common word would be replaced wherever it appears in a step's settings.
127
133
  - **Read-only designer** (the `ENTITY_BUILDER_READ_ONLY=true` env variable, read by the package itself through `isDesignerReadOnly()`, for production; `config.designer.readOnly` wins when set): `create-entity`, `apply-change`, `apply-destructive-change` and flow `insert` / `update` / `delete` / `publish` / `restore-version` / `discard-draft` answer **423** `entity_builder.designer.read.only` (`DesignerWritableInterceptor` + `@DesignerWrites`; an interceptor, so it runs after every guard: a caller not signed in or without the permission still gets 401 / 403). Imports, every read, and a `repair` through `apply-change` (it fixes drift without changing a definition) still work, so production changes only from git.
128
- - **Uploaded as a file**: `plan-import` and `apply-import` take the bundle as a `multipart/form-data` upload (field `bundle`), with `deprecateMissingFields`, `confirm`, `expectedChecksum` and `expectedPlanChecksum` as form fields (`true` / `false` as text). Express's JSON parser never reads a multipart body, so the app's JSON body limit (100kb by default) never applies. The package enforces its own limit, `ENTITY_BUNDLE_MAX_BYTES` (20 MB, `@flusys/nestjs-entity-builder/config`, 413 above it), through `FileInterceptor`. **An app needs no setup in `main.ts`.** `readBundleFile()` parses the file (a UTF-8 BOM is allowed) and validates it as strictly as a request body: unknown keys are refused, and every problem comes back in `errors` named by its path (`entities.0.fields.2.code`), under `entity_builder.bundle.file.invalid`. A missing file answers `entity_builder.bundle.file.required`.
134
+ - **Uploaded as a file**: `plan-import` and `apply-import` take the bundle as a `multipart/form-data` upload (field `bundle`), with `deprecateMissingFields`, `deactivateMissingFlows`, `confirm`, `expectedChecksum` and `expectedPlanChecksum` as form fields (`true` / `false` as text). Express's JSON parser never reads a multipart body, so the app's JSON body limit (100kb by default) never applies. The package enforces its own limit, `ENTITY_BUNDLE_MAX_BYTES` (20 MB, `@flusys/nestjs-entity-builder/config`, 413 above it), through `FileInterceptor`. **An app needs no setup in `main.ts`.** `readBundleFile()` parses the file (a UTF-8 BOM is allowed) and validates it as strictly as a request body: unknown keys are refused, and every problem comes back in `errors` named by its path (`entities.0.fields.2.code`), under `entity_builder.bundle.file.invalid`. A missing file answers `entity_builder.bundle.file.required`.
129
135
 
130
- Recommended flow: design in dev → export → commit the bundle in a PR (reviewers read the diff) → CI runs `entity-bundle plan` then `entity-bundle apply` against staging → smoke-test → the same commit runs `apply` against production, where `--confirm` is a manual approval. `scripts/entity-bundle.js` reads `FLUSYS_API_URL`, `FLUSYS_API_TOKEN` and optionally `FLUSYS_TENANT_ID` / `FLUSYS_TENANT_HEADER`. It writes new API keys to a `0600` file (`--keys-out`, git-ignored by default) and never prints them. Exit codes: 1 when blocked, unreachable or failed, 2 when the plan may lose data and `--confirm` was not given.
136
+ Recommended flow: design in dev → export → commit the bundle in a PR (reviewers read the diff) → CI runs `entity-bundle plan` then `entity-bundle apply` against staging → smoke-test → the same commit runs `apply` against production, where `--confirm` is a manual approval. `scripts/entity-bundle.js` reads `FLUSYS_API_URL`, `FLUSYS_API_TOKEN` and optionally `FLUSYS_TENANT_ID` / `FLUSYS_TENANT_HEADER`. `--deprecate-missing-fields` and `--deactivate-missing-flows` set the import options. It writes new API keys to a `0600` file (`--keys-out`, git-ignored by default) and never prints them. Exit codes: 1 when blocked, unreachable or failed, 2 when the plan may lose data and `--confirm` was not given.
131
137
 
132
138
  ## Messages and localization
133
139
 
@@ -274,6 +274,8 @@ export declare const FLOW_MESSAGES: {
274
274
  readonly TEST_SUCCESS: "entity_builder.flow.test.success";
275
275
  readonly INPUT_INVALID: "entity_builder.flow.input.invalid";
276
276
  readonly INPUT_BODY_NOT_LIST: "entity_builder.flow.input.body.not.list";
277
+ readonly INPUT_SORT_INVALID: "entity_builder.flow.input.sort.invalid";
278
+ readonly INPUT_SELECT_INVALID: "entity_builder.flow.input.select.invalid";
277
279
  readonly INPUT_BODY_ITEM_NOT_OBJECT: "entity_builder.flow.input.body.item.not.object";
278
280
  readonly CHECKS_FAILED: "entity_builder.flow.checks.failed";
279
281
  readonly PERMISSION_DENIED: "entity_builder.flow.permission.denied";
@@ -295,6 +297,8 @@ export declare const FLOW_ERROR_MESSAGES: {
295
297
  readonly UNKNOWN_NODE_TYPE: "entity_builder.flow.error.unknown.node.type";
296
298
  readonly RECORD_ID_MISSING: "entity_builder.flow.error.record.id.missing";
297
299
  readonly LIST_NOT_A_LIST: "entity_builder.flow.error.list.not.a.list";
300
+ readonly RESPOND_DATA_NOT_LIST: "entity_builder.flow.error.respond.data.not.list";
301
+ readonly RESPOND_META_INVALID: "entity_builder.flow.error.respond.meta.invalid";
298
302
  readonly TOO_MANY_WRITES: "entity_builder.flow.error.too.many.writes";
299
303
  readonly WRITE_FILTER_EMPTY: "entity_builder.flow.error.write.filter.empty";
300
304
  readonly TOO_MANY_MATCHES: "entity_builder.flow.error.too.many.matches";
@@ -357,6 +361,7 @@ export declare const FLOW_VALIDATION_MESSAGES: {
357
361
  readonly TRIGGER_MISSING: "entity_builder.flow.validation.trigger.missing";
358
362
  readonly TRIGGER_MAIN_DUPLICATE: "entity_builder.flow.validation.trigger.main.duplicate";
359
363
  readonly TRIGGER_PATH_INVALID: "entity_builder.flow.validation.trigger.path.invalid";
364
+ readonly TRIGGER_CONTRACT_INVALID: "entity_builder.flow.validation.trigger.contract.invalid";
360
365
  readonly TRIGGER_PATH_DUPLICATE: "entity_builder.flow.validation.trigger.path.duplicate";
361
366
  readonly EDGE_ID_DUPLICATE: "entity_builder.flow.validation.edge.id.duplicate";
362
367
  readonly EDGE_DANGLING: "entity_builder.flow.validation.edge.dangling";
@@ -386,6 +391,7 @@ export declare const FLOW_VALIDATION_MESSAGES: {
386
391
  readonly INPUT_TOO_MANY: "entity_builder.flow.validation.input.too.many";
387
392
  readonly INPUT_NAME_INVALID: "entity_builder.flow.validation.input.name.invalid";
388
393
  readonly INPUT_NAME_DUPLICATE: "entity_builder.flow.validation.input.name.duplicate";
394
+ readonly INPUT_NAME_RESERVED: "entity_builder.flow.validation.input.name.reserved";
389
395
  readonly INPUT_TYPE_UNKNOWN: "entity_builder.flow.validation.input.type.unknown";
390
396
  readonly INPUT_CHOICE_NO_OPTIONS: "entity_builder.flow.validation.input.choice.no.options";
391
397
  readonly INPUT_ITEM_TYPE_UNKNOWN: "entity_builder.flow.validation.input.item.type.unknown";
@@ -404,6 +410,9 @@ export declare const FLOW_VALIDATION_MESSAGES: {
404
410
  readonly NODE_CASE_DUPLICATE: "entity_builder.flow.validation.node.case.duplicate";
405
411
  readonly NODE_MAX_ITEMS_RANGE: "entity_builder.flow.validation.node.max.items.range";
406
412
  readonly NODE_LIMIT_RANGE: "entity_builder.flow.validation.node.limit.range";
413
+ readonly NODE_QUERY_FROM_REQUEST_INVALID: "entity_builder.flow.validation.node.query.from.request.invalid";
414
+ readonly NODE_QUERY_NOT_GET_ALL: "entity_builder.flow.validation.node.query.not.get.all";
415
+ readonly NODE_GET_NOT_GET_BY_ID: "entity_builder.flow.validation.node.get.not.get.by.id";
407
416
  readonly NODE_SORT_INVALID: "entity_builder.flow.validation.node.sort.invalid";
408
417
  readonly NODE_WRITE_TARGET_INVALID: "entity_builder.flow.validation.node.write.target.invalid";
409
418
  readonly NODE_NOT_FOUND_INVALID: "entity_builder.flow.validation.node.not.found.invalid";
@@ -439,6 +448,9 @@ export declare const FLOW_VALIDATION_MESSAGES: {
439
448
  readonly MESSAGE_AFTER_COMMIT: "entity_builder.flow.validation.message.after.commit";
440
449
  readonly PUBLIC_MESSAGE: "entity_builder.flow.validation.public.message";
441
450
  readonly NODE_STATUS_2XX_5XX: "entity_builder.flow.validation.node.status.2xx.5xx";
451
+ readonly NODE_RESPOND_FORMAT_INVALID: "entity_builder.flow.validation.node.respond.format.invalid";
452
+ readonly NODE_RESPOND_MESSAGE_REQUIRED: "entity_builder.flow.validation.node.respond.message.required";
453
+ readonly NODE_RESPOND_LIST_VALUE_REQUIRED: "entity_builder.flow.validation.node.respond.list.value.required";
442
454
  readonly REF_BAD_SCOPE: "entity_builder.flow.validation.ref.bad.scope";
443
455
  readonly REF_NODE_MISSING: "entity_builder.flow.validation.ref.node.missing";
444
456
  readonly REF_SELF: "entity_builder.flow.validation.ref.self";
@@ -506,6 +518,12 @@ export declare const FLOW_SUBJECT_MESSAGES: {
506
518
  readonly THE_URL: "entity_builder.flow.subject.the.url";
507
519
  readonly THE_INPUT_LIST: "entity_builder.flow.subject.the.input.list";
508
520
  readonly THE_RESPONSE: "entity_builder.flow.subject.the.response";
521
+ readonly THE_DATA: "entity_builder.flow.subject.the.data";
522
+ readonly THE_TOTAL: "entity_builder.flow.subject.the.total";
523
+ readonly THE_PAGE: "entity_builder.flow.subject.the.page";
524
+ readonly THE_PAGE_SIZE: "entity_builder.flow.subject.the.page.size";
525
+ readonly MESSAGE_VARIABLES: "entity_builder.flow.subject.message.variables";
526
+ readonly MESSAGE_VARIABLE: "entity_builder.flow.subject.message.variable";
509
527
  readonly THE_RECIPIENTS: "entity_builder.flow.subject.the.recipients";
510
528
  readonly THE_TITLE: "entity_builder.flow.subject.the.title";
511
529
  readonly THE_MESSAGE: "entity_builder.flow.subject.the.message";
@@ -567,6 +585,14 @@ export declare const BUNDLE_MESSAGES: {
567
585
  readonly FLOW_UNPUBLISHED_SKIPPED: "entity_builder.bundle.flow.unpublished.skipped";
568
586
  readonly FLOW_DRAFT_REPLACED: "entity_builder.bundle.flow.draft.replaced";
569
587
  readonly FLOW_API_KEY_GENERATED: "entity_builder.bundle.flow.api.key.generated";
588
+ readonly FLOW_FIXED_IDS: "entity_builder.bundle.flow.fixed.ids";
589
+ readonly FLOW_DEACTIVATE_CALLED: "entity_builder.bundle.flow.deactivate.called";
590
+ readonly FLOW_SECRET_WRITTEN: "entity_builder.bundle.flow.secret.written";
591
+ readonly FLOW_PERMISSION_UNKNOWN: "entity_builder.bundle.flow.permission.unknown";
592
+ readonly PERMISSIONS_TO_GRANT: "entity_builder.bundle.permissions.to.grant";
593
+ readonly EXPORT_RELATION_LEFT_OUT: "entity_builder.bundle.export.relation.left.out";
594
+ readonly EXPORT_CALLED_FLOW_LEFT_OUT: "entity_builder.bundle.export.called.flow.left.out";
595
+ readonly IMPORT_IN_PROGRESS: "entity_builder.bundle.import.in.progress";
570
596
  readonly STEP_AFTER_EARLIER: "entity_builder.bundle.step.after.earlier";
571
597
  readonly FLOWS_CHECKED_ON_APPLY: "entity_builder.bundle.flows.checked.on.apply";
572
598
  readonly DESIGNER_READ_ONLY: "entity_builder.designer.read.only";
@@ -9,6 +9,7 @@ declare const EntityDefinitionController_base: abstract new (service: EntityDefi
9
9
  readonly enabledEndpoints: import("@flusys/nestjs-shared").ApiEndpoint[] | "all";
10
10
  service: EntityDefinitionService;
11
11
  isEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): boolean;
12
+ assertEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): void;
12
13
  insert(addDto: CreateDynamicEntityDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<EntityDefinitionResponseDto>>;
13
14
  insertMany(addDto: CreateDynamicEntityDto[], user: ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared").BulkResponseDto<EntityDefinitionResponseDto>>;
14
15
  getById(id: string, body: import("@flusys/nestjs-shared").GetByIdBodyDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<EntityDefinitionResponseDto>>;
@@ -4,6 +4,7 @@ declare const FieldDefinitionController_base: abstract new (service: FieldDefini
4
4
  readonly enabledEndpoints: import("@flusys/nestjs-shared").ApiEndpoint[] | "all";
5
5
  service: FieldDefinitionService;
6
6
  isEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): boolean;
7
+ assertEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): void;
7
8
  insert(addDto: CreateFieldDefinitionDto, user: import("@flusys/nestjs-shared").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared").SingleResponseDto<FieldDefinitionResponseDto>>;
8
9
  insertMany(addDto: CreateFieldDefinitionDto[], user: import("@flusys/nestjs-shared").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared").BulkResponseDto<FieldDefinitionResponseDto>>;
9
10
  getById(id: string, body: import("@flusys/nestjs-shared").GetByIdBodyDto, user: import("@flusys/nestjs-shared").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared").SingleResponseDto<FieldDefinitionResponseDto>>;
@@ -8,6 +8,7 @@ declare const FlowDefinitionController_base: abstract new (service: FlowDefiniti
8
8
  readonly enabledEndpoints: import("@flusys/nestjs-shared").ApiEndpoint[] | "all";
9
9
  service: FlowDefinitionService;
10
10
  isEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): boolean;
11
+ assertEnabled(endpoint: import("@flusys/nestjs-shared").ApiEndpoint): void;
11
12
  insert(addDto: CreateFlowDefinitionDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<FlowDefinitionResponseDto>>;
12
13
  insertMany(addDto: CreateFlowDefinitionDto[], user: ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared").BulkResponseDto<FlowDefinitionResponseDto>>;
13
14
  getById(id: string, body: import("@flusys/nestjs-shared").GetByIdBodyDto, user: ILoggedUserInfo | null): Promise<SingleResponseDto<FlowDefinitionResponseDto>>;
@@ -51,6 +51,7 @@ export declare class ExportBundleDto {
51
51
  }
52
52
  export declare class ImportBundleOptionsDto {
53
53
  deprecateMissingFields?: boolean;
54
+ deactivateMissingFlows?: boolean;
54
55
  confirm?: boolean;
55
56
  expectedChecksum?: string;
56
57
  expectedPlanChecksum?: string;
@@ -31,13 +31,12 @@ export declare class EntityFlowNamesDto {
31
31
  }
32
32
  export declare class EntityFlowInputLabelsDto {
33
33
  id?: string;
34
- ids?: string;
35
- search?: string;
36
34
  }
37
35
  export declare class EntityFlowTextsDto {
38
36
  names?: EntityFlowNamesDto;
39
37
  inputLabels?: EntityFlowInputLabelsDto;
40
38
  noMatchMessage?: string;
39
+ deleteOnlyMessage?: string;
41
40
  }
42
41
  export declare class CreateDynamicEntityDto extends CreateEntityDefinitionDto {
43
42
  fields?: EntityFieldSpecDto[];
package/fesm/21.js CHANGED
@@ -280,6 +280,8 @@ const FLOW_MESSAGES = {
280
280
  TEST_SUCCESS: 'entity_builder.flow.test.success',
281
281
  INPUT_INVALID: 'entity_builder.flow.input.invalid',
282
282
  INPUT_BODY_NOT_LIST: 'entity_builder.flow.input.body.not.list',
283
+ INPUT_SORT_INVALID: 'entity_builder.flow.input.sort.invalid',
284
+ INPUT_SELECT_INVALID: 'entity_builder.flow.input.select.invalid',
283
285
  INPUT_BODY_ITEM_NOT_OBJECT: 'entity_builder.flow.input.body.item.not.object',
284
286
  CHECKS_FAILED: 'entity_builder.flow.checks.failed',
285
287
  PERMISSION_DENIED: 'entity_builder.flow.permission.denied',
@@ -301,6 +303,8 @@ const FLOW_MESSAGES = {
301
303
  UNKNOWN_NODE_TYPE: 'entity_builder.flow.error.unknown.node.type',
302
304
  RECORD_ID_MISSING: 'entity_builder.flow.error.record.id.missing',
303
305
  LIST_NOT_A_LIST: 'entity_builder.flow.error.list.not.a.list',
306
+ RESPOND_DATA_NOT_LIST: 'entity_builder.flow.error.respond.data.not.list',
307
+ RESPOND_META_INVALID: 'entity_builder.flow.error.respond.meta.invalid',
304
308
  TOO_MANY_WRITES: 'entity_builder.flow.error.too.many.writes',
305
309
  WRITE_FILTER_EMPTY: 'entity_builder.flow.error.write.filter.empty',
306
310
  TOO_MANY_MATCHES: 'entity_builder.flow.error.too.many.matches',
@@ -363,6 +367,7 @@ const FLOW_MESSAGES = {
363
367
  TRIGGER_MISSING: 'entity_builder.flow.validation.trigger.missing',
364
368
  TRIGGER_MAIN_DUPLICATE: 'entity_builder.flow.validation.trigger.main.duplicate',
365
369
  TRIGGER_PATH_INVALID: 'entity_builder.flow.validation.trigger.path.invalid',
370
+ TRIGGER_CONTRACT_INVALID: 'entity_builder.flow.validation.trigger.contract.invalid',
366
371
  TRIGGER_PATH_DUPLICATE: 'entity_builder.flow.validation.trigger.path.duplicate',
367
372
  EDGE_ID_DUPLICATE: 'entity_builder.flow.validation.edge.id.duplicate',
368
373
  EDGE_DANGLING: 'entity_builder.flow.validation.edge.dangling',
@@ -392,6 +397,7 @@ const FLOW_MESSAGES = {
392
397
  INPUT_TOO_MANY: 'entity_builder.flow.validation.input.too.many',
393
398
  INPUT_NAME_INVALID: 'entity_builder.flow.validation.input.name.invalid',
394
399
  INPUT_NAME_DUPLICATE: 'entity_builder.flow.validation.input.name.duplicate',
400
+ INPUT_NAME_RESERVED: 'entity_builder.flow.validation.input.name.reserved',
395
401
  INPUT_TYPE_UNKNOWN: 'entity_builder.flow.validation.input.type.unknown',
396
402
  INPUT_CHOICE_NO_OPTIONS: 'entity_builder.flow.validation.input.choice.no.options',
397
403
  INPUT_ITEM_TYPE_UNKNOWN: 'entity_builder.flow.validation.input.item.type.unknown',
@@ -410,6 +416,9 @@ const FLOW_MESSAGES = {
410
416
  NODE_CASE_DUPLICATE: 'entity_builder.flow.validation.node.case.duplicate',
411
417
  NODE_MAX_ITEMS_RANGE: 'entity_builder.flow.validation.node.max.items.range',
412
418
  NODE_LIMIT_RANGE: 'entity_builder.flow.validation.node.limit.range',
419
+ NODE_QUERY_FROM_REQUEST_INVALID: 'entity_builder.flow.validation.node.query.from.request.invalid',
420
+ NODE_QUERY_NOT_GET_ALL: 'entity_builder.flow.validation.node.query.not.get.all',
421
+ NODE_GET_NOT_GET_BY_ID: 'entity_builder.flow.validation.node.get.not.get.by.id',
413
422
  NODE_SORT_INVALID: 'entity_builder.flow.validation.node.sort.invalid',
414
423
  NODE_WRITE_TARGET_INVALID: 'entity_builder.flow.validation.node.write.target.invalid',
415
424
  NODE_NOT_FOUND_INVALID: 'entity_builder.flow.validation.node.not.found.invalid',
@@ -445,6 +454,9 @@ const FLOW_MESSAGES = {
445
454
  MESSAGE_AFTER_COMMIT: 'entity_builder.flow.validation.message.after.commit',
446
455
  PUBLIC_MESSAGE: 'entity_builder.flow.validation.public.message',
447
456
  NODE_STATUS_2XX_5XX: 'entity_builder.flow.validation.node.status.2xx.5xx',
457
+ NODE_RESPOND_FORMAT_INVALID: 'entity_builder.flow.validation.node.respond.format.invalid',
458
+ NODE_RESPOND_MESSAGE_REQUIRED: 'entity_builder.flow.validation.node.respond.message.required',
459
+ NODE_RESPOND_LIST_VALUE_REQUIRED: 'entity_builder.flow.validation.node.respond.list.value.required',
448
460
  REF_BAD_SCOPE: 'entity_builder.flow.validation.ref.bad.scope',
449
461
  REF_NODE_MISSING: 'entity_builder.flow.validation.ref.node.missing',
450
462
  REF_SELF: 'entity_builder.flow.validation.ref.self',
@@ -512,6 +524,12 @@ const FLOW_MESSAGES = {
512
524
  THE_URL: 'entity_builder.flow.subject.the.url',
513
525
  THE_INPUT_LIST: 'entity_builder.flow.subject.the.input.list',
514
526
  THE_RESPONSE: 'entity_builder.flow.subject.the.response',
527
+ THE_DATA: 'entity_builder.flow.subject.the.data',
528
+ THE_TOTAL: 'entity_builder.flow.subject.the.total',
529
+ THE_PAGE: 'entity_builder.flow.subject.the.page',
530
+ THE_PAGE_SIZE: 'entity_builder.flow.subject.the.page.size',
531
+ MESSAGE_VARIABLES: 'entity_builder.flow.subject.message.variables',
532
+ MESSAGE_VARIABLE: 'entity_builder.flow.subject.message.variable',
515
533
  THE_RECIPIENTS: 'entity_builder.flow.subject.the.recipients',
516
534
  THE_TITLE: 'entity_builder.flow.subject.the.title',
517
535
  THE_MESSAGE: 'entity_builder.flow.subject.the.message',
@@ -573,6 +591,14 @@ const FLOW_MESSAGES = {
573
591
  FLOW_UNPUBLISHED_SKIPPED: 'entity_builder.bundle.flow.unpublished.skipped',
574
592
  FLOW_DRAFT_REPLACED: 'entity_builder.bundle.flow.draft.replaced',
575
593
  FLOW_API_KEY_GENERATED: 'entity_builder.bundle.flow.api.key.generated',
594
+ FLOW_FIXED_IDS: 'entity_builder.bundle.flow.fixed.ids',
595
+ FLOW_DEACTIVATE_CALLED: 'entity_builder.bundle.flow.deactivate.called',
596
+ FLOW_SECRET_WRITTEN: 'entity_builder.bundle.flow.secret.written',
597
+ FLOW_PERMISSION_UNKNOWN: 'entity_builder.bundle.flow.permission.unknown',
598
+ PERMISSIONS_TO_GRANT: 'entity_builder.bundle.permissions.to.grant',
599
+ EXPORT_RELATION_LEFT_OUT: 'entity_builder.bundle.export.relation.left.out',
600
+ EXPORT_CALLED_FLOW_LEFT_OUT: 'entity_builder.bundle.export.called.flow.left.out',
601
+ IMPORT_IN_PROGRESS: 'entity_builder.bundle.import.in.progress',
576
602
  STEP_AFTER_EARLIER: 'entity_builder.bundle.step.after.earlier',
577
603
  FLOWS_CHECKED_ON_APPLY: 'entity_builder.bundle.flows.checked.on.apply',
578
604
  DESIGNER_READ_ONLY: 'entity_builder.designer.read.only'
package/fesm/458.js CHANGED
@@ -255,6 +255,7 @@ __webpack_require__.d(__webpack_exports__, {
255
255
  DG: () => (/* reexport */ generic_entity_service/* .actorMetadata */.DG),
256
256
  $6: () => (/* reexport */ schema_columns/* .addUnique */.$6),
257
257
  gT: () => (/* reexport */ definition_bundle_diff/* .applyVariables */.gT),
258
+ b$: () => (/* reexport */ flow_definition_service/* .asksExecutePermission */.b$),
258
259
  fY: () => (/* reexport */ identifier_rules/* .assertIndexableField */.fY),
259
260
  kV: () => (/* reexport */ identifier_rules/* .assertValidIdentifier */.kV),
260
261
  HK: () => (/* reexport */ schema_columns/* .auditColumns */.HK),
@@ -270,8 +271,10 @@ __webpack_require__.d(__webpack_exports__, {
270
271
  an: () => (/* reexport */ schema_change_types/* .describeImpact */.a),
271
272
  Sy: () => (/* reexport */ definition_bundle_diff/* .diffEntities */.zb),
272
273
  x2: () => (/* reexport */ entity_flow_scaffold/* .entityFlowSlug */.x2),
274
+ ZM: () => (/* reexport */ entity_permission_service/* .entityPermissionCodes */.Z),
273
275
  lj: () => (/* reexport */ definition_bundle_diff/* .extractVariables */.lj),
274
276
  HQ: () => (/* reexport */ schema_dialect_adapter_service/* .fitIdentifier */.H),
277
+ f7: () => (/* reexport */ definition_bundle_diff/* .fixedIds */.f7),
275
278
  YW: () => (/* reexport */ flow_definition_service/* .flowNotFound */.YW),
276
279
  j4: () => (/* reexport */ flow_definition_service/* .flowPermissions */.j4),
277
280
  gl: () => (/* reexport */ flow_executor_service/* .flowTimeout */.g),
@@ -283,6 +286,7 @@ __webpack_require__.d(__webpack_exports__, {
283
286
  iw: () => (/* reexport */ database_error_mapper/* .mapSchemaError */.i),
284
287
  Og: () => (/* reexport */ definition_bundle_diff/* .matchDefinitions */.Og),
285
288
  qG: () => (/* reexport */ definition_bundle_diff/* .orderFlows */.qG),
289
+ QJ: () => (/* reexport */ definition_bundle_diff/* .permissionCodes */.QJ),
286
290
  v7: () => (/* reexport */ schema_columns/* .plainIndex */.v7),
287
291
  IM: () => (/* reexport */ field_type_conversion/* .planTypeConversion */.I),
288
292
  zi: () => (/* reexport */ definition_bundle_file/* .readBundleFile */.z),
@@ -297,7 +301,8 @@ __webpack_require__.d(__webpack_exports__, {
297
301
  RK: () => (/* reexport */ flow_definition_service/* .toFlowSettings */.RK),
298
302
  jp: () => (/* reexport */ schema_columns/* .typeFamily */.jp),
299
303
  eV: () => (/* reexport */ field_value_validator/* .validateRecord */.e),
300
- lw: () => (/* reexport */ flow_definition_service/* .workingCopy */.lw)
304
+ lw: () => (/* reexport */ flow_definition_service/* .workingCopy */.lw),
305
+ l6: () => (/* reexport */ definition_bundle_diff/* .writtenSecrets */.l6)
301
306
  });
302
307
 
303
308
  // EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/entity-builder-config.service.ts