@flusys/nestjs-entity-builder 9.1.1 → 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 +24 -12
- package/config/message-keys.d.ts +29 -0
- package/controllers/entity-definition.controller.d.ts +1 -0
- package/controllers/field-definition.controller.d.ts +1 -0
- package/controllers/flow-definition.controller.d.ts +1 -0
- package/dtos/definition-bundle.dto.d.ts +1 -0
- package/dtos/entity-definition.dto.d.ts +1 -2
- package/fesm/21.js +31 -2
- package/fesm/458.js +8 -3
- package/fesm/677.js +890 -465
- package/fesm/{582.js → 857.js} +201 -265
- package/fesm/870.js +580 -247
- package/fesm/897.js +164 -22
- package/fesm/946.js +5 -1
- package/fesm/controllers/index.js +1 -1
- package/fesm/dtos/index.js +11 -0
- package/fesm/guards/index.js +2 -2
- package/fesm/index.js +39 -6
- package/fesm/interfaces/index.js +74 -3
- package/fesm/modules/index.js +1 -1
- package/fesm/rule-engine/index.js +2 -2
- package/fesm/services/index.js +13 -3
- package/flow-engine/flow-input.validator.d.ts +2 -1
- package/flow-engine/flow-run.types.d.ts +1 -0
- package/flow-engine/flow.types.d.ts +25 -4
- package/interfaces/definition-bundle.interface.d.ts +2 -1
- package/interfaces/generic-record.interface.d.ts +11 -1
- package/package.json +3 -4
- package/rule-engine/rule-engine.service.d.ts +2 -1
- package/rule-engine/rule-regex.d.ts +0 -2
- package/rule-engine/rule-values.d.ts +4 -1
- package/rule-engine/rule.interface.d.ts +2 -3
- package/services/definition-bundle.diff.d.ts +6 -0
- package/services/definition-bundle.service.d.ts +10 -2
- package/services/entity-flow-scaffold.d.ts +2 -1
- package/services/entity-permission.service.d.ts +1 -0
- package/services/flow-definition.service.d.ts +1 -1
- package/services/flow-runtime.service.d.ts +1 -0
- package/services/generic-entity.service.d.ts +5 -0
- package/services/schema-change-executor.service.d.ts +1 -0
- package/services/schema-dialect-adapter.service.d.ts +2 -0
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`
|
|
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,8 +52,15 @@ 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
|
-
- 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 (
|
|
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`):
|
|
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).
|
|
59
|
+
- **list** (at most 100 scalars; a single scalar counts as a one-item list) - `in` (empty: matches nothing), `not_in` (empty: matches everything), and the multi-select operators `has_any` (empty: nothing), `has_all`, `has_none` (empty: everything).
|
|
60
|
+
- **range** - `between` / `not_between` on `[from, to]`, both ends included (in a flow: `{ from, to }`, each its own expression).
|
|
61
|
+
- **none** - `is_null` / `not_null` (SQL null, any field), `is_empty` / `not_empty` (null or blank text, or null or an empty multi-select list).
|
|
62
|
+
|
|
63
|
+
Operators must fit the field (`entity_builder.generic_record.filter.operator.not.for.field` otherwise): text operators need a text, long text, email or single-select field; `has_*` a multi-select; blank checks a text or multi-select; on multi-select and JSON fields only the null and blank checks are allowed. As in SQL, a negation (`ne`, `not_in`, `not_contains`, `not_between`, `has_none`) never matches a record whose field has no value - combine with `is_null` in another filter when those are wanted. Text matching is `ILIKE` on PostgreSQL and `LIKE` on MySQL (case-insensitive under its default collation); multi-select matching is `@>` / `JSON_CONTAINS`. Every value is a bound parameter. A Find step's `sort` is `{ field, direction? }` with a column code and `ASC` / `DESC` (left out: `ASC`); anything else is a save error (`flow.validation.node.sort.invalid`).
|
|
57
64
|
- **Search** (a Find records step's search text) matches every searchable field by its text form, case-insensitively on both drivers (`CAST ... AS TEXT ILIKE` / `CAST ... AS CHAR LIKE`), so non-text fields can be searchable; `%` and `_` in the search text are literal.
|
|
58
65
|
- Each entity gets the IAM actions `entity_builder.entity.<code>.<create|read|update|delete>` (only when IAM is installed). Flow steps do not check them (a flow's URL access is its gate): the entity's ready-made flow asks for them as its URLs' `permission` access, and any Request step or Check permission step can name them in its rule. DECIMAL fields are returned as numbers. `RELATION`/`FILE` are plain uuid columns (`char(36)` on MySQL, which has no uuid type) with no foreign-key constraint. Record events are `entity-builder.<code>.created` / `updated` / `deleted`, or `purged` for a delete on an entity with soft delete turned off - the same name whether the write ran inside a transactional flow or not.
|
|
59
66
|
- **Tenants never share schemas**: every tenant DataSource gets its own copy of the entities array (`getEntityBuilderEntities()` returns a copy), and runtime schemas are registered per DataSource.
|
|
@@ -81,7 +88,7 @@ A flow is an endpoint (`POST /api-flows/<slug>`) whose behaviour is a graph of s
|
|
|
81
88
|
- **Reference timing** (validation warnings): a step's result exists for the steps on a path after it; once a loop is done (`done` branch) its whole body's results exist too (their last pass). The validator warns when a step reads `context.<id>` of a step that has not run yet when it runs (later on the path, on another branch, or later in the same loop body), `loop.*` outside a loop's `each` branch (a foreach's own `collect` counts as inside), `loop.parent` beyond the loops around the step, or `context.<loop>.results` inside that loop (only `count` exists until it is done). Unreachable steps are not checked (they already warn).
|
|
82
89
|
|
|
83
90
|
- **Validate**: `{ checks: [{ id, rule, message, field? }], mode: 'all' | 'first', statusCode? }`. `all` (default) evaluates every check and rejects with every failure; `first` stops at the first. The answer is `statusCode` (default 400, or 403 when every failed check is a permission check) with `errors: [{ field, message }]` (`field` defaults to the check id); one failure uses its message as the top-level message, several use `flow.checks.failed`. Example (attendance check-out): check 1 `left` = lookup `attendance` with `employee_id = input.employeeId`, `out_time is_null`, `in_time gte START_OF_DAY(NOW())`, mode `exists`, `is_true`; check 2 `left` = `DATE_DIFF(NOW(), <same lookup, mode first, field in_time>, 'hour')` `greater_or_equal` 5.
|
|
84
|
-
- **Check permission** (`permission_check`): `{ permissions, onDenied: 'reject' | 'branch', statusCode?, message? }` - `permissions` is 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) `reject` (default) answers `statusCode` (403) with `message` (or `flow.permission.denied`) and stops, otherwise follows `out`; `branch` follows `allowed` / `denied` and never rejects. Checks the caller's
|
|
91
|
+
- **Check permission** (`permission_check`): `{ permissions, subject?, userId?, companyId?, branchId?, onDenied: 'reject' | 'branch', statusCode?, message? }` - `permissions` is 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) `reject` (default) answers `statusCode` (403) with `message` (or `flow.permission.denied`) and stops, otherwise follows `out`; `branch` follows `allowed` / `denied` and never rejects. Checks the effective permissions (the same codes `my-permissions` returns) of the signed-in caller (`subject: 'caller'`, the default) or of the user `userId` names (`subject: 'user'`, signed in or not - e.g. the approver of a record, also on a public or API-key URL). The scope is `companyId` (left out: the caller's current company) plus `branchId` (left out: the caller's current branch when checking the caller in that company, otherwise company-wide grants only; resolving to nothing: company-wide only) - with the company feature a branch adds its own grants on top of the company-wide ones; without it IAM ignores both. The caller in their current company and branch uses the run's cached codes; any other scope is first checked against what the user is granted (`COMPANY_ACCESS_RESOLVER`: the company, and the branch inside it) - a company or branch they are not granted holds no permissions - then asked of `PERMISSION_RESOLVER` (`RuleEngineService.permissionCodes`). Output `{ allowed, missing }` - `missing` lists every code the rule names that the user does not hold, whether or not the rule passed. No user (a caller who is not signed in, or a `userId` that resolves to nothing) is denied; the validator warns when a check of the caller sits behind a `public` / `api_key` URL (not one of another user), and requires `userId` for `subject: 'user'` (`node.access.user.required`).
|
|
85
92
|
- **Company & Branch check** (`company_branch_check`, company feature): `{ subject?, userId?, companyIds?, branchIds?, branchMatch?, branchCompany?, onDenied?, statusCode?, message? }` - what a user may work in: the signed-in caller (`subject: 'caller'`, the default) or the user `userId` names (`subject: 'user'`, signed in or not - e.g. the owner of a record, also on a public or API-key URL). It needs no permission codes: it gets exactly what company select offers that user - the active companies granted to them and, inside those, the active branches granted to them. Output: `{ userId, currentCompanyId, currentBranchId, companies, branches }` - `companies` as `{ id, name }` (by name), `branches` as `{ id, companyId, parentId, name }` (per company, by serial); `currentCompanyId` / `currentBranchId` are the caller's current ones (null for another user). `companyIds` / `branchIds` (expressions: one id or a list, typically the record being changed; the designer starts the caller's on `user.companyId` / `user.branchId`) turn it into a guard: each id must be within reach (`allowed`, `missing: { companyIds, branchIds }`); a target that resolves to nothing is a denial. A company is within reach when it is granted; a branch per `branchMatch`: `direct` (default) - one of the granted branches; `within` - a granted branch or anywhere under one (`parentId` tree, `getDescendantIds(granted, { includeSelf: true })`, walked only when an asked-for branch is not granted itself), a child needing no grant of its own and the current branch playing no part. A user with no granted branch reaches none; without the branch tree `within` passes only granted branches. `branchCompany` picks whose branches count, for both matches (a walk starts only from the granted branches it keeps): `any` (default, stored as no key) - every company of the user; `current` - the caller's current company (refused on save for another user, who has none; no current company counts none). Then `onDenied` rejects (default, `statusCode` 403) or follows `allowed` / `denied` (which needs a target). No user means nothing is reachable. The data comes from `COMPANY_ACCESS_RESOLVER` and the tree from `BRANCH_HIERARCHY_RESOLVER` (nestjs-shared), both provided by nestjs-auth when the company feature is on (the grants `UserPermissionService` lists, kept to active companies and branches, looked up once per user per run); without them nothing is reachable. Save warns about a check of the caller on a URL reached without signing in (`permission.without.user`), not about one of another user. The designer offers the step only with the company feature.
|
|
86
93
|
- **Code**: `{ code, timeoutMs? }` runs `code` as the body of a synchronous function in a QuickJS WebAssembly sandbox (`quickjs-emscripten`) on a worker thread (a small pool, at most 4), so a busy code step never blocks the server's event loop; the host also kills a worker that overruns its deadline. It reads a deep-frozen JSON copy of `{ input, vars, context, loop, user: { id, email, name, companyId, branchId, permissions? } }` as the global `ctx` (no request headers; `permissions`, the caller's codes in the current branch, is fetched only when the code mentions permissions). `hasPermission(code)`, `hasAnyPermission(...codes)` and `hasAllPermissions(...codes)` are plain JavaScript inside the sandbox over `ctx.user.permissions` with the server's wildcards and `return`s a JSON value, which becomes `context.<id>`. Nothing of the host is reachable (no `require`, `process`, network, filesystem, timers or database); `console.log` is captured into the trace (`trace[].logs`, 50 lines of 500 characters, sensitive-looking keys masked). Every run gets a fresh runtime with a 32 MB memory limit, a 512 KB stack and an interrupt deadline of `timeoutMs` (default 1000, 10-5000) clamped to the time left in the flow; the output may be at most 1 MB. A throw, time-out or memory overflow is a node error (`flow.error.code.*`), so `onError: continue` and the `error` port work. Code nodes also run in dry runs. Saving a flow that contains a code node needs `entity_builder.flow_definition.code`.
|
|
87
94
|
- **Create / Update / Delete record** write several records in one step through `target`: `one` (default, left out) writes one record; `many` writes one per item of `items` (up to `FLOW_LIMITS.MAX_WRITE_ITEMS` = 1000), and the step's per-record values (`fields`, `id`) read the item as `loop.item` / `loop.index` with a loop around the step as `loop.parent`; `filter` (update / delete) writes every record matching `filter` (same shape as Find), read as `loop.item` (e.g. `stock = loop.item.stock - 1`). A filter that resolves to nothing is refused (`flow.error.write.filter.empty`) instead of changing every record, and more matches than `limit` (1-1000, default 100) fail the step (`flow.error.too.many.matches`) instead of changing some. In `many` mode `id` defaults to the item's own id (the item itself when it is text, else `item.id`). `onNotFound`: `error` (default), `skip`, or for update `insert` (upsert: a missing record, or in `many` mode an item with an empty id, is created with the same field values - needs the entity's `create` permission too). Create takes `children` (up to 10): `{ as, entityCode, parentField, items, fields }` saves child records under every saved record, `parentField` filled with the parent's id; `items` is read in the parent's scope (`loop.item.lines` in `many` mode, `input.lines` otherwise) and child `fields` read the child item as `loop.item` and the parent's scope as `loop.parent`. Results: create one = the record plus one array per `as`; create many = `{ items, count }`; update one = the record (`null` when skipped); update many / filter = `{ items, count, updated, created, skipped }`; delete one = `{ id, deleted }`; delete many / filter = `{ ids, count, skipped }` (an id listed twice is deleted once). A create never takes an `id` field (`generic_record.system.column.readonly`), so it cannot overwrite an existing record; an upsert's created record ignores a mapped `id`. A step that writes several records (`many`, `filter`, or any `children`) is **all-or-nothing on its own**: inside a transactional run it uses the run's transaction, otherwise it opens a transaction for just that step and publishes its record events after that commit. Every record written counts against `FLOW_LIMITS.MAX_WRITES_PER_RUN` (5000, shared with called flows).
|
|
@@ -91,8 +98,12 @@ A flow is an endpoint (`POST /api-flows/<slug>`) whose behaviour is a graph of s
|
|
|
91
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`).
|
|
92
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.
|
|
93
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.
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
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).
|
|
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`).
|
|
96
107
|
- **Checks cannot be carried past**: a failed Validate or Check permission step (like Save progress) always ends the run, whatever its `onError` says.
|
|
97
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.
|
|
98
109
|
- **Transactions** (a Request step's `config.isTransactional`, the designer's "All writes together"): the entity writes of a run that starts at a transactional Request step commit or roll back together; the flow's other URLs open no transaction unless they set it too; entity events and **Publish event** steps are published only after the commit, in order, and never for a run that rolled back - nor for the rows of a step whose savepoint (`onError: continue`) rolled back. Metadata a transactional run needs (entity and field definitions, called flows) is read on the run's own transaction, so a run never waits on a second pooled connection. HTTP calls cannot be rolled back, so a transactional Request step whose runs reach an HTTP step is flagged in the warnings. In a run without a transaction each step commits on its own, except that a multi-record write step (many, or with child records) is always all-or-nothing (see above); make the Request step transactional when several steps must succeed or fail together, and add **Save progress** steps where the work so far must be kept even if a later step fails.
|
|
@@ -110,18 +121,19 @@ A flow is an endpoint (`POST /api-flows/<slug>`) whose behaviour is a graph of s
|
|
|
110
121
|
|
|
111
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`).
|
|
112
123
|
|
|
113
|
-
- **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}`.
|
|
114
|
-
- **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.
|
|
115
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.
|
|
116
|
-
- **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.
|
|
117
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).
|
|
118
130
|
- **Ids**: a design-time id is reused only when no row has it, soft-deleted rows included.
|
|
119
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.
|
|
120
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.
|
|
121
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.
|
|
122
|
-
- **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`.
|
|
123
135
|
|
|
124
|
-
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.
|
|
125
137
|
|
|
126
138
|
## Messages and localization
|
|
127
139
|
|
package/config/message-keys.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export declare const GENERIC_RECORD_MESSAGES: {
|
|
|
56
56
|
readonly FILTER_OPERATOR_UNKNOWN: "entity_builder.generic_record.filter.operator.unknown";
|
|
57
57
|
readonly FILTER_VALUE_INVALID: "entity_builder.generic_record.filter.value.invalid";
|
|
58
58
|
readonly FILTER_TOO_MANY_VALUES: "entity_builder.generic_record.filter.too.many.values";
|
|
59
|
+
readonly FILTER_OPERATOR_NOT_FOR_FIELD: "entity_builder.generic_record.filter.operator.not.for.field";
|
|
59
60
|
};
|
|
60
61
|
export declare const SCHEMA_MESSAGES: {
|
|
61
62
|
readonly BUSY: "entity_builder.schema.busy";
|
|
@@ -273,6 +274,8 @@ export declare const FLOW_MESSAGES: {
|
|
|
273
274
|
readonly TEST_SUCCESS: "entity_builder.flow.test.success";
|
|
274
275
|
readonly INPUT_INVALID: "entity_builder.flow.input.invalid";
|
|
275
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";
|
|
276
279
|
readonly INPUT_BODY_ITEM_NOT_OBJECT: "entity_builder.flow.input.body.item.not.object";
|
|
277
280
|
readonly CHECKS_FAILED: "entity_builder.flow.checks.failed";
|
|
278
281
|
readonly PERMISSION_DENIED: "entity_builder.flow.permission.denied";
|
|
@@ -294,6 +297,8 @@ export declare const FLOW_ERROR_MESSAGES: {
|
|
|
294
297
|
readonly UNKNOWN_NODE_TYPE: "entity_builder.flow.error.unknown.node.type";
|
|
295
298
|
readonly RECORD_ID_MISSING: "entity_builder.flow.error.record.id.missing";
|
|
296
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";
|
|
297
302
|
readonly TOO_MANY_WRITES: "entity_builder.flow.error.too.many.writes";
|
|
298
303
|
readonly WRITE_FILTER_EMPTY: "entity_builder.flow.error.write.filter.empty";
|
|
299
304
|
readonly TOO_MANY_MATCHES: "entity_builder.flow.error.too.many.matches";
|
|
@@ -356,6 +361,7 @@ export declare const FLOW_VALIDATION_MESSAGES: {
|
|
|
356
361
|
readonly TRIGGER_MISSING: "entity_builder.flow.validation.trigger.missing";
|
|
357
362
|
readonly TRIGGER_MAIN_DUPLICATE: "entity_builder.flow.validation.trigger.main.duplicate";
|
|
358
363
|
readonly TRIGGER_PATH_INVALID: "entity_builder.flow.validation.trigger.path.invalid";
|
|
364
|
+
readonly TRIGGER_CONTRACT_INVALID: "entity_builder.flow.validation.trigger.contract.invalid";
|
|
359
365
|
readonly TRIGGER_PATH_DUPLICATE: "entity_builder.flow.validation.trigger.path.duplicate";
|
|
360
366
|
readonly EDGE_ID_DUPLICATE: "entity_builder.flow.validation.edge.id.duplicate";
|
|
361
367
|
readonly EDGE_DANGLING: "entity_builder.flow.validation.edge.dangling";
|
|
@@ -385,6 +391,7 @@ export declare const FLOW_VALIDATION_MESSAGES: {
|
|
|
385
391
|
readonly INPUT_TOO_MANY: "entity_builder.flow.validation.input.too.many";
|
|
386
392
|
readonly INPUT_NAME_INVALID: "entity_builder.flow.validation.input.name.invalid";
|
|
387
393
|
readonly INPUT_NAME_DUPLICATE: "entity_builder.flow.validation.input.name.duplicate";
|
|
394
|
+
readonly INPUT_NAME_RESERVED: "entity_builder.flow.validation.input.name.reserved";
|
|
388
395
|
readonly INPUT_TYPE_UNKNOWN: "entity_builder.flow.validation.input.type.unknown";
|
|
389
396
|
readonly INPUT_CHOICE_NO_OPTIONS: "entity_builder.flow.validation.input.choice.no.options";
|
|
390
397
|
readonly INPUT_ITEM_TYPE_UNKNOWN: "entity_builder.flow.validation.input.item.type.unknown";
|
|
@@ -403,6 +410,9 @@ export declare const FLOW_VALIDATION_MESSAGES: {
|
|
|
403
410
|
readonly NODE_CASE_DUPLICATE: "entity_builder.flow.validation.node.case.duplicate";
|
|
404
411
|
readonly NODE_MAX_ITEMS_RANGE: "entity_builder.flow.validation.node.max.items.range";
|
|
405
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";
|
|
406
416
|
readonly NODE_SORT_INVALID: "entity_builder.flow.validation.node.sort.invalid";
|
|
407
417
|
readonly NODE_WRITE_TARGET_INVALID: "entity_builder.flow.validation.node.write.target.invalid";
|
|
408
418
|
readonly NODE_NOT_FOUND_INVALID: "entity_builder.flow.validation.node.not.found.invalid";
|
|
@@ -438,6 +448,9 @@ export declare const FLOW_VALIDATION_MESSAGES: {
|
|
|
438
448
|
readonly MESSAGE_AFTER_COMMIT: "entity_builder.flow.validation.message.after.commit";
|
|
439
449
|
readonly PUBLIC_MESSAGE: "entity_builder.flow.validation.public.message";
|
|
440
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";
|
|
441
454
|
readonly REF_BAD_SCOPE: "entity_builder.flow.validation.ref.bad.scope";
|
|
442
455
|
readonly REF_NODE_MISSING: "entity_builder.flow.validation.ref.node.missing";
|
|
443
456
|
readonly REF_SELF: "entity_builder.flow.validation.ref.self";
|
|
@@ -490,6 +503,7 @@ export declare const FLOW_SHAPE_MESSAGES: {
|
|
|
490
503
|
readonly LOOKUP_FIELD_INVALID: "entity_builder.flow.shape.lookup.field.invalid";
|
|
491
504
|
readonly FILTER_OPERATOR_UNKNOWN: "entity_builder.flow.shape.filter.operator.unknown";
|
|
492
505
|
readonly FILTER_VALUE_REQUIRED: "entity_builder.flow.shape.filter.value.required";
|
|
506
|
+
readonly FILTER_RANGE_INVALID: "entity_builder.flow.shape.filter.range.invalid";
|
|
493
507
|
};
|
|
494
508
|
export declare const FLOW_SUBJECT_MESSAGES: {
|
|
495
509
|
readonly THE_RULE: "entity_builder.flow.subject.the.rule";
|
|
@@ -504,12 +518,19 @@ export declare const FLOW_SUBJECT_MESSAGES: {
|
|
|
504
518
|
readonly THE_URL: "entity_builder.flow.subject.the.url";
|
|
505
519
|
readonly THE_INPUT_LIST: "entity_builder.flow.subject.the.input.list";
|
|
506
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";
|
|
507
527
|
readonly THE_RECIPIENTS: "entity_builder.flow.subject.the.recipients";
|
|
508
528
|
readonly THE_TITLE: "entity_builder.flow.subject.the.title";
|
|
509
529
|
readonly THE_MESSAGE: "entity_builder.flow.subject.the.message";
|
|
510
530
|
readonly THE_COMPANY: "entity_builder.flow.subject.the.company";
|
|
511
531
|
readonly THE_COMPANIES: "entity_builder.flow.subject.the.companies";
|
|
512
532
|
readonly THE_BRANCHES: "entity_builder.flow.subject.the.branches";
|
|
533
|
+
readonly THE_BRANCH: "entity_builder.flow.subject.the.branch";
|
|
513
534
|
readonly THE_USER: "entity_builder.flow.subject.the.user";
|
|
514
535
|
readonly THE_CC: "entity_builder.flow.subject.the.cc";
|
|
515
536
|
readonly THE_BCC: "entity_builder.flow.subject.the.bcc";
|
|
@@ -564,6 +585,14 @@ export declare const BUNDLE_MESSAGES: {
|
|
|
564
585
|
readonly FLOW_UNPUBLISHED_SKIPPED: "entity_builder.bundle.flow.unpublished.skipped";
|
|
565
586
|
readonly FLOW_DRAFT_REPLACED: "entity_builder.bundle.flow.draft.replaced";
|
|
566
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";
|
|
567
596
|
readonly STEP_AFTER_EARLIER: "entity_builder.bundle.step.after.earlier";
|
|
568
597
|
readonly FLOWS_CHECKED_ON_APPLY: "entity_builder.bundle.flows.checked.on.apply";
|
|
569
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>>;
|
|
@@ -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
|
@@ -61,7 +61,8 @@ const GENERIC_RECORD_MESSAGES = {
|
|
|
61
61
|
REFERENCE_VIOLATION: 'entity_builder.generic_record.reference.violation',
|
|
62
62
|
FILTER_OPERATOR_UNKNOWN: 'entity_builder.generic_record.filter.operator.unknown',
|
|
63
63
|
FILTER_VALUE_INVALID: 'entity_builder.generic_record.filter.value.invalid',
|
|
64
|
-
FILTER_TOO_MANY_VALUES: 'entity_builder.generic_record.filter.too.many.values'
|
|
64
|
+
FILTER_TOO_MANY_VALUES: 'entity_builder.generic_record.filter.too.many.values',
|
|
65
|
+
FILTER_OPERATOR_NOT_FOR_FIELD: 'entity_builder.generic_record.filter.operator.not.for.field'
|
|
65
66
|
};
|
|
66
67
|
const SCHEMA_MESSAGES = {
|
|
67
68
|
BUSY: 'entity_builder.schema.busy',
|
|
@@ -279,6 +280,8 @@ const FLOW_MESSAGES = {
|
|
|
279
280
|
TEST_SUCCESS: 'entity_builder.flow.test.success',
|
|
280
281
|
INPUT_INVALID: 'entity_builder.flow.input.invalid',
|
|
281
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',
|
|
282
285
|
INPUT_BODY_ITEM_NOT_OBJECT: 'entity_builder.flow.input.body.item.not.object',
|
|
283
286
|
CHECKS_FAILED: 'entity_builder.flow.checks.failed',
|
|
284
287
|
PERMISSION_DENIED: 'entity_builder.flow.permission.denied',
|
|
@@ -300,6 +303,8 @@ const FLOW_MESSAGES = {
|
|
|
300
303
|
UNKNOWN_NODE_TYPE: 'entity_builder.flow.error.unknown.node.type',
|
|
301
304
|
RECORD_ID_MISSING: 'entity_builder.flow.error.record.id.missing',
|
|
302
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',
|
|
303
308
|
TOO_MANY_WRITES: 'entity_builder.flow.error.too.many.writes',
|
|
304
309
|
WRITE_FILTER_EMPTY: 'entity_builder.flow.error.write.filter.empty',
|
|
305
310
|
TOO_MANY_MATCHES: 'entity_builder.flow.error.too.many.matches',
|
|
@@ -362,6 +367,7 @@ const FLOW_MESSAGES = {
|
|
|
362
367
|
TRIGGER_MISSING: 'entity_builder.flow.validation.trigger.missing',
|
|
363
368
|
TRIGGER_MAIN_DUPLICATE: 'entity_builder.flow.validation.trigger.main.duplicate',
|
|
364
369
|
TRIGGER_PATH_INVALID: 'entity_builder.flow.validation.trigger.path.invalid',
|
|
370
|
+
TRIGGER_CONTRACT_INVALID: 'entity_builder.flow.validation.trigger.contract.invalid',
|
|
365
371
|
TRIGGER_PATH_DUPLICATE: 'entity_builder.flow.validation.trigger.path.duplicate',
|
|
366
372
|
EDGE_ID_DUPLICATE: 'entity_builder.flow.validation.edge.id.duplicate',
|
|
367
373
|
EDGE_DANGLING: 'entity_builder.flow.validation.edge.dangling',
|
|
@@ -391,6 +397,7 @@ const FLOW_MESSAGES = {
|
|
|
391
397
|
INPUT_TOO_MANY: 'entity_builder.flow.validation.input.too.many',
|
|
392
398
|
INPUT_NAME_INVALID: 'entity_builder.flow.validation.input.name.invalid',
|
|
393
399
|
INPUT_NAME_DUPLICATE: 'entity_builder.flow.validation.input.name.duplicate',
|
|
400
|
+
INPUT_NAME_RESERVED: 'entity_builder.flow.validation.input.name.reserved',
|
|
394
401
|
INPUT_TYPE_UNKNOWN: 'entity_builder.flow.validation.input.type.unknown',
|
|
395
402
|
INPUT_CHOICE_NO_OPTIONS: 'entity_builder.flow.validation.input.choice.no.options',
|
|
396
403
|
INPUT_ITEM_TYPE_UNKNOWN: 'entity_builder.flow.validation.input.item.type.unknown',
|
|
@@ -409,6 +416,9 @@ const FLOW_MESSAGES = {
|
|
|
409
416
|
NODE_CASE_DUPLICATE: 'entity_builder.flow.validation.node.case.duplicate',
|
|
410
417
|
NODE_MAX_ITEMS_RANGE: 'entity_builder.flow.validation.node.max.items.range',
|
|
411
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',
|
|
412
422
|
NODE_SORT_INVALID: 'entity_builder.flow.validation.node.sort.invalid',
|
|
413
423
|
NODE_WRITE_TARGET_INVALID: 'entity_builder.flow.validation.node.write.target.invalid',
|
|
414
424
|
NODE_NOT_FOUND_INVALID: 'entity_builder.flow.validation.node.not.found.invalid',
|
|
@@ -444,6 +454,9 @@ const FLOW_MESSAGES = {
|
|
|
444
454
|
MESSAGE_AFTER_COMMIT: 'entity_builder.flow.validation.message.after.commit',
|
|
445
455
|
PUBLIC_MESSAGE: 'entity_builder.flow.validation.public.message',
|
|
446
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',
|
|
447
460
|
REF_BAD_SCOPE: 'entity_builder.flow.validation.ref.bad.scope',
|
|
448
461
|
REF_NODE_MISSING: 'entity_builder.flow.validation.ref.node.missing',
|
|
449
462
|
REF_SELF: 'entity_builder.flow.validation.ref.self',
|
|
@@ -495,7 +508,8 @@ const FLOW_MESSAGES = {
|
|
|
495
508
|
LOOKUP_FILTER_INVALID: 'entity_builder.flow.shape.lookup.filter.invalid',
|
|
496
509
|
LOOKUP_FIELD_INVALID: 'entity_builder.flow.shape.lookup.field.invalid',
|
|
497
510
|
FILTER_OPERATOR_UNKNOWN: 'entity_builder.flow.shape.filter.operator.unknown',
|
|
498
|
-
FILTER_VALUE_REQUIRED: 'entity_builder.flow.shape.filter.value.required'
|
|
511
|
+
FILTER_VALUE_REQUIRED: 'entity_builder.flow.shape.filter.value.required',
|
|
512
|
+
FILTER_RANGE_INVALID: 'entity_builder.flow.shape.filter.range.invalid'
|
|
499
513
|
};
|
|
500
514
|
/** What a flow node problem is about (`what` variable of the node validation messages). */ const FLOW_SUBJECT_MESSAGES = {
|
|
501
515
|
THE_RULE: 'entity_builder.flow.subject.the.rule',
|
|
@@ -510,12 +524,19 @@ const FLOW_MESSAGES = {
|
|
|
510
524
|
THE_URL: 'entity_builder.flow.subject.the.url',
|
|
511
525
|
THE_INPUT_LIST: 'entity_builder.flow.subject.the.input.list',
|
|
512
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',
|
|
513
533
|
THE_RECIPIENTS: 'entity_builder.flow.subject.the.recipients',
|
|
514
534
|
THE_TITLE: 'entity_builder.flow.subject.the.title',
|
|
515
535
|
THE_MESSAGE: 'entity_builder.flow.subject.the.message',
|
|
516
536
|
THE_COMPANY: 'entity_builder.flow.subject.the.company',
|
|
517
537
|
THE_COMPANIES: 'entity_builder.flow.subject.the.companies',
|
|
518
538
|
THE_BRANCHES: 'entity_builder.flow.subject.the.branches',
|
|
539
|
+
THE_BRANCH: 'entity_builder.flow.subject.the.branch',
|
|
519
540
|
THE_USER: 'entity_builder.flow.subject.the.user',
|
|
520
541
|
THE_CC: 'entity_builder.flow.subject.the.cc',
|
|
521
542
|
THE_BCC: 'entity_builder.flow.subject.the.bcc',
|
|
@@ -570,6 +591,14 @@ const FLOW_MESSAGES = {
|
|
|
570
591
|
FLOW_UNPUBLISHED_SKIPPED: 'entity_builder.bundle.flow.unpublished.skipped',
|
|
571
592
|
FLOW_DRAFT_REPLACED: 'entity_builder.bundle.flow.draft.replaced',
|
|
572
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',
|
|
573
602
|
STEP_AFTER_EARLIER: 'entity_builder.bundle.step.after.earlier',
|
|
574
603
|
FLOWS_CHECKED_ON_APPLY: 'entity_builder.bundle.flows.checked.on.apply',
|
|
575
604
|
DESIGNER_READ_ONLY: 'entity_builder.designer.read.only'
|
package/fesm/458.js
CHANGED
|
@@ -34,8 +34,8 @@ var event_actions = __webpack_require__(982);
|
|
|
34
34
|
var controllers = __webpack_require__(729);
|
|
35
35
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/interfaces/reference-provider.interface.ts
|
|
36
36
|
var reference_provider_interface = __webpack_require__(2492);
|
|
37
|
-
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/index.ts +
|
|
38
|
-
var rule_engine = __webpack_require__(
|
|
37
|
+
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/rule-engine/index.ts + 2 modules
|
|
38
|
+
var rule_engine = __webpack_require__(8552);
|
|
39
39
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/services/index.ts + 3 modules
|
|
40
40
|
var services = __webpack_require__(8752);
|
|
41
41
|
// EXTERNAL MODULE: ./projects/nestjs-entity-builder/src/guards/index.ts
|
|
@@ -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
|