From 81037b1c8a812118df3ab9f3454c2352af2c2374 Mon Sep 17 00:00:00 2001 From: dothinh115 Date: Fri, 19 Jun 2026 00:50:35 +0700 Subject: [PATCH 1/5] refactor: rename user_definition to enfyra_user across tests and services - Updated references in cascade-policy.spec.ts to use enfyra_user instead of user_definition. - Modified graphql-schema-filter.spec.ts to reflect the new table name enfyra_user. - Changed public-methods-cleanup.spec.ts to replace route_definition with enfyra_route. - Adjusted runtime-metrics-collector.service.spec.ts to track queries for enfyra_route. - Updated upload-file-helper.spec.ts to use enfyra_file instead of file_definition. - Refactored zod-from-metadata.spec.ts to change method_definition to enfyra_method. - Altered relation-on-delete-metadata-writer.spec.ts to replace relation_definition with enfyra_relation. - Introduced system-core-table-resolver.service.ts to manage core system table names. - Added metadata-migration.util.ts for handling schema migrations. - Created metadata-physical-migration.util.ts for physical migration operations. - Defined system-tables.types.ts for system table type definitions. - Established script-table-contract.constants.ts for legacy script fields. - Implemented system-tables.constants.ts for core system table constants. --- README.md | 14 +- data/data-migration.json | 136 ++--- data/default-data.json | 204 +++---- data/snapshot-migration.json | 188 +++++- data/snapshot.json | 266 ++++----- package.json | 2 +- scripts/bench-handler-worker-memory.mjs | 2 +- scripts/init-db-mongo.ts | 8 +- scripts/init-db-sql.ts | 4 +- scripts/migrate-default-handlers.ts | 16 +- scripts/test-schema-migration.ts | 16 +- src/container.ts | 6 +- src/domain/auth/services/api-token.service.ts | 2 +- src/domain/auth/services/auth.service.ts | 16 +- .../services/oauth-exchange-code.service.ts | 2 +- src/domain/auth/services/oauth.service.ts | 18 +- .../auth/services/session-cleanup.service.ts | 4 +- .../auth/services/user-revocation.service.ts | 4 +- .../processors/base-table-processor.ts | 14 +- .../bootstrap-script-definition.processor.ts | 2 +- .../field-permission-definition.processor.ts | 6 +- .../flow-step-definition.processor.ts | 6 +- .../processors/generic-table.processor.ts | 20 +- .../graphql-definition.processor.ts | 6 +- .../processors/hook-definition.processor.ts | 189 ------ src/domain/bootstrap/processors/index.ts | 1 - .../processors/menu-definition.processor.ts | 8 +- .../post-hook-definition.processor.ts | 14 +- .../pre-hook-definition.processor.ts | 14 +- .../processors/route-definition.processor.ts | 100 ++-- .../route-handler-definition.processor.ts | 6 +- .../route-permission-definition.processor.ts | 4 +- .../processors/user-definition.processor.ts | 2 +- .../websocket-definition.processor.ts | 2 +- .../websocket-event-definition.processor.ts | 8 +- .../services/bootstrap-script.service.ts | 12 +- .../utils/bootstrap-data-validator.util.ts | 56 +- .../bootstrap/utils/snapshot-meta.util.ts | 18 +- .../utils/sql-junction-metadata.util.ts | 6 +- .../schema-migration-validator.service.ts | 2 +- .../services/system-safety-auditor.service.ts | 28 +- .../services/data-migration.service.ts | 32 +- .../services/data-provision.service.ts | 50 +- .../services/first-run-initializer.service.ts | 42 +- src/engines/bootstrap/services/index.ts | 1 + .../services/metadata-migration.service.ts | 557 ++++++++++++------ .../metadata-provision-mongo.service.ts | 39 +- .../metadata-provision-sql.service.ts | 57 +- .../services/schema-healing.service.ts | 117 ++-- .../system-core-table-resolver.service.ts | 49 ++ .../utils/metadata-migration.util.ts | 96 +++ .../utils/metadata-physical-migration.util.ts | 184 ++++++ .../services/cache-orchestrator.service.ts | 81 +-- .../services/column-rule-cache.service.ts | 6 +- .../field-permission-cache.service.ts | 6 +- .../cache/services/flow-cache.service.ts | 6 +- .../services/folder-tree-cache.service.ts | 2 +- .../services/gql-definition-cache.service.ts | 2 +- .../cache/services/guard-cache.service.ts | 4 +- .../cache/services/metadata-cache.service.ts | 28 +- .../services/oauth-config-cache.service.ts | 4 +- .../cache/services/package-cache.service.ts | 6 +- .../cache/services/route-cache.service.ts | 46 +- .../cache/services/setting-cache.service.ts | 2 +- .../services/storage-config-cache.service.ts | 2 +- .../cache/services/websocket-cache.service.ts | 20 +- src/engines/knex/knex.service.ts | 10 +- .../services/migration-journal.service.ts | 22 +- .../knex/services/sql-schema-diff.service.ts | 2 +- .../services/sql-schema-migration.service.ts | 14 +- src/engines/knex/types/knex-extended.types.ts | 4 +- src/engines/knex/utils/cascade-handler.ts | 6 +- src/engines/knex/utils/metadata-loader.ts | 14 +- .../knex/utils/migration/pk-type.util.ts | 14 +- .../knex/utils/migration/relation-changes.ts | 8 +- .../utils/migration/sql-diff-generator.ts | 2 +- .../knex/utils/provision/sync-table.ts | 12 +- .../mongo-migration-journal.service.ts | 2 +- .../mongo-physical-migration.service.ts | 2 +- .../services/mongo-schema-diff.service.ts | 2 +- .../mongo-schema-migration.service.ts | 22 +- src/http/routes/admin.routes.ts | 6 +- src/http/routes/extension.routes.ts | 6 +- src/http/routes/file.routes.ts | 16 +- src/http/routes/folder.routes.ts | 2 +- src/http/routes/package.routes.ts | 18 +- .../repositories/dynamic.repository.ts | 46 +- .../dynamic-api/services/dynamic.service.ts | 2 +- .../services/file-assets.service.ts | 16 +- .../queues/flow-execution-queue.service.ts | 14 +- .../flow-queue-maintenance.service.ts | 2 +- .../graphql/services/graphql.service.ts | 6 +- src/modules/me/services/me.service.ts | 6 +- .../mongo-metadata-snapshot.service.ts | 34 +- .../services/mongo-table-create.service.ts | 30 +- .../services/mongo-table-delete.service.ts | 20 +- .../mongo-table-handler-base.service.ts | 22 +- .../services/mongo-table-update.service.ts | 72 +-- .../services/sql-table-create.service.ts | 28 +- .../services/sql-table-delete.service.ts | 16 +- .../sql-table-metadata-builder.service.ts | 20 +- .../sql-table-metadata-writer.service.ts | 76 +-- .../services/sql-table-update.service.ts | 16 +- .../services/table-post-migration.service.ts | 12 +- .../services/table-route-artifacts.service.ts | 28 +- src/shared/helpers/upload-file.helper.ts | 4 +- src/shared/types/index.ts | 1 + src/shared/types/schema-migration.types.ts | 25 + src/shared/types/system-tables.types.ts | 11 + src/shared/utils/cache-events.constants.ts | 63 +- src/shared/utils/load-user-with-role.util.ts | 4 +- src/shared/utils/metadata-access.util.ts | 2 +- .../utils/provision-schema-migration.ts | 16 +- src/shared/utils/script-code.util.ts | 33 +- .../utils/script-persistence-contract.util.ts | 20 +- .../utils/script-table-contract.constants.ts | 20 + src/shared/utils/system-tables.constants.ts | 56 ++ src/shared/utils/zod-from-metadata.ts | 6 +- test/cache-load.e2e.js | 48 +- .../cache/cache-orchestrator-granular.spec.ts | 122 ++-- .../cache-orchestrator-reload-notify.spec.ts | 16 +- .../column-rule-cache-invalidation.spec.ts | 18 +- test/cache/column-rule-cache-partial.spec.ts | 14 +- .../field-permission-cache-partial.spec.ts | 24 +- test/cache/granular-cache-reload.spec.ts | 160 ++--- test/cache/guard-cache.service.spec.ts | 4 +- test/cache/metadata-cache.service.spec.ts | 20 +- .../cache/multi-instance-orchestrator.spec.ts | 30 +- test/cache/redis-runtime-cache-store.spec.ts | 2 +- .../route-cache-hook-partial-reload.spec.ts | 58 +- test/cache/route-cache-partial-reload.spec.ts | 20 +- test/cache/websocket-cache-partial.spec.ts | 12 +- test/domain/api-token.service.spec.ts | 8 +- test/domain/bootstrap-data-validator.spec.ts | 54 +- .../domain/canonical-table-route.util.spec.ts | 16 +- test/domain/data-migration.service.spec.ts | 54 +- ...ld-permission-definition-processor.spec.ts | 16 +- test/domain/load-user-with-role.util.spec.ts | 16 +- test/domain/menu-processor-lazy-batch.spec.ts | 22 +- .../metadata-provision-sql-relation.spec.ts | 14 +- .../oauth-exchange-code.service.spec.ts | 2 +- test/domain/provision-junction-swap.spec.ts | 62 +- .../domain/route-definition-processor.spec.ts | 10 +- .../schema-healing.mongo.integration.spec.ts | 90 +-- test/domain/schema-healing.service.spec.ts | 28 +- test/domain/script-code.util.spec.ts | 8 +- test/domain/snapshot-meta.util.spec.ts | 16 +- test/engines/cache/script-repair.spec.ts | 6 +- .../script-persistence-contract.spec.ts | 8 +- test/flow-engine.e2e.js | 8 +- test/http/admin-test-run.spec.ts | 2 +- test/http/assets-cache.e2e.spec.ts | 28 +- test/http/metadata-access.spec.ts | 6 +- test/knex/pg-enum-check-constraint.spec.ts | 8 +- .../relation-changes-inverse-junction.spec.ts | 4 +- .../knex/sql-physical-schema-contract.spec.ts | 40 +- test/metadata-reload.e2e.js | 6 +- ...dynamic-repository-field-selection.spec.ts | 48 +- .../dynamic-repository-route-methods.spec.ts | 6 +- test/modules/me-service.spec.ts | 4 +- .../migration-compensation-mongo.spec.ts | 28 +- test/mongo/physical-migration.service.spec.ts | 12 +- test/mongo/strip-unknown-columns.spec.ts | 10 +- .../package-cdn-loader.service.spec.ts | 2 +- test/query-builder.e2e.js | 152 ++--- test/realtime-chat-app.e2e.ts | 40 +- test/security/cascade-policy.spec.ts | 12 +- test/security/graphql-schema-filter.spec.ts | 52 +- test/shared/public-methods-cleanup.spec.ts | 40 +- .../runtime-metrics-collector.service.spec.ts | 16 +- test/shared/upload-file-helper.spec.ts | 2 +- test/shared/zod-from-metadata.spec.ts | 4 +- ...relation-on-delete-metadata-writer.spec.ts | 24 +- 173 files changed, 3015 insertions(+), 2299 deletions(-) delete mode 100644 src/domain/bootstrap/processors/hook-definition.processor.ts create mode 100644 src/engines/bootstrap/services/system-core-table-resolver.service.ts create mode 100644 src/engines/bootstrap/utils/metadata-migration.util.ts create mode 100644 src/engines/bootstrap/utils/metadata-physical-migration.util.ts create mode 100644 src/shared/types/system-tables.types.ts create mode 100644 src/shared/utils/script-table-contract.constants.ts create mode 100644 src/shared/utils/system-tables.constants.ts diff --git a/README.md b/README.md index 803dd87e..6058d33b 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Both physical DB and metadata are updated from the same source, ensuring consist ### Destructive schema changes via API (confirm-hash) -When changing schema through the API (e.g. updating or deleting a row in `table_definition`), destructive changes are protected by a confirm-hash challenge. +When changing schema through the API (e.g. updating or deleting a row in `enfyra_table`), destructive changes are protected by a confirm-hash challenge. - The server returns **422** with `code = "SCHEMA_CONFIRM_REQUIRED"` and `details` including: - `requiredConfirmHash` @@ -234,13 +234,13 @@ Example flow: ```bash # 1) Attempt destructive update -curl -X PATCH "http://localhost:1105/api/table_definition/" \ +curl -X PATCH "http://localhost:1105/api/enfyra_table/" \ -H "Content-Type: application/json" \ -d '{"columns":[{"name":"id","type":"int"}]}' # 2) Server responds 422 with details.requiredConfirmHash + details.confirmToken # 3) Retry with headers -curl -X PATCH "http://localhost:1105/api/table_definition/" \ +curl -X PATCH "http://localhost:1105/api/enfyra_table/" \ -H "Content-Type: application/json" \ -H "x-schema-confirm-hash: " \ -H "x-schema-confirm-token: " \ @@ -256,7 +256,7 @@ Migrate existing data when the system is already initialized: ```json { "_deletedTables": ["deprecated_table"], - "role_definition": [ + "enfyra_role": [ { "name": "Admin", "description": "Updated admin description", @@ -272,13 +272,13 @@ Migrate existing data when the system is already initialized: #### Delete specific records (`_deletedRecords`) -Use `_deletedRecords` when you need to remove a small set of rows (e.g. remove an old seeded `route_definition`) without wiping the entire table. +Use `_deletedRecords` when you need to remove a small set of rows (e.g. remove an old seeded `enfyra_route`) without wiping the entire table. ```json { "_deletedRecords": [ - { "table": "route_definition", "filter": { "path": { "_eq": "/old-route" } } }, - { "table": "menu_definition", "filter": { "path": { "_eq": "/old-menu" } } } + { "table": "enfyra_route", "filter": { "path": { "_eq": "/old-route" } } }, + { "table": "enfyra_menu", "filter": { "path": { "_eq": "/old-menu" } } } ] } ``` diff --git a/data/data-migration.json b/data/data-migration.json index ccdef837..b3926fd5 100644 --- a/data/data-migration.json +++ b/data/data-migration.json @@ -1,7 +1,7 @@ { "_deletedRecords": [ { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/api-docs" @@ -9,7 +9,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/admin/metadata-sync/:id" @@ -17,7 +17,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/admin/flow/test-step" @@ -25,7 +25,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/ai_config_definition" @@ -33,7 +33,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/ai_conversation_definition" @@ -41,7 +41,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/ai_message_definition" @@ -49,7 +49,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/ai-agent/chat" @@ -57,7 +57,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/ai-agent/chat/stream" @@ -65,7 +65,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/ai-agent/cancel" @@ -73,7 +73,7 @@ } }, { - "table": "menu_definition", + "table": "enfyra_menu", "filter": { "path": { "_eq": "/ai" @@ -81,7 +81,7 @@ } }, { - "table": "menu_definition", + "table": "enfyra_menu", "filter": { "path": { "_eq": "/ai-agent/chat" @@ -89,7 +89,7 @@ } }, { - "table": "menu_definition", + "table": "enfyra_menu", "filter": { "path": { "_eq": "/ai-agent/config" @@ -97,7 +97,7 @@ } }, { - "table": "menu_definition", + "table": "enfyra_menu", "filter": { "path": { "_eq": "/settings/field-permissions" @@ -105,7 +105,7 @@ } }, { - "table": "menu_definition", + "table": "enfyra_menu", "filter": { "path": { "_eq": "/settings/admin/cache" @@ -113,7 +113,7 @@ } }, { - "table": "menu_definition", + "table": "enfyra_menu", "filter": { "path": { "_eq": "/settings/routings" @@ -121,7 +121,7 @@ } }, { - "table": "websocket_event_definition", + "table": "enfyra_websocket_event", "filter": { "eventName": { "_eq": "ping" @@ -129,7 +129,7 @@ } }, { - "table": "route_definition", + "table": "enfyra_route", "filter": { "path": { "_eq": "/admin/reload/swagger" @@ -137,7 +137,7 @@ } }, { - "table": "method_definition", + "table": "enfyra_method", "filter": { "name": { "_eq": "GQL_QUERY" @@ -145,7 +145,7 @@ } }, { - "table": "method_definition", + "table": "enfyra_method", "filter": { "name": { "_eq": "GQL_MUTATION" @@ -153,7 +153,7 @@ } } ], - "method_definition": [ + "enfyra_method": [ { "_unique": { "name": { @@ -191,7 +191,7 @@ "textColor": "#b91c1c" } ], - "field_permission_definition": [ + "enfyra_field_permission": [ { "_unique": { "_and": [ @@ -212,7 +212,7 @@ }, "table": { "name": { - "_eq": "user_definition" + "_eq": "enfyra_user" } } } @@ -227,7 +227,7 @@ "isSystem": true } ], - "menu_definition": [ + "enfyra_menu": [ { "_unique": { "path": { @@ -246,7 +246,7 @@ "permission": { "or": [ { - "route": "/route_definition", + "route": "/enfyra_route", "methods": [ "GET" ] @@ -263,7 +263,7 @@ "path": "/settings/runtime" } ], - "post_hook_definition": [ + "enfyra_post_hook": [ { "_unique": { "name": { @@ -274,11 +274,11 @@ "scriptLanguage": "typescript" } ], - "route_definition": [ + "enfyra_route": [ { "_unique": { "path": { - "_eq": "/package_definition" + "_eq": "/enfyra_package" } }, "publicMethods": [], @@ -385,7 +385,7 @@ { "_unique": { "path": { - "_eq": "/folder_definition/tree" + "_eq": "/enfyra_folder/tree" } }, "publicMethods": [], @@ -404,7 +404,7 @@ { "_unique": { "path": { - "_eq": "/route_definition" + "_eq": "/enfyra_route" } }, "publicMethods": [], @@ -421,7 +421,7 @@ { "_unique": { "path": { - "_eq": "/table_definition" + "_eq": "/enfyra_table" } }, "publicMethods": [] @@ -429,7 +429,7 @@ { "_unique": { "path": { - "_eq": "/setting_definition" + "_eq": "/enfyra_setting" } }, "publicMethods": [], @@ -462,7 +462,7 @@ { "_unique": { "path": { - "_eq": "/method_definition" + "_eq": "/enfyra_method" } }, "availableMethods": [ @@ -475,7 +475,7 @@ { "_unique": { "path": { - "_eq": "/pre_hook_definition" + "_eq": "/enfyra_pre_hook" } }, "availableMethods": [ @@ -488,7 +488,7 @@ { "_unique": { "path": { - "_eq": "/post_hook_definition" + "_eq": "/enfyra_post_hook" } }, "availableMethods": [ @@ -501,7 +501,7 @@ { "_unique": { "path": { - "_eq": "/route_handler_definition" + "_eq": "/enfyra_route_handler" } }, "availableMethods": [ @@ -514,7 +514,7 @@ { "_unique": { "path": { - "_eq": "/route_permission_definition" + "_eq": "/enfyra_route_permission" } }, "availableMethods": [ @@ -527,7 +527,7 @@ { "_unique": { "path": { - "_eq": "/menu_definition" + "_eq": "/enfyra_menu" } }, "publicMethods": [], @@ -544,7 +544,7 @@ { "_unique": { "path": { - "_eq": "/extension_definition" + "_eq": "/enfyra_extension" } }, "publicMethods": [], @@ -558,7 +558,7 @@ { "_unique": { "path": { - "_eq": "/extension_definition/preview" + "_eq": "/enfyra_extension/preview" } }, "publicMethods": [] @@ -566,7 +566,7 @@ { "_unique": { "path": { - "_eq": "/bootstrap_script_definition" + "_eq": "/enfyra_bootstrap_script" } }, "availableMethods": [ @@ -579,7 +579,7 @@ { "_unique": { "path": { - "_eq": "/storage_config_definition" + "_eq": "/enfyra_storage_config" } }, "publicMethods": [], @@ -596,7 +596,7 @@ { "_unique": { "path": { - "_eq": "/oauth_config_definition" + "_eq": "/enfyra_oauth_config" } }, "availableMethods": [ @@ -609,7 +609,7 @@ { "_unique": { "path": { - "_eq": "/oauth_account_definition" + "_eq": "/enfyra_oauth_account" } }, "availableMethods": [ @@ -622,7 +622,7 @@ { "_unique": { "path": { - "_eq": "/websocket_definition" + "_eq": "/enfyra_websocket" } }, "availableMethods": [ @@ -635,7 +635,7 @@ { "_unique": { "path": { - "_eq": "/websocket_event_definition" + "_eq": "/enfyra_websocket_event" } }, "availableMethods": [ @@ -648,7 +648,7 @@ { "_unique": { "path": { - "_eq": "/flow_definition" + "_eq": "/enfyra_flow" } }, "availableMethods": [ @@ -661,7 +661,7 @@ { "_unique": { "path": { - "_eq": "/flow_step_definition" + "_eq": "/enfyra_flow_step" } }, "availableMethods": [ @@ -674,7 +674,7 @@ { "_unique": { "path": { - "_eq": "/flow_execution_definition" + "_eq": "/enfyra_flow_execution" } }, "availableMethods": [ @@ -687,7 +687,7 @@ { "_unique": { "path": { - "_eq": "/file_permission_definition" + "_eq": "/enfyra_file_permission" } }, "availableMethods": [ @@ -700,7 +700,7 @@ { "_unique": { "path": { - "_eq": "/guard_rule_definition" + "_eq": "/enfyra_guard_rule" } }, "availableMethods": [ @@ -713,7 +713,7 @@ { "_unique": { "path": { - "_eq": "/user_definition" + "_eq": "/enfyra_user" } }, "availableMethods": [ @@ -726,7 +726,7 @@ { "_unique": { "path": { - "_eq": "/role_definition" + "_eq": "/enfyra_role" } }, "availableMethods": [ @@ -739,7 +739,7 @@ { "_unique": { "path": { - "_eq": "/folder_definition" + "_eq": "/enfyra_folder" } }, "availableMethods": [ @@ -750,12 +750,12 @@ ] } ], - "gql_definition": [ + "enfyra_graphql": [ { "_unique": { "table": { "name": { - "_eq": "table_definition" + "_eq": "enfyra_table" } } }, @@ -766,7 +766,7 @@ "_unique": { "table": { "name": { - "_eq": "user_definition" + "_eq": "enfyra_user" } } }, @@ -777,7 +777,7 @@ "_unique": { "table": { "name": { - "_eq": "role_definition" + "_eq": "enfyra_role" } } }, @@ -788,7 +788,7 @@ "_unique": { "table": { "name": { - "_eq": "setting_definition" + "_eq": "enfyra_setting" } } }, @@ -799,7 +799,7 @@ "_unique": { "table": { "name": { - "_eq": "folder_definition" + "_eq": "enfyra_folder" } } }, @@ -807,24 +807,24 @@ "isSystem": true } ], - "table_definition": [ + "enfyra_table": [ { "_unique": { "name": { - "_eq": "file_definition" + "_eq": "enfyra_file" } }, "validateBody": false } ], - "column_definition": [ + "enfyra_column": [ { "_unique": { "_and": [ { "table": { "name": { - "_eq": "column_definition" + "_eq": "enfyra_column" } } }, @@ -860,7 +860,7 @@ { "table": { "name": { - "_eq": "route_definition" + "_eq": "enfyra_route" } } }, @@ -879,7 +879,7 @@ { "table": { "name": { - "_eq": "menu_definition" + "_eq": "enfyra_menu" } } }, @@ -898,7 +898,7 @@ { "table": { "name": { - "_eq": "extension_definition" + "_eq": "enfyra_extension" } } }, @@ -922,7 +922,7 @@ { "table": { "name": { - "_eq": "folder_definition" + "_eq": "enfyra_folder" } } }, @@ -941,7 +941,7 @@ { "table": { "name": { - "_eq": "flow_definition" + "_eq": "enfyra_flow" } } }, diff --git a/data/default-data.json b/data/default-data.json index 2f4dafa7..8fe722a3 100644 --- a/data/default-data.json +++ b/data/default-data.json @@ -1,14 +1,14 @@ { - "role_definition": { + "enfyra_role": { "name": "Admin" }, - "setting_definition": { + "enfyra_setting": { "isInit": false, "isSystem": true, "projectName": "Enfyra CMS", "projectDescription": "Description for Enfyra CMS" }, - "method_definition": [ + "enfyra_method": [ { "buttonColor": "#dbeafe", "textColor": "#1d4ed8", @@ -34,10 +34,10 @@ "name": "DELETE" } ], - "route_definition": [ + "enfyra_route": [ { - "path": "/route_definition", - "mainTable": "route_definition", + "path": "/enfyra_route", + "mainTable": "enfyra_route", "isEnabled": true, "isSystem": true, "icon": "lucide:git-branch", @@ -52,8 +52,8 @@ ] }, { - "path": "/table_definition", - "mainTable": "table_definition", + "path": "/enfyra_table", + "mainTable": "enfyra_table", "isEnabled": true, "isSystem": true, "icon": "lucide:table", @@ -65,8 +65,8 @@ ] }, { - "path": "/relation_definition", - "mainTable": "relation_definition", + "path": "/enfyra_relation", + "mainTable": "enfyra_relation", "isEnabled": true, "isSystem": true, "icon": "lucide:git-branch", @@ -78,8 +78,8 @@ ] }, { - "path": "/user_definition", - "mainTable": "user_definition", + "path": "/enfyra_user", + "mainTable": "enfyra_user", "isEnabled": true, "isSystem": true, "icon": "lucide:user", @@ -91,8 +91,8 @@ ] }, { - "path": "/setting_definition", - "mainTable": "setting_definition", + "path": "/enfyra_setting", + "mainTable": "enfyra_setting", "isEnabled": true, "isSystem": true, "icon": "lucide:settings", @@ -107,8 +107,8 @@ ] }, { - "path": "/cors_origin_definition", - "mainTable": "cors_origin_definition", + "path": "/enfyra_cors_origin", + "mainTable": "enfyra_cors_origin", "isEnabled": true, "isSystem": true, "icon": "lucide:globe", @@ -123,8 +123,8 @@ ] }, { - "path": "/role_definition", - "mainTable": "role_definition", + "path": "/enfyra_role", + "mainTable": "enfyra_role", "isEnabled": true, "isSystem": true, "icon": "lucide:shield-check", @@ -234,8 +234,8 @@ ] }, { - "path": "/method_definition", - "mainTable": "method_definition", + "path": "/enfyra_method", + "mainTable": "enfyra_method", "isEnabled": true, "isSystem": true, "icon": "lucide:route", @@ -247,8 +247,8 @@ ] }, { - "path": "/pre_hook_definition", - "mainTable": "pre_hook_definition", + "path": "/enfyra_pre_hook", + "mainTable": "enfyra_pre_hook", "isEnabled": true, "isSystem": true, "icon": "lucide:route", @@ -260,8 +260,8 @@ ] }, { - "path": "/post_hook_definition", - "mainTable": "post_hook_definition", + "path": "/enfyra_post_hook", + "mainTable": "enfyra_post_hook", "isEnabled": true, "isSystem": true, "icon": "lucide:route", @@ -273,8 +273,8 @@ ] }, { - "path": "/route_handler_definition", - "mainTable": "route_handler_definition", + "path": "/enfyra_route_handler", + "mainTable": "enfyra_route_handler", "isEnabled": true, "isSystem": true, "icon": "lucide:route", @@ -286,8 +286,8 @@ ] }, { - "path": "/route_permission_definition", - "mainTable": "route_permission_definition", + "path": "/enfyra_route_permission", + "mainTable": "enfyra_route_permission", "isEnabled": true, "isSystem": true, "icon": "lucide:route", @@ -299,8 +299,8 @@ ] }, { - "path": "/field_permission_definition", - "mainTable": "field_permission_definition", + "path": "/enfyra_field_permission", + "mainTable": "enfyra_field_permission", "isEnabled": true, "isSystem": true, "icon": "lucide:shield", @@ -312,8 +312,8 @@ ] }, { - "path": "/column_rule_definition", - "mainTable": "column_rule_definition", + "path": "/enfyra_column_rule", + "mainTable": "enfyra_column_rule", "isEnabled": true, "isSystem": true, "icon": "lucide:ruler", @@ -325,8 +325,8 @@ ] }, { - "path": "/menu_definition", - "mainTable": "menu_definition", + "path": "/enfyra_menu", + "mainTable": "enfyra_menu", "isEnabled": true, "isSystem": true, "icon": "lucide:menu", @@ -341,8 +341,8 @@ ] }, { - "path": "/extension_definition", - "mainTable": "extension_definition", + "path": "/enfyra_extension", + "mainTable": "enfyra_extension", "isEnabled": true, "isSystem": true, "icon": "lucide:puzzle", @@ -354,7 +354,7 @@ ] }, { - "path": "/extension_definition/preview", + "path": "/enfyra_extension/preview", "isEnabled": true, "isSystem": true, "availableMethods": [ @@ -362,8 +362,8 @@ ] }, { - "path": "/folder_definition", - "mainTable": "folder_definition", + "path": "/enfyra_folder", + "mainTable": "enfyra_folder", "isEnabled": true, "isSystem": true, "icon": "lucide:folder", @@ -375,7 +375,7 @@ ] }, { - "path": "/folder_definition/tree", + "path": "/enfyra_folder/tree", "isEnabled": true, "isSystem": true, "icon": "lucide:folder-tree", @@ -387,8 +387,8 @@ ] }, { - "path": "/file_definition", - "mainTable": "file_definition", + "path": "/enfyra_file", + "mainTable": "enfyra_file", "isEnabled": true, "isSystem": true, "icon": "lucide:file", @@ -400,8 +400,8 @@ ] }, { - "path": "/file_permission_definition", - "mainTable": "file_permission_definition", + "path": "/enfyra_file_permission", + "mainTable": "enfyra_file_permission", "isEnabled": true, "isSystem": true, "icon": "lucide:file", @@ -425,8 +425,8 @@ ] }, { - "path": "/package_definition", - "mainTable": "package_definition", + "path": "/enfyra_package", + "mainTable": "enfyra_package", "isEnabled": true, "isSystem": true, "icon": "lucide:package", @@ -481,8 +481,8 @@ ] }, { - "path": "/bootstrap_script_definition", - "mainTable": "bootstrap_script_definition", + "path": "/enfyra_bootstrap_script", + "mainTable": "enfyra_bootstrap_script", "isEnabled": true, "isSystem": true, "icon": "lucide:play", @@ -494,8 +494,8 @@ ] }, { - "path": "/storage_config_definition", - "mainTable": "storage_config_definition", + "path": "/enfyra_storage_config", + "mainTable": "enfyra_storage_config", "isEnabled": true, "isSystem": true, "icon": "lucide:hard-drive", @@ -651,8 +651,8 @@ ] }, { - "path": "/websocket_definition", - "mainTable": "websocket_definition", + "path": "/enfyra_websocket", + "mainTable": "enfyra_websocket", "isEnabled": true, "isSystem": true, "icon": "lucide:radio-tower", @@ -664,8 +664,8 @@ ] }, { - "path": "/websocket_event_definition", - "mainTable": "websocket_event_definition", + "path": "/enfyra_websocket_event", + "mainTable": "enfyra_websocket_event", "isEnabled": true, "isSystem": true, "icon": "lucide:zap", @@ -677,8 +677,8 @@ ] }, { - "path": "/oauth_config_definition", - "mainTable": "oauth_config_definition", + "path": "/enfyra_oauth_config", + "mainTable": "enfyra_oauth_config", "isEnabled": true, "isSystem": true, "icon": "lucide:key", @@ -690,8 +690,8 @@ ] }, { - "path": "/oauth_account_definition", - "mainTable": "oauth_account_definition", + "path": "/enfyra_oauth_account", + "mainTable": "enfyra_oauth_account", "isEnabled": true, "isSystem": true, "icon": "lucide:link", @@ -703,8 +703,8 @@ ] }, { - "path": "/flow_definition", - "mainTable": "flow_definition", + "path": "/enfyra_flow", + "mainTable": "enfyra_flow", "isEnabled": true, "isSystem": true, "icon": "lucide:workflow", @@ -716,8 +716,8 @@ ] }, { - "path": "/flow_step_definition", - "mainTable": "flow_step_definition", + "path": "/enfyra_flow_step", + "mainTable": "enfyra_flow_step", "isEnabled": true, "isSystem": true, "icon": "lucide:git-branch", @@ -729,8 +729,8 @@ ] }, { - "path": "/flow_execution_definition", - "mainTable": "flow_execution_definition", + "path": "/enfyra_flow_execution", + "mainTable": "enfyra_flow_execution", "isEnabled": true, "isSystem": true, "icon": "lucide:play-circle", @@ -742,8 +742,8 @@ ] }, { - "path": "/gql_definition", - "mainTable": "gql_definition", + "path": "/enfyra_graphql", + "mainTable": "enfyra_graphql", "isEnabled": true, "isSystem": true, "icon": "lucide:file-code", @@ -755,8 +755,8 @@ ] }, { - "path": "/guard_definition", - "mainTable": "guard_definition", + "path": "/enfyra_guard", + "mainTable": "enfyra_guard", "isEnabled": true, "isSystem": true, "icon": "lucide:shield", @@ -768,8 +768,8 @@ ] }, { - "path": "/guard_rule_definition", - "mainTable": "guard_rule_definition", + "path": "/enfyra_guard_rule", + "mainTable": "enfyra_guard_rule", "isEnabled": true, "isSystem": true, "icon": "lucide:shield-check", @@ -791,8 +791,8 @@ ] }, { - "path": "/schema_migration_definition", - "mainTable": "schema_migration_definition", + "path": "/enfyra_schema_migration", + "mainTable": "enfyra_schema_migration", "isEnabled": true, "isSystem": true, "icon": "lucide:scroll-text", @@ -801,13 +801,13 @@ ] } ], - "pre_hook_definition": [ + "enfyra_pre_hook": [ { "isEnabled": true, "isGlobal": false, "name": "User definition hash password", "isSystem": true, - "route": "/user_definition", + "route": "/enfyra_user", "methods": [ "POST", "PATCH" @@ -832,7 +832,7 @@ "isGlobal": false, "name": "Folder definition auto slug", "isSystem": true, - "route": "/folder_definition", + "route": "/enfyra_folder", "methods": [ "POST", "PATCH" @@ -841,7 +841,7 @@ "scriptLanguage": "typescript" } ], - "post_hook_definition": [ + "enfyra_post_hook": [ { "isEnabled": true, "isGlobal": true, @@ -857,7 +857,7 @@ "scriptLanguage": "typescript" } ], - "storage_config_definition": [ + "enfyra_storage_config": [ { "name": "Local", "type": "Local Storage", @@ -866,7 +866,7 @@ "description": "Default local file storage" } ], - "menu_definition": [ + "enfyra_menu": [ { "type": "Menu", "label": "Dashboard", @@ -905,7 +905,7 @@ "permission": { "or": [ { - "route": "/table_definition", + "route": "/enfyra_table", "methods": [ "GET" ] @@ -939,7 +939,7 @@ "permission": { "or": [ { - "route": "/setting_definition", + "route": "/enfyra_setting", "methods": [ "GET" ] @@ -960,7 +960,7 @@ "permission": { "or": [ { - "route": "/menu_definition", + "route": "/enfyra_menu", "methods": [ "GET" ] @@ -981,7 +981,7 @@ "permission": { "or": [ { - "route": "/extension_definition", + "route": "/enfyra_extension", "methods": [ "GET" ] @@ -1002,7 +1002,7 @@ "permission": { "or": [ { - "route": "/route_definition", + "route": "/enfyra_route", "methods": [ "GET" ] @@ -1023,7 +1023,7 @@ "permission": { "or": [ { - "route": "/method_definition", + "route": "/enfyra_method", "methods": [ "GET" ] @@ -1044,7 +1044,7 @@ "permission": { "or": [ { - "route": "/guard_definition", + "route": "/enfyra_guard", "methods": [ "GET" ] @@ -1065,7 +1065,7 @@ "permission": { "or": [ { - "route": "/route_definition", + "route": "/enfyra_route", "methods": [ "GET" ] @@ -1086,7 +1086,7 @@ "permission": { "or": [ { - "route": "/websocket_definition", + "route": "/enfyra_websocket", "methods": [ "GET" ] @@ -1107,7 +1107,7 @@ "permission": { "or": [ { - "route": "/flow_definition", + "route": "/enfyra_flow", "methods": [ "GET" ] @@ -1128,7 +1128,7 @@ "permission": { "or": [ { - "route": "/bootstrap_script_definition", + "route": "/enfyra_bootstrap_script", "methods": [ "GET" ] @@ -1183,7 +1183,7 @@ "permission": { "or": [ { - "route": "/package_definition", + "route": "/enfyra_package", "methods": [ "GET" ] @@ -1204,7 +1204,7 @@ "permission": { "or": [ { - "route": "/user_definition", + "route": "/enfyra_user", "methods": [ "GET" ] @@ -1225,7 +1225,7 @@ "permission": { "or": [ { - "route": "/role_definition", + "route": "/enfyra_role", "methods": [ "GET" ] @@ -1246,13 +1246,13 @@ "permission": { "or": [ { - "route": "/oauth_config_definition", + "route": "/enfyra_oauth_config", "methods": [ "GET" ] }, { - "route": "/oauth_account_definition", + "route": "/enfyra_oauth_account", "methods": [ "GET" ] @@ -1273,7 +1273,7 @@ "permission": { "or": [ { - "route": "/oauth_config_definition", + "route": "/enfyra_oauth_config", "methods": [ "GET" ] @@ -1294,7 +1294,7 @@ "permission": { "or": [ { - "route": "/oauth_account_definition", + "route": "/enfyra_oauth_account", "methods": [ "GET" ] @@ -1314,7 +1314,7 @@ "permission": { "or": [ { - "route": "/file_definition", + "route": "/enfyra_file", "methods": [ "GET" ] @@ -1362,7 +1362,7 @@ "permission": { "or": [ { - "route": "/file_definition", + "route": "/enfyra_file", "methods": [ "GET" ] @@ -1404,7 +1404,7 @@ "parent": "Packages" } ], - "websocket_definition": [ + "enfyra_websocket": [ { "path": "/enfyra-admin", "isEnabled": true, @@ -1415,8 +1415,8 @@ "scriptLanguage": "typescript" } ], - "websocket_event_definition": [], - "field_permission_definition": [ + "enfyra_websocket_event": [], + "enfyra_field_permission": [ { "isEnabled": true, "isSystem": true, @@ -1424,7 +1424,7 @@ "effect": "allow", "description": "Allow authenticated user to update own password via /me", "_column": { - "table": "user_definition", + "table": "enfyra_user", "name": "password" }, "_role": null, diff --git a/data/snapshot-migration.json b/data/snapshot-migration.json index 4963ac72..3d7c5e5e 100644 --- a/data/snapshot-migration.json +++ b/data/snapshot-migration.json @@ -3,7 +3,7 @@ { "_unique": { "name": { - "_eq": "column_definition" + "_eq": "enfyra_column" } }, "columnsToRemove": [ @@ -43,7 +43,7 @@ { "_unique": { "name": { - "_eq": "route_definition" + "_eq": "enfyra_route" } }, "relationsToModify": [ @@ -79,7 +79,7 @@ { "_unique": { "name": { - "_eq": "pre_hook_definition" + "_eq": "enfyra_pre_hook" } }, "columnsToRemove": [ @@ -102,7 +102,7 @@ { "_unique": { "name": { - "_eq": "post_hook_definition" + "_eq": "enfyra_post_hook" } }, "columnsToRemove": [ @@ -125,7 +125,7 @@ { "_unique": { "name": { - "_eq": "file_definition" + "_eq": "enfyra_file" } }, "relationsToModify": [ @@ -142,7 +142,7 @@ { "_unique": { "name": { - "_eq": "method_definition" + "_eq": "enfyra_method" } }, "relationsToModify": [ @@ -172,7 +172,7 @@ { "_unique": { "name": { - "_eq": "field_permission_definition" + "_eq": "enfyra_field_permission" } }, "relationsToRemove": [ @@ -182,7 +182,7 @@ { "_unique": { "name": { - "_eq": "package_definition" + "_eq": "enfyra_package" } }, "relationsToModify": [ @@ -201,7 +201,7 @@ { "_unique": { "name": { - "_eq": "relation_definition" + "_eq": "enfyra_relation" } }, "columnsToRemove": [ @@ -225,7 +225,7 @@ { "_unique": { "name": { - "_eq": "websocket_definition" + "_eq": "enfyra_websocket" } }, "relationsToRemove": [ @@ -238,7 +238,7 @@ { "_unique": { "name": { - "_eq": "setting_definition" + "_eq": "enfyra_setting" } }, "columnsToRemove": [ @@ -248,7 +248,7 @@ { "_unique": { "name": { - "_eq": "route_handler_definition" + "_eq": "enfyra_route_handler" } }, "columnsToRemove": [ @@ -258,7 +258,7 @@ { "_unique": { "name": { - "_eq": "bootstrap_script_definition" + "_eq": "enfyra_bootstrap_script" } }, "columnsToRemove": [ @@ -268,7 +268,7 @@ { "_unique": { "name": { - "_eq": "websocket_event_definition" + "_eq": "enfyra_websocket_event" } }, "columnsToRemove": [ @@ -281,5 +281,165 @@ ], "tablesToDrop": [ "schema_history" + ], + "coreTablesToRename": [ + { + "from": "table_definition", + "to": "enfyra_table" + }, + { + "from": "column_definition", + "to": "enfyra_column" + }, + { + "from": "relation_definition", + "to": "enfyra_relation" + } + ], + "tablesToRename": [ + { + "from": "column_rule_definition", + "to": "enfyra_column_rule" + }, + { + "from": "user_definition", + "to": "enfyra_user" + }, + { + "from": "oauth_config_definition", + "to": "enfyra_oauth_config" + }, + { + "from": "oauth_account_definition", + "to": "enfyra_oauth_account" + }, + { + "from": "setting_definition", + "to": "enfyra_setting" + }, + { + "from": "cors_origin_definition", + "to": "enfyra_cors_origin" + }, + { + "from": "route_definition", + "to": "enfyra_route" + }, + { + "from": "role_definition", + "to": "enfyra_role" + }, + { + "from": "route_permission_definition", + "to": "enfyra_route_permission" + }, + { + "from": "field_permission_definition", + "to": "enfyra_field_permission" + }, + { + "from": "route_handler_definition", + "to": "enfyra_route_handler" + }, + { + "from": "pre_hook_definition", + "to": "enfyra_pre_hook" + }, + { + "from": "post_hook_definition", + "to": "enfyra_post_hook" + }, + { + "from": "session_definition", + "to": "enfyra_session" + }, + { + "from": "api_token_definition", + "to": "enfyra_api_token" + }, + { + "from": "schema_migration_definition", + "to": "enfyra_schema_migration" + }, + { + "from": "method_definition", + "to": "enfyra_method" + }, + { + "from": "menu_definition", + "to": "enfyra_menu" + }, + { + "from": "extension_definition", + "to": "enfyra_extension" + }, + { + "from": "folder_definition", + "to": "enfyra_folder" + }, + { + "from": "file_definition", + "to": "enfyra_file" + }, + { + "from": "file_permission_definition", + "to": "enfyra_file_permission" + }, + { + "from": "package_definition", + "to": "enfyra_package" + }, + { + "from": "bootstrap_script_definition", + "to": "enfyra_bootstrap_script" + }, + { + "from": "storage_config_definition", + "to": "enfyra_storage_config" + }, + { + "from": "websocket_definition", + "to": "enfyra_websocket" + }, + { + "from": "websocket_event_definition", + "to": "enfyra_websocket_event" + }, + { + "from": "flow_definition", + "to": "enfyra_flow" + }, + { + "from": "flow_step_definition", + "to": "enfyra_flow_step" + }, + { + "from": "flow_execution_definition", + "to": "enfyra_flow_execution" + }, + { + "from": "guard_definition", + "to": "enfyra_guard" + }, + { + "from": "guard_rule_definition", + "to": "enfyra_guard_rule" + }, + { + "from": "gql_definition", + "to": "enfyra_graphql" + } + ], + "physicalTablesToDrop": [ + "file_permission_definition_allowedUsers_user_definition", + "method_definition_route_permissions_route_permission_definition", + "route_definition_targetTables_table_definition", + "websocket_definition_targetTables_table_definition" + ], + "physicalTablesToRename": [ + { + "from": "schema_physical_migration_definition", + "to": "enfyra_schema_physical_migration" + } ] } diff --git a/data/snapshot.json b/data/snapshot.json index 8cbec44b..e5dbf91c 100644 --- a/data/snapshot.json +++ b/data/snapshot.json @@ -1,6 +1,6 @@ { - "column_definition": { - "name": "column_definition", + "enfyra_column": { + "name": "enfyra_column", "description": "Stores column/field definitions for all tables in the system", "isSystem": true, "uniques": [ @@ -27,11 +27,11 @@ { "name": "metadata", "type": "simple-json", "isSystem": true, "description": "Additional metadata for the column" } ], "relations": [ - { "propertyName": "table", "type": "many-to-one", "targetTable": "table_definition", "isSystem": true, "isUpdatable": false, "inversePropertyName": "columns", "onDelete": "CASCADE", "description": "The table that this column belongs to" } + { "propertyName": "table", "type": "many-to-one", "targetTable": "enfyra_table", "isSystem": true, "isUpdatable": false, "inversePropertyName": "columns", "onDelete": "CASCADE", "description": "The table that this column belongs to" } ] }, - "column_rule_definition": { - "name": "column_rule_definition", + "enfyra_column_rule": { + "name": "enfyra_column_rule", "description": "Validation rules attached to columns (min/max/pattern/format/etc.)", "isSystem": true, "columns": [ @@ -43,11 +43,11 @@ { "name": "description", "type": "text", "isSystem": true, "description": "Admin note about this rule" } ], "relations": [ - { "propertyName": "column", "type": "many-to-one", "targetTable": "column_definition", "isSystem": true, "isNullable": false, "inversePropertyName": "rules", "onDelete": "CASCADE", "description": "The column this rule applies to" } + { "propertyName": "column", "type": "many-to-one", "targetTable": "enfyra_column", "isSystem": true, "isNullable": false, "inversePropertyName": "rules", "onDelete": "CASCADE", "description": "The column this rule applies to" } ] }, - "relation_definition": { - "name": "relation_definition", + "enfyra_relation": { + "name": "enfyra_relation", "description": "Defines relationships between tables (one-to-one, one-to-many, many-to-one, many-to-many)", "isSystem": true, "uniques": [ @@ -78,13 +78,13 @@ { "name": "isUpdatable", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": true, "description": "Whether this relation can be updated. When false, the foreign key column will be protected from updates." } ], "relations": [ - { "propertyName": "sourceTable", "type": "many-to-one", "targetTable": "table_definition", "isSystem": true, "isUpdatable": false, "inversePropertyName": "relations", "onDelete": "CASCADE", "description": "The table where this relation is defined" }, - { "propertyName": "targetTable", "type": "many-to-one", "targetTable": "table_definition", "isSystem": true, "isUpdatable": false, "isNullable": true, "description": "The table that this relation points to" }, - { "propertyName": "mappedBy", "type": "many-to-one", "targetTable": "relation_definition", "isSystem": true, "isNullable": true, "onDelete": "CASCADE", "description": "Owning-side relation that this inverse maps to (self-referencing FK)" } + { "propertyName": "sourceTable", "type": "many-to-one", "targetTable": "enfyra_table", "isSystem": true, "isUpdatable": false, "inversePropertyName": "relations", "onDelete": "CASCADE", "description": "The table where this relation is defined" }, + { "propertyName": "targetTable", "type": "many-to-one", "targetTable": "enfyra_table", "isSystem": true, "isUpdatable": false, "isNullable": true, "description": "The table that this relation points to" }, + { "propertyName": "mappedBy", "type": "many-to-one", "targetTable": "enfyra_relation", "isSystem": true, "isNullable": true, "onDelete": "CASCADE", "description": "Owning-side relation that this inverse maps to (self-referencing FK)" } ] }, - "table_definition": { - "name": "table_definition", + "enfyra_table": { + "name": "enfyra_table", "description": "Stores metadata for all tables/collections in the database", "isSystem": true, "uniques": [ @@ -99,7 +99,7 @@ { "name": "id", "type": "int", "isPrimary": true, "isGenerated": true, "isNullable": false, "isSystem": true, "description": "Primary key identifier" }, { "name": "name", "type": "varchar", "isNullable": false, "isSystem": true, "description": "Table name in the database" }, { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system table" }, - { "name": "isSingleRecord", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this table should only have a single record (e.g., setting_definition)" }, + { "name": "isSingleRecord", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this table should only have a single record (e.g., enfyra_setting)" }, { "name": "uniques", "type": "simple-json", "isNullable": true, "isSystem": true, "description": "Array of unique constraints (e.g., [['email'], ['name', 'parent']])" }, { "name": "indexes", "type": "simple-json", "isNullable": true, "isSystem": true, "description": "Array of database indexes for performance optimization" }, { "name": "alias", "type": "varchar", "isNullable": true, "isSystem": true, "description": "Human-readable alias for the table" }, @@ -108,8 +108,8 @@ { "name": "validateBody", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": true, "description": "Auto-validate POST/PATCH bodies against column + relation metadata and column rules. Default true (opt-out per table); set false to skip middleware validation for a table" } ] }, - "user_definition": { - "name": "user_definition", + "enfyra_user": { + "name": "enfyra_user", "description": "Stores user accounts and authentication information", "isSystem": true, "uniques": [ @@ -125,11 +125,11 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-generated user account" } ], "relations": [ - { "propertyName": "role", "type": "many-to-one", "isSystem": true, "targetTable": "role_definition", "description": "User's assigned role for permissions" } + { "propertyName": "role", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_role", "description": "User's assigned role for permissions" } ] }, - "oauth_config_definition": { - "name": "oauth_config_definition", + "enfyra_oauth_config": { + "name": "enfyra_oauth_config", "description": "Configures OAuth providers for authentication (Google, Facebook, GitHub, etc.)", "isSystem": true, "uniques": [ @@ -153,8 +153,8 @@ ], "relations": [] }, - "oauth_account_definition": { - "name": "oauth_account_definition", + "enfyra_oauth_account": { + "name": "enfyra_oauth_account", "description": "Links user accounts to OAuth providers for authentication", "isSystem": true, "uniques": [ @@ -174,11 +174,11 @@ { "name": "providerUserId", "type": "varchar", "isNullable": false, "isSystem": true, "description": "Unique user ID from the OAuth provider" } ], "relations": [ - { "propertyName": "user", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "The user account linked to this OAuth provider" } + { "propertyName": "user", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "The user account linked to this OAuth provider" } ] }, - "setting_definition": { - "name": "setting_definition", + "enfyra_setting": { + "name": "enfyra_setting", "description": "Stores global system configuration and settings", "isSystem": true, "isSingleRecord": true, @@ -196,9 +196,9 @@ ], "relations": [] }, - "cors_origin_definition": { - "name": "cors_origin_definition", - "description": "Allowed CORS origins served by the public /cors_origin_definition endpoint", + "enfyra_cors_origin": { + "name": "enfyra_cors_origin", + "description": "Allowed CORS origins served by the public /enfyra_cors_origin endpoint", "isSystem": true, "uniques": [ [ @@ -213,8 +213,8 @@ ], "relations": [] }, - "route_definition": { - "name": "route_definition", + "enfyra_route": { + "name": "enfyra_route", "description": "Defines API routes/endpoints for the application", "isSystem": true, "uniques": [ @@ -228,18 +228,18 @@ { "name": "isEnabled", "type": "boolean", "isNullable": true, "isSystem": true, "defaultValue": true, "description": "Whether the route is active and accessible" }, { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-defined route" }, { "name": "icon", "type": "varchar", "isNullable": true, "isSystem": true, "defaultValue": "lucide:route", "description": "Icon identifier for UI display" }, - { "name": "maxUploadFileSize", "type": "int", "isNullable": true, "isSystem": true, "defaultValue": null, "description": "Optional route-specific maximum upload file size in MB. Overrides setting_definition.maxUploadFileSize for multipart requests when set." }, + { "name": "maxUploadFileSize", "type": "int", "isNullable": true, "isSystem": true, "defaultValue": null, "description": "Optional route-specific maximum upload file size in MB. Overrides enfyra_setting.maxUploadFileSize for multipart requests when set." }, { "name": "description", "type": "text", "isSystem": true, "description": "Description of what the route does" } ], "relations": [ - { "propertyName": "mainTable", "type": "many-to-one", "targetTable": "table_definition", "isSystem": true, "onDelete": "CASCADE", "description": "Primary table that this route operates on" }, - { "propertyName": "availableMethods", "type": "many-to-many", "targetTable": "method_definition", "isSystem": true, "inversePropertyName": "routesWithAvailable", "description": "HTTP methods this route supports" }, - { "propertyName": "preHooks", "type": "one-to-many", "targetTable": "pre_hook_definition", "isSystem": true, "inversePropertyName": "route", "description": "Pre-hooks that run before route execution" }, - { "propertyName": "postHooks", "type": "one-to-many", "targetTable": "post_hook_definition", "isSystem": true, "inversePropertyName": "route", "description": "Post-hooks that run after route execution" } + { "propertyName": "mainTable", "type": "many-to-one", "targetTable": "enfyra_table", "isSystem": true, "onDelete": "CASCADE", "description": "Primary table that this route operates on" }, + { "propertyName": "availableMethods", "type": "many-to-many", "targetTable": "enfyra_method", "isSystem": true, "inversePropertyName": "routesWithAvailable", "description": "HTTP methods this route supports" }, + { "propertyName": "preHooks", "type": "one-to-many", "targetTable": "enfyra_pre_hook", "isSystem": true, "inversePropertyName": "route", "description": "Pre-hooks that run before route execution" }, + { "propertyName": "postHooks", "type": "one-to-many", "targetTable": "enfyra_post_hook", "isSystem": true, "inversePropertyName": "route", "description": "Post-hooks that run after route execution" } ] }, - "role_definition": { - "name": "role_definition", + "enfyra_role": { + "name": "enfyra_role", "description": "Defines user roles for permission management", "isSystem": true, "uniques": [ @@ -253,8 +253,8 @@ { "name": "description", "type": "text", "isSystem": true, "description": "Description of the role's purpose and permissions" } ] }, - "route_permission_definition": { - "name": "route_permission_definition", + "enfyra_route_permission": { + "name": "enfyra_route_permission", "description": "Maps permissions between roles, users, and routes", "isSystem": true, "columns": [ @@ -263,13 +263,13 @@ { "name": "description", "type": "text", "isSystem": true, "description": "Description of what this permission allows" } ], "relations": [ - { "propertyName": "role", "type": "many-to-one", "isSystem": true, "targetTable": "role_definition", "inversePropertyName": "routePermissions", "onDelete": "CASCADE", "description": "Role that this permission applies to" }, - { "propertyName": "route", "type": "many-to-one", "isSystem": true, "targetTable": "route_definition", "inversePropertyName": "routePermissions", "onDelete": "CASCADE", "description": "Route that this permission controls access to" }, - { "propertyName": "allowedUsers", "type": "many-to-many", "isSystem": true, "targetTable": "user_definition", "inversePropertyName": "allowedRoutePermissions", "description": "Specific users who have this permission" } + { "propertyName": "role", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_role", "inversePropertyName": "routePermissions", "onDelete": "CASCADE", "description": "Role that this permission applies to" }, + { "propertyName": "route", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_route", "inversePropertyName": "routePermissions", "onDelete": "CASCADE", "description": "Route that this permission controls access to" }, + { "propertyName": "allowedUsers", "type": "many-to-many", "isSystem": true, "targetTable": "enfyra_user", "inversePropertyName": "allowedRoutePermissions", "description": "Specific users who have this permission" } ] }, - "field_permission_definition": { - "name": "field_permission_definition", + "enfyra_field_permission": { + "name": "enfyra_field_permission", "description": "Defines per-field permissions (columns and relations) by role and user, with optional conditions", "isSystem": true, "uniques": [ @@ -294,14 +294,14 @@ { "name": "condition", "type": "simple-json", "isNullable": true, "isSystem": true, "defaultValue": null, "description": "Optional filter DSL condition that must match for this rule to apply" } ], "relations": [ - { "propertyName": "role", "type": "many-to-one", "isSystem": true, "targetTable": "role_definition", "onDelete": "CASCADE", "description": "Role that this rule applies to" }, - { "propertyName": "column", "type": "many-to-one", "isSystem": true, "targetTable": "column_definition", "isNullable": true, "onDelete": "CASCADE", "inversePropertyName": "fieldPermissions", "description": "Column this rule applies to" }, - { "propertyName": "relation", "type": "many-to-one", "isSystem": true, "targetTable": "relation_definition", "isNullable": true, "onDelete": "CASCADE", "inversePropertyName": "fieldPermissions", "description": "Relation this rule applies to" }, - { "propertyName": "allowedUsers", "type": "many-to-many", "isSystem": true, "targetTable": "user_definition", "description": "Specific users this rule applies to" } + { "propertyName": "role", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_role", "onDelete": "CASCADE", "description": "Role that this rule applies to" }, + { "propertyName": "column", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_column", "isNullable": true, "onDelete": "CASCADE", "inversePropertyName": "fieldPermissions", "description": "Column this rule applies to" }, + { "propertyName": "relation", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_relation", "isNullable": true, "onDelete": "CASCADE", "inversePropertyName": "fieldPermissions", "description": "Relation this rule applies to" }, + { "propertyName": "allowedUsers", "type": "many-to-many", "isSystem": true, "targetTable": "enfyra_user", "description": "Specific users this rule applies to" } ] }, - "route_handler_definition": { - "name": "route_handler_definition", + "enfyra_route_handler": { + "name": "enfyra_route_handler", "description": "Stores custom logic handlers for route endpoints", "uniques": [ [ @@ -319,12 +319,12 @@ { "name": "description", "type": "text", "isSystem": true, "description": "Description of what this handler does" } ], "relations": [ - { "propertyName": "route", "type": "many-to-one", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "handlers", "isNullable": false, "onDelete": "CASCADE", "description": "The route this handler is attached to" }, - { "propertyName": "method", "type": "many-to-one", "targetTable": "method_definition", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "HTTP method (GET, POST, etc.) this handler responds to", "inversePropertyName": "handlers" } + { "propertyName": "route", "type": "many-to-one", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "handlers", "isNullable": false, "onDelete": "CASCADE", "description": "The route this handler is attached to" }, + { "propertyName": "method", "type": "many-to-one", "targetTable": "enfyra_method", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "HTTP method (GET, POST, etc.) this handler responds to", "inversePropertyName": "handlers" } ] }, - "pre_hook_definition": { - "name": "pre_hook_definition", + "enfyra_pre_hook": { + "name": "enfyra_pre_hook", "description": "Defines lifecycle hooks that run before route execution", "isSystem": true, "columns": [ @@ -340,12 +340,12 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-defined hook" } ], "relations": [ - { "propertyName": "route", "type": "many-to-one", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "preHooks", "onDelete": "CASCADE", "description": "Route this hook is attached to (null when isGlobal is true)" }, - { "propertyName": "methods", "type": "many-to-many", "targetTable": "method_definition", "isSystem": true, "inversePropertyName": "preHooks", "description": "HTTP methods this hook applies to" } + { "propertyName": "route", "type": "many-to-one", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "preHooks", "onDelete": "CASCADE", "description": "Route this hook is attached to (null when isGlobal is true)" }, + { "propertyName": "methods", "type": "many-to-many", "targetTable": "enfyra_method", "isSystem": true, "inversePropertyName": "preHooks", "description": "HTTP methods this hook applies to" } ] }, - "post_hook_definition": { - "name": "post_hook_definition", + "enfyra_post_hook": { + "name": "enfyra_post_hook", "description": "Defines lifecycle hooks that run after route execution", "isSystem": true, "columns": [ @@ -361,12 +361,12 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-defined hook" } ], "relations": [ - { "propertyName": "route", "type": "many-to-one", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "postHooks", "onDelete": "CASCADE", "description": "Route this hook is attached to (null when isGlobal is true)" }, - { "propertyName": "methods", "type": "many-to-many", "targetTable": "method_definition", "isSystem": true, "inversePropertyName": "postHooks", "description": "HTTP methods this hook applies to" } + { "propertyName": "route", "type": "many-to-one", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "postHooks", "onDelete": "CASCADE", "description": "Route this hook is attached to (null when isGlobal is true)" }, + { "propertyName": "methods", "type": "many-to-many", "targetTable": "enfyra_method", "isSystem": true, "inversePropertyName": "postHooks", "description": "HTTP methods this hook applies to" } ] }, - "session_definition": { - "name": "session_definition", + "enfyra_session": { + "name": "enfyra_session", "description": "Stores active user sessions and authentication tokens", "isSystem": true, "columns": [ @@ -377,11 +377,11 @@ { "name": "refreshTokenHash", "type": "varchar", "isNullable": true, "isSystem": true, "description": "SHA-256 hash of the current valid refresh token" } ], "relations": [ - { "propertyName": "user", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "User who owns this session" } + { "propertyName": "user", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "User who owns this session" } ] }, - "api_token_definition": { - "name": "api_token_definition", + "enfyra_api_token": { + "name": "enfyra_api_token", "description": "Stores user-owned API tokens for programmatic access", "isSystem": true, "columns": [ @@ -400,11 +400,11 @@ ] ], "relations": [ - { "propertyName": "user", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "User who owns this API token" } + { "propertyName": "user", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "User who owns this API token" } ] }, - "schema_migration_definition": { - "name": "schema_migration_definition", + "enfyra_schema_migration": { + "name": "enfyra_schema_migration", "description": "Tracks schema migration operations with up/down scripts for automatic recovery", "isSystem": true, "columns": [ @@ -426,8 +426,8 @@ ] ] }, - "method_definition": { - "name": "method_definition", + "enfyra_method": { + "name": "enfyra_method", "description": "Defines available HTTP methods (GET, POST, etc.) and GraphQL operations", "isSystem": true, "uniques": [["name"]], @@ -439,14 +439,14 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-defined method" } ], "relations": [ - { "propertyName": "routePermissions", "type": "many-to-many", "targetTable": "route_permission_definition", "isSystem": true, "inversePropertyName": "methods", "description": "Permissions associated with this method", "isNullable": true }, - { "propertyName": "routes", "type": "many-to-many", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "publicMethods", "description": "Routes that allow this method without authentication" }, - { "propertyName": "routesWithAvailable", "type": "many-to-many", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "availableMethods", "description": "Routes that have this method available" }, - { "propertyName": "routesSkipRoleGuard", "type": "many-to-many", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "skipRoleGuardMethods", "description": "Routes where this method skips RoleGuard (still requires authentication)" } + { "propertyName": "routePermissions", "type": "many-to-many", "targetTable": "enfyra_route_permission", "isSystem": true, "inversePropertyName": "methods", "description": "Permissions associated with this method", "isNullable": true }, + { "propertyName": "routes", "type": "many-to-many", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "publicMethods", "description": "Routes that allow this method without authentication" }, + { "propertyName": "routesWithAvailable", "type": "many-to-many", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "availableMethods", "description": "Routes that have this method available" }, + { "propertyName": "routesSkipRoleGuard", "type": "many-to-many", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "skipRoleGuardMethods", "description": "Routes where this method skips RoleGuard (still requires authentication)" } ] }, - "menu_definition": { - "name": "menu_definition", + "enfyra_menu": { + "name": "enfyra_menu", "description": "Defines the navigation menu structure for the application", "uniques": [ [ @@ -476,11 +476,11 @@ { "name": "permission", "type": "simple-json", "isNullable": true, "isSystem": true, "description": "Permission rules controlling who can see this menu item" } ], "relations": [ - { "propertyName": "parent", "type": "many-to-one", "targetTable": "menu_definition", "isSystem": true, "inversePropertyName": "children", "isNullable": true, "description": "Parent menu item (null for top-level items)" } + { "propertyName": "parent", "type": "many-to-one", "targetTable": "enfyra_menu", "isSystem": true, "inversePropertyName": "children", "isNullable": true, "description": "Parent menu item (null for top-level items)" } ] }, - "extension_definition": { - "name": "extension_definition", + "enfyra_extension": { + "name": "enfyra_extension", "description": "Stores custom UI extensions (pages, widgets, and global shell extensions) written in Vue", "isSystem": true, "uniques": [ @@ -501,13 +501,13 @@ { "name": "code", "type": "code", "isSystem": true, "isNullable": false, "description": "Source code for the extension (React/Vue component)" } ], "relations": [ - { "propertyName": "menu", "type": "one-to-one", "targetTable": "menu_definition", "isSystem": true, "inversePropertyName": "extension", "isNullable": true, "description": "Menu item that links to this extension" }, - { "propertyName": "createdBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who created this extension" }, - { "propertyName": "updatedBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who last updated this extension" } + { "propertyName": "menu", "type": "one-to-one", "targetTable": "enfyra_menu", "isSystem": true, "inversePropertyName": "extension", "isNullable": true, "description": "Menu item that links to this extension" }, + { "propertyName": "createdBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who created this extension" }, + { "propertyName": "updatedBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who last updated this extension" } ] }, - "folder_definition": { - "name": "folder_definition", + "enfyra_folder": { + "name": "enfyra_folder", "description": "Organizes files into a hierarchical folder structure", "isSystem": true, "uniques": [ @@ -534,12 +534,12 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "defaultValue": false, "description": "Whether this is a system-defined folder" } ], "relations": [ - { "propertyName": "parent", "type": "many-to-one", "targetTable": "folder_definition", "isNullable": true, "inversePropertyName": "children", "description": "Parent folder (null for root folders)" }, - { "propertyName": "user", "type": "many-to-one", "targetTable": "user_definition", "isNullable": true, "description": "User who owns this folder" } + { "propertyName": "parent", "type": "many-to-one", "targetTable": "enfyra_folder", "isNullable": true, "inversePropertyName": "children", "description": "Parent folder (null for root folders)" }, + { "propertyName": "user", "type": "many-to-one", "targetTable": "enfyra_user", "isNullable": true, "description": "User who owns this folder" } ] }, - "file_definition": { - "name": "file_definition", + "enfyra_file": { + "name": "enfyra_file", "description": "Stores uploaded files and their metadata", "isSystem": true, "uniques": [ @@ -569,13 +569,13 @@ { "name": "status", "type": "enum", "isNullable": false, "options": ["active", "archived", "quarantine"], "defaultValue": "active", "description": "File status (active, archived, or quarantined)" } ], "relations": [ - { "propertyName": "folder", "type": "many-to-one", "targetTable": "folder_definition", "isNullable": true, "inversePropertyName": "files", "description": "Folder containing this file" }, - { "propertyName": "uploadedBy", "type": "many-to-one", "targetTable": "user_definition", "isNullable": true, "isGenerated": true, "description": "User who uploaded the file" }, - { "propertyName": "storageConfig", "type": "many-to-one", "targetTable": "storage_config_definition", "isNullable": true, "description": "Storage configuration (account) used for this file" } + { "propertyName": "folder", "type": "many-to-one", "targetTable": "enfyra_folder", "isNullable": true, "inversePropertyName": "files", "description": "Folder containing this file" }, + { "propertyName": "uploadedBy", "type": "many-to-one", "targetTable": "enfyra_user", "isNullable": true, "isGenerated": true, "description": "User who uploaded the file" }, + { "propertyName": "storageConfig", "type": "many-to-one", "targetTable": "enfyra_storage_config", "isNullable": true, "description": "Storage configuration (account) used for this file" } ] }, - "file_permission_definition": { - "name": "file_permission_definition", + "enfyra_file_permission": { + "name": "enfyra_file_permission", "description": "Controls access permissions for files", "isSystem": true, "uniques": [ @@ -592,13 +592,13 @@ { "name": "description", "type": "text", "isNullable": true, "description": "Description of what this permission allows" } ], "relations": [ - { "propertyName": "file", "type": "many-to-one", "targetTable": "file_definition", "isNullable": true, "inversePropertyName": "permissions", "onDelete": "CASCADE", "description": "File this permission applies to" }, - { "propertyName": "role", "type": "many-to-one", "targetTable": "role_definition", "isNullable": true, "onDelete": "CASCADE", "description": "Role that has this permission" }, - { "propertyName": "allowedUsers", "type": "many-to-many", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "junctionTableName": "j_efaab69d5b9c", "junctionSourceColumn": "sourceId", "junctionTargetColumn": "targetId", "description": "Specific users who have this permission" } + { "propertyName": "file", "type": "many-to-one", "targetTable": "enfyra_file", "isNullable": true, "inversePropertyName": "permissions", "onDelete": "CASCADE", "description": "File this permission applies to" }, + { "propertyName": "role", "type": "many-to-one", "targetTable": "enfyra_role", "isNullable": true, "onDelete": "CASCADE", "description": "Role that has this permission" }, + { "propertyName": "allowedUsers", "type": "many-to-many", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "junctionTableName": "j_efaab69d5b9c", "junctionSourceColumn": "sourceId", "junctionTargetColumn": "targetId", "description": "Specific users who have this permission" } ] }, - "package_definition": { - "name": "package_definition", + "enfyra_package": { + "name": "enfyra_package", "description": "Manages npm packages installed in the application (app and server)", "isSystem": true, "columns": [ @@ -615,11 +615,11 @@ { "name": "lastError", "type": "text", "isNullable": true, "isSystem": true, "description": "Last error message from install/update/uninstall operation" } ], "relations": [ - { "propertyName": "installedBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who installed this package" } + { "propertyName": "installedBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who installed this package" } ] }, - "bootstrap_script_definition": { - "name": "bootstrap_script_definition", + "enfyra_bootstrap_script": { + "name": "enfyra_bootstrap_script", "description": "Stores custom scripts that run during application initialization", "isSystem": true, "uniques": [ @@ -640,12 +640,12 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-generated script" } ], "relations": [ - { "propertyName": "createdBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who created the script" }, - { "propertyName": "updatedBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who last updated the script" } + { "propertyName": "createdBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who created the script" }, + { "propertyName": "updatedBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who last updated the script" } ] }, - "storage_config_definition": { - "name": "storage_config_definition", + "enfyra_storage_config": { + "name": "enfyra_storage_config", "description": "Configures storage backends for file uploads (local, S3, GCS, etc.)", "isSystem": true, "uniques": [ @@ -668,12 +668,12 @@ { "name": "isSystem", "type": "boolean", "isNullable": false, "isSystem": true, "defaultValue": false, "description": "Whether this is a system-defined storage configuration" } ], "relations": [ - { "propertyName": "createdBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who created this configuration" }, - { "propertyName": "updatedBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who last updated this configuration" } + { "propertyName": "createdBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who created this configuration" }, + { "propertyName": "updatedBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who last updated this configuration" } ] }, - "websocket_definition": { - "name": "websocket_definition", + "enfyra_websocket": { + "name": "enfyra_websocket", "description": "Defines WebSocket gateway namespaces for real-time communication", "isSystem": true, "uniques": [ @@ -695,8 +695,8 @@ ], "relations": [] }, - "websocket_event_definition": { - "name": "websocket_event_definition", + "enfyra_websocket_event": { + "name": "enfyra_websocket_event", "description": "Defines event handlers for WebSocket gateways", "isSystem": true, "uniques": [ @@ -717,11 +717,11 @@ { "name": "timeout", "type": "int", "isSystem": true, "isNullable": true, "description": "Timeout in milliseconds for event handler execution. If not set, uses default system timeout." } ], "relations": [ - { "propertyName": "gateway", "type": "many-to-one", "targetTable": "websocket_definition", "isSystem": true, "inversePropertyName": "events", "isNullable": false, "onDelete": "CASCADE", "description": "Gateway this event belongs to" } + { "propertyName": "gateway", "type": "many-to-one", "targetTable": "enfyra_websocket", "isSystem": true, "inversePropertyName": "events", "isNullable": false, "onDelete": "CASCADE", "description": "Gateway this event belongs to" } ] }, - "flow_definition": { - "name": "flow_definition", + "enfyra_flow": { + "name": "enfyra_flow", "description": "Defines automated workflows with triggers, steps, and execution logic", "isSystem": true, "uniques": [ @@ -743,8 +743,8 @@ ], "relations": [] }, - "flow_step_definition": { - "name": "flow_step_definition", + "enfyra_flow_step": { + "name": "enfyra_flow_step", "description": "Defines individual steps within a flow", "isSystem": true, "columns": [ @@ -763,12 +763,12 @@ { "name": "branch", "type": "enum", "options": ["true", "false"], "isNullable": true, "isSystem": true, "description": "Which branch of the parent condition (true or false)" } ], "relations": [ - { "propertyName": "flow", "type": "many-to-one", "targetTable": "flow_definition", "isSystem": true, "inversePropertyName": "steps", "isNullable": false, "onDelete": "CASCADE", "description": "Flow this step belongs to" }, - { "propertyName": "parent", "type": "many-to-one", "targetTable": "flow_step_definition", "isSystem": true, "inversePropertyName": "children", "isNullable": true, "description": "Parent condition step (null = root level)" } + { "propertyName": "flow", "type": "many-to-one", "targetTable": "enfyra_flow", "isSystem": true, "inversePropertyName": "steps", "isNullable": false, "onDelete": "CASCADE", "description": "Flow this step belongs to" }, + { "propertyName": "parent", "type": "many-to-one", "targetTable": "enfyra_flow_step", "isSystem": true, "inversePropertyName": "children", "isNullable": true, "description": "Parent condition step (null = root level)" } ] }, - "flow_execution_definition": { - "name": "flow_execution_definition", + "enfyra_flow_execution": { + "name": "enfyra_flow_execution", "description": "Stores execution history and state for flow runs", "isSystem": true, "columns": [ @@ -783,12 +783,12 @@ { "name": "duration", "type": "int", "isNullable": true, "isSystem": true, "description": "Execution duration in milliseconds" } ], "relations": [ - { "propertyName": "flow", "type": "many-to-one", "targetTable": "flow_definition", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "Flow that was executed" }, - { "propertyName": "triggeredBy", "type": "many-to-one", "targetTable": "user_definition", "isSystem": true, "isNullable": true, "description": "User who triggered the execution (null for cron/event triggers)" } + { "propertyName": "flow", "type": "many-to-one", "targetTable": "enfyra_flow", "isSystem": true, "isNullable": false, "onDelete": "CASCADE", "description": "Flow that was executed" }, + { "propertyName": "triggeredBy", "type": "many-to-one", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "description": "User who triggered the execution (null for cron/event triggers)" } ] }, - "guard_definition": { - "name": "guard_definition", + "enfyra_guard": { + "name": "enfyra_guard", "description": "Defines metadata-driven guards with nestable AND/OR logic tree for rate limiting, IP filtering, etc.", "isSystem": true, "columns": [ @@ -803,13 +803,13 @@ { "name": "description", "type": "text", "isSystem": true, "description": "Description of what the guard does" } ], "relations": [ - { "propertyName": "parent", "type": "many-to-one", "targetTable": "guard_definition", "isSystem": true, "inversePropertyName": "children", "isNullable": true, "onDelete": "CASCADE", "description": "Parent guard for nested logic (null for root-level guards)" }, - { "propertyName": "route", "type": "many-to-one", "targetTable": "route_definition", "isSystem": true, "inversePropertyName": "guards", "isNullable": true, "onDelete": "CASCADE", "description": "Route this guard is attached to (null when isGlobal is true). Only meaningful for root guards" }, - { "propertyName": "methods", "type": "many-to-many", "targetTable": "method_definition", "isSystem": true, "description": "HTTP methods this guard applies to. Only meaningful for root guards" } + { "propertyName": "parent", "type": "many-to-one", "targetTable": "enfyra_guard", "isSystem": true, "inversePropertyName": "children", "isNullable": true, "onDelete": "CASCADE", "description": "Parent guard for nested logic (null for root-level guards)" }, + { "propertyName": "route", "type": "many-to-one", "targetTable": "enfyra_route", "isSystem": true, "inversePropertyName": "guards", "isNullable": true, "onDelete": "CASCADE", "description": "Route this guard is attached to (null when isGlobal is true). Only meaningful for root guards" }, + { "propertyName": "methods", "type": "many-to-many", "targetTable": "enfyra_method", "isSystem": true, "description": "HTTP methods this guard applies to. Only meaningful for root guards" } ] }, - "guard_rule_definition": { - "name": "guard_rule_definition", + "enfyra_guard_rule": { + "name": "enfyra_guard_rule", "description": "Defines individual rules/conditions within a guard (rate limit, IP whitelist/blacklist)", "isSystem": true, "columns": [ @@ -822,12 +822,12 @@ { "name": "description", "type": "text", "isSystem": true, "description": "Description of what this rule checks" } ], "relations": [ - { "propertyName": "guard", "type": "many-to-one", "targetTable": "guard_definition", "isSystem": true, "inversePropertyName": "rules", "isNullable": false, "onDelete": "CASCADE", "description": "Guard this rule belongs to" }, - { "propertyName": "users", "type": "many-to-many", "targetTable": "user_definition", "isSystem": true, "description": "Specific users this rule applies to (empty = all users)" } + { "propertyName": "guard", "type": "many-to-one", "targetTable": "enfyra_guard", "isSystem": true, "inversePropertyName": "rules", "isNullable": false, "onDelete": "CASCADE", "description": "Guard this rule belongs to" }, + { "propertyName": "users", "type": "many-to-many", "targetTable": "enfyra_user", "isSystem": true, "description": "Specific users this rule applies to (empty = all users)" } ] }, - "gql_definition": { - "name": "gql_definition", + "enfyra_graphql": { + "name": "enfyra_graphql", "description": "GraphQL configuration per table — enables/disables GraphQL API for a specific table", "isSystem": true, "uniques": [ @@ -843,7 +843,7 @@ { "name": "metadata", "type": "simple-json", "isNullable": true, "isSystem": true, "description": "Extended configuration (custom handler, timeout, etc.)" } ], "relations": [ - { "propertyName": "table", "type": "many-to-one", "isSystem": true, "targetTable": "table_definition", "isNullable": false, "onDelete": "CASCADE", "inversePropertyName": "gqlConfig", "description": "The table this GraphQL config belongs to" } + { "propertyName": "table", "type": "many-to-one", "isSystem": true, "targetTable": "enfyra_table", "isNullable": false, "onDelete": "CASCADE", "inversePropertyName": "gqlConfig", "description": "The table this GraphQL config belongs to" } ] } } diff --git a/package.json b/package.json index 058c6960..f6f3bb68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "enfyra-server", - "version": "2.2.1", + "version": "2.2.2", "description": "Dynamic backend platform that auto-generates REST and GraphQL APIs from database schemas", "author": "dothinh115 ", "private": false, diff --git a/scripts/bench-handler-worker-memory.mjs b/scripts/bench-handler-worker-memory.mjs index 42d6ca85..fe4208a8 100644 --- a/scripts/bench-handler-worker-memory.mjs +++ b/scripts/bench-handler-worker-memory.mjs @@ -159,7 +159,7 @@ function fatSnapshot() { $user: { id: 1, email: 'bench@enfyra.local', role: { name: 'admin' } }, $share: { $logs: [] }, $data: {}, - $api: { request: { method: 'GET', url: '/api/table_definition' } }, + $api: { request: { method: 'GET', url: '/api/enfyra_table' } }, $repos: { main: { preview: rows } }, }; } diff --git a/scripts/init-db-mongo.ts b/scripts/init-db-mongo.ts index 3e346a1e..17cda24c 100644 --- a/scripts/init-db-mongo.ts +++ b/scripts/init-db-mongo.ts @@ -150,9 +150,9 @@ async function createCollection( return; } const METADATA_TABLES = [ - 'table_definition', - 'column_definition', - 'relation_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', ]; if (METADATA_TABLES.includes(collectionName)) { await db.createCollection(collectionName); @@ -180,7 +180,7 @@ export async function initializeDatabaseMongo(): Promise { console.log('✅ Connected to MongoDB'); const dbName = DB_URI.match(/\/([^/?]+)(\?|$)/)?.[1] || 'enfyra'; const db = client.db(dbName); - const settingCollection = db.collection('setting_definition'); + const settingCollection = db.collection('enfyra_setting'); const existingSettings = await settingCollection.findOne({ isInit: true }); if (existingSettings) { console.log('⚠️ Database already initialized, skipping init.'); diff --git a/scripts/init-db-sql.ts b/scripts/init-db-sql.ts index c761cf1b..fc0caf25 100644 --- a/scripts/init-db-sql.ts +++ b/scripts/init-db-sql.ts @@ -51,10 +51,10 @@ export async function initializeDatabaseSql(): Promise { try { const hasSettingTable = - await knexInstance.schema.hasTable('setting_definition'); + await knexInstance.schema.hasTable('enfyra_setting'); if (hasSettingTable) { - const result = await knexInstance('setting_definition') + const result = await knexInstance('enfyra_setting') .select('isInit') .first(); diff --git a/scripts/migrate-default-handlers.ts b/scripts/migrate-default-handlers.ts index f21edea1..e3d8c26d 100644 --- a/scripts/migrate-default-handlers.ts +++ b/scripts/migrate-default-handlers.ts @@ -23,8 +23,8 @@ const BUILTIN_PATHS = [ '/me', '/me/oauth-accounts', '/assets/:id', - '/folder_definition/tree', - '/extension_definition/preview', + '/enfyra_folder/tree', + '/enfyra_extension/preview', '/admin/test/run', '/admin/flow/run', ]; @@ -40,14 +40,14 @@ async function migrate( try { console.log(`\n=== ${label} ===`); - const routes = await db('route_definition') + const routes = await db('enfyra_route') .select('id', 'path') .whereNotNull('mainTableId') .whereNotIn('path', BUILTIN_PATHS); console.log(`Found ${routes.length} dynamic routes (excluding built-in)`); - const methods = await db('method_definition').select('id', 'name'); + const methods = await db('enfyra_method').select('id', 'name'); const httpMethods = methods.filter((m: any) => DEFAULT_HANDLERS[m.name]); let created = 0; @@ -55,7 +55,7 @@ async function migrate( for (const route of routes) { for (const method of httpMethods) { - const existing = await db('route_handler_definition') + const existing = await db('enfyra_route_handler') .where({ routeId: route.id, methodId: method.id }) .first(); @@ -67,7 +67,7 @@ async function migrate( const logic = DEFAULT_HANDLERS[method.name]; if (!logic) continue; - await db('route_handler_definition').insert({ + await db('enfyra_route_handler').insert({ routeId: route.id, methodId: method.id, logic, @@ -78,12 +78,12 @@ async function migrate( } } - const existingWithoutTimeout = await db('route_handler_definition') + const existingWithoutTimeout = await db('enfyra_route_handler') .whereNull('timeout') .orWhere('timeout', 0); if (existingWithoutTimeout.length > 0) { - await db('route_handler_definition') + await db('enfyra_route_handler') .whereNull('timeout') .orWhere('timeout', 0) .update({ timeout: DEFAULT_TIMEOUT }); diff --git a/scripts/test-schema-migration.ts b/scripts/test-schema-migration.ts index 3f12a642..9704679d 100644 --- a/scripts/test-schema-migration.ts +++ b/scripts/test-schema-migration.ts @@ -74,7 +74,7 @@ function addRelationToTestTable() { snapshot[TEST_TABLE].relations.push({ propertyName: 'user', type: 'many-to-one', - targetTable: 'user_definition', + targetTable: 'enfyra_user', isSystem: false, }); fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(snapshot, null, 4)); @@ -141,7 +141,7 @@ async function testSqlMigrations(): Promise<{ console.log('\n1️⃣ Test: Create new table'); addTestTableToSnapshot(); - await knexInstance('setting_definition').update({ isInit: false }); + await knexInstance('enfyra_setting').update({ isInit: false }); await runInitScript(); const tableExists = await knexInstance.schema.hasTable(TEST_TABLE); @@ -163,7 +163,7 @@ async function testSqlMigrations(): Promise<{ console.log('\n2️⃣ Test: Add new column'); addColumnToTestTable(); - await knexInstance('setting_definition').update({ isInit: false }); + await knexInstance('enfyra_setting').update({ isInit: false }); await runInitScript(); const columnsAfterAdd = await knexInstance(TEST_TABLE).columnInfo(); @@ -177,7 +177,7 @@ async function testSqlMigrations(): Promise<{ console.log('\n3️⃣ Test: Add relation (FK)'); addRelationToTestTable(); - await knexInstance('setting_definition').update({ isInit: false }); + await knexInstance('enfyra_setting').update({ isInit: false }); await runInitScript(); const columnsAfterRelation = await knexInstance(TEST_TABLE).columnInfo(); @@ -208,7 +208,7 @@ async function testSqlMigrations(): Promise<{ console.log('\n4️⃣ Test: Delete table via deletedTables'); removeTestTableFromSnapshot(); addTableToDeletedTables(); - await knexInstance('setting_definition').update({ isInit: false }); + await knexInstance('enfyra_setting').update({ isInit: false }); await runInitScript(); const tableExistsAfterDelete = @@ -259,7 +259,7 @@ async function testMongoMigrations(): Promise<{ console.log('\n1️⃣ Test: Create new collection'); addTestTableToSnapshot(); await db - .collection('setting_definition') + .collection('enfyra_setting') .updateOne({}, { $set: { isInit: false } }); await runInitScript(); @@ -286,7 +286,7 @@ async function testMongoMigrations(): Promise<{ console.log('\n2️⃣ Test: Add new column'); addColumnToTestTable(); await db - .collection('setting_definition') + .collection('enfyra_setting') .updateOne({}, { $set: { isInit: false } }); await runInitScript(); @@ -297,7 +297,7 @@ async function testMongoMigrations(): Promise<{ removeTestTableFromSnapshot(); addTableToDeletedTables(); await db - .collection('setting_definition') + .collection('enfyra_setting') .updateOne({}, { $set: { isInit: false } }); await runInitScript(); diff --git a/src/container.ts b/src/container.ts index 0a13edc4..83033006 100644 --- a/src/container.ts +++ b/src/container.ts @@ -48,7 +48,6 @@ import { FolderDefinitionProcessor, GenericTableProcessor, GraphQLDefinitionProcessor, - HookDefinitionProcessor, MenuDefinitionProcessor, MethodDefinitionProcessor, PostHookDefinitionProcessor, @@ -74,6 +73,7 @@ import { MetadataProvisionService, ProvisionService, SchemaHealingService, + SystemCoreTableResolver, } from './engines/bootstrap'; import { LoggingService } from './domain/exceptions'; @@ -346,6 +346,7 @@ export interface Cradle { provisionService: ProvisionService; firstRunInitializer: FirstRunInitializer; schemaHealingService: SchemaHealingService; + systemCoreTableResolver: SystemCoreTableResolver; metadataProvisionService: MetadataProvisionService; metadataProvisionSqlService: MetadataProvisionSqlService; metadataProvisionMongoService: MetadataProvisionMongoService; @@ -362,7 +363,6 @@ export interface Cradle { preHookDefinitionProcessor: PreHookDefinitionProcessor; postHookDefinitionProcessor: PostHookDefinitionProcessor; fieldPermissionDefinitionProcessor: FieldPermissionDefinitionProcessor; - hookDefinitionProcessor: HookDefinitionProcessor; settingDefinitionProcessor: SettingDefinitionProcessor; extensionDefinitionProcessor: ExtensionDefinitionProcessor; folderDefinitionProcessor: FolderDefinitionProcessor; @@ -659,6 +659,7 @@ export function buildContainer(): AwilixContainer { provisionService: asClass(ProvisionService).singleton(), firstRunInitializer: asClass(FirstRunInitializer).singleton(), schemaHealingService: asClass(SchemaHealingService).singleton(), + systemCoreTableResolver: asClass(SystemCoreTableResolver).singleton(), metadataProvisionService: asClass(MetadataProvisionService).singleton(), metadataProvisionSqlService: asClass( MetadataProvisionSqlService, @@ -685,7 +686,6 @@ export function buildContainer(): AwilixContainer { fieldPermissionDefinitionProcessor: asClass( FieldPermissionDefinitionProcessor, ).singleton(), - hookDefinitionProcessor: asClass(HookDefinitionProcessor).singleton(), settingDefinitionProcessor: asClass(SettingDefinitionProcessor).singleton(), extensionDefinitionProcessor: asClass( ExtensionDefinitionProcessor, diff --git a/src/domain/auth/services/api-token.service.ts b/src/domain/auth/services/api-token.service.ts index c3999f74..4576d4c2 100644 --- a/src/domain/auth/services/api-token.service.ts +++ b/src/domain/auth/services/api-token.service.ts @@ -18,7 +18,7 @@ import { exchangeApiTokenSchema, } from '../schemas/auth.schemas'; -const API_TOKEN_TABLE = 'api_token_definition'; +const API_TOKEN_TABLE = 'enfyra_api_token'; const API_TOKEN_CACHE_PREFIX = 'auth:api-token'; const API_TOKEN_REVOKED_CHANNEL = 'api-token:revoked'; const API_TOKEN_STATE_TTL_MS = 60_000; diff --git a/src/domain/auth/services/auth.service.ts b/src/domain/auth/services/auth.service.ts index d58168ea..ec48dc5a 100644 --- a/src/domain/auth/services/auth.service.ts +++ b/src/domain/auth/services/auth.service.ts @@ -65,7 +65,7 @@ export class AuthService { const { email, password } = body; const user = await this.queryBuilder.findOne({ - table: 'user_definition', + table: 'enfyra_user', where: { email }, }); @@ -103,7 +103,7 @@ export class AuthService { }; const insertedSession = await this.queryBuilder.insert( - 'session_definition', + 'enfyra_session', sessionData, ); @@ -138,7 +138,7 @@ export class AuthService { }, ); - await this.queryBuilder.update('session_definition', sessionId, { + await this.queryBuilder.update('enfyra_session', sessionId, { refreshTokenHash: this.hashToken(refreshToken), }); @@ -167,7 +167,7 @@ export class AuthService { const sessionIdField = this.queryBuilder.getPkField(); const session = await this.queryBuilder.findOne({ - table: 'session_definition', + table: 'enfyra_session', where: { [sessionIdField]: sessionId }, }); @@ -187,7 +187,7 @@ export class AuthService { } await this.queryBuilder.delete( - 'session_definition', + 'enfyra_session', session._id || session.id, ); return 'Logout successfully!'; @@ -207,7 +207,7 @@ export class AuthService { const sessionIdField = this.queryBuilder.getPkField(); const session = await this.queryBuilder.findOne({ - table: 'session_definition', + table: 'enfyra_session', where: { [sessionIdField]: decoded.sessionId }, }); @@ -276,7 +276,7 @@ export class AuthService { }; const result = await this.queryBuilder .getMongoDb() - .collection('session_definition') + .collection('enfyra_session') .findOneAndUpdate(filter, { $set: { expiredAt: newExpiredAt, @@ -291,7 +291,7 @@ export class AuthService { } } else { const knex = this.queryBuilder.getKnex(); - const affected = await knex('session_definition') + const affected = await knex('enfyra_session') .where('id', sessionId) .andWhere(function (this: any) { this.where('refreshTokenHash', incomingHash).orWhereNull( diff --git a/src/domain/auth/services/oauth-exchange-code.service.ts b/src/domain/auth/services/oauth-exchange-code.service.ts index e5774e80..e4159507 100644 --- a/src/domain/auth/services/oauth-exchange-code.service.ts +++ b/src/domain/auth/services/oauth-exchange-code.service.ts @@ -99,7 +99,7 @@ export class OAuthExchangeCodeService { if (pending?.sessionId) { try { await this.queryBuilderService.delete( - 'session_definition', + 'enfyra_session', pending.sessionId, ); deleted++; diff --git a/src/domain/auth/services/oauth.service.ts b/src/domain/auth/services/oauth.service.ts index 65ed1963..ce7b6aca 100644 --- a/src/domain/auth/services/oauth.service.ts +++ b/src/domain/auth/services/oauth.service.ts @@ -249,7 +249,7 @@ export class OAuthService { const isMongoDB = this.queryBuilderService.isMongoDb(); const existingAccount = await this.queryBuilderService.findOne({ - table: 'oauth_account_definition', + table: 'enfyra_oauth_account', where: { provider, providerUserId: userInfo.id, @@ -262,7 +262,7 @@ export class OAuthService { : existingAccount.userId; const user = await this.queryBuilderService.findOne({ - table: 'user_definition', + table: 'enfyra_user', where: { [DatabaseConfigService.getPkField()]: userId }, }); @@ -274,7 +274,7 @@ export class OAuthService { } let user = await this.queryBuilderService.findOne({ - table: 'user_definition', + table: 'enfyra_user', where: { email: userInfo.email }, }); @@ -301,7 +301,7 @@ export class OAuthService { try { user = await this.queryBuilderService.insert( - 'user_definition', + 'enfyra_user', userData, ); console.log( @@ -309,7 +309,7 @@ export class OAuthService { ); } catch { user = await this.queryBuilderService.findOne({ - table: 'user_definition', + table: 'enfyra_user', where: { email: userInfo.email }, }); if (!user) @@ -324,7 +324,7 @@ export class OAuthService { try { await this.queryBuilderService.insert( - 'oauth_account_definition', + 'enfyra_oauth_account', accountData, ); console.log(`Linked ${provider} account to user: ${userInfo.email}`); @@ -352,7 +352,7 @@ export class OAuthService { const ctx = this.dynamicContextFactory.createBase({ helpers: {}, user: null }); ctx.$repos = this.repoRegistryService.createReposProxy( ctx, - 'user_definition', + 'enfyra_user', ); const result = await this.executorEngineService.run(executable, ctx); @@ -392,7 +392,7 @@ export class OAuthService { loginProvider: provider, }; - return this.queryBuilderService.insert('session_definition', sessionData); + return this.queryBuilderService.insert('enfyra_session', sessionData); } private async generateTokens( @@ -425,7 +425,7 @@ export class OAuthService { .update(refreshToken) .digest('hex'); await this.queryBuilderService.update( - 'session_definition', + 'enfyra_session', sessionId?.toString(), { refreshTokenHash }, ); diff --git a/src/domain/auth/services/session-cleanup.service.ts b/src/domain/auth/services/session-cleanup.service.ts index c7d05492..7aa5246e 100644 --- a/src/domain/auth/services/session-cleanup.service.ts +++ b/src/domain/auth/services/session-cleanup.service.ts @@ -61,7 +61,7 @@ export class SessionCleanupService { while (hasMore && iterations < MAX_ITERATIONS) { iterations++; const result = await this.queryBuilderService.find({ - table: 'session_definition', + table: 'enfyra_session', filter: { expiredAt: { _lt: now } }, fields: [idField], limit: BATCH_SIZE, @@ -74,7 +74,7 @@ export class SessionCleanupService { for (const session of expired) { try { await this.queryBuilderService.delete( - 'session_definition', + 'enfyra_session', session[idField], ); totalDeleted++; diff --git a/src/domain/auth/services/user-revocation.service.ts b/src/domain/auth/services/user-revocation.service.ts index 69531c32..1972c693 100644 --- a/src/domain/auth/services/user-revocation.service.ts +++ b/src/domain/auth/services/user-revocation.service.ts @@ -57,11 +57,11 @@ export class UserRevocationService { typeof userId === 'string' && ObjectId.isValid(userId) ? new ObjectId(userId) : userId; - await this.queryBuilderService.delete('session_definition', { + await this.queryBuilderService.delete('enfyra_session', { where: { user: idValue }, }); } else { - await this.queryBuilderService.delete('session_definition', { + await this.queryBuilderService.delete('enfyra_session', { where: { userId: String(userId) }, }); } diff --git a/src/domain/bootstrap/processors/base-table-processor.ts b/src/domain/bootstrap/processors/base-table-processor.ts index e089d13f..0180d76b 100644 --- a/src/domain/bootstrap/processors/base-table-processor.ts +++ b/src/domain/bootstrap/processors/base-table-processor.ts @@ -203,22 +203,24 @@ export abstract class BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } return { created: createdCount, skipped: skippedCount }; } private resolveRecordIdField(record: any): string { - if (record?.id !== undefined) return 'id'; - if (record?._id !== undefined) return '_id'; + if (record?.id !== undefined && record.id !== null) return 'id'; + if (record?._id !== undefined && record._id !== null) return '_id'; return DatabaseConfigService.getPkField(); } private resolveInsertedId(inserted: any, defaultIdField: string): any { if (!inserted || typeof inserted !== 'object') return inserted; - if (inserted[defaultIdField] !== undefined) return inserted[defaultIdField]; - if (inserted.id !== undefined) return inserted.id; - if (inserted._id !== undefined) return inserted._id; + if (inserted[defaultIdField] !== undefined && inserted[defaultIdField] !== null) + return inserted[defaultIdField]; + if (inserted.id !== undefined && inserted.id !== null) return inserted.id; + if (inserted._id !== undefined && inserted._id !== null) return inserted._id; return inserted; } @@ -313,6 +315,7 @@ export abstract class BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } return { created: createdCount, skipped: skippedCount }; @@ -459,6 +462,7 @@ export abstract class BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } return { created: createdCount, skipped: skippedCount }; diff --git a/src/domain/bootstrap/processors/bootstrap-script-definition.processor.ts b/src/domain/bootstrap/processors/bootstrap-script-definition.processor.ts index 65be324b..05bd20b5 100644 --- a/src/domain/bootstrap/processors/bootstrap-script-definition.processor.ts +++ b/src/domain/bootstrap/processors/bootstrap-script-definition.processor.ts @@ -17,7 +17,7 @@ export class BootstrapScriptDefinitionProcessor extends BaseTableProcessor { if (!transformed.createdAt) transformed.createdAt = now; if (!transformed.updatedAt) transformed.updatedAt = now; } - return normalizeScriptRecord('bootstrap_script_definition', transformed); + return normalizeScriptRecord('enfyra_bootstrap_script', transformed); }); } getUniqueIdentifier(record: any): object { diff --git a/src/domain/bootstrap/processors/field-permission-definition.processor.ts b/src/domain/bootstrap/processors/field-permission-definition.processor.ts index b9b85132..63d2b9cb 100644 --- a/src/domain/bootstrap/processors/field-permission-definition.processor.ts +++ b/src/domain/bootstrap/processors/field-permission-definition.processor.ts @@ -25,7 +25,7 @@ export class FieldPermissionDefinitionProcessor extends BaseTableProcessor { if (t._column) { const { table, name } = t._column; const tableDef = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: table }, }); if (!tableDef) { @@ -36,7 +36,7 @@ export class FieldPermissionDefinitionProcessor extends BaseTableProcessor { } const tableFkVal = isMongoDB ? tableDef._id : tableDef.id; const column = await this.queryBuilderService.findOne({ - table: 'column_definition', + table: 'enfyra_column', where: isMongoDB ? { table: tableFkVal, name } : { tableId: tableFkVal, name }, @@ -69,7 +69,7 @@ export class FieldPermissionDefinitionProcessor extends BaseTableProcessor { } } else { const role = await this.queryBuilderService.findOne({ - table: 'role_definition', + table: 'enfyra_role', where: { name: t._role }, }); if (!role) { diff --git a/src/domain/bootstrap/processors/flow-step-definition.processor.ts b/src/domain/bootstrap/processors/flow-step-definition.processor.ts index 19a88bb5..c112bcb4 100644 --- a/src/domain/bootstrap/processors/flow-step-definition.processor.ts +++ b/src/domain/bootstrap/processors/flow-step-definition.processor.ts @@ -28,7 +28,7 @@ export class FlowStepDefinitionProcessor extends BaseTableProcessor { return this.autoTransformFkFields( transformed, - 'flow_step_definition', + 'enfyra_flow_step', this.queryBuilderService, ); }), @@ -36,11 +36,11 @@ export class FlowStepDefinitionProcessor extends BaseTableProcessor { } getUniqueIdentifier(record: any): object { - return this.autoGetUniqueIdentifier(record, 'flow_step_definition'); + return this.autoGetUniqueIdentifier(record, 'enfyra_flow_step'); } protected getCompareFields(): string[] { - return this.autoGetCompareFields('flow_step_definition'); + return this.autoGetCompareFields('enfyra_flow_step'); } protected getRecordIdentifier(record: any): string { diff --git a/src/domain/bootstrap/processors/generic-table.processor.ts b/src/domain/bootstrap/processors/generic-table.processor.ts index c02dc13d..f81bb10d 100644 --- a/src/domain/bootstrap/processors/generic-table.processor.ts +++ b/src/domain/bootstrap/processors/generic-table.processor.ts @@ -29,10 +29,10 @@ export class GenericTableProcessor extends BaseTableProcessor { const identifiers: object[] = []; const criticalUniqueKeys: Record = { - column_definition: ['table', 'name'], - relation_definition: ['table', 'propertyName'], - route_permission_definition: ['route', 'role'], - route_handler_definition: ['route', 'method'], + enfyra_column: ['table', 'name'], + enfyra_relation: ['table', 'propertyName'], + enfyra_route_permission: ['route', 'role'], + enfyra_route_handler: ['route', 'method'], }; const knownKey = criticalUniqueKeys[this.tableName]; @@ -98,16 +98,16 @@ export class GenericTableProcessor extends BaseTableProcessor { protected getCompareFields(): string[] { const fieldMap: Record = { - role_definition: ['name', 'description'], - setting_definition: ['projectName', 'projectDescription', 'projectUrl'], - route_permission_definition: ['isEnabled'], - route_handler_definition: [ + enfyra_role: ['name', 'description'], + enfyra_setting: ['projectName', 'projectDescription', 'projectUrl'], + enfyra_route_permission: ['isEnabled'], + enfyra_route_handler: [ 'description', 'sourceCode', 'scriptLanguage', 'compiledCode', ], - extension_definition: [ + enfyra_extension: [ 'name', 'type', 'version', @@ -115,7 +115,7 @@ export class GenericTableProcessor extends BaseTableProcessor { 'description', 'code', ], - folder_definition: ['name', 'order', 'icon', 'description'], + enfyra_folder: ['name', 'order', 'icon', 'description'], }; return fieldMap[this.tableName] || ['name', 'description']; diff --git a/src/domain/bootstrap/processors/graphql-definition.processor.ts b/src/domain/bootstrap/processors/graphql-definition.processor.ts index ac23277e..3f4745b9 100644 --- a/src/domain/bootstrap/processors/graphql-definition.processor.ts +++ b/src/domain/bootstrap/processors/graphql-definition.processor.ts @@ -33,7 +33,7 @@ export class GraphQLDefinitionProcessor extends BaseTableProcessor { if (record.table) { if (isMongoDB) { const table = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: record.table }, }); if (!table) { @@ -48,7 +48,7 @@ export class GraphQLDefinitionProcessor extends BaseTableProcessor { : table._id; } else { const table = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: record.table }, }); if (!table) { @@ -70,7 +70,7 @@ export class GraphQLDefinitionProcessor extends BaseTableProcessor { } getUniqueIdentifier(record: any): object { - return this.autoGetUniqueIdentifier(record, 'gql_definition'); + return this.autoGetUniqueIdentifier(record, 'enfyra_graphql'); } protected getCompareFields(): string[] { diff --git a/src/domain/bootstrap/processors/hook-definition.processor.ts b/src/domain/bootstrap/processors/hook-definition.processor.ts deleted file mode 100644 index 4d6d577b..00000000 --- a/src/domain/bootstrap/processors/hook-definition.processor.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { BaseTableProcessor } from './base-table-processor'; -import { IQueryBuilder } from '../../shared/interfaces/query-builder.interface'; -import { ObjectId } from 'mongodb'; -import { getJunctionColumnNames } from '@enfyra/kernel'; -import { DatabaseConfigService } from '../../../shared/services'; - -export class HookDefinitionProcessor extends BaseTableProcessor { - private readonly queryBuilderService: IQueryBuilder; - constructor(deps: { queryBuilderService: IQueryBuilder }) { - super(); - this.queryBuilderService = deps.queryBuilderService; - } - async transformRecords(records: any[], _context?: any): Promise { - const isMongoDB = DatabaseConfigService.instanceIsMongoDb(); - const transformedRecords = await Promise.all( - records.map(async (hook) => { - const transformedHook = { ...hook }; - if (transformedHook.priority === undefined) { - transformedHook.priority = 0; - } - if (transformedHook.isEnabled === undefined) { - transformedHook.isEnabled = false; - } - if (transformedHook.isSystem === undefined) { - transformedHook.isSystem = false; - } - if (transformedHook.preHook === undefined) { - transformedHook.preHook = null; - } - if (transformedHook.afterHook === undefined) { - transformedHook.afterHook = null; - } - if (transformedHook.preHookTimeout === undefined) { - transformedHook.preHookTimeout = null; - } - if (transformedHook.afterHookTimeout === undefined) { - transformedHook.afterHookTimeout = null; - } - if (transformedHook.description === undefined) { - transformedHook.description = null; - } - if (isMongoDB) { - const now = new Date(); - if (!transformedHook.createdAt) { - transformedHook.createdAt = now; - } - if (!transformedHook.updatedAt) { - transformedHook.updatedAt = now; - } - } - if (hook.route && typeof hook.route === 'string') { - const rawPath = hook.route; - const pathsToTry = [ - rawPath, - rawPath.startsWith('/') ? rawPath.slice(1) : '/' + rawPath, - ]; - let route = null; - for (const path of pathsToTry) { - route = await this.queryBuilderService.findOne({ - table: 'route_definition', - where: { - path, - }, - }); - if (route) break; - } - if (!route) { - this.logger.warn( - `Route '${hook.route}' not found for hook ${hook.name}, skipping.`, - ); - return null; - } - if (isMongoDB) { - transformedHook.route = - typeof route._id === 'string' - ? new ObjectId(route._id) - : route._id; - } else { - transformedHook.routeId = route.id; - delete transformedHook.route; - } - } else { - if (isMongoDB) { - transformedHook.route = null; - } else { - transformedHook.routeId = null; - delete transformedHook.route; - } - } - if ( - hook.methods && - Array.isArray(hook.methods) && - hook.methods.length > 0 - ) { - if (isMongoDB) { - const result = await this.queryBuilderService.find({ - table: 'method_definition', - filter: { name: { _in: hook.methods } }, - fields: ['_id', 'name'], - }); - const methods = result.data; - transformedHook.methods = methods.map((m: any) => - typeof m._id === 'string' ? new ObjectId(m._id) : m._id, - ); - } else { - transformedHook._methods = hook.methods; - delete transformedHook.methods; - } - } else { - if (isMongoDB) { - transformedHook.methods = []; - } else { - transformedHook._methods = []; - delete transformedHook.methods; - } - } - return transformedHook; - }), - ); - return transformedRecords.filter(Boolean); - } - async afterUpsert( - record: any, - _isNew: boolean, - _context?: any, - ): Promise { - const isMongoDB = DatabaseConfigService.instanceIsMongoDb(); - if (!isMongoDB && record._methods && Array.isArray(record._methods)) { - const methodNames = record._methods; - const result = await this.queryBuilderService.find({ - table: 'method_definition', - filter: { name: { _in: methodNames } }, - fields: ['id', 'name'], - }); - const methods = result.data; - const methodIds = methods.map((m: any) => m.id); - if (methodIds.length > 0) { - const junctionTable = 'hook_definition_methods_method_definition'; - const { sourceColumn, targetColumn } = getJunctionColumnNames( - 'hook_definition', - 'methods', - 'method_definition', - ); - await this.queryBuilderService.delete(junctionTable, { - where: [{ field: sourceColumn, operator: '=', value: record.id }], - }); - const junctionData = methodIds.map((methodId) => ({ - [targetColumn]: methodId, - [sourceColumn]: record.id, - })); - await this.queryBuilderService.insertWithOptions({ - table: junctionTable, - data: junctionData, - }); - this.logger.log( - ` 🔗 Linked ${methodIds.length} methods to hook ${record.name}`, - ); - } - } - } - getUniqueIdentifier(record: any): object { - return { name: record.name }; - } - protected getCompareFields(): string[] { - return [ - 'name', - 'description', - 'preHook', - 'afterHook', - 'priority', - 'isEnabled', - ]; - } - protected getRecordIdentifier(record: any): string { - const route = record.route; - const methods = record.methods; - let routeStr = ''; - if (route) { - routeStr = typeof route === 'string' ? route : route.path; - } - let methodsStr = ''; - if (methods && Array.isArray(methods)) { - methodsStr = methods - .map((m) => (typeof m === 'string' ? m : m.name)) - .join(', '); - } - return `[Hook] ${record.name}${routeStr ? ` on ${routeStr}` : ''}${methodsStr ? ` (${methodsStr})` : ''}`; - } -} diff --git a/src/domain/bootstrap/processors/index.ts b/src/domain/bootstrap/processors/index.ts index 288decfe..41d01fd3 100644 --- a/src/domain/bootstrap/processors/index.ts +++ b/src/domain/bootstrap/processors/index.ts @@ -8,7 +8,6 @@ export * from './flow-step-definition.processor'; export * from './folder-definition.processor'; export * from './generic-table.processor'; export * from './graphql-definition.processor'; -export * from './hook-definition.processor'; export * from './menu-definition.processor'; export * from './method-definition.processor'; export * from './post-hook-definition.processor'; diff --git a/src/domain/bootstrap/processors/menu-definition.processor.ts b/src/domain/bootstrap/processors/menu-definition.processor.ts index c7d6da56..7f01eed2 100644 --- a/src/domain/bootstrap/processors/menu-definition.processor.ts +++ b/src/domain/bootstrap/processors/menu-definition.processor.ts @@ -47,6 +47,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } @@ -69,6 +70,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } @@ -91,6 +93,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } @@ -189,6 +192,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } @@ -211,6 +215,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } @@ -372,6 +377,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { this.logger.error( ` Record: ${JSON.stringify(record).substring(0, 200)}`, ); + throw error; } } } @@ -397,7 +403,7 @@ export class MenuDefinitionProcessor extends BaseTableProcessor { if (transformed.parent && typeof transformed.parent === 'string') { const parentLabel = transformed.parent; const parent = await this.queryBuilderService.findOne({ - table: 'menu_definition', + table: 'enfyra_menu', where: { type: 'Dropdown Menu', label: parentLabel }, }); if (parent) { diff --git a/src/domain/bootstrap/processors/post-hook-definition.processor.ts b/src/domain/bootstrap/processors/post-hook-definition.processor.ts index d8381a01..803cb6f8 100644 --- a/src/domain/bootstrap/processors/post-hook-definition.processor.ts +++ b/src/domain/bootstrap/processors/post-hook-definition.processor.ts @@ -55,7 +55,7 @@ export class PostHookDefinitionProcessor extends BaseTableProcessor { let route = null; for (const path of pathsToTry) { route = await this.queryBuilderService.findOne({ - table: 'route_definition', + table: 'enfyra_route', where: { path, }, @@ -92,7 +92,7 @@ export class PostHookDefinitionProcessor extends BaseTableProcessor { ) { if (isMongoDB) { const result = await this.queryBuilderService.find({ - table: 'method_definition', + table: 'enfyra_method', filter: { name: { _in: hook.methods } }, fields: ['_id', 'name'], }); @@ -112,7 +112,7 @@ export class PostHookDefinitionProcessor extends BaseTableProcessor { delete transformedHook.methods; } } - return normalizeScriptRecord('post_hook_definition', transformedHook); + return normalizeScriptRecord('enfyra_post_hook', transformedHook); }), ); return transformedRecords.filter(Boolean); @@ -128,7 +128,7 @@ export class PostHookDefinitionProcessor extends BaseTableProcessor { let hookId = record.id; if (!hookId && record.name) { const hook = await this.queryBuilderService - .getKnex()('post_hook_definition') + .getKnex()('enfyra_post_hook') .select('id') .where({ name: record.name }) .first(); @@ -136,7 +136,7 @@ export class PostHookDefinitionProcessor extends BaseTableProcessor { } if (hookId === undefined || hookId === null) return; const methods = await this.queryBuilderService - .getKnex()('method_definition') + .getKnex()('enfyra_method') .select('id', 'name') .whereIn('name', methodNames); const methodIds = methods @@ -145,9 +145,9 @@ export class PostHookDefinitionProcessor extends BaseTableProcessor { if (methodIds.length > 0) { const { junctionTable, sourceColumn, targetColumn } = await getSqlJunctionMetadata(this.queryBuilderService, { - sourceTable: 'post_hook_definition', + sourceTable: 'enfyra_post_hook', propertyName: 'methods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); if (!junctionTable || !sourceColumn || !targetColumn) return; const knex = this.queryBuilderService.getKnex(); diff --git a/src/domain/bootstrap/processors/pre-hook-definition.processor.ts b/src/domain/bootstrap/processors/pre-hook-definition.processor.ts index a0e37c98..c601c97a 100644 --- a/src/domain/bootstrap/processors/pre-hook-definition.processor.ts +++ b/src/domain/bootstrap/processors/pre-hook-definition.processor.ts @@ -55,7 +55,7 @@ export class PreHookDefinitionProcessor extends BaseTableProcessor { let route = null; for (const path of pathsToTry) { route = await this.queryBuilderService.findOne({ - table: 'route_definition', + table: 'enfyra_route', where: { path, }, @@ -92,7 +92,7 @@ export class PreHookDefinitionProcessor extends BaseTableProcessor { ) { if (isMongoDB) { const result = await this.queryBuilderService.find({ - table: 'method_definition', + table: 'enfyra_method', filter: { name: { _in: hook.methods } }, fields: ['_id', 'name'], }); @@ -112,7 +112,7 @@ export class PreHookDefinitionProcessor extends BaseTableProcessor { delete transformedHook.methods; } } - return normalizeScriptRecord('pre_hook_definition', transformedHook); + return normalizeScriptRecord('enfyra_pre_hook', transformedHook); }), ); return transformedRecords.filter(Boolean); @@ -128,7 +128,7 @@ export class PreHookDefinitionProcessor extends BaseTableProcessor { let hookId = record.id; if (!hookId && record.name) { const hook = await this.queryBuilderService - .getKnex()('pre_hook_definition') + .getKnex()('enfyra_pre_hook') .select('id') .where({ name: record.name }) .first(); @@ -136,7 +136,7 @@ export class PreHookDefinitionProcessor extends BaseTableProcessor { } if (hookId === undefined || hookId === null) return; const methods = await this.queryBuilderService - .getKnex()('method_definition') + .getKnex()('enfyra_method') .select('id', 'name') .whereIn('name', methodNames); const methodIds = methods @@ -145,9 +145,9 @@ export class PreHookDefinitionProcessor extends BaseTableProcessor { if (methodIds.length > 0) { const { junctionTable, sourceColumn, targetColumn } = await getSqlJunctionMetadata(this.queryBuilderService, { - sourceTable: 'pre_hook_definition', + sourceTable: 'enfyra_pre_hook', propertyName: 'methods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); if (!junctionTable || !sourceColumn || !targetColumn) return; const knex = this.queryBuilderService.getKnex(); diff --git a/src/domain/bootstrap/processors/route-definition.processor.ts b/src/domain/bootstrap/processors/route-definition.processor.ts index b8dfffd1..f633f28d 100644 --- a/src/domain/bootstrap/processors/route-definition.processor.ts +++ b/src/domain/bootstrap/processors/route-definition.processor.ts @@ -26,7 +26,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { } async transformRecords(records: any[], _context?: any): Promise { const isMongoDB = DatabaseConfigService.instanceIsMongoDb(); - const pkField = DatabaseConfigService.getPkField(); + const pkField = isMongoDB ? '_id' : 'id'; const transformedRecords = await Promise.all( records.map(async (record) => { const transformedRecord = { ...record }; @@ -46,7 +46,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (record.mainTable) { if (isMongoDB) { const mainTable = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: record.mainTable, }, @@ -63,7 +63,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { : mainTable._id; } else { const mainTable = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: record.mainTable, }, @@ -118,7 +118,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (isMongoDB) { const methods = await this.queryBuilderService .getMongoDb() - .collection('method_definition') + .collection('enfyra_method') .find({ name: { $in: methodNames } }) .project({ [pkField]: 1, name: 1 }) .toArray(); @@ -126,7 +126,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { } const methods = await this.queryBuilderService - .getKnex()('method_definition') + .getKnex()('enfyra_method') .select(pkField, 'name') .whereIn('name', methodNames); return methods.map((method: any) => method[pkField]).filter(Boolean); @@ -143,7 +143,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { } protected prepareRecordForWrite(record: any, tableName: string): any { - if (tableName !== 'route_definition') { + if (tableName !== 'enfyra_route') { return record; } @@ -191,13 +191,13 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (isMongoDB) { const route = await this.queryBuilderService .getMongoDb() - .collection('route_definition') + .collection('enfyra_route') .findOne({ path: record.path }, { projection: { _id: 1 } }); return route?._id; } const route = await this.queryBuilderService - .getKnex()('route_definition') + .getKnex()('enfyra_route') .select('id') .where({ path: record.path }) .first(); @@ -212,9 +212,9 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { ): Promise { const { junctionTable, sourceColumn, targetColumn } = await getSqlJunctionMetadata(this.queryBuilderService, { - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: field, - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); try { await replaceSqlJunctionRows(this.queryBuilderService, { @@ -230,7 +230,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { [targetColumn]: methodId, })); throw new Error( - `Failed to sync route_definition.${field} for ${record.path}: routeId=${String(routeId)}, methodIds=${JSON.stringify(uniqueMethodIds)}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, + `Failed to sync enfyra_route.${field} for ${record.path}: routeId=${String(routeId)}, methodIds=${JSON.stringify(uniqueMethodIds)}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, ); } } @@ -266,7 +266,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { [targetColumn]: methodId, })); throw new Error( - `Failed to sync route_definition.${field} for ${record.path}: routeId=${String(routeId)}, methodIds=${JSON.stringify(uniqueMethodIds.map(String))}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, + `Failed to sync enfyra_route.${field} for ${record.path}: routeId=${String(routeId)}, methodIds=${JSON.stringify(uniqueMethodIds.map(String))}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, ); } } @@ -280,18 +280,18 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { }> { const db = this.queryBuilderService.getMongoDb(); const [routeTable, methodTable] = await Promise.all([ - db.collection('table_definition').findOne({ name: 'route_definition' }), - db.collection('table_definition').findOne({ name: 'method_definition' }), + db.collection('enfyra_table').findOne({ name: 'enfyra_route' }), + db.collection('enfyra_table').findOne({ name: 'enfyra_method' }), ]); - const relation = await db.collection('relation_definition').findOne({ + const relation = await db.collection('enfyra_relation').findOne({ sourceTable: routeTable?._id, targetTable: methodTable?._id, propertyName: field, }); const fallback = getSqlJunctionPhysicalNames({ - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: field, - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); return { junctionTable: relation?.junctionTableName || fallback.junctionTableName, @@ -313,7 +313,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { this.logger.log('[ensureMissingHandlers] Starting handler check...'); const { data: routes } = await this.queryBuilderService.find({ - table: 'route_definition', + table: 'enfyra_route', filter: { isEnabled: { _eq: true } }, }); @@ -364,12 +364,12 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { const routeIdObj = typeof routeId === 'string' ? new ObjectId(routeId) : routeId; const result = await db - .collection('route_handler_definition') + .collection('enfyra_route_handler') .deleteMany({ route: routeIdObj, method: null }); return result.deletedCount || 0; } const knex = this.queryBuilderService.getKnex(); - return await knex('route_handler_definition') + return await knex('enfyra_route_handler') .where({ routeId }) .whereNull('methodId') .delete(); @@ -379,12 +379,12 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (isMongoDB) { const db = this.queryBuilderService.getMongoDb(); const result = await db - .collection('route_handler_definition') + .collection('enfyra_route_handler') .deleteMany({ route: null }); return result.deletedCount || 0; } const knex = this.queryBuilderService.getKnex(); - return await knex('route_handler_definition').whereNull('routeId').delete(); + return await knex('enfyra_route_handler').whereNull('routeId').delete(); } private async ensureDefaultCrudHandlers( @@ -412,10 +412,10 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { const tableRow = isMongoDB ? await this.queryBuilderService .getMongoDb() - .collection('table_definition') + .collection('enfyra_table') .findOne({ _id: mainTableFk }) : await this.queryBuilderService - .getKnex()('table_definition') + .getKnex()('enfyra_table') .where({ id: mainTableFk }) .first(); tableName = tableRow?.name; @@ -438,9 +438,9 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { } else { if (isMongoDB) { const junction = getSqlJunctionPhysicalNames({ - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: 'availableMethods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); const mongoService = this.queryBuilderService.getMongoDb(); const routeIdObj = @@ -453,9 +453,9 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { } else { const { junctionTable, sourceColumn, targetColumn } = await getSqlJunctionMetadata(this.queryBuilderService, { - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: 'availableMethods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); const knex = this.queryBuilderService.getKnex(); const rows = await knex(junctionTable) @@ -467,15 +467,31 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (methodIds.length === 0) return; - const idStrings = methodIds.map((id: any) => id.toString()); - const methodResult = await this.queryBuilderService.find({ - table: 'method_definition', - filter: { id: { _in: idStrings } }, - fields: ['name'], - }); - const available: string[] = (methodResult.data || []) - .map((m: any) => m.name) - .filter(Boolean); + const available: string[] = isMongoDB + ? ( + await this.queryBuilderService + .getMongoDb() + .collection('enfyra_method') + .find({ + _id: { + $in: methodIds.map((id: any) => + id instanceof ObjectId ? id : new ObjectId(String(id)), + ), + }, + }) + .project({ name: 1 }) + .toArray() + ) + .map((m: any) => m.name) + .filter(Boolean) + : ( + await this.queryBuilderService + .getKnex()('enfyra_method') + .select('name') + .whereIn('id', methodIds) + ) + .map((m: any) => m.name) + .filter(Boolean); if (available.length === 0) return; @@ -484,7 +500,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (!logic) continue; const methodRow = await this.queryBuilderService.findOne({ - table: 'method_definition', + table: 'enfyra_method', where: { name: methodName }, }); if (!methodRow) { @@ -512,14 +528,14 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { ? new ObjectId(methodKeyId) : methodKeyId; existing = await mongoService - .collection('route_handler_definition') + .collection('enfyra_route_handler') .findOne({ route: routeIdObj, method: methodIdObj, }); } else { existing = await this.queryBuilderService.findOne({ - table: 'route_handler_definition', + table: 'enfyra_route_handler', where: { routeId, methodId: methodKeyId, @@ -558,11 +574,11 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (isMongoDB) { const mongoService = this.queryBuilderService.getMongoDb(); await mongoService - .collection('route_handler_definition') + .collection('enfyra_route_handler') .insertOne(data); } else { await this.queryBuilderService.insertWithOptions({ - table: 'route_handler_definition', + table: 'enfyra_route_handler', data, }); } diff --git a/src/domain/bootstrap/processors/route-handler-definition.processor.ts b/src/domain/bootstrap/processors/route-handler-definition.processor.ts index 5aa52eee..a362943b 100644 --- a/src/domain/bootstrap/processors/route-handler-definition.processor.ts +++ b/src/domain/bootstrap/processors/route-handler-definition.processor.ts @@ -15,7 +15,7 @@ export class RouteHandlerDefinitionProcessor extends BaseTableProcessor { records.map(async (handler) => { if (handler.route && typeof handler.route === 'string') { const route = await this.queryBuilderService.findOne({ - table: 'route_definition', + table: 'enfyra_route', where: { path: handler.route, }, @@ -35,7 +35,7 @@ export class RouteHandlerDefinitionProcessor extends BaseTableProcessor { } if (handler.method && typeof handler.method === 'string') { const method = await this.queryBuilderService.findOne({ - table: 'method_definition', + table: 'enfyra_method', where: { name: handler.method, }, @@ -59,7 +59,7 @@ export class RouteHandlerDefinitionProcessor extends BaseTableProcessor { if (!handler.createdAt) handler.createdAt = now; if (!handler.updatedAt) handler.updatedAt = now; } - return normalizeScriptRecord('route_handler_definition', handler); + return normalizeScriptRecord('enfyra_route_handler', handler); }), ); return transformedRecords.filter(Boolean); diff --git a/src/domain/bootstrap/processors/route-permission-definition.processor.ts b/src/domain/bootstrap/processors/route-permission-definition.processor.ts index 95e333bc..fe54ef19 100644 --- a/src/domain/bootstrap/processors/route-permission-definition.processor.ts +++ b/src/domain/bootstrap/processors/route-permission-definition.processor.ts @@ -24,7 +24,7 @@ export class RoutePermissionDefinitionProcessor extends BaseTableProcessor { const result = await this.autoTransformFkFields( transformed, - 'route_permission_definition', + 'enfyra_route_permission', this.queryBuilderService, ); if (!result.route && !result.routeId) return null; @@ -36,7 +36,7 @@ export class RoutePermissionDefinitionProcessor extends BaseTableProcessor { } getUniqueIdentifier(record: any): object { - return this.autoGetUniqueIdentifier(record, 'route_permission_definition'); + return this.autoGetUniqueIdentifier(record, 'enfyra_route_permission'); } protected getCompareFields(): string[] { diff --git a/src/domain/bootstrap/processors/user-definition.processor.ts b/src/domain/bootstrap/processors/user-definition.processor.ts index 08bb4b2e..53b63ff0 100644 --- a/src/domain/bootstrap/processors/user-definition.processor.ts +++ b/src/domain/bootstrap/processors/user-definition.processor.ts @@ -36,7 +36,7 @@ export class UserDefinitionProcessor extends BaseTableProcessor { const result = await this.autoTransformFkFields( transformed, - 'user_definition', + 'enfyra_user', this.queryBuilderService, ); return result; diff --git a/src/domain/bootstrap/processors/websocket-definition.processor.ts b/src/domain/bootstrap/processors/websocket-definition.processor.ts index 13e20b91..57a29aa9 100644 --- a/src/domain/bootstrap/processors/websocket-definition.processor.ts +++ b/src/domain/bootstrap/processors/websocket-definition.processor.ts @@ -39,7 +39,7 @@ export class WebsocketDefinitionProcessor extends BaseTableProcessor { if (!transformedRecord.updatedAt) transformedRecord.updatedAt = now; } - return normalizeScriptRecord('websocket_definition', transformedRecord); + return normalizeScriptRecord('enfyra_websocket', transformedRecord); }), ); diff --git a/src/domain/bootstrap/processors/websocket-event-definition.processor.ts b/src/domain/bootstrap/processors/websocket-event-definition.processor.ts index 43433745..cf7a1267 100644 --- a/src/domain/bootstrap/processors/websocket-event-definition.processor.ts +++ b/src/domain/bootstrap/processors/websocket-event-definition.processor.ts @@ -35,11 +35,11 @@ export class WebsocketEventDefinitionProcessor extends BaseTableProcessor { const result = await this.autoTransformFkFields( transformed, - 'websocket_event_definition', + 'enfyra_websocket_event', this.queryBuilderService, ); if (!result.gateway && !result.gatewayId) return null; - return normalizeScriptRecord('websocket_event_definition', result); + return normalizeScriptRecord('enfyra_websocket_event', result); }), ); @@ -47,11 +47,11 @@ export class WebsocketEventDefinitionProcessor extends BaseTableProcessor { } getUniqueIdentifier(record: any): object { - return this.autoGetUniqueIdentifier(record, 'websocket_event_definition'); + return this.autoGetUniqueIdentifier(record, 'enfyra_websocket_event'); } protected getCompareFields(): string[] { - return this.autoGetCompareFields('websocket_event_definition'); + return this.autoGetCompareFields('enfyra_websocket_event'); } protected getRecordIdentifier(record: any): string { diff --git a/src/domain/bootstrap/services/bootstrap-script.service.ts b/src/domain/bootstrap/services/bootstrap-script.service.ts index ed9db975..f79c1913 100644 --- a/src/domain/bootstrap/services/bootstrap-script.service.ts +++ b/src/domain/bootstrap/services/bootstrap-script.service.ts @@ -101,20 +101,20 @@ export class BootstrapScriptService { if (this.queryBuilderService.isMongoDb()) { const db = this.queryBuilderService.getMongoDb(); const collections = await db - .listCollections({ name: 'bootstrap_script_definition' }) + .listCollections({ name: 'enfyra_bootstrap_script' }) .toArray(); if (collections.length > 0) return; } else { const knex = this.queryBuilderService.getKnex(); const exists = await knex.schema.hasTable( - 'bootstrap_script_definition', + 'enfyra_bootstrap_script', ); if (exists) return; } } catch (error) { if (attempt === maxRetries) { throw new Error( - `bootstrap_script_definition not found after ${maxRetries} attempts`, + `enfyra_bootstrap_script not found after ${maxRetries} attempts`, ); } await new Promise((resolve) => setTimeout(resolve, delayMs)); @@ -132,14 +132,14 @@ export class BootstrapScriptService { private async executeBootstrapScriptsWithoutLock(): Promise { const result = await this.queryBuilderService.find({ - table: 'bootstrap_script_definition', + table: 'enfyra_bootstrap_script', filter: { isEnabled: { _eq: true } }, sort: ['priority'], }); const scripts = result.data; for (let i = 0; i < scripts.length; i++) { const script = normalizeScriptRecord( - 'bootstrap_script_definition', + 'enfyra_bootstrap_script', scripts[i], ); const resolved = resolveExecutableScript(script); @@ -149,7 +149,7 @@ export class BootstrapScriptService { const id = DatabaseConfigService.getRecordId(script); if (id != null) { await this.queryBuilderService.update( - 'bootstrap_script_definition', + 'enfyra_bootstrap_script', id, { compiledCode: resolved.compiledCode, diff --git a/src/domain/bootstrap/utils/bootstrap-data-validator.util.ts b/src/domain/bootstrap/utils/bootstrap-data-validator.util.ts index f9006704..8765c654 100644 --- a/src/domain/bootstrap/utils/bootstrap-data-validator.util.ts +++ b/src/domain/bootstrap/utils/bootstrap-data-validator.util.ts @@ -14,7 +14,7 @@ function routePath(record: any) { function methodNames(defaultData: Record) { return new Set( - (defaultData.method_definition ?? []) + (defaultData.enfyra_method ?? []) .map((method: any) => method.name) .filter(Boolean), ); @@ -63,7 +63,7 @@ function validateRouteRefs(input: { table: input.table, path: route, field: input.field ?? 'route', - message: `Route "${route}" is not defined in bootstrap route_definition.`, + message: `Route "${route}" is not defined in bootstrap enfyra_route.`, }); } if (input.record.actions !== undefined) { @@ -95,7 +95,7 @@ function validateRouteRefs(input: { table: input.table, path: route, field, - message: `Method "${method}" is not defined in method_definition.`, + message: `Method "${method}" is not defined in enfyra_method.`, }); } } @@ -115,7 +115,7 @@ function validateMenuPermission(input: { issues.push( ...validateRouteRefs({ file: input.file, - table: 'menu_definition', + table: 'enfyra_menu', record: rule, routes: new Set([...input.routes, ...input.menuPaths]), methods: input.methods, @@ -136,7 +136,7 @@ function validateGqlRecord(input: { return [ { file: input.file, - table: 'gql_definition', + table: 'enfyra_graphql', field: 'table', message: `GraphQL table "${table}" does not exist in snapshot.json.`, }, @@ -154,9 +154,9 @@ function validateWebsocketEvent(input: { return [ { file: input.file, - table: 'websocket_event_definition', + table: 'enfyra_websocket_event', field: 'gateway', - message: `WebSocket gateway "${name}" is not defined in websocket_definition.`, + message: `WebSocket gateway "${name}" is not defined in enfyra_websocket.`, }, ] satisfies BootstrapValidationIssue[]; } @@ -172,7 +172,7 @@ function validateFlowStep(input: { return [ { file: input.file, - table: 'flow_step_definition', + table: 'enfyra_flow_step', field: 'flow', message: `Flow step references unknown flow "${name}".`, }, @@ -190,7 +190,7 @@ function validateRouteRecord(input: { if (!path) { issues.push({ file: input.file, - table: 'route_definition', + table: 'enfyra_route', field: 'path', message: 'Route record must define path or _unique.path._eq.', }); @@ -199,7 +199,7 @@ function validateRouteRecord(input: { if (input.record.mainTable && !input.tables.has(input.record.mainTable)) { issues.push({ file: input.file, - table: 'route_definition', + table: 'enfyra_route', path, field: 'mainTable', message: `Route mainTable "${input.record.mainTable}" does not exist in snapshot.json.`, @@ -211,7 +211,7 @@ function validateRouteRecord(input: { if (!Array.isArray(input.record[field])) { issues.push({ file: input.file, - table: 'route_definition', + table: 'enfyra_route', path, field, message: `${field} must be an array of method names.`, @@ -223,10 +223,10 @@ function validateRouteRecord(input: { if (!input.methods.has(method)) { issues.push({ file: input.file, - table: 'route_definition', + table: 'enfyra_route', path, field, - message: `Method "${method}" is not defined in default-data.json method_definition.`, + message: `Method "${method}" is not defined in default-data.json enfyra_method.`, }); } } @@ -247,7 +247,7 @@ function validateUniqueRoutePaths( if (seen.has(path)) { issues.push({ file, - table: 'route_definition', + table: 'enfyra_route', path, field: 'path', message: `Duplicate route path "${path}".`, @@ -262,23 +262,23 @@ export function validateBootstrapDataFiles(input: BootstrapDataFiles) { const issues: BootstrapValidationIssue[] = []; const methods = methodNames(input.defaultData); const tables = tableNames(input.snapshot); - const defaultRoutes = input.defaultData.route_definition ?? []; - const migrationRoutes = input.dataMigration.route_definition ?? []; + const defaultRoutes = input.defaultData.enfyra_route ?? []; + const migrationRoutes = input.dataMigration.enfyra_route ?? []; const routes = new Set([ ...defaultRoutes.map(routePath).filter(Boolean), ...migrationRoutes.map(routePath).filter(Boolean), ]); const menuPaths = new Set([ - ...(input.defaultData.menu_definition ?? []).map(routePath).filter(Boolean), - ...(input.dataMigration.menu_definition ?? []).map(routePath).filter(Boolean), + ...(input.defaultData.enfyra_menu ?? []).map(routePath).filter(Boolean), + ...(input.dataMigration.enfyra_menu ?? []).map(routePath).filter(Boolean), ]); const gateways = new Set( - (input.defaultData.websocket_definition ?? []) + (input.defaultData.enfyra_websocket ?? []) .map(recordName) .filter(Boolean), ); const flows = new Set( - (input.defaultData.flow_definition ?? []) + (input.defaultData.enfyra_flow ?? []) .map(recordName) .filter(Boolean), ); @@ -313,10 +313,10 @@ export function validateBootstrapDataFiles(input: BootstrapDataFiles) { file === 'default-data.json' ? input.defaultData : input.dataMigration; for (const table of [ - 'route_permission_definition', - 'route_handler_definition', - 'pre_hook_definition', - 'post_hook_definition', + 'enfyra_route_permission', + 'enfyra_route_handler', + 'enfyra_pre_hook', + 'enfyra_post_hook', ]) { for (const record of source[table] ?? []) { issues.push( @@ -331,7 +331,7 @@ export function validateBootstrapDataFiles(input: BootstrapDataFiles) { } } - for (const record of source.menu_definition ?? []) { + for (const record of source.enfyra_menu ?? []) { issues.push( ...validateMenuPermission({ file, @@ -343,15 +343,15 @@ export function validateBootstrapDataFiles(input: BootstrapDataFiles) { ); } - for (const record of source.gql_definition ?? []) { + for (const record of source.enfyra_graphql ?? []) { issues.push(...validateGqlRecord({ file, record, tables })); } - for (const record of source.websocket_event_definition ?? []) { + for (const record of source.enfyra_websocket_event ?? []) { issues.push(...validateWebsocketEvent({ file, record, gateways })); } - for (const record of source.flow_step_definition ?? []) { + for (const record of source.enfyra_flow_step ?? []) { issues.push(...validateFlowStep({ file, record, flows })); } } diff --git a/src/domain/bootstrap/utils/snapshot-meta.util.ts b/src/domain/bootstrap/utils/snapshot-meta.util.ts index ea73e582..fbd0fa13 100644 --- a/src/domain/bootstrap/utils/snapshot-meta.util.ts +++ b/src/domain/bootstrap/utils/snapshot-meta.util.ts @@ -16,15 +16,15 @@ export function getTableDef(tableName: string): any | null { } const LOOKUP_KEY_MAP: Record = { - table_definition: 'name', - route_definition: 'path', - role_definition: 'name', - method_definition: 'name', - user_definition: 'email', - menu_definition: 'label', - websocket_definition: 'path', - flow_definition: 'name', - flow_step_definition: 'key', + enfyra_table: 'name', + enfyra_route: 'path', + enfyra_role: 'name', + enfyra_method: 'name', + enfyra_user: 'email', + enfyra_menu: 'label', + enfyra_websocket: 'path', + enfyra_flow: 'name', + enfyra_flow_step: 'key', }; export function getLookupKey(targetTable: string): string { diff --git a/src/domain/bootstrap/utils/sql-junction-metadata.util.ts b/src/domain/bootstrap/utils/sql-junction-metadata.util.ts index 971f40e6..aa856d93 100644 --- a/src/domain/bootstrap/utils/sql-junction-metadata.util.ts +++ b/src/domain/bootstrap/utils/sql-junction-metadata.util.ts @@ -17,14 +17,14 @@ export async function getSqlJunctionMetadata( targetColumn: string; }> { const knex = queryBuilderService.getKnex(); - const relation = await knex('relation_definition as r') + const relation = await knex('enfyra_relation as r') .leftJoin( - 'table_definition as sourceTable', + 'enfyra_table as sourceTable', 'r.sourceTableId', 'sourceTable.id', ) .leftJoin( - 'table_definition as targetTable', + 'enfyra_table as targetTable', 'r.targetTableId', 'targetTable.id', ) diff --git a/src/domain/policy/services/schema-migration-validator.service.ts b/src/domain/policy/services/schema-migration-validator.service.ts index b3b3ffa6..570a37c3 100644 --- a/src/domain/policy/services/schema-migration-validator.service.ts +++ b/src/domain/policy/services/schema-migration-validator.service.ts @@ -432,7 +432,7 @@ export class SchemaMigrationValidatorService { } } const baseRelations = [...new Set([...relations, ...inverseRelations])]; - if (tableName === 'table_definition') { + if (tableName === 'enfyra_table') { baseRelations.push( 'columns.table', 'relations.sourceTable', diff --git a/src/domain/policy/services/system-safety-auditor.service.ts b/src/domain/policy/services/system-safety-auditor.service.ts index 728890ea..003d5ceb 100644 --- a/src/domain/policy/services/system-safety-auditor.service.ts +++ b/src/domain/policy/services/system-safety-auditor.service.ts @@ -22,7 +22,7 @@ export class SystemSafetyAuditorService { const { operation, tableName, data, existing, currentUser } = ctx; let fullExisting = existing; - if (existing?.isSystem && tableName === 'table_definition') { + if (existing?.isSystem && tableName === 'enfyra_table') { fullExisting = await this.schemaMigrationValidatorService.enrichTableDefinitionData( existing, @@ -62,7 +62,7 @@ export class SystemSafetyAuditorService { ); } - if (tableName === 'route_definition' && fullExisting?.isSystem) { + if (tableName === 'enfyra_route' && fullExisting?.isSystem) { const allowed = this.schemaMigrationValidatorService.getAllowedFields([ 'description', 'publicMethods', @@ -94,8 +94,8 @@ export class SystemSafetyAuditorService { } if ( - tableName === 'pre_hook_definition' || - tableName === 'post_hook_definition' + tableName === 'enfyra_pre_hook' || + tableName === 'enfyra_post_hook' ) { if (operation === 'create' && data?.isSystem) { throw new Error('Cannot create system hook'); @@ -127,7 +127,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'user_definition') { + if (tableName === 'enfyra_user') { const isRoot = fullExisting?.isRootAdmin; if (operation === 'delete' && isRoot) throw new Error('Cannot delete Root Admin user'); @@ -145,7 +145,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'field_permission_definition') { + if (tableName === 'enfyra_field_permission') { if (operation === 'create' || operation === 'update') { const hasColumnInData = data && 'column' in data ? data.column != null : undefined; @@ -163,7 +163,7 @@ export class SystemSafetyAuditorService { : data?.relation != null; if ((hasColumn && hasRelation) || (!hasColumn && !hasRelation)) { throw new Error( - 'field_permission_definition requires exactly one of: column or relation', + 'enfyra_field_permission requires exactly one of: column or relation', ); } @@ -180,7 +180,7 @@ export class SystemSafetyAuditorService { Array.isArray(data?.allowedUsers) && data.allowedUsers.length > 0; if (!hasRole && !hasUsers) { throw new Error( - 'field_permission_definition requires scope: role or allowedUsers', + 'enfyra_field_permission requires scope: role or allowedUsers', ); } } @@ -200,7 +200,7 @@ export class SystemSafetyAuditorService { if (!hasRoleFinal && !hasUsersFinal) { throw new Error( - 'field_permission_definition requires scope: role or allowedUsers', + 'enfyra_field_permission requires scope: role or allowedUsers', ); } } @@ -208,7 +208,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'table_definition') { + if (tableName === 'enfyra_table') { const isSystem = fullExisting?.isSystem; if (operation === 'create' && data?.isSystem) throw new Error('Cannot create new system table!'); @@ -313,7 +313,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'websocket_definition' && fullExisting?.isSystem) { + if (tableName === 'enfyra_websocket' && fullExisting?.isSystem) { const allowed = this.schemaMigrationValidatorService.getAllowedFields([ 'description', 'sourceCode', @@ -340,7 +340,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'menu_definition') { + if (tableName === 'enfyra_menu') { const isSystem = fullExisting?.isSystem; if (operation === 'create' && data?.isSystem) { throw new Error('Cannot create new system menu!'); @@ -377,7 +377,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'extension_definition') { + if (tableName === 'enfyra_extension') { const isSystem = fullExisting?.isSystem; if (operation === 'create' && data?.isSystem) { throw new Error('Cannot create new system extension!'); @@ -426,7 +426,7 @@ export class SystemSafetyAuditorService { } } - if (tableName === 'storage_config_definition') { + if (tableName === 'enfyra_storage_config') { const isSystem = fullExisting?.isSystem; if (operation === 'update' && isSystem) { const allowed = this.schemaMigrationValidatorService.getAllowedFields([ diff --git a/src/engines/bootstrap/services/data-migration.service.ts b/src/engines/bootstrap/services/data-migration.service.ts index 141da718..88d121f0 100644 --- a/src/engines/bootstrap/services/data-migration.service.ts +++ b/src/engines/bootstrap/services/data-migration.service.ts @@ -244,9 +244,9 @@ export class DataMigrationService { tableName: string, data: any, ): Promise { - if (tableName === 'route_definition' && data.mainTable) { + if (tableName === 'enfyra_route' && data.mainTable) { const mainTable = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: data.mainTable }, }); if (!mainTable) { @@ -270,7 +270,7 @@ export class DataMigrationService { recordId: any, relationUpdates: any, ): Promise { - if (tableName === 'route_definition') { + if (tableName === 'enfyra_route') { for (const [field, methodNames] of Object.entries(relationUpdates)) { if ( field === 'publicMethods' || @@ -302,7 +302,7 @@ export class DataMigrationService { try { const routes = await this.queryBuilderService.find({ - table: 'route_definition', + table: 'enfyra_route', filter: {}, limit: -1, fields: [idField, 'path', 'mainTable.name'], @@ -320,7 +320,7 @@ export class DataMigrationService { : { mainTableId: null }; await this.queryBuilderService.update( - 'route_definition', + 'enfyra_route', { where: [{ field: idField, operator: '=', value: route[idField] }] }, data, ); @@ -347,7 +347,7 @@ export class DataMigrationService { if (DatabaseConfigService.instanceIsMongoDb()) { const idField = DatabaseConfigService.getPkField(); const result = await this.queryBuilderService.find({ - table: 'method_definition', + table: 'enfyra_method', filter: { name: { _in: methodNames } }, fields: [idField], }); @@ -355,7 +355,7 @@ export class DataMigrationService { } const rows = await this.queryBuilderService - .getKnex()('method_definition') + .getKnex()('enfyra_method') .select('id', 'name') .whereIn('name', methodNames); return rows.map((m: any) => m.id).filter(Boolean); @@ -368,9 +368,9 @@ export class DataMigrationService { ): Promise { const { junctionTable, sourceColumn, targetColumn } = await getSqlJunctionMetadata(this.queryBuilderService as any, { - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: field, - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); try { await replaceSqlJunctionRows(this.queryBuilderService as any, { @@ -386,7 +386,7 @@ export class DataMigrationService { [targetColumn]: methodId, })); throw new Error( - `Failed to migrate route_definition.${field}: routeId=${String(routeId)}, methodIds=${JSON.stringify(methodIds)}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, + `Failed to migrate enfyra_route.${field}: routeId=${String(routeId)}, methodIds=${JSON.stringify(methodIds)}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, ); } } @@ -418,7 +418,7 @@ export class DataMigrationService { [targetColumn]: methodId, })); throw new Error( - `Failed to migrate route_definition.${field}: routeId=${String(routeId)}, methodIds=${JSON.stringify(methodIds.map(String))}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, + `Failed to migrate enfyra_route.${field}: routeId=${String(routeId)}, methodIds=${JSON.stringify(methodIds.map(String))}, rows=${JSON.stringify(rows)}, junction=${junctionTable}(${sourceColumn},${targetColumn}): ${error instanceof Error ? error.message : String(error)}`, ); } } @@ -430,18 +430,18 @@ export class DataMigrationService { }> { const db = this.queryBuilderService.getMongoDb(); const [sourceTable, targetTable] = await Promise.all([ - db.collection('table_definition').findOne({ name: 'route_definition' }), - db.collection('table_definition').findOne({ name: 'method_definition' }), + db.collection('enfyra_table').findOne({ name: 'enfyra_route' }), + db.collection('enfyra_table').findOne({ name: 'enfyra_method' }), ]); - const relation = await db.collection('relation_definition').findOne({ + const relation = await db.collection('enfyra_relation').findOne({ sourceTable: sourceTable?._id, targetTable: targetTable?._id, propertyName: field, }); const fallback = getSqlJunctionPhysicalNames({ - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: field, - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); return { junctionTable: relation?.junctionTableName || fallback.junctionTableName, diff --git a/src/engines/bootstrap/services/data-provision.service.ts b/src/engines/bootstrap/services/data-provision.service.ts index 849768b2..ee24d509 100644 --- a/src/engines/bootstrap/services/data-provision.service.ts +++ b/src/engines/bootstrap/services/data-provision.service.ts @@ -31,6 +31,7 @@ import { GraphQLDefinitionProcessor, } from '../../../domain/bootstrap'; import { bootstrapVerboseLog } from '../utils/bootstrap-logging.util'; +import { SYSTEM_TABLES } from '../../../shared/utils/system-tables.constants'; const initJson = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'data/default-data.json'), 'utf8'), ); @@ -118,55 +119,55 @@ export class DataProvisionService { } private initializeProcessors(): void { - this.processors.set('user_definition', this.userDefinitionProcessor); - this.processors.set('menu_definition', this.menuDefinitionProcessor); - this.processors.set('route_definition', this.routeDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.user, this.userDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.menu, this.menuDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.route, this.routeDefinitionProcessor); this.processors.set( - 'route_handler_definition', + SYSTEM_TABLES.routeHandler, this.routeHandlerDefinitionProcessor, ); - this.processors.set('method_definition', this.methodDefinitionProcessor); - this.processors.set('pre_hook_definition', this.preHookDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.method, this.methodDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.preHook, this.preHookDefinitionProcessor); this.processors.set( - 'post_hook_definition', + SYSTEM_TABLES.postHook, this.postHookDefinitionProcessor, ); this.processors.set( - 'field_permission_definition', + SYSTEM_TABLES.fieldPermission, this.fieldPermissionDefinitionProcessor, ); - this.processors.set('setting_definition', this.settingDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.setting, this.settingDefinitionProcessor); this.processors.set( - 'extension_definition', + SYSTEM_TABLES.extension, this.extensionDefinitionProcessor, ); - this.processors.set('folder_definition', this.folderDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.folder, this.folderDefinitionProcessor); this.processors.set( - 'bootstrap_script_definition', + SYSTEM_TABLES.bootstrapScript, this.bootstrapScriptDefinitionProcessor, ); this.processors.set( - 'route_permission_definition', + SYSTEM_TABLES.routePermission, this.routePermissionDefinitionProcessor, ); this.processors.set( - 'websocket_definition', + SYSTEM_TABLES.websocket, this.websocketDefinitionProcessor, ); this.processors.set( - 'websocket_event_definition', + SYSTEM_TABLES.websocketEvent, this.websocketEventDefinitionProcessor, ); - this.processors.set('flow_definition', this.flowDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.flow, this.flowDefinitionProcessor); this.processors.set( - 'flow_step_definition', + SYSTEM_TABLES.flowStep, this.flowStepDefinitionProcessor, ); this.processors.set( - 'flow_execution_definition', + SYSTEM_TABLES.flowExecution, this.flowExecutionDefinitionProcessor, ); - this.processors.set('gql_definition', this.graphqlDefinitionProcessor); + this.processors.set(SYSTEM_TABLES.graphql, this.graphqlDefinitionProcessor); const allTables = Object.keys(initJson); const registeredTables = Array.from(this.processors.keys()); @@ -187,24 +188,25 @@ export class DataProvisionService { let totalCreated = 0; let totalSkipped = 0; - const userProcessor = this.processors.get('user_definition'); + const userProcessor = this.processors.get(SYSTEM_TABLES.user); if (userProcessor) { try { this.verbose( - `Processing 'user_definition' (ensure rootAdmin from env)...`, + `Processing '${SYSTEM_TABLES.user}' (ensure rootAdmin from env)...`, ); const result = await userProcessor.processWithQueryBuilder( [], this.queryBuilderService, - 'user_definition', + SYSTEM_TABLES.user, {}, ); totalCreated += result.created; totalSkipped += result.skipped; } catch (error) { this.logger.error( - `Error processing 'user_definition': ${getErrorMessage(error)}`, + `Error processing '${SYSTEM_TABLES.user}': ${getErrorMessage(error)}`, ); + throw error; } } @@ -246,6 +248,7 @@ export class DataProvisionService { `Error processing '${tableName}': ${getErrorMessage(error)}`, ); this.logger.debug(`Error: ${getErrorMessage(error)}`); + throw error; } } @@ -262,6 +265,7 @@ export class DataProvisionService { `Error ensuring route handlers: ${getErrorMessage(error)}`, ); this.logger.debug(getErrorMessage(error)); + throw error; } } } diff --git a/src/engines/bootstrap/services/first-run-initializer.service.ts b/src/engines/bootstrap/services/first-run-initializer.service.ts index bc965f41..ab1320fd 100644 --- a/src/engines/bootstrap/services/first-run-initializer.service.ts +++ b/src/engines/bootstrap/services/first-run-initializer.service.ts @@ -99,6 +99,11 @@ export class FirstRunInitializer { } try { + const coreT0 = Date.now(); + this.logProgress(mode, 3, 'migrating core metadata tables'); + await this.metadataMigrationService.runCoreTableRenamesBeforeMetadataSync(); + this.logVerbose(`Core system table migration: ${Date.now() - coreT0}ms`); + if (!(await this.isNeeded())) { this.logProgress( mode, @@ -112,6 +117,7 @@ export class FirstRunInitializer { const t0 = Date.now(); this.logProgress(mode, 8, 'preparing system schema'); + await this.metadataMigrationService.runTableRenamesBeforeMetadataSync(); await this.metadataMigrationService.runPhysicalMigrationsBeforeMetadataSync(); await this.schemaHealingService.repairSystemPhysicalColumnsBeforeMetadataProvision(); this.logVerbose(`System schema preflight: ${Date.now() - t0}ms`); @@ -160,6 +166,7 @@ export class FirstRunInitializer { this.logger.error( `Error ensuring route handlers: ${(error as Error).message}`, ); + throw error; } if (this.dataMigrationService.hasMigrations()) { @@ -258,15 +265,23 @@ export class FirstRunInitializer { const settingId = setting._id || setting.id; if (DatabaseConfigService.instanceIsMongoDb()) { + const collectionName = await this.findMongoSettingCollectionName(); + if (!collectionName) { + throw new Error('Setting collection not found.'); + } await this.queryBuilderService .getMongoDb() - .collection('setting_definition') + .collection(collectionName) .updateOne({ _id: settingId }, { $set: { isInit: true } }); return; } + const tableName = await this.findSqlSettingTableName(); + if (!tableName) { + throw new Error('Setting table not found.'); + } await this.queryBuilderService - .getKnex()('setting_definition') + .getKnex()(tableName) .where({ id: settingId }) .update({ isInit: true }); } @@ -287,15 +302,34 @@ export class FirstRunInitializer { private async findFirstSetting(): Promise { if (DatabaseConfigService.instanceIsMongoDb()) { + const collectionName = await this.findMongoSettingCollectionName(); + if (!collectionName) return null; return this.queryBuilderService .getMongoDb() - .collection('setting_definition') + .collection(collectionName) .findOne({}); } + const tableName = await this.findSqlSettingTableName(); + if (!tableName) return null; return this.queryBuilderService - .getKnex()('setting_definition') + .getKnex()(tableName) .orderBy('id', 'asc') .first(); } + + private async findMongoSettingCollectionName(): Promise { + const db = this.queryBuilderService.getMongoDb(); + const matches = await db + .listCollections({ name: 'enfyra_setting' }) + .toArray(); + return matches.length > 0 ? 'enfyra_setting' : null; + } + + private async findSqlSettingTableName(): Promise { + const knex = this.queryBuilderService.getKnex(); + return (await knex.schema.hasTable('enfyra_setting')) + ? 'enfyra_setting' + : null; + } } diff --git a/src/engines/bootstrap/services/index.ts b/src/engines/bootstrap/services/index.ts index 3066b055..abdef8a3 100644 --- a/src/engines/bootstrap/services/index.ts +++ b/src/engines/bootstrap/services/index.ts @@ -7,3 +7,4 @@ export * from './metadata-provision-sql.service'; export * from './metadata-provision.service'; export * from './schema-healing.service'; export * from './provision.service'; +export * from './system-core-table-resolver.service'; diff --git a/src/engines/bootstrap/services/metadata-migration.service.ts b/src/engines/bootstrap/services/metadata-migration.service.ts index ebb9b3ac..fd8b2d05 100644 --- a/src/engines/bootstrap/services/metadata-migration.service.ts +++ b/src/engines/bootstrap/services/metadata-migration.service.ts @@ -8,36 +8,50 @@ import { TableMigrationDef, ColumnModifyDef, RelationModifyDef, + TableRenameDef, } from '../../../shared/types/schema-migration.types'; -import * as fs from 'fs'; -import * as path from 'path'; import { bootstrapVerboseLog } from '../utils/bootstrap-logging.util'; +import { SystemCoreTableResolver } from './system-core-table-resolver.service'; +import { + buildColumnMetadataUpdate, + getLegacyScriptTargetColumn, + getValidTableRenames, + hasColumnMetadataChanges, + hasRelationMetadataChanges, + hasSchemaMigrations, + loadSnapshotMigrationFile, +} from '../utils/metadata-migration.util'; +import { SYSTEM_TABLES } from '../../../shared/utils/system-tables.constants'; +import { MetadataPhysicalMigrationHelper } from '../utils/metadata-physical-migration.util'; export class MetadataMigrationService { private readonly logger = new Logger(MetadataMigrationService.name); private readonly queryBuilderService: QueryBuilderService; + private readonly systemCoreTableResolver: SystemCoreTableResolver; + private readonly physicalMigration: MetadataPhysicalMigrationHelper; private migrations: SchemaMigrationDef | null = null; - constructor(deps: { queryBuilderService: QueryBuilderService }) { + constructor(deps: { + queryBuilderService: QueryBuilderService; + systemCoreTableResolver: SystemCoreTableResolver; + }) { this.queryBuilderService = deps.queryBuilderService; + this.systemCoreTableResolver = deps.systemCoreTableResolver; + this.physicalMigration = new MetadataPhysicalMigrationHelper({ + queryBuilderService: this.queryBuilderService, + verbose: (message) => this.verbose(message), + }); this.loadMigrations(); } private loadMigrations(): void { try { - const filePath = path.join(process.cwd(), 'data/snapshot-migration.json'); - if (fs.existsSync(filePath)) { - const content = fs.readFileSync(filePath, 'utf8'); - const parsed = JSON.parse(content); - if ( - parsed && - (parsed.tables?.length > 0 || parsed.tablesToDrop?.length > 0) - ) { - this.migrations = parsed; - this.verbose( - `Loaded snapshot-migration.json with ${parsed.tables?.length || 0} table migration(s)`, - ); - } + const migrations = loadSnapshotMigrationFile(); + if (migrations) { + this.migrations = migrations; + this.verbose( + `Loaded snapshot-migration.json with ${migrations.tables?.length || 0} table migration(s)`, + ); } } catch (error) { this.logger.warn( @@ -48,11 +62,7 @@ export class MetadataMigrationService { } hasMigrations(): boolean { - if (!this.migrations) return false; - return ( - (this.migrations.tables?.length ?? 0) > 0 || - (this.migrations.tablesToDrop?.length ?? 0) > 0 - ); + return hasSchemaMigrations(this.migrations); } private getMongoDb(): Db | null { @@ -66,13 +76,13 @@ export class MetadataMigrationService { return; } - this.verbose( - 'Running metadata migrations from snapshot-migration.json...', - ); + this.verbose('Running metadata migrations from snapshot-migration.json...'); const isMongoDB = this.queryBuilderService.isMongoDb(); const migrations = this.migrations!; + await this.runTableRenames(migrations.tablesToRename ?? [], isMongoDB); + const tablesToDrop = migrations.tablesToDrop ?? []; if (tablesToDrop.length > 0) { await this.dropTableMetadata(tablesToDrop, isMongoDB); @@ -85,41 +95,329 @@ export class MetadataMigrationService { this.verbose('Metadata migrations completed'); } + async runCoreTableRenamesBeforeMetadataSync(): Promise { + if (!this.migrations?.coreTablesToRename?.length) return; + + const isMongoDB = this.queryBuilderService.isMongoDb(); + if (isMongoDB) { + await this.runMongoCoreTableRenames(this.migrations.coreTablesToRename); + return; + } + + await this.runSqlCoreTableRenames(this.migrations.coreTablesToRename); + } + + async runTableRenamesBeforeMetadataSync(): Promise { + if (!this.migrations?.tablesToRename?.length) return; + + await this.runTableRenames( + this.migrations.tablesToRename, + this.queryBuilderService.isMongoDb(), + ); + + await this.physicalMigration.runPhysicalTableRenames( + this.migrations.physicalTablesToRename ?? [], + this.queryBuilderService.isMongoDb(), + ); + + await this.physicalMigration.dropPhysicalTables( + this.migrations.physicalTablesToDrop ?? [], + this.queryBuilderService.isMongoDb(), + ); + } + async runPhysicalMigrationsBeforeMetadataSync(): Promise { if (!this.hasMigrations()) return; - if (this.queryBuilderService.isMongoDb()) return; const migrations = this.migrations!; for (const tableMigration of migrations.tables || []) { const tableName = tableMigration._unique.name._eq; for (const columnMigration of tableMigration.columnsToModify || []) { if (columnMigration.from.name === columnMigration.to.name) continue; - await this.renameSqlPhysicalColumnIfNeeded( + if (this.queryBuilderService.isMongoDb()) { + await this.physicalMigration.renameMongoDocumentFieldIfNeeded( + tableName, + columnMigration.from.name, + columnMigration.to.name, + ); + } else { + await this.physicalMigration.renameSqlPhysicalColumnIfNeeded( + tableName, + columnMigration.from.name, + columnMigration.to.name, + ); + } + } + if (!this.queryBuilderService.isMongoDb()) continue; + for (const relationMigration of tableMigration.relationsToModify || []) { + if ( + relationMigration.from.propertyName === + relationMigration.to.propertyName + ) + continue; + await this.physicalMigration.renameMongoDocumentFieldIfNeeded( tableName, - columnMigration.from.name, - columnMigration.to.name, + relationMigration.from.propertyName, + relationMigration.to.propertyName, ); } } } + private async runTableRenames( + renames: TableRenameDef[], + isMongoDB: boolean, + ): Promise { + for (const rename of renames) { + if (!rename.from || !rename.to || rename.from === rename.to) continue; + if (isMongoDB) { + await this.renameMongoTable(rename); + } else { + await this.renameSqlTable(rename); + } + } + } + + private async runSqlCoreTableRenames( + renames: TableRenameDef[], + ): Promise { + const knex = this.queryBuilderService.getKnex(); + const validRenames = getValidTableRenames(renames); + + for (const rename of validRenames) { + const oldExists = await knex.schema.hasTable(rename.from); + const newExists = await knex.schema.hasTable(rename.to); + if (oldExists && newExists) { + throw new Error( + `Cannot rename core system table ${rename.from} to ${rename.to}: both physical tables exist`, + ); + } + } + + for (const rename of validRenames) { + const oldExists = await knex.schema.hasTable(rename.from); + const newExists = await knex.schema.hasTable(rename.to); + if (oldExists && !newExists) { + await knex.schema.renameTable(rename.from, rename.to); + this.verbose(` Renamed core SQL table: ${rename.from} → ${rename.to}`); + } + } + + for (const rename of validRenames) { + await this.renameSqlTableMetadataRow(SYSTEM_TABLES.table, rename); + await this.updateSqlCanonicalRoutePath(rename); + } + } + + private async runMongoCoreTableRenames( + renames: TableRenameDef[], + ): Promise { + const db = this.getMongoDb()!; + const validRenames = getValidTableRenames(renames); + + for (const rename of validRenames) { + const oldExists = await this.physicalMigration.mongoCollectionExists( + rename.from, + ); + const newExists = await this.physicalMigration.mongoCollectionExists( + rename.to, + ); + if (oldExists && newExists) { + throw new Error( + `Cannot rename core system collection ${rename.from} to ${rename.to}: both collections exist`, + ); + } + } + + for (const rename of validRenames) { + const oldExists = await this.physicalMigration.mongoCollectionExists( + rename.from, + ); + const newExists = await this.physicalMigration.mongoCollectionExists( + rename.to, + ); + if (oldExists && !newExists) { + await db.collection(rename.from).rename(rename.to); + this.verbose( + ` Renamed core Mongo collection: ${rename.from} → ${rename.to}`, + ); + } + } + + for (const rename of validRenames) { + await db + .collection(SYSTEM_TABLES.table) + .updateOne( + { name: rename.from }, + { $set: { name: rename.to, updatedAt: new Date() } }, + ); + await this.updateMongoCanonicalRoutePath(rename); + } + } + + private async renameSqlTable(rename: TableRenameDef): Promise { + const knex = this.queryBuilderService.getKnex(); + const oldExists = await knex.schema.hasTable(rename.from); + const newExists = await knex.schema.hasTable(rename.to); + + if (oldExists && newExists) { + throw new Error( + `Cannot rename system table ${rename.from} to ${rename.to}: both physical tables exist`, + ); + } + + const tableStoreBefore = + await this.systemCoreTableResolver.getTableName('table'); + const tableRecord = await this.findSqlTableRecord( + tableStoreBefore, + rename.from, + ); + await this.updateSqlCanonicalRoutePath(rename, tableRecord?.id); + + if (oldExists && !newExists) { + await knex.schema.renameTable(rename.from, rename.to); + this.verbose(` Renamed SQL table: ${rename.from} → ${rename.to}`); + } + + const tableStoreAfter = + await this.systemCoreTableResolver.getTableName('table'); + await this.renameSqlTableMetadataRow( + tableStoreAfter, + rename, + tableRecord?.id, + ); + } + + private async renameMongoTable(rename: TableRenameDef): Promise { + const db = this.getMongoDb()!; + const oldExists = await this.physicalMigration.mongoCollectionExists( + rename.from, + ); + const newExists = await this.physicalMigration.mongoCollectionExists( + rename.to, + ); + + if (oldExists && newExists) { + throw new Error( + `Cannot rename system collection ${rename.from} to ${rename.to}: both collections exist`, + ); + } + + const tableStoreBefore = + await this.systemCoreTableResolver.getTableName('table'); + const tableRecord = await db + .collection(tableStoreBefore) + .findOne({ name: rename.from }); + await this.updateMongoCanonicalRoutePath(rename, tableRecord?._id); + + if (oldExists && !newExists) { + await db.collection(rename.from).rename(rename.to); + this.verbose(` Renamed Mongo collection: ${rename.from} → ${rename.to}`); + } + + const tableStoreAfter = + await this.systemCoreTableResolver.getTableName('table'); + await db + .collection(tableStoreAfter) + .updateOne( + tableRecord?._id ? { _id: tableRecord._id } : { name: rename.from }, + { $set: { name: rename.to, updatedAt: new Date() } }, + ); + } + + private async findSqlTableRecord( + tableStore: string, + tableName: string, + ): Promise { + const knex = this.queryBuilderService.getKnex(); + if (!(await knex.schema.hasTable(tableStore))) return null; + return knex(tableStore).where({ name: tableName }).first(); + } + + private async renameSqlTableMetadataRow( + tableStore: string, + rename: TableRenameDef, + tableId?: any, + ): Promise { + const knex = this.queryBuilderService.getKnex(); + if (!(await knex.schema.hasTable(tableStore))) return; + const query = tableId + ? knex(tableStore).where({ id: tableId }) + : knex(tableStore).where({ name: rename.from }); + await query.update({ name: rename.to }); + } + + private async updateSqlCanonicalRoutePath( + rename: TableRenameDef, + tableId?: any, + ): Promise { + const routeTable = await this.detectSqlRouteTable(); + if (!routeTable) return; + + const knex = this.queryBuilderService.getKnex(); + const query = knex(routeTable).where({ path: `/${rename.from}` }); + if (tableId) query.andWhere({ mainTableId: tableId }); + await query.update({ path: `/${rename.to}` }); + } + + private async updateMongoCanonicalRoutePath( + rename: TableRenameDef, + tableId?: any, + ): Promise { + const routeTable = await this.detectMongoRouteTable(); + if (!routeTable) return; + + const filter: any = { path: `/${rename.from}` }; + if (tableId) filter.mainTable = tableId; + await this.getMongoDb()! + .collection(routeTable) + .updateMany(filter, { + $set: { path: `/${rename.to}`, updatedAt: new Date() }, + }); + } + + private async detectSqlRouteTable(): Promise { + const knex = this.queryBuilderService.getKnex(); + if (await knex.schema.hasTable(SYSTEM_TABLES.route)) + return SYSTEM_TABLES.route; + if (await knex.schema.hasTable('route_definition')) + return 'route_definition'; + return null; + } + + private async detectMongoRouteTable(): Promise { + if (await this.physicalMigration.mongoCollectionExists(SYSTEM_TABLES.route)) + return SYSTEM_TABLES.route; + if (await this.physicalMigration.mongoCollectionExists('route_definition')) + return 'route_definition'; + return null; + } + + private async mongoCollectionExists( + collectionName: string, + ): Promise { + const matches = await this.getMongoDb()! + .listCollections({ name: collectionName }) + .toArray(); + return matches.length > 0; + } + private async findTableId( tableName: string, isMongoDB: boolean, ): Promise<{ tableId: any; tableIdField: string } | null> { + const coreNames = await this.systemCoreTableResolver.getNames(); if (isMongoDB) { const db = this.getMongoDb()!; const table = await db - .collection('table_definition') + .collection(coreNames.table) .findOne({ name: tableName }); if (!table) return null; return { tableId: table._id, tableIdField: 'table' }; } const knex = this.queryBuilderService.getKnex(); - const table = await knex('table_definition') - .where('name', tableName) - .first(); + const table = await knex(coreNames.table).where('name', tableName).first(); if (!table) return null; return { tableId: table.id, tableIdField: 'tableId' }; } @@ -136,23 +434,22 @@ export class MetadataMigrationService { if (!found) continue; const { tableId } = found; + const coreNames = await this.systemCoreTableResolver.getNames(); if (isMongoDB) { const db = this.getMongoDb()!; await db - .collection('relation_definition') + .collection(coreNames.relation) .deleteMany({ sourceTable: tableId }); - await db - .collection('column_definition') - .deleteMany({ table: tableId }); - await db.collection('table_definition').deleteOne({ _id: tableId }); + await db.collection(coreNames.column).deleteMany({ table: tableId }); + await db.collection(coreNames.table).deleteOne({ _id: tableId }); } else { const knex = this.queryBuilderService.getKnex(); - await knex('relation_definition') + await knex(coreNames.relation) .where('sourceTableId', tableId) .delete(); - await knex('column_definition').where('tableId', tableId).delete(); - await knex('table_definition').where('id', tableId).delete(); + await knex(coreNames.column).where('tableId', tableId).delete(); + await knex(coreNames.table).where('id', tableId).delete(); } this.verbose(` Dropped metadata for table: ${tableName}`); @@ -221,31 +518,24 @@ export class MetadataMigrationService { isMongoDB: boolean, ): Promise { for (const mod of modifications) { - const hasChanges = - mod.to.name !== mod.from.name || - (mod.to.isNullable !== undefined && - mod.to.isNullable !== mod.from.isNullable) || - (mod.to.isUpdatable !== undefined && - mod.to.isUpdatable !== mod.from.isUpdatable) || - mod.to.description !== undefined; - - if (!hasChanges) { + if (!hasColumnMetadataChanges(mod)) { continue; } const oldName = mod.from.name; try { + const coreNames = await this.systemCoreTableResolver.getNames(); let columnId: any; let targetColumnId: any; if (isMongoDB) { const db = this.getMongoDb()!; - const column = await db.collection('column_definition').findOne({ + const column = await db.collection(coreNames.column).findOne({ table: tableId, name: oldName, }); - const targetColumn = await db.collection('column_definition').findOne({ + const targetColumn = await db.collection(coreNames.column).findOne({ table: tableId, name: mod.to.name, }); @@ -253,7 +543,7 @@ export class MetadataMigrationService { targetColumnId = targetColumn?._id; if (mod.to.name !== mod.from.name) { - await this.renameMongoDocumentFieldIfNeeded( + await this.physicalMigration.renameMongoDocumentFieldIfNeeded( tableName, mod.from.name, mod.to.name, @@ -261,11 +551,11 @@ export class MetadataMigrationService { } } else { const knex = this.queryBuilderService.getKnex(); - const column = await knex('column_definition') + const column = await knex(coreNames.column) .where(tableIdField, tableId) .where('name', oldName) .first(); - const targetColumn = await knex('column_definition') + const targetColumn = await knex(coreNames.column) .where(tableIdField, tableId) .where('name', mod.to.name) .first(); @@ -275,29 +565,10 @@ export class MetadataMigrationService { if (!columnId && !targetColumnId) continue; - const updateData: any = {}; - - if (mod.to.name !== mod.from.name) { - updateData.name = mod.to.name; - } - if ( - mod.to.isNullable !== undefined && - mod.to.isNullable !== mod.from.isNullable - ) { - updateData.isNullable = mod.to.isNullable; - } - if ( - mod.to.isUpdatable !== undefined && - mod.to.isUpdatable !== mod.from.isUpdatable - ) { - updateData.isUpdatable = mod.to.isUpdatable; - } - if (mod.to.description !== undefined) { - updateData.description = mod.to.description; - } + const updateData = buildColumnMetadataUpdate(mod); if (mod.to.name !== mod.from.name && !isMongoDB) { - await this.renameSqlPhysicalColumnIfNeeded( + await this.physicalMigration.renameSqlPhysicalColumnIfNeeded( tableName, mod.from.name, mod.to.name, @@ -308,7 +579,7 @@ export class MetadataMigrationService { if (isMongoDB) { const db = this.getMongoDb()!; updateData.updatedAt = new Date(); - await db.collection('column_definition').updateOne( + await db.collection(coreNames.column).updateOne( { _id: targetColumnId ?? columnId }, { $set: targetColumnId @@ -318,7 +589,7 @@ export class MetadataMigrationService { ); } else { const knex = this.queryBuilderService.getKnex(); - await knex('column_definition') + await knex(coreNames.column) .where('id', targetColumnId ?? columnId) .update(updateData); } @@ -330,12 +601,10 @@ export class MetadataMigrationService { if (targetColumnId && columnId && targetColumnId !== columnId) { if (isMongoDB) { const db = this.getMongoDb()!; - await db - .collection('column_definition') - .deleteOne({ _id: columnId }); + await db.collection(coreNames.column).deleteOne({ _id: columnId }); } else { const knex = this.queryBuilderService.getKnex(); - await knex('column_definition').where('id', columnId).delete(); + await knex(coreNames.column).where('id', columnId).delete(); } this.verbose(` Removed duplicate old column metadata: ${oldName}`); } @@ -347,66 +616,6 @@ export class MetadataMigrationService { } } - private async renameMongoDocumentFieldIfNeeded( - tableName: string, - oldName: string, - newName: string, - ): Promise { - const db = this.getMongoDb(); - if (!db) return; - - await db.collection(tableName).updateMany( - { [oldName]: { $exists: true }, [newName]: { $exists: false } }, - [{ $set: { [newName]: `$${oldName}` } }], - ); - await db - .collection(tableName) - .updateMany( - { [oldName]: { $exists: true } }, - { $unset: { [oldName]: '' } }, - ); - this.verbose( - ` Renamed document field: ${tableName}.${oldName} → ${newName}`, - ); - } - - private async renameSqlPhysicalColumnIfNeeded( - tableName: string, - oldName: string, - newName: string, - ): Promise { - const knex = this.queryBuilderService.getKnex(); - if (!knex?.schema?.hasTable) return; - if (!(await knex.schema.hasTable(tableName))) return; - - const oldExists = await knex.schema.hasColumn(tableName, oldName); - const newExists = await knex.schema.hasColumn(tableName, newName); - if (!oldExists) return; - - if (newExists) { - await knex.raw('UPDATE ?? SET ?? = ?? WHERE ?? IS NULL', [ - tableName, - newName, - oldName, - newName, - ]); - await knex.schema.alterTable(tableName, (table: any) => { - table.dropColumn(oldName); - }); - this.verbose( - ` Dropped duplicate old physical column: ${tableName}.${oldName}`, - ); - return; - } - - await knex.schema.alterTable(tableName, (table: any) => { - table.renameColumn(oldName, newName); - }); - this.verbose( - ` Renamed physical column: ${tableName}.${oldName} → ${newName}`, - ); - } - private async removeColumnMetadata( tableName: string, tableId: any, @@ -416,6 +625,7 @@ export class MetadataMigrationService { ): Promise { for (const colName of columns) { try { + const coreNames = await this.systemCoreTableResolver.getNames(); await this.copyLegacyScriptColumnBeforeRemove( tableName, colName, @@ -425,19 +635,19 @@ export class MetadataMigrationService { if (isMongoDB) { const db = this.getMongoDb()!; const result = await db - .collection('column_definition') + .collection(coreNames.column) .deleteOne({ table: tableId, name: colName }); if (result.deletedCount > 0) { this.verbose(` Removed column metadata: ${colName}`); } } else { const knex = this.queryBuilderService.getKnex(); - const column = await knex('column_definition') + const column = await knex(coreNames.column) .where(tableIdField, tableId) .where('name', colName) .first(); if (column) { - await knex('column_definition').where('id', column.id).delete(); + await knex(coreNames.column).where('id', column.id).delete(); this.verbose(` Removed column metadata: ${colName}`); } } @@ -446,7 +656,11 @@ export class MetadataMigrationService { !isMongoDB || !(await this.isMongoRelationField(tableId, colName)) ) { - await this.dropPhysicalColumn(tableName, colName, isMongoDB); + await this.physicalMigration.dropPhysicalColumn( + tableName, + colName, + isMongoDB, + ); } } catch (err) { this.logger.warn( @@ -462,8 +676,9 @@ export class MetadataMigrationService { ): Promise { const db = this.getMongoDb(); if (!db) return false; + const coreNames = await this.systemCoreTableResolver.getNames(); - const relation = await db.collection('relation_definition').findOne({ + const relation = await db.collection(coreNames.relation).findOne({ sourceTable: tableId, propertyName, }); @@ -474,15 +689,7 @@ export class MetadataMigrationService { tableName: string, colName: string, ): string | null { - const legacyMap: Record = { - route_handler_definition: 'logic', - pre_hook_definition: 'code', - post_hook_definition: 'code', - bootstrap_script_definition: 'logic', - websocket_definition: 'connectionHandlerScript', - websocket_event_definition: 'handlerScript', - }; - return legacyMap[tableName] === colName ? 'sourceCode' : null; + return getLegacyScriptTargetColumn(tableName, colName); } private async copyLegacyScriptColumnBeforeRemove( @@ -565,34 +772,25 @@ export class MetadataMigrationService { const sourceTableField = isMongoDB ? 'sourceTable' : 'sourceTableId'; for (const mod of modifications) { - const hasChanges = - mod.to.propertyName !== mod.from.propertyName || - (mod.to.mappedBy !== undefined && - mod.to.mappedBy !== mod.from.mappedBy) || - (mod.to.isNullable !== undefined && - mod.to.isNullable !== mod.from.isNullable) || - (mod.to.isUpdatable !== undefined && - mod.to.isUpdatable !== mod.from.isUpdatable) || - mod.to.onDelete !== undefined; - - if (!hasChanges) { + if (!hasRelationMetadataChanges(mod)) { continue; } const oldName = mod.from.propertyName; try { + const coreNames = await this.systemCoreTableResolver.getNames(); let relation: any; if (isMongoDB) { const db = this.getMongoDb()!; - relation = await db.collection('relation_definition').findOne({ + relation = await db.collection(coreNames.relation).findOne({ sourceTable: tableId, propertyName: oldName, }); } else { const knex = this.queryBuilderService.getKnex(); - relation = await knex('relation_definition') + relation = await knex(coreNames.relation) .where(sourceTableField, tableId) .where('propertyName', oldName) .first(); @@ -615,17 +813,15 @@ export class MetadataMigrationService { if (mod.to.mappedBy && isMongoDB) { const db = this.getMongoDb()!; const targetTableId = relation.targetTable; - const owningRel = await db - .collection('relation_definition') - .findOne({ - sourceTable: targetTableId, - propertyName: mod.to.mappedBy, - }); + const owningRel = await db.collection(coreNames.relation).findOne({ + sourceTable: targetTableId, + propertyName: mod.to.mappedBy, + }); updateData.mappedBy = owningRel?._id || null; } else if (mod.to.mappedBy && !isMongoDB) { const knex = this.queryBuilderService.getKnex(); const targetTableId = relation.targetTableId; - const owningRel = await knex('relation_definition') + const owningRel = await knex(coreNames.relation) .where('sourceTableId', targetTableId) .where('propertyName', mod.to.mappedBy) .first(); @@ -656,11 +852,11 @@ export class MetadataMigrationService { const db = this.getMongoDb()!; updateData.updatedAt = new Date(); await db - .collection('relation_definition') + .collection(coreNames.relation) .updateOne({ _id: relationId }, { $set: updateData }); } else { const knex = this.queryBuilderService.getKnex(); - await knex('relation_definition') + await knex(coreNames.relation) .where('id', relationId) .update(updateData); } @@ -685,22 +881,23 @@ export class MetadataMigrationService { for (const relName of relations) { try { + const coreNames = await this.systemCoreTableResolver.getNames(); if (isMongoDB) { const db = this.getMongoDb()!; const result = await db - .collection('relation_definition') + .collection(coreNames.relation) .deleteOne({ sourceTable: tableId, propertyName: relName }); if (result.deletedCount > 0) { this.verbose(` Removed relation metadata: ${relName}`); } } else { const knex = this.queryBuilderService.getKnex(); - const relation = await knex('relation_definition') + const relation = await knex(coreNames.relation) .where(sourceTableField, tableId) .where('propertyName', relName) .first(); if (relation) { - await knex('relation_definition').where('id', relation.id).delete(); + await knex(coreNames.relation).where('id', relation.id).delete(); this.verbose(` Removed relation metadata: ${relName}`); } } diff --git a/src/engines/bootstrap/services/metadata-provision-mongo.service.ts b/src/engines/bootstrap/services/metadata-provision-mongo.service.ts index 12cc3121..b7bab8f5 100644 --- a/src/engines/bootstrap/services/metadata-provision-mongo.service.ts +++ b/src/engines/bootstrap/services/metadata-provision-mongo.service.ts @@ -9,6 +9,7 @@ import { buildMongoFullIndexSpecs } from '../../mongo'; import { normalizeMongoPrimaryKeyColumn } from '../../../modules/table-management/utils/mongo-primary-key.util'; import { bootstrapVerboseLog } from '../utils/bootstrap-logging.util'; import { getSqlJunctionPhysicalNames } from '../../../modules/table-management/utils/sql-junction-naming.util'; +import { SystemCoreTableResolver } from './system-core-table-resolver.service'; class TableDefinitionProcessor extends BaseTableProcessor { async transformRecords(records: any[]): Promise { const now = new Date(); @@ -89,8 +90,13 @@ class RelationDefinitionProcessor extends BaseTableProcessor { export class MetadataProvisionMongoService { private readonly logger = new Logger(MetadataProvisionMongoService.name); private readonly queryBuilderService: QueryBuilderService; - constructor(deps: { queryBuilderService: QueryBuilderService }) { + private readonly systemCoreTableResolver: SystemCoreTableResolver; + constructor(deps: { + queryBuilderService: QueryBuilderService; + systemCoreTableResolver: SystemCoreTableResolver; + }) { this.queryBuilderService = deps.queryBuilderService; + this.systemCoreTableResolver = deps.systemCoreTableResolver; } private async syncPhysicalIndexesFromSnapshot( snapshot: any, @@ -141,13 +147,14 @@ export class MetadataProvisionMongoService { async createInitMetadata(snapshot: any): Promise { this.verbose('MongoDB: Creating metadata from snapshot...'); const db = this.queryBuilderService.getMongoDb(); + const coreNames = await this.systemCoreTableResolver.getNames(); const tableNameToId: Record = {}; - const columnDef = snapshot['column_definition']; + const columnDef = snapshot[coreNames.column]; const tableRelation = columnDef?.relations?.find( - (r: any) => r.targetTable === 'table_definition', + (r: any) => r.targetTable === coreNames.table, ); const tableFieldName = tableRelation?.propertyName || 'table'; - const relationDef = snapshot['relation_definition']; + const relationDef = snapshot[coreNames.relation]; const sourceTableRelation = relationDef?.relations?.find( (r: any) => r.propertyName === 'sourceTable', ); @@ -167,9 +174,9 @@ export class MetadataProvisionMongoService { sourceTableFieldName, ); this.verbose('Step 1: Upserting tables...'); - const tableDef = snapshot['table_definition']; + const tableDef = snapshot[coreNames.table]; if (!tableDef || !tableDef.columns) { - throw new Error('table_definition not found in snapshot'); + throw new Error(`${coreNames.table} not found in snapshot`); } const tableRecords = Object.entries(snapshot).map( ([_tableName, defRaw]) => { @@ -183,7 +190,7 @@ export class MetadataProvisionMongoService { const tableResult = await tableProcessor.processMongo( tableRecords, db, - 'table_definition', + coreNames.table, ); this.verbose( `Tables: ${tableResult.created} created, ${tableResult.skipped} skipped`, @@ -191,7 +198,7 @@ export class MetadataProvisionMongoService { for (const [tableName, defRaw] of Object.entries(snapshot)) { const def = defRaw as any; const table = await db - .collection('table_definition') + .collection(coreNames.table) .findOne({ name: def.name }); if (table) { tableNameToId[tableName] = table._id; @@ -199,7 +206,7 @@ export class MetadataProvisionMongoService { } this.verbose('Step 2: Upserting columns...'); if (!columnDef || !columnDef.columns) { - throw new Error('column_definition not found in snapshot'); + throw new Error(`${coreNames.column} not found in snapshot`); } for (const [tableName, defRaw] of Object.entries(snapshot)) { const def = defRaw as any; @@ -212,7 +219,7 @@ export class MetadataProvisionMongoService { columnDef.columns, ); record[tableFieldName] = tableId; - if (tableName === 'table_definition' && col.name === 'name') { + if (tableName === coreNames.table && col.name === 'name') { this.logger.debug( `Sample column record: ${JSON.stringify(record, null, 2)}`, ); @@ -223,7 +230,7 @@ export class MetadataProvisionMongoService { const columnResult = await columnProcessor.processMongo( columnRecords, db, - 'column_definition', + coreNames.column, ); this.logger.debug( `${tableName} columns: ${columnResult.created} created, ${columnResult.skipped} skipped`, @@ -232,9 +239,9 @@ export class MetadataProvisionMongoService { } this.verbose('Step 3: Upserting owning relations...'); const relationRenameMap = loadRelationRenameMap(); - const relationColl = db.collection('relation_definition'); + const relationColl = db.collection(coreNames.relation); if (!relationDef || !relationDef.columns) { - throw new Error('relation_definition not found in snapshot'); + throw new Error(`${coreNames.relation} not found in snapshot`); } const owningIdMap = new Map(); const pendingInverses: Array<{ @@ -344,7 +351,7 @@ export class MetadataProvisionMongoService { await relationProcessor.processMongo( [directRelationRecord], db, - 'relation_definition', + coreNames.relation, ); const insertedDoc = await relationColl.findOne({ [sourceTableFieldName]: tableId, @@ -419,7 +426,7 @@ export class MetadataProvisionMongoService { await relationProcessor.processMongo( [reverseRelationRecord], db, - 'relation_definition', + coreNames.relation, ); this.logger.debug( `Added generated reverse relation ${sourceTableName}.${propertyName} for ${targetTableName}`, @@ -545,7 +552,7 @@ export class MetadataProvisionMongoService { await relationProcessor.processMongo( [inverseRelationRecord], db, - 'relation_definition', + coreNames.relation, ); if (isGeneratedManyToOne && snapshotRelId) { const insertedDoc = await relationColl.findOne({ diff --git a/src/engines/bootstrap/services/metadata-provision-sql.service.ts b/src/engines/bootstrap/services/metadata-provision-sql.service.ts index dc7d17e3..cc20c215 100644 --- a/src/engines/bootstrap/services/metadata-provision-sql.service.ts +++ b/src/engines/bootstrap/services/metadata-provision-sql.service.ts @@ -15,21 +15,25 @@ import { } from '../../knex'; import { loadRelationRenameMap } from '../../../domain/bootstrap'; import { bootstrapVerboseLog } from '../utils/bootstrap-logging.util'; +import { SystemCoreTableResolver } from './system-core-table-resolver.service'; export class MetadataProvisionSqlService { private readonly logger = new Logger(MetadataProvisionSqlService.name); private readonly queryBuilderService: QueryBuilderService; private readonly databaseConfigService: DatabaseConfigService; private readonly schemaMigrationService: SqlSchemaMigrationService; + private readonly systemCoreTableResolver: SystemCoreTableResolver; private readonly dbType: string; constructor(deps: { queryBuilderService: QueryBuilderService; databaseConfigService: DatabaseConfigService; sqlSchemaMigrationService: SqlSchemaMigrationService; + systemCoreTableResolver: SystemCoreTableResolver; }) { this.queryBuilderService = deps.queryBuilderService; this.databaseConfigService = deps.databaseConfigService; this.schemaMigrationService = deps.sqlSchemaMigrationService; + this.systemCoreTableResolver = deps.systemCoreTableResolver; this.dbType = this.databaseConfigService.getDbType(); } private async insertAndGetId( @@ -47,17 +51,14 @@ export class MetadataProvisionSqlService { } private async ensureCoreTables(): Promise { const qb = this.queryBuilderService.getConnection(); - const coreTables = [ - 'table_definition', - 'column_definition', - 'relation_definition', - ]; + const coreNames = await this.systemCoreTableResolver.getNames(); + const coreTables = [coreNames.table, coreNames.column, coreNames.relation]; for (const tableName of coreTables) { const exists = await qb.schema.hasTable(tableName); if (!exists) { this.verbose(`Creating core table: ${tableName}`); - if (tableName === 'table_definition') { + if (tableName === coreNames.table) { await qb.schema.createTable(tableName, (table: any) => { table.increments('id').primary(); table.string('name').notNullable().unique(); @@ -71,7 +72,7 @@ export class MetadataProvisionSqlService { table.timestamp('createdAt').defaultTo(qb.fn.now()); table.timestamp('updatedAt').defaultTo(qb.fn.now()); }); - } else if (tableName === 'column_definition') { + } else if (tableName === coreNames.column) { await qb.schema.createTable(tableName, (table: any) => { table.increments('id').primary(); table @@ -79,7 +80,7 @@ export class MetadataProvisionSqlService { .notNullable() .unsigned() .references('id') - .inTable('table_definition') + .inTable(coreNames.table) .onDelete('CASCADE'); table.string('name').notNullable(); table.string('type').notNullable(); @@ -98,7 +99,7 @@ export class MetadataProvisionSqlService { table.timestamp('createdAt').defaultTo(qb.fn.now()); table.timestamp('updatedAt').defaultTo(qb.fn.now()); }); - } else if (tableName === 'relation_definition') { + } else if (tableName === coreNames.relation) { await qb.schema.createTable(tableName, (table: any) => { table.increments('id').primary(); table @@ -106,21 +107,21 @@ export class MetadataProvisionSqlService { .notNullable() .unsigned() .references('id') - .inTable('table_definition') + .inTable(coreNames.table) .onDelete('CASCADE'); table .integer('targetTableId') .nullable() .unsigned() .references('id') - .inTable('table_definition') + .inTable(coreNames.table) .onDelete('SET NULL'); table .integer('mappedById') .nullable() .unsigned() .references('id') - .inTable('relation_definition') + .inTable(coreNames.relation) .onDelete('CASCADE'); table.string('type').notNullable(); table.string('propertyName').notNullable(); @@ -143,24 +144,25 @@ export class MetadataProvisionSqlService { table.timestamp('updatedAt').defaultTo(qb.fn.now()); }); } - } else if (tableName === 'relation_definition') { - await this.ensureRelationDefinitionPhysicalColumns(qb); + } else if (tableName === coreNames.relation) { + await this.ensureRelationDefinitionPhysicalColumns(qb, coreNames.relation); } } } private async ensureRelationDefinitionPhysicalColumns( qb: any, + relationTableName: string, ): Promise { const columns = ['foreignKeyColumn', 'referencedColumn', 'constraintName']; for (const columnName of columns) { const hasColumn = await qb.schema.hasColumn( - 'relation_definition', + relationTableName, columnName, ); if (hasColumn) continue; - await qb.schema.alterTable('relation_definition', (table: any) => { + await qb.schema.alterTable(relationTableName, (table: any) => { table.string(columnName).nullable(); }); } @@ -169,6 +171,7 @@ export class MetadataProvisionSqlService { async createInitMetadata(snapshot: any): Promise { const qb = this.queryBuilderService.getConnection(); await this.ensureCoreTables(); + const coreNames = await this.systemCoreTableResolver.getNames(); let hasExistingMetadata = false; await qb.transaction(async (trx: any) => { const tableNameToId: Record = {}; @@ -176,7 +179,7 @@ export class MetadataProvisionSqlService { const tableEntries = Object.entries(snapshot); let existingTables: any[] = []; try { - existingTables = await trx('table_definition').select('*'); + existingTables = await trx(coreNames.table).select('*'); } catch (error: any) { if (error.code !== 'ER_NO_SUCH_TABLE') { throw error; @@ -199,7 +202,7 @@ export class MetadataProvisionSqlService { tableNameToId[name] = exist.id; const { columns: _c, relations: _r, ...rest } = def; if (this.detectTableChanges(rest, exist)) { - await trx('table_definition') + await trx(coreNames.table) .where('id', exist.id) .update({ isSystem: rest.isSystem, @@ -220,7 +223,7 @@ export class MetadataProvisionSqlService { } const insertedId = await this.insertAndGetId( trx, - 'table_definition', + coreNames.table, { name: rest.name, isSystem: rest.isSystem || false, @@ -239,7 +242,7 @@ export class MetadataProvisionSqlService { this.verbose('Phase 2: Processing column definitions...'); let allColumns: any[] = []; try { - allColumns = await trx('column_definition').select('*'); + allColumns = await trx(coreNames.column).select('*'); } catch (error: any) { if (error.code !== 'ER_NO_SUCH_TABLE') { throw error; @@ -259,7 +262,7 @@ export class MetadataProvisionSqlService { for (const snapshotCol of def.columns || []) { const existingCol = existingColumnsMap.get(snapshotCol.name); if (!existingCol) { - await trx('column_definition').insert({ + await trx(coreNames.column).insert({ name: snapshotCol.name, type: snapshotCol.type, isPrimary: snapshotCol.isPrimary || false, @@ -276,7 +279,7 @@ export class MetadataProvisionSqlService { tableId, }); } else if (this.detectColumnChanges(snapshotCol, existingCol)) { - await trx('column_definition') + await trx(coreNames.column) .where('id', existingCol.id) .update({ type: snapshotCol.type, @@ -297,7 +300,7 @@ export class MetadataProvisionSqlService { this.verbose('Phase 3: Processing relation definitions...'); let allRelations: any[] = []; try { - allRelations = await trx('relation_definition').select('*'); + allRelations = await trx(coreNames.relation).select('*'); } catch (error: any) { if (error.code !== 'ER_NO_SUCH_TABLE') { throw error; @@ -464,7 +467,7 @@ export class MetadataProvisionSqlService { existingRel.junctionTargetColumn || getForeignKeyColumnName(rel.targetTable); } - await trx('relation_definition') + await trx(coreNames.relation) .where('id', existingRel.id) .update(updateData); } @@ -503,7 +506,7 @@ export class MetadataProvisionSqlService { } const id = await this.insertAndGetId( trx, - 'relation_definition', + coreNames.relation, insertData, ); const newRel = { ...insertData, id }; @@ -544,7 +547,7 @@ export class MetadataProvisionSqlService { if (generatedId) relationIdMap.set(`${tableName}.${rel.propertyName}`, generatedId); if (snapshotRelId && generatedId) { - await trx('relation_definition') + await trx(coreNames.relation) .where('id', snapshotRelId) .update({ mappedById: generatedId }); } else if (!snapshotRelId && generatedId) { @@ -573,7 +576,7 @@ export class MetadataProvisionSqlService { if (rel.type === 'many-to-many' && snapshotRelId) { const owningRel = allRelations.find((r: any) => r.id === snapshotRelId) || - (await trx('relation_definition') + (await trx(coreNames.relation) .where('id', snapshotRelId) .first()); if (owningRel) { diff --git a/src/engines/bootstrap/services/schema-healing.service.ts b/src/engines/bootstrap/services/schema-healing.service.ts index 8bf772c7..d3285ef0 100644 --- a/src/engines/bootstrap/services/schema-healing.service.ts +++ b/src/engines/bootstrap/services/schema-healing.service.ts @@ -16,18 +16,22 @@ import { import { getSqlJunctionPhysicalNames } from '../../../modules/table-management/utils/sql-junction-naming.util'; import { buildSqlJunctionTableContract } from '../../knex/utils/sql-physical-schema-contract'; import { addColumnToTable } from '../../knex/utils/migration/column-operations'; +import { SystemCoreTableResolver } from './system-core-table-resolver.service'; export class SchemaHealingService { private readonly logger = new Logger(SchemaHealingService.name); private readonly queryBuilderService: QueryBuilderService; private readonly metadataCacheService: MetadataCacheService; + private readonly systemCoreTableResolver: SystemCoreTableResolver; constructor(deps: { queryBuilderService: QueryBuilderService; metadataCacheService: MetadataCacheService; + systemCoreTableResolver: SystemCoreTableResolver; }) { this.queryBuilderService = deps.queryBuilderService; this.metadataCacheService = deps.metadataCacheService; + this.systemCoreTableResolver = deps.systemCoreTableResolver; } async runIfNeeded(): Promise { @@ -157,9 +161,10 @@ export class SchemaHealingService { private async repairSqlRelationPhysicalMappings(): Promise { const knex = this.queryBuilderService.getKnex(); - const rows = await knex('relation_definition as r') + const coreNames = await this.systemCoreTableResolver.getNames(); + const rows = await knex(`${coreNames.relation} as r`) .leftJoin( - 'table_definition as sourceTable', + `${coreNames.table} as sourceTable`, 'r.sourceTableId', 'sourceTable.id', ) @@ -187,7 +192,7 @@ export class SchemaHealingService { if (!rel.constraintName) updateData.constraintName = constraintName; if (Object.keys(updateData).length === 0) continue; - await knex('relation_definition').where({ id: rel.id }).update(updateData); + await knex(coreNames.relation).where({ id: rel.id }).update(updateData); repaired++; } @@ -197,10 +202,11 @@ export class SchemaHealingService { private async repairSqlSystemPhysicalColumns(): Promise { const knex = this.queryBuilderService.getKnex(); if (!knex?.schema?.hasTable) return 0; - if (!(await knex.schema.hasTable('table_definition'))) return 0; - if (!(await knex.schema.hasTable('column_definition'))) return 0; + const coreNames = await this.systemCoreTableResolver.getNames(); + if (!(await knex.schema.hasTable(coreNames.table))) return 0; + if (!(await knex.schema.hasTable(coreNames.column))) return 0; - const systemTables = await knex('table_definition') + const systemTables = await knex(coreNames.table) .where({ isSystem: true }) .select('id', 'name'); let repaired = 0; @@ -209,7 +215,7 @@ export class SchemaHealingService { if (!tableDef?.id || !tableDef?.name) continue; if (!(await knex.schema.hasTable(tableDef.name))) continue; - const columns = await knex('column_definition') + const columns = await knex(coreNames.column) .where({ tableId: tableDef.id }) .select('*'); for (const column of columns) { @@ -267,8 +273,9 @@ export class SchemaHealingService { ): Promise { const knex = this.queryBuilderService.getKnex(); if (!knex?.schema?.hasTable) return 0; - if (!(await knex.schema.hasTable('table_definition'))) return 0; - if (!(await knex.schema.hasTable('column_definition'))) return 0; + const coreNames = await this.systemCoreTableResolver.getNames(); + if (!(await knex.schema.hasTable(coreNames.table))) return 0; + if (!(await knex.schema.hasTable(coreNames.column))) return 0; let repaired = 0; for (const tableDef of Object.values(snapshot)) { @@ -277,19 +284,19 @@ export class SchemaHealingService { continue; } - const tableRecord = await knex('table_definition') + const tableRecord = await knex(coreNames.table) .where({ name: tableName }) .first(); if (!tableRecord?.id) continue; - const existingColumns = await knex('column_definition') + const existingColumns = await knex(coreNames.column) .where({ tableId: tableRecord.id }) .select('name'); const existingNames = new Set(existingColumns.map((column: any) => column.name)); for (const column of tableDef.columns) { if (!column?.name || existingNames.has(column.name)) continue; - await knex('column_definition').insert({ + await knex(coreNames.column).insert({ name: column.name, type: column.type, isPrimary: column.isPrimary || false, @@ -317,9 +324,10 @@ export class SchemaHealingService { ): Promise { const db = this.queryBuilderService.getMongoDb?.(); if (!db) return 0; + const coreNames = await this.systemCoreTableResolver.getNames(); - const tableCollection = db.collection('table_definition'); - const columnCollection = db.collection('column_definition'); + const tableCollection = db.collection(coreNames.table); + const columnCollection = db.collection(coreNames.column); let repaired = 0; for (const tableDef of Object.values(snapshot)) { @@ -365,14 +373,15 @@ export class SchemaHealingService { private async healSqlJunctionContracts(): Promise { const knex = this.queryBuilderService.getKnex(); - const rows = await knex('relation_definition as r') + const coreNames = await this.systemCoreTableResolver.getNames(); + const rows = await knex(`${coreNames.relation} as r`) .leftJoin( - 'table_definition as sourceTable', + `${coreNames.table} as sourceTable`, 'r.sourceTableId', 'sourceTable.id', ) .leftJoin( - 'table_definition as targetTable', + `${coreNames.table} as targetTable`, 'r.targetTableId', 'targetTable.id', ) @@ -417,7 +426,7 @@ export class SchemaHealingService { const owningUpdate = this.diffJunctionMetadata(rel, standard); if (Object.keys(owningUpdate).length > 0) { - await knex('relation_definition').where({ id: rel.id }).update(owningUpdate); + await knex(coreNames.relation).where({ id: rel.id }).update(owningUpdate); repaired++; } @@ -432,7 +441,7 @@ export class SchemaHealingService { inverseStandard, ); if (Object.keys(inverseUpdate).length === 0) continue; - await knex('relation_definition') + await knex(coreNames.relation) .where({ id: inverseRel.id }) .update(inverseUpdate); repaired++; @@ -602,8 +611,9 @@ export class SchemaHealingService { private async healMongoJunctionContracts(): Promise { const db = this.queryBuilderService.getMongoDb(); - const relations = await db.collection('relation_definition').find({}).toArray(); - const tables = await db.collection('table_definition').find({}).toArray(); + const coreNames = await this.systemCoreTableResolver.getNames(); + const relations = await db.collection(coreNames.relation).find({}).toArray(); + const tables = await db.collection(coreNames.table).find({}).toArray(); const tableById = new Map( tables.map((table: any) => [String(table._id), table]), ); @@ -657,7 +667,7 @@ export class SchemaHealingService { const owningUpdate = this.diffJunctionMetadata(rel, standard); if (Object.keys(owningUpdate).length > 0) { await db - .collection('relation_definition') + .collection(coreNames.relation) .updateOne({ _id: rel._id }, { $set: owningUpdate }); repaired++; } @@ -670,7 +680,7 @@ export class SchemaHealingService { }); if (Object.keys(inverseUpdate).length === 0) continue; await db - .collection('relation_definition') + .collection(coreNames.relation) .updateOne({ _id: inverseRel._id }, { $set: inverseUpdate }); repaired++; } @@ -683,8 +693,9 @@ export class SchemaHealingService { private async cleanupMongoLegacyJunctionCollections(): Promise { const db = this.queryBuilderService.getMongoDb(); - const relations = await db.collection('relation_definition').find({}).toArray(); - const tables = await db.collection('table_definition').find({}).toArray(); + const coreNames = await this.systemCoreTableResolver.getNames(); + const relations = await db.collection(coreNames.relation).find({}).toArray(); + const tables = await db.collection(coreNames.table).find({}).toArray(); const tableById = new Map( tables.map((table: any) => [String(table._id), table]), ); @@ -1003,9 +1014,10 @@ export class SchemaHealingService { } private async repairMongoRelationPhysicalMappings(): Promise { + const coreNames = await this.systemCoreTableResolver.getNames(); const collection = this.queryBuilderService .getMongoDb() - .collection('relation_definition'); + .collection(coreNames.relation); const relations = await collection.find({}).toArray(); const relationsById = new Map( relations.map((rel: any) => [String(rel._id), rel]), @@ -1056,8 +1068,9 @@ export class SchemaHealingService { private async repairMongoSystemRecordShapes(): Promise { const db = this.queryBuilderService.getMongoDb(); + const coreNames = await this.systemCoreTableResolver.getNames(); const tables = await db - .collection('table_definition') + .collection(coreNames.table) .find({ isSystem: true }) .toArray(); let repairedCollections = 0; @@ -1065,7 +1078,7 @@ export class SchemaHealingService { for (const table of tables) { const tableId = table._id; const columns = await db - .collection('column_definition') + .collection(coreNames.column) .find({ table: tableId }) .toArray(); const missingFieldSet: Record = {}; @@ -1174,13 +1187,13 @@ export class SchemaHealingService { } private async repairMongoPrimaryKeyColumns(): Promise { - const result = await this.queryBuilderService.find({ - table: 'column_definition', - limit: 10000, - }); - const columns = result?.data || []; + const coreNames = await this.systemCoreTableResolver.getNames(); + const db = this.queryBuilderService.getMongoDb(); + const columnCollection = db.collection(coreNames.column); + const columns = await columnCollection + .find({ isPrimary: true, name: { $in: ['id', MONGO_PRIMARY_KEY_NAME] } }) + .toArray(); let repaired = 0; - const idField = DatabaseConfigService.getPkField(); for (const primaryColumn of columns) { if (primaryColumn.isPrimary !== true) continue; @@ -1200,10 +1213,24 @@ export class SchemaHealingService { const columnId = primaryColumn._id ?? primaryColumn.id; if (!columnId) continue; - await this.queryBuilderService.update( - 'column_definition', - { where: [{ field: idField, operator: '=', value: columnId }] }, - { name: MONGO_PRIMARY_KEY_NAME, type: MONGO_PRIMARY_KEY_TYPE }, + if (primaryColumn.name === 'id') { + const duplicate = await columnCollection.findOne({ + table: primaryColumn.table, + name: MONGO_PRIMARY_KEY_NAME, + }); + if (duplicate?._id) { + await columnCollection.deleteOne({ _id: columnId }); + repaired++; + this.logger.log( + `Removed duplicate Mongo primary key column metadata '${primaryColumn.name}' for table '${primaryColumn.table}'`, + ); + continue; + } + } + + await columnCollection.updateOne( + { _id: columnId }, + { $set: { name: MONGO_PRIMARY_KEY_NAME, type: MONGO_PRIMARY_KEY_TYPE } }, ); repaired++; this.logger.log( @@ -1239,7 +1266,7 @@ export class SchemaHealingService { const idField = DatabaseConfigService.getPkField(); await this.queryBuilderService.update( - 'table_definition', + await this.systemCoreTableResolver.getTableName('table'), { where: [{ field: idField, operator: '=', value: table.id }] }, { uniques: newUniques, indexes: newIndexes }, ); @@ -1289,18 +1316,18 @@ export class SchemaHealingService { if (DatabaseConfigService.instanceIsMongoDb()) { return await this.queryBuilderService .getMongoDb() - .collection('setting_definition') + .collection('enfyra_setting') .findOne({}); } return await this.queryBuilderService - .getKnex()('setting_definition') + .getKnex()('enfyra_setting') .orderBy('id', 'asc') .first(); } catch { try { const result = await this.queryBuilderService.find({ - table: 'setting_definition', + table: 'enfyra_setting', sort: [DatabaseConfigService.getPkField()], limit: 1, }); @@ -1316,7 +1343,7 @@ export class SchemaHealingService { if (DatabaseConfigService.instanceIsMongoDb()) { await this.queryBuilderService .getMongoDb() - .collection('setting_definition') + .collection('enfyra_setting') .updateOne( { _id: setting._id }, { $set: { uniquesIndexesRepaired: true } }, @@ -1325,7 +1352,7 @@ export class SchemaHealingService { } await this.queryBuilderService - .getKnex()('setting_definition') + .getKnex()('enfyra_setting') .where({ id: setting.id }) .update({ uniquesIndexesRepaired: true }); return; @@ -1334,7 +1361,7 @@ export class SchemaHealingService { const idField = DatabaseConfigService.getPkField(); const settingId = setting._id || setting.id; await this.queryBuilderService.update( - 'setting_definition', + 'enfyra_setting', { where: [{ field: idField, operator: '=', value: settingId }] }, { uniquesIndexesRepaired: true }, ); diff --git a/src/engines/bootstrap/services/system-core-table-resolver.service.ts b/src/engines/bootstrap/services/system-core-table-resolver.service.ts new file mode 100644 index 00000000..11d06069 --- /dev/null +++ b/src/engines/bootstrap/services/system-core-table-resolver.service.ts @@ -0,0 +1,49 @@ +import { QueryBuilderService } from '@enfyra/kernel'; +import { + CORE_SYSTEM_TABLES, + LEGACY_CORE_SYSTEM_TABLES, +} from '../../../shared/utils/system-tables.constants'; +import type { + CoreSystemTableKey, + CoreSystemTableNames, +} from '../../../shared/types/system-tables.types'; + +export class SystemCoreTableResolver { + private readonly queryBuilderService: QueryBuilderService; + + constructor(deps: { queryBuilderService: QueryBuilderService }) { + this.queryBuilderService = deps.queryBuilderService; + } + + async getNames(): Promise { + return { + table: await this.resolveOne('table'), + column: await this.resolveOne('column'), + relation: await this.resolveOne('relation'), + }; + } + + async getTableName(key: CoreSystemTableKey): Promise { + return this.resolveOne(key); + } + + private async resolveOne(key: CoreSystemTableKey): Promise { + const canonical = CORE_SYSTEM_TABLES[key]; + const legacy = LEGACY_CORE_SYSTEM_TABLES[key]; + + if (await this.physicalTableExists(canonical)) return canonical; + if (await this.physicalTableExists(legacy)) return legacy; + return canonical; + } + + async physicalTableExists(tableName: string): Promise { + if (this.queryBuilderService.isMongoDb()) { + const db = this.queryBuilderService.getMongoDb(); + const matches = await db.listCollections({ name: tableName }).toArray(); + return matches.length > 0; + } + + const knex = this.queryBuilderService.getKnex(); + return knex.schema.hasTable(tableName); + } +} diff --git a/src/engines/bootstrap/utils/metadata-migration.util.ts b/src/engines/bootstrap/utils/metadata-migration.util.ts new file mode 100644 index 00000000..1a880572 --- /dev/null +++ b/src/engines/bootstrap/utils/metadata-migration.util.ts @@ -0,0 +1,96 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { + ColumnModifyDef, + RelationModifyDef, + SchemaMigrationDef, + TableRenameDef, +} from '../../../shared/types/schema-migration.types'; +import { getScriptLegacyField } from '../../../shared/utils/script-code.util'; + +export function hasSchemaMigrations( + migrations: SchemaMigrationDef | null | undefined, +): migrations is SchemaMigrationDef { + if (!migrations) return false; + return ( + (migrations.coreTablesToRename?.length ?? 0) > 0 || + (migrations.tablesToRename?.length ?? 0) > 0 || + (migrations.physicalTablesToRename?.length ?? 0) > 0 || + (migrations.physicalTablesToDrop?.length ?? 0) > 0 || + (migrations.tables?.length ?? 0) > 0 || + (migrations.tablesToDrop?.length ?? 0) > 0 + ); +} + +export function loadSnapshotMigrationFile( + cwd = process.cwd(), +): SchemaMigrationDef | null { + const filePath = path.join(cwd, 'data/snapshot-migration.json'); + if (!fs.existsSync(filePath)) return null; + + const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')); + return hasSchemaMigrations(parsed) ? parsed : null; +} + +export function getValidTableRenames( + renames: TableRenameDef[], +): TableRenameDef[] { + return renames.filter( + (rename) => rename.from && rename.to && rename.from !== rename.to, + ); +} + +export function hasColumnMetadataChanges(mod: ColumnModifyDef): boolean { + return ( + mod.to.name !== mod.from.name || + (mod.to.isNullable !== undefined && + mod.to.isNullable !== mod.from.isNullable) || + (mod.to.isUpdatable !== undefined && + mod.to.isUpdatable !== mod.from.isUpdatable) || + mod.to.description !== undefined + ); +} + +export function buildColumnMetadataUpdate(mod: ColumnModifyDef): any { + const updateData: any = {}; + + if (mod.to.name !== mod.from.name) { + updateData.name = mod.to.name; + } + if ( + mod.to.isNullable !== undefined && + mod.to.isNullable !== mod.from.isNullable + ) { + updateData.isNullable = mod.to.isNullable; + } + if ( + mod.to.isUpdatable !== undefined && + mod.to.isUpdatable !== mod.from.isUpdatable + ) { + updateData.isUpdatable = mod.to.isUpdatable; + } + if (mod.to.description !== undefined) { + updateData.description = mod.to.description; + } + + return updateData; +} + +export function hasRelationMetadataChanges(mod: RelationModifyDef): boolean { + return ( + mod.to.propertyName !== mod.from.propertyName || + (mod.to.mappedBy !== undefined && mod.to.mappedBy !== mod.from.mappedBy) || + (mod.to.isNullable !== undefined && + mod.to.isNullable !== mod.from.isNullable) || + (mod.to.isUpdatable !== undefined && + mod.to.isUpdatable !== mod.from.isUpdatable) || + mod.to.onDelete !== undefined + ); +} + +export function getLegacyScriptTargetColumn( + tableName: string, + colName: string, +): string | null { + return getScriptLegacyField(tableName) === colName ? 'sourceCode' : null; +} diff --git a/src/engines/bootstrap/utils/metadata-physical-migration.util.ts b/src/engines/bootstrap/utils/metadata-physical-migration.util.ts new file mode 100644 index 00000000..65f3a634 --- /dev/null +++ b/src/engines/bootstrap/utils/metadata-physical-migration.util.ts @@ -0,0 +1,184 @@ +import type { Db } from 'mongodb'; +import type { QueryBuilderService } from '@enfyra/kernel'; +import type { TableRenameDef } from '../../../shared/types/schema-migration.types'; + +type VerboseLogger = (message: string) => void; + +export class MetadataPhysicalMigrationHelper { + private readonly queryBuilderService: QueryBuilderService; + private readonly verbose: VerboseLogger; + + constructor(deps: { + queryBuilderService: QueryBuilderService; + verbose: VerboseLogger; + }) { + this.queryBuilderService = deps.queryBuilderService; + this.verbose = deps.verbose; + } + + async dropPhysicalTables( + tableNames: string[], + isMongoDB: boolean, + ): Promise { + for (const tableName of tableNames) { + if (!tableName) continue; + if (isMongoDB) { + if (!(await this.mongoCollectionExists(tableName))) continue; + await this.getMongoDb().collection(tableName).drop(); + this.verbose(` Dropped legacy Mongo collection: ${tableName}`); + continue; + } + + const knex = this.queryBuilderService.getKnex(); + if (!(await knex.schema.hasTable(tableName))) continue; + await knex.schema.dropTable(tableName); + this.verbose(` Dropped legacy SQL table: ${tableName}`); + } + } + + async runPhysicalTableRenames( + renames: TableRenameDef[], + isMongoDB: boolean, + ): Promise { + for (const rename of renames) { + if (!rename.from || !rename.to || rename.from === rename.to) continue; + if (isMongoDB) { + await this.renameMongoCollection(rename); + continue; + } + + await this.renameSqlTable(rename); + } + } + + async renameMongoDocumentFieldIfNeeded( + tableName: string, + oldName: string, + newName: string, + ): Promise { + const db = this.queryBuilderService.isMongoDb() + ? this.queryBuilderService.getMongoDb() + : null; + if (!db) return; + + await db + .collection(tableName) + .updateMany( + { [oldName]: { $exists: true }, [newName]: { $exists: false } }, + [{ $set: { [newName]: `$${oldName}` } }], + ); + await db + .collection(tableName) + .updateMany( + { [oldName]: { $exists: true } }, + { $unset: { [oldName]: '' } }, + ); + this.verbose( + ` Renamed document field: ${tableName}.${oldName} → ${newName}`, + ); + } + + async renameSqlPhysicalColumnIfNeeded( + tableName: string, + oldName: string, + newName: string, + ): Promise { + const knex = this.queryBuilderService.getKnex(); + if (!knex?.schema?.hasTable) return; + if (!(await knex.schema.hasTable(tableName))) return; + + const oldExists = await knex.schema.hasColumn(tableName, oldName); + const newExists = await knex.schema.hasColumn(tableName, newName); + if (!oldExists) return; + + if (newExists) { + await knex.raw('UPDATE ?? SET ?? = ?? WHERE ?? IS NULL', [ + tableName, + newName, + oldName, + newName, + ]); + await knex.schema.alterTable(tableName, (table: any) => { + table.dropColumn(oldName); + }); + this.verbose( + ` Dropped duplicate old physical column: ${tableName}.${oldName}`, + ); + return; + } + + await knex.schema.alterTable(tableName, (table: any) => { + table.renameColumn(oldName, newName); + }); + this.verbose( + ` Renamed physical column: ${tableName}.${oldName} → ${newName}`, + ); + } + + async dropPhysicalColumn( + tableName: string, + colName: string, + isMongoDB: boolean, + ): Promise { + if (isMongoDB) { + const db = this.getMongoDb(); + await db + .collection(tableName) + .updateMany( + { [colName]: { $exists: true } }, + { $unset: { [colName]: '' } }, + ); + return; + } + + const knex = this.queryBuilderService.getKnex(); + const hasColumn = await knex.schema.hasColumn(tableName, colName); + if (!hasColumn) return; + await knex.schema.alterTable(tableName, (table: any) => { + table.dropColumn(colName); + }); + this.verbose(` Dropped physical column: ${tableName}.${colName}`); + } + + async mongoCollectionExists(collectionName: string): Promise { + const matches = await this.getMongoDb() + .listCollections({ name: collectionName }) + .toArray(); + return matches.length > 0; + } + + private async renameMongoCollection(rename: TableRenameDef): Promise { + const oldExists = await this.mongoCollectionExists(rename.from); + const newExists = await this.mongoCollectionExists(rename.to); + if (oldExists && newExists) { + throw new Error( + `Cannot rename physical collection ${rename.from} to ${rename.to}: both collections exist`, + ); + } + if (oldExists && !newExists) { + await this.getMongoDb().collection(rename.from).rename(rename.to); + this.verbose( + ` Renamed legacy Mongo collection: ${rename.from} → ${rename.to}`, + ); + } + } + + private async renameSqlTable(rename: TableRenameDef): Promise { + const knex = this.queryBuilderService.getKnex(); + const oldExists = await knex.schema.hasTable(rename.from); + const newExists = await knex.schema.hasTable(rename.to); + if (oldExists && newExists) { + throw new Error( + `Cannot rename physical table ${rename.from} to ${rename.to}: both physical tables exist`, + ); + } + if (oldExists && !newExists) { + await knex.schema.renameTable(rename.from, rename.to); + this.verbose(` Renamed legacy SQL table: ${rename.from} → ${rename.to}`); + } + } + + private getMongoDb(): Db { + return this.queryBuilderService.getMongoDb(); + } +} diff --git a/src/engines/cache/services/cache-orchestrator.service.ts b/src/engines/cache/services/cache-orchestrator.service.ts index 8c5adab7..80af34b1 100644 --- a/src/engines/cache/services/cache-orchestrator.service.ts +++ b/src/engines/cache/services/cache-orchestrator.service.ts @@ -23,6 +23,7 @@ import { TCacheInvalidationPayload } from '../../../shared/types/cache.types'; import { CACHE_EVENTS } from '../../../shared/utils/cache-events.constants'; import { ENFYRA_ADMIN_WEBSOCKET_NAMESPACE } from '../../../shared/utils/constant'; import { logMemory } from '../../../shared/utils/memory-log.util'; +import { SYSTEM_TABLES } from '../../../shared/utils/system-tables.constants'; import { DynamicWebSocketGateway } from '../../../modules/websocket'; import { GraphqlService } from '../../../modules/graphql'; import { BootstrapScriptService } from '../../../domain/bootstrap'; @@ -68,7 +69,7 @@ type CacheReloadStepMetric = { }; export const RELOAD_CHAINS: Record = { - table_definition: [ + [SYSTEM_TABLES.table]: [ 'metadata', 'repoRegistry', 'route', @@ -76,7 +77,7 @@ export const RELOAD_CHAINS: Record = { 'fieldPermission', 'column-rule', ], - column_definition: [ + [SYSTEM_TABLES.column]: [ 'metadata', 'repoRegistry', 'route', @@ -84,7 +85,7 @@ export const RELOAD_CHAINS: Record = { 'fieldPermission', 'column-rule', ], - relation_definition: [ + [SYSTEM_TABLES.relation]: [ 'metadata', 'repoRegistry', 'route', @@ -93,34 +94,34 @@ export const RELOAD_CHAINS: Record = { 'column-rule', ], - route_definition: ['route', 'graphql', 'guard'], - pre_hook_definition: ['route'], - post_hook_definition: ['route'], - route_handler_definition: ['route'], - route_permission_definition: ['route'], - role_definition: ['route'], - method_definition: ['route', 'graphql'], + [SYSTEM_TABLES.route]: ['route', 'graphql', 'guard'], + [SYSTEM_TABLES.preHook]: ['route'], + [SYSTEM_TABLES.postHook]: ['route'], + [SYSTEM_TABLES.routeHandler]: ['route'], + [SYSTEM_TABLES.routePermission]: ['route'], + [SYSTEM_TABLES.role]: ['route'], + [SYSTEM_TABLES.method]: ['route', 'graphql'], - guard_definition: ['guard'], - guard_rule_definition: ['guard'], + [SYSTEM_TABLES.guard]: ['guard'], + [SYSTEM_TABLES.guardRule]: ['guard'], - field_permission_definition: ['fieldPermission', 'graphql'], + [SYSTEM_TABLES.fieldPermission]: ['fieldPermission', 'graphql'], - column_rule_definition: ['column-rule'], + [SYSTEM_TABLES.columnRule]: ['column-rule'], - setting_definition: ['setting', 'settingGraphql'], - storage_config_definition: ['storage'], - oauth_config_definition: ['oauth'], - websocket_definition: ['websocket'], - websocket_event_definition: ['websocket'], - package_definition: ['package'], - flow_definition: ['flow'], - flow_step_definition: ['flow'], - folder_definition: ['folder'], - bootstrap_script_definition: ['bootstrap'], - menu_definition: ['menu', 'extension'], - extension_definition: ['extension'], - gql_definition: ['graphql'], + [SYSTEM_TABLES.setting]: ['setting', 'settingGraphql'], + [SYSTEM_TABLES.storageConfig]: ['storage'], + [SYSTEM_TABLES.oauthConfig]: ['oauth'], + [SYSTEM_TABLES.websocket]: ['websocket'], + [SYSTEM_TABLES.websocketEvent]: ['websocket'], + [SYSTEM_TABLES.package]: ['package'], + [SYSTEM_TABLES.flow]: ['flow'], + [SYSTEM_TABLES.flowStep]: ['flow'], + [SYSTEM_TABLES.folder]: ['folder'], + [SYSTEM_TABLES.bootstrapScript]: ['bootstrap'], + [SYSTEM_TABLES.menu]: ['menu', 'extension'], + [SYSTEM_TABLES.extension]: ['extension'], + [SYSTEM_TABLES.graphql]: ['graphql'], }; export class CacheOrchestratorService implements LifecycleAware { @@ -246,7 +247,11 @@ export class CacheOrchestratorService implements LifecycleAware { options?.sharedReplay, ), 'column-rule': (p, options) => - this.reloadSimple(this.columnRuleCacheService, p, options?.sharedReplay), + this.reloadSimple( + this.columnRuleCacheService, + p, + options?.sharedReplay, + ), settingGraphql: () => this.reloadSettingGraphql(), bootstrap: () => this.reloadBootstrapScripts(), }; @@ -562,7 +567,9 @@ export class CacheOrchestratorService implements LifecycleAware { } private async reloadRepoRegistry(): Promise { - await this.repoRegistryService.rebuildFromMetadata(this.metadataCacheService); + await this.repoRegistryService.rebuildFromMetadata( + this.metadataCacheService, + ); } private async reloadRoute( @@ -731,7 +738,7 @@ export class CacheOrchestratorService implements LifecycleAware { const steps = ['metadata', 'repoRegistry', 'route', 'graphql']; await this.runTrackedAdminReload({ flow: 'metadata', - table: 'table_definition', + table: 'enfyra_table', steps, logLabel: 'Admin reload metadata+deps', run: async (runStep) => { @@ -744,7 +751,7 @@ export class CacheOrchestratorService implements LifecycleAware { }, }); await this.publishSignal({ - table: 'table_definition', + table: 'enfyra_table', action: 'reload', scope: 'full', timestamp: Date.now(), @@ -755,7 +762,7 @@ export class CacheOrchestratorService implements LifecycleAware { const steps = ['route']; await this.runTrackedAdminReload({ flow: 'route', - table: 'route_definition', + table: 'enfyra_route', steps, logLabel: 'Admin reload routes', run: async (runStep) => { @@ -763,7 +770,7 @@ export class CacheOrchestratorService implements LifecycleAware { }, }); await this.publishSignal({ - table: 'route_definition', + table: 'enfyra_route', action: 'reload', scope: 'full', timestamp: Date.now(), @@ -774,7 +781,7 @@ export class CacheOrchestratorService implements LifecycleAware { const steps = ['graphql']; await this.runTrackedAdminReload({ flow: 'graphql', - table: 'gql_definition', + table: 'enfyra_graphql', steps, logLabel: 'Admin reload graphql', run: async (runStep) => { @@ -784,7 +791,7 @@ export class CacheOrchestratorService implements LifecycleAware { }, }); await this.publishSignal({ - table: 'gql_definition', + table: 'enfyra_graphql', action: 'reload', scope: 'full', timestamp: Date.now(), @@ -795,7 +802,7 @@ export class CacheOrchestratorService implements LifecycleAware { const steps = ['guard']; await this.runTrackedAdminReload({ flow: 'guard', - table: 'guard_definition', + table: 'enfyra_guard', steps, logLabel: 'Admin reload guards', run: async (runStep) => { @@ -803,7 +810,7 @@ export class CacheOrchestratorService implements LifecycleAware { }, }); await this.publishSignal({ - table: 'guard_definition', + table: 'enfyra_guard', action: 'reload', scope: 'full', timestamp: Date.now(), diff --git a/src/engines/cache/services/column-rule-cache.service.ts b/src/engines/cache/services/column-rule-cache.service.ts index 6733e0b7..78813134 100644 --- a/src/engines/cache/services/column-rule-cache.service.ts +++ b/src/engines/cache/services/column-rule-cache.service.ts @@ -56,7 +56,7 @@ export class ColumnRuleCacheService extends BaseCacheService< protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ - table: 'column_rule_definition', + table: 'enfyra_column_rule', fields: ['*', 'column.id', 'column._id'], filter: { isEnabled: { _eq: true } }, limit: 100000, @@ -87,7 +87,7 @@ export class ColumnRuleCacheService extends BaseCacheService< payload: TCacheInvalidationPayload, publish = true, ): Promise { - if (payload.table !== 'column_rule_definition') { + if (payload.table !== 'enfyra_column_rule') { await this.reload(publish); return; } @@ -105,7 +105,7 @@ export class ColumnRuleCacheService extends BaseCacheService< const idField = this.queryBuilderService.isMongoDb() ? '_id' : 'id'; const result = await this.queryBuilderService.find({ - table: 'column_rule_definition', + table: 'enfyra_column_rule', fields: ['*', 'column.id', 'column._id'], filter: { [idField]: { _in: ids } }, limit: ids.length, diff --git a/src/engines/cache/services/field-permission-cache.service.ts b/src/engines/cache/services/field-permission-cache.service.ts index a3f46ab0..217a05e5 100644 --- a/src/engines/cache/services/field-permission-cache.service.ts +++ b/src/engines/cache/services/field-permission-cache.service.ts @@ -89,7 +89,7 @@ export class FieldPermissionCacheService extends BaseCacheService< protected async loadFromDb(): Promise { const [result, metadata] = await Promise.all([ this.queryBuilderService.find({ - table: 'field_permission_definition', + table: 'enfyra_field_permission', fields: FIELD_PERMISSION_CACHE_FIELDS, filter: { isEnabled: { _eq: true } }, limit: 100000, @@ -119,7 +119,7 @@ export class FieldPermissionCacheService extends BaseCacheService< payload: TCacheInvalidationPayload, publish = true, ): Promise { - if (payload.table !== 'field_permission_definition') { + if (payload.table !== 'enfyra_field_permission') { await this.reload(publish); return; } @@ -136,7 +136,7 @@ export class FieldPermissionCacheService extends BaseCacheService< if (ids.length === 0) return; const result = await this.queryBuilderService.find({ - table: 'field_permission_definition', + table: 'enfyra_field_permission', fields: FIELD_PERMISSION_CACHE_FIELDS, filter: { id: { _in: ids } }, limit: ids.length, diff --git a/src/engines/cache/services/flow-cache.service.ts b/src/engines/cache/services/flow-cache.service.ts index 65dd9c72..99641ebf 100644 --- a/src/engines/cache/services/flow-cache.service.ts +++ b/src/engines/cache/services/flow-cache.service.ts @@ -42,7 +42,7 @@ export class FlowCacheService extends BaseCacheService { const idField = DatabaseConfigService.getPkField(); const flowsResult = await this.queryBuilderService.find({ - table: 'flow_definition', + table: 'enfyra_flow', filter: { isEnabled: { _eq: true } }, fields: ['*'], }); @@ -50,7 +50,7 @@ export class FlowCacheService extends BaseCacheService { const flows = []; for (const flow of flowsResult.data) { const stepsResult = await this.queryBuilderService.find({ - table: 'flow_step_definition', + table: 'enfyra_flow_step', filter: { flow: { [idField]: { _eq: flow[idField] } } }, fields: ['*', 'parent.*'], limit: 1000, @@ -142,7 +142,7 @@ export class FlowCacheService extends BaseCacheService { delete config.scriptLanguage; delete config.compiledCode; delete config.code; - await this.queryBuilderService.update('flow_step_definition', id, { + await this.queryBuilderService.update('enfyra_flow_step', id, { sourceCode: step.sourceCode ?? null, scriptLanguage: step.scriptLanguage ?? 'typescript', compiledCode: step.compiledCode ?? null, diff --git a/src/engines/cache/services/folder-tree-cache.service.ts b/src/engines/cache/services/folder-tree-cache.service.ts index 07cbc7de..6b498a6d 100644 --- a/src/engines/cache/services/folder-tree-cache.service.ts +++ b/src/engines/cache/services/folder-tree-cache.service.ts @@ -40,7 +40,7 @@ export class FolderTreeCacheService extends BaseCacheService { protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ - table: 'folder_definition', + table: 'enfyra_folder', fields: ['id', 'name', 'slug', 'order', 'icon', 'description', 'parent'], sort: ['order'], }); diff --git a/src/engines/cache/services/gql-definition-cache.service.ts b/src/engines/cache/services/gql-definition-cache.service.ts index c3a7ca90..57e13153 100644 --- a/src/engines/cache/services/gql-definition-cache.service.ts +++ b/src/engines/cache/services/gql-definition-cache.service.ts @@ -41,7 +41,7 @@ export class GqlDefinitionCacheService extends BaseCacheService< protected async loadFromDb(): Promise { try { const result = await this.queryBuilderService.find({ - table: 'gql_definition', + table: 'enfyra_graphql', fields: ['*', 'table.id', 'table.name'], limit: 10000, }); diff --git a/src/engines/cache/services/guard-cache.service.ts b/src/engines/cache/services/guard-cache.service.ts index 3ae50ba2..3af93272 100644 --- a/src/engines/cache/services/guard-cache.service.ts +++ b/src/engines/cache/services/guard-cache.service.ts @@ -77,13 +77,13 @@ export class GuardCacheService extends BaseCacheService { protected async loadFromDb(): Promise { const [guardsResult, rulesResult] = await Promise.all([ this.queryBuilderService.find({ - table: 'guard_definition', + table: 'enfyra_guard', filter: { isEnabled: { _eq: true } }, fields: ['*', 'parent', 'route.id', 'route.path', 'methods.name'], sort: ['priority'], }), this.queryBuilderService.find({ - table: 'guard_rule_definition', + table: 'enfyra_guard_rule', filter: { isEnabled: { _eq: true } }, fields: ['*', 'guard', 'users.id'], sort: ['priority'], diff --git a/src/engines/cache/services/metadata-cache.service.ts b/src/engines/cache/services/metadata-cache.service.ts index 1f0e5ab9..7c3305ec 100644 --- a/src/engines/cache/services/metadata-cache.service.ts +++ b/src/engines/cache/services/metadata-cache.service.ts @@ -884,7 +884,7 @@ export class MetadataCacheService implements IMetadataCache { if (isMongoDB && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('table_definition'); + .collection('enfyra_table'); const docs = await collection .find({ _id: { $in: ids.map((id) => new ObjectId(id as string)) } }) .toArray(); @@ -893,7 +893,7 @@ export class MetadataCacheService implements IMetadataCache { const pkField = DatabaseConfigService.getPkField(); const rows = await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('table_definition') + .table('enfyra_table') .whereIn(pkField, ids); return rows; } @@ -904,13 +904,13 @@ export class MetadataCacheService implements IMetadataCache { if (this.dbType === 'mongodb' && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('table_definition'); + .collection('enfyra_table'); const docs = await collection.find({ name: { $in: names } }).toArray(); return docs; } else if (this.lazyRef.knexService) { const rows = await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('table_definition') + .table('enfyra_table') .whereIn('name', names); return rows; } @@ -924,7 +924,7 @@ export class MetadataCacheService implements IMetadataCache { if (isMongoDB && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('column_definition'); + .collection('enfyra_column'); const docs = await collection .find({ table: { $in: tableIds.map((id) => new ObjectId(id)) } }) .toArray(); @@ -932,7 +932,7 @@ export class MetadataCacheService implements IMetadataCache { } else if (this.lazyRef.knexService) { const rows = await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('column_definition') + .table('enfyra_column') .whereIn('tableId', tableIds); return rows; } @@ -946,7 +946,7 @@ export class MetadataCacheService implements IMetadataCache { if (isMongoDB && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('relation_definition'); + .collection('enfyra_relation'); const docs = await collection .find({ sourceTable: { $in: tableIds.map((id) => new ObjectId(id)) } }) .toArray(); @@ -954,7 +954,7 @@ export class MetadataCacheService implements IMetadataCache { } else if (this.lazyRef.knexService) { const rows = await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('relation_definition') + .table('enfyra_relation') .whereIn('sourceTableId', tableIds); return rows; } @@ -965,12 +965,12 @@ export class MetadataCacheService implements IMetadataCache { if (this.dbType === 'mongodb' && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('table_definition'); + .collection('enfyra_table'); return await collection.find({}).toArray(); } else if (this.lazyRef.knexService) { return await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('table_definition') + .table('enfyra_table') .select(); } return []; @@ -980,12 +980,12 @@ export class MetadataCacheService implements IMetadataCache { if (isMongoDB && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('column_definition'); + .collection('enfyra_column'); return await collection.find({}).toArray(); } else if (this.lazyRef.knexService) { return await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('column_definition') + .table('enfyra_column') .select(); } return []; @@ -995,12 +995,12 @@ export class MetadataCacheService implements IMetadataCache { if (isMongoDB && this.lazyRef.mongoService) { const collection = this.lazyRef.mongoService .getDb() - .collection('relation_definition'); + .collection('enfyra_relation'); return await collection.find({}).toArray(); } else if (this.lazyRef.knexService) { return await this.lazyRef.knexService .getKnex({ skipMetadataHooks: true }) - .table('relation_definition') + .table('enfyra_relation') .select(); } return []; diff --git a/src/engines/cache/services/oauth-config-cache.service.ts b/src/engines/cache/services/oauth-config-cache.service.ts index e9814316..3edd22e1 100644 --- a/src/engines/cache/services/oauth-config-cache.service.ts +++ b/src/engines/cache/services/oauth-config-cache.service.ts @@ -44,14 +44,14 @@ export class OAuthConfigCacheService protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ - table: 'oauth_config_definition', + table: 'enfyra_oauth_config', filter: { isEnabled: { _eq: true } }, }); if (!result.data || result.data.length === 0) { return []; } return result.data.map((config: any) => { - const normalized = normalizeScriptRecord('oauth_config_definition', config); + const normalized = normalizeScriptRecord('enfyra_oauth_config', config); return { id: normalized.id, provider: normalized.provider, diff --git a/src/engines/cache/services/package-cache.service.ts b/src/engines/cache/services/package-cache.service.ts index 54784eee..0692da8e 100644 --- a/src/engines/cache/services/package-cache.service.ts +++ b/src/engines/cache/services/package-cache.service.ts @@ -52,7 +52,7 @@ export class PackageCacheService extends BaseCacheService { protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ - table: 'package_definition', + table: 'enfyra_package', fields: ['name'], filter: { isEnabled: true, @@ -127,7 +127,7 @@ export class PackageCacheService extends BaseCacheService { extra?: Record, ) { try { - await this.queryBuilderService.update('package_definition', id, { + await this.queryBuilderService.update('enfyra_package', id, { status, ...extra, }); @@ -209,7 +209,7 @@ export class PackageCacheService extends BaseCacheService { }> > { const result = await this.queryBuilderService.find({ - table: 'package_definition', + table: 'enfyra_package', fields: ['id', 'name', 'version', 'status'], filter: { isEnabled: true, diff --git a/src/engines/cache/services/route-cache.service.ts b/src/engines/cache/services/route-cache.service.ts index 07881e73..38617b4b 100644 --- a/src/engines/cache/services/route-cache.service.ts +++ b/src/engines/cache/services/route-cache.service.ts @@ -135,7 +135,7 @@ export class RouteCacheService extends BaseCacheService { ): Promise { const affectedTableNames = new Set(payload.affectedTables || []); - if (payload.table === 'table_definition' && payload.ids?.length) { + if (payload.table === 'enfyra_table' && payload.ids?.length) { const isMongoDB = this.queryBuilderService.isMongoDb(); const idField = DatabaseConfigService.getPkField(); const filter = isMongoDB @@ -143,7 +143,7 @@ export class RouteCacheService extends BaseCacheService { : { mainTableId: { _in: payload.ids } }; const result = await this.queryBuilderService.find({ - table: 'route_definition', + table: 'enfyra_route', filter, fields: [idField], }); @@ -176,13 +176,13 @@ export class RouteCacheService extends BaseCacheService { return; } - if (payload.table === 'route_definition' && payload.ids?.length) { + if (payload.table === 'enfyra_route' && payload.ids?.length) { await this.reloadSpecificRoutes(payload.ids); return; } if ( - ['route_handler_definition', 'route_permission_definition'].includes( + ['enfyra_route_handler', 'enfyra_route_permission'].includes( payload.table, ) && payload.ids?.length @@ -198,7 +198,7 @@ export class RouteCacheService extends BaseCacheService { } if ( - ['pre_hook_definition', 'post_hook_definition'].includes(payload.table) + ['enfyra_pre_hook', 'enfyra_post_hook'].includes(payload.table) ) { await this.reloadGlobalHooksAndMerge(); if (payload.ids?.length) { @@ -213,7 +213,7 @@ export class RouteCacheService extends BaseCacheService { return; } - if (['role_definition', 'method_definition'].includes(payload.table)) { + if (['enfyra_role', 'enfyra_method'].includes(payload.table)) { await this.reload(); return; } @@ -244,7 +244,7 @@ export class RouteCacheService extends BaseCacheService { const idField = DatabaseConfigService.getPkField(); const result = await this.queryBuilderService.find({ - table: 'route_definition', + table: 'enfyra_route', filter: { _and: [{ isEnabled: { _eq: true } }, { [idField]: { _in: routeIds } }], }, @@ -305,13 +305,13 @@ export class RouteCacheService extends BaseCacheService { private getChildArrayKeyForTable(tableName: string): string | null { switch (tableName) { - case 'pre_hook_definition': + case 'enfyra_pre_hook': return 'preHooks'; - case 'post_hook_definition': + case 'enfyra_post_hook': return 'postHooks'; - case 'route_handler_definition': + case 'enfyra_route_handler': return 'handlers'; - case 'route_permission_definition': + case 'enfyra_route_permission': return 'routePermissions'; default: return null; @@ -361,8 +361,8 @@ export class RouteCacheService extends BaseCacheService { const isMongoDB = this.queryBuilderService.isMongoDb(); const [newGlobalPreHooks, newGlobalPostHooks] = await Promise.all([ - this.loadGlobalHooks('pre_hook_definition'), - this.loadGlobalHooks('post_hook_definition'), + this.loadGlobalHooks('enfyra_pre_hook'), + this.loadGlobalHooks('enfyra_post_hook'), ]); this.globalPreHooks = newGlobalPreHooks; @@ -384,13 +384,13 @@ export class RouteCacheService extends BaseCacheService { protected async loadFromDb(): Promise { const methodsResult = await this.queryBuilderService.find({ - table: 'method_definition', + table: 'enfyra_method', fields: ['id', 'name'], }); this.setMethodCache(methodsResult.data); const result = await this.queryBuilderService.find({ - table: 'route_definition', + table: 'enfyra_route', filter: { isEnabled: { _eq: true } }, fields: ROUTE_CACHE_ROUTE_FIELDS, }); @@ -399,8 +399,8 @@ export class RouteCacheService extends BaseCacheService { const isMongoDB = this.queryBuilderService.isMongoDb(); const [globalPreHooks, globalPostHooks] = await Promise.all([ - this.loadGlobalHooks('pre_hook_definition'), - this.loadGlobalHooks('post_hook_definition'), + this.loadGlobalHooks('enfyra_pre_hook'), + this.loadGlobalHooks('enfyra_post_hook'), ]); this.globalPreHooks = globalPreHooks; @@ -546,12 +546,12 @@ export class RouteCacheService extends BaseCacheService { if (route.handlers && Array.isArray(route.handlers)) { for (const handler of route.handlers) { const normalized = normalizeScriptRecord( - 'route_handler_definition', + 'enfyra_route_handler', handler, ); Object.assign(handler, normalized); const code = await this.resolveAndRepairScript( - 'route_handler_definition', + 'enfyra_route_handler', handler, ); if (code) { @@ -562,10 +562,10 @@ export class RouteCacheService extends BaseCacheService { if (route.preHooks && Array.isArray(route.preHooks)) { for (const hook of route.preHooks) { - const normalized = normalizeScriptRecord('pre_hook_definition', hook); + const normalized = normalizeScriptRecord('enfyra_pre_hook', hook); Object.assign(hook, normalized); const code = await this.resolveAndRepairScript( - 'pre_hook_definition', + 'enfyra_pre_hook', hook, ); if (code) { @@ -576,10 +576,10 @@ export class RouteCacheService extends BaseCacheService { if (route.postHooks && Array.isArray(route.postHooks)) { for (const hook of route.postHooks) { - const normalized = normalizeScriptRecord('post_hook_definition', hook); + const normalized = normalizeScriptRecord('enfyra_post_hook', hook); Object.assign(hook, normalized); const code = await this.resolveAndRepairScript( - 'post_hook_definition', + 'enfyra_post_hook', hook, ); if (code) { diff --git a/src/engines/cache/services/setting-cache.service.ts b/src/engines/cache/services/setting-cache.service.ts index fa9efe67..78bff90e 100644 --- a/src/engines/cache/services/setting-cache.service.ts +++ b/src/engines/cache/services/setting-cache.service.ts @@ -41,7 +41,7 @@ export class SettingCacheService extends BaseCacheService { protected async loadFromDb(): Promise { try { const result = await this.queryBuilderService.find({ - table: 'setting_definition', + table: 'enfyra_setting', limit: 1, }); return result?.data?.[0] || {}; diff --git a/src/engines/cache/services/storage-config-cache.service.ts b/src/engines/cache/services/storage-config-cache.service.ts index e7d948f0..e5c1c52a 100644 --- a/src/engines/cache/services/storage-config-cache.service.ts +++ b/src/engines/cache/services/storage-config-cache.service.ts @@ -27,7 +27,7 @@ export class StorageConfigCacheService extends BaseCacheService< protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ - table: 'storage_config_definition', + table: 'enfyra_storage_config', filter: { isEnabled: { _eq: true } }, fields: ['*'], }); diff --git a/src/engines/cache/services/websocket-cache.service.ts b/src/engines/cache/services/websocket-cache.service.ts index c57f1f4d..14a5b46d 100644 --- a/src/engines/cache/services/websocket-cache.service.ts +++ b/src/engines/cache/services/websocket-cache.service.ts @@ -54,7 +54,7 @@ export class WebsocketCacheService extends BaseCacheService< protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ - table: 'websocket_definition', + table: 'enfyra_websocket', fields: ['*', 'events.*'], filter: { isEnabled: { _eq: true } }, }); @@ -113,8 +113,8 @@ export class WebsocketCacheService extends BaseCacheService< publish = true, ): Promise { if ( - payload.table !== 'websocket_definition' && - payload.table !== 'websocket_event_definition' + payload.table !== 'enfyra_websocket' && + payload.table !== 'enfyra_websocket_event' ) { await this.reload(publish); return; @@ -128,7 +128,7 @@ export class WebsocketCacheService extends BaseCacheService< const ids = payload.ids ?? []; if (ids.length === 0) return; - if (payload.table === 'websocket_definition') { + if (payload.table === 'enfyra_websocket') { await this.reloadSpecificGateways(ids); return; } @@ -143,7 +143,7 @@ export class WebsocketCacheService extends BaseCacheService< ): Promise { const idField = this.queryBuilderService.isMongoDb() ? '_id' : 'id'; const result = await this.queryBuilderService.find({ - table: 'websocket_definition', + table: 'enfyra_websocket', fields: ['*', 'events.*'], filter: { _and: [ @@ -170,7 +170,7 @@ export class WebsocketCacheService extends BaseCacheService< ): Promise<(string | number)[]> { const idField = this.queryBuilderService.isMongoDb() ? '_id' : 'id'; const result = await this.queryBuilderService.find({ - table: 'websocket_event_definition', + table: 'enfyra_websocket_event', fields: ['gateway.id', 'gateway._id'], filter: { [idField]: { _in: eventIds } }, limit: eventIds.length, @@ -202,12 +202,12 @@ export class WebsocketCacheService extends BaseCacheService< private async prepareGateways(gateways: any[]): Promise { for (const gateway of gateways) { const normalizedGateway = normalizeScriptRecord( - 'websocket_definition', + 'enfyra_websocket', gateway, ); Object.assign(gateway, normalizedGateway); const connectionCode = await this.resolveAndRepairScript( - 'websocket_definition', + 'enfyra_websocket', gateway, ); if (connectionCode) { @@ -216,12 +216,12 @@ export class WebsocketCacheService extends BaseCacheService< if (gateway.events) { for (const event of gateway.events) { const normalizedEvent = normalizeScriptRecord( - 'websocket_event_definition', + 'enfyra_websocket_event', event, ); Object.assign(event, normalizedEvent); const code = await this.resolveAndRepairScript( - 'websocket_event_definition', + 'enfyra_websocket_event', event, ); if (code) { diff --git a/src/engines/knex/knex.service.ts b/src/engines/knex/knex.service.ts index ea81a63f..9ab97270 100644 --- a/src/engines/knex/knex.service.ts +++ b/src/engines/knex/knex.service.ts @@ -585,7 +585,7 @@ export class KnexService implements LifecycleAware { const promise = (async () => { try { - const tableDef = await this.knexInstance('table_definition') + const tableDef = await this.knexInstance('enfyra_table') .where('name', tableName) .first(); @@ -593,7 +593,7 @@ export class KnexService implements LifecycleAware { return; } - const columns = await this.knexInstance('column_definition') + const columns = await this.knexInstance('enfyra_column') .where('tableId', tableDef.id) .select('name', 'type'); @@ -734,7 +734,7 @@ export class KnexService implements LifecycleAware { } } - const tableDef = await this.knexInstance('table_definition') + const tableDef = await this.knexInstance('enfyra_table') .where('name', parentTableName) .first(); @@ -742,7 +742,7 @@ export class KnexService implements LifecycleAware { return null; } - const relationDef = await this.knexInstance('relation_definition') + const relationDef = await this.knexInstance('enfyra_relation') .where('sourceTableId', tableDef.id) .where('propertyName', relationName) .first(); @@ -751,7 +751,7 @@ export class KnexService implements LifecycleAware { return null; } - const targetTableDef = await this.knexInstance('table_definition') + const targetTableDef = await this.knexInstance('enfyra_table') .where('id', relationDef.targetTableId) .first(); diff --git a/src/engines/knex/services/migration-journal.service.ts b/src/engines/knex/services/migration-journal.service.ts index 32cd7c30..c1d55c75 100644 --- a/src/engines/knex/services/migration-journal.service.ts +++ b/src/engines/knex/services/migration-journal.service.ts @@ -48,7 +48,7 @@ export class MigrationJournalService { const uuid = `mj-${randomUUID()}`; const knex = this.getKnex(); - await knex('schema_migration_definition').insert({ + await knex('enfyra_schema_migration').insert({ uuid, tableName: params.tableName, operation: params.operation, @@ -69,14 +69,14 @@ export class MigrationJournalService { async markRunning(uuid: string): Promise { const knex = this.getKnex(); - await knex('schema_migration_definition') + await knex('enfyra_schema_migration') .where({ uuid }) .update({ status: 'running', startedAt: new Date() }); } async markCompleted(uuid: string): Promise { const knex = this.getKnex(); - await knex('schema_migration_definition') + await knex('enfyra_schema_migration') .where({ uuid }) .update({ status: 'completed', completedAt: new Date() }); this.logger.log(`Journal completed: ${uuid}`); @@ -84,7 +84,7 @@ export class MigrationJournalService { async markFailed(uuid: string, error: string): Promise { const knex = this.getKnex(); - await knex('schema_migration_definition') + await knex('enfyra_schema_migration') .where({ uuid }) .update({ status: 'failed', @@ -96,7 +96,7 @@ export class MigrationJournalService { async markRolledBack(uuid: string): Promise { const knex = this.getKnex(); - await knex('schema_migration_definition') + await knex('enfyra_schema_migration') .where({ uuid }) .update({ status: 'rolled_back', completedAt: new Date() }); this.logger.warn(`Journal rolled back: ${uuid}`); @@ -104,7 +104,7 @@ export class MigrationJournalService { async executeRollback(uuid: string): Promise { const knex = this.getKnex(); - const entry = await knex('schema_migration_definition') + const entry = await knex('enfyra_schema_migration') .where({ uuid }) .first(); @@ -144,12 +144,12 @@ export class MigrationJournalService { let pending: any[]; try { - pending = await knex('schema_migration_definition') + pending = await knex('enfyra_schema_migration') .whereIn('status', ['pending', 'running']) .select('*'); } catch { this.logger.warn( - 'schema_migration_definition table not found, skipping recovery', + 'enfyra_schema_migration table not found, skipping recovery', ); return; } @@ -185,7 +185,7 @@ export class MigrationJournalService { const staleCutoff = new Date(Date.now() - staleThresholdMs); try { - const stale = await knex('schema_migration_definition') + const stale = await knex('enfyra_schema_migration') .whereIn('status', ['pending', 'running']) .where('startedAt', '<', staleCutoff) .select('uuid', 'tableName', 'createdAt', 'startedAt'); @@ -201,7 +201,7 @@ export class MigrationJournalService { this.logger.warn( `Marking stale journal ${entry.uuid} as failed (age: ${Math.round(ageMs / 1000)}s)`, ); - await knex('schema_migration_definition') + await knex('enfyra_schema_migration') .where({ uuid: entry.uuid }) .update({ status: 'failed', @@ -221,7 +221,7 @@ export class MigrationJournalService { const knex = this.getKnex(); const cutoff = new Date(Date.now() - maxAgeDays * 24 * 60 * 60 * 1000); try { - const deleted = await knex('schema_migration_definition') + const deleted = await knex('enfyra_schema_migration') .whereIn('status', ['completed', 'rolled_back']) .where('completedAt', '<', cutoff) .delete(); diff --git a/src/engines/knex/services/sql-schema-diff.service.ts b/src/engines/knex/services/sql-schema-diff.service.ts index 0e9d51e4..d851ff9b 100644 --- a/src/engines/knex/services/sql-schema-diff.service.ts +++ b/src/engines/knex/services/sql-schema-diff.service.ts @@ -401,7 +401,7 @@ export class SqlSchemaDiffService { const mergedIndexes = [...userDefinedIndexes, ...newIndexes]; try { - await knex('table_definition') + await knex('enfyra_table') .where('name', tableName) .update({ indexes: JSON.stringify(mergedIndexes) }); } catch (error) { diff --git a/src/engines/knex/services/sql-schema-migration.service.ts b/src/engines/knex/services/sql-schema-migration.service.ts index fe607098..871f3724 100644 --- a/src/engines/knex/services/sql-schema-migration.service.ts +++ b/src/engines/knex/services/sql-schema-migration.service.ts @@ -254,10 +254,10 @@ export class SqlSchemaMigrationService { ); } const systemTables = [ - 'table_definition', - 'column_definition', - 'relation_definition', - 'route_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', + 'enfyra_route', ]; if (systemTables.includes(targetTableName)) { throw new Error( @@ -703,7 +703,7 @@ export class SqlSchemaMigrationService { if (Object.keys(updateData).length > 0) { try { - await query('table_definition') + await query('enfyra_table') .where('name', tableName) .update(updateData); } catch (error) { @@ -798,7 +798,7 @@ export class SqlSchemaMigrationService { ): Promise { const knex = this.knexService.getKnex(); try { - const row = await knex('table_definition') + const row = await knex('enfyra_table') .where('name', tableName) .first(); const updateData: Record = {}; @@ -817,7 +817,7 @@ export class SqlSchemaMigrationService { } } if (Object.keys(updateData).length === 0) return; - await knex('table_definition') + await knex('enfyra_table') .where('name', tableName) .update(updateData); } catch (error) { diff --git a/src/engines/knex/types/knex-extended.types.ts b/src/engines/knex/types/knex-extended.types.ts index a15eaf2d..e2f4df6e 100644 --- a/src/engines/knex/types/knex-extended.types.ts +++ b/src/engines/knex/types/knex-extended.types.ts @@ -19,9 +19,9 @@ export interface ExtendedQueryBuilder< * @returns QueryBuilder with relations joined * * @example - * const routes = await knex('route_definition') + * const routes = await knex('enfyra_route') * .relations(['mainTable']) - * .select('route_definition.*', 'mainTable.id', 'mainTable.name'); + * .select('enfyra_route.*', 'mainTable.id', 'mainTable.name'); * * // Returns: [{ id: 1, path: '/test', mainTable: { id: 2, name: 'test' } }] */ diff --git a/src/engines/knex/utils/cascade-handler.ts b/src/engines/knex/utils/cascade-handler.ts index ff0dce13..c3cca29a 100644 --- a/src/engines/knex/utils/cascade-handler.ts +++ b/src/engines/knex/utils/cascade-handler.ts @@ -774,7 +774,7 @@ export class CascadeHandler { return relationMeta.targetTableName || relationMeta.targetTable; } - const parentTable = await this.knexInstance('table_definition') + const parentTable = await this.knexInstance('enfyra_table') .where('name', parentTableName) .first('id'); @@ -782,7 +782,7 @@ export class CascadeHandler { return null; } - const relationDef = await this.knexInstance('relation_definition') + const relationDef = await this.knexInstance('enfyra_relation') .where('sourceTableId', parentTable.id) .where('propertyName', relationName) .first('targetTableId'); @@ -791,7 +791,7 @@ export class CascadeHandler { return null; } - const targetTable = await this.knexInstance('table_definition') + const targetTable = await this.knexInstance('enfyra_table') .where('id', relationDef.targetTableId) .first('name'); diff --git a/src/engines/knex/utils/metadata-loader.ts b/src/engines/knex/utils/metadata-loader.ts index b30bc926..31dbb7fd 100644 --- a/src/engines/knex/utils/metadata-loader.ts +++ b/src/engines/knex/utils/metadata-loader.ts @@ -10,7 +10,7 @@ import { getJunctionTableName, getForeignKeyColumnName } from '@enfyra/kernel'; export async function loadAllTableMetadata( knex: Knex, ): Promise> { - const tables = await knex('table_definition').select('*'); + const tables = await knex('enfyra_table').select('*'); const metadataMap = new Map(); for (const table of tables) { const metadata = await loadTableMetadata(knex, table.name); @@ -24,17 +24,17 @@ export async function loadTableMetadata( knex: Knex, tableName: string, ): Promise { - const tableDef = await knex('table_definition') + const tableDef = await knex('enfyra_table') .where('name', tableName) .first(); if (!tableDef) { return null; } - const columns = await knex('column_definition') + const columns = await knex('enfyra_column') .where('tableId', tableDef.id) .select('*') .orderBy('id', 'asc'); - const relations = await knex('relation_definition as r') + const relations = await knex('enfyra_relation as r') .select( 'r.*', 'sourceTable.name as sourceTableName', @@ -42,17 +42,17 @@ export async function loadTableMetadata( 'owningRel.propertyName as mappedByPropertyName', ) .leftJoin( - 'table_definition as sourceTable', + 'enfyra_table as sourceTable', 'r.sourceTableId', 'sourceTable.id', ) .leftJoin( - 'table_definition as targetTable', + 'enfyra_table as targetTable', 'r.targetTableId', 'targetTable.id', ) .leftJoin( - 'relation_definition as owningRel', + 'enfyra_relation as owningRel', 'r.mappedById', 'owningRel.id', ) diff --git a/src/engines/knex/utils/migration/pk-type.util.ts b/src/engines/knex/utils/migration/pk-type.util.ts index b8380580..54d4414f 100644 --- a/src/engines/knex/utils/migration/pk-type.util.ts +++ b/src/engines/knex/utils/migration/pk-type.util.ts @@ -23,16 +23,16 @@ export async function getPrimaryKeyTypeForTable( } } - const pkInfo = await knex('column_definition') + const pkInfo = await knex('enfyra_column') .join( - 'table_definition', - 'column_definition.table', + 'enfyra_table', + 'enfyra_column.table', '=', - 'table_definition.id', + 'enfyra_table.id', ) - .where('table_definition.name', tableName) - .where('column_definition.isPrimary', true) - .select('column_definition.type') + .where('enfyra_table.name', tableName) + .where('enfyra_column.isPrimary', true) + .select('enfyra_column.type') .first(); if (pkInfo) { diff --git a/src/engines/knex/utils/migration/relation-changes.ts b/src/engines/knex/utils/migration/relation-changes.ts index cd643788..9bb6fea4 100644 --- a/src/engines/knex/utils/migration/relation-changes.ts +++ b/src/engines/knex/utils/migration/relation-changes.ts @@ -48,7 +48,7 @@ async function hasOtherOwningJunctionRelation( relation: any, junctionTableName: string, ): Promise { - const rows = await knex('relation_definition') + const rows = await knex('enfyra_relation') .where({ junctionTableName }) .select('id', 'mappedById'); @@ -151,7 +151,7 @@ export async function analyzeRelationChanges( const targetTablesMap = new Map(); if (targetTableIds.length > 0) { - const targetTables = await knex('table_definition') + const targetTables = await knex('enfyra_table') .select('id', 'name') .whereIn('id', targetTableIds); @@ -274,14 +274,14 @@ async function handleDeletedRelations( }); } - const inverseRels = await knex('relation_definition') + const inverseRels = await knex('enfyra_relation') .where({ mappedById: relId }) .select('id', 'propertyName', 'sourceTableId'); for (const inv of inverseRels) { if (!diff.cascadeDeletedInverses) { diff.cascadeDeletedInverses = []; } - const sourceTable = await knex('table_definition') + const sourceTable = await knex('enfyra_table') .where({ id: inv.sourceTableId }) .select('name') .first(); diff --git a/src/engines/knex/utils/migration/sql-diff-generator.ts b/src/engines/knex/utils/migration/sql-diff-generator.ts index 1115fbed..152c4ac6 100644 --- a/src/engines/knex/utils/migration/sql-diff-generator.ts +++ b/src/engines/knex/utils/migration/sql-diff-generator.ts @@ -48,7 +48,7 @@ function isIdempotentDDLError(err: any, dbType: string): boolean { '42701', // duplicate_column '42P07', // duplicate_table '42710', // duplicate_object (constraint/index) - '42P16', // invalid_table_definition (PK already) + '42P16', // invalid_enfyra_table (PK already) ]); if (pgCodes.has(String(code))) return true; } diff --git a/src/engines/knex/utils/provision/sync-table.ts b/src/engines/knex/utils/provision/sync-table.ts index 8271797e..ad1623f9 100644 --- a/src/engines/knex/utils/provision/sync-table.ts +++ b/src/engines/knex/utils/provision/sync-table.ts @@ -765,24 +765,24 @@ async function syncRelationOnDeleteChanges( ): Promise { const dbType = knex.client.config.client; const hasOnDeleteColumn = await knex.schema.hasColumn( - 'relation_definition', + 'enfyra_relation', 'onDelete', ); if (!hasOnDeleteColumn) { - await knex.schema.alterTable('relation_definition', (table) => { + await knex.schema.alterTable('enfyra_relation', (table) => { table .enum('onDelete', ['CASCADE', 'RESTRICT', 'SET NULL']) .notNullable() .defaultTo('SET NULL'); }); } - const tableDefRow = await knex('table_definition') + const tableDefRow = await knex('enfyra_table') .where('name', tableName) .first(); if (!tableDefRow) { return; } - const dbRelations = await knex('relation_definition') + const dbRelations = await knex('enfyra_relation') .where('sourceTableId', tableDefRow.id) .select('id', 'propertyName', 'type', 'onDelete'); const snapshotRelations = schema.definition.relations || []; @@ -857,14 +857,14 @@ async function syncRelationOnDeleteChanges( ` Recreated FK constraint with onDelete: ${snapshotOnDelete}`, ); await knex.raw(`UPDATE ?? SET ?? = ? WHERE ?? = ?`, [ - 'relation_definition', + 'enfyra_relation', 'onDelete', snapshotOnDelete, 'id', dbRel.id, ]); console.log( - ` Updated relation_definition.onDelete to: ${snapshotOnDelete}`, + ` Updated enfyra_relation.onDelete to: ${snapshotOnDelete}`, ); } } diff --git a/src/engines/mongo/services/mongo-migration-journal.service.ts b/src/engines/mongo/services/mongo-migration-journal.service.ts index 6c93bd84..c4144304 100644 --- a/src/engines/mongo/services/mongo-migration-journal.service.ts +++ b/src/engines/mongo/services/mongo-migration-journal.service.ts @@ -21,7 +21,7 @@ export class MongoMigrationJournalService { private readonly mongoService: MongoService; private readonly cacheService?: CacheService; private readonly instanceService?: InstanceService; - private readonly collectionName = 'schema_migration_definition'; + private readonly collectionName = 'enfyra_schema_migration'; constructor(deps: { mongoService: MongoService; diff --git a/src/engines/mongo/services/mongo-physical-migration.service.ts b/src/engines/mongo/services/mongo-physical-migration.service.ts index 2a7b10d6..e6e825eb 100644 --- a/src/engines/mongo/services/mongo-physical-migration.service.ts +++ b/src/engines/mongo/services/mongo-physical-migration.service.ts @@ -35,7 +35,7 @@ interface FieldRenameJobData { migrationId: string; } -const COLLECTION_NAME = 'schema_physical_migration_definition'; +const COLLECTION_NAME = 'enfyra_schema_physical_migration'; const ACTIVE_STATUSES: MongoPhysicalMigrationStatus[] = [ 'pending', 'running', diff --git a/src/engines/mongo/services/mongo-schema-diff.service.ts b/src/engines/mongo/services/mongo-schema-diff.service.ts index d9ad7462..acda4c6c 100644 --- a/src/engines/mongo/services/mongo-schema-diff.service.ts +++ b/src/engines/mongo/services/mongo-schema-diff.service.ts @@ -110,7 +110,7 @@ export class MongoSchemaDiffService { try { await this.queryBuilderService.update( - 'table_definition', + 'enfyra_table', { where: [{ field: 'name', operator: '=', value: collectionName }] }, { indexes: mergedIndexes }, ); diff --git a/src/engines/mongo/services/mongo-schema-migration.service.ts b/src/engines/mongo/services/mongo-schema-migration.service.ts index 061af0df..88ccd89a 100644 --- a/src/engines/mongo/services/mongo-schema-migration.service.ts +++ b/src/engines/mongo/services/mongo-schema-migration.service.ts @@ -307,7 +307,7 @@ export class MongoSchemaMigrationService { } if (Object.keys(updateData).length === 0) return; await this.queryBuilderService.update( - 'table_definition', + 'enfyra_table', { where: [{ field: 'name', operator: '=', value: collectionName }] }, updateData, ); @@ -403,18 +403,18 @@ export class MongoSchemaMigrationService { `Restoring metadata from raw snapshot for table ${snapshot.table.name} (${oid})`, ); await db - .collection('table_definition') + .collection('enfyra_table') .replaceOne({ _id: oid }, snapshot.table, { upsert: true }); - await db.collection('column_definition').deleteMany({ table: oid }); + await db.collection('enfyra_column').deleteMany({ table: oid }); if (snapshot.columns && snapshot.columns.length > 0) { - await db.collection('column_definition').insertMany(snapshot.columns); + await db.collection('enfyra_column').insertMany(snapshot.columns); } - await db.collection('relation_definition').deleteMany({ sourceTable: oid }); + await db.collection('enfyra_relation').deleteMany({ sourceTable: oid }); if (snapshot.relations && snapshot.relations.length > 0) { - await db.collection('relation_definition').insertMany(snapshot.relations); + await db.collection('enfyra_relation').insertMany(snapshot.relations); } const currentSourceRels = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ sourceTable: oid }) .toArray(); const owningRelIds = currentSourceRels @@ -422,7 +422,7 @@ export class MongoSchemaMigrationService { .map((r: any) => r._id); if (owningRelIds.length > 0) { const currentInverse = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ mappedBy: { $in: owningRelIds } }) .toArray(); const snapshotInverseIds = new Set( @@ -431,17 +431,17 @@ export class MongoSchemaMigrationService { for (const inv of currentInverse) { if (!snapshotInverseIds.has(String(inv._id))) { await db - .collection('relation_definition') + .collection('enfyra_relation') .deleteOne({ _id: inv._id }); } } } for (const invRel of snapshot.inverseRelations || []) { const exists = await db - .collection('relation_definition') + .collection('enfyra_relation') .findOne({ _id: invRel._id }); if (!exists) { - await db.collection('relation_definition').insertOne(invRel); + await db.collection('enfyra_relation').insertOne(invRel); } } this.logger.warn( diff --git a/src/http/routes/admin.routes.ts b/src/http/routes/admin.routes.ts index c1edfa04..2ced9b00 100644 --- a/src/http/routes/admin.routes.ts +++ b/src/http/routes/admin.routes.ts @@ -552,11 +552,11 @@ function findRouteScriptRecord(options: { const source = String(tableName || body?.source || body?.target || '').trim(); const pools = - source === 'pre_hook_definition' + source === 'enfyra_pre_hook' ? [route.preHooks || []] - : source === 'post_hook_definition' + : source === 'enfyra_post_hook' ? [route.postHooks || []] - : source === 'route_handler_definition' + : source === 'enfyra_route_handler' ? [route.handlers || []] : [route.handlers || [], route.preHooks || [], route.postHooks || []]; diff --git a/src/http/routes/extension.routes.ts b/src/http/routes/extension.routes.ts index 8088fe64..ca7a6712 100644 --- a/src/http/routes/extension.routes.ts +++ b/src/http/routes/extension.routes.ts @@ -7,7 +7,7 @@ export function registerExtensionRoutes( app: Express, container: AwilixContainer, ) { - app.post('/extension_definition/preview', async (req: any, res: Response) => { + app.post('/enfyra_extension/preview', async (req: any, res: Response) => { const body = req.body; if (!body?.code || typeof body.code !== 'string') { throw new BadRequestException('Code is required'); @@ -31,7 +31,7 @@ export function registerExtensionRoutes( return res.json({ success: true, compiledCode: code, extensionId }); }); - app.post('/extension_definition', async (req: any, res: Response) => { + app.post('/enfyra_extension', async (req: any, res: Response) => { const { processExtensionDefinition } = await import('../../modules/extension-definition/utils/processor.util'); const { processedBody } = await processExtensionDefinition( @@ -51,7 +51,7 @@ export function registerExtensionRoutes( return res.json(result); }); - app.patch('/extension_definition/:id', async (req: any, res: Response) => { + app.patch('/enfyra_extension/:id', async (req: any, res: Response) => { const { processExtensionDefinition } = await import('../../modules/extension-definition/utils/processor.util'); const { processedBody } = await processExtensionDefinition( diff --git a/src/http/routes/file.routes.ts b/src/http/routes/file.routes.ts index 3024aac6..54fb9f5f 100644 --- a/src/http/routes/file.routes.ts +++ b/src/http/routes/file.routes.ts @@ -37,7 +37,7 @@ export function registerFileRoutes( app: Express, container: AwilixContainer, ) { - app.post('/file_definition', async (req: any, res: Response) => { + app.post('/enfyra_file', async (req: any, res: Response) => { const fileManagementService = req.scope?.cradle?.fileManagementService ?? container.cradle.fileManagementService; @@ -49,7 +49,7 @@ export function registerFileRoutes( const fileRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.file_definition; + req.routeData?.context?.$repos?.enfyra_file; if (!fileRepo) { const { ValidationException } = await import('../../domain/exceptions'); @@ -80,10 +80,10 @@ export function registerFileRoutes( } }); - app.get('/file_definition', async (req: any, res: Response) => { + app.get('/enfyra_file', async (req: any, res: Response) => { const fileRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.file_definition; + req.routeData?.context?.$repos?.enfyra_file; if (!fileRepo) { const { ValidationException } = await import('../../domain/exceptions'); @@ -94,7 +94,7 @@ export function registerFileRoutes( return res.json(result); }); - app.patch('/file_definition/:id', async (req: any, res: Response) => { + app.patch('/enfyra_file/:id', async (req: any, res: Response) => { const fileManagementService = req.scope?.cradle?.fileManagementService ?? container.cradle.fileManagementService; @@ -104,7 +104,7 @@ export function registerFileRoutes( const fileRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.file_definition; + req.routeData?.context?.$repos?.enfyra_file; if (!fileRepo) { const { ValidationException } = await import('../../domain/exceptions'); @@ -161,7 +161,7 @@ export function registerFileRoutes( return res.json(result); }); - app.delete('/file_definition/:id', async (req: any, res: Response) => { + app.delete('/enfyra_file/:id', async (req: any, res: Response) => { const fileManagementService = req.scope?.cradle?.fileManagementService ?? container.cradle.fileManagementService; @@ -169,7 +169,7 @@ export function registerFileRoutes( const fileRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.file_definition; + req.routeData?.context?.$repos?.enfyra_file; if (!fileRepo) { const { ValidationException } = await import('../../domain/exceptions'); diff --git a/src/http/routes/folder.routes.ts b/src/http/routes/folder.routes.ts index 12b008f9..1f440ee5 100644 --- a/src/http/routes/folder.routes.ts +++ b/src/http/routes/folder.routes.ts @@ -6,7 +6,7 @@ export function registerFolderRoutes( app: Express, container: AwilixContainer, ) { - app.get('/folder_definition/tree', async (req: any, res: Response) => { + app.get('/enfyra_folder/tree', async (req: any, res: Response) => { const folderTreeCache = req.scope?.cradle?.folderTreeCacheService ?? container.cradle.folderTreeCacheService; diff --git a/src/http/routes/package.routes.ts b/src/http/routes/package.routes.ts index 4e9b18ed..fde27bcc 100644 --- a/src/http/routes/package.routes.ts +++ b/src/http/routes/package.routes.ts @@ -9,7 +9,7 @@ export function registerPackageRoutes( app: Express, container: AwilixContainer, ) { - app.post('/package_definition', async (req: any, res: Response) => { + app.post('/enfyra_package', async (req: any, res: Response) => { const cdnLoader = req.scope?.cradle?.packageCdnLoaderService ?? container.cradle.packageCdnLoaderService; @@ -38,7 +38,7 @@ export function registerPackageRoutes( const packageRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.package_definition; + req.routeData?.context?.$repos?.enfyra_package; if (!packageRepo) { const { ValidationException } = @@ -64,7 +64,7 @@ export function registerPackageRoutes( } const userId = req.routeData?.context?.$user?.id; - const savedPackage = await queryBuilder.insert('package_definition', { + const savedPackage = await queryBuilder.insert('enfyra_package', { ...body, version: body.version || 'latest', description: body.description || '', @@ -109,7 +109,7 @@ export function registerPackageRoutes( return res.json(result); }); - app.patch('/package_definition/:id', async (req: any, res: Response) => { + app.patch('/enfyra_package/:id', async (req: any, res: Response) => { const cdnLoader = req.scope?.cradle?.packageCdnLoaderService ?? container.cradle.packageCdnLoaderService; @@ -127,7 +127,7 @@ export function registerPackageRoutes( const packageRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.package_definition; + req.routeData?.context?.$repos?.enfyra_package; if (!packageRepo) { const { ValidationException } = @@ -183,7 +183,7 @@ export function registerPackageRoutes( return res.json(result); }); - app.delete('/package_definition/:id', async (req: any, res: Response) => { + app.delete('/enfyra_package/:id', async (req: any, res: Response) => { const cdnLoader = req.scope?.cradle?.packageCdnLoaderService ?? container.cradle.packageCdnLoaderService; @@ -197,7 +197,7 @@ export function registerPackageRoutes( const packageRepo = req.routeData?.context?.$repos?.main || - req.routeData?.context?.$repos?.package_definition; + req.routeData?.context?.$repos?.enfyra_package; if (!packageRepo) { const { ValidationException } = @@ -236,7 +236,7 @@ export function registerPackageRoutes( function invalidatePackageCache(eventEmitter: any) { eventEmitter.emit(CACHE_EVENTS.INVALIDATE, { - table: 'package_definition', + table: 'enfyra_package', action: 'reload', scope: 'full', timestamp: Date.now(), @@ -264,7 +264,7 @@ async function updateStatus( extra?: Record, ) { try { - await queryBuilder.update('package_definition', id, { status, ...extra }); + await queryBuilder.update('enfyra_package', id, { status, ...extra }); } catch (error) { console.error( `Failed to update status to ${status} for package ${id}:`, diff --git a/src/modules/dynamic-api/repositories/dynamic.repository.ts b/src/modules/dynamic-api/repositories/dynamic.repository.ts index 8c40de1b..0a9cbf77 100644 --- a/src/modules/dynamic-api/repositories/dynamic.repository.ts +++ b/src/modules/dynamic-api/repositories/dynamic.repository.ts @@ -785,7 +785,7 @@ export class DynamicRepository { sortValue, cleanDeep || {}, ); - if (this.tableName === 'table_definition') { + if (this.tableName === 'enfyra_table') { } const result = await this.queryBuilderService.find({ table: this.tableName, @@ -870,7 +870,7 @@ export class DynamicRepository { ...writeMeta, bodyKeys: Object.keys(body).length, }); - if (this.tableName === 'table_definition') { + if (this.tableName === 'enfyra_table') { body.isSystem = false; const table: any = await this.tableHandlerService.createTable( body as any, @@ -955,7 +955,7 @@ export class DynamicRepository { } private async createBatch(data: any): Promise { - if (this.tableName === 'table_definition') { + if (this.tableName === 'enfyra_table') { throw new BadRequestException('Batch create is not supported for tables'); } @@ -1005,21 +1005,21 @@ export class DynamicRepository { if (isPolicyDeny(createDecision)) { throw new BadRequestException(createDecision.message); } - if (this.tableName === 'route_definition') { + if (this.tableName === 'enfyra_route') { this.filterMethodsSubsetOfAvailable(body, null, 'publicMethods'); this.filterMethodsSubsetOfAvailable(body, null, 'skipRoleGuardMethods'); } - if (this.tableName === 'extension_definition' && body.code) { + if (this.tableName === 'enfyra_extension' && body.code) { const { processExtensionDefinition } = await import('../../extension-definition/utils/processor.util'); const { processedBody } = await processExtensionDefinition(body, 'POST'); Object.assign(body, processedBody); } Object.assign(body, this.normalizeScriptRecordOrThrow(body)); - if (this.tableName === 'flow_step_definition') { + if (this.tableName === 'enfyra_flow_step') { Object.assign(body, this.normalizeFlowStepScriptConfigOrThrow(body)); } - if (this.tableName === 'column_rule_definition') { + if (this.tableName === 'enfyra_column_rule') { await this.assertColumnRuleUnique(body, null); } if (body.id !== undefined) { @@ -1092,17 +1092,17 @@ export class DynamicRepository { if (isPolicyDeny(updateDecision)) { throw new BadRequestException(updateDecision.message); } - if (this.tableName === 'route_definition' && body.publicMethods) { + if (this.tableName === 'enfyra_route' && body.publicMethods) { this.filterMethodsSubsetOfAvailable(body, exists, 'publicMethods'); } - if (this.tableName === 'route_definition' && body.skipRoleGuardMethods) { + if (this.tableName === 'enfyra_route' && body.skipRoleGuardMethods) { this.filterMethodsSubsetOfAvailable( body, exists, 'skipRoleGuardMethods', ); } - if (this.tableName === 'extension_definition' && body.code) { + if (this.tableName === 'enfyra_extension' && body.code) { const { processExtensionDefinition } = await import('../../extension-definition/utils/processor.util'); const { processedBody } = await processExtensionDefinition( @@ -1112,7 +1112,7 @@ export class DynamicRepository { Object.assign(body, processedBody); } Object.assign(body, this.normalizeScriptPatchOrThrow(body, exists)); - if (this.tableName === 'flow_step_definition') { + if (this.tableName === 'enfyra_flow_step') { const normalizedFlowStep = this.normalizeFlowStepScriptConfigOrThrow({ ...exists, ...body, @@ -1130,10 +1130,10 @@ export class DynamicRepository { body.config = normalizedFlowStep.config; } } - if (this.tableName === 'column_rule_definition') { + if (this.tableName === 'enfyra_column_rule') { await this.assertColumnRuleUnique(body, id); } - if (this.tableName === 'table_definition') { + if (this.tableName === 'enfyra_table') { const table: any = await this.tableHandlerService.updateTable( id, body, @@ -1186,7 +1186,7 @@ export class DynamicRepository { durationMs: Date.now() - startedAt, }); if ( - this.tableName === 'user_definition' && + this.tableName === 'enfyra_user' && body && Object.prototype.hasOwnProperty.call(body, 'password') && this.userRevocationService @@ -1241,7 +1241,7 @@ export class DynamicRepository { if (isPolicyDeny(deleteDecision)) { throw new BadRequestException(deleteDecision.message); } - if (this.tableName === 'table_definition') { + if (this.tableName === 'enfyra_table') { const deleted: any = await this.tableHandlerService.delete( id, this.context, @@ -1256,20 +1256,20 @@ export class DynamicRepository { }); return { message: 'Success', statusCode: 200 }; } - if (this.tableName === 'relation_definition') { + if (this.tableName === 'enfyra_relation') { const relRow: any = await this.queryBuilderService.findOne({ - table: 'relation_definition', + table: 'enfyra_relation', where: { id }, fields: ['*', 'sourceTable.id', 'sourceTable.name'], }); const sourceTableId = relRow?.sourceTable?.id; if (!sourceTableId) { throw new BadRequestException( - `relation_definition ${id}: sourceTable not found`, + `enfyra_relation ${id}: sourceTable not found`, ); } const tableRow: any = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { id: sourceTableId }, fields: [ '*', @@ -1283,7 +1283,7 @@ export class DynamicRepository { }); if (!tableRow) { throw new BadRequestException( - `relation_definition ${id}: source table_definition ${sourceTableId} not found`, + `enfyra_relation ${id}: source enfyra_table ${sourceTableId} not found`, ); } const remainingRelations = (tableRow.relations || []).filter( @@ -1347,7 +1347,7 @@ export class DynamicRepository { (tbl, op, d) => this.cascadePolicyCheck(tbl, op, d), () => this.queryBuilderService.delete(this.tableName, id), ); - if (this.tableName === 'flow_definition') { + if (this.tableName === 'enfyra_flow') { await this.flowQueueMaintenanceService?.removeFlowJobs({ id, name: exists.name, @@ -1358,7 +1358,7 @@ export class DynamicRepository { ...writeMeta, durationMs: Date.now() - startedAt, }); - if (this.tableName === 'user_definition' && this.userRevocationService) { + if (this.tableName === 'enfyra_user' && this.userRevocationService) { await this.userRevocationService.publish(id); } return { message: 'Delete successfully!', statusCode: 200 }; @@ -1577,7 +1577,7 @@ export class DynamicRepository { if (columnId == null) return; const existing = await this.queryBuilderService.find({ - table: 'column_rule_definition', + table: 'enfyra_column_rule', filter: { ruleType: { _eq: ruleType }, column: { id: { _eq: columnId } }, diff --git a/src/modules/dynamic-api/services/dynamic.service.ts b/src/modules/dynamic-api/services/dynamic.service.ts index 2bd901f7..8f827dc8 100644 --- a/src/modules/dynamic-api/services/dynamic.service.ts +++ b/src/modules/dynamic-api/services/dynamic.service.ts @@ -91,7 +91,7 @@ export class DynamicService { throw new BusinessLogicException('Route data is required'); } const isTableDefinitionOperation = - routeData.mainTable?.name === 'table_definition'; + routeData.mainTable?.name === 'enfyra_table'; try { const handler = routeData.handler?.trim(); if (!handler) { diff --git a/src/modules/file-management/services/file-assets.service.ts b/src/modules/file-management/services/file-assets.service.ts index b815bfc1..87bc5bd7 100644 --- a/src/modules/file-management/services/file-assets.service.ts +++ b/src/modules/file-management/services/file-assets.service.ts @@ -145,7 +145,7 @@ export class FileAssetsService { if (cached) return this.cloneRow(cached); const fileResult = await this.queryBuilderService.find({ - table: 'file_definition', + table: 'enfyra_file', filter: { [this.queryBuilderService.getPkField()]: { _eq: fileId } }, fields: ['*', 'storageConfig.*'], }); @@ -172,7 +172,7 @@ export class FileAssetsService { const idField = this.queryBuilderService.getPkField(); const permissionsResult = await this.queryBuilderService.find({ - table: 'file_permission_definition', + table: 'enfyra_file_permission', filter: { _and: [ { isEnabled: { _eq: true } }, @@ -282,7 +282,7 @@ export class FileAssetsService { try { const result = await this.queryBuilderService.find({ - table: 'file_permission_definition', + table: 'enfyra_file_permission', filter: { [this.queryBuilderService.getPkField()]: { _in: permissionIds }, }, @@ -306,7 +306,7 @@ export class FileAssetsService { private async handleCacheInvalidation( payload: TCacheInvalidationPayload, ): Promise { - if (payload.table === 'file_definition') { + if (payload.table === 'enfyra_file') { if (payload.scope === 'partial' && payload.ids?.length) { for (const id of payload.ids) this.invalidateFile(id); } else { @@ -317,7 +317,7 @@ export class FileAssetsService { return; } - if (payload.table === 'file_permission_definition') { + if (payload.table === 'enfyra_file_permission') { if (payload.scope === 'partial' && payload.ids?.length) { const fileIds = await this.getPermissionFileIds(payload.ids); for (const fileId of fileIds) this.invalidatePermissionsForFile(fileId); @@ -328,12 +328,12 @@ export class FileAssetsService { return; } - if (payload.table === 'storage_config_definition') { + if (payload.table === 'enfyra_storage_config') { this.fileCache.clear(); return; } - if (payload.table === 'role_definition') { + if (payload.table === 'enfyra_role') { this.permissionsByFileCache.clear(); this.permissionToFileIndex.clear(); } @@ -496,7 +496,7 @@ export class FileAssetsService { for (const perm of permissions) { if (perm.roleId && !perm.role) { perm.role = await this.queryBuilderService.findOne({ - table: 'role_definition', + table: 'enfyra_role', where: { id: perm.roleId }, }); } diff --git a/src/modules/flow/queues/flow-execution-queue.service.ts b/src/modules/flow/queues/flow-execution-queue.service.ts index 064ec5cf..03d7275a 100644 --- a/src/modules/flow/queues/flow-execution-queue.service.ts +++ b/src/modules/flow/queues/flow-execution-queue.service.ts @@ -532,7 +532,7 @@ export class FlowExecutionQueueService { const maxExecutions = flow.maxExecutions || 100; try { const countResult = await this.queryBuilderService.find({ - table: 'flow_execution_definition', + table: 'enfyra_flow_execution', filter: { flow: { _eq: flow.id } }, fields: ['id'], limit: 1, @@ -544,7 +544,7 @@ export class FlowExecutionQueueService { const deleteCount = Math.min(total - maxExecutions, 200); const oldResult = await this.queryBuilderService.find({ - table: 'flow_execution_definition', + table: 'enfyra_flow_execution', filter: { flow: { _eq: flow.id } }, sort: ['startedAt'], fields: ['id'], @@ -553,7 +553,7 @@ export class FlowExecutionQueueService { for (const record of oldResult.data || []) { await this.queryBuilderService.delete( - 'flow_execution_definition', + 'enfyra_flow_execution', record.id, ); } @@ -572,7 +572,7 @@ export class FlowExecutionQueueService { ): Promise { try { const result = await (this.queryBuilderService as any).insert( - 'flow_execution_definition', + 'enfyra_flow_execution', { flow: flow.id, status: 'running', @@ -605,7 +605,7 @@ export class FlowExecutionQueueService { try { if (executionHistoryId != null) { await (this.queryBuilderService as any).update( - 'flow_execution_definition', + 'enfyra_flow_execution', executionHistoryId, finalState, ); @@ -613,7 +613,7 @@ export class FlowExecutionQueueService { } await (this.queryBuilderService as any).insert( - 'flow_execution_definition', + 'enfyra_flow_execution', { flow: flow.id, status: finalState.status, @@ -637,7 +637,7 @@ export class FlowExecutionQueueService { if (executionHistoryId == null) return; try { await (this.queryBuilderService as any).update( - 'flow_execution_definition', + 'enfyra_flow_execution', executionHistoryId, { status: 'running', diff --git a/src/modules/flow/services/flow-queue-maintenance.service.ts b/src/modules/flow/services/flow-queue-maintenance.service.ts index cb29fd5d..7b83d88d 100644 --- a/src/modules/flow/services/flow-queue-maintenance.service.ts +++ b/src/modules/flow/services/flow-queue-maintenance.service.ts @@ -61,7 +61,7 @@ export class FlowQueueMaintenanceService { this.cleanupRunning = true; try { const flowsResult = await this.queryBuilderService.find({ - table: 'flow_definition', + table: 'enfyra_flow', fields: ['id', 'name'], }); const ids = new Set( diff --git a/src/modules/graphql/services/graphql.service.ts b/src/modules/graphql/services/graphql.service.ts index 71a9cd8f..ea1ae3ca 100644 --- a/src/modules/graphql/services/graphql.service.ts +++ b/src/modules/graphql/services/graphql.service.ts @@ -164,9 +164,9 @@ export class GraphqlService { if (queryableChanged) return null; const isMetadata = [ - 'table_definition', - 'column_definition', - 'relation_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', ].includes(payload.table); if (!isMetadata) { diff --git a/src/modules/me/services/me.service.ts b/src/modules/me/services/me.service.ts index 96cd997f..fcdc7368 100644 --- a/src/modules/me/services/me.service.ts +++ b/src/modules/me/services/me.service.ts @@ -44,7 +44,7 @@ export class MeService { async find(req: Request & { user: any; routeData?: any }) { if (!req.user) throw new UnauthorizedException(); - const repo = this.getTrustedRepo(req, 'user_definition'); + const repo = this.getTrustedRepo(req, 'enfyra_user'); if (!repo) { throw new Error('Repository not found in route context'); } @@ -62,7 +62,7 @@ export class MeService { async update(body: any, req: Request & { user: any; routeData?: any }) { if (!req.user) throw new UnauthorizedException(); - const repo = this.getSecureRepo(req, 'user_definition'); + const repo = this.getSecureRepo(req, 'enfyra_user'); if (!repo) { throw new Error('Repository not found in route context'); } @@ -72,7 +72,7 @@ export class MeService { async findOAuthAccounts(req: Request & { user: any; routeData?: any }) { if (!req.user) throw new UnauthorizedException(); - const repo = this.getTrustedRepo(req, 'oauth_account_definition'); + const repo = this.getTrustedRepo(req, 'enfyra_oauth_account'); if (!repo) { throw new Error('Repository not found in route context'); } diff --git a/src/modules/table-management/services/mongo-metadata-snapshot.service.ts b/src/modules/table-management/services/mongo-metadata-snapshot.service.ts index 6e334b23..cd97dc1f 100644 --- a/src/modules/table-management/services/mongo-metadata-snapshot.service.ts +++ b/src/modules/table-management/services/mongo-metadata-snapshot.service.ts @@ -31,7 +31,7 @@ export class MongoMetadataSnapshotService { }; const rawTable = await db - .collection('table_definition') + .collection('enfyra_table') .findOne({ _id: queryId }); if (!rawTable) return null; const table = normalize(rawTable); @@ -51,7 +51,7 @@ export class MongoMetadataSnapshotService { } } const rawColumns = await db - .collection('column_definition') + .collection('enfyra_column') .find({ table: queryId }) .toArray(); const columns = rawColumns.map(normalize); @@ -69,7 +69,7 @@ export class MongoMetadataSnapshotService { } } const rawRelations = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ sourceTable: queryId }) .toArray(); const relations = rawRelations.map(normalize); @@ -86,7 +86,7 @@ export class MongoMetadataSnapshotService { const db = this.mongoService.getDb(); const oid = typeof tableId === 'string' ? new ObjectId(tableId) : tableId; const sourceRelations = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ sourceTable: oid }) .toArray(); const owningRelIds = sourceRelations @@ -95,14 +95,14 @@ export class MongoMetadataSnapshotService { const inverseRelations = owningRelIds.length > 0 ? await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ mappedBy: { $in: owningRelIds } }) .toArray() : []; return { - table: await db.collection('table_definition').findOne({ _id: oid }), + table: await db.collection('enfyra_table').findOne({ _id: oid }), columns: await db - .collection('column_definition') + .collection('enfyra_column') .find({ table: oid }) .toArray(), relations: sourceRelations, @@ -127,22 +127,22 @@ export class MongoMetadataSnapshotService { if (snapshot.table) { await db - .collection('table_definition') + .collection('enfyra_table') .replaceOne({ _id: oid }, snapshot.table, { upsert: true }); } - await db.collection('column_definition').deleteMany({ table: oid }); + await db.collection('enfyra_column').deleteMany({ table: oid }); if (snapshot.columns && snapshot.columns.length > 0) { - await db.collection('column_definition').insertMany(snapshot.columns); + await db.collection('enfyra_column').insertMany(snapshot.columns); } - await db.collection('relation_definition').deleteMany({ sourceTable: oid }); + await db.collection('enfyra_relation').deleteMany({ sourceTable: oid }); if (snapshot.relations && snapshot.relations.length > 0) { - await db.collection('relation_definition').insertMany(snapshot.relations); + await db.collection('enfyra_relation').insertMany(snapshot.relations); } const currentSourceRels = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ sourceTable: oid }) .toArray(); const owningRelIds = currentSourceRels @@ -150,7 +150,7 @@ export class MongoMetadataSnapshotService { .map((r: any) => r._id); if (owningRelIds.length > 0) { const currentInverse = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ mappedBy: { $in: owningRelIds } }) .toArray(); const snapshotInverseIds = new Set( @@ -159,7 +159,7 @@ export class MongoMetadataSnapshotService { for (const inv of currentInverse) { if (!snapshotInverseIds.has(String(inv._id))) { await db - .collection('relation_definition') + .collection('enfyra_relation') .deleteOne({ _id: inv._id }); this.logger.warn( `Cleaned up auto-created inverse relation ${inv.propertyName} (${inv._id})`, @@ -170,10 +170,10 @@ export class MongoMetadataSnapshotService { for (const invRel of snapshot.inverseRelations || []) { const exists = await db - .collection('relation_definition') + .collection('enfyra_relation') .findOne({ _id: invRel._id }); if (!exists) { - await db.collection('relation_definition').insertOne(invRel); + await db.collection('enfyra_relation').insertOne(invRel); this.logger.warn( `Restored inverse relation ${invRel.propertyName} (${invRel._id})`, ); diff --git a/src/modules/table-management/services/mongo-table-create.service.ts b/src/modules/table-management/services/mongo-table-create.service.ts index 7a8a2257..dcb33c2c 100644 --- a/src/modules/table-management/services/mongo-table-create.service.ts +++ b/src/modules/table-management/services/mongo-table-create.service.ts @@ -45,7 +45,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { async createTable(body: TCreateTableBody, context?: TDynamicContext) { const decision = await this.policyService.checkSchemaMigration({ operation: 'create', - tableName: 'table_definition', + tableName: 'enfyra_table', data: body, currentUser: context?.$user, }); @@ -81,14 +81,14 @@ export class MongoTableCreateService extends MongoTableHandlerService { .toArray(); const hasCollection = collections.length > 0; const existing = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: body.name, }, }); if (existing) { throw new DuplicateResourceException( - 'table_definition', + 'enfyra_table', 'name', body.name, ); @@ -142,7 +142,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { validateUniquePropertyNames(body.columns || [], body.relations || []); body.isSystem = false; const tableRecord = await this.queryBuilderService.insert( - 'table_definition', + 'enfyra_table', { name: body.name, isSystem: body.isSystem, @@ -162,7 +162,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { if (body.columns?.length > 0) { for (const col of body.columns) { const columnRecord = await this.queryBuilderService.insert( - 'column_definition', + 'enfyra_column', { name: col.name, type: col.type, @@ -192,7 +192,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { this.logger.error( ` Failed to insert columns, rolling back table creation`, ); - await this.queryBuilderService.delete('table_definition', tableId); + await this.queryBuilderService.delete('enfyra_table', tableId); throw new ValidationException( `Failed to create table: ${error.message}`, { tableName: body.name, error: error.message }, @@ -215,7 +215,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { } else if (typeof rel.targetTable === 'string') { const targetTableRecord = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: rel.targetTable }, }); if (targetTableRecord) { @@ -236,7 +236,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { if (mappedByProperty) { const { data: owningRels } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: targetTableObjectId, propertyName: mappedByProperty, @@ -272,7 +272,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { rel.foreignKeyColumn || rel.propertyName; } else if (resolvedMappedBy) { const owningRel = await this.queryBuilderService.findOne({ - table: 'relation_definition', + table: 'enfyra_relation', where: { _id: resolvedMappedBy }, }); relationData.foreignKeyColumn = @@ -297,7 +297,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { junction.junctionTargetColumn; } const relationRecord = await this.queryBuilderService.insert( - 'relation_definition', + 'enfyra_relation', relationData, ); const relId = @@ -314,7 +314,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { } const { data: existingOnTarget } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: targetTableObjectId, propertyName: rel.inversePropertyName, @@ -328,7 +328,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { } const { data: existingInverse } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { mappedBy: relId }, }); if (existingInverse.length > 0) { @@ -379,7 +379,7 @@ export class MongoTableCreateService extends MongoTableHandlerService { } } const inverseRecord = await this.queryBuilderService.insert( - 'relation_definition', + 'enfyra_relation', inverseData, ); const inverseId = @@ -403,9 +403,9 @@ export class MongoTableCreateService extends MongoTableHandlerService { ` Failed to insert relations, rolling back table creation`, ); for (const colId of insertedColumnIds) { - await this.queryBuilderService.delete('column_definition', colId); + await this.queryBuilderService.delete('enfyra_column', colId); } - await this.queryBuilderService.delete('table_definition', tableId); + await this.queryBuilderService.delete('enfyra_table', tableId); throw new ValidationException( `Failed to create table: ${error.message}`, { tableName: body.name, error: error.message }, diff --git a/src/modules/table-management/services/mongo-table-delete.service.ts b/src/modules/table-management/services/mongo-table-delete.service.ts index 6c8fead1..40fdbb4c 100644 --- a/src/modules/table-management/services/mongo-table-delete.service.ts +++ b/src/modules/table-management/services/mongo-table-delete.service.ts @@ -48,11 +48,11 @@ export class MongoTableDeleteService extends MongoTableHandlerService { try { const tableId = typeof id === 'string' ? new ObjectId(id) : id; const exists = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { _id: tableId }, }); if (!exists) { - throw new ResourceNotFoundException('table_definition', String(id)); + throw new ResourceNotFoundException('enfyra_table', String(id)); } if (exists.isSystem) { throw new ValidationException('Cannot delete system table', { @@ -71,22 +71,22 @@ export class MongoTableDeleteService extends MongoTableHandlerService { throw new ValidationException(decision.message, decision.details); } const { data: routes } = await this.queryBuilderService.find({ - table: 'route_definition', + table: 'enfyra_route', where: { mainTable: tableId, }, }); for (const route of routes) { - await this.queryBuilderService.delete('route_definition', route._id); + await this.queryBuilderService.delete('enfyra_route', route._id); } const { data: relations } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: tableId, }, }); const { data: targetRelations } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { targetTable: tableId, }, @@ -106,18 +106,18 @@ export class MongoTableDeleteService extends MongoTableHandlerService { ); droppedJunctions.add(rel.junctionTableName); } - await this.queryBuilderService.delete('relation_definition', rel._id); + await this.queryBuilderService.delete('enfyra_relation', rel._id); } const { data: columns } = await this.queryBuilderService.find({ - table: 'column_definition', + table: 'enfyra_column', where: { table: tableId, }, }); for (const col of columns) { - await this.queryBuilderService.delete('column_definition', col._id); + await this.queryBuilderService.delete('enfyra_column', col._id); } - await this.queryBuilderService.delete('table_definition', tableId); + await this.queryBuilderService.delete('enfyra_table', tableId); await this.mongoSchemaMigrationService.dropCollection(collectionName); exists.affectedTables = [...affectedTableNames]; return exists; diff --git a/src/modules/table-management/services/mongo-table-handler-base.service.ts b/src/modules/table-management/services/mongo-table-handler-base.service.ts index 0be2dded..68149ba2 100644 --- a/src/modules/table-management/services/mongo-table-handler-base.service.ts +++ b/src/modules/table-management/services/mongo-table-handler-base.service.ts @@ -82,12 +82,12 @@ export class MongoTableHandlerService { }): Promise { if (!Array.isArray(opts.rules)) return; const { data: existing } = await this.queryBuilderService.find({ - table: 'column_rule_definition', + table: 'enfyra_column_rule', where: { [opts.subjectFk]: opts.subjectFkValue }, }); const deletedIds = getDeletedIds(existing, opts.rules); for (const rid of deletedIds) { - await this.queryBuilderService.delete('column_rule_definition', rid); + await this.queryBuilderService.delete('enfyra_column_rule', rid); } for (const rule of opts.rules) { const ruleData: any = { @@ -100,13 +100,13 @@ export class MongoTableHandlerService { const ruleId = rule._id || rule.id; if (ruleId) { await this.queryBuilderService.update( - 'column_rule_definition', + 'enfyra_column_rule', ruleId, ruleData, ); } else { await this.queryBuilderService.insert( - 'column_rule_definition', + 'enfyra_column_rule', ruleData, ); } @@ -120,12 +120,12 @@ export class MongoTableHandlerService { }): Promise { if (!Array.isArray(opts.permissions)) return; const { data: existing } = await this.queryBuilderService.find({ - table: 'field_permission_definition', + table: 'enfyra_field_permission', where: { [opts.subjectFk]: opts.subjectFkValue }, }); const deletedIds = getDeletedIds(existing, opts.permissions); for (const pid of deletedIds) { - await this.queryBuilderService.delete('field_permission_definition', pid); + await this.queryBuilderService.delete('enfyra_field_permission', pid); } for (const perm of opts.permissions) { const roleRef = @@ -151,13 +151,13 @@ export class MongoTableHandlerService { const permId = perm._id || perm.id; if (permId) { await this.queryBuilderService.update( - 'field_permission_definition', + 'enfyra_field_permission', permId, permData, ); } else { await this.queryBuilderService.insert( - 'field_permission_definition', + 'enfyra_field_permission', permData, ); } @@ -187,7 +187,7 @@ export class MongoTableHandlerService { }; const rawTable = await db - .collection('table_definition') + .collection('enfyra_table') .findOne({ _id: queryId }); if (!rawTable) return null; const table = normalize(rawTable); @@ -207,7 +207,7 @@ export class MongoTableHandlerService { } } const rawColumns = await db - .collection('column_definition') + .collection('enfyra_column') .find({ table: queryId }) .toArray(); const columns = rawColumns.map(normalize); @@ -225,7 +225,7 @@ export class MongoTableHandlerService { } } const rawRelations = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ sourceTable: queryId }) .toArray(); const relations = rawRelations.map(normalize); diff --git a/src/modules/table-management/services/mongo-table-update.service.ts b/src/modules/table-management/services/mongo-table-update.service.ts index cce3560b..f9c36ae4 100644 --- a/src/modules/table-management/services/mongo-table-update.service.ts +++ b/src/modules/table-management/services/mongo-table-update.service.ts @@ -84,12 +84,12 @@ export class MongoTableUpdateService extends MongoTableHandlerService { try { const queryId = typeof id === 'string' ? new ObjectId(id) : id; const exists = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { _id: queryId }, }); - stepLog(`STEP 3 fetched table_definition (+${lap()}ms)`); + stepLog(`STEP 3 fetched enfyra_table (+${lap()}ms)`); if (!exists) { - throw new ResourceNotFoundException('table_definition', String(id)); + throw new ResourceNotFoundException('enfyra_table', String(id)); } if (exists.isSystem) { throw new ValidationException('Cannot modify system table', { @@ -101,7 +101,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (tableRenamed) { const incomingRelations = await this.mongoService .getDb() - .collection('relation_definition') + .collection('enfyra_relation') .find({ targetTable: queryId, sourceTable: { $ne: queryId }, @@ -119,7 +119,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (incomingSourceIds.length > 0) { const incomingSourceTables = await this.mongoService .getDb() - .collection('table_definition') + .collection('enfyra_table') .find({ _id: { $in: incomingSourceIds.map( @@ -144,7 +144,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (body.relations) { const { data: existingRelations } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: queryId, }, @@ -182,7 +182,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { ); } const targetExists = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { _id: targetTableObjectId }, }); if (!targetExists) { @@ -193,7 +193,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { } } else if (typeof rel.targetTable === 'string') { const targetTableRecord = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: rel.targetTable }, }); if (!targetTableRecord) { @@ -211,7 +211,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { const mappedByProperty = getRelationMappedByProperty(rel); if (mappedByProperty && targetTableObjectId) { const { data: owningRels } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: targetTableObjectId, propertyName: mappedByProperty, @@ -295,7 +295,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if ('validateBody' in body) updateData.validateBody = body.validateBody; if (Object.keys(updateData).length > 0) { await this.queryBuilderService.update( - 'table_definition', + 'enfyra_table', id, updateData, ); @@ -309,25 +309,25 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (tableRenamed) { await this.mongoService .getDb() - .collection('relation_definition') + .collection('enfyra_relation') .updateMany( { sourceTable: queryId }, { $set: { sourceTableName: body.name, updatedAt: new Date() } }, ); await this.mongoService .getDb() - .collection('relation_definition') + .collection('enfyra_relation') .updateMany( { targetTable: queryId }, { $set: { targetTableName: body.name, updatedAt: new Date() } }, ); } - stepLog(`STEP 7 updated table_definition row (+${lap()}ms)`); + stepLog(`STEP 7 updated enfyra_table row (+${lap()}ms)`); const renamedColumns: Array<{ oldName: string; newName: string }> = []; if (body.columns) { const { data: existingColumns } = await this.queryBuilderService.find( { - table: 'column_definition', + table: 'enfyra_column', where: { table: queryId, }, @@ -339,13 +339,13 @@ export class MongoTableUpdateService extends MongoTableHandlerService { typeof colId === 'string' ? new ObjectId(colId) : colId; await this.mongoService .getDb() - .collection('column_rule_definition') + .collection('enfyra_column_rule') .deleteMany({ column: colObjectId }); await this.mongoService .getDb() - .collection('field_permission_definition') + .collection('enfyra_field_permission') .deleteMany({ column: colObjectId }); - await this.queryBuilderService.delete('column_definition', colId); + await this.queryBuilderService.delete('enfyra_column', colId); } for (const col of body.columns) { if (col._id || col.id) { @@ -386,7 +386,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (col._id || col.id) { const colId = col._id || col.id; await this.queryBuilderService.update( - 'column_definition', + 'enfyra_column', colId, columnData, ); @@ -394,7 +394,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { typeof colId === 'string' ? new ObjectId(colId) : colId; } else { const inserted = await this.queryBuilderService.insert( - 'column_definition', + 'enfyra_column', columnData, ); colObjectId = @@ -423,7 +423,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { preloadedRelations ?? ( await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: queryId, }, @@ -438,7 +438,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { typeof relId === 'string' ? new ObjectId(relId) : relId; await this.mongoService .getDb() - .collection('field_permission_definition') + .collection('enfyra_field_permission') .deleteMany({ relation: relObjectId }); const deletedRelation = existingRelations.find( (r: any) => r._id?.toString() === relId.toString(), @@ -446,7 +446,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (deletedRelation) { const { data: inverseRels } = await this.queryBuilderService.find( { - table: 'relation_definition', + table: 'enfyra_relation', where: { mappedBy: deletedRelation._id }, }, ); @@ -455,15 +455,15 @@ export class MongoTableUpdateService extends MongoTableHandlerService { affectedTableNames.add(inv.sourceTableName); await this.mongoService .getDb() - .collection('field_permission_definition') + .collection('enfyra_field_permission') .deleteMany({ relation: inv._id }); await this.queryBuilderService.delete( - 'relation_definition', + 'enfyra_relation', inv._id, ); } } - await this.queryBuilderService.delete('relation_definition', relId); + await this.queryBuilderService.delete('enfyra_relation', relId); } const relationIds = []; for (const rel of body.relations) { @@ -491,7 +491,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { : targetTableIdFromObj; } else if (typeof rel.targetTable === 'string') { const targetTableRecord = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: rel.targetTable }, }); if (targetTableRecord) { @@ -514,7 +514,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { resolvedTargetTableName = rel.targetTable?.name; if (!resolvedTargetTableName) { const targetRec = await this.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { _id: targetTableObjectId }, }); resolvedTargetTableName = targetRec?.name; @@ -527,7 +527,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { const mappedByProperty = getRelationMappedByProperty(rel); if (mappedByProperty) { const { data: owningRels } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: targetTableObjectId, propertyName: mappedByProperty, @@ -561,7 +561,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { rel.propertyName; } else if (updateResolvedMappedBy) { const owningRel = await this.queryBuilderService.findOne({ - table: 'relation_definition', + table: 'enfyra_relation', where: { _id: updateResolvedMappedBy }, }); relationData.foreignKeyColumn = @@ -573,7 +573,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { if (rel.type === 'many-to-many' && targetRelName) { if (updateResolvedMappedBy) { const owningRel = await this.queryBuilderService.findOne({ - table: 'relation_definition', + table: 'enfyra_relation', where: { _id: updateResolvedMappedBy }, }); if (owningRel?.junctionTableName) { @@ -605,7 +605,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { let relObjectId; if (relId) { await this.queryBuilderService.update( - 'relation_definition', + 'enfyra_relation', relId, relationData, ); @@ -613,7 +613,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { typeof relId === 'string' ? new ObjectId(relId) : relId; } else { const inserted = await this.queryBuilderService.insert( - 'relation_definition', + 'enfyra_relation', relationData, ); relObjectId = @@ -636,7 +636,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { } const { data: existingOnTarget } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { sourceTable: targetTableObjectId, propertyName: rel.inversePropertyName, @@ -650,7 +650,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { } const { data: existingInverse } = await this.queryBuilderService.find({ - table: 'relation_definition', + table: 'enfyra_relation', where: { mappedBy: relObjectId }, }); if (existingInverse.length > 0) { @@ -693,7 +693,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { junction.junctionSourceColumn; } await this.queryBuilderService.insert( - 'relation_definition', + 'enfyra_relation', inverseData, ); const invTargetName = @@ -845,7 +845,7 @@ export class MongoTableUpdateService extends MongoTableHandlerService { isEnabled: body.graphqlEnabled === true, isSystem: exists.isSystem || false, }); - stepLog(`STEP 14 gql_definition sync done (+${lap()}ms)`); + stepLog(`STEP 14 enfyra_graphql sync done (+${lap()}ms)`); } finalMetadata.affectedTables = [...affectedTableNames]; diff --git a/src/modules/table-management/services/sql-table-create.service.ts b/src/modules/table-management/services/sql-table-create.service.ts index bc04e06d..b8ec579a 100644 --- a/src/modules/table-management/services/sql-table-create.service.ts +++ b/src/modules/table-management/services/sql-table-create.service.ts @@ -42,7 +42,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { async createTable(body: TCreateTableBody, context?: TDynamicContext) { const decision = await this.policyService.checkSchemaMigration({ operation: 'create', - tableName: 'table_definition', + tableName: 'enfyra_table', data: body, currentUser: context?.$user, }); @@ -103,13 +103,13 @@ export class SqlTableCreateService extends SqlTableHandlerService { abortSignal.addEventListener('abort', onAbort, { once: true }); } const hasTable = await knex.schema.hasTable(body.name); - const existing = await trx('table_definition') + const existing = await trx('enfyra_table') .where({ name: body.name }) .first(); if (existing) { await trx.rollback(); throw new DuplicateResourceException( - 'table_definition', + 'enfyra_table', 'name', body.name, ); @@ -172,7 +172,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { ?.filter((id: any) => id != null) || []; const targetTablesMap = new Map(); if (targetTableIds.length > 0) { - const targetTables = await trx('table_definition') + const targetTables = await trx('enfyra_table') .select('id', 'name') .whereIn('id', targetTableIds); for (const table of targetTables) { @@ -192,7 +192,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { } body.isSystem = false; const dbType = this.queryBuilderService.getDatabaseType(); - const insertResult = await trx('table_definition').insert( + const insertResult = await trx('enfyra_table').insert( { name: body.name, isSystem: body.isSystem, @@ -229,7 +229,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { col.metadata !== undefined ? JSON.stringify(col.metadata) : null, tableId: tableId, })); - await trx('column_definition').insert(columnsToInsert); + await trx('enfyra_column').insert(columnsToInsert); } if (bodyRelations.length > 0) { const targetTableIds = bodyRelations @@ -237,7 +237,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { .filter((id: any) => id != null); const targetTablesMap = new Map(); if (targetTableIds.length > 0) { - const targetTables = await trx('table_definition') + const targetTables = await trx('enfyra_table') .select('id', 'name') .whereIn('id', targetTableIds); for (const table of targetTables) { @@ -252,7 +252,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { const targetTableId = getRelationTargetTableId(rel); let mappedById: number | null = null; if (rel.mappedBy) { - const owningRel = await trx('relation_definition') + const owningRel = await trx('enfyra_relation') .where({ sourceTableId: targetTableId, propertyName: rel.mappedBy, @@ -298,7 +298,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { } relationsToInsert.push({ insertData, rel }); } - await trx('relation_definition').insert( + await trx('enfyra_relation').insert( relationsToInsert.map((r: any) => r.insertData), ); for (const { insertData: inserted, rel } of relationsToInsert) { @@ -313,7 +313,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { const targetTableName = targetTablesMap.get( relationTargetTableMapKey(targetTableId), ); - const existingOnTarget = await trx('relation_definition') + const existingOnTarget = await trx('enfyra_relation') .where({ sourceTableId: targetTableId, propertyName: rel.inversePropertyName, @@ -328,14 +328,14 @@ export class SqlTableCreateService extends SqlTableHandlerService { }, ); } - const owningRel = await trx('relation_definition') + const owningRel = await trx('enfyra_relation') .where({ sourceTableId: tableId, propertyName: rel.propertyName, }) .first(); if (!owningRel) continue; - const existingInverse = await trx('relation_definition') + const existingInverse = await trx('enfyra_relation') .where({ mappedById: owningRel.id }) .first(); if (existingInverse) { @@ -364,7 +364,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { inverseData.junctionSourceColumn = inserted.junctionTargetColumn; inverseData.junctionTargetColumn = inserted.junctionSourceColumn; } - await trx('relation_definition').insert(inverseData); + await trx('enfyra_relation').insert(inverseData); this.logger.log( `Auto-created inverse relation '${rel.inversePropertyName}' on '${targetTableName}'`, ); @@ -457,7 +457,7 @@ export class SqlTableCreateService extends SqlTableHandlerService { if (metadataCommitted && !schemaCreated) { try { await this.queryBuilderService - .getKnex()('table_definition') + .getKnex()('enfyra_table') .where({ name: body.name }) .delete(); } catch (metadataCleanupError: any) { diff --git a/src/modules/table-management/services/sql-table-delete.service.ts b/src/modules/table-management/services/sql-table-delete.service.ts index d700018d..913fe3eb 100644 --- a/src/modules/table-management/services/sql-table-delete.service.ts +++ b/src/modules/table-management/services/sql-table-delete.service.ts @@ -63,9 +63,9 @@ export class SqlTableDeleteService extends SqlTableHandlerService { abortSignal.addEventListener('abort', onAbort, { once: true }); } try { - const exists = await trx('table_definition').where({ id }).first(); + const exists = await trx('enfyra_table').where({ id }).first(); if (!exists) { - throw new ResourceNotFoundException('table_definition', String(id)); + throw new ResourceNotFoundException('enfyra_table', String(id)); } if (exists.isSystem) { throw new ValidationException('Cannot delete system table', { @@ -83,16 +83,16 @@ export class SqlTableDeleteService extends SqlTableHandlerService { if (isPolicyDeny(decision)) { throw new ValidationException(decision.message, decision.details); } - const allRelations = await trx('relation_definition') + const allRelations = await trx('enfyra_relation') .where({ sourceTableId: id }) .orWhere({ targetTableId: id }) .select('*'); - const targetRelations = await trx('relation_definition') + const targetRelations = await trx('enfyra_relation') .where({ targetTableId: id }) .select('*'); for (const rel of targetRelations) { if (['one-to-many', 'many-to-one', 'one-to-one'].includes(rel.type)) { - const sourceTable = await trx('table_definition') + const sourceTable = await trx('enfyra_table') .where({ id: rel.sourceTableId }) .first(); if (sourceTable) { @@ -222,14 +222,14 @@ export class SqlTableDeleteService extends SqlTableHandlerService { } } catch (error: any) {} for (const rel of targetRelations) { - const sourceTable = await trx('table_definition') + const sourceTable = await trx('enfyra_table') .where({ id: rel.sourceTableId }) .select('name') .first(); if (sourceTable?.name) affectedTableNames.add(sourceTable.name); } - await trx('relation_definition').where({ targetTableId: id }).delete(); - await trx('table_definition').where({ id }).delete(); + await trx('enfyra_relation').where({ targetTableId: id }).delete(); + await trx('enfyra_table').where({ id }).delete(); await this.schemaMigrationService.dropTable( tableName, allRelations, diff --git a/src/modules/table-management/services/sql-table-metadata-builder.service.ts b/src/modules/table-management/services/sql-table-metadata-builder.service.ts index 8b3380b4..fc02c1bf 100644 --- a/src/modules/table-management/services/sql-table-metadata-builder.service.ts +++ b/src/modules/table-management/services/sql-table-metadata-builder.service.ts @@ -25,7 +25,7 @@ export class SqlTableMetadataBuilderService { trx: any, tableId: string | number, ): Promise { - const table = await trx('table_definition').where({ id: tableId }).first(); + const table = await trx('enfyra_table').where({ id: tableId }).first(); if (!table) return null; if (table.uniques && typeof table.uniques === 'string') { try { @@ -41,7 +41,7 @@ export class SqlTableMetadataBuilderService { table.indexes = []; } } - table.columns = await trx('column_definition') + table.columns = await trx('enfyra_column') .where({ tableId }) .select('*'); for (const col of table.columns) { @@ -56,21 +56,21 @@ export class SqlTableMetadataBuilderService { } catch (e: any) {} } } - const relations = await trx('relation_definition') - .where({ 'relation_definition.sourceTableId': tableId }) + const relations = await trx('enfyra_relation') + .where({ 'enfyra_relation.sourceTableId': tableId }) .leftJoin( - 'table_definition', - 'relation_definition.targetTableId', - 'table_definition.id', + 'enfyra_table', + 'enfyra_relation.targetTableId', + 'enfyra_table.id', ) .select( - 'relation_definition.*', - 'table_definition.name as targetTableName', + 'enfyra_relation.*', + 'enfyra_table.name as targetTableName', ); for (const rel of relations) { rel.sourceTableName = table.name; if (!rel.targetTableName && rel.targetTableId) { - const targetTable = await trx('table_definition') + const targetTable = await trx('enfyra_table') .where({ id: rel.targetTableId }) .first(); if (targetTable) { diff --git a/src/modules/table-management/services/sql-table-metadata-writer.service.ts b/src/modules/table-management/services/sql-table-metadata-writer.service.ts index 41282e24..d20869fd 100644 --- a/src/modules/table-management/services/sql-table-metadata-writer.service.ts +++ b/src/modules/table-management/services/sql-table-metadata-writer.service.ts @@ -32,7 +32,7 @@ export class SqlTableMetadataWriterService { ? this.filterConstraintGroups(body.indexes, allowedConstraintFields) : body.indexes; - await queryRunner('table_definition') + await queryRunner('enfyra_table') .where({ id }) .update({ name: body.name, @@ -64,12 +64,12 @@ export class SqlTableMetadataWriterService { const bodyColumns = body.columns.filter( (col: any) => !ignoredFkColumns.has(col.name), ); - const existingColumns = await queryRunner('column_definition') + const existingColumns = await queryRunner('enfyra_column') .where({ tableId: id }) .select('id'); const deletedColumnIds = getDeletedIds(existingColumns, bodyColumns); if (deletedColumnIds.length > 0) { - await queryRunner('column_definition') + await queryRunner('enfyra_column') .whereIn('id', deletedColumnIds) .delete(); } @@ -105,14 +105,14 @@ export class SqlTableMetadataWriterService { }; let columnId: number | string; if (col.id) { - await queryRunner('column_definition') + await queryRunner('enfyra_column') .where({ id: col.id }) .update(columnData); columnId = col.id; } else { columnId = await this.insertAndGetId( queryRunner, - 'column_definition', + 'enfyra_column', columnData, ); } @@ -130,7 +130,7 @@ export class SqlTableMetadataWriterService { } if (body.relations) { - const existingRelations = await queryRunner('relation_definition') + const existingRelations = await queryRunner('enfyra_relation') .where({ sourceTableId: id }) .select('id'); const deletedRelationIds = getDeletedIds( @@ -138,20 +138,20 @@ export class SqlTableMetadataWriterService { body.relations, ); if (deletedRelationIds.length > 0) { - const inverseRelations = await queryRunner('relation_definition') + const inverseRelations = await queryRunner('enfyra_relation') .whereIn('mappedById', deletedRelationIds) .select('sourceTableId'); for (const inv of inverseRelations) { - const invTable = await queryRunner('table_definition') + const invTable = await queryRunner('enfyra_table') .where({ id: inv.sourceTableId }) .select('name') .first(); if (invTable?.name) affectedTableNames.add(invTable.name); } - await queryRunner('relation_definition') + await queryRunner('enfyra_relation') .whereIn('mappedById', deletedRelationIds) .delete(); - await queryRunner('relation_definition') + await queryRunner('enfyra_relation') .whereIn('id', deletedRelationIds) .delete(); } @@ -160,7 +160,7 @@ export class SqlTableMetadataWriterService { .filter((tid: any) => tid != null); const targetTablesMap = new Map(); if (targetTableIds.length > 0) { - const targetTables = await queryRunner('table_definition') + const targetTables = await queryRunner('enfyra_table') .select('id', 'name') .whereIn('id', targetTableIds); for (const table of targetTables) { @@ -175,7 +175,7 @@ export class SqlTableMetadataWriterService { ); if (targetTableName) affectedTableNames.add(targetTableName); const existingRel = rel.id - ? await queryRunner('relation_definition') + ? await queryRunner('enfyra_relation') .where({ id: rel.id }) .first() : null; @@ -190,7 +190,7 @@ export class SqlTableMetadataWriterService { let updateMappedById: number | null = null; let mappedByRelation: any = null; if (mappedByProperty) { - mappedByRelation = await queryRunner('relation_definition') + mappedByRelation = await queryRunner('enfyra_relation') .where({ sourceTableId: targetTableId, propertyName: mappedByProperty, @@ -207,7 +207,7 @@ export class SqlTableMetadataWriterService { } else if (rel.id && existingRel) { updateMappedById = existingRel.mappedById || null; if (updateMappedById) { - mappedByRelation = await queryRunner('relation_definition') + mappedByRelation = await queryRunner('enfyra_relation') .where({ id: updateMappedById }) .select( 'id', @@ -279,7 +279,7 @@ export class SqlTableMetadataWriterService { relationData.junctionTargetColumn = junction.junctionTargetColumn; } } else if (updateMappedById) { - const owningRel = await queryRunner('relation_definition') + const owningRel = await queryRunner('enfyra_relation') .where({ id: updateMappedById }) .first(); if (owningRel?.junctionTableName) { @@ -304,14 +304,14 @@ export class SqlTableMetadataWriterService { } let relationId: number | string; if (rel.id) { - await queryRunner('relation_definition') + await queryRunner('enfyra_relation') .where({ id: rel.id }) .update(relationData); relationId = rel.id; } else { relationId = await this.insertAndGetId( queryRunner, - 'relation_definition', + 'enfyra_relation', relationData, ); } @@ -328,7 +328,7 @@ export class SqlTableMetadataWriterService { { relationName: rel.propertyName }, ); } - const existingOnTarget = await queryRunner('relation_definition') + const existingOnTarget = await queryRunner('enfyra_relation') .where({ sourceTableId: targetTableId, propertyName: rel.inversePropertyName, @@ -345,7 +345,7 @@ export class SqlTableMetadataWriterService { }, ); } - const existingInverse = await queryRunner('relation_definition') + const existingInverse = await queryRunner('enfyra_relation') .where({ mappedById: relationId }) .first(); if (existingInverse) { @@ -370,7 +370,7 @@ export class SqlTableMetadataWriterService { onDelete: rel.onDelete || 'SET NULL', }; if (rel.type === 'many-to-many') { - const owningRel = await queryRunner('relation_definition') + const owningRel = await queryRunner('enfyra_relation') .where({ id: relationId }) .first(); if (owningRel?.junctionTableName) { @@ -379,7 +379,7 @@ export class SqlTableMetadataWriterService { inverseData.junctionTargetColumn = owningRel.junctionSourceColumn; } } - await queryRunner('relation_definition').insert(inverseData); + await queryRunner('enfyra_relation').insert(inverseData); const targetName = targetTablesMap.get( relationTargetTableMapKey(targetTableId), ); @@ -413,7 +413,7 @@ export class SqlTableMetadataWriterService { tableId: string | number, relations: any[], ): Promise> { - const existingRelations = await queryRunner('relation_definition') + const existingRelations = await queryRunner('enfyra_relation') .where({ sourceTableId: tableId }) .select('id', 'propertyName', 'foreignKeyColumn', 'mappedById'); const existingById = new Map( @@ -474,12 +474,12 @@ export class SqlTableMetadataWriterService { }, ): Promise { if (!Array.isArray(opts.rules)) return; - const existing = await queryRunner('column_rule_definition') + const existing = await queryRunner('enfyra_column_rule') .where({ [opts.fkField]: opts.fkValue }) .select('id'); const deletedIds = getDeletedIds(existing, opts.rules); if (deletedIds.length > 0) { - await queryRunner('column_rule_definition') + await queryRunner('enfyra_column_rule') .whereIn('id', deletedIds) .delete(); } @@ -492,11 +492,11 @@ export class SqlTableMetadataWriterService { [opts.fkField]: opts.fkValue, }; if (rule.id) { - await queryRunner('column_rule_definition') + await queryRunner('enfyra_column_rule') .where({ id: rule.id }) .update(ruleData); } else { - await queryRunner('column_rule_definition').insert(ruleData); + await queryRunner('enfyra_column_rule').insert(ruleData); } } } @@ -510,26 +510,26 @@ export class SqlTableMetadataWriterService { }, ): Promise { if (!Array.isArray(opts.permissions)) return; - const existing = await queryRunner('field_permission_definition') + const existing = await queryRunner('enfyra_field_permission') .where({ [opts.subjectFk]: opts.subjectFkValue }) .select('id'); const deletedIds = getDeletedIds(existing, opts.permissions); if (deletedIds.length > 0) { const junctionRows = await queryRunner( - 'field_permission_definition_allowedUsers_user_definition', + 'enfyra_field_permission_allowedUsers_enfyra_user', ) - .whereIn('field_permission_definitionId', deletedIds) + .whereIn('enfyra_field_permissionId', deletedIds) .select('*') .catch(() => [] as any[]); if (Array.isArray(junctionRows) && junctionRows.length > 0) { await queryRunner( - 'field_permission_definition_allowedUsers_user_definition', + 'enfyra_field_permission_allowedUsers_enfyra_user', ) - .whereIn('field_permission_definitionId', deletedIds) + .whereIn('enfyra_field_permissionId', deletedIds) .delete() .catch((): undefined => undefined); } - await queryRunner('field_permission_definition') + await queryRunner('enfyra_field_permission') .whereIn('id', deletedIds) .delete(); } @@ -552,14 +552,14 @@ export class SqlTableMetadataWriterService { }; let permId: number | string; if (perm.id) { - await queryRunner('field_permission_definition') + await queryRunner('enfyra_field_permission') .where({ id: perm.id }) .update(permData); permId = perm.id; } else { permId = await this.insertAndGetId( queryRunner, - 'field_permission_definition', + 'enfyra_field_permission', permData, ); } @@ -578,16 +578,16 @@ export class SqlTableMetadataWriterService { .map((u: any) => (typeof u === 'object' ? (u.id ?? u._id) : u)) .filter((v: any) => v != null); const junctionTable = - 'field_permission_definition_allowedUsers_user_definition'; + 'enfyra_field_permission_allowedUsers_enfyra_user'; try { await queryRunner(junctionTable) - .where({ field_permission_definitionId: permId }) + .where({ enfyra_field_permissionId: permId }) .delete(); if (userIds.length > 0) { await queryRunner(junctionTable).insert( userIds.map((uid: any) => ({ - field_permission_definitionId: permId, - user_definitionId: uid, + enfyra_field_permissionId: permId, + enfyra_userId: uid, })), ); } diff --git a/src/modules/table-management/services/sql-table-update.service.ts b/src/modules/table-management/services/sql-table-update.service.ts index 854fc52e..f101039a 100644 --- a/src/modules/table-management/services/sql-table-update.service.ts +++ b/src/modules/table-management/services/sql-table-update.service.ts @@ -102,14 +102,14 @@ export class SqlTableUpdateService extends SqlTableHandlerService { try { // === VALIDATION PHASE (read-only, no transaction) === - const exists = await knex('table_definition').where({ id }).first(); - stepLog(`STEP 3 fetched table_definition row (+${lap()}ms)`); + const exists = await knex('enfyra_table').where({ id }).first(); + stepLog(`STEP 3 fetched enfyra_table row (+${lap()}ms)`); if (!exists) { - throw new ResourceNotFoundException('table_definition', String(id)); + throw new ResourceNotFoundException('enfyra_table', String(id)); } const tableRenamed = !!body.name && body.name !== exists.name; if (tableRenamed) { - const incomingRelations = await knex('relation_definition') + const incomingRelations = await knex('enfyra_relation') .where({ targetTableId: exists.id }) .whereNot({ sourceTableId: exists.id }) .select('sourceTableId'); @@ -117,7 +117,7 @@ export class SqlTableUpdateService extends SqlTableHandlerService { ...new Set(incomingRelations.map((rel: any) => rel.sourceTableId)), ].filter((sourceId) => sourceId != null); if (incomingSourceIds.length > 0) { - const incomingSourceTables = await knex('table_definition') + const incomingSourceTables = await knex('enfyra_table') .whereIn('id', incomingSourceIds) .select('name'); for (const table of incomingSourceTables) { @@ -134,7 +134,7 @@ export class SqlTableUpdateService extends SqlTableHandlerService { ?.filter((tid: any) => tid != null) || []; const m2mTargetTablesMap = new Map(); if (m2mTargetTableIds.length > 0) { - const targetTables = await knex('table_definition') + const targetTables = await knex('enfyra_table') .select('id', 'name') .whereIn('id', m2mTargetTableIds); for (const table of targetTables) { @@ -166,7 +166,7 @@ export class SqlTableUpdateService extends SqlTableHandlerService { ?.filter((tid: any) => tid != null) || []; const allTargetTablesMap = new Map(); if (allTargetTableIds.length > 0) { - const targetTables = await knex('table_definition') + const targetTables = await knex('enfyra_table') .select('id', 'name') .whereIn('id', allTargetTableIds); for (const table of targetTables) { @@ -448,7 +448,7 @@ export class SqlTableUpdateService extends SqlTableHandlerService { isEnabled: body.graphqlEnabled === true, isSystem: exists.isSystem || false, }); - stepLog(`gql_definition sync done (+${lap()}ms)`); + stepLog(`enfyra_graphql sync done (+${lap()}ms)`); } const latestMetadata = diff --git a/src/modules/table-management/services/table-post-migration.service.ts b/src/modules/table-management/services/table-post-migration.service.ts index 5cf169da..848afc6d 100644 --- a/src/modules/table-management/services/table-post-migration.service.ts +++ b/src/modules/table-management/services/table-post-migration.service.ts @@ -32,16 +32,16 @@ export async function syncSqlGqlDefinition(input: { isSystem: boolean; }): Promise { const { knex, tableId, isEnabled, isSystem } = input; - const existingGql = await knex('gql_definition') + const existingGql = await knex('enfyra_graphql') .where({ tableId }) .first(); if (existingGql) { - await knex('gql_definition') + await knex('enfyra_graphql') .where({ id: existingGql.id }) .update({ isEnabled }); return; } - await knex('gql_definition').insert({ + await knex('enfyra_graphql').insert({ tableId, isEnabled, isSystem, @@ -154,11 +154,11 @@ export async function syncMongoGqlDefinition(input: { input; const db = mongoService.getDb(); const existingGql = await queryBuilderService.findOne({ - table: 'gql_definition', + table: 'enfyra_graphql', where: { table: tableId }, }); if (existingGql) { - await db.collection('gql_definition').updateOne( + await db.collection('enfyra_graphql').updateOne( { _id: existingGql._id }, { $set: { @@ -169,7 +169,7 @@ export async function syncMongoGqlDefinition(input: { ); return; } - await db.collection('gql_definition').insertOne({ + await db.collection('enfyra_graphql').insertOne({ table: tableId, isEnabled, isSystem, diff --git a/src/modules/table-management/services/table-route-artifacts.service.ts b/src/modules/table-management/services/table-route-artifacts.service.ts index 5f6e7e67..8bbe2454 100644 --- a/src/modules/table-management/services/table-route-artifacts.service.ts +++ b/src/modules/table-management/services/table-route-artifacts.service.ts @@ -16,7 +16,7 @@ export async function ensureSqlTableRouteArtifacts(input: { logger: { warn(message: string): void }; }): Promise { const { trx, metadataCacheService, tableName, tableId, logger } = input; - const existingRoute = await trx('route_definition') + const existingRoute = await trx('enfyra_route') .where({ path: `/${tableName}` }) .first(); if (existingRoute) { @@ -24,21 +24,21 @@ export async function ensureSqlTableRouteArtifacts(input: { return; } - await trx('route_definition').insert({ + await trx('enfyra_route').insert({ path: `/${tableName}`, mainTableId: tableId, isEnabled: true, isSystem: false, icon: 'lucide:table', }); - const newRoute = await trx('route_definition') + const newRoute = await trx('enfyra_route') .where({ path: `/${tableName}` }) .first(); if (!newRoute?.id) return; - const methods = await trx('method_definition').select('id', 'name'); + const methods = await trx('enfyra_method').select('id', 'name'); const routeTableMeta = - await metadataCacheService.getTableMetadata('route_definition'); + await metadataCacheService.getTableMetadata('enfyra_route'); const availableMethodsRel = routeTableMeta?.relations?.find( (relation: any) => relation.propertyName === ROUTE_METHOD_PROPERTY, ); @@ -60,7 +60,7 @@ export async function ensureSqlTableRouteArtifacts(input: { (method: any) => DEFAULT_REST_HANDLER_LOGIC[method.name], ); if (httpMethods.length === 0) return; - await trx('route_handler_definition').insert( + await trx('enfyra_route_handler').insert( httpMethods.map((method: any) => ({ routeId: newRoute.id, methodId: method.id, @@ -83,7 +83,7 @@ export async function renameSqlAutoTableRoute(input: { }): Promise { const { trx, tableId, oldTableName, newTableName } = input; if (!newTableName || oldTableName === newTableName) return; - await trx('route_definition') + await trx('enfyra_route') .where({ mainTableId: tableId, path: `/${oldTableName}` }) .update({ path: `/${newTableName}`, @@ -99,18 +99,18 @@ export async function ensureMongoTableRouteArtifacts(input: { }): Promise { const { mongoService, queryBuilderService, tableName, tableId } = input; const existingRoute = await queryBuilderService.findOne({ - table: 'route_definition', + table: 'enfyra_route', where: { path: `/${tableName}` }, }); if (existingRoute) return; const db = mongoService.getDb(); const methods = await db - .collection('method_definition') + .collection('enfyra_method') .find({}, { projection: { _id: 1, name: 1 } }) .toArray(); const allMethodIds = methods.map((method: any) => method._id); - const routeResult = await db.collection('route_definition').insertOne({ + const routeResult = await db.collection('enfyra_route').insertOne({ path: `/${tableName}`, mainTable: tableId, isEnabled: true, @@ -141,13 +141,13 @@ export async function ensureMongoTableRouteArtifacts(input: { updatedAt: new Date(), })); if (handlers.length > 0) { - await db.collection('route_handler_definition').insertMany(handlers); + await db.collection('enfyra_route_handler').insertMany(handlers); } const junction = getSqlJunctionPhysicalNames({ - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: ROUTE_METHOD_PROPERTY, - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); const junctionRows = allMethodIds.map((methodId: any) => ({ [junction.junctionSourceColumn]: routeId, @@ -171,7 +171,7 @@ export async function renameMongoAutoTableRoute(input: { }): Promise { const { mongoService, tableId, oldTableName, newTableName } = input; if (!newTableName || oldTableName === newTableName) return; - await mongoService.getDb().collection('route_definition').updateOne( + await mongoService.getDb().collection('enfyra_route').updateOne( { mainTable: tableId, path: `/${oldTableName}`, diff --git a/src/shared/helpers/upload-file.helper.ts b/src/shared/helpers/upload-file.helper.ts index 04e429c0..d39d4ba6 100644 --- a/src/shared/helpers/upload-file.helper.ts +++ b/src/shared/helpers/upload-file.helper.ts @@ -26,11 +26,11 @@ export class UploadFileHelper { } private getFileRepo(context: TDynamicContext) { - const fileRepo = context.$repos?.file_definition || context.$repos?.main; + const fileRepo = context.$repos?.enfyra_file || context.$repos?.main; if (!fileRepo) { throw new Error( `File repository not found in context. ` + - `Ensure table "file_definition" exists in metadata.`, + `Ensure table "enfyra_file" exists in metadata.`, ); } return fileRepo; diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index dec2d878..39401f80 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -9,3 +9,4 @@ export * from './flow.types'; export * from './cache.types'; export * from './runtime-metrics.types'; export * from './package-runtime.types'; +export * from './system-tables.types'; diff --git a/src/shared/types/schema-migration.types.ts b/src/shared/types/schema-migration.types.ts index 0259de1d..0d38abb1 100644 --- a/src/shared/types/schema-migration.types.ts +++ b/src/shared/types/schema-migration.types.ts @@ -37,6 +37,11 @@ export interface RelationModifyDef { }; } +export interface TableRenameDef { + from: string; + to: string; +} + /** * Table migration definition */ @@ -75,6 +80,26 @@ export interface TableMigrationDef { * Schema migration file structure */ export interface SchemaMigrationDef { + /** + * Core metadata tables must be renamed before any normal metadata query. + */ + coreTablesToRename?: TableRenameDef[]; + + /** + * Table renames applied after core metadata tables are available. + */ + tablesToRename?: TableRenameDef[]; + + /** + * Physical tables/collections that no longer have metadata but may exist in old installs. + */ + physicalTablesToDrop?: string[]; + + /** + * Physical tables/collections without metadata that need a direct rename. + */ + physicalTablesToRename?: TableRenameDef[]; + /** * Table migrations */ diff --git a/src/shared/types/system-tables.types.ts b/src/shared/types/system-tables.types.ts new file mode 100644 index 00000000..4ad9cdaf --- /dev/null +++ b/src/shared/types/system-tables.types.ts @@ -0,0 +1,11 @@ +import type { + CORE_SYSTEM_TABLES, + SYSTEM_TABLES, +} from '../utils/system-tables.constants'; + +export type SystemTableName = + (typeof SYSTEM_TABLES)[keyof typeof SYSTEM_TABLES]; + +export type CoreSystemTableKey = keyof typeof CORE_SYSTEM_TABLES; + +export type CoreSystemTableNames = Record; diff --git a/src/shared/utils/cache-events.constants.ts b/src/shared/utils/cache-events.constants.ts index 9a598065..f7d257a6 100644 --- a/src/shared/utils/cache-events.constants.ts +++ b/src/shared/utils/cache-events.constants.ts @@ -1,4 +1,5 @@ export type { TCacheInvalidationPayload } from '../types/cache.types'; +import { SYSTEM_TABLES } from './system-tables.constants'; export const CACHE_EVENTS = { INVALIDATE: 'cache:invalidate', @@ -49,39 +50,39 @@ const METADATA_GROUP: CacheIdentifier[] = [ ]; export const CACHE_INVALIDATION_MAP: Record = { - table_definition: METADATA_GROUP, - column_definition: METADATA_GROUP, - relation_definition: METADATA_GROUP, - column_rule_definition: [CACHE_IDENTIFIERS.COLUMN_RULE], + [SYSTEM_TABLES.table]: METADATA_GROUP, + [SYSTEM_TABLES.column]: METADATA_GROUP, + [SYSTEM_TABLES.relation]: METADATA_GROUP, + [SYSTEM_TABLES.columnRule]: [CACHE_IDENTIFIERS.COLUMN_RULE], - route_definition: ROUTE_GROUP, - pre_hook_definition: ROUTE_GROUP, - post_hook_definition: ROUTE_GROUP, - route_handler_definition: ROUTE_GROUP, - route_permission_definition: ROUTE_GROUP, - role_definition: ROUTE_GROUP, - method_definition: ROUTE_GROUP, + [SYSTEM_TABLES.route]: ROUTE_GROUP, + [SYSTEM_TABLES.preHook]: ROUTE_GROUP, + [SYSTEM_TABLES.postHook]: ROUTE_GROUP, + [SYSTEM_TABLES.routeHandler]: ROUTE_GROUP, + [SYSTEM_TABLES.routePermission]: ROUTE_GROUP, + [SYSTEM_TABLES.role]: ROUTE_GROUP, + [SYSTEM_TABLES.method]: ROUTE_GROUP, - field_permission_definition: [ + [SYSTEM_TABLES.fieldPermission]: [ CACHE_IDENTIFIERS.FIELD_PERMISSION, CACHE_IDENTIFIERS.GRAPHQL, ], - storage_config_definition: [CACHE_IDENTIFIERS.STORAGE], - oauth_config_definition: [CACHE_IDENTIFIERS.OAUTH_CONFIG], - websocket_definition: [CACHE_IDENTIFIERS.WEBSOCKET], - websocket_event_definition: [CACHE_IDENTIFIERS.WEBSOCKET], - package_definition: [CACHE_IDENTIFIERS.PACKAGE], - bootstrap_script_definition: [CACHE_IDENTIFIERS.BOOTSTRAP], - folder_definition: [CACHE_IDENTIFIERS.FOLDER_TREE], - flow_definition: [CACHE_IDENTIFIERS.FLOW], - flow_step_definition: [CACHE_IDENTIFIERS.FLOW], - guard_definition: [CACHE_IDENTIFIERS.GUARD], - guard_rule_definition: [CACHE_IDENTIFIERS.GUARD], - setting_definition: [CACHE_IDENTIFIERS.SETTING], - menu_definition: [CACHE_IDENTIFIERS.MENU, CACHE_IDENTIFIERS.EXTENSION], - extension_definition: [CACHE_IDENTIFIERS.EXTENSION], - gql_definition: [CACHE_IDENTIFIERS.GRAPHQL], + [SYSTEM_TABLES.storageConfig]: [CACHE_IDENTIFIERS.STORAGE], + [SYSTEM_TABLES.oauthConfig]: [CACHE_IDENTIFIERS.OAUTH_CONFIG], + [SYSTEM_TABLES.websocket]: [CACHE_IDENTIFIERS.WEBSOCKET], + [SYSTEM_TABLES.websocketEvent]: [CACHE_IDENTIFIERS.WEBSOCKET], + [SYSTEM_TABLES.package]: [CACHE_IDENTIFIERS.PACKAGE], + [SYSTEM_TABLES.bootstrapScript]: [CACHE_IDENTIFIERS.BOOTSTRAP], + [SYSTEM_TABLES.folder]: [CACHE_IDENTIFIERS.FOLDER_TREE], + [SYSTEM_TABLES.flow]: [CACHE_IDENTIFIERS.FLOW], + [SYSTEM_TABLES.flowStep]: [CACHE_IDENTIFIERS.FLOW], + [SYSTEM_TABLES.guard]: [CACHE_IDENTIFIERS.GUARD], + [SYSTEM_TABLES.guardRule]: [CACHE_IDENTIFIERS.GUARD], + [SYSTEM_TABLES.setting]: [CACHE_IDENTIFIERS.SETTING], + [SYSTEM_TABLES.menu]: [CACHE_IDENTIFIERS.MENU, CACHE_IDENTIFIERS.EXTENSION], + [SYSTEM_TABLES.extension]: [CACHE_IDENTIFIERS.EXTENSION], + [SYSTEM_TABLES.graphql]: [CACHE_IDENTIFIERS.GRAPHQL], }; export function shouldReloadCache( @@ -92,10 +93,10 @@ export function shouldReloadCache( return cachesToReload?.includes(cacheIdentifier) ?? false; } -const METADATA_TABLES = new Set([ - 'table_definition', - 'column_definition', - 'relation_definition', +const METADATA_TABLES = new Set([ + SYSTEM_TABLES.table, + SYSTEM_TABLES.column, + SYSTEM_TABLES.relation, ]); export function isMetadataTable(tableName: string): boolean { diff --git a/src/shared/utils/load-user-with-role.util.ts b/src/shared/utils/load-user-with-role.util.ts index 16998c65..f6957402 100644 --- a/src/shared/utils/load-user-with-role.util.ts +++ b/src/shared/utils/load-user-with-role.util.ts @@ -44,7 +44,7 @@ export async function loadUserWithRole( if (!idValue) return null; const user = await queryBuilder.findOne({ - table: 'user_definition', + table: 'enfyra_user', where: { [idField]: idValue }, }); @@ -56,7 +56,7 @@ export async function loadUserWithRole( : toSqlId(user[roleField]); if (roleId) { user.role = await queryBuilder.findOne({ - table: 'role_definition', + table: 'enfyra_role', where: { [idField]: roleId }, }); } diff --git a/src/shared/utils/metadata-access.util.ts b/src/shared/utils/metadata-access.util.ts index fc64adeb..d75b3650 100644 --- a/src/shared/utils/metadata-access.util.ts +++ b/src/shared/utils/metadata-access.util.ts @@ -108,7 +108,7 @@ export async function getAccessibleMetadataTableActions({ } if (user && !user.isAnonymous) { - add('user_definition', ['read', 'update']); + add('enfyra_user', ['read', 'update']); } return tableActions; diff --git a/src/shared/utils/provision-schema-migration.ts b/src/shared/utils/provision-schema-migration.ts index 9d6c175f..e50598d5 100644 --- a/src/shared/utils/provision-schema-migration.ts +++ b/src/shared/utils/provision-schema-migration.ts @@ -143,7 +143,7 @@ async function applySqlTableMigration( if (migration.relationsToRemove && migration.relationsToRemove.length > 0) { if ( - tableName === 'file_permission_definition' && + tableName === 'enfyra_file_permission' && migration.relationsToRemove.includes('allowedUsers') ) { await migrateFilePermissionAllowedUsersToJunction(knex, dbType); @@ -160,7 +160,7 @@ async function migrateFilePermissionAllowedUsersToJunction( knex: Knex, _dbType: string, ): Promise { - const tableName = 'file_permission_definition'; + const tableName = 'enfyra_file_permission'; const fkColumn = getForeignKeyColumnName('allowedUsers'); const hasOldColumn = await knex.schema.hasColumn(tableName, fkColumn); if (!hasOldColumn) return; @@ -168,12 +168,12 @@ async function migrateFilePermissionAllowedUsersToJunction( const junctionTableName = getJunctionTableName( tableName, 'allowedUsers', - 'user_definition', + 'enfyra_user', ); const { sourceColumn, targetColumn } = getJunctionColumnNames( tableName, 'allowedUsers', - 'user_definition', + 'enfyra_user', ); const exists = await knex.schema.hasTable(junctionTableName); @@ -185,10 +185,10 @@ async function migrateFilePermissionAllowedUsersToJunction( } console.log( - ` 📦 Migrating file_permission_definition.allowedUsers to junction ${junctionTableName}`, + ` 📦 Migrating enfyra_file_permission.allowedUsers to junction ${junctionTableName}`, ); const pkType = await getPrimaryKeyType(knex, tableName); - const targetPkType = await getPrimaryKeyType(knex, 'user_definition'); + const targetPkType = await getPrimaryKeyType(knex, 'enfyra_user'); await knex.schema.createTable(junctionTableName, (table) => { if (pkType === 'uuid') { @@ -203,7 +203,7 @@ async function migrateFilePermissionAllowedUsersToJunction( } table.primary([sourceColumn, targetColumn]); table.foreign(sourceColumn).references('id').inTable(tableName); - table.foreign(targetColumn).references('id').inTable('user_definition'); + table.foreign(targetColumn).references('id').inTable('enfyra_user'); }); const rows = await knex(tableName) @@ -805,7 +805,7 @@ async function applyMongoCollectionMigration( if (migration.relationsToRemove && migration.relationsToRemove.length > 0) { const toRemove = [...migration.relationsToRemove]; if ( - collectionName === 'file_permission_definition' && + collectionName === 'enfyra_file_permission' && toRemove.includes('allowedUsers') ) { try { diff --git a/src/shared/utils/script-code.util.ts b/src/shared/utils/script-code.util.ts index 349fa309..5eaeca51 100644 --- a/src/shared/utils/script-code.util.ts +++ b/src/shared/utils/script-code.util.ts @@ -1,5 +1,6 @@ import ts from 'typescript'; import { transformTemplateSyntax } from './template-syntax.util'; +import { SCRIPT_TABLE_LEGACY_FIELDS } from './script-table-contract.constants'; type ScriptLanguage = 'javascript' | 'typescript'; @@ -21,16 +22,6 @@ interface ScriptContractRepairResult extends ExecutableScriptResult { scriptLanguage: ScriptLanguage; } -const SCRIPT_TABLE_LEGACY_FIELDS: Record = { - oauth_config_definition: '', - route_handler_definition: 'logic', - pre_hook_definition: 'code', - post_hook_definition: 'code', - bootstrap_script_definition: 'logic', - websocket_definition: 'connectionHandlerScript', - websocket_event_definition: 'handlerScript', -}; - export function getScriptLegacyField(tableName: string): string | undefined { return scriptContractService.getLegacyField(tableName); } @@ -84,7 +75,11 @@ class ScriptContractService { } normalizeRecord(tableName: string, record: ScriptFields): ScriptFields { - if (!this.isScriptTable(tableName) || !record || typeof record !== 'object') { + if ( + !this.isScriptTable(tableName) || + !record || + typeof record !== 'object' + ) { return record; } @@ -138,9 +133,9 @@ class ScriptContractService { if (!touchesSource && !touchesLanguage) return patch; const sourceCode = touchesSource - ? patch.sourceCode ?? (legacyField ? patch[legacyField] : undefined) - : existing?.sourceCode ?? - (legacyField && existing ? existing[legacyField] : undefined); + ? (patch.sourceCode ?? (legacyField ? patch[legacyField] : undefined)) + : (existing?.sourceCode ?? + (legacyField && existing ? existing[legacyField] : undefined)); const scriptLanguage = touchesLanguage ? patch.scriptLanguage : existing?.scriptLanguage; @@ -168,7 +163,11 @@ class ScriptContractService { resolveExecutableScript(record: ScriptFields): ExecutableScriptResult { if (!record || typeof record !== 'object') { - return { code: null, compiledCode: null, shouldPersistCompiledCode: false }; + return { + code: null, + compiledCode: null, + shouldPersistCompiledCode: false, + }; } if (typeof record.sourceCode === 'string' && record.sourceCode !== '') { @@ -311,7 +310,9 @@ export function compileScriptSource( return scriptContractService.compileSource(sourceCode, scriptLanguage); } -export function isExecutableJavaScript(code: string | null | undefined): boolean { +export function isExecutableJavaScript( + code: string | null | undefined, +): boolean { return scriptContractService.isExecutableJavaScript(code); } diff --git a/src/shared/utils/script-persistence-contract.util.ts b/src/shared/utils/script-persistence-contract.util.ts index 7976e931..17c9d8a1 100644 --- a/src/shared/utils/script-persistence-contract.util.ts +++ b/src/shared/utils/script-persistence-contract.util.ts @@ -1,18 +1,14 @@ -const GENERATED_SCRIPT_FIELDS = new Set(['compiledCode']); -const SCRIPT_TABLES = new Set([ - 'oauth_config_definition', - 'route_handler_definition', - 'pre_hook_definition', - 'post_hook_definition', - 'bootstrap_script_definition', - 'websocket_definition', - 'websocket_event_definition', - 'flow_step_definition', -]); +import { + GENERATED_SCRIPT_FIELD_SET, + SCRIPT_TABLE_NAME_SET, +} from './script-table-contract.constants'; export function isGeneratedScriptPersistenceField( tableName: string, fieldName: string, ): boolean { - return SCRIPT_TABLES.has(tableName) && GENERATED_SCRIPT_FIELDS.has(fieldName); + return ( + SCRIPT_TABLE_NAME_SET.has(tableName) && + GENERATED_SCRIPT_FIELD_SET.has(fieldName as any) + ); } diff --git a/src/shared/utils/script-table-contract.constants.ts b/src/shared/utils/script-table-contract.constants.ts new file mode 100644 index 00000000..429dbf08 --- /dev/null +++ b/src/shared/utils/script-table-contract.constants.ts @@ -0,0 +1,20 @@ +import { SYSTEM_TABLES } from './system-tables.constants'; + +export const GENERATED_SCRIPT_FIELDS = ['compiledCode'] as const; + +export const SCRIPT_TABLE_LEGACY_FIELDS: Record = { + [SYSTEM_TABLES.oauthConfig]: '', + [SYSTEM_TABLES.routeHandler]: 'logic', + [SYSTEM_TABLES.preHook]: 'code', + [SYSTEM_TABLES.postHook]: 'code', + [SYSTEM_TABLES.bootstrapScript]: 'logic', + [SYSTEM_TABLES.websocket]: 'connectionHandlerScript', + [SYSTEM_TABLES.websocketEvent]: 'handlerScript', + [SYSTEM_TABLES.flowStep]: '', +}; + +export const SCRIPT_TABLE_NAMES = Object.keys(SCRIPT_TABLE_LEGACY_FIELDS); + +export const SCRIPT_TABLE_NAME_SET = new Set(SCRIPT_TABLE_NAMES); + +export const GENERATED_SCRIPT_FIELD_SET = new Set(GENERATED_SCRIPT_FIELDS); diff --git a/src/shared/utils/system-tables.constants.ts b/src/shared/utils/system-tables.constants.ts new file mode 100644 index 00000000..62d1456a --- /dev/null +++ b/src/shared/utils/system-tables.constants.ts @@ -0,0 +1,56 @@ +export const SYSTEM_TABLES = { + table: 'enfyra_table', + column: 'enfyra_column', + relation: 'enfyra_relation', + columnRule: 'enfyra_column_rule', + user: 'enfyra_user', + oauthConfig: 'enfyra_oauth_config', + oauthAccount: 'enfyra_oauth_account', + setting: 'enfyra_setting', + corsOrigin: 'enfyra_cors_origin', + route: 'enfyra_route', + role: 'enfyra_role', + routePermission: 'enfyra_route_permission', + fieldPermission: 'enfyra_field_permission', + routeHandler: 'enfyra_route_handler', + preHook: 'enfyra_pre_hook', + postHook: 'enfyra_post_hook', + session: 'enfyra_session', + apiToken: 'enfyra_api_token', + schemaMigration: 'enfyra_schema_migration', + method: 'enfyra_method', + menu: 'enfyra_menu', + extension: 'enfyra_extension', + folder: 'enfyra_folder', + file: 'enfyra_file', + filePermission: 'enfyra_file_permission', + package: 'enfyra_package', + bootstrapScript: 'enfyra_bootstrap_script', + storageConfig: 'enfyra_storage_config', + websocket: 'enfyra_websocket', + websocketEvent: 'enfyra_websocket_event', + flow: 'enfyra_flow', + flowStep: 'enfyra_flow_step', + flowExecution: 'enfyra_flow_execution', + guard: 'enfyra_guard', + guardRule: 'enfyra_guard_rule', + graphql: 'enfyra_graphql', +} as const; + +export const LEGACY_CORE_SYSTEM_TABLES = { + table: 'table_definition', + column: 'column_definition', + relation: 'relation_definition', +} as const; + +export const CORE_SYSTEM_TABLES = { + table: SYSTEM_TABLES.table, + column: SYSTEM_TABLES.column, + relation: SYSTEM_TABLES.relation, +} as const; + +export const METADATA_SYSTEM_TABLE_NAMES = [ + SYSTEM_TABLES.table, + SYSTEM_TABLES.column, + SYSTEM_TABLES.relation, +] as const; diff --git a/src/shared/utils/zod-from-metadata.ts b/src/shared/utils/zod-from-metadata.ts index 4e75072a..2664a3f7 100644 --- a/src/shared/utils/zod-from-metadata.ts +++ b/src/shared/utils/zod-from-metadata.ts @@ -10,8 +10,8 @@ const AUTO_MANAGED_COLUMNS = new Set(['id', '_id', 'createdAt', 'updatedAt']); * for everything else. */ const TABLE_VIRTUAL_FIELDS: Record = { - table_definition: ['graphqlEnabled'], - field_permission_definition: ['config'], + enfyra_table: ['graphqlEnabled'], + enfyra_field_permission: ['config'], }; export interface BuildZodOpts { @@ -197,7 +197,7 @@ function buildRelationZod( ): z.ZodType | null { // NOTE: `isUpdatable=false` on a relation semantically means the LINK can't // be swapped out, but nested CRUD through the relation may still be allowed - // (e.g. table_definition.columns is not-updatable as a link but server PATCH + // (e.g. enfyra_table.columns is not-updatable as a link but server PATCH // handles nested column changes). Don't filter here; server enforces. const targetName = rel.targetTableName || rel.targetTable; diff --git a/test/cache-load.e2e.js b/test/cache-load.e2e.js index 06b054b3..8967b69d 100644 --- a/test/cache-load.e2e.js +++ b/test/cache-load.e2e.js @@ -41,13 +41,13 @@ class MockQueryBuilder { if (params.filter?.isEnabled) { filtered = filtered.filter(r => r.isEnabled === true); } - if (tableName === 'flow_step_definition' && params.filter?.flow) { + if (tableName === 'enfyra_flow_step' && params.filter?.flow) { const expected = params.filter.flow.id?._eq ?? params.filter.flow._id?._eq; filtered = filtered.filter(s => s.flowId === expected); } if (params.fields?.includes('events.*')) { - const allEvents = this.data['websocket_event_definition'] || []; + const allEvents = this.data['enfyra_websocket_event'] || []; filtered = filtered.map(gw => ({ ...gw, events: allEvents.filter(e => e.gatewayId === (gw._id || gw.id)), @@ -85,7 +85,7 @@ async function simulateFlowCacheLoad(queryBuilder) { const idField = isMongoDB ? '_id' : 'id'; const flowsResult = await queryBuilder.select({ - table: 'flow_definition', + table: 'enfyra_flow', filter: { isEnabled: { _eq: true } }, fields: ['*'], }); @@ -93,7 +93,7 @@ async function simulateFlowCacheLoad(queryBuilder) { const flows = []; for (const flow of flowsResult.data) { const stepsResult = await queryBuilder.select({ - table: 'flow_step_definition', + table: 'enfyra_flow_step', filter: { flow: { [idField]: { _eq: flow[idField] } } }, fields: ['*', 'parent.*'], limit: 1000, @@ -141,7 +141,7 @@ async function simulateFlowCacheLoad(queryBuilder) { async function simulateWebsocketCacheLoad(queryBuilder) { const result = await queryBuilder.select({ - tableName: 'websocket_definition', + tableName: 'enfyra_websocket', filter: { isEnabled: { _eq: true } }, fields: ['*', 'events.*'], }); @@ -167,14 +167,14 @@ async function simulateWebsocketCacheLoad(queryBuilder) { async function testFlowCacheSingleQuery() { const qb = new MockQueryBuilder(); - qb.setTableData('flow_definition', [ + qb.setTableData('enfyra_flow', [ { id: 1, name: 'daily-report', triggerType: 'schedule', triggerConfig: { cron: '0 2 * * *' }, timeout: 30000, isEnabled: true }, { id: 2, name: 'disabled-flow', triggerType: 'manual', isEnabled: false }, { id: 3, name: 'manual-cleanup', triggerType: 'manual', timeout: 60000, isEnabled: true }, ]); - qb.setTableData('flow_step_definition', [ - { id: 10, flowId: 1, key: 'fetch-data', stepOrder: 1, type: 'query', config: { table: 'user_definition' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, + qb.setTableData('enfyra_flow_step', [ + { id: 10, flowId: 1, key: 'fetch-data', stepOrder: 1, type: 'query', config: { table: 'enfyra_user' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, { id: 11, flowId: 1, key: 'send-email', stepOrder: 2, type: 'http', config: { url: 'https://api.email.com' }, isEnabled: true, onError: 'retry', retryAttempts: 3 }, { id: 12, flowId: 1, key: 'disabled-step', stepOrder: 3, type: 'log', config: {}, isEnabled: false, onError: 'stop', retryAttempts: 0 }, { id: 20, flowId: 2, key: 'should-not-appear', stepOrder: 1, type: 'script', config: {}, isEnabled: true, onError: 'stop', retryAttempts: 0 }, @@ -201,11 +201,11 @@ async function testFlowCacheSingleQuery() { async function testFlowCacheStepOrdering() { const qb = new MockQueryBuilder(); - qb.setTableData('flow_definition', [ + qb.setTableData('enfyra_flow', [ { id: 1, name: 'order-test', triggerType: 'manual', isEnabled: true }, ]); - qb.setTableData('flow_step_definition', [ + qb.setTableData('enfyra_flow_step', [ { id: 13, flowId: 1, key: 'third', stepOrder: 30, type: 'log', config: {}, isEnabled: true, onError: 'stop', retryAttempts: 0 }, { id: 11, flowId: 1, key: 'first', stepOrder: 10, type: 'log', config: {}, isEnabled: true, onError: 'stop', retryAttempts: 0 }, { id: 12, flowId: 1, key: 'second', stepOrder: 20, type: 'log', config: {}, isEnabled: true, onError: 'stop', retryAttempts: 0 }, @@ -220,11 +220,11 @@ async function testFlowCacheStepOrdering() { async function testFlowCacheBranching() { const qb = new MockQueryBuilder(); - qb.setTableData('flow_definition', [ + qb.setTableData('enfyra_flow', [ { id: 1, name: 'branching-test', triggerType: 'manual', isEnabled: true }, ]); - qb.setTableData('flow_step_definition', [ + qb.setTableData('enfyra_flow_step', [ { id: 10, flowId: 1, key: 'check', stepOrder: 1, type: 'condition', config: { code: 'return true' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, { id: 11, flowId: 1, key: 'on-true', stepOrder: 2, type: 'log', config: {}, isEnabled: true, onError: 'stop', retryAttempts: 0, parentId: 10, branch: 'true' }, { id: 12, flowId: 1, key: 'on-false', stepOrder: 3, type: 'log', config: {}, isEnabled: true, onError: 'stop', retryAttempts: 0, parentId: 10, branch: 'false' }, @@ -244,11 +244,11 @@ async function testFlowCacheBranching() { async function testFlowCacheEmptySteps() { const qb = new MockQueryBuilder(); - qb.setTableData('flow_definition', [ + qb.setTableData('enfyra_flow', [ { id: 1, name: 'empty-flow', triggerType: 'manual', isEnabled: true }, ]); - qb.setTableData('flow_step_definition', []); + qb.setTableData('enfyra_flow_step', []); const flows = await simulateFlowCacheLoad(qb); @@ -258,11 +258,11 @@ async function testFlowCacheEmptySteps() { async function testFlowCacheCodeTransform() { const qb = new MockQueryBuilder(); - qb.setTableData('flow_definition', [ + qb.setTableData('enfyra_flow', [ { id: 1, name: 'code-test', triggerType: 'manual', isEnabled: true }, ]); - qb.setTableData('flow_step_definition', [ + qb.setTableData('enfyra_flow_step', [ { id: 10, flowId: 1, key: 'script-step', stepOrder: 1, type: 'script', config: { code: 'return 1' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, { id: 11, flowId: 1, key: 'condition-step', stepOrder: 2, type: 'condition', config: { code: 'return true' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, { id: 12, flowId: 1, key: 'query-step', stepOrder: 3, type: 'query', config: { table: 'users', code: 'should-not-transform' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, @@ -281,13 +281,13 @@ async function testFlowCacheCodeTransform() { async function testWebsocketCacheSingleQuery() { const qb = new MockQueryBuilder(); - qb.setTableData('websocket_definition', [ + qb.setTableData('enfyra_websocket', [ { id: 1, path: '/chat', isEnabled: true, requireAuth: true, connectionHandlerScript: 'console.log("connected")', connectionHandlerTimeout: 5000 }, { id: 2, path: '/notifications', isEnabled: true, requireAuth: false, connectionHandlerScript: null, connectionHandlerTimeout: 3000 }, { id: 3, path: '/disabled', isEnabled: false, requireAuth: false, connectionHandlerScript: null, connectionHandlerTimeout: 3000 }, ]); - qb.setTableData('websocket_event_definition', [ + qb.setTableData('enfyra_websocket_event', [ { id: 100, gatewayId: 1, eventName: 'message', isEnabled: true, handlerScript: 'return data', timeout: 5000 }, { id: 101, gatewayId: 1, eventName: 'typing', isEnabled: true, handlerScript: null, timeout: 3000 }, { id: 102, gatewayId: 1, eventName: 'disabled-event', isEnabled: false, handlerScript: 'nope', timeout: 3000 }, @@ -314,11 +314,11 @@ async function testWebsocketCacheSingleQuery() { async function testWebsocketCacheEmptyEvents() { const qb = new MockQueryBuilder(); - qb.setTableData('websocket_definition', [ + qb.setTableData('enfyra_websocket', [ { id: 1, path: '/empty', isEnabled: true, requireAuth: false, connectionHandlerScript: null, connectionHandlerTimeout: 3000 }, ]); - qb.setTableData('websocket_event_definition', []); + qb.setTableData('enfyra_websocket_event', []); const gateways = await simulateWebsocketCacheLoad(qb); @@ -328,11 +328,11 @@ async function testWebsocketCacheEmptyEvents() { async function testWebsocketCacheAllEventsDisabled() { const qb = new MockQueryBuilder(); - qb.setTableData('websocket_definition', [ + qb.setTableData('enfyra_websocket', [ { id: 1, path: '/all-disabled', isEnabled: true, requireAuth: false, connectionHandlerScript: null, connectionHandlerTimeout: 3000 }, ]); - qb.setTableData('websocket_event_definition', [ + qb.setTableData('enfyra_websocket_event', [ { id: 100, gatewayId: 1, eventName: 'e1', isEnabled: false, handlerScript: null, timeout: 3000 }, { id: 101, gatewayId: 1, eventName: 'e2', isEnabled: false, handlerScript: null, timeout: 3000 }, ]); @@ -348,11 +348,11 @@ async function testFlowCacheMongoMode() { const qb = new MockQueryBuilder(); qb.setMongoMode(true); - qb.setTableData('flow_definition', [ + qb.setTableData('enfyra_flow', [ { _id: 'abc123', name: 'mongo-flow', triggerType: 'manual', isEnabled: true }, ]); - qb.setTableData('flow_step_definition', [ + qb.setTableData('enfyra_flow_step', [ { _id: 'step1', flowId: 'abc123', key: 'first', stepOrder: 1, type: 'script', config: { code: 'return 1' }, isEnabled: true, onError: 'stop', retryAttempts: 0 }, ]); diff --git a/test/cache/cache-orchestrator-granular.spec.ts b/test/cache/cache-orchestrator-granular.spec.ts index f7f2273e..cffa3b0c 100644 --- a/test/cache/cache-orchestrator-granular.spec.ts +++ b/test/cache/cache-orchestrator-granular.spec.ts @@ -1,68 +1,68 @@ describe('CacheOrchestratorService — RELOAD_CHAINS + multi-instance', () => { const RELOAD_CHAINS: Record = { - table_definition: [ + enfyra_table: [ 'metadata', 'repoRegistry', 'route', 'graphql', 'fieldPermission', ], - column_definition: [ + enfyra_column: [ 'metadata', 'repoRegistry', 'route', 'graphql', 'fieldPermission', ], - relation_definition: [ + enfyra_relation: [ 'metadata', 'repoRegistry', 'route', 'graphql', 'fieldPermission', ], - route_definition: ['route', 'graphql', 'guard'], - pre_hook_definition: ['route'], - post_hook_definition: ['route'], - route_handler_definition: ['route'], - route_permission_definition: ['route'], - role_definition: ['route'], - method_definition: ['route', 'graphql'], - guard_definition: ['guard'], - guard_rule_definition: ['guard'], - field_permission_definition: ['fieldPermission'], - setting_definition: ['setting', 'settingGraphql'], - storage_config_definition: ['storage'], - oauth_config_definition: ['oauth'], - websocket_definition: ['websocket'], - websocket_event_definition: ['websocket'], - package_definition: ['package'], - flow_definition: ['flow'], - flow_step_definition: ['flow'], - folder_definition: ['folder'], - bootstrap_script_definition: ['bootstrap'], + enfyra_route: ['route', 'graphql', 'guard'], + enfyra_pre_hook: ['route'], + enfyra_post_hook: ['route'], + enfyra_route_handler: ['route'], + enfyra_route_permission: ['route'], + enfyra_role: ['route'], + enfyra_method: ['route', 'graphql'], + enfyra_guard: ['guard'], + enfyra_guard_rule: ['guard'], + enfyra_field_permission: ['fieldPermission'], + enfyra_setting: ['setting', 'settingGraphql'], + enfyra_storage_config: ['storage'], + enfyra_oauth_config: ['oauth'], + enfyra_websocket: ['websocket'], + enfyra_websocket_event: ['websocket'], + enfyra_package: ['package'], + enfyra_flow: ['flow'], + enfyra_flow_step: ['flow'], + enfyra_folder: ['folder'], + enfyra_bootstrap_script: ['bootstrap'], }; describe('RELOAD_CHAINS — dependency correctness', () => { it('table/column/relation changes should invalidate fieldPermission', () => { for (const t of [ - 'table_definition', - 'column_definition', - 'relation_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', ]) { expect(RELOAD_CHAINS[t]).toContain('fieldPermission'); } }); - it('route_definition changes should invalidate guard (path-keyed)', () => { - expect(RELOAD_CHAINS['route_definition']).toContain('guard'); + it('enfyra_route changes should invalidate guard (path-keyed)', () => { + expect(RELOAD_CHAINS['enfyra_route']).toContain('guard'); }); it('structural metadata changes should reload metadata → route → graphql', () => { for (const t of [ - 'table_definition', - 'column_definition', - 'relation_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', ]) { const chain = RELOAD_CHAINS[t]; expect(chain.indexOf('metadata')).toBeLessThan(chain.indexOf('route')); @@ -72,23 +72,23 @@ describe('CacheOrchestratorService — RELOAD_CHAINS + multi-instance', () => { it('hook/handler/permission changes should NOT trigger graphql rebuild', () => { for (const t of [ - 'pre_hook_definition', - 'post_hook_definition', - 'route_handler_definition', - 'route_permission_definition', - 'role_definition', + 'enfyra_pre_hook', + 'enfyra_post_hook', + 'enfyra_route_handler', + 'enfyra_route_permission', + 'enfyra_role', ]) { expect(RELOAD_CHAINS[t]).not.toContain('graphql'); } }); it('setting changes should use settingGraphql (lightweight), not full graphql', () => { - expect(RELOAD_CHAINS['setting_definition']).toContain('settingGraphql'); - expect(RELOAD_CHAINS['setting_definition']).not.toContain('graphql'); + expect(RELOAD_CHAINS['enfyra_setting']).toContain('settingGraphql'); + expect(RELOAD_CHAINS['enfyra_setting']).not.toContain('graphql'); }); - it('method_definition should still trigger graphql (GQL_QUERY/GQL_MUTATION flags)', () => { - expect(RELOAD_CHAINS['method_definition']).toContain('graphql'); + it('enfyra_method should still trigger graphql (GQL_QUERY/GQL_MUTATION flags)', () => { + expect(RELOAD_CHAINS['enfyra_method']).toContain('graphql'); }); it('every chain entry should have at least one step', () => { @@ -120,7 +120,7 @@ describe('CacheOrchestratorService — RELOAD_CHAINS + multi-instance', () => { }, }; - const chain = RELOAD_CHAINS['table_definition']; + const chain = RELOAD_CHAINS['enfyra_table']; if (chain.includes('metadata')) { await stepMap['metadata'](); @@ -151,7 +151,7 @@ describe('CacheOrchestratorService — RELOAD_CHAINS + multi-instance', () => { }, }; - const chain = RELOAD_CHAINS['pre_hook_definition']; + const chain = RELOAD_CHAINS['enfyra_pre_hook']; if (chain.includes('metadata')) await stepMap['metadata']?.(); const middle = chain.filter((s) => s !== 'metadata' && s !== 'graphql'); await Promise.all(middle.map((s) => stepMap[s]?.())); @@ -208,7 +208,7 @@ describe('CacheOrchestratorService — RELOAD_CHAINS + multi-instance', () => { let reloadAllLocalCalled = false; let executeChainCalled = false; - const signal = { tableName: 'table_definition', scope: 'partial' }; + const signal = { tableName: 'enfyra_table', scope: 'partial' }; if (signal.tableName === '__admin_reload_all') { reloadAllLocalCalled = true; @@ -222,47 +222,47 @@ describe('CacheOrchestratorService — RELOAD_CHAINS + multi-instance', () => { }); describe('granular admin reloads — Redis publish', () => { - it('reloadMetadataAndDeps should publish table_definition/full', () => { - const signal = { tableName: 'table_definition', scope: 'full' }; + it('reloadMetadataAndDeps should publish enfyra_table/full', () => { + const signal = { tableName: 'enfyra_table', scope: 'full' }; const chain = RELOAD_CHAINS[signal.tableName]; expect(chain).toContain('metadata'); expect(chain).toContain('route'); expect(chain).toContain('graphql'); }); - it('reloadRoutesOnly should publish route_definition/full', () => { - const signal = { tableName: 'route_definition', scope: 'full' }; + it('reloadRoutesOnly should publish enfyra_route/full', () => { + const signal = { tableName: 'enfyra_route', scope: 'full' }; const chain = RELOAD_CHAINS[signal.tableName]; expect(chain).toContain('route'); expect(chain).toContain('guard'); }); - it('reloadGuardsOnly should publish guard_definition/full', () => { - const signal = { tableName: 'guard_definition', scope: 'full' }; + it('reloadGuardsOnly should publish enfyra_guard/full', () => { + const signal = { tableName: 'enfyra_guard', scope: 'full' }; const chain = RELOAD_CHAINS[signal.tableName]; expect(chain).toContain('guard'); }); }); describe('mergePayload — cross-table merge picks longer chain', () => { - it('should pick table_definition over route_definition (4 > 3)', () => { - const chainA = RELOAD_CHAINS['table_definition']; - const chainB = RELOAD_CHAINS['route_definition']; + it('should pick enfyra_table over enfyra_route (4 > 3)', () => { + const chainA = RELOAD_CHAINS['enfyra_table']; + const chainB = RELOAD_CHAINS['enfyra_route']; const winner = chainA.length >= chainB.length - ? 'table_definition' - : 'route_definition'; - expect(winner).toBe('table_definition'); + ? 'enfyra_table' + : 'enfyra_route'; + expect(winner).toBe('enfyra_table'); }); - it('should pick column_definition over pre_hook_definition (5 > 1)', () => { - const chainA = RELOAD_CHAINS['column_definition']; - const chainB = RELOAD_CHAINS['pre_hook_definition']; + it('should pick enfyra_column over enfyra_pre_hook (5 > 1)', () => { + const chainA = RELOAD_CHAINS['enfyra_column']; + const chainB = RELOAD_CHAINS['enfyra_pre_hook']; const winner = chainA.length >= chainB.length - ? 'column_definition' - : 'pre_hook_definition'; - expect(winner).toBe('column_definition'); + ? 'enfyra_column' + : 'enfyra_pre_hook'; + expect(winner).toBe('enfyra_column'); }); }); }); diff --git a/test/cache/cache-orchestrator-reload-notify.spec.ts b/test/cache/cache-orchestrator-reload-notify.spec.ts index d52f19a5..7b6cfade 100644 --- a/test/cache/cache-orchestrator-reload-notify.spec.ts +++ b/test/cache/cache-orchestrator-reload-notify.spec.ts @@ -57,7 +57,7 @@ describe('CacheOrchestratorService reload notifications', () => { await expect( (orchestrator as any).executeChain( { - table: 'package_definition', + table: 'enfyra_package', action: 'reload', scope: 'full', timestamp: Date.now(), @@ -95,7 +95,7 @@ describe('CacheOrchestratorService reload notifications', () => { await (orchestrator as any).executeChain( { - table: 'extension_definition', + table: 'enfyra_extension', action: 'reload', scope: 'partial', ids: [8], @@ -133,7 +133,7 @@ describe('CacheOrchestratorService reload notifications', () => { await (orchestrator as any).executeChain( { - table: 'menu_definition', + table: 'enfyra_menu', action: 'reload', scope: 'partial', ids: [12], @@ -171,25 +171,25 @@ describe('CacheOrchestratorService reload notifications', () => { { method: 'reloadMetadataAndDeps', flow: 'metadata', - table: 'table_definition', + table: 'enfyra_table', steps: ['metadata', 'repoRegistry', 'route', 'graphql'], }, { method: 'reloadRoutesOnly', flow: 'route', - table: 'route_definition', + table: 'enfyra_route', steps: ['route'], }, { method: 'reloadGraphqlOnly', flow: 'graphql', - table: 'gql_definition', + table: 'enfyra_graphql', steps: ['graphql'], }, { method: 'reloadGuardsOnly', flow: 'guard', - table: 'guard_definition', + table: 'enfyra_guard', steps: ['guard'], }, ] as const; @@ -255,7 +255,7 @@ describe('CacheOrchestratorService reload notifications', () => { expect(metric).toEqual( expect.objectContaining({ flow: 'route', - table: 'route_definition', + table: 'enfyra_route', status: 'failed', error: 'route reload failed', }), diff --git a/test/cache/column-rule-cache-invalidation.spec.ts b/test/cache/column-rule-cache-invalidation.spec.ts index ab6f2364..0620cc1d 100644 --- a/test/cache/column-rule-cache-invalidation.spec.ts +++ b/test/cache/column-rule-cache-invalidation.spec.ts @@ -6,23 +6,23 @@ import { CACHE_IDENTIFIERS, } from '../../src/shared/utils/cache-events.constants'; -describe('column_rule_definition — cache invalidation chain', () => { - it('CACHE_INVALIDATION_MAP includes COLUMN_RULE for column_rule_definition', () => { - expect(CACHE_INVALIDATION_MAP['column_rule_definition']).toContain( +describe('enfyra_column_rule — cache invalidation chain', () => { + it('CACHE_INVALIDATION_MAP includes COLUMN_RULE for enfyra_column_rule', () => { + expect(CACHE_INVALIDATION_MAP['enfyra_column_rule']).toContain( CACHE_IDENTIFIERS.COLUMN_RULE, ); }); - it('RELOAD_CHAINS has an entry for column_rule_definition', () => { - expect(RELOAD_CHAINS['column_rule_definition']).toBeDefined(); + it('RELOAD_CHAINS has an entry for enfyra_column_rule', () => { + expect(RELOAD_CHAINS['enfyra_column_rule']).toBeDefined(); }); - it('RELOAD_CHAINS for column_rule_definition includes the column-rule step', () => { - expect(RELOAD_CHAINS['column_rule_definition']).toContain('column-rule'); + it('RELOAD_CHAINS for enfyra_column_rule includes the column-rule step', () => { + expect(RELOAD_CHAINS['enfyra_column_rule']).toContain('column-rule'); }); - it('column_rule_definition chain does NOT trigger metadata/route/graphql rebuild (rules are isolated)', () => { - const chain = RELOAD_CHAINS['column_rule_definition']; + it('enfyra_column_rule chain does NOT trigger metadata/route/graphql rebuild (rules are isolated)', () => { + const chain = RELOAD_CHAINS['enfyra_column_rule']; expect(chain).not.toContain('metadata'); expect(chain).not.toContain('route'); expect(chain).not.toContain('graphql'); diff --git a/test/cache/column-rule-cache-partial.spec.ts b/test/cache/column-rule-cache-partial.spec.ts index e4bca859..1477adf4 100644 --- a/test/cache/column-rule-cache-partial.spec.ts +++ b/test/cache/column-rule-cache-partial.spec.ts @@ -56,7 +56,7 @@ describe('ColumnRuleCacheService — partial reload', () => { qb.find.mockClear(); await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', @@ -98,7 +98,7 @@ describe('ColumnRuleCacheService — partial reload', () => { await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', @@ -129,7 +129,7 @@ describe('ColumnRuleCacheService — partial reload', () => { data.length = 0; await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', @@ -162,7 +162,7 @@ describe('ColumnRuleCacheService — partial reload', () => { await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', @@ -193,7 +193,7 @@ describe('ColumnRuleCacheService — partial reload', () => { await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', @@ -224,7 +224,7 @@ describe('ColumnRuleCacheService — partial reload', () => { await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', @@ -252,7 +252,7 @@ describe('ColumnRuleCacheService — partial reload', () => { await svc.partialReload( { - table: 'column_rule_definition', + table: 'enfyra_column_rule', action: 'reload', timestamp: 0, scope: 'partial', diff --git a/test/cache/field-permission-cache-partial.spec.ts b/test/cache/field-permission-cache-partial.spec.ts index e0525278..bfa947f7 100644 --- a/test/cache/field-permission-cache-partial.spec.ts +++ b/test/cache/field-permission-cache-partial.spec.ts @@ -114,7 +114,7 @@ describe('FieldPermissionCacheService — partial reload', () => { ); await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -159,7 +159,7 @@ describe('FieldPermissionCacheService — partial reload', () => { expect(qb.find).toHaveBeenCalledWith( expect.objectContaining({ - table: 'field_permission_definition', + table: 'enfyra_field_permission', fields: [ 'id', 'isEnabled', @@ -235,7 +235,7 @@ describe('FieldPermissionCacheService — partial reload', () => { data.splice(1, 1); await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -268,7 +268,7 @@ describe('FieldPermissionCacheService — partial reload', () => { data.length = 0; await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -306,7 +306,7 @@ describe('FieldPermissionCacheService — partial reload', () => { await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -382,7 +382,7 @@ describe('FieldPermissionCacheService — partial reload', () => { await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -407,7 +407,7 @@ describe('FieldPermissionCacheService — partial reload', () => { column: { id: 1, name: 'secret', - table: { id: 1, name: 'user_definition' }, + table: { id: 1, name: 'enfyra_user' }, }, }), ]; @@ -416,7 +416,7 @@ describe('FieldPermissionCacheService — partial reload', () => { let policies = await svc.getPoliciesFor( { id: 99, role: { id: 10 } }, - 'user_definition', + 'enfyra_user', 'read', ); expect(policies[0].unconditionalDeniedColumns.has('secret')).toBe(true); @@ -424,7 +424,7 @@ describe('FieldPermissionCacheService — partial reload', () => { data.length = 0; await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -435,7 +435,7 @@ describe('FieldPermissionCacheService — partial reload', () => { policies = await svc.getPoliciesFor( { id: 99, role: { id: 10 } }, - 'user_definition', + 'enfyra_user', 'read', ); expect(policies).toHaveLength(0); @@ -460,7 +460,7 @@ describe('FieldPermissionCacheService — partial reload', () => { ); await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', @@ -492,7 +492,7 @@ describe('FieldPermissionCacheService — partial reload', () => { await svc.partialReload( { - table: 'field_permission_definition', + table: 'enfyra_field_permission', action: 'reload', timestamp: 0, scope: 'partial', diff --git a/test/cache/granular-cache-reload.spec.ts b/test/cache/granular-cache-reload.spec.ts index 5129c078..f2340ce6 100644 --- a/test/cache/granular-cache-reload.spec.ts +++ b/test/cache/granular-cache-reload.spec.ts @@ -217,7 +217,7 @@ import { describe('A. TCacheInvalidationPayload & mergePayload', () => { const base = (): TCacheInvalidationPayload => ({ - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: 1000, scope: 'partial', @@ -359,7 +359,7 @@ describe('B. BaseCacheService partial reload', () => { svc._supportsPartial = false; const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -378,7 +378,7 @@ describe('B. BaseCacheService partial reload', () => { svc._supportsPartial = true; const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -398,7 +398,7 @@ describe('B. BaseCacheService partial reload', () => { svc.applyError = new Error('apply failed'); const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -419,7 +419,7 @@ describe('B. BaseCacheService partial reload', () => { svc._supportsPartial = true; const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -441,7 +441,7 @@ describe('B. BaseCacheService partial reload', () => { svc._supportsPartial = true; const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -470,7 +470,7 @@ describe('B. BaseCacheService partial reload', () => { svc._supportsPartial = true; const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -490,7 +490,7 @@ describe('B. BaseCacheService partial reload', () => { svc.applyError = new Error('broken'); const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -608,7 +608,7 @@ describe('C. MetadataCache partial reload logic', () => { columns: [{ name: 'title', type: 'text' }], }); const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -635,7 +635,7 @@ describe('C. MetadataCache partial reload logic', () => { }); const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -655,7 +655,7 @@ describe('C. MetadataCache partial reload logic', () => { const cache = makeInitialCache([tableA, tableB]); const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -672,7 +672,7 @@ describe('C. MetadataCache partial reload logic', () => { it('partial reload when cache not initialized → throws', () => { const cache: MetaCache = null as any; const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -691,7 +691,7 @@ describe('C. MetadataCache partial reload logic', () => { const oldTs = cache.timestamp; const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -712,7 +712,7 @@ describe('C. MetadataCache partial reload logic', () => { columns: [{ name: 'foo', type: 'varchar' }], }); const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -735,7 +735,7 @@ describe('C. MetadataCache partial reload logic', () => { } const makeP = (id: number): TCacheInvalidationPayload => ({ - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -771,14 +771,14 @@ describe('C. MetadataCache partial reload logic', () => { } receivePayload({ - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', ids: [1], }); receivePayload({ - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'full', @@ -855,7 +855,7 @@ describe('D. RouteCache partial reload logic', () => { ): Promise { const affectedTableNames = new Set(payload.affectedTables || []); - if (payload.tableName === 'table_definition' && payload.ids?.length) { + if (payload.tableName === 'enfyra_table' && payload.ids?.length) { for (const route of this.cache.routes) { const mainTableId = route.mainTable?.id; if (payload.ids.some((id) => String(id) === String(mainTableId))) { @@ -864,13 +864,13 @@ describe('D. RouteCache partial reload logic', () => { } } - if (payload.tableName === 'route_definition' && payload.ids?.length) { + if (payload.tableName === 'enfyra_route' && payload.ids?.length) { await this._reloadSpecificRoutes(payload.ids); return; } if ( - ['route_handler_definition', 'route_permission_definition'].includes( + ['enfyra_route_handler', 'enfyra_route_permission'].includes( payload.tableName, ) && payload.ids?.length @@ -889,7 +889,7 @@ describe('D. RouteCache partial reload logic', () => { } if ( - ['pre_hook_definition', 'post_hook_definition'].includes( + ['enfyra_pre_hook', 'enfyra_post_hook'].includes( payload.tableName, ) ) { @@ -898,7 +898,7 @@ describe('D. RouteCache partial reload logic', () => { } if ( - ['role_definition', 'method_definition'].includes(payload.tableName) + ['enfyra_role', 'enfyra_method'].includes(payload.tableName) ) { await this._fullReload(); return; @@ -947,7 +947,7 @@ describe('D. RouteCache partial reload logic', () => { } } - it('table_definition change → finds routes by mainTable.id', async () => { + it('enfyra_table change → finds routes by mainTable.id', async () => { const svc = new TestRouteCachePartial(); svc.cache.routes = [ makeRoute(1, '/tasks', 10, 'tasks'), @@ -956,7 +956,7 @@ describe('D. RouteCache partial reload logic', () => { svc.setDbRoutes([makeRoute(1, '/tasks', 10, 'tasks')]); const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -969,7 +969,7 @@ describe('D. RouteCache partial reload logic', () => { expect(svc.reloadedRouteIds).not.toContain(2); }); - it('route_definition change → reloads specific routes only', async () => { + it('enfyra_route change → reloads specific routes only', async () => { const svc = new TestRouteCachePartial(); svc.cache.routes = [ makeRoute(1, '/tasks', 10, 'tasks'), @@ -978,7 +978,7 @@ describe('D. RouteCache partial reload logic', () => { svc.setDbRoutes([makeRoute(1, '/tasks', 10, 'tasks')]); const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -992,14 +992,14 @@ describe('D. RouteCache partial reload logic', () => { expect(svc.fullReloadCount).toBe(0); }); - it('route_handler_definition change → finds parent route and reloads it', async () => { + it('enfyra_route_handler change → finds parent route and reloads it', async () => { const svc = new TestRouteCachePartial(); svc.cache.routes = [makeRoute(1, '/tasks', 10, 'tasks')]; svc.setDbRoutes([makeRoute(1, '/tasks', 10, 'tasks')]); - svc.setDbChildren('route_handler_definition', [makeChildRecord(50, 1)]); + svc.setDbChildren('enfyra_route_handler', [makeChildRecord(50, 1)]); const payload: TCacheInvalidationPayload = { - tableName: 'route_handler_definition', + tableName: 'enfyra_route_handler', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1012,12 +1012,12 @@ describe('D. RouteCache partial reload logic', () => { expect(svc.fullReloadCount).toBe(0); }); - it('pre_hook_definition change → reloads global hooks and re-merges', async () => { + it('enfyra_pre_hook change → reloads global hooks and re-merges', async () => { const svc = new TestRouteCachePartial(); svc.cache.routes = [makeRoute(1, '/tasks', 10, 'tasks')]; const payload: TCacheInvalidationPayload = { - tableName: 'pre_hook_definition', + tableName: 'enfyra_pre_hook', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1034,12 +1034,12 @@ describe('D. RouteCache partial reload logic', () => { }); }); - it('post_hook_definition change → reloads global hooks and re-merges', async () => { + it('enfyra_post_hook change → reloads global hooks and re-merges', async () => { const svc = new TestRouteCachePartial(); svc.cache.routes = [makeRoute(1, '/tasks', 10, 'tasks')]; const payload: TCacheInvalidationPayload = { - tableName: 'post_hook_definition', + tableName: 'enfyra_post_hook', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1051,12 +1051,12 @@ describe('D. RouteCache partial reload logic', () => { expect(svc.reloadedGlobalHooks).toBe(true); }); - it('role_definition change → falls back to full reload', async () => { + it('enfyra_role change → falls back to full reload', async () => { const svc = new TestRouteCachePartial(); svc.cache.routes = [makeRoute(1, '/tasks', 10, 'tasks')]; const payload: TCacheInvalidationPayload = { - tableName: 'role_definition', + tableName: 'enfyra_role', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1069,10 +1069,10 @@ describe('D. RouteCache partial reload logic', () => { expect(svc.reloadedRouteIds).toHaveLength(0); }); - it('method_definition change → falls back to full reload', async () => { + it('enfyra_method change → falls back to full reload', async () => { const svc = new TestRouteCachePartial(); const payload: TCacheInvalidationPayload = { - tableName: 'method_definition', + tableName: 'enfyra_method', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1093,7 +1093,7 @@ describe('D. RouteCache partial reload logic', () => { svc.setDbRoutes([]); const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1114,7 +1114,7 @@ describe('D. RouteCache partial reload logic', () => { svc.setDbRoutes([updatedRoute]); const payload: TCacheInvalidationPayload = { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1137,7 +1137,7 @@ describe('D. RouteCache partial reload logic', () => { svc.setDbRoutes([makeRoute(2, '/users', 20, 'users')]); const payload: TCacheInvalidationPayload = { - tableName: 'column_definition', + tableName: 'enfyra_column', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1718,7 +1718,7 @@ describe('F. Cross-table cascade / affectedTables scenarios', () => { affectedTables.push(deletedRelation.sourceTableName); affectedTables.push(deletedRelation.targetTableName); - const payload = buildPayload('relation_definition', [5], affectedTables); + const payload = buildPayload('enfyra_relation', [5], affectedTables); expect(payload.affectedTables).toContain('tasks'); expect(payload.affectedTables).toContain('users'); expect(payload.scope).toBe('partial'); @@ -1727,7 +1727,7 @@ describe('F. Cross-table cascade / affectedTables scenarios', () => { it('create table with relations → target tables in affectedTables', () => { const newTable = { name: 'comments', relations: ['tasks', 'users'] }; const affectedTables = [...newTable.relations]; - const payload = buildPayload('table_definition', [10], affectedTables); + const payload = buildPayload('enfyra_table', [10], affectedTables); expect(payload.affectedTables).toContain('tasks'); expect(payload.affectedTables).toContain('users'); }); @@ -1736,7 +1736,7 @@ describe('F. Cross-table cascade / affectedTables scenarios', () => { const deletedTable = 'tasks'; const inboundRelations = ['comments', 'attachments']; const payload = buildPayload( - 'table_definition', + 'enfyra_table', [10], [deletedTable, ...inboundRelations], ); @@ -1745,8 +1745,8 @@ describe('F. Cross-table cascade / affectedTables scenarios', () => { }); it('cascade payload merging deduplicates repeated affectedTables', () => { - const p1 = buildPayload('table_definition', [1], ['tasks', 'users']); - const p2 = buildPayload('table_definition', [2], ['tasks', 'comments']); + const p1 = buildPayload('enfyra_table', [1], ['tasks', 'users']); + const p2 = buildPayload('enfyra_table', [2], ['tasks', 'comments']); const merged = mergePayload(p1, p2); const tableSet = new Set(merged.affectedTables); expect(tableSet.size).toBe(3); @@ -1776,7 +1776,7 @@ describe('F. Cross-table cascade / affectedTables scenarios', () => { mockEmitter.emit('cache:invalidate', payload); } - reloadFn('table_definition', { + reloadFn('enfyra_table', { ids: [1, 2], affectedTables: ['relatedTable'], }); @@ -1784,7 +1784,7 @@ describe('F. Cross-table cascade / affectedTables scenarios', () => { expect(emitted[0].ids).toEqual([1, 2]); expect(emitted[0].affectedTables).toEqual(['relatedTable']); - reloadFn('table_definition'); + reloadFn('enfyra_table'); expect(emitted[1].scope).toBe('full'); expect(emitted[1].ids).toBeUndefined(); }); @@ -1820,18 +1820,18 @@ describe('G. End-to-end flow simulation', () => { this.ee.on('cache:invalidate', (payload: TCacheInvalidationPayload) => { if ( [ - 'table_definition', - 'column_definition', - 'relation_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', ].includes(payload.tableName) ) { this._handleMetadataInvalidation(payload); } if ( [ - 'route_definition', - 'pre_hook_definition', - 'post_hook_definition', + 'enfyra_route', + 'enfyra_pre_hook', + 'enfyra_post_hook', ].includes(payload.tableName) ) { this._handleRouteInvalidation(payload); @@ -1919,7 +1919,7 @@ describe('G. End-to-end flow simulation', () => { sys.setupInitialState(); sys.emit('cache:invalidate', { - tableName: 'column_definition', + tableName: 'enfyra_column', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1951,12 +1951,12 @@ describe('G. End-to-end flow simulation', () => { expect(sys.gqlReloads).toBe(1); }); - it('route_definition partial update → only routes reloaded, not metadata', () => { + it('enfyra_route partial update → only routes reloaded, not metadata', () => { const sys = new SimulatedSystem(); sys.setupInitialState(); sys.emit('cache:invalidate', { - tableName: 'route_definition', + tableName: 'enfyra_route', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -1968,12 +1968,12 @@ describe('G. End-to-end flow simulation', () => { expect(sys.gqlReloads).toBe(1); }); - it('table_definition full reload → all caches reloaded in cascade', () => { + it('enfyra_table full reload → all caches reloaded in cascade', () => { const sys = new SimulatedSystem(); sys.setupInitialState(); sys.emit('cache:invalidate', { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'full', @@ -1994,7 +1994,7 @@ describe('G. End-to-end flow simulation', () => { svcB._supportsPartial = true; const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -2024,7 +2024,7 @@ describe('G. End-to-end flow simulation', () => { for (const { ids, expectedScope } of cases) { const payload: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: ids?.length ? 'partial' : 'full', @@ -2038,20 +2038,20 @@ describe('G. End-to-end flow simulation', () => { let pending: TCacheInvalidationPayload | null = null; const p1: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', ids: [1], }; const p2: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'full', }; const p3: TCacheInvalidationPayload = { - tableName: 'table_definition', + tableName: 'enfyra_table', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -2068,26 +2068,26 @@ describe('G. End-to-end flow simulation', () => { it('CACHE_INVALIDATION_MAP: correct caches reloaded per table', () => { const CACHE_INVALIDATION_MAP: Record = { - table_definition: ['metadata', 'route', 'graphql'], - column_definition: ['metadata', 'route', 'graphql'], - relation_definition: ['metadata', 'route', 'graphql'], - route_definition: ['route', 'graphql'], - pre_hook_definition: ['route', 'graphql'], - post_hook_definition: ['route', 'graphql'], - role_definition: ['route', 'graphql'], - field_permission_definition: ['field-permission'], - setting_definition: ['setting'], + enfyra_table: ['metadata', 'route', 'graphql'], + enfyra_column: ['metadata', 'route', 'graphql'], + enfyra_relation: ['metadata', 'route', 'graphql'], + enfyra_route: ['route', 'graphql'], + enfyra_pre_hook: ['route', 'graphql'], + enfyra_post_hook: ['route', 'graphql'], + enfyra_role: ['route', 'graphql'], + enfyra_field_permission: ['field-permission'], + enfyra_setting: ['setting'], }; const expectedMetadataTables = [ - 'table_definition', - 'column_definition', - 'relation_definition', + 'enfyra_table', + 'enfyra_column', + 'enfyra_relation', ]; const expectedRouteOnlyTables = [ - 'route_definition', - 'pre_hook_definition', - 'post_hook_definition', + 'enfyra_route', + 'enfyra_pre_hook', + 'enfyra_post_hook', ]; for (const t of expectedMetadataTables) { expect(CACHE_INVALIDATION_MAP[t]).toContain('metadata'); @@ -2100,9 +2100,9 @@ describe('G. End-to-end flow simulation', () => { expect(CACHE_INVALIDATION_MAP[t]).toContain('route'); } - expect(CACHE_INVALIDATION_MAP['field_permission_definition']).toEqual([ + expect(CACHE_INVALIDATION_MAP['enfyra_field_permission']).toEqual([ 'field-permission', ]); - expect(CACHE_INVALIDATION_MAP['setting_definition']).toEqual(['setting']); + expect(CACHE_INVALIDATION_MAP['enfyra_setting']).toEqual(['setting']); }); }); diff --git a/test/cache/guard-cache.service.spec.ts b/test/cache/guard-cache.service.spec.ts index 21fdca44..ce745d74 100644 --- a/test/cache/guard-cache.service.spec.ts +++ b/test/cache/guard-cache.service.spec.ts @@ -7,8 +7,8 @@ async function loadGuardCache( rules: any[], ): Promise { const find = jest.fn(async (params: any) => { - if (params.table === 'guard_definition') return { data: guards }; - if (params.table === 'guard_rule_definition') return { data: rules }; + if (params.table === 'enfyra_guard') return { data: guards }; + if (params.table === 'enfyra_guard_rule') return { data: rules }; return { data: [] }; }); const qb = { find, isMongoDb: () => false }; diff --git a/test/cache/metadata-cache.service.spec.ts b/test/cache/metadata-cache.service.spec.ts index ecfe1574..025f271f 100644 --- a/test/cache/metadata-cache.service.spec.ts +++ b/test/cache/metadata-cache.service.spec.ts @@ -47,11 +47,11 @@ describe('MetadataCacheService', () => { it('builds SQL metadata from stored metadata without physical schema introspection', async () => { const service = makeService({ - table_definition: [ + enfyra_table: [ { id: 1, name: 'authors', indexes: '[]', uniques: '[]' }, { id: 2, name: 'posts', indexes: '[]', uniques: '[]' }, ], - column_definition: [ + enfyra_column: [ { id: 10, tableId: 1, @@ -89,7 +89,7 @@ describe('MetadataCacheService', () => { isPublished: true, }, ], - relation_definition: [ + enfyra_relation: [ { id: 30, sourceTableId: 2, @@ -150,11 +150,11 @@ describe('MetadataCacheService', () => { it('marks explicit FK and timestamp metadata instead of duplicating it', async () => { const service = makeService({ - table_definition: [ + enfyra_table: [ { id: 1, name: 'authors', indexes: '[]', uniques: '[]' }, { id: 2, name: 'posts', indexes: '[]', uniques: '[]' }, ], - column_definition: [ + enfyra_column: [ { id: 10, tableId: 1, @@ -192,7 +192,7 @@ describe('MetadataCacheService', () => { isSystem: false, }, ], - relation_definition: [ + enfyra_relation: [ { id: 30, sourceTableId: 2, @@ -232,9 +232,9 @@ describe('MetadataCacheService', () => { it('resolves inverse FK metadata from existing cache during partial reload', async () => { const service = makeService({ - table_definition: [{ id: 2, name: 'teachers', indexes: '[]', uniques: '[]' }], - column_definition: [{ id: 20, tableId: 2, name: 'id', type: 'int', isPrimary: true }], - relation_definition: [ + enfyra_table: [{ id: 2, name: 'teachers', indexes: '[]', uniques: '[]' }], + enfyra_column: [{ id: 20, tableId: 2, name: 'id', type: 'int', isPrimary: true }], + enfyra_relation: [ { id: 41, sourceTableId: 2, @@ -306,7 +306,7 @@ describe('MetadataCacheService', () => { }; await (service as any).applyPartialUpdate({ - table: 'table_definition', + table: 'enfyra_table', action: 'reload', scope: 'partial', ids: [2], diff --git a/test/cache/multi-instance-orchestrator.spec.ts b/test/cache/multi-instance-orchestrator.spec.ts index e12ffea7..c5217f86 100644 --- a/test/cache/multi-instance-orchestrator.spec.ts +++ b/test/cache/multi-instance-orchestrator.spec.ts @@ -1,23 +1,23 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const RELOAD_CHAINS: Record = { - table_definition: [ + enfyra_table: [ 'metadata', 'repoRegistry', 'route', 'graphql', 'fieldPermission', ], - column_definition: [ + enfyra_column: [ 'metadata', 'repoRegistry', 'route', 'graphql', 'fieldPermission', ], - route_definition: ['route', 'graphql', 'guard'], - guard_definition: ['guard'], - package_definition: ['package'], - setting_definition: ['setting', 'settingGraphql'], + enfyra_route: ['route', 'graphql', 'guard'], + enfyra_guard: ['guard'], + enfyra_package: ['package'], + enfyra_setting: ['setting', 'settingGraphql'], }; type Signal = { instanceId: string; payload: any }; @@ -126,19 +126,19 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { notifyClients('pending'); reloaded.push('metadata', 'repoRegistry', 'route', 'graphql'); notifyClients('done'); - await publishSignal({ tableName: 'table_definition', scope: 'full' }); + await publishSignal({ tableName: 'enfyra_table', scope: 'full' }); }, async reloadRoutesOnly() { notifyClients('pending'); reloaded.push('route'); notifyClients('done'); - await publishSignal({ tableName: 'route_definition', scope: 'full' }); + await publishSignal({ tableName: 'enfyra_route', scope: 'full' }); }, async reloadGuardsOnly() { reloaded.push('guard'); - await publishSignal({ tableName: 'guard_definition', scope: 'full' }); + await publishSignal({ tableName: 'enfyra_guard', scope: 'full' }); }, }; } @@ -161,7 +161,7 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const B = createInstance('B', redis); await A.handleInvalidation({ - tableName: 'table_definition', + tableName: 'enfyra_table', scope: 'partial', ids: [99], }); @@ -208,7 +208,7 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const B = createInstance('B', redis); await A.handleInvalidation({ - tableName: 'column_definition', + tableName: 'enfyra_column', scope: 'partial', ids: [5], }); @@ -231,7 +231,7 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const A = createInstance('A', redis); await A.handleInvalidation({ - tableName: 'guard_definition', + tableName: 'enfyra_guard', scope: 'full', }); await deliverSignals(redis, [A]); @@ -248,7 +248,7 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const C = createInstance('C', redis); await A.handleInvalidation({ - tableName: 'table_definition', + tableName: 'enfyra_table', scope: 'partial', ids: [50], }); @@ -271,7 +271,7 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const B = createInstance('B', redis); await A.handleInvalidation({ - tableName: 'package_definition', + tableName: 'enfyra_package', scope: 'full', }); await deliverSignals(redis, [A, B]); @@ -290,7 +290,7 @@ describe('CacheOrchestrator — multi-instance Redis sync', () => { const B = createInstance('B', redis); await A.handleInvalidation({ - tableName: 'setting_definition', + tableName: 'enfyra_setting', scope: 'full', }); await deliverSignals(redis, [A, B]); diff --git a/test/cache/redis-runtime-cache-store.spec.ts b/test/cache/redis-runtime-cache-store.spec.ts index deda20e5..e34ef79b 100644 --- a/test/cache/redis-runtime-cache-store.spec.ts +++ b/test/cache/redis-runtime-cache-store.spec.ts @@ -174,7 +174,7 @@ describe('Redis runtime cache mode', () => { await cache.reload(false); await cache.partialReload( { - table: 'setting_definition', + table: 'enfyra_setting', action: 'reload', scope: 'partial', timestamp: Date.now(), diff --git a/test/cache/route-cache-hook-partial-reload.spec.ts b/test/cache/route-cache-hook-partial-reload.spec.ts index 10e264d1..b72fa574 100644 --- a/test/cache/route-cache-hook-partial-reload.spec.ts +++ b/test/cache/route-cache-hook-partial-reload.spec.ts @@ -1,9 +1,9 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', () => { const CHILD_ARRAY_KEY: Record = { - pre_hook_definition: 'preHooks', - post_hook_definition: 'postHooks', - route_handler_definition: 'handlers', - route_permission_definition: 'routePermissions', + enfyra_pre_hook: 'preHooks', + enfyra_post_hook: 'postHooks', + enfyra_route_handler: 'handlers', + enfyra_route_permission: 'routePermissions', }; let cacheRoutes: any[]; @@ -79,7 +79,7 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', async function applyPartialUpdate(payload: any) { if ( - ['route_handler_definition', 'route_permission_definition'].includes( + ['enfyra_route_handler', 'enfyra_route_permission'].includes( payload.table, ) && payload.ids?.length @@ -95,7 +95,7 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', } if ( - ['pre_hook_definition', 'post_hook_definition'].includes(payload.table) + ['enfyra_pre_hook', 'enfyra_post_hook'].includes(payload.table) ) { reloadGlobalHooksAndMergeCalls++; if (payload.ids?.length) { @@ -121,10 +121,10 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', describe('hooks', () => { it('add global hook (no route): only reloads global hooks, no specific route reload', async () => { - dbRecords.pre_hook_definition = [{ id: 1, isGlobal: true, route: null }]; + dbRecords.enfyra_pre_hook = [{ id: 1, isGlobal: true, route: null }]; await applyPartialUpdate({ - table: 'pre_hook_definition', + table: 'enfyra_pre_hook', scope: 'partial', ids: [1], }); @@ -134,12 +134,12 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', }); it('add route-specific pre-hook: reloads global hooks AND the affected route', async () => { - dbRecords.pre_hook_definition = [ + dbRecords.enfyra_pre_hook = [ { id: 42, isGlobal: false, route: { id: 7 } }, ]; await applyPartialUpdate({ - table: 'pre_hook_definition', + table: 'enfyra_pre_hook', scope: 'partial', ids: [42], }); @@ -153,10 +153,10 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', { id: 5, preHooks: [{ id: 42 }] }, { id: 6, preHooks: [] }, ]; - dbRecords.pre_hook_definition = []; + dbRecords.enfyra_pre_hook = []; await applyPartialUpdate({ - table: 'pre_hook_definition', + table: 'enfyra_pre_hook', scope: 'partial', ids: [42], }); @@ -170,12 +170,12 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', { id: 10, preHooks: [{ id: 99 }] }, { id: 20, preHooks: [] }, ]; - dbRecords.pre_hook_definition = [ + dbRecords.enfyra_pre_hook = [ { id: 99, isGlobal: false, route: { id: 20 } }, ]; await applyPartialUpdate({ - table: 'pre_hook_definition', + table: 'enfyra_pre_hook', scope: 'partial', ids: [99], }); @@ -189,12 +189,12 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', it('update in place (same route): dedupes to single route ID', async () => { cacheRoutes = [{ id: 7, preHooks: [{ id: 42 }] }]; - dbRecords.pre_hook_definition = [ + dbRecords.enfyra_pre_hook = [ { id: 42, isGlobal: false, route: { id: 7 } }, ]; await applyPartialUpdate({ - table: 'pre_hook_definition', + table: 'enfyra_pre_hook', scope: 'partial', ids: [42], }); @@ -204,10 +204,10 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', it('post-hook flow mirrors pre-hook', async () => { cacheRoutes = [{ id: 3, postHooks: [{ id: 11 }] }]; - dbRecords.post_hook_definition = []; + dbRecords.enfyra_post_hook = []; await applyPartialUpdate({ - table: 'post_hook_definition', + table: 'enfyra_post_hook', scope: 'partial', ids: [11], }); @@ -217,7 +217,7 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', }); it('full-scope (no ids): only global hooks refreshed, no route lookup', async () => { - await applyPartialUpdate({ table: 'pre_hook_definition', scope: 'full' }); + await applyPartialUpdate({ table: 'enfyra_pre_hook', scope: 'full' }); expect(reloadGlobalHooksAndMergeCalls).toBe(1); expect(reloadSpecificRoutesCalls).toEqual([]); @@ -225,13 +225,13 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', }); }); - describe('route_handler_definition', () => { + describe('enfyra_route_handler', () => { it('delete handler: reloads old route from cache scan', async () => { cacheRoutes = [{ id: 1, handlers: [{ id: 77 }] }]; - dbRecords.route_handler_definition = []; + dbRecords.enfyra_route_handler = []; await applyPartialUpdate({ - table: 'route_handler_definition', + table: 'enfyra_route_handler', scope: 'partial', ids: [77], }); @@ -244,10 +244,10 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', { id: 1, handlers: [{ id: 77 }] }, { id: 2, handlers: [] }, ]; - dbRecords.route_handler_definition = [{ id: 77, route: { id: 2 } }]; + dbRecords.enfyra_route_handler = [{ id: 77, route: { id: 2 } }]; await applyPartialUpdate({ - table: 'route_handler_definition', + table: 'enfyra_route_handler', scope: 'partial', ids: [77], }); @@ -259,13 +259,13 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', }); }); - describe('route_permission_definition', () => { + describe('enfyra_route_permission', () => { it('delete permission: reloads old route from cache scan', async () => { cacheRoutes = [{ id: 9, routePermissions: [{ id: 500 }] }]; - dbRecords.route_permission_definition = []; + dbRecords.enfyra_route_permission = []; await applyPartialUpdate({ - table: 'route_permission_definition', + table: 'enfyra_route_permission', scope: 'partial', ids: [500], }); @@ -278,10 +278,10 @@ describe('RouteCacheService — partial reload for hooks/handlers/permissions', { id: 9, routePermissions: [{ id: 500 }] }, { id: 10, routePermissions: [] }, ]; - dbRecords.route_permission_definition = [{ id: 500, route: { id: 10 } }]; + dbRecords.enfyra_route_permission = [{ id: 500, route: { id: 10 } }]; await applyPartialUpdate({ - table: 'route_permission_definition', + table: 'enfyra_route_permission', scope: 'partial', ids: [500], }); diff --git a/test/cache/route-cache-partial-reload.spec.ts b/test/cache/route-cache-partial-reload.spec.ts index 1a739d3a..9ea31953 100644 --- a/test/cache/route-cache-partial-reload.spec.ts +++ b/test/cache/route-cache-partial-reload.spec.ts @@ -1,4 +1,4 @@ -describe('RouteCacheService — partial reload for table_definition', () => { +describe('RouteCacheService — partial reload for enfyra_table', () => { let cache: { routes: any[] }; let dbRoutes: any[]; let reloadSpecificRoutesCalled: { ids: any[] } | null; @@ -8,7 +8,7 @@ describe('RouteCacheService — partial reload for table_definition', () => { const queryBuilder = { isMongoDb: () => false, select: jest.fn(async ({ tableName, filter }: any) => { - if (tableName === 'route_definition' && filter?.mainTableId) { + if (tableName === 'enfyra_route' && filter?.mainTableId) { const ids = filter.mainTableId._in || []; const matched = dbRoutes.filter((r) => ids.some((id: any) => String(id) === String(r.mainTableId)), @@ -22,9 +22,9 @@ describe('RouteCacheService — partial reload for table_definition', () => { async function applyPartialUpdate(payload: any) { const affectedTableNames = new Set(payload.affectedTables || []); - if (payload.tableName === 'table_definition' && payload.ids?.length) { + if (payload.tableName === 'enfyra_table' && payload.ids?.length) { const result = await queryBuilder.select({ - tableName: 'route_definition', + tableName: 'enfyra_route', filter: { mainTableId: { _in: payload.ids } }, fields: ['id'], }); @@ -68,7 +68,7 @@ describe('RouteCacheService — partial reload for table_definition', () => { dbRoutes = [{ id: 99, mainTableId: 50 }]; await applyPartialUpdate({ - tableName: 'table_definition', + tableName: 'enfyra_table', scope: 'partial', ids: [50], }); @@ -93,7 +93,7 @@ describe('RouteCacheService — partial reload for table_definition', () => { dbRoutes = []; await applyPartialUpdate({ - tableName: 'table_definition', + tableName: 'enfyra_table', scope: 'partial', ids: [50], }); @@ -112,7 +112,7 @@ describe('RouteCacheService — partial reload for table_definition', () => { dbRoutes = [{ id: 10, mainTableId: 50 }]; await applyPartialUpdate({ - tableName: 'table_definition', + tableName: 'enfyra_table', scope: 'partial', ids: [50], }); @@ -126,7 +126,7 @@ describe('RouteCacheService — partial reload for table_definition', () => { dbRoutes = []; await applyPartialUpdate({ - tableName: 'table_definition', + tableName: 'enfyra_table', scope: 'partial', ids: [999], }); @@ -136,9 +136,9 @@ describe('RouteCacheService — partial reload for table_definition', () => { expect(engineRebuilt).toBe(false); }); - it('non-table_definition payload: falls through to other handlers', async () => { + it('non-enfyra_table payload: falls through to other handlers', async () => { await applyPartialUpdate({ - tableName: 'setting_definition', + tableName: 'enfyra_setting', scope: 'partial', ids: [1], }); diff --git a/test/cache/websocket-cache-partial.spec.ts b/test/cache/websocket-cache-partial.spec.ts index 2c349a0b..79a4c522 100644 --- a/test/cache/websocket-cache-partial.spec.ts +++ b/test/cache/websocket-cache-partial.spec.ts @@ -10,7 +10,7 @@ function makeQb(gateways: any[], events: any[]) { const ids = args?.filter?.id?._in ?? args?.filter?._and?.find((item: any) => item?.id?._in)?.id?._in; - if (table === 'websocket_definition') { + if (table === 'enfyra_websocket') { let rows = gateways.filter((gateway) => gateway.isEnabled !== false); if (ids) { const idSet = new Set(ids.map(String)); @@ -18,7 +18,7 @@ function makeQb(gateways: any[], events: any[]) { } return { data: rows.map((row) => ({ ...row })) }; } - if (table === 'websocket_event_definition') { + if (table === 'enfyra_websocket_event') { let rows = events; if (ids) { const idSet = new Set(ids.map(String)); @@ -76,7 +76,7 @@ describe('WebsocketCacheService partial reload', () => { await svc.partialReload( { - table: 'websocket_event_definition', + table: 'enfyra_websocket_event', action: 'reload', timestamp: 0, scope: 'partial', @@ -95,13 +95,13 @@ describe('WebsocketCacheService partial reload', () => { ); expect(qb.find).toHaveBeenCalledWith( expect.objectContaining({ - table: 'websocket_event_definition', + table: 'enfyra_websocket_event', filter: { id: { _in: [10] } }, }), ); expect(qb.find).toHaveBeenCalledWith( expect.objectContaining({ - table: 'websocket_definition', + table: 'enfyra_websocket', filter: { _and: [{ isEnabled: { _eq: true } }, { id: { _in: [1] } }], }, @@ -121,7 +121,7 @@ describe('WebsocketCacheService partial reload', () => { await svc.partialReload( { - table: 'websocket_definition', + table: 'enfyra_websocket', action: 'reload', timestamp: 0, scope: 'partial', diff --git a/test/domain/api-token.service.spec.ts b/test/domain/api-token.service.spec.ts index 8422a1a6..befc9ad1 100644 --- a/test/domain/api-token.service.spec.ts +++ b/test/domain/api-token.service.spec.ts @@ -13,16 +13,16 @@ function createHarness() { isMongoDb: () => false, getPkField: () => 'id', find: vi.fn(async ({ table, filter }: any) => { - if (table !== 'api_token_definition') return { data: [] }; + if (table !== 'enfyra_api_token') return { data: [] }; const userId = filter?.user?._eq; return { data: [...tokens.values()].filter((token) => token.userId === userId), }; }), findOne: vi.fn(async ({ table, where }: any) => { - if (table === 'user_definition') return users.get(where.id) || null; - if (table === 'role_definition') return null; - if (table !== 'api_token_definition') return null; + if (table === 'enfyra_user') return users.get(where.id) || null; + if (table === 'enfyra_role') return null; + if (table !== 'enfyra_api_token') return null; if (where.id) return tokens.get(where.id) || null; if (where.tokenHash) { return ( diff --git a/test/domain/bootstrap-data-validator.spec.ts b/test/domain/bootstrap-data-validator.spec.ts index 60dd4d0e..37d92116 100644 --- a/test/domain/bootstrap-data-validator.spec.ts +++ b/test/domain/bootstrap-data-validator.spec.ts @@ -22,12 +22,12 @@ describe('validateBootstrapDataFiles', () => { it('reports unknown route mainTable and method names', () => { const issues = validateBootstrapDataFiles({ snapshot: { - method_definition: {}, - route_definition: {}, + enfyra_method: {}, + enfyra_route: {}, }, defaultData: { - method_definition: [{ name: 'GET' }], - route_definition: [ + enfyra_method: [{ name: 'GET' }], + enfyra_route: [ { path: '/bad', mainTable: 'missing_definition', @@ -47,42 +47,42 @@ describe('validateBootstrapDataFiles', () => { it('reports broken bootstrap references outside route records', () => { const issues = validateBootstrapDataFiles({ snapshot: { - route_definition: {}, - method_definition: {}, - menu_definition: {}, - gql_definition: {}, - websocket_definition: {}, - websocket_event_definition: {}, - flow_definition: {}, - flow_step_definition: {}, + enfyra_route: {}, + enfyra_method: {}, + enfyra_menu: {}, + enfyra_graphql: {}, + enfyra_websocket: {}, + enfyra_websocket_event: {}, + enfyra_flow: {}, + enfyra_flow_step: {}, known_table: {}, }, defaultData: { - method_definition: [{ name: 'GET' }], - route_definition: [{ path: '/ok', availableMethods: ['GET'] }], - menu_definition: [ + enfyra_method: [{ name: 'GET' }], + enfyra_route: [{ path: '/ok', availableMethods: ['GET'] }], + enfyra_menu: [ { path: '/menu', permission: { route: '/missing', methods: ['NOPE'] }, }, ], - pre_hook_definition: [{ route: '/missing-hook', methods: ['GET'] }], - gql_definition: [{ table: { name: 'missing_table' } }], - websocket_definition: [{ name: 'admin' }], - websocket_event_definition: [{ gateway: { name: 'missing_ws' } }], - flow_definition: [{ name: 'main_flow' }], - flow_step_definition: [{ flow: { name: 'missing_flow' } }], + enfyra_pre_hook: [{ route: '/missing-hook', methods: ['GET'] }], + enfyra_graphql: [{ table: { name: 'missing_table' } }], + enfyra_websocket: [{ name: 'admin' }], + enfyra_websocket_event: [{ gateway: { name: 'missing_ws' } }], + enfyra_flow: [{ name: 'main_flow' }], + enfyra_flow_step: [{ flow: { name: 'missing_flow' } }], }, dataMigration: {}, }); expect(issues.map((issue) => [issue.table, issue.field])).toEqual([ - ['pre_hook_definition', 'route'], - ['menu_definition', 'permission'], - ['menu_definition', 'methods'], - ['gql_definition', 'table'], - ['websocket_event_definition', 'gateway'], - ['flow_step_definition', 'flow'], + ['enfyra_pre_hook', 'route'], + ['enfyra_menu', 'permission'], + ['enfyra_menu', 'methods'], + ['enfyra_graphql', 'table'], + ['enfyra_websocket_event', 'gateway'], + ['enfyra_flow_step', 'flow'], ]); }); }); diff --git a/test/domain/canonical-table-route.util.spec.ts b/test/domain/canonical-table-route.util.spec.ts index 7f85059b..2ba164ba 100644 --- a/test/domain/canonical-table-route.util.spec.ts +++ b/test/domain/canonical-table-route.util.spec.ts @@ -6,22 +6,22 @@ import { describe('isCanonicalTableRoutePath', () => { it('accepts only single-segment path equal to table name', () => { expect( - isCanonicalTableRoutePath('/menu_definition', 'menu_definition'), + isCanonicalTableRoutePath('/enfyra_menu', 'enfyra_menu'), ).toBe(true); expect( - isCanonicalTableRoutePath('menu_definition', 'menu_definition'), + isCanonicalTableRoutePath('enfyra_menu', 'enfyra_menu'), ).toBe(true); }); it('rejects auth and profile-style paths even when mainTable differs', () => { - expect(isCanonicalTableRoutePath('/auth/login', 'user_definition')).toBe( + expect(isCanonicalTableRoutePath('/auth/login', 'enfyra_user')).toBe( false, ); - expect(isCanonicalTableRoutePath('/me', 'user_definition')).toBe(false); + expect(isCanonicalTableRoutePath('/me', 'enfyra_user')).toBe(false); expect( isCanonicalTableRoutePath( '/me/oauth-accounts', - 'oauth_account_definition', + 'enfyra_oauth_account', ), ).toBe(false); }); @@ -29,12 +29,12 @@ describe('isCanonicalTableRoutePath', () => { it('rejects nested resource paths', () => { expect( isCanonicalTableRoutePath( - '/extension_definition/preview', - 'extension_definition', + '/enfyra_extension/preview', + 'enfyra_extension', ), ).toBe(false); expect( - isCanonicalTableRoutePath('/folder_definition/tree', 'folder_definition'), + isCanonicalTableRoutePath('/enfyra_folder/tree', 'enfyra_folder'), ).toBe(false); }); diff --git a/test/domain/data-migration.service.spec.ts b/test/domain/data-migration.service.spec.ts index fbd98167..e5cd0df3 100644 --- a/test/domain/data-migration.service.spec.ts +++ b/test/domain/data-migration.service.spec.ts @@ -29,7 +29,7 @@ function makeKnex(methodRows: any[] = []) { const inserts: any[] = []; const rawCalls: any[] = []; const knex = jest.fn((table: string) => { - if (table === 'method_definition') { + if (table === 'enfyra_method') { return { select: jest.fn().mockReturnThis(), whereIn: jest.fn((_field: string, values: string[]) => @@ -39,7 +39,7 @@ function makeKnex(methodRows: any[] = []) { ), }; } - if (table === 'relation_definition as r') { + if (table === 'enfyra_relation as r') { let propertyName = 'publicMethods'; const chain: any = { leftJoin: jest.fn(() => chain), @@ -87,20 +87,20 @@ function makeMongoDb() { deletes, inserts, collection: jest.fn((name: string) => { - if (name === 'table_definition') { + if (name === 'enfyra_table') { return { findOne: jest.fn(async (filter: any) => { - if (filter.name === 'route_definition') { - return { _id: routeTableId, name: 'route_definition' }; + if (filter.name === 'enfyra_route') { + return { _id: routeTableId, name: 'enfyra_route' }; } - if (filter.name === 'method_definition') { - return { _id: methodTableId, name: 'method_definition' }; + if (filter.name === 'enfyra_method') { + return { _id: methodTableId, name: 'enfyra_method' }; } return null; }), }; } - if (name === 'relation_definition') { + if (name === 'enfyra_relation') { return { findOne: jest.fn(async (filter: any) => filter.propertyName @@ -137,7 +137,7 @@ describe('DataMigrationService.transformRecord', () => { it('captures non-empty publicMethods as relation update', () => { const svc = makeService(makeQueryBuilder()); const { newRecord, relationUpdates } = (svc as any).transformRecord( - 'route_definition', + 'enfyra_route', { _unique: { path: { _eq: '/me' } }, publicMethods: ['GET', 'POST'], @@ -152,7 +152,7 @@ describe('DataMigrationService.transformRecord', () => { it('captures empty array publicMethods as relation update (the bug fix)', () => { const svc = makeService(makeQueryBuilder()); const { newRecord, relationUpdates } = (svc as any).transformRecord( - 'route_definition', + 'enfyra_route', { _unique: { path: { _eq: '/metadata' } }, publicMethods: [] }, ); expect(relationUpdates.publicMethods).toEqual([]); @@ -162,7 +162,7 @@ describe('DataMigrationService.transformRecord', () => { it('captures empty array availableMethods as relation update', () => { const svc = makeService(makeQueryBuilder()); const { newRecord, relationUpdates } = (svc as any).transformRecord( - 'route_definition', + 'enfyra_route', { _unique: { path: { _eq: '/test' } }, availableMethods: [] }, ); expect(relationUpdates.availableMethods).toEqual([]); @@ -172,7 +172,7 @@ describe('DataMigrationService.transformRecord', () => { it('does not capture undefined relation field', () => { const svc = makeService(makeQueryBuilder()); const { relationUpdates } = (svc as any).transformRecord( - 'route_definition', + 'enfyra_route', { _unique: { path: { _eq: '/test' } }, name: 'hello' }, ); expect(relationUpdates.publicMethods).toBeUndefined(); @@ -182,7 +182,7 @@ describe('DataMigrationService.transformRecord', () => { it('does not capture null relation field', () => { const svc = makeService(makeQueryBuilder()); const { relationUpdates } = (svc as any).transformRecord( - 'route_definition', + 'enfyra_route', { _unique: { path: { _eq: '/test' } }, publicMethods: null }, ); expect(relationUpdates.publicMethods).toBeUndefined(); @@ -204,7 +204,7 @@ describe('DataMigrationService.updateRelations', () => { }); const svc = makeService(qb); - await (svc as any).updateRelations('route_definition', 99, { + await (svc as any).updateRelations('enfyra_route', 99, { publicMethods: [], }); @@ -224,7 +224,7 @@ describe('DataMigrationService.updateRelations', () => { }); const svc = makeService(qb); - await (svc as any).updateRelations('route_definition', 10, { + await (svc as any).updateRelations('enfyra_route', 10, { publicMethods: ['GET', 'POST'], }); @@ -240,7 +240,7 @@ describe('DataMigrationService.updateRelations', () => { }); const svc = makeService(qb); - await (svc as any).updateRelations('route_definition', 5, { + await (svc as any).updateRelations('enfyra_route', 5, { availableMethods: [], }); @@ -250,11 +250,11 @@ describe('DataMigrationService.updateRelations', () => { }); }); - it('does nothing for non-route_definition tables', async () => { + it('does nothing for non-enfyra_route tables', async () => { const qb = makeQueryBuilder(); const svc = makeService(qb); - await (svc as any).updateRelations('user_definition', 1, { + await (svc as any).updateRelations('enfyra_user', 1, { publicMethods: [], }); @@ -268,7 +268,7 @@ describe('DataMigrationService.updateRelations', () => { }); const svc = makeService(qb); - await (svc as any).updateRelations('route_definition', 7, { + await (svc as any).updateRelations('enfyra_route', 7, { publicMethods: [], availableMethods: ['POST'], }); @@ -299,7 +299,7 @@ describe('DataMigrationService.updateRelations', () => { const svc = makeService(qb); await (svc as any).updateRelations( - 'route_definition', + 'enfyra_route', '655555555555555555555555', { availableMethods: ['GET', 'POST'], @@ -340,7 +340,7 @@ describe('DataMigrationService.migrateTable — end-to-end for publicMethods cle }); const svc = makeService(qb); - await (svc as any).migrateTable('route_definition', [ + await (svc as any).migrateTable('enfyra_route', [ { _unique: { path: { _eq: '/metadata' } }, publicMethods: [] }, ]); @@ -357,7 +357,7 @@ describe('DataMigrationService.migrateTable — end-to-end for publicMethods cle }); const svc = makeService(qb); - await (svc as any).migrateTable('route_definition', [ + await (svc as any).migrateTable('enfyra_route', [ { _unique: { path: { _eq: '/nonexistent' } }, publicMethods: [] }, ]); @@ -370,7 +370,7 @@ describe('DataMigrationService.migrateTable — end-to-end for publicMethods cle }); const svc = makeService(qb); - await (svc as any).migrateTable('route_definition', [ + await (svc as any).migrateTable('enfyra_route', [ { _unique: { path: { _eq: '/me' } }, isEnabled: true }, ]); @@ -385,7 +385,7 @@ describe('DataMigrationService.migrateTable — end-to-end for publicMethods cle { column: { name: { _eq: 'password' }, - table: { name: { _eq: 'user_definition' } }, + table: { name: { _eq: 'enfyra_user' } }, }, }, { @@ -400,18 +400,18 @@ describe('DataMigrationService.migrateTable — end-to-end for publicMethods cle }); const svc = makeService(qb); - await (svc as any).migrateTable('field_permission_definition', [ + await (svc as any).migrateTable('enfyra_field_permission', [ { _unique: uniqueFilter, isSystem: true }, ]); expect(qb.find).toHaveBeenCalledWith({ - table: 'field_permission_definition', + table: 'enfyra_field_permission', filter: uniqueFilter, limit: 1, fields: ['id'], }); expect(qb.update).toHaveBeenCalledWith( - 'field_permission_definition', + 'enfyra_field_permission', { where: [{ field: 'id', operator: '=', value: 7 }] }, { isSystem: true }, ); diff --git a/test/domain/field-permission-definition-processor.spec.ts b/test/domain/field-permission-definition-processor.spec.ts index 8203dbe8..5228f51c 100644 --- a/test/domain/field-permission-definition-processor.spec.ts +++ b/test/domain/field-permission-definition-processor.spec.ts @@ -39,13 +39,13 @@ describe('FieldPermissionDefinitionProcessor.processWithQueryBuilder', () => { first: vi.fn().mockResolvedValue(existingPermission), }; const knex = vi.fn((table: string) => { - expect(table).toBe('field_permission_definition'); + expect(table).toBe('enfyra_field_permission'); return builder; }); const queryBuilder: any = { findOne: vi .fn() - .mockResolvedValueOnce({ id: 10, name: 'user_definition' }) + .mockResolvedValueOnce({ id: 10, name: 'enfyra_user' }) .mockResolvedValueOnce({ id: 20, name: 'password' }), getKnex: vi.fn(() => knex), insert: vi.fn(), @@ -64,13 +64,13 @@ describe('FieldPermissionDefinitionProcessor.processWithQueryBuilder', () => { effect: 'allow', description: 'Allow authenticated user to update own password via /me', - _column: { table: 'user_definition', name: 'password' }, + _column: { table: 'enfyra_user', name: 'password' }, _role: null, condition, }, ], queryBuilder, - 'field_permission_definition', + 'enfyra_field_permission', ); expect(result).toEqual({ created: 0, skipped: 1 }); @@ -105,13 +105,13 @@ describe('FieldPermissionDefinitionProcessor.processWithQueryBuilder', () => { relation: null, }); const collection = vi.fn((table: string) => { - expect(table).toBe('field_permission_definition'); + expect(table).toBe('enfyra_field_permission'); return { findOne }; }); const queryBuilder: any = { findOne: vi .fn() - .mockResolvedValueOnce({ _id: tableId, name: 'user_definition' }) + .mockResolvedValueOnce({ _id: tableId, name: 'enfyra_user' }) .mockResolvedValueOnce({ _id: columnId, name: 'password' }), getMongoDb: vi.fn(() => ({ collection })), insert: vi.fn(), @@ -130,13 +130,13 @@ describe('FieldPermissionDefinitionProcessor.processWithQueryBuilder', () => { effect: 'allow', description: 'Allow authenticated user to update own password via /me', - _column: { table: 'user_definition', name: 'password' }, + _column: { table: 'enfyra_user', name: 'password' }, _role: null, condition, }, ], queryBuilder, - 'field_permission_definition', + 'enfyra_field_permission', ); expect(result).toEqual({ created: 0, skipped: 1 }); diff --git a/test/domain/load-user-with-role.util.spec.ts b/test/domain/load-user-with-role.util.spec.ts index b80dba34..6b096dd7 100644 --- a/test/domain/load-user-with-role.util.spec.ts +++ b/test/domain/load-user-with-role.util.spec.ts @@ -27,8 +27,8 @@ describe('loadUserWithRole', () => { const role = { _id: roleId, name: 'Admin' }; const user = { _id: userId, email: 'root@example.com', role: roleId }; const findOne = vi.fn(async ({ table }) => { - if (table === 'user_definition') return user; - if (table === 'role_definition') return role; + if (table === 'enfyra_user') return user; + if (table === 'enfyra_role') return role; return null; }); const queryBuilder = { @@ -39,11 +39,11 @@ describe('loadUserWithRole', () => { const result = await loadUserWithRole(queryBuilder, userId.toHexString()); expect(findOne).toHaveBeenNthCalledWith(1, { - table: 'user_definition', + table: 'enfyra_user', where: { _id: userId }, }); expect(findOne).toHaveBeenNthCalledWith(2, { - table: 'role_definition', + table: 'enfyra_role', where: { _id: roleId }, }); expect(result?.role).toEqual(role); @@ -83,8 +83,8 @@ describe('loadUserWithRole', () => { const role = { id: 1, name: 'Admin' }; const user = { id: userId, email: 'root@example.com', roleId: 1 }; const findOne = vi.fn(async ({ table }) => { - if (table === 'user_definition') return user; - if (table === 'role_definition') return role; + if (table === 'enfyra_user') return user; + if (table === 'enfyra_role') return role; return null; }); const queryBuilder = { @@ -95,11 +95,11 @@ describe('loadUserWithRole', () => { const result = await loadUserWithRole(queryBuilder, userId); expect(findOne).toHaveBeenNthCalledWith(1, { - table: 'user_definition', + table: 'enfyra_user', where: { id: userId }, }); expect(findOne).toHaveBeenNthCalledWith(2, { - table: 'role_definition', + table: 'enfyra_role', where: { id: 1 }, }); expect(result?.role).toEqual(role); diff --git a/test/domain/menu-processor-lazy-batch.spec.ts b/test/domain/menu-processor-lazy-batch.spec.ts index 9cbb52bb..691582f4 100644 --- a/test/domain/menu-processor-lazy-batch.spec.ts +++ b/test/domain/menu-processor-lazy-batch.spec.ts @@ -5,7 +5,7 @@ */ describe('MenuDefinitionProcessor — lazy batch transform', () => { it('should transform and insert each batch sequentially so parents exist for children', async () => { - const db: Record = { menu_definition: [] }; + const db: Record = { enfyra_menu: [] }; let insertId = 0; const queryBuilder = { @@ -58,7 +58,7 @@ describe('MenuDefinitionProcessor — lazy batch transform', () => { for (const record of recs) { const transformed = { ...record }; if (transformed.parent && typeof transformed.parent === 'string') { - const parent = await queryBuilder.findOneWhere('menu_definition', { + const parent = await queryBuilder.findOneWhere('enfyra_menu', { type: 'Dropdown Menu', label: transformed.parent, }); @@ -80,21 +80,21 @@ describe('MenuDefinitionProcessor — lazy batch transform', () => { for (const rawBatch of rawBatches) { const batch = await transformRecords(rawBatch); for (const record of batch) { - await queryBuilder.insertAndGet('menu_definition', record); + await queryBuilder.insertAndGet('enfyra_menu', record); } } // All 3 records inserted - expect(db.menu_definition).toHaveLength(3); + expect(db.enfyra_menu).toHaveLength(3); // "Settings" dropdown was created first (id=1) - const settings = db.menu_definition.find((r) => r.label === 'Settings'); + const settings = db.enfyra_menu.find((r) => r.label === 'Settings'); expect(settings).toBeDefined(); expect(settings!.id).toBe(1); // Menu items should have parentId resolved correctly - const users = db.menu_definition.find((r) => r.label === 'Users'); - const roles = db.menu_definition.find((r) => r.label === 'Roles'); + const users = db.enfyra_menu.find((r) => r.label === 'Users'); + const roles = db.enfyra_menu.find((r) => r.label === 'Roles'); expect(users!.parentId).toBe(settings!.id); expect(roles!.parentId).toBe(settings!.id); expect(users!._parentNotFound).toBeUndefined(); @@ -102,7 +102,7 @@ describe('MenuDefinitionProcessor — lazy batch transform', () => { }); it('should fail to resolve parent with eager transform (demonstrates the bug)', async () => { - const db: Record = { menu_definition: [] }; + const db: Record = { enfyra_menu: [] }; let insertId = 0; const queryBuilder = { @@ -139,7 +139,7 @@ describe('MenuDefinitionProcessor — lazy batch transform', () => { for (const record of recs) { const transformed = { ...record }; if (transformed.parent && typeof transformed.parent === 'string') { - const parent = await queryBuilder.findOneWhere('menu_definition', { + const parent = await queryBuilder.findOneWhere('enfyra_menu', { type: 'Dropdown Menu', label: transformed.parent, }); @@ -161,11 +161,11 @@ describe('MenuDefinitionProcessor — lazy batch transform', () => { const batch2 = await transformRecords(menuItems); // parent lookup fails here! for (const record of [...batch1, ...batch2]) { - await queryBuilder.insertAndGet('menu_definition', record); + await queryBuilder.insertAndGet('enfyra_menu', record); } // "Users" menu item has _parentNotFound because "Settings" wasn't in DB yet during transform - const users = db.menu_definition.find((r) => r.label === 'Users'); + const users = db.enfyra_menu.find((r) => r.label === 'Users'); expect(users!._parentNotFound).toBe(true); expect(users!.parentId).toBeUndefined(); }); diff --git a/test/domain/metadata-provision-sql-relation.spec.ts b/test/domain/metadata-provision-sql-relation.spec.ts index 9b7b794b..f2f0a233 100644 --- a/test/domain/metadata-provision-sql-relation.spec.ts +++ b/test/domain/metadata-provision-sql-relation.spec.ts @@ -61,8 +61,8 @@ describe('MetadataProvisionSqlService — relation upsert logic', () => { expect(methodRoutesId).toBe(2); expect(insertedRows).toHaveLength(2); - // Phase B: try to insert inverse of route_definition.availableMethods - // → should find method_definition.routesWithAvailable already exists + // Phase B: try to insert inverse of enfyra_route.availableMethods + // → should find enfyra_method.routesWithAvailable already exists const inverseId = await upsertRelation( 20, 'routesWithAvailable', @@ -108,15 +108,15 @@ describe('MetadataProvisionSqlService — relation upsert logic', () => { // Simulate inverse entries from both sides of M2M const inverses = [ { - tableName: 'method_definition', + tableName: 'enfyra_method', propertyName: 'routesWithAvailable', - owningTableName: 'route_definition', + owningTableName: 'enfyra_route', owningPropertyName: 'availableMethods', }, { - tableName: 'route_definition', + tableName: 'enfyra_route', propertyName: 'availableMethods', - owningTableName: 'method_definition', + owningTableName: 'enfyra_method', owningPropertyName: 'routesWithAvailable', }, ]; @@ -131,7 +131,7 @@ describe('MetadataProvisionSqlService — relation upsert logic', () => { } // First entry processed, second skipped (reverseKey matches first's inverseKey) - expect(processed).toEqual(['method_definition.routesWithAvailable']); + expect(processed).toEqual(['enfyra_method.routesWithAvailable']); expect(processed).toHaveLength(1); }); }); diff --git a/test/domain/oauth-exchange-code.service.spec.ts b/test/domain/oauth-exchange-code.service.spec.ts index de672262..1a54b663 100644 --- a/test/domain/oauth-exchange-code.service.spec.ts +++ b/test/domain/oauth-exchange-code.service.spec.ts @@ -90,7 +90,7 @@ describe('OAuthExchangeCodeService', () => { expect(result).toEqual({ deleted: 1 }); expect(queryBuilderService.delete).toHaveBeenCalledWith( - 'session_definition', + 'enfyra_session', 'session-1', ); expect(store.has('auth:oauth-exchange:pending:expired-code')).toBe(false); diff --git a/test/domain/provision-junction-swap.spec.ts b/test/domain/provision-junction-swap.spec.ts index 7330a3df..c5974c88 100644 --- a/test/domain/provision-junction-swap.spec.ts +++ b/test/domain/provision-junction-swap.spec.ts @@ -29,11 +29,11 @@ describe('Provision M2M inverse junction column swap', () => { }; } - it('should swap junction columns from owning side for pre_hook_definition.methods', () => { + it('should swap junction columns from owning side for enfyra_pre_hook.methods', () => { const owning = { - tableName: 'pre_hook_definition', + tableName: 'enfyra_pre_hook', propertyName: 'methods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', junctionSourceColumn: 'preHookDefinitionId', junctionTargetColumn: 'methodDefinitionId', }; @@ -42,18 +42,18 @@ describe('Provision M2M inverse junction column swap', () => { expect(inverse.junctionTargetColumn).toBe('preHookDefinitionId'); expect(inverse.junctionTableName).toBe( getJunctionTableName( - 'pre_hook_definition', + 'enfyra_pre_hook', 'methods', - 'method_definition', + 'enfyra_method', ), ); }); - it('should swap junction columns from owning side for post_hook_definition.methods', () => { + it('should swap junction columns from owning side for enfyra_post_hook.methods', () => { const owning = { - tableName: 'post_hook_definition', + tableName: 'enfyra_post_hook', propertyName: 'methods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', junctionSourceColumn: 'postHookDefinitionId', junctionTargetColumn: 'methodDefinitionId', }; @@ -62,11 +62,11 @@ describe('Provision M2M inverse junction column swap', () => { expect(inverse.junctionTargetColumn).toBe('postHookDefinitionId'); }); - it('should swap junction columns for route_definition.availableMethods', () => { + it('should swap junction columns for enfyra_route.availableMethods', () => { const owning = { - tableName: 'route_definition', + tableName: 'enfyra_route', propertyName: 'availableMethods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', junctionSourceColumn: 'routeDefinitionId', junctionTargetColumn: 'methodDefinitionId', }; @@ -75,11 +75,11 @@ describe('Provision M2M inverse junction column swap', () => { expect(inverse.junctionTargetColumn).toBe('routeDefinitionId'); }); - it('should swap junction columns for method_definition.routes', () => { + it('should swap junction columns for enfyra_method.routes', () => { const owning = { - tableName: 'method_definition', + tableName: 'enfyra_method', propertyName: 'routes', - targetTable: 'route_definition', + targetTable: 'enfyra_route', junctionSourceColumn: 'methodDefinitionId', junctionTargetColumn: 'routeDefinitionId', }; @@ -88,11 +88,11 @@ describe('Provision M2M inverse junction column swap', () => { expect(inverse.junctionTargetColumn).toBe('methodDefinitionId'); }); - it('should swap junction columns for route_permission_definition.allowedUsers', () => { + it('should swap junction columns for enfyra_route_permission.allowedUsers', () => { const owning = { - tableName: 'route_permission_definition', + tableName: 'enfyra_route_permission', propertyName: 'allowedUsers', - targetTable: 'user_definition', + targetTable: 'enfyra_user', junctionSourceColumn: 'routePermissionDefinitionId', junctionTargetColumn: 'userDefinitionId', }; @@ -114,9 +114,9 @@ describe('Provision M2M inverse junction column swap', () => { it('getJunctionColumnNames should produce consistent columns for owning side', () => { const { sourceColumn, targetColumn } = getJunctionColumnNames( - 'pre_hook_definition', + 'enfyra_pre_hook', 'methods', - 'method_definition', + 'enfyra_method', ); expect(sourceColumn).toBe('sourceId'); expect(targetColumn).toBe('targetId'); @@ -125,25 +125,25 @@ describe('Provision M2M inverse junction column swap', () => { it('should never produce mixed naming when swapping from owning with explicit columns', () => { const snakeCaseTables = [ { - tableName: 'pre_hook_definition', + tableName: 'enfyra_pre_hook', propertyName: 'methods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', junctionSourceColumn: 'preHookDefinitionId', junctionTargetColumn: 'methodDefinitionId', }, { - tableName: 'post_hook_definition', + tableName: 'enfyra_post_hook', propertyName: 'methods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', junctionSourceColumn: 'postHookDefinitionId', junctionTargetColumn: 'methodDefinitionId', }, { - tableName: 'guard_definition', + tableName: 'enfyra_guard', propertyName: 'methods', - targetTable: 'method_definition', - junctionSourceColumn: 'guard_definitionId', - junctionTargetColumn: 'method_definitionId', + targetTable: 'enfyra_method', + junctionSourceColumn: 'enfyra_guardId', + junctionTargetColumn: 'enfyra_methodId', }, ]; for (const owning of snakeCaseTables) { @@ -155,14 +155,14 @@ describe('Provision M2M inverse junction column swap', () => { it('self-referencing M2M should swap correctly', () => { const owning = { - tableName: 'user_definition', + tableName: 'enfyra_user', propertyName: 'friends', - targetTable: 'user_definition', - junctionSourceColumn: 'user_definitionId', + targetTable: 'enfyra_user', + junctionSourceColumn: 'enfyra_userId', junctionTargetColumn: 'friendsId', }; const inverse = buildInverseJunctionColumns(owning); expect(inverse.junctionSourceColumn).toBe('friendsId'); - expect(inverse.junctionTargetColumn).toBe('user_definitionId'); + expect(inverse.junctionTargetColumn).toBe('enfyra_userId'); }); }); diff --git a/test/domain/route-definition-processor.spec.ts b/test/domain/route-definition-processor.spec.ts index 526f0de9..1bd47bc9 100644 --- a/test/domain/route-definition-processor.spec.ts +++ b/test/domain/route-definition-processor.spec.ts @@ -6,7 +6,7 @@ function makeKnex(methodRows: any[] = []) { const inserts: any[] = []; const rawCalls: any[] = []; const knex = jest.fn((table: string) => { - if (table === 'method_definition') { + if (table === 'enfyra_method') { return { select: jest.fn().mockReturnThis(), whereIn: jest.fn((_field: string, values: string[]) => @@ -16,7 +16,7 @@ function makeKnex(methodRows: any[] = []) { ), }; } - if (table === 'relation_definition as r') { + if (table === 'enfyra_relation as r') { let propertyName = 'availableMethods'; const chain: any = { leftJoin: jest.fn(() => chain), @@ -89,11 +89,11 @@ describe('RouteDefinitionProcessor SQL relation writes', () => { }, ], queryBuilder, - 'route_definition', + 'enfyra_route', ); expect(queryBuilder.insert).toHaveBeenCalledWith( - 'route_definition', + 'enfyra_route', expect.not.objectContaining({ availableMethods: expect.anything(), skipRoleGuardMethods: expect.anything(), @@ -128,7 +128,7 @@ describe('RouteDefinitionProcessor SQL relation writes', () => { await processor.processWithQueryBuilder( [{ path: '/assets/:id', availableMethods: ['GET'] }], queryBuilder, - 'route_definition', + 'enfyra_route', ); expect(knex.rawCalls).toContainEqual({ diff --git a/test/domain/schema-healing.mongo.integration.spec.ts b/test/domain/schema-healing.mongo.integration.spec.ts index 0bfc16cb..ae0446e4 100644 --- a/test/domain/schema-healing.mongo.integration.spec.ts +++ b/test/domain/schema-healing.mongo.integration.spec.ts @@ -69,11 +69,11 @@ describe('SchemaHealingService Mongo integration', () => { const settingId = new ObjectId(); const columnId = new ObjectId(); - await db.collection('setting_definition').insertOne({ + await db.collection('enfyra_setting').insertOne({ _id: settingId, uniquesIndexesRepaired: true, }); - await db.collection('column_definition').insertOne({ + await db.collection('enfyra_column').insertOne({ _id: columnId, tableId: new ObjectId(), name: '_id', @@ -83,11 +83,11 @@ describe('SchemaHealingService Mongo integration', () => { isNullable: false, }); - const settingTable = makeTableMetadata('setting_definition'); - const columnTable = makeTableMetadata('column_definition'); + const settingTable = makeTableMetadata('enfyra_setting'); + const columnTable = makeTableMetadata('enfyra_column'); const tables = new Map([ - ['setting_definition', settingTable], - ['column_definition', columnTable], + ['enfyra_setting', settingTable], + ['enfyra_column', columnTable], ]); const queryBuilderService = new QueryBuilderService({ mongoService: { @@ -124,7 +124,7 @@ describe('SchemaHealingService Mongo integration', () => { await service.runIfNeeded(); const repairedColumn = await db - .collection('column_definition') + .collection('enfyra_column') .findOne({ _id: columnId }); expect(repairedColumn).toMatchObject({ @@ -141,20 +141,20 @@ describe('SchemaHealingService Mongo integration', () => { return; } - await db.collection('setting_definition').deleteMany({}); - await db.collection('table_definition').deleteMany({}); - await db.collection('relation_definition').deleteMany({}); + await db.collection('enfyra_setting').deleteMany({}); + await db.collection('enfyra_table').deleteMany({}); + await db.collection('enfyra_relation').deleteMany({}); const oldCollectionName = - 'route_definition_availableMethods_method_definition'; + 'enfyra_route_availableMethods_enfyra_method'; try { await db.collection(oldCollectionName).drop(); } catch {} const junction = getSqlJunctionPhysicalNames({ - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: 'availableMethods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); try { await db.collection(junction.junctionTableName).drop(); @@ -169,15 +169,15 @@ describe('SchemaHealingService Mongo integration', () => { const methodIdA = new ObjectId(); const methodIdB = new ObjectId(); - await db.collection('setting_definition').insertOne({ + await db.collection('enfyra_setting').insertOne({ _id: new ObjectId(), uniquesIndexesRepaired: true, }); - await db.collection('table_definition').insertMany([ - { _id: routeTableId, name: 'route_definition', isSystem: true }, - { _id: methodTableId, name: 'method_definition', isSystem: true }, + await db.collection('enfyra_table').insertMany([ + { _id: routeTableId, name: 'enfyra_route', isSystem: true }, + { _id: methodTableId, name: 'enfyra_method', isSystem: true }, ]); - await db.collection('relation_definition').insertMany([ + await db.collection('enfyra_relation').insertMany([ { _id: owningRelationId, sourceTable: routeTableId, @@ -185,8 +185,8 @@ describe('SchemaHealingService Mongo integration', () => { propertyName: 'availableMethods', type: 'many-to-many', junctionTableName: oldCollectionName, - junctionSourceColumn: 'route_definitionId', - junctionTargetColumn: 'method_definitionId', + junctionSourceColumn: 'enfyra_routeId', + junctionTargetColumn: 'enfyra_methodId', }, { _id: inverseRelationId, @@ -196,13 +196,13 @@ describe('SchemaHealingService Mongo integration', () => { type: 'many-to-many', mappedBy: owningRelationId, junctionTableName: oldCollectionName, - junctionSourceColumn: 'method_definitionId', - junctionTargetColumn: 'route_definitionId', + junctionSourceColumn: 'enfyra_methodId', + junctionTargetColumn: 'enfyra_routeId', }, ]); await db.collection(oldCollectionName).insertOne({ - route_definitionId: routeIdA, - method_definitionId: methodIdA, + enfyra_routeId: routeIdA, + enfyra_methodId: methodIdA, }); await db.collection(junction.junctionTableName).insertOne({ [junction.junctionSourceColumn]: routeIdB, @@ -210,9 +210,9 @@ describe('SchemaHealingService Mongo integration', () => { }); const tables = new Map([ - ['setting_definition', makeTableMetadata('setting_definition')], - ['table_definition', makeTableMetadata('table_definition')], - ['relation_definition', makeTableMetadata('relation_definition')], + ['enfyra_setting', makeTableMetadata('enfyra_setting')], + ['enfyra_table', makeTableMetadata('enfyra_table')], + ['enfyra_relation', makeTableMetadata('enfyra_relation')], ]); const queryBuilderService = new QueryBuilderService({ mongoService: { @@ -238,10 +238,10 @@ describe('SchemaHealingService Mongo integration', () => { await service.runIfNeeded(); const owningRelation = await db - .collection('relation_definition') + .collection('enfyra_relation') .findOne({ _id: owningRelationId }); const inverseRelation = await db - .collection('relation_definition') + .collection('enfyra_relation') .findOne({ _id: inverseRelationId }); const oldCollectionExists = await db .listCollections({ name: oldCollectionName }) @@ -282,20 +282,20 @@ describe('SchemaHealingService Mongo integration', () => { return; } - await db.collection('setting_definition').deleteMany({}); - await db.collection('table_definition').deleteMany({}); - await db.collection('relation_definition').deleteMany({}); + await db.collection('enfyra_setting').deleteMany({}); + await db.collection('enfyra_table').deleteMany({}); + await db.collection('enfyra_relation').deleteMany({}); const oldCollectionName = - 'route_definition_availableMethods_method_definition'; + 'enfyra_route_availableMethods_enfyra_method'; try { await db.collection(oldCollectionName).drop(); } catch {} const junction = getSqlJunctionPhysicalNames({ - sourceTable: 'route_definition', + sourceTable: 'enfyra_route', propertyName: 'availableMethods', - targetTable: 'method_definition', + targetTable: 'enfyra_method', }); try { await db.collection(junction.junctionTableName).drop(); @@ -308,15 +308,15 @@ describe('SchemaHealingService Mongo integration', () => { const routeId = new ObjectId(); const methodId = new ObjectId(); - await db.collection('setting_definition').insertOne({ + await db.collection('enfyra_setting').insertOne({ _id: new ObjectId(), uniquesIndexesRepaired: true, }); - await db.collection('table_definition').insertMany([ - { _id: routeTableId, name: 'route_definition', isSystem: true }, - { _id: methodTableId, name: 'method_definition', isSystem: true }, + await db.collection('enfyra_table').insertMany([ + { _id: routeTableId, name: 'enfyra_route', isSystem: true }, + { _id: methodTableId, name: 'enfyra_method', isSystem: true }, ]); - await db.collection('relation_definition').insertMany([ + await db.collection('enfyra_relation').insertMany([ { _id: owningRelationId, sourceTable: routeTableId, @@ -340,8 +340,8 @@ describe('SchemaHealingService Mongo integration', () => { }, ]); await db.collection(oldCollectionName).insertOne({ - route_definitionId: routeId, - method_definitionId: methodId, + enfyra_routeId: routeId, + enfyra_methodId: methodId, }); await db.collection(junction.junctionTableName).insertOne({ [junction.junctionSourceColumn]: routeId, @@ -349,9 +349,9 @@ describe('SchemaHealingService Mongo integration', () => { }); const tables = new Map([ - ['setting_definition', makeTableMetadata('setting_definition')], - ['table_definition', makeTableMetadata('table_definition')], - ['relation_definition', makeTableMetadata('relation_definition')], + ['enfyra_setting', makeTableMetadata('enfyra_setting')], + ['enfyra_table', makeTableMetadata('enfyra_table')], + ['enfyra_relation', makeTableMetadata('enfyra_relation')], ]); const queryBuilderService = new QueryBuilderService({ mongoService: { diff --git a/test/domain/schema-healing.service.spec.ts b/test/domain/schema-healing.service.spec.ts index 03c9a719..643d7853 100644 --- a/test/domain/schema-healing.service.spec.ts +++ b/test/domain/schema-healing.service.spec.ts @@ -122,7 +122,7 @@ describe('SchemaHealingService.runIfNeeded', () => { expect(update).toHaveBeenCalledTimes(1); const call = update.mock.calls[0]; - expect(call[0]).toBe('setting_definition'); + expect(call[0]).toBe('enfyra_setting'); expect(call[2]).toEqual({ uniquesIndexesRepaired: true }); }); @@ -150,7 +150,7 @@ describe('SchemaHealingService.runIfNeeded', () => { expect(update).toHaveBeenCalledTimes(2); const tableUpdate = update.mock.calls.find( - (c: any) => c[0] === 'table_definition', + (c: any) => c[0] === 'enfyra_table', ); expect(tableUpdate).toBeDefined(); expect(tableUpdate![2]).toEqual({ @@ -159,7 +159,7 @@ describe('SchemaHealingService.runIfNeeded', () => { }); const settingUpdate = update.mock.calls.find( - (c: any) => c[0] === 'setting_definition', + (c: any) => c[0] === 'enfyra_setting', ); expect(settingUpdate![2]).toEqual({ uniquesIndexesRepaired: true }); }); @@ -170,7 +170,7 @@ describe('SchemaHealingService.runIfNeeded', () => { const columnId = '65f000000000000000000001'; const qb = makeQb( (args: any) => - args.table === 'column_definition' + args.table === 'enfyra_column' ? { data: [ { @@ -202,7 +202,7 @@ describe('SchemaHealingService.runIfNeeded', () => { expect(update).toHaveBeenCalledTimes(1); expect(update).toHaveBeenCalledWith( - 'column_definition', + 'enfyra_column', { where: [{ field: '_id', operator: '=', value: columnId }] }, { name: '_id', type: 'ObjectId' }, ); @@ -214,7 +214,7 @@ describe('SchemaHealingService.runIfNeeded', () => { const cache = makeCache([ makeTable({ id: 1, - name: 'user_definition', + name: 'enfyra_user', isSystem: true, indexes: [['userId']], relations: [{ propertyName: 'user', foreignKeyColumn: 'userId' }], @@ -228,7 +228,7 @@ describe('SchemaHealingService.runIfNeeded', () => { await svc.runIfNeeded(); const tableCalls = update.mock.calls.filter( - (c: any) => c[0] === 'table_definition', + (c: any) => c[0] === 'enfyra_table', ); expect(tableCalls).toHaveLength(0); }); @@ -251,7 +251,7 @@ describe('SchemaHealingService.runIfNeeded', () => { await svc.runIfNeeded(); const tableCalls = update.mock.calls.filter( - (c: any) => c[0] === 'table_definition', + (c: any) => c[0] === 'enfyra_table', ); expect(tableCalls).toHaveLength(0); }); @@ -275,7 +275,7 @@ describe('SchemaHealingService.runIfNeeded', () => { await svc.runIfNeeded(); const tableUpdate = update.mock.calls.find( - (c: any) => c[0] === 'table_definition', + (c: any) => c[0] === 'enfyra_table', ); expect(tableUpdate![2].uniques).toEqual([['author']]); }); @@ -309,13 +309,13 @@ describe('SchemaHealingService.runIfNeeded', () => { }, ]; const knex: any = vi.fn((tableName: string) => { - if (tableName === 'relation_definition as r') { + if (tableName === 'enfyra_relation as r') { return { leftJoin: vi.fn().mockReturnThis(), select: vi.fn().mockResolvedValue(rows), }; } - if (tableName === 'relation_definition') { + if (tableName === 'enfyra_relation') { return { where: vi.fn().mockReturnValue(relationTable()) }; } throw new Error(`Unexpected table ${tableName}`); @@ -410,11 +410,11 @@ describe('SchemaHealingService.runIfNeeded', () => { }, ]; const collections: Record = { - relation_definition: { + enfyra_relation: { find: vi.fn().mockReturnValue({ toArray: vi.fn().mockResolvedValue(relations) }), updateOne, }, - table_definition: { + enfyra_table: { find: vi.fn().mockReturnValue({ toArray: vi.fn().mockResolvedValue(tables) }), }, bad_junction: { @@ -439,7 +439,7 @@ describe('SchemaHealingService.runIfNeeded', () => { }; const qb = { find: vi.fn((args: any) => - args.table === 'column_definition' + args.table === 'enfyra_column' ? { data: [] } : { data: [{ _id: 'setting-id', isInit: true, uniquesIndexesRepaired: true }] }, ), diff --git a/test/domain/script-code.util.spec.ts b/test/domain/script-code.util.spec.ts index 72e5364e..b30662a3 100644 --- a/test/domain/script-code.util.spec.ts +++ b/test/domain/script-code.util.spec.ts @@ -20,7 +20,7 @@ describe('script-code util', () => { }); it('defaults script records to TypeScript and removes legacy fields', () => { - const record = normalizeScriptRecord('route_handler_definition', { + const record = normalizeScriptRecord('enfyra_route_handler', { logic: 'return await @REPOS.main.find();', }); @@ -83,7 +83,7 @@ describe('script-code util', () => { const patch = { code: 'const value: string = @BODY.name; return value;', }; - const normalized = normalizeScriptPatch('pre_hook_definition', patch); + const normalized = normalizeScriptPatch('enfyra_pre_hook', patch); expect(patch).toEqual({ code: 'const value: string = @BODY.name; return value;', @@ -100,7 +100,7 @@ describe('script-code util', () => { it('recompiles from existing source when only scriptLanguage is patched', () => { const normalized = normalizeScriptPatch( - 'route_handler_definition', + 'enfyra_route_handler', { scriptLanguage: 'javascript' }, { sourceCode: 'return @BODY.name;', @@ -116,7 +116,7 @@ describe('script-code util', () => { it('clears compiled code when source is explicitly cleared', () => { const normalized = normalizeScriptPatch( - 'post_hook_definition', + 'enfyra_post_hook', { sourceCode: null }, { sourceCode: 'return @BODY.name;', diff --git a/test/domain/snapshot-meta.util.spec.ts b/test/domain/snapshot-meta.util.spec.ts index 877a5284..b29bd0d7 100644 --- a/test/domain/snapshot-meta.util.spec.ts +++ b/test/domain/snapshot-meta.util.spec.ts @@ -7,26 +7,26 @@ import { describe('snapshot-meta.util', () => { it('getLookupKey uses LOOKUP_KEY_MAP for known system tables', () => { - expect(getLookupKey('route_definition')).toBe('path'); - expect(getLookupKey('method_definition')).toBe('name'); - expect(getLookupKey('user_definition')).toBe('email'); + expect(getLookupKey('enfyra_route')).toBe('path'); + expect(getLookupKey('enfyra_method')).toBe('name'); + expect(getLookupKey('enfyra_user')).toBe('email'); }); - it('getManyToOneRelations returns propertyName, targetTable, lookupKey for column_definition', () => { - const rels = getManyToOneRelations('column_definition'); + it('getManyToOneRelations returns propertyName, targetTable, lookupKey for enfyra_column', () => { + const rels = getManyToOneRelations('enfyra_column'); const tableRel = rels.find((r) => r.propertyName === 'table'); expect(tableRel).toBeDefined(); - expect(tableRel!.targetTable).toBe('table_definition'); + expect(tableRel!.targetTable).toBe('enfyra_table'); expect(tableRel!.lookupKey).toBe('name'); }); it('getUniqueFields matches snapshot uniques when present', () => { - const u = getUniqueFields('route_definition'); + const u = getUniqueFields('enfyra_route'); expect(Array.isArray(u)).toBe(true); }); it('getScalarColumns skips id and timestamp fields', () => { - const cols = getScalarColumns('table_definition'); + const cols = getScalarColumns('enfyra_table'); expect(cols).not.toContain('id'); expect(cols).not.toContain('createdAt'); expect(cols).not.toContain('updatedAt'); diff --git a/test/engines/cache/script-repair.spec.ts b/test/engines/cache/script-repair.spec.ts index 9415a114..97a8a4ef 100644 --- a/test/engines/cache/script-repair.spec.ts +++ b/test/engines/cache/script-repair.spec.ts @@ -11,7 +11,7 @@ describe('script cache repair', () => { }); const code = await (service as any).resolveAndRepairScript( - 'route_handler_definition', + 'enfyra_route_handler', { id: 10, scriptLanguage: 'typescript', @@ -22,7 +22,7 @@ describe('script cache repair', () => { expect(code).toContain('const value = $ctx.$body.name;'); expect(update).toHaveBeenCalledTimes(1); - expect(update).toHaveBeenCalledWith('route_handler_definition', 10, { + expect(update).toHaveBeenCalledWith('enfyra_route_handler', 10, { compiledCode: code, }); }); @@ -36,7 +36,7 @@ describe('script cache repair', () => { }); const code = await (service as any).resolveAndRepairScript( - 'route_handler_definition', + 'enfyra_route_handler', { id: 10, scriptLanguage: 'typescript', diff --git a/test/engines/script-persistence-contract.spec.ts b/test/engines/script-persistence-contract.spec.ts index 0e02b7d1..1e448b35 100644 --- a/test/engines/script-persistence-contract.spec.ts +++ b/test/engines/script-persistence-contract.spec.ts @@ -26,7 +26,7 @@ describe('script persistence contract', () => { it('marks compiledCode as an internal generated field only on script tables', () => { expect( isGeneratedScriptPersistenceField( - 'route_handler_definition', + 'enfyra_route_handler', 'compiledCode', ), ).toBe(true); @@ -34,17 +34,17 @@ describe('script persistence contract', () => { isGeneratedScriptPersistenceField('article_definition', 'compiledCode'), ).toBe(false); expect( - isGeneratedScriptPersistenceField('route_handler_definition', 'createdAt'), + isGeneratedScriptPersistenceField('enfyra_route_handler', 'createdAt'), ).toBe(false); }); it('keeps generated compiledCode while stripping other non-updatable fields', async () => { const stripper = new FieldStripper({ - getMetadata: async () => metadataFor('route_handler_definition'), + getMetadata: async () => metadataFor('enfyra_route_handler'), } as any); const stripped = await stripper.stripNonUpdatableFields( - 'route_handler_definition', + 'enfyra_route_handler', { id: 10, sourceCode: 'return @BODY.name;', diff --git a/test/flow-engine.e2e.js b/test/flow-engine.e2e.js index c4278149..76a5ed4d 100644 --- a/test/flow-engine.e2e.js +++ b/test/flow-engine.e2e.js @@ -189,7 +189,7 @@ async function testBasicFlow() { id: 1, name: 'test-basic', steps: [ { key: 'step1', stepOrder: 1, type: 'log', config: { message: 'Hello' }, timeout: 5000, onError: 'stop', isEnabled: true }, - { key: 'step2', stepOrder: 2, type: 'query', config: { table: 'user_definition', filter: { status: 'active' } }, timeout: 5000, onError: 'stop', isEnabled: true }, + { key: 'step2', stepOrder: 2, type: 'query', config: { table: 'enfyra_user', filter: { status: 'active' } }, timeout: 5000, onError: 'stop', isEnabled: true }, { key: 'step3', stepOrder: 3, type: 'log', config: { message: 'Done' }, timeout: 5000, onError: 'stop', isEnabled: true }, ], }; @@ -205,7 +205,7 @@ async function testDataChain() { const flow = { id: 2, name: 'test-chain', steps: [ - { key: 'fetch_user', stepOrder: 1, type: 'query', config: { table: 'user_definition' }, timeout: 5000, onError: 'stop', isEnabled: true }, + { key: 'fetch_user', stepOrder: 1, type: 'query', config: { table: 'enfyra_user' }, timeout: 5000, onError: 'stop', isEnabled: true }, { key: 'check_result', stepOrder: 2, type: 'script', config: { code: 'return { hasUser: $ctx.$flow.fetch_user?.data?.length > 0, lastId: $ctx.$flow.$last?.data?.[0]?.id }' }, timeout: 5000, onError: 'stop', isEnabled: true }, ], }; @@ -470,7 +470,7 @@ async function testScriptAccessesRepos() { const flow = { id: 18, name: 'test-script-repos', steps: [ - { key: 'query', stepOrder: 1, type: 'script', config: { code: 'return await $ctx.$repos.user_definition.find({ filter: { role: "admin" }, limit: 5 })' }, timeout: 5000, onError: 'stop', isEnabled: true }, + { key: 'query', stepOrder: 1, type: 'script', config: { code: 'return await $ctx.$repos.enfyra_user.find({ filter: { role: "admin" }, limit: 5 })' }, timeout: 5000, onError: 'stop', isEnabled: true }, { key: 'create', stepOrder: 2, type: 'script', config: { code: 'return await $ctx.$repos.order.create({ data: { total: $ctx.$flow.query.data[0].id * 100 } })' }, timeout: 5000, onError: 'stop', isEnabled: true }, ], }; @@ -515,7 +515,7 @@ async function testMultiStepDataPipeline() { const flow = { id: 21, name: 'test-pipeline', steps: [ - { key: 'users', stepOrder: 1, type: 'query', config: { table: 'user_definition', limit: 10 }, timeout: 5000, onError: 'stop', isEnabled: true }, + { key: 'users', stepOrder: 1, type: 'query', config: { table: 'enfyra_user', limit: 10 }, timeout: 5000, onError: 'stop', isEnabled: true }, { key: 'has_users', stepOrder: 2, type: 'condition', config: { code: 'return $ctx.$flow.users?.data?.length > 0' }, timeout: 5000, onError: 'stop', isEnabled: true }, { key: 'order', stepOrder: 3, type: 'create', config: { table: 'order', data: { userId: 1, status: 'new' } }, timeout: 5000, onError: 'stop', isEnabled: true }, { key: 'done', stepOrder: 4, type: 'log', config: { message: 'pipeline complete' }, timeout: 5000, onError: 'stop', isEnabled: true }, diff --git a/test/http/admin-test-run.spec.ts b/test/http/admin-test-run.spec.ts index de775c72..54c4287b 100644 --- a/test/http/admin-test-run.spec.ts +++ b/test/http/admin-test-run.spec.ts @@ -90,7 +90,7 @@ describe('admin test run', () => { const result = await postTest({ kind: 'script', - tableName: 'route_handler_definition', + tableName: 'enfyra_route_handler', routeId: 1, method: 'POST', body: { value: 42 }, diff --git a/test/http/assets-cache.e2e.spec.ts b/test/http/assets-cache.e2e.spec.ts index 14d941ef..874c59cb 100644 --- a/test/http/assets-cache.e2e.spec.ts +++ b/test/http/assets-cache.e2e.spec.ts @@ -37,14 +37,14 @@ function makeQueryBuilder(state: { files: any[]; permissions: any[] }) { return { getPkField: vi.fn(() => 'id'), find: vi.fn(async (args: any) => { - if (args.table === 'file_definition') { + if (args.table === 'enfyra_file') { const id = args.filter?.id?._eq; return { data: state.files.filter((file) => String(file.id) === String(id)), }; } - if (args.table === 'file_permission_definition') { + if (args.table === 'enfyra_file_permission') { const ids = args.filter?.id?._in; if (ids) { const set = new Set(ids.map(String)); @@ -69,7 +69,7 @@ function makeQueryBuilder(state: { files: any[]; permissions: any[] }) { }; } - if (args.table === 'role_definition') { + if (args.table === 'enfyra_role') { return { data: [] }; } @@ -183,7 +183,7 @@ describe('assets route cache e2e', () => { expect(first.status).toBe(200); expect(second.status).toBe(200); expect(await second.text()).toBe('asset-body'); - expect(countFinds(queryBuilderService, 'file_definition')).toBe(1); + expect(countFinds(queryBuilderService, 'enfyra_file')).toBe(1); }); it('streams byte ranges for video playback', async () => { @@ -251,10 +251,10 @@ describe('assets route cache e2e', () => { await service.streamFile(req, makeResponse()); await service.streamFile(req, makeResponse()); - expect(countFinds(queryBuilderService, 'file_definition')).toBe(1); + expect(countFinds(queryBuilderService, 'enfyra_file')).toBe(1); await eventEmitter.emitAsync(CACHE_EVENTS.INVALIDATE, { - table: 'file_definition', + table: 'enfyra_file', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -262,7 +262,7 @@ describe('assets route cache e2e', () => { }); await service.streamFile(req, makeResponse()); - expect(countFinds(queryBuilderService, 'file_definition')).toBe(2); + expect(countFinds(queryBuilderService, 'enfyra_file')).toBe(2); }); it('caches private file permissions by file and invalidates by changed permission id', async () => { @@ -279,13 +279,13 @@ describe('assets route cache e2e', () => { await service.streamFile(req, makeResponse()); await service.streamFile(req, makeResponse()); - expect(countFinds(queryBuilderService, 'file_definition')).toBe(1); - expect(countFinds(queryBuilderService, 'file_permission_definition')).toBe( + expect(countFinds(queryBuilderService, 'enfyra_file')).toBe(1); + expect(countFinds(queryBuilderService, 'enfyra_file_permission')).toBe( 1, ); await eventEmitter.emitAsync(CACHE_EVENTS.INVALIDATE, { - table: 'file_permission_definition', + table: 'enfyra_file_permission', action: 'reload', timestamp: Date.now(), scope: 'partial', @@ -293,8 +293,8 @@ describe('assets route cache e2e', () => { }); await service.streamFile(req, makeResponse()); - expect(countFinds(queryBuilderService, 'file_definition')).toBe(1); - expect(countFinds(queryBuilderService, 'file_permission_definition')).toBe( + expect(countFinds(queryBuilderService, 'enfyra_file')).toBe(1); + expect(countFinds(queryBuilderService, 'enfyra_file_permission')).toBe( 3, ); }); @@ -313,8 +313,8 @@ describe('assets route cache e2e', () => { await service.streamFile(req, makeResponse()); - expect(countFinds(queryBuilderService, 'file_definition')).toBe(1); - expect(countFinds(queryBuilderService, 'file_permission_definition')).toBe( + expect(countFinds(queryBuilderService, 'enfyra_file')).toBe(1); + expect(countFinds(queryBuilderService, 'enfyra_file_permission')).toBe( 0, ); }); diff --git a/test/http/metadata-access.spec.ts b/test/http/metadata-access.spec.ts index 21a75060..ea47781d 100644 --- a/test/http/metadata-access.spec.ts +++ b/test/http/metadata-access.spec.ts @@ -115,7 +115,7 @@ describe('metadata access projection', () => { const meta = metadata([ table('public_post_definition', [col('title')]), table('private_post_definition', [col('title')]), - table('user_definition', [col('email')]), + table('enfyra_user', [col('email')]), ]); const data = await projectMetadataForUser({ @@ -150,7 +150,7 @@ describe('metadata access projection', () => { const meta = metadata([ table('post_definition'), table('secret_definition'), - table('user_definition'), + table('enfyra_user'), ]); const user = { id: 10, role: { id: 2 } }; @@ -180,7 +180,7 @@ describe('metadata access projection', () => { expect([...names].sort()).toEqual([ 'post_definition', - 'user_definition', + 'enfyra_user', ]); }); diff --git a/test/knex/pg-enum-check-constraint.spec.ts b/test/knex/pg-enum-check-constraint.spec.ts index 3557e47d..9b77ef0b 100644 --- a/test/knex/pg-enum-check-constraint.spec.ts +++ b/test/knex/pg-enum-check-constraint.spec.ts @@ -77,7 +77,7 @@ describe('Postgres ENUM conversion — CHECK constraint handling', () => { it('should DROP CHECK constraint before ALTER TYPE when column is text+CHECK (Knex default)', async () => { await simulateEnumConversion( - 'column_definition', + 'enfyra_column', 'type', ['int', 'varchar', 'text'], 'text', @@ -86,7 +86,7 @@ describe('Postgres ENUM conversion — CHECK constraint handling', () => { const dropCheck = executedSql.find((s) => s.includes('DROP CONSTRAINT')); const alterType = executedSql.find((s) => - s.includes('TYPE "column_definition_type_enum"'), + s.includes('TYPE "enfyra_column_type_enum"'), ); expect(dropCheck).toBeDefined(); @@ -100,10 +100,10 @@ describe('Postgres ENUM conversion — CHECK constraint handling', () => { it('should skip conversion when column is already native ENUM', async () => { const result = await simulateEnumConversion( - 'column_definition', + 'enfyra_column', 'type', ['int', 'varchar'], - 'column_definition_type_enum', + 'enfyra_column_type_enum', false, ); diff --git a/test/knex/relation-changes-inverse-junction.spec.ts b/test/knex/relation-changes-inverse-junction.spec.ts index ad69d156..2e8dd3fc 100644 --- a/test/knex/relation-changes-inverse-junction.spec.ts +++ b/test/knex/relation-changes-inverse-junction.spec.ts @@ -3,14 +3,14 @@ import { analyzeRelationChanges } from '../../src/engines/knex/utils/migration/r function createKnexMock(relationRows: any[] = []) { return vi.fn((table: string) => { - if (table === 'table_definition') { + if (table === 'enfyra_table') { return { select: vi.fn().mockReturnThis(), whereIn: vi.fn().mockResolvedValue([{ id: 2, name: 'students' }]), }; } - if (table === 'relation_definition') { + if (table === 'enfyra_relation') { return { where: vi.fn().mockReturnThis(), select: vi.fn().mockResolvedValue(relationRows), diff --git a/test/knex/sql-physical-schema-contract.spec.ts b/test/knex/sql-physical-schema-contract.spec.ts index 4455171e..bcb09be0 100644 --- a/test/knex/sql-physical-schema-contract.spec.ts +++ b/test/knex/sql-physical-schema-contract.spec.ts @@ -9,7 +9,7 @@ import { describe('SQL physical schema contract', () => { const table = { - name: 'route_definition', + name: 'enfyra_route', columns: [ { name: 'id', type: 'int', isPrimary: true }, { name: 'mainTableId', type: 'int' }, @@ -19,7 +19,7 @@ describe('SQL physical schema contract', () => { { propertyName: 'mainTable', type: 'many-to-one', - targetTable: 'table_definition', + targetTable: 'enfyra_table', onDelete: 'CASCADE', isNullable: false, }, @@ -37,13 +37,13 @@ describe('SQL physical schema contract', () => { }); it('resolves relation FKs and unique groups to physical columns', () => { - expect(buildSqlForeignKeyContracts('route_definition', table.relations)).toEqual([ + expect(buildSqlForeignKeyContracts('enfyra_route', table.relations)).toEqual([ { - tableName: 'route_definition', + tableName: 'enfyra_route', propertyName: 'mainTable', columnName: 'mainTableId', - constraintName: 'route_definition_mainTableId_foreign', - targetTable: 'table_definition', + constraintName: 'enfyra_route_mainTableId_foreign', + targetTable: 'enfyra_table', targetColumn: 'id', onDelete: 'CASCADE', onUpdate: 'CASCADE', @@ -76,9 +76,9 @@ describe('SQL physical schema contract', () => { }, ]); - expect(buildSqlUniqueContracts('route_definition', table)).toEqual([ + expect(buildSqlUniqueContracts('enfyra_route', table)).toEqual([ { - name: 'uq_route_definition_mainTableId', + name: 'uq_enfyra_route_mainTableId', logicalColumns: ['mainTable'], physicalColumns: ['mainTableId'], }, @@ -118,27 +118,27 @@ describe('SQL physical schema contract', () => { }); it('appends id tie-breakers to non-unique physical indexes once', () => { - expect(buildSqlIndexContracts('route_definition', table)).toEqual([ + expect(buildSqlIndexContracts('enfyra_route', table)).toEqual([ { - name: 'idx_route_definition_mainTableId', + name: 'idx_enfyra_route_mainTableId', logicalColumns: ['mainTable'], physicalColumns: ['mainTableId', 'id'], source: 'metadata', }, { - name: 'idx_route_definition_scheduledAt', + name: 'idx_enfyra_route_scheduledAt', logicalColumns: ['scheduledAt'], physicalColumns: ['scheduledAt', 'id'], source: 'metadata', }, { - name: 'idx_route_definition_createdAt', + name: 'idx_enfyra_route_createdAt', logicalColumns: ['createdAt'], physicalColumns: ['createdAt', 'id'], source: 'system-timestamp', }, { - name: 'idx_route_definition_updatedAt', + name: 'idx_enfyra_route_updatedAt', logicalColumns: ['updatedAt'], physicalColumns: ['updatedAt', 'id'], source: 'system-timestamp', @@ -148,19 +148,19 @@ describe('SQL physical schema contract', () => { it('centralizes junction table names, indexes, and FK actions', () => { const contract = buildSqlJunctionTableContract({ - tableName: 'route_definition_availableMethods_method_definition', - sourceTable: 'route_definition', - targetTable: 'method_definition', + tableName: 'enfyra_route_availableMethods_enfyra_method', + sourceTable: 'enfyra_route', + targetTable: 'enfyra_method', sourceColumn: 'routeDefinitionId', targetColumn: 'methodDefinitionId', sourcePropertyName: 'availableMethods', }); expect(contract).toMatchObject({ - primaryKeyName: 'route_definition_availableMethods_method_definition_pk', - sourceIndexName: 'idx_route_definition_availableMethods_src', - targetIndexName: 'idx_route_definition_availableMethods_tgt', - reverseIndexName: 'idx_route_definition_availableMethods_rev', + primaryKeyName: 'enfyra_route_availableMethods_enfyra_method_pk', + sourceIndexName: 'idx_enfyra_route_availableMethods_src', + targetIndexName: 'idx_enfyra_route_availableMethods_tgt', + reverseIndexName: 'idx_enfyra_route_availableMethods_rev', onDelete: 'CASCADE', onUpdate: 'CASCADE', }); diff --git a/test/metadata-reload.e2e.js b/test/metadata-reload.e2e.js index 795850b5..6a2aebef 100644 --- a/test/metadata-reload.e2e.js +++ b/test/metadata-reload.e2e.js @@ -29,7 +29,7 @@ class MockQueryBuilder { async select({ tableName }) { this.queryCount++; - if (tableName === 'table_definition') { + if (tableName === 'enfyra_table') { await sleep(this.loadDelay); return { data: [ @@ -38,10 +38,10 @@ class MockQueryBuilder { ], }; } - if (tableName === 'column_definition') { + if (tableName === 'enfyra_column') { return { data: [] }; } - if (tableName === 'relation_definition') { + if (tableName === 'enfyra_relation') { return { data: [] }; } return { data: [] }; diff --git a/test/modules/dynamic-repository-field-selection.spec.ts b/test/modules/dynamic-repository-field-selection.spec.ts index e8f24a83..57b51b1d 100644 --- a/test/modules/dynamic-repository-field-selection.spec.ts +++ b/test/modules/dynamic-repository-field-selection.spec.ts @@ -5,9 +5,9 @@ import { normalizeDynamicReadProjection } from '../../src/modules/dynamic-api/ut const metadata = { tables: new Map([ [ - 'flow_step_definition', + 'enfyra_flow_step', { - name: 'flow_step_definition', + name: 'enfyra_flow_step', columns: [ { name: 'id', isPrimary: true }, { name: 'key' }, @@ -19,20 +19,20 @@ const metadata = { { propertyName: 'owner', type: 'many-to-one', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', }, { propertyName: 'flow', type: 'many-to-one', - targetTableName: 'flow_definition', + targetTableName: 'enfyra_flow', }, ], }, ], [ - 'user_definition', + 'enfyra_user', { - name: 'user_definition', + name: 'enfyra_user', columns: [ { name: 'id', isPrimary: true }, { name: 'email' }, @@ -43,15 +43,15 @@ const metadata = { { propertyName: 'role', type: 'many-to-one', - targetTableName: 'role_definition', + targetTableName: 'enfyra_role', }, ], }, ], [ - 'role_definition', + 'enfyra_role', { - name: 'role_definition', + name: 'enfyra_role', columns: [ { name: 'id', isPrimary: true }, { name: 'name' }, @@ -61,9 +61,9 @@ const metadata = { }, ], [ - 'flow_definition', + 'enfyra_flow', { - name: 'flow_definition', + name: 'enfyra_flow', columns: [ { name: 'id', isPrimary: true }, { name: 'name' }, @@ -90,7 +90,7 @@ const metadata = { function makeRepo({ fields, deep, - tableName = 'flow_step_definition', + tableName = 'enfyra_flow_step', }: { fields?: string | string[]; deep?: Record; @@ -125,7 +125,7 @@ describe('dynamic read field selection', () => { it('keeps normal include mode unchanged', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: 'id,sourceCode,owner.email', deep: undefined, metadata, @@ -139,7 +139,7 @@ describe('dynamic read field selection', () => { it('treats any negative token as exclude mode and ignores positive tokens', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: 'id,-compiledCode', metadata, }).fields, @@ -149,7 +149,7 @@ describe('dynamic read field selection', () => { it('supports root excludes without dropping the primary key unless requested', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-compiledCode,-key', metadata, }).fields, @@ -159,7 +159,7 @@ describe('dynamic read field selection', () => { it('allows excluding the primary key explicitly', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-id,-compiledCode', metadata, }).fields, @@ -169,7 +169,7 @@ describe('dynamic read field selection', () => { it('turns dotted relation excludes into deep all-minus projections', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-owner.avatar', metadata, }), @@ -194,7 +194,7 @@ describe('dynamic read field selection', () => { it('lets top-level dotted excludes win over nested include fields', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-owner.avatar', deep: { owner: { fields: 'avatar' } }, metadata, @@ -209,7 +209,7 @@ describe('dynamic read field selection', () => { it('supports nested deep exclude mode independently', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: 'id,owner', deep: { owner: { @@ -239,7 +239,7 @@ describe('dynamic read field selection', () => { it('removes deep entries when the owning relation is excluded', () => { expect( normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-owner', deep: { owner: { fields: '-avatar' } }, metadata, @@ -263,7 +263,7 @@ describe('dynamic read field selection', () => { it('rejects unknown root fields in exclude mode', () => { expect(() => normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-compildCode', metadata, }), @@ -273,7 +273,7 @@ describe('dynamic read field selection', () => { it('rejects unknown dotted relations in exclude mode', () => { expect(() => normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-missing.avatar', metadata, }), @@ -283,7 +283,7 @@ describe('dynamic read field selection', () => { it('rejects invalid wildcard exclusion', () => { expect(() => normalizeDynamicReadProjection({ - tableName: 'flow_step_definition', + tableName: 'enfyra_flow_step', fields: '-*', metadata, }), @@ -299,7 +299,7 @@ describe('dynamic read field selection', () => { expect(queryBuilderService.find).toHaveBeenCalledWith( expect.objectContaining({ - table: 'flow_step_definition', + table: 'enfyra_flow_step', fields: ['id', 'key', 'sourceCode', 'scriptLanguage', 'owner', 'flow'], }), ); diff --git a/test/modules/dynamic-repository-route-methods.spec.ts b/test/modules/dynamic-repository-route-methods.spec.ts index f37abb2c..7025c9be 100644 --- a/test/modules/dynamic-repository-route-methods.spec.ts +++ b/test/modules/dynamic-repository-route-methods.spec.ts @@ -8,7 +8,7 @@ function makeRepo(overrides: Partial { expect(queryBuilderService.find).toHaveBeenCalledWith( expect.objectContaining({ - table: 'route_definition', + table: 'enfyra_route', filter, fields: ['_id'], limit: 1, diff --git a/test/modules/me-service.spec.ts b/test/modules/me-service.spec.ts index 2b10b566..655bc669 100644 --- a/test/modules/me-service.spec.ts +++ b/test/modules/me-service.spec.ts @@ -12,9 +12,9 @@ describe('MeService', () => { }; const repoRegistryService = { createReposProxy: vi.fn(() => ({ - user_definition: userRepo, + enfyra_user: userRepo, secure: { - user_definition: { + enfyra_user: { find: vi.fn(async () => { throw new Error('secure repo should not be used for /me'); }), diff --git a/test/mongo-saga/migration-compensation-mongo.spec.ts b/test/mongo-saga/migration-compensation-mongo.spec.ts index 3bcab32f..3e92a9f1 100644 --- a/test/mongo-saga/migration-compensation-mongo.spec.ts +++ b/test/mongo-saga/migration-compensation-mongo.spec.ts @@ -9,7 +9,7 @@ describe('MongoDB Migration Compensation', () => { const mockDb = { collection: jest.fn((name: string) => { - if (name === 'table_definition') { + if (name === 'enfyra_table') { return { findOne: jest.fn().mockResolvedValue({ _id: tableId, @@ -18,7 +18,7 @@ describe('MongoDB Migration Compensation', () => { }), }; } - if (name === 'column_definition') { + if (name === 'enfyra_column') { return { find: jest.fn().mockReturnValue({ toArray: jest.fn().mockResolvedValue([ @@ -38,7 +38,7 @@ describe('MongoDB Migration Compensation', () => { }), }; } - if (name === 'relation_definition') { + if (name === 'enfyra_relation') { const findMock = jest.fn(); findMock.mockReturnValue({ toArray: jest.fn().mockResolvedValue([ @@ -69,7 +69,7 @@ describe('MongoDB Migration Compensation', () => { // Simulate the capture logic const db = mockDb; const sourceRelations = await db - .collection('relation_definition') + .collection('enfyra_relation') .find({ sourceTable: tableId }) .toArray(); const owningRelIds = sourceRelations @@ -155,22 +155,22 @@ describe('MongoDB Migration Compensation', () => { // Step 1: Restore table await db - .collection('table_definition') + .collection('enfyra_table') .replaceOne({ _id: oid }, snapshot.table, { upsert: true }); // Step 2: Restore columns - await db.collection('column_definition').deleteMany({ table: oid }); - await db.collection('column_definition').insertMany(snapshot.columns); + await db.collection('enfyra_column').deleteMany({ table: oid }); + await db.collection('enfyra_column').insertMany(snapshot.columns); // Step 3: Restore relations await db - .collection('relation_definition') + .collection('enfyra_relation') .deleteMany({ sourceTable: oid }); - await db.collection('relation_definition').insertMany(snapshot.relations); + await db.collection('enfyra_relation').insertMany(snapshot.relations); - expect(operations).toContain('table_definition:replaceOne'); - expect(operations).toContain('column_definition:deleteMany'); - expect(operations).toContain('column_definition:insertMany'); - expect(operations).toContain('relation_definition:deleteMany'); - expect(operations).toContain('relation_definition:insertMany'); + expect(operations).toContain('enfyra_table:replaceOne'); + expect(operations).toContain('enfyra_column:deleteMany'); + expect(operations).toContain('enfyra_column:insertMany'); + expect(operations).toContain('enfyra_relation:deleteMany'); + expect(operations).toContain('enfyra_relation:insertMany'); }); }); diff --git a/test/mongo/physical-migration.service.spec.ts b/test/mongo/physical-migration.service.spec.ts index 3d373b63..518f1d0e 100644 --- a/test/mongo/physical-migration.service.spec.ts +++ b/test/mongo/physical-migration.service.spec.ts @@ -123,7 +123,7 @@ describe('MongoPhysicalMigrationService', () => { ); const migration = await db - .collection('schema_physical_migration_definition') + .collection('enfyra_schema_physical_migration') .findOne({ tableName: 'posts', oldName: 'old_title', newName: 'title' }); expect(migration?.status).toBe('pending'); @@ -137,19 +137,19 @@ describe('MongoPhysicalMigrationService', () => { expect(rows.some((row) => row.old_title !== undefined)).toBe(false); const completed = await db - .collection('schema_physical_migration_definition') + .collection('enfyra_schema_physical_migration') .findOne({ migrationId: migration?.migrationId }); expect(completed?.status).toBe('completed'); }); runOrSkip('query executor reads renamed field through pending migration fallback', async () => { await db.collection('posts').deleteMany({}); - await db.collection('schema_physical_migration_definition').deleteMany({}); + await db.collection('enfyra_schema_physical_migration').deleteMany({}); await db.collection('posts').insertOne({ _id: new ObjectId(), old_title: 'visible through title', }); - await db.collection('schema_physical_migration_definition').insertOne({ + await db.collection('enfyra_schema_physical_migration').insertOne({ migrationId: 'pending-read-fallback', kind: 'field_rename', tableName: 'posts', @@ -188,7 +188,7 @@ describe('MongoPhysicalMigrationService', () => { runOrSkip('batch relation fetch reads renamed child fields through fallback', async () => { await db.collection('posts').deleteMany({}); await db.collection('authors').deleteMany({}); - await db.collection('schema_physical_migration_definition').deleteMany({}); + await db.collection('enfyra_schema_physical_migration').deleteMany({}); const authorId = new ObjectId(); await db.collection('authors').insertOne({ _id: authorId, @@ -198,7 +198,7 @@ describe('MongoPhysicalMigrationService', () => { _id: new ObjectId(), author: authorId, }); - await db.collection('schema_physical_migration_definition').insertOne({ + await db.collection('enfyra_schema_physical_migration').insertOne({ migrationId: 'pending-child-read-fallback', kind: 'field_rename', tableName: 'authors', diff --git a/test/mongo/strip-unknown-columns.spec.ts b/test/mongo/strip-unknown-columns.spec.ts index 788eae2c..93a3b366 100644 --- a/test/mongo/strip-unknown-columns.spec.ts +++ b/test/mongo/strip-unknown-columns.spec.ts @@ -23,18 +23,18 @@ describe('MongoService.stripUnknownColumns', () => { { propertyName: 'parent', type: 'many-to-one', - targetTable: 'menu_definition', + targetTable: 'enfyra_menu', }, { propertyName: 'children', type: 'one-to-many', - targetTable: 'menu_definition', + targetTable: 'enfyra_menu', }, ], }); await expect( - service.stripUnknownColumns('menu_definition', { + service.stripUnknownColumns('enfyra_menu', { label: 'Config', parent: parentId, children: [new ObjectId()], @@ -54,14 +54,14 @@ describe('MongoService.stripUnknownColumns', () => { { propertyName: 'user', type: 'many-to-one', - targetTable: 'user_definition', + targetTable: 'enfyra_user', foreignKeyColumn: 'userId', }, ], }); await expect( - service.stripUnknownColumns('file_definition', { + service.stripUnknownColumns('enfyra_file', { name: 'avatar.png', userId, unknown: 'drop', diff --git a/test/package-management/package-cdn-loader.service.spec.ts b/test/package-management/package-cdn-loader.service.spec.ts index ca58805a..e64c07e4 100644 --- a/test/package-management/package-cdn-loader.service.spec.ts +++ b/test/package-management/package-cdn-loader.service.spec.ts @@ -207,7 +207,7 @@ describe('PackageCacheService CDN preload', () => { '13.2.1', ); expect(queryBuilderService.update).toHaveBeenCalledWith( - 'package_definition', + 'enfyra_package', 7, { status: 'installed', lastError: null }, ); diff --git a/test/query-builder.e2e.js b/test/query-builder.e2e.js index 379b6a37..6a1db362 100644 --- a/test/query-builder.e2e.js +++ b/test/query-builder.e2e.js @@ -57,16 +57,16 @@ function log(name, passed, details = '') { } async function loadMetadata() { - const tables = await db('table_definition').select('*'); - const columns = await db('column_definition').select('*'); - const relations = await db('relation_definition') + const tables = await db('enfyra_table').select('*'); + const columns = await db('enfyra_column').select('*'); + const relations = await db('enfyra_relation') .select([ - 'relation_definition.*', + 'enfyra_relation.*', 'targetTable.name as targetTableName', 'sourceTable.name as sourceTableName', ]) - .leftJoin(db('table_definition').as('targetTable'), 'relation_definition.targetTableId', 'targetTable.id') - .leftJoin(db('table_definition').as('sourceTable'), 'relation_definition.sourceTableId', 'sourceTable.id'); + .leftJoin(db('enfyra_table').as('targetTable'), 'enfyra_relation.targetTableId', 'targetTable.id') + .leftJoin(db('enfyra_table').as('sourceTable'), 'enfyra_relation.sourceTableId', 'sourceTable.id'); const tablesMap = new Map(); @@ -131,7 +131,7 @@ async function testBasicSelect() { console.log('='.repeat(70)); const result = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], limit: 5, metadata, @@ -142,7 +142,7 @@ async function testBasicSelect() { log('Basic select has requested fields', result.data[0]?.id !== undefined && result.data[0]?.name !== undefined); const resultAll = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: '*', limit: 1, metadata, @@ -151,7 +151,7 @@ async function testBasicSelect() { log('Select * returns all columns', Object.keys(resultAll.data[0]).length > 2); const resultNoFields = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', limit: 1, metadata, }); @@ -165,7 +165,7 @@ async function testFieldSelection() { console.log('='.repeat(70)); const result = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name', 'alias'], limit: 1, metadata, @@ -178,7 +178,7 @@ async function testFieldSelection() { log('Select specific fields - alias present', keys.includes('alias')); const resultArray = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: 'id,name,alias', limit: 1, metadata, @@ -187,7 +187,7 @@ async function testFieldSelection() { log('Select fields as comma string', Object.keys(resultArray.data[0]).length === 3); const resultNested = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name', 'columns.id', 'columns.name'], limit: 1, metadata, @@ -204,7 +204,7 @@ async function testSortFunctionality() { console.log('='.repeat(70)); const resultAsc = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], sort: 'id', limit: 5, @@ -218,7 +218,7 @@ async function testSortFunctionality() { log('Sort ascending by id', isAsc); const resultDesc = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], sort: '-id', limit: 5, @@ -232,7 +232,7 @@ async function testSortFunctionality() { log('Sort descending by id', isDesc); const resultMulti = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], sort: 'name,id', limit: 10, @@ -243,7 +243,7 @@ async function testSortFunctionality() { const debugLog = []; const resultDefault = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 5, debugLog, @@ -254,7 +254,7 @@ async function testSortFunctionality() { log('Default sort by id applied', sql?.includes('ORDER BY') && sql?.includes('"id"')); const resultNestedSort = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'columns.id'], sort: 'columns.id', limit: 5, @@ -271,7 +271,7 @@ async function testPagination() { console.log('='.repeat(70)); const result1 = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 5, page: 1, @@ -279,7 +279,7 @@ async function testPagination() { }); const result2 = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 5, page: 2, @@ -291,7 +291,7 @@ async function testPagination() { log('Pages return different data', result1.data[0].id !== result2.data[0].id); const resultOffset = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 3, page: 3, @@ -299,7 +299,7 @@ async function testPagination() { }); const allResult = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 9, page: 1, @@ -311,7 +311,7 @@ async function testPagination() { log('Offset calculated correctly', offsetId === expectedId, `offset id: ${offsetId}, expected: ${expectedId}`); const resultLimit0 = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 0, metadata, @@ -320,7 +320,7 @@ async function testPagination() { log('Limit 0 returns all data (no limit applied)', resultLimit0.data.length > 0); const resultNoLimit = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], metadata, }); @@ -334,26 +334,26 @@ async function testFilterOperators() { console.log('='.repeat(70)); const resultEq = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], - filter: { name: { _eq: 'table_definition' } }, + filter: { name: { _eq: 'enfyra_table' } }, metadata, }); - log('Filter _eq works', resultEq.data.length === 1 && resultEq.data[0].name === 'table_definition'); + log('Filter _eq works', resultEq.data.length === 1 && resultEq.data[0].name === 'enfyra_table'); const resultNeq = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], - filter: { name: { _neq: 'table_definition' } }, + filter: { name: { _neq: 'enfyra_table' } }, limit: 5, metadata, }); - log('Filter _neq works', resultNeq.data.every(d => d.name !== 'table_definition')); + log('Filter _neq works', resultNeq.data.every(d => d.name !== 'enfyra_table')); const allTables = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], metadata, }); @@ -362,7 +362,7 @@ async function testFilterOperators() { const halfIds = allTables.data.slice(0, halfCount).map(d => d.id); const resultIn = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { id: { _in: halfIds } }, metadata, @@ -371,7 +371,7 @@ async function testFilterOperators() { log('Filter _in works', resultIn.data.length === halfCount); const resultNin = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { id: { _not_in: halfIds } }, metadata, @@ -380,7 +380,7 @@ async function testFilterOperators() { log('Filter _not_in works', resultNin.data.length === allTables.data.length - halfCount); const resultGt = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { id: { _gt: halfIds[0] } }, limit: 5, @@ -390,7 +390,7 @@ async function testFilterOperators() { log('Filter _gt works', resultGt.data.every(d => d.id > halfIds[0])); const resultGte = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { id: { _gte: halfIds[0] } }, limit: 5, @@ -400,7 +400,7 @@ async function testFilterOperators() { log('Filter _gte works', resultGte.data.every(d => d.id >= halfIds[0])); const resultLt = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { id: { _lt: halfIds[0] } }, limit: 5, @@ -410,7 +410,7 @@ async function testFilterOperators() { log('Filter _lt works', resultLt.data.every(d => d.id < halfIds[0])); const resultLte = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { id: { _lte: halfIds[0] } }, limit: 5, @@ -420,7 +420,7 @@ async function testFilterOperators() { log('Filter _lte works', resultLte.data.every(d => d.id <= halfIds[0])); const resultContains = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { name: { _contains: 'table' } }, limit: 5, @@ -430,7 +430,7 @@ async function testFilterOperators() { log('Filter _contains works', resultContains.data.every(d => d.name.toLowerCase().includes('table'))); const resultStartsWith = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { name: { _starts_with: 'table' } }, limit: 5, @@ -440,7 +440,7 @@ async function testFilterOperators() { log('Filter _starts_with works', resultStartsWith.data.every(d => d.name.toLowerCase().startsWith('table'))); const resultEndsWith = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { name: { _ends_with: 'definition' } }, limit: 5, @@ -450,7 +450,7 @@ async function testFilterOperators() { log('Filter _ends_with works', resultEndsWith.data.every(d => d.name.toLowerCase().endsWith('definition'))); const resultIsNull = await executor.execute({ - tableName: 'column_definition', + tableName: 'enfyra_column', fields: ['id'], filter: { description: { _is_null: true } }, limit: 5, @@ -460,7 +460,7 @@ async function testFilterOperators() { log('Filter _is_null works', resultIsNull.data.length >= 0); const resultIsNotNull = await executor.execute({ - tableName: 'column_definition', + tableName: 'enfyra_column', fields: ['id'], filter: { description: { _is_not_null: true } }, limit: 5, @@ -470,9 +470,9 @@ async function testFilterOperators() { log('Filter _is_not_null works', resultIsNotNull.data.length >= 0); const simpleFilter = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], - filter: { name: 'table_definition' }, + filter: { name: 'enfyra_table' }, metadata, }); @@ -485,7 +485,7 @@ async function testLogicalOperators() { console.log('='.repeat(70)); const resultAnd = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { _and: [ @@ -500,12 +500,12 @@ async function testLogicalOperators() { log('Filter _and works', resultAnd.data.every(d => d.name.includes('table') && d.name.includes('definition'))); const resultOr = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { _or: [ - { name: { _eq: 'table_definition' } }, - { name: { _eq: 'column_definition' } } + { name: { _eq: 'enfyra_table' } }, + { name: { _eq: 'enfyra_column' } } ] }, metadata, @@ -514,27 +514,27 @@ async function testLogicalOperators() { log('Filter _or works', resultOr.data.length >= 2); const resultNot = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { - _not: { name: { _eq: 'table_definition' } } + _not: { name: { _eq: 'enfyra_table' } } }, limit: 5, metadata, }); - log('Filter _not works', resultNot.data.every(d => d.name !== 'table_definition')); + log('Filter _not works', resultNot.data.every(d => d.name !== 'enfyra_table')); const resultNestedLogical = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], filter: { _and: [ { name: { _contains: 'definition' } }, { _or: [ - { name: { _eq: 'table_definition' } }, - { name: { _eq: 'column_definition' } } + { name: { _eq: 'enfyra_table' } }, + { name: { _eq: 'enfyra_column' } } ] } ] @@ -551,7 +551,7 @@ async function testNestedRelations() { console.log('='.repeat(70)); const resultOneToMany = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name', 'columns.id', 'columns.name'], limit: 3, metadata, @@ -573,7 +573,7 @@ async function testNestedRelations() { log('One-to-many columns sorted by id', isSorted); const resultManyToOne = await executor.execute({ - tableName: 'column_definition', + tableName: 'enfyra_column', fields: ['id', 'name', 'table.id', 'table.name'], limit: 5, metadata, @@ -585,7 +585,7 @@ async function testNestedRelations() { log('Many-to-one table has id', resultManyToOne.data[0]?.table?.id !== undefined); const resultManyToMany = await executor.execute({ - tableName: 'route_definition', + tableName: 'enfyra_route', fields: ['id', 'path', 'availableMethods.id', 'availableMethods.name'], limit: 3, metadata, @@ -602,7 +602,7 @@ async function testDeepNestedRelations() { console.log('='.repeat(70)); const result = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name', 'columns.id', 'columns.name', 'columns.table.id', 'columns.table.name'], limit: 2, metadata, @@ -617,7 +617,7 @@ async function testDeepNestedRelations() { log('Deep nested has table inside columns', hasDeepTable); const resultAllFields = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: '*', limit: 1, metadata, @@ -627,7 +627,7 @@ async function testDeepNestedRelations() { log('Select * includes columns', resultAllFields.data[0]?.columns !== undefined); const resultMixed = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name', 'columns.*'], limit: 1, metadata, @@ -643,7 +643,7 @@ async function testMeta() { console.log('='.repeat(70)); const resultTotal = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], meta: 'totalCount', limit: 5, @@ -655,7 +655,7 @@ async function testMeta() { log('Meta totalCount > limit', resultTotal.meta?.totalCount > 5); const resultFilter = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: { name: { _contains: 'definition' } }, meta: 'filterCount', @@ -667,7 +667,7 @@ async function testMeta() { log('Meta filterCount is number', typeof resultFilter.meta?.filterCount === 'number'); const resultAllMeta = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], meta: '*', limit: 5, @@ -678,7 +678,7 @@ async function testMeta() { log('Meta * includes filterCount', resultAllMeta.meta?.filterCount !== undefined); const resultNoMeta = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 5, metadata, @@ -693,7 +693,7 @@ async function testEdgeCases() { console.log('='.repeat(70)); const resultEmptyFilter = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: {}, limit: 1, @@ -703,7 +703,7 @@ async function testEdgeCases() { log('Empty filter returns data', resultEmptyFilter.data.length === 1); const resultNullFilter = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], filter: null, limit: 1, @@ -713,7 +713,7 @@ async function testEdgeCases() { log('Null filter returns data', resultNullFilter.data.length === 1); const resultLargeLimit = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 10000, metadata, @@ -722,7 +722,7 @@ async function testEdgeCases() { log('Large limit returns all data', resultLargeLimit.data.length > 10); const resultNegativeLimit = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: -1, metadata, @@ -731,7 +731,7 @@ async function testEdgeCases() { log('Negative limit returns empty or all', resultNegativeLimit.data.length >= 0); const resultStringLimit = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: '5', metadata, @@ -740,7 +740,7 @@ async function testEdgeCases() { log('String limit converted to number', resultStringLimit.data.length === 5); const resultStringPage = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], limit: 5, page: '2', @@ -752,7 +752,7 @@ async function testEdgeCases() { let errorOnInvalidSort = false; try { await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], sort: 'nonexistentField', limit: 5, @@ -764,7 +764,7 @@ async function testEdgeCases() { log('Invalid sort field throws error', errorOnInvalidSort); const resultEmptyFields = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: [], limit: 1, metadata, @@ -773,7 +773,7 @@ async function testEdgeCases() { log('Empty fields array returns data', resultEmptyFields.data.length >= 0); const resultWhitespaceFields = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ' id , name ', limit: 1, metadata, @@ -782,7 +782,7 @@ async function testEdgeCases() { log('Whitespace in fields string trimmed', resultWhitespaceFields.data.length === 1); const resultWhitespaceSort = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id'], sort: ' id , name ', limit: 1, @@ -792,7 +792,7 @@ async function testEdgeCases() { log('Whitespace in sort string trimmed', resultWhitespaceSort.data.length === 1); const resultMultipleSortDirections = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name'], sort: '-id,name', limit: 5, @@ -802,7 +802,7 @@ async function testEdgeCases() { log('Multiple sort with different directions works', resultMultipleSortDirections.data.length > 0); const resultFilterWithNullValue = await executor.execute({ - tableName: 'column_definition', + tableName: 'enfyra_column', fields: ['id'], filter: { description: null }, limit: 5, @@ -812,7 +812,7 @@ async function testEdgeCases() { log('Filter with null value works', resultFilterWithNullValue.data.length >= 0); const resultDeepFilter = await executor.execute({ - tableName: 'table_definition', + tableName: 'enfyra_table', fields: ['id', 'name', 'columns.id'], filter: { name: { _contains: 'definition' } }, limit: 3, diff --git a/test/realtime-chat-app.e2e.ts b/test/realtime-chat-app.e2e.ts index 9918a572..366a1443 100644 --- a/test/realtime-chat-app.e2e.ts +++ b/test/realtime-chat-app.e2e.ts @@ -212,14 +212,14 @@ async function ensureMessageTable( const c = container.cradle; const isMongo = c.databaseConfigService.isMongoDb(); let existing = await c.queryBuilderService.findOne({ - table: 'table_definition', + table: 'enfyra_table', where: { name: TABLE_NAME }, }); if (!isMongo) { const knex = c.queryBuilderService.getKnex(); const hasPhysicalTable = await knex.schema.hasTable(TABLE_NAME); if (existing && !hasPhysicalTable) { - await knex('table_definition').where({ name: TABLE_NAME }).delete(); + await knex('enfyra_table').where({ name: TABLE_NAME }).delete(); existing = null; await reloadRuntime(container); } @@ -234,7 +234,7 @@ async function ensureMessageTable( } if (!existing) { const ctx = createRootContext(container); - await ctx.$repos.table_definition.create({ + await ctx.$repos.enfyra_table.create({ data: { name: TABLE_NAME, description: 'E2E realtime chat load messages', @@ -281,10 +281,10 @@ async function ensureFlowStepScriptColumns( ) { const c = container.cradle; const table = await c.queryBuilderService.findOne({ - table: 'table_definition', - where: { name: 'flow_step_definition' }, + table: 'enfyra_table', + where: { name: 'enfyra_flow_step' }, }); - assert(table, 'flow_step_definition metadata not found'); + assert(table, 'enfyra_flow_step metadata not found'); const tableId = table.id ?? table._id; const isMongo = c.databaseConfigService.isMongoDb(); const columns = [ @@ -312,7 +312,7 @@ async function ensureFlowStepScriptColumns( for (const column of columns) { if (isMongo) { - const collection = c.mongoService.getDb().collection('column_definition'); + const collection = c.mongoService.getDb().collection('enfyra_column'); const existing = await collection.findOne({ table: tableId, name: column.name, @@ -338,11 +338,11 @@ async function ensureFlowStepScriptColumns( const knex = c.queryBuilderService.getKnex(); const hasPhysicalColumn = await knex.schema.hasColumn( - 'flow_step_definition', + 'enfyra_flow_step', column.name, ); if (!hasPhysicalColumn) { - await knex.schema.alterTable('flow_step_definition', (t) => { + await knex.schema.alterTable('enfyra_flow_step', (t) => { if (column.name === 'scriptLanguage') { t.string(column.name).notNullable().defaultTo('typescript'); } else { @@ -350,11 +350,11 @@ async function ensureFlowStepScriptColumns( } }); } - const existing = await knex('column_definition') + const existing = await knex('enfyra_column') .where({ tableId, name: column.name }) .first(); if (!existing) { - await knex('column_definition').insert({ + await knex('enfyra_column').insert({ tableId, name: column.name, type: column.type, @@ -440,10 +440,10 @@ function chatEventSource() { async function upsertPersistFlow(container: ReturnType) { const c = container.cradle; const ctx = createRootContext(container); - const flowRepo = ctx.$repos.flow_definition; - const stepRepo = ctx.$repos.flow_step_definition; + const flowRepo = ctx.$repos.enfyra_flow; + const stepRepo = ctx.$repos.enfyra_flow_step; let flow = await c.queryBuilderService.findOne({ - table: 'flow_definition', + table: 'enfyra_flow', where: { name: FLOW_NAME }, }); if (flow) { @@ -472,7 +472,7 @@ async function upsertPersistFlow(container: ReturnType) { const flowId = flow?.id ?? flow?._id; assert(flowId, 'persist flow id was not created'); const existingStep = await c.queryBuilderService.findOne({ - table: 'flow_step_definition', + table: 'enfyra_flow_step', where: { flow: flowId, key: 'persist_message' }, }); const stepData = { @@ -502,10 +502,10 @@ async function upsertPersistFlow(container: ReturnType) { async function upsertGateway(container: ReturnType) { const c = container.cradle; const ctx = createRootContext(container); - const gatewayRepo = ctx.$repos.websocket_definition; - const eventRepo = ctx.$repos.websocket_event_definition; + const gatewayRepo = ctx.$repos.enfyra_websocket; + const eventRepo = ctx.$repos.enfyra_websocket_event; let gateway = await c.queryBuilderService.findOne({ - table: 'websocket_definition', + table: 'enfyra_websocket', where: { path: NAMESPACE }, }); if (gateway) { @@ -538,7 +538,7 @@ async function upsertGateway(container: ReturnType) { assert(gatewayId, 'gateway id was not created'); const existingEvent = await c.queryBuilderService.findOne({ - table: 'websocket_event_definition', + table: 'enfyra_websocket_event', where: { gatewayId, eventName: 'chat:send' }, }); const eventConfig = { @@ -604,7 +604,7 @@ async function prepareAppMetadata() { async function purgeE2eFlowJobs(container: ReturnType) { const flow = await container.cradle.queryBuilderService.findOne({ - table: 'flow_definition', + table: 'enfyra_flow', where: { name: FLOW_NAME }, }); if (!flow) return; diff --git a/test/security/cascade-policy.spec.ts b/test/security/cascade-policy.spec.ts index 36bd4797..47a8a79a 100644 --- a/test/security/cascade-policy.spec.ts +++ b/test/security/cascade-policy.spec.ts @@ -59,7 +59,7 @@ describe('CascadeHandler – getPolicyContext callback', () => { { propertyName: 'author', type: 'many-to-one', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', foreignKeyColumn: 'authorId', }, ], @@ -88,7 +88,7 @@ describe('CascadeHandler – getPolicyContext callback', () => { ); expect(policyCheck).toHaveBeenCalledWith( - 'user_definition', + 'enfyra_user', 'create', expect.objectContaining({ name: 'Alice' }), ); @@ -106,7 +106,7 @@ describe('CascadeHandler – getPolicyContext callback', () => { { propertyName: 'author', type: 'many-to-one', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', foreignKeyColumn: 'authorId', }, ], @@ -149,7 +149,7 @@ describe('CascadeHandler – getPolicyContext callback', () => { { propertyName: 'author', type: 'many-to-one', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', foreignKeyColumn: 'authorId', }, ], @@ -194,7 +194,7 @@ describe('CascadeHandler – getPolicyContext callback', () => { { propertyName: 'author', type: 'many-to-one', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', foreignKeyColumn: 'authorId', }, ], @@ -308,7 +308,7 @@ describe('CascadeHandler – getPolicyContext callback', () => { { propertyName: 'author', type: 'many-to-one', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', foreignKeyColumn: 'authorId', }, ], diff --git a/test/security/graphql-schema-filter.spec.ts b/test/security/graphql-schema-filter.spec.ts index f35e73b3..2dd78e8f 100644 --- a/test/security/graphql-schema-filter.spec.ts +++ b/test/security/graphql-schema-filter.spec.ts @@ -40,7 +40,7 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { describe('queryableTableNames allowlist', () => { it('emits only tables present in queryableTableNames', () => { const tables = [ - makeTable('user_definition', [ + makeTable('enfyra_user', [ makeColumn('id', 'uuid', { isPrimary: true }), makeColumn('email'), ]), @@ -51,15 +51,15 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { ]; const schema = generateGraphQLTypeDefsFromTables( tables, - new Set(['user_definition']), + new Set(['enfyra_user']), ); - expect(schema).toContain('type user_definition'); + expect(schema).toContain('type enfyra_user'); expect(schema).not.toContain('type secret_config'); }); it('emits all tables when queryableTableNames is undefined', () => { const tables = [ - makeTable('user_definition', [ + makeTable('enfyra_user', [ makeColumn('id', 'uuid', { isPrimary: true }), makeColumn('email'), ]), @@ -69,20 +69,20 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { ]), ]; const schema = generateGraphQLTypeDefsFromTables(tables, undefined); - expect(schema).toContain('type user_definition'); + expect(schema).toContain('type enfyra_user'); expect(schema).toContain('type post'); }); it('produces empty query/mutation blocks when set is empty', () => { const tables = [ - makeTable('user_definition', [ + makeTable('enfyra_user', [ makeColumn('id', 'uuid', { isPrimary: true }), makeColumn('email'), ]), ]; const schema = generateGraphQLTypeDefsFromTables(tables, new Set([])); - expect(schema).not.toContain('type user_definition'); - expect(schema).not.toContain('user_definition('); + expect(schema).not.toContain('type enfyra_user'); + expect(schema).not.toContain('enfyra_user('); }); }); @@ -94,8 +94,8 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { makeColumn('password', 'varchar', { isPublished: false }), ]; const schema = generateGraphQLTypeDefsFromTables( - [makeTable('user_definition', columns)], - new Set(['user_definition']), + [makeTable('enfyra_user', columns)], + new Set(['enfyra_user']), ); expect(schema).toContain('email'); expect(schema).not.toContain('password'); @@ -108,10 +108,10 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { makeColumn('passwordHash', 'varchar', { isPublished: false }), ]; const schema = generateGraphQLTypeDefsFromTables( - [makeTable('user_definition', columns)], - new Set(['user_definition']), + [makeTable('enfyra_user', columns)], + new Set(['enfyra_user']), ); - expect(schema).toContain('input user_definitionInput'); + expect(schema).toContain('input enfyra_userInput'); expect(schema).not.toContain('passwordHash'); }); @@ -122,10 +122,10 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { makeColumn('secret', 'varchar', { isPublished: false }), ]; const schema = generateGraphQLTypeDefsFromTables( - [makeTable('user_definition', columns)], - new Set(['user_definition']), + [makeTable('enfyra_user', columns)], + new Set(['enfyra_user']), ); - expect(schema).toContain('input user_definitionUpdateInput'); + expect(schema).toContain('input enfyra_userUpdateInput'); expect(schema).not.toContain('secret'); }); @@ -137,8 +137,8 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { makeColumn('token', 'varchar', { isPublished: false }), ]; const schema = generateGraphQLTypeDefsFromTables( - [makeTable('user_definition', columns)], - new Set(['user_definition']), + [makeTable('enfyra_user', columns)], + new Set(['enfyra_user']), ); expect(schema).toContain('username'); expect(schema).toContain('bio'); @@ -167,7 +167,7 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { const relations = [ { propertyName: 'author', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', type: 'many-to-one', isPublished: false, }, @@ -175,15 +175,15 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { const schema = generateGraphQLTypeDefsFromTables( [ makeTable('post', columns, relations), - makeTable('user_definition', [ + makeTable('enfyra_user', [ makeColumn('id', 'uuid', { isPrimary: true }), makeColumn('email'), ]), ], - new Set(['post', 'user_definition']), + new Set(['post', 'enfyra_user']), ); expect(schema).toContain('type post'); - expect(schema).not.toContain('author: user_definition'); + expect(schema).not.toContain('author: enfyra_user'); }); }); @@ -246,18 +246,18 @@ describe('generateGraphQLTypeDefsFromTables – security filter', () => { const relations = [ { propertyName: 'author', - targetTableName: 'user_definition', + targetTableName: 'enfyra_user', type: 'many-to-one', }, ]; const schema = generateGraphQLTypeDefsFromTables( [ makeTable('post', postColumns, relations), - makeTable('user_definition', userColumns), + makeTable('enfyra_user', userColumns), ], - new Set(['post', 'user_definition']), + new Set(['post', 'enfyra_user']), ); - const typeMatches = (schema.match(/^type user_definition\s*\{/gm) || []) + const typeMatches = (schema.match(/^type enfyra_user\s*\{/gm) || []) .length; expect(typeMatches).toBe(1); }); diff --git a/test/shared/public-methods-cleanup.spec.ts b/test/shared/public-methods-cleanup.spec.ts index cc72a6c7..f9588f49 100644 --- a/test/shared/public-methods-cleanup.spec.ts +++ b/test/shared/public-methods-cleanup.spec.ts @@ -18,12 +18,12 @@ const AUTH_PATHS = new Set([ const PUBLIC_NON_AUTH_PATHS = new Set([ '/assets/:id', - '/cors_origin_definition', + '/enfyra_cors_origin', ]); describe('default-data.json — publicMethods', () => { const data = loadJson('default-data.json'); - const routes: any[] = data.route_definition ?? []; + const routes: any[] = data.enfyra_route ?? []; it('only auth routes and explicit public routes have publicMethods set', () => { const nonAuthPublished = routes.filter( @@ -46,23 +46,23 @@ describe('default-data.json — publicMethods', () => { describe('data-migration.json — publicMethods cleanup', () => { const migration = loadJson('data-migration.json'); - const routes: any[] = migration.route_definition ?? []; + const routes: any[] = migration.enfyra_route ?? []; const SHOULD_BE_EMPTY = [ '/me', '/metadata', '/metadata/:name', - '/folder_definition/tree', - '/package_definition', - '/route_definition', - '/table_definition', - '/setting_definition', + '/enfyra_folder/tree', + '/enfyra_package', + '/enfyra_route', + '/enfyra_table', + '/enfyra_setting', '/me/oauth-accounts', '/graphql-schema', - '/menu_definition', - '/extension_definition', - '/extension_definition/preview', - '/storage_config_definition', + '/enfyra_menu', + '/enfyra_extension', + '/enfyra_extension/preview', + '/enfyra_storage_config', ]; it.each(SHOULD_BE_EMPTY)('%s has publicMethods: []', (routePath) => { @@ -92,7 +92,7 @@ describe('settings menu seed cleanup', () => { const migration = loadJson('data-migration.json'); it('does not seed a dedicated field permissions menu', () => { - const menus: any[] = defaultData.menu_definition ?? []; + const menus: any[] = defaultData.enfyra_menu ?? []; expect( menus.find((menu) => menu.path === '/settings/field-permissions'), ).toBeUndefined(); @@ -103,14 +103,14 @@ describe('settings menu seed cleanup', () => { expect( deletedRecords.some( (record) => - record.table === 'menu_definition' && + record.table === 'enfyra_menu' && record.filter?.path?._eq === '/settings/field-permissions', ), ).toBe(true); }); it('does not seed a dedicated cache reload menu', () => { - const menus: any[] = defaultData.menu_definition ?? []; + const menus: any[] = defaultData.enfyra_menu ?? []; expect( menus.find((menu) => menu.path === '/settings/admin/cache'), ).toBeUndefined(); @@ -121,14 +121,14 @@ describe('settings menu seed cleanup', () => { expect( deletedRecords.some( (record) => - record.table === 'menu_definition' && + record.table === 'enfyra_menu' && record.filter?.path?._eq === '/settings/admin/cache', ), ).toBe(true); }); it('seeds runtime monitor at settings/runtime', () => { - const menus: any[] = defaultData.menu_definition ?? []; + const menus: any[] = defaultData.enfyra_menu ?? []; expect( menus.find((menu) => menu.path === '/settings/admin/runtime'), ).toBeUndefined(); @@ -140,7 +140,7 @@ describe('settings menu seed cleanup', () => { }); it('updates the existing runtime monitor menu path through data migration', () => { - const menus: any[] = migration.menu_definition ?? []; + const menus: any[] = migration.enfyra_menu ?? []; expect( menus.find( (menu) => menu._unique?.path?._eq === '/settings/admin/runtime', @@ -153,7 +153,7 @@ describe('settings menu seed cleanup', () => { }); it('deletes the legacy routings menu instead of migrating it into the routes menu', () => { - const menus: any[] = migration.menu_definition ?? []; + const menus: any[] = migration.enfyra_menu ?? []; const deletedRecords: any[] = migration._deletedRecords ?? []; expect( @@ -164,7 +164,7 @@ describe('settings menu seed cleanup', () => { expect( deletedRecords.some( (record) => - record.table === 'menu_definition' && + record.table === 'enfyra_menu' && record.filter?.path?._eq === '/settings/routings', ), ).toBe(true); diff --git a/test/shared/runtime-metrics-collector.service.spec.ts b/test/shared/runtime-metrics-collector.service.spec.ts index c49e158d..a1348e2f 100644 --- a/test/shared/runtime-metrics-collector.service.spec.ts +++ b/test/shared/runtime-metrics-collector.service.spec.ts @@ -57,10 +57,10 @@ describe('RuntimeMetricsCollectorService', () => { const collector = new RuntimeMetricsCollectorService(); await collector.runWithQueryContext('cache', () => - collector.trackQuery({ op: 'find', table: 'route_definition' }, async () => true), + collector.trackQuery({ op: 'find', table: 'enfyra_route' }, async () => true), ); await collector.runWithQueryContext('flow', () => - collector.trackQuery({ op: 'find', table: 'route_definition' }, async () => true), + collector.trackQuery({ op: 'find', table: 'enfyra_route' }, async () => true), ); expect(collector.snapshot().database.queries).toEqual( @@ -68,13 +68,13 @@ describe('RuntimeMetricsCollectorService', () => { expect.objectContaining({ context: 'cache', op: 'find', - table: 'route_definition', + table: 'enfyra_route', count: 1, }), expect.objectContaining({ context: 'flow', op: 'find', - table: 'route_definition', + table: 'enfyra_route', count: 1, }), ]), @@ -86,7 +86,7 @@ describe('RuntimeMetricsCollectorService', () => { collector.recordCacheReload({ flow: 'route', - table: 'route_definition', + table: 'enfyra_route', scope: 'full', status: 'failed', durationMs: 12, @@ -101,7 +101,7 @@ describe('RuntimeMetricsCollectorService', () => { expect(collector.snapshot().cache.recent[0]).toEqual( expect.objectContaining({ flow: 'route', - table: 'route_definition', + table: 'enfyra_route', status: 'failed', steps: [ { name: 'route', durationMs: 12, status: 'failed', error: 'boom' }, @@ -120,7 +120,7 @@ describe('RuntimeMetricsCollectorService', () => { collector.recordCacheReload({ flow: 'metadata', - table: 'table_definition', + table: 'enfyra_table', scope: 'full', status: 'success', durationMs: 5, @@ -135,7 +135,7 @@ describe('RuntimeMetricsCollectorService', () => { expect.objectContaining({ instanceId: 'inst-a', flow: 'metadata', - table: 'table_definition', + table: 'enfyra_table', }), ); expect(ttls).toContainEqual( diff --git a/test/shared/upload-file-helper.spec.ts b/test/shared/upload-file-helper.spec.ts index c6fe15e7..3e2c3111 100644 --- a/test/shared/upload-file-helper.spec.ts +++ b/test/shared/upload-file-helper.spec.ts @@ -18,7 +18,7 @@ function makeContext(): TDynamicContext { $helpers: {}, $cache: {}, $repos: { - file_definition: { + enfyra_file: { create: vi.fn(), }, }, diff --git a/test/shared/zod-from-metadata.spec.ts b/test/shared/zod-from-metadata.spec.ts index c24963db..0f4d8a3f 100644 --- a/test/shared/zod-from-metadata.spec.ts +++ b/test/shared/zod-from-metadata.spec.ts @@ -474,7 +474,7 @@ describe('buildZodFromMetadata — relations', () => { it('does not hide a local column when an inverse relation uses the same foreignKeyColumn name', () => { const s = build( makeMeta({ - name: 'method_definition', + name: 'enfyra_method', columns: [ col('method', 'varchar', { isNullable: false }), col('isSystem', 'boolean', { isNullable: false }), @@ -483,7 +483,7 @@ describe('buildZodFromMetadata — relations', () => { { type: 'one-to-many', propertyName: 'handlers', - targetTable: 'route_handler_definition', + targetTable: 'enfyra_route_handler', mappedBy: 'method', foreignKeyColumn: 'method', isInverse: true, diff --git a/test/table-management/relation-on-delete-metadata-writer.spec.ts b/test/table-management/relation-on-delete-metadata-writer.spec.ts index 9f73339c..ad9eff47 100644 --- a/test/table-management/relation-on-delete-metadata-writer.spec.ts +++ b/test/table-management/relation-on-delete-metadata-writer.spec.ts @@ -8,11 +8,11 @@ function createQueryRunner(existingRelations: Record = {}) { const runner: any = (table: string) => { const state: any = { table, whereValue: null, whereInValue: null }; const resolveRows = () => { - if (table === 'relation_definition') { + if (table === 'enfyra_relation') { if (state.whereValue?.sourceTableId === 1) return []; if (state.whereValue?.sourceTableId === 2) return []; } - if (table === 'table_definition' && state.whereInValue?.column === 'id') { + if (table === 'enfyra_table' && state.whereInValue?.column === 'id') { return [ { id: 1, name: 'courses' }, { id: 2, name: 'users' }, @@ -34,7 +34,7 @@ function createQueryRunner(existingRelations: Record = {}) { }, first() { if ( - table === 'relation_definition' && + table === 'enfyra_relation' && state.whereValue?.sourceTableId !== undefined && state.whereValue?.propertyName !== undefined ) { @@ -46,20 +46,20 @@ function createQueryRunner(existingRelations: Record = {}) { return Promise.resolve(relation ?? null); } if ( - table === 'relation_definition' && + table === 'enfyra_relation' && state.whereValue?.sourceTableId === 2 && state.whereValue?.propertyName === 'posts' ) { return Promise.resolve(null); } if ( - table === 'relation_definition' && + table === 'enfyra_relation' && state.whereValue?.id === 701 ) { - return Promise.resolve(inserts.relation_definition?.[0] ?? null); + return Promise.resolve(inserts.enfyra_relation?.[0] ?? null); } if ( - table === 'relation_definition' && + table === 'enfyra_relation' && existingRelations[state.whereValue?.id] ) { return Promise.resolve(existingRelations[state.whereValue.id]); @@ -80,7 +80,7 @@ function createQueryRunner(existingRelations: Record = {}) { for (const row of rows) { inserts[table].push({ ...row }); } - if (table === 'relation_definition') { + if (table === 'enfyra_relation') { return Promise.resolve([nextRelationId++]); } return Promise.resolve([1]); @@ -121,7 +121,7 @@ describe('SqlTableMetadataWriterService relation onDelete metadata', () => { new Set(), ); - const relationRows = inserts.relation_definition || []; + const relationRows = inserts.enfyra_relation || []; const owning = relationRows.find((row) => row.propertyName === 'author'); const inverse = relationRows.find((row) => row.propertyName === 'posts'); @@ -151,7 +151,7 @@ describe('SqlTableMetadataWriterService relation onDelete metadata', () => { new Set(), ); - const relationRows = inserts.relation_definition || []; + const relationRows = inserts.enfyra_relation || []; const relation = relationRows.find((row) => row.propertyName === 'likedBy'); expect(relation?.targetTableId).toBe(2); @@ -192,7 +192,7 @@ describe('SqlTableMetadataWriterService relation onDelete metadata', () => { new Set(), ); - const relationRows = inserts.relation_definition || []; + const relationRows = inserts.enfyra_relation || []; const update = relationRows.find((row) => row.__update); expect(update?.mappedById).toBe(700); @@ -235,7 +235,7 @@ describe('SqlTableMetadataWriterService relation onDelete metadata', () => { new Set(), ); - const relationRows = inserts.relation_definition || []; + const relationRows = inserts.enfyra_relation || []; const inverse = relationRows.find((row) => row.propertyName === 'mentoredCourses'); expect(inverse?.mappedById).toBe(701); From ea0292b48b992abab73d17e3d350b8186d887155 Mon Sep 17 00:00:00 2001 From: dothinh115 Date: Fri, 19 Jun 2026 02:55:13 +0700 Subject: [PATCH 2/5] feat: enhance websocket functionality with event handling and connection management improvements --- data/default-data.json | 72 ++++++++ data/snapshot.json | 2 +- .../processors/route-definition.processor.ts | 49 ++---- .../services/data-migration.service.ts | 86 +++++++--- .../metadata-provision-mongo.service.ts | 42 +++++ .../metadata-provision-sql.service.ts | 27 ++- .../services/schema-healing.service.ts | 117 +++++++++---- .../cache/services/websocket-cache.service.ts | 48 +++++- .../gateway/dynamic-websocket.gateway.ts | 161 +++++++++++------- .../websocket/queues/event-queue.service.ts | 9 +- test/query-builder.e2e.js | 13 +- test/websocket/event-script-execution.spec.ts | 2 +- 12 files changed, 463 insertions(+), 165 deletions(-) diff --git a/data/default-data.json b/data/default-data.json index 8fe722a3..2789ee38 100644 --- a/data/default-data.json +++ b/data/default-data.json @@ -171,6 +171,68 @@ "POST" ] }, + { + "path": "/auth/api-tokens", + "isEnabled": true, + "isSystem": true, + "icon": "lucide:key", + "skipRoleGuardMethods": [ + "GET", + "POST" + ], + "availableMethods": [ + "GET", + "POST" + ] + }, + { + "path": "/auth/api-tokens/:id", + "isEnabled": true, + "isSystem": true, + "icon": "lucide:key-round", + "skipRoleGuardMethods": [ + "DELETE" + ], + "availableMethods": [ + "DELETE" + ] + }, + { + "path": "/auth/token/exchange", + "isEnabled": true, + "isSystem": true, + "icon": "lucide:key-round", + "publicMethods": [ + "POST" + ], + "availableMethods": [ + "POST" + ] + }, + { + "path": "/auth/oauth/exchange", + "isEnabled": true, + "isSystem": true, + "icon": "lucide:key-round", + "publicMethods": [ + "POST" + ], + "availableMethods": [ + "POST" + ] + }, + { + "path": "/auth/set-cookies", + "isEnabled": true, + "isSystem": true, + "icon": "lucide:cookie", + "publicMethods": [ + "GET" + ], + "availableMethods": [ + "GET" + ] + }, { "path": "/auth/providers", "isEnabled": true, @@ -519,6 +581,16 @@ "POST" ] }, + { + "path": "/admin/script/validate", + "isEnabled": true, + "isSystem": true, + "icon": "lucide:code-2", + "description": "Validate dynamic script source without saving it", + "availableMethods": [ + "POST" + ] + }, { "path": "/admin/reload/metadata", "isEnabled": true, diff --git a/data/snapshot.json b/data/snapshot.json index e5dbf91c..eb690757 100644 --- a/data/snapshot.json +++ b/data/snapshot.json @@ -594,7 +594,7 @@ "relations": [ { "propertyName": "file", "type": "many-to-one", "targetTable": "enfyra_file", "isNullable": true, "inversePropertyName": "permissions", "onDelete": "CASCADE", "description": "File this permission applies to" }, { "propertyName": "role", "type": "many-to-one", "targetTable": "enfyra_role", "isNullable": true, "onDelete": "CASCADE", "description": "Role that has this permission" }, - { "propertyName": "allowedUsers", "type": "many-to-many", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "junctionTableName": "j_efaab69d5b9c", "junctionSourceColumn": "sourceId", "junctionTargetColumn": "targetId", "description": "Specific users who have this permission" } + { "propertyName": "allowedUsers", "type": "many-to-many", "targetTable": "enfyra_user", "isSystem": true, "isNullable": true, "junctionTableName": "j_8110450610b3", "junctionSourceColumn": "enfyra_file_permissionId", "junctionTargetColumn": "enfyra_userId", "description": "Specific users who have this permission" } ] }, "enfyra_package": { diff --git a/src/domain/bootstrap/processors/route-definition.processor.ts b/src/domain/bootstrap/processors/route-definition.processor.ts index f633f28d..bd29cd64 100644 --- a/src/domain/bootstrap/processors/route-definition.processor.ts +++ b/src/domain/bootstrap/processors/route-definition.processor.ts @@ -57,10 +57,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { ); return null; } - transformedRecord.mainTable = - typeof mainTable._id === 'string' - ? new ObjectId(mainTable._id) - : mainTable._id; + transformedRecord.mainTable = this.normalizeMongoId(mainTable._id); } else { const mainTable = await this.queryBuilderService.findOne({ table: 'enfyra_table', @@ -243,9 +240,9 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { ): Promise { const { junctionTable, sourceColumn, targetColumn } = await this.getMongoRouteMethodJunctionMetadata(field); - const sourceId = this.toObjectId(routeId); + const sourceId = this.normalizeMongoId(routeId); const targetIds = uniqueMethodIds.map((methodId) => - this.toObjectId(methodId), + this.normalizeMongoId(methodId), ); try { const collection = this.queryBuilderService @@ -302,9 +299,12 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { }; } - private toObjectId(value: any): ObjectId { + private normalizeMongoId(value: any): any { if (value instanceof ObjectId) return value; - return new ObjectId(String(value)); + if (typeof value === 'string' && ObjectId.isValid(value)) { + return new ObjectId(value); + } + return value; } async ensureMissingHandlers(): Promise { @@ -361,8 +361,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { ): Promise { if (isMongoDB) { const db = this.queryBuilderService.getMongoDb(); - const routeIdObj = - typeof routeId === 'string' ? new ObjectId(routeId) : routeId; + const routeIdObj = this.normalizeMongoId(routeId); const result = await db .collection('enfyra_route_handler') .deleteMany({ route: routeIdObj, method: null }); @@ -406,9 +405,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { ? mainTableValue : (record.mainTableId ?? mainTableValue?.id ?? mainTableValue?._id); if (!mainTableFk) return; - if (isMongoDB && typeof mainTableFk === 'string') { - mainTableFk = new ObjectId(mainTableFk); - } + if (isMongoDB) mainTableFk = this.normalizeMongoId(mainTableFk); const tableRow = isMongoDB ? await this.queryBuilderService .getMongoDb() @@ -443,8 +440,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { targetTable: 'enfyra_method', }); const mongoService = this.queryBuilderService.getMongoDb(); - const routeIdObj = - typeof routeId === 'string' ? new ObjectId(routeId) : routeId; + const routeIdObj = this.normalizeMongoId(routeId); const rows = await mongoService .collection(junction.junctionTableName) .find({ [junction.junctionSourceColumn]: routeIdObj }) @@ -474,9 +470,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { .collection('enfyra_method') .find({ _id: { - $in: methodIds.map((id: any) => - id instanceof ObjectId ? id : new ObjectId(String(id)), - ), + $in: methodIds.map((id: any) => this.normalizeMongoId(id)), }, }) .project({ name: 1 }) @@ -521,12 +515,8 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { let existing; if (isMongoDB) { const mongoService = this.queryBuilderService.getMongoDb(); - const routeIdObj = - typeof routeId === 'string' ? new ObjectId(routeId) : routeId; - const methodIdObj = - typeof methodKeyId === 'string' - ? new ObjectId(methodKeyId) - : methodKeyId; + const routeIdObj = this.normalizeMongoId(routeId); + const methodIdObj = this.normalizeMongoId(methodKeyId); existing = await mongoService .collection('enfyra_route_handler') .findOne({ @@ -550,11 +540,8 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { let data: Record; if (isMongoDB) { data = { - route: typeof routeId === 'string' ? new ObjectId(routeId) : routeId, - method: - typeof methodKeyId === 'string' - ? new ObjectId(methodKeyId) - : methodKeyId, + route: this.normalizeMongoId(routeId), + method: this.normalizeMongoId(methodKeyId), sourceCode: logic, scriptLanguage: 'typescript', compiledCode: compileScriptSource(logic, 'typescript'), @@ -573,9 +560,7 @@ export class RouteDefinitionProcessor extends BaseTableProcessor { if (isMongoDB) { const mongoService = this.queryBuilderService.getMongoDb(); - await mongoService - .collection('enfyra_route_handler') - .insertOne(data); + await mongoService.collection('enfyra_route_handler').insertOne(data); } else { await this.queryBuilderService.insertWithOptions({ table: 'enfyra_route_handler', diff --git a/src/engines/bootstrap/services/data-migration.service.ts b/src/engines/bootstrap/services/data-migration.service.ts index 88d121f0..e327c645 100644 --- a/src/engines/bootstrap/services/data-migration.service.ts +++ b/src/engines/bootstrap/services/data-migration.service.ts @@ -129,22 +129,29 @@ export class DataMigrationService { private async deleteRecords( records: { table: string; filter: Record }[], ): Promise { - const idField = DatabaseConfigService.getPkField(); - for (const { table, filter } of records) { try { - const existing = await this.queryBuilderService.find({ - table: table, - filter, - limit: -1, - fields: [idField], - }); - - for (const row of existing.data || []) { - await this.queryBuilderService.delete(table, row[idField]); + const exactWhere = this.toExactDeleteWhere(filter); + if (!exactWhere || Object.keys(exactWhere).length === 0) { + this.logger.warn( + `Skipping deleted-record migration for ${table}: only exact _eq filters are supported`, + ); + continue; } - const count = existing.data?.length || 0; + let count = 0; + if (DatabaseConfigService.instanceIsMongoDb()) { + const result = await this.queryBuilderService + .getMongoDb() + .collection(table) + .deleteMany(exactWhere); + count = result.deletedCount || 0; + } else { + count = await this.queryBuilderService + .getKnex()(table) + .where(exactWhere) + .delete(); + } if (count > 0) { this.verbose(`Deleted ${count} record(s) from ${table}`); } @@ -156,6 +163,39 @@ export class DataMigrationService { } } + private toExactDeleteWhere( + filter: Record, + ): Record | null { + if (!filter || typeof filter !== 'object' || Array.isArray(filter)) { + return null; + } + + const where: Record = {}; + for (const [field, value] of Object.entries(filter)) { + if (!field || field.startsWith('_')) return null; + if ( + value && + typeof value === 'object' && + !Array.isArray(value) && + Object.keys(value).length === 1 && + Object.prototype.hasOwnProperty.call(value, '_eq') + ) { + where[field] = value._eq; + continue; + } + if ( + value === null || + ['string', 'number', 'boolean'].includes(typeof value) + ) { + where[field] = value; + continue; + } + return null; + } + + return where; + } + private async migrateTable( tableName: string, records: any | any[], @@ -256,8 +296,7 @@ export class DataMigrationService { delete data.mainTable; } else if (DatabaseConfigService.instanceIsMongoDb()) { const mainTableId = mainTable._id ?? mainTable.id; - data.mainTable = - typeof mainTableId === 'string' ? new ObjectId(mainTableId) : mainTableId; + data.mainTable = this.normalizeMongoId(mainTableId); } else { data.mainTableId = mainTable.id; delete data.mainTable; @@ -277,7 +316,9 @@ export class DataMigrationService { field === 'skipRoleGuardMethods' || field === 'availableMethods' ) { - const methodIds = await this.resolveMethodIds(methodNames as string[]); + const methodIds = await this.resolveMethodIds( + methodNames as string[], + ); if (DatabaseConfigService.instanceIsMongoDb()) { await this.updateMongoRouteMethodRelation( recordId, @@ -328,9 +369,7 @@ export class DataMigrationService { } if (cleared > 0) { - this.verbose( - `Cleared mainTable from ${cleared} custom route(s)`, - ); + this.verbose(`Cleared mainTable from ${cleared} custom route(s)`); } return cleared; } catch (error) { @@ -452,9 +491,16 @@ export class DataMigrationService { }; } - private toObjectId(value: any): ObjectId { + private toObjectId(value: any): any { + return this.normalizeMongoId(value); + } + + private normalizeMongoId(value: any): any { if (value instanceof ObjectId) return value; - return new ObjectId(String(value)); + if (typeof value === 'string' && ObjectId.isValid(value)) { + return new ObjectId(value); + } + return value; } private getUniqueFilter(_tableName: string, record: any): any | null { diff --git a/src/engines/bootstrap/services/metadata-provision-mongo.service.ts b/src/engines/bootstrap/services/metadata-provision-mongo.service.ts index b7bab8f5..7f938286 100644 --- a/src/engines/bootstrap/services/metadata-provision-mongo.service.ts +++ b/src/engines/bootstrap/services/metadata-provision-mongo.service.ts @@ -112,11 +112,53 @@ export class MetadataProvisionMongoService { relations: def.relations || [], }); const collection = db.collection(tableName); + const existingIndexes = await this.listMongoIndexes(collection); for (const spec of specs) { + const sameKeyIndex = existingIndexes.find((index) => + this.isEquivalentMongoIndex(index, spec), + ); + if ( + sameKeyIndex && + sameKeyIndex.name !== '_id_' && + sameKeyIndex.name !== spec.options?.name + ) { + await collection.dropIndex(sameKeyIndex.name); + const index = existingIndexes.findIndex( + (item) => item.name === sameKeyIndex.name, + ); + if (index >= 0) existingIndexes.splice(index, 1); + } await collection.createIndex(spec.keys, spec.options); } } } + + private async listMongoIndexes(collection: any): Promise { + try { + return await collection.listIndexes().toArray(); + } catch (error: any) { + if (error?.codeName === 'NamespaceNotFound' || error?.code === 26) { + return []; + } + throw error; + } + } + + private isEquivalentMongoIndex(existing: any, spec: any): boolean { + if ( + JSON.stringify(existing.key || {}) !== JSON.stringify(spec.keys || {}) + ) { + return false; + } + const options = spec.options || {}; + return ( + Boolean(existing.unique) === Boolean(options.unique) && + Boolean(existing.sparse) === Boolean(options.sparse) && + (existing.expireAfterSeconds ?? null) === + (options.expireAfterSeconds ?? null) + ); + } + private buildRecordFromColumns(data: any, columns: any[]): any { const record: any = {}; for (const col of columns) { diff --git a/src/engines/bootstrap/services/metadata-provision-sql.service.ts b/src/engines/bootstrap/services/metadata-provision-sql.service.ts index cc20c215..0f11db88 100644 --- a/src/engines/bootstrap/services/metadata-provision-sql.service.ts +++ b/src/engines/bootstrap/services/metadata-provision-sql.service.ts @@ -145,7 +145,10 @@ export class MetadataProvisionSqlService { }); } } else if (tableName === coreNames.relation) { - await this.ensureRelationDefinitionPhysicalColumns(qb, coreNames.relation); + await this.ensureRelationDefinitionPhysicalColumns( + qb, + coreNames.relation, + ); } } } @@ -221,19 +224,15 @@ export class MetadataProvisionSqlService { ); continue; } - const insertedId = await this.insertAndGetId( - trx, - coreNames.table, - { - name: rest.name, - isSystem: rest.isSystem || false, - isSingleRecord: rest.isSingleRecord || false, - alias: rest.alias, - description: rest.description, - uniques: JSON.stringify(rest.uniques || []), - indexes: JSON.stringify(rest.indexes || []), - }, - ); + const insertedId = await this.insertAndGetId(trx, coreNames.table, { + name: rest.name, + isSystem: rest.isSystem || false, + isSingleRecord: rest.isSingleRecord || false, + alias: rest.alias, + description: rest.description, + uniques: JSON.stringify(rest.uniques || []), + indexes: JSON.stringify(rest.indexes || []), + }); tableNameToId[name] = insertedId; } } diff --git a/src/engines/bootstrap/services/schema-healing.service.ts b/src/engines/bootstrap/services/schema-healing.service.ts index d3285ef0..d6ff7d9d 100644 --- a/src/engines/bootstrap/services/schema-healing.service.ts +++ b/src/engines/bootstrap/services/schema-healing.service.ts @@ -100,7 +100,8 @@ export class SchemaHealingService { async repairSystemPhysicalColumnsBeforeMetadataProvision(): Promise { if (DatabaseConfigService.instanceIsMongoDb()) return; - const repairedCount = await this.repairSqlSystemPhysicalColumnsFromSnapshot(); + const repairedCount = + await this.repairSqlSystemPhysicalColumnsFromSnapshot(); if (repairedCount > 0) { this.logger.log( `Repaired SQL system physical columns before metadata provision on ${repairedCount} column(s)`, @@ -139,9 +140,10 @@ export class SchemaHealingService { } } - private loadSnapshot(): - | Record - | null { + private loadSnapshot(): Record< + string, + { name?: string; isSystem?: boolean; columns?: any[] } + > | null { const snapshotPath = path.resolve(process.cwd(), 'data/snapshot.json'); if (!fs.existsSync(snapshotPath)) return null; return JSON.parse(fs.readFileSync(snapshotPath, 'utf8')) as Record< @@ -221,9 +223,16 @@ export class SchemaHealingService { for (const column of columns) { if (!column?.name || column.isPrimary) continue; if (await knex.schema.hasColumn(tableDef.name, column.name)) continue; - await knex.schema.alterTable(tableDef.name, (table: Knex.TableBuilder) => { - addColumnToTable(table as any, column, this.queryBuilderService.getDatabaseType()); - }); + await knex.schema.alterTable( + tableDef.name, + (table: Knex.TableBuilder) => { + addColumnToTable( + table as any, + column, + this.queryBuilderService.getDatabaseType(), + ); + }, + ); repaired++; } } @@ -269,7 +278,10 @@ export class SchemaHealingService { } private async repairSqlSystemColumnMetadataFromSnapshot( - snapshot: Record, + snapshot: Record< + string, + { name?: string; isSystem?: boolean; columns?: any[] } + >, ): Promise { const knex = this.queryBuilderService.getKnex(); if (!knex?.schema?.hasTable) return 0; @@ -292,7 +304,9 @@ export class SchemaHealingService { const existingColumns = await knex(coreNames.column) .where({ tableId: tableRecord.id }) .select('name'); - const existingNames = new Set(existingColumns.map((column: any) => column.name)); + const existingNames = new Set( + existingColumns.map((column: any) => column.name), + ); for (const column of tableDef.columns) { if (!column?.name || existingNames.has(column.name)) continue; @@ -320,7 +334,10 @@ export class SchemaHealingService { } private async repairMongoSystemColumnMetadataFromSnapshot( - snapshot: Record, + snapshot: Record< + string, + { name?: string; isSystem?: boolean; columns?: any[] } + >, ): Promise { const db = this.queryBuilderService.getMongoDb?.(); if (!db) return 0; @@ -342,7 +359,9 @@ export class SchemaHealingService { const existingColumns = await columnCollection .find({ table: tableRecord._id }, { projection: { name: 1 } }) .toArray(); - const existingNames = new Set(existingColumns.map((column: any) => column.name)); + const existingNames = new Set( + existingColumns.map((column: any) => column.name), + ); for (const column of tableDef.columns) { if (!column?.name || existingNames.has(column.name)) continue; @@ -426,7 +445,9 @@ export class SchemaHealingService { const owningUpdate = this.diffJunctionMetadata(rel, standard); if (Object.keys(owningUpdate).length > 0) { - await knex(coreNames.relation).where({ id: rel.id }).update(owningUpdate); + await knex(coreNames.relation) + .where({ id: rel.id }) + .update(owningUpdate); repaired++; } @@ -504,11 +525,22 @@ export class SchemaHealingService { const dbType = this.queryBuilderService.getDatabaseType?.() || 'postgres'; await knex.schema.createTable(junction.tableName, (table) => { - this.addSqlJunctionColumn(table, junction.sourceColumn, sourcePkType, dbType) - .notNullable(); - this.addSqlJunctionColumn(table, junction.targetColumn, targetPkType, dbType) - .notNullable(); - table.primary([junction.sourceColumn, junction.targetColumn], junction.primaryKeyName); + this.addSqlJunctionColumn( + table, + junction.sourceColumn, + sourcePkType, + dbType, + ).notNullable(); + this.addSqlJunctionColumn( + table, + junction.targetColumn, + targetPkType, + dbType, + ).notNullable(); + table.primary( + [junction.sourceColumn, junction.targetColumn], + junction.primaryKeyName, + ); table .foreign(junction.sourceColumn) .references('id') @@ -597,8 +629,11 @@ export class SchemaHealingService { private async getSqlPrimaryKeyType( tableName: string, ): Promise<'uuid' | 'varchar' | 'integer'> { - const table = await this.metadataCacheService.lookupTableByName?.(tableName); - const primaryColumn = table?.columns?.find((column: any) => column.isPrimary); + const table = + await this.metadataCacheService.lookupTableByName?.(tableName); + const primaryColumn = table?.columns?.find( + (column: any) => column.isPrimary, + ); const type = String(primaryColumn?.type || '').toLowerCase(); if (type === 'uuid' || type === 'uuidv4' || type.includes('uuid')) { return 'uuid'; @@ -612,7 +647,10 @@ export class SchemaHealingService { private async healMongoJunctionContracts(): Promise { const db = this.queryBuilderService.getMongoDb(); const coreNames = await this.systemCoreTableResolver.getNames(); - const relations = await db.collection(coreNames.relation).find({}).toArray(); + const relations = await db + .collection(coreNames.relation) + .find({}) + .toArray(); const tables = await db.collection(coreNames.table).find({}).toArray(); const tableById = new Map( tables.map((table: any) => [String(table._id), table]), @@ -694,7 +732,10 @@ export class SchemaHealingService { private async cleanupMongoLegacyJunctionCollections(): Promise { const db = this.queryBuilderService.getMongoDb(); const coreNames = await this.systemCoreTableResolver.getNames(); - const relations = await db.collection(coreNames.relation).find({}).toArray(); + const relations = await db + .collection(coreNames.relation) + .find({}) + .toArray(); const tables = await db.collection(coreNames.table).find({}).toArray(); const tableById = new Map( tables.map((table: any) => [String(table._id), table]), @@ -770,9 +811,11 @@ export class SchemaHealingService { input.junctionTableName, ); const legacyCandidates = this.getMongoJunctionLegacyCandidates(input); - let renamedFrom: - | { tableName: string; sourceColumn: string | null; targetColumn: string | null } - | null = null; + let renamedFrom: { + tableName: string; + sourceColumn: string | null; + targetColumn: string | null; + } | null = null; if (!standardExists) { const existingLegacy = await this.findExistingMongoJunctionCandidate( @@ -943,7 +986,9 @@ export class SchemaHealingService { } for (const legacy of input.legacyJunctions || []) { if (legacy.tableName === input.junctionTableName) continue; - if (candidates.some((candidate) => candidate.tableName === legacy.tableName)) { + if ( + candidates.some((candidate) => candidate.tableName === legacy.tableName) + ) { continue; } candidates.push({ @@ -1093,7 +1138,10 @@ export class SchemaHealingService { for (const [field, value] of Object.entries(missingFieldSet)) { const result = await db .collection(table.name) - .updateMany({ [field]: { $exists: false } }, { $set: { [field]: value } }); + .updateMany( + { [field]: { $exists: false } }, + { $set: { [field]: value } }, + ); modified += result.modifiedCount; } @@ -1114,12 +1162,14 @@ export class SchemaHealingService { private isMongoOwningRelation(rel: any): boolean { return ( - rel.type === 'many-to-one' || - (rel.type === 'one-to-one' && !rel.mappedBy) + rel.type === 'many-to-one' || (rel.type === 'one-to-one' && !rel.mappedBy) ); } - private getMongoRelationForeignKeyColumn(rel: any, owningRel: any): string | null { + private getMongoRelationForeignKeyColumn( + rel: any, + owningRel: any, + ): string | null { if (this.isMongoOwningRelation(rel)) { return rel.propertyName || null; } @@ -1134,7 +1184,10 @@ export class SchemaHealingService { } private getMongoColumnDefaultValue(column: any): any { - if (this.hasOwn(column, 'defaultValue') && column.defaultValue !== undefined) { + if ( + this.hasOwn(column, 'defaultValue') && + column.defaultValue !== undefined + ) { return column.defaultValue; } return null; @@ -1230,7 +1283,9 @@ export class SchemaHealingService { await columnCollection.updateOne( { _id: columnId }, - { $set: { name: MONGO_PRIMARY_KEY_NAME, type: MONGO_PRIMARY_KEY_TYPE } }, + { + $set: { name: MONGO_PRIMARY_KEY_NAME, type: MONGO_PRIMARY_KEY_TYPE }, + }, ); repaired++; this.logger.log( diff --git a/src/engines/cache/services/websocket-cache.service.ts b/src/engines/cache/services/websocket-cache.service.ts index 14a5b46d..31a5443f 100644 --- a/src/engines/cache/services/websocket-cache.service.ts +++ b/src/engines/cache/services/websocket-cache.service.ts @@ -55,11 +55,12 @@ export class WebsocketCacheService extends BaseCacheService< protected async loadFromDb(): Promise { const result = await this.queryBuilderService.find({ table: 'enfyra_websocket', - fields: ['*', 'events.*'], + fields: ['*'], filter: { isEnabled: { _eq: true } }, }); const gateways = result.data || []; + await this.attachEvents(gateways); await this.prepareGateways(gateways); @@ -144,7 +145,7 @@ export class WebsocketCacheService extends BaseCacheService< const idField = this.queryBuilderService.isMongoDb() ? '_id' : 'id'; const result = await this.queryBuilderService.find({ table: 'enfyra_websocket', - fields: ['*', 'events.*'], + fields: ['*'], filter: { _and: [ { isEnabled: { _eq: true } }, @@ -155,6 +156,7 @@ export class WebsocketCacheService extends BaseCacheService< }); const updatedGateways = result?.data ?? []; + await this.attachEvents(updatedGateways); await this.prepareGateways(updatedGateways); const idSet = new Set(gatewayIds.map(String)); @@ -178,8 +180,7 @@ export class WebsocketCacheService extends BaseCacheService< const gatewayIds = new Map(); for (const row of result?.data ?? []) { - const gatewayId = - row?.gateway?._id ?? row?.gateway?.id ?? row?.gatewayId; + const gatewayId = row?.gateway?._id ?? row?.gateway?.id ?? row?.gatewayId; if (gatewayId != null) gatewayIds.set(String(gatewayId), gatewayId); } @@ -199,6 +200,45 @@ export class WebsocketCacheService extends BaseCacheService< return [...gatewayIds.values()]; } + private async attachEvents(gateways: any[]): Promise { + if (!gateways.length) return; + + const gatewayIds = gateways + .map((gateway) => DatabaseConfigService.getRecordId(gateway)) + .filter((id) => id !== undefined && id !== null); + if (!gatewayIds.length) return; + + const gatewayById = new Map(); + for (const gateway of gateways) { + gateway.events = []; + const id = DatabaseConfigService.getRecordId(gateway); + if (id !== undefined && id !== null) gatewayById.set(String(id), gateway); + } + + const eventResult = await this.queryBuilderService.find({ + table: 'enfyra_websocket_event', + fields: ['*', 'gateway.id', 'gateway._id'], + filter: { + _and: [ + { isEnabled: { _eq: true } }, + { + gateway: { + [DatabaseConfigService.getPkField()]: { _in: gatewayIds }, + }, + }, + ], + }, + limit: Math.max(gatewayIds.length * 100, 100), + }); + + for (const event of eventResult?.data ?? []) { + const gatewayId = + event?.gateway?._id ?? event?.gateway?.id ?? event?.gatewayId; + const gateway = gatewayById.get(String(gatewayId)); + if (gateway) gateway.events.push(event); + } + } + private async prepareGateways(gateways: any[]): Promise { for (const gateway of gateways) { const normalizedGateway = normalizeScriptRecord( diff --git a/src/modules/websocket/gateway/dynamic-websocket.gateway.ts b/src/modules/websocket/gateway/dynamic-websocket.gateway.ts index 3b39f9ac..49de0d58 100644 --- a/src/modules/websocket/gateway/dynamic-websocket.gateway.ts +++ b/src/modules/websocket/gateway/dynamic-websocket.gateway.ts @@ -101,7 +101,9 @@ export class DynamicWebSocketGateway { private setupEventListeners() { this.eventEmitter.on(`${CACHE_IDENTIFIERS.WEBSOCKET}_LOADED`, () => { - const reload = this.server ? this.reloadGateways() : this.registerGateways(); + const reload = this.server + ? this.reloadGateways() + : this.registerGateways(); reload.catch((error) => this.logger.error('Failed to reload websocket gateways:', error), ); @@ -275,48 +277,20 @@ export class DynamicWebSocketGateway { socket.disconnect(true); return; } - if (gatewayData.requireAuth) { - const user = await this.loadSocketUser(socket); - if (!user) { - this.logger.warn( - `Connection rejected: authenticated user not found for ${gatewayData.path}`, - ); - socket.emit('auth_error', { - code: 'AUTH_USER_NOT_FOUND', - message: 'Authenticated user not found', - }); - socket.disconnect(true); - return; - } - } - if (path === ENFYRA_ADMIN_WEBSOCKET_NAMESPACE) { - try { - await this.setupAdminSocket(socket); - } catch (error) { - this.logger.warn( - `Admin socket setup failed: ${getErrorMessage(error)}`, - ); - } - } - const connectionScript = - this.builtInRegistry.getConnectionScript(path) ?? - gatewayData.connectionHandlerScript; - if (connectionScript) { - try { - await this.runConnectionScript(socket, gatewayData, connectionScript); - } catch (error) { - this.logger.error( - `Connection handler failed for ${socket.id}:`, - error, - ); - socket.disconnect(); - return; - } - } + let releaseConnectionReady!: () => void; + let connectionReadyError: any = null; + const connectionReady = new Promise((resolve) => { + releaseConnectionReady = resolve; + }); const roomName = socket.data.userId ? `user_${socket.data.userId}` : `user_${socket.id}`; socket.join(roomName); + this.writeEventTrace({ + type: 'ws_register_socket_events', + path: gatewayData.path, + events: (gatewayData.events ?? []).map((event: any) => event.eventName), + }); for (const event of gatewayData.events) { const eventName = event.eventName; socket.on(eventName, async (payload: any, ack: any) => { @@ -324,6 +298,8 @@ export class DynamicWebSocketGateway { const socketReceivedAt = Date.now(); let ackSentAt: number | null = null; try { + await connectionReady; + if (connectionReadyError) throw connectionReadyError; const builtInScript = this.builtInRegistry.getEventScript( path, eventName, @@ -332,6 +308,10 @@ export class DynamicWebSocketGateway { const script = builtInScript ?? event.handlerScript; if (script) { + if (typeof ack === 'function') { + ack({ accepted: true, queued: true, requestId, eventName }); + ackSentAt = Date.now(); + } await this.runEventScript({ requestId, socket, @@ -343,10 +323,6 @@ export class DynamicWebSocketGateway { socketReceivedAt, ackSentAt, }); - if (typeof ack === 'function') { - ack({ accepted: true, queued: false, requestId, eventName }); - ackSentAt = Date.now(); - } return; } @@ -368,7 +344,7 @@ export class DynamicWebSocketGateway { return; } catch (error) { this.logger.error(`Event handler failed for ${eventName}:`, error); - if (typeof ack === 'function') { + if (typeof ack === 'function' && ackSentAt === null) { ack({ accepted: false, queued: false, @@ -389,6 +365,60 @@ export class DynamicWebSocketGateway { } }); } + const failConnectionReady = (error: any) => { + connectionReadyError = error; + releaseConnectionReady(); + }; + try { + if (gatewayData.requireAuth) { + const user = await this.loadSocketUser(socket); + if (!user) { + this.logger.warn( + `Connection rejected: authenticated user not found for ${gatewayData.path}`, + ); + socket.emit('auth_error', { + code: 'AUTH_USER_NOT_FOUND', + message: 'Authenticated user not found', + }); + failConnectionReady(new Error('Authenticated user not found')); + socket.disconnect(true); + return; + } + } + if (path === ENFYRA_ADMIN_WEBSOCKET_NAMESPACE) { + try { + await this.setupAdminSocket(socket); + } catch (error) { + this.logger.warn( + `Admin socket setup failed: ${getErrorMessage(error)}`, + ); + } + } + const connectionScript = + this.builtInRegistry.getConnectionScript(path) ?? + gatewayData.connectionHandlerScript; + if (connectionScript) { + try { + await this.runConnectionScript( + socket, + gatewayData, + connectionScript, + ); + } catch (error) { + this.logger.error( + `Connection handler failed for ${socket.id}:`, + error, + ); + failConnectionReady(error); + socket.disconnect(); + return; + } + } + releaseConnectionReady(); + } catch (error) { + failConnectionReady(error); + socket.disconnect(); + } }); } @@ -412,11 +442,7 @@ export class DynamicWebSocketGateway { }); ctx.$repos = this.lazyRef.repoRegistryService.createReposProxy(ctx); ctx.$trigger = (flowIdOrName: string | number, payload?: any) => - this.lazyRef.flowService.trigger( - flowIdOrName, - payload, - user, - ); + this.lazyRef.flowService.trigger(flowIdOrName, payload, user); await this.lazyRef.executorEngineService.run( script, @@ -459,18 +485,21 @@ export class DynamicWebSocketGateway { }); ctx.$repos = this.lazyRef.repoRegistryService.createReposProxy(ctx); ctx.$trigger = (flowIdOrName: string | number, payload?: any) => - this.lazyRef.flowService.trigger( - flowIdOrName, - payload, - user, - ); + this.lazyRef.flowService.trigger(flowIdOrName, payload, user); try { - await this.lazyRef.executorEngineService.run( + const result = await this.lazyRef.executorEngineService.run( script, ctx, timeout, ); + ctx.$socket?.reply?.('ws:result', { + requestId, + eventName, + success: true, + result, + logs: ctx.$share?.$logs || [], + }); const endedAt = Date.now(); this.writeEventTrace({ type: 'ws_event', @@ -692,13 +721,23 @@ export class DynamicWebSocketGateway { this.server.of(path).emit(event, data); } - async emitToNamespaceRoom(path: string, room: string, event: string, data: any) { + async emitToNamespaceRoom( + path: string, + room: string, + event: string, + data: any, + ) { const localSize = this.localNamespaceRoomSize(path, room); if (localSize < this.roomFanoutChunkThreshold) { this.server.of(path).to(room).emit(event, data); return; } - const backpressure = this.enqueueChunkedLocalRoomEmit(path, room, event, data); + const backpressure = this.enqueueChunkedLocalRoomEmit( + path, + room, + event, + data, + ); this.emitRoomFanoutCommand(path, room, event, data); await backpressure; } @@ -710,7 +749,13 @@ export class DynamicWebSocketGateway { data: any, ) { try { - this.server.serverSideEmit(this.roomFanoutCommand, path, room, event, data); + this.server.serverSideEmit( + this.roomFanoutCommand, + path, + room, + event, + data, + ); } catch (error) { this.logger.error( `Failed to publish chunked room fanout for ${path}:${room}:`, diff --git a/src/modules/websocket/queues/event-queue.service.ts b/src/modules/websocket/queues/event-queue.service.ts index 593eb35a..9013967d 100644 --- a/src/modules/websocket/queues/event-queue.service.ts +++ b/src/modules/websocket/queues/event-queue.service.ts @@ -114,7 +114,14 @@ export class EventQueueService { ); try { - await this.executorEngineService.run(script, ctx, timeout); + const result = await this.executorEngineService.run(script, ctx, timeout); + ctx.$socket?.reply?.('ws:result', { + requestId, + eventName, + success: true, + result, + logs: ctx.$share?.$logs || [], + }); const executorEndedAt = Date.now(); this.writeTrace({ type: 'ws_event', diff --git a/test/query-builder.e2e.js b/test/query-builder.e2e.js index 6a1db362..ff57b619 100644 --- a/test/query-builder.e2e.js +++ b/test/query-builder.e2e.js @@ -41,8 +41,15 @@ const db = knex({ pool: { min: 1, max: 10 }, }); -const distPath = path.join(__dirname, '..', 'dist', 'src'); -const SqlQueryExecutor = require(path.join(distPath, 'infrastructure', 'query-builder', 'executors', 'sql-query-executor')).SqlQueryExecutor; +const distPath = path.join(__dirname, '..', 'dist'); +const SqlQueryExecutor = require(path.join( + distPath, + 'kernel', + 'query', + 'query-builder', + 'executors', + 'sql-query-executor', +)).SqlQueryExecutor; let testsPassed = 0; let testsFailed = 0; @@ -826,4 +833,4 @@ async function testEdgeCases() { runTests().catch(e => { console.error('Error:', e); process.exit(1); -}); \ No newline at end of file +}); diff --git a/test/websocket/event-script-execution.spec.ts b/test/websocket/event-script-execution.spec.ts index bb84b495..001feec9 100644 --- a/test/websocket/event-script-execution.spec.ts +++ b/test/websocket/event-script-execution.spec.ts @@ -144,7 +144,7 @@ describe('DynamicWebSocketGateway event script execution — real socket E2E', ( expect(ack).toMatchObject({ accepted: true, - queued: false, + queued: true, eventName: 'profile:update', }); await expect(received).resolves.toEqual({ From 9793b0525e4b88afe86df642bb53c002451eb0f5 Mon Sep 17 00:00:00 2001 From: dothinh115 Date: Fri, 19 Jun 2026 09:43:01 +0700 Subject: [PATCH 3/5] feat: implement field guard for protected user fields in /me updates --- data/default-data.json | 13 +++++ src/modules/me/services/me.service.ts | 67 ++++++++++++++++++++++- test/modules/me-service.spec.ts | 78 +++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/data/default-data.json b/data/default-data.json index 2789ee38..d1bcc3d8 100644 --- a/data/default-data.json +++ b/data/default-data.json @@ -887,6 +887,19 @@ "sourceCode": "if (@BODY.password) { @BODY.password = await @HELPERS.$bcrypt.hash(@BODY.password); }", "scriptLanguage": "typescript" }, + { + "isEnabled": true, + "isGlobal": false, + "name": "Me route protected field guard", + "isSystem": true, + "route": "/me", + "methods": [ + "PATCH" + ], + "priority": 100, + "sourceCode": "const body = @BODY || {};\nconst allowedProtectedSelfFields = new Set(['password']);\nconst alwaysBlocked = new Set(['id', '_id', 'createdAt', 'updatedAt', 'roleId']);\nconst tableResult = await @REPOS.enfyra_table.find({ filter: { name: { _eq: 'enfyra_user' } }, fields: ['id', 'name', 'columns.name', 'columns.isSystem', 'columns.isPublished', 'columns.isUpdatable', 'columns.isPrimary', 'relations.propertyName', 'relations.isSystem'], limit: 1 });\nconst userTable = tableResult.data?.[0];\nconst columns = Array.isArray(userTable?.columns) ? userTable.columns : [];\nconst relations = Array.isArray(userTable?.relations) ? userTable.relations : [];\nconst blocked = [];\nfor (const key of Object.keys(body)) {\n if (allowedProtectedSelfFields.has(key)) continue;\n if (alwaysBlocked.has(key)) { blocked.push(key); continue; }\n const column = columns.find((item) => item?.name === key);\n if (column) {\n if (column.isPrimary || column.isSystem || column.isPublished === false || column.isUpdatable === false) blocked.push(key);\n continue;\n }\n const relation = relations.find((item) => item?.propertyName === key);\n if (relation?.isSystem) blocked.push(key);\n}\nif (blocked.length) @THROW400('Protected user fields cannot be updated through /me: ' + [...new Set(blocked)].join(', '));", + "scriptLanguage": "typescript" + }, { "isEnabled": true, "isGlobal": false, diff --git a/src/modules/me/services/me.service.ts b/src/modules/me/services/me.service.ts index fcdc7368..a88ceb95 100644 --- a/src/modules/me/services/me.service.ts +++ b/src/modules/me/services/me.service.ts @@ -1,4 +1,4 @@ -import { UnauthorizedException } from '../../../shared/errors'; +import { BadRequestException, UnauthorizedException } from '../../../shared/errors'; import { Request } from 'express'; import { RepoRegistryService } from '../../../engines/cache'; import { DynamicContextFactory } from '../../../shared/services'; @@ -60,8 +60,73 @@ export class MeService { return result; } + private async assertMeUpdateAllowed(body: any, req: Request & { routeData?: any }) { + if (!body || typeof body !== 'object') return; + const context = this.getRepoContext(req); + const tableRepo = context.$repos?.enfyra_table; + if (!tableRepo) { + throw new Error('Repository not found in route context'); + } + const tableResult = await tableRepo.find({ + filter: { name: { _eq: 'enfyra_user' } }, + fields: [ + 'id', + 'name', + 'columns.name', + 'columns.isSystem', + 'columns.isPublished', + 'columns.isUpdatable', + 'columns.isPrimary', + 'relations.propertyName', + 'relations.isSystem', + ], + limit: 1, + }); + const table = tableResult?.data?.[0]; + const columns = Array.isArray(table?.columns) ? table.columns : []; + const relations = Array.isArray(table?.relations) ? table.relations : []; + const allowedProtectedSelfFields = new Set(['password']); + const alwaysBlocked = new Set([ + 'id', + '_id', + 'createdAt', + 'updatedAt', + 'roleId', + ]); + const blocked: string[] = []; + for (const key of Object.keys(body)) { + if (allowedProtectedSelfFields.has(key)) continue; + if (alwaysBlocked.has(key)) { + blocked.push(key); + continue; + } + const column = columns.find((item: any) => item?.name === key); + if (column) { + if ( + column.isPrimary || + column.isSystem || + column.isPublished === false || + column.isUpdatable === false + ) { + blocked.push(key); + } + continue; + } + const relation = relations.find((item: any) => item?.propertyName === key); + if (relation?.isSystem) blocked.push(key); + } + if (blocked.length > 0) { + throw new BadRequestException( + `Protected user fields cannot be updated through /me: ${[ + ...new Set(blocked), + ].join(', ')}`, + ); + } + } + async update(body: any, req: Request & { user: any; routeData?: any }) { if (!req.user) throw new UnauthorizedException(); + await this.assertMeUpdateAllowed(body, req); const repo = this.getSecureRepo(req, 'enfyra_user'); if (!repo) { throw new Error('Repository not found in route context'); diff --git a/test/modules/me-service.spec.ts b/test/modules/me-service.spec.ts index 655bc669..c4927241 100644 --- a/test/modules/me-service.spec.ts +++ b/test/modules/me-service.spec.ts @@ -52,4 +52,82 @@ describe('MeService', () => { loginProvider: 'google', }); }); + + + it('rejects protected user fields on /me updates while allowing profile fields', async () => { + const userRepo = { + update: vi.fn(async ({ data }) => ({ data: [{ id: 'user-1', ...data }] })), + }; + const tableRepo = { + find: vi.fn(async () => ({ + data: [ + { + id: 1, + name: 'enfyra_user', + columns: [ + { name: 'id', isPrimary: true, isSystem: true }, + { name: 'email', isSystem: true, isPublished: true, isUpdatable: true }, + { name: 'password', isSystem: true, isPublished: false, isUpdatable: true }, + { name: 'isRootAdmin', isSystem: true, isPublished: true, isUpdatable: false }, + { name: 'isSystem', isSystem: true, isPublished: true, isUpdatable: true }, + { name: 'emailVerificationStatus', isSystem: true, isPublished: true, isUpdatable: true }, + { name: 'fullName', isSystem: false, isPublished: true, isUpdatable: true }, + { name: 'secretNote', isSystem: false, isPublished: false, isUpdatable: true }, + ], + relations: [{ propertyName: 'role', isSystem: true }], + }, + ], + })), + }; + const context: any = {}; + const service = new MeService({ + dynamicContextFactory: { + createHttp: vi.fn(() => context), + } as any, + repoRegistryService: { + createReposProxy: vi.fn(() => ({ + enfyra_table: tableRepo, + secure: { enfyra_user: userRepo }, + })), + } as any, + }); + const req = { + user: { id: 'user-1' }, + method: 'PATCH', + url: '/me', + originalUrl: '/me', + path: '/me', + query: {}, + params: {}, + headers: {}, + hostname: 'example.test', + protocol: 'https', + ip: '127.0.0.1', + } as any; + + await expect(service.update({ role: { id: 1 } }, req)).rejects.toThrow( + 'Protected user fields cannot be updated through /me: role', + ); + await expect(service.update({ isSystem: true }, req)).rejects.toThrow( + 'Protected user fields cannot be updated through /me: isSystem', + ); + await expect(service.update({ email: 'new@test.dev' }, req)).rejects.toThrow( + 'Protected user fields cannot be updated through /me: email', + ); + await expect(service.update({ secretNote: 'x' }, req)).rejects.toThrow( + 'Protected user fields cannot be updated through /me: secretNote', + ); + + await expect( + service.update({ fullName: 'Safe Profile', password: 'hashed' }, req), + ).resolves.toEqual({ + data: [{ id: 'user-1', fullName: 'Safe Profile', password: 'hashed' }], + }); + expect(userRepo.update).toHaveBeenCalledTimes(1); + expect(userRepo.update).toHaveBeenCalledWith({ + id: 'user-1', + data: { fullName: 'Safe Profile', password: 'hashed' }, + }); + }); + }); From 8013932d90a5b834ce503b1a094db9cd5bc25106 Mon Sep 17 00:00:00 2001 From: dothinh115 Date: Fri, 19 Jun 2026 10:12:48 +0700 Subject: [PATCH 4/5] feat: add route permission checks for GraphQL mutations in DynamicResolver --- .../graphql/resolvers/dynamic.resolver.ts | 35 +++++ .../security/graphql-route-permission.spec.ts | 142 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 test/security/graphql-route-permission.spec.ts diff --git a/src/modules/graphql/resolvers/dynamic.resolver.ts b/src/modules/graphql/resolvers/dynamic.resolver.ts index a035fb8d..a66c1679 100644 --- a/src/modules/graphql/resolvers/dynamic.resolver.ts +++ b/src/modules/graphql/resolvers/dynamic.resolver.ts @@ -11,7 +11,9 @@ import { RepoRegistryService, GuardCacheService, GuardEvaluatorService, + RouteCacheService, } from '../../../engines/cache'; +import { PolicyService, isPolicyDeny } from '../../../domain/policy'; import { resolveClientIpFromRequest } from '../../../shared/utils/client-ip.util'; import { isMetadataTable } from '../../../shared/utils/cache-events.constants'; import { loadUserWithRole } from '../../../shared/utils/load-user-with-role.util'; @@ -23,6 +25,8 @@ export class DynamicResolver { private readonly repoRegistryService: RepoRegistryService; private readonly guardCacheService: GuardCacheService; private readonly guardEvaluatorService: GuardEvaluatorService; + private readonly routeCacheService: RouteCacheService; + private readonly policyService: PolicyService; private readonly envService: EnvService; private readonly dynamicContextFactory: DynamicContextFactory; @@ -33,6 +37,8 @@ export class DynamicResolver { repoRegistryService: RepoRegistryService; guardCacheService: GuardCacheService; guardEvaluatorService: GuardEvaluatorService; + routeCacheService: RouteCacheService; + policyService: PolicyService; envService: EnvService; dynamicContextFactory: DynamicContextFactory; }) { @@ -42,6 +48,8 @@ export class DynamicResolver { this.repoRegistryService = deps.repoRegistryService; this.guardCacheService = deps.guardCacheService; this.guardEvaluatorService = deps.guardEvaluatorService; + this.routeCacheService = deps.routeCacheService; + this.policyService = deps.policyService; this.envService = deps.envService; this.dynamicContextFactory = deps.dynamicContextFactory; } @@ -63,6 +71,7 @@ export class DynamicResolver { tableName, 'GQL_QUERY', context, + 'GET', ); const selections = info.fieldNodes?.[0]?.selectionSet?.selections || []; const fullFieldPicker = convertFieldNodesToFieldPicker(selections); @@ -126,10 +135,12 @@ export class DynamicResolver { } const operation = match[1]; const tableName = match[2]; + const routeAccessMethod = this.graphqlOperationToHttp(operation); const { user } = await this.middleware( tableName, 'GQL_MUTATION', context, + routeAccessMethod, ); const handlerCtx: any = this.dynamicContextFactory.createGraphql({ request: context.request, @@ -173,6 +184,7 @@ export class DynamicResolver { mainTableName: string, method: string, context: any, + routeAccessMethod: string, ) { if (!mainTableName) { throwGqlError('400', 'Missing table name'); @@ -206,6 +218,7 @@ export class DynamicResolver { const userId = user && !user.isAnonymous ? user._id || user.id || null : null; await this.runGuards('post_auth', routePath, method, clientIp, userId); + await this.assertRouteAccess(routePath, routeAccessMethod, user); return { user, @@ -213,6 +226,28 @@ export class DynamicResolver { }; } + private graphqlOperationToHttp(operation: string): string { + if (operation === 'create') return 'POST'; + if (operation === 'update') return 'PATCH'; + if (operation === 'delete') return 'DELETE'; + return 'GET'; + } + + private async assertRouteAccess(routePath: string, method: string, user: any) { + const match = await this.routeCacheService.matchRoute(method, routePath); + if (!match?.route) { + throwGqlError('403', 'Forbidden'); + } + const decision = this.policyService.checkRequestAccess({ + method, + routeData: match.route, + user, + }); + if (isPolicyDeny(decision)) { + throwGqlError(String(decision.statusCode || 403), decision.message); + } + } + private async checkAccess( tableName: string, method: string, diff --git a/test/security/graphql-route-permission.spec.ts b/test/security/graphql-route-permission.spec.ts new file mode 100644 index 00000000..b568a88f --- /dev/null +++ b/test/security/graphql-route-permission.spec.ts @@ -0,0 +1,142 @@ +import jwt from 'jsonwebtoken'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { DynamicResolver } from '../../src/modules/graphql/resolvers/dynamic.resolver'; + +const mocks = vi.hoisted(() => ({ + loadUserWithRole: vi.fn(), +})); + +vi.mock('../../src/shared/utils/load-user-with-role.util', () => ({ + loadUserWithRole: mocks.loadUserWithRole, +})); + +function makeResolver(overrides: Record = {}) { + const executorEngineService = { + run: vi.fn().mockResolvedValue({ data: [{ id: '1', title: 'Updated' }] }), + }; + const routeCacheService = { + matchRoute: vi.fn(), + }; + const resolver = new DynamicResolver({ + queryBuilderService: {}, + executorEngineService, + gqlDefinitionCacheService: { + isEnabledForTable: vi.fn().mockResolvedValue(true), + }, + repoRegistryService: { + createReposProxy: vi.fn().mockReturnValue({ + main: {}, + }), + }, + guardCacheService: { + ensureGuardsLoaded: vi.fn().mockResolvedValue(undefined), + getGuardsForRoute: vi.fn().mockResolvedValue([]), + }, + guardEvaluatorService: { + evaluateGuard: vi.fn(), + }, + routeCacheService, + policyService: { + checkRequestAccess: vi.fn().mockReturnValue({ allow: true }), + }, + envService: { + get: vi.fn().mockReturnValue('test-secret'), + }, + dynamicContextFactory: { + createGraphql: vi.fn().mockImplementation((input) => ({ + $user: input.user, + $body: input.body, + $params: input.params, + })), + }, + ...overrides, + } as any); + + return { resolver, executorEngineService, routeCacheService }; +} + +function authContext() { + const token = jwt.sign({ id: 'user-1' }, 'test-secret'); + return { + request: { + headers: new Map([['authorization', `Bearer ${token}`]]), + }, + }; +} + +describe('DynamicResolver route permissions', () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.loadUserWithRole.mockResolvedValue({ + id: 'user-1', + role: { id: 'role-user' }, + isRootAdmin: false, + }); + }); + + it('checks PATCH route permission for GraphQL update mutations', async () => { + const policyService = { + checkRequestAccess: vi.fn().mockReturnValue({ + allow: false, + statusCode: 403, + message: 'Forbidden', + }), + }; + const { resolver, executorEngineService, routeCacheService } = makeResolver({ + policyService, + }); + routeCacheService.matchRoute.mockResolvedValue({ + route: { + path: '/posts', + routePermissions: [], + publicMethods: [], + skipRoleGuardMethods: [], + }, + params: {}, + }); + + await expect( + resolver.dynamicMutationResolver( + 'update_posts', + { id: '1', input: { title: 'Blocked' } }, + authContext(), + {}, + ), + ).rejects.toMatchObject({ + extensions: { code: 'MUTATION_ERROR' }, + }); + + expect(routeCacheService.matchRoute).toHaveBeenCalledWith('PATCH', '/posts'); + expect(policyService.checkRequestAccess).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'PATCH', + user: expect.objectContaining({ id: 'user-1' }), + }), + ); + expect(executorEngineService.run).not.toHaveBeenCalled(); + }); + + it('checks DELETE route permission for GraphQL delete mutations', async () => { + const { resolver, routeCacheService } = makeResolver(); + routeCacheService.matchRoute.mockResolvedValue({ + route: { + path: '/posts', + routePermissions: [ + { methods: [{ name: 'DELETE' }], role: { id: 'role-user' } }, + ], + publicMethods: [], + skipRoleGuardMethods: [], + }, + params: {}, + }); + + await resolver.dynamicMutationResolver( + 'delete_posts', + { id: '1' }, + authContext(), + {}, + ); + + expect(routeCacheService.matchRoute).toHaveBeenCalledWith('DELETE', '/posts'); + }); +}); From e64dbb5eaed0c4bd025fda66739b574099b2ac8b Mon Sep 17 00:00:00 2001 From: dothinh115 Date: Fri, 19 Jun 2026 10:55:54 +0700 Subject: [PATCH 5/5] feat: implement sensitive data redaction in logger and add corresponding tests --- src/http/routes/file.routes.ts | 2 + src/shared/logger.ts | 67 +++++++++++++++++++++++++++++++--- test/shared/logger.spec.ts | 47 ++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 5 deletions(-) diff --git a/src/http/routes/file.routes.ts b/src/http/routes/file.routes.ts index 54fb9f5f..22915724 100644 --- a/src/http/routes/file.routes.ts +++ b/src/http/routes/file.routes.ts @@ -37,6 +37,7 @@ export function registerFileRoutes( app: Express, container: AwilixContainer, ) { + // codeql[js/missing-rate-limiting] Built-in routes use metadata guards for admin-configured rate limit policies. app.post('/enfyra_file', async (req: any, res: Response) => { const fileManagementService = req.scope?.cradle?.fileManagementService ?? @@ -94,6 +95,7 @@ export function registerFileRoutes( return res.json(result); }); + // codeql[js/missing-rate-limiting] Built-in routes use metadata guards for admin-configured rate limit policies. app.patch('/enfyra_file/:id', async (req: any, res: Response) => { const fileManagementService = req.scope?.cradle?.fileManagementService ?? diff --git a/src/shared/logger.ts b/src/shared/logger.ts index 5c45576b..2a9c3076 100644 --- a/src/shared/logger.ts +++ b/src/shared/logger.ts @@ -64,6 +64,60 @@ const LOG_LEVEL_PRIORITY: Record = { trace: 4, }; +const REDACTED = '[REDACTED]'; +const SENSITIVE_KEY_PATTERN = + /(?:password|passwd|pwd|secret|token|api[_-]?key|authorization|cookie|credential|private[_-]?key|client[_-]?secret|oauthconfig)/i; +const SENSITIVE_TEXT_PATTERNS: Array<[RegExp, string]> = [ + [/\b(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, `$1${REDACTED}`], + [/\b(efy_pat_)[A-Za-z0-9._~+/=-]+/gi, `$1${REDACTED}`], + [ + /\b([A-Za-z0-9_-]{20,})\.([A-Za-z0-9_-]{20,})\.([A-Za-z0-9_-]{20,})\b/g, + REDACTED, + ], + [ + /((?:client[_-]?secret|access[_-]?token|refresh[_-]?token|api[_-]?key|secret|token|password)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, + `$1${REDACTED}`, + ], +]; + +function redactSensitiveText(value: string): string { + return SENSITIVE_TEXT_PATTERNS.reduce( + (current, [pattern, replacement]) => current.replace(pattern, replacement), + value, + ); +} + +function sanitizeLogValue(value: any, seen = new WeakSet()): any { + if (typeof value === 'string') { + return redactSensitiveText(value); + } + if (value === null || typeof value !== 'object') { + return value; + } + if (value instanceof Error) { + return { + name: value.name, + message: redactSensitiveText(value.message), + stack: value.stack ? redactSensitiveText(value.stack) : undefined, + }; + } + if (seen.has(value)) { + return '[Circular]'; + } + seen.add(value); + if (Array.isArray(value)) { + return value.map((item) => sanitizeLogValue(item, seen)); + } + return Object.fromEntries( + Object.entries(value).map(([key, child]) => [ + key, + SENSITIVE_KEY_PATTERN.test(key) + ? REDACTED + : sanitizeLogValue(child, seen), + ]), + ); +} + const BOOTSTRAP_QUIET_CONTEXTS = new Set([ 'TableDefinitionProcessor', 'ColumnDefinitionProcessor', @@ -206,12 +260,13 @@ function printPretty( const ctxColor = emphasize ? iconColor : SERVICE; const ctxStr = context ? `${ctxColor}${context}${RESET} ` : ''; const corrStr = correlationId ? `${CORR}[${correlationId}]${RESET} ` : ''; - const msgColored = emphasize ? `${iconColor}${msg}${RESET}` : msg; + const safeMsg = redactSensitiveText(msg); + const msgColored = emphasize ? `${iconColor}${safeMsg}${RESET}` : safeMsg; const line = `${BRACKET}[${time}]${RESET} ${iconColor}${icon}${RESET} ${ctxStr}${corrStr}${ARROW} ${msgColored}`; const target = level === 'error' ? console.error : console.log; target(line); if (trace) { - console.error(`${DIM} ${trace}${RESET}`); + console.error(`${DIM} ${redactSensitiveText(trace)}${RESET}`); } } @@ -323,10 +378,12 @@ export class Logger { } const pinoLevel = PINO_LEVEL[level]; - pinoInstance[pinoLevel](meta, msg); + const safeMeta = sanitizeLogValue(meta); + const safeMsg = redactSensitiveText(msg); + pinoInstance[pinoLevel](safeMeta, safeMsg); const correlationId = logStore.getStore()?.correlationId; - const consoleTrace = level === 'error' ? meta.stack : undefined; - printPretty(level, msg, ctx, correlationId, consoleTrace); + const consoleTrace = level === 'error' ? safeMeta.stack : undefined; + printPretty(level, safeMsg, ctx, correlationId, consoleTrace); } } diff --git a/test/shared/logger.spec.ts b/test/shared/logger.spec.ts index c7309ba5..6d76e9e7 100644 --- a/test/shared/logger.spec.ts +++ b/test/shared/logger.spec.ts @@ -152,6 +152,53 @@ describe('Logger — object trace parameter', () => { }); }); +describe('Logger — sensitive data redaction', () => { + let cap: ReturnType; + beforeEach(() => { + cap = captureCalls(); + }); + afterEach(() => cap.restore()); + + it('redacts sensitive object keys before pino emit', () => { + const logger = new Logger('OAuth'); + logger.error({ + message: 'OAuth failed', + oauthConfig: { + clientId: 'public-client', + clientSecret: 'super-secret', + }, + nested: { + refreshToken: 'refresh-secret', + safe: 'visible', + }, + }); + + const log = JSON.stringify(cap.calls[0]); + expect(log).toContain('[REDACTED]'); + expect(log).toContain('visible'); + expect(log).not.toContain('super-secret'); + expect(log).not.toContain('refresh-secret'); + }); + + it('redacts sensitive strings from messages and traces', () => { + const logger = new Logger('Auth'); + logger.error( + 'token=plain-secret Authorization: Bearer bearer-secret', + 'client_secret=client-secret efy_pat_secret-value', + ); + + const log = JSON.stringify(cap.calls[0]); + expect(log).toContain('token=[REDACTED]'); + expect(log).toContain('Authorization: Bearer [REDACTED]'); + expect(log).toContain('client_secret=[REDACTED]'); + expect(log).toContain('efy_pat_[REDACTED]'); + expect(log).not.toContain('plain-secret'); + expect(log).not.toContain('bearer-secret'); + expect(log).not.toContain('client-secret'); + expect(log).not.toContain('secret-value'); + }); +}); + describe('Logger — correlationId auto-inject from ALS', () => { let cap: ReturnType; beforeEach(() => {